chore: migrate to pnpm workspaces monorepo with Turborepo

- Restructure directories: apps/api, apps/admin, apps/website
- Add root pnpm-workspace.yaml, turbo.json, .prettierrc, .gitignore
- Rename packages to @inkreach/api, @inkreach/admin, @inkreach/website
- Add shared packages: packages/tsconfig, packages/shared-types
- Add pnpm.onlyBuiltDependencies for native builds
- Update docs: README.md, structs.md
- All three projects build successfully
This commit is contained in:
yeuimu
2026-07-11 16:54:05 +08:00
parent 69945b8749
commit 7e04877bb6
155 changed files with 20134 additions and 14393 deletions
@@ -0,0 +1,187 @@
-- CreateEnum
CREATE TYPE "SyncType" AS ENUM ('CATEGORIES', 'PRODUCTS');
-- CreateEnum
CREATE TYPE "SyncStatus" AS ENUM ('RUNNING', 'SUCCESS', 'FAILED');
-- CreateTable
CREATE TABLE "origin_goods" (
"origin_good_id" BIGSERIAL NOT NULL,
"sds_good_id" TEXT NOT NULL,
"sds_category_id" TEXT,
"good_name" TEXT,
"good_image" TEXT,
"good_price" DECIMAL(12,2),
"created_at" TIMESTAMPTZ(6) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMPTZ(6) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "origin_goods_pkey" PRIMARY KEY ("origin_good_id")
);
-- CreateTable
CREATE TABLE "countries" (
"country_id" BIGSERIAL NOT NULL,
"country_name" TEXT NOT NULL,
"country_icon" TEXT,
"created_at" TIMESTAMPTZ(6) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMPTZ(6) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "countries_pkey" PRIMARY KEY ("country_id")
);
-- CreateTable
CREATE TABLE "categories" (
"category_id" BIGSERIAL NOT NULL,
"parent_category_id" BIGINT,
"category_name" TEXT NOT NULL,
"category_icon" TEXT,
"sds_category_id" TEXT,
"created_at" TIMESTAMPTZ(6) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMPTZ(6) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "categories_pkey" PRIMARY KEY ("category_id")
);
-- CreateTable
CREATE TABLE "tags" (
"tag_id" BIGSERIAL NOT NULL,
"tag_name" TEXT NOT NULL,
"tag_color" TEXT,
"timing" TEXT,
"created_at" TIMESTAMPTZ(6) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMPTZ(6) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "tags_pkey" PRIMARY KEY ("tag_id")
);
-- CreateTable
CREATE TABLE "positions" (
"position_id" BIGSERIAL NOT NULL,
"index_val" INTEGER NOT NULL,
"country_id" BIGINT,
"category_id" BIGINT,
"created_at" TIMESTAMPTZ(6) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMPTZ(6) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "positions_pkey" PRIMARY KEY ("position_id")
);
-- CreateTable
CREATE TABLE "goods" (
"good_id" BIGSERIAL NOT NULL,
"origin_good_id" BIGINT NOT NULL,
"country_id" BIGINT NOT NULL,
"category_id" BIGINT NOT NULL,
"tag_id" BIGINT,
"position_id" BIGINT,
"good_name" TEXT NOT NULL,
"good_priority" INTEGER NOT NULL DEFAULT 0,
"created_at" TIMESTAMPTZ(6) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMPTZ(6) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "goods_pkey" PRIMARY KEY ("good_id")
);
-- CreateTable
CREATE TABLE "users" (
"id" BIGSERIAL NOT NULL,
"username" TEXT NOT NULL,
"password_hash" TEXT NOT NULL,
"created_at" TIMESTAMPTZ(6) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMPTZ(6) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "users_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "sync_logs" (
"id" BIGSERIAL NOT NULL,
"type" "SyncType" NOT NULL,
"status" "SyncStatus" NOT NULL,
"message" TEXT,
"started_at" TIMESTAMPTZ(6) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"finished_at" TIMESTAMPTZ(6),
CONSTRAINT "sync_logs_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE UNIQUE INDEX "origin_goods_sds_good_id_key" ON "origin_goods"("sds_good_id");
-- CreateIndex
CREATE INDEX "origin_goods_sds_category_id_idx" ON "origin_goods"("sds_category_id");
-- CreateIndex
CREATE UNIQUE INDEX "countries_country_name_key" ON "countries"("country_name");
-- CreateIndex
CREATE UNIQUE INDEX "categories_sds_category_id_key" ON "categories"("sds_category_id");
-- CreateIndex
CREATE INDEX "categories_parent_category_id_idx" ON "categories"("parent_category_id");
-- CreateIndex
CREATE UNIQUE INDEX "tags_tag_name_key" ON "tags"("tag_name");
-- CreateIndex
CREATE INDEX "positions_country_id_idx" ON "positions"("country_id");
-- CreateIndex
CREATE INDEX "positions_category_id_idx" ON "positions"("category_id");
-- CreateIndex
CREATE INDEX "positions_country_id_category_id_idx" ON "positions"("country_id", "category_id");
-- CreateIndex
CREATE INDEX "goods_origin_good_id_idx" ON "goods"("origin_good_id");
-- CreateIndex
CREATE INDEX "goods_country_id_idx" ON "goods"("country_id");
-- CreateIndex
CREATE INDEX "goods_category_id_idx" ON "goods"("category_id");
-- CreateIndex
CREATE INDEX "goods_tag_id_idx" ON "goods"("tag_id");
-- CreateIndex
CREATE INDEX "goods_position_id_idx" ON "goods"("position_id");
-- CreateIndex
CREATE INDEX "goods_good_priority_idx" ON "goods"("good_priority" DESC);
-- CreateIndex
CREATE INDEX "goods_country_id_good_priority_idx" ON "goods"("country_id", "good_priority" DESC);
-- CreateIndex
CREATE INDEX "goods_category_id_country_id_idx" ON "goods"("category_id", "country_id");
-- CreateIndex
CREATE UNIQUE INDEX "users_username_key" ON "users"("username");
-- CreateIndex
CREATE INDEX "sync_logs_type_started_at_idx" ON "sync_logs"("type", "started_at");
-- AddForeignKey
ALTER TABLE "categories" ADD CONSTRAINT "categories_parent_category_id_fkey" FOREIGN KEY ("parent_category_id") REFERENCES "categories"("category_id") ON DELETE RESTRICT ON UPDATE NO ACTION;
-- AddForeignKey
ALTER TABLE "positions" ADD CONSTRAINT "positions_country_id_fkey" FOREIGN KEY ("country_id") REFERENCES "countries"("country_id") ON DELETE CASCADE ON UPDATE NO ACTION;
-- AddForeignKey
ALTER TABLE "positions" ADD CONSTRAINT "positions_category_id_fkey" FOREIGN KEY ("category_id") REFERENCES "categories"("category_id") ON DELETE CASCADE ON UPDATE NO ACTION;
-- AddForeignKey
ALTER TABLE "goods" ADD CONSTRAINT "goods_origin_good_id_fkey" FOREIGN KEY ("origin_good_id") REFERENCES "origin_goods"("origin_good_id") ON DELETE RESTRICT ON UPDATE NO ACTION;
-- AddForeignKey
ALTER TABLE "goods" ADD CONSTRAINT "goods_country_id_fkey" FOREIGN KEY ("country_id") REFERENCES "countries"("country_id") ON DELETE RESTRICT ON UPDATE NO ACTION;
-- AddForeignKey
ALTER TABLE "goods" ADD CONSTRAINT "goods_category_id_fkey" FOREIGN KEY ("category_id") REFERENCES "categories"("category_id") ON DELETE RESTRICT ON UPDATE NO ACTION;
-- AddForeignKey
ALTER TABLE "goods" ADD CONSTRAINT "goods_tag_id_fkey" FOREIGN KEY ("tag_id") REFERENCES "tags"("tag_id") ON DELETE SET NULL ON UPDATE NO ACTION;
-- AddForeignKey
ALTER TABLE "goods" ADD CONSTRAINT "goods_position_id_fkey" FOREIGN KEY ("position_id") REFERENCES "positions"("position_id") ON DELETE SET NULL ON UPDATE NO ACTION;
@@ -0,0 +1,53 @@
-- AlterTable
ALTER TABLE "tags" ADD COLUMN "tag_group_id" BIGINT,
ADD COLUMN "sort_order" INTEGER NOT NULL DEFAULT 0;
-- CreateTable
CREATE TABLE "tag_groups" (
"tag_group_id" BIGSERIAL NOT NULL,
"group_name" TEXT NOT NULL,
"group_icon" TEXT,
"group_color" TEXT,
"sort_order" INTEGER NOT NULL DEFAULT 0,
"created_at" TIMESTAMPTZ(6) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMPTZ(6) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "tag_groups_pkey" PRIMARY KEY ("tag_group_id")
);
-- CreateIndex
CREATE UNIQUE INDEX "tag_groups_group_name_key" ON "tag_groups"("group_name");
-- CreateIndex
CREATE INDEX "tags_tag_group_id_idx" ON "tags"("tag_group_id");
-- CreateIndex
CREATE INDEX "tags_tag_group_id_sort_order_idx" ON "tags"("tag_group_id", "sort_order");
-- AddForeignKey
ALTER TABLE "tags" ADD CONSTRAINT "tags_tag_group_id_fkey" FOREIGN KEY ("tag_group_id") REFERENCES "tag_groups"("tag_group_id") ON DELETE SET NULL ON UPDATE NO ACTION;
-- Seed: insert 3 default tag groups
INSERT INTO "tag_groups" ("group_name", "sort_order", "created_at", "updated_at") VALUES
('物流渠道', 1, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP),
('印刷位置', 2, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP),
('印刷工艺', 3, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP);
-- Seed: assign existing tags to groups
UPDATE "tags" SET "tag_group_id" = (SELECT "tag_group_id" FROM "tag_groups" WHERE "group_name" = '物流渠道')
WHERE "tag_name" IN ('包邮', '不包邮');
UPDATE "tags" SET "tag_group_id" = (SELECT "tag_group_id" FROM "tag_groups" WHERE "group_name" = '印刷位置')
WHERE "tag_name" IN ('单面印', '双面印');
UPDATE "tags" SET "tag_group_id" = (SELECT "tag_group_id" FROM "tag_groups" WHERE "group_name" = '印刷工艺')
WHERE "tag_name" IN ('烫画', '直喷', '不打印');
-- Seed: assign sortOrder within each group by tag_id
WITH ordered AS (
SELECT "tag_id",
ROW_NUMBER() OVER (PARTITION BY "tag_group_id" ORDER BY "tag_id") AS rn
FROM "tags"
WHERE "tag_group_id" IS NOT NULL
)
UPDATE "tags" SET "sort_order" = ordered.rn
FROM ordered
WHERE "tags"."tag_id" = ordered."tag_id";
@@ -0,0 +1,3 @@
# Please do not edit this file manually
# It should be added in your version-control system (i.e. Git)
provider = "postgresql"
+201
View File
@@ -0,0 +1,201 @@
// InkReach Product Center Prisma Schema
// Generated based on docs/dev/database-table-design.md
// All TIMESTAMPTZ columns use DateTime @db.Timestamptz(6)
// All BIGINT columns use BigInt
// snake_case table & column names via @@map / @map
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
// ---------- Origin Goods ----------
model OriginGood {
id BigInt @id @default(autoincrement()) @map("origin_good_id")
sdsGoodId String @unique @map("sds_good_id")
// Cached SDS product metadata (filled during sync)
sdsCategoryId String? @map("sds_category_id")
goodName String? @map("good_name")
goodImage String? @map("good_image")
goodPrice Decimal? @map("good_price") @db.Decimal(12, 2)
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz(6)
updatedAt DateTime @default(now()) @updatedAt @map("updated_at") @db.Timestamptz(6)
goods Good[]
@@index([sdsCategoryId])
@@map("origin_goods")
}
// ---------- Countries ----------
model Country {
id BigInt @id @default(autoincrement()) @map("country_id")
countryName String @unique @map("country_name")
countryIcon String? @map("country_icon")
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz(6)
updatedAt DateTime @default(now()) @updatedAt @map("updated_at") @db.Timestamptz(6)
goods Good[]
positions Position[]
@@map("countries")
}
// ---------- Categories (self-referential tree) ----------
model Category {
id BigInt @id @default(autoincrement()) @map("category_id")
parentCategoryId BigInt? @map("parent_category_id")
categoryName String @map("category_name")
categoryIcon String? @map("category_icon")
sdsCategoryId String? @unique @map("sds_category_id")
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz(6)
updatedAt DateTime @default(now()) @updatedAt @map("updated_at") @db.Timestamptz(6)
parent Category? @relation("CategoryToCategory", fields: [parentCategoryId], references: [id], onDelete: Restrict, onUpdate: NoAction)
children Category[] @relation("CategoryToCategory")
goods Good[]
positions Position[]
@@index([parentCategoryId])
@@map("categories")
}
// ---------- Tag Groups ----------
model TagGroup {
id BigInt @id @default(autoincrement()) @map("tag_group_id")
groupName String @unique @map("group_name")
groupIcon String? @map("group_icon")
groupColor String? @map("group_color")
sortOrder Int @default(0) @map("sort_order")
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz(6)
updatedAt DateTime @default(now()) @updatedAt @map("updated_at") @db.Timestamptz(6)
tags Tag[]
@@map("tag_groups")
}
// ---------- Tags ----------
model Tag {
id BigInt @id @default(autoincrement()) @map("tag_id")
tagName String @unique @map("tag_name")
tagColor String? @map("tag_color")
tagFontColor String? @map("tag_font_color")
timing String?
tagGroupId BigInt? @map("tag_group_id")
sortOrder Int @default(0) @map("sort_order")
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz(6)
updatedAt DateTime @default(now()) @updatedAt @map("updated_at") @db.Timestamptz(6)
goods Good[]
goodTags GoodTag[]
tagGroup TagGroup? @relation(fields: [tagGroupId], references: [id], onDelete: SetNull, onUpdate: NoAction)
@@index([tagGroupId])
@@index([tagGroupId, sortOrder])
@@map("tags")
}
// ---------- Positions ----------
model Position {
id BigInt @id @default(autoincrement()) @map("position_id")
indexVal Int @map("index_val")
countryId BigInt? @map("country_id")
categoryId BigInt? @map("category_id")
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz(6)
updatedAt DateTime @default(now()) @updatedAt @map("updated_at") @db.Timestamptz(6)
country Country? @relation(fields: [countryId], references: [id], onDelete: Cascade, onUpdate: NoAction)
category Category? @relation(fields: [categoryId], references: [id], onDelete: Cascade, onUpdate: NoAction)
goods Good[]
@@index([countryId])
@@index([categoryId])
@@index([countryId, categoryId])
@@map("positions")
}
// ---------- Goods ----------
model Good {
id BigInt @id @default(autoincrement()) @map("good_id")
originGoodId BigInt @map("origin_good_id")
countryId BigInt @map("country_id")
categoryId BigInt @map("category_id")
tagId BigInt? @map("tag_id")
positionId BigInt? @map("position_id")
goodName String @map("good_name")
goodImage String? @map("good_image")
goodPriority Int @default(0) @map("good_priority")
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz(6)
updatedAt DateTime @default(now()) @updatedAt @map("updated_at") @db.Timestamptz(6)
originGood OriginGood @relation(fields: [originGoodId], references: [id], onDelete: Restrict, onUpdate: NoAction)
country Country @relation(fields: [countryId], references: [id], onDelete: Restrict, onUpdate: NoAction)
category Category @relation(fields: [categoryId], references: [id], onDelete: Restrict, onUpdate: NoAction)
tag Tag? @relation(fields: [tagId], references: [id], onDelete: SetNull, onUpdate: NoAction)
position Position? @relation(fields: [positionId], references: [id], onDelete: SetNull, onUpdate: NoAction)
goodTags GoodTag[]
@@index([originGoodId])
@@index([countryId])
@@index([categoryId])
@@index([tagId])
@@index([positionId])
@@index([goodPriority(sort: Desc)])
@@index([countryId, goodPriority(sort: Desc)])
@@index([categoryId, countryId])
@@map("goods")
}
// ---------- Good-Tag Junction (M:N) ----------
model GoodTag {
goodId BigInt @map("good_id")
tagId BigInt @map("tag_id")
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz(6)
good Good @relation(fields: [goodId], references: [id], onDelete: Cascade, onUpdate: NoAction)
tag Tag @relation(fields: [tagId], references: [id], onDelete: Cascade, onUpdate: NoAction)
@@id([goodId, tagId])
@@index([tagId])
@@map("good_tags")
}
// ---------- Users (admin authentication) ----------
model User {
id BigInt @id @default(autoincrement())
username String @unique
passwordHash String @map("password_hash")
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz(6)
updatedAt DateTime @default(now()) @updatedAt @map("updated_at") @db.Timestamptz(6)
@@map("users")
}
// ---------- Sync Logs ----------
enum SyncType {
CATEGORIES
PRODUCTS
}
enum SyncStatus {
RUNNING
SUCCESS
FAILED
}
model SyncLog {
id BigInt @id @default(autoincrement())
type SyncType
status SyncStatus
message String?
startedAt DateTime @default(now()) @map("started_at") @db.Timestamptz(6)
finishedAt DateTime? @map("finished_at") @db.Timestamptz(6)
@@index([type, startedAt])
@@map("sync_logs")
}