all repos — caroster @ 5b2bff1c4921d7880400037670dac9c88258965a

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

refactor: 🎨 Always link passengers to an event
Tim Izzo tim@octree.ch
Mon, 26 Sep 2022 08:38:00 +0000
commit

5b2bff1c4921d7880400037670dac9c88258965a

parent

e22ee9c064d006eb9bd3af3cc9709ce4d28df633

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

@@ -55,7 +55,7 @@ "relation": "oneToMany",

"target": "api::travel.travel", "mappedBy": "event" }, - "waitingPassengers": { + "passengers": { "type": "relation", "relation": "oneToMany", "target": "api::passenger.passenger",
M backend/src/api/event/services/event.tsbackend/src/api/event/services/event.ts

@@ -8,6 +8,19 @@

export default factories.createCoreService( "api::event.event", ({ strapi }) => ({ + getWaitingPassengers: async (event) => { + return strapi.entityService.findMany("api::passenger.passenger", { + filters: { + event: { id: event.id }, + travel: { + id: { + $null: true, + }, + }, + }, + populate: ["passengers", "passengers.travel"], + }); + }, sendDailyRecap: async (event) => { const referenceDate = DateTime.now().minus({ day: 1 }); const hasBeenModified =

@@ -33,6 +46,10 @@ );

return null; } + const waitingPassengers = await strapi + .service("api::event.event") + .getWaitingPassengers(event); + await strapi .service("plugin::email-designer.email") .sendTemplatedEmail(

@@ -45,7 +62,7 @@ },

{ event, eventLink: `${STRAPI_URL}/e/${event.uuid}`, - waitingListCount: event.waitingPassengers?.length || 0, + waitingListCount: waitingPassengers?.length || 0, travelsCount: event.travels?.length || 0, newTravelsCount: newTravels?.length || 0, }
M backend/src/api/passenger/content-types/passenger/schema.jsonbackend/src/api/passenger/content-types/passenger/schema.json

@@ -25,12 +25,6 @@ },

"location": { "type": "string" }, - "event": { - "type": "relation", - "relation": "manyToOne", - "target": "api::event.event", - "inversedBy": "waitingPassengers" - }, "user": { "type": "relation", "relation": "manyToOne",

@@ -41,6 +35,12 @@ "travel": {

"type": "relation", "relation": "manyToOne", "target": "api::travel.travel", + "inversedBy": "passengers" + }, + "event": { + "type": "relation", + "relation": "manyToOne", + "target": "api::event.event", "inversedBy": "passengers" } }
M backend/src/api/travel/content-types/travel/lifecycles.tsbackend/src/api/travel/content-types/travel/lifecycles.ts

@@ -54,9 +54,10 @@

const sendEmailsToWaitingPassengers = async (travel, eventId: string) => { const event = await strapi.db.query("api::event.event").findOne({ where: { id: eventId }, - populate: ["waitingPassengers"], }); - const eventWaitingPassengers = event?.waitingPassengers || []; + const eventWaitingPassengers = await strapi + .service("api::event.event") + .getWaitingPassengers(event); const userEmails = eventWaitingPassengers .map((user) => user.email) .filter(Boolean);
M backend/src/graphql/event/index.tsbackend/src/graphql/event/index.ts

@@ -1,6 +1,14 @@

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", {

@@ -25,6 +33,20 @@ },

}), ], 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",
M backend/src/graphql/passenger/createPassenger.tsbackend/src/graphql/passenger/createPassenger.ts

@@ -1,75 +1,31 @@

+import pMap from "p-map"; + const createPassenger = { description: "Create a passenger", async resolve(_root, args) { const { data: passengerInput } = args; - - const { user: userId, travel: travelId } = passengerInput; + const { user: userId, event: eventId } = passengerInput; try { - //Avoid duplicity when the connected users add themself to a new car - if (userId && travelId) { - const travel = await strapi.entityService.findOne( - "api::travel.travel", - travelId, - { populate: ["event"] } - ); - - const userPassengersIds = ( + //Avoid duplicity when the connected users add themself + if (userId) { + const userPassengersInEvent: { id: string }[] = await strapi.entityService.findMany("api::passenger.passenger", { filters: { - user: userId, - }, - }) - ).map((userPassenger) => userPassenger.id); - - const travelsIdsBelongingToUserInEvent = ( - await strapi.entityService.findMany("api::travel.travel", { - filters: { - event: travel.event.id, - passengers: { id: { $containsi: userPassengersIds } }, - }, - }) - ).map((travel) => travel.id); - - const userDuplicatesinEvent = await strapi.entityService.findMany( - "api::passenger.passenger", - { - filters: { - $or: [ - { - event: travel.event.id, - user: userId, - }, - { - travel: { id: { $in: travelsIdsBelongingToUserInEvent } }, - user: userId, - }, - ], + event: eventId, }, - } - ); - - if (userDuplicatesinEvent.length > 0) { - const [existingPassenger, ...duplicated] = userDuplicatesinEvent; - - await Promise.all( - duplicated?.map(async (passenger) => { - await strapi.entityService.delete( - "api::passenger.passenger", - passenger.id - ); - }) - ); + }); - return makeResponse({ - operation: strapi.entityService.update( + // Delete existing passenger linked to the user in targeted event + await pMap( + userPassengersInEvent, + async (passenger) => + strapi.entityService.delete( "api::passenger.passenger", - existingPassenger.id, - { data: { ...passengerInput, event: null } } + passenger.id ), - args, - }); - } + { concurrency: 5 } + ); } return makeResponse({
A backend/src/migrations/passengers-event.ts

@@ -0,0 +1,43 @@

+const Strapi = require("@strapi/strapi"); + +const main = async () => { + const appContext = await Strapi.compile(); + await Strapi(appContext).load(); + + const passengers = await strapi.entityService.findMany( + "api::passenger.passenger", + { + limit: -1, + populate: ["event", "travel", "travel.event"], + } + ); + + const passengersWithoutEvent = passengers.filter( + (passenger) => !passenger.event + ); + + for (const passenger of passengersWithoutEvent) { + if (!passenger.travel) + throw new Error(`Passenger ${passenger.id} has no travel and no event`); + const eventId = passenger.travel.event?.id; + if (!eventId) + throw new Error(`Travel of passenger ${passenger.id} has no event`); + await strapi.entityService.update( + "api::passenger.passenger", + passenger.id, + { + data: { + event: eventId, + }, + } + ); + console.log(`Passenger ${passenger.id} linked to event ${eventId}`); + } + + process.exit(0); +}; + +main().catch((error) => { + console.error(error); + process.exit(1); +});
M e2e/graphql.tse2e/graphql.ts

@@ -168,6 +168,7 @@ date?: Maybe<Scalars['Date']>;

description?: Maybe<Scalars['String']>; email: Scalars['String']; name: Scalars['String']; + passengers?: Maybe<PassengerRelationResponseCollection>; position?: Maybe<Scalars['JSON']>; travels?: Maybe<TravelRelationResponseCollection>; updatedAt?: Maybe<Scalars['DateTime']>;

@@ -176,15 +177,15 @@ waitingPassengers?: Maybe<PassengerRelationResponseCollection>;

}; -export type EventTravelsArgs = { - filters?: InputMaybe<TravelFiltersInput>; +export type EventPassengersArgs = { + filters?: InputMaybe<PassengerFiltersInput>; pagination?: InputMaybe<PaginationArg>; sort?: InputMaybe<Array<InputMaybe<Scalars['String']>>>; }; -export type EventWaitingPassengersArgs = { - filters?: InputMaybe<PassengerFiltersInput>; +export type EventTravelsArgs = { + filters?: InputMaybe<TravelFiltersInput>; pagination?: InputMaybe<PaginationArg>; sort?: InputMaybe<Array<InputMaybe<Scalars['String']>>>; };

@@ -212,12 +213,12 @@ name?: InputMaybe<StringFilterInput>;

newsletter?: InputMaybe<BooleanFilterInput>; not?: InputMaybe<EventFiltersInput>; or?: InputMaybe<Array<InputMaybe<EventFiltersInput>>>; + passengers?: InputMaybe<PassengerFiltersInput>; position?: InputMaybe<JsonFilterInput>; travels?: InputMaybe<TravelFiltersInput>; updatedAt?: InputMaybe<DateTimeFilterInput>; users?: InputMaybe<UsersPermissionsUserFiltersInput>; uuid?: InputMaybe<StringFilterInput>; - waitingPassengers?: InputMaybe<PassengerFiltersInput>; }; export type EventInput = {

@@ -227,11 +228,11 @@ description?: InputMaybe<Scalars['String']>;

email?: InputMaybe<Scalars['String']>; name?: InputMaybe<Scalars['String']>; newsletter?: InputMaybe<Scalars['Boolean']>; + passengers?: InputMaybe<Array<InputMaybe<Scalars['ID']>>>; position?: InputMaybe<Scalars['JSON']>; travels?: InputMaybe<Array<InputMaybe<Scalars['ID']>>>; users?: InputMaybe<Array<InputMaybe<Scalars['ID']>>>; uuid?: InputMaybe<Scalars['String']>; - waitingPassengers?: InputMaybe<Array<InputMaybe<Scalars['ID']>>>; }; export type EventRelationResponseCollection = {

@@ -386,6 +387,7 @@ changePassword?: Maybe<UsersPermissionsLoginPayload>;

createEmailDesignerEmailTemplate?: Maybe<EmailDesignerEmailTemplateEntityResponse>; createEvent?: Maybe<EventEntityResponse>; createPage?: Maybe<PageEntityResponse>; + /** Create a passenger */ createPassenger?: Maybe<PassengerEntityResponse>; createSettingLocalization?: Maybe<SettingEntityResponse>; createTravel?: Maybe<TravelEntityResponse>;

@@ -1686,16 +1688,6 @@ }

} } ${MeFieldsFragmentDoc}`; -export const LoginDocument = gql` - mutation login($identifier: String!, $password: String!) { - login(input: {identifier: $identifier, password: $password}) { - jwt - user { - ...MeFields - } - } -} - ${MeFieldsFragmentDoc}`; export const ForgotPasswordDocument = gql` mutation forgotPassword($email: String!) { forgotPassword(email: $email) {

@@ -1877,9 +1869,6 @@ return {

register(variables: RegisterMutationVariables, requestHeaders?: Dom.RequestInit["headers"]): Promise<RegisterMutation> { return withWrapper((wrappedRequestHeaders) => client.request<RegisterMutation>(RegisterDocument, variables, {...requestHeaders, ...wrappedRequestHeaders}), 'register', 'mutation'); }, - login(variables: LoginMutationVariables, requestHeaders?: Dom.RequestInit["headers"]): Promise<LoginMutation> { - return withWrapper((wrappedRequestHeaders) => client.request<LoginMutation>(LoginDocument, variables, {...requestHeaders, ...wrappedRequestHeaders}), 'login', 'mutation'); - }, forgotPassword(variables: ForgotPasswordMutationVariables, requestHeaders?: Dom.RequestInit["headers"]): Promise<ForgotPasswordMutation> { return withWrapper((wrappedRequestHeaders) => client.request<ForgotPasswordMutation>(ForgotPasswordDocument, variables, {...requestHeaders, ...wrappedRequestHeaders}), 'forgotPassword', 'mutation'); },

@@ -1939,14 +1928,6 @@ }>;

export type RegisterMutation = { __typename?: 'Mutation', register: { __typename?: 'UsersPermissionsLoginPayload', jwt?: string | null, user: { __typename?: 'UsersPermissionsMe', id: string, username: string, email?: string | null, confirmed?: boolean | null } } }; - -export type LoginMutationVariables = Exact<{ - identifier: Scalars['String']; - password: Scalars['String']; -}>; - - -export type LoginMutation = { __typename?: 'Mutation', login: { __typename?: 'UsersPermissionsLoginPayload', jwt?: string | null, user: { __typename?: 'UsersPermissionsMe', id: string, username: string, email?: string | null, confirmed?: boolean | null } } }; export type ForgotPasswordMutationVariables = Exact<{ email: Scalars['String'];
M frontend/containers/NewPassengerDialog/AddPassengerToTravel.tsxfrontend/containers/NewPassengerDialog/AddPassengerToTravel.tsx

@@ -43,7 +43,7 @@ name,

}; try { - await addPassenger({...passenger, travel: travel.id}); + await addPassenger({...passenger, travel: travel.id, event: event.id}); addToEvent(event.id); addToast(t('passenger.success.added_to_car', {name})); toggle();
M frontend/containers/TravelColumns/index.tsxfrontend/containers/TravelColumns/index.tsx

@@ -42,6 +42,7 @@ user: userId,

email: profile.email, name: profile.username, travel: travel.id, + event: event.id, }); addToEvent(event.id); addToast(t('passenger.success.added_self_to_car'));