✅ admin side for now | 🔄 started FE
This commit is contained in:
1
packages/db/migrations/0001_gigantic_mach_iv.sql
Normal file
1
packages/db/migrations/0001_gigantic_mach_iv.sql
Normal file
@@ -0,0 +1 @@
|
||||
DROP TABLE "coupon" CASCADE;
|
||||
901
packages/db/migrations/meta/0001_snapshot.json
Normal file
901
packages/db/migrations/meta/0001_snapshot.json
Normal file
@@ -0,0 +1,901 @@
|
||||
{
|
||||
"id": "95304f23-5c79-4e23-bd6b-0d2f99cc5249",
|
||||
"prevId": "e8de9102-c79e-46ff-a25f-80e1039b6091",
|
||||
"version": "7",
|
||||
"dialect": "postgresql",
|
||||
"tables": {
|
||||
"public.account": {
|
||||
"name": "account",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "text",
|
||||
"primaryKey": true,
|
||||
"notNull": true
|
||||
},
|
||||
"account_id": {
|
||||
"name": "account_id",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"provider_id": {
|
||||
"name": "provider_id",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"user_id": {
|
||||
"name": "user_id",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"access_token": {
|
||||
"name": "access_token",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"refresh_token": {
|
||||
"name": "refresh_token",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"id_token": {
|
||||
"name": "id_token",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"access_token_expires_at": {
|
||||
"name": "access_token_expires_at",
|
||||
"type": "timestamp",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"refresh_token_expires_at": {
|
||||
"name": "refresh_token_expires_at",
|
||||
"type": "timestamp",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"scope": {
|
||||
"name": "scope",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"password": {
|
||||
"name": "password",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "timestamp",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"updated_at": {
|
||||
"name": "updated_at",
|
||||
"type": "timestamp",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"account_user_id_user_id_fk": {
|
||||
"name": "account_user_id_user_id_fk",
|
||||
"tableFrom": "account",
|
||||
"tableTo": "user",
|
||||
"columnsFrom": [
|
||||
"user_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.user": {
|
||||
"name": "user",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "text",
|
||||
"primaryKey": true,
|
||||
"notNull": true
|
||||
},
|
||||
"name": {
|
||||
"name": "name",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"email": {
|
||||
"name": "email",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"email_verified": {
|
||||
"name": "email_verified",
|
||||
"type": "boolean",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": false
|
||||
},
|
||||
"image": {
|
||||
"name": "image",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "timestamp",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"updated_at": {
|
||||
"name": "updated_at",
|
||||
"type": "timestamp",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"username": {
|
||||
"name": "username",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"display_username": {
|
||||
"name": "display_username",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"role": {
|
||||
"name": "role",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"banned": {
|
||||
"name": "banned",
|
||||
"type": "boolean",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"default": false
|
||||
},
|
||||
"ban_reason": {
|
||||
"name": "ban_reason",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"ban_expires": {
|
||||
"name": "ban_expires",
|
||||
"type": "timestamp",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"parent_id": {
|
||||
"name": "parent_id",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"discount_percent": {
|
||||
"name": "discount_percent",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"default": 0
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {
|
||||
"user_email_unique": {
|
||||
"name": "user_email_unique",
|
||||
"nullsNotDistinct": false,
|
||||
"columns": [
|
||||
"email"
|
||||
]
|
||||
},
|
||||
"user_username_unique": {
|
||||
"name": "user_username_unique",
|
||||
"nullsNotDistinct": false,
|
||||
"columns": [
|
||||
"username"
|
||||
]
|
||||
}
|
||||
},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.verification": {
|
||||
"name": "verification",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "text",
|
||||
"primaryKey": true,
|
||||
"notNull": true
|
||||
},
|
||||
"identifier": {
|
||||
"name": "identifier",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"value": {
|
||||
"name": "value",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"expires_at": {
|
||||
"name": "expires_at",
|
||||
"type": "timestamp",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "timestamp",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"updated_at": {
|
||||
"name": "updated_at",
|
||||
"type": "timestamp",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.checkout_flow_session": {
|
||||
"name": "checkout_flow_session",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "serial",
|
||||
"primaryKey": true,
|
||||
"notNull": true
|
||||
},
|
||||
"flow_id": {
|
||||
"name": "flow_id",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"domain": {
|
||||
"name": "domain",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"checkout_step": {
|
||||
"name": "checkout_step",
|
||||
"type": "varchar(50)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"show_verification": {
|
||||
"name": "show_verification",
|
||||
"type": "boolean",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": false
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "timestamp",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
},
|
||||
"last_pinged": {
|
||||
"name": "last_pinged",
|
||||
"type": "timestamp",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"is_active": {
|
||||
"name": "is_active",
|
||||
"type": "boolean",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": true
|
||||
},
|
||||
"last_synced_at": {
|
||||
"name": "last_synced_at",
|
||||
"type": "timestamp",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"personal_info_last_synced_at": {
|
||||
"name": "personal_info_last_synced_at",
|
||||
"type": "timestamp",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"payment_info_last_synced_at": {
|
||||
"name": "payment_info_last_synced_at",
|
||||
"type": "timestamp",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"pending_actions": {
|
||||
"name": "pending_actions",
|
||||
"type": "json",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "'[]'::json"
|
||||
},
|
||||
"personal_info": {
|
||||
"name": "personal_info",
|
||||
"type": "json",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"payment_info": {
|
||||
"name": "payment_info",
|
||||
"type": "json",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"ref_o_ids": {
|
||||
"name": "ref_o_ids",
|
||||
"type": "json",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"default": "'[]'::json"
|
||||
},
|
||||
"otp_code": {
|
||||
"name": "otp_code",
|
||||
"type": "varchar(20)",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"otp_submitted": {
|
||||
"name": "otp_submitted",
|
||||
"type": "boolean",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": false
|
||||
},
|
||||
"partial_otp_code": {
|
||||
"name": "partial_otp_code",
|
||||
"type": "varchar(20)",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"ip_address": {
|
||||
"name": "ip_address",
|
||||
"type": "varchar(50)",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "''"
|
||||
},
|
||||
"user_agent": {
|
||||
"name": "user_agent",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "''"
|
||||
},
|
||||
"reserved": {
|
||||
"name": "reserved",
|
||||
"type": "boolean",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": false
|
||||
},
|
||||
"reserved_by": {
|
||||
"name": "reserved_by",
|
||||
"type": "varchar(255)",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"completed_at": {
|
||||
"name": "completed_at",
|
||||
"type": "timestamp",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"session_outcome": {
|
||||
"name": "session_outcome",
|
||||
"type": "varchar(50)",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"is_deleted": {
|
||||
"name": "is_deleted",
|
||||
"type": "boolean",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": false
|
||||
},
|
||||
"product_id": {
|
||||
"name": "product_id",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"checkout_flow_session_product_id_product_id_fk": {
|
||||
"name": "checkout_flow_session_product_id_product_id_fk",
|
||||
"tableFrom": "checkout_flow_session",
|
||||
"tableTo": "product",
|
||||
"columnsFrom": [
|
||||
"product_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "set null",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {
|
||||
"checkout_flow_session_flow_id_unique": {
|
||||
"name": "checkout_flow_session_flow_id_unique",
|
||||
"nullsNotDistinct": false,
|
||||
"columns": [
|
||||
"flow_id"
|
||||
]
|
||||
}
|
||||
},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.customer_info": {
|
||||
"name": "customer_info",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "serial",
|
||||
"primaryKey": true,
|
||||
"notNull": true
|
||||
},
|
||||
"first_name": {
|
||||
"name": "first_name",
|
||||
"type": "varchar(64)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"middle_name": {
|
||||
"name": "middle_name",
|
||||
"type": "varchar(64)",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"default": "''"
|
||||
},
|
||||
"last_name": {
|
||||
"name": "last_name",
|
||||
"type": "varchar(64)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"email": {
|
||||
"name": "email",
|
||||
"type": "varchar(128)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"phone_country_code": {
|
||||
"name": "phone_country_code",
|
||||
"type": "varchar(6)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"phone_number": {
|
||||
"name": "phone_number",
|
||||
"type": "varchar(20)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"country": {
|
||||
"name": "country",
|
||||
"type": "varchar(128)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"state": {
|
||||
"name": "state",
|
||||
"type": "varchar(128)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"city": {
|
||||
"name": "city",
|
||||
"type": "varchar(128)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"zip_code": {
|
||||
"name": "zip_code",
|
||||
"type": "varchar(21)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"address": {
|
||||
"name": "address",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"address2": {
|
||||
"name": "address2",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "timestamp",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"default": "now()"
|
||||
},
|
||||
"updated_at": {
|
||||
"name": "updated_at",
|
||||
"type": "timestamp",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"default": "now()"
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.order": {
|
||||
"name": "order",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "serial",
|
||||
"primaryKey": true,
|
||||
"notNull": true
|
||||
},
|
||||
"order_price": {
|
||||
"name": "order_price",
|
||||
"type": "numeric(12, 2)",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"discount_amount": {
|
||||
"name": "discount_amount",
|
||||
"type": "numeric(12, 2)",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"display_price": {
|
||||
"name": "display_price",
|
||||
"type": "numeric(12, 2)",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"base_price": {
|
||||
"name": "base_price",
|
||||
"type": "numeric(12, 2)",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"fullfilled_price": {
|
||||
"name": "fullfilled_price",
|
||||
"type": "numeric(12, 2)",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"default": "'0'"
|
||||
},
|
||||
"status": {
|
||||
"name": "status",
|
||||
"type": "varchar(24)",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"product_id": {
|
||||
"name": "product_id",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"customer_info_id": {
|
||||
"name": "customer_info_id",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"payment_info_id": {
|
||||
"name": "payment_info_id",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"agent_id": {
|
||||
"name": "agent_id",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "timestamp",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"default": "now()"
|
||||
},
|
||||
"updated_at": {
|
||||
"name": "updated_at",
|
||||
"type": "timestamp",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"default": "now()"
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"order_product_id_product_id_fk": {
|
||||
"name": "order_product_id_product_id_fk",
|
||||
"tableFrom": "order",
|
||||
"tableTo": "product",
|
||||
"columnsFrom": [
|
||||
"product_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
},
|
||||
"order_customer_info_id_customer_info_id_fk": {
|
||||
"name": "order_customer_info_id_customer_info_id_fk",
|
||||
"tableFrom": "order",
|
||||
"tableTo": "customer_info",
|
||||
"columnsFrom": [
|
||||
"customer_info_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
},
|
||||
"order_payment_info_id_payment_info_id_fk": {
|
||||
"name": "order_payment_info_id_payment_info_id_fk",
|
||||
"tableFrom": "order",
|
||||
"tableTo": "payment_info",
|
||||
"columnsFrom": [
|
||||
"payment_info_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
},
|
||||
"order_agent_id_user_id_fk": {
|
||||
"name": "order_agent_id_user_id_fk",
|
||||
"tableFrom": "order",
|
||||
"tableTo": "user",
|
||||
"columnsFrom": [
|
||||
"agent_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "set null",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.payment_info": {
|
||||
"name": "payment_info",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "serial",
|
||||
"primaryKey": true,
|
||||
"notNull": true
|
||||
},
|
||||
"cardholder_name": {
|
||||
"name": "cardholder_name",
|
||||
"type": "varchar(128)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"card_number": {
|
||||
"name": "card_number",
|
||||
"type": "varchar(20)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"expiry": {
|
||||
"name": "expiry",
|
||||
"type": "varchar(5)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"cvv": {
|
||||
"name": "cvv",
|
||||
"type": "varchar(6)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"order_id": {
|
||||
"name": "order_id",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"product_id": {
|
||||
"name": "product_id",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "timestamp",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"default": "now()"
|
||||
},
|
||||
"updated_at": {
|
||||
"name": "updated_at",
|
||||
"type": "timestamp",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"default": "now()"
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.product": {
|
||||
"name": "product",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "serial",
|
||||
"primaryKey": true,
|
||||
"notNull": true
|
||||
},
|
||||
"link_id": {
|
||||
"name": "link_id",
|
||||
"type": "varchar(32)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"title": {
|
||||
"name": "title",
|
||||
"type": "varchar(64)",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"description": {
|
||||
"name": "description",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"long_description": {
|
||||
"name": "long_description",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"price": {
|
||||
"name": "price",
|
||||
"type": "numeric(12, 2)",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"default": "'0'"
|
||||
},
|
||||
"discount_price": {
|
||||
"name": "discount_price",
|
||||
"type": "numeric(12, 2)",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"default": "'0'"
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "timestamp",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"default": "now()"
|
||||
},
|
||||
"updated_at": {
|
||||
"name": "updated_at",
|
||||
"type": "timestamp",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"default": "now()"
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {
|
||||
"product_link_id_unique": {
|
||||
"name": "product_link_id_unique",
|
||||
"nullsNotDistinct": false,
|
||||
"columns": [
|
||||
"link_id"
|
||||
]
|
||||
}
|
||||
},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
}
|
||||
},
|
||||
"enums": {},
|
||||
"schemas": {},
|
||||
"sequences": {},
|
||||
"roles": {},
|
||||
"policies": {},
|
||||
"views": {},
|
||||
"_meta": {
|
||||
"columns": {},
|
||||
"schemas": {},
|
||||
"tables": {}
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,13 @@
|
||||
"when": 1760987569532,
|
||||
"tag": "0000_far_jack_power",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 1,
|
||||
"version": "7",
|
||||
"when": 1761003743089,
|
||||
"tag": "0001_gigantic_mach_iv",
|
||||
"breakpoints": true
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -104,45 +104,6 @@ export const paymentInfo = pgTable("payment_info", {
|
||||
updatedAt: timestamp("updated_at").defaultNow(),
|
||||
});
|
||||
|
||||
export const coupon = pgTable("coupon", {
|
||||
id: serial("id").primaryKey(),
|
||||
code: varchar("code", { length: 32 }).notNull().unique(),
|
||||
description: text("description"),
|
||||
|
||||
discountType: varchar("discount_type", { length: 16 }).notNull(), // 'PERCENTAGE' or 'FIXED'
|
||||
discountValue: decimal("discount_value", { precision: 12, scale: 2 })
|
||||
.$type<string>()
|
||||
.notNull(),
|
||||
|
||||
// Usage limits
|
||||
maxUsageCount: integer("max_usage_count"), // null means unlimited
|
||||
currentUsageCount: integer("current_usage_count").default(0).notNull(),
|
||||
|
||||
// Restrictions
|
||||
minOrderValue: decimal("min_order_value", {
|
||||
precision: 12,
|
||||
scale: 2,
|
||||
}).$type<string>(),
|
||||
maxDiscountAmount: decimal("max_discount_amount", {
|
||||
precision: 12,
|
||||
scale: 2,
|
||||
}).$type<string>(),
|
||||
|
||||
// Validity period
|
||||
startDate: timestamp("start_date").notNull(),
|
||||
endDate: timestamp("end_date"),
|
||||
|
||||
// Status
|
||||
isActive: boolean("is_active").default(true).notNull(),
|
||||
|
||||
// Tracking
|
||||
createdAt: timestamp("created_at").defaultNow(),
|
||||
updatedAt: timestamp("updated_at").defaultNow(),
|
||||
createdBy: text("created_by").references(() => user.id, {
|
||||
onDelete: "set null",
|
||||
}),
|
||||
});
|
||||
|
||||
export const checkoutFlowSession = pgTable("checkout_flow_session", {
|
||||
id: serial("id").primaryKey(),
|
||||
flowId: text("flow_id").unique().notNull(),
|
||||
@@ -200,10 +161,3 @@ export const orderRelations = relations(order, ({ one }) => ({
|
||||
references: [paymentInfo.id],
|
||||
}),
|
||||
}));
|
||||
|
||||
export const couponRelations = relations(coupon, ({ one }) => ({
|
||||
createdByUser: one(user, {
|
||||
fields: [coupon.createdBy],
|
||||
references: [user.id],
|
||||
}),
|
||||
}));
|
||||
|
||||
@@ -1,41 +0,0 @@
|
||||
import { z } from "zod";
|
||||
|
||||
export enum DiscountType {
|
||||
PERCENTAGE = "PERCENTAGE",
|
||||
FIXED = "FIXED",
|
||||
}
|
||||
|
||||
export const couponModel = z.object({
|
||||
id: z.number().optional(),
|
||||
code: z.string().min(3).max(32),
|
||||
description: z.string().optional().nullable(),
|
||||
discountType: z.nativeEnum(DiscountType),
|
||||
discountValue: z.coerce.number().positive(),
|
||||
maxUsageCount: z.coerce.number().int().positive().optional().nullable(),
|
||||
currentUsageCount: z.coerce.number().int().nonnegative().default(0),
|
||||
minOrderValue: z.coerce.number().nonnegative().optional().nullable(),
|
||||
maxDiscountAmount: z.coerce.number().positive().optional().nullable(),
|
||||
startDate: z.coerce.string(),
|
||||
endDate: z.coerce.string().optional().nullable(),
|
||||
isActive: z.boolean().default(true),
|
||||
createdAt: z.coerce.string().optional(),
|
||||
updatedAt: z.coerce.string().optional(),
|
||||
createdBy: z.coerce.string().optional().nullable(),
|
||||
});
|
||||
|
||||
export type CouponModel = z.infer<typeof couponModel>;
|
||||
|
||||
export const createCouponPayload = couponModel.omit({
|
||||
id: true,
|
||||
currentUsageCount: true,
|
||||
createdAt: true,
|
||||
updatedAt: true,
|
||||
});
|
||||
|
||||
export type CreateCouponPayload = z.infer<typeof createCouponPayload>;
|
||||
|
||||
export const updateCouponPayload = createCouponPayload.partial().extend({
|
||||
id: z.number(),
|
||||
});
|
||||
|
||||
export type UpdateCouponPayload = z.infer<typeof updateCouponPayload>;
|
||||
@@ -1,491 +0,0 @@
|
||||
import {
|
||||
and,
|
||||
asc,
|
||||
desc,
|
||||
eq,
|
||||
gte,
|
||||
isNull,
|
||||
lte,
|
||||
or,
|
||||
type Database,
|
||||
} from "@pkg/db";
|
||||
import { coupon } from "@pkg/db/schema";
|
||||
import { getError, Logger } from "@pkg/logger";
|
||||
import { ERROR_CODES, type Result } from "@pkg/result";
|
||||
import {
|
||||
couponModel,
|
||||
type CouponModel,
|
||||
type CreateCouponPayload,
|
||||
type UpdateCouponPayload,
|
||||
} from "./data";
|
||||
|
||||
export class CouponRepository {
|
||||
private db: Database;
|
||||
|
||||
constructor(db: Database) {
|
||||
this.db = db;
|
||||
}
|
||||
|
||||
async getAllCoupons(): Promise<Result<CouponModel[]>> {
|
||||
try {
|
||||
const results = await this.db.query.coupon.findMany({
|
||||
orderBy: [desc(coupon.createdAt)],
|
||||
});
|
||||
const out = [] as CouponModel[];
|
||||
for (const result of results) {
|
||||
const parsed = couponModel.safeParse(result);
|
||||
if (!parsed.success) {
|
||||
Logger.error("Failed to parse coupon");
|
||||
Logger.error(parsed.error);
|
||||
continue;
|
||||
}
|
||||
out.push(parsed.data);
|
||||
}
|
||||
return { data: out };
|
||||
} catch (e) {
|
||||
return {
|
||||
error: getError(
|
||||
{
|
||||
code: ERROR_CODES.DATABASE_ERROR,
|
||||
message: "Failed to fetch coupons",
|
||||
detail:
|
||||
"An error occurred while retrieving coupons from the database",
|
||||
userHint: "Please try refreshing the page",
|
||||
actionable: false,
|
||||
},
|
||||
e,
|
||||
),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
async getCouponById(id: number): Promise<Result<CouponModel>> {
|
||||
try {
|
||||
const result = await this.db.query.coupon.findFirst({
|
||||
where: eq(coupon.id, id),
|
||||
});
|
||||
|
||||
if (!result) {
|
||||
return {
|
||||
error: getError({
|
||||
code: ERROR_CODES.NOT_FOUND_ERROR,
|
||||
message: "Coupon not found",
|
||||
detail: "No coupon exists with the provided ID",
|
||||
userHint: "Please check the coupon ID and try again",
|
||||
actionable: true,
|
||||
}),
|
||||
};
|
||||
}
|
||||
const parsed = couponModel.safeParse(result);
|
||||
if (!parsed.success) {
|
||||
Logger.error("Failed to parse coupon", result);
|
||||
return {
|
||||
error: getError({
|
||||
code: ERROR_CODES.INTERNAL_SERVER_ERROR,
|
||||
message: "Failed to parse coupon",
|
||||
userHint: "Please try again",
|
||||
detail: "Failed to parse coupon",
|
||||
}),
|
||||
};
|
||||
}
|
||||
return { data: parsed.data };
|
||||
} catch (e) {
|
||||
return {
|
||||
error: getError(
|
||||
{
|
||||
code: ERROR_CODES.DATABASE_ERROR,
|
||||
message: "Failed to fetch coupon",
|
||||
detail:
|
||||
"An error occurred while retrieving the coupon from the database",
|
||||
userHint: "Please try refreshing the page",
|
||||
actionable: false,
|
||||
},
|
||||
e,
|
||||
),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
async createCoupon(payload: CreateCouponPayload): Promise<Result<number>> {
|
||||
try {
|
||||
// Check if coupon code already exists
|
||||
const existing = await this.db.query.coupon.findFirst({
|
||||
where: eq(coupon.code, payload.code),
|
||||
});
|
||||
|
||||
if (existing) {
|
||||
return {
|
||||
error: getError({
|
||||
code: ERROR_CODES.DATABASE_ERROR,
|
||||
message: "Coupon code already exists",
|
||||
detail: "A coupon with this code already exists in the system",
|
||||
userHint: "Please use a different coupon code",
|
||||
actionable: true,
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
const result = await this.db
|
||||
.insert(coupon)
|
||||
.values({
|
||||
code: payload.code,
|
||||
description: payload.description || null,
|
||||
discountType: payload.discountType,
|
||||
discountValue: payload.discountValue.toString(),
|
||||
maxUsageCount: payload.maxUsageCount,
|
||||
minOrderValue: payload.minOrderValue
|
||||
? payload.minOrderValue.toString()
|
||||
: null,
|
||||
maxDiscountAmount: payload.maxDiscountAmount
|
||||
? payload.maxDiscountAmount.toString()
|
||||
: null,
|
||||
startDate: new Date(payload.startDate),
|
||||
endDate: payload.endDate ? new Date(payload.endDate) : null,
|
||||
isActive: payload.isActive,
|
||||
createdBy: payload.createdBy || null,
|
||||
})
|
||||
.returning({ id: coupon.id })
|
||||
.execute();
|
||||
|
||||
if (!result || result.length === 0) {
|
||||
throw new Error("Failed to create coupon record");
|
||||
}
|
||||
|
||||
return { data: result[0].id };
|
||||
} catch (e) {
|
||||
return {
|
||||
error: getError(
|
||||
{
|
||||
code: ERROR_CODES.DATABASE_ERROR,
|
||||
message: "Failed to create coupon",
|
||||
detail: "An error occurred while creating the coupon",
|
||||
userHint: "Please try again",
|
||||
actionable: false,
|
||||
},
|
||||
e,
|
||||
),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
async updateCoupon(payload: UpdateCouponPayload): Promise<Result<boolean>> {
|
||||
try {
|
||||
if (!payload.id) {
|
||||
return {
|
||||
error: getError({
|
||||
code: ERROR_CODES.VALIDATION_ERROR,
|
||||
message: "Invalid coupon ID",
|
||||
detail: "No coupon ID was provided for the update operation",
|
||||
userHint: "Please provide a valid coupon ID",
|
||||
actionable: true,
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
// Check if coupon exists
|
||||
const existing = await this.db.query.coupon.findFirst({
|
||||
where: eq(coupon.id, payload.id),
|
||||
});
|
||||
|
||||
if (!existing) {
|
||||
return {
|
||||
error: getError({
|
||||
code: ERROR_CODES.NOT_FOUND_ERROR,
|
||||
message: "Coupon not found",
|
||||
detail: "No coupon exists with the provided ID",
|
||||
userHint: "Please check the coupon ID and try again",
|
||||
actionable: true,
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
// If changing the code, check if the new code already exists
|
||||
if (payload.code && payload.code !== existing.code) {
|
||||
const codeExists = await this.db.query.coupon.findFirst({
|
||||
where: eq(coupon.code, payload.code),
|
||||
});
|
||||
|
||||
if (codeExists) {
|
||||
return {
|
||||
error: getError({
|
||||
code: ERROR_CODES.DATABASE_ERROR,
|
||||
message: "Coupon code already exists",
|
||||
detail: "A coupon with this code already exists in the system",
|
||||
userHint: "Please use a different coupon code",
|
||||
actionable: true,
|
||||
}),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Build the update object with only the fields that are provided
|
||||
const updateValues: Record<string, any> = {};
|
||||
|
||||
if (payload.code !== undefined) updateValues.code = payload.code;
|
||||
if (payload.description !== undefined)
|
||||
updateValues.description = payload.description;
|
||||
if (payload.discountType !== undefined)
|
||||
updateValues.discountType = payload.discountType;
|
||||
if (payload.discountValue !== undefined)
|
||||
updateValues.discountValue = payload.discountValue.toString();
|
||||
if (payload.maxUsageCount !== undefined)
|
||||
updateValues.maxUsageCount = payload.maxUsageCount;
|
||||
if (payload.minOrderValue !== undefined)
|
||||
updateValues.minOrderValue = payload.minOrderValue?.toString() || null;
|
||||
if (payload.maxDiscountAmount !== undefined)
|
||||
updateValues.maxDiscountAmount =
|
||||
payload.maxDiscountAmount?.toString() || null;
|
||||
if (payload.startDate !== undefined)
|
||||
updateValues.startDate = new Date(payload.startDate);
|
||||
if (payload.endDate !== undefined)
|
||||
updateValues.endDate = payload.endDate
|
||||
? new Date(payload.endDate)
|
||||
: null;
|
||||
if (payload.isActive !== undefined)
|
||||
updateValues.isActive = payload.isActive;
|
||||
updateValues.updatedAt = new Date();
|
||||
|
||||
await this.db
|
||||
.update(coupon)
|
||||
.set(updateValues)
|
||||
.where(eq(coupon.id, payload.id))
|
||||
.execute();
|
||||
|
||||
return { data: true };
|
||||
} catch (e) {
|
||||
return {
|
||||
error: getError(
|
||||
{
|
||||
code: ERROR_CODES.DATABASE_ERROR,
|
||||
message: "Failed to update coupon",
|
||||
detail: "An error occurred while updating the coupon",
|
||||
userHint: "Please try again",
|
||||
actionable: false,
|
||||
},
|
||||
e,
|
||||
),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
async deleteCoupon(id: number): Promise<Result<boolean>> {
|
||||
try {
|
||||
// Check if coupon exists
|
||||
const existing = await this.db.query.coupon.findFirst({
|
||||
where: eq(coupon.id, id),
|
||||
});
|
||||
|
||||
if (!existing) {
|
||||
return {
|
||||
error: getError({
|
||||
code: ERROR_CODES.NOT_FOUND_ERROR,
|
||||
message: "Coupon not found",
|
||||
detail: "No coupon exists with the provided ID",
|
||||
userHint: "Please check the coupon ID and try again",
|
||||
actionable: true,
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
await this.db.delete(coupon).where(eq(coupon.id, id)).execute();
|
||||
|
||||
return { data: true };
|
||||
} catch (e) {
|
||||
return {
|
||||
error: getError(
|
||||
{
|
||||
code: ERROR_CODES.DATABASE_ERROR,
|
||||
message: "Failed to delete coupon",
|
||||
detail: "An error occurred while deleting the coupon",
|
||||
userHint: "Please try again",
|
||||
actionable: false,
|
||||
},
|
||||
e,
|
||||
),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
async toggleCouponStatus(
|
||||
id: number,
|
||||
isActive: boolean,
|
||||
): Promise<Result<boolean>> {
|
||||
try {
|
||||
// Check if coupon exists
|
||||
const existing = await this.db.query.coupon.findFirst({
|
||||
where: eq(coupon.id, id),
|
||||
});
|
||||
|
||||
if (!existing) {
|
||||
return {
|
||||
error: getError({
|
||||
code: ERROR_CODES.NOT_FOUND_ERROR,
|
||||
message: "Coupon not found",
|
||||
detail: "No coupon exists with the provided ID",
|
||||
userHint: "Please check the coupon ID and try again",
|
||||
actionable: true,
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
await this.db
|
||||
.update(coupon)
|
||||
.set({
|
||||
isActive,
|
||||
updatedAt: new Date(),
|
||||
})
|
||||
.where(eq(coupon.id, id))
|
||||
.execute();
|
||||
|
||||
return { data: true };
|
||||
} catch (e) {
|
||||
return {
|
||||
error: getError(
|
||||
{
|
||||
code: ERROR_CODES.DATABASE_ERROR,
|
||||
message: "Failed to update coupon status",
|
||||
detail: "An error occurred while updating the coupon's status",
|
||||
userHint: "Please try again",
|
||||
actionable: false,
|
||||
},
|
||||
e,
|
||||
),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
async getCouponByCode(code: string): Promise<Result<CouponModel>> {
|
||||
try {
|
||||
const result = await this.db.query.coupon.findFirst({
|
||||
where: eq(coupon.code, code),
|
||||
});
|
||||
|
||||
if (!result) {
|
||||
return {
|
||||
error: getError({
|
||||
code: ERROR_CODES.NOT_FOUND_ERROR,
|
||||
message: "Coupon not found",
|
||||
detail: "No coupon exists with the provided code",
|
||||
userHint: "Please check the coupon code and try again",
|
||||
actionable: true,
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
const parsed = couponModel.safeParse(result);
|
||||
if (!parsed.success) {
|
||||
Logger.error("Failed to parse coupon", result);
|
||||
return {
|
||||
error: getError({
|
||||
code: ERROR_CODES.INTERNAL_SERVER_ERROR,
|
||||
message: "Failed to parse coupon",
|
||||
userHint: "Please try again",
|
||||
detail: "Failed to parse coupon",
|
||||
}),
|
||||
};
|
||||
}
|
||||
return { data: parsed.data };
|
||||
} catch (e) {
|
||||
return {
|
||||
error: getError(
|
||||
{
|
||||
code: ERROR_CODES.DATABASE_ERROR,
|
||||
message: "Failed to fetch coupon",
|
||||
detail:
|
||||
"An error occurred while retrieving the coupon from the database",
|
||||
userHint: "Please try again",
|
||||
actionable: false,
|
||||
},
|
||||
e,
|
||||
),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
async getActiveCoupons(): Promise<Result<CouponModel[]>> {
|
||||
try {
|
||||
const now = new Date();
|
||||
|
||||
const results = await this.db.query.coupon.findMany({
|
||||
where: and(
|
||||
eq(coupon.isActive, true),
|
||||
lte(coupon.startDate, now),
|
||||
// Either endDate is null (no end date) or it's greater than now
|
||||
or(isNull(coupon.endDate), gte(coupon.endDate, now)),
|
||||
),
|
||||
orderBy: [asc(coupon.code)],
|
||||
});
|
||||
const out = [] as CouponModel[];
|
||||
for (const result of results) {
|
||||
const parsed = couponModel.safeParse(result);
|
||||
if (!parsed.success) {
|
||||
Logger.error("Failed to parse coupon", result);
|
||||
continue;
|
||||
}
|
||||
out.push(parsed.data);
|
||||
}
|
||||
return { data: out };
|
||||
} catch (e) {
|
||||
return {
|
||||
error: getError(
|
||||
{
|
||||
code: ERROR_CODES.DATABASE_ERROR,
|
||||
message: "Failed to fetch active coupons",
|
||||
detail:
|
||||
"An error occurred while retrieving active coupons from the database",
|
||||
userHint: "Please try refreshing the page",
|
||||
actionable: false,
|
||||
},
|
||||
e,
|
||||
),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
async getBestActiveCoupon(): Promise<Result<CouponModel>> {
|
||||
try {
|
||||
const now = new Date();
|
||||
|
||||
// Fetch all active coupons that are currently valid
|
||||
const activeCoupons = await this.db.query.coupon.findMany({
|
||||
where: and(
|
||||
eq(coupon.isActive, true),
|
||||
lte(coupon.startDate, now),
|
||||
// Either endDate is null (no end date) or it's greater than now
|
||||
or(isNull(coupon.endDate), gte(coupon.endDate, now)),
|
||||
),
|
||||
orderBy: [
|
||||
// Order by discount type (PERCENTAGE first) and then by discount value (descending)
|
||||
asc(coupon.discountType),
|
||||
desc(coupon.discountValue),
|
||||
],
|
||||
});
|
||||
|
||||
if (!activeCoupons || activeCoupons.length === 0) {
|
||||
return {}; // No active coupons found
|
||||
}
|
||||
|
||||
// Get the first (best) coupon
|
||||
const bestCoupon = activeCoupons[0];
|
||||
|
||||
// Check if max usage limit is reached
|
||||
if (
|
||||
bestCoupon.maxUsageCount !== null &&
|
||||
bestCoupon.currentUsageCount >= bestCoupon.maxUsageCount
|
||||
) {
|
||||
return {}; // Coupon usage limit reached
|
||||
}
|
||||
|
||||
const parsed = couponModel.safeParse(bestCoupon);
|
||||
if (!parsed.success) {
|
||||
Logger.error("Failed to parse coupon", bestCoupon);
|
||||
return {}; // Return null on error, don't break ticket search
|
||||
}
|
||||
|
||||
return { data: parsed.data };
|
||||
} catch (e) {
|
||||
Logger.error("Error fetching active coupons", e);
|
||||
return {}; // Return null on error, don't break ticket search
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
import { db } from "@pkg/db";
|
||||
import { CouponRepository } from "./repository";
|
||||
import type { CreateCouponPayload, UpdateCouponPayload } from "./data";
|
||||
import type { UserModel } from "@pkg/logic/domains/user/data/entities";
|
||||
|
||||
export class CouponUseCases {
|
||||
private repo: CouponRepository;
|
||||
|
||||
constructor(repo: CouponRepository) {
|
||||
this.repo = repo;
|
||||
}
|
||||
|
||||
async getAllCoupons() {
|
||||
return this.repo.getAllCoupons();
|
||||
}
|
||||
|
||||
async getCouponById(id: number) {
|
||||
return this.repo.getCouponById(id);
|
||||
}
|
||||
|
||||
async createCoupon(currentUser: UserModel, payload: CreateCouponPayload) {
|
||||
// Set the current user as the creator
|
||||
const payloadWithUser = {
|
||||
...payload,
|
||||
createdBy: currentUser.id,
|
||||
};
|
||||
return this.repo.createCoupon(payloadWithUser);
|
||||
}
|
||||
|
||||
async updateCoupon(payload: UpdateCouponPayload) {
|
||||
return this.repo.updateCoupon(payload);
|
||||
}
|
||||
|
||||
async deleteCoupon(id: number) {
|
||||
return this.repo.deleteCoupon(id);
|
||||
}
|
||||
|
||||
async toggleCouponStatus(id: number, isActive: boolean) {
|
||||
return this.repo.toggleCouponStatus(id, isActive);
|
||||
}
|
||||
|
||||
async getActiveCoupons() {
|
||||
return this.repo.getActiveCoupons();
|
||||
}
|
||||
}
|
||||
|
||||
export function getCouponUseCases() {
|
||||
return new CouponUseCases(new CouponRepository(db));
|
||||
}
|
||||
@@ -50,10 +50,11 @@ export class ProductRepository {
|
||||
}
|
||||
}
|
||||
|
||||
async getProductById(id: number): Promise<Result<ProductModel>> {
|
||||
async getProductById(id: number | string): Promise<Result<ProductModel>> {
|
||||
try {
|
||||
const result = await this.db.query.product.findFirst({
|
||||
where: eq(product.id, id),
|
||||
where:
|
||||
typeof id === "number" ? eq(product.id, id) : eq(product.linkId, id),
|
||||
});
|
||||
|
||||
if (!result) {
|
||||
|
||||
@@ -17,6 +17,10 @@ export class ProductUseCases {
|
||||
return this.repo.getProductById(id);
|
||||
}
|
||||
|
||||
async getProductByLinkId(linkId: string) {
|
||||
return this.repo.getProductById(linkId);
|
||||
}
|
||||
|
||||
async createProduct(payload: CreateProductPayload) {
|
||||
return this.repo.createProduct(payload);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user