feat(deploy): production deployment setup and fixes
- Debian-based api image (bookworm-slim), docker/debian mirrors, prisma binaryTargets for openssl 3.0 - nginx: admin SPA under /admin, TLS via acme.sh (ZeroSSL) + auto-renewal cron, http->https redirect - prisma: add origin_goods.delisted migration, sync missing schema (good_image/tag_font_color/good_tags), fix users.createdAt Timestamptz - api: CORS wildcard reflection, helmet CORP cross-origin, price backfill in persistProductDetail, categoryIcon ancestor fallback, mediaByColor per-color gallery in public goods detail - admin: /admin base path (vite + router) - import-data.mjs: udt_name casting, serial sequence advance fix
This commit is contained in:
+136
-136
@@ -1,17 +1,17 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import type { Good as PrismaGood } from '@prisma/client';
|
||||
|
||||
export interface GoodRelations {
|
||||
country?: { id: bigint; countryName: string; countryIcon: string | null } | null;
|
||||
category?: { id: bigint; categoryName: string; categoryIcon: string | null } | null;
|
||||
tag?: { id: bigint; tagName: string; tagColor: string | null; tagFontColor: string | null } | null;
|
||||
position?: { id: bigint; indexVal: number } | null;
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import type { Good as PrismaGood } from '@prisma/client';
|
||||
|
||||
export interface GoodRelations {
|
||||
country?: { id: bigint; countryName: string; countryIcon: string | null } | null;
|
||||
category?: { id: bigint; categoryName: string; categoryIcon: string | null } | null;
|
||||
tag?: { id: bigint; tagName: string; tagColor: string | null; tagFontColor: string | null } | null;
|
||||
position?: { id: bigint; indexVal: number } | null;
|
||||
originGood?: {
|
||||
id: bigint;
|
||||
id: bigint;
|
||||
sdsGoodId: string;
|
||||
source: 'SDS' | 'CUSTOM';
|
||||
goodName: string | null;
|
||||
goodImage: string | null;
|
||||
goodName: string | null;
|
||||
goodImage: string | null;
|
||||
goodPrice: unknown;
|
||||
detail?: {
|
||||
productCode: string | null;
|
||||
@@ -31,67 +31,67 @@ export interface GoodRelations {
|
||||
[key: string]: unknown;
|
||||
}>;
|
||||
_count?: { variants: number };
|
||||
} | null;
|
||||
goodTags?: { tag: { id: bigint; tagName: string; tagColor: string | null; tagFontColor: string | null } }[];
|
||||
}
|
||||
|
||||
export class GoodDto {
|
||||
@ApiProperty()
|
||||
id!: string;
|
||||
|
||||
@ApiProperty()
|
||||
goodName!: string;
|
||||
|
||||
@ApiProperty({ nullable: true })
|
||||
goodImage!: string | null;
|
||||
|
||||
@ApiProperty()
|
||||
goodPriority!: number;
|
||||
|
||||
@ApiProperty()
|
||||
countryId!: string;
|
||||
|
||||
@ApiProperty()
|
||||
categoryId!: string;
|
||||
|
||||
@ApiProperty({ nullable: true })
|
||||
tagId!: string | null;
|
||||
|
||||
@ApiProperty({ nullable: true })
|
||||
positionId!: string | null;
|
||||
|
||||
@ApiProperty()
|
||||
originGoodId!: string;
|
||||
|
||||
@ApiProperty()
|
||||
createdAt!: string;
|
||||
|
||||
@ApiProperty()
|
||||
updatedAt!: string;
|
||||
|
||||
@ApiProperty({ required: false, nullable: true })
|
||||
country?: { id: string; countryName: string; countryIcon: string | null } | null;
|
||||
|
||||
@ApiProperty({ required: false, nullable: true })
|
||||
category?: { id: string; categoryName: string; categoryIcon: string | null } | null;
|
||||
|
||||
@ApiProperty({ required: false, nullable: true })
|
||||
tag?: { id: string; tagName: string; tagColor: string | null; tagFontColor: string | null } | null;
|
||||
|
||||
@ApiProperty({ required: false, type: Array })
|
||||
tags!: Array<{ id: string; tagName: string; tagColor: string | null; tagFontColor: string | null }>;
|
||||
|
||||
@ApiProperty({ required: false, nullable: true })
|
||||
position?: { id: string; indexVal: number } | null;
|
||||
|
||||
@ApiProperty({ required: false, nullable: true })
|
||||
} | null;
|
||||
goodTags?: { tag: { id: bigint; tagName: string; tagColor: string | null; tagFontColor: string | null } }[];
|
||||
}
|
||||
|
||||
export class GoodDto {
|
||||
@ApiProperty()
|
||||
id!: string;
|
||||
|
||||
@ApiProperty()
|
||||
goodName!: string;
|
||||
|
||||
@ApiProperty({ nullable: true })
|
||||
goodImage!: string | null;
|
||||
|
||||
@ApiProperty()
|
||||
goodPriority!: number;
|
||||
|
||||
@ApiProperty()
|
||||
countryId!: string;
|
||||
|
||||
@ApiProperty()
|
||||
categoryId!: string;
|
||||
|
||||
@ApiProperty({ nullable: true })
|
||||
tagId!: string | null;
|
||||
|
||||
@ApiProperty({ nullable: true })
|
||||
positionId!: string | null;
|
||||
|
||||
@ApiProperty()
|
||||
originGoodId!: string;
|
||||
|
||||
@ApiProperty()
|
||||
createdAt!: string;
|
||||
|
||||
@ApiProperty()
|
||||
updatedAt!: string;
|
||||
|
||||
@ApiProperty({ required: false, nullable: true })
|
||||
country?: { id: string; countryName: string; countryIcon: string | null } | null;
|
||||
|
||||
@ApiProperty({ required: false, nullable: true })
|
||||
category?: { id: string; categoryName: string; categoryIcon: string | null } | null;
|
||||
|
||||
@ApiProperty({ required: false, nullable: true })
|
||||
tag?: { id: string; tagName: string; tagColor: string | null; tagFontColor: string | null } | null;
|
||||
|
||||
@ApiProperty({ required: false, type: Array })
|
||||
tags!: Array<{ id: string; tagName: string; tagColor: string | null; tagFontColor: string | null }>;
|
||||
|
||||
@ApiProperty({ required: false, nullable: true })
|
||||
position?: { id: string; indexVal: number } | null;
|
||||
|
||||
@ApiProperty({ required: false, nullable: true })
|
||||
originGood?: {
|
||||
id: string;
|
||||
id: string;
|
||||
sdsGoodId: string;
|
||||
source: 'SDS' | 'CUSTOM';
|
||||
isCustom: boolean;
|
||||
goodName: string | null;
|
||||
goodImage: string | null;
|
||||
goodName: string | null;
|
||||
goodImage: string | null;
|
||||
goodPrice: string | null;
|
||||
hasDetail: boolean;
|
||||
detailSyncedAt: string | null;
|
||||
@@ -99,72 +99,72 @@ export class GoodDto {
|
||||
sizeRowCount: number;
|
||||
packageRowCount: number;
|
||||
productCode: string | null;
|
||||
} | null;
|
||||
|
||||
static from(
|
||||
good: PrismaGood,
|
||||
rel: GoodRelations = {},
|
||||
): GoodDto {
|
||||
return {
|
||||
id: good.id.toString(),
|
||||
goodName: good.goodName,
|
||||
goodImage: good.goodImage,
|
||||
goodPriority: good.goodPriority,
|
||||
countryId: good.countryId.toString(),
|
||||
categoryId: good.categoryId.toString(),
|
||||
tagId: good.tagId === null || good.tagId === undefined ? null : good.tagId.toString(),
|
||||
positionId: good.positionId === null || good.positionId === undefined ? null : good.positionId.toString(),
|
||||
originGoodId: good.originGoodId.toString(),
|
||||
createdAt: good.createdAt.toISOString(),
|
||||
updatedAt: good.updatedAt.toISOString(),
|
||||
country: rel.country
|
||||
? {
|
||||
id: rel.country.id.toString(),
|
||||
countryName: rel.country.countryName,
|
||||
countryIcon: rel.country.countryIcon,
|
||||
}
|
||||
: null,
|
||||
category: rel.category
|
||||
? {
|
||||
id: rel.category.id.toString(),
|
||||
categoryName: rel.category.categoryName,
|
||||
categoryIcon: rel.category.categoryIcon,
|
||||
}
|
||||
: null,
|
||||
tag: rel.tag
|
||||
? {
|
||||
id: rel.tag.id.toString(),
|
||||
tagName: rel.tag.tagName,
|
||||
tagColor: rel.tag.tagColor,
|
||||
tagFontColor: rel.tag.tagFontColor,
|
||||
}
|
||||
: null,
|
||||
tags: rel.goodTags
|
||||
? rel.goodTags.map((gt) => ({
|
||||
id: gt.tag.id.toString(),
|
||||
tagName: gt.tag.tagName,
|
||||
tagColor: gt.tag.tagColor,
|
||||
tagFontColor: gt.tag.tagFontColor,
|
||||
}))
|
||||
: [],
|
||||
position: rel.position
|
||||
? {
|
||||
id: rel.position.id.toString(),
|
||||
indexVal: rel.position.indexVal,
|
||||
}
|
||||
: null,
|
||||
} | null;
|
||||
|
||||
static from(
|
||||
good: PrismaGood,
|
||||
rel: GoodRelations = {},
|
||||
): GoodDto {
|
||||
return {
|
||||
id: good.id.toString(),
|
||||
goodName: good.goodName,
|
||||
goodImage: good.goodImage,
|
||||
goodPriority: good.goodPriority,
|
||||
countryId: good.countryId.toString(),
|
||||
categoryId: good.categoryId.toString(),
|
||||
tagId: good.tagId === null || good.tagId === undefined ? null : good.tagId.toString(),
|
||||
positionId: good.positionId === null || good.positionId === undefined ? null : good.positionId.toString(),
|
||||
originGoodId: good.originGoodId.toString(),
|
||||
createdAt: good.createdAt.toISOString(),
|
||||
updatedAt: good.updatedAt.toISOString(),
|
||||
country: rel.country
|
||||
? {
|
||||
id: rel.country.id.toString(),
|
||||
countryName: rel.country.countryName,
|
||||
countryIcon: rel.country.countryIcon,
|
||||
}
|
||||
: null,
|
||||
category: rel.category
|
||||
? {
|
||||
id: rel.category.id.toString(),
|
||||
categoryName: rel.category.categoryName,
|
||||
categoryIcon: rel.category.categoryIcon,
|
||||
}
|
||||
: null,
|
||||
tag: rel.tag
|
||||
? {
|
||||
id: rel.tag.id.toString(),
|
||||
tagName: rel.tag.tagName,
|
||||
tagColor: rel.tag.tagColor,
|
||||
tagFontColor: rel.tag.tagFontColor,
|
||||
}
|
||||
: null,
|
||||
tags: rel.goodTags
|
||||
? rel.goodTags.map((gt) => ({
|
||||
id: gt.tag.id.toString(),
|
||||
tagName: gt.tag.tagName,
|
||||
tagColor: gt.tag.tagColor,
|
||||
tagFontColor: gt.tag.tagFontColor,
|
||||
}))
|
||||
: [],
|
||||
position: rel.position
|
||||
? {
|
||||
id: rel.position.id.toString(),
|
||||
indexVal: rel.position.indexVal,
|
||||
}
|
||||
: null,
|
||||
originGood: rel.originGood
|
||||
? {
|
||||
id: rel.originGood.id.toString(),
|
||||
id: rel.originGood.id.toString(),
|
||||
sdsGoodId: rel.originGood.sdsGoodId,
|
||||
source: rel.originGood.source,
|
||||
isCustom: rel.originGood.source === 'CUSTOM',
|
||||
goodName: rel.originGood.goodName,
|
||||
goodImage: rel.originGood.goodImage,
|
||||
goodPrice:
|
||||
rel.originGood.goodPrice === null ||
|
||||
rel.originGood.goodPrice === undefined
|
||||
? null
|
||||
goodName: rel.originGood.goodName,
|
||||
goodImage: rel.originGood.goodImage,
|
||||
goodPrice:
|
||||
rel.originGood.goodPrice === null ||
|
||||
rel.originGood.goodPrice === undefined
|
||||
? null
|
||||
: (rel.originGood.goodPrice as { toString(): string }).toString(),
|
||||
hasDetail: Boolean(rel.originGood.detail),
|
||||
detailSyncedAt: rel.originGood.detail?.syncedAt.toISOString() ?? null,
|
||||
@@ -173,7 +173,7 @@ export class GoodDto {
|
||||
packageRowCount: GoodDto.jsonRows(rel.originGood.detail?.packageSpecs),
|
||||
productCode: rel.originGood.detail?.productCode ?? null,
|
||||
}
|
||||
: null,
|
||||
: null,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -207,10 +207,10 @@ export class GoodDetailDto extends GoodDto {
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export interface PaginatedGoods {
|
||||
items: GoodDto[];
|
||||
total: number;
|
||||
page: number;
|
||||
pageSize: number;
|
||||
|
||||
export interface PaginatedGoods {
|
||||
items: GoodDto[];
|
||||
total: number;
|
||||
page: number;
|
||||
pageSize: number;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user