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/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/connect-student-card/[newCard]/page.tsx b/src/app/connect-student-card/[newCard]/page.tsx new file mode 100644 index 000000000..c01c6326f --- /dev/null +++ b/src/app/connect-student-card/[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/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/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

-

- For å registrere studentkortet til brukeren din trykk på knappen under. - Deretter skan kortet ditt på skanneren til Koigeskabet på Lophtet, uten at du har lagt inn noen varer enda. - Fra du trykker på knappen har du { studentCardRegistrationExpiry } - minutter på deg til å skanne kortet ditt. -

-
-} 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/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/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) 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/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..a52e56514 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,8 +11,7 @@ export const userAuth = { readOrNull: RequireUserFieldOrPermission.staticFields({ permission: 'USERS_READ' }), readPage: RequirePermission.staticFields({ permission: 'USERS_READ' }), create: RequirePermission.staticFields({ permission: 'USERS_CREATE' }), - connectStudentCard: RequirePermission.staticFields({ permission: 'USERS_CONNECT_STUDENT_CARD' }), - registerStudentCardInQueue: RequireUserIdOrPermission.staticFields({ permission: 'USERS_CONNECT_STUDENT_CARD' }), + connectStudentCard: RequireUser.staticFields({}), registerNewEmail: RequireUserIdOrPermission.staticFields({ permission: 'USERS_UPDATE' }), updatePassword: RequireUserIdOrPermission.staticFields({ permission: 'USERS_UPDATE' }), update: RequirePermission.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 d25a69f69..502023505 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' @@ -296,71 +295,20 @@ 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({ 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, - }, - update: { - expiry: new Date(expiry), + id: session.user.id }, - create: { - user: { - connect: { - id: args.params.userId, - }, - }, - expiry: new Date(expiry), + data: { + studentCard: params.studentCard?.toString() } }) } @@ -587,6 +535,12 @@ export const userOperations = { } }) + if (!user.image) { + user.image = await imageOperations.readSpecial({ + params: { special: 'DEFAULT_PROFILE_IMAGE' }, + }) + } + return { balance: 191900, user,