diff --git a/README.md b/README.md index e09ce8a..76bc2c8 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,19 @@ # CoinFlowSystem A flexible and secure API-driven coin management system for integrating virtual currency functionality in games and applications. + +## Few rules we follow internally + +- **Always take input from req.body.input**, Make sure to take and validate input from `req.body.input` and not from `req.body` directly. + +## Code Commit Guidelines + +When committing code, We follow the following guidelines for commit messages. + +``` +feat: feature xyz +fix: fix xyz +refactor: refactor xyz +docs: update docs +optimize: optimize xyz +``` diff --git a/changelog.md b/changelog.md new file mode 100644 index 0000000..d85bcc1 --- /dev/null +++ b/changelog.md @@ -0,0 +1,4 @@ +### 1.0.0 + +- Initial project structure and repo setup +- Initial API's for User, Wallet, and Transaction management diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 904bb12..cfbbb23 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -7,35 +7,33 @@ datasource db { url = env("DATABASE_URL") } -model User { +model Wallet { id Int @id @default(autoincrement()) username String @unique email String @unique application_user_id String @unique - Wallet Wallet[] - Transaction Transaction[] created_at DateTime @default(now()) updated_at DateTime @updatedAt + Transaction Transaction[] } -model Wallet { +model Coin { id Int @id @default(autoincrement()) - user_id Int - user User @relation(fields: [user_id], references: [id]) - balance Int - Transaction Transaction[] + name String + symbol String created_at DateTime @default(now()) updated_at DateTime @updatedAt + Transaction Transaction[] } model Transaction { id Int @id @default(autoincrement()) transaction_type String - user_id Int - user User @relation(fields: [user_id], references: [id]) wallet_id Int - wallet Wallet @relation(fields: [wallet_id], references: [id]) + coin_id Int amount Int created_at DateTime @default(now()) updated_at DateTime @updatedAt + wallet Wallet @relation(fields: [wallet_id], references: [id]) + coin Coin @relation(fields: [coin_id], references: [id]) } diff --git a/src/utils/middlewares.ts b/src/utils/middlewares.ts index d3c9f21..aab231d 100644 --- a/src/utils/middlewares.ts +++ b/src/utils/middlewares.ts @@ -36,7 +36,7 @@ export const validateUserExistsSentThroughReqBody = function (path: any) { return async function (req: any, res: any, next: any) { let user = await prisma.user.findUnique({ where: { - id: get(req, path, null), + id: parseInt(get(req, path, null)), }, }); if (user === null) { diff --git a/src/v1/routes.ts b/src/v1/routes.ts index d22f501..73c2fe7 100644 --- a/src/v1/routes.ts +++ b/src/v1/routes.ts @@ -1,53 +1,68 @@ import { middlewareValidateYupSchemaAgainstReqBody, validateUserExistsSentThroughReqBody } from "../utils/middlewares"; -import { createUserSchema, updateUserSchema } from "./user.validation"; -import { createUser, getUserBalance, updateUser } from "./user"; +import { createWalletSchema, updateWalletSchema } from "./wallet/wallet.validation"; +import { createWallet, updateWallet, getWalletBalance } from "./wallet/wallet"; import express from "express"; +import { + createTransaction, + deleteTransaction, + getTransaction, + getTransactions, + getTransactionsByUser, + updateTransaction, +} from "./transactions/transaction"; +import { createTransactionSchema, updateTransactionSchema } from "./transactions/transaction.validation"; const v1Routes = express.Router(); -v1Routes.get("/createTransaction", function (req, res) { - res.status(200).json({ - message: "createTransaction", - }); -}); -v1Routes.get("/updateTransaction", function (req, res) { - res.status(200).json({ - message: "updateTransaction", - }); -}); -v1Routes.delete("/deleteTransaction", function (req, res) { - res.status(200).json({ - message: "deleteTransaction", - }); -}); -v1Routes.get("/getTransaction", function (req, res) { - res.status(200).json({ - message: "getTransaction", - }); -}); -v1Routes.get("/getTransactions", function (req, res) { - res.status(200).json({ - message: "getTransactions", - }); -}); -v1Routes.get("/getTransactionsByUser", function (req, res) { - res.status(200).json({ - message: "getTransactionsByUser", - }); -}); - -v1Routes.post("/createWallet", function (req, res) { - res.status(200).json({ - message: "createWallet", - }); -}); - -v1Routes.post("/createUser", middlewareValidateYupSchemaAgainstReqBody(createUserSchema), createUser); + +/** + * Wallet Apis Start + */ + +v1Routes.post("/createWallet", middlewareValidateYupSchemaAgainstReqBody(createWalletSchema), createWallet); v1Routes.put( - "/updateUser", - validateUserExistsSentThroughReqBody("body.input.id"), - middlewareValidateYupSchemaAgainstReqBody(updateUserSchema), - updateUser, + "/updateWallet", + validateUserExistsSentThroughReqBody("body.id"), + middlewareValidateYupSchemaAgainstReqBody(updateWalletSchema), + updateWallet, +); +v1Routes.get("/getWalletBalance", validateUserExistsSentThroughReqBody("body.input.id"), getWalletBalance); + +/** + * Wallet Apis End + */ + +/** + * Transaction Apis Start + * */ + +v1Routes.post( + "/createTransaction", + validateUserExistsSentThroughReqBody("body.user_id"), + middlewareValidateYupSchemaAgainstReqBody(createTransactionSchema), + createTransaction, ); -v1Routes.get("/getUserBalance", validateUserExistsSentThroughReqBody("body.input.id"), getUserBalance); + +v1Routes.put( + "/updateTransaction/:id", + validateUserExistsSentThroughReqBody("body.user_id"), + middlewareValidateYupSchemaAgainstReqBody(updateTransactionSchema), + updateTransaction, +); + +v1Routes.delete("/deleteTransaction/:id", deleteTransaction); + +v1Routes.get("/getTransaction/:id", getTransaction); + +v1Routes.get("/getTransactions", getTransactions); + +v1Routes.get( + "/getTransactionsByUser/:userId", + validateUserExistsSentThroughReqBody("params.userId"), + getTransactionsByUser, +); +/** + * Transaction Apis End + */ + export default v1Routes; diff --git a/src/v1/transaction.ts b/src/v1/transaction.ts deleted file mode 100644 index 7da3afa..0000000 --- a/src/v1/transaction.ts +++ /dev/null @@ -1,6 +0,0 @@ -export const createTransaction = () => {}; -export const updateTransaction = () => {}; -export const deleteTransaction = () => {}; -export const getTransaction = () => {}; -export const getTransactions = () => {}; -export const getTransactionsByUser = () => {}; diff --git a/src/v1/transactions/transaction.ts b/src/v1/transactions/transaction.ts new file mode 100644 index 0000000..f941d34 --- /dev/null +++ b/src/v1/transactions/transaction.ts @@ -0,0 +1,145 @@ +import prisma from "../../database/prisma"; +import { Response, Request } from "express"; +import { generateError } from "../../utils/errors"; + +export const createTransaction = async (req: Request, res: Response) => { + try { + // Check if wallet_id exists + const wallet = await prisma.wallet.findUnique({ + where: { id: req.body.input.wallet_id }, + }); + if (!wallet) { + return res.status(400).json({ message: "Wallet not found" }); + } + + // Create transaction + let response = await prisma.transaction.create({ + data: req.body.input, + }); + + res.status(200).json({ + message: "Transaction created successfully", + user: response, + }); + } catch (e) { + generateError(res, e); + } +}; + +export const updateTransaction = async (req: Request, res: Response) => { + try { + const { id } = req.params; + const updateData = req.body.input; + + // Check if transaction exists + const existingTransaction = await prisma.transaction.findUnique({ + where: { id: parseInt(id) }, + }); + if (!existingTransaction) { + return res.status(404).json({ message: "Transaction not found" }); + } + + if (updateData.wallet_id) { + const wallet = await prisma.wallet.findUnique({ + where: { id: existingTransaction.wallet_id }, + }); + if (!wallet) { + return res.status(400).json({ message: "Wallet not found" }); + } + } + + // Update the transaction + const updatedTransaction = await prisma.transaction.update({ + where: { id: parseInt(id) }, + data: updateData, + }); + + res.status(200).json({ + message: "Transaction updated successfully", + transaction: updatedTransaction, + }); + } catch (e) { + generateError(res, e); + } +}; + +export const deleteTransaction = async (req: Request, res: Response) => { + try { + const { id } = req.params; + + // Check if the transaction exists + const existingTransaction = await prisma.transaction.findUnique({ + where: { id: parseInt(id) }, + }); + if (!existingTransaction) { + return res.status(404).json({ message: "Transaction not found" }); + } + + // Delete the transaction + await prisma.transaction.delete({ + where: { id: parseInt(id) }, + }); + + res.status(200).json({ message: "Transaction deleted successfully" }); + } catch (e) { + generateError(res, e); + } +}; + +export const getTransaction = async (req: Request, res: Response) => { + try { + const { id } = req.params; + + // Retrieve the transaction + const transaction = await prisma.transaction.findUnique({ + where: { id: parseInt(id) }, + }); + + if (!transaction) { + return res.status(404).json({ message: "Transaction not found" }); + } + + res.status(200).json(transaction); + } catch (e) { + generateError(res, e); + } +}; + +export const getTransactions = async (req: Request, res: Response) => { + try { + const transactions = await prisma.transaction.findMany(); + + if (!transactions) { + return res.status(404).json({ message: "Transactions not found" }); + } + + res.status(200).json(transactions); + } catch (e) { + generateError(res, e); + } +}; + +export const getTransactionsByUser = async (req: Request, res: Response) => { + try { + const { userId } = req.params; + + // Check if user exists + const user = await prisma.user.findUnique({ + where: { id: parseInt(userId) }, + }); + + // Retrieve transactions for the user + const transactions = await prisma.transaction.findMany({ + where: { + user_id: user?.id, + }, + }); + + res.status(200).json({ + transactions: transactions, + message: "Transactions retrieved successfully", + }); + } catch (e) { + generateError(res, e); + } +}; diff --git a/src/v1/transactions/transaction.validation.ts b/src/v1/transactions/transaction.validation.ts new file mode 100644 index 0000000..f22fbaa --- /dev/null +++ b/src/v1/transactions/transaction.validation.ts @@ -0,0 +1,29 @@ +import { object, string, number, InferType, date } from "yup"; + +export const createTransactionSchema = object({ + transaction_type: string() + .required("Transaction type is required") + .oneOf(["credit", "debit"], "Transaction type must be either credit or debit"), + wallet_id: number() + .required("Wallet ID is required") + .positive("Wallet ID must be a positive integer") + .integer("Wallet ID must be an integer"), + amount: number() + .required("Amount is required") + .positive("Amount must be a positive integer") + .integer("Amount must be an integer"), + created_at: date() + .default(() => new Date()) + .required("Creation date is required"), + updated_at: date() + .default(() => new Date()) + .required("Update date is required"), +}); +export type CreateTransactionSchema = InferType; + +export const updateTransactionSchema = object({ + id: number().required("ID is required"), + transaction_type: string().optional().oneOf(["credit", "debit"], "Transaction type must be either credit or debit"), + amount: number().optional().positive("Amount must be a positive integer").integer("Amount must be an integer"), +}); +export type UpdateTransactionSchema = InferType; diff --git a/src/v1/wallet.ts b/src/v1/wallet.ts deleted file mode 100644 index 56eae8a..0000000 --- a/src/v1/wallet.ts +++ /dev/null @@ -1 +0,0 @@ -export const createWallet = () => {}; diff --git a/src/v1/user.ts b/src/v1/wallet/wallet.ts similarity index 56% rename from src/v1/user.ts rename to src/v1/wallet/wallet.ts index d86e6e9..21e5dfb 100644 --- a/src/v1/user.ts +++ b/src/v1/wallet/wallet.ts @@ -1,15 +1,11 @@ -import prisma from "./../database/prisma"; +import prisma from "../../database/prisma"; import { Response, Request } from "express"; -import { generateError } from "../utils/errors"; +import { generateError } from "../../utils/errors"; -export const createUser = async (req: Request, res: Response) => { +export const createWallet = async (req: Request, res: Response) => { try { let response = await prisma.user.create({ - data: { - email: req.body.input.email, - username: req.body.input.username, - application_user_id: req.body.input.application_user_id, - }, + data: req.body.input, }); res.status(200).json({ message: "User created successfully", @@ -20,7 +16,7 @@ export const createUser = async (req: Request, res: Response) => { } }; -export const updateUser = async (req: Request, res: Response) => { +export const updateWallet = async (req: Request, res: Response) => { try { const user = req.CurrentRequestUser; const update = await prisma.user.update({ @@ -38,8 +34,8 @@ export const updateUser = async (req: Request, res: Response) => { } }; -export const getUserBalance = (req: Request, res: Response) => { +export const getWalletBalance = (req: Request, res: Response) => { res.status(200).json({ - message: "getUserBalance", + message: "getWalletBalance", }); }; diff --git a/src/v1/user.validation.ts b/src/v1/wallet/wallet.validation.ts similarity index 60% rename from src/v1/user.validation.ts rename to src/v1/wallet/wallet.validation.ts index a853490..3a08f92 100644 --- a/src/v1/user.validation.ts +++ b/src/v1/wallet/wallet.validation.ts @@ -1,16 +1,16 @@ -import { object, string, number, date, InferType } from "yup"; +import { object, string, number, InferType } from "yup"; -export const createUserSchema = object({ +export const createWalletSchema = object({ username: string().required("Username is required"), application_user_id: string().required("Application User ID is required"), email: string().email().required("Email is required"), }); -export type CreateUserSchema = InferType; +export type CreateWalletSchema = InferType; -export const updateUserSchema = object({ +export const updateWalletSchema = object({ id: number().required("ID is required"), username: string().required("Username is required"), application_user_id: string().required("Application User ID is required"), email: string().email().required("Email is required"), }); -export type UpdateUserSchema = InferType; +export type UpdateWalletSchema = InferType;