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
16 changes: 16 additions & 0 deletions backend/collaboration/drizzle.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { defineConfig } from 'drizzle-kit';

const config = {
host: process.env.EXPRESS_DB_HOST!,
port: Number.parseInt(process.env.EXPRESS_DB_PORT!),
database: process.env.POSTGRES_DB!,
user: process.env.POSTGRES_USER,
password: process.env.POSTGRES_PASSWORD,
};

export default defineConfig({
schema: './src/lib/db/schema.ts',
out: './drizzle',
dialect: 'postgresql',
dbCredentials: config,
});
14 changes: 14 additions & 0 deletions backend/collaboration/drizzle/0000_initial_schema.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
CREATE TYPE "public"."action" AS ENUM('SEED');--> statement-breakpoint
CREATE TABLE IF NOT EXISTS "admin" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"created_at" timestamp DEFAULT now(),
"action" "action" NOT NULL
);
--> statement-breakpoint
CREATE TABLE IF NOT EXISTS "rooms" (
"room_id" varchar(255) PRIMARY KEY NOT NULL,
"user_id_1" uuid NOT NULL,
"user_id_2" uuid NOT NULL,
"question_id" serial NOT NULL,
"created_at" timestamp DEFAULT now()
);
105 changes: 105 additions & 0 deletions backend/collaboration/drizzle/meta/0000_snapshot.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
{
"id": "0fa8a8f0-f2e1-432e-890f-4a1da22f1a18",
"prevId": "00000000-0000-0000-0000-000000000000",
"version": "7",
"dialect": "postgresql",
"tables": {
"public.admin": {
"name": "admin",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "uuid",
"primaryKey": true,
"notNull": true,
"default": "gen_random_uuid()"
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": false,
"default": "now()"
},
"action": {
"name": "action",
"type": "action",
"typeSchema": "public",
"primaryKey": false,
"notNull": true
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.rooms": {
"name": "rooms",
"schema": "",
"columns": {
"room_id": {
"name": "room_id",
"type": "varchar(255)",
"primaryKey": true,
"notNull": true
},
"user_id_1": {
"name": "user_id_1",
"type": "uuid",
"primaryKey": false,
"notNull": true
},
"user_id_2": {
"name": "user_id_2",
"type": "uuid",
"primaryKey": false,
"notNull": true
},
"question_id": {
"name": "question_id",
"type": "serial",
"primaryKey": false,
"notNull": true
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": false,
"default": "now()"
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
}
},
"enums": {
"public.action": {
"name": "action",
"schema": "public",
"values": [
"SEED"
]
}
},
"schemas": {},
"sequences": {},
"roles": {},
"policies": {},
"views": {},
"_meta": {
"columns": {},
"schemas": {},
"tables": {}
}
}
13 changes: 13 additions & 0 deletions backend/collaboration/drizzle/meta/_journal.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"version": "7",
"dialect": "postgresql",
"entries": [
{
"idx": 0,
"version": "7",
"when": 1731050544004,
"tag": "0000_initial_schema",
"breakpoints": true
}
]
}
11 changes: 11 additions & 0 deletions backend/collaboration/entrypoint.sh
Original file line number Diff line number Diff line change
@@ -1,2 +1,13 @@
#!/bin/sh

# Drizzle will handle its own logic to remove conflicts
npm run db:prod:migrate

# Checks admin table and will not seed if data exists
npm run db:prod:seed

rm -rf drizzle src tsconfig.json

npm uninstall tsx drizzle-kit

npm run start
6 changes: 6 additions & 0 deletions backend/collaboration/express.Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,12 @@ RUN npm ci --omit=dev

RUN sed -i 's|./ws|ws|g' ./dist/ws.js

# For migration
RUN npm install tsx drizzle-kit
COPY drizzle ./drizzle
COPY src/lib/db/ ./src/lib/db
COPY src/config.ts ./src
COPY tsconfig.json .
COPY entrypoint.sh .

ARG port
Expand Down
8 changes: 8 additions & 0 deletions backend/collaboration/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,12 @@
"start": "node dist/index.js",
"build:local": "env-cmd -f .env.local tsc && tsc-alias",
"start:local": "env-cmd -f .env.local node dist/index.js",
"db:generate": "env-cmd -f .env.local drizzle-kit generate",
"db:migrate": "env-cmd -f .env.local tsx ./src/lib/db/migrate.ts",
"db:prod:migrate": "tsx ./src/lib/db/migrate.ts",
"db:prod:seed": "tsx ./src/lib/db/seed.ts",
"db:seed": "env-cmd -f .env.local tsx src/lib/db/seed.ts",
"db:seed:prod": "tsx src/lib/db/seed.ts",
"fmt": "prettier --config .prettierrc src --write",
"test": "echo \"Error: no test specified\" && exit 1"
},
Expand All @@ -18,6 +24,7 @@
"dependencies": {
"cors": "^2.8.5",
"dotenv": "^16.4.5",
"drizzle-orm": "^0.36.1",
"env-cmd": "^10.1.0",
"express": "^4.21.1",
"http-status-codes": "^2.3.0",
Expand All @@ -33,6 +40,7 @@
"yjs": "^13.6.19"
},
"devDependencies": {
"drizzle-kit": "^0.28.0",
"@types/cors": "^2.8.17",
"@types/express": "^4.17.21",
"@types/node": "^22.5.5",
Expand Down
31 changes: 31 additions & 0 deletions backend/collaboration/src/controller/get-rooms-controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import type { Request, Response } from 'express';
import { StatusCodes } from 'http-status-codes';

import { getRoomsService } from '@/service/get/rooms-get-service';

type QueryParams = {
userId: string;
offset?: number;
limit?: number;
};

export async function getRoomsController(
req: Request<unknown, unknown, unknown, Partial<QueryParams>>,
res: Response
) {
const { userId, ...rest } = req.query;

if (!userId) {
return res.status(StatusCodes.UNPROCESSABLE_ENTITY).json('Malformed Request');
}

const response = await getRoomsService({ userId, ...rest });

if (response.data) {
return res.status(response.code).json(response.data);
}

return res
.status(response.code)
.json({ error: response.error || { message: 'An error occurred' } });
}
43 changes: 43 additions & 0 deletions backend/collaboration/src/controller/room-auth-controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import type { Request, Response } from 'express';
import { StatusCodes } from 'http-status-codes';

import { logger } from '@/lib/utils';
import { roomAuthService } from '@/service/get/room-auth-service';

type QueryParams = {
roomId: string;
userId: string;
};

// Returns the questionId if valid.
export async function authCheck(
req: Request<unknown, unknown, unknown, Partial<QueryParams>>,
res: Response
) {
const { roomId, userId } = req.query;

if (!roomId || !userId) {
return res.status(StatusCodes.UNPROCESSABLE_ENTITY).json('Malformed request');
}

try {
const response = await roomAuthService({
roomId,
userId,
});

if (response.data) {
return res.status(response.code).json(response.data);
}

return res
.status(response.code)
.json({ error: response.error || { message: 'An error occurred.' } });
} catch (error) {
const { name, stack, cause, message } = error as Error;
logger.error('Error authenticating room: ' + JSON.stringify({ name, stack, message, cause }));
return res.status(StatusCodes.INTERNAL_SERVER_ERROR).json({
error: { message: 'An error occurred while authenticating the room' },
});
}
}
7 changes: 6 additions & 1 deletion backend/collaboration/src/lib/db/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { drizzle } from 'drizzle-orm/postgres-js';
import postgres from 'postgres';

export const config = {
Expand All @@ -8,4 +9,8 @@ export const config = {
password: process.env.POSTGRES_PASSWORD,
};

export const db = postgres(config);
const queryClient = postgres(config);

export const db = drizzle(queryClient);

export * from './schema';
21 changes: 21 additions & 0 deletions backend/collaboration/src/lib/db/migrate.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { drizzle } from 'drizzle-orm/postgres-js';
import { migrate } from 'drizzle-orm/postgres-js/migrator';
import postgres from 'postgres';

const config = {
host: process.env.EXPRESS_DB_HOST!,
port: Number.parseInt(process.env.EXPRESS_DB_PORT!),
database: process.env.POSTGRES_DB,
user: process.env.POSTGRES_USER,
password: process.env.POSTGRES_PASSWORD,
};
const migrationConnection = postgres({ ...config, max: 1 });

const db = drizzle(migrationConnection);

const main = async () => {
await migrate(db, { migrationsFolder: 'drizzle' });
await migrationConnection.end();
};

void main();
17 changes: 17 additions & 0 deletions backend/collaboration/src/lib/db/schema.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { pgEnum, pgTable, serial, timestamp, uuid, varchar } from 'drizzle-orm/pg-core';

export const rooms = pgTable('rooms', {
roomId: varchar('room_id', { length: 255 }).primaryKey().notNull(),
userId1: uuid('user_id_1').notNull(),
userId2: uuid('user_id_2').notNull(),
questionId: serial('question_id').notNull(),
createdAt: timestamp('created_at').defaultNow(),
});

export const actionEnum = pgEnum('action', ['SEED']);

export const admin = pgTable('admin', {
id: uuid('id').primaryKey().notNull().defaultRandom(),
createdAt: timestamp('created_at').defaultNow(),
action: actionEnum('action').notNull(),
});
4 changes: 4 additions & 0 deletions backend/collaboration/src/routes/room.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
import express from 'express';

import { getCollabRoom } from '@/controller/collab-controller';
import { getRoomsController } from '@/controller/get-rooms-controller';
import { authCheck } from '@/controller/room-auth-controller';

const router = express.Router();

router.get('/', getCollabRoom);
router.get('/rooms', getRoomsController);
router.get('/auth', authCheck);

export default router;
3 changes: 2 additions & 1 deletion backend/collaboration/src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import http from 'http';
import { exit } from 'process';

import cors from 'cors';
import { sql } from 'drizzle-orm';
import express, { json } from 'express';
import { StatusCodes } from 'http-status-codes';
import pino from 'pino-http';
Expand Down Expand Up @@ -47,7 +48,7 @@ app.get('/health', (_req, res) => res.status(StatusCodes.OK).send('OK'));

export const dbHealthCheck = async () => {
try {
await db`SELECT 1`;
await db.execute(sql`SELECT 1`);
logger.info('Connected to DB');
} catch (error) {
const { message } = error as Error;
Expand Down
Loading