diff --git a/.tmp/data.db b/.tmp/data.db index 4afa4f2..59d5d59 100644 Binary files a/.tmp/data.db and b/.tmp/data.db differ diff --git a/package.json b/package.json index dfa380c..e93403c 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,7 @@ "@strapi/plugin-graphql": "^5.1.0", "@strapi/plugin-users-permissions": "5.1.0", "@strapi/strapi": "5.1.0", + "@strapi/utils": "5.1.0", "better-sqlite3": "11.3.0", "react": "^18.0.0", "react-dom": "^18.0.0", diff --git a/src/api/booking/content-types/booking/schema.json b/src/api/booking/content-types/booking/schema.json index a1006f7..285ed1f 100644 --- a/src/api/booking/content-types/booking/schema.json +++ b/src/api/booking/content-types/booking/schema.json @@ -7,10 +7,16 @@ "displayName": "Booking" }, "options": { - "draftAndPublish": true + "draftAndPublish": false }, "pluginOptions": {}, "attributes": { + "ref_number": { + "type": "string", + "unique": true, + "required": true, + "configurable": false + }, "from": { "type": "datetime", "required": true diff --git a/src/api/booking/controllers/booking.ts b/src/api/booking/controllers/booking.ts index 8f4af8e..2e42c46 100644 --- a/src/api/booking/controllers/booking.ts +++ b/src/api/booking/controllers/booking.ts @@ -2,6 +2,100 @@ * booking controller */ -import { factories } from '@strapi/strapi' +import { factories } from '@strapi/strapi'; +import { errors } from '@strapi/utils'; -export default factories.createCoreController('api::booking.booking'); +export default factories.createCoreController('api::booking.booking', ({ strapi }) => ({ + // Validate the availability of a car within the following dates + async validateAvailability(ctx) { + const sanitizedQuery = await this.sanitizeQuery(ctx); + + const { from, to, car } = sanitizedQuery; + try { + return strapi.service('api::booking.booking').validateAvailability(from, to, car); + } catch (err) { + throw new errors.HttpError('Not available'); + } + }, + + // Extend the create booking method + async create(ctx, next) { + + // Validate availablity + await this.validateAvailability(ctx, next); + + // Before creation generate reference number + const refNumber = strapi.service('api::booking.booking').generateReferenceNumber(); + + const { car, ...restBody } = ctx.request.body.data; + + const nextCtx = { + ...ctx, + query: {}, // Clean the query to avoid any conflict + request: { + ...ctx.request, + body: { + ...ctx.request.body, + data: { + ...restBody, + ref_number: refNumber, + } + }, + }, + }; + + console.log(nextCtx.request.body) + + // Call the native create booking method + const { data } = await super.create(nextCtx); + const { documentId } = data; + + // Link booking to car + const bookedCar = await strapi.service('api::booking.booking').linkBookingToCar(documentId, car); + + // Link booking to user + const customer = await strapi.service('api::booking.booking').linkBookingToUser(documentId, ctx.state.user.documentId); + + console.log('bookedCar', bookedCar); + console.log('customer', customer); + const { ref_number } = data; + + console.log('data', data); + + // try { + // // Send a confirmation email + // await strapi.plugins['email'].services.email.send({ + // to: ctx.state.user.email, + // subject: `[#${ref_number}]Booking confirmation`, + // text: `Your booking number #${ref_number} has been confirmed` + // }); + // } catch (err) { + // console.error('Error sending email', err); + // } + + return { + data: { + ...data, + car: bookedCar, + customer, + }, + }; + }, + + // Method 3: Replacing a core action with proper sanitization + // async find(ctx) { + // // validateQuery (optional) + // // to throw an error on query params that are invalid or the user does not have access to + // await this.validateQuery(ctx); + + // // sanitizeQuery to remove any query params that are invalid or the user does not have access to + // // It is strongly recommended to use sanitizeQuery even if validateQuery is used + // const sanitizedQueryParams = await this.sanitizeQuery(ctx); + // const { results, pagination } = await strapi.service('api::restaurant.restaurant').find(sanitizedQueryParams); + + // // sanitizeOutput to ensure the user does not receive any data they do not have access to + // const sanitizedResults = await this.sanitizeOutput(results, ctx); + + // return this.transformResponse(sanitizedResults, { pagination }); + // } +})); \ No newline at end of file diff --git a/src/api/booking/routes/booking.ts b/src/api/booking/routes/booking.ts index 68165e5..4eeb982 100644 --- a/src/api/booking/routes/booking.ts +++ b/src/api/booking/routes/booking.ts @@ -4,4 +4,6 @@ import { factories } from '@strapi/strapi'; -export default factories.createCoreRouter('api::booking.booking'); + + +export default factories.createCoreRouter('api::booking.booking'); \ No newline at end of file diff --git a/src/api/booking/routes/utils.ts b/src/api/booking/routes/utils.ts new file mode 100644 index 0000000..3fc5234 --- /dev/null +++ b/src/api/booking/routes/utils.ts @@ -0,0 +1,13 @@ + +export default { + routes: [ + { + method: 'POST', + path: '/bookings/validate-availability', + handler: 'api::booking.booking.validateAvailability', + config: { + auth: false, + }, + }, + ], + }; \ No newline at end of file diff --git a/src/api/booking/services/booking.ts b/src/api/booking/services/booking.ts index e956352..644e700 100644 --- a/src/api/booking/services/booking.ts +++ b/src/api/booking/services/booking.ts @@ -2,6 +2,58 @@ * booking service */ -import { factories } from '@strapi/strapi'; +import { factories, type Data } from '@strapi/strapi'; -export default factories.createCoreService('api::booking.booking'); +export default factories.createCoreService('api::booking.booking', ({ strapi }) => ({ + + // Generate unique reference number + generateReferenceNumber(): string { + return Date.now().toString(36).toUpperCase(); + }, + + + // Validate the availability of a car within the following dates + async validateAvailability(from: string, to: string, id: Data.DocumentID): Promise { + const bookings = await strapi.documents('api::booking.booking').findMany({ + filters: { + car: { + documentId: id + }, + from: { $lt: new Date(to) }, + to: { $gt: new Date(from) } + }, + }); + + console.log('bookings', bookings); + + return !bookings.length; + }, + + // Link booking to car + async linkBookingToCar(booking: Data.DocumentID, car: Data.DocumentID): Promise { + // Link booking to car + return strapi.documents('api::car.car').update({ + documentId: car, + data: { + bookings: { + connect: [booking], + }, + }, + status: 'published', + }); + }, + + // Link booking to user + async linkBookingToUser(booking: Data.DocumentID, user: Data.DocumentID): Promise { + return strapi.documents('plugin::users-permissions.user').update({ + documentId: user, + data: { + bookings: { + connect: [booking], + }, + }, + status: 'published', + }); + }, + +})); diff --git a/types/generated/contentTypes.d.ts b/types/generated/contentTypes.d.ts index ee61437..8f65c53 100644 --- a/types/generated/contentTypes.d.ts +++ b/types/generated/contentTypes.d.ts @@ -8,9 +8,12 @@ export interface ApiBookingBooking extends Struct.CollectionTypeSchema { displayName: 'Booking'; }; options: { - draftAndPublish: true; + draftAndPublish: false; }; attributes: { + ref_number: Schema.Attribute.String & + Schema.Attribute.Required & + Schema.Attribute.Unique; from: Schema.Attribute.DateTime & Schema.Attribute.Required; to: Schema.Attribute.DateTime; car: Schema.Attribute.Relation<'manyToOne', 'api::car.car'>;