and so it begins

This commit is contained in:
user
2025-10-20 18:59:38 +03:00
parent f5b99afc8f
commit e239b3bbf6
53 changed files with 4813 additions and 2887 deletions

View File

@@ -1,175 +0,0 @@
# Based on https://raw.githubusercontent.com/github/gitignore/main/Node.gitignore
# Logs
logs
_.log
npm-debug.log_
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
.pnpm-debug.log*
# Caches
.cache
# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json
# Runtime data
pids
_.pid
_.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
*.lcov
# nyc test coverage
.nyc_output
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# Snowpack dependency directory (https://snowpack.dev/)
web_modules/
# TypeScript cache
*.tsbuildinfo
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional stylelint cache
.stylelintcache
# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variable files
.env
.env.development.local
.env.test.local
.env.production.local
.env.local
# parcel-bundler cache (https://parceljs.org/)
.parcel-cache
# Next.js build output
.next
out
# Nuxt.js build / generate output
.nuxt
dist
# Gatsby files
# Comment in the public line in if your project uses Gatsby and not Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support
# public
# vuepress build output
.vuepress/dist
# vuepress v2.x temp and cache directory
.temp
# Docusaurus cache and generated files
.docusaurus
# Serverless directories
.serverless/
# FuseBox cache
.fusebox/
# DynamoDB Local files
.dynamodb/
# TernJS port file
.tern-port
# Stores VSCode versions used for testing VSCode extensions
.vscode-test
# yarn v2
.yarn/cache
.yarn/unplugged
.yarn/build-state.yml
.yarn/install-state.gz
.pnp.*
# IntelliJ based IDEs
.idea
# Finder (MacOS) folder config
.DS_Store

View File

@@ -1,14 +0,0 @@
import { defineConfig } from "drizzle-kit";
import "dotenv/config";
export default defineConfig({
schema: "./schema",
out: "./migrations",
dialect: "postgresql",
verbose: true,
strict: false,
dbCredentials: {
url: process.env.AIRPORTS_DB_URL ?? "",
},
});

View File

@@ -1,17 +0,0 @@
import "dotenv/config";
import { drizzle } from "drizzle-orm/postgres-js";
import postgres from "postgres";
import * as schema from "./schema";
const dbUrl = process.env.AIRPORTS_DB_URL ?? "";
const client = postgres(dbUrl);
const db = drizzle(client, { schema });
export type AirportsDatabase = typeof db;
export * from "drizzle-orm";
export { db as airportsDb, schema };

View File

@@ -1,30 +0,0 @@
CREATE TABLE IF NOT EXISTS "airport" (
"id" serial PRIMARY KEY NOT NULL,
"type" varchar(64) NOT NULL,
"name" varchar(128) NOT NULL,
"gps_code" varchar(64) NOT NULL,
"ident" varchar(128),
"latitude_deg" numeric(10, 2),
"longitude_deg" numeric(10, 2),
"elevation_ft" numeric(10, 2),
"continent" varchar(64),
"country" varchar(172),
"iso_country" varchar(4) NOT NULL,
"iso_region" varchar(64) NOT NULL,
"municipality" varchar(64),
"scheduled_service" varchar(64),
"iata_code" varchar(16) NOT NULL,
"local_code" varchar(16) NOT NULL,
"created_at" timestamp DEFAULT now(),
"updated_at" timestamp DEFAULT now(),
"search_vector" "tsvector" GENERATED ALWAYS AS (
setweight(to_tsvector('english', coalesce("airport"."name", '')), 'A') ||
setweight(to_tsvector('english', coalesce("airport"."iata_code", '')), 'A') ||
setweight(to_tsvector('english', coalesce("airport"."municipality", '')), 'B') ||
setweight(to_tsvector('english', coalesce("airport"."country", '')), 'C')
) STORED
);
--> statement-breakpoint
CREATE INDEX IF NOT EXISTS "name_idx" ON "airport" USING btree ("name");--> statement-breakpoint
CREATE INDEX IF NOT EXISTS "gps_code_idx" ON "airport" USING btree ("gps_code");--> statement-breakpoint
CREATE INDEX IF NOT EXISTS "search_vector_idx" ON "airport" USING gin ("search_vector");

View File

@@ -1,16 +0,0 @@
-- Custom SQL migration file, put your code below! --
CREATE EXTENSION IF NOT EXISTS pg_trgm;
CREATE EXTENSION IF NOT EXISTS btree_gin;
CREATE INDEX IF NOT EXISTS "trgm_idx_airport_name"
ON airport USING gin ("name" gin_trgm_ops);
CREATE INDEX IF NOT EXISTS "trgm_idx_airport_iatacode"
ON airport USING gin ("iata_code" gin_trgm_ops);
CREATE INDEX IF NOT EXISTS "trgm_idx_airport_municipality"
ON airport USING gin ("municipality" gin_trgm_ops);
CREATE INDEX IF NOT EXISTS "trgm_idx_airport_country"
ON airport USING gin ("country" gin_trgm_ops);

View File

@@ -1,198 +0,0 @@
{
"id": "2202b3d8-281c-474f-903d-27296ca06458",
"prevId": "00000000-0000-0000-0000-000000000000",
"version": "7",
"dialect": "postgresql",
"tables": {
"public.airport": {
"name": "airport",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "serial",
"primaryKey": true,
"notNull": true
},
"type": {
"name": "type",
"type": "varchar(64)",
"primaryKey": false,
"notNull": true
},
"name": {
"name": "name",
"type": "varchar(128)",
"primaryKey": false,
"notNull": true
},
"gps_code": {
"name": "gps_code",
"type": "varchar(64)",
"primaryKey": false,
"notNull": true
},
"ident": {
"name": "ident",
"type": "varchar(128)",
"primaryKey": false,
"notNull": false
},
"latitude_deg": {
"name": "latitude_deg",
"type": "numeric(10, 2)",
"primaryKey": false,
"notNull": false
},
"longitude_deg": {
"name": "longitude_deg",
"type": "numeric(10, 2)",
"primaryKey": false,
"notNull": false
},
"elevation_ft": {
"name": "elevation_ft",
"type": "numeric(10, 2)",
"primaryKey": false,
"notNull": false
},
"continent": {
"name": "continent",
"type": "varchar(64)",
"primaryKey": false,
"notNull": false
},
"country": {
"name": "country",
"type": "varchar(172)",
"primaryKey": false,
"notNull": false
},
"iso_country": {
"name": "iso_country",
"type": "varchar(4)",
"primaryKey": false,
"notNull": true
},
"iso_region": {
"name": "iso_region",
"type": "varchar(64)",
"primaryKey": false,
"notNull": true
},
"municipality": {
"name": "municipality",
"type": "varchar(64)",
"primaryKey": false,
"notNull": false
},
"scheduled_service": {
"name": "scheduled_service",
"type": "varchar(64)",
"primaryKey": false,
"notNull": false
},
"iata_code": {
"name": "iata_code",
"type": "varchar(16)",
"primaryKey": false,
"notNull": true
},
"local_code": {
"name": "local_code",
"type": "varchar(16)",
"primaryKey": false,
"notNull": true
},
"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()"
},
"search_vector": {
"name": "search_vector",
"type": "tsvector",
"primaryKey": false,
"notNull": false,
"generated": {
"as": "\n setweight(to_tsvector('english', coalesce(\"airport\".\"name\", '')), 'A') ||\n setweight(to_tsvector('english', coalesce(\"airport\".\"iata_code\", '')), 'A') ||\n setweight(to_tsvector('english', coalesce(\"airport\".\"municipality\", '')), 'B') ||\n setweight(to_tsvector('english', coalesce(\"airport\".\"country\", '')), 'C')\n ",
"type": "stored"
}
}
},
"indexes": {
"name_idx": {
"name": "name_idx",
"columns": [
{
"expression": "name",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": false,
"concurrently": false,
"method": "btree",
"with": {}
},
"gps_code_idx": {
"name": "gps_code_idx",
"columns": [
{
"expression": "gps_code",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": false,
"concurrently": false,
"method": "btree",
"with": {}
},
"search_vector_idx": {
"name": "search_vector_idx",
"columns": [
{
"expression": "search_vector",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": false,
"concurrently": false,
"method": "gin",
"with": {}
}
},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
}
},
"enums": {},
"schemas": {},
"sequences": {},
"roles": {},
"policies": {},
"views": {},
"_meta": {
"columns": {},
"schemas": {},
"tables": {}
}
}

View File

@@ -1,198 +0,0 @@
{
"id": "864dcba9-6d53-4311-be3e-0a51a483dac2",
"prevId": "2202b3d8-281c-474f-903d-27296ca06458",
"version": "7",
"dialect": "postgresql",
"tables": {
"public.airport": {
"name": "airport",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "serial",
"primaryKey": true,
"notNull": true
},
"type": {
"name": "type",
"type": "varchar(64)",
"primaryKey": false,
"notNull": true
},
"name": {
"name": "name",
"type": "varchar(128)",
"primaryKey": false,
"notNull": true
},
"gps_code": {
"name": "gps_code",
"type": "varchar(64)",
"primaryKey": false,
"notNull": true
},
"ident": {
"name": "ident",
"type": "varchar(128)",
"primaryKey": false,
"notNull": false
},
"latitude_deg": {
"name": "latitude_deg",
"type": "numeric(10, 2)",
"primaryKey": false,
"notNull": false
},
"longitude_deg": {
"name": "longitude_deg",
"type": "numeric(10, 2)",
"primaryKey": false,
"notNull": false
},
"elevation_ft": {
"name": "elevation_ft",
"type": "numeric(10, 2)",
"primaryKey": false,
"notNull": false
},
"continent": {
"name": "continent",
"type": "varchar(64)",
"primaryKey": false,
"notNull": false
},
"country": {
"name": "country",
"type": "varchar(172)",
"primaryKey": false,
"notNull": false
},
"iso_country": {
"name": "iso_country",
"type": "varchar(4)",
"primaryKey": false,
"notNull": true
},
"iso_region": {
"name": "iso_region",
"type": "varchar(64)",
"primaryKey": false,
"notNull": true
},
"municipality": {
"name": "municipality",
"type": "varchar(64)",
"primaryKey": false,
"notNull": false
},
"scheduled_service": {
"name": "scheduled_service",
"type": "varchar(64)",
"primaryKey": false,
"notNull": false
},
"iata_code": {
"name": "iata_code",
"type": "varchar(16)",
"primaryKey": false,
"notNull": true
},
"local_code": {
"name": "local_code",
"type": "varchar(16)",
"primaryKey": false,
"notNull": true
},
"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()"
},
"search_vector": {
"name": "search_vector",
"type": "tsvector",
"primaryKey": false,
"notNull": false,
"generated": {
"type": "stored",
"as": "\n setweight(to_tsvector('english', coalesce(\"airport\".\"name\", '')), 'A') ||\n setweight(to_tsvector('english', coalesce(\"airport\".\"iata_code\", '')), 'A') ||\n setweight(to_tsvector('english', coalesce(\"airport\".\"municipality\", '')), 'B') ||\n setweight(to_tsvector('english', coalesce(\"airport\".\"country\", '')), 'C')\n "
}
}
},
"indexes": {
"name_idx": {
"name": "name_idx",
"columns": [
{
"expression": "name",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": false,
"with": {},
"method": "btree",
"concurrently": false
},
"gps_code_idx": {
"name": "gps_code_idx",
"columns": [
{
"expression": "gps_code",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": false,
"with": {},
"method": "btree",
"concurrently": false
},
"search_vector_idx": {
"name": "search_vector_idx",
"columns": [
{
"expression": "search_vector",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": false,
"with": {},
"method": "gin",
"concurrently": false
}
},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
}
},
"enums": {},
"schemas": {},
"views": {},
"sequences": {},
"roles": {},
"policies": {},
"_meta": {
"columns": {},
"schemas": {},
"tables": {}
}
}

View File

@@ -1,20 +0,0 @@
{
"version": "7",
"dialect": "postgresql",
"entries": [
{
"idx": 0,
"version": "7",
"when": 1746008605929,
"tag": "0000_friendly_thunderbolts",
"breakpoints": true
},
{
"idx": 1,
"version": "7",
"when": 1746008876617,
"tag": "0001_military_hannibal_king",
"breakpoints": true
}
]
}

View File

@@ -1,26 +0,0 @@
{
"name": "@pkg/airports-db",
"module": "index.ts",
"type": "module",
"scripts": {
"db:gen": "drizzle-kit generate --config=drizzle.config.ts",
"db:drop": "drizzle-kit drop --config=drizzle.config.ts",
"db:push": "drizzle-kit push --config=drizzle.config.ts",
"db:migrate": "drizzle-kit generate --config=drizzle.config.ts && drizzle-kit push --config=drizzle.config.ts",
"db:forcemigrate": "drizzle-kit generate --config=drizzle.config.ts && drizzle-kit push --config=drizzle.config.ts --force",
"dev": "drizzle-kit studio --host=0.0.0.0 --port=5421 --config=drizzle.config.ts --verbose"
},
"devDependencies": {
"@types/bun": "latest",
"@types/pg": "^8.11.10",
"drizzle-kit": "^0.28.0"
},
"peerDependencies": {
"typescript": "^5.0.0"
},
"dependencies": {
"dotenv": "^16.4.7",
"drizzle-orm": "^0.36.1",
"postgres": "^3.4.5"
}
}

View File

@@ -1,70 +0,0 @@
import { SQL, sql } from "drizzle-orm";
import {
pgTable,
serial,
varchar,
decimal,
timestamp,
index,
customType,
} from "drizzle-orm/pg-core";
const tsvector = customType<{ data: unknown }>({
dataType() {
return "tsvector";
},
});
export const airport = pgTable(
"airport",
{
id: serial("id").primaryKey(),
type: varchar("type", { length: 64 }).notNull(),
name: varchar("name", { length: 128 }).notNull(),
gpsCode: varchar("gps_code", { length: 64 }).notNull(),
ident: varchar("ident", { length: 128 }),
latitudeDeg: decimal("latitude_deg", { precision: 10, scale: 2 }),
longitudeDeg: decimal("longitude_deg", {
precision: 10,
scale: 2,
}),
elevationFt: decimal("elevation_ft", { precision: 10, scale: 2 }),
continent: varchar("continent", { length: 64 }),
country: varchar("country", { length: 172 }),
isoCountry: varchar("iso_country", { length: 4 }).notNull(),
isoRegion: varchar("iso_region", { length: 64 }).notNull(),
municipality: varchar("municipality", { length: 64 }),
scheduledService: varchar("scheduled_service", { length: 64 }),
iataCode: varchar("iata_code", { length: 16 }).notNull(),
localCode: varchar("local_code", { length: 16 }).notNull(),
createdAt: timestamp("created_at").defaultNow(),
updatedAt: timestamp("updated_at").defaultNow(),
searchVector: tsvector("search_vector").generatedAlwaysAs(
(): SQL => sql`
setweight(to_tsvector('english', coalesce(${airport.name}, '')), 'A') ||
setweight(to_tsvector('english', coalesce(${airport.iataCode}, '')), 'A') ||
setweight(to_tsvector('english', coalesce(${airport.municipality}, '')), 'B') ||
setweight(to_tsvector('english', coalesce(${airport.country}, '')), 'C')
`,
),
},
(table) => {
return {
nameIdx: index("name_idx").on(table.name),
gpsCodeIdx: index("gps_code_idx").on(table.gpsCode),
searchVectorIdx: index("search_vector_idx").using(
"gin",
table.searchVector,
),
};
},
);

View File

@@ -0,0 +1,10 @@
ALTER TABLE "account" ALTER COLUMN "created_at" SET DEFAULT now();--> statement-breakpoint
ALTER TABLE "user" ALTER COLUMN "email_verified" SET DEFAULT false;--> statement-breakpoint
ALTER TABLE "user" ALTER COLUMN "created_at" SET DEFAULT now();--> statement-breakpoint
ALTER TABLE "user" ALTER COLUMN "updated_at" SET DEFAULT now();--> statement-breakpoint
ALTER TABLE "user" ALTER COLUMN "banned" SET DEFAULT false;--> statement-breakpoint
ALTER TABLE "user" ALTER COLUMN "discount_percent" SET DEFAULT 0;--> statement-breakpoint
ALTER TABLE "verification" ALTER COLUMN "created_at" SET DEFAULT now();--> statement-breakpoint
ALTER TABLE "verification" ALTER COLUMN "created_at" SET NOT NULL;--> statement-breakpoint
ALTER TABLE "verification" ALTER COLUMN "updated_at" SET DEFAULT now();--> statement-breakpoint
ALTER TABLE "verification" ALTER COLUMN "updated_at" SET NOT NULL;

View File

@@ -0,0 +1,9 @@
CREATE TABLE IF NOT EXISTS "product" (
"id" serial PRIMARY KEY NOT NULL,
"title" varchar(64) NOT NULL,
"description" text NOT NULL,
"long_description" text NOT NULL,
"price" numeric(12, 2) DEFAULT '0',
"created_at" timestamp DEFAULT now(),
"updated_at" timestamp DEFAULT now()
);

View File

@@ -0,0 +1 @@
ALTER TABLE "product" ADD COLUMN "discount_price" numeric(12, 2) DEFAULT '0';

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -29,6 +29,27 @@
"when": 1746375159329,
"tag": "0003_unique_stephen_strange",
"breakpoints": true
},
{
"idx": 4,
"version": "7",
"when": 1760973109915,
"tag": "0004_parched_blue_shield",
"breakpoints": true
},
{
"idx": 5,
"version": "7",
"when": 1760975750129,
"tag": "0005_great_doomsday",
"breakpoints": true
},
{
"idx": 6,
"version": "7",
"when": 1760975763245,
"tag": "0006_puzzling_avengers",
"breakpoints": true
}
]
}

View File

@@ -1,27 +1,30 @@
import {
boolean,
integer,
pgTable,
text,
timestamp,
boolean,
integer,
} from "drizzle-orm/pg-core";
export const user = pgTable("user", {
id: text("id").primaryKey(),
name: text("name").notNull(),
email: text("email").notNull().unique(),
emailVerified: boolean("email_verified").notNull(),
emailVerified: boolean("email_verified").default(false).notNull(),
image: text("image"),
createdAt: timestamp("created_at").notNull(),
updatedAt: timestamp("updated_at").notNull(),
createdAt: timestamp("created_at").defaultNow().notNull(),
updatedAt: timestamp("updated_at")
.defaultNow()
.$onUpdate(() => /* @__PURE__ */ new Date())
.notNull(),
username: text("username").unique(),
displayUsername: text("display_username"),
role: text("role"),
banned: boolean("banned"),
banned: boolean("banned").default(false),
banReason: text("ban_reason"),
banExpires: timestamp("ban_expires"),
parentId: text("parent_id"),
discountPercent: integer("discount_percent"),
discount_percent: integer("discount_percent").default(0),
});
export const account = pgTable("account", {
@@ -38,8 +41,10 @@ export const account = pgTable("account", {
refreshTokenExpiresAt: timestamp("refresh_token_expires_at"),
scope: text("scope"),
password: text("password"),
createdAt: timestamp("created_at").notNull(),
updatedAt: timestamp("updated_at").notNull(),
createdAt: timestamp("created_at").defaultNow().notNull(),
updatedAt: timestamp("updated_at")
.$onUpdate(() => /* @__PURE__ */ new Date())
.notNull(),
});
export const verification = pgTable("verification", {
@@ -47,6 +52,9 @@ export const verification = pgTable("verification", {
identifier: text("identifier").notNull(),
value: text("value").notNull(),
expiresAt: timestamp("expires_at").notNull(),
createdAt: timestamp("created_at"),
updatedAt: timestamp("updated_at"),
createdAt: timestamp("created_at").defaultNow().notNull(),
updatedAt: timestamp("updated_at")
.defaultNow()
.$onUpdate(() => /* @__PURE__ */ new Date())
.notNull(),
});

View File

@@ -1,17 +1,17 @@
import { relations } from "drizzle-orm";
import {
pgTable,
serial,
varchar,
decimal,
boolean,
timestamp,
date,
decimal,
integer,
json,
pgTable,
serial,
text,
date,
timestamp,
varchar,
} from "drizzle-orm/pg-core";
import { user } from "./auth.out";
import { relations } from "drizzle-orm";
export * from "./auth.out";
@@ -60,6 +60,21 @@ export const order = pgTable("order", {
updatedAt: timestamp("updated_at").defaultNow(),
});
export const product = pgTable("product", {
id: serial("id").primaryKey(),
title: varchar("title", { length: 64 }).notNull(),
description: text("description").notNull(),
longDescription: text("long_description").notNull(),
price: decimal("price", { precision: 12, scale: 2 })
.$type<string>()
.default("0"),
discountPrice: decimal("discount_price", { precision: 12, scale: 2 })
.$type<string>()
.default("0"),
createdAt: timestamp("created_at").defaultNow(),
updatedAt: timestamp("updated_at").defaultNow(),
});
export const passengerPII = pgTable("passenger_pii", {
id: serial("id").primaryKey(),
firstName: varchar("first_name", { length: 64 }).notNull(),

View File

@@ -10,25 +10,3 @@ export const numberModel = z
.transform((value) => {
return value !== undefined && isNaN(value) ? undefined : value;
});
export const airportModel = z.object({
id: z.coerce.number().int(),
ident: z.string().nullable().optional(),
type: z.string(),
name: z.string(),
latitudeDeg: numberModel.default(0.0),
longitudeDeg: numberModel.default(0.0),
elevationFt: numberModel.default(0.0),
continent: z.string(),
isoCountry: z.string(),
country: z.string(),
isoRegion: z.string(),
municipality: z.string(),
scheduledService: z.string(),
gpsCode: z.coerce.string().default("----"),
iataCode: z.coerce.string().min(1),
localCode: z.coerce.string().default("----"),
createdAt: z.coerce.date(),
updatedAt: z.coerce.date(),
});
export type Airport = z.infer<typeof airportModel>;

View File

@@ -10,8 +10,8 @@ import {
type Database,
} from "@pkg/db";
import { coupon } from "@pkg/db/schema";
import { ERROR_CODES, type Result } from "@pkg/result";
import { getError, Logger } from "@pkg/logger";
import { ERROR_CODES, type Result } from "@pkg/result";
import {
couponModel,
type CouponModel,

View File

@@ -0,0 +1,14 @@
import { z } from "zod";
export const productModel = z.object({
id: z.string(),
linkId: z.string(),
title: z.string(),
description: z.string(),
longDescription: z.string(),
price: z.number(),
discountPrice: z.number(),
createdAt: z.coerce.string().optional(),
updatedAt: z.coerce.string().optional(),
});
export type ProductModel = z.infer<typeof productModel>;

View File

@@ -0,0 +1,15 @@
import { Database } from "@pkg/db";
export class ProductRepository {
private db: Database;
constructor(db: Database) {
this.db = db;
}
// TODO: compelte the crud method implementation
async listAllProducts() {}
async createProduct() {}
async updateProduct() {}
async deleteProduct() {}
}