From 7bcebddc63e4d9e0fb0f58516c374f4295131cc0 Mon Sep 17 00:00:00 2001 From: Muncherkin <48158637+Muncherkin@users.noreply.github.com> Date: Tue, 27 Feb 2024 13:55:52 +0100 Subject: [PATCH 01/16] cross-platform package.json script fix --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 6b00519f..5504fa42 100644 --- a/package.json +++ b/package.json @@ -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", From 1a5a2c042e3ab8a6986654459bf7f084f37d0ec3 Mon Sep 17 00:00:00 2001 From: Muncherkin <48158637+Muncherkin@users.noreply.github.com> Date: Sat, 9 Mar 2024 21:00:42 +0100 Subject: [PATCH 02/16] activity and ticket features added --- prisma/schema.prisma | 32 +++++ src/api/activity.api.ts | 98 +++++++++++++ src/api/ticket.api.ts | 96 +++++++++++++ src/models/generated/graphql.ts | 212 +++++++++++++++++++++++++++++ src/reducers/activity.reducer.ts | 20 +++ src/reducers/ticket.reducer.ts | 6 + src/resolvers/activity.resolver.ts | 44 ++++++ src/resolvers/index.ts | 2 + src/resolvers/ticket.resolver.ts | 46 +++++++ src/schemas/activity.graphql | 63 +++++++++ src/schemas/ticket.graphql | 37 +++++ 11 files changed, 656 insertions(+) create mode 100644 src/api/activity.api.ts create mode 100644 src/api/ticket.api.ts create mode 100644 src/reducers/activity.reducer.ts create mode 100644 src/reducers/ticket.reducer.ts create mode 100644 src/resolvers/activity.resolver.ts create mode 100644 src/resolvers/ticket.resolver.ts create mode 100644 src/schemas/activity.graphql create mode 100644 src/schemas/ticket.graphql diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 7a86c76c..29fdecfd 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -178,6 +178,30 @@ 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[] +} + +model PrismaTicket { + id String @id @default(cuid()) + name String + count Int? + price Int? + currency String? + activity PrismaActivity? @relation(fields: [activityID], references: [id]) + activityID String? +} + model PrismaProposal { user PrismaUser @relation(name: "PrismaProposalToPrismaUser", fields: [refUser], references: [username]) refUser String @map("ref_user") @@ -346,6 +370,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..c2603291 --- /dev/null +++ b/src/api/activity.api.ts @@ -0,0 +1,98 @@ +import { NotFoundError, ServerError } from '@/errors/request.errors'; +import { Logger } from '@/logger'; +import { StrictObject } from '@/models/base'; +import { stripObject } from '@/util'; +import { ModifiedActivity, NewActivity, Utskott } from '@generated/graphql'; +import { Prisma, PrismaActivity, PrismaActivitySource } from '@prisma/client'; + +import prisma from './prisma'; + +const logger = Logger.getLogger('ActivityAPI'); + +export class ActivityAPI { + async getActivity(id: string): Promise { + const a = await prisma.prismaActivity.findFirst({ + where: { + id: id, + }, + }); + + if (a == null) { + throw new NotFoundError('Kunde inte hitta denna aktivitet!'); + } + + return a; + } + async getActivities( + from: Date, + to: Date, + utskott: Utskott[] | null | undefined, + ): Promise { + const whereAnd: Prisma.PrismaActivityWhereInput[] = []; + + if (utskott != null) { + whereAnd.push({ utskott: { in: utskott } }); + } + + const a = await prisma.prismaActivity.findMany({ + where: { + startDate: { + lte: to, + }, + OR: [ + { + endDate: { + gte: from, + }, + }, + { AND: [{ startDate: { gte: from } }, { endDate: null }] }, + ], + AND: whereAnd, + }, + }); + + return a; + } + + async addActivity(activity: NewActivity): Promise { + const res = await prisma.prismaActivity.create({ + data: { + source: PrismaActivitySource.WEBSITE, + title: activity.title, + description: activity.description, + startDate: activity.startDate, + endDate: activity.endDate, + utskott: activity.utskott as Utskott, + imageUrl: activity.imageUrl, + locationTitle: activity.location?.title, + locationLink: activity.location?.link, + }, + }); + + return res; + } + async modifyActivity(id: string, mod: ModifiedActivity): Promise { + const { ...rest } = mod; + + const update: StrictObject = stripObject(rest); + + const res = prisma.prismaActivity.update({ + data: { ...update }, + where: { id: id }, + }); + + return res; + } + + async removeActivity(id: string): Promise { + try { + await prisma.prismaActivity.delete({ where: { id: id } }); + return true; + } catch { + logger.debug(`Could not delete activity with ID ${id}`); + throw new ServerError( + 'Kunde inte radera aktiviteten, vilket kan bero på att den inte finns.', + ); + } + } +} diff --git a/src/api/ticket.api.ts b/src/api/ticket.api.ts new file mode 100644 index 00000000..a84cb7bc --- /dev/null +++ b/src/api/ticket.api.ts @@ -0,0 +1,96 @@ +import { NotFoundError, ServerError } from '@/errors/request.errors'; +import { Logger } from '@/logger'; +import { StrictObject } from '@/models/base'; +import { stripObject } from '@/util'; +import { ModifiedTicket, NewTicket } from '@generated/graphql'; +import { Prisma, PrismaTicket } from '@prisma/client'; + +import { ActivityAPI } from './activity.api'; +import prisma from './prisma'; + +const logger = Logger.getLogger('TicketAPI'); + +const activityApi = new ActivityAPI(); + +export class TicketAPI { + async getTicket(id: string): Promise { + const t = await prisma.prismaTicket.findFirst({ where: { id: id } }); + + if (t == null) { + throw new NotFoundError('Kunde inte hitta den biljetten!'); + } + + return t; + } + + async getTickets(activityID: string | null | undefined): Promise { + const whereAnd: Prisma.PrismaTicketWhereInput[] = []; + if (activityID != null) { + whereAnd.push({ activityID: activityID }); + } + const t = await prisma.prismaTicket.findMany({ + where: { + AND: whereAnd, + }, + }); + + return t; + } + + async addTicket(ticket: NewTicket): Promise { + if (ticket.activityID != null) { + try { + await activityApi.getActivity(ticket.activityID); + } catch { + throw new NotFoundError( + 'Biljett kunde inte skapas! Specifierad activityID existerade inte på någon activity.', + ); + } + } + + const t = await prisma.prismaTicket.create({ + data: { + name: ticket.name, + count: ticket.count, + price: ticket.price, + currency: ticket.currency, + activityID: ticket.activityID, + }, + }); + + return t; + } + + async modifyTicket(id: string, mod: ModifiedTicket): Promise { + if (mod.activityID != null) { + try { + await activityApi.getActivity(mod.activityID); + } catch { + throw new NotFoundError( + 'Biljett kunde inte modifieras! Modifierad activityID existerade inte på någon activity.', + ); + } + } + + const { ...rest } = mod; + + const update: StrictObject = stripObject(rest); + + const t = prisma.prismaTicket.update({ + data: { ...update }, + where: { id: id }, + }); + + return t; + } + + async removeTicket(id: string): Promise { + try { + await prisma.prismaTicket.delete({ where: { id: id } }); + return true; + } catch { + logger.debug(`Could not delete ticket with ID ${id}`); + throw new ServerError('Kunde inte radera biljetten, vilket kan bero på att den inte finns.'); + } + } +} diff --git a/src/models/generated/graphql.ts b/src/models/generated/graphql.ts index 51279a35..bb6399fb 100644 --- a/src/models/generated/graphql.ts +++ b/src/models/generated/graphql.ts @@ -41,6 +41,24 @@ export enum AccessType { Public = 'PUBLIC' } +export type Activity = { + description?: Maybe; + endDate?: Maybe; + id: Scalars['String']; + imageUrl?: Maybe; + location?: Maybe; + source: ActivitySource; + startDate: Scalars['Date']; + title: Scalars['String']; + utskott?: Maybe; +}; + +export enum ActivitySource { + Orbi = 'ORBI', + Other = 'OTHER', + Website = 'WEBSITE' +} + export type ApiKey = { access: Access; creator: User; @@ -211,6 +229,11 @@ export type HistoryEntry = { start: Scalars['Date']; }; +export type Location = { + link?: Maybe; + title: Scalars['String']; +}; + export type LoginProvider = { email?: Maybe; id: Scalars['Int']; @@ -279,6 +302,24 @@ export enum MeetingType { 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; @@ -308,6 +349,7 @@ export type ModifyPost = { export type Mutation = { activatePost: Scalars['Boolean']; + addActivity: Activity; addArticle: Article; addElectables: Scalars['Boolean']; addEmergencyContact: EmergencyContact; @@ -315,6 +357,7 @@ export type Mutation = { addHehe: Scalars['Boolean']; addMeeting: Meeting; addPost: Post; + addTicket: Ticket; addUsersToPost: Post; casCreateUser: User; casLogin: CasLoginResponse; @@ -333,14 +376,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: Scalars['Boolean']; removeArticle: Scalars['Boolean']; removeElectables: Scalars['Boolean']; removeEmergencyContact: Scalars['Boolean']; @@ -349,6 +395,7 @@ export type Mutation = { removeHistoryEntry: Scalars['Boolean']; removeMeeting: Scalars['Boolean']; removeProposal: Scalars['Boolean']; + removeTicket: Scalars['Boolean']; requestPasswordReset: Scalars['Boolean']; resetPassword: Scalars['Boolean']; /** Only possible during open election, so electionId is known */ @@ -372,6 +419,11 @@ export type MutationActivatePostArgs = { }; +export type MutationAddActivityArgs = { + activity: NewActivity; +}; + + export type MutationAddArticleArgs = { entry: NewArticle; }; @@ -416,6 +468,11 @@ export type MutationAddPostArgs = { }; +export type MutationAddTicketArgs = { + ticket: NewTicket; +}; + + export type MutationAddUsersToPostArgs = { end?: InputMaybe; id: Scalars['Int']; @@ -499,6 +556,12 @@ export type MutationLoginArgs = { }; +export type MutationModifyActivityArgs = { + id: Scalars['String']; + mod: ModifiedActivity; +}; + + export type MutationModifyArticleArgs = { articleId: Scalars['Int']; entry: ModifyArticle; @@ -510,6 +573,12 @@ export type MutationModifyPostArgs = { }; +export type MutationModifyTicketArgs = { + id: Scalars['String']; + mod: ModifiedTicket; +}; + + export type MutationNominateArgs = { postIds: Array; username: Scalars['String']; @@ -538,6 +607,11 @@ export type MutationRefreshArgs = { }; +export type MutationRemoveActivityArgs = { + id: Scalars['String']; +}; + + export type MutationRemoveArticleArgs = { articleId: Scalars['Int']; }; @@ -583,6 +657,11 @@ export type MutationRemoveProposalArgs = { }; +export type MutationRemoveTicketArgs = { + id: Scalars['String']; +}; + + export type MutationRequestPasswordResetArgs = { resetLink: Scalars['String']; returnTo?: InputMaybe; @@ -664,6 +743,16 @@ export type MutationValidateTokenArgs = { token: Scalars['String']; }; +export type NewActivity = { + description?: InputMaybe; + endDate?: InputMaybe; + imageUrl?: InputMaybe; + location?: InputMaybe; + startDate: Scalars['Date']; + title: Scalars['String']; + utskott?: InputMaybe; +}; + export type NewArticle = { articleType: ArticleType; body: Scalars['String']; @@ -672,6 +761,11 @@ export type NewArticle = { title: Scalars['String']; }; +export type NewLocation = { + link?: InputMaybe; + title: Scalars['String']; +}; + export type NewPost = { active?: InputMaybe; description?: InputMaybe; @@ -690,6 +784,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; @@ -770,6 +872,8 @@ export type ProviderOptions = { * does not take an `electionId` parameter. */ export type Query = { + activities: Array; + activity: Activity; apiKey: ApiKey; apiKeys: Array; article: Article; @@ -807,6 +911,8 @@ export type Query = { posts: Array; searchFiles: Array; searchUser: Array; + ticket: Ticket; + tickets: Array>; user: User; userByCard: User; users: Array; @@ -815,6 +921,26 @@ export type Query = { }; +/** + * Queries and mutations that relies on an election being open + * does not take an `electionId` parameter. + */ +export type QueryActivitiesArgs = { + from: Scalars['Date']; + to: Scalars['Date']; + utskott?: InputMaybe>; +}; + + +/** + * 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. @@ -1102,6 +1228,24 @@ export type QuerySearchUserArgs = { }; +/** + * 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. @@ -1150,6 +1294,15 @@ export enum SortOrder { 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']; @@ -1290,6 +1443,8 @@ export type ResolversTypes = ResolversObject<{ AccessInput: AccessInput; AccessResourceType: AccessResourceType; AccessType: AccessType; + Activity: ResolverTypeWrapper; + ActivitySource: ActivitySource; ApiKey: ResolverTypeWrapper; Article: ResolverTypeWrapper; ArticleType: ArticleType; @@ -1312,16 +1467,22 @@ 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; @@ -1334,6 +1495,7 @@ export type ResolversTypes = ResolversObject<{ SendEmailOptions: SendEmailOptions; SortOrder: SortOrder; String: ResolverTypeWrapper; + Ticket: ResolverTypeWrapper; TokenResponse: ResolverTypeWrapper; UpdateUser: UpdateUser; User: ResolverTypeWrapper; @@ -1345,6 +1507,7 @@ export type ResolversTypes = ResolversObject<{ export type ResolversParentTypes = ResolversObject<{ Access: Access; AccessInput: AccessInput; + Activity: Activity; ApiKey: ApiKeyResponse; Article: ArticleResponse; Boolean: Scalars['Boolean']; @@ -1362,14 +1525,20 @@ export type ResolversParentTypes = ResolversObject<{ 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']; @@ -1379,6 +1548,7 @@ export type ResolversParentTypes = ResolversObject<{ Query: {}; SendEmailOptions: SendEmailOptions; String: Scalars['String']; + Ticket: Ticket; TokenResponse: TokenResponse; UpdateUser: UpdateUser; User: User; @@ -1391,6 +1561,19 @@ export type AccessResolvers; }>; +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, ParentType, ContextType>; + __isTypeOf?: IsTypeOfResolverFn; +}>; + export type ApiKeyResolvers = ResolversObject<{ access?: Resolver; creator?: Resolver; @@ -1506,6 +1689,12 @@ export type HistoryEntryResolvers; }>; +export type LocationResolvers = ResolversObject<{ + link?: Resolver, ParentType, ContextType>; + title?: Resolver; + __isTypeOf?: IsTypeOfResolverFn; +}>; + export type LoginProviderResolvers = ResolversObject<{ email?: Resolver, ParentType, ContextType>; id?: Resolver; @@ -1538,6 +1727,7 @@ export type MeetingResolvers = ResolversObject<{ activatePost?: Resolver>; + addActivity?: Resolver>; addArticle?: Resolver>; addElectables?: Resolver>; addEmergencyContact?: Resolver>; @@ -1545,6 +1735,7 @@ export type MutationResolvers>; addMeeting?: Resolver>; addPost?: Resolver>; + addTicket?: Resolver>; addUsersToPost?: Resolver>; casCreateUser?: Resolver>; casLogin?: Resolver>; @@ -1562,13 +1753,16 @@ export type MutationResolvers>; login?: Resolver>; logout?: Resolver; + 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>; @@ -1577,6 +1771,7 @@ export type MutationResolvers>; removeMeeting?: Resolver>; removeProposal?: Resolver>; + removeTicket?: Resolver>; requestPasswordReset?: Resolver>; resetPassword?: Resolver>; respondToNomination?: Resolver>; @@ -1627,6 +1822,8 @@ export type ProposalResolvers; export type QueryResolvers = ResolversObject<{ + activities?: Resolver, ParentType, ContextType, RequireFields>; + activity?: Resolver>; apiKey?: Resolver>; apiKeys?: Resolver, ParentType, ContextType>; article?: Resolver>; @@ -1662,6 +1859,8 @@ export type QueryResolvers, ParentType, ContextType, Partial>; 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>; @@ -1669,6 +1868,16 @@ export type QueryResolvers>; }>; +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; @@ -1706,6 +1915,7 @@ export type UserPostHistoryEntryResolvers = ResolversObject<{ Access?: AccessResolvers; + Activity?: ActivityResolvers; ApiKey?: ApiKeyResolvers; Article?: ArticleResolvers; CasLoginResponse?: CasLoginResponseResolvers; @@ -1720,6 +1930,7 @@ export type Resolvers = ResolversObject<{ GroupedPost?: GroupedPostResolvers; Hehe?: HeheResolvers; HistoryEntry?: HistoryEntryResolvers; + Location?: LocationResolvers; LoginProvider?: LoginProviderResolvers; LoginResponse?: LoginResponseResolvers; Meeting?: MeetingResolvers; @@ -1729,6 +1940,7 @@ export type Resolvers = ResolversObject<{ Post?: PostResolvers; Proposal?: ProposalResolvers; Query?: QueryResolvers; + Ticket?: TicketResolvers; TokenResponse?: TokenResponseResolvers; User?: UserResolvers; UserPostHistoryEntry?: UserPostHistoryEntryResolvers; 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..03c10593 --- /dev/null +++ b/src/resolvers/activity.resolver.ts @@ -0,0 +1,44 @@ +import { reduce } from '@/reducers'; +import { hasAccess, hasAuthenticated } from '@/util'; +import { ActivityAPI } from '@api/activity'; +import { Feature } from '@esek/auth-server'; +import { 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 a = await activityApi.getActivity(id); + + return activityReducer(a); + }, + activities: async (_, { from, to, utskott }, ctx) => { + await hasAuthenticated(ctx); + const a = await activityApi.getActivities(from, to, utskott); + + return reduce(a, activityReducer); + }, + }, + Mutation: { + addActivity: async (_, { activity }, ctx) => { + await hasAccess(ctx, Feature.Superadmin); + const a = await activityApi.addActivity(activity); + return activityReducer(a); + }, + modifyActivity: async (_, { id, mod }, ctx) => { + await hasAccess(ctx, Feature.Superadmin); + const a = await activityApi.modifyActivity(id, mod); + return activityReducer(a); + }, + removeActivity: async (_, { id }, ctx) => { + await hasAccess(ctx, Feature.Superadmin); + const res = activityApi.removeActivity(id); + return res; + }, + }, +}; + +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..87ff210c --- /dev/null +++ b/src/resolvers/ticket.resolver.ts @@ -0,0 +1,46 @@ +import { reduce } from '@/reducers'; +import { hasAccess, hasAuthenticated } from '@/util'; +import { TicketAPI } from '@api/ticket'; +import { Feature } from '@esek/auth-server'; +import { 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 t = await tikcketApi.getTickets(activityID); + + return reduce(t, ticketReducer); + }, + }, + Mutation: { + addTicket: async (_, { ticket }, ctx) => { + await hasAccess(ctx, Feature.Superadmin); + const t = await tikcketApi.addTicket(ticket); + return ticketReducer(t); + }, + + modifyTicket: async (_, { id, mod }, ctx) => { + await hasAccess(ctx, Feature.Superadmin); + const t = await tikcketApi.modifyTicket(id, mod); + return ticketReducer(t); + }, + + removeTicket: async (_, { id }, ctx) => { + await hasAccess(ctx, Feature.Superadmin); + const res = await tikcketApi.removeTicket(id); + return res; + }, + }, +}; + +export default ticketResolver; diff --git a/src/schemas/activity.graphql b/src/schemas/activity.graphql new file mode 100644 index 00000000..801ccf13 --- /dev/null +++ b/src/schemas/activity.graphql @@ -0,0 +1,63 @@ +# import Utskott from 'utskott.graphql' +# import Date from 'user.graphql' + +scalar Date + +type Query{ + activity(id: String!) : Activity! + activities(from: Date!, to: Date!, utskott: [Utskott!]) : [Activity!]! +} + +type Mutation{ + addActivity(activity: NewActivity!) : Activity! + modifyActivity(id: String!, mod: ModifiedActivity!) : Activity! + removeActivity(id: String!) : Boolean! +} + +type Activity{ + id: String! + source: ActivitySource! + title: String! + description: String + startDate: Date! + endDate: Date + utskott: String + imageUrl: String + location: Location +} + +type Location{ + title: String! + link: String +} + +input NewLocation{ + title: String! + link: String +} + +input NewActivity{ + title: String! + description: String + startDate: Date! + endDate: Date + utskott: String + imageUrl: String + location: NewLocation +} + +input ModifiedActivity{ + title: String + description: String + startDate: Date + endDate: Date + utskott: String + 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..a6725310 --- /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!, mod: ModifiedTicket!) : Ticket! + removeTicket(id: String!) : Boolean! +} + +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 From ac33534d08d8f308dcda567591a8b966a4209d03 Mon Sep 17 00:00:00 2001 From: Muncherkin <48158637+Muncherkin@users.noreply.github.com> Date: Sun, 10 Mar 2024 17:33:08 +0100 Subject: [PATCH 03/16] made ticket and activity tests and updated apis to work accordingly --- src/api/activity.api.ts | 61 +++++++--- src/api/ticket.api.ts | 39 +++--- test/unit/activity.api.test.ts | 187 +++++++++++++++++++++++++++++ test/unit/activity.reducer.test.ts | 62 ++++++++++ test/unit/ticket.api.test.ts | 109 +++++++++++++++++ 5 files changed, 421 insertions(+), 37 deletions(-) create mode 100644 test/unit/activity.api.test.ts create mode 100644 test/unit/activity.reducer.test.ts create mode 100644 test/unit/ticket.api.test.ts diff --git a/src/api/activity.api.ts b/src/api/activity.api.ts index c2603291..c0729d87 100644 --- a/src/api/activity.api.ts +++ b/src/api/activity.api.ts @@ -1,19 +1,16 @@ -import { NotFoundError, ServerError } from '@/errors/request.errors'; -import { Logger } from '@/logger'; +import { BadRequestError, NotFoundError } from '@/errors/request.errors'; import { StrictObject } from '@/models/base'; -import { stripObject } from '@/util'; +import { devGuard, stripObject } from '@/util'; import { ModifiedActivity, NewActivity, Utskott } from '@generated/graphql'; import { Prisma, PrismaActivity, PrismaActivitySource } from '@prisma/client'; import prisma from './prisma'; -const logger = Logger.getLogger('ActivityAPI'); - export class ActivityAPI { async getActivity(id: string): Promise { const a = await prisma.prismaActivity.findFirst({ where: { - id: id, + id, }, }); @@ -55,6 +52,10 @@ export class ActivityAPI { } async addActivity(activity: NewActivity): Promise { + if (activity.endDate && activity.endDate.getTime() - activity.startDate.getTime() < 0) { + throw new BadRequestError('Sluttid för aktivitet är före starttid!'); + } + const res = await prisma.prismaActivity.create({ data: { source: PrismaActivitySource.WEBSITE, @@ -72,27 +73,51 @@ export class ActivityAPI { return res; } async modifyActivity(id: string, mod: ModifiedActivity): Promise { - const { ...rest } = mod; + const a = await this.getActivity(id); + if (mod.endDate) { + if (mod.startDate && mod.endDate.getTime() - mod.startDate.getTime() < 0) { + throw new BadRequestError('Modifierad sluttid för aktivitet är före modifierad starttid!'); + } else if (!mod.startDate && mod.endDate.getTime() - a.startDate.getTime() < 0) { + throw new BadRequestError('Modifierad sluttid för aktivitet är före starttiden!'); + } + } + if (a.source !== PrismaActivitySource.WEBSITE) { + throw new BadRequestError( + 'Ej tillåtet att ändra i evenemang som inte är skapade på hemsidan!', + ); + } - const update: StrictObject = stripObject(rest); + const { location, ...reduced } = mod; - const res = prisma.prismaActivity.update({ + const refact = { + ...reduced, + locationTitle: mod.location?.title, + locationLink: mod.location?.link, + }; + + const update: StrictObject = stripObject(refact); + + const res = await prisma.prismaActivity.update({ data: { ...update }, - where: { id: id }, + where: { id }, }); - return res; } async removeActivity(id: string): Promise { - try { - await prisma.prismaActivity.delete({ where: { id: id } }); - return true; - } catch { - logger.debug(`Could not delete activity with ID ${id}`); - throw new ServerError( - 'Kunde inte radera aktiviteten, vilket kan bero på att den inte finns.', + const a = await this.getActivity(id); + if (a.source !== PrismaActivitySource.WEBSITE) { + throw new BadRequestError( + 'Ej tillåtet att ta bort evenemang som inte är skapade på hemsidan!', ); } + await prisma.prismaActivity.delete({ where: { id } }); + return true; + } + + 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 index a84cb7bc..0336be85 100644 --- a/src/api/ticket.api.ts +++ b/src/api/ticket.api.ts @@ -1,20 +1,17 @@ -import { NotFoundError, ServerError } from '@/errors/request.errors'; -import { Logger } from '@/logger'; +import { BadRequestError, NotFoundError } from '@/errors/request.errors'; import { StrictObject } from '@/models/base'; -import { stripObject } from '@/util'; +import { devGuard, stripObject } from '@/util'; import { ModifiedTicket, NewTicket } from '@generated/graphql'; import { Prisma, PrismaTicket } from '@prisma/client'; import { ActivityAPI } from './activity.api'; import prisma from './prisma'; -const logger = Logger.getLogger('TicketAPI'); - const activityApi = new ActivityAPI(); export class TicketAPI { async getTicket(id: string): Promise { - const t = await prisma.prismaTicket.findFirst({ where: { id: id } }); + const t = await prisma.prismaTicket.findFirst({ where: { id } }); if (t == null) { throw new NotFoundError('Kunde inte hitta den biljetten!'); @@ -42,8 +39,8 @@ export class TicketAPI { try { await activityApi.getActivity(ticket.activityID); } catch { - throw new NotFoundError( - 'Biljett kunde inte skapas! Specifierad activityID existerade inte på någon activity.', + throw new BadRequestError( + 'Biljett kunde inte skapas! Specifierad activityID tillhörde inte någon activity.', ); } } @@ -62,35 +59,39 @@ export class TicketAPI { } async modifyTicket(id: string, mod: ModifiedTicket): Promise { + await this.getTicket(id); + if (mod.activityID != null) { try { await activityApi.getActivity(mod.activityID); } catch { - throw new NotFoundError( - 'Biljett kunde inte modifieras! Modifierad activityID existerade inte på någon activity.', + throw new BadRequestError( + 'Biljett kunde inte modifieras! Modifierad activityID tillhörde inte någon activity.', ); } } const { ...rest } = mod; - const update: StrictObject = stripObject(rest); const t = prisma.prismaTicket.update({ data: { ...update }, - where: { id: id }, + where: { id }, }); return t; } async removeTicket(id: string): Promise { - try { - await prisma.prismaTicket.delete({ where: { id: id } }); - return true; - } catch { - logger.debug(`Could not delete ticket with ID ${id}`); - throw new ServerError('Kunde inte radera biljetten, vilket kan bero på att den inte finns.'); - } + await this.getTicket(id); + + await prisma.prismaTicket.delete({ where: { id } }); + return true; + } + + async clear() { + devGuard('Cannot clear tickets in production'); + + await prisma.prismaTicket.deleteMany(); } } diff --git a/test/unit/activity.api.test.ts b/test/unit/activity.api.test.ts new file mode 100644 index 00000000..c7a22a5f --- /dev/null +++ b/test/unit/activity.api.test.ts @@ -0,0 +1,187 @@ +import prisma from '@/api/prisma'; +import { BadRequestError, NotFoundError } from '@/errors/request.errors'; +import { ActivityAPI } from '@api/activity'; +import { ModifiedActivity, NewActivity } from '@generated/graphql'; +import { PrismaActivity, PrismaActivitySource, PrismaUtskott } from '@prisma/client'; + +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: PrismaUtskott.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: PrismaUtskott.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 activityApi.clear(); +}); + +afterAll(async () => { + 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(generatePrismaActivity({})); //in range + await activityApi.addActivity(generatePrismaActivity({ startDate: new Date('2024-02-01') })); //in range + await activityApi.addActivity( + generatePrismaActivity({ startDate: new Date('2024-01-10'), endDate: new Date('2024-02-10') }), //endDate in range + ); + await activityApi.addActivity(generatePrismaActivity({ startDate: new Date('2024-05-01') })); //not in range + await activityApi.addActivity( + generatePrismaActivity({ startDate: new Date('2024-01-01'), endDate: new Date('2024-03-10') }), //in range + ); + await activityApi.addActivity( + generatePrismaActivity({ 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'), + null, + ); + + 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 = 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, + ); +}); diff --git a/test/unit/activity.reducer.test.ts b/test/unit/activity.reducer.test.ts new file mode 100644 index 00000000..7ee4830e --- /dev/null +++ b/test/unit/activity.reducer.test.ts @@ -0,0 +1,62 @@ +import { Activity, ActivitySource } 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: PrismaUtskott.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/ticket.api.test.ts b/test/unit/ticket.api.test.ts new file mode 100644 index 00000000..c7b3cc8d --- /dev/null +++ b/test/unit/ticket.api.test.ts @@ -0,0 +1,109 @@ +import prisma from '@/api/prisma'; +import { BadRequestError, NotFoundError } from '@/errors/request.errors'; +import { TicketAPI } from '@api/ticket'; +import { ModifiedTicket, NewTicket } 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, +}; + +beforeEach(async () => { + await ticketApi.clear(); + await prisma.prismaActivity.deleteMany(); + await prisma.prismaActivity.create({ + data: { + ...EXISTING_ACT, + }, + }); +}); + +afterAll(async () => { + await ticketApi.clear(); + await prisma.prismaActivity.deleteMany(); +}); + +test('Adding and removing tickets', async () => { + 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(BadRequestError); + + expect(await ticketApi.removeTicket(notActSuccess.id)).toBeTruthy(); + + expect(await ticketApi.removeTicket(actSuccess.id)).toBeTruthy(); +}); + +test('Modifying tickets', async () => { + 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( + BadRequestError, + ); + + //Modifying non existing ticket + await expect(ticketApi.modifyTicket('not an existing ticket ID', notActMod)).rejects.toThrowError( + NotFoundError, + ); +}); From af66ef29995455196b824071af3e1592cec4750d Mon Sep 17 00:00:00 2001 From: Muncherkin <48158637+Muncherkin@users.noreply.github.com> Date: Sun, 10 Mar 2024 17:39:44 +0100 Subject: [PATCH 04/16] updated changelog and contributors and versioning in package/-lock.json --- CHANGELOG.MD | 7 +++++++ package-lock.json | 4 ++-- package.json | 5 +++-- 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.MD b/CHANGELOG.MD index 2f6ea629..57776cd0 100644 --- a/CHANGELOG.MD +++ b/CHANGELOG.MD @@ -5,6 +5,13 @@ 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.9.0] - 2024-03-10 + +### Tillagt +- Prisma and graphql feature for activities (events) and tickets (to an separate from activities). +- Tests for activity reducer and API. +- Test for ticket API. + ## [1.8.0] - 2024-01-31 ### Tillagt diff --git a/package-lock.json b/package-lock.json index ab1980dc..6df8fe91 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "ekorre-ts", - "version": "1.8.0", + "version": "1.9.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "ekorre-ts", - "version": "1.8.0", + "version": "1.9.0", "license": "AGPL-3.0-only", "dependencies": { "@esek/auth-server": "5.0.2", diff --git a/package.json b/package.json index 5504fa42..f34bc2db 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ekorre-ts", - "version": "1.7.1", + "version": "1.9.0", "description": "E-Sektionens backend", "main": "src/index.ts", "scripts": { @@ -30,7 +30,8 @@ "Emil Eriksson ", "Axel Froborg ", "Marcus Lindell ", - "Eric Weidow " + "Eric Weidow ", + "Axel Andersson " ], "bugs": { "email": "macapar@esek.se" From a6af44788b882d276f925ded255dcc681618dc85 Mon Sep 17 00:00:00 2001 From: Muncherkin <48158637+Muncherkin@users.noreply.github.com> Date: Mon, 11 Mar 2024 01:28:42 +0100 Subject: [PATCH 05/16] Added ActivityAdmin and utskott type in activity graph --- src/models/generated/graphql.ts | 9 +++++---- src/reducers/access.reducer.ts | 1 + src/resolvers/activity.resolver.ts | 6 +++--- src/resolvers/ticket.resolver.ts | 6 +++--- src/schemas/access.graphql | 1 + src/schemas/activity.graphql | 6 +++--- test/unit/activity.api.test.ts | 18 +++++++++--------- test/unit/activity.reducer.test.ts | 4 ++-- 8 files changed, 27 insertions(+), 24 deletions(-) diff --git a/src/models/generated/graphql.ts b/src/models/generated/graphql.ts index bb6399fb..b8c449af 100644 --- a/src/models/generated/graphql.ts +++ b/src/models/generated/graphql.ts @@ -50,7 +50,7 @@ export type Activity = { source: ActivitySource; startDate: Scalars['Date']; title: Scalars['String']; - utskott?: Maybe; + utskott?: Maybe; }; export enum ActivitySource { @@ -159,6 +159,7 @@ export enum EmergencyContactType { /** 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', ElectionAdmin = 'election_admin', @@ -309,7 +310,7 @@ export type ModifiedActivity = { location?: InputMaybe; startDate?: InputMaybe; title?: InputMaybe; - utskott?: InputMaybe; + utskott?: InputMaybe; }; export type ModifiedTicket = { @@ -750,7 +751,7 @@ export type NewActivity = { location?: InputMaybe; startDate: Scalars['Date']; title: Scalars['String']; - utskott?: InputMaybe; + utskott?: InputMaybe; }; export type NewArticle = { @@ -1570,7 +1571,7 @@ export type ActivityResolvers; startDate?: Resolver; title?: Resolver; - utskott?: Resolver, ParentType, ContextType>; + utskott?: Resolver, ParentType, ContextType>; __isTypeOf?: IsTypeOfResolverFn; }>; diff --git a/src/reducers/access.reducer.ts b/src/reducers/access.reducer.ts index 5c71dcb4..aa7dc441 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', }; export const featureReducer = (features: Feature[]): FeatureInfo[] => { diff --git a/src/resolvers/activity.resolver.ts b/src/resolvers/activity.resolver.ts index 03c10593..d4108b65 100644 --- a/src/resolvers/activity.resolver.ts +++ b/src/resolvers/activity.resolver.ts @@ -24,17 +24,17 @@ const activityresolver: Resolvers = { }, Mutation: { addActivity: async (_, { activity }, ctx) => { - await hasAccess(ctx, Feature.Superadmin); + await hasAccess(ctx, Feature.ActivityAdmin); const a = await activityApi.addActivity(activity); return activityReducer(a); }, modifyActivity: async (_, { id, mod }, ctx) => { - await hasAccess(ctx, Feature.Superadmin); + await hasAccess(ctx, Feature.ActivityAdmin); const a = await activityApi.modifyActivity(id, mod); return activityReducer(a); }, removeActivity: async (_, { id }, ctx) => { - await hasAccess(ctx, Feature.Superadmin); + await hasAccess(ctx, Feature.ActivityAdmin); const res = activityApi.removeActivity(id); return res; }, diff --git a/src/resolvers/ticket.resolver.ts b/src/resolvers/ticket.resolver.ts index 87ff210c..829e6d7e 100644 --- a/src/resolvers/ticket.resolver.ts +++ b/src/resolvers/ticket.resolver.ts @@ -24,19 +24,19 @@ const ticketResolver: Resolvers = { }, Mutation: { addTicket: async (_, { ticket }, ctx) => { - await hasAccess(ctx, Feature.Superadmin); + await hasAccess(ctx, Feature.ActivityAdmin); const t = await tikcketApi.addTicket(ticket); return ticketReducer(t); }, modifyTicket: async (_, { id, mod }, ctx) => { - await hasAccess(ctx, Feature.Superadmin); + await hasAccess(ctx, Feature.ActivityAdmin); const t = await tikcketApi.modifyTicket(id, mod); return ticketReducer(t); }, removeTicket: async (_, { id }, ctx) => { - await hasAccess(ctx, Feature.Superadmin); + await hasAccess(ctx, Feature.ActivityAdmin); const res = await tikcketApi.removeTicket(id); return res; }, diff --git a/src/schemas/access.graphql b/src/schemas/access.graphql index 73217bfa..4e6f3832 100644 --- a/src/schemas/access.graphql +++ b/src/schemas/access.graphql @@ -79,4 +79,5 @@ enum Feature { user_admin, email_admin, ahs_admin, + activity_admin } diff --git a/src/schemas/activity.graphql b/src/schemas/activity.graphql index 801ccf13..f076e155 100644 --- a/src/schemas/activity.graphql +++ b/src/schemas/activity.graphql @@ -21,7 +21,7 @@ type Activity{ description: String startDate: Date! endDate: Date - utskott: String + utskott: Utskott imageUrl: String location: Location } @@ -41,7 +41,7 @@ input NewActivity{ description: String startDate: Date! endDate: Date - utskott: String + utskott: Utskott imageUrl: String location: NewLocation } @@ -51,7 +51,7 @@ input ModifiedActivity{ description: String startDate: Date endDate: Date - utskott: String + utskott: Utskott imageUrl: String location: NewLocation } diff --git a/test/unit/activity.api.test.ts b/test/unit/activity.api.test.ts index c7a22a5f..b15db6fa 100644 --- a/test/unit/activity.api.test.ts +++ b/test/unit/activity.api.test.ts @@ -1,7 +1,7 @@ import prisma from '@/api/prisma'; import { BadRequestError, NotFoundError } from '@/errors/request.errors'; import { ActivityAPI } from '@api/activity'; -import { ModifiedActivity, NewActivity } from '@generated/graphql'; +import { ModifiedActivity, NewActivity, Utskott } from '@generated/graphql'; import { PrismaActivity, PrismaActivitySource, PrismaUtskott } from '@prisma/client'; const activityApi = new ActivityAPI(); @@ -26,7 +26,7 @@ const DUMMY_NEWACTIVITY: NewActivity = { description: 'TestDescription', startDate: new Date('2024-02-03'), endDate: null, - utskott: PrismaUtskott.E6, + utskott: Utskott.E6, imageUrl: null, location: { title: 'LocationTestTitle', @@ -39,7 +39,7 @@ const DUMMY_MODACTIVITY: ModifiedActivity = { description: 'TestDescription', startDate: new Date('2024-02-03'), endDate: null, - utskott: PrismaUtskott.E6, + utskott: Utskott.E6, imageUrl: null, location: { title: 'LocationTestTitle', @@ -90,17 +90,17 @@ test('Getting activities', async () => { }); expect(orbiAct).toBeTruthy(); - await activityApi.addActivity(generatePrismaActivity({})); //in range - await activityApi.addActivity(generatePrismaActivity({ startDate: new Date('2024-02-01') })); //in range + await activityApi.addActivity(generateNewActivity({})); //in range + await activityApi.addActivity(generateNewActivity({ startDate: new Date('2024-02-01') })); //in range await activityApi.addActivity( - generatePrismaActivity({ startDate: new Date('2024-01-10'), endDate: new Date('2024-02-10') }), //endDate in range + generateNewActivity({ startDate: new Date('2024-01-10'), endDate: new Date('2024-02-10') }), //endDate in range ); - await activityApi.addActivity(generatePrismaActivity({ startDate: new Date('2024-05-01') })); //not in range + await activityApi.addActivity(generateNewActivity({ startDate: new Date('2024-05-01') })); //not in range await activityApi.addActivity( - generatePrismaActivity({ startDate: new Date('2024-01-01'), endDate: new Date('2024-03-10') }), //in range + generateNewActivity({ startDate: new Date('2024-01-01'), endDate: new Date('2024-03-10') }), //in range ); await activityApi.addActivity( - generatePrismaActivity({ startDate: new Date('2024-02-10'), endDate: new Date('2024-03-10') }), //startDate in range + generateNewActivity({ startDate: new Date('2024-02-10'), endDate: new Date('2024-03-10') }), //startDate in range ); const acts = await activityApi.getActivities( diff --git a/test/unit/activity.reducer.test.ts b/test/unit/activity.reducer.test.ts index 7ee4830e..42000e0a 100644 --- a/test/unit/activity.reducer.test.ts +++ b/test/unit/activity.reducer.test.ts @@ -1,4 +1,4 @@ -import { Activity, ActivitySource } from '@generated/graphql'; +import { Activity, ActivitySource, Utskott } from '@generated/graphql'; import { PrismaActivity, PrismaActivitySource, PrismaUtskott } from '@prisma/client'; import { activityReducer } from '@reducer/activity'; @@ -23,7 +23,7 @@ test('reducing valid DatabaseActivity', () => { description: 'TestDescription', startDate: new Date('2024-02-03'), endDate: null, - utskott: PrismaUtskott.E6, + utskott: Utskott.E6, imageUrl: null, location: { title: 'LocationTestTitle', From 3eb724967ae0ac0fcd48dda46bfeb936574f833d Mon Sep 17 00:00:00 2001 From: Muncherkin <48158637+Muncherkin@users.noreply.github.com> Date: Mon, 11 Mar 2024 01:36:06 +0100 Subject: [PATCH 06/16] Messed up Feature in resolvers... now fixed --- src/resolvers/activity.resolver.ts | 3 +-- src/resolvers/ticket.resolver.ts | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/resolvers/activity.resolver.ts b/src/resolvers/activity.resolver.ts index d4108b65..27b23616 100644 --- a/src/resolvers/activity.resolver.ts +++ b/src/resolvers/activity.resolver.ts @@ -1,8 +1,7 @@ import { reduce } from '@/reducers'; import { hasAccess, hasAuthenticated } from '@/util'; import { ActivityAPI } from '@api/activity'; -import { Feature } from '@esek/auth-server'; -import { Resolvers } from '@generated/graphql'; +import { Feature, Resolvers } from '@generated/graphql'; import { activityReducer } from '@reducer/activity'; const activityApi = new ActivityAPI(); diff --git a/src/resolvers/ticket.resolver.ts b/src/resolvers/ticket.resolver.ts index 829e6d7e..1b22f927 100644 --- a/src/resolvers/ticket.resolver.ts +++ b/src/resolvers/ticket.resolver.ts @@ -1,8 +1,7 @@ import { reduce } from '@/reducers'; import { hasAccess, hasAuthenticated } from '@/util'; import { TicketAPI } from '@api/ticket'; -import { Feature } from '@esek/auth-server'; -import { Resolvers } from '@generated/graphql'; +import { Feature, Resolvers } from '@generated/graphql'; import { ticketReducer } from '@reducer/ticket'; const tikcketApi = new TicketAPI(); From 9e716f2c784c0671065b82e3a3311cb6ae427030 Mon Sep 17 00:00:00 2001 From: Muncherkin <48158637+Muncherkin@users.noreply.github.com> Date: Mon, 11 Mar 2024 15:09:02 +0100 Subject: [PATCH 07/16] orderBy startDate asc in getActivities --- src/api/activity.api.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/api/activity.api.ts b/src/api/activity.api.ts index c0729d87..48bcc51f 100644 --- a/src/api/activity.api.ts +++ b/src/api/activity.api.ts @@ -46,6 +46,7 @@ export class ActivityAPI { ], AND: whereAnd, }, + orderBy: { startDate: 'asc' }, }); return a; From 3f9c2ed291f5515fab7d00a6228fabe6b1ca8c20 Mon Sep 17 00:00:00 2001 From: Muncherkin <48158637+Muncherkin@users.noreply.github.com> Date: Tue, 19 Mar 2024 10:55:55 +0100 Subject: [PATCH 08/16] Fixed after Froborgs comments --- prisma/data/activity.seed.ts | 133 +++++++++++++++++++++++++++++ prisma/data/seed.ts | 3 + prisma/schema.prisma | 3 +- src/api/activity.api.ts | 67 ++++++++++----- src/api/ticket.api.ts | 48 +++++------ src/models/generated/graphql.ts | 26 +++--- src/resolvers/activity.resolver.ts | 8 +- src/resolvers/ticket.resolver.ts | 8 +- src/schemas/activity.graphql | 10 +-- src/schemas/ticket.graphql | 4 +- test/unit/activity.api.test.ts | 4 +- test/unit/ticket.api.test.ts | 7 +- 12 files changed, 241 insertions(+), 80 deletions(-) create mode 100644 prisma/data/activity.seed.ts diff --git a/prisma/data/activity.seed.ts b/prisma/data/activity.seed.ts new file mode 100644 index 00000000..670a4533 --- /dev/null +++ b/prisma/data/activity.seed.ts @@ -0,0 +1,133 @@ +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(), + imageUrl: 'https://esek.se/_app/immutable/assets/1-3fd67044.webp', + 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(), + imageUrl: 'https://esek.se/_app/immutable/assets/1-3fd67044.webp', + 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(), + imageUrl: 'https://esek.se/_app/immutable/assets/1-3fd67044.webp', + 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(), + imageUrl: 'https://esek.se/_app/immutable/assets/1-3fd67044.webp', + 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(), + imageUrl: 'https://esek.se/_app/immutable/assets/1-3fd67044.webp', + 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(), + imageUrl: 'https://esek.se/_app/immutable/assets/1-3fd67044.webp', + 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(), + imageUrl: 'https://esek.se/_app/immutable/assets/1-3fd67044.webp', + 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(), + imageUrl: 'https://esek.se/_app/immutable/assets/1-3fd67044.webp', + 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(), + imageUrl: 'https://esek.se/_app/immutable/assets/1-3fd67044.webp', + 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(), + imageUrl: 'https://esek.se/_app/immutable/assets/1-3fd67044.webp', + 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(), + imageUrl: 'https://esek.se/_app/immutable/assets/1-3fd67044.webp', + 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 29fdecfd..e6c6233f 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -185,12 +185,13 @@ model PrismaActivity { description String? startDate DateTime @map("start_date") endDate DateTime? @map("end_date") - utskott PrismaUtskott? + utskott PrismaUtskott imageUrl String? @map("image_url") locationTitle String? @map("location_title") locationLink String? @map("location_link") tickets PrismaTicket[] } +//sourceUrl for link to event from other apps/websites. model PrismaTicket { id String @id @default(cuid()) diff --git a/src/api/activity.api.ts b/src/api/activity.api.ts index 48bcc51f..c08371fa 100644 --- a/src/api/activity.api.ts +++ b/src/api/activity.api.ts @@ -2,7 +2,7 @@ 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 { Prisma, PrismaActivity, PrismaActivitySource } from '@prisma/client'; +import { PrismaActivity, PrismaActivitySource } from '@prisma/client'; import prisma from './prisma'; @@ -23,14 +23,8 @@ export class ActivityAPI { async getActivities( from: Date, to: Date, - utskott: Utskott[] | null | undefined, + utskott: Utskott[] = [Utskott.Other], ): Promise { - const whereAnd: Prisma.PrismaActivityWhereInput[] = []; - - if (utskott != null) { - whereAnd.push({ utskott: { in: utskott } }); - } - const a = await prisma.prismaActivity.findMany({ where: { startDate: { @@ -44,7 +38,7 @@ export class ActivityAPI { }, { AND: [{ startDate: { gte: from } }, { endDate: null }] }, ], - AND: whereAnd, + AND: { utskott: { in: utskott } }, }, orderBy: { startDate: 'asc' }, }); @@ -53,7 +47,17 @@ export class ActivityAPI { } async addActivity(activity: NewActivity): Promise { - if (activity.endDate && activity.endDate.getTime() - activity.startDate.getTime() < 0) { + 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!'); } @@ -64,7 +68,7 @@ export class ActivityAPI { description: activity.description, startDate: activity.startDate, endDate: activity.endDate, - utskott: activity.utskott as Utskott, + utskott: activity.utskott ?? Utskott.Other, imageUrl: activity.imageUrl, locationTitle: activity.location?.title, locationLink: activity.location?.link, @@ -73,27 +77,43 @@ export class ActivityAPI { return res; } - async modifyActivity(id: string, mod: ModifiedActivity): Promise { + async modifyActivity(id: string, entry: ModifiedActivity): Promise { const a = await this.getActivity(id); - if (mod.endDate) { - if (mod.startDate && mod.endDate.getTime() - mod.startDate.getTime() < 0) { - throw new BadRequestError('Modifierad sluttid för aktivitet är före modifierad starttid!'); - } else if (!mod.startDate && mod.endDate.getTime() - a.startDate.getTime() < 0) { - throw new BadRequestError('Modifierad sluttid för aktivitet är före starttiden!'); + + const isAcceptableTime = () => { + const { startDate, endDate } = entry; + + if (endDate) { + if (startDate) { + return endDate.getTime() - startDate.getTime() >= 0; + } + return endDate.getTime() - a.startDate.getTime() >= 0; + } else if (startDate) { + if (!a.endDate) { + return true; + } + return a.endDate.getTime() - startDate.getTime() >= 0; } + + return true; + }; + + if (!isAcceptableTime()) { + throw new BadRequestError('Ny slut- och starttid för aktivitet är omöjlig!'); } + if (a.source !== PrismaActivitySource.WEBSITE) { throw new BadRequestError( 'Ej tillåtet att ändra i evenemang som inte är skapade på hemsidan!', ); } - const { location, ...reduced } = mod; + const { location, ...reduced } = entry; const refact = { ...reduced, - locationTitle: mod.location?.title, - locationLink: mod.location?.link, + locationTitle: entry.location?.title, + locationLink: entry.location?.link, }; const update: StrictObject = stripObject(refact); @@ -105,15 +125,16 @@ export class ActivityAPI { return res; } - async removeActivity(id: string): Promise { + async removeActivity(id: string): Promise { const a = await this.getActivity(id); if (a.source !== PrismaActivitySource.WEBSITE) { throw new BadRequestError( 'Ej tillåtet att ta bort evenemang som inte är skapade på hemsidan!', ); } - await prisma.prismaActivity.delete({ where: { id } }); - return true; + const res = await prisma.prismaActivity.delete({ where: { id } }); + + return res; } async clear() { diff --git a/src/api/ticket.api.ts b/src/api/ticket.api.ts index 0336be85..1894822a 100644 --- a/src/api/ticket.api.ts +++ b/src/api/ticket.api.ts @@ -35,15 +35,15 @@ export class TicketAPI { } async addTicket(ticket: NewTicket): Promise { - if (ticket.activityID != null) { - try { - await activityApi.getActivity(ticket.activityID); - } catch { - throw new BadRequestError( - 'Biljett kunde inte skapas! Specifierad activityID tillhörde inte någon activity.', - ); - } - } + // if (ticket.activityID != null) { + // try { + // await activityApi.getActivity(ticket.activityID); + // } catch { + // throw new BadRequestError( + // 'Biljett kunde inte skapas! Specifierad activityID tillhörde inte någon activity.', + // ); + // } + // } const t = await prisma.prismaTicket.create({ data: { @@ -58,20 +58,20 @@ export class TicketAPI { return t; } - async modifyTicket(id: string, mod: ModifiedTicket): Promise { + async modifyTicket(id: string, entry: ModifiedTicket): Promise { await this.getTicket(id); - if (mod.activityID != null) { - try { - await activityApi.getActivity(mod.activityID); - } catch { - throw new BadRequestError( - 'Biljett kunde inte modifieras! Modifierad activityID tillhörde inte någon activity.', - ); - } - } - - const { ...rest } = mod; + // if (mod.activityID != null) { + // try { + // await activityApi.getActivity(mod.activityID); + // } catch { + // throw new BadRequestError( + // 'Biljett kunde inte modifieras! Modifierad activityID tillhörde inte någon activity.', + // ); + // } + // } + + const { ...rest } = entry; const update: StrictObject = stripObject(rest); const t = prisma.prismaTicket.update({ @@ -82,11 +82,11 @@ export class TicketAPI { return t; } - async removeTicket(id: string): Promise { + async removeTicket(id: string): Promise { await this.getTicket(id); - await prisma.prismaTicket.delete({ where: { id } }); - return true; + const t = await prisma.prismaTicket.delete({ where: { id } }); + return t; } async clear() { diff --git a/src/models/generated/graphql.ts b/src/models/generated/graphql.ts index b8c449af..235bdce9 100644 --- a/src/models/generated/graphql.ts +++ b/src/models/generated/graphql.ts @@ -50,7 +50,7 @@ export type Activity = { source: ActivitySource; startDate: Scalars['Date']; title: Scalars['String']; - utskott?: Maybe; + utskott: Utskott; }; export enum ActivitySource { @@ -387,7 +387,7 @@ export type Mutation = { propose: Scalars['Boolean']; providerLogin: LoginResponse; refresh: TokenResponse; - removeActivity: Scalars['Boolean']; + removeActivity: Activity; removeArticle: Scalars['Boolean']; removeElectables: Scalars['Boolean']; removeEmergencyContact: Scalars['Boolean']; @@ -396,7 +396,7 @@ export type Mutation = { removeHistoryEntry: Scalars['Boolean']; removeMeeting: Scalars['Boolean']; removeProposal: Scalars['Boolean']; - removeTicket: Scalars['Boolean']; + removeTicket: Ticket; requestPasswordReset: Scalars['Boolean']; resetPassword: Scalars['Boolean']; /** Only possible during open election, so electionId is known */ @@ -558,8 +558,8 @@ export type MutationLoginArgs = { export type MutationModifyActivityArgs = { + entry: ModifiedActivity; id: Scalars['String']; - mod: ModifiedActivity; }; @@ -575,8 +575,8 @@ export type MutationModifyPostArgs = { export type MutationModifyTicketArgs = { + entry: ModifiedTicket; id: Scalars['String']; - mod: ModifiedTicket; }; @@ -751,7 +751,7 @@ export type NewActivity = { location?: InputMaybe; startDate: Scalars['Date']; title: Scalars['String']; - utskott?: InputMaybe; + utskott: Utskott; }; export type NewArticle = { @@ -929,7 +929,7 @@ export type Query = { export type QueryActivitiesArgs = { from: Scalars['Date']; to: Scalars['Date']; - utskott?: InputMaybe>; + utskott: Array; }; @@ -1571,7 +1571,7 @@ export type ActivityResolvers; startDate?: Resolver; title?: Resolver; - utskott?: Resolver, ParentType, ContextType>; + utskott?: Resolver; __isTypeOf?: IsTypeOfResolverFn; }>; @@ -1754,16 +1754,16 @@ export type MutationResolvers>; login?: Resolver>; logout?: Resolver; - modifyActivity?: Resolver>; + modifyActivity?: Resolver>; modifyArticle?: Resolver>; modifyPost?: Resolver>; - modifyTicket?: Resolver>; + modifyTicket?: Resolver>; nominate?: Resolver>; openElection?: Resolver>; propose?: Resolver>; providerLogin?: Resolver>; refresh?: Resolver>; - removeActivity?: Resolver>; + removeActivity?: Resolver>; removeArticle?: Resolver>; removeElectables?: Resolver>; removeEmergencyContact?: Resolver>; @@ -1772,7 +1772,7 @@ export type MutationResolvers>; removeMeeting?: Resolver>; removeProposal?: Resolver>; - removeTicket?: Resolver>; + removeTicket?: Resolver>; requestPasswordReset?: Resolver>; resetPassword?: Resolver>; respondToNomination?: Resolver>; @@ -1823,7 +1823,7 @@ export type ProposalResolvers; export type QueryResolvers = ResolversObject<{ - activities?: Resolver, ParentType, ContextType, RequireFields>; + activities?: Resolver, ParentType, ContextType, RequireFields>; activity?: Resolver>; apiKey?: Resolver>; apiKeys?: Resolver, ParentType, ContextType>; diff --git a/src/resolvers/activity.resolver.ts b/src/resolvers/activity.resolver.ts index 27b23616..2bf5c607 100644 --- a/src/resolvers/activity.resolver.ts +++ b/src/resolvers/activity.resolver.ts @@ -27,15 +27,15 @@ const activityresolver: Resolvers = { const a = await activityApi.addActivity(activity); return activityReducer(a); }, - modifyActivity: async (_, { id, mod }, ctx) => { + modifyActivity: async (_, { id, entry }, ctx) => { await hasAccess(ctx, Feature.ActivityAdmin); - const a = await activityApi.modifyActivity(id, mod); + const a = await activityApi.modifyActivity(id, entry); return activityReducer(a); }, removeActivity: async (_, { id }, ctx) => { await hasAccess(ctx, Feature.ActivityAdmin); - const res = activityApi.removeActivity(id); - return res; + const a = await activityApi.removeActivity(id); + return activityReducer(a); }, }, }; diff --git a/src/resolvers/ticket.resolver.ts b/src/resolvers/ticket.resolver.ts index 1b22f927..2779c262 100644 --- a/src/resolvers/ticket.resolver.ts +++ b/src/resolvers/ticket.resolver.ts @@ -28,16 +28,16 @@ const ticketResolver: Resolvers = { return ticketReducer(t); }, - modifyTicket: async (_, { id, mod }, ctx) => { + modifyTicket: async (_, { id, entry }, ctx) => { await hasAccess(ctx, Feature.ActivityAdmin); - const t = await tikcketApi.modifyTicket(id, mod); + const t = await tikcketApi.modifyTicket(id, entry); return ticketReducer(t); }, removeTicket: async (_, { id }, ctx) => { await hasAccess(ctx, Feature.ActivityAdmin); - const res = await tikcketApi.removeTicket(id); - return res; + const t = await tikcketApi.removeTicket(id); + return ticketReducer(t); }, }, }; diff --git a/src/schemas/activity.graphql b/src/schemas/activity.graphql index f076e155..23e7037c 100644 --- a/src/schemas/activity.graphql +++ b/src/schemas/activity.graphql @@ -5,13 +5,13 @@ scalar Date type Query{ activity(id: String!) : Activity! - activities(from: Date!, to: Date!, utskott: [Utskott!]) : [Activity!]! + activities(from: Date!, to: Date!, utskott: [Utskott!]!) : [Activity!]! } type Mutation{ addActivity(activity: NewActivity!) : Activity! - modifyActivity(id: String!, mod: ModifiedActivity!) : Activity! - removeActivity(id: String!) : Boolean! + modifyActivity(id: String!, entry: ModifiedActivity!) : Activity! + removeActivity(id: String!) : Activity! } type Activity{ @@ -21,7 +21,7 @@ type Activity{ description: String startDate: Date! endDate: Date - utskott: Utskott + utskott: Utskott! imageUrl: String location: Location } @@ -41,7 +41,7 @@ input NewActivity{ description: String startDate: Date! endDate: Date - utskott: Utskott + utskott: Utskott! imageUrl: String location: NewLocation } diff --git a/src/schemas/ticket.graphql b/src/schemas/ticket.graphql index a6725310..3712966b 100644 --- a/src/schemas/ticket.graphql +++ b/src/schemas/ticket.graphql @@ -7,8 +7,8 @@ type Query{ type Mutation{ addTicket(ticket: NewTicket!) : Ticket! - modifyTicket(id: String!, mod: ModifiedTicket!) : Ticket! - removeTicket(id: String!) : Boolean! + modifyTicket(id: String!, entry: ModifiedTicket!) : Ticket! + removeTicket(id: String!) : Ticket! } type Ticket{ diff --git a/test/unit/activity.api.test.ts b/test/unit/activity.api.test.ts index b15db6fa..854b89ed 100644 --- a/test/unit/activity.api.test.ts +++ b/test/unit/activity.api.test.ts @@ -106,7 +106,7 @@ test('Getting activities', async () => { const acts = await activityApi.getActivities( new Date('2024-01-30'), new Date('2024-02-28'), - null, + Object.entries(Utskott).map((u) => u[1]), ); expect(acts.length).toBe(6); @@ -127,7 +127,7 @@ test('Adding and removing activities', async () => { const added = await activityApi.addActivity(newAct); expect(added).toBeTruthy(); - const removed = activityApi.removeActivity(added.id); + 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 diff --git a/test/unit/ticket.api.test.ts b/test/unit/ticket.api.test.ts index c7b3cc8d..2d2e225f 100644 --- a/test/unit/ticket.api.test.ts +++ b/test/unit/ticket.api.test.ts @@ -3,6 +3,7 @@ import { BadRequestError, NotFoundError } from '@/errors/request.errors'; import { TicketAPI } from '@api/ticket'; import { ModifiedTicket, NewTicket } from '@generated/graphql'; import { PrismaActivity, PrismaActivitySource, PrismaUtskott } from '@prisma/client'; +import { PrismaClientKnownRequestError } from '@prisma/client/runtime'; const ticketApi = new TicketAPI(); @@ -54,7 +55,9 @@ test('Adding and removing tickets', async () => { expect(actSuccess).toBeTruthy(); //Throws if activityID does not belong to an existing activity - await expect(ticketApi.addTicket(falseActTicket)).rejects.toThrowError(BadRequestError); + await expect(ticketApi.addTicket(falseActTicket)).rejects.toThrowError( + PrismaClientKnownRequestError, + ); expect(await ticketApi.removeTicket(notActSuccess.id)).toBeTruthy(); @@ -99,7 +102,7 @@ test('Modifying tickets', async () => { //Modifying a ticket to belong to a non existing act await expect(ticketApi.modifyTicket(actSuccess.id, falseActMod)).rejects.toThrowError( - BadRequestError, + PrismaClientKnownRequestError, ); //Modifying non existing ticket From a4fc4ec18e558e97bcbeb949a9e32dc3f46a1cdb Mon Sep 17 00:00:00 2001 From: Muncherkin <48158637+Muncherkin@users.noreply.github.com> Date: Tue, 19 Mar 2024 14:00:12 +0100 Subject: [PATCH 09/16] no longer failing tests and also good lint times --- src/api/ticket.api.ts | 29 +---- ....test.ts => activityandticket.api.test.ts} | 113 +++++++++++++++++- test/unit/ticket.api.test.ts | 112 ----------------- 3 files changed, 113 insertions(+), 141 deletions(-) rename test/unit/{activity.api.test.ts => activityandticket.api.test.ts} (60%) delete mode 100644 test/unit/ticket.api.test.ts diff --git a/src/api/ticket.api.ts b/src/api/ticket.api.ts index 1894822a..6d82a3d6 100644 --- a/src/api/ticket.api.ts +++ b/src/api/ticket.api.ts @@ -1,14 +1,11 @@ -import { BadRequestError, NotFoundError } from '@/errors/request.errors'; +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 { ActivityAPI } from './activity.api'; import prisma from './prisma'; -const activityApi = new ActivityAPI(); - export class TicketAPI { async getTicket(id: string): Promise { const t = await prisma.prismaTicket.findFirst({ where: { id } }); @@ -35,16 +32,6 @@ export class TicketAPI { } async addTicket(ticket: NewTicket): Promise { - // if (ticket.activityID != null) { - // try { - // await activityApi.getActivity(ticket.activityID); - // } catch { - // throw new BadRequestError( - // 'Biljett kunde inte skapas! Specifierad activityID tillhörde inte någon activity.', - // ); - // } - // } - const t = await prisma.prismaTicket.create({ data: { name: ticket.name, @@ -59,18 +46,6 @@ export class TicketAPI { } async modifyTicket(id: string, entry: ModifiedTicket): Promise { - await this.getTicket(id); - - // if (mod.activityID != null) { - // try { - // await activityApi.getActivity(mod.activityID); - // } catch { - // throw new BadRequestError( - // 'Biljett kunde inte modifieras! Modifierad activityID tillhörde inte någon activity.', - // ); - // } - // } - const { ...rest } = entry; const update: StrictObject = stripObject(rest); @@ -83,8 +58,6 @@ export class TicketAPI { } async removeTicket(id: string): Promise { - await this.getTicket(id); - const t = await prisma.prismaTicket.delete({ where: { id } }); return t; } diff --git a/test/unit/activity.api.test.ts b/test/unit/activityandticket.api.test.ts similarity index 60% rename from test/unit/activity.api.test.ts rename to test/unit/activityandticket.api.test.ts index 854b89ed..3c73d137 100644 --- a/test/unit/activity.api.test.ts +++ b/test/unit/activityandticket.api.test.ts @@ -1,8 +1,39 @@ import prisma from '@/api/prisma'; import { BadRequestError, NotFoundError } from '@/errors/request.errors'; import { ActivityAPI } from '@api/activity'; -import { ModifiedActivity, NewActivity, Utskott } from '@generated/graphql'; +import { TicketAPI } from '@api/ticket'; +import { + ModifiedActivity, + ModifiedTicket, + NewActivity, + NewTicket, + Utskott, +} from '@generated/graphql'; import { PrismaActivity, PrismaActivitySource, PrismaUtskott } from '@prisma/client'; +import { PrismaClientKnownRequestError } from '@prisma/client/runtime'; + +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(); @@ -75,10 +106,12 @@ const generateModActivity = (overrides: Partial): ModifiedActi }; beforeEach(async () => { + await ticketApi.clear(); await activityApi.clear(); }); afterAll(async () => { + await ticketApi.clear(); await activityApi.clear(); }); @@ -185,3 +218,81 @@ test('Modifying activities', async () => { 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( + PrismaClientKnownRequestError, + ); + + 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( + PrismaClientKnownRequestError, + ); + + //Modifying non existing ticket + await expect(ticketApi.modifyTicket('not an existing ticket ID', notActMod)).rejects.toThrowError( + PrismaClientKnownRequestError, + ); +}); diff --git a/test/unit/ticket.api.test.ts b/test/unit/ticket.api.test.ts deleted file mode 100644 index 2d2e225f..00000000 --- a/test/unit/ticket.api.test.ts +++ /dev/null @@ -1,112 +0,0 @@ -import prisma from '@/api/prisma'; -import { BadRequestError, NotFoundError } from '@/errors/request.errors'; -import { TicketAPI } from '@api/ticket'; -import { ModifiedTicket, NewTicket } from '@generated/graphql'; -import { PrismaActivity, PrismaActivitySource, PrismaUtskott } from '@prisma/client'; -import { PrismaClientKnownRequestError } from '@prisma/client/runtime'; - -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, -}; - -beforeEach(async () => { - await ticketApi.clear(); - await prisma.prismaActivity.deleteMany(); - await prisma.prismaActivity.create({ - data: { - ...EXISTING_ACT, - }, - }); -}); - -afterAll(async () => { - await ticketApi.clear(); - await prisma.prismaActivity.deleteMany(); -}); - -test('Adding and removing tickets', async () => { - 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( - PrismaClientKnownRequestError, - ); - - expect(await ticketApi.removeTicket(notActSuccess.id)).toBeTruthy(); - - expect(await ticketApi.removeTicket(actSuccess.id)).toBeTruthy(); -}); - -test('Modifying tickets', async () => { - 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( - PrismaClientKnownRequestError, - ); - - //Modifying non existing ticket - await expect(ticketApi.modifyTicket('not an existing ticket ID', notActMod)).rejects.toThrowError( - NotFoundError, - ); -}); From 99a4486a18cf23a121ddd816715be2e3622d66fe Mon Sep 17 00:00:00 2001 From: Muncherkin <48158637+Muncherkin@users.noreply.github.com> Date: Tue, 19 Mar 2024 14:12:44 +0100 Subject: [PATCH 10/16] test could not contain prisma/client/runtime. --- test/unit/activityandticket.api.test.ts | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/test/unit/activityandticket.api.test.ts b/test/unit/activityandticket.api.test.ts index 3c73d137..40a05339 100644 --- a/test/unit/activityandticket.api.test.ts +++ b/test/unit/activityandticket.api.test.ts @@ -10,7 +10,6 @@ import { Utskott, } from '@generated/graphql'; import { PrismaActivity, PrismaActivitySource, PrismaUtskott } from '@prisma/client'; -import { PrismaClientKnownRequestError } from '@prisma/client/runtime'; const ticketApi = new TicketAPI(); @@ -236,9 +235,7 @@ test('Adding and removing tickets', async () => { expect(actSuccess).toBeTruthy(); //Throws if activityID does not belong to an existing activity - await expect(ticketApi.addTicket(falseActTicket)).rejects.toThrowError( - PrismaClientKnownRequestError, - ); + await expect(ticketApi.addTicket(falseActTicket)).rejects.toThrowError(); expect(await ticketApi.removeTicket(notActSuccess.id)).toBeTruthy(); @@ -287,12 +284,10 @@ test('Modifying tickets', async () => { 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( - PrismaClientKnownRequestError, - ); + 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( - PrismaClientKnownRequestError, - ); + await expect( + ticketApi.modifyTicket('not an existing ticket ID', notActMod), + ).rejects.toThrowError(); }); From 1c9c329db28c63a9ada123afce1ce87648e20e1c Mon Sep 17 00:00:00 2001 From: Muncherkin <48158637+Muncherkin@users.noreply.github.com> Date: Thu, 28 Mar 2024 00:59:49 +0100 Subject: [PATCH 11/16] fixed broken image urls by removing all image urls --- prisma/data/activity.seed.ts | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/prisma/data/activity.seed.ts b/prisma/data/activity.seed.ts index 670a4533..3b25f2c9 100644 --- a/prisma/data/activity.seed.ts +++ b/prisma/data/activity.seed.ts @@ -16,7 +16,6 @@ export const activities: Prisma.PrismaActivityCreateInput[] = [ 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(), - imageUrl: 'https://esek.se/_app/immutable/assets/1-3fd67044.webp', locationTitle: '', locationLink: '', }, @@ -27,7 +26,6 @@ export const activities: Prisma.PrismaActivityCreateInput[] = [ 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(), - imageUrl: 'https://esek.se/_app/immutable/assets/1-3fd67044.webp', locationTitle: '', locationLink: '', }, @@ -38,7 +36,6 @@ export const activities: Prisma.PrismaActivityCreateInput[] = [ 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(), - imageUrl: 'https://esek.se/_app/immutable/assets/1-3fd67044.webp', locationTitle: '', locationLink: '', }, @@ -49,7 +46,6 @@ export const activities: Prisma.PrismaActivityCreateInput[] = [ 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(), - imageUrl: 'https://esek.se/_app/immutable/assets/1-3fd67044.webp', locationTitle: '', locationLink: '', }, @@ -60,7 +56,6 @@ export const activities: Prisma.PrismaActivityCreateInput[] = [ 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(), - imageUrl: 'https://esek.se/_app/immutable/assets/1-3fd67044.webp', locationTitle: '', locationLink: '', }, @@ -71,7 +66,6 @@ export const activities: Prisma.PrismaActivityCreateInput[] = [ 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(), - imageUrl: 'https://esek.se/_app/immutable/assets/1-3fd67044.webp', locationTitle: '', locationLink: '', }, @@ -82,7 +76,6 @@ export const activities: Prisma.PrismaActivityCreateInput[] = [ 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(), - imageUrl: 'https://esek.se/_app/immutable/assets/1-3fd67044.webp', locationTitle: '', locationLink: '', }, @@ -93,7 +86,6 @@ export const activities: Prisma.PrismaActivityCreateInput[] = [ 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(), - imageUrl: 'https://esek.se/_app/immutable/assets/1-3fd67044.webp', locationTitle: '', locationLink: '', }, @@ -104,7 +96,6 @@ export const activities: Prisma.PrismaActivityCreateInput[] = [ 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(), - imageUrl: 'https://esek.se/_app/immutable/assets/1-3fd67044.webp', locationTitle: '', locationLink: '', }, @@ -115,7 +106,6 @@ export const activities: Prisma.PrismaActivityCreateInput[] = [ 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(), - imageUrl: 'https://esek.se/_app/immutable/assets/1-3fd67044.webp', locationTitle: '', locationLink: '', }, @@ -126,7 +116,6 @@ export const activities: Prisma.PrismaActivityCreateInput[] = [ 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(), - imageUrl: 'https://esek.se/_app/immutable/assets/1-3fd67044.webp', locationTitle: '', locationLink: '', }, From 38f2c63e1ca48b202139ef550b423d6f92e56a0e Mon Sep 17 00:00:00 2001 From: Muncherkin <48158637+Muncherkin@users.noreply.github.com> Date: Thu, 28 Mar 2024 20:31:17 +0100 Subject: [PATCH 12/16] DateTime added --- CHANGELOG.MD | 1 + codegen.yml | 1 + src/app/serverconfig.ts | 6 +++--- src/models/generated/graphql.ts | 28 ++++++++++++++++++---------- src/schemas/activity.graphql | 17 ++++++++--------- 5 files changed, 31 insertions(+), 22 deletions(-) diff --git a/CHANGELOG.MD b/CHANGELOG.MD index 57776cd0..0550cce6 100644 --- a/CHANGELOG.MD +++ b/CHANGELOG.MD @@ -11,6 +11,7 @@ och följer [Semantic Versioning](https://semver.org/spec/v2.0.0.html). - Prisma and graphql feature for activities (events) and tickets (to an separate from activities). - Tests for activity reducer and API. - Test for ticket API. +- DateTime scalar for graphql. ## [1.8.0] - 2024-01-31 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/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 235bdce9..1645a1cd 100644 --- a/src/models/generated/graphql.ts +++ b/src/models/generated/graphql.ts @@ -16,6 +16,7 @@ export type Scalars = { Int: number; Float: number; Date: Date; + DateTime: Date; Object: Record; }; @@ -43,12 +44,12 @@ export enum AccessType { export type Activity = { description?: Maybe; - endDate?: Maybe; + endDate?: Maybe; id: Scalars['String']; imageUrl?: Maybe; location?: Maybe; source: ActivitySource; - startDate: Scalars['Date']; + startDate: Scalars['DateTime']; title: Scalars['String']; utskott: Utskott; }; @@ -305,10 +306,10 @@ export enum MeetingType { export type ModifiedActivity = { description?: InputMaybe; - endDate?: InputMaybe; + endDate?: InputMaybe; imageUrl?: InputMaybe; location?: InputMaybe; - startDate?: InputMaybe; + startDate?: InputMaybe; title?: InputMaybe; utskott?: InputMaybe; }; @@ -746,10 +747,10 @@ export type MutationValidateTokenArgs = { export type NewActivity = { description?: InputMaybe; - endDate?: InputMaybe; + endDate?: InputMaybe; imageUrl?: InputMaybe; location?: InputMaybe; - startDate: Scalars['Date']; + startDate: Scalars['DateTime']; title: Scalars['String']; utskott: Utskott; }; @@ -927,8 +928,8 @@ export type Query = { * does not take an `electionId` parameter. */ export type QueryActivitiesArgs = { - from: Scalars['Date']; - to: Scalars['Date']; + from: Scalars['DateTime']; + to: Scalars['DateTime']; utskott: Array; }; @@ -1452,6 +1453,7 @@ export type ResolversTypes = ResolversObject<{ Boolean: ResolverTypeWrapper; CasLoginResponse: ResolverTypeWrapper; Date: ResolverTypeWrapper; + DateTime: ResolverTypeWrapper; Door: Door; DoorInfo: ResolverTypeWrapper; Election: ResolverTypeWrapper; @@ -1514,6 +1516,7 @@ export type ResolversParentTypes = ResolversObject<{ Boolean: Scalars['Boolean']; CasLoginResponse: CasLoginResponse; Date: Scalars['Date']; + DateTime: Scalars['DateTime']; DoorInfo: DoorInfo; Election: ElectionResponse; EmergencyContact: EmergencyContact; @@ -1564,12 +1567,12 @@ export type AccessResolvers = ResolversObject<{ description?: Resolver, ParentType, ContextType>; - endDate?: Resolver, ParentType, ContextType>; + endDate?: Resolver, ParentType, ContextType>; id?: Resolver; imageUrl?: Resolver, ParentType, ContextType>; location?: Resolver, ParentType, ContextType>; source?: Resolver; - startDate?: Resolver; + startDate?: Resolver; title?: Resolver; utskott?: Resolver; __isTypeOf?: IsTypeOfResolverFn; @@ -1609,6 +1612,10 @@ export interface DateScalarConfig extends GraphQLScalarTypeConfig { + name: 'DateTime'; +} + export type DoorInfoResolvers = ResolversObject<{ description?: Resolver; name?: Resolver; @@ -1921,6 +1928,7 @@ export type Resolvers = ResolversObject<{ Article?: ArticleResolvers; CasLoginResponse?: CasLoginResponseResolvers; Date?: GraphQLScalarType; + DateTime?: GraphQLScalarType; DoorInfo?: DoorInfoResolvers; Election?: ElectionResolvers; EmergencyContact?: EmergencyContactResolvers; diff --git a/src/schemas/activity.graphql b/src/schemas/activity.graphql index 23e7037c..cde03df4 100644 --- a/src/schemas/activity.graphql +++ b/src/schemas/activity.graphql @@ -1,11 +1,10 @@ # import Utskott from 'utskott.graphql' -# import Date from 'user.graphql' -scalar Date +scalar DateTime type Query{ activity(id: String!) : Activity! - activities(from: Date!, to: Date!, utskott: [Utskott!]!) : [Activity!]! + activities(from: DateTime!, to: DateTime!, utskott: [Utskott!]!) : [Activity!]! } type Mutation{ @@ -19,8 +18,8 @@ type Activity{ source: ActivitySource! title: String! description: String - startDate: Date! - endDate: Date + startDate: DateTime! + endDate: DateTime utskott: Utskott! imageUrl: String location: Location @@ -39,8 +38,8 @@ input NewLocation{ input NewActivity{ title: String! description: String - startDate: Date! - endDate: Date + startDate: DateTime! + endDate: DateTime utskott: Utskott! imageUrl: String location: NewLocation @@ -49,8 +48,8 @@ input NewActivity{ input ModifiedActivity{ title: String description: String - startDate: Date - endDate: Date + startDate: DateTime + endDate: DateTime utskott: Utskott imageUrl: String location: NewLocation From 009b0bbe022f4cff0e3a0ed4740843b3c995a800 Mon Sep 17 00:00:00 2001 From: Muncherkin <48158637+Muncherkin@users.noreply.github.com> Date: Sun, 21 Apr 2024 02:35:39 +0200 Subject: [PATCH 13/16] added orbi integration --- .env.example | 6 ++- src/config.ts | 11 ++++++ src/resolvers/activity.resolver.ts | 4 ++ src/services/orbi.service.ts | 61 ++++++++++++++++++++++++++++++ 4 files changed, 81 insertions(+), 1 deletion(-) create mode 100644 src/services/orbi.service.ts diff --git a/.env.example b/.env.example index 81834ad8..0dde9649 100644 --- a/.env.example +++ b/.env.example @@ -27,4 +27,8 @@ WIKI_PASSWORD= WIKI_BASE_URL=https://wiki.esek.se SKIP_ACCESS_CHECKS=false -POST_ACCESS_COOLDOWN_DAYS=90 \ No newline at end of file +POST_ACCESS_COOLDOWN_DAYS=90 + +# Orbi service +ORBI=http://localhost:6970 +ORBI_API_TOKEN= \ No newline at end of file diff --git a/src/config.ts b/src/config.ts index df4d0e81..17af8c26 100644 --- a/src/config.ts +++ b/src/config.ts @@ -48,6 +48,16 @@ const JWT = { SECRET: (process.env.JWT_SECRET as string) ?? '', }; +/** + * Config for Orbi - our orbi API service + * @param {string} URL - The base URL for the Orbi API + * @param {string} API_TOKEN - The API token set as an env-variable in Orbi + */ +const ORBI = { + URL: process.env.ORBI ?? 'https://localhost:6970', + API_TOKEN: process.env.ORBI_API_TOKEN, +}; + const config = { PORT: parseInt(process.env.PORT ?? '3000', 10), HOST: process.env.HOST ?? '0.0.0.0', @@ -60,6 +70,7 @@ const config = { LU, WIKI, JWT, + ORBI, }; export default config; diff --git a/src/resolvers/activity.resolver.ts b/src/resolvers/activity.resolver.ts index 2bf5c607..e8971188 100644 --- a/src/resolvers/activity.resolver.ts +++ b/src/resolvers/activity.resolver.ts @@ -3,6 +3,7 @@ import { hasAccess, hasAuthenticated } from '@/util'; import { ActivityAPI } from '@api/activity'; import { Feature, Resolvers } from '@generated/graphql'; import { activityReducer } from '@reducer/activity'; +import { updateOrbiActivities } from '@service/orbi'; const activityApi = new ActivityAPI(); @@ -16,6 +17,9 @@ const activityresolver: Resolvers = { }, activities: async (_, { from, to, utskott }, ctx) => { await hasAuthenticated(ctx); + //async call to update orbi because it is + //painfully slow + updateOrbiActivities(); const a = await activityApi.getActivities(from, to, utskott); return reduce(a, activityReducer); diff --git a/src/services/orbi.service.ts b/src/services/orbi.service.ts new file mode 100644 index 00000000..4cf5c879 --- /dev/null +++ b/src/services/orbi.service.ts @@ -0,0 +1,61 @@ +import prisma from '@/api/prisma'; +import config from '@/config'; +import { ServerError } from '@/errors/request.errors'; +import { PrismaActivity, PrismaTicket } from '@prisma/client'; +import axios from 'axios'; + +/* + Creates an axios-instance and sets the baseUrl and authorization header + to the corresponding values in the config +*/ + +const { + ORBI: { URL, API_TOKEN }, +} = config; + +const api = axios.create({ + baseURL: URL, + headers: { 'x-api-key': API_TOKEN }, +}); + +type OrbiResponse = [ + { + activity: PrismaActivity; + tickets: [PrismaTicket]; + departmentName: string; + }, +]; + +let latestCall = 0; + +export const updateOrbiActivities = async () => { + const timestamp = Date.now(); + //If latest call was < 10 minutes ago + if (timestamp - latestCall < 600000) { + return; + } + try { + const res = await api.get('/activities', { params: { timestamp } }); + + const promises = res.data.map(async (item) => { + const act = item.activity; + + //Since the API only returns numbers for dates + act.startDate = new Date(act.startDate); + if (act.endDate) { + act.endDate = new Date(act.endDate); + } + + await prisma.prismaActivity.upsert({ + where: { id: act.id }, + update: act, + create: act, + }); + }); + + await Promise.all(promises); + } catch { + throw new ServerError('Det gick inte att updatera aktiviteter från Orbi'); + } + latestCall = timestamp; +}; From 76c6438eb3592db5dd43dc18b7da8f8ce2c6e7cc Mon Sep 17 00:00:00 2001 From: Muncherkin <48158637+Muncherkin@users.noreply.github.com> Date: Mon, 6 May 2024 16:55:58 +0200 Subject: [PATCH 14/16] removed the garbage orbi integration and moved it to a microservice --- .env.example | 6 +-- prisma/schema.prisma | 7 +++- src/api/activity.api.ts | 36 +++++++++--------- src/api/ticket.api.ts | 22 +++++------ src/config.ts | 11 ------ src/resolvers/activity.resolver.ts | 24 +++++------- src/resolvers/ticket.resolver.ts | 16 ++++---- src/services/orbi.service.ts | 61 ------------------------------ 8 files changed, 54 insertions(+), 129 deletions(-) delete mode 100644 src/services/orbi.service.ts diff --git a/.env.example b/.env.example index 0dde9649..81834ad8 100644 --- a/.env.example +++ b/.env.example @@ -27,8 +27,4 @@ WIKI_PASSWORD= WIKI_BASE_URL=https://wiki.esek.se SKIP_ACCESS_CHECKS=false -POST_ACCESS_COOLDOWN_DAYS=90 - -# Orbi service -ORBI=http://localhost:6970 -ORBI_API_TOKEN= \ No newline at end of file +POST_ACCESS_COOLDOWN_DAYS=90 \ No newline at end of file diff --git a/prisma/schema.prisma b/prisma/schema.prisma index e6c6233f..28e1f2a9 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -190,7 +190,10 @@ model PrismaActivity { 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 { @@ -200,7 +203,9 @@ model PrismaTicket { price Int? currency String? activity PrismaActivity? @relation(fields: [activityID], references: [id]) - activityID String? + activityID String? @map("activity_id") + + @@map("tickets") } model PrismaProposal { diff --git a/src/api/activity.api.ts b/src/api/activity.api.ts index c08371fa..bdaecee3 100644 --- a/src/api/activity.api.ts +++ b/src/api/activity.api.ts @@ -8,24 +8,24 @@ import prisma from './prisma'; export class ActivityAPI { async getActivity(id: string): Promise { - const a = await prisma.prismaActivity.findFirst({ + const activity = await prisma.prismaActivity.findFirst({ where: { id, }, }); - if (a == null) { + if (activity == null) { throw new NotFoundError('Kunde inte hitta denna aktivitet!'); } - return a; + return activity; } async getActivities( from: Date, to: Date, utskott: Utskott[] = [Utskott.Other], ): Promise { - const a = await prisma.prismaActivity.findMany({ + const activities = await prisma.prismaActivity.findMany({ where: { startDate: { lte: to, @@ -43,7 +43,7 @@ export class ActivityAPI { orderBy: { startDate: 'asc' }, }); - return a; + return activities; } async addActivity(activity: NewActivity): Promise { @@ -61,7 +61,7 @@ export class ActivityAPI { throw new BadRequestError('Sluttid för aktivitet är före starttid!'); } - const res = await prisma.prismaActivity.create({ + const addedActivity = await prisma.prismaActivity.create({ data: { source: PrismaActivitySource.WEBSITE, title: activity.title, @@ -75,10 +75,10 @@ export class ActivityAPI { }, }); - return res; + return addedActivity; } async modifyActivity(id: string, entry: ModifiedActivity): Promise { - const a = await this.getActivity(id); + const activity = await this.getActivity(id); const isAcceptableTime = () => { const { startDate, endDate } = entry; @@ -87,12 +87,12 @@ export class ActivityAPI { if (startDate) { return endDate.getTime() - startDate.getTime() >= 0; } - return endDate.getTime() - a.startDate.getTime() >= 0; + return endDate.getTime() - activity.startDate.getTime() >= 0; } else if (startDate) { - if (!a.endDate) { + if (!activity.endDate) { return true; } - return a.endDate.getTime() - startDate.getTime() >= 0; + return activity.endDate.getTime() - startDate.getTime() >= 0; } return true; @@ -102,7 +102,7 @@ export class ActivityAPI { throw new BadRequestError('Ny slut- och starttid för aktivitet är omöjlig!'); } - if (a.source !== PrismaActivitySource.WEBSITE) { + if (activity.source !== PrismaActivitySource.WEBSITE) { throw new BadRequestError( 'Ej tillåtet att ändra i evenemang som inte är skapade på hemsidan!', ); @@ -118,23 +118,23 @@ export class ActivityAPI { const update: StrictObject = stripObject(refact); - const res = await prisma.prismaActivity.update({ + const modifiedActivity = await prisma.prismaActivity.update({ data: { ...update }, where: { id }, }); - return res; + return modifiedActivity; } async removeActivity(id: string): Promise { - const a = await this.getActivity(id); - if (a.source !== PrismaActivitySource.WEBSITE) { + 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 res = await prisma.prismaActivity.delete({ where: { id } }); + const removedActivity = await prisma.prismaActivity.delete({ where: { id } }); - return res; + return removedActivity; } async clear() { diff --git a/src/api/ticket.api.ts b/src/api/ticket.api.ts index 6d82a3d6..094a539a 100644 --- a/src/api/ticket.api.ts +++ b/src/api/ticket.api.ts @@ -8,13 +8,13 @@ import prisma from './prisma'; export class TicketAPI { async getTicket(id: string): Promise { - const t = await prisma.prismaTicket.findFirst({ where: { id } }); + const ticket = await prisma.prismaTicket.findFirst({ where: { id } }); - if (t == null) { + if (ticket == null) { throw new NotFoundError('Kunde inte hitta den biljetten!'); } - return t; + return ticket; } async getTickets(activityID: string | null | undefined): Promise { @@ -22,17 +22,17 @@ export class TicketAPI { if (activityID != null) { whereAnd.push({ activityID: activityID }); } - const t = await prisma.prismaTicket.findMany({ + const tickets = await prisma.prismaTicket.findMany({ where: { AND: whereAnd, }, }); - return t; + return tickets; } async addTicket(ticket: NewTicket): Promise { - const t = await prisma.prismaTicket.create({ + const addedTicket = await prisma.prismaTicket.create({ data: { name: ticket.name, count: ticket.count, @@ -42,24 +42,24 @@ export class TicketAPI { }, }); - return t; + return addedTicket; } async modifyTicket(id: string, entry: ModifiedTicket): Promise { const { ...rest } = entry; const update: StrictObject = stripObject(rest); - const t = prisma.prismaTicket.update({ + const modifiedTicket = prisma.prismaTicket.update({ data: { ...update }, where: { id }, }); - return t; + return modifiedTicket; } async removeTicket(id: string): Promise { - const t = await prisma.prismaTicket.delete({ where: { id } }); - return t; + const removedTicket = await prisma.prismaTicket.delete({ where: { id } }); + return removedTicket; } async clear() { diff --git a/src/config.ts b/src/config.ts index 17af8c26..df4d0e81 100644 --- a/src/config.ts +++ b/src/config.ts @@ -48,16 +48,6 @@ const JWT = { SECRET: (process.env.JWT_SECRET as string) ?? '', }; -/** - * Config for Orbi - our orbi API service - * @param {string} URL - The base URL for the Orbi API - * @param {string} API_TOKEN - The API token set as an env-variable in Orbi - */ -const ORBI = { - URL: process.env.ORBI ?? 'https://localhost:6970', - API_TOKEN: process.env.ORBI_API_TOKEN, -}; - const config = { PORT: parseInt(process.env.PORT ?? '3000', 10), HOST: process.env.HOST ?? '0.0.0.0', @@ -70,7 +60,6 @@ const config = { LU, WIKI, JWT, - ORBI, }; export default config; diff --git a/src/resolvers/activity.resolver.ts b/src/resolvers/activity.resolver.ts index e8971188..d81a2c2c 100644 --- a/src/resolvers/activity.resolver.ts +++ b/src/resolvers/activity.resolver.ts @@ -3,7 +3,6 @@ import { hasAccess, hasAuthenticated } from '@/util'; import { ActivityAPI } from '@api/activity'; import { Feature, Resolvers } from '@generated/graphql'; import { activityReducer } from '@reducer/activity'; -import { updateOrbiActivities } from '@service/orbi'; const activityApi = new ActivityAPI(); @@ -11,35 +10,32 @@ const activityresolver: Resolvers = { Query: { activity: async (_, { id }, ctx) => { await hasAuthenticated(ctx); - const a = await activityApi.getActivity(id); + const activity = await activityApi.getActivity(id); - return activityReducer(a); + return activityReducer(activity); }, activities: async (_, { from, to, utskott }, ctx) => { await hasAuthenticated(ctx); - //async call to update orbi because it is - //painfully slow - updateOrbiActivities(); - const a = await activityApi.getActivities(from, to, utskott); + const activities = await activityApi.getActivities(from, to, utskott); - return reduce(a, activityReducer); + return reduce(activities, activityReducer); }, }, Mutation: { addActivity: async (_, { activity }, ctx) => { await hasAccess(ctx, Feature.ActivityAdmin); - const a = await activityApi.addActivity(activity); - return activityReducer(a); + const addedActivity = await activityApi.addActivity(activity); + return activityReducer(addedActivity); }, modifyActivity: async (_, { id, entry }, ctx) => { await hasAccess(ctx, Feature.ActivityAdmin); - const a = await activityApi.modifyActivity(id, entry); - return activityReducer(a); + const modifiedActivity = await activityApi.modifyActivity(id, entry); + return activityReducer(modifiedActivity); }, removeActivity: async (_, { id }, ctx) => { await hasAccess(ctx, Feature.ActivityAdmin); - const a = await activityApi.removeActivity(id); - return activityReducer(a); + const removedActivity = await activityApi.removeActivity(id); + return activityReducer(removedActivity); }, }, }; diff --git a/src/resolvers/ticket.resolver.ts b/src/resolvers/ticket.resolver.ts index 2779c262..59fe07cd 100644 --- a/src/resolvers/ticket.resolver.ts +++ b/src/resolvers/ticket.resolver.ts @@ -16,28 +16,28 @@ const ticketResolver: Resolvers = { }, tickets: async (_, { activityID }, ctx) => { await hasAuthenticated(ctx); - const t = await tikcketApi.getTickets(activityID); + const tickets = await tikcketApi.getTickets(activityID); - return reduce(t, ticketReducer); + return reduce(tickets, ticketReducer); }, }, Mutation: { addTicket: async (_, { ticket }, ctx) => { await hasAccess(ctx, Feature.ActivityAdmin); - const t = await tikcketApi.addTicket(ticket); - return ticketReducer(t); + const addedTicket = await tikcketApi.addTicket(ticket); + return ticketReducer(addedTicket); }, modifyTicket: async (_, { id, entry }, ctx) => { await hasAccess(ctx, Feature.ActivityAdmin); - const t = await tikcketApi.modifyTicket(id, entry); - return ticketReducer(t); + const modifiedTicket = await tikcketApi.modifyTicket(id, entry); + return ticketReducer(modifiedTicket); }, removeTicket: async (_, { id }, ctx) => { await hasAccess(ctx, Feature.ActivityAdmin); - const t = await tikcketApi.removeTicket(id); - return ticketReducer(t); + const removedTicket = await tikcketApi.removeTicket(id); + return ticketReducer(removedTicket); }, }, }; diff --git a/src/services/orbi.service.ts b/src/services/orbi.service.ts deleted file mode 100644 index 4cf5c879..00000000 --- a/src/services/orbi.service.ts +++ /dev/null @@ -1,61 +0,0 @@ -import prisma from '@/api/prisma'; -import config from '@/config'; -import { ServerError } from '@/errors/request.errors'; -import { PrismaActivity, PrismaTicket } from '@prisma/client'; -import axios from 'axios'; - -/* - Creates an axios-instance and sets the baseUrl and authorization header - to the corresponding values in the config -*/ - -const { - ORBI: { URL, API_TOKEN }, -} = config; - -const api = axios.create({ - baseURL: URL, - headers: { 'x-api-key': API_TOKEN }, -}); - -type OrbiResponse = [ - { - activity: PrismaActivity; - tickets: [PrismaTicket]; - departmentName: string; - }, -]; - -let latestCall = 0; - -export const updateOrbiActivities = async () => { - const timestamp = Date.now(); - //If latest call was < 10 minutes ago - if (timestamp - latestCall < 600000) { - return; - } - try { - const res = await api.get('/activities', { params: { timestamp } }); - - const promises = res.data.map(async (item) => { - const act = item.activity; - - //Since the API only returns numbers for dates - act.startDate = new Date(act.startDate); - if (act.endDate) { - act.endDate = new Date(act.endDate); - } - - await prisma.prismaActivity.upsert({ - where: { id: act.id }, - update: act, - create: act, - }); - }); - - await Promise.all(promises); - } catch { - throw new ServerError('Det gick inte att updatera aktiviteter från Orbi'); - } - latestCall = timestamp; -}; From dddbe3806054ed860a805d30b529d177a7365f09 Mon Sep 17 00:00:00 2001 From: Muncherkin <48158637+Muncherkin@users.noreply.github.com> Date: Fri, 7 Jun 2024 13:14:56 +0200 Subject: [PATCH 15/16] updated changelog --- CHANGELOG.MD | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.MD b/CHANGELOG.MD index d22232f4..3f594b59 100644 --- a/CHANGELOG.MD +++ b/CHANGELOG.MD @@ -5,10 +5,10 @@ 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.10.0] - 2024-03-28 +## [1.10.0] - 2024-06-07 ### Tillagt -- Prisma and graphql feature for activities (events) and tickets (to an separate from activities). +- Prisma and graphql feature for activities (events) and tickets. - Tests for activity reducer and API. - Test for ticket API. - DateTime scalar for graphql. From 13c70a90eb6f050ce98b2441f2e8179081c7c001 Mon Sep 17 00:00:00 2001 From: Muncherkin <48158637+Muncherkin@users.noreply.github.com> Date: Fri, 7 Jun 2024 13:23:59 +0200 Subject: [PATCH 16/16] update package.json and changelog after main merge --- package-lock.json | 4 +- package.json | 2 +- src/models/generated/graphql.ts | 1039 ++++++++----------------------- 3 files changed, 272 insertions(+), 773 deletions(-) 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 6550ee90..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": { diff --git a/src/models/generated/graphql.ts b/src/models/generated/graphql.ts index ab2dd6dd..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] }; @@ -44,13 +33,13 @@ 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 = { @@ -68,7 +57,7 @@ export type Activity = { export enum ActivitySource { Orbi = 'ORBI', Other = 'OTHER', - Website = 'WEBSITE', + Website = 'WEBSITE' } export type ApiKey = { @@ -95,6 +84,7 @@ export type Article = { title: Scalars['String']; }; + /** Body is saved as HTML serversInte, but edited in MarkDown */ export type ArticleTagsArgs = { includeSpecial?: InputMaybe; @@ -103,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 = { @@ -127,7 +117,7 @@ export enum Door { Pa = 'pa', Pump = 'pump', Sikrit = 'sikrit', - Ulla = 'ulla', + Ulla = 'ulla' } export type DoorInfo = { @@ -164,7 +154,7 @@ 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 */ @@ -182,7 +172,7 @@ export enum Feature { NewsEditor = 'news_editor', PostAdmin = 'post_admin', Superadmin = 'superadmin', - UserAdmin = 'user_admin', + UserAdmin = 'user_admin' } export type FeatureInfo = { @@ -220,7 +210,7 @@ export enum FileType { Pdf = 'PDF', Powerpoint = 'POWERPOINT', Spreadsheet = 'SPREADSHEET', - Text = 'TEXT', + Text = 'TEXT' } export type GroupedPost = { @@ -296,7 +286,7 @@ export enum MeetingDocumentType { LateDocuments = 'lateDocuments', Protocol = 'protocol', /** Kallelse */ - Summons = 'summons', + Summons = 'summons' } export enum MeetingType { @@ -315,7 +305,7 @@ export enum MeetingType { /** Valmöte */ Vm = 'VM', /** Vårterminsmöte */ - Vtm = 'VTM', + Vtm = 'VTM' } export type ModifiedActivity = { @@ -429,55 +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']; @@ -485,218 +486,265 @@ 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']; }; @@ -768,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 = { @@ -821,6 +869,7 @@ export type Post = { utskott: Utskott; }; + export type PostHistoryArgs = { current?: InputMaybe; }; @@ -837,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 */ @@ -906,6 +955,7 @@ export type Query = { utskott: Utskott; }; + /** * Queries and mutations that relies on an election being open * does not take an `electionId` parameter. @@ -916,6 +966,7 @@ export type QueryActivitiesArgs = { utskott: Array; }; + /** * Queries and mutations that relies on an election being open * does not take an `electionId` parameter. @@ -924,6 +975,7 @@ export type QueryActivityArgs = { id: Scalars['String']; }; + /** * Queries and mutations that relies on an election being open * does not take an `electionId` parameter. @@ -932,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. @@ -941,6 +994,7 @@ export type QueryArticleArgs = { slug?: InputMaybe; }; + /** * Queries and mutations that relies on an election being open * does not take an `electionId` parameter. @@ -952,6 +1006,7 @@ export type QueryArticlesArgs = { type?: InputMaybe; }; + /** * Queries and mutations that relies on an election being open * does not take an `electionId` parameter. @@ -960,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. @@ -968,6 +1024,7 @@ export type QueryElectionsArgs = { electionIds: Array; }; + /** * Queries and mutations that relies on an election being open * does not take an `electionId` parameter. @@ -976,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. @@ -984,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. @@ -992,6 +1051,7 @@ export type QueryFilesArgs = { type?: InputMaybe; }; + /** * Queries and mutations that relies on an election being open * does not take an `electionId` parameter. @@ -1000,6 +1060,7 @@ export type QueryGroupedPostsArgs = { includeInactive?: InputMaybe; }; + /** * Queries and mutations that relies on an election being open * does not take an `electionId` parameter. @@ -1009,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. @@ -1017,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. @@ -1026,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. @@ -1034,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. @@ -1042,6 +1107,7 @@ export type QueryLatestBoardMeetingsArgs = { limit?: InputMaybe; }; + /** * Queries and mutations that relies on an election being open * does not take an `electionId` parameter. @@ -1052,6 +1118,7 @@ export type QueryLatestElectionsArgs = { limit?: InputMaybe; }; + /** * Queries and mutations that relies on an election being open * does not take an `electionId` parameter. @@ -1061,6 +1128,7 @@ export type QueryLatestHeheArgs = { sortOrder?: InputMaybe; }; + /** * Queries and mutations that relies on an election being open * does not take an `electionId` parameter. @@ -1069,6 +1137,7 @@ export type QueryLatestnewsArgs = { limit?: InputMaybe; }; + /** * Queries and mutations that relies on an election being open * does not take an `electionId` parameter. @@ -1077,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. @@ -1087,6 +1157,7 @@ export type QueryMeetingsArgs = { year?: InputMaybe; }; + /** * Queries and mutations that relies on an election being open * does not take an `electionId` parameter. @@ -1096,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. @@ -1106,6 +1178,7 @@ export type QueryNewsentriesArgs = { before?: InputMaybe; }; + /** * Queries and mutations that relies on an election being open * does not take an `electionId` parameter. @@ -1114,6 +1187,7 @@ export type QueryNumberOfMembersArgs = { noAlumni?: InputMaybe; }; + /** * Queries and mutations that relies on an election being open * does not take an `electionId` parameter. @@ -1123,6 +1197,7 @@ export type QueryNumberOfNominationsArgs = { postId?: InputMaybe; }; + /** * Queries and mutations that relies on an election being open * does not take an `electionId` parameter. @@ -1132,6 +1207,7 @@ export type QueryNumberOfProposalsArgs = { postId?: InputMaybe; }; + /** * Queries and mutations that relies on an election being open * does not take an `electionId` parameter. @@ -1140,6 +1216,16 @@ export type QueryNumberOfVolunteersArgs = { date?: InputMaybe; }; + +/** + * Queries and mutations that relies on an election being open + * does not take an `electionId` parameter. + */ +export type QueryPaginatedHehesArgs = { + pagination?: InputMaybe; +}; + + /** * Queries and mutations that relies on an election being open * does not take an `electionId` parameter. @@ -1148,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. @@ -1156,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. @@ -1165,6 +1253,7 @@ export type QueryPostsArgs = { utskott?: InputMaybe; }; + /** * Queries and mutations that relies on an election being open * does not take an `electionId` parameter. @@ -1173,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. @@ -1181,6 +1271,7 @@ export type QuerySearchUserArgs = { search: Scalars['String']; }; + /** * Queries and mutations that relies on an election being open * does not take an `electionId` parameter. @@ -1189,6 +1280,7 @@ export type QueryTicketArgs = { id: Scalars['String']; }; + /** * Queries and mutations that relies on an election being open * does not take an `electionId` parameter. @@ -1197,6 +1289,7 @@ export type QueryTicketsArgs = { activityID?: InputMaybe; }; + /** * Queries and mutations that relies on an election being open * does not take an `electionId` parameter. @@ -1205,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. @@ -1213,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. @@ -1221,6 +1316,7 @@ export type QueryUsersArgs = { usernames: Array; }; + /** * Queries and mutations that relies on an election being open * does not take an `electionId` parameter. @@ -1239,7 +1335,7 @@ export type SendEmailOptions = { export enum SortOrder { Asc = 'asc', - Desc = 'desc', + Desc = 'desc' } export type Ticket = { @@ -1291,6 +1387,7 @@ export type User = { zipCode?: Maybe; }; + export type UserPostHistoryArgs = { current?: InputMaybe; }; @@ -1313,7 +1410,7 @@ export enum Utskott { Other = 'OTHER', Pengu = 'PENGU', Sre = 'SRE', - Styrelsen = 'STYRELSEN', + Styrelsen = 'STYRELSEN' } export type WithIndex = TObject & Record; @@ -1321,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; } @@ -1369,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; @@ -1398,7 +1478,7 @@ export type DirectiveResolverFn TResult | Promise; /** Mapping between all available schema types and the resolvers types */ @@ -1424,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; @@ -1456,9 +1534,7 @@ export type ResolversTypes = ResolversObject<{ Object: ResolverTypeWrapper; Order: Order; PageInfo: ResolverTypeWrapper; - PaginatedHehes: ResolverTypeWrapper< - Omit & { values: Array } - >; + PaginatedHehes: ResolverTypeWrapper & { values: Array }>; Pagination: ResolversTypes['PaginatedHehes']; PaginationParams: PaginationParams; Post: ResolverTypeWrapper; @@ -1493,9 +1569,7 @@ export type ResolversParentTypes = ResolversObject<{ EmergencyContact: EmergencyContact; FeatureInfo: FeatureInfo; File: FileResponse; - FileSystemResponse: Omit & { - files: Array; - }; + FileSystemResponse: Omit & { files: Array }; FileSystemResponsePath: FileSystemResponsePath; GroupedPost: GroupedPost; Hehe: HeheResponse; @@ -1536,19 +1610,13 @@ export type ResolversParentTypes = ResolversObject<{ 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 ActivityResolvers< - ContextType = Context, - ParentType extends ResolversParentTypes['Activity'] = ResolversParentTypes['Activity'], -> = ResolversObject<{ +export type ActivityResolvers = ResolversObject<{ description?: Resolver, ParentType, ContextType>; endDate?: Resolver, ParentType, ContextType>; id?: Resolver; @@ -1561,10 +1629,7 @@ export type ActivityResolvers< __isTypeOf?: IsTypeOfResolverFn; }>; -export type ApiKeyResolvers< - ContextType = Context, - ParentType extends ResolversParentTypes['ApiKey'] = ResolversParentTypes['ApiKey'], -> = ResolversObject<{ +export type ApiKeyResolvers = ResolversObject<{ access?: Resolver; creator?: Resolver; description?: Resolver; @@ -1572,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; @@ -1585,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; @@ -1609,29 +1663,17 @@ export interface DateScalarConfig extends GraphQLScalarTypeConfig { +export interface DateTimeScalarConfig extends GraphQLScalarTypeConfig { name: 'DateTime'; } -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; @@ -1645,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; @@ -1656,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>; @@ -1681,37 +1714,27 @@ 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; number?: Resolver; uploadedAt?: Resolver; @@ -1720,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; @@ -1731,19 +1751,13 @@ export type HistoryEntryResolvers< __isTypeOf?: IsTypeOfResolverFn; }>; -export type LocationResolvers< - ContextType = Context, - ParentType extends ResolversParentTypes['Location'] = ResolversParentTypes['Location'], -> = ResolversObject<{ +export type LocationResolvers = ResolversObject<{ link?: Resolver, ParentType, ContextType>; title?: Resolver; __isTypeOf?: IsTypeOfResolverFn; }>; -export type LoginProviderResolvers< - ContextType = Context, - ParentType extends ResolversParentTypes['LoginProvider'] = ResolversParentTypes['LoginProvider'], -> = ResolversObject<{ +export type LoginProviderResolvers = ResolversObject<{ email?: Resolver, ParentType, ContextType>; id?: Resolver; provider?: Resolver; @@ -1751,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>; @@ -1779,366 +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 - >; - addActivity?: Resolver< - ResolversTypes['Activity'], - 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 - >; - addTicket?: Resolver< - ResolversTypes['Ticket'], - 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; - modifyActivity?: Resolver< - ResolversTypes['Activity'], - ParentType, - ContextType, - RequireFields - >; - modifyArticle?: Resolver< - ResolversTypes['Article'], - ParentType, - ContextType, - RequireFields - >; - modifyPost?: Resolver< - ResolversTypes['Boolean'], - ParentType, - ContextType, - RequireFields - >; - modifyTicket?: Resolver< - ResolversTypes['Ticket'], - 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 - >; - removeActivity?: Resolver< - ResolversTypes['Activity'], - 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 - >; - removeTicket?: Resolver< - ResolversTypes['Ticket'], - 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; @@ -2149,10 +1861,7 @@ export interface ObjectScalarConfig extends GraphQLScalarTypeConfig = ResolversObject<{ +export type PageInfoResolvers = ResolversObject<{ firstPage?: Resolver; hasNextPage?: Resolver; hasPreviousPage?: Resolver; @@ -2161,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; @@ -2202,244 +1897,61 @@ 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<{ - activities?: Resolver< - Array, - ParentType, - ContextType, - RequireFields - >; - activity?: Resolver< - ResolversTypes['Activity'], - ParentType, - ContextType, - RequireFields - >; - 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; - 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 - >; - ticket?: Resolver< - ResolversTypes['Ticket'], - ParentType, - ContextType, - RequireFields - >; - tickets?: Resolver< - Array>, - ParentType, - ContextType, - Partial - >; - 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 TicketResolvers< - ContextType = Context, - ParentType extends ResolversParentTypes['Ticket'] = ResolversParentTypes['Ticket'], -> = ResolversObject<{ +export type TicketResolvers = ResolversObject<{ activityID?: Resolver, ParentType, ContextType>; count?: Resolver, ParentType, ContextType>; currency?: Resolver, ParentType, ContextType>; @@ -2449,19 +1961,13 @@ export type TicketResolvers< __isTypeOf?: IsTypeOfResolverFn; }>; -export type TokenResponseResolvers< - ContextType = Context, - ParentType extends ResolversParentTypes['TokenResponse'] = ResolversParentTypes['TokenResponse'], -> = ResolversObject<{ +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; @@ -2474,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>; @@ -2488,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; @@ -2534,3 +2032,4 @@ export type Resolvers = ResolversObject<{ User?: UserResolvers; UserPostHistoryEntry?: UserPostHistoryEntryResolvers; }>; +