@oj-module/cli 0.2.6 → 0.2.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/devkit/README.md +70 -24
- package/devkit/framework-dev.md +14 -5
- package/devkit/manual.md +28 -23
- package/devkit/module-dev-and-cdn-playground.md +197 -0
- package/devkit/sql-builder-missing-capabilities-pr.md +268 -0
- package/package.json +2 -2
- package/shell-dist/assets/runtime.js +130 -45
- package/shell-dist/index.html +2 -2
- package/shell-dist/versions.json +3 -3
- package/src/__tests__/build-backend.test.ts +64 -0
- package/src/__tests__/versions.test.ts +26 -0
- package/src/build.ts +9 -0
- package/src/bundle.ts +8 -5
- package/src/contract/__tests__/handler-scan.test.ts +29 -0
- package/src/contract/__tests__/market-client.integration.test.ts +170 -0
- package/src/contract/handler-scan.ts +35 -4
- package/src/dev.ts +10 -2
- package/src/index.ts +9 -3
- package/src/init.ts +24 -0
- package/src/lifecycle/__tests__/bundle.test.ts +3 -3
- package/src/market/__tests__/bootstrap.test.ts +204 -0
- package/src/market/__tests__/build-index.test.ts +154 -0
- package/src/market/__tests__/cdn-layout.test.ts +96 -0
- package/src/market/__tests__/failure-playbook.test.ts +33 -0
- package/src/market/__tests__/fixture.test.ts +48 -0
- package/src/market/__tests__/publish.test.ts +109 -0
- package/src/market/__tests__/report.test.ts +51 -0
- package/src/market/__tests__/setup.test.ts +114 -0
- package/src/market/__tests__/verify.test.ts +111 -0
- package/src/market/bootstrap.ts +255 -0
- package/src/market/build-index.ts +159 -0
- package/src/market/cdn-layout.ts +122 -0
- package/src/market/command.ts +139 -0
- package/src/market/failure-playbook.ts +57 -0
- package/src/market/fixture.ts +138 -0
- package/src/market/index-builder.ts +76 -0
- package/src/market/publish.ts +156 -0
- package/src/market/report.ts +67 -0
- package/src/market/scan.ts +99 -0
- package/src/market/setup.ts +144 -0
- package/src/market/trial.ts +121 -0
- package/src/market/verify.ts +298 -0
- package/src/market/walkthrough.ts +506 -0
- package/src/oj.ts +3 -1
- package/src/preview-config.ts +177 -0
- package/src/preview.ts +106 -56
- package/src/sign.ts +21 -6
- package/src/usage.ts +22 -2
- package/src/vendor.ts +4 -2
- package/src/versions.ts +28 -0
- package/templates/api/.ojm-api-exempt.json +6 -2
- package/templates/api/config.yaml +24 -2
- package/templates/api/src/_platform/README.md +31 -0
- package/templates/api/src/_platform/_lifecycle/atomic.ts +1 -1
- package/templates/api/src/_platform/_lifecycle/history.ts +10 -5
- package/templates/api/src/_platform/_lifecycle/installer.ts +4 -4
- package/templates/api/src/_platform/_lifecycle/sha384.ts +1 -1
- package/templates/api/src/_platform/_lifecycle/steps/download.ts +1 -1
- package/templates/api/src/_platform/_lifecycle/steps/extract.ts +8 -8
- package/templates/api/src/_platform/_lifecycle/steps/register.ts +57 -29
- package/templates/api/src/_platform/_lifecycle/trust.ts +1 -1
- package/templates/api/src/_platform/_lifecycle/uninstall.ts +29 -56
- package/templates/api/src/_platform/_lifecycle/verify-checks.ts +2 -2
- package/templates/api/src/_platform/_shared/service/{contacts.ts → domain/contacts.ts} +20 -13
- package/templates/api/src/_platform/_shared/service/{roles.ts → domain/roles.ts} +18 -19
- package/templates/api/src/_platform/_shared/service/{settings.ts → domain/settings.ts} +6 -6
- package/templates/api/src/_platform/_shared/service/{solution-perms.ts → domain/solution-perms.ts} +25 -22
- package/templates/api/src/_platform/_shared/service/{users.ts → domain/users.ts} +20 -26
- package/templates/api/src/_platform/_shared/service/{access.ts → infra/access.ts} +5 -8
- package/templates/api/src/_platform/_shared/service/{audit.ts → infra/audit.ts} +26 -26
- package/templates/api/src/_platform/_shared/service/infra/oplog.ts +40 -0
- package/templates/api/src/_platform/_shared/service/{query.ts → infra/query.ts} +18 -0
- package/templates/api/src/_platform/_shared/service/{rate.ts → infra/rate.ts} +16 -13
- package/templates/api/src/_platform/_shared/site-facade.ts +1 -1
- package/templates/api/src/_platform/contacts/api.ts +2 -2
- package/templates/api/src/_platform/contacts/detail/api.ts +2 -2
- package/templates/api/src/_platform/fixtures/l1-test-user.sql +13 -0
- package/templates/api/src/_platform/manifest.yaml +4 -1
- package/templates/api/src/_platform/market/api.ts +54 -16
- package/templates/api/src/_platform/migrations/0013__users_profile.sql +4 -0
- package/templates/api/src/_platform/schema.yaml +26 -0
- package/templates/api/src/_platform/solutions/api.ts +13 -12
- package/templates/api/src/auth/_shared/ldap-login.ts +11 -7
- package/templates/api/src/auth/_shared/login-throttle.ts +2 -2
- package/templates/api/src/auth/_shared/session.ts +26 -22
- package/templates/api/src/auth/api.ts +1 -1
- package/templates/api/src/demo/todos/api.ts +8 -8
- package/templates/api/src/home/line/api.ts +4 -4
- package/templates/api/src/home/pie/api.ts +4 -4
- package/templates/api/src/notification/_shared/notify.ts +97 -57
- package/templates/api/src/notification/api.ts +1 -1
- package/templates/api/src/notification/manifest.yaml +1 -0
- package/templates/api/src/notification/migrations/0004__notifications_broadcast_hidden.sql +12 -0
- package/templates/api/src/notification/schema.yaml +7 -0
- package/templates/api/src/notification/seed.sql +5 -4
- package/templates/api/src/personal-center/profile/api.ts +63 -0
- package/templates/api/src/personal-center/upload/api.ts +1 -1
- package/templates/api/src/system/login-methods/api.ts +1 -1
- package/templates/api/src/system/oplog/api.ts +3 -3
- package/templates/api/src/system/permissions/api.ts +2 -2
- package/templates/api/src/system/roles/api.ts +2 -2
- package/templates/api/src/system/roles/bindings/api.ts +2 -2
- package/templates/api/src/system/settings/api.ts +2 -2
- package/templates/api/src/system/users/api.ts +2 -2
- package/templates/api/src/system/users/forbidden/api.ts +2 -2
- package/templates/api/src/system/users/password/api.ts +2 -2
- package/templates/api/src/system/users/roles/api.ts +2 -2
- package/templates/api/src/web/user-info/api.ts +4 -5
- package/templates/api/tests/auth-session.spec.ts +1 -1
- package/templates/api/tests/auth-throttle.spec.ts +1 -1
- package/templates/api/tests/fixture.ts +8 -16
- package/templates/api/tests/guard.spec.ts +1 -1
- package/templates/api/tests/l1-builder.test.ts +91 -0
- package/templates/api/tests/lifecycle/installer.spec.ts +1 -4
- package/templates/api/tests/lifecycle/market-api.spec.ts +2 -4
- package/templates/api/tests/lifecycle/steps-fs.spec.ts +5 -8
- package/templates/api/tests/lifecycle/trust.spec.ts +1 -1
- package/templates/api/tests/lifecycle/uninstall.spec.ts +29 -89
- package/templates/api/tests/query-utils.spec.ts +85 -1
- package/templates/api/tests/rbac-audit.spec.ts +1 -1
- package/templates/api/tests/rbac-contacts.spec.ts +2 -2
- package/templates/api/tests/rbac-guard.spec.ts +1 -1
- package/templates/api/tests/rbac-roles.spec.ts +2 -2
- package/templates/api/tests/rbac-settings.spec.ts +66 -21
- package/templates/api/tests/rbac-solution-perms.spec.ts +1 -1
- package/templates/api/tests/rbac-users.spec.ts +1 -1
- package/templates/api/tsconfig.json +8 -0
- package/templates/cdn/README.md +48 -0
- package/templates/env.d.ts +2 -0
- package/templates/global.d.ts +850 -0
- package/templates/init.include.yaml +14 -2
- package/templates/logs/task-events.jsonl +0 -0
- package/templates/openapi.json +580 -23
- package/templates/package.json +2 -1
- package/templates/web/src/_platform/__client/api.schemas.ts +97 -0
- package/templates/web/src/_platform/__client/api.ts +152 -39
- package/templates/web/src/notification/entry.ts +46 -9
- package/templates/web/src/notification/locales/en-US.json +19 -0
- package/templates/web/src/notification/locales/zh-CN.json +19 -0
- package/templates/web/src/notification/pages/center/index.tsx +131 -0
- package/templates/web/src/oj-market/__tests__/CardGrid.test.tsx +19 -0
- package/templates/web/src/oj-market/__tests__/InstallDialog.test.tsx +3 -0
- package/templates/web/src/oj-market/__tests__/RollbackDialog.test.tsx +1 -0
- package/templates/web/src/oj-market/__tests__/marketClient.fake.ts +1 -0
- package/templates/web/src/oj-market/api/client.ts +5 -105
- package/templates/web/src/oj-market/api/defaultClient.ts +111 -5
- package/templates/web/src/oj-market/components/CardGrid.tsx +4 -1
- package/templates/web/src/oj-market/components/CardItem.tsx +9 -5
- package/templates/web/src/oj-market/components/DetailDrawer.tsx +7 -3
- package/templates/web/src/oj-market/components/InstallDialog.tsx +1 -1
- package/templates/web/src/oj-market/entry.ts +8 -2
- package/templates/web/src/oj-market/hooks/useMarket.ts +4 -1
- package/templates/web/src/oj-market/locales/en-US.json +2 -1
- package/templates/web/src/oj-market/locales/zh-CN.json +2 -1
- package/templates/web/src/oj-market/pages/MarketApp.tsx +16 -1
- package/templates/web/src/personal-center/__client/api.schemas.ts +9 -0
- package/templates/web/src/personal-center/__client/api.ts +25 -1
- package/templates/web/src/personal-center/entry.ts +5 -0
- package/templates/web/src/personal-center/pages/my-profile/index.tsx +25 -3
- package/templates/web/src/system/entry.ts +2 -1
- package/templates/web.config.ts +13 -10
- package/vendor/host-versions.json +3 -3
- package/templates/api/src/_platform/_shared/service/oplog.ts +0 -47
- package/templates/api/src/notifications/api.ts +0 -32
- package/templates/api/src/notifications/manifest.yaml +0 -5
- package/templates/web/src/notifications/__client/api.schemas.ts +0 -8
- package/templates/web/src/notifications/__client/api.ts +0 -80
- /package/templates/api/src/_platform/_shared/service/{guard.ts → infra/guard.ts} +0 -0
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { IndexRow, TaskRecord } from "../types";
|
|
2
|
-
import {
|
|
2
|
+
import { useUserStore } from "@oj-module/runtime";
|
|
3
|
+
import { Alert, Typography } from "antd";
|
|
3
4
|
import React, { useState } from "react";
|
|
4
5
|
import { useTranslation } from "react-i18next";
|
|
5
6
|
import { useMarketClient } from "../api/context";
|
|
@@ -16,6 +17,9 @@ export default function MarketApp() {
|
|
|
16
17
|
const { t } = useTranslation();
|
|
17
18
|
const client = useMarketClient();
|
|
18
19
|
const index = useMarketIndex();
|
|
20
|
+
// 管理权限 = superadmin(与后端 assertSuperAdmin 口径一致)。仅屏蔽写操作,浏览(详情/历史)对所有登录用户开放。
|
|
21
|
+
const roles = useUserStore(s => s.roles);
|
|
22
|
+
const canManage = roles.includes("superadmin");
|
|
19
23
|
|
|
20
24
|
const [detailRow, setDetailRow] = useState<IndexRow | null>(null);
|
|
21
25
|
const [installRow, setInstallRow] = useState<IndexRow | null>(null);
|
|
@@ -81,11 +85,21 @@ export default function MarketApp() {
|
|
|
81
85
|
<Typography.Title level={4}>{t("oj-market:card:title")}</Typography.Title>
|
|
82
86
|
<Typography.Paragraph type="secondary">{t("oj-market:card:subtitle")}</Typography.Paragraph>
|
|
83
87
|
|
|
88
|
+
{!canManage && (
|
|
89
|
+
<Alert
|
|
90
|
+
type="info"
|
|
91
|
+
showIcon
|
|
92
|
+
style={{ marginBottom: 16 }}
|
|
93
|
+
message={t("oj-market:card:superOnly")}
|
|
94
|
+
/>
|
|
95
|
+
)}
|
|
96
|
+
|
|
84
97
|
<CardGrid
|
|
85
98
|
loading={index.status === "loading"}
|
|
86
99
|
error={index.status === "error" ? index.error : null}
|
|
87
100
|
rows={rows}
|
|
88
101
|
busyName={busyName}
|
|
102
|
+
canManage={canManage}
|
|
89
103
|
onInstall={handleInstall}
|
|
90
104
|
onEnableToggle={handleEnableToggle}
|
|
91
105
|
onDetails={r => setDetailRow(r)}
|
|
@@ -99,6 +113,7 @@ export default function MarketApp() {
|
|
|
99
113
|
row={detailRow}
|
|
100
114
|
open={detailRow != null}
|
|
101
115
|
onClose={() => setDetailRow(null)}
|
|
116
|
+
canManage={canManage}
|
|
102
117
|
onInstall={handleInstall}
|
|
103
118
|
onHistory={(r) => {
|
|
104
119
|
setDetailRow(null);
|
|
@@ -4,6 +4,15 @@
|
|
|
4
4
|
import { z } from "@oj-module/runtime";
|
|
5
5
|
|
|
6
6
|
export const schemas = {
|
|
7
|
+
"personalCenterProfilePost": {
|
|
8
|
+
body: z.object({
|
|
9
|
+
"username": z.string().optional(),
|
|
10
|
+
"email": z.string().optional(),
|
|
11
|
+
"phoneNumber": z.string().optional(),
|
|
12
|
+
"description": z.string().optional()
|
|
13
|
+
}),
|
|
14
|
+
data: z.null(),
|
|
15
|
+
},
|
|
7
16
|
"personalCenterUploadPost": {
|
|
8
17
|
data: z.string(),
|
|
9
18
|
},
|
|
@@ -33,7 +33,7 @@ export function createPersonalCenterClient(ctx: ModuleContext) {
|
|
|
33
33
|
}
|
|
34
34
|
ctx.register.apiPrefix(API_PREFIX);
|
|
35
35
|
bindRequest(ctx.utils.request);
|
|
36
|
-
return { personalCenterUploadPost };
|
|
36
|
+
return { personalCenterProfilePost, personalCenterUploadPost };
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
/**
|
|
@@ -64,6 +64,30 @@ async function toApiError(e: unknown): Promise<unknown> {
|
|
|
64
64
|
return e;
|
|
65
65
|
}
|
|
66
66
|
|
|
67
|
+
export type PersonalCenterProfilePostBody = z.input<(typeof schemas)["personalCenterProfilePost"]["body"]>;
|
|
68
|
+
export type PersonalCenterProfilePostData = z.infer<(typeof schemas)["personalCenterProfilePost"]["data"]>;
|
|
69
|
+
|
|
70
|
+
export async function personalCenterProfilePost(body: PersonalCenterProfilePostBody): Promise<PersonalCenterProfilePostData> {
|
|
71
|
+
const client = ensureReq();
|
|
72
|
+
try {
|
|
73
|
+
const env = await client.post(`personal-center/profile`, { json: body }).json<OjEnvelope<PersonalCenterProfilePostData>>();
|
|
74
|
+
// 2xx + code!==0 也是业务错误(§6.2 通道 a):oj 不会这么发,但契约机制的价值恰是防漂移
|
|
75
|
+
if (typeof env.code === "number" && env.code !== 0)
|
|
76
|
+
throw new ContractApiError(env.code, env.msg ?? "业务错误(信封 code 非 0)");
|
|
77
|
+
const data = env.data as PersonalCenterProfilePostData;
|
|
78
|
+
if (import.meta.env.DEV) {
|
|
79
|
+
const { schemas } = await import("./api.schemas");
|
|
80
|
+
const r = schemas["personalCenterProfilePost"].data.safeParse(data);
|
|
81
|
+
if (!r.success)
|
|
82
|
+
throw new ContractApiError(-1, `[契约违例] personalCenterProfilePost 响应与契约不符:${r.error.issues.map(i => `${i.path.join(".") || "(root)"}: ${i.message}`).join("; ")}`);
|
|
83
|
+
}
|
|
84
|
+
return data;
|
|
85
|
+
}
|
|
86
|
+
catch (e) {
|
|
87
|
+
throw await toApiError(e);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
67
91
|
export type PersonalCenterUploadPostData = z.infer<(typeof schemas)["personalCenterUploadPost"]["data"]>;
|
|
68
92
|
|
|
69
93
|
export async function personalCenterUploadPost(): Promise<PersonalCenterUploadPostData> {
|
|
@@ -3,6 +3,8 @@ import { ProfileCardIcon, RiAccountCircleLine, useAuthStore } from "@oj-module/r
|
|
|
3
3
|
|
|
4
4
|
import { createElement, lazy } from "react";
|
|
5
5
|
|
|
6
|
+
import { createPersonalCenterClient } from "./__client/api";
|
|
7
|
+
|
|
6
8
|
const MyProfile = lazy(() => import("./pages/my-profile"));
|
|
7
9
|
|
|
8
10
|
const routes: AppRouteRecordRaw[] = [
|
|
@@ -40,6 +42,9 @@ const mod: ModuleDefinition = {
|
|
|
40
42
|
// P4-3:接管头像上传端点(D9)。antd Upload 直连 action,headers 在上传瞬间
|
|
41
43
|
// 取当前 token 注入 Bearer(token 存 runtime auth store,跨渲染期读 getState)。
|
|
42
44
|
async onInit(ctx) {
|
|
45
|
+
// 构造即接线:登记契约前缀 /personal-center + 绑定 scoped request,
|
|
46
|
+
// 页面直接 import 的 personalCenterProfilePost 才有可用的请求通道
|
|
47
|
+
createPersonalCenterClient(ctx);
|
|
43
48
|
const provider: UploadApiProvider = {
|
|
44
49
|
action: "/api/personal-center/upload",
|
|
45
50
|
headers: (): Record<string, string> => {
|
|
@@ -5,10 +5,13 @@ import {
|
|
|
5
5
|
ProFormTextArea,
|
|
6
6
|
} from "@ant-design/pro-components";
|
|
7
7
|
import { BasicContent, FormAvatarItem, useUserStore } from "@oj-module/runtime";
|
|
8
|
-
import { Form, Input } from "antd";
|
|
8
|
+
import { App, Form, Input } from "antd";
|
|
9
|
+
|
|
10
|
+
import { personalCenterProfilePost } from "../../__client/api";
|
|
9
11
|
|
|
10
12
|
export default function Profile() {
|
|
11
13
|
const currentUser = useUserStore();
|
|
14
|
+
const { message } = App.useApp();
|
|
12
15
|
const getAvatarURL = () => {
|
|
13
16
|
if (currentUser) {
|
|
14
17
|
if (currentUser.avatar) {
|
|
@@ -20,8 +23,27 @@ export default function Profile() {
|
|
|
20
23
|
return "";
|
|
21
24
|
};
|
|
22
25
|
|
|
23
|
-
|
|
24
|
-
|
|
26
|
+
// 真实入库:POST /api/personal-center/profile(只传资料四字段,avatar 走 upload 通道),
|
|
27
|
+
// 成功后重拉 user-info 刷新全局 store(表单 initialValues 随之更新)。
|
|
28
|
+
const handleFinish = async (values: {
|
|
29
|
+
username?: string
|
|
30
|
+
email?: string
|
|
31
|
+
phoneNumber?: string
|
|
32
|
+
description?: string
|
|
33
|
+
}) => {
|
|
34
|
+
try {
|
|
35
|
+
await personalCenterProfilePost({
|
|
36
|
+
username: values.username,
|
|
37
|
+
email: values.email,
|
|
38
|
+
phoneNumber: values.phoneNumber,
|
|
39
|
+
description: values.description,
|
|
40
|
+
});
|
|
41
|
+
await useUserStore.getState().getUserInfo();
|
|
42
|
+
message.success("更新基本信息成功");
|
|
43
|
+
}
|
|
44
|
+
catch (e) {
|
|
45
|
+
message.error(`更新基本信息失败:${e instanceof Error ? e.message : String(e)}`);
|
|
46
|
+
}
|
|
25
47
|
};
|
|
26
48
|
|
|
27
49
|
return (
|
package/templates/web.config.ts
CHANGED
|
@@ -2,12 +2,15 @@
|
|
|
2
2
|
* 模块工程配置:共享依赖一律不进 dependencies(宿主 importmap 提供),
|
|
3
3
|
* 这里只登记模块清单(entry 相对本工程根)。
|
|
4
4
|
*
|
|
5
|
-
*
|
|
5
|
+
* 注意:这里的登记顺序只影响模块加载顺序,**不决定侧边栏导航顺序**——
|
|
6
|
+
* 菜单顺序由各模块 entry 路由的 handle.order 决定(≥0 主菜单区升序;
|
|
7
|
+
* <0 底部固定区,pin 在侧边栏底部;同 order 按根 path 字母序)。
|
|
8
|
+
* `home` 必须第一个登记(shell 的 "/" 重定向到它)。
|
|
6
9
|
*/
|
|
7
10
|
const allModules = [
|
|
8
11
|
// /home 必须是首个模块:shell 的 "/" → VITE_BASE_HOME_PATH(=/home)重定向
|
|
9
12
|
{ name: "home", entry: "web/src/home/entry.ts" },
|
|
10
|
-
{ name: "demo", entry: "web/src/demo/entry.ts" },
|
|
13
|
+
// { name: "demo", entry: "web/src/demo/entry.ts" },
|
|
11
14
|
// /login 路由由模块提供:shell 宿主不挂 runtime 内置 baseRoutes
|
|
12
15
|
{ name: "login", entry: "web/src/login/entry.ts" },
|
|
13
16
|
// 个人中心:runtime 用户菜单固定导航 /personal-center/my-profile,缺则点击落空
|
|
@@ -17,19 +20,19 @@ const allModules = [
|
|
|
17
20
|
{ name: "notification", entry: "web/src/notification/entry.ts" },
|
|
18
21
|
// 系统管理:用户/成员资格/角色权限/操作日志/站点设置/登录方式(oj 后端 /api/system/*)
|
|
19
22
|
{ name: "system", entry: "web/src/system/entry.ts" },
|
|
23
|
+
// 解决方案市场:第一方能力(消费 _platform 编排面 /api/_platform/market/*,
|
|
24
|
+
// R29:第三方方案不得仿照此调用面),默认随脚手架下发
|
|
25
|
+
{ name: "oj-market", entry: "web/src/oj-market/entry.ts" },
|
|
20
26
|
// #region demo-only
|
|
21
27
|
// 以下为模板内置的演示模块(about/access/exception/outside/route-nest),
|
|
22
28
|
// 用于把 templates 当可运行 demo 直接跑;其文件不在 `ojm init` 的
|
|
23
29
|
// init.include.yaml 白名单里,不拷入新工程,故本区间连同这些条目会被
|
|
24
30
|
// init 一并剥离,脚手架产出的配置不会残留指向缺失文件的死条目。
|
|
25
|
-
{ name: "about", entry: "web/src/about/entry.ts" },
|
|
26
|
-
{ name: "access", entry: "web/src/access/entry.ts" },
|
|
27
|
-
{ name: "exception", entry: "web/src/exception/entry.ts" },
|
|
28
|
-
{ name: "outside", entry: "web/src/outside/entry.ts" },
|
|
29
|
-
{ name: "route-nest", entry: "web/src/route-nest/entry.ts" },
|
|
30
|
-
// 解决方案市场:第一方能力(消费 _platform 编排面 /api/_platform/market/*),
|
|
31
|
-
// 模板自带以便自演示;新工程默认不带,同上由 init 剥离。
|
|
32
|
-
{ name: "oj-market", entry: "web/src/oj-market/entry.ts" },
|
|
31
|
+
// { name: "about", entry: "web/src/about/entry.ts" },
|
|
32
|
+
// { name: "access", entry: "web/src/access/entry.ts" },
|
|
33
|
+
// { name: "exception", entry: "web/src/exception/entry.ts" },
|
|
34
|
+
// { name: "outside", entry: "web/src/outside/entry.ts" },
|
|
35
|
+
// { name: "route-nest", entry: "web/src/route-nest/entry.ts" },
|
|
33
36
|
// #endregion demo-only
|
|
34
37
|
];
|
|
35
38
|
|
|
@@ -8,9 +8,9 @@
|
|
|
8
8
|
"react-router": "7.18.2",
|
|
9
9
|
"react-router/dom": "7.18.2",
|
|
10
10
|
"@tanstack/react-query": "5.102.8",
|
|
11
|
-
"@oj-module/runtime": "0.2.
|
|
12
|
-
"@oj-module/runtime/contract": "0.2.
|
|
13
|
-
"@oj-module/runtime/contract/errors": "0.2.
|
|
11
|
+
"@oj-module/runtime": "0.2.14",
|
|
12
|
+
"@oj-module/runtime/contract": "0.2.14",
|
|
13
|
+
"@oj-module/runtime/contract/errors": "0.2.14",
|
|
14
14
|
"@plane/kit": "0.1.20",
|
|
15
15
|
"@plane/editor": "0.1.20",
|
|
16
16
|
"antd": "6.6.1",
|
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
// 操作日志查询(AC1-29/D13:admin 可查;读操作不记审计的红线不适用——本表本身就是审计)。
|
|
2
|
-
// 过滤维度:action 精确 / username 前缀 / module 精确;分页复用 query.ts(limit 钳 200/truncated)。
|
|
3
|
-
import { likePattern, paged } from "./query";
|
|
4
|
-
|
|
5
|
-
export interface OplogRow {
|
|
6
|
-
id: number
|
|
7
|
-
create_time: number
|
|
8
|
-
user_id: number
|
|
9
|
-
username: string
|
|
10
|
-
action: string
|
|
11
|
-
module: string
|
|
12
|
-
version: string | null
|
|
13
|
-
target: string | null
|
|
14
|
-
detail: string | null
|
|
15
|
-
level: number
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export interface OplogPage {
|
|
19
|
-
list: OplogRow[]
|
|
20
|
-
total: number
|
|
21
|
-
truncated: boolean
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export async function searchOplog(filter: { action?: unknown, username?: unknown, module?: unknown, limit?: unknown }): Promise<OplogPage> {
|
|
25
|
-
const conds: string[] = [];
|
|
26
|
-
const params: unknown[] = [];
|
|
27
|
-
if (filter.action) {
|
|
28
|
-
conds.push("action = ?");
|
|
29
|
-
params.push(String(filter.action));
|
|
30
|
-
}
|
|
31
|
-
if (filter.module) {
|
|
32
|
-
conds.push("module = ?");
|
|
33
|
-
params.push(String(filter.module));
|
|
34
|
-
}
|
|
35
|
-
if (filter.username) {
|
|
36
|
-
conds.push("username like ? escape '!'");
|
|
37
|
-
params.push(likePattern(filter.username));
|
|
38
|
-
}
|
|
39
|
-
const where = conds.length ? `where ${conds.join(" and ")}` : "";
|
|
40
|
-
const r = await paged<OplogRow>(
|
|
41
|
-
`select * from sys_oplog ${where} order by id desc`,
|
|
42
|
-
`select count(*) as c from sys_oplog ${where}`,
|
|
43
|
-
params,
|
|
44
|
-
{ size: filter.limit },
|
|
45
|
-
);
|
|
46
|
-
return { list: r.list, total: r.total, truncated: r.truncated };
|
|
47
|
-
}
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
// GET /api/notifications —— 通知列表(runtime 通知铃的内置 root 级兜底端点)。
|
|
2
|
-
//
|
|
3
|
-
// runtime 的 fetchNotifications 在未注册 notificationsApi provider 时,直接
|
|
4
|
-
// `request.get("notifications")` → 本端点;返回 NotificationItem[](信封内 data)。
|
|
5
|
-
// 查 notifications 表,is_read(0/1) 映射为布尔 isRead;Bearer 守卫保护。
|
|
6
|
-
//
|
|
7
|
-
// G5:本目录只保留只读兜底——表结构/种子迁归 api/src/notification/ 模块
|
|
8
|
-
// (评审 b3 双模块同表冲突决策);查询须返回 id(G4 NotificationItem.id 必填)。
|
|
9
|
-
//
|
|
10
|
-
// 注意:root 级路径无法写进业务契约(defineApi 要求 route 以 "/" 开头),
|
|
11
|
-
// 故本模块已在 api/.ojm-api-exempt.json 豁免,ojm api --check 不会误报。
|
|
12
|
-
export default {
|
|
13
|
-
async get() {
|
|
14
|
-
try {
|
|
15
|
-
const rows = await db.query(
|
|
16
|
-
"SELECT id, avatar, date, is_read, message, title FROM notifications ORDER BY id DESC",
|
|
17
|
-
[],
|
|
18
|
-
);
|
|
19
|
-
json.ok(rows.map(r => ({
|
|
20
|
-
id: Number(r.id),
|
|
21
|
-
avatar: String(r.avatar),
|
|
22
|
-
date: String(r.date),
|
|
23
|
-
isRead: Number(r.is_read) === 1,
|
|
24
|
-
message: String(r.message),
|
|
25
|
-
title: String(r.title),
|
|
26
|
-
})));
|
|
27
|
-
}
|
|
28
|
-
catch (e) {
|
|
29
|
-
json.fail(500, String(e));
|
|
30
|
-
}
|
|
31
|
-
},
|
|
32
|
-
};
|
|
@@ -1,80 +0,0 @@
|
|
|
1
|
-
/* eslint-disable */
|
|
2
|
-
// 生成物:ojm api 从契约生成,勿手改(改动请改契约文件后重跑 ojm api)
|
|
3
|
-
import { ContractApiError } from "@oj-module/runtime/contract/errors";
|
|
4
|
-
import type { ScopedRequestLike } from "@oj-module/runtime/contract/errors";
|
|
5
|
-
import type { ModuleContext } from "@oj-module/runtime";
|
|
6
|
-
import type { z } from "@oj-module/runtime";
|
|
7
|
-
import type { schemas } from "./api.schemas";
|
|
8
|
-
|
|
9
|
-
/** oj 信封(AC-D16):code=0 成功;非 0 时 HTTP status=code,由 toApiError 归一为 ContractApiError */
|
|
10
|
-
interface OjEnvelope<T> { code: number, msg?: string, data?: T }
|
|
11
|
-
|
|
12
|
-
let req: ScopedRequestLike | undefined;
|
|
13
|
-
|
|
14
|
-
/** 线上 API 前缀(ojm api 从契约抽取,唯一真源,勿手改):请求基址 + 前缀登记用 */
|
|
15
|
-
export const API_PREFIX = "/notifications";
|
|
16
|
-
|
|
17
|
-
/** 认领身份前缀(契约 apiPrefix,字面等于模块目录名):跨模块认领护栏对比基准 */
|
|
18
|
-
export const MODULE_PREFIX = "/notifications";
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* 模块 entry 的 onInit 里一行创建(构造即 install):完成 apiPrefix 登记 +
|
|
22
|
-
* scoped request 绑定,返回绑好前缀的端点函数集合。一模块一 client;
|
|
23
|
-
* 重复构造幂等安全(前缀重复登记同值、request 重绑同值)。
|
|
24
|
-
*/
|
|
25
|
-
export function createNotificationsClient(ctx: ModuleContext) {
|
|
26
|
-
// 跨模块认领护栏(DEV):工厂知道自己该属于谁(MODULE_PREFIX = 契约 apiPrefix),
|
|
27
|
-
// ctx 又带 module.name——名实不符说明别的模块在替本模块创建 client,会共享 request
|
|
28
|
-
// 单槽、卸载互相耦合;跨模块需求应由本模块创建后经 provider 暴露。
|
|
29
|
-
// 注意对比基准是 MODULE_PREFIX(目录名)而非 API_PREFIX——D-M8 映射时线上前缀
|
|
30
|
-
// 属于别的命名空间,认领身份仍按目录名。
|
|
31
|
-
if (import.meta.env.DEV && ctx.module.name !== MODULE_PREFIX.slice(1)) {
|
|
32
|
-
console.warn(`[ojm-api] 模块 "${ctx.module.name}" 正在认领 ${MODULE_PREFIX} 的 client——跨模块认领会共享 request 单槽、卸载互相耦合;应由 "${MODULE_PREFIX.slice(1)}" 模块在自身 onInit 创建,跨模块需求经 provider 暴露。`);
|
|
33
|
-
}
|
|
34
|
-
ctx.register.apiPrefix(API_PREFIX);
|
|
35
|
-
bindRequest(ctx.utils.request);
|
|
36
|
-
return { notificationsGet };
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* @deprecated 请改用 createNotificationsClient(ctx)——工厂构造即完成
|
|
41
|
-
* 前缀登记 + request 绑定,不会出现「登记了前缀没绑 request」的半接线状态。
|
|
42
|
-
*/
|
|
43
|
-
export function bindRequest(r: ScopedRequestLike): void {
|
|
44
|
-
req = r;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
function ensureReq(): ScopedRequestLike {
|
|
48
|
-
if (!req)
|
|
49
|
-
throw new ContractApiError(-1, "[ojm-api] 请求未绑定——请在模块 entry.ts 的 onInit 里调用 createNotificationsClient(ctx)(或 bindRequest(ctx.utils.request))。");
|
|
50
|
-
return req;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
/** ky HTTPError → ContractApiError(错误体为信封时取 code/msg);契约违例与原错误原样透传 */
|
|
54
|
-
async function toApiError(e: unknown): Promise<unknown> {
|
|
55
|
-
if (e instanceof ContractApiError)
|
|
56
|
-
return e;
|
|
57
|
-
// ky 2.x 构造 HTTPError 时已用 getReader() 消费掉原 response 体(res.clone().json() 按规范抛 TypeError,
|
|
58
|
-
// 体方法一律失效),解析好的错误体在公开字段 error.data——故读 error.data,勿改回 clone().json()。
|
|
59
|
-
const err = e as { data?: { code?: unknown, msg?: string }, response?: Response } | null;
|
|
60
|
-
const res = err?.response;
|
|
61
|
-
const d = err?.data;
|
|
62
|
-
if (res instanceof Response && d && typeof d.code === "number")
|
|
63
|
-
return new ContractApiError(d.code, d.msg ?? res.statusText);
|
|
64
|
-
return e;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
export async function notificationsGet(): Promise<unknown> {
|
|
68
|
-
const client = ensureReq();
|
|
69
|
-
try {
|
|
70
|
-
const env = await client.get(`notifications`).json<OjEnvelope<unknown>>();
|
|
71
|
-
// 2xx + code!==0 也是业务错误(§6.2 通道 a):oj 不会这么发,但契约机制的价值恰是防漂移
|
|
72
|
-
if (typeof env.code === "number" && env.code !== 0)
|
|
73
|
-
throw new ContractApiError(env.code, env.msg ?? "业务错误(信封 code 非 0)");
|
|
74
|
-
const data = env.data as unknown;
|
|
75
|
-
return data;
|
|
76
|
-
}
|
|
77
|
-
catch (e) {
|
|
78
|
-
throw await toApiError(e);
|
|
79
|
-
}
|
|
80
|
-
}
|
|
File without changes
|