Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
b309d5a
format: order errors by the error code
Kasper24 Apr 23, 2025
5b21633
refactor: use zod.safeParse
Kasper24 Apr 23, 2025
e4fab24
format: remove trailing comma
Kasper24 Apr 23, 2025
7f0676a
feat: add block module
Kasper24 Apr 23, 2025
19b1a2d
fix: missing async and await
Kasper24 Apr 23, 2025
2b23141
fix: check for "true" not true
Kasper24 Apr 23, 2025
996d4c8
feat: add verify route
Kasper24 Apr 23, 2025
2c86725
refactor: use better error handling
Kasper24 Apr 23, 2025
187f4d7
refactor: use .all for the /api/ paths
Kasper24 Apr 23, 2025
4fdddde
refactor: extract access token from cookie rather than body
Kasper24 Apr 23, 2025
1e5d363
refactor: chat module
Kasper24 Apr 23, 2025
398cf58
feat: add typiclient and typiserver
Kasper24 Jun 4, 2025
0e90772
refactor: improve typistack code quality
Kasper24 Jun 6, 2025
33e4263
feat: add shadcn components
Kasper24 Jun 6, 2025
2308b57
feat: update package-lock
Kasper24 Jun 6, 2025
d396675
refactor: update backend to use typistack
Kasper24 Jun 6, 2025
50587aa
refactor: reset_db defaults to false
Kasper24 Jun 6, 2025
98028bc
feat: update database schema
Kasper24 Jun 6, 2025
6e6d980
format: rename backend project to api
Kasper24 Jun 6, 2025
7c331ae
feat: initial web commit
Kasper24 Jun 6, 2025
13e3660
refactor: move enums into their realted files
Kasper24 Jun 7, 2025
a558930
feat: add calls db schema
Kasper24 Jun 7, 2025
9e97553
refactor: extract db scripts into their own files
Kasper24 Jun 7, 2025
049aee1
refactor: make responses format consistent
Kasper24 Jun 9, 2025
cb7b344
fix: tsc complaints
Kasper24 Jun 9, 2025
53fd0cd
refactor: update calls db schema
Kasper24 Jun 9, 2025
bc69cdd
fix: typiclient http methods
Kasper24 Jun 9, 2025
cdafd9c
refactor: update user patch handler input
Kasper24 Jun 9, 2025
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
File renamed without changes.
File renamed without changes.
12 changes: 6 additions & 6 deletions apps/backend/jest.setup.ts → apps/api/jest.setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { drizzle } from "drizzle-orm/pglite";
import { jest, afterEach, beforeAll, afterAll } from "@jest/globals";
import * as schema from "@repo/database/schema";
import { db, dbPush, dbReset } from "@repo/database";
import redis from "@repo/backend/redis";
import redis from "@repo/api/redis";

jest.mock("@repo/database", () => {
const originalModule =
Expand All @@ -18,12 +18,12 @@ jest.mock("@repo/database", () => {
};
});

jest.mock("@repo/backend/middlewares/rate-limit", () => {
jest.mock("@repo/api/middlewares/rate-limit", () => {
const originalModule = jest.requireActual<
typeof import("@repo/backend/middlewares/rate-limit")
>("@repo/backend/middlewares/rate-limit");
typeof import("@repo/api/middlewares/rate-limit")
>("@repo/api/middlewares/rate-limit");

const rateLimitHandler = jest
const rateLimitMiddleware = jest
.fn()
.mockReturnValue((_: Request, __: Response, next: NextFunction) => {
next();
Expand All @@ -32,7 +32,7 @@ jest.mock("@repo/backend/middlewares/rate-limit", () => {
return {
...originalModule,
__esModule: true,
rateLimitHandler,
rateLimitMiddleware,
};
});

Expand Down
File renamed without changes.
11 changes: 8 additions & 3 deletions apps/backend/package.json → apps/api/package.json
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
{
"name": "backend",
"name": "@repo/api",
"version": "0.0.0",
"type": "module",
"private": true,
"exports": {
"./router": {
"types": "./dist/modules/index.d.ts"
}
},
"scripts": {
"start": "node dist/index.js",
"predev": "docker compose -f ../../docker-compose.yml --profile redis up -d",
Expand All @@ -16,7 +21,7 @@
"jest": {
"preset": "@repo/jest-presets/node",
"moduleNameMapper": {
"^@repo/backend/(.*)$": "<rootDir>/src/$1"
"^@repo/api/(.*)$": "<rootDir>/src/$1"
},
"setupFilesAfterEnv": [
"<rootDir>/jest.setup.ts"
Expand All @@ -25,13 +30,13 @@
"dependencies": {
"@electric-sql/pglite": "^0.2.17",
"@repo/database": "*",
"@repo/typiserver": "*",
"argon2": "^0.41.1",
"cookie-parser": "^1.4.7",
"cors": "^2.8.5",
"drizzle-orm": "^0.40.0",
"drizzle-zod": "^0.7.0",
"express": "^5.0.1",
"http-status-codes": "^2.3.0",
"ioredis": "^5.6.0",
"jsonwebtoken": "^9.0.2",
"morgan": "^1.10.0",
Expand Down
13 changes: 6 additions & 7 deletions apps/backend/src/env/index.ts → apps/api/src/env/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { z, ZodError } from "zod";
import { z } from "zod";

const timeSpanType = z
.string()
Expand All @@ -9,7 +9,7 @@ const envSchema = z.object({
// Node Environment
NODE_ENV: z.enum(["development", "test", "production"]),

RESET_DB: z.coerce.boolean(),
RESET_DB: z.coerce.string().default("false"),

PORT: z.coerce.number().positive().int().default(5000),

Expand Down Expand Up @@ -75,10 +75,9 @@ const envSchema = z.object({
});

const envValidate = () => {
try {
envSchema.parse(process.env);
} catch (error) {
if (error instanceof ZodError) console.error(error.errors);
const { error } = envSchema.safeParse(process.env);
if (error) {
console.error(error.errors);
process.exit(1);
}
};
Expand All @@ -87,7 +86,7 @@ declare global {
// eslint-disable-next-line @typescript-eslint/no-namespace
namespace NodeJS {
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
interface ProcessEnv extends z.infer<typeof environmentVariableSchema> {}
interface ProcessEnv extends z.infer<typeof envSchema> {}
}
}

Expand Down
13 changes: 9 additions & 4 deletions apps/backend/src/index.ts → apps/api/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
import { createServer } from "@repo/backend/server";
import envValidate from "@repo/backend/env";
import { dbPush, dbReset, dbSeed, dbWaitForConnection } from "@repo/database";
import { createServer } from "@repo/api/server";
import envValidate from "@repo/api/env";
import {
dbPush,
dbReset,
dbSeed,
dbWaitForConnection,
} from "@repo/database/scripts";

envValidate();
await dbWaitForConnection();

if (process.env.NODE_ENV === "development" && process.env.RESET_DB === true) {
if (process.env.NODE_ENV === "development" && process.env.RESET_DB === "true") {
await dbPush();
await dbReset();
await dbSeed();
Expand Down
27 changes: 27 additions & 0 deletions apps/api/src/middlewares/auth.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { Request } from "express";
import { jwtVerifyAccessToken } from "@repo/api/utils/jwt";
import { attempt } from "@repo/api/utils";
import { RouteHandlerContext } from "@repo/typiserver";

export interface AuthenticatedRequest extends Request {
userId: number;
}

const authMiddleware = async (ctx: RouteHandlerContext) => {
// const { authorization } = req.headers;
// if (!authorization) return ctx.error("UNAUTHORIZED", Authorization header not found.");

// const token = authorization.split(" ")[1];
// if (!token) return ctx.error("UNAUTHORIZED", "No token provided.");

const token = ctx.request.cookies[process.env.JWT_ACCESS_TOKEN_COOKIE_KEY];

const [error, data] = await attempt(() => jwtVerifyAccessToken(token));
if (error) return ctx.error("UNAUTHORIZED", error.message);

return ctx.success({
userId: data.userId,
});
};

export default authMiddleware;
21 changes: 21 additions & 0 deletions apps/api/src/middlewares/error.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { getStatus } from "@repo/typiserver/http";
import { Request, Response, ErrorRequestHandler, NextFunction } from "express";

const errorHandler: ErrorRequestHandler = (
error: unknown,
req: Request,
res: Response,
next: NextFunction
) => {
const status = getStatus("INTERNAL_SERVER_ERROR");

res.status(status.code).json({
key: status.key,
code: status.code,
label: status.label,
message:
error instanceof Error ? error.message : "An unexpected error occurred",
});
};

export default errorHandler;
35 changes: 35 additions & 0 deletions apps/api/src/middlewares/rate-limit.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import redis from "@repo/api/redis";
import { parseTimeSpan, type TimeSpan } from "@repo/api/utils/time";
import { RouteHandlerContext } from "@repo/typiserver";

const rateLimitMiddleware = ({
endpoint = "global",
timeSpan,
limit,
message,
}: {
timeSpan: TimeSpan;
limit: number;
endpoint?: string;
message?: string;
}) => {
return async (ctx: RouteHandlerContext) => {
const { ip } = ctx.request;
const redisId = `rate-limit:${endpoint}/${ip}`;

const requests = await redis.incr(redisId);
if (requests === 1) await redis.expire(redisId, parseTimeSpan(timeSpan));

limit = process.env.NODE_ENV === "development" ? Infinity : limit;
if (requests > limit) {
return ctx.error(
"TOO_MANY_REQUESTS",
message ?? "Too many requests, please try again later"
);
}

return ctx.success();
};
};

export default rateLimitMiddleware;
Loading
Loading