diff --git a/src/actions/groups/interestGroups/create.ts b/src/actions/groups/interestGroups/create.ts new file mode 100644 index 000000000..3ff6d0fb5 --- /dev/null +++ b/src/actions/groups/interestGroups/create.ts @@ -0,0 +1,5 @@ +'use server' +import { Action } from '@/actions/Action' +import { InterestGroups } from '@/services/groups/interestGroups' + +export const createInterestGroupAction = Action(InterestGroups.create) diff --git a/src/actions/groups/interestGroups/destroy.ts b/src/actions/groups/interestGroups/destroy.ts new file mode 100644 index 000000000..66b15e038 --- /dev/null +++ b/src/actions/groups/interestGroups/destroy.ts @@ -0,0 +1,5 @@ +'use server' +import { ActionNoData } from '@/actions/Action' +import { InterestGroups } from '@/services/groups/interestGroups' + +export const destroyInterestGroupAction = ActionNoData(InterestGroups.destroy) diff --git a/src/actions/groups/interestGroups/read.ts b/src/actions/groups/interestGroups/read.ts new file mode 100644 index 000000000..7e86b328b --- /dev/null +++ b/src/actions/groups/interestGroups/read.ts @@ -0,0 +1,5 @@ +'use server' +import { ActionNoData } from '@/actions/Action' +import { InterestGroups } from '@/services/groups/interestGroups' + +export const readInterestGroupsAction = ActionNoData(InterestGroups.readAll) diff --git a/src/actions/groups/interestGroups/update.ts b/src/actions/groups/interestGroups/update.ts new file mode 100644 index 000000000..331dc5ecf --- /dev/null +++ b/src/actions/groups/interestGroups/update.ts @@ -0,0 +1,5 @@ +'use server' +import { Action } from '@/actions/Action' +import { InterestGroups } from '@/services/groups/interestGroups' + +export const updateInterestGroupAction = Action(InterestGroups.update) diff --git a/src/app/_components/NavBar/navDef.ts b/src/app/_components/NavBar/navDef.ts index 4e8eaa6dd..cf753edd9 100644 --- a/src/app/_components/NavBar/navDef.ts +++ b/src/app/_components/NavBar/navDef.ts @@ -144,7 +144,7 @@ export const itemsForMenu: NavItem[] = [ }, { name: 'Intressegrupper', - href: '/infopages/interessegrupper', + href: '/interest-groups', show: 'all', icon: faGamepad, }, diff --git a/src/app/interest-groups/CreateInterestGroupForm.module.scss b/src/app/interest-groups/CreateInterestGroupForm.module.scss new file mode 100644 index 000000000..238fe1438 --- /dev/null +++ b/src/app/interest-groups/CreateInterestGroupForm.module.scss @@ -0,0 +1,19 @@ +@use '@/styles/ohma'; + +.CreateInterestGroupForm { + min-width: 50vw; + min-height: 50vh; + display: grid; + place-items: center; + form { + display: grid; + place-items: center; + > * { + width: 200px; + margin: 0; + } + button[type="submit"] { + width: 220px; + } + } +} \ No newline at end of file diff --git a/src/app/interest-groups/CreateInterestGroupForm.tsx b/src/app/interest-groups/CreateInterestGroupForm.tsx new file mode 100644 index 000000000..ab71826d0 --- /dev/null +++ b/src/app/interest-groups/CreateInterestGroupForm.tsx @@ -0,0 +1,20 @@ +import styles from './CreateInterestGroupForm.module.scss' +import Form from '@/components/Form/Form' +import { createInterestGroupAction } from '@/actions/groups/interestGroups/create' +import TextInput from '@/components/UI/TextInput' + +export default function CreateInterestGroupForm() { + return ( +
+

Lag interessegruppe

+
+ + + +
+ ) +} diff --git a/src/app/interest-groups/InterestGroup.module.scss b/src/app/interest-groups/InterestGroup.module.scss new file mode 100644 index 000000000..22d9f8546 --- /dev/null +++ b/src/app/interest-groups/InterestGroup.module.scss @@ -0,0 +1,16 @@ +@use '@/styles/ohma'; + +.interestGroup { + margin-top: 2rem; + border-top: 8px solid ohma.$colors-secondary; + padding-top: 1rem; + position: relative; + h2 { + font-size: ohma.$fonts-xl; + } + .admin { + position: absolute; + top: 1em; + right: 1em; + } +} \ No newline at end of file diff --git a/src/app/interest-groups/InterestGroup.tsx b/src/app/interest-groups/InterestGroup.tsx new file mode 100644 index 000000000..6135be600 --- /dev/null +++ b/src/app/interest-groups/InterestGroup.tsx @@ -0,0 +1,80 @@ +import styles from './InterestGroup.module.scss' +import Form from '@/components/Form/Form' +import TextInput from '@/components/UI/TextInput' +import ArticleSection from '@/components/Cms/ArticleSection/ArticleSection' +import { DestroyInterestGroupAuther, UpdateInterestGroupAuther } from '@/services/groups/interestGroups/Auther' +import { SettingsHeaderItemPopUp } from '@/components/HeaderItems/HeaderItemPopUp' +import { updateInterestGroupAction } from '@/actions/groups/interestGroups/update' +import { destroyInterestGroupAction } from '@/actions/groups/interestGroups/destroy' +import type { SessionMaybeUser } from '@/auth/Session' +import type { ExpandedInterestGroup } from '@/services/groups/interestGroups/Types' + +type PropTypes = { + interestGroup: ExpandedInterestGroup + session: SessionMaybeUser +} + +export default function InterestGroup({ interestGroup, session }: PropTypes) { + const canUpdate = UpdateInterestGroupAuther.dynamicFields({ groupId: interestGroup.groupId }).auth(session) + const canDestroy = DestroyInterestGroupAuther.dynamicFields({}).auth(session) + + const PopUpKey = `Update interest group ${interestGroup.name}` + + return ( +
+

{interestGroup.name}

+
+ { + canUpdate.authorized || canDestroy.authorized ? ( + + { + canUpdate.authorized && ( + <> +

Update interest group

+
+ + + + + ) + } + { + canDestroy.authorized && ( +
+ ) + } + + ) : <> + } +
+ +
+ ) +} diff --git a/src/app/interest-groups/page.tsx b/src/app/interest-groups/page.tsx new file mode 100644 index 000000000..3efcdecf8 --- /dev/null +++ b/src/app/interest-groups/page.tsx @@ -0,0 +1,34 @@ +import CreateInterestGroupForm from './CreateInterestGroupForm' +import InterestGroup from './InterestGroup' +import { readInterestGroupsAction } from '@/actions/groups/interestGroups/read' +import SpecialCmsParagraph from '@/cms/CmsParagraph/SpecialCmsParagraph' +import PageWrapper from '@/components/PageWrapper/PageWrapper' +import { AddHeaderItemPopUp } from '@/components/HeaderItems/HeaderItemPopUp' +import { CreateInterestGroupAuther } from '@/services/groups/interestGroups/Auther' + +export default async function InterestGroups() { + const { session, ...interestGroupsRes } = await readInterestGroupsAction.bind(null, {})() + if (!interestGroupsRes.success) return
Failed to load interest groups
//TODO: Change to unwrap + const interestGroups = interestGroupsRes.data + + const canCreate = CreateInterestGroupAuther.dynamicFields({}).auth(session) + + return ( + + + + ) + }> + +
+ { + interestGroups.map(interestGroup => ( + + )) + } +
+
+ ) +} diff --git a/src/auth/auther/RequirePermissionOrGroupAdmin.ts b/src/auth/auther/RequirePermissionOrGroupAdmin.ts new file mode 100644 index 000000000..98eb052a0 --- /dev/null +++ b/src/auth/auther/RequirePermissionOrGroupAdmin.ts @@ -0,0 +1,13 @@ +import { AutherFactory } from './Auther' +import type { Permission } from '@prisma/client' + +export const RequirePermissionOrGroupAdmin = AutherFactory< + { permission: Permission }, + { groupId: number }, + 'USER_NOT_REQUIERED_FOR_AUTHORIZED' +>(({ session, staticFields, dynamicFields }) => ({ + success: session.permissions.includes(staticFields.permission) || session.memberships.some( + membersip => membersip.groupId === dynamicFields.groupId && membersip.admin && membersip.active + ), + session, +})) diff --git a/src/prisma/prismaservice/cms_paragraphs/interest_groups/general_info.md b/src/prisma/prismaservice/cms_paragraphs/interest_groups/general_info.md new file mode 100644 index 000000000..b6c4f4590 --- /dev/null +++ b/src/prisma/prismaservice/cms_paragraphs/interest_groups/general_info.md @@ -0,0 +1,11 @@ +Har du en interesse du har lyst til å dele med andre? Interessegrupper er noe vi har startet for at det skal være lettere for deg å samle folk rundt en felles interesse. + +**Hva er en interessegruppe?** En interessegruppe er en gruppe med studenter som møtes for å snakke om eller dyrke en interesse. En interessegruppe kan omhandle akkurat det du interesserer deg for som du ønsker å dele med andre. Er det noe du føler mangler av tilbud i studenttilværelsen, er det godt mulig at interessegrupper er noe for deg! + +**Hvordan bli en interessegruppe?** Det er meget lett å bli en interessegruppe, bare send en mail til hs@omega.ntnu.no med navn på gruppen, kontaktinformasjon gruppeleder og en kort beskrivelse av hva dere gjør. Hvis dere trenger ekstra utstyr er det mulig å søke støtte fra fondet. + +**Ønsker du å opprette en gruppe?** Send en mail til hs@omega.ntnu.no, så tar vi kontakt. + +Få støtte! Ønsker din gruppe å søke støtte, send en søknad til scatmeister@omega.ntnu.no. For å få penger må gruppen bestå av flere aktive medlemmer i Omega, samt ha klart et budsjett over hva de planlegger å bruke pengene på. Søknaden vil så bli vurdert av fondsstyret. Det er altså ikke en garanti å få penger, men hvis formålet er noe Broedre iitem systre kan nyte godt av lover det godt for søknaden. + +Høres dette vrient ut, så fortvil ikke: Vi er her for å hjelpe dere. \ No newline at end of file diff --git a/src/prisma/prismaservice/src/development/seedDevGroups.ts b/src/prisma/prismaservice/src/development/seedDevGroups.ts index 3bf9f0c66..06a04d3b7 100644 --- a/src/prisma/prismaservice/src/development/seedDevGroups.ts +++ b/src/prisma/prismaservice/src/development/seedDevGroups.ts @@ -125,6 +125,13 @@ export default async function seedDevGroups(prisma: PrismaClient) { data: { name: `Interessegruppe ${i}`, shortName: `IG${i}`, + articleSection: { + create: { + cmsImage: { create: {} }, + cmsParagraph: { create: {} }, + cmsLink: { create: {} }, + } + }, group: { create: { groupType: 'INTEREST_GROUP', diff --git a/src/prisma/prismaservice/src/seedCmsConfig.ts b/src/prisma/prismaservice/src/seedCmsConfig.ts index c95490ef5..4f33930e7 100644 --- a/src/prisma/prismaservice/src/seedCmsConfig.ts +++ b/src/prisma/prismaservice/src/seedCmsConfig.ts @@ -160,6 +160,10 @@ export const seedSpecialCmsParagraphConfig: CmsParagraphSeedSpecialConfig = { name: 'frontpage_4_paragraph', file: 'frontpage/frontpage_4.md' }, + INTEREST_GROUP_GENERAL_INFO: { + name: 'interest_group_general_info', + file: 'interest_groups/general_info.md' + }, CAREER_INFO: { name: 'career_info', file: 'career/career_info.md' diff --git a/src/prisma/schema/cms.prisma b/src/prisma/schema/cms.prisma index cd35030a0..d383e1074 100644 --- a/src/prisma/schema/cms.prisma +++ b/src/prisma/schema/cms.prisma @@ -43,7 +43,8 @@ enum SpecialCmsParagraph { FRONTPAGE_2 FRONTPAGE_3 FRONTPAGE_4 - + + INTEREST_GROUP_GENERAL_INFO CAREER_INFO } @@ -99,10 +100,11 @@ model ArticleSection { order Int @default(autoincrement()) //The order "position" of the article section in the article //If true, the article section will be deleted if it is empty, ie has no relation to paragraph, link or image - destroyOnEmpty Boolean @default(true) + destroyOnEmpty Boolean @default(true) cmsImage CmsImage? cmsParagraph CmsParagraph? cmsLink CmsLink? + InterestGroup InterestGroup? @@unique([articleId, order]) //There can only be one article section with a given order in an article } diff --git a/src/prisma/schema/group.prisma b/src/prisma/schema/group.prisma index 42ae83298..8b25be0b2 100644 --- a/src/prisma/schema/group.prisma +++ b/src/prisma/schema/group.prisma @@ -83,7 +83,10 @@ model InterestGroup { name String shortName String @unique - // TODOD - add intereset group data + + articleSection ArticleSection @relation(fields: [articleSectionId], references: [id], onDelete: Restrict, onUpdate: Cascade) + articleSectionId Int @unique + // TODO - add intereset group data } model ManualGroup { diff --git a/src/prisma/schema/permission.prisma b/src/prisma/schema/permission.prisma index ec7b539e4..f135f8376 100644 --- a/src/prisma/schema/permission.prisma +++ b/src/prisma/schema/permission.prisma @@ -41,10 +41,8 @@ enum Permission { COMMITTEE_DESTROY // Groups - InterestGroups - INTEREST_GROUP_CREATE + INTEREST_GROUP_ADMIN INTEREST_GROUP_READ - INTEREST_GROUP_UPDATE - INTEREST_GROUP_DESTROY // Groups - OmegaMembershipGroup OMEGA_MEMBERSHIP_GROUP_READ diff --git a/src/services/ServiceMethod.ts b/src/services/ServiceMethod.ts index aeb1e3b80..991a2d831 100644 --- a/src/services/ServiceMethod.ts +++ b/src/services/ServiceMethod.ts @@ -79,10 +79,13 @@ export function ServiceMethod< return config.withData ? { withData: true, client: (prisma) => ({ - execute: ({ data, params, session }, authRunConfig) => { + execute: async ({ data, params, session }, authRunConfig) => { if (authRunConfig.withAuth) { const authRes = config.auther.dynamicFields( - config.dynamicFields({ + config.dynamicFields ? config.dynamicFields({ + params, + data: config.serviceMethodHandler.detailedValidate(data) + }) : await config.dynamicFieldsAsync({ params, data: config.serviceMethodHandler.detailedValidate(data) }) @@ -99,10 +102,12 @@ export function ServiceMethod< } satisfies ServiceMethod : { withData: false, client: (prisma) => ({ - execute: ({ params, session }, authRunConfig) => { + execute: async ({ params, session }, authRunConfig) => { if (authRunConfig.withAuth) { const authRes = config.auther.dynamicFields( - config.dynamicFields({ + config.dynamicFields ? config.dynamicFields({ + params + }) : await config.dynamicFieldsAsync({ params }) ).auth( diff --git a/src/services/ServiceTypes.ts b/src/services/ServiceTypes.ts index 62bd9a24d..552c4b779 100644 --- a/src/services/ServiceTypes.ts +++ b/src/services/ServiceTypes.ts @@ -147,6 +147,7 @@ export type ServiceMethodHandlerConfig< ) => Promise, } : { withData: false, + wantsToOpenTransaction?: WantsToOpenTransaction, handler: ( prisma: PrismaPossibleTransaction, params: Params, @@ -172,8 +173,13 @@ type ServiceMethodHandlerAuthConfig< DynamicFields extends object, > = { auther: AutherStaticFieldsBound +} & ({ dynamicFields: (dataParams: DynamicFieldsInput) => DynamicFields -} + dynamicFieldsAsync?: never +} | { + dynamicFieldsAsync: (dataParams: DynamicFieldsInput) => Promise + dynamicFields?: never +}) export type ServiceMethodConfig< WithValidation extends boolean, diff --git a/src/services/groups/interestGroups/Auther.ts b/src/services/groups/interestGroups/Auther.ts new file mode 100644 index 000000000..1fbcf299a --- /dev/null +++ b/src/services/groups/interestGroups/Auther.ts @@ -0,0 +1,10 @@ +import { RequirePermission } from '@/auth/auther/RequirePermission' +import { RequirePermissionOrGroupAdmin } from '@/auth/auther/RequirePermissionOrGroupAdmin' + +export const ReadInterestGroupAuther = RequirePermission.staticFields({ permission: 'INTEREST_GROUP_READ' }) + +export const CreateInterestGroupAuther = RequirePermission.staticFields({ permission: 'INTEREST_GROUP_ADMIN' }) + +export const UpdateInterestGroupAuther = RequirePermissionOrGroupAdmin.staticFields({ permission: 'INTEREST_GROUP_ADMIN' }) + +export const DestroyInterestGroupAuther = RequirePermission.staticFields({ permission: 'INTEREST_GROUP_ADMIN' }) diff --git a/src/services/groups/interestGroups/Types.ts b/src/services/groups/interestGroups/Types.ts index ac25656b9..4ab8df3dd 100644 --- a/src/services/groups/interestGroups/Types.ts +++ b/src/services/groups/interestGroups/Types.ts @@ -1,3 +1,6 @@ +import type { ExpandedArticleSection } from '@/services/cms/articleSections/Types' import type { InterestGroup } from '@prisma/client' -export type ExpandedInterestGroup = InterestGroup +export type ExpandedInterestGroup = InterestGroup & { + articleSection: ExpandedArticleSection +} diff --git a/src/services/groups/interestGroups/create.ts b/src/services/groups/interestGroups/create.ts index 574546550..e6e1f7a6e 100644 --- a/src/services/groups/interestGroups/create.ts +++ b/src/services/groups/interestGroups/create.ts @@ -1,26 +1,31 @@ -import { prismaCall } from '@/services/prismaCall' -import prisma from '@/prisma' +import 'server-only' +import { createInterestGroupValidation } from './validation' import { readCurrentOmegaOrder } from '@/services/omegaOrder/read' -import type { ExpandedInterestGroup } from './Types' +import { ServiceMethodHandler } from '@/services/ServiceMethodHandler' -type CreateInterestGroupArgs = { - name: string, - shortName: string, -} +export const create = ServiceMethodHandler({ + withData: true, + validation: createInterestGroupValidation, + handler: async (prisma, _, data) => { + const { order } = await readCurrentOmegaOrder() -export async function createInterestGroup({ name, shortName }: CreateInterestGroupArgs): Promise { - const order = (await readCurrentOmegaOrder()).order - - return await prismaCall(() => prisma.interestGroup.create({ - data: { - name, - shortName, - group: { - create: { - groupType: 'INTEREST_GROUP', - order, + await prisma.interestGroup.create({ + data: { + ...data, + articleSection: { + create: { + cmsImage: {}, + cmsParagraph: {}, + cmsLink: {}, + } + }, + group: { + create: { + groupType: 'INTEREST_GROUP', + order, + } } } - } - })) -} + }) + } +}) diff --git a/src/services/groups/interestGroups/destroy.ts b/src/services/groups/interestGroups/destroy.ts index be0dfcbef..ef997dd02 100644 --- a/src/services/groups/interestGroups/destroy.ts +++ b/src/services/groups/interestGroups/destroy.ts @@ -1,11 +1,17 @@ -import { prismaCall } from '@/services/prismaCall' -import prisma from '@/prisma' -import type { ExpandedInterestGroup } from './Types' +import 'server-only' +import { ServiceMethodHandler } from '@/services/ServiceMethodHandler' -export async function destroyInterestGroup(id: number): Promise { - return await prismaCall(() => prisma.interestGroup.delete({ - where: { - id, - }, - })) -} +export const destroy = ServiceMethodHandler({ + withData: false, + wantsToOpenTransaction: true, + handler: async (prisma, { id }: { id: number }) => { + await prisma.$transaction(async tx => { + const intrestGroup = await tx.interestGroup.delete({ + where: { id } + }) + await tx.group.delete({ + where: { id: intrestGroup.groupId } + }) + }) + } +}) diff --git a/src/services/groups/interestGroups/index.ts b/src/services/groups/interestGroups/index.ts new file mode 100644 index 000000000..3449b3578 --- /dev/null +++ b/src/services/groups/interestGroups/index.ts @@ -0,0 +1,54 @@ +import 'server-only' +import { + CreateInterestGroupAuther, + DestroyInterestGroupAuther, + ReadInterestGroupAuther, + UpdateInterestGroupAuther +} from './Auther' +import { read, readAll } from './read' +import { create } from './create' +import { update } from './update' +import { destroy } from './destroy' +import { ServiceMethod } from '@/services/ServiceMethod' + +export const InterestGroups = { + read: ServiceMethod({ + withData: false, + hasAuther: true, + auther: ReadInterestGroupAuther, + dynamicFields: () => ({}), + serviceMethodHandler: read, + }), + readAll: ServiceMethod({ + withData: false, + hasAuther: true, + auther: ReadInterestGroupAuther, + dynamicFields: () => ({}), + serviceMethodHandler: readAll, + }), + create: ServiceMethod({ + withData: true, + hasAuther: true, + auther: CreateInterestGroupAuther, + dynamicFields: () => ({}), + serviceMethodHandler: create, + }), + update: ServiceMethod({ + withData: true, + hasAuther: true, + auther: UpdateInterestGroupAuther, + dynamicFieldsAsync: async ({ params }) => ({ + groupId: (await read.client('NEW').execute( + { params: { id: params.id }, session: null } + ).then(interestGroup => interestGroup.groupId)) + }), + serviceMethodHandler: update, + }), + destroy: ServiceMethod({ + withData: false, + hasAuther: true, + auther: DestroyInterestGroupAuther, + dynamicFields: () => ({}), + serviceMethodHandler: destroy + }) +} as const diff --git a/src/services/groups/interestGroups/read.ts b/src/services/groups/interestGroups/read.ts index ffee51c11..1cd34871f 100644 --- a/src/services/groups/interestGroups/read.ts +++ b/src/services/groups/interestGroups/read.ts @@ -1,21 +1,39 @@ -import { prismaCall } from '@/services/prismaCall' -import prisma from '@/prisma' +import 'server-only' +import { articleSectionsRealtionsIncluder } from '@/services/cms/articleSections/ConfigVars' +import { ServiceMethodHandler } from '@/services/ServiceMethodHandler' import type { ExpandedInterestGroup } from './Types' -export async function readInterestGroups(): Promise { - return await prismaCall(() => prisma.interestGroup.findMany()) -} - type ReadInterestGroupArgs = { id?: number, shortName?: string, } -export async function readInterestGroup({ id, shortName }: ReadInterestGroupArgs): Promise { - return await prismaCall(() => prisma.interestGroup.findUniqueOrThrow({ +export const readAll = ServiceMethodHandler({ + withData: false, + handler: async (prisma): Promise => prisma.interestGroup.findMany({ + include: { + articleSection: { + include: articleSectionsRealtionsIncluder, + }, + }, + orderBy: [ + { name: 'asc' }, + { id: 'asc' }, + ] + }) +}) + +export const read = ServiceMethodHandler({ + withData: false, + handler: async (prisma, { id, shortName }: ReadInterestGroupArgs) => await prisma.interestGroup.findUniqueOrThrow({ where: { id, shortName, + }, + include: { + articleSection: { + include: articleSectionsRealtionsIncluder, + }, } - })) -} + }) +}) diff --git a/src/services/groups/interestGroups/update.ts b/src/services/groups/interestGroups/update.ts index 888bfd47e..abf96ee86 100644 --- a/src/services/groups/interestGroups/update.ts +++ b/src/services/groups/interestGroups/update.ts @@ -1,17 +1,12 @@ -import { prismaCall } from '@/services/prismaCall' -import prisma from '@/prisma' -import type { ExpandedInterestGroup } from './Types' +import 'server-only' +import { updateInterestGroupValidation } from './validation' +import { ServiceMethodHandler } from '@/services/ServiceMethodHandler' -type CreateInterestGroupArgs = { - name: string, - shortName: string, -} - -export async function updateInterestGroup(id: number, data: CreateInterestGroupArgs): Promise { - return await prismaCall(() => prisma.interestGroup.update({ - where: { - id, - }, +export const update = ServiceMethodHandler({ + withData: true, + validation: updateInterestGroupValidation, + handler: async (prisma, { id }: { id: number }, data) => prisma.interestGroup.update({ + where: { id }, data, - })) -} + }) +}) diff --git a/src/services/groups/interestGroups/validation.ts b/src/services/groups/interestGroups/validation.ts new file mode 100644 index 000000000..bac7d4a08 --- /dev/null +++ b/src/services/groups/interestGroups/validation.ts @@ -0,0 +1,31 @@ +import { ValidationBase } from '@/services/Validation' +import { z } from 'zod' + +const baseInterestGroupValidation = new ValidationBase({ + type: { + name: z.string(), + shortName: z.string(), + }, + details: { + name: z.string().min( + 3, 'Navn må ha minst 3 tegn' + ).max( + 30, 'Navn kan ha maks 30 tegn' + ).trim(), + shortName: z.string().min( + 3, 'Kortnavn må ha minst 3 tegn' + ).max( + 10, 'Kortnavn kan ha maks 10 tegn' + ).trim(), + }, +}) + +export const createInterestGroupValidation = baseInterestGroupValidation.createValidation({ + keys: ['name', 'shortName'], + transformer: data => data +}) + +export const updateInterestGroupValidation = baseInterestGroupValidation.createValidationPartial({ + keys: ['name', 'shortName'], + transformer: data => data +}) diff --git a/src/services/groups/memberships/create.ts b/src/services/groups/memberships/create.ts index 5dea0e8fa..61b43dc44 100644 --- a/src/services/groups/memberships/create.ts +++ b/src/services/groups/memberships/create.ts @@ -5,6 +5,7 @@ import { readCurrentOmegaOrder } from '@/services/omegaOrder/read' import { prismaCall } from '@/services/prismaCall' import { ServerError } from '@/services/error' import prisma from '@/prisma' +import { invalidateManyUserSessionData, invalidateOneUserSessionData } from '@/services/auth/invalidateSession' import type { ExpandedMembership } from './Types' export async function createMembershipForUser( @@ -18,7 +19,7 @@ export async function createMembershipForUser( } const order = orderArg ?? await readCurrentGroupOrder(groupId) - return await prismaCall(() => prisma.membership.create({ + const membership = await prismaCall(() => prisma.membership.create({ data: { group: { connect: { @@ -39,6 +40,8 @@ export async function createMembershipForUser( active: true, }, })) + await invalidateOneUserSessionData(userId) + return membership } /** @@ -80,6 +83,7 @@ export async function createMembershipsForGroup( })), skipDuplicates: true, })) + await invalidateManyUserSessionData(data.map(({ userId }) => userId)) } /** @@ -121,4 +125,6 @@ export async function createMembershipsForUser( })), skipDuplicates: true, })) + + await invalidateOneUserSessionData(userId) } diff --git a/src/services/groups/memberships/destroy.ts b/src/services/groups/memberships/destroy.ts index 910346d24..e5439239a 100644 --- a/src/services/groups/memberships/destroy.ts +++ b/src/services/groups/memberships/destroy.ts @@ -4,6 +4,7 @@ import { readCurrentGroupOrder } from '@/services/groups/read' import { prismaCall } from '@/services/prismaCall' import { ServerError } from '@/services/error' import prisma from '@/prisma' +import { invalidateManyUserSessionData, invalidateOneUserSessionData } from '@/services/auth/invalidateSession' import type { ExpandedMembership } from './Types' export async function destoryMembershipOfUser({ @@ -20,7 +21,7 @@ export async function destoryMembershipOfUser({ } const order = orderArg ?? await readCurrentGroupOrder(groupId) - return await prismaCall(() => prisma.membership.delete({ + const membership = await prismaCall(() => prisma.membership.delete({ where: { userId_groupId_order: { groupId, @@ -29,6 +30,8 @@ export async function destoryMembershipOfUser({ } }, })) + await invalidateOneUserSessionData(userId) + return membership } export async function destroyMembershipOfUsers( @@ -50,4 +53,5 @@ export async function destroyMembershipOfUsers( order, }, })) + invalidateManyUserSessionData(userIds) } diff --git a/src/services/groups/memberships/update.ts b/src/services/groups/memberships/update.ts index 8b5cd084d..c08523baa 100644 --- a/src/services/groups/memberships/update.ts +++ b/src/services/groups/memberships/update.ts @@ -2,6 +2,7 @@ import 'server-only' import { readCurrentGroupOrder } from '@/services/groups/read' import { prismaCall } from '@/services/prismaCall' import prisma from '@/prisma' +import { invalidateOneUserSessionData } from '@/services/auth/invalidateSession' import type { ExpandedMembership } from './Types' export async function updateMembership({ @@ -18,7 +19,7 @@ export async function updateMembership({ }): Promise { const order = (orderArg && typeof orderArg === 'number') ? orderArg : await readCurrentGroupOrder(groupId) - return await prismaCall(() => prisma.membership.update({ + const membership = await prismaCall(() => prisma.membership.update({ where: { userId_groupId_order: { groupId, @@ -28,4 +29,6 @@ export async function updateMembership({ }, data })) + invalidateOneUserSessionData(userId) + return membership } diff --git a/src/services/permissionRoles/ConfigVars.ts b/src/services/permissionRoles/ConfigVars.ts index 616675e41..937c13e23 100644 --- a/src/services/permissionRoles/ConfigVars.ts +++ b/src/services/permissionRoles/ConfigVars.ts @@ -92,24 +92,14 @@ export const PermissionConfig = { description: 'kan oppdatere komite', category: 'groups', }, - INTEREST_GROUP_CREATE: { - name: 'Lage interessegruppe', - description: 'kan lage interessegruppe', - category: 'groups', - }, - INTEREST_GROUP_DESTROY: { - name: 'Slette interessegruppe', - description: 'kan slette interessegruppe', - category: 'groups', - }, INTEREST_GROUP_READ: { name: 'Les interessegruppe', description: 'kan lese interessegruppe', category: 'groups', }, - INTEREST_GROUP_UPDATE: { - name: 'Oppdatere interessegruppe', - description: 'kan oppdatere interessegruppe', + INTEREST_GROUP_ADMIN: { + name: 'Administrere interessegruppe', + description: 'Administrere interessegruppe uten å være admin i gruppen. Og lage nye grupper', category: 'groups', }, OMEGA_MEMBERSHIP_GROUP_UPDATE: {