From 2ccd98c697d4c6bd43c6952a9f6544c1a668bd7e Mon Sep 17 00:00:00 2001
From: Theodor Kvalsvik Lauritzen
<19690242+theodorklauritzen@users.noreply.github.com>
Date: Mon, 27 Oct 2025 19:24:51 +0100
Subject: [PATCH 1/5] chore(api): Add image details when fetching user data
---
src/app/events/[nameAndId]/page.tsx | 4 ++--
src/services/users/operations.ts | 6 ++++++
2 files changed, 8 insertions(+), 2 deletions(-)
diff --git a/src/app/events/[nameAndId]/page.tsx b/src/app/events/[nameAndId]/page.tsx
index c47ab9619..926c59704 100644
--- a/src/app/events/[nameAndId]/page.tsx
+++ b/src/app/events/[nameAndId]/page.tsx
@@ -40,7 +40,7 @@ export default async function Event({ params }: PropTypes) {
const tags = unwrapActionReturn(await readEventTagsAction())
- const ownRegitration = event.eventRegistrations.length ? event.eventRegistrations[0] : undefined
+ const ownRegistration = event.eventRegistrations.length ? event.eventRegistrations[0] : undefined
return (
@@ -111,7 +111,7 @@ export default async function Event({ params }: PropTypes) {
{event.waitingList &&
På venteliste: {event.numOnWaitingList}
}
-
+
> :
Dette arrangementet tar ikke påmeldinger
diff --git a/src/services/users/operations.ts b/src/services/users/operations.ts
index d25a69f69..94857d65c 100644
--- a/src/services/users/operations.ts
+++ b/src/services/users/operations.ts
@@ -587,6 +587,12 @@ export const userOperations = {
}
})
+ if (!user.image) {
+ user.image = await imageOperations.readSpecial({
+ params: { special: 'DEFAULT_PROFILE_IMAGE' },
+ })
+ }
+
return {
balance: 191900,
user,
From 0a57c2a692014c7e40a4c02e75a9c47af6829755 Mon Sep 17 00:00:00 2001
From: Theodor Kvalsvik Lauritzen
<19690242+theodorklauritzen@users.noreply.github.com>
Date: Mon, 27 Oct 2025 20:09:04 +0100
Subject: [PATCH 2/5] refactor(fridge-api): Change the way to register a new
studentCard with a QR code
---
.../connectStudentCard/[newCard]/page.tsx | 32 ++++++++
src/app/api/users/connectStudentCard/route.ts | 7 --
.../settings/RegisterStudentCardButton.tsx | 24 ------
.../[username]/(user-admin)/settings/page.tsx | 2 -
src/services/users/actions.ts | 3 +-
src/services/users/auth.ts | 2 +
src/services/users/constants.ts | 1 -
src/services/users/operations.ts | 74 +++----------------
8 files changed, 47 insertions(+), 98 deletions(-)
create mode 100644 src/app/api/users/connectStudentCard/[newCard]/page.tsx
delete mode 100644 src/app/api/users/connectStudentCard/route.ts
delete mode 100644 src/app/users/[username]/(user-admin)/settings/RegisterStudentCardButton.tsx
diff --git a/src/app/api/users/connectStudentCard/[newCard]/page.tsx b/src/app/api/users/connectStudentCard/[newCard]/page.tsx
new file mode 100644
index 000000000..c01c6326f
--- /dev/null
+++ b/src/app/api/users/connectStudentCard/[newCard]/page.tsx
@@ -0,0 +1,32 @@
+import Form from '@/components/Form/Form'
+import PageWrapper from '@/components/PageWrapper/PageWrapper'
+import { configureAction } from '@/services/configureAction'
+import { connectStudentCardAction } from '@/services/users/actions'
+
+
+export default async function ConnectStucentCard({ params }: { params: Promise<{ newCard: string }> }) {
+ const newCard = (await params).newCard
+
+ const cardParsed = parseInt(newCard, 10)
+
+ if (isNaN(cardParsed)) {
+ return
Kortet er ikke gyldig. Det må være et heltall.
+ }
+
+
+ return
+ Trykk på knappen under for å registrere kortet ditt: {cardParsed}
+
+
+
+}
diff --git a/src/app/api/users/connectStudentCard/route.ts b/src/app/api/users/connectStudentCard/route.ts
deleted file mode 100644
index 6ce1703b6..000000000
--- a/src/app/api/users/connectStudentCard/route.ts
+++ /dev/null
@@ -1,7 +0,0 @@
-import { apiHandler } from '@/api/apiHandler'
-import { userOperations } from '@/services/users/operations'
-
-
-export const POST = apiHandler({
- serviceOperation: userOperations.connectStudentCard
-})
diff --git a/src/app/users/[username]/(user-admin)/settings/RegisterStudentCardButton.tsx b/src/app/users/[username]/(user-admin)/settings/RegisterStudentCardButton.tsx
deleted file mode 100644
index ca098eb37..000000000
--- a/src/app/users/[username]/(user-admin)/settings/RegisterStudentCardButton.tsx
+++ /dev/null
@@ -1,24 +0,0 @@
-'use client'
-import { registerStudentCardInQueueAction } from '@/services/users/actions'
-import { configureAction } from '@/services/configureAction'
-import Form from '@/app/_components/Form/Form'
-import { studentCardRegistrationExpiry } from '@/services/users/constants'
-
-
-export default function RegisterStudentCardButton({
- userId,
-}: {
- userId: number,
-}) {
- return
-}
diff --git a/src/app/users/[username]/(user-admin)/settings/page.tsx b/src/app/users/[username]/(user-admin)/settings/page.tsx
index ffefa3d44..98a53a843 100644
--- a/src/app/users/[username]/(user-admin)/settings/page.tsx
+++ b/src/app/users/[username]/(user-admin)/settings/page.tsx
@@ -1,4 +1,3 @@
-import RegisterStudentCardButton from './RegisterStudentCardButton'
import { getProfileForAdmin } from '@/app/users/[username]/(user-admin)/getProfileForAdmin'
import Image from '@/components/Image/Image'
import type { PropTypes } from '@/app/users/[username]/page'
@@ -10,7 +9,6 @@ export default async function UserSettings({ params }: PropTypes) {
Generelle Instillinger
-
)
}
diff --git a/src/services/users/actions.ts b/src/services/users/actions.ts
index 17de12e36..04fb4edd1 100644
--- a/src/services/users/actions.ts
+++ b/src/services/users/actions.ts
@@ -41,4 +41,5 @@ export const readGroupsForPageFilteringAction = makeAction(groupOperations.readG
export const updateUserAction = makeAction(userOperations.update)
export const registerNewEmailAction = makeAction(userOperations.registerNewEmail)
export const registerUser = makeAction(userOperations.register)
-export const registerStudentCardInQueueAction = makeAction(userOperations.registerStudentCardInQueue)
+
+export const connectStudentCardAction = makeAction(userOperations.connectStudentCard)
diff --git a/src/services/users/auth.ts b/src/services/users/auth.ts
index 6ca87888c..636532e78 100644
--- a/src/services/users/auth.ts
+++ b/src/services/users/auth.ts
@@ -1,4 +1,5 @@
import { RequirePermission } from '@/auth/auther/RequirePermission'
+import { RequireUser } from '@/auth/auther/RequireUser'
import { RequireUserFieldOrPermission } from '@/auth/auther/RequireUserFieldOrPermission'
import { RequireUserId } from '@/auth/auther/RequireUserId'
import { RequireUserIdOrPermission } from '@/auth/auther/RequireUserIdOrPermission'
@@ -10,6 +11,7 @@ export const userAuth = {
readOrNull: RequireUserFieldOrPermission.staticFields({ permission: 'USERS_READ' }),
readPage: RequirePermission.staticFields({ permission: 'USERS_READ' }),
create: RequirePermission.staticFields({ permission: 'USERS_CREATE' }),
+ connectStudentCardQR: RequireUser.staticFields({}),
connectStudentCard: RequirePermission.staticFields({ permission: 'USERS_CONNECT_STUDENT_CARD' }),
registerStudentCardInQueue: RequireUserIdOrPermission.staticFields({ permission: 'USERS_CONNECT_STUDENT_CARD' }),
registerNewEmail: RequireUserIdOrPermission.staticFields({ permission: 'USERS_UPDATE' }),
diff --git a/src/services/users/constants.ts b/src/services/users/constants.ts
index fd21e4d5e..308aeae12 100644
--- a/src/services/users/constants.ts
+++ b/src/services/users/constants.ts
@@ -2,7 +2,6 @@ import { createSelection } from '@/services/createSelection'
import type { Prisma, User, SEX } from '@prisma/client'
export const maxNumberOfGroupsInFilter = 7
-export const studentCardRegistrationExpiry = 2 // minutter
// TODO: This needs to be divived into seperate filters, depending on how much information is needed
export const userFieldsToExpose = [
diff --git a/src/services/users/operations.ts b/src/services/users/operations.ts
index 94857d65c..17203a9e7 100644
--- a/src/services/users/operations.ts
+++ b/src/services/users/operations.ts
@@ -4,7 +4,6 @@ import { userAuth } from './auth'
import {
maxNumberOfGroupsInFilter,
standardMembershipSelection,
- studentCardRegistrationExpiry,
userFilterSelection
} from './constants'
import { imageOperations } from '@/services/images/operations'
@@ -295,72 +294,21 @@ export const userOperations = {
}),
connectStudentCard: defineOperation({
- authorizer: () => userAuth.connectStudentCard.dynamicFields({}),
- dataSchema: userSchemas.connectStudentCard,
- opensTransaction: true,
- operation: async ({ prisma, data }) => {
- const currentQueue = await prisma.registerStudentCardQueue.findMany({
- where: {
- expiry: {
- gt: new Date(),
- },
- },
- orderBy: {
- expiry: 'desc',
- },
- take: 1,
- })
-
- if (currentQueue.length === 0) {
- throw new ServerError(
- 'NOT FOUND',
- `No user has placed them selves in the registration queue at ${process.env.DOMAIN}`
- )
- }
-
- const userId = currentQueue[0].userId
- const result = await prisma.$transaction([
- prisma.registerStudentCardQueue.delete({
- where: {
- userId,
- }
- }),
- prisma.user.update({
- where: {
- id: userId,
- },
- data: {
- studentCard: data.studentCard,
- },
- select: userFilterSelection,
- })
- ])
-
- return result[1]
- }
- }),
-
- registerStudentCardInQueue: defineOperation({
+ authorizer: () => userAuth.connectStudentCardQR.dynamicFields({}),
paramsSchema: z.object({
- userId: z.number(),
+ studentCard: z.coerce.number().int().min(0).optional(),
}),
- authorizer: ({ params }) => userAuth.registerStudentCardInQueue.dynamicFields(params),
- operation: async (args) => {
- const expiry = (new Date()).getTime() + studentCardRegistrationExpiry * 60 * 1000
- await args.prisma.registerStudentCardQueue.upsert({
+ operation: async ({ prisma, params, session }) => {
+ if (!session.user) {
+ throw new ServerError('DISSALLOWED', 'This endpoint requires a user conencted to the session.')
+ }
+
+ await prisma.user.update({
where: {
- userId: args.params.userId,
+ id: session.user.id
},
- update: {
- expiry: new Date(expiry),
- },
- create: {
- user: {
- connect: {
- id: args.params.userId,
- },
- },
- expiry: new Date(expiry),
+ data: {
+ studentCard: params.studentCard?.toString()
}
})
}
From 26439f34428a5d772a54b557fa6efe9a5486517c Mon Sep 17 00:00:00 2001
From: Theodor Kvalsvik Lauritzen
<19690242+theodorklauritzen@users.noreply.github.com>
Date: Mon, 27 Oct 2025 21:33:34 +0100
Subject: [PATCH 3/5] chore(fridge-api): Remove unused db table, more api url,
add studentcard to dobbelOmega
---
package.json | 2 +-
.../[newCard]/page.tsx | 0
src/prisma/schema/user.prisma | 7 ----
.../src/dobbelOmega/migrateCommittees.ts | 10 +++---
.../seeder/src/dobbelOmega/migrateUsers.ts | 36 ++++++++++++++++---
src/prisma/vevenSchema/schema.veven.prisma | 14 ++++----
6 files changed, 44 insertions(+), 25 deletions(-)
rename src/app/{api/users/connectStudentCard => connect-student-card}/[newCard]/page.tsx (100%)
diff --git a/package.json b/package.json
index 8b36cb3a8..e7092fa17 100644
--- a/package.json
+++ b/package.json
@@ -19,7 +19,7 @@
"docker:dobbelOmega:run": "docker compose -f docker-compose.dev.yml exec projectnext /bin/sh -c 'npm run dobbelOmega:run'",
"docker:seed": "docker compose -f docker-compose.dev.yml exec projectnext /bin/sh -c 'npm run seed'",
"seed": "npx prisma db push --force-reset --skip-generate && IGNORE_SERVER_ONLY=true npx prisma db seed",
- "dev-seed": "(if [ ! -d './node_modules/.prisma-dobbel-omega' ]; then npm run dobbelOmega-generate; fi) && npm run seed && npm run dev"
+ "dev-seed": "(if [ ! -d './node_modules/.prisma-dobbel-omega' ]; then npm run dobbelOmega:generate; fi) && npm run seed && npm run dev"
},
"prisma": {
"schema": "src/prisma/schema",
diff --git a/src/app/api/users/connectStudentCard/[newCard]/page.tsx b/src/app/connect-student-card/[newCard]/page.tsx
similarity index 100%
rename from src/app/api/users/connectStudentCard/[newCard]/page.tsx
rename to src/app/connect-student-card/[newCard]/page.tsx
diff --git a/src/prisma/schema/user.prisma b/src/prisma/schema/user.prisma
index c31cd5548..ffe30f225 100644
--- a/src/prisma/schema/user.prisma
+++ b/src/prisma/schema/user.prisma
@@ -37,7 +37,6 @@ model User {
dots DotWrapper[] @relation(name: "dot_user")
dotsAccused DotWrapper[] @relation(name: "dot_accuser")
- registerStudentCardQueue RegisterStudentCardQueue[]
Event Event[]
cabinBooking Booking[] @relation()
@@ -70,12 +69,6 @@ model FeideAccount {
user User @relation(fields: [userId], references: [id], onDelete: Cascade, onUpdate: Cascade)
}
-model RegisterStudentCardQueue {
- userId Int @id
- user User @relation(fields: [userId], references: [id], onDelete: Cascade)
- expiry DateTime
-}
-
model ContactDetails {
id Int @id @default(autoincrement())
name String
diff --git a/src/prisma/seeder/src/dobbelOmega/migrateCommittees.ts b/src/prisma/seeder/src/dobbelOmega/migrateCommittees.ts
index 0ba09d88b..01ba4bf81 100644
--- a/src/prisma/seeder/src/dobbelOmega/migrateCommittees.ts
+++ b/src/prisma/seeder/src/dobbelOmega/migrateCommittees.ts
@@ -59,13 +59,13 @@ export default async function migrateCommittees(
// })).order
await Promise.all(committees.map(async committee => {
- const committeeParagraph = await readCommitteParagraph(`${committee.shortName}_p.md`)
- const committeArticle = await readCommitteArticle(`${committee.shortName}_a.md`)
+ const committeeParagraph = await readCommitteParagraph(`${committee.shortname}_p.md`)
+ const committeArticle = await readCommitteArticle(`${committee.shortname}_a.md`)
const newCommittee = await pnPrisma.committee.create({
data: {
name: committee.name,
- shortName: committee.shortName,
+ shortName: committee.shortname,
videoLink: committee.applicationVideo,
logoImage: {
create: {
@@ -85,7 +85,7 @@ export default async function migrateCommittees(
name: committee.name,
coverImage: {
create: {
- name: `${committee.shortName}'s bilde`
+ name: `${committee.shortname}'s bilde`
}
},
articleSections: committeArticle
@@ -102,7 +102,7 @@ export default async function migrateCommittees(
await Promise.all(committee.CommitteeMembers.map(async member => {
if (member.UserId === null) {
- console.warn(`${committee.shortName} has a member that is not connected to a user!`)
+ console.warn(`${committee.shortname} has a member that is not connected to a user!`)
console.warn(member)
return
}
diff --git a/src/prisma/seeder/src/dobbelOmega/migrateUsers.ts b/src/prisma/seeder/src/dobbelOmega/migrateUsers.ts
index 9668819be..111e6d164 100644
--- a/src/prisma/seeder/src/dobbelOmega/migrateUsers.ts
+++ b/src/prisma/seeder/src/dobbelOmega/migrateUsers.ts
@@ -38,10 +38,16 @@ type ExtendedMemberGroup = Prisma.OmegaMembershipGroupGetPayload<{
const userIncluder = {
StudyProgrammes: {
select: {
- years: true
- }
- }
-}
+ years: true,
+ },
+ },
+ MoneySourceAccounts: {
+ select: {
+ NTNUCard: true,
+ },
+ },
+} satisfies VevenPrisma.UsersInclude
+
type userExtended = VevenPrisma.UsersGetPayload<{
include: typeof userIncluder
}>
@@ -192,7 +198,8 @@ export class UserMigrator {
updatedAt: user.updatedAt,
imageId: vevenIdToPnId(this.imageIdMap, user.ImageId),
archived: user.archived,
- }
+ } satisfies Prisma.UserUncheckedCreateInput
+
try {
pnUser = await this.pnPrisma.user.create({
data: userData
@@ -215,6 +222,25 @@ export class UserMigrator {
}
this.userIdMap[user.id] = pnUser.id
+ // Try to add the studentCard, this can throw an unique contraint exception
+ if (user.MoneySourceAccounts?.NTNUCard) {
+ try {
+ await this.pnPrisma.user.update({
+ where: {
+ id: pnUser.id
+ },
+ data: {
+ studentCard: user.MoneySourceAccounts?.NTNUCard
+ }
+ })
+ } catch (e) {
+ console.error(
+ `Failed to conenct StudentCard to user. StudentCard: ${user.MoneySourceAccounts?.NTNUCard} `,
+ `User: ${pnUser} Error: ${e}`
+ )
+ }
+ }
+
return pnUser
}
diff --git a/src/prisma/vevenSchema/schema.veven.prisma b/src/prisma/vevenSchema/schema.veven.prisma
index 78fb22d53..94c375c25 100644
--- a/src/prisma/vevenSchema/schema.veven.prisma
+++ b/src/prisma/vevenSchema/schema.veven.prisma
@@ -1,13 +1,13 @@
-datasource db {
- provider = "postgresql"
- url = env("VEVEN_DB_URI")
-}
-
generator veven {
provider = "prisma-client-js"
output = "../../../node_modules/.prisma-dobbel-omega"
}
+datasource db {
+ provider = "postgresql"
+ url = env("VEVEN_DB_URI")
+}
+
model AliasAssociations {
MailAliasId Int
UserId Int
@@ -109,7 +109,7 @@ model CommitteeMembersHist {
model Committees {
id Int @id @default(autoincrement())
name String @db.VarChar(255)
- shortName String @unique @db.VarChar(255)
+ shortname String @unique @db.VarChar(255)
applicationLead String?
applicationVideo String? @db.VarChar(255)
applicationText String?
@@ -509,7 +509,7 @@ model PollVotes {
model Polls {
id Int @id @default(autoincrement())
question String
- deadline DateTime @default(dbgenerated("'2022-10-17 19:43:15.587+02'::timestamp with time zone")) @db.Timestamptz(6)
+ deadline DateTime @default(dbgenerated("'2022-10-17 17:43:15.587+00'::timestamp with time zone")) @db.Timestamptz(6)
anonymous Boolean @default(false)
allow enum_Polls_allow @default(everyone)
createdAt DateTime @db.Timestamptz(6)
From d90048b50da3e6fbbdaab8f53d5d7885b512964d Mon Sep 17 00:00:00 2001
From: Theodor Kvalsvik Lauritzen
<19690242+theodorklauritzen@users.noreply.github.com>
Date: Mon, 27 Oct 2025 21:37:31 +0100
Subject: [PATCH 4/5] chore(fridge-api): Remove unused permission
---
src/prisma/schema/permission.prisma | 2 --
src/services/permissions/constants.ts | 6 ------
src/services/users/auth.ts | 3 +--
src/services/users/operations.ts | 2 +-
4 files changed, 2 insertions(+), 11 deletions(-)
diff --git a/src/prisma/schema/permission.prisma b/src/prisma/schema/permission.prisma
index 918d9021a..347105e96 100644
--- a/src/prisma/schema/permission.prisma
+++ b/src/prisma/schema/permission.prisma
@@ -71,8 +71,6 @@ enum Permission {
USERS_DESTROY
USERS_CREATE
- USERS_CONNECT_STUDENT_CARD
-
IMAGE_COLLECTION_CREATE
IMAGE_ADMIN
diff --git a/src/services/permissions/constants.ts b/src/services/permissions/constants.ts
index 51abb9070..e90e0fe40 100644
--- a/src/services/permissions/constants.ts
+++ b/src/services/permissions/constants.ts
@@ -206,12 +206,6 @@ export const permissionConfig = {
description: 'kan oppdatere bruker',
category: 'brukere',
},
- USERS_CONNECT_STUDENT_CARD: {
- name: 'Koble studentkort til andre brukere',
- description: `Kan legge til andre personer i registreringskøen for å
- registrere et kort og kan registrere et kort på en person`,
- category: 'brukere'
- },
IMAGE_ADMIN: {
name: 'Bildeadministrator',
description: 'kan administrere bilder',
diff --git a/src/services/users/auth.ts b/src/services/users/auth.ts
index 636532e78..065ffb03b 100644
--- a/src/services/users/auth.ts
+++ b/src/services/users/auth.ts
@@ -11,8 +11,7 @@ export const userAuth = {
readOrNull: RequireUserFieldOrPermission.staticFields({ permission: 'USERS_READ' }),
readPage: RequirePermission.staticFields({ permission: 'USERS_READ' }),
create: RequirePermission.staticFields({ permission: 'USERS_CREATE' }),
- connectStudentCardQR: RequireUser.staticFields({}),
- connectStudentCard: RequirePermission.staticFields({ permission: 'USERS_CONNECT_STUDENT_CARD' }),
+ connectStudentCard: RequireUser.staticFields({}),
registerStudentCardInQueue: RequireUserIdOrPermission.staticFields({ permission: 'USERS_CONNECT_STUDENT_CARD' }),
registerNewEmail: RequireUserIdOrPermission.staticFields({ permission: 'USERS_UPDATE' }),
updatePassword: RequireUserIdOrPermission.staticFields({ permission: 'USERS_UPDATE' }),
diff --git a/src/services/users/operations.ts b/src/services/users/operations.ts
index 17203a9e7..502023505 100644
--- a/src/services/users/operations.ts
+++ b/src/services/users/operations.ts
@@ -294,7 +294,7 @@ export const userOperations = {
}),
connectStudentCard: defineOperation({
- authorizer: () => userAuth.connectStudentCardQR.dynamicFields({}),
+ authorizer: () => userAuth.connectStudentCard.dynamicFields({}),
paramsSchema: z.object({
studentCard: z.coerce.number().int().min(0).optional(),
}),
From deb889508b9f3c230b242cf4a3bae3a3d7d8b0e3 Mon Sep 17 00:00:00 2001
From: Theodor Kvalsvik Lauritzen
<19690242+theodorklauritzen@users.noreply.github.com>
Date: Mon, 27 Oct 2025 22:05:37 +0100
Subject: [PATCH 5/5] chore(fridge-api): Remove unused auth
---
src/services/users/auth.ts | 1 -
1 file changed, 1 deletion(-)
diff --git a/src/services/users/auth.ts b/src/services/users/auth.ts
index 065ffb03b..a52e56514 100644
--- a/src/services/users/auth.ts
+++ b/src/services/users/auth.ts
@@ -12,7 +12,6 @@ export const userAuth = {
readPage: RequirePermission.staticFields({ permission: 'USERS_READ' }),
create: RequirePermission.staticFields({ permission: 'USERS_CREATE' }),
connectStudentCard: RequireUser.staticFields({}),
- registerStudentCardInQueue: RequireUserIdOrPermission.staticFields({ permission: 'USERS_CONNECT_STUDENT_CARD' }),
registerNewEmail: RequireUserIdOrPermission.staticFields({ permission: 'USERS_UPDATE' }),
updatePassword: RequireUserIdOrPermission.staticFields({ permission: 'USERS_UPDATE' }),
update: RequirePermission.staticFields({ permission: 'USERS_UPDATE' }),