├── src ├── modules │ ├── trait │ │ ├── trait.constant.ts │ │ ├── trait.types.ts │ │ ├── trait.validate.ts │ │ ├── trait.model.ts │ │ ├── trait.router.ts │ │ ├── trait.service.ts │ │ └── trait.controller.ts │ ├── leaderboard │ │ ├── leaderboard.interface.ts │ │ ├── index.ts │ │ ├── leaderboard.types.ts │ │ ├── leaderboard.constant.ts │ │ ├── leaderboard.validate.ts │ │ ├── socket │ │ │ └── leaderboard.listener.ts │ │ ├── leaderboard.controller.ts │ │ └── leaderboard.service.ts │ ├── logs │ │ ├── index.ts │ │ ├── logs.router.ts │ │ └── logs.controller.ts │ ├── auto-crash-bet │ │ ├── auto-crash-bet.constant.ts │ │ ├── auto-crash-bet.types.ts │ │ ├── index.ts │ │ ├── auto-crash-bet.service.ts │ │ ├── auto-crash-bet.interface.ts │ │ ├── auto-crash-bet.validate.ts │ │ ├── auto-crash-bet.router.ts │ │ ├── auto-crash-bet.model.ts │ │ └── auto-crash-bet.controller.ts │ ├── auth │ │ ├── auth.constant.ts │ │ ├── swagger │ │ │ ├── index.ts │ │ │ └── auth.schema.ts │ │ ├── auth.interface.ts │ │ ├── auth.model.ts │ │ ├── auth.types.ts │ │ ├── auth.validate.ts │ │ ├── auth.service.ts │ │ └── auth.router.ts │ ├── user │ │ ├── swagger │ │ │ ├── index.ts │ │ │ ├── user.schema.ts │ │ │ └── user.swagger.ts │ │ ├── user.constant.ts │ │ ├── user.types.ts │ │ ├── user.validate.ts │ │ ├── user.interface.ts │ │ ├── user.router.ts │ │ ├── user.service.ts │ │ └── user.controller.ts │ ├── example │ │ ├── example.constant.ts │ │ ├── swagger │ │ │ ├── index.ts │ │ │ └── example.schema.ts │ │ ├── index.ts │ │ ├── example.interface.ts │ │ ├── example.types.ts │ │ ├── example.validate.ts │ │ ├── example.service.ts │ │ ├── example.model.ts │ │ ├── example.router.ts │ │ └── example.controller.ts │ ├── site-transaction │ │ ├── site-transaction.constant.ts │ │ ├── index.ts │ │ ├── site-transaction.types.ts │ │ ├── site-transaction.service.ts │ │ ├── site-transaction.validate.ts │ │ ├── site-transaction.interface.ts │ │ ├── site-transaction.router.ts │ │ ├── site-transaction.model.ts │ │ └── site-transaction.controller.ts │ ├── staking │ │ ├── staking.types.ts │ │ ├── staking.constant.ts │ │ ├── staking.service.ts │ │ ├── staking.interface.ts │ │ ├── staking.validate.ts │ │ ├── staking.model.ts │ │ └── staking.router.ts │ ├── dashboard │ │ ├── dashboard.types.ts │ │ ├── dashboard.interface.ts │ │ ├── index.ts │ │ ├── dashboard.validate.ts │ │ ├── dashboard.constant.ts │ │ ├── dashboard.model.ts │ │ ├── dashboard.router.ts │ │ ├── dashboard.service.ts │ │ └── dashboard.controller.ts │ ├── payment │ │ ├── index.ts │ │ ├── payment.interface.ts │ │ ├── payment.model.ts │ │ ├── payment.validate.ts │ │ ├── payment.types.ts │ │ ├── payment.router.ts │ │ ├── socket │ │ │ └── payment.listener.ts │ │ └── payment.constant.ts │ ├── chat-history │ │ ├── chat-history.interface.ts │ │ ├── chat-history.types.ts │ │ ├── index.ts │ │ ├── chat-history.constant.ts │ │ ├── chat-history.validate.ts │ │ ├── chat-history.model.ts │ │ ├── socket │ │ │ └── chat-history.listener.ts │ │ ├── chat-history.service.ts │ │ ├── chat-history.router.ts │ │ └── chat-history.controller.ts │ ├── crash-game │ │ ├── index.ts │ │ ├── crash-game.validate.ts │ │ ├── crash-game.service.ts │ │ ├── crash-game.router.ts │ │ ├── crash-game.model.ts │ │ ├── crash-game.constant.ts │ │ ├── crash-game.interface.ts │ │ ├── crash-game.types.ts │ │ └── crash-game.controller.ts │ └── user-bot │ │ ├── user-bot.constant.ts │ │ ├── user-bot.types.ts │ │ ├── user-bot.service.ts │ │ ├── user-bot.router.ts │ │ ├── user-bot.interface.ts │ │ ├── user-bot.validate.ts │ │ ├── user-bot.controller.ts │ │ └── user-bot.model.ts ├── utils │ ├── validations │ │ ├── validate-seed.ts │ │ ├── index.ts │ │ ├── is-email.ts │ │ ├── router.ts │ │ └── validate.ts │ ├── interfaces │ │ ├── index.ts │ │ └── request.interface.ts │ ├── localizations │ │ ├── index.ts │ │ ├── localizations.interface.ts │ │ └── en.ts │ ├── helpers │ │ ├── generate-code.ts │ │ ├── string.ts │ │ ├── custom-error.ts │ │ ├── index.ts │ │ ├── sliceArr.ts │ │ ├── response.ts │ │ ├── string-helper.ts │ │ ├── get-randomHash.ts │ │ ├── detect-mobFromUserAgent.ts │ │ ├── get-paginationInfo.ts │ │ └── file-helpers.ts │ ├── encryption │ │ ├── init.ts │ │ ├── aes-wrapper.ts │ │ └── rsa-wrapper.ts │ ├── date.ts │ ├── swagger │ │ ├── global.schema.ts │ │ ├── errors.ts │ │ └── swagger.setup.ts │ ├── jwt │ │ └── create-token.ts │ ├── crypto │ │ └── get-seed.ts │ ├── logger.ts │ ├── db.ts │ ├── mailing.ts │ ├── setting │ │ └── site.ts │ ├── socket │ │ └── throttler.ts │ └── customer │ │ └── vip.ts ├── cron │ ├── crons │ │ ├── trait-update │ │ │ ├── index.ts │ │ │ └── trait-update.ts │ │ ├── customer-update │ │ │ ├── index.ts │ │ │ └── customer-update.ts │ │ ├── dashboard-update │ │ │ ├── index.ts │ │ │ └── dashboard-update.ts │ │ ├── index.ts │ │ └── base.cron.ts │ └── index.ts ├── constant │ ├── crypto.ts │ ├── game.ts │ ├── types.d.ts │ ├── enum.ts │ ├── customer.ts │ └── socket.ts ├── middleware │ ├── authorize.ts │ ├── logger.ts │ ├── add-dirForUpload.ts │ ├── validate-schema.ts │ ├── error-handler.ts │ ├── attach-currentUser.ts │ ├── is-auth.ts │ ├── action-handler.ts │ └── check-permissions.ts ├── root.socket.ts ├── index.ts ├── root.router.ts └── config │ └── index.ts ├── assets └── main │ └── font │ └── avenir-regular.ttf ├── logs └── .87963bbc7c723ad6cb7f9cd6daeb94b0e4a665be-audit.json ├── README.md └── doc └── CrashPoint.md /src/modules/trait/trait.constant.ts: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/utils/validations/validate-seed.ts: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/modules/leaderboard/leaderboard.interface.ts: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/modules/logs/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./logs.controller"; 2 | -------------------------------------------------------------------------------- /src/cron/crons/trait-update/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./trait-update"; 2 | -------------------------------------------------------------------------------- /src/utils/interfaces/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./request.interface"; 2 | -------------------------------------------------------------------------------- /src/cron/crons/customer-update/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./customer-update"; 2 | -------------------------------------------------------------------------------- /src/utils/localizations/index.ts: -------------------------------------------------------------------------------- 1 | export { default as en } from "./en"; 2 | -------------------------------------------------------------------------------- /src/cron/crons/dashboard-update/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./dashboard-update"; 2 | -------------------------------------------------------------------------------- /src/modules/auto-crash-bet/auto-crash-bet.constant.ts: -------------------------------------------------------------------------------- 1 | export const AutoCrashBet = "auto_crash_bet"; 2 | -------------------------------------------------------------------------------- /src/constant/crypto.ts: -------------------------------------------------------------------------------- 1 | import { IS_MAINNET } from "@/config"; 2 | 3 | export const CDENOM_TOKENS = { 4 | // 5 | }; 6 | -------------------------------------------------------------------------------- /src/utils/validations/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./is-email"; 2 | export * from "./router"; 3 | export * from "./validate"; 4 | -------------------------------------------------------------------------------- /src/modules/auth/auth.constant.ts: -------------------------------------------------------------------------------- 1 | export enum PLATFORM { 2 | WEB = "WEB", 3 | ANDROID = "ANDROID", 4 | IOS = "IOS", 5 | } 6 | -------------------------------------------------------------------------------- /src/modules/auth/swagger/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./auth.schema"; 2 | export { default as authSwagger } from "./auth.swagger"; 3 | -------------------------------------------------------------------------------- /assets/main/font/avenir-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/devcarron/casino-multi-chain/HEAD/assets/main/font/avenir-regular.ttf -------------------------------------------------------------------------------- /src/cron/crons/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./customer-update"; 2 | export * from "./dashboard-update"; 3 | export * from "./trait-update"; 4 | -------------------------------------------------------------------------------- /src/modules/auto-crash-bet/auto-crash-bet.types.ts: -------------------------------------------------------------------------------- 1 | export const AUTO_CRASH_BET_ENUM = { 2 | AUTO_CRASH_BET: "auto_crash_bet", 3 | }; 4 | -------------------------------------------------------------------------------- /src/modules/user/swagger/index.ts: -------------------------------------------------------------------------------- 1 | export * as userSchema from "./user.schema"; 2 | export { default as userSwagger } from "./user.swagger"; 3 | -------------------------------------------------------------------------------- /src/utils/helpers/generate-code.ts: -------------------------------------------------------------------------------- 1 | export const generateCode = (): number => 2 | Math.floor(Math.random() * (999999 - 100000 + 1)) + 100000; 3 | -------------------------------------------------------------------------------- /src/modules/example/example.constant.ts: -------------------------------------------------------------------------------- 1 | export enum EXAMPLE_ENUM { 2 | EXAMPLE_FIRST = "EXAMPLE_FIRST", 3 | EXAMPLE_TWO = "EXAMPLE_TWO", 4 | } 5 | -------------------------------------------------------------------------------- /src/utils/helpers/string.ts: -------------------------------------------------------------------------------- 1 | export const stringToBoolean = (str: string = "false"): boolean => { 2 | return str.toLowerCase() === "true"; 3 | }; 4 | -------------------------------------------------------------------------------- /src/modules/example/swagger/index.ts: -------------------------------------------------------------------------------- 1 | export * as exampleSchema from "./example.schema"; 2 | export { default as exampleSwagger } from "./example.swagger"; 3 | -------------------------------------------------------------------------------- /src/constant/game.ts: -------------------------------------------------------------------------------- 1 | export const CGAME_LIST = { 2 | crash: "crash", 3 | mines: "mines", 4 | coinflip: "coinflip", 5 | blackjack: "blackjack" 6 | }; 7 | -------------------------------------------------------------------------------- /src/modules/site-transaction/site-transaction.constant.ts: -------------------------------------------------------------------------------- 1 | export enum EXAMPLE_ENUM { 2 | EXAMPLE_FIRST = "EXAMPLE_FIRST", 3 | EXAMPLE_TWO = "EXAMPLE_TWO", 4 | } 5 | -------------------------------------------------------------------------------- /src/middleware/authorize.ts: -------------------------------------------------------------------------------- 1 | import attachCurrentUser from "./attach-currentUser"; 2 | import isAuth from "./is-auth"; 3 | 4 | export default [isAuth, attachCurrentUser]; 5 | -------------------------------------------------------------------------------- /src/utils/encryption/init.ts: -------------------------------------------------------------------------------- 1 | import RsaWrapper from "./rsa-wrapper"; 2 | 3 | const rsaWrapper = new RsaWrapper(); 4 | 5 | rsaWrapper.generate("client"); 6 | rsaWrapper.generate("server"); 7 | -------------------------------------------------------------------------------- /src/utils/validations/is-email.ts: -------------------------------------------------------------------------------- 1 | export const isEmail = (email: string) => { 2 | const regex = /^[a-zA-Z0-9._-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,6}$/; 3 | return regex.test(email); 4 | }; 5 | -------------------------------------------------------------------------------- /src/constant/types.d.ts: -------------------------------------------------------------------------------- 1 | import jwt from "jsonwebtoken"; 2 | 3 | declare module "jsonwebtoken" { 4 | export interface UserJwtPayload extends jwt.JwtPayload { 5 | userId: string; 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/modules/staking/staking.types.ts: -------------------------------------------------------------------------------- 1 | import { IStakingModel } from "./staking.interface"; 2 | 3 | export type TStaking = Pick< 4 | IStakingModel, 5 | "_id" | "address" | "txDate" | "amount" | "txType" 6 | >; 7 | -------------------------------------------------------------------------------- /src/modules/example/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./example.constant"; 2 | export * from "./example.controller"; 3 | export * from "./example.service"; 4 | export * from "./example.types"; 5 | export * from "./example.validate"; 6 | -------------------------------------------------------------------------------- /src/modules/example/example.interface.ts: -------------------------------------------------------------------------------- 1 | export const getIdFromParamsWithMe = (req) => 2 | req.params.id === "me" 3 | ? req?.user?.userId 4 | ? req.user.userId 5 | : "000000000000000000000000" 6 | : req.params.id; 7 | -------------------------------------------------------------------------------- /src/modules/leaderboard/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./leaderboard.constant"; 2 | export * from "./leaderboard.controller"; 3 | export * from "./leaderboard.service"; 4 | export * from "./leaderboard.types"; 5 | export * from "./leaderboard.validate"; 6 | -------------------------------------------------------------------------------- /src/modules/dashboard/dashboard.types.ts: -------------------------------------------------------------------------------- 1 | import { IUserModel } from "../user/user.interface"; 2 | 3 | export type TDashboardDocumentType = Pick< 4 | IUserModel, 5 | "_id" | "username" | "leaderboard" | "avatar" | "hasVerifiedAccount" | "rank" 6 | >; 7 | -------------------------------------------------------------------------------- /src/modules/dashboard/dashboard.interface.ts: -------------------------------------------------------------------------------- 1 | import { Document } from "mongoose"; 2 | 3 | export declare interface IDashboardModel extends Document { 4 | revenueType: number; 5 | denom: string; 6 | lastBalance: number; 7 | insertDate?: Date; 8 | } 9 | -------------------------------------------------------------------------------- /src/modules/auto-crash-bet/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./auto-crash-bet.constant"; 2 | export * from "./auto-crash-bet.controller"; 3 | export * from "./auto-crash-bet.service"; 4 | export * from "./auto-crash-bet.types"; 5 | export * from "./auto-crash-bet.validate"; 6 | -------------------------------------------------------------------------------- /src/modules/leaderboard/leaderboard.types.ts: -------------------------------------------------------------------------------- 1 | import { IUserModel } from "../user/user.interface"; 2 | 3 | export type TLeaderboardDocumentType = Pick< 4 | IUserModel, 5 | "_id" | "username" | "leaderboard" | "avatar" | "hasVerifiedAccount" | "rank" 6 | >; 7 | -------------------------------------------------------------------------------- /src/modules/payment/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./payment.constant"; 2 | export * from "./payment.controller"; 3 | export * from "./payment.interface"; 4 | export * from "./payment.service"; 5 | export * from "./payment.types"; 6 | export * from "./payment.validate"; 7 | -------------------------------------------------------------------------------- /src/modules/chat-history/chat-history.interface.ts: -------------------------------------------------------------------------------- 1 | import { Document, ObjectId, Types } from "mongoose"; 2 | 3 | export interface IChatHistoryModel extends Document { 4 | _id: Types.ObjectId; 5 | message: string; 6 | user: ObjectId; 7 | sentAt: Date; 8 | } 9 | -------------------------------------------------------------------------------- /src/utils/helpers/custom-error.ts: -------------------------------------------------------------------------------- 1 | export class CustomError extends Error { 2 | status: number; 3 | 4 | constructor(statusCode: number, message: string) { 5 | super(message); 6 | this.message = message; 7 | this.status = statusCode; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/utils/helpers/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./custom-error"; 2 | export * from "./file-helpers"; 3 | export * from "./generate-code"; 4 | export * from "./get-paginationInfo"; 5 | export * from "./get-randomHash"; 6 | export * from "./sliceArr"; 7 | export * from "./string"; 8 | -------------------------------------------------------------------------------- /src/modules/dashboard/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./dashboard.constant"; 2 | export * from "./dashboard.controller"; 3 | export * from "./dashboard.interface"; 4 | export * from "./dashboard.service"; 5 | export * from "./dashboard.types"; 6 | export * from "./dashboard.validate"; 7 | -------------------------------------------------------------------------------- /src/modules/crash-game/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./crash-game.constant"; 2 | export * from "./crash-game.controller"; 3 | export * from "./crash-game.interface"; 4 | export * from "./crash-game.service"; 5 | export * from "./crash-game.types"; 6 | export * from "./crash-game.validate"; 7 | -------------------------------------------------------------------------------- /src/constant/enum.ts: -------------------------------------------------------------------------------- 1 | export enum ESOCKET_NAMESPACE { 2 | crash = "/crash", 3 | coinflip = "/coinflip", 4 | chat = "/813474ade739d5d", 5 | mines = "/mines", 6 | leaderboard = "/aaf366284dd1", 7 | payment = "/e3b86d17bc14e9", 8 | dashboard = "/d133087e2w21", 9 | } 10 | -------------------------------------------------------------------------------- /src/middleware/logger.ts: -------------------------------------------------------------------------------- 1 | import { NextFunction, Request, Response } from "express"; 2 | 3 | import logger from "@/utils/logger"; 4 | 5 | export default (req: Request, _res: Response, next: NextFunction) => { 6 | logger.info(`${req.method} ${req.originalUrl}`); 7 | next(); 8 | }; 9 | -------------------------------------------------------------------------------- /src/modules/staking/staking.constant.ts: -------------------------------------------------------------------------------- 1 | export enum ETXTYPE { 2 | STAKE = "STAKE", 3 | UNSTAKE = "UNSTAKE", 4 | CLAIM = "CLAIM", 5 | WITHDRAW = "WITHDRAW", 6 | } 7 | 8 | export enum EFilterStakeDate { 9 | week = "7d", 10 | month = "30d", 11 | year = "1Y", 12 | } 13 | -------------------------------------------------------------------------------- /src/modules/chat-history/chat-history.types.ts: -------------------------------------------------------------------------------- 1 | import { Types } from "mongoose"; 2 | 3 | import { TChatUser } from "../user/user.types"; 4 | 5 | export type IChatEmitHistory = { 6 | _id: Types.ObjectId; 7 | message: string; 8 | sentAt: Date; 9 | user?: TChatUser; 10 | }; 11 | -------------------------------------------------------------------------------- /src/modules/chat-history/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./chat-history.constant"; 2 | export * from "./chat-history.controller"; 3 | export * from "./chat-history.interface"; 4 | export * from "./chat-history.service"; 5 | export * from "./chat-history.types"; 6 | export * from "./chat-history.validate"; 7 | -------------------------------------------------------------------------------- /src/utils/helpers/sliceArr.ts: -------------------------------------------------------------------------------- 1 | export function sliceArr(array: T[], size): [[T]] { 2 | const subarray = []; 3 | 4 | for (let i = 0; i < Math.ceil(array.length / size); i++) { 5 | subarray[i] = array.slice(i * size, i * size + size); 6 | } 7 | 8 | return <[[T]]>subarray; 9 | } 10 | -------------------------------------------------------------------------------- /src/modules/site-transaction/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./site-transaction.constant"; 2 | export * from "./site-transaction.controller"; 3 | export * from "./site-transaction.interface"; 4 | export * from "./site-transaction.service"; 5 | export * from "./site-transaction.types"; 6 | export * from "./site-transaction.validate"; 7 | -------------------------------------------------------------------------------- /src/modules/trait/trait.types.ts: -------------------------------------------------------------------------------- 1 | import { Document } from "mongoose"; 2 | 3 | export type TTrait = Pick; 4 | 5 | export interface ITraitModel extends Document { 6 | key: string; 7 | name: string; 8 | value: string; 9 | createdAt: Date; 10 | updatedAt: Date; 11 | } 12 | -------------------------------------------------------------------------------- /src/modules/staking/staking.service.ts: -------------------------------------------------------------------------------- 1 | import BaseService from "@/utils/base/service"; 2 | import { Staking } from "@/utils/db"; 3 | 4 | import { IStakingModel } from "./staking.interface"; 5 | 6 | export default class StakingService extends BaseService { 7 | constructor() { 8 | super(Staking); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/modules/leaderboard/leaderboard.constant.ts: -------------------------------------------------------------------------------- 1 | export enum EXAMPLE_ENUM { 2 | EXAMPLE_FIRST = "EXAMPLE_FIRST", 3 | EXAMPLE_TWO = "EXAMPLE_TWO", 4 | } 5 | 6 | export enum ELeaderboardEvents { 7 | leaderboardHistoryFetch = "leaderboard-fetch-all", 8 | notifyError = "notify-error", 9 | disconnect = "disconnect", 10 | } 11 | 12 | -------------------------------------------------------------------------------- /src/modules/crash-game/crash-game.validate.ts: -------------------------------------------------------------------------------- 1 | import Joi from "joi"; 2 | 3 | import * as validations from "@/utils/validations"; 4 | 5 | export const CreateCrashGameSchema = Joi.object({ 6 | name: Joi.string(), 7 | }); 8 | 9 | export const UpdateCrashGameSchema = Joi.object({ 10 | id: validations.byId, 11 | name: Joi.string(), 12 | }); 13 | -------------------------------------------------------------------------------- /src/middleware/add-dirForUpload.ts: -------------------------------------------------------------------------------- 1 | import { NextFunction, Request, Response } from "express"; 2 | 3 | export default (dir_for_files: string) => { 4 | return ( 5 | req: Request & { dir_for_files: string }, 6 | _res: Response, 7 | next: NextFunction 8 | ) => { 9 | req.dir_for_files = dir_for_files; 10 | next(); 11 | }; 12 | }; 13 | -------------------------------------------------------------------------------- /src/modules/example/example.types.ts: -------------------------------------------------------------------------------- 1 | import { Document, SchemaTimestampsConfig } from "mongoose"; 2 | 3 | import { EXAMPLE_ENUM } from "./"; 4 | 5 | export interface IExampleObject { 6 | name: string; 7 | type: EXAMPLE_ENUM; 8 | } 9 | 10 | export interface IExample 11 | extends IExampleObject, 12 | Document, 13 | SchemaTimestampsConfig {} 14 | -------------------------------------------------------------------------------- /src/modules/user/user.constant.ts: -------------------------------------------------------------------------------- 1 | export enum ROLE { 2 | MEMBER = "MEMBER", 3 | ADMIN = "ADMIN", 4 | } 5 | 6 | export enum STATUS { 7 | BLOCKED = "BLOCKED", 8 | NOT_VERIFIED = "NOT_VERIFIED", 9 | VERIFIED = "VERIFIED", 10 | WITHOUT_PAYMENT = "WITHOUT_PAYMENT", 11 | WITHOUT_SUBSCRIBE = "WITHOUT_SUBSCRIBE", 12 | WITH_PAYMENT = "WITH_PAYMENT", 13 | } 14 | -------------------------------------------------------------------------------- /src/modules/site-transaction/site-transaction.types.ts: -------------------------------------------------------------------------------- 1 | import { Document, SchemaTimestampsConfig } from "mongoose"; 2 | 3 | import { EXAMPLE_ENUM } from "."; 4 | 5 | export interface IExampleObject { 6 | name: string; 7 | type: EXAMPLE_ENUM; 8 | } 9 | 10 | export interface IExample 11 | extends IExampleObject, 12 | Document, 13 | SchemaTimestampsConfig {} 14 | -------------------------------------------------------------------------------- /src/modules/user-bot/user-bot.constant.ts: -------------------------------------------------------------------------------- 1 | export enum ROLE { 2 | MEMBER = "MEMBER", 3 | ADMIN = "ADMIN", 4 | } 5 | 6 | export enum STATUS { 7 | BLOCKED = "BLOCKED", 8 | NOT_VERIFIED = "NOT_VERIFIED", 9 | VERIFIED = "VERIFIED", 10 | WITHOUT_PAYMENT = "WITHOUT_PAYMENT", 11 | WITHOUT_SUBSCRIBE = "WITHOUT_SUBSCRIBE", 12 | WITH_PAYMENT = "WITH_PAYMENT", 13 | } 14 | -------------------------------------------------------------------------------- /src/utils/helpers/response.ts: -------------------------------------------------------------------------------- 1 | export enum Messages { 2 | CREATED = "Created", 3 | } 4 | 5 | export type Response = { 6 | status: 201; 7 | payload: { 8 | message: Messages; 9 | }; 10 | }; 11 | 12 | export function response(message: Messages): Response { 13 | return { 14 | status: 201, 15 | payload: { 16 | message, 17 | }, 18 | }; 19 | } 20 | -------------------------------------------------------------------------------- /src/utils/helpers/string-helper.ts: -------------------------------------------------------------------------------- 1 | export function adjustStringLength(inputString: string, length: number) { 2 | if (inputString.length > length) { 3 | return inputString.substring(0, length); 4 | } 5 | 6 | return inputString; 7 | } 8 | 9 | export const fromBase64 = (base64String: string): Uint8Array => { 10 | return Buffer.from(base64String, "base64"); 11 | }; 12 | -------------------------------------------------------------------------------- /src/utils/helpers/get-randomHash.ts: -------------------------------------------------------------------------------- 1 | export function getRandomHash(length) { 2 | let result = ""; 3 | const characters = "abcdefghijklmnopqrstuvwxyz0123456789"; 4 | const charactersLength = characters.length; 5 | 6 | for (let i = 0; i < length; i++) { 7 | result += characters.charAt(Math.floor(Math.random() * charactersLength)); 8 | } 9 | 10 | return result; 11 | } 12 | -------------------------------------------------------------------------------- /src/utils/helpers/detect-mobFromUserAgent.ts: -------------------------------------------------------------------------------- 1 | const toMatch = [ 2 | /Android/i, 3 | /webOS/i, 4 | /iPhone/i, 5 | /iPad/i, 6 | /iPod/i, 7 | /BlackBerry/i, 8 | /Windows Phone/i, 9 | /okhttp/i, 10 | ]; 11 | 12 | export default (userAgent) => { 13 | return Boolean( 14 | toMatch.find((toMatchItem) => { 15 | return userAgent.match(toMatchItem); 16 | }) 17 | ); 18 | }; 19 | -------------------------------------------------------------------------------- /src/utils/date.ts: -------------------------------------------------------------------------------- 1 | export function getMonthName(monthNumber: number) { 2 | const date = new Date(); 3 | date.setMonth(monthNumber - 1); // JavaScript months are 0-based 4 | return date.toLocaleString("default", { month: "short" }); 5 | } 6 | 7 | export function getDayName(day: number): string { 8 | const days = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]; 9 | return days[day % 7]; 10 | } 11 | -------------------------------------------------------------------------------- /src/modules/trait/trait.validate.ts: -------------------------------------------------------------------------------- 1 | import Joi from "joi"; 2 | 3 | import { validateId } from "@/utils/validations"; 4 | 5 | export const Id = Joi.custom((value, helper) => validateId(value, helper)); 6 | 7 | export const main = Joi.object({ 8 | key: Joi.string().required(), 9 | name: Joi.string().required(), 10 | value: Joi.string().required(), 11 | }); 12 | 13 | export const createValidate = main; 14 | -------------------------------------------------------------------------------- /src/modules/auto-crash-bet/auto-crash-bet.service.ts: -------------------------------------------------------------------------------- 1 | // need add model to mongo index file 2 | import BaseService from "@/utils/base/service"; 3 | import { AutoCrashBet } from "@/utils/db"; 4 | 5 | // need add types 6 | import { IAutoCrashBetModel } from "./auto-crash-bet.interface"; 7 | 8 | export class AutoCrashBetService extends BaseService { 9 | constructor() { 10 | super(AutoCrashBet); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/modules/chat-history/chat-history.constant.ts: -------------------------------------------------------------------------------- 1 | export enum EXAMPLE_ENUM { 2 | EXAMPLE_FIRST = "EXAMPLE_FIRST", 3 | EXAMPLE_TWO = "EXAMPLE_TWO", 4 | } 5 | 6 | export enum EChatHistoryEvents { 7 | auth = "auth", 8 | getChatHistory = "get-chat-history", 9 | b2fMessage = "backend-frontend-message", 10 | f2bMessage = "frontend-backend-message", 11 | notifyError = "notify-error", 12 | disconnect = "disconnect", 13 | } 14 | -------------------------------------------------------------------------------- /src/modules/site-transaction/site-transaction.service.ts: -------------------------------------------------------------------------------- 1 | // need add model to mongo index file 2 | import BaseService from "@/utils/base/service"; 3 | import { SiteTransaction } from "@/utils/db"; 4 | 5 | import { ISiteTransactionModel } from "./site-transaction.interface"; 6 | 7 | export class SiteTransactionService extends BaseService { 8 | constructor() { 9 | super(SiteTransaction); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/modules/auto-crash-bet/auto-crash-bet.interface.ts: -------------------------------------------------------------------------------- 1 | import mongoose, { Document } from "mongoose"; 2 | 3 | import { IUserModel } from "../user/user.interface"; 4 | 5 | export interface IAutoCrashBetModel extends Document { 6 | _id: mongoose.Types.ObjectId; 7 | user: mongoose.Types.ObjectId | IUserModel; 8 | betAmount: number; 9 | denom: string; 10 | cashoutPoint: number; 11 | status: boolean; 12 | count: number; 13 | } 14 | -------------------------------------------------------------------------------- /src/modules/auto-crash-bet/auto-crash-bet.validate.ts: -------------------------------------------------------------------------------- 1 | import Joi from "joi"; 2 | 3 | import * as validations from "@/utils/validations"; 4 | 5 | export const CreateExampleSchema = Joi.object({ 6 | name: Joi.string(), 7 | // type: validations.byEnum(AUTO_CRASH_BET_ENUM), 8 | }); 9 | 10 | export const UpdateExampleSchema = Joi.object({ 11 | id: validations.byId, 12 | name: Joi.string(), 13 | // type: validations.byEnum(EXAMPLE_ENUM), 14 | }); 15 | -------------------------------------------------------------------------------- /src/modules/auth/auth.interface.ts: -------------------------------------------------------------------------------- 1 | export const device = ({ headers }) => ({ 2 | deviceId: headers.deviceid, 3 | platform: headers.platform, 4 | }); 5 | 6 | export const getBody = ({ body }) => body; 7 | 8 | export const getCode = ({ body }) => body.code; 9 | 10 | export const refreshToken = ({ body }) => body.refreshToken; 11 | 12 | export const session = ({ session }) => session; 13 | 14 | export const updateToken = (req) => ({ 15 | ...req.body, 16 | }); 17 | -------------------------------------------------------------------------------- /src/utils/swagger/global.schema.ts: -------------------------------------------------------------------------------- 1 | export const mongoId = (required = false) => { 2 | return { 3 | type: "string", 4 | pattern: "/^[0-9a-fA-F]{24}$/", 5 | example: "627f7889bc3d1b3795cfe5a1", 6 | required: required, 7 | }; 8 | }; 9 | 10 | export const mongoObject = { 11 | _id: mongoId(true), 12 | updatedAt: { type: "string", format: "date-time", required: true }, 13 | createdAt: { type: "string", format: "date-time", required: true }, 14 | }; 15 | -------------------------------------------------------------------------------- /src/modules/example/example.validate.ts: -------------------------------------------------------------------------------- 1 | import Joi from "joi"; 2 | 3 | import * as validations from "@/utils/validations"; 4 | 5 | import { EXAMPLE_ENUM } from "./example.constant"; 6 | 7 | export const CreateExampleSchema = Joi.object({ 8 | name: Joi.string(), 9 | type: validations.byEnum(EXAMPLE_ENUM), 10 | }); 11 | 12 | export const UpdateExampleSchema = Joi.object({ 13 | id: validations.byId, 14 | name: Joi.string(), 15 | type: validations.byEnum(EXAMPLE_ENUM), 16 | }); 17 | -------------------------------------------------------------------------------- /src/modules/dashboard/dashboard.validate.ts: -------------------------------------------------------------------------------- 1 | import Joi from "joi"; 2 | 3 | import * as validations from "@/utils/validations"; 4 | 5 | // import { EXAMPLE_ENUM } from "./coinflip-game.constant"; 6 | 7 | export const CreateDashboardSchema = Joi.object({ 8 | name: Joi.string(), 9 | // type: validations.byEnum(EXAMPLE_ENUM), 10 | }); 11 | 12 | export const UpdateDashboardSchema = Joi.object({ 13 | id: validations.byId, 14 | name: Joi.string(), 15 | // type: validations.byEnum(EXAMPLE_ENUM), 16 | }); 17 | -------------------------------------------------------------------------------- /src/modules/chat-history/chat-history.validate.ts: -------------------------------------------------------------------------------- 1 | import Joi from "joi"; 2 | 3 | import * as validations from "@/utils/validations"; 4 | 5 | // import { EXAMPLE_ENUM } from "./chat-history.constant"; 6 | 7 | export const CreateChatHistorySchema = Joi.object({ 8 | name: Joi.string(), 9 | // type: validations.byEnum(EXAMPLE_ENUM), 10 | }); 11 | 12 | export const UpdateChatHistorySchema = Joi.object({ 13 | id: validations.byId, 14 | name: Joi.string(), 15 | // type: validations.byEnum(EXAMPLE_ENUM), 16 | }); 17 | -------------------------------------------------------------------------------- /src/modules/example/swagger/example.schema.ts: -------------------------------------------------------------------------------- 1 | import { EXAMPLE_ENUM } from "../example.constant"; 2 | 3 | export const exampleSchema = { 4 | name: { type: "string", required: true }, 5 | type: { 6 | type: "string", 7 | enum: EXAMPLE_ENUM, 8 | default: EXAMPLE_ENUM.EXAMPLE_FIRST, 9 | required: false, 10 | }, 11 | }; 12 | 13 | export const fullExampleSchema = { 14 | type: "object", 15 | properties: { 16 | _id: { type: "string", required: true }, 17 | ...exampleSchema, 18 | }, 19 | }; 20 | -------------------------------------------------------------------------------- /src/modules/leaderboard/leaderboard.validate.ts: -------------------------------------------------------------------------------- 1 | import Joi from "joi"; 2 | 3 | import * as validations from "@/utils/validations"; 4 | 5 | // import { EXAMPLE_ENUM } from "./coinflip-game.constant"; 6 | 7 | export const CreateLeaderboardSchema = Joi.object({ 8 | name: Joi.string(), 9 | // type: validations.byEnum(EXAMPLE_ENUM), 10 | }); 11 | 12 | export const UpdateLeaderboardSchema = Joi.object({ 13 | id: validations.byId, 14 | name: Joi.string(), 15 | // type: validations.byEnum(EXAMPLE_ENUM), 16 | }); 17 | -------------------------------------------------------------------------------- /src/utils/validations/router.ts: -------------------------------------------------------------------------------- 1 | import Joi from "joi"; 2 | 3 | import { validateId } from "./validate"; 4 | 5 | export const byId = Joi.custom((value, helper) => validateId(value, helper)); 6 | 7 | export const byEnum = (Enum) => Joi.string().valid(...Object.keys(Enum)); 8 | export const byEnumValues = (Enum) => 9 | Joi.string().valid(...Object.values(Enum)); 10 | 11 | export const getListValidation = Joi.object({ 12 | text: Joi.string().allow(""), 13 | offset: Joi.number(), 14 | limit: Joi.number(), 15 | }); 16 | -------------------------------------------------------------------------------- /src/modules/site-transaction/site-transaction.validate.ts: -------------------------------------------------------------------------------- 1 | import Joi from "joi"; 2 | 3 | import * as validations from "@/utils/validations"; 4 | 5 | // import { EXAMPLE_ENUM } from "./payment.constant"; 6 | 7 | export const CreateSiteTransactionSchema = Joi.object({ 8 | name: Joi.string(), 9 | // type: validations.byEnum(EXAMPLE_ENUM), 10 | }); 11 | 12 | export const UpdateSiteTransactionSchema = Joi.object({ 13 | id: validations.byId, 14 | name: Joi.string(), 15 | // type: validations.byEnum(EXAMPLE_ENUM), 16 | }); 17 | -------------------------------------------------------------------------------- /src/utils/helpers/get-paginationInfo.ts: -------------------------------------------------------------------------------- 1 | type Value = { 2 | value: number; 3 | default: number; 4 | }; 5 | 6 | export function getPaginationInfo( 7 | { value: offset, default: defaultOffset = 0 }: Value, 8 | { value: limit, default: defaultLimit = 10 }: Value 9 | ): { offset: number; limit: number } { 10 | const newOffset = !+offset || offset < 0 ? defaultOffset : +offset; 11 | const newLimit = !+limit || limit < 1 ? defaultLimit : +limit; 12 | 13 | return { 14 | offset: newOffset, 15 | limit: newLimit, 16 | }; 17 | } 18 | -------------------------------------------------------------------------------- /src/middleware/validate-schema.ts: -------------------------------------------------------------------------------- 1 | export default function validateSchema(schema, mapProperty) { 2 | return (req, res, next) => { 3 | const property = mapProperty ? mapProperty(req) : req.body; 4 | const { error } = schema.validate(property); 5 | 6 | if (error == null) { 7 | return next(); 8 | } else { 9 | const { details } = error; 10 | const message = details.map((i) => i.message).join(","); 11 | 12 | return res.status(400).json({ 13 | error: message, 14 | }); 15 | } 16 | }; 17 | } 18 | -------------------------------------------------------------------------------- /src/modules/example/example.service.ts: -------------------------------------------------------------------------------- 1 | // need add model to mongo index file 2 | import BaseService from "@/utils/base/service"; 3 | import { Example } from "@/utils/db"; 4 | 5 | // need add types 6 | import { IExample } from "./example.types"; 7 | 8 | export class ExampleService extends BaseService { 9 | constructor() { 10 | super(Example); 11 | } 12 | 13 | // Do,read,write something with DB 14 | async exampleFunc() { 15 | return this.database.aggregate([ 16 | // something doing in DB 17 | ]); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/modules/payment/payment.interface.ts: -------------------------------------------------------------------------------- 1 | import { Tendermint37Client } from "@cosmjs/tendermint-rpc"; 2 | import { kujiraQueryClient } from "kujira.js/lib/cjs/queryClient.js"; 3 | import { Document } from "mongoose"; 4 | 5 | export interface IPaymentModel extends Document { 6 | userId?: string; 7 | walletAddress?: string; 8 | type: string; 9 | amount: number; 10 | denom?: string; 11 | txHash: string; 12 | } 13 | 14 | export interface IClient { 15 | tmClient: Tendermint37Client; 16 | querier: ReturnType; 17 | } 18 | -------------------------------------------------------------------------------- /src/utils/validations/validate.ts: -------------------------------------------------------------------------------- 1 | import { Types } from "mongoose"; 2 | 3 | export const validateId = (id, helpers) => { 4 | if (!Types.ObjectId.isValid(id)) { 5 | return helpers.message("Id not valid"); 6 | } 7 | 8 | return id; 9 | }; 10 | 11 | export function validateFunc(schema, object) { 12 | const { error } = schema.validate(object); 13 | const valid = error == null; 14 | 15 | if (valid) { 16 | return null; 17 | } else { 18 | const { details } = error; 19 | return details.map((i) => i.message).join(","); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/modules/user/user.types.ts: -------------------------------------------------------------------------------- 1 | import { IUserModel } from "./user.interface"; 2 | 3 | export interface IVIPLevelType { 4 | name: string; 5 | wagerNeeded?: number; 6 | rakebackPercentage?: number; 7 | levelName?: string; 8 | levelColor?: string; 9 | } 10 | 11 | export type TChatUser = Pick< 12 | IUserModel, 13 | "_id" | "username" | "avatar" | "hasVerifiedAccount" | "createdAt" 14 | >; 15 | 16 | export type TLeaderboardUserType = Pick< 17 | IUserModel, 18 | "_id" | "username" | "leaderboard" | "avatar" | "hasVerifiedAccount" | "rank" 19 | >; 20 | -------------------------------------------------------------------------------- /src/modules/dashboard/dashboard.constant.ts: -------------------------------------------------------------------------------- 1 | export enum EXAMPLE_ENUM { 2 | EXAMPLE_FIRST = "EXAMPLE_FIRST", 3 | EXAMPLE_TWO = "EXAMPLE_TWO", 4 | } 5 | 6 | export enum ELeaderboardEvents { 7 | leaderboardHistoryFetch = "leaderboard-fetch-all", 8 | notifyError = "notify-error", 9 | disconnect = "disconnect", 10 | } 11 | 12 | export enum EFilterDate { 13 | hour = "hour", 14 | day = "day", 15 | week = "week", 16 | month = "month", 17 | year = "year", 18 | } 19 | export enum ERevenueType { 20 | TOTAL = 0, 21 | COINFLIP = 1, 22 | CRASH = 2, 23 | MINE = 3, 24 | } 25 | -------------------------------------------------------------------------------- /src/modules/example/example.model.ts: -------------------------------------------------------------------------------- 1 | import { model, Schema } from "mongoose"; 2 | 3 | import { EXAMPLE_ENUM } from "./example.constant"; 4 | import { IExample } from "./example.types"; 5 | 6 | const ExampleSchema = new Schema( 7 | { 8 | name: { 9 | type: String, 10 | required: true, 11 | }, 12 | type: { 13 | type: String, 14 | enum: Object.keys(EXAMPLE_ENUM), 15 | required: true, 16 | }, 17 | }, 18 | { versionKey: false, timestamps: true } 19 | ); 20 | 21 | export default model("Example", ExampleSchema, "examples"); 22 | -------------------------------------------------------------------------------- /src/modules/staking/staking.interface.ts: -------------------------------------------------------------------------------- 1 | import { Document } from "mongoose"; 2 | 3 | import { ETXTYPE } from "./staking.constant"; 4 | 5 | export interface IStakingModel extends Document { 6 | address: string; 7 | txDate: Date; 8 | amount: number; 9 | txAmount: number; 10 | txStatus: string; 11 | txHash: string; 12 | txType: ETXTYPE; 13 | createdAt: Date; 14 | updatedAt: Date; 15 | } 16 | 17 | export const getId = (req) => { 18 | if (req.params.id === "me") { 19 | return req?.user?.userId || "000000000000000000000000"; 20 | } 21 | 22 | return req.params.id; 23 | }; 24 | -------------------------------------------------------------------------------- /src/modules/staking/staking.validate.ts: -------------------------------------------------------------------------------- 1 | import Joi from "joi"; 2 | 3 | import { validateId } from "@/utils/validations"; 4 | 5 | import { ETXTYPE } from "./staking.constant"; 6 | 7 | export const Id = Joi.custom((value, helper) => validateId(value, helper)); 8 | 9 | export const main = Joi.object({ 10 | address: Joi.string().required(), 11 | amount: Joi.number().allow(0, null).optional(), 12 | txDate: Joi.date().required(), 13 | txHash: Joi.string().required(), 14 | txType: Joi.string() 15 | .valid(...Object.keys(ETXTYPE)) 16 | .required(), 17 | }); 18 | 19 | export const createValidate = main; 20 | -------------------------------------------------------------------------------- /src/modules/trait/trait.model.ts: -------------------------------------------------------------------------------- 1 | // Import Dependencies 2 | import mongoose from "mongoose"; 3 | 4 | import { ITraitModel } from "./trait.types"; 5 | 6 | // Destructure Schema Types 7 | const { Schema } = mongoose; 8 | 9 | // Setup User Schema 10 | const TraitSchema = new Schema( 11 | { 12 | key: { type: String, required: true }, 13 | name: { type: String, required: true }, 14 | value: { type: String }, 15 | }, 16 | { 17 | timestamps: true, 18 | } 19 | ); 20 | 21 | // Create and export the new model 22 | const Trait = mongoose.model("Trait", TraitSchema); 23 | 24 | export default Trait; 25 | -------------------------------------------------------------------------------- /src/modules/site-transaction/site-transaction.interface.ts: -------------------------------------------------------------------------------- 1 | import mongoose, { Document } from "mongoose"; 2 | 3 | export interface ISiteTransactionModel extends Document { 4 | amount: number; 5 | denom: string; 6 | reason: string; 7 | extraData: { 8 | coinflipGameId?: mongoose.Types.ObjectId; 9 | crashGameId?: mongoose.Types.ObjectId; 10 | transactionId?: mongoose.Types.ObjectId; 11 | couponId?: mongoose.Types.ObjectId; 12 | affiliatorId?: mongoose.Types.ObjectId; 13 | modifierId?: mongoose.Types.ObjectId; 14 | raceId?: mongoose.Types.ObjectId; 15 | triviaGameId?: mongoose.Types.ObjectId; 16 | }; 17 | userId: mongoose.Types.ObjectId; 18 | } 19 | -------------------------------------------------------------------------------- /src/utils/jwt/create-token.ts: -------------------------------------------------------------------------------- 1 | import jwt from "jsonwebtoken"; 2 | 3 | import { TOKEN_LIFE, TOKEN_SECRET } from "@/config"; 4 | import { IAuthInfo } from "@/modules/auth/auth.types"; 5 | 6 | type TokenResult = { 7 | token: string; 8 | expiresIn?: number | string; 9 | }; 10 | 11 | export default ( 12 | info: IAuthInfo, 13 | secret = TOKEN_SECRET, 14 | expiresIn = TOKEN_LIFE 15 | ): TokenResult => { 16 | const result: Partial = {}; 17 | 18 | const option = expiresIn ? { expiresIn } : {}; 19 | 20 | if (expiresIn) { 21 | result.expiresIn = expiresIn; 22 | } 23 | 24 | result.token = jwt.sign(info, secret, option); 25 | 26 | return result as TokenResult; 27 | }; 28 | -------------------------------------------------------------------------------- /logs/.87963bbc7c723ad6cb7f9cd6daeb94b0e4a665be-audit.json: -------------------------------------------------------------------------------- 1 | { 2 | "keep": { 3 | "days": true, 4 | "amount": 14 5 | }, 6 | "auditLog": "logs\\.87963bbc7c723ad6cb7f9cd6daeb94b0e4a665be-audit.json", 7 | "files": [ 8 | { 9 | "date": 1721743235856, 10 | "name": "logs\\combined-2024-07-23.log", 11 | "hash": "27f7bbeb8265a72ac699e8d501f1a3dc3afb2e9a1f881d9bd07f0e00e42eb933" 12 | }, 13 | { 14 | "date": 1721749042863, 15 | "name": "logs\\combined-2024-07-24.log", 16 | "hash": "93f2373adad0b1b47d7a555685f3e32353631bfd208008645acc350e2cd1ce88" 17 | } 18 | ], 19 | "hashType": "sha256" 20 | } -------------------------------------------------------------------------------- /src/modules/payment/payment.model.ts: -------------------------------------------------------------------------------- 1 | // Import Dependencies 2 | import mongoose, { model } from "mongoose"; 3 | 4 | import { IPaymentModel } from "./payment.interface"; 5 | 6 | // Destructure Schema Types 7 | const { Schema } = mongoose; 8 | 9 | // Setup Payment Schema 10 | const PaymentSchema = new Schema( 11 | { 12 | // Authentication related fields 13 | userId: { type: String }, 14 | walletAddress: { type: String }, 15 | type: { type: String }, 16 | denom: { type: String }, 17 | amount: { type: Number }, 18 | txHash: { type: String }, 19 | }, 20 | { 21 | timestamps: true, 22 | } 23 | ); 24 | 25 | export default model("Payment", PaymentSchema); 26 | -------------------------------------------------------------------------------- /src/modules/crash-game/crash-game.service.ts: -------------------------------------------------------------------------------- 1 | // need add model to mongo index file 2 | import BaseService from "@/utils/base/service"; 3 | import { CrashGame } from "@/utils/db"; 4 | 5 | import { CGAME_STATES } from "./crash-game.constant"; 6 | import { ICrashGameModel } from "./crash-game.interface"; 7 | 8 | export class CrashGameService extends BaseService { 9 | constructor() { 10 | super(CrashGame); 11 | } 12 | 13 | public getUnfinishedGames = async () => { 14 | return this.database.find({ 15 | $or: [ 16 | { status: CGAME_STATES.Starting }, 17 | { status: CGAME_STATES.Blocking }, 18 | { status: CGAME_STATES.InProgress }, 19 | ], 20 | }); 21 | }; 22 | } 23 | -------------------------------------------------------------------------------- /src/modules/chat-history/chat-history.model.ts: -------------------------------------------------------------------------------- 1 | // Import Dependencies 2 | import mongoose, { model, SchemaTypes } from "mongoose"; 3 | 4 | import { IChatHistoryModel } from "./chat-history.interface"; 5 | 6 | // Destructure Schema Types 7 | const { Schema } = mongoose; 8 | 9 | // Setup Race Schema 10 | const ChatHistorySchema = new Schema({ 11 | // Basic fields 12 | message: String, 13 | 14 | // Sender 15 | user: { 16 | type: SchemaTypes.ObjectId, 17 | ref: "User", 18 | }, 19 | 20 | // When this chat history was created 21 | sentAt: { 22 | type: Date, 23 | default: Date.now, 24 | }, 25 | }); 26 | 27 | export default model("ChatHistory", ChatHistorySchema); 28 | -------------------------------------------------------------------------------- /src/modules/auth/auth.model.ts: -------------------------------------------------------------------------------- 1 | import { model, Schema } from "mongoose"; 2 | 3 | import * as AuthConstant from "./auth.constant"; 4 | import { IAuthModel } from "./auth.types"; 5 | 6 | const AuthSchema = new Schema( 7 | { 8 | userId: { 9 | type: Schema.Types.ObjectId, 10 | ref: "User", 11 | required: true, 12 | }, 13 | deviceId: { type: String }, 14 | platform: { 15 | type: String, 16 | enum: Object.keys(AuthConstant.PLATFORM), 17 | default: AuthConstant.PLATFORM.WEB, 18 | required: true, 19 | }, 20 | refreshToken: { type: String, required: false }, 21 | }, 22 | { versionKey: false, timestamps: true } 23 | ); 24 | 25 | export default model("Auth", AuthSchema, "auths"); 26 | -------------------------------------------------------------------------------- /src/modules/crash-game/crash-game.router.ts: -------------------------------------------------------------------------------- 1 | import actionHandler from "@/middleware/action-handler"; 2 | import checkPermissions from "@/middleware/check-permissions"; 3 | import { ROLE } from "@/modules/user/user.constant"; 4 | import { BaseRouter } from "@/utils/base"; 5 | 6 | import { CrashGameController } from "."; 7 | 8 | export default class CrashGameRouter extends BaseRouter { 9 | private crashGameController: CrashGameController; 10 | 11 | constructor() { 12 | super(); 13 | this.crashGameController = new CrashGameController(); 14 | this.routes(); 15 | } 16 | 17 | public routes(): void { 18 | this.router.get( 19 | "/", 20 | checkPermissions({ roles: [ROLE.ADMIN] }), 21 | actionHandler(this.crashGameController.getAll) 22 | ); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/modules/user-bot/user-bot.types.ts: -------------------------------------------------------------------------------- 1 | import { Document } from "mongoose"; 2 | 3 | import { ROLE, STATUS } from "./user-bot.constant"; 4 | 5 | export interface ISubscriptionPlan { 6 | startDay: Date; 7 | endDay: Date; 8 | isCompleted: boolean; 9 | inProgress: boolean; 10 | } 11 | 12 | export interface IUser extends Document { 13 | avatar?: string; 14 | name?: string; 15 | password?: string; 16 | phoneNumber?: string; 17 | socialLinks: Array<{ 18 | type: { type: string }; 19 | link: { type: string }; 20 | }>; 21 | wallet?: Array; 22 | role: Array; 23 | status: STATUS; 24 | stripeCard?: { 25 | brand: string; 26 | exp_month: string; 27 | exp_year: string; 28 | last4: string; 29 | pmId: string; 30 | holder: string; 31 | }; 32 | } 33 | -------------------------------------------------------------------------------- /src/cron/crons/customer-update/customer-update.ts: -------------------------------------------------------------------------------- 1 | import Cron, { ScheduleOptions } from "node-cron"; 2 | 3 | import { BaseCron } from "@/cron/crons/base.cron"; 4 | 5 | export class CustomerUpdate extends BaseCron { 6 | constructor(cronExpression: string, option = {}) { 7 | super(cronExpression, option); 8 | } 9 | 10 | public start = () => { 11 | this.initCron(); 12 | }; 13 | 14 | private initCron = () => { 15 | this.task = Cron.schedule( 16 | this.cronExpression, 17 | async () => { 18 | await this.catchWrapper( 19 | this.updateCustomerStatus, 20 | "updateCustomerStatus" 21 | ); 22 | }, 23 | this.option 24 | ); 25 | }; 26 | 27 | private updateCustomerStatus = async () => { 28 | console.log("Start updateCustomerStatus"); 29 | }; 30 | } 31 | -------------------------------------------------------------------------------- /src/modules/staking/staking.model.ts: -------------------------------------------------------------------------------- 1 | // Import Dependencies 2 | import mongoose from "mongoose"; 3 | 4 | import { IStakingModel } from "./staking.interface"; 5 | 6 | // Destructure Schema Types 7 | const { Schema } = mongoose; 8 | 9 | // Setup User Schema 10 | const StakingSchema = new Schema( 11 | { 12 | address: { type: String, required: true }, 13 | amount: { type: Number, required: true }, 14 | txAmount: { type: Number }, 15 | txDate: { type: Date, required: true }, 16 | txType: { type: String, required: true }, 17 | txHash: { type: String, required: true, unique: true }, 18 | txStatus: { type: String }, 19 | }, 20 | { 21 | timestamps: true, 22 | } 23 | ); 24 | 25 | // Create and export the new model 26 | const Staking = mongoose.model("Staking", StakingSchema); 27 | 28 | export default Staking; 29 | -------------------------------------------------------------------------------- /src/modules/auto-crash-bet/auto-crash-bet.router.ts: -------------------------------------------------------------------------------- 1 | import actionHandler from "@/middleware/action-handler"; 2 | import checkPermissions from "@/middleware/check-permissions"; 3 | import { ROLE } from "@/modules/user/user.constant"; 4 | import { BaseRouter } from "@/utils/base"; 5 | 6 | // import * as validations from "@/utils/validations"; 7 | import { AutoCrashBetController } from "./"; 8 | 9 | export default class AutoCrashBetRouter extends BaseRouter { 10 | private autoCrashBetController: AutoCrashBetController; 11 | 12 | constructor() { 13 | super(); 14 | 15 | this.autoCrashBetController = new AutoCrashBetController(); 16 | this.routes(); 17 | } 18 | 19 | public routes(): void { 20 | this.router.get( 21 | "/", 22 | checkPermissions({ roles: [ROLE.ADMIN] }), 23 | actionHandler(this.autoCrashBetController.getAll) 24 | ); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/modules/dashboard/dashboard.model.ts: -------------------------------------------------------------------------------- 1 | // Require Dependencies 2 | import mongoose, { model } from "mongoose"; 3 | 4 | import { IDashboardModel } from "."; 5 | 6 | const Schema = mongoose.Schema; 7 | 8 | // Setup TransactionHis Schema 9 | const DashboardSchema = new Schema( 10 | { 11 | // Revenue type 1: coinflip,2: crash, 3: mines 12 | revenueType: { 13 | type: Number, 14 | required: true, 15 | }, 16 | 17 | // Denom 18 | denom: { 19 | type: String, 20 | require: true, 21 | }, 22 | 23 | // Last balance 24 | lastBalance: { 25 | type: Number, 26 | require: true, 27 | }, 28 | insertDate: { 29 | type: Date, 30 | }, 31 | }, 32 | { 33 | timestamps: true, 34 | versionKey: false, 35 | } 36 | ); 37 | 38 | export default model("Dashboard", DashboardSchema); 39 | -------------------------------------------------------------------------------- /src/modules/payment/payment.validate.ts: -------------------------------------------------------------------------------- 1 | import Joi from "joi"; 2 | 3 | import * as validations from "@/utils/validations"; 4 | 5 | // import { EXAMPLE_ENUM } from "./payment.constant"; 6 | 7 | export const CreatePaymentSchema = Joi.object({ 8 | name: Joi.string(), 9 | // type: validations.byEnum(EXAMPLE_ENUM), 10 | }); 11 | 12 | export const UpdatePaymentSchema = Joi.object({ 13 | id: validations.byId, 14 | name: Joi.string(), 15 | // type: validations.byEnum(EXAMPLE_ENUM), 16 | }); 17 | 18 | export const withDraw = Joi.object({ 19 | amount: Joi.number().required(), 20 | currency: Joi.string().required(), 21 | address: Joi.string().required(), 22 | }); 23 | 24 | export const deposit = Joi.object({ 25 | amount: Joi.number().required(), 26 | currency: Joi.string().required(), 27 | address: Joi.string().required(), 28 | txHash: Joi.string().required(), 29 | }); 30 | -------------------------------------------------------------------------------- /src/cron/index.ts: -------------------------------------------------------------------------------- 1 | import { CustomerUpdate, TraitUpdate } from "./crons"; 2 | import { DashboardUpdate } from "./crons/dashboard-update"; 3 | 4 | // Cron Jobs 5 | /** 6 | * Cron expression: 7 | * 1. Minute (0 - 59) 8 | * 2. Hour (0 - 23) 9 | * 3. Day of the month (1 - 31) 10 | * 4. Month (1 - 12) 11 | * 5. Day of the week (0 - 6) (Sunday to Saturday, or use names) 12 | * 6. Year (optional) 13 | */ 14 | 15 | // User Subscription Daily Cron Job 16 | // Executes every day at 00:01 AM 17 | const customerUpdateCron = new CustomerUpdate("1 0 * * *", { 18 | scheduled: true, 19 | }); 20 | 21 | const revenueLogUpdateCron = new DashboardUpdate("*/5 * * * *", { 22 | scheduled: true, 23 | }); 24 | 25 | const traitUpdateCron = new TraitUpdate("*/10 * * * *", { 26 | scheduled: true, 27 | }); 28 | 29 | export const CronJobs = { 30 | customerUpdateCron, 31 | revenueLogUpdateCron, 32 | traitUpdateCron 33 | }; 34 | -------------------------------------------------------------------------------- /src/utils/swagger/errors.ts: -------------------------------------------------------------------------------- 1 | export const badRequest = generateErrorSchemaForSwagger(400, "Bad request"); 2 | export const unauthorized = generateErrorSchemaForSwagger(401, "Unauthorized"); 3 | export const forbidden = generateErrorSchemaForSwagger(403, "Forbidden"); 4 | export const notFound = generateErrorSchemaForSwagger(404, "Not found"); 5 | export const conflict = generateErrorSchemaForSwagger(409, "Conflict"); 6 | 7 | export default function generateErrorSchemaForSwagger( 8 | statusCode: number, 9 | description: string 10 | ) { 11 | return { 12 | [statusCode]: { 13 | description: description, 14 | content: { 15 | "application/json": { 16 | schema: { 17 | type: "object", 18 | properties: { 19 | error: { type: "string", example: description }, 20 | }, 21 | }, 22 | }, 23 | }, 24 | }, 25 | }; 26 | } 27 | -------------------------------------------------------------------------------- /src/constant/customer.ts: -------------------------------------------------------------------------------- 1 | export const CCustomerVipConfig = { 2 | minDepositForWithdraw: 5, // You must have deposited atleast this amount before withdrawing 3 | minWithdrawAmount: 5, // Minimum Withdraw Amount 4 | levelToChat: 2, // The level amount you need to chat 5 | levelToTip: 15, // The level to use the tip feature in chat 6 | levelToRain: 10, // The level amount to start a rain 7 | wagerToJoinRain: 5, // The wager amount to join the rain in chat 8 | minRakebackClaim: 2, // The min rakeback amount you need to claim rakeback 9 | numLevels: 500, // Number of total levels 10 | minWager: 0, // minWager 11 | maxWager: 502007, // maxWager 12 | rakeback: 21.66, // Max rakeback 13 | vipLevelNAME: ["Beginner", "Professional", "Expert", "Crown"], 14 | vipLevelCOLORS: [ 15 | "rgb(215, 117, 88)", 16 | "rgb(71, 190, 219)", 17 | "rgb(96, 183, 100)", 18 | "rgb(152, 38, 38)", 19 | ], 20 | }; 21 | -------------------------------------------------------------------------------- /src/modules/auth/auth.types.ts: -------------------------------------------------------------------------------- 1 | import { StdSignature } from "@keplr-wallet/types"; 2 | import { Document, ObjectId } from "mongoose"; 3 | 4 | import { PLATFORM } from "./auth.constant"; 5 | 6 | export interface IAuthInfo { 7 | deviceId?: string; 8 | platform?: PLATFORM; 9 | role: string; 10 | status: string; 11 | userId: ObjectId; 12 | } 13 | 14 | export interface IUpdateOrCreate { 15 | userId: ObjectId; 16 | } 17 | 18 | export interface IGenerateParams { 19 | deviceId?: string; 20 | platform?: PLATFORM; 21 | role: string; 22 | signAddress: string; 23 | status: string; 24 | userId: ObjectId; 25 | } 26 | 27 | export interface IAuthModel extends Document { 28 | userId: ObjectId; 29 | deviceId?: string; 30 | platform?: PLATFORM; 31 | refreshToken?: string; 32 | } 33 | 34 | export type TSignInPayload = { 35 | username: string; 36 | password: string; 37 | signAddress: string; 38 | signedSig: StdSignature; 39 | }; 40 | -------------------------------------------------------------------------------- /src/modules/site-transaction/site-transaction.router.ts: -------------------------------------------------------------------------------- 1 | import actionHandler from "@/middleware/action-handler"; 2 | import checkPermissions from "@/middleware/check-permissions"; 3 | import { ROLE } from "@/modules/user/user.constant"; 4 | import { BaseRouter } from "@/utils/base"; 5 | 6 | // import * as mapProperty from "@/utils/interfaces"; 7 | import { SiteTransactionController } from "."; 8 | 9 | export default class SiteTransactionRouter extends BaseRouter { 10 | private siteTransactionController: SiteTransactionController; 11 | 12 | constructor() { 13 | super(); 14 | 15 | this.siteTransactionController = new SiteTransactionController(); 16 | this.routes(); 17 | } 18 | 19 | public routes(): void { 20 | this.router.get( 21 | "/", 22 | checkPermissions({ roles: [ROLE.ADMIN] }), 23 | actionHandler(this.siteTransactionController.getAll) 24 | ); 25 | 26 | this.router.get("/:id", checkPermissions({ roles: [ROLE.ADMIN] })); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/middleware/error-handler.ts: -------------------------------------------------------------------------------- 1 | import { NextFunction, Request, Response } from "express"; 2 | 3 | import * as localizations from "@/utils/localizations"; 4 | import logger from "@/utils/logger"; 5 | 6 | export const routeNotFound = ( 7 | req: Request, 8 | res: Response, 9 | _next: NextFunction 10 | ) => { 11 | logger.error(`Route not found: ${req.method} ${req.originalUrl}`); 12 | return res 13 | .status(404) 14 | .json({ message: localizations["en"].ERRORS.OTHER.NOT_FOUND_ENDPOINT }); 15 | }; 16 | 17 | export const errorHandler = ( 18 | err: any, 19 | _req: Request, 20 | res: Response, 21 | _next: NextFunction 22 | ) => { 23 | const { message, status } = err; 24 | 25 | logger.error(err); 26 | 27 | switch (err.name) { 28 | case "UnauthorizedError": 29 | return res.status(401).json({ 30 | error: localizations["en"].ERRORS.OTHER.UNAUTHORIZED, 31 | }); 32 | 33 | default: 34 | return res.status(status || 500).json({ message }); 35 | } 36 | }; 37 | -------------------------------------------------------------------------------- /src/utils/helpers/file-helpers.ts: -------------------------------------------------------------------------------- 1 | import { promises as fsPromises } from "fs"; 2 | import multer from "multer"; 3 | import path from "path"; 4 | 5 | import { FILE_FOLDER } from "../../config"; 6 | 7 | //req path add 8 | const storage = multer.diskStorage({ 9 | destination: async function (req, _file, cb) { 10 | const dir = req.dir_for_files ? req.dir_for_files : ""; 11 | const pathUploads = path.join(__dirname, "../../../../", FILE_FOLDER, dir); 12 | await fsPromises.mkdir(pathUploads, { recursive: true }); 13 | cb(null, pathUploads); 14 | }, 15 | 16 | filename: function (_req, file, cb) { 17 | const file_name = file.originalname.split("."); 18 | const uniqueSuffix = Date.now() + "-" + Math.round(Math.random() * 1e9); 19 | cb( 20 | null, 21 | file.fieldname + 22 | "-" + 23 | uniqueSuffix + 24 | "." + 25 | file_name[file_name.length - 1] 26 | ); 27 | }, 28 | }); 29 | 30 | export const fileHelpers = multer({ storage: storage }); 31 | -------------------------------------------------------------------------------- /src/modules/logs/logs.router.ts: -------------------------------------------------------------------------------- 1 | import actionHandler from "@/middleware/action-handler"; 2 | import checkPermissions from "@/middleware/check-permissions"; 3 | import { ROLE } from "@/modules/user/user.constant"; 4 | import { BaseRouter } from "@/utils/base"; 5 | import * as mapProperty from "@/utils/interfaces"; 6 | 7 | import { LogsController } from "."; 8 | 9 | export default class LogsRouter extends BaseRouter { 10 | private logController: LogsController; 11 | 12 | constructor() { 13 | super(); 14 | 15 | this.logController = new LogsController(); 16 | this.routes(); 17 | } 18 | 19 | public routes(): void { 20 | this.router.get( 21 | "/", 22 | checkPermissions({ roles: [ROLE.ADMIN] }), 23 | actionHandler(this.logController.list, mapProperty.getQuery) 24 | ); 25 | 26 | this.router.get( 27 | "/:id", 28 | checkPermissions({ roles: [ROLE.ADMIN] }), 29 | actionHandler(this.logController.get, mapProperty.getIdFromParams) 30 | ); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/modules/payment/payment.types.ts: -------------------------------------------------------------------------------- 1 | import { StdSignature } from "@keplr-wallet/types"; 2 | 3 | export type TransactionDetails = { 4 | sender: string; 5 | receiver: string; 6 | amount: string; 7 | denom: string; 8 | }; 9 | 10 | 11 | export type TWithDrawProps = { 12 | amount: number; 13 | tokenType: ETokenType; 14 | address: string; 15 | }; 16 | 17 | export type TCheckDepositParam = { 18 | amount: number; 19 | tokenType: ETokenType; 20 | address: string; 21 | txHash: string; 22 | }; 23 | 24 | export type TUpdateBalance = { 25 | balanceType: ETokenType; 26 | amount: number; 27 | txHash?: string; 28 | address: string; 29 | }; 30 | 31 | export type TSocketDepositParam = { 32 | amount: number; 33 | currency: string; 34 | address: string; 35 | txHash: string; 36 | signedTx: StdSignature; 37 | }; 38 | 39 | export type TSocketWithDrawParam = { 40 | amount: number; 41 | currency: string; 42 | address: string; 43 | txHash: string; 44 | signedTx: StdSignature; 45 | }; 46 | -------------------------------------------------------------------------------- /src/modules/user-bot/user-bot.service.ts: -------------------------------------------------------------------------------- 1 | import bcrypt from "bcryptjs"; 2 | import { ObjectId } from "mongoose"; 3 | 4 | import BaseService from "@/utils/base/service"; 5 | import { UserBot } from "@/utils/db"; 6 | import { validateFunc } from "@/utils/validations"; 7 | 8 | import { IUserBotModel } from "./user-bot.interface"; 9 | import * as validateUser from "./user-bot.validate"; 10 | 11 | export default class UserBotService extends BaseService { 12 | constructor() { 13 | super(UserBot); 14 | } 15 | 16 | async create(user: IUserBotModel) { 17 | const error = validateFunc(validateUser.full, user); 18 | 19 | if (error) { 20 | throw new Error(error); 21 | } 22 | 23 | if (user.password) { 24 | user.password = await bcrypt.hash(user.password, 10); 25 | } 26 | 27 | return super.create(user); 28 | } 29 | 30 | async resetPassword(id: ObjectId, password: string) { 31 | password = await bcrypt.hash(password, 10); 32 | return this.updateById(id, { password }); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/modules/trait/trait.router.ts: -------------------------------------------------------------------------------- 1 | import actionHandler from "@/middleware/action-handler"; 2 | import checkPermissions from "@/middleware/check-permissions"; 3 | import validateSchema from "@/middleware/validate-schema"; 4 | import { BaseRouter } from "@/utils/base"; 5 | import * as mapProperty from "@/utils/interfaces"; 6 | 7 | import { ROLE } from "../user/user.constant"; 8 | import TraitController from "./trait.controller"; 9 | import * as validateTrait from "./trait.validate"; 10 | 11 | export default class TraitRouter extends BaseRouter { 12 | private traitController: TraitController; 13 | 14 | constructor() { 15 | super(); 16 | this.traitController = new TraitController(); 17 | this.routes(); 18 | } 19 | 20 | public routes(): void { 21 | this.router.post( 22 | "/", 23 | checkPermissions({ roles: [ROLE.ADMIN] }), 24 | validateSchema(validateTrait.createValidate, mapProperty.getBody), 25 | actionHandler(this.traitController.create, mapProperty.getBody) 26 | ); 27 | 28 | 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/modules/user-bot/user-bot.router.ts: -------------------------------------------------------------------------------- 1 | import actionHandler from "@/middleware/action-handler"; 2 | import checkPermissions from "@/middleware/check-permissions"; 3 | import validateSchema from "@/middleware/validate-schema"; 4 | import { BaseRouter } from "@/utils/base"; 5 | import * as mapProperty from "@/utils/interfaces"; 6 | import * as validations from "@/utils/validations"; 7 | 8 | import { ROLE } from "./user-bot.constant"; 9 | import UserBotController from "./user-bot.controller"; 10 | 11 | export default class UserBotRouter extends BaseRouter { 12 | private userBotController: UserBotController; 13 | 14 | constructor() { 15 | super(); 16 | 17 | this.userBotController = new UserBotController(); 18 | 19 | this.routes(); 20 | } 21 | 22 | public routes(): void { 23 | this.router.get( 24 | "/", 25 | checkPermissions({ roles: [ROLE.ADMIN] }), 26 | validateSchema(validations.getListValidation, mapProperty.getQuery), 27 | actionHandler(this.userBotController.getUsers, mapProperty.getQuery) 28 | ); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/middleware/attach-currentUser.ts: -------------------------------------------------------------------------------- 1 | import { NextFunction, Request, Response } from "express"; 2 | import { ObjectId } from "mongoose"; 3 | 4 | import { PLATFORM } from "@/modules/auth/auth.constant"; 5 | import { IAuthInfo } from "@/modules/auth/auth.types"; 6 | import { ROLE, STATUS } from "@/modules/user/user.constant"; 7 | import * as localizations from "@/utils/localizations"; 8 | 9 | export default async ( 10 | req: Request & { token: { [key: string]: unknown }; user: IAuthInfo }, 11 | res: Response, 12 | next: NextFunction 13 | ) => { 14 | if (req.token) { 15 | req.user = { 16 | userId: req.token.userId as ObjectId, 17 | role: req.token.role as ROLE, 18 | status: req.token.status as STATUS, 19 | deviceId: req.token.deviceId as string, 20 | platform: req.token.platform as PLATFORM, 21 | }; 22 | 23 | if (!req.user) { 24 | return res.status(401).json({ 25 | error: localizations["en"].ERRORS.OTHER.UNAUTHORIZED, 26 | }); 27 | } 28 | 29 | return next(); 30 | } else { 31 | return next(); 32 | } 33 | }; 34 | -------------------------------------------------------------------------------- /src/modules/logs/logs.controller.ts: -------------------------------------------------------------------------------- 1 | import fs from "fs"; 2 | import path from "path"; 3 | import { fileURLToPath } from "url"; 4 | 5 | import * as localizations from "@/utils/localizations"; 6 | import ILocalization from "@/utils/localizations/localizations.interface"; 7 | 8 | const filename = fileURLToPath(import.meta.url); // get the resolved path to the file 9 | const dirname = path.dirname(filename); 10 | 11 | export class LogsController { 12 | private localizations: ILocalization; 13 | private logsPath = path.join(dirname, "@/../logs"); 14 | 15 | constructor() { 16 | this.localizations = localizations["en"]; 17 | } 18 | 19 | public list = async () => { 20 | const files = fs 21 | .readdirSync(this.logsPath, { withFileTypes: true }) 22 | .filter((item) => !item.isDirectory()) 23 | .map((item) => item.name); 24 | 25 | return files; 26 | }; 27 | 28 | public get = async (name) => { 29 | const filePath = path.join(this.logsPath, name); 30 | const file = fs.readFileSync(filePath, { encoding: "utf8" }); 31 | 32 | return file; 33 | }; 34 | } 35 | -------------------------------------------------------------------------------- /src/modules/auth/auth.validate.ts: -------------------------------------------------------------------------------- 1 | import Joi from "joi"; 2 | 3 | import { PLATFORM } from "./auth.constant"; 4 | 5 | export const device = Joi.object({ 6 | deviceId: Joi.string().required().min(3), 7 | platform: Joi.string().valid(...Object.keys(PLATFORM)), 8 | }); 9 | 10 | export const signUp = Joi.object({ 11 | username: Joi.string().required().min(2), 12 | password: Joi.string().required(), 13 | signAddress: Joi.string().required(), 14 | }); 15 | 16 | export const walletSignIn = Joi.object({ 17 | wallet: Joi.string().required().min(12), 18 | }); 19 | 20 | export const signIn = Joi.object({ 21 | username: Joi.string().required().min(2), 22 | password: Joi.string().required(), 23 | signAddress: Joi.string().required(), 24 | signedSig: Joi.object().optional(), 25 | }); 26 | 27 | export const updateToken = Joi.object({ 28 | refreshToken: Joi.string(), 29 | }); 30 | 31 | export const resetPassword = Joi.object({ 32 | userId: Joi.string().required(), 33 | oldPassword: Joi.string().required(), 34 | newPassword: Joi.string().required(), 35 | confirmPassword: Joi.string().required(), 36 | }); 37 | -------------------------------------------------------------------------------- /src/root.socket.ts: -------------------------------------------------------------------------------- 1 | import { Server } from "socket.io"; 2 | 3 | import logger from "@/utils/logger"; 4 | 5 | import ChatHistorySocketListener from "./modules/chat-history/socket/chat-history.listener"; 6 | import CrashGameSocketListener from "./modules/crash-game/socket/crash-game.listener"; 7 | import LeaderboardSocketListener from "./modules/leaderboard/socket/leaderboard.listener"; 8 | import PaymentSocketListener from "./modules/payment/socket/payment.listener"; 9 | 10 | class SocketServer { 11 | private socketServer: Server; 12 | 13 | constructor(socketServer: Server) { 14 | this.socketServer = socketServer; 15 | this.start(); 16 | } 17 | 18 | private start() { 19 | try { 20 | new ChatHistorySocketListener(this.socketServer); 21 | new CrashGameSocketListener(this.socketServer); 22 | new LeaderboardSocketListener(this.socketServer); 23 | new PaymentSocketListener(this.socketServer); 24 | 25 | logger.info("Socket server started"); 26 | } catch (error) { 27 | logger.error(`Error starting socket server: ${error}`); 28 | } 29 | } 30 | } 31 | 32 | export default SocketServer; 33 | -------------------------------------------------------------------------------- /src/modules/user-bot/user-bot.interface.ts: -------------------------------------------------------------------------------- 1 | import mongoose, { Document } from "mongoose"; 2 | 3 | export interface IUserBotModel extends Document { 4 | provider: string; 5 | providerId: string; 6 | username: string; 7 | password: string; 8 | avatar: string; 9 | rank: number; 10 | wallet: number; 11 | wager: number; 12 | crypto: any; // Specify more detailed type if possible 13 | hasVerifiedAccount: boolean; 14 | verifiedPhoneNumber: string | null; 15 | accountVerified: Date | null; 16 | banExpires: string; 17 | selfExcludes: { 18 | crash: number; 19 | coinflip: number; 20 | }; 21 | muteExpires: string; 22 | transactionsLocked: boolean; 23 | betsLocked: boolean; 24 | _affiliatedBy: mongoose.Schema.Types.ObjectId | null; 25 | affiliateClaimed: Date | null; 26 | affiliateCode: string | null; 27 | affiliateMoney: number; 28 | affiliateMoneyCollected: number; 29 | forgotToken: string | null; 30 | forgotExpires: number; 31 | rakebackBalance: number; 32 | wagerNeededForWithdraw: number; 33 | totalDeposited: number; 34 | totalWithdrawn: number; 35 | customWagerLimit: number; 36 | avatarLastUpdate: number; 37 | } 38 | -------------------------------------------------------------------------------- /src/utils/swagger/swagger.setup.ts: -------------------------------------------------------------------------------- 1 | import { URL } from "url"; 2 | 3 | import { BASE_URL, PORT } from "@/config"; 4 | import { 5 | authSchema, 6 | authSwagger, 7 | deviceParameters, 8 | } from "@/modules/auth/swagger"; 9 | import { userSchema, userSwagger } from "@/modules/user/swagger"; 10 | 11 | export default { 12 | openapi: "3.0.0", 13 | info: { 14 | version: "1.0.0", 15 | title: "APIs Document for YieldLab method", 16 | }, 17 | servers: [ 18 | { 19 | url: new URL("/api/v1", BASE_URL), 20 | }, 21 | { 22 | url: `http://127.0.0.1:${PORT}/api/v1`, 23 | }, 24 | { 25 | url: "https://coral-app-2-tnvsk.ondigitalocean.app/api/v1", 26 | }, 27 | {}, 28 | ], 29 | components: { 30 | parameters: deviceParameters, 31 | schemas: Object.assign({}, userSchema, authSchema, {}), 32 | securitySchemes: { 33 | bearerAuth: { 34 | type: "http", 35 | scheme: "bearer", 36 | bearerFormat: "JWT", 37 | }, 38 | }, 39 | }, 40 | security: [ 41 | { 42 | bearerAuth: ["read", "write"], 43 | }, 44 | ], 45 | paths: Object.assign({}, userSwagger, authSwagger), 46 | }; 47 | -------------------------------------------------------------------------------- /src/modules/auto-crash-bet/auto-crash-bet.model.ts: -------------------------------------------------------------------------------- 1 | // Require Dependencies 2 | import mongoose, { model, SchemaTypes } from "mongoose"; 3 | 4 | import { IAutoCrashBetModel } from "./auto-crash-bet.interface"; 5 | 6 | // Setup autobet CrashGame Schema 7 | const AutoCrashBetSchema = new mongoose.Schema( 8 | { 9 | // Basic fields 10 | user: { 11 | type: SchemaTypes.ObjectId, 12 | ref: "User", 13 | }, 14 | 15 | // Game Betting Amount 16 | betAmount: { 17 | type: Number, 18 | default: 0, 19 | }, 20 | 21 | 22 | 23 | // Game auto cashout point 24 | cashoutPoint: { 25 | type: Number, 26 | default: 0, 27 | }, 28 | 29 | // Game auto Betting status false = not started, true = started 30 | status: { 31 | type: Boolean, 32 | default: false, 33 | }, 34 | 35 | // remaining Betting number 36 | count: { 37 | type: Number, 38 | default: 0, 39 | }, 40 | }, 41 | { 42 | minimize: false, 43 | timestamps: true, 44 | } 45 | ); 46 | 47 | export default model( 48 | "AutoCrashBet", 49 | AutoCrashBetSchema, 50 | "autoCrashBet" 51 | ); 52 | -------------------------------------------------------------------------------- /src/modules/dashboard/dashboard.router.ts: -------------------------------------------------------------------------------- 1 | import actionHandler from "@/middleware/action-handler"; 2 | import checkPermissions from "@/middleware/check-permissions"; 3 | import { ROLE } from "@/modules/user/user.constant"; 4 | import { BaseRouter } from "@/utils/base"; 5 | import * as mapProperty from "@/utils/interfaces"; 6 | 7 | import { DashboardController } from "."; 8 | 9 | export default class DashboardRouter extends BaseRouter { 10 | private dashboardController: DashboardController; 11 | 12 | constructor() { 13 | super(); 14 | 15 | this.dashboardController = new DashboardController(); 16 | this.routes(); 17 | } 18 | 19 | public routes(): void { 20 | this.router.get( 21 | "/", 22 | checkPermissions({ roles: [ROLE.ADMIN] }), 23 | actionHandler(this.dashboardController.getAll) 24 | ); 25 | 26 | this.router.post( 27 | "/dashboard-history", 28 | checkPermissions(), 29 | actionHandler(this.dashboardController.getDashboard, mapProperty.getQuery) 30 | ); 31 | 32 | this.router.get( 33 | "/top-players", 34 | checkPermissions(), 35 | actionHandler(this.dashboardController.getTopPlayers) 36 | ); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/utils/crypto/get-seed.ts: -------------------------------------------------------------------------------- 1 | // Require Dependencies 2 | import { JsonRpc } from "eosjs"; 3 | import got from "got"; 4 | import fetch from "node-fetch"; // node only; not needed in browsers 5 | 6 | import { BLOCKCHAIN_HTTPPROVIDER_API } from "@/config"; 7 | 8 | import logger from "../logger"; 9 | 10 | const rpc = new JsonRpc(BLOCKCHAIN_HTTPPROVIDER_API, { fetch }); 11 | const gpc = got.post; 12 | 13 | // Grab EOS block with id 14 | const getPublicSeed = async (): Promise => { 15 | try { 16 | const info = await rpc.get_info(); 17 | const blockNumber = info.last_irreversible_block_num + 1; 18 | const block = await rpc.get_block(blockNumber || 1); 19 | return block.id; 20 | } catch (error) { 21 | logger.error("[SEED]::: Error get public seed" + error); 22 | } 23 | }; 24 | 25 | const getCrypto = async (crypto: string, mod: any): Promise => { 26 | try { 27 | const res = await gpc(crypto, { 28 | json: { mod: typeof mod === "number" ? mod : Array.from(mod) }, 29 | } as any); 30 | return res; 31 | } catch (error) { 32 | logger.error("[SEED]::: Error get crypto" + error); 33 | } 34 | }; 35 | 36 | // Export functions 37 | export { getCrypto, getPublicSeed }; 38 | -------------------------------------------------------------------------------- /src/utils/interfaces/request.interface.ts: -------------------------------------------------------------------------------- 1 | import { Request } from "express"; 2 | 3 | import { PLATFORM } from "@/modules/auth/auth.constant"; 4 | import { IAuthInfo } from "@/modules/auth/auth.types"; 5 | 6 | import detectMobFromUserAgent from "../helpers/detect-mobFromUserAgent"; 7 | 8 | export const deviceInfo = ({ 9 | headers: { deviceid, platform }, 10 | }): { deviceId: string; platform: PLATFORM } => ({ 11 | deviceId: deviceid, 12 | platform, 13 | }); 14 | 15 | export const getIdFromParams = (req: Request): string => req.params.id; 16 | export const getQuery = ({ query }: Request) => query; 17 | 18 | export const isMobile = (req: Request) => { 19 | return detectMobFromUserAgent(req.get("User-Agent")); 20 | }; 21 | 22 | export const getBody = (req: Request) => { 23 | return req.body; 24 | }; 25 | 26 | interface RequestWithuser extends Request { 27 | user: IAuthInfo; 28 | } 29 | 30 | export const getUserInfo = ({ user }: RequestWithuser): IAuthInfo => 31 | user as IAuthInfo; 32 | 33 | export const getNameFromParam = (req: Request) => { 34 | return req.params.name; 35 | }; 36 | 37 | export const getUTCFromHeader = (req: Request): number => 38 | req.headers["x-timezone"] ? Number(req.headers["x-timezone"]) : 0; 39 | -------------------------------------------------------------------------------- /src/modules/trait/trait.service.ts: -------------------------------------------------------------------------------- 1 | import BaseService from "@/utils/base/service"; 2 | import { Trait } from "@/utils/db"; 3 | 4 | import { ITraitModel } from "./trait.types"; 5 | 6 | export default class TraitService extends BaseService { 7 | constructor() { 8 | super(Trait); 9 | } 10 | 11 | public getKartCurrencyWithApi = async () => { 12 | try { 13 | const currentTimestamp = new Date(); 14 | const twoHoursAgoTimestamp = new Date( 15 | currentTimestamp.getTime() - 2 * 60 * 60 * 1000 16 | ); 17 | 18 | const fetchKartCurrency = await fetch( 19 | `https://api.kujira.app/api/trades/candles?contract=kujira19n770r9q5haax7mfgy8acrgz7gsamgyjhcvqvxfgrq25983lc42qtszngq&from=${twoHoursAgoTimestamp.toISOString()}&to=${currentTimestamp.toISOString()}&precision=120` 20 | ); 21 | const kartCurrencyData = await fetchKartCurrency.json(); 22 | const candles = kartCurrencyData.candles; 23 | 24 | if (candles && candles.length > 0) { 25 | const latestCurrency = candles[candles.length - 1].close; 26 | return Number(latestCurrency).toFixed(3); 27 | } 28 | } catch (error) { 29 | console.log("error", error); 30 | } 31 | 32 | return "0.02"; 33 | }; 34 | } 35 | -------------------------------------------------------------------------------- /src/modules/crash-game/crash-game.model.ts: -------------------------------------------------------------------------------- 1 | // Require Dependencies 2 | import mongoose, { model } from "mongoose"; 3 | 4 | import { ICrashGameModel } from "./crash-game.interface"; 5 | 6 | // Setup CrashGame Schema 7 | const CrashGameSchema = new mongoose.Schema( 8 | { 9 | // Basic fields 10 | crashPoint: Number, 11 | players: Object, 12 | refundedPlayers: Array, 13 | 14 | // Provably Fair fields 15 | privateSeed: String, 16 | privateHash: String, 17 | publicSeed: { 18 | type: String, 19 | default: null, 20 | }, 21 | 22 | // Game status 23 | status: { 24 | type: Number, 25 | default: 1, 26 | /** 27 | * Status list: 28 | * 29 | * 1 = Not Started 30 | * 2 = Starting 31 | * 3 = In Progress 32 | * 4 = Over 33 | * 5 = Blocking 34 | * 6 = Refunded 35 | */ 36 | }, 37 | 38 | // When game was started 39 | startedAt: { 40 | type: Date, 41 | }, 42 | // When game was started 43 | userCounts: { 44 | type: Number, 45 | default: 0, 46 | }, 47 | }, 48 | { 49 | minimize: false, 50 | timestamps: true, 51 | } 52 | ); 53 | 54 | export default model("CrashGame", CrashGameSchema); 55 | -------------------------------------------------------------------------------- /src/utils/logger.ts: -------------------------------------------------------------------------------- 1 | import * as winston from "winston"; 2 | import DailyRotateFile from "winston-daily-rotate-file"; 3 | 4 | import config from "@/config"; 5 | 6 | const { combine, colorize, timestamp, align, printf, errors, splat, json } = 7 | winston.format; 8 | 9 | const logger = winston.createLogger({ 10 | level: "debug", 11 | format: combine( 12 | timestamp({ 13 | format: "YYYY-MM-DD HH:mm:ss", 14 | }), 15 | errors({ stack: true }), 16 | splat(), 17 | json() 18 | ), 19 | transports: [ 20 | new DailyRotateFile({ 21 | filename: "combined-%DATE%.log", 22 | dirname: "logs", 23 | maxSize: "20m", 24 | maxFiles: "14d", 25 | }), 26 | ], 27 | }); 28 | 29 | // 30 | // If we're not in production then log to the `console` with the format: 31 | // `${info.level}: ${info.message} JSON.stringify({ ...rest }) ` 32 | // 33 | if (!config.IS_PRODUCTION) { 34 | logger.add( 35 | new winston.transports.Console({ 36 | format: combine( 37 | colorize({ all: true }), 38 | timestamp({ 39 | format: "YYYY-MM-DD HH:mm:ss", 40 | }), 41 | align(), 42 | printf((info) => `[${info.timestamp}] ${info.level}: ${info.message}`) 43 | ), 44 | }) 45 | ); 46 | } 47 | 48 | export default logger; 49 | -------------------------------------------------------------------------------- /src/modules/auth/swagger/auth.schema.ts: -------------------------------------------------------------------------------- 1 | import * as AuthConstant from "@/modules/auth/auth.constant"; 2 | import { shortUserSchemaWithoutPassword } from "@/modules/user/swagger/user.schema"; 3 | 4 | const deviceId = { 5 | name: "deviceId", 6 | in: "header", 7 | default: "123", 8 | required: true, 9 | }; 10 | 11 | const platform = { 12 | name: "platform", 13 | in: "header", 14 | schema: { 15 | type: "string", 16 | enum: Object.keys(AuthConstant.PLATFORM), 17 | }, 18 | required: true, 19 | default: AuthConstant.PLATFORM.WEB, 20 | }; 21 | 22 | const authResponse = { 23 | type: "object", 24 | properties: { 25 | accessToken: { type: "string", required: true }, 26 | refreshToken: { type: "string", required: true }, 27 | expiresIn: { type: "number" }, 28 | }, 29 | }; 30 | 31 | const signUpResponse = { 32 | type: "object", 33 | properties: { 34 | auth: authResponse, 35 | user: shortUserSchemaWithoutPassword, 36 | }, 37 | }; 38 | 39 | const signInResponse = { 40 | type: "object", 41 | properties: { 42 | auth: authResponse, 43 | user: shortUserSchemaWithoutPassword, 44 | }, 45 | }; 46 | 47 | export const authSchema = { 48 | authResponse, 49 | signUpResponse, 50 | signInResponse, 51 | }; 52 | 53 | export const deviceParameters = { 54 | deviceId, 55 | platform, 56 | }; 57 | -------------------------------------------------------------------------------- /src/cron/crons/base.cron.ts: -------------------------------------------------------------------------------- 1 | import { ScheduledTask, ScheduleOptions } from "node-cron"; 2 | 3 | import * as localizations from "@/utils/localizations"; 4 | import ILocalization from "@/utils/localizations/localizations.interface"; 5 | 6 | export class BaseCron { 7 | protected localizations: ILocalization; 8 | protected task: ScheduledTask; // Cron need types 9 | protected readonly option: ScheduleOptions; // Cron need types 10 | protected cronExpression: string; // Cron need types 11 | 12 | constructor(cronExpression: string, option: ScheduleOptions) { 13 | this.localizations = localizations["en"]; 14 | this.option = option; 15 | this.cronExpression = cronExpression; 16 | } 17 | 18 | public startCron() { 19 | this.task.start(); 20 | } 21 | 22 | public stopCron() { 23 | this.task.stop(); 24 | } 25 | 26 | protected async catchWrapper(func, nameFunc = "") { 27 | const red = "\x1b[31m%s\x1b[0m"; 28 | const green = "\x1b[32m%s\x1b[0m"; 29 | 30 | try { 31 | await func(); 32 | console.log(green, "Done Schedule func: " + nameFunc); 33 | } catch (error) { 34 | /* 35 | * Save error info to mongoDB in cronLog collection 36 | * time: 37 | * name: 38 | * message: error.message 39 | * */ 40 | console.log(red, error.message); 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/modules/crash-game/crash-game.constant.ts: -------------------------------------------------------------------------------- 1 | export const CCrashConfig = { 2 | minBetAmount: 0.1, // Min bet amount (in coins) 3 | maxBetAmount: 100, // Max bet amount (in coins) 4 | maxProfit: 1000, // Max profit on crash, forces auto cashout 5 | houseEdge: 0.05, // House edge percentage 6 | }; 7 | 8 | export enum ECrashGameEvents { 9 | auth = "auth", 10 | getHistory = "previous-crashgame-history", 11 | autoCrashBet = "auto-crashgame-bet", 12 | cancelAutoBet = "cancel-auto-bet", 13 | joinGame = "join-crash-game", 14 | betCashout = "bet-cashout", 15 | disconnect = "disconnect", 16 | } 17 | 18 | export const CGAME_STATES = { 19 | NotStarted: 1, 20 | Starting: 2, 21 | InProgress: 3, 22 | Over: 4, 23 | Blocking: 5, 24 | Refunded: 6, 25 | }; 26 | 27 | export const CBET_STATES = { 28 | Playing: 1, 29 | CashedOut: 2, 30 | }; 31 | 32 | export const CTime = { 33 | tick_rate: 150, 34 | start_wait_time: 4000, 35 | restart_wait_time: 9000, 36 | }; 37 | 38 | export const CBotBetAmountLimit = { 39 | usk: { 40 | min: 1, 41 | max: 20, 42 | }, 43 | kart: { 44 | min: 1, 45 | max: 1500, 46 | }, 47 | }; 48 | 49 | export const CLimitCrashPoint = { 50 | amount: { 51 | min: 3, 52 | max: 10, 53 | }, 54 | stopPoint: { 55 | min: 300, 56 | max: 1000, 57 | }, 58 | kartRate: 0.02, 59 | }; 60 | -------------------------------------------------------------------------------- /src/middleware/is-auth.ts: -------------------------------------------------------------------------------- 1 | import { Request } from "express"; 2 | import expressJwt from "express-jwt"; 3 | 4 | import { TOKEN_SECRET } from "@/config"; 5 | 6 | const publicPaths = [ 7 | "/favicon.ico", 8 | { url: /\/api\/v1\/docs.*/ }, 9 | { url: /\/api\/v[0-9]{1}\/version/ }, 10 | { url: /\/api\/v[0-9]{1}\/status/ }, 11 | { url: "/api/v1/auth/sign-up" }, 12 | { url: "/api/v1/auth/sign-in" }, 13 | { url: "/api/v1/auth/wallet" }, 14 | { url: "/api/v1/auth/update-token" }, 15 | { url: "/api/v1/support" }, 16 | { url: /\/api\/v1\/project-user\/invite\/.*/ }, 17 | { url: /\/api\/v1\/project\/name\/.*/ }, 18 | { url: /\/api\/v1\/project\/id\/.*/ }, 19 | { url: /\/api\/v1\/support\/.*/ }, 20 | { url: "/api/v1/staking/create" }, 21 | { url: "/api/v1/staking/history" }, 22 | { url: "/api/v1/staking/user" }, 23 | { url: /\/api\/v1\/trait\/kart\/.*/ }, 24 | ]; 25 | 26 | export default expressJwt({ 27 | secret: TOKEN_SECRET, 28 | algorithms: ["HS256"], 29 | userProperty: "token", 30 | getToken: (req: Request) => { 31 | if ( 32 | req.headers.authorization && 33 | req.headers.authorization.split(" ")[0] === "Bearer" 34 | ) { 35 | return req.headers.authorization.split(" ")[1]; 36 | } else if (req.query && req.query.token) { 37 | return req.query.token; 38 | } 39 | }, 40 | }).unless({ 41 | path: publicPaths, 42 | }); 43 | -------------------------------------------------------------------------------- /src/modules/auth/auth.service.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ACCESS_TOKEN_LIFE, 3 | REFRESH_TOKEN_LIFE, 4 | REFRESH_TOKEN_SECRET, 5 | TOKEN_SECRET, 6 | } from "@/config"; 7 | import BaseService from "@/utils/base/service"; 8 | import { Auth } from "@/utils/db"; 9 | import createToken from "@/utils/jwt/create-token"; 10 | 11 | import { IAuthModel, IGenerateParams, IUpdateOrCreate } from "./auth.types"; 12 | 13 | export default class AuthService extends BaseService { 14 | constructor() { 15 | super(Auth); 16 | } 17 | 18 | updateOrCreate = async (params: IUpdateOrCreate, refreshToken: string) => { 19 | const isAuthExist = await this.exists(params); 20 | let doc: IAuthModel; 21 | 22 | if (!isAuthExist) { 23 | doc = await this.create({ ...params, refreshToken }); 24 | } else { 25 | doc = await this.update(params, { refreshToken }); 26 | } 27 | 28 | return doc; 29 | }; 30 | 31 | generate(params: IGenerateParams, expiresIn?: number) { 32 | const accessExpiresIn = expiresIn || ACCESS_TOKEN_LIFE; 33 | const refreshExpiresIn = expiresIn || REFRESH_TOKEN_LIFE; 34 | const access = createToken(params, TOKEN_SECRET, accessExpiresIn); 35 | const refresh = createToken(params, REFRESH_TOKEN_SECRET, refreshExpiresIn); 36 | 37 | return { 38 | accessToken: access.token, 39 | refreshToken: refresh.token, 40 | expiresIn: access.expiresIn, 41 | }; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Multi-Chain-Casino 2 | Completed Casino(Crash, Coinflip, Mines, BlackJack, etc) supported multi-chain coins(eth, sol, base, bitcoin, inj) 3 | 4 | # User-friendly Casino Games 5 | Crash, Coinflip, Mines and BlackJack are fully completed with positive supports and feedbacks from users. 6 | 7 | # Powerful Random Number Genererator by using Chain Link VRF 8 | By using chain link VRF, game is getting well-randomized variable for user trust. 9 | 10 | # Strong Security 11 | By using strong encryption ways, all backend calling and data management processes are multi-protected. 12 | 3 times withdraw and deposit checking process protect user's funds. 13 | 14 | # Real Time Tracking Dashboard 15 | For comfortable game management for the team, it has real-time tracking dashboard for every games.. 16 | 17 |

18 | Twitter 20 | Telegram 22 | Discord 24 |

25 | 26 | I am ready to build wonderful casino on new space with fresh idea. 27 | -------------------------------------------------------------------------------- /src/modules/user-bot/user-bot.validate.ts: -------------------------------------------------------------------------------- 1 | import Joi from "joi"; 2 | 3 | import { validateId } from "@/utils/validations"; 4 | 5 | import { ROLE, STATUS } from "./user-bot.constant"; 6 | 7 | export const Id = Joi.custom((value, helper) => validateId(value, helper)); 8 | 9 | export const main = Joi.object({ 10 | avatar: Joi.string().allow("").optional(), 11 | name: Joi.string().allow("").optional(), 12 | password: Joi.string().allow("").optional(), 13 | phoneNumber: Joi.string().allow("").optional(), 14 | wallet: Joi.array().items(Joi.string()).optional(), 15 | socialLinks: Joi.array().optional(), 16 | role: Joi.string().allow("").optional(), 17 | status: Joi.string().allow("").optional(), 18 | }); 19 | 20 | export const addition = Joi.object({ 21 | avatar: Joi.string().allow("").optional(), 22 | name: Joi.string().allow("").optional(), 23 | password: Joi.string().allow("").optional(), 24 | phoneNumber: Joi.string().allow("").optional(), 25 | wallet: Joi.array().items(Joi.string()).optional(), 26 | socialLinks: Joi.array().optional(), 27 | role: Joi.string().allow("").optional(), 28 | stripeCard: Joi.object().optional(), 29 | }); 30 | 31 | export const additionAdmin = Joi.object({ 32 | avatar: Joi.string(), 33 | name: Joi.string(), 34 | status: Joi.string().valid(...Object.keys(STATUS)), 35 | role: Joi.string().valid(...Object.keys(ROLE)), 36 | wallet: Joi.array().items(Joi.string()), 37 | }); 38 | 39 | export const full = Joi.object().concat(main).concat(addition); 40 | -------------------------------------------------------------------------------- /src/cron/crons/trait-update/trait-update.ts: -------------------------------------------------------------------------------- 1 | import Cron, { ScheduleOptions } from "node-cron"; 2 | 3 | import { BaseCron } from "@/cron/crons/base.cron"; 4 | import { CKartTraits } from "@/modules/trait/trait.constant"; 5 | import TraitService from "@/modules/trait/trait.service"; 6 | import logger from "@/utils/logger"; 7 | 8 | export class TraitUpdate extends BaseCron { 9 | private traitService: TraitService; 10 | 11 | constructor(cronExpression: string, option = {}) { 12 | super(cronExpression, option); 13 | 14 | this.traitService = new TraitService(); 15 | } 16 | 17 | public start = () => { 18 | this.initCron(); 19 | }; 20 | 21 | private initCron = () => { 22 | this.task = Cron.schedule( 23 | this.cronExpression, 24 | async () => { 25 | await this.catchWrapper(this.updateTraitStatus, "updateTraitStatus"); 26 | }, 27 | this.option 28 | ); 29 | }; 30 | 31 | private updateTraitStatus = async () => { 32 | try { 33 | const kartCurrency = this.traitService.getKartCurrencyWithApi(); 34 | await this.traitService.updateOrInsert( 35 | { 36 | key: CKartTraits.getCurrency.key, 37 | name: CKartTraits.getCurrency.name, 38 | }, 39 | { 40 | value: kartCurrency, 41 | key: CKartTraits.getCurrency.key, 42 | name: CKartTraits.getCurrency.name, 43 | } 44 | ); 45 | } catch (error) { 46 | logger.error(error); 47 | } 48 | }; 49 | } 50 | -------------------------------------------------------------------------------- /src/modules/crash-game/crash-game.interface.ts: -------------------------------------------------------------------------------- 1 | import { Document } from "mongoose"; 2 | 3 | export interface ICrashGameModel extends Document { 4 | crashPoint?: number; // Optional as per schema 5 | players: Record; // Required as per schema 6 | refundedPlayers?: any[]; // Optional as per schema 7 | privateSeed?: string; // Optional as per schema 8 | privateHash?: string; // Optional as per schema 9 | publicSeed?: string; // Optional as per schema, with default null 10 | status: number; // Required as per schema 11 | userCounts: number; // Required as per schema 12 | startedAt?: Date; // Optional as per schema 13 | } 14 | 15 | export type TAutoCrashBetPayload = { 16 | betAmount: number; 17 | cashoutPoint: number; 18 | count: number; 19 | denom: string; 20 | }; 21 | export interface VIPLevelType { 22 | name: string; 23 | wagerNeeded?: number; 24 | rakebackPercentage?: number; 25 | levelName?: string; 26 | levelColor?: string; 27 | } 28 | 29 | export interface BetType { 30 | playerID: string; 31 | username: string; 32 | avatar?: string; 33 | betAmount: number; 34 | denom: string; 35 | status: number; 36 | level: VIPLevelType; 37 | stoppedAt?: number; 38 | autoCashOut: number; 39 | winningAmount?: number; 40 | forcedCashout?: boolean; 41 | } 42 | 43 | export interface IUpdateParams { 44 | $set: { 45 | [key: string]: BetType; 46 | }; 47 | } 48 | 49 | export type TJoinGamePayload = { 50 | target: number; 51 | betAmount: number; 52 | denom: string; 53 | }; 54 | -------------------------------------------------------------------------------- /src/modules/user/user.validate.ts: -------------------------------------------------------------------------------- 1 | import Joi from "joi"; 2 | 3 | import { validateId } from "@/utils/validations"; 4 | 5 | import { ROLE, STATUS } from "./user.constant"; 6 | 7 | export const Id = Joi.custom((value, helper) => validateId(value, helper)); 8 | 9 | export const main = Joi.object({ 10 | avatar: Joi.string().allow("").optional(), 11 | name: Joi.string().allow("").optional(), 12 | password: Joi.string().allow("").optional(), 13 | phoneNumber: Joi.string().allow("").optional(), 14 | wallet: Joi.array().items(Joi.string()).optional(), 15 | socialLinks: Joi.array().optional(), 16 | role: Joi.string().allow("").optional(), 17 | status: Joi.string().allow("").optional(), 18 | stripeCard: Joi.object().optional(), 19 | }); 20 | 21 | export const addition = Joi.object({ 22 | avatar: Joi.string().allow("").optional(), 23 | name: Joi.string().allow("").optional(), 24 | password: Joi.string().allow("").optional(), 25 | phoneNumber: Joi.string().allow("").optional(), 26 | wallet: Joi.array().items(Joi.string()).optional(), 27 | socialLinks: Joi.array().optional(), 28 | role: Joi.string().allow("").optional(), 29 | stripeCard: Joi.object().optional(), 30 | }); 31 | 32 | export const additionAdmin = Joi.object({ 33 | avatar: Joi.string(), 34 | name: Joi.string(), 35 | status: Joi.string().valid(...Object.keys(STATUS)), 36 | role: Joi.string().valid(...Object.keys(ROLE)), 37 | wallet: Joi.array().items(Joi.string()), 38 | }); 39 | 40 | export const full = Joi.object().concat(main).concat(addition); 41 | -------------------------------------------------------------------------------- /src/utils/db.ts: -------------------------------------------------------------------------------- 1 | import mongoose from "mongoose"; 2 | 3 | import { IS_PRODUCTION, MONGO_URL } from "@/config"; 4 | 5 | const optionConnect: object = { 6 | maxPoolSize: !IS_PRODUCTION ? 10 : 50, 7 | }; 8 | 9 | mongoose.connect(MONGO_URL, optionConnect); 10 | 11 | mongoose.Promise = global.Promise; 12 | 13 | mongoose.connection.on("connected", function () { 14 | console.info("🚀 MongoDB Database connection established successfully"); 15 | }); 16 | 17 | mongoose.connection.on("error", function (err) { 18 | console.error("connection to mongo failed " + err); 19 | }); 20 | 21 | mongoose.connection.on("disconnected", function () { 22 | console.info("mongo db connection closed"); 23 | mongoose.connect(MONGO_URL, optionConnect); 24 | }); 25 | 26 | export { default as Auth } from "@/modules/auth/auth.model"; 27 | export { default as AutoCrashBet } from "@/modules/auto-crash-bet/auto-crash-bet.model"; 28 | export { default as ChatHistory } from "@/modules/chat-history/chat-history.model"; 29 | export { default as CrashGame } from "@/modules/crash-game/crash-game.model"; 30 | export { default as Dashboard } from "@/modules/dashboard/dashboard.model"; 31 | export { default as Payment } from "@/modules/payment/payment.model"; 32 | export { default as SiteTransaction } from "@/modules/site-transaction/site-transaction.model"; 33 | export { default as Staking } from "@/modules/staking/staking.model"; 34 | export { default as Trait } from "@/modules/trait/trait.model"; 35 | export { default as User } from "@/modules/user/user.model"; 36 | export { default as UserBot } from "@/modules/user-bot/user-bot.model"; 37 | -------------------------------------------------------------------------------- /src/modules/staking/staking.router.ts: -------------------------------------------------------------------------------- 1 | import actionHandler from "@/middleware/action-handler"; 2 | import checkPermissions from "@/middleware/check-permissions"; 3 | import validateSchema from "@/middleware/validate-schema"; 4 | import { BaseRouter } from "@/utils/base"; 5 | import * as mapProperty from "@/utils/interfaces"; 6 | import * as validations from "@/utils/validations"; 7 | 8 | import { ROLE } from "../user/user.constant"; 9 | import StakingController from "./staking.controller"; 10 | import * as validateStaking from "./staking.validate"; 11 | 12 | export default class StakingRouter extends BaseRouter { 13 | private stakingController: StakingController; 14 | 15 | constructor() { 16 | super(); 17 | this.stakingController = new StakingController(); 18 | this.routes(); 19 | } 20 | 21 | public routes(): void { 22 | this.router.get( 23 | "/", 24 | checkPermissions({ roles: [ROLE.ADMIN] }), 25 | validateSchema(validations.getListValidation, mapProperty.getQuery), 26 | actionHandler(this.stakingController.getAll, mapProperty.getQuery) 27 | ); 28 | 29 | this.router.post( 30 | "/create", 31 | validateSchema(validateStaking.createValidate, mapProperty.getBody), 32 | actionHandler(this.stakingController.create, mapProperty.getBody) 33 | ); 34 | 35 | this.router.get( 36 | "/history", 37 | actionHandler(this.stakingController.stakeHistory, mapProperty.getQuery) 38 | ); 39 | 40 | this.router.get( 41 | "/user", 42 | actionHandler(this.stakingController.getUserStaking, mapProperty.getQuery) 43 | ); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/modules/payment/payment.router.ts: -------------------------------------------------------------------------------- 1 | import actionHandler from "@/middleware/action-handler"; 2 | import checkPermissions from "@/middleware/check-permissions"; 3 | import { ROLE } from "@/modules/user/user.constant"; 4 | import { BaseRouter } from "@/utils/base"; 5 | 6 | import { PaymentController } from "."; 7 | 8 | export default class PaymentRouter extends BaseRouter { 9 | private paymentController: PaymentController; 10 | 11 | constructor() { 12 | super(); 13 | this.paymentController = new PaymentController(); 14 | this.routes(); 15 | } 16 | 17 | public routes(): void { 18 | this.router.get( 19 | "/", 20 | checkPermissions({ roles: [ROLE.ADMIN] }), 21 | actionHandler(this.paymentController.getAll) 22 | ); 23 | 24 | // this.router.post( 25 | // "/withdraw", 26 | // checkPermissions(), 27 | // validateSchema(validatePayment.withDraw, mapProperty.getBody), 28 | // actionHandler(this.paymentController.userBalanceWithdraw, [ 29 | // mapProperty.getBody, 30 | // mapProperty.getUserInfo, 31 | // ]) 32 | // ); 33 | 34 | // this.router.get( 35 | // "/admin-wallet", 36 | // actionHandler(this.paymentController.getAddress, mapProperty.getUserInfo) 37 | // ); 38 | 39 | // this.router.post( 40 | // "/deposit", 41 | // checkPermissions(), 42 | // validateSchema(validatePayment.deposit, mapProperty.getBody), 43 | // actionHandler(this.paymentController.userBalanceDeposit, [ 44 | // mapProperty.getBody, 45 | // mapProperty.getUserInfo, 46 | // ]) 47 | // ); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/middleware/action-handler.ts: -------------------------------------------------------------------------------- 1 | import logger from "@/utils/logger"; 2 | 3 | export default function actionHandler(action, mapProperty?, accumulator?) { 4 | return async (req, res) => { 5 | let property = []; 6 | 7 | if (mapProperty) { 8 | if (Array.isArray(mapProperty)) { 9 | property = mapProperty.map((propertyAction) => propertyAction(req)); 10 | } else { 11 | property = [mapProperty(req)]; 12 | } 13 | } 14 | 15 | if (accumulator) { 16 | if (Array.isArray(accumulator)) { 17 | property = property.concat(accumulator); 18 | } else { 19 | property.push(accumulator); 20 | } 21 | } 22 | 23 | return action(...property) 24 | .then((result) => sendResponseSuccess(res, result)) 25 | .catch((err) => sendResponseFail(res, err)); 26 | }; 27 | } 28 | 29 | function sendResponseSuccess( 30 | res, 31 | result: { status?: number | boolean; payload?: object | boolean } 32 | ) { 33 | if (!result) { 34 | return res.status(200); 35 | } 36 | 37 | const { status = false, payload = false } = result; 38 | 39 | if (status && typeof status === "number" && payload) { 40 | return res.status(status).json(payload); 41 | } else if (status && typeof status === "number" && !payload) { 42 | return res.sendStatus(status); 43 | } else if (!status && payload) { 44 | return res.status(200).json(payload); 45 | } else { 46 | return res.status(200).json(result); 47 | } 48 | } 49 | 50 | function sendResponseFail(res, error) { 51 | logger.error(error); 52 | res.status(error.status || 400).json({ error: error.message }); 53 | } 54 | -------------------------------------------------------------------------------- /src/modules/crash-game/crash-game.types.ts: -------------------------------------------------------------------------------- 1 | import mongoose from "mongoose"; 2 | 3 | import { IVIPLevelType } from "../user/user.types"; 4 | 5 | export interface IGameStateType { 6 | _id: mongoose.Types.ObjectId | null; 7 | status: number; 8 | crashPoint: number | null; 9 | startedAt: Date | null; 10 | duration: number | null; 11 | players: { [key: string]: IBetType }; 12 | bots: { [key: string]: IBetType }; 13 | pending: { [key: string]: IPendingBetType }; 14 | botCount: number; 15 | pendingCount: number; 16 | pendingBets: IPendingBetType[]; 17 | privateSeed: string | null; 18 | privateHash: string | null; 19 | publicSeed: string | null; 20 | connectedUsers: { [key: string]: string }; 21 | } 22 | 23 | export interface IBetType { 24 | playerID: string; 25 | username: string; 26 | avatar?: string; 27 | betAmount: number; 28 | denom: string; 29 | status: number; 30 | level: IVIPLevelType; 31 | stoppedAt?: number; 32 | autoCashOut: number; 33 | winningAmount?: number; 34 | forcedCashout?: boolean; 35 | autobet?: boolean; 36 | } 37 | 38 | export interface IPendingBetType { 39 | betAmount: number; 40 | denom: string; 41 | autoCashOut?: number; 42 | username: string; 43 | } 44 | 45 | export type TFormattedPlayerBetType = Pick< 46 | IBetType, 47 | | "playerID" 48 | | "username" 49 | | "avatar" 50 | | "betAmount" 51 | | "denom" 52 | | "status" 53 | | "level" 54 | | "stoppedAt" 55 | | "winningAmount" 56 | | "autobet" 57 | >; 58 | 59 | export interface IFormattedGameHistoryType 60 | extends Pick< 61 | IGameStateType, 62 | "_id" | "privateHash" | "privateSeed" | "publicSeed" | "crashPoint" 63 | > {} 64 | -------------------------------------------------------------------------------- /src/modules/payment/socket/payment.listener.ts: -------------------------------------------------------------------------------- 1 | import { Event as SocketEvent, Namespace, Server, Socket } from "socket.io"; 2 | 3 | import { ESOCKET_NAMESPACE } from "@/constant/enum"; 4 | 5 | import { EPaymentEvents } from "../payment.constant"; 6 | import PaymentSocketHandler from "./payment.socket-controller"; 7 | 8 | class PaymentSocketListener { 9 | private socketServer: Namespace; 10 | private logPrefix = "[Payment Socket:::]"; 11 | 12 | constructor(socketServer: Server) { 13 | // Initialize service 14 | 15 | this.socketServer = socketServer.of(ESOCKET_NAMESPACE.payment); 16 | this.subscribeListener(); 17 | } 18 | 19 | private subscribeListener(): void { 20 | this.socketServer.on("connection", (socket: Socket) => { 21 | const paymentSocketHandler = new PaymentSocketHandler( 22 | this.socketServer, 23 | socket 24 | ); 25 | 26 | // Auth handler 27 | socket.on(EPaymentEvents.login, async (token: string) => { 28 | await paymentSocketHandler.authHandler(token); 29 | }); 30 | // User deposit 31 | socket.on(EPaymentEvents.deposit, async (depositParam: string) => { 32 | await paymentSocketHandler.depositHandler(depositParam); 33 | }); 34 | // User withdraw 35 | socket.on(EPaymentEvents.withdraw, async (withdrawParam: string) => { 36 | await paymentSocketHandler.withdrawHandler(withdrawParam); 37 | }); 38 | 39 | // Check for users ban status 40 | socket.use((packet: SocketEvent, next: (err?: any) => void) => 41 | paymentSocketHandler.banStatusCheckMiddleware(packet, next) 42 | ); 43 | }); 44 | } 45 | } 46 | 47 | export default PaymentSocketListener; 48 | -------------------------------------------------------------------------------- /doc/CrashPoint.md: -------------------------------------------------------------------------------- 1 | # Crash Endpoint 2 | 3 | To calculate the cash out point in a crash game, we typically use a provably fair algorithm that ensures the game is fair and transparent. Here's a simplified explanation of how you might calculate the cash out point. 4 | 5 | 6 | 1. Provably Fair Algorithm: 7 | - Private Seed: A server-generated random seed that is kept secret until the game ends. 8 | - Public Seed: A random seed obtained from a blockchain network hash function that will combine with the private seed. 9 | - Hash Function: A cryptographic hash function (e.g., SHA-256) is used to combine the seeds and generate a random number. 10 | 11 | 2. Crash Point Calculation: 12 | - The crash point is determined by the combined seeds and the hash function. The result is a number that represents the multiplier at which the game will crash. 13 | 14 | 15 | ## Example Calculation 16 | 17 | 1. Generate Seeds: 18 | - Private Seed: server_seed 19 | - Public Seed: client_seed 20 | 2. Combine Seeds: 21 | - Combined Seed: combined_seed = server_seed + client_seed 22 | 3. Hash the Combined Seed: 23 | - Hash: hash = SHA-256(combined_seed) 24 | 4. Convert Hash to a Number: 25 | - Convert the hash to a decimal number. 26 | 5. Calculate Crash Point: 27 | - Use the decimal number to determine the crash point. For example: 28 | 29 | 30 | 31 | ## Code Example 32 | Here's a simplified example in JavaScript: 33 | ```javascript 34 | const crypto = require('crypto'); 35 | 36 | function calculateCrashPoint(serverSeed, clientSeed) { 37 | // 38 | } 39 | 40 | // Example usage 41 | const serverSeed = 'server_random_seed'; 42 | const clientSeed = 'client_random_seed'; 43 | const crashPoint = calculateCrashPoint(serverSeed, clientSeed); 44 | console.log('Crash Point:', crashPoint); 45 | ``` -------------------------------------------------------------------------------- /src/modules/user/user.interface.ts: -------------------------------------------------------------------------------- 1 | import mongoose, { Document } from "mongoose"; 2 | 3 | interface LeaderboardEntry { 4 | betAmount: number; 5 | winAmount: number; 6 | } 7 | 8 | export interface IUserModel extends Document { 9 | provider: string; 10 | role: string; 11 | status: string; 12 | providerId: string; 13 | username: string; 14 | password: string; 15 | avatar: string; 16 | rank: number; 17 | wallet: Map; 18 | wager: Map>; 19 | leaderboard: Map>; 20 | crypto: any; // Specify more detailed type if possible 21 | hasVerifiedAccount: boolean; 22 | verifiedPhoneNumber: string | null; 23 | accountVerified: Date | null; 24 | banExpires: string; 25 | selfExcludes: { 26 | crash: number; 27 | coinflip: number; 28 | mines: number; 29 | }; 30 | muteExpires: string; 31 | transactionsLocked: boolean; 32 | betsLocked: boolean; 33 | _affiliatedBy: mongoose.Schema.Types.ObjectId | null; 34 | affiliateClaimed: Date | null; 35 | affiliateCode: string | null; 36 | affiliateMoney: Map; 37 | affiliateMoneyCollected: Map; 38 | forgotToken: string | null; 39 | forgotExpires: number; 40 | rakebackBalance: Map; 41 | wagerNeededForWithdraw: Map; 42 | totalDeposited: Map; 43 | totalWithdrawn: Map; 44 | customWagerLimit: Map; 45 | avatarLastUpdate: number; 46 | signAddress: string; 47 | createdAt: Date; 48 | updatedAt: Date; 49 | } 50 | 51 | export const getId = (req) => { 52 | if (req.params.id === "me") { 53 | return req?.user?.userId || "000000000000000000000000"; 54 | } 55 | 56 | return req.params.id; 57 | }; 58 | -------------------------------------------------------------------------------- /src/modules/payment/payment.constant.ts: -------------------------------------------------------------------------------- 1 | import { BigNumber } from "@ethersproject/bignumber"; 2 | 3 | export enum EXAMPLE_ENUM { 4 | EXAMPLE_FIRST = "EXAMPLE_FIRST", 5 | EXAMPLE_TWO = "EXAMPLE_TWO", 6 | } 7 | 8 | export const toHuman = (amount: BigNumber, decimals: number): number => { 9 | console.log({ amount }) 10 | return parseFloat(amount.div(BigNumber.from(10).pow(decimals)).toString()); 11 | }; 12 | 13 | export const fromHumanString = ( 14 | amount: string, 15 | decimals: number 16 | ): BigNumber => { 17 | const [integerPart, fractionalPart = ""] = amount.split("."); 18 | const fractionalLength = fractionalPart.length; 19 | 20 | if (fractionalLength > decimals) { 21 | throw new Error("Too many decimal places"); 22 | } 23 | 24 | const integerBigNumber = BigNumber.from(integerPart); 25 | const fractionalBigNumber = BigNumber.from( 26 | fractionalPart.padEnd(decimals, "0") 27 | ); 28 | 29 | return integerBigNumber 30 | .mul(BigNumber.from(10).pow(decimals)) 31 | .add(fractionalBigNumber); 32 | }; 33 | 34 | export const mulDec = (a: BigNumber, x: number): BigNumber => { 35 | return a.mul(BigNumber.from(x)); 36 | }; 37 | 38 | export const divToNumber = (a: BigNumber, b: BigNumber): number => { 39 | return parseFloat(a.div(b).toString()); 40 | }; 41 | 42 | export const bigCompare = (a: BigNumber, b: BigNumber): 0 | 1 | -1 => { 43 | if (a.eq(b)) { 44 | return 0; 45 | } 46 | 47 | return a.gt(b) ? 1 : -1; 48 | }; 49 | 50 | export enum EPaymentEvents { 51 | login = "7234cd9d55", 52 | withdraw = "6f6363325c", 53 | deposit = "13474ade7", 54 | setAdminWallet = "9a2d27683581f", 55 | updateBalance = "updateBalance", 56 | paymentFailed = "payment-failed", 57 | } 58 | 59 | // 30 seconds 60 | export const CAllowTimeDiff = 30 * 1000; 61 | -------------------------------------------------------------------------------- /src/modules/chat-history/socket/chat-history.listener.ts: -------------------------------------------------------------------------------- 1 | import { Namespace, Server, Socket } from "socket.io"; 2 | 3 | import { ESOCKET_NAMESPACE } from "@/constant/enum"; 4 | 5 | import { EChatHistoryEvents } from "../chat-history.constant"; 6 | import ChatHistorySocketHandler from "./chat-history.socket-controller"; 7 | 8 | class ChatHistorySocketListener { 9 | private socketServer: Namespace; 10 | private logoPrefix: string = "[Chat Socket]::: "; 11 | 12 | constructor(socketServer: Server) { 13 | this.socketServer = socketServer.of(ESOCKET_NAMESPACE.chat); 14 | this.subscribeListener(); 15 | } 16 | 17 | private subscribeListener(): void { 18 | this.socketServer.on("connection", (socket: Socket) => { 19 | const chatHistorySocketHandler = new ChatHistorySocketHandler( 20 | this.socketServer, 21 | socket 22 | ); 23 | // Auth handler 24 | socket.on(EChatHistoryEvents.auth, async (token: string) => { 25 | chatHistorySocketHandler.authHandler(token); 26 | }); 27 | socket.on(EChatHistoryEvents.getChatHistory, async (sentAt: Date) => { 28 | chatHistorySocketHandler.getChatHistoryHandler(sentAt); 29 | }); 30 | // Send message handler 31 | socket.on(EChatHistoryEvents.f2bMessage, async (message: string) => { 32 | chatHistorySocketHandler.sendMessageHandler(message); 33 | }); 34 | // Disconnect Handler 35 | socket.on(EChatHistoryEvents.disconnect, async () => { 36 | chatHistorySocketHandler.disconnectHandler(); 37 | }); 38 | 39 | // // Check for users ban status 40 | // socket.use(this.banStatusCheckMiddleware); 41 | 42 | // // Throttle connections 43 | // socket.use(throttleConnections(socket)); 44 | }); 45 | } 46 | } 47 | 48 | export default ChatHistorySocketListener; 49 | -------------------------------------------------------------------------------- /src/modules/site-transaction/site-transaction.model.ts: -------------------------------------------------------------------------------- 1 | // Import Dependencies 2 | import mongoose, { model } from "mongoose"; 3 | 4 | import { ISiteTransactionModel } from "./site-transaction.interface"; 5 | const { Schema, SchemaTypes } = mongoose; 6 | 7 | // Setup SiteTransaction Schema 8 | const SiteTransactionSchema = new Schema( 9 | { 10 | // Amount that was increased or decreased 11 | amount: Number, 12 | denom: String, 13 | // Reason for this wallet transaction 14 | reason: String, 15 | 16 | // Extra data relating to this transaction 17 | // game data, crypto transaction data, etc. 18 | extraData: { 19 | coinflipGameId: { 20 | type: SchemaTypes.ObjectId, 21 | ref: "CoinflipGame", 22 | }, 23 | crashGameId: { 24 | type: SchemaTypes.ObjectId, 25 | ref: "CrashGame", 26 | }, 27 | transactionId: { 28 | type: SchemaTypes.ObjectId, 29 | ref: "CryptoTransaction", 30 | }, 31 | couponId: { 32 | type: SchemaTypes.ObjectId, 33 | ref: "CouponCode", 34 | }, 35 | affiliatorId: { 36 | type: SchemaTypes.ObjectId, 37 | ref: "User", 38 | }, 39 | modifierId: { 40 | type: SchemaTypes.ObjectId, 41 | ref: "User", 42 | }, 43 | raceId: { 44 | type: SchemaTypes.ObjectId, 45 | ref: "Race", 46 | }, 47 | triviaGameId: { 48 | type: SchemaTypes.ObjectId, 49 | ref: "Trivia", 50 | }, 51 | }, 52 | 53 | // What user does this belong to 54 | userId: { 55 | type: SchemaTypes.ObjectId, 56 | ref: "User", 57 | }, 58 | }, 59 | { 60 | timestamps: true, 61 | } 62 | ); 63 | 64 | export default model( 65 | "SiteTransaction", 66 | SiteTransactionSchema 67 | ); 68 | -------------------------------------------------------------------------------- /src/utils/localizations/localizations.interface.ts: -------------------------------------------------------------------------------- 1 | interface ILocalization { 2 | ERRORS: { 3 | AUTH: { 4 | USE_NEW_TOKEN: string; 5 | }; 6 | USER: { 7 | NOT_EXIST: string; 8 | USER_ALREADY_EXIST: string; 9 | USER_NOT_CREATED: string; 10 | USERNAME_OR_PASSWORD_INVALID: string; 11 | OLD_PASSWORD_INVALID: string; 12 | SIGN_WALLETADDRESS_INCORRECT: string; 13 | SIGN_WALLETINFO_INCORRECT: string; 14 | UPDATE_USER_INFO: string; 15 | }; 16 | OTHER: { 17 | EMAIL_WITH_CODE_NOT_SENDED: string; 18 | ERROR_WITH_SEND_MESSAGE: string; 19 | ITEM_NOT_FOUND: string; 20 | ID_IS_INVALID: string; 21 | CODE_IS_INVALID: string; 22 | UNAUTHORIZED: string; 23 | FORBIDDEN: string; 24 | NOT_FOUND_ENDPOINT: string; 25 | CONFLICT: string; 26 | SOMETHING_WENT_WRONG: string; 27 | REFRESH_TOKEN_INVALID: string; 28 | PASSWORD_ERROR: string; 29 | NO_WALLET: string; 30 | DUPLICATE_ITEM: string; 31 | USER_PLAN_AND_PROGRESS_UPDATE_FAILED: string; 32 | }; 33 | PAYMENT: { 34 | GET_CUSTOMER: string; 35 | REPLACE_CUSTOMER: string; 36 | CREATE_CARD_TOKEN: string; 37 | CREATE_CARD: string; 38 | CREATE_SUBSCRIBE: string; 39 | GET_CUSTOMER_INFO: string; 40 | GET_SUBSCRIBE: string; 41 | DELETE_SUBSCRIBE: string; 42 | SUBSCRIBE_NOT_FOUND: string; 43 | PERMISSION_DENIED: string; 44 | USER_CARDS_EMPTY: string; 45 | GET_CARD: string; 46 | USER_ALREADY_HAVE_SUBSCRIBE: string; 47 | USER_ALREADY_HAVE_CARD: string; 48 | CREATE_CUSTOMER: string; 49 | }; 50 | PAYMENT_WALLET: { 51 | GET: string; 52 | UPDATE_ADDRESS: string; 53 | ADD_WALLET: string; 54 | STATUS: string; 55 | }; 56 | }; 57 | } 58 | export default ILocalization; 59 | -------------------------------------------------------------------------------- /src/modules/chat-history/chat-history.service.ts: -------------------------------------------------------------------------------- 1 | // need add model to mongo index file 2 | import BaseService from "@/utils/base/service"; 3 | import { ChatHistory } from "@/utils/db"; 4 | import logger from "@/utils/logger"; 5 | 6 | // need add types 7 | import { IChatHistoryModel } from "./chat-history.interface"; 8 | import { IChatEmitHistory } from "./chat-history.types"; 9 | 10 | export class ChatHistoryService extends BaseService { 11 | constructor() { 12 | super(ChatHistory); 13 | } 14 | 15 | fetchEarlierChatHistories = async ( 16 | date: Date, 17 | limit: number 18 | ): Promise => { 19 | try { 20 | const chatHistories = await this.aggregateByPipeline([ 21 | { $match: { sentAt: { $lt: new Date(date) } } }, 22 | { $sort: { sentAt: -1 } }, 23 | { $limit: limit }, 24 | { 25 | $lookup: { 26 | from: "users", 27 | localField: "user", 28 | foreignField: "_id", 29 | as: "user", 30 | }, 31 | }, 32 | { $unwind: "$user" }, 33 | { 34 | $project: { 35 | "user._id": 1, 36 | "user.username": 1, 37 | "user.avatar": 1, 38 | "user.hasVerifiedAccount": 1, 39 | "user.createdAt": 1, 40 | sentAt: 1, 41 | message: 1, 42 | }, 43 | }, 44 | ]); 45 | chatHistories.sort( 46 | (a: any, b: any) => a.sentAt.getTime() - b.sentAt.getTime() 47 | ); 48 | 49 | if (chatHistories.length == 0) { 50 | return []; 51 | } 52 | 53 | return chatHistories as IChatEmitHistory[]; 54 | } catch (ex) { 55 | logger.error( 56 | "Error finding chat histories that sent earlier than ${date}: ${(ex as Error).message}" 57 | ); 58 | return []; 59 | } 60 | }; 61 | } 62 | -------------------------------------------------------------------------------- /src/utils/mailing.ts: -------------------------------------------------------------------------------- 1 | import { MailDataRequired } from "@sendgrid/helpers/classes/mail"; 2 | import sgMail from "@sendgrid/mail"; 3 | import nodemailer from "nodemailer"; 4 | import postmark, { Message } from "postmark"; 5 | 6 | import { 7 | POSTMARK_SERVER_TOKEN, 8 | SENDGRID_API_KEY, 9 | SENDGRID_SENDER, 10 | SMTP_APP_PASSWORD, 11 | SMTP_APP_USER, 12 | } from "@/config"; 13 | 14 | import logger from "./logger"; 15 | 16 | sgMail.setApiKey(SENDGRID_API_KEY); 17 | 18 | export type TNodeMailOptions = { 19 | from: string; 20 | to: string; 21 | subject: string; 22 | html: string; 23 | }; 24 | 25 | const nodeMailerTrasnport = nodemailer.createTransport({ 26 | service: "Gmail", 27 | host: "smtp.gmail.com", 28 | port: 465, 29 | secure: true, 30 | auth: { 31 | user: SMTP_APP_USER, 32 | pass: SMTP_APP_PASSWORD, 33 | }, 34 | }); 35 | 36 | const postmarkClient = new postmark.ServerClient(POSTMARK_SERVER_TOKEN); 37 | 38 | export const sendEmailWithSendGrid = ( 39 | userEmail: string, 40 | messageObj: { subject: string; text: string } & Partial 41 | ) => { 42 | const { subject, text, replyTo } = messageObj; 43 | 44 | const msg: MailDataRequired = { 45 | to: userEmail, 46 | from: SENDGRID_SENDER, 47 | subject, 48 | text, 49 | }; 50 | 51 | if (replyTo) { 52 | msg.replyTo = replyTo; 53 | } 54 | 55 | return sgMail.send(msg); 56 | }; 57 | 58 | export const sendEmailWithNodeMailer = async (mailOption: TNodeMailOptions) => { 59 | try { 60 | const resMail = await nodeMailerTrasnport.sendMail(mailOption); 61 | return resMail; 62 | } catch { 63 | return null; 64 | } 65 | }; 66 | 67 | export const sendEmailWithPostmark = async (mailOption: Message) => { 68 | try { 69 | const resMail = postmarkClient.sendEmail(mailOption); 70 | return resMail; 71 | return; 72 | } catch (error) { 73 | logger.error("Postmark Error::: " + error); 74 | } 75 | }; 76 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import "dotenv/config"; 2 | 3 | import cors from "cors"; 4 | import express from "express"; 5 | import { Server } from "http"; 6 | import path from "path"; 7 | import { Server as SocketIOServer } from "socket.io"; 8 | import customParser from "socket.io-msgpack-parser"; 9 | import { fileURLToPath } from "url"; 10 | 11 | import { ALLOW_HOSTS, PORT, SOCKET_ALLOW_HOSTS } from "@/config"; 12 | import authorize from "@/middleware/authorize"; 13 | import { errorHandler, routeNotFound } from "@/middleware/error-handler"; 14 | import logger from "@/middleware/logger"; 15 | 16 | import { CronJobs } from "./cron"; 17 | import RootRouter from "./root.router"; 18 | import SocketServer from "./root.socket"; 19 | 20 | const filename = fileURLToPath(import.meta.url); // get the resolved path to the file 21 | const dirname = path.dirname(filename); 22 | 23 | const app = express(); 24 | app.use( 25 | cors({ 26 | origin: ALLOW_HOSTS, 27 | methods: "OPTIONS,GET,HEAD,PUT,PATCH,POST,DELETE", 28 | credentials: true, 29 | }) 30 | ); 31 | 32 | app.use(express.urlencoded({ limit: "50mb", extended: true })); 33 | app.use(express.json({ limit: "50mb" })); 34 | 35 | app.use("/api/v1", logger, authorize, new RootRouter().router); 36 | app.use("/assets", express.static(path.join(dirname, "../assets"))); 37 | 38 | // install routers 39 | app.get("/", (_, res) => { 40 | res.status(200).json({ 41 | message: "Kartel backend is running", 42 | }); 43 | }); 44 | 45 | app.use([routeNotFound, errorHandler]); 46 | 47 | const httpServer = new Server(app); 48 | 49 | const socketServer = new SocketIOServer(httpServer, { 50 | cors: { 51 | origin: SOCKET_ALLOW_HOSTS, 52 | methods: ["GET", "POST"], 53 | credentials: true, 54 | }, 55 | parser: customParser, 56 | }); 57 | 58 | new SocketServer(socketServer); 59 | app.set("socketio", socketServer); 60 | 61 | CronJobs.customerUpdateCron.start(); 62 | 63 | httpServer.listen(PORT, () => console.info("Server listening on port " + PORT)); 64 | -------------------------------------------------------------------------------- /src/modules/auth/auth.router.ts: -------------------------------------------------------------------------------- 1 | import { Router } from "express"; 2 | 3 | import actionHandler from "@/middleware/action-handler"; 4 | import checkPermissions from "@/middleware/check-permissions"; 5 | import validateSchema from "@/middleware/validate-schema"; 6 | import * as mapProperty from "@/utils/interfaces"; 7 | 8 | import AuthController from "./auth.controller"; 9 | import * as validate from "./auth.validate"; 10 | 11 | export default class AuthRouter { 12 | public router: Router; 13 | private authController: AuthController; 14 | 15 | constructor() { 16 | this.router = Router(); 17 | this.authController = new AuthController(); 18 | this.routes(); 19 | } 20 | 21 | public routes(): void { 22 | this.router.post( 23 | "/sign-up", 24 | validateSchema(validate.signUp, mapProperty.getBody), 25 | actionHandler(this.authController.signUp, [mapProperty.getBody]) 26 | ); 27 | 28 | this.router.post( 29 | "/sign-in", 30 | validateSchema(validate.signIn, mapProperty.getBody), 31 | actionHandler(this.authController.signIn, [mapProperty.getBody]) 32 | ); 33 | 34 | this.router.post( 35 | "/reset-password", 36 | validateSchema(validate.resetPassword, mapProperty.getBody), 37 | actionHandler(this.authController.resetPassword, [mapProperty.getBody]) 38 | ); 39 | 40 | this.router.post( 41 | "/update-token", 42 | validateSchema(validate.device, mapProperty.deviceInfo), 43 | validateSchema(validate.updateToken, mapProperty.getBody), 44 | actionHandler(this.authController.updateToken, [ 45 | mapProperty.deviceInfo, 46 | mapProperty.getBody, 47 | ]) 48 | ); 49 | 50 | this.router.post( 51 | "/logout", 52 | checkPermissions(), 53 | validateSchema(validate.device, mapProperty.deviceInfo), 54 | actionHandler(this.authController.logout, [ 55 | mapProperty.deviceInfo, 56 | mapProperty.getUserInfo, 57 | ]) 58 | ); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/utils/setting/site.ts: -------------------------------------------------------------------------------- 1 | // Import Dependencies 2 | 3 | import { ENABLE_LOGIN_ONSTART, ENABLE_MAINTENANCE_ONSTART } from "@/config"; 4 | 5 | // Store site toggle switch states here 6 | // and initialize them to config values 7 | let MAINTENANCE_ENABLED = ENABLE_MAINTENANCE_ONSTART; 8 | let LOGIN_ENABLED = ENABLE_LOGIN_ONSTART; 9 | let DEPOSITS_ENABLED = true; 10 | let WITHDRAWS_ENABLED = true; 11 | let COINFLIP_ENABLED = true; 12 | let MINES_ENABLED = true; 13 | let CRASH_ENABLED = true; 14 | 15 | // Create getters 16 | const getMaintenanceState = () => MAINTENANCE_ENABLED; 17 | const getLoginState = () => LOGIN_ENABLED; 18 | const getDepositState = () => DEPOSITS_ENABLED; 19 | const getWithdrawState = () => WITHDRAWS_ENABLED; 20 | const getCoinflipState = () => COINFLIP_ENABLED; 21 | const getMinesState = () => MINES_ENABLED; 22 | const getSiteCrashState = () => CRASH_ENABLED; 23 | 24 | // Create reducers 25 | const toggleMaintenance = () => { 26 | MAINTENANCE_ENABLED = !MAINTENANCE_ENABLED; 27 | return true; 28 | }; 29 | 30 | const toggleLogin = () => { 31 | LOGIN_ENABLED = !LOGIN_ENABLED; 32 | return true; 33 | }; 34 | 35 | const toggleDeposits = () => { 36 | DEPOSITS_ENABLED = !DEPOSITS_ENABLED; 37 | return true; 38 | }; 39 | 40 | const toggleWithdraws = () => { 41 | WITHDRAWS_ENABLED = !WITHDRAWS_ENABLED; 42 | return true; 43 | }; 44 | 45 | const toggleCoinflip = () => { 46 | COINFLIP_ENABLED = !COINFLIP_ENABLED; 47 | return true; 48 | }; 49 | 50 | const toggleCrash = () => { 51 | CRASH_ENABLED = !CRASH_ENABLED; 52 | return true; 53 | }; 54 | 55 | const toggleMines = () => { 56 | MINES_ENABLED = !MINES_ENABLED; 57 | return true; 58 | }; 59 | 60 | // Export functions 61 | export { 62 | getCoinflipState, 63 | getDepositState, 64 | getLoginState, 65 | getMaintenanceState, 66 | getMinesState, 67 | getSiteCrashState, 68 | getWithdrawState, 69 | toggleCoinflip, 70 | toggleCrash, 71 | toggleDeposits, 72 | toggleLogin, 73 | toggleMaintenance, 74 | toggleMines, 75 | toggleWithdraws, 76 | }; 77 | -------------------------------------------------------------------------------- /src/modules/user/user.router.ts: -------------------------------------------------------------------------------- 1 | import actionHandler from "@/middleware/action-handler"; 2 | import checkPermissions from "@/middleware/check-permissions"; 3 | import validateSchema from "@/middleware/validate-schema"; 4 | import { BaseRouter } from "@/utils/base"; 5 | import * as mapProperty from "@/utils/interfaces"; 6 | import * as validations from "@/utils/validations"; 7 | 8 | import { ROLE } from "./user.constant"; 9 | import UserController from "./user.controller"; 10 | import * as mapPropertyUser from "./user.interface"; 11 | import * as validateUser from "./user.validate"; 12 | 13 | export default class UserRouter extends BaseRouter { 14 | private userController: UserController; 15 | 16 | constructor() { 17 | super(); 18 | this.userController = new UserController(); 19 | this.routes(); 20 | } 21 | 22 | public routes(): void { 23 | this.router.get( 24 | "/", 25 | checkPermissions({ roles: [ROLE.ADMIN] }), 26 | validateSchema(validations.getListValidation, mapProperty.getQuery), 27 | actionHandler(this.userController.getUsers, mapProperty.getQuery) 28 | ); 29 | 30 | this.router.get( 31 | "/permissions", 32 | checkPermissions({ roles: [ROLE.ADMIN] }), 33 | actionHandler(this.userController.getPermissions) 34 | ); 35 | 36 | this.router.get( 37 | "/user/:id", 38 | checkPermissions({ roles: [ROLE.ADMIN] }), 39 | validateSchema(validateUser.Id, mapPropertyUser.getId), 40 | actionHandler(this.userController.getUserById, [ 41 | mapPropertyUser.getId, 42 | mapProperty.getUserInfo, 43 | mapProperty.getUTCFromHeader, 44 | ]) 45 | ); 46 | 47 | this.router.get( 48 | "/balance", 49 | checkPermissions(), 50 | actionHandler(this.userController.getUserBalance, [ 51 | mapProperty.getUserInfo, 52 | ]) 53 | ); 54 | 55 | this.router.get( 56 | "/admin-wallet", 57 | checkPermissions(), 58 | actionHandler(this.userController.getAdminWalletBalance, [ 59 | mapProperty.getUserInfo, 60 | ]) 61 | ); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/modules/user/user.service.ts: -------------------------------------------------------------------------------- 1 | import bcrypt from "bcryptjs"; 2 | import { ObjectId } from "mongoose"; 3 | 4 | import { SITE_USER_ID } from "@/config"; 5 | import BaseService from "@/utils/base/service"; 6 | import { User } from "@/utils/db"; 7 | import logger from "@/utils/logger"; 8 | import { validateFunc } from "@/utils/validations"; 9 | 10 | import { IUserModel } from "./user.interface"; 11 | import * as validateUser from "./user.validate"; 12 | 13 | export default class UserService extends BaseService { 14 | constructor() { 15 | super(User); 16 | } 17 | 18 | async create(user: IUserModel) { 19 | const error = validateFunc(validateUser.full, user); 20 | 21 | if (error) { 22 | throw new Error(error); 23 | } 24 | 25 | if (user.password) { 26 | user.password = await bcrypt.hash(user.password, 10); 27 | } 28 | 29 | return super.create(user); 30 | } 31 | 32 | async resetPassword(id: ObjectId, password: string) { 33 | password = await bcrypt.hash(password, 10); 34 | return this.updateById(id, { password }); 35 | } 36 | 37 | async updateUserBalance( 38 | userId: ObjectId, 39 | updateParams: string, 40 | updatefield: number 41 | ) { 42 | try { 43 | await this.update( 44 | { _id: userId }, 45 | { 46 | $set: { 47 | [updateParams]: updatefield, 48 | }, 49 | } 50 | ); 51 | const updatedUser = await this.getItemById(userId); 52 | 53 | if (!updatedUser) { 54 | return "User update Failed"; 55 | } 56 | 57 | return { status: "success", data: updatedUser.wallet }; 58 | } catch (ex) { 59 | const errorMessage = `Error updating User`; 60 | logger.error(errorMessage); 61 | console.error(ex); 62 | return "User update Failed"; 63 | } 64 | } 65 | 66 | public async getSiteUserData(): Promise { 67 | try { 68 | const result = await this.getItemById(SITE_USER_ID); 69 | return result; 70 | } catch (error) { 71 | logger.error("Error fetching the latest revenue log", error); 72 | return null; 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/modules/dashboard/dashboard.service.ts: -------------------------------------------------------------------------------- 1 | // need add model to mongo index file 2 | import BaseService from "@/utils/base/service"; 3 | import { Dashboard } from "@/utils/db"; 4 | 5 | import { EFilterDate, ERevenueType } from "./dashboard.constant"; 6 | // need add types 7 | import { IDashboardModel } from "./dashboard.interface"; 8 | 9 | export class DashboardService extends BaseService { 10 | constructor() { 11 | super(Dashboard); 12 | } 13 | 14 | { 15 | let date = 5; 16 | let limit = 12; 17 | 18 | switch (dateType) { 19 | case EFilterDate.hour: 20 | date = 5; 21 | limit = 12; 22 | break; 23 | case EFilterDate.day: 24 | date = 60; 25 | limit = 24; 26 | break; 27 | case EFilterDate.week: 28 | date = 60 * 24; 29 | limit = 7; 30 | break; 31 | case EFilterDate.month: 32 | date = 60 * 24; 33 | limit = 30; 34 | break; 35 | default: 36 | date = 60 * 24 * 30; 37 | limit = 12; 38 | } 39 | 40 | const revenueLogs = await this.aggregateByPipeline([ 41 | { 42 | $addFields: { 43 | insertMod: { 44 | $mod: [ 45 | { 46 | $toLong: "$insertDate", 47 | }, 48 | 1000 * 60 * date, 49 | ], 50 | }, 51 | }, 52 | }, 53 | { 54 | $match: { 55 | insertMod: 0, 56 | revenueType: Number(desiredRevenueType), 57 | }, 58 | }, 59 | { 60 | $sort: { 61 | createdAt: -1, 62 | }, 63 | }, 64 | { 65 | $limit: limit * 2, 66 | }, 67 | { 68 | $sort: { 69 | createdAt: 1, 70 | }, 71 | }, 72 | ]); 73 | 74 | 75 | } 76 | 77 | private async getLastRevenueLog(denom: string): Promise { 78 | return await this.aggregateByPipeline([ 79 | { 80 | $match: { 81 | denom, 82 | }, 83 | }, 84 | { 85 | $sort: { 86 | insertDate: -1, 87 | }, 88 | }, 89 | { $limit: 1 }, 90 | ]); 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/root.router.ts: -------------------------------------------------------------------------------- 1 | import { Router } from "express"; 2 | 3 | // import swaggerUi from "swagger-ui-express"; 4 | import AuthRouter from "./modules/auth/auth.router"; 5 | import AutoCrashBetRouter from "./modules/auto-crash-bet/auto-crash-bet.router"; 6 | import ChatHistoryRouter from "./modules/chat-history/chat-history.router"; 7 | import CrashGameRouter from "./modules/crash-game/crash-game.router"; 8 | import DashboardRouter from "./modules/dashboard/dashboard.router"; 9 | import LogsRouter from "./modules/logs/logs.router"; 10 | import PaymentRouter from "./modules/payment/payment.router"; 11 | import SiteTransactionRouter from "./modules/site-transaction/site-transaction.router"; 12 | import StakingRouter from "./modules/staking/staking.router"; 13 | import UserRouter from "./modules/user/user.router"; 14 | import UserBotRouter from "./modules/user-bot/user-bot.router"; 15 | import TraitRouter from "./modules/trait/trait.router"; 16 | // import swaggerSetup from "./utils/swagger/swagger.setup"; 17 | 18 | export default class RootRouter { 19 | public router: Router; 20 | 21 | constructor() { 22 | this.router = Router(); 23 | 24 | this.routes(); 25 | } 26 | 27 | public routes(): void { 28 | // this.router.use("/docs", swaggerUi.serve, swaggerUi.setup(swaggerSetup)) 29 | 30 | this.router.use("/auth", new AuthRouter().router); 31 | this.router.use("/dashboard", new DashboardRouter().router); 32 | this.router.use("/auto-crash-bet", new AutoCrashBetRouter().router); 33 | this.router.use("/logs", new LogsRouter().router); 34 | this.router.use("/chat-history", new ChatHistoryRouter().router); 35 | this.router.use("/crash-game", new CrashGameRouter().router); 36 | this.router.use("/site-transaction", new SiteTransactionRouter().router); 37 | this.router.use("/payment", new PaymentRouter().router); 38 | this.router.use("/user", new UserRouter().router); 39 | this.router.use("/user-bot", new UserBotRouter().router); 40 | this.router.use("/staking", new StakingRouter().router); 41 | this.router.use("/trait", new TraitRouter().router); 42 | 43 | this.router.get("/version", (_req, res) => res.json({ version: 1 })); 44 | this.router.get("/status", (_req, res) => 45 | ); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/middleware/check-permissions.ts: -------------------------------------------------------------------------------- 1 | import { NextFunction, Request, Response } from "express"; 2 | 3 | import { IAuthInfo } from "@/modules/auth/auth.types"; 4 | import { ROLE, STATUS } from "@/modules/user/user.constant"; 5 | import UserService from "@/modules/user/user.service"; 6 | import * as ILocalizations from "@/utils/localizations"; 7 | 8 | const localizations = ILocalizations["en"]; 9 | 10 | export default function checkPermissions({ 11 | roles, 12 | statusArr, 13 | }: { 14 | roles?: Array; 15 | statusArr?: Array; 16 | } = {}) { 17 | return async ( 18 | req: Request & { 19 | token: { [key: string]: unknown }; 20 | user: IAuthInfo; 21 | }, 22 | res: Response, 23 | next: NextFunction 24 | ) => { 25 | try { 26 | const baseRoles = [ROLE.ADMIN, ROLE.MEMBER]; 27 | const baseStatusArr = [ 28 | STATUS.VERIFIED, 29 | STATUS.WITH_PAYMENT, 30 | STATUS.WITHOUT_PAYMENT, 31 | ]; 32 | roles = !roles ? baseRoles : roles; 33 | statusArr = !statusArr ? baseStatusArr : statusArr; 34 | 35 | const userService = new UserService(); 36 | const user = req.user?.userId 37 | ? await userService.getItemById(req.user.userId) 38 | : null; 39 | 40 | // Set user role as member by default 41 | if (user?.role !== ROLE.ADMIN) { 42 | user.role = ROLE.MEMBER; 43 | } 44 | 45 | if (user?.role) { 46 | if (roles.includes(user?.role as ROLE)) { 47 | return next(); 48 | } else { 49 | return res.status(401).json({ 50 | error: localizations.ERRORS.AUTH.USE_NEW_TOKEN, 51 | oldUser: req.user, 52 | newUser: { 53 | userId: user._id, 54 | role: user.role, 55 | status: user.status, 56 | }, 57 | }); 58 | } 59 | } else { 60 | return res.status(403).json({ 61 | error: localizations.ERRORS.OTHER.FORBIDDEN, 62 | oldUser: req.user, 63 | newUser: user 64 | ? { userId: user._id, role: user.role, status: user.status } 65 | : null, 66 | }); 67 | } 68 | } catch (error) { 69 | res.status(error.status || 400).json({ error: error.message }); 70 | } 71 | }; 72 | } 73 | -------------------------------------------------------------------------------- /src/utils/encryption/aes-wrapper.ts: -------------------------------------------------------------------------------- 1 | import * as crypto from "crypto"; 2 | 3 | interface IvAndMessage { 4 | iv: Buffer; 5 | message: string; 6 | } 7 | 8 | class AESWrapper { 9 | // get list of supportable encryption algorithms 10 | 11 | private static instance: AESWrapper; 12 | 13 | public static getInstance(): AESWrapper { 14 | if (!AESWrapper.instance) { 15 | AESWrapper.instance = new AESWrapper(); 16 | } 17 | 18 | return AESWrapper.instance; 19 | } 20 | 21 | public static getAlgorithmList(): void { 22 | console.log(crypto.getCiphers()); 23 | } 24 | 25 | public static generateKey(): Buffer { 26 | return crypto.randomBytes(32); 27 | } 28 | 29 | public static generateIv(): Buffer { 30 | return crypto.randomBytes(16); 31 | } 32 | 33 | // separate initialization vector from message 34 | public static separateVectorFromData(data: string): IvAndMessage { 35 | const iv = Buffer.from(data.slice(-24), "base64"); 36 | const message = data.substring(0, data.length - 24); 37 | 38 | return { 39 | iv, 40 | message, 41 | }; 42 | } 43 | 44 | public static encrypt(key: Buffer, iv: Buffer, text: string): string { 45 | let encrypted = ""; 46 | const cipher = crypto.createCipheriv("aes-256-cbc", key, iv); 47 | encrypted += cipher.update(text, "utf8", "base64"); 48 | encrypted += cipher.final("base64"); 49 | 50 | return encrypted; 51 | } 52 | 53 | public static decrypt(key: Buffer, text: string): string { 54 | let decrypted = ""; 55 | const data = AESWrapper.separateVectorFromData(text); 56 | const decipher = crypto.createDecipheriv("aes-256-cbc", key, data.iv); 57 | decrypted += decipher.update(data.message, "base64", "utf8"); 58 | decrypted += decipher.final("utf8"); 59 | 60 | return decrypted; 61 | } 62 | 63 | // add initialization vector to message 64 | public static addIvToBody(iv: Buffer, encryptedBase64: string): string { 65 | encryptedBase64 += iv.toString("base64"); 66 | return encryptedBase64; 67 | } 68 | 69 | public static createAesMessage(aesKey: Buffer, message: string): string { 70 | const aesIv = AESWrapper.generateIv(); 71 | let encryptedMessage = AESWrapper.encrypt(aesKey, aesIv, message); 72 | encryptedMessage = AESWrapper.addIvToBody(aesIv, encryptedMessage); 73 | 74 | return encryptedMessage; 75 | } 76 | } 77 | 78 | export default AESWrapper; 79 | -------------------------------------------------------------------------------- /src/modules/chat-history/chat-history.router.ts: -------------------------------------------------------------------------------- 1 | import actionHandler from "@/middleware/action-handler"; 2 | import checkPermissions from "@/middleware/check-permissions"; 3 | import validateSchema from "@/middleware/validate-schema"; 4 | import { ROLE } from "@/modules/user/user.constant"; 5 | import { BaseRouter } from "@/utils/base"; 6 | import * as mapProperty from "@/utils/interfaces"; 7 | 8 | import { ChatHistoryController, CreateChatHistorySchema } from "."; 9 | 10 | export default class ChatHistoryRouter extends BaseRouter { 11 | private chatHistoryController: ChatHistoryController; 12 | 13 | constructor() { 14 | super(); 15 | this.chatHistoryController = new ChatHistoryController(); 16 | this.routes(); 17 | } 18 | 19 | public routes(): void { 20 | this.router.get( 21 | "/", 22 | checkPermissions(), 23 | actionHandler(this.chatHistoryController.getAll) 24 | ); 25 | 26 | this.router.post( 27 | "/", 28 | checkPermissions({ roles: [ROLE.ADMIN] }), 29 | validateSchema(CreateChatHistorySchema, mapProperty.getBody), 30 | actionHandler(this.chatHistoryController.create, mapProperty.getBody) 31 | ); 32 | 33 | this.router.get( 34 | "/name/:name", 35 | checkPermissions(), 36 | actionHandler( 37 | this.chatHistoryController.getByName, 38 | mapProperty.getNameFromParam 39 | ) 40 | ); 41 | 42 | this.router.get( 43 | "/:id", 44 | checkPermissions() 45 | // validateSchema( 46 | // validations.byId, 47 | // mapPropertyExample.getIdFromParamsWithMe 48 | // ), 49 | // validateSchema(CreateExampleSchema, mapProperty.getBody), 50 | // actionHandler( 51 | // this.exampleController.getById, 52 | // mapPropertyExample.getIdFromParamsWithMe 53 | // ) 54 | ); 55 | 56 | this.router.put( 57 | "/:name", 58 | checkPermissions({ roles: [ROLE.ADMIN] }), 59 | // validateSchema(UpdateExampleSchema, mapProperty.getBody), 60 | actionHandler(this.chatHistoryController.update, [ 61 | mapProperty.getNameFromParam, 62 | mapProperty.getBody, 63 | ]) 64 | ); 65 | 66 | this.router.delete( 67 | "/:name", 68 | checkPermissions({ roles: [ROLE.ADMIN] }), 69 | actionHandler( 70 | this.chatHistoryController.delete, 71 | mapProperty.getNameFromParam 72 | ) 73 | ); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/modules/leaderboard/socket/leaderboard.listener.ts: -------------------------------------------------------------------------------- 1 | import { Namespace, Server, Socket } from "socket.io"; 2 | 3 | import { ESOCKET_NAMESPACE } from "@/constant/enum"; 4 | import TraitController from "@/modules/trait/trait.controller"; 5 | import logger from "@/utils/logger"; 6 | 7 | import { LeaderboardController } from "../leaderboard.controller"; 8 | import { LeaderboardService } from "../leaderboard.service"; 9 | 10 | class LeaderboardSocketListener { 11 | private socketServer: Namespace; 12 | private leaderboardService: LeaderboardService; 13 | private leaderboardController: LeaderboardController; 14 | private traitController: TraitController; 15 | 16 | private logPrefix = "[Leaderboard Socket:::]"; 17 | 18 | constructor(socketServer: Server) { 19 | // Initialize service 20 | this.leaderboardService = new LeaderboardService(); 21 | this.leaderboardController = new LeaderboardController(); 22 | this.traitController = new TraitController(); 23 | 24 | this.socketServer = socketServer.of(ESOCKET_NAMESPACE.leaderboard); 25 | this.initializeListener(); 26 | this.subscribeListener(); 27 | } 28 | 29 | private subscribeListener(): void { 30 | this.socketServer.on("connection", (_socket: Socket) => { 31 | // this.initializeSubscribe(socket); 32 | }); 33 | } 34 | 35 | private initializeListener = async () => { 36 | try { 37 | const emitLeaderboard = async () => { 38 | const start = Date.now(); 39 | 40 | const leaderboardResponse = 41 | await this.leaderboardService.getTopLearderboards( 42 | 10, 43 | 44 | ); 45 | 46 | if ( 47 | leaderboardResponse && 48 | Object.keys(leaderboardResponse).length > 0 49 | ) { 50 | this.socketServer.emit("leaderboard-fetch-all", { 51 | message: "success", 52 | leaderboard: leaderboardResponse!, 53 | }); 54 | } else { 55 | this.socketServer.emit( 56 | "notify-error", 57 | "Error ocurred when fetched leaderboard!" 58 | ); 59 | } 60 | 61 | const elapsed = Date.now() - start; 62 | setTimeout(emitLeaderboard, Math.max(0, 20000 - elapsed)); 63 | }; 64 | 65 | emitLeaderboard(); 66 | } catch (error) { 67 | logger.error(this.logPrefix + "Emit leaderboard error: " + error); 68 | } 69 | }; 70 | } 71 | 72 | export default LeaderboardSocketListener; 73 | -------------------------------------------------------------------------------- /src/modules/example/example.router.ts: -------------------------------------------------------------------------------- 1 | import actionHandler from "@/middleware/action-handler"; 2 | import checkPermissions from "@/middleware/check-permissions"; 3 | import validateSchema from "@/middleware/validate-schema"; 4 | import { ROLE } from "@/modules/user/user.constant"; 5 | import { BaseRouter } from "@/utils/base"; 6 | import * as mapProperty from "@/utils/interfaces"; 7 | import * as validations from "@/utils/validations"; 8 | 9 | import { 10 | CreateExampleSchema, 11 | ExampleController, 12 | UpdateExampleSchema, 13 | } from "./"; 14 | import * as mapPropertyExample from "./example.interface"; 15 | 16 | export default class ExampleRouter extends BaseRouter { 17 | private exampleController: ExampleController; 18 | 19 | constructor() { 20 | super(); 21 | 22 | this.exampleController = new ExampleController(); 23 | this.routes(); 24 | } 25 | 26 | public routes(): void { 27 | this.router.get( 28 | "/", 29 | checkPermissions(), 30 | actionHandler(this.exampleController.getAll) 31 | ); 32 | 33 | this.router.post( 34 | "/", 35 | checkPermissions({ roles: [ROLE.ADMIN] }), 36 | validateSchema(CreateExampleSchema, mapProperty.getBody), 37 | actionHandler(this.exampleController.create, mapProperty.getBody) 38 | ); 39 | 40 | this.router.get( 41 | "/name/:name", 42 | checkPermissions(), 43 | actionHandler( 44 | this.exampleController.getByName, 45 | mapProperty.getNameFromParam 46 | ) 47 | ); 48 | 49 | this.router.get( 50 | "/:id", 51 | checkPermissions(), 52 | validateSchema( 53 | validations.byId, 54 | mapPropertyExample.getIdFromParamsWithMe 55 | ), 56 | validateSchema(CreateExampleSchema, mapProperty.getBody), 57 | actionHandler( 58 | this.exampleController.getById, 59 | mapPropertyExample.getIdFromParamsWithMe 60 | ) 61 | ); 62 | 63 | this.router.put( 64 | "/:name", 65 | checkPermissions({ roles: [ROLE.ADMIN] }), 66 | validateSchema(UpdateExampleSchema, mapProperty.getBody), 67 | actionHandler(this.exampleController.update, [ 68 | mapProperty.getNameFromParam, 69 | mapProperty.getBody, 70 | ]) 71 | ); 72 | 73 | this.router.delete( 74 | "/:name", 75 | checkPermissions({ roles: [ROLE.ADMIN] }), 76 | actionHandler(this.exampleController.delete, mapProperty.getNameFromParam) 77 | ); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/constant/socket.ts: -------------------------------------------------------------------------------- 1 | import { Types } from "mongoose"; 2 | 3 | import { IChatEmitHistory } from "@/modules/chat-history"; 4 | import { 5 | IBetType, 6 | ICrashGameModel, 7 | IFormattedGameHistoryType, 8 | IPendingBetType, 9 | TFormattedPlayerBetType, 10 | } from "@/modules/crash-game"; 11 | import { TChatUser, TLeaderboardUserType } from "@/modules/user/user.types"; 12 | 13 | export interface IClientToServerEvents { 14 | hello: () => void; 15 | 16 | // //conflipgame Events 17 | // "game-creation-error": (message: string) => void; 18 | // "new-coinflip-game": (gameData: any) => void; 19 | // "coinflipgame-join-success": () => void; 20 | // "coinflipgame-joined": (data: { 21 | // _id: string; 22 | // newPlayer: ICoinPlayer; 23 | // }) => void; 24 | // "coinflipgame-rolling": (data: { 25 | // game_id: string; 26 | // animation_time: number; 27 | // }) => void; 28 | // "coinflipgame-rolled": ({ 29 | // _id, 30 | // randomModule, 31 | // coinflipResult, 32 | // isEarn, 33 | // }: { 34 | // _id: string; 35 | // randomModule: number; 36 | // coinflipResult: boolean[]; 37 | // isEarn: boolean; 38 | // }) => void; 39 | // "game-called-bot": (data: { _id: string; playerId: string }) => void; 40 | 41 | // //minesgame events 42 | // "created-mines-game": (data: number[]) => void; 43 | // "minesgame-rolled": (data: boolean) => void; 44 | // "minesgame-ended": (data: { 45 | // winAmount: number | null; 46 | // mines: number[]; 47 | // }) => void; 48 | 49 | //chat 50 | message: (data: { 51 | _id: Types.ObjectId; 52 | user: TChatUser; 53 | message: string; 54 | sentAt: Date; 55 | }) => void; 56 | "send-chat-history": (data: { 57 | message: string; 58 | chatHistories: IChatEmitHistory[]; 59 | }) => void; 60 | 61 | //leaderboard 62 | "leaderboard-fetch-all": (data: { 63 | message: string; 64 | leaderboard: { [key: string]: TLeaderboardUserType[] }; 65 | }) => void; 66 | // 'leaderboard-bet-update': (data: { game: string; updateData: PendingBetType[] }) => void; 67 | // 'leaderboard-win-update': (data: { game: string; updateData: BetType }) => void; 68 | 69 | //dashboard 70 | "dashboard-fetch-all": (data: { 71 | message: string; 72 | dashboard: { [key: string]: TLeaderboardUserType[] }; 73 | }) => void; 74 | 75 | "dashboard-pnl": (data: { 76 | message: string; 77 | pnl: { [key: string]: number }; 78 | }) => void; 79 | } 80 | 81 | export interface ISocketData { 82 | lastAccess?: number; 83 | markedForDisconnect?: boolean; 84 | } 85 | -------------------------------------------------------------------------------- /src/utils/localizations/en.ts: -------------------------------------------------------------------------------- 1 | import ILocalization from "./localizations.interface"; 2 | 3 | const en: ILocalization = { 4 | ERRORS: { 5 | AUTH: { 6 | USE_NEW_TOKEN: "Auth change need use new token", 7 | }, 8 | USER: { 9 | NOT_EXIST: "User does not exist", 10 | USER_ALREADY_EXIST: "User already exists", 11 | USER_NOT_CREATED: "User not created", 12 | USERNAME_OR_PASSWORD_INVALID: "Username or password invalid", 13 | SIGN_WALLETADDRESS_INCORRECT: "Wallet address invalid", 14 | SIGN_WALLETINFO_INCORRECT: "Wallet signature invalid", 15 | UPDATE_USER_INFO: "Please update your info", 16 | OLD_PASSWORD_INVALID: "Current password invalid", 17 | }, 18 | OTHER: { 19 | EMAIL_WITH_CODE_NOT_SENDED: "Email with code not sended", 20 | ERROR_WITH_SEND_MESSAGE: "Message not sent", 21 | ITEM_NOT_FOUND: "Item not found", 22 | ID_IS_INVALID: "Id is invalid", 23 | CODE_IS_INVALID: "Code is not valid", 24 | UNAUTHORIZED: "Unauthorized", 25 | FORBIDDEN: "Forbidden", 26 | NOT_FOUND_ENDPOINT: "Not Found! Wrong api endpoint", 27 | CONFLICT: "Conflict", 28 | DUPLICATE_ITEM: "Duplicate Item!", 29 | SOMETHING_WENT_WRONG: "Something went wrong", 30 | REFRESH_TOKEN_INVALID: "Refresh token not valid", 31 | PASSWORD_ERROR: 32 | "The password must contain 1 lowercase and uppercase letter, 1 number and be more than 8 characters", 33 | NO_WALLET: "Wallet is required", 34 | USER_PLAN_AND_PROGRESS_UPDATE_FAILED: 35 | "User plan and progress update failed", 36 | }, 37 | PAYMENT: { 38 | GET_CUSTOMER: "Get customer error", 39 | REPLACE_CUSTOMER: "Replace customer error", 40 | CREATE_CARD_TOKEN: "Create card token error", 41 | CREATE_CARD: "Create card error", 42 | CREATE_SUBSCRIBE: "Create subscribe error", 43 | GET_CUSTOMER_INFO: "Get customer info error", 44 | GET_SUBSCRIBE: "Get subscribe error", 45 | DELETE_SUBSCRIBE: "Delete subscribe error", 46 | SUBSCRIBE_NOT_FOUND: "Subscribe not found", 47 | PERMISSION_DENIED: "Permission denied", 48 | USER_CARDS_EMPTY: "No Card on File", 49 | GET_CARD: "Get cards by user error", 50 | USER_ALREADY_HAVE_SUBSCRIBE: "User have subscribe", 51 | USER_ALREADY_HAVE_CARD: "User have card", 52 | CREATE_CUSTOMER: "Create customer error", 53 | }, 54 | PAYMENT_WALLET: { 55 | GET: "Get wallet info", 56 | UPDATE_ADDRESS: "Update wallet address", 57 | ADD_WALLET: "Add wallet", 58 | STATUS: "Get wallet status", 59 | }, 60 | }, 61 | }; 62 | 63 | export default en; 64 | -------------------------------------------------------------------------------- /src/modules/user/swagger/user.schema.ts: -------------------------------------------------------------------------------- 1 | import * as UserConstant from "../user.constant"; 2 | 3 | const user = { 4 | name: { type: "string", required: true }, 5 | role: { 6 | type: "string", 7 | enum: UserConstant.ROLE, 8 | default: UserConstant.ROLE.MEMBER, 9 | required: false, 10 | }, 11 | }; 12 | 13 | const additionUser = { 14 | avatar: { 15 | type: "string", 16 | description: "avatar name file in store", 17 | required: true, 18 | }, 19 | role: { 20 | type: "string", 21 | enum: Object.keys(UserConstant.ROLE), 22 | default: UserConstant.ROLE.MEMBER, 23 | }, 24 | status: { 25 | type: "string", 26 | enum: Object.keys(UserConstant.STATUS), 27 | default: UserConstant.STATUS.NOT_VERIFIED, 28 | }, 29 | }; 30 | 31 | export const permissionsSchema = { 32 | type: "object", 33 | properties: { 34 | roles: { 35 | type: "array", 36 | items: { 37 | type: "string", 38 | enum: Object.keys(UserConstant.ROLE), 39 | }, 40 | example: Object.keys(UserConstant.ROLE), 41 | required: true, 42 | }, 43 | statusArr: { 44 | type: "array", 45 | items: { 46 | type: "string", 47 | enum: Object.keys(UserConstant.STATUS), 48 | }, 49 | example: Object.keys(UserConstant.STATUS), 50 | required: true, 51 | }, 52 | }, 53 | }; 54 | 55 | export const shortUserSchemaWithoutPassword = { 56 | type: "object", 57 | properties: { 58 | _id: { type: "string", required: true }, 59 | ...user, 60 | status: { 61 | type: "string", 62 | enum: Object.keys(UserConstant.STATUS), 63 | default: UserConstant.STATUS.VERIFIED, 64 | }, 65 | }, 66 | }; 67 | 68 | export const shortUserSchema = { 69 | type: "object", 70 | properties: { 71 | _id: { type: "string", required: true }, 72 | ...user, 73 | password: { type: "string", required: true }, 74 | }, 75 | }; 76 | 77 | export const fullUserSchema = { 78 | type: "object", 79 | properties: { 80 | _id: { type: "string", required: true }, 81 | ...user, 82 | ...additionUser, 83 | planId: { type: "string", description: "current plan id" }, 84 | avatarUrl: { 85 | type: "string", 86 | description: "url for getting avatar in store", 87 | }, 88 | currentDay: { 89 | type: "number", 90 | description: "day progress for user", 91 | required: true, 92 | }, 93 | userAnswers: { 94 | type: "array", 95 | items: { 96 | type: "object", 97 | }, 98 | }, 99 | }, 100 | }; 101 | -------------------------------------------------------------------------------- /src/config/index.ts: -------------------------------------------------------------------------------- 1 | export enum ENVS { 2 | Production = "production", 3 | Development = "development", 4 | Local = "local", 5 | } 6 | 7 | // Load environment variables 8 | export const IS_PRODUCTION: boolean = process.env.NODE_ENV === ENVS.Production; 9 | 10 | export const BASE_URL: string = process.env.BASE_URL; 11 | export const PORT: number = parseInt(process.env.PORT); 12 | export const ALLOW_HOSTS: Array = process.env.ALLOW_HOSTS?.split(","); 13 | export const SOCKET_ALLOW_HOSTS: Array = 14 | process.env.SOCKET_ALLOW_HOSTS?.split(","); 15 | export const FILE_FOLDER: string = "files"; 16 | 17 | export const ENABLE_MAINTENANCE_ONSTART: boolean = 18 | process.env.ENABLE_MAINTENANCE_ONSTART === "true"; 19 | export const ENABLE_LOGIN_ONSTART: boolean = 20 | process.env.ENABLE_LOGIN_ONSTART === "true"; 21 | 22 | // Database configs 23 | export const MONGO_URL: string = process.env.MONGO_URL; 24 | 25 | // Security configs 26 | export const TOKEN_SECRET: string = process.env.TOKEN_SECRET; 27 | export const REFRESH_TOKEN_SECRET: string = process.env.TOKEN_SECRET; 28 | export const TOKEN_LIFE: number | string = process.env.TOKEN_LIFE; 29 | export const ACCESS_TOKEN_LIFE = Number(process.env.ACCESS_TOKEN_LIFE); 30 | export const REFRESH_TOKEN_LIFE = Number(process.env.REFRESH_TOKEN_LIFE); 31 | 32 | export const SECURITY_CRYPTO_ENC_KEY: string = 33 | process.env.SECURITY_CRYPTO_ENC_KEY; 34 | export const SECURITY_CRYPTO_SEC_KEY: string = 35 | process.env.SECURITY_CRYPTO_SEC_KEY; 36 | 37 | // Email service configs 38 | export const SENDGRID_API_KEY: string = process.env.SENDGRID_API_KEY; 39 | export const SENDGRID_SENDER: string = process.env.SENDGRID_SENDER; 40 | export const SENDGRID_SUPPORT: string = 41 | process.env.SENDGRID_SUPPORT || SENDGRID_SENDER; 42 | export const SMTP_APP_PASSWORD: string = process.env.SMTP_APP_PASSWORD; 43 | export const SMTP_APP_USER: string = process.env.SMTP_APP_USER; 44 | 45 | export const POSTMARK_SERVER_TOKEN: string = process.env.POSTMARK_SERVER_TOKEN; 46 | 47 | // Blockchain configs 48 | export const BLOCKCHAIN_HTTPPROVIDER_API: string = 49 | process.env.BLOCKCHAIN_HTTPPROVIDER_API; 50 | export const IS_MAINNET: boolean = 51 | process.env.BLOCKCHAIN_KUJIRA_NETWORK === "mainnet"; 52 | 53 | // Socket.io configs 54 | 55 | // Site configs 56 | export const ALLOW_GAME_LIST: Array = 57 | process.env.ALLOW_GAME_LIST?.split(",") ?? []; 58 | export const SITE_USER_ID: string = process.env.REVENUE_ID; 59 | 60 | // Admin configs 61 | export const ADMIN_WALLET_ADDRESS: string = process.env.ADMIN_WALLET_ADDRESS; 62 | export const ADMIN_WALLET_MNEMONIC: string = process.env.ADMIN_WALLET_MNEMONIC; 63 | 64 | export default process.env; 65 | -------------------------------------------------------------------------------- /src/utils/encryption/rsa-wrapper.ts: -------------------------------------------------------------------------------- 1 | import * as crypto from "crypto"; 2 | import * as fs from "fs"; 3 | import NodeRSA from "node-rsa"; 4 | import * as path from "path"; 5 | 6 | class RSAPrivateWrapper { 7 | private static instance: RSAPrivateWrapper; 8 | 9 | private serverPub: Buffer; 10 | private serverPrivate: Buffer; 11 | private clientPub: Buffer; 12 | 13 | constructor() { 14 | this.serverPub = Buffer.alloc(256); 15 | this.serverPrivate = Buffer.alloc(256); 16 | this.clientPub = Buffer.alloc(256); 17 | 18 | const keysDir = path.resolve(__dirname, "../../"); 19 | this.initLoadServerKeys(keysDir); 20 | } 21 | 22 | public static getInstance(): RSAPrivateWrapper { 23 | if (!RSAPrivateWrapper.instance) { 24 | RSAPrivateWrapper.instance = new RSAPrivateWrapper(); 25 | } 26 | 27 | return RSAPrivateWrapper.instance; 28 | } 29 | 30 | public initLoadServerKeys(basePath: string): void { 31 | this.serverPub = fs.readFileSync( 32 | path.resolve(basePath, "keys", "server.public.pem") 33 | ); 34 | this.serverPrivate = fs.readFileSync( 35 | path.resolve(basePath, "keys", "server.private.pem") 36 | ); 37 | this.clientPub = fs.readFileSync( 38 | path.resolve(basePath, "keys", "client.public.pem") 39 | ); 40 | } 41 | 42 | public generate(direction: string): boolean { 43 | const key = new NodeRSA(); 44 | key.generateKeyPair(2048, 65537); 45 | const keysDir = path.resolve(__dirname, "../../keys"); 46 | 47 | if (!fs.existsSync(keysDir)) { 48 | fs.mkdirSync(keysDir, { recursive: true }); 49 | } 50 | 51 | fs.writeFileSync( 52 | path.resolve(__dirname, "../../keys", `${direction}.private.pem`), 53 | key.exportKey("pkcs8-private-pem") 54 | ); 55 | fs.writeFileSync( 56 | path.resolve(__dirname, "../../keys", `${direction}.public.pem`), 57 | key.exportKey("pkcs8-public-pem") 58 | ); 59 | 60 | return true; 61 | } 62 | 63 | public serverExampleEncrypt(): string { 64 | const enc = this.encrypt("Server init hello"); 65 | const dec = this.decrypt(enc); 66 | return dec; 67 | } 68 | 69 | public encrypt(message: string): string { 70 | const encrypted = crypto.publicEncrypt( 71 | { 72 | key: this.clientPub, 73 | padding: crypto.constants.RSA_PKCS1_OAEP_PADDING, 74 | }, 75 | Buffer.from(message) 76 | ); 77 | 78 | return encrypted.toString("base64"); 79 | } 80 | 81 | public decrypt(encryptedMessage: string): string { 82 | const decrypted = crypto.privateDecrypt( 83 | { 84 | key: this.serverPrivate, 85 | padding: crypto.constants.RSA_PKCS1_OAEP_PADDING, 86 | }, 87 | Buffer.from(encryptedMessage, "base64") 88 | ); 89 | 90 | return decrypted.toString(); 91 | } 92 | } 93 | 94 | export default RSAPrivateWrapper; 95 | -------------------------------------------------------------------------------- /src/utils/socket/throttler.ts: -------------------------------------------------------------------------------- 1 | // Import Dependencies 2 | import { Socket } from "socket.io"; 3 | 4 | import { 5 | IClientToServerEvents, 6 | IInterServerEvents, 7 | IServerToClientEvents, 8 | ISocketData, 9 | } from "@/constant/socket"; 10 | 11 | import logger from "../logger"; 12 | 13 | // Declare variables 14 | const TIME_LIMIT = 100; // How often the socket can emit an event (ms) 15 | 16 | // Define a type for the packet to improve type safety 17 | type PacketType = [string, ...any[]]; 18 | 19 | // Socket.io socket middleware 20 | const throttleConnections = 21 | ( 22 | socket: Socket< 23 | IClientToServerEvents, 24 | IServerToClientEvents, 25 | IInterServerEvents, 26 | ISocketData 27 | > 28 | ) => 29 | (packet: PacketType, next: (err?: any) => void) => { 30 | if (canBeServed(socket, packet)) { 31 | return next(); 32 | } else { 33 | return socket.emit( 34 | "notify-error", 35 | "Slow down! You must wait a while before doing that again." 36 | ); 37 | } 38 | }; 39 | 40 | // If socket connection can be served 41 | const canBeServed = ( 42 | socket: Socket< 43 | IClientToServerEvents, 44 | IServerToClientEvents, 45 | IInterServerEvents, 46 | ISocketData 47 | >, 48 | packet: PacketType 49 | ) => { 50 | // If socket is marked for disconnect, deny access 51 | if (socket.data.markedForDisconnect) { 52 | return false; 53 | } 54 | 55 | // Get last request timestamp 56 | const previous = socket.data.lastAccess; 57 | const now = Date.now(); 58 | 59 | // If socket had previous interaction 60 | if (previous) { 61 | // Get time difference 62 | const diff = now - previous; 63 | 64 | // If it was an auth packet 65 | if (packet[0] === "auth") { 66 | // Else add a last access timestamp and move on 67 | socket.data.lastAccess = now; 68 | return true; 69 | } 70 | 71 | // Check the time difference and disconnect if needed 72 | if (diff < TIME_LIMIT) { 73 | // Set socket as not serveable 74 | socket.data.markedForDisconnect = true; 75 | 76 | const clientIp = socket.handshake.headers["x-real-ip"]; 77 | 78 | // Timeout to disconnect the socket 79 | setTimeout(() => { 80 | logger.info( 81 | `Socket:: IP: ${clientIp} Packet: [${packet.toString()}] NSP: ${socket.nsp.name} Disconnected, reason: TOO_MANY_EMITS` 82 | ); 83 | logger.error( 84 | `Socket:: connection kicked due to too many emits in a short time. IP: ${clientIp}` 85 | ); 86 | // Disconnect the underlying connection 87 | socket.disconnect(true); 88 | }, 1000); 89 | 90 | // Deny access 91 | return false; 92 | } 93 | } 94 | 95 | // Else add a last access timestamp and move on 96 | socket.data.lastAccess = now; 97 | return true; 98 | }; 99 | 100 | // Export the functions 101 | export default throttleConnections; 102 | -------------------------------------------------------------------------------- /src/modules/example/example.controller.ts: -------------------------------------------------------------------------------- 1 | import { FilterQuery } from "mongoose"; 2 | 3 | import { CustomError } from "@/utils/helpers"; 4 | import * as localizations from "@/utils/localizations"; 5 | import ILocalization from "@/utils/localizations/localizations.interface"; 6 | 7 | import { ExampleService, IExample } from "./"; 8 | 9 | export class ExampleController { 10 | // Services 11 | private exampleService: ExampleService; 12 | 13 | // Diff services 14 | private localizations: ILocalization; 15 | 16 | constructor() { 17 | this.exampleService = new ExampleService(); 18 | 19 | this.localizations = localizations["en"]; 20 | } 21 | 22 | public getAll = async () => { 23 | const exampleFilter = >{}; 24 | const [item, count] = await Promise.all([ 25 | this.exampleService.get(exampleFilter), 26 | this.exampleService.getCount(exampleFilter), 27 | ]); 28 | 29 | return { 30 | item, 31 | count, 32 | }; 33 | }; 34 | 35 | public getByName = async (name) => { 36 | const example = await this.exampleService.getItem({ name }); 37 | 38 | // need add to localizations 39 | if (!example) { 40 | throw new CustomError(404, "Example not found"); 41 | } 42 | 43 | return example; 44 | }; 45 | 46 | public getById = async (exampleId) => { 47 | const example = await this.exampleService.getItemById(exampleId); 48 | 49 | // need add to localizations 50 | if (!example) { 51 | throw new CustomError(404, "Example not found"); 52 | } 53 | 54 | return example; 55 | }; 56 | 57 | public create = async (example) => { 58 | try { 59 | return await this.exampleService.create(example); 60 | } catch (error) { 61 | if (error.code === 11000) { 62 | throw new CustomError(409, this.localizations.ERRORS.OTHER.CONFLICT); 63 | } 64 | 65 | throw new Error(this.localizations.ERRORS.OTHER.SOMETHING_WENT_WRONG); 66 | } 67 | }; 68 | 69 | public update = async ({ id }, exampleData) => { 70 | try { 71 | const example = await this.exampleService.updateById(id, exampleData); 72 | 73 | // need add to localizations 74 | if (!example) { 75 | throw new CustomError(404, "Example not found"); 76 | } 77 | 78 | return example; 79 | } catch (error) { 80 | if (error.code === 11000) { 81 | throw new CustomError(409, this.localizations.ERRORS.OTHER.CONFLICT); 82 | } else if (error.status) { 83 | throw new CustomError(error.status, error.message); 84 | } else { 85 | throw new Error(this.localizations.ERRORS.OTHER.SOMETHING_WENT_WRONG); 86 | } 87 | } 88 | }; 89 | 90 | public delete = async ({ id }) => { 91 | const example = await this.exampleService.deleteById(id); 92 | 93 | // need add to localizations 94 | if (!example) { 95 | throw new CustomError(404, "Example not found"); 96 | } 97 | 98 | return example; 99 | }; 100 | } 101 | -------------------------------------------------------------------------------- /src/modules/crash-game/crash-game.controller.ts: -------------------------------------------------------------------------------- 1 | import { FilterQuery } from "mongoose"; 2 | 3 | import { CustomError } from "@/utils/helpers"; 4 | import * as localizations from "@/utils/localizations"; 5 | import ILocalization from "@/utils/localizations/localizations.interface"; 6 | 7 | import { CrashGameService, ICrashGameModel } from "."; 8 | 9 | export class CrashGameController { 10 | // Services 11 | private crashGameService: CrashGameService; 12 | 13 | // Diff services 14 | private localizations: ILocalization; 15 | 16 | constructor() { 17 | this.crashGameService = new CrashGameService(); 18 | 19 | this.localizations = localizations["en"]; 20 | } 21 | 22 | public getAll = async () => { 23 | const crashGameFilter = >{}; 24 | const [item, count] = await Promise.all([ 25 | this.crashGameService.get(crashGameFilter), 26 | this.crashGameService.getCount(crashGameFilter), 27 | ]); 28 | 29 | return { 30 | item, 31 | count, 32 | }; 33 | }; 34 | 35 | public getByName = async (name) => { 36 | const crashGame = await this.crashGameService.getItem({ name }); 37 | 38 | // need add to localizations 39 | if (!crashGame) { 40 | throw new CustomError(404, "Crash game not found"); 41 | } 42 | 43 | return crashGame; 44 | }; 45 | 46 | public getById = async (crashGameId) => { 47 | const crashGame = await this.crashGameService.getItemById(crashGameId); 48 | 49 | // need add to localizations 50 | if (!crashGame) { 51 | throw new CustomError(404, "Crash game not found"); 52 | } 53 | 54 | return crashGame; 55 | }; 56 | 57 | public create = async (crashGame) => { 58 | try { 59 | return await this.crashGameService.create(crashGame); 60 | } catch (error) { 61 | if (error.code === 11000) { 62 | throw new CustomError(409, this.localizations.ERRORS.OTHER.CONFLICT); 63 | } 64 | 65 | throw new Error(this.localizations.ERRORS.OTHER.SOMETHING_WENT_WRONG); 66 | } 67 | }; 68 | 69 | public update = async ({ id }, crashGameData) => { 70 | try { 71 | const crashGame = await this.crashGameService.updateById( 72 | id, 73 | crashGameData 74 | ); 75 | 76 | // need add to localizations 77 | if (!crashGame) { 78 | throw new CustomError(404, "Crash game not found"); 79 | } 80 | 81 | return crashGame; 82 | } catch (error) { 83 | if (error.code === 11000) { 84 | throw new CustomError(409, this.localizations.ERRORS.OTHER.CONFLICT); 85 | } else if (error.status) { 86 | throw new CustomError(error.status, error.message); 87 | } else { 88 | throw new Error(this.localizations.ERRORS.OTHER.SOMETHING_WENT_WRONG); 89 | } 90 | } 91 | }; 92 | 93 | public delete = async ({ id }) => { 94 | const crashGame = await this.crashGameService.deleteById(id); 95 | 96 | // need add to localizations 97 | if (!crashGame) { 98 | throw new CustomError(404, "Crash game not found"); 99 | } 100 | 101 | return crashGame; 102 | }; 103 | } 104 | -------------------------------------------------------------------------------- /src/modules/chat-history/chat-history.controller.ts: -------------------------------------------------------------------------------- 1 | import { FilterQuery } from "mongoose"; 2 | 3 | import { CustomError } from "@/utils/helpers"; 4 | import * as localizations from "@/utils/localizations"; 5 | import ILocalization from "@/utils/localizations/localizations.interface"; 6 | 7 | import { ChatHistoryService, IChatHistoryModel } from "."; 8 | 9 | export class ChatHistoryController { 10 | // Services 11 | private chatHistoryService: ChatHistoryService; 12 | 13 | // Diff services 14 | private localizations: ILocalization; 15 | 16 | constructor() { 17 | this.chatHistoryService = new ChatHistoryService(); 18 | 19 | this.localizations = localizations["en"]; 20 | } 21 | 22 | public getAll = async () => { 23 | const chatHistoryFilter = >{}; 24 | const [item, count] = await Promise.all([ 25 | this.chatHistoryService.get(chatHistoryFilter), 26 | this.chatHistoryService.getCount(chatHistoryFilter), 27 | ]); 28 | 29 | return { 30 | item, 31 | count, 32 | }; 33 | }; 34 | 35 | public getByName = async (name) => { 36 | const chatHistory = await this.chatHistoryService.getItem({ name }); 37 | 38 | // need add to localizations 39 | if (!chatHistory) { 40 | throw new CustomError(404, "ChatHistory not found"); 41 | } 42 | 43 | return chatHistory; 44 | }; 45 | 46 | public getById = async (chatHistoryId) => { 47 | const chatHistory = 48 | await this.chatHistoryService.getItemById(chatHistoryId); 49 | 50 | // need add to localizations 51 | if (!chatHistory) { 52 | throw new CustomError(404, "ChatHistory not found"); 53 | } 54 | 55 | return chatHistory; 56 | }; 57 | 58 | public create = async (chatHistory) => { 59 | try { 60 | return await this.chatHistoryService.create(chatHistory); 61 | } catch (error) { 62 | if (error.code === 11000) { 63 | throw new CustomError(409, this.localizations.ERRORS.OTHER.CONFLICT); 64 | } 65 | 66 | throw new Error(this.localizations.ERRORS.OTHER.SOMETHING_WENT_WRONG); 67 | } 68 | }; 69 | 70 | public update = async ({ id }, chatHistoryData) => { 71 | try { 72 | const chatHistory = await this.chatHistoryService.updateById( 73 | id, 74 | chatHistoryData 75 | ); 76 | 77 | // need add to localizations 78 | if (!chatHistory) { 79 | throw new CustomError(404, "ChatHistory not found"); 80 | } 81 | 82 | return chatHistory; 83 | } catch (error) { 84 | if (error.code === 11000) { 85 | throw new CustomError(409, this.localizations.ERRORS.OTHER.CONFLICT); 86 | } else if (error.status) { 87 | throw new CustomError(error.status, error.message); 88 | } else { 89 | throw new Error(this.localizations.ERRORS.OTHER.SOMETHING_WENT_WRONG); 90 | } 91 | } 92 | }; 93 | 94 | public delete = async ({ id }) => { 95 | const chatHistory = await this.chatHistoryService.deleteById(id); 96 | 97 | // need add to localizations 98 | if (!chatHistory) { 99 | throw new CustomError(404, "ChatHistory not found"); 100 | } 101 | 102 | return chatHistory; 103 | }; 104 | } 105 | -------------------------------------------------------------------------------- /src/modules/leaderboard/leaderboard.controller.ts: -------------------------------------------------------------------------------- 1 | import { FilterQuery } from "mongoose"; 2 | 3 | import { CustomError } from "@/utils/helpers"; 4 | import * as localizations from "@/utils/localizations"; 5 | import ILocalization from "@/utils/localizations/localizations.interface"; 6 | 7 | import { LeaderboardService } from "./leaderboard.service"; 8 | import { TLeaderboardDocumentType } from "./leaderboard.types"; 9 | 10 | export class LeaderboardController { 11 | // Services 12 | private leaderboardService: LeaderboardService; 13 | 14 | // Diff services 15 | private localizations: ILocalization; 16 | 17 | constructor() { 18 | this.leaderboardService = new LeaderboardService(); 19 | 20 | this.localizations = localizations["en"]; 21 | } 22 | 23 | public getAll = async () => { 24 | const leaderboardFilter = >{}; 25 | const [item, count] = await Promise.all([ 26 | this.leaderboardService.get(leaderboardFilter), 27 | this.leaderboardService.getCount(leaderboardFilter), 28 | ]); 29 | 30 | return { 31 | item, 32 | count, 33 | }; 34 | }; 35 | 36 | public getByName = async (name) => { 37 | const leaderboard = await this.leaderboardService.getItem({ name }); 38 | 39 | // need add to localizations 40 | if (!leaderboard) { 41 | throw new CustomError(404, "Leaderboard not found"); 42 | } 43 | 44 | return leaderboard; 45 | }; 46 | 47 | public getById = async (leaderboardId) => { 48 | const leaderboard = 49 | await this.leaderboardService.getItemById(leaderboardId); 50 | 51 | // need add to localizations 52 | if (!leaderboard) { 53 | throw new CustomError(404, "Leaderboard not found"); 54 | } 55 | 56 | return leaderboard; 57 | }; 58 | 59 | public create = async (leaderboard) => { 60 | try { 61 | return await this.leaderboardService.create(leaderboard); 62 | } catch (error) { 63 | if (error.code === 11000) { 64 | throw new CustomError(409, this.localizations.ERRORS.OTHER.CONFLICT); 65 | } 66 | 67 | throw new Error(this.localizations.ERRORS.OTHER.SOMETHING_WENT_WRONG); 68 | } 69 | }; 70 | 71 | public update = async ({ id }, leaderboardData) => { 72 | try { 73 | const leaderboard = await this.leaderboardService.updateById( 74 | id, 75 | leaderboardData 76 | ); 77 | 78 | // need add to localizations 79 | if (!leaderboard) { 80 | throw new CustomError(404, "Leaderboard not found"); 81 | } 82 | 83 | return leaderboard; 84 | } catch (error) { 85 | if (error.code === 11000) { 86 | throw new CustomError(409, this.localizations.ERRORS.OTHER.CONFLICT); 87 | } else if (error.status) { 88 | throw new CustomError(error.status, error.message); 89 | } else { 90 | throw new Error(this.localizations.ERRORS.OTHER.SOMETHING_WENT_WRONG); 91 | } 92 | } 93 | }; 94 | 95 | public delete = async ({ id }) => { 96 | const leaderboard = await this.leaderboardService.deleteById(id); 97 | 98 | // need add to localizations 99 | if (!leaderboard) { 100 | throw new CustomError(404, "Leaderboard not found"); 101 | } 102 | 103 | return leaderboard; 104 | }; 105 | } 106 | -------------------------------------------------------------------------------- /src/modules/auto-crash-bet/auto-crash-bet.controller.ts: -------------------------------------------------------------------------------- 1 | import { FilterQuery } from "mongoose"; 2 | 3 | import { CustomError } from "@/utils/helpers"; 4 | import * as localizations from "@/utils/localizations"; 5 | import ILocalization from "@/utils/localizations/localizations.interface"; 6 | 7 | import { AutoCrashBetService } from "."; 8 | import { IAutoCrashBetModel } from "./auto-crash-bet.interface"; 9 | 10 | export class AutoCrashBetController { 11 | // Services 12 | private autoCrashBetService: AutoCrashBetService; 13 | 14 | // Diff services 15 | private localizations: ILocalization; 16 | 17 | constructor() { 18 | this.autoCrashBetService = new AutoCrashBetService(); 19 | this.localizations = localizations["en"]; 20 | } 21 | 22 | public getAll = async () => { 23 | const autoCrashBetFilter = >{}; 24 | const [item, count] = await Promise.all([ 25 | this.autoCrashBetService.get(autoCrashBetFilter), 26 | this.autoCrashBetService.getCount(autoCrashBetFilter), 27 | ]); 28 | 29 | return { 30 | item, 31 | count, 32 | }; 33 | }; 34 | 35 | public getByName = async (name) => { 36 | const autoCrashBet = await this.autoCrashBetService.getItem({ name }); 37 | 38 | // need add to localizations 39 | if (!autoCrashBet) { 40 | throw new CustomError(404, "AutoCrashBet not found"); 41 | } 42 | 43 | return autoCrashBet; 44 | }; 45 | 46 | public getById = async (autoCrashBetId) => { 47 | const autoCrashBet = 48 | await this.autoCrashBetService.getItemById(autoCrashBetId); 49 | 50 | // need add to localizations 51 | if (!autoCrashBet) { 52 | throw new CustomError(404, "AutoCrashBet not found"); 53 | } 54 | 55 | return autoCrashBet; 56 | }; 57 | 58 | public create = async (autoCrashBet) => { 59 | try { 60 | return await this.autoCrashBetService.create(autoCrashBet); 61 | } catch (error) { 62 | if (error.code === 11000) { 63 | throw new CustomError(409, this.localizations.ERRORS.OTHER.CONFLICT); 64 | } 65 | 66 | throw new Error(this.localizations.ERRORS.OTHER.SOMETHING_WENT_WRONG); 67 | } 68 | }; 69 | 70 | public update = async ({ id }, autoCrashBetData) => { 71 | try { 72 | const autoCrashBet = await this.autoCrashBetService.updateById( 73 | id, 74 | autoCrashBetData 75 | ); 76 | 77 | // need add to localizations 78 | if (!autoCrashBet) { 79 | throw new CustomError(404, "AutoCrashBet not found"); 80 | } 81 | 82 | return autoCrashBet; 83 | } catch (error) { 84 | if (error.code === 11000) { 85 | throw new CustomError(409, this.localizations.ERRORS.OTHER.CONFLICT); 86 | } else if (error.status) { 87 | throw new CustomError(error.status, error.message); 88 | } else { 89 | throw new Error(this.localizations.ERRORS.OTHER.SOMETHING_WENT_WRONG); 90 | } 91 | } 92 | }; 93 | 94 | public delete = async ({ id }) => { 95 | const autoCrashBet = await this.autoCrashBetService.deleteById(id); 96 | 97 | // need add to localizations 98 | if (!autoCrashBet) { 99 | throw new CustomError(404, "AutoCrashBet not found"); 100 | } 101 | 102 | return autoCrashBet; 103 | }; 104 | } 105 | -------------------------------------------------------------------------------- /src/modules/site-transaction/site-transaction.controller.ts: -------------------------------------------------------------------------------- 1 | import { FilterQuery } from "mongoose"; 2 | 3 | import { CustomError } from "@/utils/helpers"; 4 | import * as localizations from "@/utils/localizations"; 5 | import ILocalization from "@/utils/localizations/localizations.interface"; 6 | 7 | import { ISiteTransactionModel } from "./site-transaction.interface"; 8 | import { SiteTransactionService } from "./site-transaction.service"; 9 | 10 | export class SiteTransactionController { 11 | // Services 12 | private siteTransactionService: SiteTransactionService; 13 | 14 | // Diff services 15 | private localizations: ILocalization; 16 | 17 | constructor() { 18 | this.siteTransactionService = new SiteTransactionService(); 19 | 20 | this.localizations = localizations["en"]; 21 | } 22 | 23 | public getAll = async () => { 24 | const siteTransactionFilter = >{}; 25 | const [item, count] = await Promise.all([ 26 | this.siteTransactionService.get(siteTransactionFilter), 27 | this.siteTransactionService.getCount(siteTransactionFilter), 28 | ]); 29 | 30 | return { 31 | item, 32 | count, 33 | }; 34 | }; 35 | 36 | public getByName = async (name) => { 37 | const siteTransaction = await this.siteTransactionService.getItem({ 38 | name, 39 | }); 40 | 41 | // need add to localizations 42 | if (!siteTransaction) { 43 | throw new CustomError(404, "SiteTransaction not found"); 44 | } 45 | 46 | return siteTransaction; 47 | }; 48 | 49 | public getById = async (siteTransactionId) => { 50 | const siteTransaction = 51 | await this.siteTransactionService.getItemById(siteTransactionId); 52 | 53 | // need add to localizations 54 | if (!siteTransaction) { 55 | throw new CustomError(404, "SiteTransaction not found"); 56 | } 57 | 58 | return siteTransaction; 59 | }; 60 | 61 | public create = async (siteTransaction) => { 62 | try { 63 | return await this.siteTransactionService.create(siteTransaction); 64 | } catch (error) { 65 | if (error.code === 11000) { 66 | throw new CustomError(409, this.localizations.ERRORS.OTHER.CONFLICT); 67 | } 68 | 69 | throw new Error(this.localizations.ERRORS.OTHER.SOMETHING_WENT_WRONG); 70 | } 71 | }; 72 | 73 | public update = async ({ id }, siteTransactionData) => { 74 | try { 75 | const siteTransaction = await this.siteTransactionService.updateById( 76 | id, 77 | siteTransactionData 78 | ); 79 | 80 | // need add to localizations 81 | if (!siteTransaction) { 82 | throw new CustomError(404, "SiteTransaction not found"); 83 | } 84 | 85 | return siteTransaction; 86 | } catch (error) { 87 | if (error.code === 11000) { 88 | throw new CustomError(409, this.localizations.ERRORS.OTHER.CONFLICT); 89 | } else if (error.status) { 90 | throw new CustomError(error.status, error.message); 91 | } else { 92 | throw new Error(this.localizations.ERRORS.OTHER.SOMETHING_WENT_WRONG); 93 | } 94 | } 95 | }; 96 | 97 | public delete = async ({ id }) => { 98 | const siteTransaction = await this.siteTransactionService.deleteById(id); 99 | 100 | // need add to localizations 101 | if (!siteTransaction) { 102 | throw new CustomError(404, "SiteTransaction not found"); 103 | } 104 | 105 | return siteTransaction; 106 | }; 107 | } 108 | -------------------------------------------------------------------------------- /src/cron/crons/dashboard-update/dashboard-update.ts: -------------------------------------------------------------------------------- 1 | import Cron, { ScheduleOptions } from "node-cron"; 2 | 3 | import { ALLOW_GAME_LIST } from "@/config"; 4 | import { CGAME_LIST } from "@/constant/game"; 5 | import { BaseCron } from "@/cron/crons/base.cron"; 6 | import { 7 | DashboardService, 8 | ERevenueType, 9 | IDashboardModel, 10 | } from "@/modules/dashboard"; 11 | import UserService from "@/modules/user/user.service"; 12 | 13 | export class DashboardUpdate extends BaseCron { 14 | private dashboardService = new DashboardService(); 15 | private userService = new UserService(); 16 | 17 | constructor(cronExpression: string, option = {}) { 18 | super(cronExpression, option); 19 | this.start(); 20 | } 21 | 22 | public start = () => { 23 | this.initCron(); 24 | }; 25 | 26 | private initCron = () => { 27 | this.task = Cron.schedule( 28 | this.cronExpression, 29 | async () => { 30 | await this.catchWrapper( 31 | this.updateDashboardStatus, 32 | "updateDashboardStatus" 33 | ); 34 | }, 35 | this.option 36 | ); 37 | }; 38 | 39 | private updateDashboardStatus = async () => { 40 | try { 41 | console.log("Start updateDashboardStatus"); 42 | await this.fetchAndSaveRevenulogData(); 43 | } catch (error) { 44 | console.error("Error in updateDashboardStatus:", error); 45 | } 46 | }; 47 | 48 | private fetchAndSaveRevenulogData = async () => { 49 | try { 50 | const insertDate = new Date(); 51 | insertDate.setSeconds(0); 52 | insertDate.setMilliseconds(0); 53 | const siteUserData = await this.userService.getSiteUserData(); 54 | const dashboardPayloads: IDashboardModel[] = []; 55 | 56 | const createPayload = ( 57 | revenueType: ERevenueType, 58 | denom: string, 59 | lastBalance: number 60 | ) => 61 | ({ 62 | revenueType, 63 | denom, 64 | lastBalance, 65 | insertDate, 66 | }) as IDashboardModel; 67 | 68 | for (const allowGame of ALLOW_GAME_LIST) { 69 | let gameDashboardBalance = { usk: 0, kart: 0 }; 70 | let gameType: ERevenueType; 71 | 72 | if (allowGame === CGAME_LIST.crash) { 73 | gameType = ERevenueType.CRASH; 74 | gameDashboardBalance = { 75 | usk: siteUserData.leaderboard?.["crash"]?.["usk"]?.winAmount || 0, 76 | kart: siteUserData.leaderboard?.["crash"]?.["kart"]?.winAmount || 0, 77 | }; 78 | } 79 | 80 | dashboardPayloads.push( 81 | createPayload(gameType, "usk", gameDashboardBalance.usk) 82 | ); 83 | dashboardPayloads.push( 84 | createPayload(gameType, "kart", gameDashboardBalance.kart) 85 | ); 86 | } 87 | 88 | dashboardPayloads.push( 89 | createPayload( 90 | ERevenueType.TOTAL, 91 | "usk", 92 | siteUserData.wallet?.["usk"] || 0 93 | ) 94 | ); 95 | dashboardPayloads.push( 96 | createPayload( 97 | ERevenueType.TOTAL, 98 | "kart", 99 | siteUserData.wallet?.["kart"] || 0 100 | ) 101 | ); 102 | 103 | for (const payload of dashboardPayloads) { 104 | await new Promise((resolve) => setTimeout(resolve, 300)); 105 | await this.dashboardService.create(payload); 106 | } 107 | } catch (error) { 108 | console.error("Error fetching or saving revenulog data:", error); 109 | } 110 | }; 111 | } 112 | -------------------------------------------------------------------------------- /src/utils/customer/vip.ts: -------------------------------------------------------------------------------- 1 | // Import Dependencies 2 | import { CCustomerVipConfig } from "@/constant/customer"; 3 | import UserService from "@/modules/user/user.service"; 4 | import { IVIPLevelType } from "@/modules/user/user.types"; 5 | import UserBotService from "@/modules/user-bot/user-bot.service"; 6 | 7 | import logger from "../logger"; 8 | 9 | const { 10 | numLevels, 11 | minWager, 12 | maxWager, 13 | rakeback, 14 | vipLevelNAME, 15 | vipLevelCOLORS, 16 | } = CCustomerVipConfig; 17 | 18 | // Generate VIP Levels 19 | const generateVIPLevels = ( 20 | numLevels: number, 21 | minWager: number, 22 | maxWager: number, 23 | rakeback: number, 24 | levelNames: string[], 25 | levelColors: string[] 26 | ) => { 27 | const levels = []; 28 | 29 | for (let i = 0; i < numLevels; i++) { 30 | const level = { 31 | name: (i + 1).toString(), 32 | wagerNeeded: parseFloat( 33 | (minWager + (maxWager - minWager) * Math.pow(i / numLevels, 2)).toFixed( 34 | 2 35 | ) 36 | ), 37 | rakebackPercentage: parseFloat( 38 | (rakeback / (1 + Math.exp(-5 * (i / numLevels - 0.5)))).toFixed(2) 39 | ), 40 | levelName: levelNames[Math.floor((i * levelNames.length) / numLevels)], 41 | levelColor: levelColors[Math.floor((i * levelColors.length) / numLevels)], 42 | }; 43 | levels.push(level); 44 | } 45 | 46 | return levels; 47 | }; 48 | 49 | const vipLevels = generateVIPLevels( 50 | numLevels, 51 | minWager, 52 | maxWager, 53 | rakeback, 54 | vipLevelNAME, 55 | vipLevelCOLORS 56 | ); 57 | 58 | // Get user VIP level 59 | const getVipLevelFromWager = (wager: number): IVIPLevelType => { 60 | if (wager < vipLevels[1].wagerNeeded) { 61 | return vipLevels[0]; 62 | } else if (wager > vipLevels[numLevels - 1].wagerNeeded) { 63 | return vipLevels[numLevels - 1]; 64 | } else { 65 | return vipLevels 66 | .filter((level) => wager >= level.wagerNeeded) 67 | .sort((a, b) => b.wagerNeeded - a.wagerNeeded)[0]; 68 | } 69 | }; 70 | 71 | // Get user next VIP level 72 | const getNextVipLevelFromWager = (wager: number) => { 73 | return vipLevels 74 | .filter((level) => wager < level.wagerNeeded) 75 | .sort((a, b) => a.wagerNeeded - b.wagerNeeded)[0]; 76 | }; 77 | 78 | // Check if user is eligible for rakeback 79 | const checkAndApplyRakeback = async ( 80 | userId: string, 81 | _houseRake: number 82 | ): Promise => { 83 | try { 84 | const userService = new UserService(); 85 | const userBotService = new UserBotService(); 86 | const usero = await userBotService.getItemById(userId); 87 | 88 | if (usero) { 89 | // Skip rakeback calculation for excluded users 90 | return; 91 | } 92 | 93 | const user = await userService.getItemById(userId); 94 | 95 | if (!user) { 96 | // User not found 97 | return; 98 | } 99 | 100 | // Find the corresponding level 101 | // @TO-DO I will update this to use the new VIP level system 102 | // const currentLevel = getVipLevelFromWager(user.wager); 103 | 104 | // Update document 105 | await userService.update( 106 | { _id: user._id }, 107 | { 108 | // $inc: { rakebackBalance: houseRake * (currentLevel.rakebackPercentage! / 100) }, 109 | } 110 | ); 111 | 112 | // Resolve to continue successfully 113 | } catch (error) { 114 | logger.error("[VIP]::: Error applying rakeback" + error); 115 | } 116 | }; 117 | 118 | // Export functions 119 | export { 120 | checkAndApplyRakeback, 121 | getNextVipLevelFromWager, 122 | getVipLevelFromWager, 123 | vipLevelCOLORS, 124 | vipLevelNAME, 125 | vipLevels, 126 | }; 127 | -------------------------------------------------------------------------------- /src/modules/trait/trait.controller.ts: -------------------------------------------------------------------------------- 1 | import { FilterQuery } from "mongoose"; 2 | 3 | import { CustomError } from "@/utils/helpers"; 4 | import * as localizations from "@/utils/localizations"; 5 | import ILocalization from "@/utils/localizations/localizations.interface"; 6 | 7 | 8 | import TraitService from "./trait.service"; 9 | import { ITraitModel } from "./trait.types"; 10 | 11 | export default class TraitController { 12 | private baseService: TraitService; 13 | 14 | private localizations: ILocalization; 15 | 16 | constructor() { 17 | this.baseService = new TraitService(); 18 | this.localizations = localizations["en"]; 19 | } 20 | 21 | public getAll = async () => { 22 | const traitFilter = >{}; 23 | const [item, count] = await Promise.all([ 24 | this.baseService.get(traitFilter), 25 | this.baseService.getCount(traitFilter), 26 | ]); 27 | 28 | return { 29 | item, 30 | count, 31 | }; 32 | }; 33 | 34 | public getByName = async (name) => { 35 | const trait = await this.baseService.getItem({ name }); 36 | 37 | // need add to localizations 38 | if (!trait) { 39 | throw new CustomError(404, "Trait not found"); 40 | } 41 | 42 | return trait; 43 | }; 44 | 45 | public getById = async (traitId) => { 46 | const trait = await this.baseService.getItemById(traitId); 47 | 48 | // need add to localizations 49 | if (!trait) { 50 | throw new CustomError(404, "Trait not found"); 51 | } 52 | 53 | return trait; 54 | }; 55 | 56 | public create = async (trait) => { 57 | try { 58 | return await this.baseService.create(trait); 59 | } catch (error) { 60 | if (error.code === 11000) { 61 | throw new CustomError(409, this.localizations.ERRORS.OTHER.CONFLICT); 62 | } 63 | 64 | throw new Error(this.localizations.ERRORS.OTHER.SOMETHING_WENT_WRONG); 65 | } 66 | }; 67 | 68 | public update = async ({ id }, traitData) => { 69 | try { 70 | const trait = await this.baseService.updateById(id, traitData); 71 | 72 | // need add to localizations 73 | if (!trait) { 74 | throw new CustomError(404, "Trait not found"); 75 | } 76 | 77 | return trait; 78 | } catch (error) { 79 | if (error.code === 11000) { 80 | throw new CustomError(409, this.localizations.ERRORS.OTHER.CONFLICT); 81 | } else if (error.status) { 82 | throw new CustomError(error.status, error.message); 83 | } else { 84 | throw new Error(this.localizations.ERRORS.OTHER.SOMETHING_WENT_WRONG); 85 | } 86 | } 87 | }; 88 | 89 | public delete = async ({ id }) => { 90 | const trait = await this.baseService.deleteById(id); 91 | 92 | // need add to localizations 93 | if (!trait) { 94 | throw new CustomError(404, "Trait not found"); 95 | } 96 | 97 | return trait; 98 | }; 99 | 100 | public getKartCurrency = async () => { 101 | const currencyTrait = await this.baseService.getItem({ 102 | key: CKartTraits.getCurrency.key, 103 | name: CKartTraits.getCurrency.name, 104 | }); 105 | let currency = currencyTrait?.value; 106 | 107 | if (!currency) { 108 | currency = await this.baseService.getKartCurrencyWithApi(); 109 | await this.baseService.create({ 110 | key: CKartTraits.getCurrency.key, 111 | name: CKartTraits.getCurrency.name, 112 | value: currency, 113 | }); 114 | } 115 | 116 | return { value: currency }; 117 | }; 118 | 119 | public getKartTotalStake = async () => { 120 | const totalStakeTrait = await this.baseService.getItem({ 121 | key: CKartTraits.getTotalStake.key, 122 | name: CKartTraits.getTotalStake.name, 123 | }); 124 | const totalStake = totalStakeTrait?.value ?? 0; 125 | return { value: Number(totalStake).valueOf() }; 126 | }; 127 | } 128 | -------------------------------------------------------------------------------- /src/modules/user-bot/user-bot.controller.ts: -------------------------------------------------------------------------------- 1 | import bcrypt from "bcryptjs"; 2 | import { FilterQuery, ObjectId } from "mongoose"; 3 | 4 | import { IAuthInfo } from "@/modules/auth/auth.types"; 5 | import { CustomError, getPaginationInfo } from "@/utils/helpers"; 6 | import * as localizations from "@/utils/localizations"; 7 | import ILocalization from "@/utils/localizations/localizations.interface"; 8 | 9 | import { ROLE, STATUS } from "./user-bot.constant"; 10 | import UserBotService from "./user-bot.service"; 11 | import { IUser } from "./user-bot.types"; 12 | 13 | export default class UserBotController { 14 | private userBotService: UserBotService; 15 | 16 | private localizations: ILocalization; 17 | 18 | constructor() { 19 | this.userBotService = new UserBotService(); 20 | this.localizations = localizations["en"]; 21 | } 22 | 23 | editUserForAdmin = async (id: string, user: IUser) => { 24 | const updatedUser = await this.userBotService.updateById(id, user, { 25 | password: 0, 26 | }); 27 | return { status: 200, payload: updatedUser }; 28 | }; 29 | 30 | editUser = async (user: IUser, { userId }: IAuthInfo) => { 31 | let updatedUser; 32 | 33 | try { 34 | if (user.password) { 35 | user.password = await bcrypt.hash(user.password, 10); 36 | } 37 | 38 | updatedUser = await this.userBotService.updateById(userId, user, { 39 | password: 0, 40 | }); 41 | } catch (error) { 42 | if (error.code === 11000 && error.keyPattern?.username === 1) { 43 | throw new CustomError(400, "This username is already registered"); 44 | } 45 | 46 | throw new CustomError(500, "Update user fail"); 47 | } 48 | 49 | return { status: 200, payload: updatedUser }; 50 | }; 51 | 52 | setVerifiedUser = (id: ObjectId) => { 53 | return this.userBotService.updateById( 54 | id, 55 | { status: STATUS.VERIFIED }, 56 | { password: 0 } 57 | ); 58 | }; 59 | 60 | getUsers = async ({ offset, limit, text }) => { 61 | const range = getPaginationInfo( 62 | { value: offset, default: 0 }, 63 | { value: limit, default: 10 } 64 | ); 65 | const filter: FilterQuery = {}; 66 | 67 | if (text) { 68 | const regexp = new RegExp(text.split(/ +/).join("| ?"), "i"); 69 | filter["$or"] = [{ name: { $regex: regexp } }]; 70 | } 71 | 72 | const [users, count] = await Promise.all([ 73 | this.userBotService.get(filter, {}, range), 74 | this.userBotService.getCount(filter), 75 | ]); 76 | 77 | return { 78 | items: users, 79 | count, 80 | }; 81 | }; 82 | 83 | getUserByToken = async ({ userId }: IAuthInfo) => { 84 | const user = this.userBotService.getItemById(userId); 85 | return { user }; 86 | }; 87 | 88 | getUserById = async (id: string, { userId, role }: IAuthInfo, _UTC) => { 89 | if (role === ROLE.ADMIN && id !== userId.toString()) { 90 | throw new CustomError(403, this.localizations.ERRORS.OTHER.FORBIDDEN); 91 | } 92 | 93 | const user = await this.userBotService.getItemById(id, { 94 | password: 0, 95 | }); 96 | 97 | if (!user) { 98 | throw new CustomError(404, this.localizations.ERRORS.USER.NOT_EXIST); 99 | } 100 | 101 | return { 102 | user: { 103 | ...user, 104 | }, 105 | }; 106 | }; 107 | 108 | deleteUser = async (id: string) => { 109 | await this.userBotService.deleteById(id); 110 | return { status: 204 }; 111 | }; 112 | 113 | getPermissions = async () => { 114 | return { 115 | roles: Object.keys(ROLE), 116 | statusArr: Object.keys(STATUS), 117 | }; 118 | }; 119 | 120 | private _diff(a1: Array, a2: Array) { 121 | return (>a1.filter((i) => !a2.includes(i))).concat( 122 | >a2.filter((i) => !a1.includes(i)) 123 | ); 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /src/modules/dashboard/dashboard.controller.ts: -------------------------------------------------------------------------------- 1 | import { FilterQuery } from "mongoose"; 2 | 3 | import { CustomError } from "@/utils/helpers"; 4 | import * as localizations from "@/utils/localizations"; 5 | import ILocalization from "@/utils/localizations/localizations.interface"; 6 | 7 | import { LeaderboardController, LeaderboardService } from "../leaderboard"; 8 | import TraitController from "../trait/trait.controller"; 9 | import { EFilterDate, ERevenueType } from "./dashboard.constant"; 10 | import { DashboardService } from "./dashboard.service"; 11 | import { TDashboardDocumentType } from "./dashboard.types"; 12 | 13 | export class DashboardController { 14 | // Services 15 | private dashboardService: DashboardService; 16 | private leaderboard: LeaderboardService; 17 | private leaderboardController: LeaderboardController; 18 | private traitController: TraitController; 19 | // Diff services 20 | private localizations: ILocalization; 21 | 22 | constructor() { 23 | this.dashboardService = new DashboardService(); 24 | this.leaderboard = new LeaderboardService(); 25 | this.leaderboardController = new LeaderboardController(); 26 | this.traitController = new TraitController(); 27 | 28 | this.localizations = localizations["en"]; 29 | } 30 | 31 | public getAll = async () => { 32 | const dashboardFilter = >{}; 33 | const [item, count] = await Promise.all([ 34 | this.dashboardService.get(dashboardFilter), 35 | this.dashboardService.getCount(dashboardFilter), 36 | ]); 37 | 38 | return { 39 | item, 40 | count, 41 | }; 42 | }; 43 | 44 | public getByName = async (name) => { 45 | const leaderboard = await this.dashboardService.getItem({ name }); 46 | 47 | // need add to localizations 48 | if (!leaderboard) { 49 | throw new CustomError(404, "Dashboard not found"); 50 | } 51 | 52 | return leaderboard; 53 | }; 54 | 55 | public getById = async (dashboardId) => { 56 | const dashboard = await this.dashboardService.getItemById(dashboardId); 57 | 58 | // need add to localizations 59 | if (!dashboard) { 60 | throw new CustomError(404, "Dashboard not found"); 61 | } 62 | 63 | return dashboard; 64 | }; 65 | 66 | public create = async (dashboard) => { 67 | try { 68 | return await this.dashboardService.create(dashboard); 69 | } catch (error) { 70 | if (error.code === 11000) { 71 | throw new CustomError(409, this.localizations.ERRORS.OTHER.CONFLICT); 72 | } 73 | 74 | throw new Error(this.localizations.ERRORS.OTHER.SOMETHING_WENT_WRONG); 75 | } 76 | }; 77 | 78 | public update = async ({ id }, dashboardData) => { 79 | try { 80 | const dashboard = await this.dashboardService.updateById( 81 | id, 82 | dashboardData 83 | ); 84 | 85 | // need add to localizations 86 | if (!dashboard) { 87 | throw new CustomError(404, "Dashboard not found"); 88 | } 89 | 90 | return dashboard; 91 | } catch (error) { 92 | if (error.code === 11000) { 93 | throw new CustomError(409, this.localizations.ERRORS.OTHER.CONFLICT); 94 | } else if (error.status) { 95 | throw new CustomError(error.status, error.message); 96 | } else { 97 | throw new Error(this.localizations.ERRORS.OTHER.SOMETHING_WENT_WRONG); 98 | } 99 | } 100 | }; 101 | 102 | public delete = async ({ id }) => { 103 | const dashboard = await this.dashboardService.deleteById(id); 104 | 105 | // need add to localizations 106 | if (!dashboard) { 107 | throw new CustomError(404, "Dashboard not found"); 108 | } 109 | 110 | return dashboard; 111 | }; 112 | 113 | public getDashboard = async (query: { 114 | date: EFilterDate; 115 | revenueType: ERevenueType; 116 | }) => { 117 | const kartCurrency = (await this.traitController.getKartCurrency()).value; 118 | return await this.dashboardService.getDashboardChart( 119 | query.date, 120 | query.revenueType, 121 | Number(kartCurrency) 122 | ); 123 | }; 124 | 125 | public getTopPlayers = async () => { 126 | const kartCurrency = (await this.traitController.getKartCurrency()).value; 127 | return await this.leaderboard.fetchTopPlayers(5, Number(kartCurrency)); 128 | }; 129 | } 130 | -------------------------------------------------------------------------------- /src/modules/user/user.controller.ts: -------------------------------------------------------------------------------- 1 | import bcrypt from "bcryptjs"; 2 | import { FilterQuery, ObjectId } from "mongoose"; 3 | 4 | import { IAuthInfo } from "@/modules/auth/auth.types"; 5 | import { CustomError, getPaginationInfo } from "@/utils/helpers"; 6 | import * as localizations from "@/utils/localizations"; 7 | import ILocalization from "@/utils/localizations/localizations.interface"; 8 | 9 | import { ROLE, STATUS } from "./user.constant"; 10 | import { IUserModel } from "./user.interface"; 11 | import UserService from "./user.service"; 12 | 13 | export default class UserController { 14 | private userService: UserService; 15 | 16 | private localizations: ILocalization; 17 | 18 | constructor() { 19 | this.userService = new UserService(); 20 | this.localizations = localizations["en"]; 21 | } 22 | 23 | editUserForAdmin = async (id: string, user: IUserModel) => { 24 | const updatedUser = await this.userService.updateById(id, user, { 25 | password: 0, 26 | }); 27 | return { status: 200, payload: updatedUser }; 28 | }; 29 | 30 | editUser = async (user: IUserModel, { userId }: IAuthInfo) => { 31 | let updatedUser; 32 | 33 | try { 34 | if (user.password) { 35 | user.password = await bcrypt.hash(user.password, 10); 36 | } 37 | 38 | updatedUser = await this.userService.updateById(userId, user, { 39 | password: 0, 40 | }); 41 | } catch (error) { 42 | if (error.code === 11000 && error.keyPattern?.username === 1) { 43 | throw new CustomError(400, "This username is already registered"); 44 | } 45 | 46 | throw new CustomError(500, "Update user fail"); 47 | } 48 | 49 | return { status: 200, payload: updatedUser }; 50 | }; 51 | 52 | setVerifiedUser = (id: ObjectId) => { 53 | return this.userService.updateById( 54 | id, 55 | { status: STATUS.VERIFIED }, 56 | { password: 0 } 57 | ); 58 | }; 59 | 60 | getUsers = async ({ offset, limit, text }) => { 61 | const range = getPaginationInfo( 62 | { value: offset, default: 0 }, 63 | { value: limit, default: 10 } 64 | ); 65 | const filter: FilterQuery = {}; 66 | 67 | if (text) { 68 | const regexp = new RegExp(text.split(/ +/).join("| ?"), "i"); 69 | filter["$or"] = [{ name: { $regex: regexp } }]; 70 | } 71 | 72 | const [users, count] = await Promise.all([ 73 | this.userService.get( 74 | filter, 75 | { _id: 1, username: 1 }, 76 | { skip: range.offset, limit: range.limit } 77 | ), 78 | this.userService.getCount(filter), 79 | ]); 80 | 81 | return { 82 | items: users, 83 | count, 84 | }; 85 | }; 86 | 87 | getUserByToken = async ({ userId }: IAuthInfo) => { 88 | const user = this.userService.getItemById(userId); 89 | return { user }; 90 | }; 91 | 92 | getUserById = async (id: string, { userId, role }: IAuthInfo, _UTC) => { 93 | if (role === ROLE.ADMIN && id !== userId.toString()) { 94 | throw new CustomError(403, this.localizations.ERRORS.OTHER.FORBIDDEN); 95 | } 96 | 97 | const user = await this.userService.getItemById(id, { 98 | password: 0, 99 | }); 100 | 101 | if (!user) { 102 | throw new CustomError(404, this.localizations.ERRORS.USER.NOT_EXIST); 103 | } 104 | 105 | return { 106 | user: user, 107 | }; 108 | }; 109 | 110 | deleteUser = async (id: string) => { 111 | await this.userService.deleteById(id); 112 | return { status: 204 }; 113 | }; 114 | 115 | getPermissions = async () => { 116 | return { 117 | roles: Object.keys(ROLE), 118 | statusArr: Object.keys(STATUS), 119 | }; 120 | }; 121 | 122 | getUserBalance = async ({ userId }: IAuthInfo) => { 123 | const user = await this.userService.getItemById(userId); 124 | return { 125 | balance: user.wallet, 126 | }; 127 | }; 128 | 129 | getAdminWalletBalance = async () => { 130 | const siteUser = process.env.REVENUE_ID; 131 | const admin = await this.userService.getItemById(siteUser); 132 | return { 133 | balance: admin.wallet, 134 | }; 135 | }; 136 | 137 | private _diff(a1: Array, a2: Array) { 138 | return (>a1.filter((i) => !a2.includes(i))).concat( 139 | >a2.filter((i) => !a1.includes(i)) 140 | ); 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /src/modules/user/swagger/user.swagger.ts: -------------------------------------------------------------------------------- 1 | import * as errorResponse from "@/utils/swagger/errors"; 2 | 3 | import { STATUS } from "../user.constant"; 4 | import { permissionsSchema } from "./user.schema"; 5 | 6 | const tags = ["User"]; 7 | const urlPrefix = "/user"; 8 | 9 | const getUsers = { 10 | get: { 11 | summary: "get users list | [ For admin ]", 12 | tags, 13 | parameters: [ 14 | { 15 | name: "offset", 16 | in: "query", 17 | required: false, 18 | default: 0, 19 | schema: { 20 | type: "integer", 21 | }, 22 | }, 23 | { 24 | name: "limit", 25 | in: "query", 26 | required: false, 27 | default: 10, 28 | schema: { 29 | type: "integer", 30 | }, 31 | }, 32 | { 33 | name: "text", 34 | in: "query", 35 | required: false, 36 | default: "", 37 | }, 38 | ], 39 | responses: { 40 | 200: { 41 | description: "Successfully get all users!", 42 | content: { 43 | "application/json": { 44 | schema: { 45 | type: "object", 46 | properties: { 47 | items: { 48 | type: "array", 49 | items: { 50 | $ref: "#/components/schemas/shortUserSchemaWithoutPassword", 51 | }, 52 | }, 53 | count: { type: "number" }, 54 | }, 55 | }, 56 | }, 57 | }, 58 | }, 59 | }, 60 | }, 61 | }; 62 | 63 | const getUserByToken = { 64 | get: { 65 | summary: "get user by token | [ For user ]", 66 | tags, 67 | responses: { 68 | 200: { 69 | description: "Successfully get user!", 70 | content: { 71 | "application/json": { 72 | schema: { 73 | $ref: "#/components/schemas/fullUserSchema", 74 | }, 75 | }, 76 | }, 77 | }, 78 | ...errorResponse.unauthorized, 79 | ...errorResponse.forbidden, 80 | ...errorResponse.notFound, 81 | default: { 82 | description: "Error edit user!", 83 | }, 84 | }, 85 | }, 86 | }; 87 | 88 | const editUser = { 89 | put: { 90 | summary: "edit user | [ For user ]", 91 | tags, 92 | requestBody: { 93 | content: { 94 | "multipart/form-data": { 95 | schema: { 96 | type: "object", 97 | properties: { 98 | avatar: { type: "string", format: "binary" }, 99 | name: { type: "string", required: false }, 100 | wallet: { 101 | type: "object", 102 | properties: { 103 | items: { 104 | type: "array", 105 | items: { 106 | type: "string", 107 | }, 108 | }, 109 | }, 110 | required: false, 111 | }, 112 | status: { 113 | type: "string", 114 | enum: Object.keys(STATUS), 115 | required: false, 116 | }, 117 | }, 118 | }, 119 | }, 120 | }, 121 | }, 122 | responses: { 123 | 200: { 124 | description: "Successfully edit user!", 125 | content: { 126 | "application/json": { 127 | schema: { 128 | $ref: "#/components/schemas/fullUserSchema", 129 | }, 130 | }, 131 | }, 132 | }, 133 | ...errorResponse.unauthorized, 134 | ...errorResponse.forbidden, 135 | ...errorResponse.notFound, 136 | default: { 137 | description: "Error edit user!", 138 | }, 139 | }, 140 | }, 141 | }; 142 | 143 | const getPermissions = { 144 | get: { 145 | summary: "get all permissions | [ For admin ]", 146 | tags, 147 | responses: { 148 | 200: { 149 | content: { 150 | "application/json": { 151 | schema: permissionsSchema, 152 | }, 153 | }, 154 | }, 155 | ...errorResponse.unauthorized, 156 | default: { 157 | description: "Error get permissions!", 158 | }, 159 | }, 160 | }, 161 | }; 162 | 163 | export default { 164 | [`${urlPrefix}`]: getUsers, 165 | [`${urlPrefix}/permissions`]: getPermissions, 166 | [`${urlPrefix}/me`]: Object.assign({}, editUser, getUserByToken), 167 | }; 168 | -------------------------------------------------------------------------------- /src/modules/leaderboard/leaderboard.service.ts: -------------------------------------------------------------------------------- 1 | // need add model to mongo index file 2 | import mongoose from "mongoose"; 3 | 4 | import { ALLOW_GAME_LIST, SITE_USER_ID } from "@/config"; 5 | import BaseService from "@/utils/base/service"; 6 | import { User } from "@/utils/db"; 7 | import logger from "@/utils/logger"; 8 | 9 | import { token_currency } from "./leaderboard.constant"; 10 | // need add types 11 | import { TLeaderboardDocumentType } from "./leaderboard.types"; 12 | 13 | export class LeaderboardService extends BaseService { 14 | private gameList = ALLOW_GAME_LIST; 15 | 16 | constructor() { 17 | super(User); 18 | } 19 | 20 | getTopLearderboards = async (count: number) => { 21 | const ignoreId = SITE_USER_ID; 22 | const leaderboard: { [key: string]: TLeaderboardDocumentType[] } = {}; 23 | 24 | for (const game of this.gameList) { 25 | const createPipeline = () => [ 26 | { 27 | $match: { _id: { $ne: new mongoose.Types.ObjectId(ignoreId) } }, 28 | }, 29 | { 30 | $addFields: { 31 | totalBetAmount: { 32 | $sum: { 33 | $map: { 34 | input: { $objectToArray: `$wager.${game}` }, 35 | as: "denom", 36 | in: { 37 | $cond: { 38 | 39 | }, 40 | }, 41 | }, 42 | }, 43 | }, 44 | }, 45 | }, 46 | { $sort: { totalBetAmount: -1 as 1 | -1 } }, 47 | { $limit: count }, 48 | { 49 | $project: { 50 | _id: 1, 51 | username: 1, 52 | leaderboard: 1, 53 | avatar: 1, 54 | createdAt: 1, 55 | hasVerifiedAccount: 1, 56 | totalBetAmount: 1, 57 | rank: 1, 58 | }, 59 | }, 60 | ]; 61 | 62 | const gameLeaderboard = await this.aggregateByPipeline(createPipeline()); 63 | 64 | leaderboard[game] = gameLeaderboard; 65 | } 66 | 67 | return leaderboard; 68 | }; 69 | 70 | fetchTopPlayers = async ( 71 | count: number, 72 | 73 | ): Promise<{ [key: string]: { winners: any[]; losers: any[] } } | []> => { 74 | try { 75 | const dashboard: { [key: string]: { winners: any[]; losers: any[] } } = 76 | {}; 77 | const ignoreId = SITE_USER_ID; 78 | 79 | const createPipeline = (game: string, sortOrder: 1 | -1) => [ 80 | { 81 | $match: { _id: { $ne: new mongoose.Types.ObjectId(ignoreId) } }, 82 | }, 83 | { 84 | $addFields: { 85 | totalWinAmount: { 86 | $sum: { 87 | $map: { 88 | input: { 89 | $objectToArray: `$leaderboard.${game}`, 90 | }, 91 | as: "denom", 92 | in: { 93 | $cond: { 94 | 95 | // 96 | }, 97 | }, 98 | }, 99 | }, 100 | }, 101 | }, 102 | }, 103 | { 104 | $addFields: { 105 | totalBetAmount: { 106 | $sum: { 107 | $map: { 108 | input: { 109 | $objectToArray: `$leaderboard.${game}`, 110 | }, 111 | as: "denom", 112 | in: { 113 | $cond: { 114 | 115 | }, 116 | }, 117 | }, 118 | }, 119 | }, 120 | }, 121 | }, 122 | { 123 | $addFields: { 124 | profit: { 125 | $subtract: ["$totalWinAmount", "$totalBetAmount"], 126 | }, 127 | }, 128 | }, 129 | { 130 | $match: { 131 | profit: sortOrder === -1 ? { $gt: 0 } : { $lt: 0 }, 132 | }, 133 | }, 134 | { $sort: { profit: sortOrder } }, 135 | { $limit: count }, 136 | ]; 137 | 138 | for (const game of this.gameList) { 139 | const winnersPipeline = createPipeline(game, -1); 140 | const losersPipeline = createPipeline(game, 1); 141 | 142 | const winners = await User.aggregate(winnersPipeline); 143 | const losers = await User.aggregate(losersPipeline); 144 | 145 | dashboard[game] = { winners, losers }; 146 | } 147 | 148 | return dashboard; 149 | } catch (ex) { 150 | logger.error("Error finding top players", ex); 151 | return []; 152 | } 153 | }; 154 | } 155 | -------------------------------------------------------------------------------- /src/modules/user-bot/user-bot.model.ts: -------------------------------------------------------------------------------- 1 | // Import Dependencies 2 | import mongoose, { Document, model } from "mongoose"; 3 | 4 | import { IUserBotModel } from "./user-bot.interface"; 5 | const { Schema, Types } = mongoose; 6 | 7 | // Setup User Schema 8 | const UserBotSchema = new Schema( 9 | { 10 | // Authentication related fields 11 | provider: { type: String }, 12 | providerId: { type: String }, 13 | username: { type: String }, 14 | password: { type: String }, 15 | avatar: { type: String }, 16 | 17 | // User's on-site rank 18 | rank: { 19 | type: Number, 20 | default: 1, 21 | /** 22 | * Ranks: 23 | * 24 | * 1 = User 25 | * 2 = Sponsor 26 | * 3 = Developer 27 | * 4 = Moderator 28 | * 5 = Admin 29 | */ 30 | }, 31 | 32 | // Site balance 33 | wallet: { 34 | type: Number, 35 | default: 0, 36 | }, 37 | 38 | // Wager amount 39 | wager: { 40 | type: Number, 41 | default: 0, 42 | }, 43 | 44 | // Holds user's crypto address information (addresses) 45 | crypto: { 46 | address: { type: String }, 47 | }, 48 | 49 | // Whether the user has verified their account (via mobile phone or csgo loyalty badge) normal it is false 50 | hasVerifiedAccount: { 51 | type: Boolean, 52 | default: true, 53 | }, 54 | 55 | // Store their phone number to prevent multi-account verifications 56 | verifiedPhoneNumber: { 57 | type: String, 58 | default: null, 59 | }, 60 | 61 | // When the account was verified 62 | accountVerified: { 63 | type: Date, 64 | default: null, 65 | }, 66 | 67 | // Unix ms timestamp when the ban will end, 0 = no ban 68 | banExpires: { 69 | type: String, 70 | default: "0", 71 | }, 72 | 73 | // Unix ms timestamps when the self-exclude will end, 0 = no ban 74 | selfExcludes: { 75 | crash: { 76 | type: Number, 77 | default: 0, 78 | }, 79 | coinflip: { 80 | type: Number, 81 | default: 0, 82 | }, 83 | }, 84 | 85 | // Unix ms timestamp when the mute will end, 0 = no mute 86 | muteExpires: { 87 | type: String, 88 | default: "0", 89 | }, 90 | 91 | // If user has restricted transactions 92 | transactionsLocked: { 93 | type: Boolean, 94 | default: false, 95 | }, 96 | 97 | // If user has restricted bets 98 | betsLocked: { 99 | type: Boolean, 100 | default: false, 101 | }, 102 | 103 | // UserID of the user who affiliated 104 | _affiliatedBy: { 105 | type: Types.ObjectId, 106 | ref: "User", 107 | default: null, 108 | }, 109 | 110 | // When the affiliate was redeemed 111 | affiliateClaimed: { 112 | type: Date, 113 | default: null, 114 | }, 115 | 116 | // Unique affiliate code 117 | affiliateCode: { 118 | type: String, 119 | default: null, 120 | // unique: true, // doesn't work with multiple "null" values :( 121 | }, 122 | 123 | // User affiliation bonus amount 124 | affiliateMoney: { 125 | type: Number, 126 | default: 0, 127 | }, 128 | 129 | // How much affiliation bonus has been claimed (withdrawn) 130 | affiliateMoneyCollected: { 131 | type: Number, 132 | default: 0, 133 | }, 134 | 135 | // Forgot Password 136 | forgotToken: { 137 | type: String, 138 | default: null, 139 | }, 140 | 141 | forgotExpires: { 142 | type: Number, 143 | default: 0, 144 | }, 145 | 146 | // How much rakeback has been collected 147 | rakebackBalance: { 148 | type: Number, 149 | default: 0, 150 | }, 151 | 152 | // Keep track of 50% deposit amount 153 | // required by mitch :/ 154 | wagerNeededForWithdraw: { 155 | type: Number, 156 | default: 0, 157 | }, 158 | 159 | // Total amount of deposits 160 | totalDeposited: { 161 | type: Number, 162 | default: 0, 163 | }, 164 | 165 | // Total amount of withdraws 166 | totalWithdrawn: { 167 | type: Number, 168 | default: 0, 169 | }, 170 | 171 | // User custom wager limit (for sponsors) 172 | customWagerLimit: { 173 | type: Number, 174 | default: 0, 175 | }, 176 | 177 | // User avatar last update 178 | avatarLastUpdate: { 179 | type: Number, 180 | default: 0, 181 | }, 182 | }, 183 | { 184 | timestamps: true, 185 | } 186 | ); 187 | 188 | export interface UseroDocumentType extends Document, IUserBotModel {} 189 | 190 | export default model("UserBot", UserBotSchema); 191 | --------------------------------------------------------------------------------