diff --git a/CHANGELOG.MD b/CHANGELOG.MD index 50a2e324..877b01b4 100644 --- a/CHANGELOG.MD +++ b/CHANGELOG.MD @@ -5,6 +5,14 @@ Alla märkbara ändringar ska dokumenteras i denna fil. Baserat på [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), och följer [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [1.11.0] - 2024-06-07 + +### Tillagt +- Prisma and graphql feature for activities (events) and tickets. +- Tests for activity reducer and API. +- Test for ticket API. +- DateTime scalar for graphql. + ## [1.10.0] - 2024-05-31 ### Tillagt @@ -31,7 +39,7 @@ och följer [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [1.9.0] - 2024-03-22 ### Tillagt - adds decibel_admin feature - + ## [1.8.0] - 2024-01-31 ### Tillagt diff --git a/codegen.yml b/codegen.yml index d4cf3480..db1b42ce 100644 --- a/codegen.yml +++ b/codegen.yml @@ -10,6 +10,7 @@ generates: scalars: Object: Record Date: Date + DateTime: Date mappers: Article: ../mappers#ArticleResponse File: ../mappers#FileResponse diff --git a/package-lock.json b/package-lock.json index ae74c9e7..f25c798c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "ekorre-ts", - "version": "1.10.0", + "version": "1.11.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "ekorre-ts", - "version": "1.10.0", + "version": "1.11.0", "license": "AGPL-3.0-only", "dependencies": { "@esek/auth-server": "5.0.2", diff --git a/package.json b/package.json index 86db31b4..cda769c2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ekorre-ts", - "version": "1.10.0", + "version": "1.11.0", "description": "E-Sektionens backend", "main": "src/index.ts", "scripts": { @@ -18,7 +18,7 @@ "test:regression": "jest test/regression --coverage=false", "benchmark": "ts-node tools/benchmark/index.ts", "prisma:ci": "prisma generate && prisma db push --accept-data-loss && prisma db seed", - "prisma:reset": "prisma migrate reset --skip-seed; prisma db push; prisma db seed", + "prisma:reset": "prisma migrate reset --skip-seed && prisma db push && prisma db seed", "prisma:generate": "prisma generate", "prisma:push": "prisma db push", "prisma:migrate": "prisma migrate dev", @@ -30,7 +30,8 @@ "Emil Eriksson ", "Axel Froborg ", "Marcus Lindell ", - "Eric Weidow " + "Eric Weidow ", + "Axel Andersson " ], "bugs": { "email": "macapar@esek.se" diff --git a/prisma/data/activity.seed.ts b/prisma/data/activity.seed.ts new file mode 100644 index 00000000..3b25f2c9 --- /dev/null +++ b/prisma/data/activity.seed.ts @@ -0,0 +1,122 @@ +import { Prisma, PrismaActivitySource, PrismaUtskott } from '@prisma/client'; + +function getRandomDate(): Date { + const today = new Date(); + const oneYearForward = new Date(today.getTime() + 365 * 24 * 60 * 60 * 1000); // Adding milliseconds for one year + + const randomTime = today.getTime() + Math.random() * (oneYearForward.getTime() - today.getTime()); + return new Date(randomTime); +} + +export const activities: Prisma.PrismaActivityCreateInput[] = [ + { + utskott: PrismaUtskott.CM, + source: PrismaActivitySource.WEBSITE, + title: 'Baktävling', + description: + '"Välkommen till vårt mysiga café där doften av nybakat bröd och kaffe möter dig i dörren. Varje dag bjuder vi på en smakupplevelse utöver det vanliga, och nu är det din chans att delta i vår spännande baktävling! Ge din kreativitet fria tyglar och skapa det perfekta bakverket som kommer att imponera på vår jury. Vem vet, kanske blir just din skapelse den nya favoriten på vårt café! Så plocka fram dina bästa recept och låt bakningen börja!"', + startDate: getRandomDate(), + locationTitle: '', + locationLink: '', + }, + { + utskott: PrismaUtskott.FVU, + source: PrismaActivitySource.WEBSITE, + title: 'Fixar-kväll', + description: + 'Känn pulsen i vår fixar-kväll, där passion och kreativitet förenas i en atmosfär av skapande. Tillsammans bygger vi, skapar och inspireras av varandras projekt. Låt idéerna flöda och låt dina händer forma det du drömmer om. Kom och var en del av vår gemenskap för en oförglömlig kväll!', + startDate: getRandomDate(), + locationTitle: '', + locationLink: '', + }, + { + utskott: PrismaUtskott.INFU, + source: PrismaActivitySource.WEBSITE, + title: 'Hacker', + description: + 'Välkommen till det ultimata hacker eventet där kreativitet och teknologi möts i en spännande atmosfär. Här samlas hackare från hela världen för att utforska nya gränser och lösa utmaningar. Ta del av inspirerande föreläsningar, knäck koder och skapa banbrytande innovationer tillsammans med oss!', + startDate: getRandomDate(), + locationTitle: '', + locationLink: '', + }, + { + utskott: PrismaUtskott.KM, + source: PrismaActivitySource.WEBSITE, + title: 'Gille', + description: + '"Välkommen till vårt gemytliga gille där vänner samlas för goda drycker och skrattfyllda stunder. Njut av vårt utbud av förfriskningar och klassiska rätter i en avslappnad atmosfär. Låt kvällen ta fart med livlig musik och härlig stämning. Kom och upplev en kväll att minnas på vårt charmiga gille!"', + startDate: getRandomDate(), + locationTitle: '', + locationLink: '', + }, + { + utskott: PrismaUtskott.NOLLU, + source: PrismaActivitySource.WEBSITE, + title: 'Regattan', + description: + 'Känn spänningen på regattan, där havet blir slagfält och seglen sträcks mot vindens kraft. Deltagare från alla hörn av världen tävlar om ära och berömmelse i detta episka sjökrig. Stå öga mot öga med dina motståndare och visa din skicklighet på de vilda vågorna. Detta är inte bara en tävling, det är ett äventyr till havs!', + startDate: getRandomDate(), + locationTitle: '', + locationLink: '', + }, + { + utskott: PrismaUtskott.ENU, + source: PrismaActivitySource.WEBSITE, + title: 'Lunchföreläsning', + description: + 'Välkommen till vår inspirerande lunchföreläsning där vi välkomnar ett ledande företag för att dela med sig av sin kunskap och erfarenhet. Njut av en lärorik stund med spännande insikter och möjligheter till nätverkande. Ge dig själv en energiboost mitt på dagen och låt dig inspireras av framgångsrika företagare.', + startDate: getRandomDate(), + locationTitle: '', + locationLink: '', + }, + { + utskott: PrismaUtskott.PENGU, + source: PrismaActivitySource.WEBSITE, + title: 'Bokföringskväll', + description: + 'Det är dags att ta tag i bokföringen tillsammans! I vår grupp samlas vi för att effektivt hantera våra ekonomiska transaktioner och säkerställa noggrannheten i våra bokföringsposter. Med teamwork och noggrant arbete ser vi till att varje siffra landar rätt och att vår ekonomi är i balans.', + startDate: getRandomDate(), + locationTitle: '', + locationLink: '', + }, + { + utskott: PrismaUtskott.E6, + source: PrismaActivitySource.WEBSITE, + title: 'Sittning', + description: + 'Välkommen till en kväll fylld av gemenskap och glädje! Vi öppnar våra dörrar för en minnesvärd sittning där vi samlas för god mat, gott sällskap och underhållande samtal. Låt oss skapa minnen tillsammans och njuta av en kväll att komma ihåg. Välkommen till vårt bord!', + startDate: getRandomDate(), + locationTitle: '', + locationLink: '', + }, + { + utskott: PrismaUtskott.SRE, + source: PrismaActivitySource.WEBSITE, + title: 'Pluggkväll', + description: + 'Det är dags att sätta näsan i böckerna! Välkommen till vår pluggkväll där vi tillsammans fokuserar på att nå våra studiemål. Med lugn atmosfär och gemensamt stöd tar vi itu med utmaningarna och strävar efter framgång. Låt oss inspirera varandra till framsteg och lärande. Tillsammans når vi nya höjder!', + startDate: getRandomDate(), + locationTitle: '', + locationLink: '', + }, + { + utskott: PrismaUtskott.STYRELSEN, + source: PrismaActivitySource.WEBSITE, + title: 'Styrelsemöte', + description: + 'Välkommen till vårt styrelsemöte där vi samlas för att diskutera strategier och fatta beslut för sektionens framtid. Med fokus och engagemang går vi igenom dagordningen och arbetar tillsammans mot gemensamma mål. Låt oss samarbeta för att forma en framgångsrik väg framåt. Mötet är nu öppnat!', + startDate: getRandomDate(), + locationTitle: '', + locationLink: '', + }, + { + utskott: PrismaUtskott.OTHER, + source: PrismaActivitySource.WEBSITE, + title: 'NolleGasque', + description: + 'Välkomna till årets mest efterlängtade fest, NolleGasquen! En storslagen sittning där vi firar gemenskap, glädje och minnen för livet. Med uppdukade festligheter och en sprakande atmosfär skapar vi magiska ögonblick tillsammans. Låt oss fira studentlivet och välkomna de nya äventyren som väntar!', + startDate: getRandomDate(), + locationTitle: '', + locationLink: '', + }, +]; diff --git a/prisma/data/seed.ts b/prisma/data/seed.ts index 15c25a0a..618f5dd5 100644 --- a/prisma/data/seed.ts +++ b/prisma/data/seed.ts @@ -1,6 +1,7 @@ import { PrismaClient } from '@prisma/client'; import { apiKeyAccess, apiKeys, individualAccess, postAccess } from './access.seed'; +import { activities } from './activity.seed'; import { posts } from './post.seed'; import { users } from './user.seed'; @@ -19,6 +20,8 @@ const run = async () => { prisma.prismaPostAccess.createMany({ data: postAccess, skipDuplicates: true }), prisma.prismaApiKeyAccess.createMany({ data: apiKeyAccess, skipDuplicates: true }), ]); + + await prisma.prismaActivity.createMany({ data: activities, skipDuplicates: true }); }; run() diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 042349ff..0fdb9335 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -178,6 +178,36 @@ model PrismaNomination { @@map("nominations") } +model PrismaActivity { + id String @id @default(cuid()) + source PrismaActivitySource + title String + description String? + startDate DateTime @map("start_date") + endDate DateTime? @map("end_date") + utskott PrismaUtskott + imageUrl String? @map("image_url") + locationTitle String? @map("location_title") + locationLink String? @map("location_link") + tickets PrismaTicket[] + + @@map("activities") +} + +//sourceUrl for link to event from other apps/websites. + +model PrismaTicket { + id String @id @default(cuid()) + name String + count Int? + price Int? + currency String? + activity PrismaActivity? @relation(fields: [activityID], references: [id]) + activityID String? @map("activity_id") + + @@map("tickets") +} + model PrismaProposal { user PrismaUser @relation(name: "PrismaProposalToPrismaUser", fields: [refUser], references: [username]) refUser String @map("ref_user") @@ -348,6 +378,14 @@ enum PrismaMeetingType { @@map("meeting_types") } +enum PrismaActivitySource { + WEBSITE + ORBI + OTHER + + @@map("activity_source") +} + enum PrismaPostType { U EA diff --git a/src/api/activity.api.ts b/src/api/activity.api.ts new file mode 100644 index 00000000..bdaecee3 --- /dev/null +++ b/src/api/activity.api.ts @@ -0,0 +1,145 @@ +import { BadRequestError, NotFoundError } from '@/errors/request.errors'; +import { StrictObject } from '@/models/base'; +import { devGuard, stripObject } from '@/util'; +import { ModifiedActivity, NewActivity, Utskott } from '@generated/graphql'; +import { PrismaActivity, PrismaActivitySource } from '@prisma/client'; + +import prisma from './prisma'; + +export class ActivityAPI { + async getActivity(id: string): Promise { + const activity = await prisma.prismaActivity.findFirst({ + where: { + id, + }, + }); + + if (activity == null) { + throw new NotFoundError('Kunde inte hitta denna aktivitet!'); + } + + return activity; + } + async getActivities( + from: Date, + to: Date, + utskott: Utskott[] = [Utskott.Other], + ): Promise { + const activities = await prisma.prismaActivity.findMany({ + where: { + startDate: { + lte: to, + }, + OR: [ + { + endDate: { + gte: from, + }, + }, + { AND: [{ startDate: { gte: from } }, { endDate: null }] }, + ], + AND: { utskott: { in: utskott } }, + }, + orderBy: { startDate: 'asc' }, + }); + + return activities; + } + + async addActivity(activity: NewActivity): Promise { + const isAcceptableTime = () => { + const { startDate, endDate } = activity; + + if (!endDate) { + return true; + } + + return endDate.getTime() - startDate.getTime() >= 0; + }; + + if (!isAcceptableTime()) { + throw new BadRequestError('Sluttid för aktivitet är före starttid!'); + } + + const addedActivity = await prisma.prismaActivity.create({ + data: { + source: PrismaActivitySource.WEBSITE, + title: activity.title, + description: activity.description, + startDate: activity.startDate, + endDate: activity.endDate, + utskott: activity.utskott ?? Utskott.Other, + imageUrl: activity.imageUrl, + locationTitle: activity.location?.title, + locationLink: activity.location?.link, + }, + }); + + return addedActivity; + } + async modifyActivity(id: string, entry: ModifiedActivity): Promise { + const activity = await this.getActivity(id); + + const isAcceptableTime = () => { + const { startDate, endDate } = entry; + + if (endDate) { + if (startDate) { + return endDate.getTime() - startDate.getTime() >= 0; + } + return endDate.getTime() - activity.startDate.getTime() >= 0; + } else if (startDate) { + if (!activity.endDate) { + return true; + } + return activity.endDate.getTime() - startDate.getTime() >= 0; + } + + return true; + }; + + if (!isAcceptableTime()) { + throw new BadRequestError('Ny slut- och starttid för aktivitet är omöjlig!'); + } + + if (activity.source !== PrismaActivitySource.WEBSITE) { + throw new BadRequestError( + 'Ej tillåtet att ändra i evenemang som inte är skapade på hemsidan!', + ); + } + + const { location, ...reduced } = entry; + + const refact = { + ...reduced, + locationTitle: entry.location?.title, + locationLink: entry.location?.link, + }; + + const update: StrictObject = stripObject(refact); + + const modifiedActivity = await prisma.prismaActivity.update({ + data: { ...update }, + where: { id }, + }); + return modifiedActivity; + } + + async removeActivity(id: string): Promise { + const activity = await this.getActivity(id); + if (activity.source !== PrismaActivitySource.WEBSITE) { + throw new BadRequestError( + 'Ej tillåtet att ta bort evenemang som inte är skapade på hemsidan!', + ); + } + const removedActivity = await prisma.prismaActivity.delete({ where: { id } }); + + return removedActivity; + } + + async clear() { + devGuard('Cannot clear activities in production'); + + await prisma.prismaActivity.deleteMany(); + } +} diff --git a/src/api/ticket.api.ts b/src/api/ticket.api.ts new file mode 100644 index 00000000..094a539a --- /dev/null +++ b/src/api/ticket.api.ts @@ -0,0 +1,70 @@ +import { NotFoundError } from '@/errors/request.errors'; +import { StrictObject } from '@/models/base'; +import { devGuard, stripObject } from '@/util'; +import { ModifiedTicket, NewTicket } from '@generated/graphql'; +import { Prisma, PrismaTicket } from '@prisma/client'; + +import prisma from './prisma'; + +export class TicketAPI { + async getTicket(id: string): Promise { + const ticket = await prisma.prismaTicket.findFirst({ where: { id } }); + + if (ticket == null) { + throw new NotFoundError('Kunde inte hitta den biljetten!'); + } + + return ticket; + } + + async getTickets(activityID: string | null | undefined): Promise { + const whereAnd: Prisma.PrismaTicketWhereInput[] = []; + if (activityID != null) { + whereAnd.push({ activityID: activityID }); + } + const tickets = await prisma.prismaTicket.findMany({ + where: { + AND: whereAnd, + }, + }); + + return tickets; + } + + async addTicket(ticket: NewTicket): Promise { + const addedTicket = await prisma.prismaTicket.create({ + data: { + name: ticket.name, + count: ticket.count, + price: ticket.price, + currency: ticket.currency, + activityID: ticket.activityID, + }, + }); + + return addedTicket; + } + + async modifyTicket(id: string, entry: ModifiedTicket): Promise { + const { ...rest } = entry; + const update: StrictObject = stripObject(rest); + + const modifiedTicket = prisma.prismaTicket.update({ + data: { ...update }, + where: { id }, + }); + + return modifiedTicket; + } + + async removeTicket(id: string): Promise { + const removedTicket = await prisma.prismaTicket.delete({ where: { id } }); + return removedTicket; + } + + async clear() { + devGuard('Cannot clear tickets in production'); + + await prisma.prismaTicket.deleteMany(); + } +} diff --git a/src/app/serverconfig.ts b/src/app/serverconfig.ts index 14849a5a..62d9c0b5 100644 --- a/src/app/serverconfig.ts +++ b/src/app/serverconfig.ts @@ -23,7 +23,7 @@ import { } from 'apollo-server-core'; import { ExpressContext } from 'apollo-server-express'; // Use scalarTypeDefs and scalarResolvers for more scalar types -import { DateResolver } from 'graphql-scalars'; +import { DateResolver, DateTimeResolver } from 'graphql-scalars'; // Ladda alla scheman från .graphql filer const typeDefs = loadSchemaSync('./src/schemas/*.graphql', { @@ -35,8 +35,8 @@ const resolvers = Object.entries(Resolvers).map(([_, value]) => value); // Konstruera root schema. VIKTIGT! Det senaste schemat kommer skugga andra. const schema = makeExecutableSchema({ - typeDefs: [typeDefs, 'scalar Date'], - resolvers: [{ Date: DateResolver }, ...resolvers], + typeDefs: [typeDefs, 'scalar Date', 'scalar DateTime'], + resolvers: [{ Date: DateResolver, DateTime: DateTimeResolver }, ...resolvers], }); const apolloLogger = Logger.getLogger('Apollo'); diff --git a/src/models/generated/graphql.ts b/src/models/generated/graphql.ts index c89314a8..091e9f98 100644 --- a/src/models/generated/graphql.ts +++ b/src/models/generated/graphql.ts @@ -1,17 +1,6 @@ import type { GraphQLResolveInfo, GraphQLScalarType, GraphQLScalarTypeConfig } from 'graphql'; - +import type { ArticleResponse, FileResponse, MeetingResponse, ElectionResponse, ProposalResponse, NominationResponse, HeheResponse, ApiKeyResponse } from '../mappers'; import type { Context } from '../context'; -import type { - ArticleResponse, - FileResponse, - MeetingResponse, - ElectionResponse, - ProposalResponse, - NominationResponse, - HeheResponse, - ApiKeyResponse, -} from '../mappers'; - export type Maybe = T | null; export type InputMaybe = Maybe; export type Exact = { [K in keyof T]: T[K] }; @@ -27,7 +16,7 @@ export type Scalars = { Int: number; Float: number; Date: Date; - DateTime: any; + DateTime: Date; Object: Record; }; @@ -44,13 +33,31 @@ export type AccessInput = { export enum AccessResourceType { Door = 'door', - Feature = 'feature', + Feature = 'feature' } export enum AccessType { Admin = 'ADMIN', Authenticated = 'AUTHENTICATED', - Public = 'PUBLIC', + Public = 'PUBLIC' +} + +export type Activity = { + description?: Maybe; + endDate?: Maybe; + id: Scalars['String']; + imageUrl?: Maybe; + location?: Maybe; + source: ActivitySource; + startDate: Scalars['DateTime']; + title: Scalars['String']; + utskott: Utskott; +}; + +export enum ActivitySource { + Orbi = 'ORBI', + Other = 'OTHER', + Website = 'WEBSITE' } export type ApiKey = { @@ -77,6 +84,7 @@ export type Article = { title: Scalars['String']; }; + /** Body is saved as HTML serversInte, but edited in MarkDown */ export type ArticleTagsArgs = { includeSpecial?: InputMaybe; @@ -85,7 +93,7 @@ export type ArticleTagsArgs = { /** News are the ones to be used by a website newsreel */ export enum ArticleType { Information = 'INFORMATION', - News = 'NEWS', + News = 'NEWS' } export type CasLoginResponse = { @@ -109,7 +117,7 @@ export enum Door { Pa = 'pa', Pump = 'pump', Sikrit = 'sikrit', - Ulla = 'ulla', + Ulla = 'ulla' } export type DoorInfo = { @@ -146,12 +154,13 @@ export enum EmergencyContactType { Mom = 'MOM', Other = 'OTHER', SignificantOther = 'SIGNIFICANT_OTHER', - Sister = 'SISTER', + Sister = 'SISTER' } /** Features are used for mapping access to a feature (ex article or election) for user or a post. This is not limited to efterphest */ export enum Feature { AccessAdmin = 'access_admin', + ActivityAdmin = 'activity_admin', AhsAdmin = 'ahs_admin', ArticleEditor = 'article_editor', DecibelAdmin = 'decibel_admin', @@ -163,7 +172,7 @@ export enum Feature { NewsEditor = 'news_editor', PostAdmin = 'post_admin', Superadmin = 'superadmin', - UserAdmin = 'user_admin', + UserAdmin = 'user_admin' } export type FeatureInfo = { @@ -201,7 +210,7 @@ export enum FileType { Pdf = 'PDF', Powerpoint = 'POWERPOINT', Spreadsheet = 'SPREADSHEET', - Text = 'TEXT', + Text = 'TEXT' } export type GroupedPost = { @@ -226,6 +235,11 @@ export type HistoryEntry = { start: Scalars['Date']; }; +export type Location = { + link?: Maybe; + title: Scalars['String']; +}; + export type LoginProvider = { email?: Maybe; id: Scalars['Int']; @@ -272,7 +286,7 @@ export enum MeetingDocumentType { LateDocuments = 'lateDocuments', Protocol = 'protocol', /** Kallelse */ - Summons = 'summons', + Summons = 'summons' } export enum MeetingType { @@ -291,9 +305,27 @@ export enum MeetingType { /** Valmöte */ Vm = 'VM', /** Vårterminsmöte */ - Vtm = 'VTM', + Vtm = 'VTM' } +export type ModifiedActivity = { + description?: InputMaybe; + endDate?: InputMaybe; + imageUrl?: InputMaybe; + location?: InputMaybe; + startDate?: InputMaybe; + title?: InputMaybe; + utskott?: InputMaybe; +}; + +export type ModifiedTicket = { + activityID?: InputMaybe; + count?: InputMaybe; + currency?: InputMaybe; + name?: InputMaybe; + price?: InputMaybe; +}; + /** We don't need every part; It should already exist */ export type ModifyArticle = { articleType?: InputMaybe; @@ -323,6 +355,7 @@ export type ModifyPost = { export type Mutation = { activatePost: Scalars['Boolean']; + addActivity: Activity; addArticle: Article; addElectables: Scalars['Boolean']; addEmergencyContact: EmergencyContact; @@ -330,6 +363,7 @@ export type Mutation = { addHehe: Scalars['Boolean']; addMeeting: Meeting; addPost: Post; + addTicket: Ticket; addUsersToPost: Post; casCreateUser: User; casLogin: CasLoginResponse; @@ -348,14 +382,17 @@ export type Mutation = { /** Test user credentials and if valid get a jwt token */ login: LoginResponse; logout: Scalars['Boolean']; + modifyActivity: Activity; modifyArticle: Article; modifyPost: Scalars['Boolean']; + modifyTicket: Ticket; /** Only possible during open election, so electionId is known */ nominate: Scalars['Boolean']; openElection: Scalars['Boolean']; propose: Scalars['Boolean']; providerLogin: LoginResponse; refresh: TokenResponse; + removeActivity: Activity; removeArticle: Scalars['Boolean']; removeElectables: Scalars['Boolean']; removeEmergencyContact: Scalars['Boolean']; @@ -364,6 +401,7 @@ export type Mutation = { removeHistoryEntry: Scalars['Boolean']; removeMeeting: Scalars['Boolean']; removeProposal: Scalars['Boolean']; + removeTicket: Ticket; requestPasswordReset: Scalars['Boolean']; resetPassword: Scalars['Boolean']; /** Only possible during open election, so electionId is known */ @@ -381,47 +419,66 @@ export type Mutation = { validateToken: Scalars['Boolean']; }; + export type MutationActivatePostArgs = { id: Scalars['Int']; }; + +export type MutationAddActivityArgs = { + activity: NewActivity; +}; + + export type MutationAddArticleArgs = { entry: NewArticle; }; + export type MutationAddElectablesArgs = { electionId: Scalars['Int']; postIds: Array; }; + export type MutationAddEmergencyContactArgs = { name: Scalars['String']; phone: Scalars['String']; type: EmergencyContactType; }; + export type MutationAddFileToMeetingArgs = { fileId: Scalars['String']; fileType: MeetingDocumentType; meetingId: Scalars['Int']; }; + export type MutationAddHeheArgs = { fileId: Scalars['ID']; number: Scalars['Int']; year: Scalars['Int']; }; + export type MutationAddMeetingArgs = { number?: InputMaybe; type: MeetingType; year?: InputMaybe; }; + export type MutationAddPostArgs = { info: NewPost; }; + +export type MutationAddTicketArgs = { + ticket: NewTicket; +}; + + export type MutationAddUsersToPostArgs = { end?: InputMaybe; id: Scalars['Int']; @@ -429,204 +486,279 @@ export type MutationAddUsersToPostArgs = { usernames: Array; }; + export type MutationCasCreateUserArgs = { hash: Scalars['String']; input: NewUser; }; + export type MutationCasLoginArgs = { token: Scalars['String']; }; + export type MutationChangePasswordArgs = { newPassword: Scalars['String']; oldPassword: Scalars['String']; }; + export type MutationCreateApiKeyArgs = { description: Scalars['String']; }; + export type MutationCreateElectionArgs = { electables: Array; nominationsHidden: Scalars['Boolean']; }; + export type MutationCreateFolderArgs = { name: Scalars['String']; path: Scalars['String']; }; + export type MutationCreateUserArgs = { input: NewUser; }; + export type MutationDeactivatePostArgs = { id: Scalars['Int']; }; + export type MutationDeleteApiKeyArgs = { key: Scalars['String']; }; + export type MutationDeleteFileArgs = { id: Scalars['ID']; }; + export type MutationForgetUserArgs = { username: Scalars['String']; }; + export type MutationIssueTokensArgs = { username: Scalars['String']; }; + export type MutationLinkLoginProviderArgs = { input: ProviderOptions; }; + export type MutationLoginArgs = { password: Scalars['String']; username: Scalars['String']; }; + +export type MutationModifyActivityArgs = { + entry: ModifiedActivity; + id: Scalars['String']; +}; + + export type MutationModifyArticleArgs = { articleId: Scalars['Int']; entry: ModifyArticle; }; + export type MutationModifyPostArgs = { info: ModifyPost; }; + +export type MutationModifyTicketArgs = { + entry: ModifiedTicket; + id: Scalars['String']; +}; + + export type MutationNominateArgs = { postIds: Array; username: Scalars['String']; }; + export type MutationOpenElectionArgs = { electionId: Scalars['Int']; }; + export type MutationProposeArgs = { electionId: Scalars['Int']; postId: Scalars['Int']; username: Scalars['String']; }; + export type MutationProviderLoginArgs = { input: ProviderOptions; }; + export type MutationRefreshArgs = { refreshToken: Scalars['String']; }; + +export type MutationRemoveActivityArgs = { + id: Scalars['String']; +}; + + export type MutationRemoveArticleArgs = { articleId: Scalars['Int']; }; + export type MutationRemoveElectablesArgs = { electionId: Scalars['Int']; postIds: Array; }; + export type MutationRemoveEmergencyContactArgs = { id: Scalars['Int']; }; + export type MutationRemoveFileFromMeetingArgs = { fileType: MeetingDocumentType; meetingId: Scalars['Int']; }; + export type MutationRemoveHeheArgs = { number: Scalars['Int']; year: Scalars['Int']; }; + export type MutationRemoveHistoryEntryArgs = { id: Scalars['Int']; }; + export type MutationRemoveMeetingArgs = { id: Scalars['Int']; }; + export type MutationRemoveProposalArgs = { electionId: Scalars['Int']; postId: Scalars['Int']; username: Scalars['String']; }; + +export type MutationRemoveTicketArgs = { + id: Scalars['String']; +}; + + export type MutationRequestPasswordResetArgs = { resetLink: Scalars['String']; returnTo?: InputMaybe; username: Scalars['String']; }; + export type MutationResetPasswordArgs = { password: Scalars['String']; token: Scalars['String']; username: Scalars['String']; }; + export type MutationRespondToNominationArgs = { accepts: NominationAnswer; postId: Scalars['Int']; }; + export type MutationSendEmailArgs = { options: SendEmailOptions; }; + export type MutationSetApiKeyAccessArgs = { access: AccessInput; key: Scalars['String']; }; + export type MutationSetElectablesArgs = { electionId: Scalars['Int']; postIds: Array; }; + export type MutationSetHiddenNominationsArgs = { electionId: Scalars['Int']; hidden: Scalars['Boolean']; }; + export type MutationSetIndividualAccessArgs = { access: AccessInput; username: Scalars['String']; }; + export type MutationSetPostAccessArgs = { access: AccessInput; postId: Scalars['Int']; }; + export type MutationSetUserPostEndArgs = { end: Scalars['Date']; id: Scalars['Int']; }; + export type MutationUnlinkLoginProviderArgs = { id: Scalars['Int']; }; + export type MutationUpdateUserArgs = { input: UpdateUser; }; + export type MutationValidatePasswordResetTokenArgs = { token: Scalars['String']; username: Scalars['String']; }; + export type MutationValidateTokenArgs = { token: Scalars['String']; }; +export type NewActivity = { + description?: InputMaybe; + endDate?: InputMaybe; + imageUrl?: InputMaybe; + location?: InputMaybe; + startDate: Scalars['DateTime']; + title: Scalars['String']; + utskott: Utskott; +}; + export type NewArticle = { articleType: ArticleType; body: Scalars['String']; @@ -635,6 +767,11 @@ export type NewArticle = { title: Scalars['String']; }; +export type NewLocation = { + link?: InputMaybe; + title: Scalars['String']; +}; + export type NewPost = { active?: InputMaybe; description?: InputMaybe; @@ -653,6 +790,14 @@ export type NewPost = { utskott: Utskott; }; +export type NewTicket = { + activityID?: InputMaybe; + count?: InputMaybe; + currency?: InputMaybe; + name: Scalars['String']; + price?: InputMaybe; +}; + export type NewUser = { class: Scalars['String']; email?: InputMaybe; @@ -671,12 +816,12 @@ export type Nomination = { export enum NominationAnswer { No = 'NO', NotAnswered = 'NOT_ANSWERED', - Yes = 'YES', + Yes = 'YES' } export enum Order { Asc = 'asc', - Desc = 'desc', + Desc = 'desc' } export type PageInfo = { @@ -724,6 +869,7 @@ export type Post = { utskott: Utskott; }; + export type PostHistoryArgs = { current?: InputMaybe; }; @@ -740,7 +886,7 @@ export enum PostType { /** Upp till _n_ stycken */ N = 'N', /** Unik, finns bara 1, t.ex. utskottsordförande */ - U = 'U', + U = 'U' } /** Valberedningens förslag */ @@ -760,6 +906,8 @@ export type ProviderOptions = { * does not take an `electionId` parameter. */ export type Query = { + activities: Array; + activity: Activity; apiKey: ApiKey; apiKeys: Array; article: Article; @@ -798,6 +946,8 @@ export type Query = { posts: Array; searchFiles: Array; searchUser: Array; + ticket: Ticket; + tickets: Array>; user: User; userByCard: User; users: Array; @@ -805,6 +955,27 @@ export type Query = { utskott: Utskott; }; + +/** + * Queries and mutations that relies on an election being open + * does not take an `electionId` parameter. + */ +export type QueryActivitiesArgs = { + from: Scalars['DateTime']; + to: Scalars['DateTime']; + utskott: Array; +}; + + +/** + * Queries and mutations that relies on an election being open + * does not take an `electionId` parameter. + */ +export type QueryActivityArgs = { + id: Scalars['String']; +}; + + /** * Queries and mutations that relies on an election being open * does not take an `electionId` parameter. @@ -813,6 +984,7 @@ export type QueryApiKeyArgs = { key: Scalars['String']; }; + /** * Queries and mutations that relies on an election being open * does not take an `electionId` parameter. @@ -822,6 +994,7 @@ export type QueryArticleArgs = { slug?: InputMaybe; }; + /** * Queries and mutations that relies on an election being open * does not take an `electionId` parameter. @@ -833,6 +1006,7 @@ export type QueryArticlesArgs = { type?: InputMaybe; }; + /** * Queries and mutations that relies on an election being open * does not take an `electionId` parameter. @@ -841,6 +1015,7 @@ export type QueryElectionArgs = { electionId: Scalars['Int']; }; + /** * Queries and mutations that relies on an election being open * does not take an `electionId` parameter. @@ -849,6 +1024,7 @@ export type QueryElectionsArgs = { electionIds: Array; }; + /** * Queries and mutations that relies on an election being open * does not take an `electionId` parameter. @@ -857,6 +1033,7 @@ export type QueryFileArgs = { id: Scalars['ID']; }; + /** * Queries and mutations that relies on an election being open * does not take an `electionId` parameter. @@ -865,6 +1042,7 @@ export type QueryFileSystemArgs = { folder: Scalars['String']; }; + /** * Queries and mutations that relies on an election being open * does not take an `electionId` parameter. @@ -873,6 +1051,7 @@ export type QueryFilesArgs = { type?: InputMaybe; }; + /** * Queries and mutations that relies on an election being open * does not take an `electionId` parameter. @@ -881,6 +1060,7 @@ export type QueryGroupedPostsArgs = { includeInactive?: InputMaybe; }; + /** * Queries and mutations that relies on an election being open * does not take an `electionId` parameter. @@ -890,6 +1070,7 @@ export type QueryHeheArgs = { year: Scalars['Int']; }; + /** * Queries and mutations that relies on an election being open * does not take an `electionId` parameter. @@ -898,6 +1079,7 @@ export type QueryHehesArgs = { year: Scalars['Int']; }; + /** * Queries and mutations that relies on an election being open * does not take an `electionId` parameter. @@ -907,6 +1089,7 @@ export type QueryHiddenNominationsArgs = { electionId: Scalars['Int']; }; + /** * Queries and mutations that relies on an election being open * does not take an `electionId` parameter. @@ -915,6 +1098,7 @@ export type QueryIndividualAccessArgs = { username: Scalars['String']; }; + /** * Queries and mutations that relies on an election being open * does not take an `electionId` parameter. @@ -923,6 +1107,7 @@ export type QueryLatestBoardMeetingsArgs = { limit?: InputMaybe; }; + /** * Queries and mutations that relies on an election being open * does not take an `electionId` parameter. @@ -933,6 +1118,7 @@ export type QueryLatestElectionsArgs = { limit?: InputMaybe; }; + /** * Queries and mutations that relies on an election being open * does not take an `electionId` parameter. @@ -942,6 +1128,7 @@ export type QueryLatestHeheArgs = { sortOrder?: InputMaybe; }; + /** * Queries and mutations that relies on an election being open * does not take an `electionId` parameter. @@ -950,6 +1137,7 @@ export type QueryLatestnewsArgs = { limit?: InputMaybe; }; + /** * Queries and mutations that relies on an election being open * does not take an `electionId` parameter. @@ -958,6 +1146,7 @@ export type QueryMeetingArgs = { id: Scalars['Int']; }; + /** * Queries and mutations that relies on an election being open * does not take an `electionId` parameter. @@ -968,6 +1157,7 @@ export type QueryMeetingsArgs = { year?: InputMaybe; }; + /** * Queries and mutations that relies on an election being open * does not take an `electionId` parameter. @@ -977,6 +1167,7 @@ export type QueryMyNominationsArgs = { electionId: Scalars['Int']; }; + /** * Queries and mutations that relies on an election being open * does not take an `electionId` parameter. @@ -987,6 +1178,7 @@ export type QueryNewsentriesArgs = { before?: InputMaybe; }; + /** * Queries and mutations that relies on an election being open * does not take an `electionId` parameter. @@ -995,6 +1187,7 @@ export type QueryNumberOfMembersArgs = { noAlumni?: InputMaybe; }; + /** * Queries and mutations that relies on an election being open * does not take an `electionId` parameter. @@ -1004,6 +1197,7 @@ export type QueryNumberOfNominationsArgs = { postId?: InputMaybe; }; + /** * Queries and mutations that relies on an election being open * does not take an `electionId` parameter. @@ -1013,6 +1207,7 @@ export type QueryNumberOfProposalsArgs = { postId?: InputMaybe; }; + /** * Queries and mutations that relies on an election being open * does not take an `electionId` parameter. @@ -1021,6 +1216,7 @@ export type QueryNumberOfVolunteersArgs = { date?: InputMaybe; }; + /** * Queries and mutations that relies on an election being open * does not take an `electionId` parameter. @@ -1029,6 +1225,7 @@ export type QueryPaginatedHehesArgs = { pagination?: InputMaybe; }; + /** * Queries and mutations that relies on an election being open * does not take an `electionId` parameter. @@ -1037,6 +1234,7 @@ export type QueryPostArgs = { id: Scalars['Int']; }; + /** * Queries and mutations that relies on an election being open * does not take an `electionId` parameter. @@ -1045,6 +1243,7 @@ export type QueryPostAccessArgs = { postId: Scalars['Int']; }; + /** * Queries and mutations that relies on an election being open * does not take an `electionId` parameter. @@ -1054,6 +1253,7 @@ export type QueryPostsArgs = { utskott?: InputMaybe; }; + /** * Queries and mutations that relies on an election being open * does not take an `electionId` parameter. @@ -1062,6 +1262,7 @@ export type QuerySearchFilesArgs = { search: Scalars['String']; }; + /** * Queries and mutations that relies on an election being open * does not take an `electionId` parameter. @@ -1070,6 +1271,25 @@ export type QuerySearchUserArgs = { search: Scalars['String']; }; + +/** + * Queries and mutations that relies on an election being open + * does not take an `electionId` parameter. + */ +export type QueryTicketArgs = { + id: Scalars['String']; +}; + + +/** + * Queries and mutations that relies on an election being open + * does not take an `electionId` parameter. + */ +export type QueryTicketsArgs = { + activityID?: InputMaybe; +}; + + /** * Queries and mutations that relies on an election being open * does not take an `electionId` parameter. @@ -1078,6 +1298,7 @@ export type QueryUserArgs = { username: Scalars['String']; }; + /** * Queries and mutations that relies on an election being open * does not take an `electionId` parameter. @@ -1086,6 +1307,7 @@ export type QueryUserByCardArgs = { luCard: Scalars['String']; }; + /** * Queries and mutations that relies on an election being open * does not take an `electionId` parameter. @@ -1094,6 +1316,7 @@ export type QueryUsersArgs = { usernames: Array; }; + /** * Queries and mutations that relies on an election being open * does not take an `electionId` parameter. @@ -1112,9 +1335,18 @@ export type SendEmailOptions = { export enum SortOrder { Asc = 'asc', - Desc = 'desc', + Desc = 'desc' } +export type Ticket = { + activityID?: Maybe; + count?: Maybe; + currency?: Maybe; + id: Scalars['String']; + name: Scalars['String']; + price?: Maybe; +}; + export type TokenResponse = { accessToken: Scalars['String']; refreshToken: Scalars['String']; @@ -1155,6 +1387,7 @@ export type User = { zipCode?: Maybe; }; + export type UserPostHistoryArgs = { current?: InputMaybe; }; @@ -1177,7 +1410,7 @@ export enum Utskott { Other = 'OTHER', Pengu = 'PENGU', Sre = 'SRE', - Styrelsen = 'STYRELSEN', + Styrelsen = 'STYRELSEN' } export type WithIndex = TObject & Record; @@ -1185,41 +1418,34 @@ export type ResolversObject = WithIndex; export type ResolverTypeWrapper = Promise | T; + export type ResolverWithResolve = { resolve: ResolverFn; }; -export type Resolver = - | ResolverFn - | ResolverWithResolve; +export type Resolver = ResolverFn | ResolverWithResolve; export type ResolverFn = ( parent: TParent, args: TArgs, context: TContext, - info: GraphQLResolveInfo, + info: GraphQLResolveInfo ) => Promise | TResult; export type SubscriptionSubscribeFn = ( parent: TParent, args: TArgs, context: TContext, - info: GraphQLResolveInfo, + info: GraphQLResolveInfo ) => AsyncIterable | Promise>; export type SubscriptionResolveFn = ( parent: TParent, args: TArgs, context: TContext, - info: GraphQLResolveInfo, + info: GraphQLResolveInfo ) => TResult | Promise; -export interface SubscriptionSubscriberObject< - TResult, - TKey extends string, - TParent, - TContext, - TArgs, -> { +export interface SubscriptionSubscriberObject { subscribe: SubscriptionSubscribeFn<{ [key in TKey]: TResult }, TParent, TContext, TArgs>; resolve?: SubscriptionResolveFn; } @@ -1233,27 +1459,17 @@ export type SubscriptionObject | SubscriptionResolverObject; -export type SubscriptionResolver< - TResult, - TKey extends string, - TParent = {}, - TContext = {}, - TArgs = {}, -> = +export type SubscriptionResolver = | ((...args: any[]) => SubscriptionObject) | SubscriptionObject; export type TypeResolveFn = ( parent: TParent, context: TContext, - info: GraphQLResolveInfo, + info: GraphQLResolveInfo ) => Maybe | Promise>; -export type IsTypeOfResolverFn = ( - obj: T, - context: TContext, - info: GraphQLResolveInfo, -) => boolean | Promise; +export type IsTypeOfResolverFn = (obj: T, context: TContext, info: GraphQLResolveInfo) => boolean | Promise; export type NextResolverFn = () => Promise; @@ -1262,7 +1478,7 @@ export type DirectiveResolverFn TResult | Promise; /** Mapping between all available schema types and the resolvers types */ @@ -1271,6 +1487,8 @@ export type ResolversTypes = ResolversObject<{ AccessInput: AccessInput; AccessResourceType: AccessResourceType; AccessType: AccessType; + Activity: ResolverTypeWrapper; + ActivitySource: ActivitySource; ApiKey: ResolverTypeWrapper; Article: ResolverTypeWrapper; ArticleType: ArticleType; @@ -1286,9 +1504,7 @@ export type ResolversTypes = ResolversObject<{ Feature: Feature; FeatureInfo: ResolverTypeWrapper; File: ResolverTypeWrapper; - FileSystemResponse: ResolverTypeWrapper< - Omit & { files: Array } - >; + FileSystemResponse: ResolverTypeWrapper & { files: Array }>; FileSystemResponsePath: ResolverTypeWrapper; FileType: FileType; GroupedPost: ResolverTypeWrapper; @@ -1296,25 +1512,29 @@ export type ResolversTypes = ResolversObject<{ HistoryEntry: ResolverTypeWrapper; ID: ResolverTypeWrapper; Int: ResolverTypeWrapper; + Location: ResolverTypeWrapper; LoginProvider: ResolverTypeWrapper; LoginResponse: ResolverTypeWrapper; Meeting: ResolverTypeWrapper; MeetingDocumentType: MeetingDocumentType; MeetingType: MeetingType; + ModifiedActivity: ModifiedActivity; + ModifiedTicket: ModifiedTicket; ModifyArticle: ModifyArticle; ModifyPost: ModifyPost; Mutation: ResolverTypeWrapper<{}>; + NewActivity: NewActivity; NewArticle: NewArticle; + NewLocation: NewLocation; NewPost: NewPost; + NewTicket: NewTicket; NewUser: NewUser; Nomination: ResolverTypeWrapper; NominationAnswer: NominationAnswer; Object: ResolverTypeWrapper; Order: Order; PageInfo: ResolverTypeWrapper; - PaginatedHehes: ResolverTypeWrapper< - Omit & { values: Array } - >; + PaginatedHehes: ResolverTypeWrapper & { values: Array }>; Pagination: ResolversTypes['PaginatedHehes']; PaginationParams: PaginationParams; Post: ResolverTypeWrapper; @@ -1325,6 +1545,7 @@ export type ResolversTypes = ResolversObject<{ SendEmailOptions: SendEmailOptions; SortOrder: SortOrder; String: ResolverTypeWrapper; + Ticket: ResolverTypeWrapper; TokenResponse: ResolverTypeWrapper; UpdateUser: UpdateUser; User: ResolverTypeWrapper; @@ -1336,6 +1557,7 @@ export type ResolversTypes = ResolversObject<{ export type ResolversParentTypes = ResolversObject<{ Access: Access; AccessInput: AccessInput; + Activity: Activity; ApiKey: ApiKeyResponse; Article: ArticleResponse; Boolean: Scalars['Boolean']; @@ -1347,23 +1569,27 @@ export type ResolversParentTypes = ResolversObject<{ EmergencyContact: EmergencyContact; FeatureInfo: FeatureInfo; File: FileResponse; - FileSystemResponse: Omit & { - files: Array; - }; + FileSystemResponse: Omit & { files: Array }; FileSystemResponsePath: FileSystemResponsePath; GroupedPost: GroupedPost; Hehe: HeheResponse; HistoryEntry: HistoryEntry; ID: Scalars['ID']; Int: Scalars['Int']; + Location: Location; LoginProvider: LoginProvider; LoginResponse: LoginResponse; Meeting: MeetingResponse; + ModifiedActivity: ModifiedActivity; + ModifiedTicket: ModifiedTicket; ModifyArticle: ModifyArticle; ModifyPost: ModifyPost; Mutation: {}; + NewActivity: NewActivity; NewArticle: NewArticle; + NewLocation: NewLocation; NewPost: NewPost; + NewTicket: NewTicket; NewUser: NewUser; Nomination: NominationResponse; Object: Scalars['Object']; @@ -1377,25 +1603,33 @@ export type ResolversParentTypes = ResolversObject<{ Query: {}; SendEmailOptions: SendEmailOptions; String: Scalars['String']; + Ticket: Ticket; TokenResponse: TokenResponse; UpdateUser: UpdateUser; User: User; UserPostHistoryEntry: UserPostHistoryEntry; }>; -export type AccessResolvers< - ContextType = Context, - ParentType extends ResolversParentTypes['Access'] = ResolversParentTypes['Access'], -> = ResolversObject<{ +export type AccessResolvers = ResolversObject<{ doors?: Resolver, ParentType, ContextType>; features?: Resolver, ParentType, ContextType>; __isTypeOf?: IsTypeOfResolverFn; }>; -export type ApiKeyResolvers< - ContextType = Context, - ParentType extends ResolversParentTypes['ApiKey'] = ResolversParentTypes['ApiKey'], -> = ResolversObject<{ +export type ActivityResolvers = ResolversObject<{ + description?: Resolver, ParentType, ContextType>; + endDate?: Resolver, ParentType, ContextType>; + id?: Resolver; + imageUrl?: Resolver, ParentType, ContextType>; + location?: Resolver, ParentType, ContextType>; + source?: Resolver; + startDate?: Resolver; + title?: Resolver; + utskott?: Resolver; + __isTypeOf?: IsTypeOfResolverFn; +}>; + +export type ApiKeyResolvers = ResolversObject<{ access?: Resolver; creator?: Resolver; description?: Resolver; @@ -1403,10 +1637,7 @@ export type ApiKeyResolvers< __isTypeOf?: IsTypeOfResolverFn; }>; -export type ArticleResolvers< - ContextType = Context, - ParentType extends ResolversParentTypes['Article'] = ResolversParentTypes['Article'], -> = ResolversObject<{ +export type ArticleResolvers = ResolversObject<{ articleType?: Resolver; author?: Resolver; body?: Resolver; @@ -1416,20 +1647,12 @@ export type ArticleResolvers< lastUpdatedBy?: Resolver; signature?: Resolver; slug?: Resolver; - tags?: Resolver< - Array, - ParentType, - ContextType, - Partial - >; + tags?: Resolver, ParentType, ContextType, Partial>; title?: Resolver; __isTypeOf?: IsTypeOfResolverFn; }>; -export type CasLoginResponseResolvers< - ContextType = Context, - ParentType extends ResolversParentTypes['CasLoginResponse'] = ResolversParentTypes['CasLoginResponse'], -> = ResolversObject<{ +export type CasLoginResponseResolvers = ResolversObject<{ exists?: Resolver; hash?: Resolver; username?: Resolver; @@ -1440,24 +1663,17 @@ export interface DateScalarConfig extends GraphQLScalarTypeConfig { +export interface DateTimeScalarConfig extends GraphQLScalarTypeConfig { name: 'DateTime'; } -export type DoorInfoResolvers< - ContextType = Context, - ParentType extends ResolversParentTypes['DoorInfo'] = ResolversParentTypes['DoorInfo'], -> = ResolversObject<{ +export type DoorInfoResolvers = ResolversObject<{ description?: Resolver; name?: Resolver; __isTypeOf?: IsTypeOfResolverFn; }>; -export type ElectionResolvers< - ContextType = Context, - ParentType extends ResolversParentTypes['Election'] = ResolversParentTypes['Election'], -> = ResolversObject<{ +export type ElectionResolvers = ResolversObject<{ acceptedNominations?: Resolver, ParentType, ContextType>; closedAt?: Resolver, ParentType, ContextType>; createdAt?: Resolver; @@ -1471,10 +1687,7 @@ export type ElectionResolvers< __isTypeOf?: IsTypeOfResolverFn; }>; -export type EmergencyContactResolvers< - ContextType = Context, - ParentType extends ResolversParentTypes['EmergencyContact'] = ResolversParentTypes['EmergencyContact'], -> = ResolversObject<{ +export type EmergencyContactResolvers = ResolversObject<{ id?: Resolver; name?: Resolver; phone?: Resolver; @@ -1482,19 +1695,13 @@ export type EmergencyContactResolvers< __isTypeOf?: IsTypeOfResolverFn; }>; -export type FeatureInfoResolvers< - ContextType = Context, - ParentType extends ResolversParentTypes['FeatureInfo'] = ResolversParentTypes['FeatureInfo'], -> = ResolversObject<{ +export type FeatureInfoResolvers = ResolversObject<{ description?: Resolver; name?: Resolver; __isTypeOf?: IsTypeOfResolverFn; }>; -export type FileResolvers< - ContextType = Context, - ParentType extends ResolversParentTypes['File'] = ResolversParentTypes['File'], -> = ResolversObject<{ +export type FileResolvers = ResolversObject<{ accessType?: Resolver; createdAt?: Resolver, ParentType, ContextType>; createdBy?: Resolver, ParentType, ContextType>; @@ -1507,37 +1714,25 @@ export type FileResolvers< __isTypeOf?: IsTypeOfResolverFn; }>; -export type FileSystemResponseResolvers< - ContextType = Context, - ParentType extends ResolversParentTypes['FileSystemResponse'] = ResolversParentTypes['FileSystemResponse'], -> = ResolversObject<{ +export type FileSystemResponseResolvers = ResolversObject<{ files?: Resolver, ParentType, ContextType>; path?: Resolver, ParentType, ContextType>; __isTypeOf?: IsTypeOfResolverFn; }>; -export type FileSystemResponsePathResolvers< - ContextType = Context, - ParentType extends ResolversParentTypes['FileSystemResponsePath'] = ResolversParentTypes['FileSystemResponsePath'], -> = ResolversObject<{ +export type FileSystemResponsePathResolvers = ResolversObject<{ id?: Resolver; name?: Resolver; __isTypeOf?: IsTypeOfResolverFn; }>; -export type GroupedPostResolvers< - ContextType = Context, - ParentType extends ResolversParentTypes['GroupedPost'] = ResolversParentTypes['GroupedPost'], -> = ResolversObject<{ +export type GroupedPostResolvers = ResolversObject<{ posts?: Resolver, ParentType, ContextType>; utskott?: Resolver; __isTypeOf?: IsTypeOfResolverFn; }>; -export type HeheResolvers< - ContextType = Context, - ParentType extends ResolversParentTypes['Hehe'] = ResolversParentTypes['Hehe'], -> = ResolversObject<{ +export type HeheResolvers = ResolversObject<{ coverEndpoint?: Resolver; coverId?: Resolver; file?: Resolver; @@ -1548,10 +1743,7 @@ export type HeheResolvers< __isTypeOf?: IsTypeOfResolverFn; }>; -export type HistoryEntryResolvers< - ContextType = Context, - ParentType extends ResolversParentTypes['HistoryEntry'] = ResolversParentTypes['HistoryEntry'], -> = ResolversObject<{ +export type HistoryEntryResolvers = ResolversObject<{ end?: Resolver, ParentType, ContextType>; holder?: Resolver; id?: Resolver; @@ -1559,10 +1751,13 @@ export type HistoryEntryResolvers< __isTypeOf?: IsTypeOfResolverFn; }>; -export type LoginProviderResolvers< - ContextType = Context, - ParentType extends ResolversParentTypes['LoginProvider'] = ResolversParentTypes['LoginProvider'], -> = ResolversObject<{ +export type LocationResolvers = ResolversObject<{ + link?: Resolver, ParentType, ContextType>; + title?: Resolver; + __isTypeOf?: IsTypeOfResolverFn; +}>; + +export type LoginProviderResolvers = ResolversObject<{ email?: Resolver, ParentType, ContextType>; id?: Resolver; provider?: Resolver; @@ -1570,20 +1765,14 @@ export type LoginProviderResolvers< __isTypeOf?: IsTypeOfResolverFn; }>; -export type LoginResponseResolvers< - ContextType = Context, - ParentType extends ResolversParentTypes['LoginResponse'] = ResolversParentTypes['LoginResponse'], -> = ResolversObject<{ +export type LoginResponseResolvers = ResolversObject<{ accessToken?: Resolver; refreshToken?: Resolver; user?: Resolver; __isTypeOf?: IsTypeOfResolverFn; }>; -export type MeetingResolvers< - ContextType = Context, - ParentType extends ResolversParentTypes['Meeting'] = ResolversParentTypes['Meeting'], -> = ResolversObject<{ +export type MeetingResolvers = ResolversObject<{ agenda?: Resolver, ParentType, ContextType>; appendix?: Resolver, ParentType, ContextType>; documents?: Resolver, ParentType, ContextType>; @@ -1598,330 +1787,70 @@ export type MeetingResolvers< __isTypeOf?: IsTypeOfResolverFn; }>; -export type MutationResolvers< - ContextType = Context, - ParentType extends ResolversParentTypes['Mutation'] = ResolversParentTypes['Mutation'], -> = ResolversObject<{ - activatePost?: Resolver< - ResolversTypes['Boolean'], - ParentType, - ContextType, - RequireFields - >; - addArticle?: Resolver< - ResolversTypes['Article'], - ParentType, - ContextType, - RequireFields - >; - addElectables?: Resolver< - ResolversTypes['Boolean'], - ParentType, - ContextType, - RequireFields - >; - addEmergencyContact?: Resolver< - ResolversTypes['EmergencyContact'], - ParentType, - ContextType, - RequireFields - >; - addFileToMeeting?: Resolver< - ResolversTypes['Boolean'], - ParentType, - ContextType, - RequireFields - >; - addHehe?: Resolver< - ResolversTypes['Boolean'], - ParentType, - ContextType, - RequireFields - >; - addMeeting?: Resolver< - ResolversTypes['Meeting'], - ParentType, - ContextType, - RequireFields - >; - addPost?: Resolver< - ResolversTypes['Post'], - ParentType, - ContextType, - RequireFields - >; - addUsersToPost?: Resolver< - ResolversTypes['Post'], - ParentType, - ContextType, - RequireFields - >; - casCreateUser?: Resolver< - ResolversTypes['User'], - ParentType, - ContextType, - RequireFields - >; - casLogin?: Resolver< - ResolversTypes['CasLoginResponse'], - ParentType, - ContextType, - RequireFields - >; - changePassword?: Resolver< - ResolversTypes['Boolean'], - ParentType, - ContextType, - RequireFields - >; +export type MutationResolvers = ResolversObject<{ + activatePost?: Resolver>; + addActivity?: Resolver>; + addArticle?: Resolver>; + addElectables?: Resolver>; + addEmergencyContact?: Resolver>; + addFileToMeeting?: Resolver>; + addHehe?: Resolver>; + addMeeting?: Resolver>; + addPost?: Resolver>; + addTicket?: Resolver>; + addUsersToPost?: Resolver>; + casCreateUser?: Resolver>; + casLogin?: Resolver>; + changePassword?: Resolver>; closeElection?: Resolver; - createApiKey?: Resolver< - ResolversTypes['String'], - ParentType, - ContextType, - RequireFields - >; - createElection?: Resolver< - ResolversTypes['Election'], - ParentType, - ContextType, - RequireFields - >; - createFolder?: Resolver< - ResolversTypes['File'], - ParentType, - ContextType, - RequireFields - >; - createUser?: Resolver< - ResolversTypes['User'], - ParentType, - ContextType, - RequireFields - >; - deactivatePost?: Resolver< - ResolversTypes['Boolean'], - ParentType, - ContextType, - RequireFields - >; - deleteApiKey?: Resolver< - ResolversTypes['Boolean'], - ParentType, - ContextType, - RequireFields - >; - deleteFile?: Resolver< - ResolversTypes['Boolean'], - ParentType, - ContextType, - RequireFields - >; - forgetUser?: Resolver< - ResolversTypes['User'], - ParentType, - ContextType, - RequireFields - >; - issueTokens?: Resolver< - ResolversTypes['TokenResponse'], - ParentType, - ContextType, - RequireFields - >; - linkLoginProvider?: Resolver< - ResolversTypes['LoginProvider'], - ParentType, - ContextType, - RequireFields - >; - login?: Resolver< - ResolversTypes['LoginResponse'], - ParentType, - ContextType, - RequireFields - >; + createApiKey?: Resolver>; + createElection?: Resolver>; + createFolder?: Resolver>; + createUser?: Resolver>; + deactivatePost?: Resolver>; + deleteApiKey?: Resolver>; + deleteFile?: Resolver>; + forgetUser?: Resolver>; + issueTokens?: Resolver>; + linkLoginProvider?: Resolver>; + login?: Resolver>; logout?: Resolver; - modifyArticle?: Resolver< - ResolversTypes['Article'], - ParentType, - ContextType, - RequireFields - >; - modifyPost?: Resolver< - ResolversTypes['Boolean'], - ParentType, - ContextType, - RequireFields - >; - nominate?: Resolver< - ResolversTypes['Boolean'], - ParentType, - ContextType, - RequireFields - >; - openElection?: Resolver< - ResolversTypes['Boolean'], - ParentType, - ContextType, - RequireFields - >; - propose?: Resolver< - ResolversTypes['Boolean'], - ParentType, - ContextType, - RequireFields - >; - providerLogin?: Resolver< - ResolversTypes['LoginResponse'], - ParentType, - ContextType, - RequireFields - >; - refresh?: Resolver< - ResolversTypes['TokenResponse'], - ParentType, - ContextType, - RequireFields - >; - removeArticle?: Resolver< - ResolversTypes['Boolean'], - ParentType, - ContextType, - RequireFields - >; - removeElectables?: Resolver< - ResolversTypes['Boolean'], - ParentType, - ContextType, - RequireFields - >; - removeEmergencyContact?: Resolver< - ResolversTypes['Boolean'], - ParentType, - ContextType, - RequireFields - >; - removeFileFromMeeting?: Resolver< - ResolversTypes['Boolean'], - ParentType, - ContextType, - RequireFields - >; - removeHehe?: Resolver< - ResolversTypes['Boolean'], - ParentType, - ContextType, - RequireFields - >; - removeHistoryEntry?: Resolver< - ResolversTypes['Boolean'], - ParentType, - ContextType, - RequireFields - >; - removeMeeting?: Resolver< - ResolversTypes['Boolean'], - ParentType, - ContextType, - RequireFields - >; - removeProposal?: Resolver< - ResolversTypes['Boolean'], - ParentType, - ContextType, - RequireFields - >; - requestPasswordReset?: Resolver< - ResolversTypes['Boolean'], - ParentType, - ContextType, - RequireFields - >; - resetPassword?: Resolver< - ResolversTypes['Boolean'], - ParentType, - ContextType, - RequireFields - >; - respondToNomination?: Resolver< - ResolversTypes['Boolean'], - ParentType, - ContextType, - RequireFields - >; - sendEmail?: Resolver< - ResolversTypes['Boolean'], - ParentType, - ContextType, - RequireFields - >; - setApiKeyAccess?: Resolver< - ResolversTypes['Boolean'], - ParentType, - ContextType, - RequireFields - >; - setElectables?: Resolver< - ResolversTypes['Boolean'], - ParentType, - ContextType, - RequireFields - >; - setHiddenNominations?: Resolver< - ResolversTypes['Boolean'], - ParentType, - ContextType, - RequireFields - >; - setIndividualAccess?: Resolver< - ResolversTypes['Boolean'], - ParentType, - ContextType, - RequireFields - >; - setPostAccess?: Resolver< - ResolversTypes['Boolean'], - ParentType, - ContextType, - RequireFields - >; - setUserPostEnd?: Resolver< - ResolversTypes['Boolean'], - ParentType, - ContextType, - RequireFields - >; - unlinkLoginProvider?: Resolver< - ResolversTypes['Boolean'], - ParentType, - ContextType, - RequireFields - >; - updateUser?: Resolver< - ResolversTypes['User'], - ParentType, - ContextType, - RequireFields - >; - validatePasswordResetToken?: Resolver< - ResolversTypes['Boolean'], - ParentType, - ContextType, - RequireFields - >; - validateToken?: Resolver< - ResolversTypes['Boolean'], - ParentType, - ContextType, - RequireFields - >; + modifyActivity?: Resolver>; + modifyArticle?: Resolver>; + modifyPost?: Resolver>; + modifyTicket?: Resolver>; + nominate?: Resolver>; + openElection?: Resolver>; + propose?: Resolver>; + providerLogin?: Resolver>; + refresh?: Resolver>; + removeActivity?: Resolver>; + removeArticle?: Resolver>; + removeElectables?: Resolver>; + removeEmergencyContact?: Resolver>; + removeFileFromMeeting?: Resolver>; + removeHehe?: Resolver>; + removeHistoryEntry?: Resolver>; + removeMeeting?: Resolver>; + removeProposal?: Resolver>; + removeTicket?: Resolver>; + requestPasswordReset?: Resolver>; + resetPassword?: Resolver>; + respondToNomination?: Resolver>; + sendEmail?: Resolver>; + setApiKeyAccess?: Resolver>; + setElectables?: Resolver>; + setHiddenNominations?: Resolver>; + setIndividualAccess?: Resolver>; + setPostAccess?: Resolver>; + setUserPostEnd?: Resolver>; + unlinkLoginProvider?: Resolver>; + updateUser?: Resolver>; + validatePasswordResetToken?: Resolver>; + validateToken?: Resolver>; }>; -export type NominationResolvers< - ContextType = Context, - ParentType extends ResolversParentTypes['Nomination'] = ResolversParentTypes['Nomination'], -> = ResolversObject<{ +export type NominationResolvers = ResolversObject<{ answer?: Resolver; post?: Resolver; user?: Resolver; @@ -1932,10 +1861,7 @@ export interface ObjectScalarConfig extends GraphQLScalarTypeConfig = ResolversObject<{ +export type PageInfoResolvers = ResolversObject<{ firstPage?: Resolver; hasNextPage?: Resolver; hasPreviousPage?: Resolver; @@ -1944,37 +1870,23 @@ export type PageInfoResolvers< __isTypeOf?: IsTypeOfResolverFn; }>; -export type PaginatedHehesResolvers< - ContextType = Context, - ParentType extends ResolversParentTypes['PaginatedHehes'] = ResolversParentTypes['PaginatedHehes'], -> = ResolversObject<{ +export type PaginatedHehesResolvers = ResolversObject<{ pageInfo?: Resolver; values?: Resolver, ParentType, ContextType>; __isTypeOf?: IsTypeOfResolverFn; }>; -export type PaginationResolvers< - ContextType = Context, - ParentType extends ResolversParentTypes['Pagination'] = ResolversParentTypes['Pagination'], -> = ResolversObject<{ +export type PaginationResolvers = ResolversObject<{ __resolveType: TypeResolveFn<'PaginatedHehes', ParentType, ContextType>; pageInfo?: Resolver; }>; -export type PostResolvers< - ContextType = Context, - ParentType extends ResolversParentTypes['Post'] = ResolversParentTypes['Post'], -> = ResolversObject<{ +export type PostResolvers = ResolversObject<{ access?: Resolver; active?: Resolver; description?: Resolver; email?: Resolver, ParentType, ContextType>; - history?: Resolver< - Array, - ParentType, - ContextType, - Partial - >; + history?: Resolver, ParentType, ContextType, Partial>; id?: Resolver; interviewRequired?: Resolver, ParentType, ContextType>; postType?: Resolver; @@ -1985,235 +1897,77 @@ export type PostResolvers< __isTypeOf?: IsTypeOfResolverFn; }>; -export type ProposalResolvers< - ContextType = Context, - ParentType extends ResolversParentTypes['Proposal'] = ResolversParentTypes['Proposal'], -> = ResolversObject<{ +export type ProposalResolvers = ResolversObject<{ post?: Resolver; user?: Resolver; __isTypeOf?: IsTypeOfResolverFn; }>; -export type QueryResolvers< - ContextType = Context, - ParentType extends ResolversParentTypes['Query'] = ResolversParentTypes['Query'], -> = ResolversObject<{ - apiKey?: Resolver< - ResolversTypes['ApiKey'], - ParentType, - ContextType, - RequireFields - >; +export type QueryResolvers = ResolversObject<{ + activities?: Resolver, ParentType, ContextType, RequireFields>; + activity?: Resolver>; + apiKey?: Resolver>; apiKeys?: Resolver, ParentType, ContextType>; article?: Resolver>; - articles?: Resolver< - Array, - ParentType, - ContextType, - Partial - >; + articles?: Resolver, ParentType, ContextType, Partial>; doors?: Resolver, ParentType, ContextType>; - election?: Resolver< - ResolversTypes['Election'], - ParentType, - ContextType, - RequireFields - >; - elections?: Resolver< - Array, - ParentType, - ContextType, - RequireFields - >; + election?: Resolver>; + elections?: Resolver, ParentType, ContextType, RequireFields>; features?: Resolver, ParentType, ContextType>; - file?: Resolver< - ResolversTypes['File'], - ParentType, - ContextType, - RequireFields - >; - fileSystem?: Resolver< - ResolversTypes['FileSystemResponse'], - ParentType, - ContextType, - RequireFields - >; + file?: Resolver>; + fileSystem?: Resolver>; files?: Resolver, ParentType, ContextType, Partial>; - groupedPosts?: Resolver< - Array, - ParentType, - ContextType, - Partial - >; - hehe?: Resolver< - ResolversTypes['Hehe'], - ParentType, - ContextType, - RequireFields - >; - hehes?: Resolver< - Array, - ParentType, - ContextType, - RequireFields - >; - hiddenNominations?: Resolver< - Array, - ParentType, - ContextType, - RequireFields - >; - individualAccess?: Resolver< - ResolversTypes['Access'], - ParentType, - ContextType, - RequireFields - >; - latestBoardMeetings?: Resolver< - Array, - ParentType, - ContextType, - Partial - >; - latestElections?: Resolver< - Array, - ParentType, - ContextType, - Partial - >; - latestHehe?: Resolver< - Array, - ParentType, - ContextType, - Partial - >; - latestnews?: Resolver< - Array, - ParentType, - ContextType, - Partial - >; + groupedPosts?: Resolver, ParentType, ContextType, Partial>; + hehe?: Resolver>; + hehes?: Resolver, ParentType, ContextType, RequireFields>; + hiddenNominations?: Resolver, ParentType, ContextType, RequireFields>; + individualAccess?: Resolver>; + latestBoardMeetings?: Resolver, ParentType, ContextType, Partial>; + latestElections?: Resolver, ParentType, ContextType, Partial>; + latestHehe?: Resolver, ParentType, ContextType, Partial>; + latestnews?: Resolver, ParentType, ContextType, Partial>; me?: Resolver; - meeting?: Resolver< - ResolversTypes['Meeting'], - ParentType, - ContextType, - RequireFields - >; - meetings?: Resolver< - Array, - ParentType, - ContextType, - Partial - >; - myNominations?: Resolver< - Array, - ParentType, - ContextType, - RequireFields - >; - newsentries?: Resolver< - Array, - ParentType, - ContextType, - Partial - >; - numberOfMembers?: Resolver< - ResolversTypes['Int'], - ParentType, - ContextType, - Partial - >; - numberOfNominations?: Resolver< - ResolversTypes['Int'], - ParentType, - ContextType, - RequireFields - >; - numberOfProposals?: Resolver< - ResolversTypes['Int'], - ParentType, - ContextType, - RequireFields - >; - numberOfVolunteers?: Resolver< - ResolversTypes['Int'], - ParentType, - ContextType, - Partial - >; + meeting?: Resolver>; + meetings?: Resolver, ParentType, ContextType, Partial>; + myNominations?: Resolver, ParentType, ContextType, RequireFields>; + newsentries?: Resolver, ParentType, ContextType, Partial>; + numberOfMembers?: Resolver>; + numberOfNominations?: Resolver>; + numberOfProposals?: Resolver>; + numberOfVolunteers?: Resolver>; openElection?: Resolver; - paginatedHehes?: Resolver< - ResolversTypes['PaginatedHehes'], - ParentType, - ContextType, - Partial - >; - post?: Resolver< - ResolversTypes['Post'], - ParentType, - ContextType, - RequireFields - >; - postAccess?: Resolver< - ResolversTypes['Access'], - ParentType, - ContextType, - RequireFields - >; + paginatedHehes?: Resolver>; + post?: Resolver>; + postAccess?: Resolver>; posts?: Resolver, ParentType, ContextType, Partial>; - searchFiles?: Resolver< - Array, - ParentType, - ContextType, - RequireFields - >; - searchUser?: Resolver< - Array, - ParentType, - ContextType, - RequireFields - >; - user?: Resolver< - ResolversTypes['User'], - ParentType, - ContextType, - RequireFields - >; - userByCard?: Resolver< - ResolversTypes['User'], - ParentType, - ContextType, - RequireFields - >; - users?: Resolver< - Array, - ParentType, - ContextType, - RequireFields - >; + searchFiles?: Resolver, ParentType, ContextType, RequireFields>; + searchUser?: Resolver, ParentType, ContextType, RequireFields>; + ticket?: Resolver>; + tickets?: Resolver>, ParentType, ContextType, Partial>; + user?: Resolver>; + userByCard?: Resolver>; + users?: Resolver, ParentType, ContextType, RequireFields>; usersWithIndividualAccess?: Resolver, ParentType, ContextType>; - utskott?: Resolver< - ResolversTypes['Utskott'], - ParentType, - ContextType, - RequireFields - >; + utskott?: Resolver>; }>; -export type TokenResponseResolvers< - ContextType = Context, - ParentType extends ResolversParentTypes['TokenResponse'] = ResolversParentTypes['TokenResponse'], -> = ResolversObject<{ +export type TicketResolvers = ResolversObject<{ + activityID?: Resolver, ParentType, ContextType>; + count?: Resolver, ParentType, ContextType>; + currency?: Resolver, ParentType, ContextType>; + id?: Resolver; + name?: Resolver; + price?: Resolver, ParentType, ContextType>; + __isTypeOf?: IsTypeOfResolverFn; +}>; + +export type TokenResponseResolvers = ResolversObject<{ accessToken?: Resolver; refreshToken?: Resolver; __isTypeOf?: IsTypeOfResolverFn; }>; -export type UserResolvers< - ContextType = Context, - ParentType extends ResolversParentTypes['User'] = ResolversParentTypes['User'], -> = ResolversObject<{ +export type UserResolvers = ResolversObject<{ access?: Resolver; address?: Resolver, ParentType, ContextType>; class?: Resolver; @@ -2226,12 +1980,7 @@ export type UserResolvers< luCard?: Resolver, ParentType, ContextType>; phone?: Resolver, ParentType, ContextType>; photoUrl?: Resolver, ParentType, ContextType>; - postHistory?: Resolver< - Array, - ParentType, - ContextType, - Partial - >; + postHistory?: Resolver, ParentType, ContextType, Partial>; posts?: Resolver, ParentType, ContextType>; username?: Resolver; website?: Resolver, ParentType, ContextType>; @@ -2240,10 +1989,7 @@ export type UserResolvers< __isTypeOf?: IsTypeOfResolverFn; }>; -export type UserPostHistoryEntryResolvers< - ContextType = Context, - ParentType extends ResolversParentTypes['UserPostHistoryEntry'] = ResolversParentTypes['UserPostHistoryEntry'], -> = ResolversObject<{ +export type UserPostHistoryEntryResolvers = ResolversObject<{ end?: Resolver, ParentType, ContextType>; post?: Resolver; start?: Resolver; @@ -2252,6 +1998,7 @@ export type UserPostHistoryEntryResolvers< export type Resolvers = ResolversObject<{ Access?: AccessResolvers; + Activity?: ActivityResolvers; ApiKey?: ApiKeyResolvers; Article?: ArticleResolvers; CasLoginResponse?: CasLoginResponseResolvers; @@ -2267,6 +2014,7 @@ export type Resolvers = ResolversObject<{ GroupedPost?: GroupedPostResolvers; Hehe?: HeheResolvers; HistoryEntry?: HistoryEntryResolvers; + Location?: LocationResolvers; LoginProvider?: LoginProviderResolvers; LoginResponse?: LoginResponseResolvers; Meeting?: MeetingResolvers; @@ -2279,7 +2027,9 @@ export type Resolvers = ResolversObject<{ Post?: PostResolvers; Proposal?: ProposalResolvers; Query?: QueryResolvers; + Ticket?: TicketResolvers; TokenResponse?: TokenResponseResolvers; User?: UserResolvers; UserPostHistoryEntry?: UserPostHistoryEntryResolvers; }>; + diff --git a/src/reducers/access.reducer.ts b/src/reducers/access.reducer.ts index a98c3cec..56f208ee 100644 --- a/src/reducers/access.reducer.ts +++ b/src/reducers/access.reducer.ts @@ -60,6 +60,7 @@ const featureDescriptions: Record = { [Feature.FilesAdmin]: 'För att kunna administrera filer', [Feature.EmailAdmin]: 'För att kunna skicka mejl', [Feature.AhsAdmin]: 'För att kunna administrera AHS', + [Feature.ActivityAdmin]: 'För att kunna administrera aktiviteter', [Feature.DecibelAdmin]: 'För att kunna gå in på decibel', }; diff --git a/src/reducers/activity.reducer.ts b/src/reducers/activity.reducer.ts new file mode 100644 index 00000000..522d4843 --- /dev/null +++ b/src/reducers/activity.reducer.ts @@ -0,0 +1,20 @@ +import { Activity, ActivitySource, Utskott } from '@generated/graphql'; +import { PrismaActivity } from '@prisma/client'; + +export const activityReducer = (dbActivity: PrismaActivity): Activity => { + let location = undefined; + if (dbActivity.locationTitle) { + location = { title: dbActivity.locationTitle, link: dbActivity.locationLink }; + } + return { + id: dbActivity.id, + source: dbActivity.source as ActivitySource, + title: dbActivity.title, + description: dbActivity.description, + startDate: dbActivity.startDate, + endDate: dbActivity.endDate, + utskott: dbActivity.utskott as Utskott, + imageUrl: dbActivity.imageUrl, + location: location, + }; +}; diff --git a/src/reducers/ticket.reducer.ts b/src/reducers/ticket.reducer.ts new file mode 100644 index 00000000..73f86f07 --- /dev/null +++ b/src/reducers/ticket.reducer.ts @@ -0,0 +1,6 @@ +import { Ticket } from '@generated/graphql'; +import { PrismaTicket } from '@prisma/client'; + +export const ticketReducer = (dbTicket: PrismaTicket): Ticket => { + return dbTicket as Ticket; +}; diff --git a/src/resolvers/activity.resolver.ts b/src/resolvers/activity.resolver.ts new file mode 100644 index 00000000..d81a2c2c --- /dev/null +++ b/src/resolvers/activity.resolver.ts @@ -0,0 +1,43 @@ +import { reduce } from '@/reducers'; +import { hasAccess, hasAuthenticated } from '@/util'; +import { ActivityAPI } from '@api/activity'; +import { Feature, Resolvers } from '@generated/graphql'; +import { activityReducer } from '@reducer/activity'; + +const activityApi = new ActivityAPI(); + +const activityresolver: Resolvers = { + Query: { + activity: async (_, { id }, ctx) => { + await hasAuthenticated(ctx); + const activity = await activityApi.getActivity(id); + + return activityReducer(activity); + }, + activities: async (_, { from, to, utskott }, ctx) => { + await hasAuthenticated(ctx); + const activities = await activityApi.getActivities(from, to, utskott); + + return reduce(activities, activityReducer); + }, + }, + Mutation: { + addActivity: async (_, { activity }, ctx) => { + await hasAccess(ctx, Feature.ActivityAdmin); + const addedActivity = await activityApi.addActivity(activity); + return activityReducer(addedActivity); + }, + modifyActivity: async (_, { id, entry }, ctx) => { + await hasAccess(ctx, Feature.ActivityAdmin); + const modifiedActivity = await activityApi.modifyActivity(id, entry); + return activityReducer(modifiedActivity); + }, + removeActivity: async (_, { id }, ctx) => { + await hasAccess(ctx, Feature.ActivityAdmin); + const removedActivity = await activityApi.removeActivity(id); + return activityReducer(removedActivity); + }, + }, +}; + +export default activityresolver; diff --git a/src/resolvers/index.ts b/src/resolvers/index.ts index 6540e2c7..97c2ad19 100644 --- a/src/resolvers/index.ts +++ b/src/resolvers/index.ts @@ -1,4 +1,5 @@ export { default as access } from './access.resolver'; +export { default as activity } from './activity.resolver'; export { default as article } from './article.resolver'; export { default as auth } from './auth.resolver'; export { default as election } from './election.resolver'; @@ -8,4 +9,5 @@ export { default as file } from './file.resolver'; export { default as hehe } from './hehe.resolver'; export { default as meeting } from './meeting.resolver'; export { default as post } from './post.resolver'; +export { default as ticket } from './ticket.resolver'; export { default as user } from './user.resolver'; diff --git a/src/resolvers/ticket.resolver.ts b/src/resolvers/ticket.resolver.ts new file mode 100644 index 00000000..59fe07cd --- /dev/null +++ b/src/resolvers/ticket.resolver.ts @@ -0,0 +1,45 @@ +import { reduce } from '@/reducers'; +import { hasAccess, hasAuthenticated } from '@/util'; +import { TicketAPI } from '@api/ticket'; +import { Feature, Resolvers } from '@generated/graphql'; +import { ticketReducer } from '@reducer/ticket'; + +const tikcketApi = new TicketAPI(); + +const ticketResolver: Resolvers = { + Query: { + ticket: async (_, { id }, ctx) => { + await hasAuthenticated(ctx); + const ticket = await tikcketApi.getTicket(id); + + return ticketReducer(ticket); + }, + tickets: async (_, { activityID }, ctx) => { + await hasAuthenticated(ctx); + const tickets = await tikcketApi.getTickets(activityID); + + return reduce(tickets, ticketReducer); + }, + }, + Mutation: { + addTicket: async (_, { ticket }, ctx) => { + await hasAccess(ctx, Feature.ActivityAdmin); + const addedTicket = await tikcketApi.addTicket(ticket); + return ticketReducer(addedTicket); + }, + + modifyTicket: async (_, { id, entry }, ctx) => { + await hasAccess(ctx, Feature.ActivityAdmin); + const modifiedTicket = await tikcketApi.modifyTicket(id, entry); + return ticketReducer(modifiedTicket); + }, + + removeTicket: async (_, { id }, ctx) => { + await hasAccess(ctx, Feature.ActivityAdmin); + const removedTicket = await tikcketApi.removeTicket(id); + return ticketReducer(removedTicket); + }, + }, +}; + +export default ticketResolver; diff --git a/src/schemas/access.graphql b/src/schemas/access.graphql index ee84886b..14900ed8 100644 --- a/src/schemas/access.graphql +++ b/src/schemas/access.graphql @@ -79,5 +79,6 @@ enum Feature { user_admin, email_admin, ahs_admin, + activity_admin decibel_admin, } diff --git a/src/schemas/activity.graphql b/src/schemas/activity.graphql new file mode 100644 index 00000000..cde03df4 --- /dev/null +++ b/src/schemas/activity.graphql @@ -0,0 +1,62 @@ +# import Utskott from 'utskott.graphql' + +scalar DateTime + +type Query{ + activity(id: String!) : Activity! + activities(from: DateTime!, to: DateTime!, utskott: [Utskott!]!) : [Activity!]! +} + +type Mutation{ + addActivity(activity: NewActivity!) : Activity! + modifyActivity(id: String!, entry: ModifiedActivity!) : Activity! + removeActivity(id: String!) : Activity! +} + +type Activity{ + id: String! + source: ActivitySource! + title: String! + description: String + startDate: DateTime! + endDate: DateTime + utskott: Utskott! + imageUrl: String + location: Location +} + +type Location{ + title: String! + link: String +} + +input NewLocation{ + title: String! + link: String +} + +input NewActivity{ + title: String! + description: String + startDate: DateTime! + endDate: DateTime + utskott: Utskott! + imageUrl: String + location: NewLocation +} + +input ModifiedActivity{ + title: String + description: String + startDate: DateTime + endDate: DateTime + utskott: Utskott + imageUrl: String + location: NewLocation +} + +enum ActivitySource{ + WEBSITE + ORBI + OTHER +} \ No newline at end of file diff --git a/src/schemas/ticket.graphql b/src/schemas/ticket.graphql new file mode 100644 index 00000000..3712966b --- /dev/null +++ b/src/schemas/ticket.graphql @@ -0,0 +1,37 @@ + +type Query{ + ticket(id: String!) : Ticket! + tickets(activityID: String) : [Ticket]! +} + + +type Mutation{ + addTicket(ticket: NewTicket!) : Ticket! + modifyTicket(id: String!, entry: ModifiedTicket!) : Ticket! + removeTicket(id: String!) : Ticket! +} + +type Ticket{ + id: String! + name: String! + count: Int + price: Int + currency: String + activityID: String +} + +input ModifiedTicket{ + name: String + count: Int + price: Int + currency: String + activityID: String +} + +input NewTicket{ + name: String! + count: Int + price: Int + currency: String + activityID: String +} \ No newline at end of file diff --git a/test/unit/activity.reducer.test.ts b/test/unit/activity.reducer.test.ts new file mode 100644 index 00000000..42000e0a --- /dev/null +++ b/test/unit/activity.reducer.test.ts @@ -0,0 +1,62 @@ +import { Activity, ActivitySource, Utskott } from '@generated/graphql'; +import { PrismaActivity, PrismaActivitySource, PrismaUtskott } from '@prisma/client'; +import { activityReducer } from '@reducer/activity'; + +test('reducing valid DatabaseActivity', () => { + const activity: PrismaActivity = { + id: 'testID', + source: PrismaActivitySource.WEBSITE, + title: 'TestTitle', + description: 'TestDescription', + startDate: new Date('2024-02-03'), + endDate: null, + utskott: PrismaUtskott.E6, + imageUrl: null, + locationTitle: 'LocationTestTitle', + locationLink: 'LocationLinkTest', + }; + + const expected: Activity = { + id: 'testID', + source: ActivitySource.Website, + title: 'TestTitle', + description: 'TestDescription', + startDate: new Date('2024-02-03'), + endDate: null, + utskott: Utskott.E6, + imageUrl: null, + location: { + title: 'LocationTestTitle', + link: 'LocationLinkTest', + }, + }; + + const impossibleActivity: PrismaActivity = { + ...activity, + locationTitle: null, + locationLink: 'Link with no title not possible through graph, should reduce to remove this', + }; + + const impossbileExpected: Activity = { + ...expected, + location: undefined, //because link should not exist if there is no title. + }; + + const linkNullActivity: PrismaActivity = { + ...activity, + locationTitle: 'title with no link is possible through graph, should reduce to make link null', + locationLink: null, + }; + + const linkNullExpected: Activity = { + ...expected, + location: { + title: 'title with no link is possible through graph, should reduce to make link null', + link: null, + }, + }; + + expect(activityReducer(activity)).toMatchObject(expected); + expect(activityReducer(impossibleActivity)).toMatchObject(impossbileExpected); + expect(activityReducer(linkNullActivity)).toMatchObject(linkNullExpected); +}); diff --git a/test/unit/activityandticket.api.test.ts b/test/unit/activityandticket.api.test.ts new file mode 100644 index 00000000..40a05339 --- /dev/null +++ b/test/unit/activityandticket.api.test.ts @@ -0,0 +1,293 @@ +import prisma from '@/api/prisma'; +import { BadRequestError, NotFoundError } from '@/errors/request.errors'; +import { ActivityAPI } from '@api/activity'; +import { TicketAPI } from '@api/ticket'; +import { + ModifiedActivity, + ModifiedTicket, + NewActivity, + NewTicket, + Utskott, +} from '@generated/graphql'; +import { PrismaActivity, PrismaActivitySource, PrismaUtskott } from '@prisma/client'; + +const ticketApi = new TicketAPI(); + +const EXISTING_ACT: PrismaActivity = { + id: 'ExistingID', + source: PrismaActivitySource.WEBSITE, + title: 'ExistingTitle', + description: 'This is an existing activity.', + startDate: new Date('2024-01-01'), + endDate: new Date('2024-01-02'), + utskott: PrismaUtskott.INFU, + imageUrl: 'Existing imageUrl', + locationTitle: 'Existing location', + locationLink: 'Existing link', +}; + +const DUMMY_NEWTICKET: NewTicket = { + count: 50, + currency: 'SEK', + name: 'TicketName', + price: 69, + activityID: null, +}; + +const activityApi = new ActivityAPI(); + +let ctr = 1; + +const DUMMY_PRISMAACTIVITY: PrismaActivity = { + id: 'dummy', + source: PrismaActivitySource.WEBSITE, + title: 'Title', + description: 'TestDescription', + startDate: new Date('2024-02-03'), + endDate: null, + utskott: PrismaUtskott.E6, + imageUrl: null, + locationTitle: 'LocationTestTitle', + locationLink: 'LocationLinkTest', +}; + +const DUMMY_NEWACTIVITY: NewActivity = { + title: 'TestTitle', + description: 'TestDescription', + startDate: new Date('2024-02-03'), + endDate: null, + utskott: Utskott.E6, + imageUrl: null, + location: { + title: 'LocationTestTitle', + link: 'LocationLinkTest', + }, +}; + +const DUMMY_MODACTIVITY: ModifiedActivity = { + title: 'TestTitle', + description: 'TestDescription', + startDate: new Date('2024-02-03'), + endDate: null, + utskott: Utskott.E6, + imageUrl: null, + location: { + title: 'LocationTestTitle', + link: 'LocationLinkTest', + }, +}; + +const generatePrismaActivity = (overrides: Partial): PrismaActivity => { + ctr += 1; + + const a: PrismaActivity = { + ...DUMMY_PRISMAACTIVITY, + ...overrides, + id: ctr.toString(), + }; + return a; +}; + +const generateNewActivity = (overrides: Partial): NewActivity => { + const a: NewActivity = { + ...DUMMY_NEWACTIVITY, + ...overrides, + }; + return a; +}; + +const generateModActivity = (overrides: Partial): ModifiedActivity => { + const a: ModifiedActivity = { + ...DUMMY_MODACTIVITY, + ...overrides, + }; + return a; +}; + +beforeEach(async () => { + await ticketApi.clear(); + await activityApi.clear(); +}); + +afterAll(async () => { + await ticketApi.clear(); + await activityApi.clear(); +}); + +test('Getting activities', async () => { + const orbiAct = await prisma.prismaActivity.create({ + data: { + ...generatePrismaActivity({ source: PrismaActivitySource.ORBI }), //in date range + }, + }); + expect(orbiAct).toBeTruthy(); + + await activityApi.addActivity(generateNewActivity({})); //in range + await activityApi.addActivity(generateNewActivity({ startDate: new Date('2024-02-01') })); //in range + await activityApi.addActivity( + generateNewActivity({ startDate: new Date('2024-01-10'), endDate: new Date('2024-02-10') }), //endDate in range + ); + await activityApi.addActivity(generateNewActivity({ startDate: new Date('2024-05-01') })); //not in range + await activityApi.addActivity( + generateNewActivity({ startDate: new Date('2024-01-01'), endDate: new Date('2024-03-10') }), //in range + ); + await activityApi.addActivity( + generateNewActivity({ startDate: new Date('2024-02-10'), endDate: new Date('2024-03-10') }), //startDate in range + ); + + const acts = await activityApi.getActivities( + new Date('2024-01-30'), + new Date('2024-02-28'), + Object.entries(Utskott).map((u) => u[1]), + ); + + expect(acts.length).toBe(6); + + const success = await activityApi.getActivity(orbiAct.id); + expect(success).toBeTruthy(); +}); + +test('Adding and removing activities', async () => { + const orbiAct = await prisma.prismaActivity.create({ + data: { + ...generatePrismaActivity({ source: PrismaActivitySource.ORBI }), + }, + }); + expect(orbiAct).toBeTruthy(); + + const newAct = generateNewActivity({}); + const added = await activityApi.addActivity(newAct); + expect(added).toBeTruthy(); + + const removed = await activityApi.removeActivity(added.id); + expect(removed).toBeTruthy(); + + await expect(activityApi.removeActivity(orbiAct.id)).rejects.toThrowError(BadRequestError); //Not allowed to remove non website activity + + await expect(activityApi.removeActivity('Not an existing activity ID')).rejects.toThrowError( + NotFoundError, + ); + + const badAct = generateNewActivity({ endDate: new Date('2024-01-03') }); //endDate before startDate + await expect(activityApi.addActivity(badAct)).rejects.toThrowError(BadRequestError); +}); + +test('Modifying activities', async () => { + const orbiAct = await prisma.prismaActivity.create({ + data: { + ...generatePrismaActivity({ source: PrismaActivitySource.ORBI }), + }, + }); + expect(orbiAct).toBeTruthy(); + + const newAct = generateNewActivity({}); + const added = await activityApi.addActivity(newAct); + expect(added).toBeTruthy(); + + //Just a new startDate is expected to work! + const modAct = generateModActivity({ startDate: new Date('2025-01-01') }); + const modified = await activityApi.modifyActivity(added.id, modAct); + expect(modified).toBeTruthy(); + + //New endDate is before old startDate but the new starDate is before the new endDate, expected to work! + const modAct2 = generateModActivity({ + startDate: new Date('1962-01-01'), + endDate: new Date('1962-05-01'), + }); + const modified2 = await activityApi.modifyActivity(added.id, modAct2); + expect(modified2).toBeTruthy(); + + //New endDate before old startDate, expected not to work! + const badModAct = generateModActivity({ endDate: new Date('1900-01-01') }); + await expect(activityApi.modifyActivity(added.id, badModAct)).rejects.toThrowError( + BadRequestError, + ); + + //New endDate before new startDate, expected not to work! + const badModAct2 = generateModActivity({ + endDate: new Date('2023-01-01'), + startDate: new Date('2024-01-01'), + }); + await expect(activityApi.modifyActivity(added.id, badModAct2)).rejects.toThrowError( + BadRequestError, + ); + + //Not allowed to modify non website activity, expected not to work! + await expect(activityApi.modifyActivity(orbiAct.id, modAct)).rejects.toThrowError( + BadRequestError, + ); +}); + +test('Adding and removing tickets', async () => { + await prisma.prismaActivity.create({ + data: { + ...EXISTING_ACT, + }, + }); + const notActTicket = DUMMY_NEWTICKET; + const actTicket = { ...DUMMY_NEWTICKET, activityID: EXISTING_ACT.id }; + const falseActTicket = { ...DUMMY_NEWTICKET, activityID: 'Not an ' + EXISTING_ACT.id }; + + const notActSuccess = await ticketApi.addTicket(notActTicket); + expect(notActSuccess).toBeTruthy(); + + const actSuccess = await ticketApi.addTicket(actTicket); + expect(actSuccess).toBeTruthy(); + + //Throws if activityID does not belong to an existing activity + await expect(ticketApi.addTicket(falseActTicket)).rejects.toThrowError(); + + expect(await ticketApi.removeTicket(notActSuccess.id)).toBeTruthy(); + + expect(await ticketApi.removeTicket(actSuccess.id)).toBeTruthy(); +}); + +test('Modifying tickets', async () => { + await prisma.prismaActivity.create({ + data: { + ...EXISTING_ACT, + }, + }); + const notActTicket = DUMMY_NEWTICKET; + const actTicket = { ...DUMMY_NEWTICKET, activityID: EXISTING_ACT.id }; + + const notActMod: ModifiedTicket = { name: 'New Name!' }; + const actMod: ModifiedTicket = { name: 'New Name!', activityID: EXISTING_ACT.id }; + const falseActMod: ModifiedTicket = { + name: 'New Name!', + activityID: 'Not an ' + EXISTING_ACT.id, + }; + + //Adding to tickets, one belonging to an existing act, one not. + const notActSuccess = await ticketApi.addTicket(notActTicket); + const actSuccess = await ticketApi.addTicket(actTicket); + + //Modifying ticket not belonging to an act, modifying it with still no act + const notActModSuccess = await ticketApi.modifyTicket(notActSuccess.id, notActMod); + expect(notActModSuccess).toBeTruthy(); + + //Modifying ticket belonging to an act, modifying it with same act + const actModSuccess = await ticketApi.modifyTicket(actSuccess.id, actMod); + expect(actModSuccess).toBeTruthy(); + + //#Tickets belonging to existing act should be 1 + expect((await ticketApi.getTickets(EXISTING_ACT.id)).length).toBe(1); + + //Modifying ticket not belonging to an act, modifying it to belong to existing act + const newActModSuccess = await ticketApi.modifyTicket(notActSuccess.id, actMod); + expect(newActModSuccess).toBeTruthy(); + + //#Tickets belonging to existing act should be 2 + expect((await ticketApi.getTickets(EXISTING_ACT.id)).length).toBe(2); + + //#Tickets total should be 2 + expect((await ticketApi.getTickets(null)).length).toBe(2); + + //Modifying a ticket to belong to a non existing act + await expect(ticketApi.modifyTicket(actSuccess.id, falseActMod)).rejects.toThrowError(); + + //Modifying non existing ticket + await expect( + ticketApi.modifyTicket('not an existing ticket ID', notActMod), + ).rejects.toThrowError(); +});