Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions apps/api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
"ioredis": "^5.6.0",
"jsonwebtoken": "^9.0.2",
"morgan": "^1.10.0",
"multer": "^2.0.1",
"twilio": "^5.4.5",
"zod": "^3.25.6"
},
Expand All @@ -54,6 +55,7 @@
"@types/express": "^4.17.17",
"@types/jsonwebtoken": "^9.0.9",
"@types/morgan": "^1.9.9",
"@types/multer": "^1.4.13",
"@types/node": "^22.13.5",
"@types/supertest": "^6.0.2",
"eslint": "^9.20.0",
Expand Down
83 changes: 78 additions & 5 deletions apps/api/src/modules/user/user.route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,13 @@ import {
createTypiRouter,
} from "@repo/typiserver";
import { db } from "@repo/database";
import { users } from "@repo/database/schema";
import {
refreshTokens,
userPrivacySettings,
users,
} from "@repo/database/schema";
import authMiddleware from "@repo/api/middlewares/auth";
import { createUpdateSchema } from "drizzle-zod";

const userRouter = createTypiRouter({
"/": createTypiRoute({
Expand All @@ -27,18 +32,21 @@ const userRouter = createTypiRouter({
patch: createTypiRouteHandler({
input: {
body: z.object({
name: z.string().optional(),
description: z.string().optional(),
picture: z.string().optional(),
user: z.object({
name: z.string().optional(),
description: z.string().optional(),
picture: z.instanceof(File).optional(),
}),
}),
},
middlewares: [authMiddleware],
handler: async (ctx) => {
const user = await db
.update(users)
.set({
name: ctx.input.body.user.name,
description: ctx.input.body.user.description,
updatedAt: new Date(),
...ctx.input.body,
})
.where(eq(users.id, ctx.data.userId))
.returning();
Expand All @@ -50,6 +58,71 @@ const userRouter = createTypiRouter({
});
},
}),
delete: createTypiRouteHandler({
middlewares: [authMiddleware],
handler: async (ctx) => {
const deleted = await db
.delete(users)
.where(eq(users.id, ctx.data.userId))
.returning();

if (!deleted.length) return ctx.error("NOT_FOUND", "User not found.");

await db
.delete(refreshTokens)
.where(eq(refreshTokens.userId, ctx.data.userId));

ctx.response.clearCookie(process.env.JWT_ACCESS_TOKEN_COOKIE_KEY);
ctx.response.clearCookie(process.env.JWT_REFRESH_TOKEN_COOKIE_KEY);

return ctx.success({
message: "User deleted successfully.",
});
},
}),
}),
"/privacy": createTypiRoute({
get: createTypiRouteHandler({
middlewares: [authMiddleware],
handler: async (ctx) => {
const privacySettings = await db.query.userPrivacySettings.findFirst({
where: and(eq(users.id, ctx.data.userId)),
});
if (!privacySettings) return ctx.error("NOT_FOUND", "User not found.");

return ctx.success({
privacySettings,
});
},
}),
patch: createTypiRouteHandler({
input: {
body: z.object({
privacySettings: createUpdateSchema(userPrivacySettings, {
createdAt: z.undefined(),
updatedAt: z.undefined(),
}),
}),
},
middlewares: [authMiddleware],
handler: async (ctx) => {
const updatedPrivacySettings = await db
.update(userPrivacySettings)
.set({
updatedAt: new Date(),
...ctx.input.body.privacySettings,
})
.where(eq(userPrivacySettings.userId, ctx.data.userId))
.returning();

if (!updatedPrivacySettings.length)
return ctx.error("NOT_FOUND", "User not found.");

return ctx.success({
privacySettings: updatedPrivacySettings[0],
});
},
}),
}),
});

Expand Down
23 changes: 9 additions & 14 deletions apps/api/src/server.ts
Original file line number Diff line number Diff line change
@@ -1,31 +1,26 @@
import express, { type Express } from "express";
import morgan from "morgan";
import multer from "multer";
import cors from "cors";
import cookieParser from "cookie-parser";
import rootRouter from "@repo/api/modules";
import errorHandler from "@repo/api/middlewares/error";
import rateLimitMiddleware from "@repo/api/middlewares/rate-limit";

export const createServer = (): Express => {
const app = express();
app
.disable("x-powered-by")
.use(
morgan(
`:remote-addr - :remote-user [:date[clf]] ":method :url HTTP/:http-version" :status :res[content-length]`,
{ immediate: true }
)
)
.use(express.urlencoded({ extended: true }))
.use(express.json())
.use(
multer({
storage: multer.memoryStorage(), // Store files in memory
limits: {
fileSize: 10 * 1024 * 1024, // 10MB limit
},
}).any()
)
.use(cookieParser())
.use(cors({ origin: "http://localhost:3002", credentials: true }))
// .use(
// rateLimitMiddleware({
// timeSpan: process.env.WINDOW_SIZE_IN_MINUTES,
// limit: process.env.MAX_NUMBER_OF_REQUESTS_PER_WINDOW_SIZE,
// })
// )
.use("/api/v1", rootRouter.router)
.all("/*splat", () => {
throw new Error("You look a little lost.");
Expand Down
Loading