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:
yeuimu
2026-08-26 14:23:09 +08:00
parent be0b90e68f
commit 6c61a4e871
982 changed files with 74156 additions and 179393 deletions
+14 -14
View File
@@ -1,14 +1,14 @@
import { Global, Module } from '@nestjs/common';
import { PrismaService } from './prisma.service';
/**
* Global Prisma module exporting {@link PrismaService}.
*
* Marked `@Global()` so feature modules do not need to import it again.
*/
@Global()
@Module({
providers: [PrismaService],
exports: [PrismaService],
})
export class PrismaModule {}
import { Global, Module } from '@nestjs/common';
import { PrismaService } from './prisma.service';
/**
* Global Prisma module exporting {@link PrismaService}.
*
* Marked `@Global()` so feature modules do not need to import it again.
*/
@Global()
@Module({
providers: [PrismaService],
exports: [PrismaService],
})
export class PrismaModule {}
+31 -31
View File
@@ -1,31 +1,31 @@
import { PrismaService } from './prisma.service';
import { Test } from '@nestjs/testing';
describe('PrismaService', () => {
let service: PrismaService;
beforeAll(async () => {
const moduleRef = await Test.createTestingModule({
providers: [PrismaService],
}).compile();
service = moduleRef.get(PrismaService);
await service.onModuleInit();
});
afterAll(async () => {
await service.onModuleDestroy();
});
it('should be defined', () => {
expect(service).toBeDefined();
});
it('should be able to run a trivial query against the database', async () => {
// Execute a raw query that does not depend on application tables
const result = await service.$queryRaw<Array<{ now: Date }>>`SELECT now() AS now`;
expect(Array.isArray(result)).toBe(true);
expect(result.length).toBe(1);
expect(result[0].now).toBeInstanceOf(Date);
});
});
import { PrismaService } from './prisma.service';
import { Test } from '@nestjs/testing';
describe('PrismaService', () => {
let service: PrismaService;
beforeAll(async () => {
const moduleRef = await Test.createTestingModule({
providers: [PrismaService],
}).compile();
service = moduleRef.get(PrismaService);
await service.onModuleInit();
});
afterAll(async () => {
await service.onModuleDestroy();
});
it('should be defined', () => {
expect(service).toBeDefined();
});
it('should be able to run a trivial query against the database', async () => {
// Execute a raw query that does not depend on application tables
const result = await service.$queryRaw<Array<{ now: Date }>>`SELECT now() AS now`;
expect(Array.isArray(result)).toBe(true);
expect(result.length).toBe(1);
expect(result[0].now).toBeInstanceOf(Date);
});
});
+20 -20
View File
@@ -1,20 +1,20 @@
import { Injectable, OnModuleInit, OnModuleDestroy } from '@nestjs/common';
import { PrismaClient } from '@prisma/client';
/**
* Global Prisma client wrapper.
*
* - `onModuleInit`: connects to the database so the first request
* does not pay the connection cost.
* - `onModuleDestroy`: cleanly disconnects during shutdown.
*/
@Injectable()
export class PrismaService extends PrismaClient implements OnModuleInit, OnModuleDestroy {
async onModuleInit(): Promise<void> {
await this.$connect();
}
async onModuleDestroy(): Promise<void> {
await this.$disconnect();
}
}
import { Injectable, OnModuleInit, OnModuleDestroy } from '@nestjs/common';
import { PrismaClient } from '@prisma/client';
/**
* Global Prisma client wrapper.
*
* - `onModuleInit`: connects to the database so the first request
* does not pay the connection cost.
* - `onModuleDestroy`: cleanly disconnects during shutdown.
*/
@Injectable()
export class PrismaService extends PrismaClient implements OnModuleInit, OnModuleDestroy {
async onModuleInit(): Promise<void> {
await this.$connect();
}
async onModuleDestroy(): Promise<void> {
await this.$disconnect();
}
}