From 2786781c43e9bc89a8fa60de68222acf224e8f57 Mon Sep 17 00:00:00 2001 From: Kristoffer Fredrik Dahle Melen Date: Tue, 20 Oct 2026 10:48:29 +0200 Subject: [PATCH 01/10] Added a teamPosition table, and added isTeamLeader and teamPositionId to teamUser table. Also added profilePictureUrl to user table. --- db/tables/teams.ts | 37 +++++++++++++++++++++------------- db/tables/users.ts | 16 ++++++++++++++- src/db-access/users.ts | 31 ++++++++++++++++++++++++++-- src/request-handling/users.ts | 3 +++ src/response-handling/users.ts | 3 ++- src/routers/teams.ts | 3 --- 6 files changed, 72 insertions(+), 21 deletions(-) diff --git a/db/tables/teams.ts b/db/tables/teams.ts index 86e4df6..93e834d 100644 --- a/db/tables/teams.ts +++ b/db/tables/teams.ts @@ -2,23 +2,32 @@ import { teamApplicationsTable } from "@/db/tables/applications"; import { departmentsTable } from "@/db/tables/departments"; import { mainSchema } from "@/db/tables/schema"; import { relations } from "drizzle-orm"; -import { boolean, serial, text, timestamp } from "drizzle-orm/pg-core"; +import { boolean, serial, text, timestamp, unique } from "drizzle-orm/pg-core"; import { integer } from "drizzle-orm/pg-core"; import { teamSemesterUsersTable } from "./team-semester-user"; -export const teamsTable = mainSchema.table("teams", { - id: serial("id").primaryKey(), - departmentId: integer("departmentId") - .notNull() - .references(() => departmentsTable.id), - name: text("name").notNull().unique(), - email: text("email").notNull().unique(), - description: text("description").notNull(), - shortDescription: text("shortDescription").notNull(), - acceptApplication: boolean("acceptApplication").notNull(), - active: boolean("active").notNull(), - deadline: timestamp("deadline"), -}); +export const teamsTable = mainSchema.table( + "teams", + { + id: serial("id").primaryKey(), + departmentId: integer("departmentId") + .notNull() + .references(() => departmentsTable.id), + name: text("name").notNull(), + email: text("email").notNull().unique(), + description: text("description").notNull(), + shortDescription: text("shortDescription").notNull(), + acceptApplication: boolean("acceptApplication").notNull(), + active: boolean("active").notNull(), + deadline: timestamp("deadline"), + }, + (table) => ({ + departmentIdNameUnique: unique("teams_departmentId_name_unique").on( + table.departmentId, + table.name, + ), + }), +); export const teamRelations = relations(teamsTable, ({ one, many }) => ({ department: one(departmentsTable, { diff --git a/db/tables/users.ts b/db/tables/users.ts index f3405c4..1b996ec 100644 --- a/db/tables/users.ts +++ b/db/tables/users.ts @@ -1,6 +1,6 @@ import { mainSchema } from "@/db/tables/schema"; import { relations } from "drizzle-orm"; -import { integer, serial, text } from "drizzle-orm/pg-core"; +import { boolean, integer, serial, text } from "drizzle-orm/pg-core"; import { expensesTable } from "@/db/tables/expenses"; import { fieldsOfStudyTable } from "@/db/tables/fields-of-study"; @@ -18,6 +18,7 @@ export const usersTable = mainSchema.table("users", { bankAccountNumber: text("accountNumber").unique(), personalEmail: text("personalEmail").notNull().unique(), phoneNumber: text("phoneNumber").notNull().unique(), + profilePictureUrl: text("profilePictureUrl"), }); export const usersRelations = relations(usersTable, ({ one, many }) => ({ fieldOfStudy: one(fieldsOfStudyTable, { @@ -29,6 +30,11 @@ export const usersRelations = relations(usersTable, ({ one, many }) => ({ assistantUser: one(assistantUsersTable), })); +export const teamPositionTable = mainSchema.table("teamPosition", { + id: serial("id").primaryKey(), + name: text("name").notNull().unique(), +}); + export const teamUsersTable = mainSchema.table("teamUsers", { id: integer("id") .primaryKey() @@ -37,6 +43,10 @@ export const teamUsersTable = mainSchema.table("teamUsers", { .notNull() .references(() => teamsTable.id), username: text("username").notNull().unique(), + teamPositionId: integer("teamPositionId") + .notNull() + .references(() => teamPositionTable.id), + isTeamLeader: boolean("isTeamLeader").notNull().default(false), }); export const teamUsersRelations = relations( teamUsersTable, @@ -50,6 +60,10 @@ export const teamUsersRelations = relations( references: [teamsTable.id], }), teamSemesters: many(teamSemesterUsersTable), + teamPosition: one(teamPositionTable, { + fields: [teamUsersTable.teamPositionId], + references: [teamPositionTable.id], + }), }), ); diff --git a/src/db-access/users.ts b/src/db-access/users.ts index 8c60c73..20351fa 100644 --- a/src/db-access/users.ts +++ b/src/db-access/users.ts @@ -1,6 +1,7 @@ import { database } from "@/db/setup/query-postgres"; import { assistantUsersTable, + teamPositionTable, teamUsersTable, usersTable, } from "@/db/tables/users"; @@ -56,12 +57,20 @@ export async function selectTeamUsersById( bankAccountNumber: usersTable.bankAccountNumber, phoneNumber: usersTable.phoneNumber, personalEmail: usersTable.personalEmail, + profilePictureUrl: usersTable.profilePictureUrl, teamId: teamUsersTable.teamId, username: teamUsersTable.username, + teamPositionId: teamUsersTable.teamPositionId, + teamPositionName: teamPositionTable.name, + isTeamLeader: teamUsersTable.isTeamLeader, }) .from(teamUsersTable) - .where(inArray(assistantUsersTable.id, userIds)) - .innerJoin(usersTable, eq(teamUsersTable.id, usersTable.id)); + .innerJoin(usersTable, eq(teamUsersTable.id, usersTable.id)) + .innerJoin( + teamPositionTable, + eq(teamUsersTable.teamPositionId, teamPositionTable.id), + ) + .where(inArray(teamUsersTable.id, userIds)); if (users.length !== userIds.length) { throw ormError("Couln't find all entries"); } @@ -84,6 +93,7 @@ export async function selectAssistantUsersById( phoneNumber: usersTable.phoneNumber, personalEmail: usersTable.personalEmail, bankAccountNumber: usersTable.bankAccountNumber, + profilePictureUrl: usersTable.profilePictureUrl, }) .from(assistantUsersTable) .where(inArray(assistantUsersTable.id, userIds)) @@ -126,11 +136,19 @@ export async function selectTeamUsers( bankAccountNumber: usersTable.bankAccountNumber, phoneNumber: usersTable.phoneNumber, personalEmail: usersTable.personalEmail, + profilePictureUrl: usersTable.profilePictureUrl, teamId: teamUsersTable.teamId, username: teamUsersTable.username, + teamPositionId: teamUsersTable.teamPositionId, + teamPositionName: teamPositionTable.name, + isTeamLeader: teamUsersTable.isTeamLeader, }) .from(teamUsersTable) .innerJoin(usersTable, eq(teamUsersTable.id, usersTable.id)) + .innerJoin( + teamPositionTable, + eq(teamUsersTable.teamPositionId, teamPositionTable.id), + ) .limit(queryParameters.limit) .offset(queryParameters.offset); @@ -154,11 +172,19 @@ export async function selectTeamUsersByTeamid( bankAccountNumber: usersTable.bankAccountNumber, phoneNumber: usersTable.phoneNumber, personalEmail: usersTable.personalEmail, + profilePictureUrl: usersTable.profilePictureUrl, teamId: teamUsersTable.teamId, username: teamUsersTable.username, + teamPositionId: teamUsersTable.teamPositionId, + teamPositionName: teamPositionTable.name, + isTeamLeader: teamUsersTable.isTeamLeader, }) .from(teamUsersTable) .innerJoin(usersTable, eq(teamUsersTable.id, usersTable.id)) + .innerJoin( + teamPositionTable, + eq(teamUsersTable.teamPositionId, teamPositionTable.id), + ) .limit(queryParameters.limit) .offset(queryParameters.offset) .where(inArray(teamUsersTable.teamId, teamIds)); @@ -182,6 +208,7 @@ export async function selectAssistantUsers( bankAccountNumber: usersTable.bankAccountNumber, phoneNumber: usersTable.phoneNumber, personalEmail: usersTable.personalEmail, + profilePictureUrl: usersTable.profilePictureUrl, }) .from(assistantUsersTable) .innerJoin(usersTable, eq(assistantUsersTable.id, usersTable.id)) diff --git a/src/request-handling/users.ts b/src/request-handling/users.ts index 055def6..2a85670 100644 --- a/src/request-handling/users.ts +++ b/src/request-handling/users.ts @@ -16,6 +16,7 @@ export const userRequestParser = z bankAccountNumber: z.string().length(11), personalEmail: z.string().email(), phoneNumber: z.string().min(8), + profilePictureUrl: z.string().nullable().optional(), }) .strict(); @@ -23,6 +24,8 @@ export const teamUserRequestParser = z .object({ id: serialIdParser, teamId: serialIdParser, + teamPositionId: serialIdParser, + isTeamLeader: z.boolean().optional(), username: z.string().nonempty(), }) .strict(); diff --git a/src/response-handling/users.ts b/src/response-handling/users.ts index 30ec0a8..8eae283 100644 --- a/src/response-handling/users.ts +++ b/src/response-handling/users.ts @@ -1,5 +1,5 @@ import { createSelectSchema } from "drizzle-zod"; -import type { z } from "zod"; +import { z } from "zod"; import { assistantUsersTable, @@ -16,6 +16,7 @@ export type UserKey = User["id"]; export const teamUserSelectSchema = createSelectSchema(teamUsersTable) .merge(createSelectSchema(usersTable)) + .extend({ teamPositionName: z.string() }) .strict() .readonly(); export type TeamUser = z.infer; diff --git a/src/routers/teams.ts b/src/routers/teams.ts index b1274f0..5e824cc 100644 --- a/src/routers/teams.ts +++ b/src/routers/teams.ts @@ -96,9 +96,6 @@ teamsRouter.get("/inactive", async (req, res, next) => { * tags: [teams] * summary: Get all active teams with department name and number of members * description: Return all active teams with extra info - * parameters: - * - $ref: "#/components/parameters/offset" - * - $ref: "#/components/parameters/limit" * responses: * 200: * description: Successfull response From df351514757377a6b717b05e44d4aff80da0ffcd Mon Sep 17 00:00:00 2001 From: Kristoffer Fredrik Dahle Melen Date: Tue, 20 Oct 2026 10:55:43 +0200 Subject: [PATCH 02/10] Renamed Position to Role --- db/tables/users.ts | 12 ++++++------ src/db-access/users.ts | 26 +++++++++++++------------- src/request-handling/users.ts | 2 +- src/response-handling/users.ts | 2 +- 4 files changed, 21 insertions(+), 21 deletions(-) diff --git a/db/tables/users.ts b/db/tables/users.ts index 1b996ec..ce34893 100644 --- a/db/tables/users.ts +++ b/db/tables/users.ts @@ -30,7 +30,7 @@ export const usersRelations = relations(usersTable, ({ one, many }) => ({ assistantUser: one(assistantUsersTable), })); -export const teamPositionTable = mainSchema.table("teamPosition", { +export const teamRoleTable = mainSchema.table("teamRole", { id: serial("id").primaryKey(), name: text("name").notNull().unique(), }); @@ -43,9 +43,9 @@ export const teamUsersTable = mainSchema.table("teamUsers", { .notNull() .references(() => teamsTable.id), username: text("username").notNull().unique(), - teamPositionId: integer("teamPositionId") + teamRoleId: integer("teamRoleId") .notNull() - .references(() => teamPositionTable.id), + .references(() => teamRoleTable.id), isTeamLeader: boolean("isTeamLeader").notNull().default(false), }); export const teamUsersRelations = relations( @@ -60,9 +60,9 @@ export const teamUsersRelations = relations( references: [teamsTable.id], }), teamSemesters: many(teamSemesterUsersTable), - teamPosition: one(teamPositionTable, { - fields: [teamUsersTable.teamPositionId], - references: [teamPositionTable.id], + teamRole: one(teamRoleTable, { + fields: [teamUsersTable.teamRoleId], + references: [teamRoleTable.id], }), }), ); diff --git a/src/db-access/users.ts b/src/db-access/users.ts index 20351fa..ec10813 100644 --- a/src/db-access/users.ts +++ b/src/db-access/users.ts @@ -1,7 +1,7 @@ import { database } from "@/db/setup/query-postgres"; import { assistantUsersTable, - teamPositionTable, + teamRoleTable, teamUsersTable, usersTable, } from "@/db/tables/users"; @@ -60,15 +60,15 @@ export async function selectTeamUsersById( profilePictureUrl: usersTable.profilePictureUrl, teamId: teamUsersTable.teamId, username: teamUsersTable.username, - teamPositionId: teamUsersTable.teamPositionId, - teamPositionName: teamPositionTable.name, + teamRoleId: teamUsersTable.teamRoleId, + teamRoleName: teamRoleTable.name, isTeamLeader: teamUsersTable.isTeamLeader, }) .from(teamUsersTable) .innerJoin(usersTable, eq(teamUsersTable.id, usersTable.id)) .innerJoin( - teamPositionTable, - eq(teamUsersTable.teamPositionId, teamPositionTable.id), + teamRoleTable, + eq(teamUsersTable.teamRoleId, teamRoleTable.id), ) .where(inArray(teamUsersTable.id, userIds)); if (users.length !== userIds.length) { @@ -139,15 +139,15 @@ export async function selectTeamUsers( profilePictureUrl: usersTable.profilePictureUrl, teamId: teamUsersTable.teamId, username: teamUsersTable.username, - teamPositionId: teamUsersTable.teamPositionId, - teamPositionName: teamPositionTable.name, + teamRoleId: teamUsersTable.teamRoleId, + teamRoleName: teamRoleTable.name, isTeamLeader: teamUsersTable.isTeamLeader, }) .from(teamUsersTable) .innerJoin(usersTable, eq(teamUsersTable.id, usersTable.id)) .innerJoin( - teamPositionTable, - eq(teamUsersTable.teamPositionId, teamPositionTable.id), + teamRoleTable, + eq(teamUsersTable.teamRoleId, teamRoleTable.id), ) .limit(queryParameters.limit) .offset(queryParameters.offset); @@ -175,15 +175,15 @@ export async function selectTeamUsersByTeamid( profilePictureUrl: usersTable.profilePictureUrl, teamId: teamUsersTable.teamId, username: teamUsersTable.username, - teamPositionId: teamUsersTable.teamPositionId, - teamPositionName: teamPositionTable.name, + teamRoleId: teamUsersTable.teamRoleId, + teamRoleName: teamRoleTable.name, isTeamLeader: teamUsersTable.isTeamLeader, }) .from(teamUsersTable) .innerJoin(usersTable, eq(teamUsersTable.id, usersTable.id)) .innerJoin( - teamPositionTable, - eq(teamUsersTable.teamPositionId, teamPositionTable.id), + teamRoleTable, + eq(teamUsersTable.teamRoleId, teamRoleTable.id), ) .limit(queryParameters.limit) .offset(queryParameters.offset) diff --git a/src/request-handling/users.ts b/src/request-handling/users.ts index 2a85670..ae41884 100644 --- a/src/request-handling/users.ts +++ b/src/request-handling/users.ts @@ -24,7 +24,7 @@ export const teamUserRequestParser = z .object({ id: serialIdParser, teamId: serialIdParser, - teamPositionId: serialIdParser, + teamRoleId: serialIdParser, isTeamLeader: z.boolean().optional(), username: z.string().nonempty(), }) diff --git a/src/response-handling/users.ts b/src/response-handling/users.ts index 8eae283..1727ffd 100644 --- a/src/response-handling/users.ts +++ b/src/response-handling/users.ts @@ -16,7 +16,7 @@ export type UserKey = User["id"]; export const teamUserSelectSchema = createSelectSchema(teamUsersTable) .merge(createSelectSchema(usersTable)) - .extend({ teamPositionName: z.string() }) + .extend({ teamRoleName: z.string() }) .strict() .readonly(); export type TeamUser = z.infer; From 3967e7407a5dee97501de83236d43836bdd3d79a Mon Sep 17 00:00:00 2001 From: Kristoffer Fredrik Dahle Melen Date: Tue, 20 Oct 2026 11:10:05 +0200 Subject: [PATCH 03/10] Fixed test failing. --- .github/workflows/pull-request-check.yml | 4 ++-- docker-compose.yml | 6 ++++++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/.github/workflows/pull-request-check.yml b/.github/workflows/pull-request-check.yml index e6dfe30..5c2c586 100644 --- a/.github/workflows/pull-request-check.yml +++ b/.github/workflows/pull-request-check.yml @@ -44,7 +44,7 @@ jobs: - name: Install pnpm run: pnpm install - name: Docker compose - run: docker compose up -d + run: docker compose up -d --wait - name: Generate migration files run: pnpm db:generate - name: Migrate database @@ -76,7 +76,7 @@ jobs: - name: Install pnpm run: pnpm install - name: Docker compose - run: docker compose up -d + run: docker compose up -d --wait - name: Generate migration files run: pnpm db:generate - name: Migrate database diff --git a/docker-compose.yml b/docker-compose.yml index c14af96..bfa0f43 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -5,6 +5,12 @@ services: - POSTGRES_PASSWORD=${DATABASE_PASSWORD} - POSTGRES_USER=${DATABASE_USER} - POSTGRES_DB=${DATABASE_NAME} + healthcheck: + test: ["CMD-SHELL", "pg_isready -U $$POSTGRES_USER -d $$POSTGRES_DB"] + interval: 5s + timeout: 5s + retries: 5 + start_period: 10s ports: - ${DATABASE_PORT}:5432 volumes: From b7b0d5ff4994bd7427f4ab3f7532c7c9206b25e3 Mon Sep 17 00:00:00 2001 From: Tobias Hegge Date: Tue, 20 Oct 2026 11:10:21 +0200 Subject: [PATCH 04/10] added companyEmail field to users --- db/tables/users.ts | 1 + package.json | 3 ++- src/db-access/users.ts | 5 +++++ 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/db/tables/users.ts b/db/tables/users.ts index ce34893..3cd3fe0 100644 --- a/db/tables/users.ts +++ b/db/tables/users.ts @@ -19,6 +19,7 @@ export const usersTable = mainSchema.table("users", { personalEmail: text("personalEmail").notNull().unique(), phoneNumber: text("phoneNumber").notNull().unique(), profilePictureUrl: text("profilePictureUrl"), + companyEmail: text("companyEmail").unique(), }); export const usersRelations = relations(usersTable, ({ one, many }) => ({ fieldOfStudy: one(fieldsOfStudyTable, { diff --git a/package.json b/package.json index cfc9b90..7629e6c 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,8 @@ "db:migrate": "drizzle-kit migrate --config=db/config/drizzle.config.ts", "db:studio": "drizzle-kit studio --config=db/config/drizzle.config.ts", "db:seed": "tsx ./db/seeding/seeding.ts", - "docs:generate": "tsx ./openapi/generate-document.ts" + "docs:generate": "tsx ./openapi/generate-document.ts", + "db:push": "drizzle-kit push --config=db/config/drizzle.config.ts" }, "license": "ISC", "dependencies": { diff --git a/src/db-access/users.ts b/src/db-access/users.ts index ec10813..fb0fd9b 100644 --- a/src/db-access/users.ts +++ b/src/db-access/users.ts @@ -58,6 +58,7 @@ export async function selectTeamUsersById( phoneNumber: usersTable.phoneNumber, personalEmail: usersTable.personalEmail, profilePictureUrl: usersTable.profilePictureUrl, + companyEmail: usersTable.companyEmail, teamId: teamUsersTable.teamId, username: teamUsersTable.username, teamRoleId: teamUsersTable.teamRoleId, @@ -94,6 +95,7 @@ export async function selectAssistantUsersById( personalEmail: usersTable.personalEmail, bankAccountNumber: usersTable.bankAccountNumber, profilePictureUrl: usersTable.profilePictureUrl, + companyEmail: usersTable.companyEmail, }) .from(assistantUsersTable) .where(inArray(assistantUsersTable.id, userIds)) @@ -137,6 +139,7 @@ export async function selectTeamUsers( phoneNumber: usersTable.phoneNumber, personalEmail: usersTable.personalEmail, profilePictureUrl: usersTable.profilePictureUrl, + companyEmail: usersTable.companyEmail, teamId: teamUsersTable.teamId, username: teamUsersTable.username, teamRoleId: teamUsersTable.teamRoleId, @@ -173,6 +176,7 @@ export async function selectTeamUsersByTeamid( phoneNumber: usersTable.phoneNumber, personalEmail: usersTable.personalEmail, profilePictureUrl: usersTable.profilePictureUrl, + companyEmail: usersTable.companyEmail, teamId: teamUsersTable.teamId, username: teamUsersTable.username, teamRoleId: teamUsersTable.teamRoleId, @@ -209,6 +213,7 @@ export async function selectAssistantUsers( phoneNumber: usersTable.phoneNumber, personalEmail: usersTable.personalEmail, profilePictureUrl: usersTable.profilePictureUrl, + companyEmail: usersTable.companyEmail, }) .from(assistantUsersTable) .innerJoin(usersTable, eq(assistantUsersTable.id, usersTable.id)) From 90b2a20bf3ac0605705fdf2d5aeb1a76caf8fddd Mon Sep 17 00:00:00 2001 From: Tobias Hegge Date: Tue, 20 Oct 2026 11:46:25 +0200 Subject: [PATCH 05/10] changed teamUsers pk to be composite of userId and teamId --- db/tables/users.ts | 40 ++++++++++++++++++++++++++-------------- 1 file changed, 26 insertions(+), 14 deletions(-) diff --git a/db/tables/users.ts b/db/tables/users.ts index 3cd3fe0..d1e6e66 100644 --- a/db/tables/users.ts +++ b/db/tables/users.ts @@ -1,6 +1,12 @@ import { mainSchema } from "@/db/tables/schema"; import { relations } from "drizzle-orm"; -import { boolean, integer, serial, text } from "drizzle-orm/pg-core"; +import { + boolean, + integer, + primaryKey, + serial, + text, +} from "drizzle-orm/pg-core"; import { expensesTable } from "@/db/tables/expenses"; import { fieldsOfStudyTable } from "@/db/tables/fields-of-study"; @@ -36,19 +42,25 @@ export const teamRoleTable = mainSchema.table("teamRole", { name: text("name").notNull().unique(), }); -export const teamUsersTable = mainSchema.table("teamUsers", { - id: integer("id") - .primaryKey() - .references(() => usersTable.id), - teamId: integer("teamId") - .notNull() - .references(() => teamsTable.id), - username: text("username").notNull().unique(), - teamRoleId: integer("teamRoleId") - .notNull() - .references(() => teamRoleTable.id), - isTeamLeader: boolean("isTeamLeader").notNull().default(false), -}); +export const teamUsersTable = mainSchema.table( + "teamUsers", + { + id: integer("id").references(() => usersTable.id), + teamId: integer("teamId") + .notNull() + .references(() => teamsTable.id), + username: text("username").notNull().unique(), + teamRoleId: integer("teamRoleId") + .notNull() + .references(() => teamRoleTable.id), + isTeamLeader: boolean("isTeamLeader").notNull().default(false), + }, + (table) => ({ + pk: primaryKey({ + columns: [table.id, table.teamId], + }), + }), +); export const teamUsersRelations = relations( teamUsersTable, ({ one, many }) => ({ From 77e1836db25445fb1b05563017ec37331c761fc0 Mon Sep 17 00:00:00 2001 From: Tobias Hegge Date: Tue, 20 Oct 2026 12:05:34 +0200 Subject: [PATCH 06/10] added notnull to userid reference from teamusers --- db/tables/users.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/db/tables/users.ts b/db/tables/users.ts index d1e6e66..6e6970c 100644 --- a/db/tables/users.ts +++ b/db/tables/users.ts @@ -45,7 +45,7 @@ export const teamRoleTable = mainSchema.table("teamRole", { export const teamUsersTable = mainSchema.table( "teamUsers", { - id: integer("id").references(() => usersTable.id), + id: integer("id").notNull().references(() => usersTable.id), teamId: integer("teamId") .notNull() .references(() => teamsTable.id), From 57983f74d5298fd5e2d12a86d2ff42f434134501 Mon Sep 17 00:00:00 2001 From: Tobias Hegge Date: Tue, 20 Oct 2026 12:50:09 +0200 Subject: [PATCH 07/10] updated fks to teamUsers to match composite pk --- db/tables/interviews.ts | 19 ++++++++++---- db/tables/parent-meetings.ts | 45 ++++++++++++++++++++++----------- db/tables/team-semester-user.ts | 28 ++++++++++++++------ db/tables/users.ts | 4 ++- 4 files changed, 67 insertions(+), 29 deletions(-) diff --git a/db/tables/interviews.ts b/db/tables/interviews.ts index f9dfc0a..041fb85 100644 --- a/db/tables/interviews.ts +++ b/db/tables/interviews.ts @@ -1,10 +1,11 @@ import { assistantApplicationsTable } from "@/db/tables/applications"; import { interviewSchemasTable } from "@/db/tables/interview-schemas"; import { mainSchema } from "@/db/tables/schema"; +import { teamsTable } from "@/db/tables/teams"; import { teamUsersTable } from "@/db/tables/users"; import type { Json } from "@/lib/json-schema"; import { relations } from "drizzle-orm"; -import { primaryKey } from "drizzle-orm/pg-core"; +import { foreignKey, primaryKey } from "drizzle-orm/pg-core"; import { boolean, integer, json, serial, timestamp } from "drizzle-orm/pg-core"; export const interviewsTable = mainSchema.table("interviews", { @@ -38,14 +39,19 @@ export const interviewHoldersTable = mainSchema.table( interviewId: integer("integerId") .notNull() .references(() => interviewsTable.id), - interviewHolderId: integer("interviewHolderId") + interviewHolderId: integer("interviewHolderId").notNull(), + teamId: integer("teamId") .notNull() - .references(() => teamUsersTable.id), + .references(() => teamsTable.id), }, (table) => ({ compositePrimaryKey: primaryKey({ columns: [table.interviewId, table.interviewHolderId], }), + teamUserForeignKey: foreignKey({ + columns: [table.interviewHolderId, table.teamId], + foreignColumns: [teamUsersTable.id, teamUsersTable.teamId], + }), }), ); @@ -57,8 +63,11 @@ export const interviewHoldersRelations = relations( references: [interviewsTable.id], }), interviewHolder: one(teamUsersTable, { - fields: [interviewHoldersTable.interviewHolderId], - references: [teamUsersTable.id], + fields: [ + interviewHoldersTable.interviewHolderId, + interviewHoldersTable.teamId, + ], + references: [teamUsersTable.id, teamUsersTable.teamId], }), }), ); diff --git a/db/tables/parent-meetings.ts b/db/tables/parent-meetings.ts index 1337298..e1166d9 100644 --- a/db/tables/parent-meetings.ts +++ b/db/tables/parent-meetings.ts @@ -1,5 +1,6 @@ import { relations } from "drizzle-orm"; import { + foreignKey, integer, interval, serial, @@ -8,21 +9,32 @@ import { } from "drizzle-orm/pg-core"; import { mainSchema } from "./schema"; import { semestersTable } from "./semesters"; +import { teamsTable } from "./teams"; import { teamUsersTable } from "./users"; -export const parentMeetingsTable = mainSchema.table("parentMeetings", { - id: serial("id").primaryKey(), - dateTime: timestamp("dateTime").notNull(), - duration: interval("duration").notNull(), - place: text("place").notNull(), - semesterId: integer("semesterId") - .notNull() - .references(() => semestersTable.id), - // id of the team user responsible for organizing and conducting the parent meeting - responsibleUserId: integer("responsibleUserId") - .notNull() - .references(() => teamUsersTable.id), -}); +export const parentMeetingsTable = mainSchema.table( + "parentMeetings", + { + id: serial("id").primaryKey(), + dateTime: timestamp("dateTime").notNull(), + duration: interval("duration").notNull(), + place: text("place").notNull(), + semesterId: integer("semesterId") + .notNull() + .references(() => semestersTable.id), + // id of the team user responsible for organizing and conducting the parent meeting + responsibleUserId: integer("responsibleUserId").notNull(), + teamId: integer("teamId") + .notNull() + .references(() => teamsTable.id), + }, + (table) => ({ + teamUserForeignKey: foreignKey({ + columns: [table.responsibleUserId, table.teamId], + foreignColumns: [teamUsersTable.id, teamUsersTable.teamId], + }), + }), +); export const parentMeetingsRelations = relations( parentMeetingsTable, @@ -32,8 +44,11 @@ export const parentMeetingsRelations = relations( references: [semestersTable.id], }), teamUser: one(teamUsersTable, { - fields: [parentMeetingsTable.responsibleUserId], - references: [teamUsersTable.id], + fields: [ + parentMeetingsTable.responsibleUserId, + parentMeetingsTable.teamId, + ], + references: [teamUsersTable.id, teamUsersTable.teamId], }), }), ); diff --git a/db/tables/team-semester-user.ts b/db/tables/team-semester-user.ts index 86de272..cd44b4a 100644 --- a/db/tables/team-semester-user.ts +++ b/db/tables/team-semester-user.ts @@ -1,15 +1,24 @@ import { relations } from "drizzle-orm"; -import { integer } from "drizzle-orm/pg-core"; +import { foreignKey, integer } from "drizzle-orm/pg-core"; import { mainSchema } from "./schema"; import { semestersTable } from "./semesters"; import { teamsTable } from "./teams"; import { teamUsersTable } from "./users"; -export const teamSemesterUsersTable = mainSchema.table("teamSemesterUser", { - teamId: integer("teamId").references(() => teamsTable.id), - semesterId: integer("semesterId").references(() => semestersTable.id), - teamUserId: integer("teamUserId").references(() => teamUsersTable.id), -}); +export const teamSemesterUsersTable = mainSchema.table( + "teamSemesterUser", + { + teamId: integer("teamId").references(() => teamsTable.id), + semesterId: integer("semesterId").references(() => semestersTable.id), + teamUserId: integer("teamUserId"), + }, + (table) => ({ + teamUserForeignKey: foreignKey({ + columns: [table.teamUserId, table.teamId], + foreignColumns: [teamUsersTable.id, teamUsersTable.teamId], + }), + }), +); export const teamSemesterUsersRelations = relations( teamSemesterUsersTable, @@ -23,8 +32,11 @@ export const teamSemesterUsersRelations = relations( references: [semestersTable.id], }), teamUser: one(teamUsersTable, { - fields: [teamSemesterUsersTable.teamUserId], - references: [teamUsersTable.id], + fields: [ + teamSemesterUsersTable.teamUserId, + teamSemesterUsersTable.teamId, + ], + references: [teamUsersTable.id, teamUsersTable.teamId], }), }), ); diff --git a/db/tables/users.ts b/db/tables/users.ts index 6e6970c..6b32f90 100644 --- a/db/tables/users.ts +++ b/db/tables/users.ts @@ -45,7 +45,9 @@ export const teamRoleTable = mainSchema.table("teamRole", { export const teamUsersTable = mainSchema.table( "teamUsers", { - id: integer("id").notNull().references(() => usersTable.id), + id: integer("id") + .notNull() + .references(() => usersTable.id), teamId: integer("teamId") .notNull() .references(() => teamsTable.id), From 27bfd70b1c1e0188e7166726c74fdfc1ea63e732 Mon Sep 17 00:00:00 2001 From: Kristoffer Fredrik Dahle Melen Date: Tue, 20 Oct 2026 10:48:29 +0200 Subject: [PATCH 08/10] Added a teamPosition table, and added isTeamLeader and teamPositionId to teamUser table. Also added profilePictureUrl to user table. --- db/tables/users.ts | 38 ++++++++++---------------------------- 1 file changed, 10 insertions(+), 28 deletions(-) diff --git a/db/tables/users.ts b/db/tables/users.ts index 6b32f90..921d2b9 100644 --- a/db/tables/users.ts +++ b/db/tables/users.ts @@ -24,8 +24,6 @@ export const usersTable = mainSchema.table("users", { bankAccountNumber: text("accountNumber").unique(), personalEmail: text("personalEmail").notNull().unique(), phoneNumber: text("phoneNumber").notNull().unique(), - profilePictureUrl: text("profilePictureUrl"), - companyEmail: text("companyEmail").unique(), }); export const usersRelations = relations(usersTable, ({ one, many }) => ({ fieldOfStudy: one(fieldsOfStudyTable, { @@ -37,32 +35,20 @@ export const usersRelations = relations(usersTable, ({ one, many }) => ({ assistantUser: one(assistantUsersTable), })); -export const teamRoleTable = mainSchema.table("teamRole", { +export const teamPositionTable = mainSchema.table("teamPosition", { id: serial("id").primaryKey(), name: text("name").notNull().unique(), }); -export const teamUsersTable = mainSchema.table( - "teamUsers", - { - id: integer("id") - .notNull() - .references(() => usersTable.id), - teamId: integer("teamId") - .notNull() - .references(() => teamsTable.id), - username: text("username").notNull().unique(), - teamRoleId: integer("teamRoleId") - .notNull() - .references(() => teamRoleTable.id), - isTeamLeader: boolean("isTeamLeader").notNull().default(false), - }, - (table) => ({ - pk: primaryKey({ - columns: [table.id, table.teamId], - }), - }), -); +export const teamUsersTable = mainSchema.table("teamUsers", { + id: integer("id") + .primaryKey() + .references(() => usersTable.id), + teamId: integer("teamId") + .notNull() + .references(() => teamsTable.id), + username: text("username").notNull().unique(), +}); export const teamUsersRelations = relations( teamUsersTable, ({ one, many }) => ({ @@ -75,10 +61,6 @@ export const teamUsersRelations = relations( references: [teamsTable.id], }), teamSemesters: many(teamSemesterUsersTable), - teamRole: one(teamRoleTable, { - fields: [teamUsersTable.teamRoleId], - references: [teamRoleTable.id], - }), }), ); From 873b30d030597e03d9e585ecf5c01944fcbd8f6f Mon Sep 17 00:00:00 2001 From: Kristoffer Fredrik Dahle Melen Date: Tue, 20 Oct 2026 10:55:43 +0200 Subject: [PATCH 09/10] Renamed Position to Role --- db/tables/users.ts | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/db/tables/users.ts b/db/tables/users.ts index 921d2b9..b07611e 100644 --- a/db/tables/users.ts +++ b/db/tables/users.ts @@ -35,7 +35,7 @@ export const usersRelations = relations(usersTable, ({ one, many }) => ({ assistantUser: one(assistantUsersTable), })); -export const teamPositionTable = mainSchema.table("teamPosition", { +export const teamRoleTable = mainSchema.table("teamRole", { id: serial("id").primaryKey(), name: text("name").notNull().unique(), }); @@ -48,6 +48,10 @@ export const teamUsersTable = mainSchema.table("teamUsers", { .notNull() .references(() => teamsTable.id), username: text("username").notNull().unique(), + teamRoleId: integer("teamRoleId") + .notNull() + .references(() => teamRoleTable.id), + isTeamLeader: boolean("isTeamLeader").notNull().default(false), }); export const teamUsersRelations = relations( teamUsersTable, @@ -61,6 +65,10 @@ export const teamUsersRelations = relations( references: [teamsTable.id], }), teamSemesters: many(teamSemesterUsersTable), + teamRole: one(teamRoleTable, { + fields: [teamUsersTable.teamRoleId], + references: [teamRoleTable.id], + }), }), ); From c9fdfb1baac57fff0318af4c59d90ebef565e223 Mon Sep 17 00:00:00 2001 From: Tobias Hegge Date: Tue, 20 Oct 2026 11:10:21 +0200 Subject: [PATCH 10/10] added companyEmail field to users --- db/tables/users.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/db/tables/users.ts b/db/tables/users.ts index b07611e..5762b42 100644 --- a/db/tables/users.ts +++ b/db/tables/users.ts @@ -24,6 +24,8 @@ export const usersTable = mainSchema.table("users", { bankAccountNumber: text("accountNumber").unique(), personalEmail: text("personalEmail").notNull().unique(), phoneNumber: text("phoneNumber").notNull().unique(), + profilePictureUrl: text("profilePictureUrl"), + companyEmail: text("companyEmail").unique(), }); export const usersRelations = relations(usersTable, ({ one, many }) => ({ fieldOfStudy: one(fieldsOfStudyTable, {