Skip to content

Commit

Permalink
Merge pull request #76 from jamalsoueidan/user-schedule-get-by-produc…
Browse files Browse the repository at this point in the history
…t-id

feat(openapi): add UserScheduleGetByProductIdResponse to openapi.yaml
  • Loading branch information
jamalsoueidan authored Nov 15, 2023
2 parents f246cc0 + a34c112 commit 5bc32f1
Show file tree
Hide file tree
Showing 15 changed files with 468 additions and 167 deletions.
6 changes: 5 additions & 1 deletion openapi/openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,8 @@ components:
$ref: paths/user/schedule/_types/product-with-locations.yaml
UserScheduleGetResponse:
$ref: paths/user/schedule/get/response.yaml
UserScheduleGetByProductIdResponse:
$ref: paths/user/schedule/get-by-product-id/response.yaml
UserGetResponse:
$ref: paths/user/get/response.yaml
UsersListResponse:
Expand Down Expand Up @@ -205,7 +207,9 @@ paths:
$ref: "./paths/user/user.yaml"
/user/{username}/products:
$ref: "./paths/user/products/list/index.yaml"
/user/{username}/schedules:
/user/{username}/schedule/get-by-product-id/{productId}:
$ref: paths/user/schedule/get-by-product-id/index.yaml
/user/{username}/schedules/locations:
$ref: "./paths/user/schedule/list/index.yaml"
/user/{username}/schedule/{scheduleId}/location/{locationId}:
$ref: "./paths/user/schedule/get/index.yaml"
Expand Down
37 changes: 37 additions & 0 deletions openapi/paths/user/schedule/get-by-product-id/index.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
get:
parameters:
- name: username
in: path
description: username
required: true
schema:
type: string
- name: productId
in: path
description: product Id
required: true
schema:
type: string
tags:
- UserSchedule
operationId: userScheduleGetByProductId
summary: GET Get user schedule
description: This endpoint gets user schedule object by product id
responses:
"200":
description: Response with payload
content:
application/json:
schema:
$ref: "./response.yaml"

"400":
$ref: "../../../../responses/bad.yaml"
"401":
$ref: "../../../../responses/unauthorized.yaml"
"403":
$ref: "../../../../responses/forbidden.yaml"
"404":
$ref: "../../../../responses/not-found.yaml"

security: []
18 changes: 18 additions & 0 deletions openapi/paths/user/schedule/get-by-product-id/response.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
type: object
properties:
success:
type: boolean
example: true
payload:
allOf:
- $ref: ../_types/schedule-with-locations.yaml
- type: object
properties:
product:
$ref: ../_types/product-with-locations.yaml
required:
- product

required:
- success
- payload
23 changes: 19 additions & 4 deletions src/functions/user.function.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ import "module-alias/register";
import { app } from "@azure/functions";
import { UserProductsControllerList } from "./user/controllers/products/list";
import { UserScheduleControllerGet } from "./user/controllers/schedule/get";
import { UserScheduleControllerList } from "./user/controllers/schedule/list";
import { UserScheduleControllerGetByProductId } from "./user/controllers/schedule/get-by-product";
import { UserScheduleControllerLocationsList } from "./user/controllers/schedule/locations-list";
import { UserControllerGet } from "./user/controllers/user/get";
import { UserControllerList } from "./user/controllers/user/list";
import { UserControllerProfessions } from "./user/controllers/user/professions";
Expand Down Expand Up @@ -36,11 +37,25 @@ app.http("userScheduleGet", {
handler: UserScheduleControllerGet,
});

app.http("userSchedulesList", {
app.http("userScheduleGetByProductId", {
methods: ["GET"],
authLevel: "anonymous",
route: "user/{username}/schedules",
handler: UserScheduleControllerList,
route: "user/{username}/schedule/get-by-product-id/{productId}",
handler: UserScheduleControllerGetByProductId,
});

app.http("userSchedulesLocationsList", {
methods: ["GET"],
authLevel: "anonymous",
route: "user/{username}/schedules/locations",
handler: UserScheduleControllerLocationsList,
});

app.http("userSchedulesLocationsListBySchedule", {
methods: ["GET"],
authLevel: "anonymous",
route: "user/{username}/schedules/locations",
handler: UserScheduleControllerLocationsList,
});

app.http("userProductsList", {
Expand Down
31 changes: 31 additions & 0 deletions src/functions/user/controllers/schedule/get-by-product.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { z } from "zod";

import { _ } from "~/library/handler";
import { NumberOrStringType } from "~/library/zod";
import { UserScheduleServiceGetByProductId } from "../../services/schedule/get-by-product";
import { UserServiceGetCustomerId } from "../../services/user";

export type UserScheduleControllerGetByProductIdRequest = {
query: z.infer<typeof UserScheduleControllerGetByProductIdQuerySchema>;
};

const UserScheduleControllerGetByProductIdQuerySchema = z.object({
username: z.string(),
productId: NumberOrStringType,
});

export type UserScheduleControllerGetByProductIdResponse = Awaited<
ReturnType<typeof UserScheduleServiceGetByProductId>
>;

export const UserScheduleControllerGetByProductId = _(
async ({ query }: UserScheduleControllerGetByProductIdRequest) => {
const validateQuery =
UserScheduleControllerGetByProductIdQuerySchema.parse(query);
const user = await UserServiceGetCustomerId(validateQuery);
return UserScheduleServiceGetByProductId({
productId: validateQuery.productId,
customerId: user.customerId,
});
}
);
2 changes: 1 addition & 1 deletion src/functions/user/controllers/schedule/get.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { z } from "zod";
import { ScheduleZodSchema } from "~/functions/schedule";

import { _ } from "~/library/handler";
import { UserScheduleServiceGet } from "../../services/schedule";
import { UserScheduleServiceGet } from "../../services/schedule/get";

export type UserScheduleControllerGetRequest = {
query: z.infer<typeof UserScheduleControllerGetQuerySchema>;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,27 +1,26 @@
import { z } from "zod";

import { _ } from "~/library/handler";
import { UserScheduleServiceLocationsList } from "../../services/schedule";
import { UserScheduleServiceLocationsList } from "../../services/schedule/locations/locations-list";
import { UserServiceGet } from "../../services/user";

export type UserScheduleControllerListRequest = {
query: z.infer<typeof UserScheduleControllerListQuerySchema>;
export type UserScheduleControllerLocationsListRequest = {
query: z.infer<typeof UserScheduleControllerLocationsListQuerySchema>;
};

const UserScheduleControllerListQuerySchema = z.object({
const UserScheduleControllerLocationsListQuerySchema = z.object({
username: z.string(),
});

export type UserScheduleControllerListResponse = Awaited<
ReturnType<typeof UserScheduleServiceLocationsList>
>;

export const UserScheduleControllerList = _(
async ({ query }: UserScheduleControllerListRequest) => {
const validateQuery = UserScheduleControllerListQuerySchema.parse(query);

export const UserScheduleControllerLocationsList = _(
async ({ query }: UserScheduleControllerLocationsListRequest) => {
const validateQuery =
UserScheduleControllerLocationsListQuerySchema.parse(query);
const { customerId } = await UserServiceGet(validateQuery);

return UserScheduleServiceLocationsList({ customerId });
}
);
4 changes: 1 addition & 3 deletions src/functions/user/controllers/user/get.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { z } from "zod";
import { _ } from "~/library/handler";

import { UserScheduleServiceLocationsList } from "../../services/schedule";
import { UserServiceGet } from "../../services/user";

export type UserControllerGetRequest = {
Expand All @@ -14,8 +13,7 @@ export const UserServiceGetSchema = z.object({

export type UserControllerGetResponse = Awaited<
ReturnType<typeof UserServiceGet>
> &
Awaited<ReturnType<typeof UserScheduleServiceLocationsList>>;
>;

// get schedule with products only belongs to specific locationId
export const UserControllerGet = _(
Expand Down
77 changes: 77 additions & 0 deletions src/functions/user/services/schedule/get-by-product.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import { faker } from "@faker-js/faker";
import { CustomerScheduleServiceCreate } from "~/functions/customer/services/schedule/create";
import { createUser } from "~/library/jest/helpers";
import { createLocation } from "~/library/jest/helpers/location";
import { getProductObject } from "~/library/jest/helpers/product";
import { UserScheduleServiceGetByProductId } from "./get-by-product";

require("~/library/jest/mongoose/mongodb.jest");

describe("UserScheduleServiceGetByProductId", () => {
const customerId = 123;
const username = "test";

beforeEach(() => createUser({ customerId }, { username }));

it("should get schedule with products only belongs to specific locationId", async () => {
const locationOrigin = await createLocation({ customerId });

const product = getProductObject({
locations: [
{
location: locationOrigin._id,
locationType: locationOrigin.locationType,
},
],
});
const schedule = await CustomerScheduleServiceCreate({
name: faker.person.firstName(),
customerId,
products: [
product,
getProductObject({
locations: [
{
location: locationOrigin._id,
locationType: locationOrigin.locationType,
},
],
}),
getProductObject({
locations: [
{
location: locationOrigin._id,
locationType: locationOrigin.locationType,
},
],
}),
],
});

// another fake schedule
await CustomerScheduleServiceCreate({
name: faker.person.firstName(),
customerId,
products: [
getProductObject({
locations: [
{
location: locationOrigin._id,
locationType: locationOrigin.locationType,
},
],
}),
],
});

const getSchedule = await UserScheduleServiceGetByProductId({
customerId: schedule.customerId,
productId: product.productId,
});

expect(getSchedule!.name).toBe(schedule.name);
expect(getSchedule!._id.toString()).toBe(schedule._id.toString());
expect(getSchedule!.locations.length).toBe(1);
expect(getSchedule!.product.productId).toBe(product.productId);
});
});
86 changes: 86 additions & 0 deletions src/functions/user/services/schedule/get-by-product.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import { Schedule, ScheduleModel, ScheduleProduct } from "~/functions/schedule";
import { NotFoundError } from "~/library/handler";

export type UesrScheduleAggreateReturnValue = Pick<
Schedule,
"_id" | "customerId" | "name"
> & { product: ScheduleProduct } & { locations: Array<Location> };

export type UserScheduleServiceGetByProductIdProps = {
customerId: number;
productId: number;
};

export async function UserScheduleServiceGetByProductId({
customerId,
productId,
}: UserScheduleServiceGetByProductIdProps) {
// first just the schedule with the product
const schedule = await ScheduleModel.findOne({
customerId,
"products.productId": productId,
}).orFail(
new NotFoundError([
{
path: ["customerId", "productId"],
message: "NOT_FOUND",
code: "custom",
},
])
);

const schedules =
await ScheduleModel.aggregate<UesrScheduleAggreateReturnValue>([
{
$match: {
_id: schedule._id,
customerId,
},
},
{
$unwind: "$products",
},
{
$match: {
"products.productId": productId,
},
},
{
$unwind: "$products.locations",
},
{
$lookup: {
from: "Location",
localField: "products.locations.location",
foreignField: "_id",
as: "products.locations",
},
},
{ $unwind: "$products.locations" },
{
$group: {
_id: "$_id",
locations: {
$addToSet: "$products.locations",
},
product: { $first: "$products" },
name: { $first: "$name" },
slots: { $first: "$slots" },
createdAt: { $first: "$createdAt" },
updatedAt: { $first: "$updatedAt" },
},
},
]);

if (schedules.length === 0) {
throw new NotFoundError([
{
path: ["customerId", "scheduleId", "locationId"],
message: "NOT_FOUND",
code: "custom",
},
]);
}

return schedules[0];
}
Loading

0 comments on commit 5bc32f1

Please sign in to comment.