Files
UHD_Media_Tool/apps/server/src/modules/sources-identity/defs.ts
T
rootandClaude Opus 5.5 52402864d6 style(copy): punctuation rules and site-wide fix with a CI check (QA U11)
- glossary: 10 punctuation rules (full-width in Chinese, CJK/Latin spacing,
  full-width parens around Chinese, single ellipsis, 「」 quotes, period rules)
- scripts/check-copy-punctuation.mjs: checks string literals, templates and JSX
  text (not comments, types, keys, match/compare strings); --fix, --strict,
  --by-file; wired into deno task ci as check:copy in warn mode
- --fix applied to web, server and contracts; files other lanes were editing
  (subscriptions, sites, browser, agent, auth/account, inventory) left for them
- tests updated for the changed messages

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-09-28 20:09:29 +08:00

315 lines
8.1 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import {
ContentUnit,
ContentUnitUpdate,
CursorQuery,
cursorPage,
Decision,
DecisionKind,
DecisionResolveInput,
DecisionStatus,
ExternalIdentity,
ExternalIdentityCreate,
ExternalIdentityUpdate,
FeedHealth,
FeedSource,
FeedSourceCreate,
FeedSourceUpdate,
Id,
IdentityRevision,
IdentityStatus,
IdParam,
MediaEntity,
MediaEntityCreate,
MediaEntityMerge,
MediaEntityStatus,
MediaEntityUpdate,
MediaKind,
Observation,
ObservationStatus,
OkResult,
ResolutionStatus,
Site,
SiteAccount,
SiteAccountCreate,
SiteAccountUpdate,
SiteCreate,
SiteListItem,
SiteListQuery,
SiteUpdate,
SourceRelease,
TitleAlias,
TitleAliasCreate,
} from "@umt/contracts";
import { z } from "zod";
// 与 contracts Api(crud())同路径、同 scope 的端点定义,但保留具体 schema 类型供路由推断入参。
// scope:读 = read;写 = admin(站点/Feed 配置)或 organize.execute(身份纠错、待处理)。
const list = <R extends z.ZodType, F extends z.ZodRawShape>(
path: string,
summary: string,
row: R,
filters: F,
tags: string[],
) => ({
method: "get" as const,
path,
scope: "read" as const,
summary,
tags,
query: CursorQuery.extend(filters),
response: cursorPage(row),
});
const get = <R extends z.ZodType>(path: string, summary: string, row: R, tags: string[]) => ({
method: "get" as const,
path,
scope: "read" as const,
summary,
tags,
params: IdParam,
response: row,
});
const write = <M extends "post" | "patch" | "delete", B extends z.ZodType | undefined, R extends z.ZodType>(
method: M,
path: string,
scope: "admin" | "organize.execute",
summary: string,
body: B,
response: R,
tags: string[],
withId = true,
) => ({
method,
path,
scope,
summary,
tags,
...(withId ? { params: IdParam } : {}),
body: body as B,
response,
});
const TS = ["sites"];
const TF = ["feed-sources"];
const TR = ["source-releases"];
const TE = ["media-entities"];
const TI = ["identity"];
const TD = ["decisions"];
export const D = {
sites: {
list: {
method: "get" as const,
path: "/sites",
scope: "read" as const,
summary: "站点列表(include=accounts 附带账号与整体账号状态)",
tags: TS,
query: SiteListQuery,
response: cursorPage(SiteListItem),
},
get: get("/sites/{id}", "站点详情", Site, TS),
create: {
method: "post" as const,
path: "/sites",
scope: "admin" as const,
summary: "登记站点",
tags: TS,
body: SiteCreate,
response: Site,
},
update: write("patch", "/sites/{id}", "admin", "修改站点(启停、快照周期、限流、代理)", SiteUpdate, Site, TS),
},
siteAccounts: {
list: list("/site-accounts", "站点账号列表", SiteAccount, { siteId: Id.optional() }, TS),
get: get("/site-accounts/{id}", "站点账号详情", SiteAccount, TS),
create: {
method: "post" as const,
path: "/site-accounts",
scope: "admin" as const,
summary: "新建站点账号(凭证只写,存为 secret)",
tags: TS,
body: SiteAccountCreate,
response: SiteAccount,
},
update: write(
"patch",
"/site-accounts/{id}",
"admin",
"修改站点账号 / 轮换凭证",
SiteAccountUpdate,
SiteAccount,
TS,
),
},
feedSources: {
list: list("/feed-sources", "Feed 列表", FeedSource, { siteId: Id.optional(), health: FeedHealth.optional() }, TF),
get: get("/feed-sources/{id}", "Feed 详情", FeedSource, TF),
create: {
method: "post" as const,
path: "/feed-sources",
scope: "admin" as const,
summary: "新建 Feed(url 只写,存为 secret;对外只给脱敏 URL)",
tags: TF,
body: FeedSourceCreate,
response: FeedSource,
},
update: write("patch", "/feed-sources/{id}", "admin", "修改 Feed", FeedSourceUpdate, FeedSource, TF),
fetchNow: write(
"post",
"/feed-sources/{id}/fetch",
"admin",
"立即抓取(仍受站点限流)",
undefined,
z.object({ taskId: Id }),
TF,
),
},
sourceReleases: {
list: list(
"/source-releases",
"资源列表",
SourceRelease,
{
siteId: Id.optional(),
mediaEntityId: Id.optional(),
resolutionStatus: ResolutionStatus.optional(),
q: z.string().max(200).optional(),
},
TR,
),
get: get("/source-releases/{id}", "资源详情", SourceRelease, TR),
},
observations: {
list: list(
"/observations",
"观察列表",
Observation,
{ feedId: Id.optional(), status: ObservationStatus.optional() },
TR,
),
get: get("/observations/{id}", "观察详情", Observation, TR),
},
mediaEntities: {
list: list(
"/media-entities",
"作品列表",
MediaEntity,
{ kind: MediaKind.optional(), status: MediaEntityStatus.optional(), q: z.string().max(200).optional() },
TE,
),
get: get("/media-entities/{id}", "作品(旧 ID 自动跳转)", MediaEntity, TE),
create: {
method: "post" as const,
path: "/media-entities",
scope: "organize.execute" as const,
summary: "手动建本地作品(TMDB 无记录时可先建档)",
tags: TE,
body: MediaEntityCreate,
response: MediaEntity,
},
update: write(
"patch",
"/media-entities/{id}",
"organize.execute",
"修改作品(标题/原名/年份按字段覆盖并锁定)",
MediaEntityUpdate,
MediaEntity,
TE,
),
merge: write(
"post",
"/media-entities/{id}/merge",
"organize.execute",
"合并重复作品(保留旧 ID 跳转)",
MediaEntityMerge,
MediaEntity,
TE,
),
},
contentUnits: {
list: list(
"/content-units",
"内容单元列表",
ContentUnit,
{ mediaEntityId: Id.optional(), seasonNumber: z.coerce.number().int().optional() },
TE,
),
get: get("/content-units/{id}", "内容单元详情", ContentUnit, TE),
update: write(
"patch",
"/content-units/{id}",
"organize.execute",
"修改内容单元(改编号即确认并锁定)",
ContentUnitUpdate,
ContentUnit,
TE,
),
},
titleAliases: {
list: list(
"/title-aliases",
"别名列表",
TitleAlias,
{ mediaEntityId: Id.optional(), q: z.string().max(200).optional() },
TI,
),
get: get("/title-aliases/{id}", "别名详情", TitleAlias, TI),
create: write("post", "/title-aliases", "organize.execute", "新增别名", TitleAliasCreate, TitleAlias, TI, false),
remove: write("delete", "/title-aliases/{id}", "organize.execute", "删除别名", undefined, OkResult, TI),
},
externalIdentities: {
list: list(
"/external-identities",
"外部身份列表",
ExternalIdentity,
{ mediaEntityId: Id.optional(), provider: z.string().optional(), status: IdentityStatus.optional() },
TI,
),
get: get("/external-identities/{id}", "外部身份详情", ExternalIdentity, TI),
create: write(
"post",
"/external-identities",
"organize.execute",
"新增外部身份(accepted 等同人工纠错)",
ExternalIdentityCreate,
ExternalIdentity,
TI,
false,
),
update: write(
"patch",
"/external-identities/{id}",
"organize.execute",
"修订外部身份(乐观并发)",
ExternalIdentityUpdate,
ExternalIdentity,
TI,
),
revisions: get(
"/external-identities/{id}/revisions",
"身份修订历史",
z.object({ items: z.array(IdentityRevision) }),
TI,
),
},
decisions: {
list: list(
"/decisions",
"待处理事项(缺省只列未关闭)",
Decision,
{ status: DecisionStatus.optional(), kind: DecisionKind.optional() },
TD,
),
get: get("/decisions/{id}", "待处理事项详情", Decision, TD),
resolve: write(
"post",
"/decisions/{id}/resolve",
"organize.execute",
"处理待处理事项",
DecisionResolveInput,
Decision,
TD,
),
},
};