diff --git a/apps/client/app/(lobby)/events/page.tsx b/apps/client/app/(lobby)/events/page.tsx
index 6ed6dea6..c4314953 100644
--- a/apps/client/app/(lobby)/events/page.tsx
+++ b/apps/client/app/(lobby)/events/page.tsx
@@ -1,83 +1,85 @@
+"use server";
import { EventCard } from "@/components/landing/Home/EventCard";
+import { getEvents } from "@/actions/Event/getEvents";
+import { TEvent } from "@opinix/types";
-const Page = () => {
- const events = [
- {
- icon: "/assets/event1.png",
- traders: 31823,
- question: "Centre to constitute the 8th Pay Commission?",
- yesValue: 4,
- noValue: 6,
- },
- {
- icon: "/assets/event2.png",
- traders: 6410,
- question:
- "Kane Williamson to announce his retirement from international T20 cricket?",
- yesValue: 4.5,
- noValue: 5.5,
- },
- {
- icon: "/assets/event3.png",
- traders: 7467,
- question:
- "Tesla to open their first showroom in India by the end of 2024?",
- yesValue: 2.5,
- noValue: 7.5,
- },
- {
- icon: "/assets/event4.png",
- traders: 1367,
- question: "Red Bull Racing to win the F1 Constructors Championship 2024?",
- yesValue: 5,
- noValue: 5,
- },
- {
- icon: "/assets/event1.png",
- traders: 31823,
- question: "Centre to constitute the 8th Pay Commission?",
- yesValue: 4,
- noValue: 6,
- },
- {
- icon: "/assets/event2.png",
- traders: 6410,
- question:
- "Kane Williamson to announce his retirement from international T20 cricket?",
- yesValue: 4.5,
- noValue: 5.5,
- },
- {
- icon: "/assets/event3.png",
- traders: 7467,
- question:
- "Tesla to open their first showroom in India by the end of 2024?",
- yesValue: 2.5,
- noValue: 7.5,
- },
- {
- icon: "/assets/event4.png",
- traders: 1367,
- question: "Red Bull Racing to win the F1 Constructors Championship 2024?",
- yesValue: 5,
- noValue: 5,
- },
- ];
+const Page = async () => {
+ const events: TEvent[] = await getEvents();
+ // const events = [
+ // {
+ // icon: "/assets/event1.png",
+ // traders: 31823,
+ // question: "Centre to constitute the 8th Pay Commission?",
+ // yesValue: 4,
+ // noValue: 6,
+ // },
+ // {
+ // icon: "/assets/event2.png",
+ // traders: 6410,
+ // question:
+ // "Kane Williamson to announce his retirement from international T20 cricket?",
+ // yesValue: 4.5,
+ // noValue: 5.5,
+ // },
+ // {
+ // icon: "/assets/event3.png",
+ // traders: 7467,
+ // question:
+ // "Tesla to open their first showroom in India by the end of 2024?",
+ // yesValue: 2.5,
+ // noValue: 7.5,
+ // },
+ // {
+ // icon: "/assets/event4.png",
+ // traders: 1367,
+ // question: "Red Bull Racing to win the F1 Constructors Championship 2024?",
+ // yesValue: 5,
+ // noValue: 5,
+ // },
+ // {
+ // icon: "/assets/event1.png",
+ // traders: 31823,
+ // question: "Centre to constitute the 8th Pay Commission?",
+ // yesValue: 4,
+ // noValue: 6,
+ // },
+ // {
+ // icon: "/assets/event2.png",
+ // traders: 6410,
+ // question:
+ // "Kane Williamson to announce his retirement from international T20 cricket?",
+ // yesValue: 4.5,
+ // noValue: 5.5,
+ // },
+ // {
+ // icon: "/assets/event3.png",
+ // traders: 7467,
+ // question:
+ // "Tesla to open their first showroom in India by the end of 2024?",
+ // yesValue: 2.5,
+ // noValue: 7.5,
+ // },
+ // {
+ // icon: "/assets/event4.png",
+ // traders: 1367,
+ // question: "Red Bull Racing to win the F1 Constructors Championship 2024?",
+ // yesValue: 5,
+ // noValue: 5,
+ // },
+ // ];
return (
<>
-
+
{/* Events Grid Section */}
{events.map((event, index) => (
-
+
))}
-
>
);
};
-
-export default Page;
\ No newline at end of file
+export default Page;
diff --git a/apps/client/app/actions/event.action.ts b/apps/client/app/actions/event.action.ts
new file mode 100644
index 00000000..35b22921
--- /dev/null
+++ b/apps/client/app/actions/event.action.ts
@@ -0,0 +1,32 @@
+"use server";
+
+import prisma from "@repo/db/client";
+
+import { withServerActionAsyncCatcher } from "@/lib/async-catch";
+import { ServerActionReturnType } from "../types/api";
+import { ErrorHandler } from "@/lib/error";
+
+import { SuccessResponse } from "@/lib/success";
+
+export const getEvents = withServerActionAsyncCatcher<
+ null,
+ ServerActionReturnType
+>(async () => {
+ let events = await prisma.event.findMany({
+ select: {
+ eventId: true,
+ title: true,
+ slug: true,
+ quantity: true,
+ },
+ });
+ if (!events) {
+ throw new ErrorHandler("No events found", "NOT_FOUND");
+ }
+
+ return new SuccessResponse(
+ "Events fetched successfully",
+ 200,
+ events
+ ).serialize();
+});
diff --git a/apps/client/app/config/error.config.ts b/apps/client/app/config/error.config.ts
new file mode 100644
index 00000000..27f204ab
--- /dev/null
+++ b/apps/client/app/config/error.config.ts
@@ -0,0 +1,44 @@
+export const ERROR_NAME = {
+ UNAUTHORIZED: "Unauthorized access",
+ INTERNAL_SERVER_ERROR: "Internal server error",
+ BAD_REQUEST: "Bad request",
+ NOT_FOUND: "Resource not found",
+ FORBIDDEN: "Access forbidden",
+ CONFLICT: "Resource conflict",
+ UNPROCESSABLE_ENTITY: "Unprocessable entity",
+ TOO_MANY_REQUESTS: "Too many requests",
+ SERVICE_UNAVAILABLE: "Service unavailable",
+ GATEWAY_TIMEOUT: "Gateway timeout",
+ VALIDATION_ERROR: "Validation error",
+ AUTHENTICATION_FAILED: "Authentication failed",
+ INSUFFICIENT_PERMISSIONS: "Insufficient permissions",
+ REQUEST_TIMEOUT: "Request timeout",
+ UNSUPPORTED_MEDIA_TYPE: "Unsupported media type",
+ METHOD_NOT_ALLOWED: "Method not allowed",
+ DATABASE_ERROR: "Database error",
+ NETWORK_ERROR: "Network error",
+ RESOURCE_GONE: "Resource gone",
+ PRECONDITION_FAILED: "Precondition failed",
+};
+export const ERROR_CODE = {
+ UNAUTHORIZED: 401,
+ INTERNAL_SERVER_ERROR: 500,
+ BAD_REQUEST: 400,
+ NOT_FOUND: 404,
+ FORBIDDEN: 403,
+ CONFLICT: 409,
+ UNPROCESSABLE_ENTITY: 422,
+ TOO_MANY_REQUESTS: 429,
+ SERVICE_UNAVAILABLE: 503,
+ GATEWAY_TIMEOUT: 504,
+ VALIDATION_ERROR: 422,
+ AUTHENTICATION_FAILED: 401,
+ INSUFFICIENT_PERMISSIONS: 403,
+ REQUEST_TIMEOUT: 408,
+ UNSUPPORTED_MEDIA_TYPE: 415,
+ METHOD_NOT_ALLOWED: 405,
+ DATABASE_ERROR: 500,
+ NETWORK_ERROR: 502,
+ RESOURCE_GONE: 410,
+ PRECONDITION_FAILED: 412,
+};
diff --git a/apps/client/app/types/api.ts b/apps/client/app/types/api.ts
new file mode 100644
index 00000000..b36e5de6
--- /dev/null
+++ b/apps/client/app/types/api.ts
@@ -0,0 +1,6 @@
+import { ErrorResponseType } from "@/lib/error";
+import { SuccessResponseType } from "@/lib/success";
+
+export type ServerActionReturnType
=
+ | SuccessResponseType
+ | ErrorResponseType;
diff --git a/apps/client/components/landing/Home/EventCard.tsx b/apps/client/components/landing/Home/EventCard.tsx
index d2898479..1d6e20d9 100644
--- a/apps/client/components/landing/Home/EventCard.tsx
+++ b/apps/client/components/landing/Home/EventCard.tsx
@@ -1,35 +1,37 @@
import Image from "next/image";
+import { TEvent } from "@opinix/types";
-interface EventCardProps {
- icon: string;
- traders: number;
- question: string;
- yesValue: number;
- noValue: number;
-}
-
-export const EventCard = ({
- icon,
- traders,
- question,
- yesValue,
- noValue,
-}: EventCardProps) => (
+export const EventCard = ({ event }: { event: TEvent }) => (
-
+
-
- {traders} traders
+
+
+ {event.traders} traders
+
-
{question}
+
{event.title}
+ {/* TODO: change the min_bet and max_bet with yes or no values ( 19th oct ) */}
diff --git a/apps/client/lib/async-catch.ts b/apps/client/lib/async-catch.ts
new file mode 100644
index 00000000..9e2ca2e4
--- /dev/null
+++ b/apps/client/lib/async-catch.ts
@@ -0,0 +1,14 @@
+import { standardizeApiError } from "./error";
+type withServerActionAsyncCatcherType = (args: T) => Promise;
+
+export function withServerActionAsyncCatcher(
+ serverAction: withServerActionAsyncCatcherType
+): withServerActionAsyncCatcherType {
+ return async (args: T): Promise => {
+ try {
+ return await serverAction(args);
+ } catch (error) {
+ return standardizeApiError(error) as R;
+ }
+ };
+}
diff --git a/apps/client/lib/error.ts b/apps/client/lib/error.ts
new file mode 100644
index 00000000..38f7f2c9
--- /dev/null
+++ b/apps/client/lib/error.ts
@@ -0,0 +1,65 @@
+import { ERROR_NAME, ERROR_CODE } from "@/app/config/error.config";
+import { ZodError } from "zod";
+import { generateErrorMessage } from "zod-error";
+export type ErrorResponseType = {
+ name: string;
+ message: string;
+ code: number;
+ status: false;
+ error?: any;
+};
+class ErrorHandler extends Error {
+ status: false;
+ error?: any;
+ code: number;
+ constructor(message: string, code: keyof typeof ERROR_CODE, error?: any) {
+ super(message);
+ this.status = false;
+ this.error = error;
+ this.code = ERROR_CODE[code];
+ this.name = ERROR_NAME[code];
+ }
+}
+
+function standardizeApiError(error: unknown): ErrorResponseType {
+ if (error instanceof ErrorHandler) {
+ return {
+ name: error.name,
+ message: error.message,
+ code: error.code,
+ status: false,
+ error: error.error,
+ };
+ }
+ if (error instanceof ZodError) {
+ return {
+ name: error.name,
+ message: generateErrorMessage(error.issues, {
+ maxErrors: 2,
+ delimiter: {
+ component: ": ",
+ },
+ message: {
+ enabled: true,
+ label: "",
+ },
+ path: {
+ enabled: false,
+ },
+ code: {
+ enabled: false,
+ },
+ }),
+ code: ERROR_CODE.UNPROCESSABLE_ENTITY,
+ status: false,
+ };
+ }
+ return {
+ name: ERROR_NAME.INTERNAL_SERVER_ERROR,
+ message:
+ "We're sorry for the inconvenience. Please report this issue to our support team ",
+ code: ERROR_CODE.INTERNAL_SERVER_ERROR,
+ status: false,
+ };
+}
+export { ErrorHandler, standardizeApiError };
diff --git a/apps/client/lib/success.ts b/apps/client/lib/success.ts
new file mode 100644
index 00000000..ccb73649
--- /dev/null
+++ b/apps/client/lib/success.ts
@@ -0,0 +1,27 @@
+class SuccessResponse {
+ status: true;
+ code: number;
+ additional?: T;
+ message: string;
+ constructor(message: string, code: number, additional?: T) {
+ this.message = message;
+ this.status = true;
+ this.code = code;
+ this.additional = additional;
+ }
+ serialize() {
+ return {
+ status: this.status,
+ code: this.code,
+ message: this.message,
+ additional: this.additional as T,
+ };
+ }
+}
+export type SuccessResponseType = {
+ status: true;
+ code: number;
+ message: string;
+ additional?: T;
+};
+export { SuccessResponse };
diff --git a/apps/client/package.json b/apps/client/package.json
index bc113011..1f643fe3 100644
--- a/apps/client/package.json
+++ b/apps/client/package.json
@@ -23,6 +23,7 @@
"@radix-ui/react-toast": "^1.2.1",
"@repo/db": "*",
"@repo/order-queue": "*",
+ "@opinix/types": "*",
"chart.js": "^4.4.4",
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.1",
diff --git a/apps/server/package.json b/apps/server/package.json
index 7547daef..46584055 100644
--- a/apps/server/package.json
+++ b/apps/server/package.json
@@ -5,16 +5,16 @@
"scripts": {
"start": "node dist/index.js",
"build": "npx esbuild ./src/index.ts --bundle --platform=node --outfile=dist/index.js",
- "dev": "npm run build && npm run start"
+ "dev": "nodemon src/index.ts"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
+ "@opinix/types": "*",
"@prisma/client": "5.20.0",
"@repo/db": "*",
- "@opinix/types":"*",
- "@repo/engine":"*",
+ "@repo/engine": "*",
"@repo/order-queue": "*",
"@types/cors": "^2.8.17",
"@types/express": "^4.17.21",
@@ -25,6 +25,7 @@
"dotenv": "^16.4.5",
"express": "^4.21.0",
"jsonwebtoken": "^9.0.2",
+ "nodemon": "^3.1.7",
"prisma": "^5.20.0",
"ws": "^7.5.5",
"zod-error": "^1.5.0"
diff --git a/apps/server/prisma/schema.prisma b/apps/server/prisma/schema.prisma
deleted file mode 100644
index cbda196b..00000000
--- a/apps/server/prisma/schema.prisma
+++ /dev/null
@@ -1,142 +0,0 @@
-generator client {
- provider = "prisma-client-js"
-}
-
-datasource db {
- provider = "postgresql"
- url = env("DATABASE_URL")
-}
-
-model User {
- id String @id @default(cuid())
- phoneNumber String @unique
- balance Float @default(0.0)
- role UserRole
- createdAt DateTime @default(now())
- updatedAt DateTime
- Event Event[]
- OTP OTP[]
- Payout Payout[]
- portfolio Portfolio?
- events Event[] @relation("EventParticipants")
-}
-
-model Portfolio {
- id String @id @default(cuid())
- userId String @unique
- currentBalances Float @default(0.0)
- createdAt DateTime @default(now())
- updatedAt DateTime @updatedAt
- user User @relation(fields: [userId], references: [id])
- trades Trade[]
-}
-
-model Event {
- id String @id @default(cuid())
- title String
- description String
- adminId String
- status EventStatus @default(ONGOING)
- createdAt DateTime @default(now())
- updatedAt DateTime @updatedAt
- admin User @relation(fields: [adminId], references: [id])
- orderBook OrderBook?
- Trade Trade[]
- participants User[] @relation("EventParticipants")
-}
-
-model OrderBook {
- id String @id @default(cuid())
- eventId String @unique
- topPriceYes Float
- topPriceNo Float
- no NoOrder[]
- event Event @relation(fields: [eventId], references: [id])
- yes YesOrder[]
-}
-
-model YesOrder {
- id String @id @default(cuid())
- orderBookId String
- price Float
- quantity Int
- status OrderStatus
- createdAt DateTime @default(now())
- orderBook OrderBook @relation(fields: [orderBookId], references: [id])
-}
-
-model NoOrder {
- id String @id @default(cuid())
- orderBookId String
- price Float
- quantity Int
- status OrderStatus
- createdAt DateTime @default(now())
- orderBook OrderBook @relation(fields: [orderBookId], references: [id])
-}
-
-model Trade {
- id String @id @default(cuid())
- portfolioId String
- eventId String
- price Float
- quantity Int
- side TradeSide
- createdAt DateTime @default(now())
- event Event @relation(fields: [eventId], references: [id])
- portfolio Portfolio @relation(fields: [portfolioId], references: [id])
- status TradeStatus @default(ACTIVE)
- gainloss Float? @default(0)
-}
-
-model Payout {
- id String @id @default(cuid())
- userId String
- amount Float
- status PayoutStatus
- createdAt DateTime @default(now())
- updatedAt DateTime @updatedAt
- user User @relation(fields: [userId], references: [id])
-}
-
-model OTP {
- id String @id @default(uuid())
- otpID String @unique
- otp String @unique
- createdAt DateTime @default(now())
- expiresAt DateTime
- isVerified Boolean @default(false)
- user User? @relation(fields: [userId], references: [id])
- userId String?
-}
-
-enum UserRole {
- ADMIN
- USER
-}
-
-enum EventStatus {
- ONGOING
- ENDED
-}
-
-enum TradeSide {
- YES
- NO
-}
-
-enum PayoutStatus {
- PENDING
- COMPLETED
- FAILED
-}
-enum TradeStatus{
- ACTIVE
- PAST
-}
-
-enum OrderStatus {
- PENDING
- PLACED
-}
-
diff --git a/apps/server/src/controllers/event/index.ts b/apps/server/src/controllers/event/index.ts
new file mode 100644
index 00000000..2b9a47cc
--- /dev/null
+++ b/apps/server/src/controllers/event/index.ts
@@ -0,0 +1,112 @@
+/**
+ *
+ */
+
+import { AsyncWrapper } from "../../utils/asynCatch";
+import { TEvent } from "@opinix/types";
+import { Request, Response } from "express";
+import { slugify, eventCodeGenerator } from "../../utils/utils";
+import prisma from "@repo/db/client";
+import {
+ SuccessResponse,
+ SuccessResponseType,
+} from "../../utils/wrappers/success.res";
+import { ErrorHandler } from "../../utils/wrappers/error.res";
+export const createEventHandler = AsyncWrapper(
+ async (req: Request<{}, {}, Omit>, res) => {
+ const {
+ title,
+ description,
+ start_date,
+ end_date,
+ min_bet,
+ max_bet,
+ sot,
+ quantity,
+ } = req.body;
+ console.log(req.body);
+ let slug = slugify(title);
+
+ let eventCode = eventCodeGenerator();
+ // check if event already exists using the slug name
+ const isEventExists = await prisma.event.findFirst({
+ where: {
+ slug: slug,
+ },
+ });
+ if (isEventExists) {
+ throw new ErrorHandler("Event already exists", "BAD_REQUEST");
+ }
+ // creating the event and sending back the response to the client with the event code
+ await prisma.event.create({
+ data: {
+ eventId: eventCode,
+ title,
+ slug,
+ description,
+ start_date,
+ end_date,
+ min_bet,
+ max_bet,
+ sot,
+ expiresAt: end_date,
+ quantity,
+ },
+ });
+
+ let response = new SuccessResponse("Event created successfully", eventCode);
+ return res.status(201).json(response);
+ }
+);
+
+/**
+ * @description
+ */
+/**
+ * url - https://prod.api.probo.in/api/v3/product/events/tradeSummary?eventId=3169798&page=1&pageSize=5
+ * @description - Get trade summary for an event
+ */
+
+type TTradeSummary = {
+ order_book_details: {
+ orderbook_config: {
+ socket_events: {
+ subscribe_msg_name: string;
+ unsubscribe_msg_name: string;
+ listener_msg_name: string;
+ subscription_data: string;
+ };
+ };
+ };
+};
+export const getTradeSummaryHandler = AsyncWrapper(
+ async (req, res: Response>) => {
+ const { eventId } = req.query;
+
+ const event = await prisma.event.findUnique({
+ where: {
+ eventId: eventId as unknown as number,
+ },
+ });
+ if (!event) {
+ throw new ErrorHandler("Event not found", "NOT_FOUND");
+ }
+ let response = new SuccessResponse(
+ "Trade summary fetched successfully",
+ 200,
+ {
+ order_book_details: {
+ orderbook_config: {
+ socket_events: {
+ subscribe_msg_name: "subscribe_orderbook",
+ unsubscribe_msg_name: "unsubscribe_orderbook",
+ listener_msg_name: `event_orderbook_${eventId}`,
+ subscription_data: `${eventId}`,
+ },
+ },
+ },
+ }
+ );
+ return res.status(200).json(response.serialize());
+ }
+);
diff --git a/apps/server/src/index.ts b/apps/server/src/index.ts
index 7d056c51..67cec610 100644
--- a/apps/server/src/index.ts
+++ b/apps/server/src/index.ts
@@ -1,8 +1,11 @@
import express from "express";
-import {ORDERBOOK} from "@repo/engine"
+import { eventRouter } from "./router/eventRouter";
const app = express();
-console.log(ORDERBOOK);
-app.listen(3001, () =>{
- console.log(`server is runnning on http://localhost:3001`)
-})
\ No newline at end of file
+app.use(express.json());
+
+app.use("/events", eventRouter);
+
+app.listen(3001, () => {
+ console.log(`server is runnning on http://localhost:3001`);
+});
diff --git a/apps/server/src/router/eventRouter.ts b/apps/server/src/router/eventRouter.ts
new file mode 100644
index 00000000..da327df6
--- /dev/null
+++ b/apps/server/src/router/eventRouter.ts
@@ -0,0 +1,12 @@
+import { Router } from "express";
+import {
+ createEventHandler,
+ getTradeSummaryHandler,
+} from "../controllers/event";
+const eventRouter = Router();
+
+// TODO; implement validation middleware inbetween
+eventRouter.post("/create", createEventHandler);
+
+eventRouter.get("/tradeSummary", getTradeSummaryHandler);
+export { eventRouter };
diff --git a/apps/server/src/utils/asynCatch.ts b/apps/server/src/utils/asynCatch.ts
index 1926e58b..d9298933 100644
--- a/apps/server/src/utils/asynCatch.ts
+++ b/apps/server/src/utils/asynCatch.ts
@@ -9,6 +9,7 @@ type AsyncHandler = (
export const AsyncWrapper = (handler: AsyncHandler) => {
return (req: Request, res: Response, next: NextFunction) => {
handler(req, res, next).catch((error: unknown) => {
+ console.log(error);
const standardizedError = standardizeApiError(error);
res.status(standardizedError.code).json(standardizedError);
});
diff --git a/apps/server/src/utils/utils.ts b/apps/server/src/utils/utils.ts
new file mode 100644
index 00000000..46050461
--- /dev/null
+++ b/apps/server/src/utils/utils.ts
@@ -0,0 +1,18 @@
+/**
+ * @description: misc utils functions are defined here
+ */
+
+function randomFourDigitNumber() {
+ return Math.random().toString(36).substring(2, 6);
+}
+
+function eventCodeGenerator(): number {
+ return parseInt(Math.random().toString(36).substring(2, 8));
+}
+function slugify(name: string) {
+ return (
+ name.toLowerCase().split(" ").join("-") + "-" + randomFourDigitNumber()
+ );
+}
+
+export { slugify, eventCodeGenerator };
diff --git a/package-lock.json b/package-lock.json
index c0b04a0c..3d054357 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -25,6 +25,7 @@
"@cashfreepayments/cashfree-js": "^1.0.5",
"@hookform/resolvers": "^3.9.0",
"@next-auth/prisma-adapter": "^1.0.7",
+ "@opinix/types": "*",
"@prisma/client": "^5.20.0",
"@radix-ui/react-accordion": "^1.2.0",
"@radix-ui/react-avatar": "^1.1.0",
@@ -366,6 +367,7 @@
"dotenv": "^16.4.5",
"express": "^4.21.0",
"jsonwebtoken": "^9.0.2",
+ "nodemon": "^3.1.7",
"prisma": "^5.20.0",
"ws": "^7.5.5",
"zod-error": "^1.5.0"
@@ -4413,7 +4415,6 @@
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
- "dev": true,
"license": "MIT"
},
"node_modules/constant-case": {
@@ -7750,7 +7751,6 @@
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
"integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
- "dev": true,
"license": "MIT",
"engines": {
"node": ">=4"
@@ -7935,6 +7935,11 @@
"node": ">= 4"
}
},
+ "node_modules/ignore-by-default": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz",
+ "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA=="
+ },
"node_modules/import-fresh": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz",
@@ -9751,6 +9756,53 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/nodemon": {
+ "version": "3.1.7",
+ "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.7.tgz",
+ "integrity": "sha512-hLj7fuMow6f0lbB0cD14Lz2xNjwsyruH251Pk4t/yIitCFJbmY1myuLlHm/q06aST4jg6EgAh74PIBBrRqpVAQ==",
+ "dependencies": {
+ "chokidar": "^3.5.2",
+ "debug": "^4",
+ "ignore-by-default": "^1.0.1",
+ "minimatch": "^3.1.2",
+ "pstree.remy": "^1.1.8",
+ "semver": "^7.5.3",
+ "simple-update-notifier": "^2.0.0",
+ "supports-color": "^5.5.0",
+ "touch": "^3.1.0",
+ "undefsafe": "^2.0.5"
+ },
+ "bin": {
+ "nodemon": "bin/nodemon.js"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/nodemon"
+ }
+ },
+ "node_modules/nodemon/node_modules/brace-expansion": {
+ "version": "1.1.11",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
+ "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
+ "dependencies": {
+ "balanced-match": "^1.0.0",
+ "concat-map": "0.0.1"
+ }
+ },
+ "node_modules/nodemon/node_modules/minimatch": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
+ "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
+ "dependencies": {
+ "brace-expansion": "^1.1.7"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
"node_modules/normalize-package-data": {
"version": "2.5.0",
"resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz",
@@ -10746,6 +10798,11 @@
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
"license": "MIT"
},
+ "node_modules/pstree.remy": {
+ "version": "1.1.8",
+ "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz",
+ "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w=="
+ },
"node_modules/punycode": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
@@ -11776,6 +11833,17 @@
"dev": true,
"license": "ISC"
},
+ "node_modules/simple-update-notifier": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz",
+ "integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==",
+ "dependencies": {
+ "semver": "^7.5.3"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
"node_modules/slash": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz",
@@ -12270,7 +12338,6 @@
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
"integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
- "dev": true,
"license": "MIT",
"dependencies": {
"has-flag": "^3.0.0"
@@ -12514,6 +12581,14 @@
"node": ">=0.6"
}
},
+ "node_modules/touch": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.1.tgz",
+ "integrity": "sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==",
+ "bin": {
+ "nodetouch": "bin/nodetouch.js"
+ }
+ },
"node_modules/ts-api-utils": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.3.0.tgz",
@@ -12936,6 +13011,11 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/undefsafe": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz",
+ "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA=="
+ },
"node_modules/undici-types": {
"version": "6.19.8",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz",
diff --git a/packages/db/prisma/migrations/20241019105418_/migration.sql b/packages/db/prisma/migrations/20241019105418_/migration.sql
new file mode 100644
index 00000000..d3302ec3
--- /dev/null
+++ b/packages/db/prisma/migrations/20241019105418_/migration.sql
@@ -0,0 +1,69 @@
+/*
+ Warnings:
+
+ - You are about to alter the column `balance` on the `User` table. The data in that column could be lost. The data in that column will be cast from `DoublePrecision` to `Integer`.
+ - You are about to drop the `NoOrder` table. If the table is not empty, all the data it contains will be lost.
+ - You are about to drop the `OrderBook` table. If the table is not empty, all the data it contains will be lost.
+ - You are about to drop the `Trade` table. If the table is not empty, all the data it contains will be lost.
+ - You are about to drop the `YesOrder` table. If the table is not empty, all the data it contains will be lost.
+ - A unique constraint covering the columns `[slug]` on the table `Event` will be added. If there are existing duplicate values, this will fail.
+ - Added the required column `end_date` to the `Event` table without a default value. This is not possible if the table is not empty.
+ - Added the required column `expiresAt` to the `Event` table without a default value. This is not possible if the table is not empty.
+ - Added the required column `max_bet` to the `Event` table without a default value. This is not possible if the table is not empty.
+ - Added the required column `min_bet` to the `Event` table without a default value. This is not possible if the table is not empty.
+ - Added the required column `quantity` to the `Event` table without a default value. This is not possible if the table is not empty.
+ - Added the required column `slug` to the `Event` table without a default value. This is not possible if the table is not empty.
+ - Added the required column `sot` to the `Event` table without a default value. This is not possible if the table is not empty.
+ - Added the required column `start_date` to the `Event` table without a default value. This is not possible if the table is not empty.
+
+*/
+-- CreateEnum
+CREATE TYPE "TradeStatus" AS ENUM ('ACTIVE', 'PAST');
+
+-- DropForeignKey
+ALTER TABLE "Event" DROP CONSTRAINT "Event_adminId_fkey";
+
+-- DropForeignKey
+ALTER TABLE "NoOrder" DROP CONSTRAINT "NoOrder_orderBookId_fkey";
+
+-- DropForeignKey
+ALTER TABLE "OrderBook" DROP CONSTRAINT "OrderBook_eventId_fkey";
+
+-- DropForeignKey
+ALTER TABLE "Trade" DROP CONSTRAINT "Trade_eventId_fkey";
+
+-- DropForeignKey
+ALTER TABLE "Trade" DROP CONSTRAINT "Trade_portfolioId_fkey";
+
+-- DropForeignKey
+ALTER TABLE "YesOrder" DROP CONSTRAINT "YesOrder_orderBookId_fkey";
+
+-- AlterTable
+ALTER TABLE "Event" ADD COLUMN "end_date" TIMESTAMP(3) NOT NULL,
+ADD COLUMN "expiresAt" TIMESTAMP(3) NOT NULL,
+ADD COLUMN "max_bet" DOUBLE PRECISION NOT NULL,
+ADD COLUMN "min_bet" DOUBLE PRECISION NOT NULL,
+ADD COLUMN "quantity" INTEGER NOT NULL,
+ADD COLUMN "slug" TEXT NOT NULL,
+ADD COLUMN "sot" TEXT NOT NULL,
+ADD COLUMN "start_date" TIMESTAMP(3) NOT NULL;
+
+-- AlterTable
+ALTER TABLE "User" ALTER COLUMN "balance" SET DEFAULT 0,
+ALTER COLUMN "balance" SET DATA TYPE INTEGER,
+ALTER COLUMN "updatedAt" SET DEFAULT CURRENT_TIMESTAMP;
+
+-- DropTable
+DROP TABLE "NoOrder";
+
+-- DropTable
+DROP TABLE "OrderBook";
+
+-- DropTable
+DROP TABLE "Trade";
+
+-- DropTable
+DROP TABLE "YesOrder";
+
+-- CreateIndex
+CREATE UNIQUE INDEX "Event_slug_key" ON "Event"("slug");
diff --git a/packages/db/prisma/migrations/20241019110708_y/migration.sql b/packages/db/prisma/migrations/20241019110708_y/migration.sql
new file mode 100644
index 00000000..d32de3c6
--- /dev/null
+++ b/packages/db/prisma/migrations/20241019110708_y/migration.sql
@@ -0,0 +1,8 @@
+/*
+ Warnings:
+
+ - You are about to drop the column `adminId` on the `Event` table. All the data in the column will be lost.
+
+*/
+-- AlterTable
+ALTER TABLE "Event" DROP COLUMN "adminId";
diff --git a/packages/db/prisma/migrations/20241019113842_/migration.sql b/packages/db/prisma/migrations/20241019113842_/migration.sql
new file mode 100644
index 00000000..fc560737
--- /dev/null
+++ b/packages/db/prisma/migrations/20241019113842_/migration.sql
@@ -0,0 +1,12 @@
+/*
+ Warnings:
+
+ - A unique constraint covering the columns `[eventId]` on the table `Event` will be added. If there are existing duplicate values, this will fail.
+ - Added the required column `eventId` to the `Event` table without a default value. This is not possible if the table is not empty.
+
+*/
+-- AlterTable
+ALTER TABLE "Event" ADD COLUMN "eventId" INTEGER NOT NULL;
+
+-- CreateIndex
+CREATE UNIQUE INDEX "Event_eventId_key" ON "Event"("eventId");
diff --git a/packages/db/prisma/migrations/20241019122505_traders/migration.sql b/packages/db/prisma/migrations/20241019122505_traders/migration.sql
new file mode 100644
index 00000000..9ac614ba
--- /dev/null
+++ b/packages/db/prisma/migrations/20241019122505_traders/migration.sql
@@ -0,0 +1,2 @@
+-- AlterTable
+ALTER TABLE "Event" ADD COLUMN "traders" INTEGER NOT NULL DEFAULT 0;
diff --git a/packages/db/prisma/schema.prisma b/packages/db/prisma/schema.prisma
index ed1f3f0c..4301b83c 100644
--- a/packages/db/prisma/schema.prisma
+++ b/packages/db/prisma/schema.prisma
@@ -10,11 +10,10 @@ datasource db {
model User {
id String @id @default(cuid())
phoneNumber String @unique
- balance Int @default(0)
+ balance Int @default(0)
role UserRole
createdAt DateTime @default(now())
updatedAt DateTime @default(now())
- Event Event[]
OTP OTP[]
Payout Payout[]
portfolio Portfolio?
@@ -28,67 +27,29 @@ model Portfolio {
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
user User @relation(fields: [userId], references: [id])
- trades Trade[]
}
+// SOT - source of truth
model Event {
id String @id @default(cuid())
- title String
+ eventId Int @unique
+ slug String @unique
description String
- adminId String
+ title String
+ start_date DateTime
+ end_date DateTime
+ expiresAt DateTime
+ min_bet Float
+ max_bet Float
+ quantity Int
+ sot String
+ traders Int @default(0)
status EventStatus @default(ONGOING)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
- admin User @relation(fields: [adminId], references: [id])
- orderBook OrderBook?
- Trade Trade[]
participants User[] @relation("EventParticipants")
}
-model OrderBook {
- id String @id @default(cuid())
- eventId String @unique
- topPriceYes Float
- topPriceNo Float
- no NoOrder[]
- event Event @relation(fields: [eventId], references: [id])
- yes YesOrder[]
-}
-
-model YesOrder {
- id String @id @default(cuid())
- orderBookId String
- price Float
- quantity Int
- createdAt DateTime @default(now())
- status OrderStatus
- orderBook OrderBook @relation(fields: [orderBookId], references: [id])
-}
-
-model NoOrder {
- id String @id @default(cuid())
- orderBookId String
- price Float
- quantity Int
- createdAt DateTime @default(now())
- status OrderStatus
- orderBook OrderBook @relation(fields: [orderBookId], references: [id])
-}
-
-model Trade {
- id String @id @default(cuid())
- portfolioId String
- eventId String
- price Float
- quantity Int
- side TradeSide
- createdAt DateTime @default(now())
- gainloss Float? @default(0)
- status TradeStatus @default(ACTIVE)
- event Event @relation(fields: [eventId], references: [id])
- portfolio Portfolio @relation(fields: [portfolioId], references: [id])
-}
-
model Payout {
id String @id @default(cuid())
userId String
diff --git a/packages/order-queue/.env.example b/packages/order-queue/.env.example
index 0d4defd3..5ef19dff 100644
--- a/packages/order-queue/.env.example
+++ b/packages/order-queue/.env.example
@@ -1,3 +1 @@
-REDIS_HOST="localhost"
-REDIS_PORT="6379"
-REDIS_PASSWORD=""
\ No newline at end of file
+REDIS_URI=redis://localhost:6379
\ No newline at end of file
diff --git a/packages/order-queue/logs/server.log b/packages/order-queue/logs/server.log
new file mode 100644
index 00000000..a9ea3e47
--- /dev/null
+++ b/packages/order-queue/logs/server.log
@@ -0,0 +1,10 @@
+[ 2024-10-19 19:59:55 ] - [32minfo[39m - WORKER | Starting order worker
+[ 2024-10-19 20:00:56 ] - [32minfo[39m - WORKER | Starting order worker
+[ 2024-10-19 20:00:56 ] - [32minfo[39m - SERVER | REDIS: Connected to Redis
+[ 2024-10-19 20:00:56 ] - [32minfo[39m - SERVER | REDIS: Redis connection is ready
+[ 2024-10-19 20:01:12 ] - [32minfo[39m - WORKER | Starting order worker
+[ 2024-10-19 20:01:12 ] - [32minfo[39m - SERVER | REDIS: Connected to Redis
+[ 2024-10-19 20:01:12 ] - [32minfo[39m - SERVER | REDIS: Redis connection is ready
+[ 2024-10-19 20:02:18 ] - [32minfo[39m - WORKER | Starting order worker
+[ 2024-10-19 20:02:18 ] - [32minfo[39m - SERVER | REDIS: Connected to Redis
+[ 2024-10-19 20:02:18 ] - [32minfo[39m - SERVER | REDIS: Redis connection is ready to start execution
diff --git a/packages/order-queue/package.json b/packages/order-queue/package.json
index 869c13b1..af93c22f 100644
--- a/packages/order-queue/package.json
+++ b/packages/order-queue/package.json
@@ -3,9 +3,15 @@
"version": "1.0.0",
"main": "index.js",
"dependencies": {
+ "@opinix/logger": "*",
"dotenv": "^16.4.5",
+ "nodemon": "^3.1.7",
"redis": "^4.7.0"
},
+ "scripts": {
+ "start": "ts-node src/index.ts",
+ "dev": "nodemon src/index.ts"
+ },
"exports": {
".": "./src/index.ts"
}
diff --git a/packages/order-queue/src/config/redisClient.ts b/packages/order-queue/src/config/redisClient.ts
new file mode 100644
index 00000000..8106799d
--- /dev/null
+++ b/packages/order-queue/src/config/redisClient.ts
@@ -0,0 +1,38 @@
+import IORedis, { Redis } from "ioredis";
+import "dotenv/config";
+import { logger } from "@opinix/logger";
+const redisUri = process.env.REDIS_URI || "redis://localhost:6379";
+let redisParams = {
+ maxRetriesPerRequest: null,
+};
+let redisClient: Redis | null;
+const getRedisClient = () => {
+ if (!redisClient) {
+ redisClient = new IORedis(redisUri, {
+ ...redisParams,
+ });
+ redisClient.on("connect", () => {
+ logger.info("SERVER | REDIS: Connected to Redis");
+ });
+
+ redisClient.on("ready", () => {
+ logger.info(
+ "SERVER | REDIS: Redis connection is ready to start execution"
+ );
+ });
+
+ redisClient.on("error", (err) => {
+ logger.error("SERVER: ERROR Connecting to Redis", err);
+ });
+
+ redisClient.on("close", () => {
+ logger.warn("SERVER | REDIS: Connection closed");
+ });
+
+ redisClient.on("reconnecting", () => {
+ logger.info("SERVER | REDIS: Reconnecting...");
+ });
+ }
+ return redisClient;
+};
+export default getRedisClient;
diff --git a/packages/order-queue/src/index.ts b/packages/order-queue/src/index.ts
index 50de55e8..b62f1f02 100644
--- a/packages/order-queue/src/index.ts
+++ b/packages/order-queue/src/index.ts
@@ -1,11 +1,10 @@
-import { createClient } from "redis";
-import dotenv from "dotenv";
-dotenv.config();
+import { addToOrderQueue } from "./queues/orderQueue";
+import orderWorker from "./queues/orderProcessor";
+import { logger } from "@opinix/logger";
+const startWorker = async () => {
+ logger.info("WORKER | Starting order worker");
+ orderWorker;
+};
-export const redisClient = createClient({
-// password: process.env.REDIS_PASSWORD,
- socket: {
- host: process.env.REDIS_HOST,
- port: process.env.REDIS_PORT as unknown as number,
- },
-});
\ No newline at end of file
+startWorker();
+export { addToOrderQueue };
diff --git a/packages/order-queue/src/queues/orderProcessor.ts b/packages/order-queue/src/queues/orderProcessor.ts
new file mode 100644
index 00000000..a188127c
--- /dev/null
+++ b/packages/order-queue/src/queues/orderProcessor.ts
@@ -0,0 +1,21 @@
+import { Worker } from "bullmq";
+import getRedisClient from "../config/redisClient";
+let redisClient = getRedisClient();
+
+const orderWorker = new Worker(
+ "orderQueue",
+ async (job) => {
+ try {
+ console.log(`Processing order: ${JSON.stringify(job.data)}`);
+ } catch (error) {
+ if (error instanceof Error)
+ console.error(`Error processing order: ${error.message}`);
+ else console.error(`Error processing order: ${error}`);
+ }
+ },
+ {
+ connection: redisClient,
+ }
+);
+
+export default orderWorker;
diff --git a/packages/order-queue/src/queues/orderQueue.ts b/packages/order-queue/src/queues/orderQueue.ts
new file mode 100644
index 00000000..5456a2e1
--- /dev/null
+++ b/packages/order-queue/src/queues/orderQueue.ts
@@ -0,0 +1,16 @@
+import { Queue } from "bullmq";
+import getRedisClient from "../config/redisClient";
+let redisClient = getRedisClient();
+export const orderQueue = new Queue("orderQueue", {
+ connection: redisClient,
+});
+
+export const addToOrderQueue = async (order: object) => {
+ await orderQueue.add("order", order, {
+ attempts: 3,
+ backoff: {
+ type: "exponential",
+ delay: 5000,
+ },
+ });
+};
diff --git a/packages/types/src/index.ts b/packages/types/src/index.ts
index abfe4d54..245222d8 100644
--- a/packages/types/src/index.ts
+++ b/packages/types/src/index.ts
@@ -1,3 +1,4 @@
+import { OrderStatus } from "@prisma/client";
export enum EOrderStatus {
PENDING = "PENDING",
PLACED = "PLACED",
@@ -8,7 +9,6 @@ export enum sides {
NO = "no",
}
-import { OrderStatus } from "@prisma/client";
export type TOrder = {
id: string;
orderBookId: string;
@@ -26,3 +26,18 @@ export type TOrderbookForOrders = {
yes: TOrder[];
no: TOrder[];
};
+
+export type TEvent = {
+ id: string;
+ title: string;
+ slug: string;
+ description: string;
+ start_date: Date;
+ end_date: Date;
+ createdAt: Date;
+ min_bet: number;
+ max_bet: number;
+ sot: string;
+ traders: number;
+ quantity: number;
+};