- {#if ticketCheckoutVM.loading}
+ {#if checkoutVM.loading}
- {:else if ticketCheckoutVM.checkoutStep === CheckoutStep.Initial}
+ {:else if checkoutVM.checkoutStep === CheckoutStep.Initial}
- {:else if ticketCheckoutVM.checkoutStep === CheckoutStep.Payment}
+ {:else if checkoutVM.checkoutStep === CheckoutStep.Payment}
- {:else if ticketCheckoutVM.checkoutStep === CheckoutStep.Verification}
+ {:else if checkoutVM.checkoutStep === CheckoutStep.Verification}
{/if}
diff --git a/apps/frontend/src/routes/(main)/search/+page.svelte b/apps/frontend/src/routes/(main)/search/+page.svelte
deleted file mode 100644
index 3c5d23a..0000000
--- a/apps/frontend/src/routes/(main)/search/+page.svelte
+++ /dev/null
@@ -1,49 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
- {#if flightTicketVM.tickets.length > 0}
-
-
-
- {/if}
-
-
-
-
diff --git a/context.md b/context.md
index 990710c..a1d8033 100644
--- a/context.md
+++ b/context.md
@@ -1,5 +1,9 @@
-# The goal
+# "Domain Wall" (honestly no idea why I named it this, but juss gonna go wid it)
-The goal is that we need to create a sort of a make-shift checkout panel
+The goal is to create a product checkout page, something like gumroad/stripe where the user is coming here to do checkout on our product page,
----
+Which is basically a proxy way for us to do the payment on their behalf, e.g how the internet billing companies do over the phone, we're cooking this live-sync panel where the checkout info is shown to the (admin panel) agent whose on the call with the user in order to do the info filling on their behalf, since the user is typing the info on the checkout page it's gonna be 100% accurate and fast.
+
+This is a reboot of my previous learning project – which was somewhat a mashup of a travel booking website with this same sync logic.
+
+But now doing a flexible product-based checkout, where the admin can flexibly set products for the customers to bill and then gather the info for the work.
diff --git a/packages/db/migrations/0001_gigantic_mach_iv.sql b/packages/db/migrations/0001_gigantic_mach_iv.sql
new file mode 100644
index 0000000..c8a4330
--- /dev/null
+++ b/packages/db/migrations/0001_gigantic_mach_iv.sql
@@ -0,0 +1 @@
+DROP TABLE "coupon" CASCADE;
\ No newline at end of file
diff --git a/packages/db/migrations/meta/0001_snapshot.json b/packages/db/migrations/meta/0001_snapshot.json
new file mode 100644
index 0000000..07b33cf
--- /dev/null
+++ b/packages/db/migrations/meta/0001_snapshot.json
@@ -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": {}
+ }
+}
\ No newline at end of file
diff --git a/packages/db/migrations/meta/_journal.json b/packages/db/migrations/meta/_journal.json
index 7e549c4..37d14a1 100644
--- a/packages/db/migrations/meta/_journal.json
+++ b/packages/db/migrations/meta/_journal.json
@@ -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
}
]
}
\ No newline at end of file
diff --git a/packages/db/schema/index.ts b/packages/db/schema/index.ts
index bf30843..d2ef6f8 100644
--- a/packages/db/schema/index.ts
+++ b/packages/db/schema/index.ts
@@ -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
()
- .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(),
- maxDiscountAmount: decimal("max_discount_amount", {
- precision: 12,
- scale: 2,
- }).$type(),
-
- // 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],
- }),
-}));
diff --git a/packages/logic/domains/coupon/data.ts b/packages/logic/domains/coupon/data.ts
deleted file mode 100644
index 3d9765a..0000000
--- a/packages/logic/domains/coupon/data.ts
+++ /dev/null
@@ -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;
-
-export const createCouponPayload = couponModel.omit({
- id: true,
- currentUsageCount: true,
- createdAt: true,
- updatedAt: true,
-});
-
-export type CreateCouponPayload = z.infer;
-
-export const updateCouponPayload = createCouponPayload.partial().extend({
- id: z.number(),
-});
-
-export type UpdateCouponPayload = z.infer;
diff --git a/packages/logic/domains/coupon/repository.ts b/packages/logic/domains/coupon/repository.ts
deleted file mode 100644
index ddce66f..0000000
--- a/packages/logic/domains/coupon/repository.ts
+++ /dev/null
@@ -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> {
- 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> {
- 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> {
- 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> {
- 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 = {};
-
- 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> {
- 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> {
- 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> {
- 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> {
- 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> {
- 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
- }
- }
-}
diff --git a/packages/logic/domains/coupon/usecases.ts b/packages/logic/domains/coupon/usecases.ts
deleted file mode 100644
index c92954e..0000000
--- a/packages/logic/domains/coupon/usecases.ts
+++ /dev/null
@@ -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));
-}
diff --git a/packages/logic/domains/product/repository.ts b/packages/logic/domains/product/repository.ts
index 523fc45..fe0eb13 100644
--- a/packages/logic/domains/product/repository.ts
+++ b/packages/logic/domains/product/repository.ts
@@ -50,10 +50,11 @@ export class ProductRepository {
}
}
- async getProductById(id: number): Promise> {
+ async getProductById(id: number | string): Promise> {
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) {
diff --git a/packages/logic/domains/product/usecases.ts b/packages/logic/domains/product/usecases.ts
index 1775358..494998c 100644
--- a/packages/logic/domains/product/usecases.ts
+++ b/packages/logic/domains/product/usecases.ts
@@ -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);
}