all repos — caroster @ d33867f4372a6c5c196e4ec4d90a10476d6d4e3b

[Octree] Group carpool to your event https://caroster.io

feat: :sparkles: Add graphql methods for event's admins management
Tim Izzo tim@octree.ch
Fri, 02 Feb 2024 14:34:18 +0000
commit

d33867f4372a6c5c196e4ec4d90a10476d6d4e3b

parent

25ef569b9aff78df82eea6d516cca3a4aeccbb8a

M backend/src/api/event/content-types/event/schema.jsonbackend/src/api/event/content-types/event/schema.json

@@ -71,6 +71,9 @@ "options": [

"caroster-plus" ], "customField": "plugin::multi-select.multi-select" + }, + "administrators": { + "type": "string" } } }
M backend/src/api/notification/content-types/notification/schema.jsonbackend/src/api/notification/content-types/notification/schema.json

@@ -4,7 +4,8 @@ "collectionName": "notifications",

"info": { "singularName": "notification", "pluralName": "notifications", - "displayName": "Notification" + "displayName": "Notification", + "description": "" }, "options": { "draftAndPublish": false

@@ -15,7 +16,8 @@ "type": {

"type": "enumeration", "enum": [ "NewPassengerInYourTrip", - "NewTrip" + "NewTrip", + "AddedAsAdmin" ], "required": true },
A backend/src/graphql/event/administrators.ts

@@ -0,0 +1,137 @@

+import { errors } from "@strapi/utils"; + +export default ({ nexus, strapi }) => ({ + types: [ + nexus.extendType({ + type: "Event", + definition(t) { + t.field("administrators", { + type: nexus.list("String"), + }); + }, + }), + nexus.mutationField("addEventAdmin", { + type: "EventEntityResponse", + args: { + eventId: nexus.nonNull("ID"), + email: nexus.nonNull("String"), + }, + }), + nexus.mutationField("deleteEventAdmin", { + type: "EventEntityResponse", + args: { + eventId: nexus.nonNull("ID"), + email: nexus.nonNull("String"), + }, + }), + ], + resolvers: { + Event: { + administrators: async (event) => event.administrators?.split(/, ?/), + }, + Mutation: { + addEventAdmin: { + async resolve(_root, args, context) { + // Retrieve targeted event + const event = await strapi.entityService.findOne( + "api::event.event", + args.eventId + ); + if (!event) throw new errors.NotFoundError(`Event not found`); + + const currentAdmins = event.administrators?.split(/, ?/) || []; + + // Check if user is authorized to add event admin + const user = context.state.user; + if (user.email !== event.email && !currentAdmins.includes(user.email)) + throw new errors.ForbiddenError(); + + // Add email to event's administrators list + const sanitizedEmail = args.email.replaceAll(",", ""); + const administrators = new Set([...currentAdmins, sanitizedEmail]); + const updatedEvent = await strapi.entityService.update( + "api::event.event", + args.eventId, + { + data: { + administrators: [...administrators].join(", "), + }, + } + ); + + // Create notification for targeted user + const targetedUser = await strapi.db + .query("plugin::users-permissions.user") + .findOne({ + where: { email: args.email }, + }); + if (targetedUser) { + strapi.entityService.create("api::notification.notification", { + data: { + type: "AddedAsAdmin", + event: args.eventId, + user: targetedUser.id, + }, + }); + } else + strapi.log.warn( + `No user with email '${args.email}'. Can't create notification AddedAsAdmin for event ${args.eventId}` + ); + + // Send formated response + const { toEntityResponse } = strapi + .plugin("graphql") + .service("format").returnTypes; + return toEntityResponse(updatedEvent, { + args, + resourceUID: "api::event.event", + }); + }, + }, + deleteEventAdmin: { + async resolve(_root, args, context) { + // Retrieve targeted event + const event = await strapi.entityService.findOne( + "api::event.event", + args.eventId + ); + if (!event) throw new errors.NotFoundError(`Event not found`); + + const currentAdmins = event.administrators?.split(/, ?/) || []; + + // Check if user is authorized to remove event admin + const user = context.state.user; + if (user.email !== event.email && !currentAdmins.includes(user.email)) + throw new errors.ForbiddenError(); + + // Remove email from event's administrators list + const administrators = currentAdmins + .filter((email) => email !== args.email) + .join(", "); + const updatedEvent = await strapi.entityService.update( + "api::event.event", + args.eventId, + { data: { administrators } } + ); + + // Send formated response + const { toEntityResponse } = strapi + .plugin("graphql") + .service("format").returnTypes; + return toEntityResponse(updatedEvent, { + args, + resourceUID: "api::event.event", + }); + }, + }, + }, + }, + resolversConfig: { + "Mutation.addEventAdmin": { + auth: true, + }, + "Mutation.deleteEventAdmin": { + auth: true, + }, + }, +});
A backend/src/graphql/event/event.ts

@@ -0,0 +1,134 @@

+export default ({ nexus, strapi }) => ({ + types: [ + nexus.extendType({ + type: "Event", + definition(t) { + t.field("waitingPassengers", { + type: "PassengerRelationResponseCollection", + }); + }, + }), + nexus.extendType({ + type: "Query", + definition(t) { + t.field("eventByUUID", { + type: "EventEntityResponse", + args: { + uuid: nexus.nonNull("String"), + }, + }); + }, + }), + nexus.extendType({ + type: "Mutation", + definition(t) { + t.field("updateEventByUUID", { + type: "EventEntityResponse", + args: { + uuid: nexus.nonNull("String"), + data: nexus.nonNull("EventInput"), + }, + }); + }, + }), + ], + resolvers: { + Event: { + waitingPassengers: async (root, args) => { + const waitingPassengers = await strapi + .service("api::event.event") + .getWaitingPassengers(root); + const { toEntityResponseCollection } = strapi + .plugin("graphql") + .service("format").returnTypes; + return toEntityResponseCollection(waitingPassengers, { + args, + resourceUID: "api::passenger.passenger", + }); + }, + }, + Query: { + eventByUUID: { + description: "Retrieve an event using its UUID", + async resolve(_root, args) { + const { uuid } = args; + const event = await strapi.db + .query("api::event.event") + .findOne({ where: { uuid } }); + if (!event) throw new Error("No matching event"); + const { toEntityResponse } = strapi + .plugin("graphql") + .service("format").returnTypes; + return toEntityResponse(event, { + args, + resourceUID: "api::event.event", + }); + }, + }, + }, + Mutation: { + updateEventByUUID: { + description: "Update an event using its UUID", + async resolve(_root, args) { + const { uuid, data: eventUpdate } = args; + + const updatedEvent = await strapi.db + .query("api::event.event") + .update({ where: { uuid }, data: eventUpdate }); + if (!updatedEvent) throw new Error("No matching event"); + + const { toEntityResponse } = strapi + .plugin("graphql") + .service("format").returnTypes; + return toEntityResponse(updatedEvent, { + args, + resourceUID: "api::event.event", + }); + }, + }, + createEvent: { + async resolve(_root, args, context) { + const { + koaContext, + state: { user }, + } = context; + + let eventData = args.data; + if (user) eventData = { ...eventData, users: [user.id] }; + + koaContext.request.body = eventData; + + const createdEvent = await strapi + .controller("api::event.event") + .create(koaContext); + + return { + value: createdEvent, + info: { args, resourceUID: "api::event.event" }, + }; + }, + }, + }, + }, + resolversConfig: { + "Query.eventByUUID": { + auth: { + scope: ["api::event.event.findOne"], + }, + }, + "Mutation.updateEventByUUID": { + auth: { + scope: ["api::event.event.update"], + }, + }, + "Event.passengers": { + auth: false, + }, + "Event.waitingPassengers": { + auth: false, + }, + "Event.travels": { + auth: false, + }, + }, +});
M backend/src/graphql/event/index.tsbackend/src/graphql/event/index.ts

@@ -1,136 +1,4 @@

-const updateEventExtension = ({ nexus, strapi }) => ({ - types: [ - nexus.extendType({ - type: "Event", - definition(t) { - t.field("waitingPassengers", { - type: "PassengerRelationResponseCollection", - }); - }, - }), - nexus.extendType({ - type: "Query", - definition(t) { - t.field("eventByUUID", { - type: "EventEntityResponse", - args: { - uuid: nexus.nonNull("String"), - }, - }); - }, - }), - nexus.extendType({ - type: "Mutation", - definition(t) { - t.field("updateEventByUUID", { - type: "EventEntityResponse", - args: { - uuid: nexus.nonNull("String"), - data: nexus.nonNull("EventInput"), - }, - }); - }, - }), - ], - resolvers: { - Event: { - waitingPassengers: async (root, args) => { - const waitingPassengers = await strapi - .service("api::event.event") - .getWaitingPassengers(root); - const { toEntityResponseCollection } = strapi - .plugin("graphql") - .service("format").returnTypes; - return toEntityResponseCollection(waitingPassengers, { - args, - resourceUID: "api::passenger.passenger", - }); - }, - }, - Query: { - eventByUUID: { - description: "Retrieve an event using its UUID", - async resolve(_root, args) { - const { uuid } = args; - const event = await strapi.db - .query("api::event.event") - .findOne({ where: { uuid } }); - if (!event) throw new Error("No matching event"); - const { toEntityResponse } = strapi - .plugin("graphql") - .service("format").returnTypes; - return toEntityResponse(event, { - args, - resourceUID: "api::event.event", - }); - }, - }, - }, - Mutation: { - updateEventByUUID: { - description: "Update an event using its UUID", - async resolve(_root, args) { - const { uuid, data: eventUpdate } = args; - - const updatedEvent = await strapi.db - .query("api::event.event") - .update({ where: { uuid }, data: eventUpdate }); - if (!updatedEvent) throw new Error("No matching event"); +import event from "./event"; +import administrators from "./administrators"; - const { toEntityResponse } = strapi - .plugin("graphql") - .service("format").returnTypes; - return toEntityResponse(updatedEvent, { - args, - resourceUID: "api::event.event", - }); - }, - }, - createEvent: { - async resolve(_root, args, context) { - const { - koaContext, - state: { user }, - } = context; - - let eventData = args.data; - if (user) eventData = { ...eventData, users: [user.id] }; - - koaContext.request.body = eventData; - - const createdEvent = await strapi - .controller("api::event.event") - .create(koaContext); - - return { - value: createdEvent, - info: { args, resourceUID: "api::event.event" }, - }; - }, - }, - }, - }, - resolversConfig: { - "Query.eventByUUID": { - auth: { - scope: ["api::event.event.findOne"], - }, - }, - "Mutation.updateEventByUUID": { - auth: { - scope: ["api::event.event.update"], - }, - }, - "Event.passengers": { - auth: false, - }, - "Event.waitingPassengers": { - auth: false, - }, - "Event.travels": { - auth: false, - }, - }, -}); - -export default [updateEventExtension]; +export default [event, administrators];
M backend/types/generated/contentTypes.d.tsbackend/types/generated/contentTypes.d.ts

@@ -890,6 +890,7 @@ Attribute.CustomField<

'plugin::multi-select.multi-select', ['caroster-plus'] >; + administrators: Attribute.String; createdAt: Attribute.DateTime; updatedAt: Attribute.DateTime; createdBy: Attribute.Relation<

@@ -995,12 +996,15 @@ info: {

singularName: 'notification'; pluralName: 'notifications'; displayName: 'Notification'; + description: ''; }; options: { draftAndPublish: false; }; attributes: { - type: Attribute.Enumeration<['NewPassengerInYourTrip', 'NewTrip']> & + type: Attribute.Enumeration< + ['NewPassengerInYourTrip', 'NewTrip', 'AddedAsAdmin'] + > & Attribute.Required; user: Attribute.Relation< 'api::notification.notification',
M frontend/locales/en.jsonfrontend/locales/en.json

@@ -111,6 +111,7 @@ "notifications.markAllRead": "Mark all in read",

"notifications.content": "No notification", "notification.type.NewPassengerInYourTrip.content": "A passenger has been added to your trip.", "notification.type.NewTrip.content": "A departure close to you is available.", + "notification.type.AddedAsAdmin.content": "You have been added as admin to an event.", "passenger.actions.place": "Assign", "passenger.actions.remove_alert": "Are you sure you want to remove <italic> <bold> {{name}} </bold> </italic> from the waitlist?", "passenger.availability.seats": "{{count}} seat available",
M frontend/locales/fr.jsonfrontend/locales/fr.json

@@ -110,7 +110,8 @@ "notifications.title": "Notifications",

"notifications.markAllRead": "Tout marquer en lu", "notifications.content": "Aucune notification", "notification.type.NewPassengerInYourTrip.content": "Un passager a été ajouté à votre trajet.", - "notification.type.NewTrip.content": "Un départ proche de vous est disponible. ", + "notification.type.NewTrip.content": "Un départ proche de vous est disponible.", + "notification.type.AddedAsAdmin.content": "Vous avez été ajouté en tant qu'admin à un événement.", "passenger.actions.place": "Placer", "passenger.actions.remove_alert": "Voulez-vous vraiment supprimer <italic><bold>{{name}}</bold></italic> de la liste d'attente ?", "passenger.availability.seats": "{{count}} place disponible",
M frontend/next.config.jsfrontend/next.config.js

@@ -41,8 +41,8 @@ source: '/api/nauth/:slug*',

destination: `/api/nauth/:slug*`, }, { - source: '/api/:slug*', - destination: `${STRAPI_URL}/api/:slug*`, + source: '/api/stripe', + destination: `${STRAPI_URL}/api/stripe`, }, { source: '/admin/:slug*',