Skip to content

Commit

Permalink
fix(openapi): change parameter name from 'productId' to 'productHandl…
Browse files Browse the repository at this point in the history
…e' in openapi.yaml and index.yaml

feat(schedule.types): add 'productHandle' property to ScheduleProductZodSchema
fix(product.schema): add 'productHandle' field to ProductSchema
fix(user-product.function): change parameter name from 'productId' to 'productHandle' in route definition
fix(user-product.function): change parameter name from 'productId' to 'productHandle' in request query
fix(user-product.function): change parameter name from 'productId' to 'productHandle' in controller
feat(user-product.function): create UserProductServiceGet function to retrieve product by productHandle
feat(user-product.function): add UserProductsControllerGet handler to call UserProductServiceGet
feat(user-product.function): add UserProductsControllerGetQuerySchema to validate request query
feat(user-product.function): update UserProductsControllerGet to use UserProductServiceGet
feat(user-product.function): add UserProductsController
  • Loading branch information
jamalsoueidan committed Nov 30, 2023
1 parent 4b68efe commit f682000
Show file tree
Hide file tree
Showing 10 changed files with 122 additions and 19 deletions.
2 changes: 1 addition & 1 deletion openapi/openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -243,7 +243,7 @@ paths:
# User
/user/{username}:
$ref: "./paths/user/user.yaml"
/user/{username}/products/{productId}:
/user/{username}/products/{productHandle}:
$ref: "./paths/user/products/get/index.yaml"
/user/{username}/products:
$ref: "./paths/user/products/list-by-schedule/index.yaml"
Expand Down
2 changes: 1 addition & 1 deletion openapi/paths/user/products/get/index.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ get:
required: true
schema:
type: string
- name: productId
- name: productHandle
in: path
required: true
schema:
Expand Down
1 change: 1 addition & 0 deletions src/functions/schedule/schedule.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ const LocationZodSchema = z.object({
export type ScheduleProductLocation = z.infer<typeof LocationZodSchema>;

export const ScheduleProductZodSchema = z.object({
productHandle: z.string(),
productId: GidFormat,
variantId: GidFormat,
selectedOptions: z.object({
Expand Down
4 changes: 4 additions & 0 deletions src/functions/schedule/schemas/product.schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ import { ScheduleProduct, TimeUnit } from "../schedule.types";

export const ProductSchema = new mongoose.Schema<ScheduleProduct>(
{
productHandle: {
type: String,
index: true,
},
productId: {
type: Number,
index: true,
Expand Down
2 changes: 1 addition & 1 deletion src/functions/user-product.function.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,6 @@ app.http("UserProductsControllerGetProducts", {
app.http("UserProductsControllerGet", {
methods: ["GET"],
authLevel: "anonymous",
route: "user/{username}/products/{productId}",
route: "user/{username}/products/{productHandle}",
handler: UserProductsControllerGet,
});
7 changes: 3 additions & 4 deletions src/functions/user/controllers/products/get.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ require("~/library/jest/mongoose/mongodb.jest");
describe("UserProductsControllerGet", () => {
let context: InvocationContext;
let request: HttpRequest;
const productId = 1000;
const product = getProductObject({
variantId: 1,
duration: 60,
Expand Down Expand Up @@ -52,7 +51,7 @@ describe("UserProductsControllerGet", () => {
const newProduct = await CustomerProductServiceUpsert(
{
customerId: newSchedule.customerId,
productId,
productId: product.productId,
},
{ ...product, scheduleId: newSchedule._id }
);
Expand All @@ -64,14 +63,14 @@ describe("UserProductsControllerGet", () => {
request = await createHttpRequest<UserProductsControllerGetRequest>({
query: {
username: user.username || "",
productId,
productHandle: product.productHandle,
},
});

const res: HttpSuccessResponse<UserProductsControllerGetResponse> =
await UserProductsControllerGet(request, context);

expect(res.jsonBody?.success).toBeTruthy();
expect(res.jsonBody?.payload?.productId).toBe(productId);
expect(res.jsonBody?.payload?.productId).toBe(product.productId);
});
});
16 changes: 4 additions & 12 deletions src/functions/user/controllers/products/get.ts
Original file line number Diff line number Diff line change
@@ -1,32 +1,24 @@
import { z } from "zod";
import { CustomerProductServiceGet } from "~/functions/customer/services/product";

import { ScheduleProductZodSchema } from "~/functions/schedule/schedule.types";
import { _ } from "~/library/handler";
import { UserServiceGetCustomerId } from "../../services/user";
import { UserProductServiceGet } from "../../services/products/get";

export type UserProductsControllerGetRequest = {
query: z.infer<typeof UserProductsControllerGetQuerySchema>;
};

const UserProductsControllerGetQuerySchema = z.object({
username: z.string(),
productId: ScheduleProductZodSchema.shape.productId,
productHandle: z.string(),
});

export type UserProductsControllerGetResponse = Awaited<
ReturnType<typeof CustomerProductServiceGet>
ReturnType<typeof UserProductServiceGet>
>;

export const UserProductsControllerGet = _(
async ({ query }: UserProductsControllerGetRequest) => {
const validateQuery = UserProductsControllerGetQuerySchema.parse(query);
const user = await UserServiceGetCustomerId({
username: validateQuery.username,
});
return CustomerProductServiceGet({
customerId: user.customerId,
productId: validateQuery.productId,
});
return UserProductServiceGet(validateQuery);
}
);
51 changes: 51 additions & 0 deletions src/functions/user/services/products/get.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { CustomerProductServiceUpsert } from "~/functions/customer/services/product";
import { CustomerScheduleServiceCreate } from "~/functions/customer/services/schedule/create";
import { TimeUnit } from "~/functions/schedule";
import { createUser } from "~/library/jest/helpers";
import { getProductObject } from "~/library/jest/helpers/product";
import { UserProductServiceGet } from "./get";

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

describe("UserProductsService", () => {
const name = "Test Schedule";
const productId = 1000;
const newProduct = getProductObject({
variantId: 1,
duration: 60,
breakTime: 0,
noticePeriod: {
value: 1,
unit: TimeUnit.DAYS,
},
bookingPeriod: {
value: 1,
unit: TimeUnit.WEEKS,
},
locations: [],
});

it("should find a product", async () => {
const user = await createUser({ customerId: 134 });

const newSchedule = await CustomerScheduleServiceCreate({
name,
customerId: user.customerId,
});

const updatedSchedule = await CustomerProductServiceUpsert(
{
customerId: newSchedule.customerId,
productId,
},
{ ...newProduct, scheduleId: newSchedule._id }
);

const foundProduct = await UserProductServiceGet({
username: user.username || "",
productHandle: newProduct.productHandle,
});

expect(foundProduct).toMatchObject({ productId });
});
});
55 changes: 55 additions & 0 deletions src/functions/user/services/products/get.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { ScheduleModel } from "~/functions/schedule";
import { NotFoundError } from "~/library/handler";
import { UserServiceGetCustomerId } from "../user";

export type UserProductServiceGetFilter = {
username: string;
productHandle: string;
};

export const UserProductServiceGet = async (
filter: UserProductServiceGetFilter
) => {
const user = await UserServiceGetCustomerId({
username: filter.username,
});

const schedule = await ScheduleModel.findOne({
customerId: user.customerId,
products: {
$elemMatch: {
productHandle: filter.productHandle,
},
},
})
.orFail(
new NotFoundError([
{
code: "custom",
message: "PRODUCT_NOT_FOUND",
path: ["customerId", "productHandle"],
},
])
)
.lean();

const product = schedule.products.find(
(p) => p.productHandle === filter.productHandle
);

if (!product) {
throw new NotFoundError([
{
code: "custom",
message: "PRODUCT_NOT_FOUND",
path: ["productHandle"],
},
]);
}

return {
...product,
scheduleId: schedule._id,
scheduleName: schedule.name,
};
};
1 change: 1 addition & 0 deletions src/library/jest/helpers/product.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { ScheduleProduct, TimeUnit } from "~/functions/schedule";
export const getProductObject = (
props: Partial<ScheduleProduct> = {}
): ScheduleProduct => ({
productHandle: faker.string.uuid(),
productId: faker.number.int({ min: 1, max: 10000000 }),
variantId: faker.number.int({ min: 1, max: 10000000 }),
duration: faker.helpers.arrayElement([30, 45, 60]),
Expand Down

0 comments on commit f682000

Please sign in to comment.