Skip to content

Commit

Permalink
✨ feat(cabins): admin queries, add feedback and questions to booking (#…
Browse files Browse the repository at this point in the history
…512)

* ✨ feat(cabins): find many bookings, booking questions and feedback

* chore: add debug config
  • Loading branch information
larwaa authored Mar 6, 2024
1 parent ea7e8ea commit e43ea2b
Show file tree
Hide file tree
Showing 24 changed files with 723 additions and 188 deletions.
12 changes: 12 additions & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"version": "0.2.0",
"configurations": [
{
"command": "pnpm dev",
"name": "Run pnpm dev",
"request": "launch",
"type": "node-terminal",
"skipFiles": ["<node_internals>/**"]
}
]
}
18 changes: 11 additions & 7 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
{
"editor.formatOnSave": true,
"[typescript][javascript][json][typescriptreact]": {
"editor.defaultFormatter": "biomejs.biome"
},
"[prisma]": {
"editor.defaultFormatter": "Prisma.prisma"
}
"editor.formatOnSave": true,
"[typescript][javascript][json][typescriptreact]": {
"editor.defaultFormatter": "biomejs.biome",
"editor.codeActionsOnSave": {
"quickfix.biome": "explicit",
"source.organizeImports.biome": "explicit"
}
},
"[prisma]": {
"editor.defaultFormatter": "Prisma.prisma"
}
}
15 changes: 3 additions & 12 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -117,18 +117,9 @@
},
"pnpm": {
"supportedArchitectures": {
"os": [
"current",
"linux"
],
"cpu": [
"current",
"x64"
],
"libc": [
"current",
"glibc"
]
"os": ["current", "linux"],
"cpu": ["current", "x64"],
"libc": ["current", "glibc"]
}
}
}
2 changes: 2 additions & 0 deletions prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,8 @@ model Booking {
totalCost Int
internalParticipantsCount Int
externalParticipantsCount Int
questions String @default("")
feedback String @default("")
}

enum Semester {
Expand Down
50 changes: 40 additions & 10 deletions schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,27 @@ type AddMemberResponse {
}

type Booking {
cabins: [Cabin]!
cabins: [Cabin!]!
createdAt: DateTime!
email: String!
endDate: DateTime!

"""
Feedback from the cabin administrators to the user, e.g. why a booking was rejected
"""
feedback: String!
firstName: String!
id: ID!
lastName: String!
phoneNumber: String!

"""Questions/comments from the user to the cabin administrators"""
questions: String!
startDate: DateTime!
status: Status!
status: BookingStatus!

"""Total cost of the booking, in NOK"""
totalCost: Int!
}

type BookingContact {
Expand All @@ -56,6 +68,22 @@ type BookingSemestersResponse {
spring: BookingSemester
}

enum BookingStatus {
CANCELLED
CONFIRMED
PENDING
REJECTED
}

input BookingsInput {
status: BookingStatus
}

type BookingsResponse {
bookings: [Booking!]!
total: Int!
}

type Cabin {
capacity: Int!
externalPrice: Int!
Expand Down Expand Up @@ -642,6 +670,9 @@ input NewBookingInput {
internalParticipantsCount: Int!
lastName: String!
phoneNumber: String!

"""Questions/comments from the user to the cabin administrators"""
questions: String
startDate: DateTime!
}

Expand Down Expand Up @@ -935,6 +966,11 @@ type PublicUser {
type Query {
bookingContact: BookingContactResponse!
bookingSemesters: BookingSemestersResponse!

"""
Find all bookings, requires that the user is in an organization with the CABIN_ADMIN permission.
"""
bookings(data: BookingsInput): BookingsResponse!
cabins: CabinsResponse!
categories: EventCategoriesResponse!
event(data: EventInput!): EventResponse!
Expand Down Expand Up @@ -1140,13 +1176,6 @@ type SignUpsWithTotalCount {
total: Int!
}

enum Status {
CANCELLED
CONFIRMED
PENDING
REJECTED
}

type StudyProgram {
externalId: String!
id: ID!
Expand Down Expand Up @@ -1223,8 +1252,9 @@ type UpdateBookingSemesterResponse {
}

input UpdateBookingStatusInput {
feedback: String
id: ID!
status: Status!
status: BookingStatus!
}

input UpdateCategoriesInput {
Expand Down
12 changes: 12 additions & 0 deletions src/domain/cabins.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ interface BookingType {
totalCost: number;
internalParticipantsCount: number;
externalParticipantsCount: number;
createdAt: Date;
questions: string;
feedback: string;
}

class Booking implements BookingType {
Expand All @@ -36,7 +39,13 @@ class Booking implements BookingType {
this.totalCost = params.totalCost;
this.internalParticipantsCount = params.internalParticipantsCount;
this.externalParticipantsCount = params.externalParticipantsCount;
this.createdAt = params.createdAt;
this.questions = params.questions;
this.feedback = params.feedback;
}
feedback: string;
questions: string;
createdAt: Date;
internalParticipantsCount: number;
externalParticipantsCount: number;
totalCost: number;
Expand Down Expand Up @@ -64,6 +73,9 @@ type NewBookingParams = {
totalCost: number;
internalParticipantsCount: number;
externalParticipantsCount: number;
createdAt: Date;
questions: string;
feedback: string;
};

export { Booking };
Expand Down
4 changes: 4 additions & 0 deletions src/graphql/cabins/resolvers/BookingsResponse.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import type { BookingsResponseResolvers } from "./../../types.generated.js";
export const BookingsResponse: BookingsResponseResolvers = {
/* Implement BookingsResponse resolver logic here */
};
2 changes: 2 additions & 0 deletions src/graphql/cabins/resolvers/Mutation/newBooking.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export const newBooking: NonNullable<MutationResolvers["newBooking"]> = async (
phoneNumber,
internalParticipantsCount,
externalParticipantsCount,
questions,
} = data;

const newBookingResult = await ctx.cabins.newBooking(ctx, {
Expand All @@ -26,6 +27,7 @@ export const newBooking: NonNullable<MutationResolvers["newBooking"]> = async (
phoneNumber,
internalParticipantsCount,
externalParticipantsCount,
questions,
});

if (!newBookingResult.ok) {
Expand Down
12 changes: 5 additions & 7 deletions src/graphql/cabins/resolvers/Mutation/updateBookingStatus.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,13 @@
import { assertIsAuthenticated } from "~/graphql/auth.js";
import type { MutationResolvers } from "./../../../types.generated.js";
export const updateBookingStatus: NonNullable<
MutationResolvers["updateBookingStatus"]
> = async (_parent, { data }, ctx) => {
assertIsAuthenticated(ctx);
const { id, status } = data;
const updateBookingStatusResult = await ctx.cabins.updateBookingStatus(
ctx,
id,
const { id, status, feedback } = data;
const updateBookingStatusResult = await ctx.cabins.updateBookingStatus(ctx, {
bookingId: id,
status,
);
feedback,
});
if (!updateBookingStatusResult.ok) {
throw updateBookingStatusResult.error;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,41 +1,12 @@
import { faker } from "@faker-js/faker";
import { mock } from "jest-mock-extended";
import type { BookingType } from "~/domain/cabins.js";
import { errorCodes } from "~/domain/errors.js";
import { createMockApolloServer } from "~/graphql/test-clients/mock-apollo-server.js";
import { graphql } from "~/graphql/test-clients/unit/gql.js";

describe("Cabin mutations", () => {
describe("updateBookingStatus", () => {
it("should raise PermissionDeniedError if the user is not authenticated", async () => {
const { client } = createMockApolloServer();

const { errors } = await client.mutate({
mutation: graphql(`
mutation UpdateBookingStatus($data: UpdateBookingStatusInput!) {
updateBookingStatus(data: $data) {
booking {
id
}
}
}
`),
variables: {
data: {
id: faker.string.uuid(),
status: "PENDING",
},
},
});
expect(errors).toBeDefined();
expect(
errors?.some(
(err) => err.extensions?.code === errorCodes.ERR_PERMISSION_DENIED,
),
).toBe(true);
});

it("should call updateBookingStatus with the correct arugments if authenticated", async () => {
it("calls updateBookingStatus with parameters", async () => {
const { client, cabinService, createMockContext } =
createMockApolloServer();

Expand Down Expand Up @@ -64,6 +35,7 @@ describe("Cabin mutations", () => {
data: {
id,
status: "PENDING",
feedback: "feedback",
},
},
},
Expand All @@ -75,8 +47,11 @@ describe("Cabin mutations", () => {
expect(errors).toBeUndefined();
expect(cabinService.updateBookingStatus).toHaveBeenCalledWith(
expect.anything(),
id,
"PENDING",
{
bookingId: id,
status: "PENDING",
feedback: "feedback",
},
);
});
});
Expand Down
27 changes: 27 additions & 0 deletions src/graphql/cabins/resolvers/Query/bookings.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import type { QueryResolvers } from "./../../../types.generated.js";
export const bookings: NonNullable<QueryResolvers["bookings"]> = async (
_parent,
{ data },
ctx,
) => {
const result = await ctx.cabins.findManyBookings(ctx, {
bookingStatus: data?.status,
});

if (!result.ok) {
switch (result.error.name) {
case "PermissionDeniedError":
case "UnauthorizedError":
return {
bookings: [],
total: 0,
};
case "InternalServerError":
throw result.error;
}
}
return {
bookings: result.data.bookings,
total: result.data.total,
};
};
Loading

0 comments on commit e43ea2b

Please sign in to comment.