From 7741396f54cd7561d2f7623ca1be2ce843d82a28 Mon Sep 17 00:00:00 2001 From: William Orlopp Date: Wed, 28 May 2025 21:09:46 -0700 Subject: [PATCH 1/3] Fixes #74 - Add API Pagination for Orders --- app/(api)/_datalib/_resolvers/Order.ts | 7 +++++-- app/(api)/_datalib/_services/Orders.ts | 26 ++++++++++++++++++-------- app/(api)/_datalib/_typeDefs/Order.ts | 2 +- 3 files changed, 24 insertions(+), 11 deletions(-) diff --git a/app/(api)/_datalib/_resolvers/Order.ts b/app/(api)/_datalib/_resolvers/Order.ts index 026f326..f2cbd83 100644 --- a/app/(api)/_datalib/_resolvers/Order.ts +++ b/app/(api)/_datalib/_resolvers/Order.ts @@ -10,8 +10,11 @@ const resolvers = { Query: { order: (_: never, args: { id: string }, ctx: ApolloContext) => Orders.find(args.id, ctx), - orders: (_: never, args: { ids: string[] }, ctx: ApolloContext) => - Orders.findMany(args.ids, ctx), + orders: ( + _: never, + args: { statuses: string[]; offset: number; limit: number }, + ctx: ApolloContext + ) => Orders.findMany(args.statuses, args.offset, args.limit, ctx), }, Mutation: { updateOrder: ( diff --git a/app/(api)/_datalib/_services/Orders.ts b/app/(api)/_datalib/_services/Orders.ts index 988ca87..7cc0a24 100644 --- a/app/(api)/_datalib/_services/Orders.ts +++ b/app/(api)/_datalib/_services/Orders.ts @@ -31,19 +31,29 @@ export default class Orders { }); } - static async findMany(ids: string[], ctx: ApolloContext) { + static async findMany( + statuses: string[], + offset: number, + limit: number, + ctx: ApolloContext + ) { if (!ctx.isOwner && !ctx.hasValidApiKey) return null; - if (!ids) { - return prisma.order.findMany(); - } + if (offset < 0 || limit <= 0) return null; // TODO: Possible some better error message. + + /* + If statuses is null, or has no length, then do not filter by statuses. + */ + const filter = + !statuses || statuses.length > 0 ? { status: { in: statuses } } : {}; return prisma.order.findMany({ - where: { - id: { - in: ids, - }, + where: filter, + orderBy: { + created_at: 'desc', }, + skip: offset * limit, + take: limit, }); } diff --git a/app/(api)/_datalib/_typeDefs/Order.ts b/app/(api)/_datalib/_typeDefs/Order.ts index 0daa9e7..bfbc472 100644 --- a/app/(api)/_datalib/_typeDefs/Order.ts +++ b/app/(api)/_datalib/_typeDefs/Order.ts @@ -67,7 +67,7 @@ const typeDefs = gql` type Query { order(id: ID!): Order - orders(id: [ID]): [Order] + orders(statuses: [String], offset: Int!, limit: Int!): [Order] } type Mutation { From b775b9ba78e31a794d0bf9f3a3b961512e1c5742 Mon Sep 17 00:00:00 2001 From: brandonw504 Date: Tue, 24 Jun 2025 17:17:16 -0700 Subject: [PATCH 2/3] Switched id to autoincrement and added order searching --- app/(api)/_datalib/_resolvers/Order.ts | 10 +++++-- app/(api)/_datalib/_services/Orders.ts | 26 +++++++++++++------ app/(api)/_datalib/_typeDefs/Order.ts | 7 ++++- .../migration.sql | 26 +++++++++++++++++++ prisma/schema/Order.prisma | 2 +- prisma/schema/ProductToOrder.prisma | 2 +- 6 files changed, 60 insertions(+), 13 deletions(-) create mode 100644 prisma/migrations/20250625001334_order_id_autoincrement/migration.sql diff --git a/app/(api)/_datalib/_resolvers/Order.ts b/app/(api)/_datalib/_resolvers/Order.ts index f2cbd83..89a560c 100644 --- a/app/(api)/_datalib/_resolvers/Order.ts +++ b/app/(api)/_datalib/_resolvers/Order.ts @@ -12,9 +12,15 @@ const resolvers = { Orders.find(args.id, ctx), orders: ( _: never, - args: { statuses: string[]; offset: number; limit: number }, + args: { + statuses: string[]; + search: string; + offset: number; + limit: number; + }, ctx: ApolloContext - ) => Orders.findMany(args.statuses, args.offset, args.limit, ctx), + ) => + Orders.findMany(args.statuses, args.search, args.offset, args.limit, ctx), }, Mutation: { updateOrder: ( diff --git a/app/(api)/_datalib/_services/Orders.ts b/app/(api)/_datalib/_services/Orders.ts index 7cc0a24..8111644 100644 --- a/app/(api)/_datalib/_services/Orders.ts +++ b/app/(api)/_datalib/_services/Orders.ts @@ -2,6 +2,7 @@ import revalidateCache from '@actions/revalidateCache'; import prisma from '../_prisma/client'; import { OrderInput, OrderProductInput } from '@datatypes/Order'; import { ApolloContext } from '../apolloServer'; +import { Prisma } from '@prisma/client'; export default class Orders { //CREATE @@ -20,7 +21,6 @@ export default class Orders { } //READ -> get order and orders, also getProducts using the ProductToOrder table - static async find(id: string, ctx: ApolloContext) { if (!ctx.isOwner && !ctx.hasValidApiKey) return null; @@ -33,22 +33,32 @@ export default class Orders { static async findMany( statuses: string[], + search: string, offset: number, limit: number, ctx: ApolloContext ) { if (!ctx.isOwner && !ctx.hasValidApiKey) return null; - if (offset < 0 || limit <= 0) return null; // TODO: Possible some better error message. + if (offset < 0 || limit <= 0) return null; + + const whereClause: Prisma.OrderWhereInput = {}; - /* - If statuses is null, or has no length, then do not filter by statuses. - */ - const filter = - !statuses || statuses.length > 0 ? { status: { in: statuses } } : {}; + if (statuses && statuses.length > 0) { + whereClause.status = { in: statuses }; + } + + if (search) { + whereClause.OR = [ + { id: { contains: search, mode: 'insensitive' } }, + { customer_name: { contains: search, mode: 'insensitive' } }, + { customer_email: { contains: search, mode: 'insensitive' } }, + { customer_phone_num: { contains: search, mode: 'insensitive' } }, + ]; + } return prisma.order.findMany({ - where: filter, + where: whereClause, orderBy: { created_at: 'desc', }, diff --git a/app/(api)/_datalib/_typeDefs/Order.ts b/app/(api)/_datalib/_typeDefs/Order.ts index bfbc472..999d214 100644 --- a/app/(api)/_datalib/_typeDefs/Order.ts +++ b/app/(api)/_datalib/_typeDefs/Order.ts @@ -67,7 +67,12 @@ const typeDefs = gql` type Query { order(id: ID!): Order - orders(statuses: [String], offset: Int!, limit: Int!): [Order] + orders( + statuses: [String] + search: String + offset: Int! + limit: Int! + ): [Order] } type Mutation { diff --git a/prisma/migrations/20250625001334_order_id_autoincrement/migration.sql b/prisma/migrations/20250625001334_order_id_autoincrement/migration.sql new file mode 100644 index 0000000..b979ba5 --- /dev/null +++ b/prisma/migrations/20250625001334_order_id_autoincrement/migration.sql @@ -0,0 +1,26 @@ +/* + Warnings: + + - The primary key for the `Order` table will be changed. If it partially fails, the table could be left without primary key constraint. + - The `id` column on the `Order` table would be dropped and recreated. This will lead to data loss if there is data in the column. + - The primary key for the `ProductToOrder` table will be changed. If it partially fails, the table could be left without primary key constraint. + - Changed the type of `order_id` on the `ProductToOrder` table. No cast exists, the column would be dropped and recreated, which cannot be done if there is data, since the column is required. + +*/ +-- DropForeignKey +ALTER TABLE "ProductToOrder" DROP CONSTRAINT "ProductToOrder_order_id_fkey"; + +-- AlterTable +ALTER TABLE "Order" DROP CONSTRAINT "Order_pkey", +DROP COLUMN "id", +ADD COLUMN "id" SERIAL NOT NULL, +ADD CONSTRAINT "Order_pkey" PRIMARY KEY ("id"); + +-- AlterTable +ALTER TABLE "ProductToOrder" DROP CONSTRAINT "ProductToOrder_pkey", +DROP COLUMN "order_id", +ADD COLUMN "order_id" INTEGER NOT NULL, +ADD CONSTRAINT "ProductToOrder_pkey" PRIMARY KEY ("product_id", "order_id"); + +-- AddForeignKey +ALTER TABLE "ProductToOrder" ADD CONSTRAINT "ProductToOrder_order_id_fkey" FOREIGN KEY ("order_id") REFERENCES "Order"("id") ON DELETE CASCADE ON UPDATE CASCADE; diff --git a/prisma/schema/Order.prisma b/prisma/schema/Order.prisma index 9408546..d9df6b7 100644 --- a/prisma/schema/Order.prisma +++ b/prisma/schema/Order.prisma @@ -1,5 +1,5 @@ model Order { - id String @id @default(uuid()) + id Int @id @default(autoincrement()) customer_name String customer_email String customer_phone_num String diff --git a/prisma/schema/ProductToOrder.prisma b/prisma/schema/ProductToOrder.prisma index aa032d4..690611e 100644 --- a/prisma/schema/ProductToOrder.prisma +++ b/prisma/schema/ProductToOrder.prisma @@ -1,7 +1,7 @@ model ProductToOrder { product_id String product Product @relation(fields: [product_id], references: [id], onDelete: Cascade) - order_id String + order_id Int order Order @relation(fields: [order_id], references: [id], onDelete: Cascade) quantity Int From 1e19e356673919c0c1e42677b81a23f690f25c79 Mon Sep 17 00:00:00 2001 From: brandonw504 Date: Tue, 24 Jun 2025 20:49:31 -0700 Subject: [PATCH 3/3] Fixed areas where id was string --- app/(api)/_datalib/_resolvers/Order.ts | 12 ++++----- app/(api)/_datalib/_services/Orders.ts | 37 +++++++++++++++++++------- app/(api)/_types/Order.ts | 4 +-- 3 files changed, 36 insertions(+), 17 deletions(-) diff --git a/app/(api)/_datalib/_resolvers/Order.ts b/app/(api)/_datalib/_resolvers/Order.ts index 89a560c..ef05b45 100644 --- a/app/(api)/_datalib/_resolvers/Order.ts +++ b/app/(api)/_datalib/_resolvers/Order.ts @@ -8,7 +8,7 @@ const resolvers = { Orders.getProducts(parent.id, ctx), }, Query: { - order: (_: never, args: { id: string }, ctx: ApolloContext) => + order: (_: never, args: { id: number }, ctx: ApolloContext) => Orders.find(args.id, ctx), orders: ( _: never, @@ -25,27 +25,27 @@ const resolvers = { Mutation: { updateOrder: ( _: never, - args: { id: string; input: OrderInput }, + args: { id: number; input: OrderInput }, ctx: ApolloContext ) => Orders.update(args.id, args.input, ctx), - deleteOrder: (_: never, args: { id: string }, ctx: ApolloContext) => + deleteOrder: (_: never, args: { id: number }, ctx: ApolloContext) => Orders.delete(args.id, ctx), createOrder: (_: never, args: { input: OrderInput }, ctx: ApolloContext) => Orders.create(args.input, ctx), addProductToOrder: ( _: never, - args: { id: string; productToAdd: OrderProductInput }, + args: { id: number; productToAdd: OrderProductInput }, ctx: ApolloContext ) => Orders.addProductToOrder(args.id, args.productToAdd, ctx), removeProductFromOrder: ( _: never, - args: { id: string; product_id: string }, + args: { id: number; product_id: string }, ctx: ApolloContext ) => Orders.removeProductFromOrder(args.id, args.product_id, ctx), editProductQuantity: ( _: never, args: { - id: string; + id: number; productToUpdate: OrderProductInput; }, ctx: ApolloContext diff --git a/app/(api)/_datalib/_services/Orders.ts b/app/(api)/_datalib/_services/Orders.ts index 8111644..7d9d613 100644 --- a/app/(api)/_datalib/_services/Orders.ts +++ b/app/(api)/_datalib/_services/Orders.ts @@ -21,7 +21,7 @@ export default class Orders { } //READ -> get order and orders, also getProducts using the ProductToOrder table - static async find(id: string, ctx: ApolloContext) { + static async find(id: number, ctx: ApolloContext) { if (!ctx.isOwner && !ctx.hasValidApiKey) return null; return prisma.order.findUnique({ @@ -49,12 +49,31 @@ export default class Orders { } if (search) { - whereClause.OR = [ - { id: { contains: search, mode: 'insensitive' } }, + const searchConditions: Prisma.OrderWhereInput[] = [ { customer_name: { contains: search, mode: 'insensitive' } }, { customer_email: { contains: search, mode: 'insensitive' } }, { customer_phone_num: { contains: search, mode: 'insensitive' } }, ]; + + const searchAsNumber = parseInt(search, 10); + if (!isNaN(searchAsNumber)) { + searchConditions.push({ id: searchAsNumber }); + searchConditions.push({ + id: { + in: await prisma.order + .findMany({ + select: { id: true }, + }) + .then((orders) => + orders + .filter((order) => order.id.toString().includes(search)) + .map((order) => order.id) + ), + }, + }); + } + + whereClause.OR = searchConditions; } return prisma.order.findMany({ @@ -67,7 +86,7 @@ export default class Orders { }); } - static async getProducts(order_id: string, ctx: ApolloContext) { + static async getProducts(order_id: number, ctx: ApolloContext) { if (!ctx.isOwner && !ctx.hasValidApiKey) return null; const productToOrder = await prisma.productToOrder.findMany({ @@ -88,7 +107,7 @@ export default class Orders { } //UPDATE - static async update(id: string, input: OrderInput, ctx: ApolloContext) { + static async update(id: number, input: OrderInput, ctx: ApolloContext) { if (!ctx.isOwner && !ctx.hasValidApiKey) return null; try { @@ -107,7 +126,7 @@ export default class Orders { // these are the services for the mutations we have left static async addProductToOrder( - id: string, + id: number, productToAdd: OrderProductInput, ctx: ApolloContext ) { @@ -166,7 +185,7 @@ export default class Orders { } static async removeProductFromOrder( - id: string, + id: number, product_id: string, ctx: ApolloContext ) { @@ -201,7 +220,7 @@ export default class Orders { } static async editProductQuantity( - id: string, + id: number, productToUpdate: OrderProductInput, ctx: ApolloContext ) { @@ -253,7 +272,7 @@ export default class Orders { } // DELETE - static async delete(id: string, ctx: ApolloContext) { + static async delete(id: number, ctx: ApolloContext) { if (!ctx.isOwner && !ctx.hasValidApiKey) return null; try { diff --git a/app/(api)/_types/Order.ts b/app/(api)/_types/Order.ts index 349249f..5cb17cc 100644 --- a/app/(api)/_types/Order.ts +++ b/app/(api)/_types/Order.ts @@ -1,7 +1,7 @@ import { Product } from './Product'; export type Order = { - id: string; + id: number; customer_name: string; customer_email: string; customer_phone_num: string; @@ -23,7 +23,7 @@ export type Order = { export type ProductToOrder = { product_id: string; product: Product; - order_id: string; + order_id: number; order: Order; quantity: number; };