sa2kit 1.6.91 → 1.6.96

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.
Files changed (117) hide show
  1. package/dist/client-BlkUL2To.d.ts +26 -0
  2. package/dist/client-DpMIhrlS.d.mts +26 -0
  3. package/dist/huarongdao/index.d.mts +8 -0
  4. package/dist/huarongdao/index.d.ts +8 -0
  5. package/dist/huarongdao/index.js +360 -0
  6. package/dist/huarongdao/index.js.map +1 -0
  7. package/dist/huarongdao/index.mjs +338 -0
  8. package/dist/huarongdao/index.mjs.map +1 -0
  9. package/dist/huarongdao/logic/index.d.mts +11 -0
  10. package/dist/huarongdao/logic/index.d.ts +11 -0
  11. package/dist/huarongdao/logic/index.js +89 -0
  12. package/dist/huarongdao/logic/index.js.map +1 -0
  13. package/dist/huarongdao/logic/index.mjs +81 -0
  14. package/dist/huarongdao/logic/index.mjs.map +1 -0
  15. package/dist/huarongdao/routes/index.d.mts +38 -0
  16. package/dist/huarongdao/routes/index.d.ts +38 -0
  17. package/dist/huarongdao/routes/index.js +114 -0
  18. package/dist/huarongdao/routes/index.js.map +1 -0
  19. package/dist/huarongdao/routes/index.mjs +108 -0
  20. package/dist/huarongdao/routes/index.mjs.map +1 -0
  21. package/dist/huarongdao/server/index.d.mts +14 -0
  22. package/dist/huarongdao/server/index.d.ts +14 -0
  23. package/dist/huarongdao/server/index.js +60 -0
  24. package/dist/huarongdao/server/index.js.map +1 -0
  25. package/dist/huarongdao/server/index.mjs +57 -0
  26. package/dist/huarongdao/server/index.mjs.map +1 -0
  27. package/dist/huarongdao/service/index.d.mts +31 -0
  28. package/dist/huarongdao/service/index.d.ts +31 -0
  29. package/dist/huarongdao/service/index.js +45 -0
  30. package/dist/huarongdao/service/index.js.map +1 -0
  31. package/dist/huarongdao/service/index.mjs +42 -0
  32. package/dist/huarongdao/service/index.mjs.map +1 -0
  33. package/dist/huarongdao/types/index.d.mts +46 -0
  34. package/dist/huarongdao/types/index.d.ts +46 -0
  35. package/dist/huarongdao/types/index.js +4 -0
  36. package/dist/huarongdao/types/index.js.map +1 -0
  37. package/dist/huarongdao/types/index.mjs +3 -0
  38. package/dist/huarongdao/types/index.mjs.map +1 -0
  39. package/dist/huarongdao/ui/web/index.d.mts +3 -0
  40. package/dist/huarongdao/ui/web/index.d.ts +3 -0
  41. package/dist/huarongdao/ui/web/index.js +237 -0
  42. package/dist/huarongdao/ui/web/index.js.map +1 -0
  43. package/dist/huarongdao/ui/web/index.mjs +229 -0
  44. package/dist/huarongdao/ui/web/index.mjs.map +1 -0
  45. package/dist/index-B48rcsqv.d.ts +27 -0
  46. package/dist/index-BNqJdwX4.d.ts +37 -0
  47. package/dist/index-C7yh6b5Q.d.mts +17 -0
  48. package/dist/index-CDapUIT5.d.mts +51 -0
  49. package/dist/index-Cv9jlnNz.d.ts +17 -0
  50. package/dist/index-D3UbkUai.d.ts +51 -0
  51. package/dist/index-DOtQI_mz.d.mts +37 -0
  52. package/dist/index-Da2X78GE.d.mts +27 -0
  53. package/dist/index.d.mts +18 -0
  54. package/dist/index.d.ts +18 -0
  55. package/dist/index.js +1707 -79
  56. package/dist/index.js.map +1 -1
  57. package/dist/index.mjs +1675 -82
  58. package/dist/index.mjs.map +1 -1
  59. package/dist/mikuContest/index.d.mts +13 -0
  60. package/dist/mikuContest/index.d.ts +13 -0
  61. package/dist/mikuContest/index.js +1310 -0
  62. package/dist/mikuContest/index.js.map +1 -0
  63. package/dist/mikuContest/index.mjs +1253 -0
  64. package/dist/mikuContest/index.mjs.map +1 -0
  65. package/dist/mikuContest/logic/index.d.mts +32 -0
  66. package/dist/mikuContest/logic/index.d.ts +32 -0
  67. package/dist/mikuContest/logic/index.js +511 -0
  68. package/dist/mikuContest/logic/index.js.map +1 -0
  69. package/dist/mikuContest/logic/index.mjs +483 -0
  70. package/dist/mikuContest/logic/index.mjs.map +1 -0
  71. package/dist/mikuContest/routes/index.d.mts +80 -0
  72. package/dist/mikuContest/routes/index.d.ts +80 -0
  73. package/dist/mikuContest/routes/index.js +821 -0
  74. package/dist/mikuContest/routes/index.js.map +1 -0
  75. package/dist/mikuContest/routes/index.mjs +791 -0
  76. package/dist/mikuContest/routes/index.mjs.map +1 -0
  77. package/dist/mikuContest/server/index.d.mts +766 -0
  78. package/dist/mikuContest/server/index.d.ts +766 -0
  79. package/dist/mikuContest/server/index.js +705 -0
  80. package/dist/mikuContest/server/index.js.map +1 -0
  81. package/dist/mikuContest/server/index.mjs +672 -0
  82. package/dist/mikuContest/server/index.mjs.map +1 -0
  83. package/dist/mikuContest/service/index.d.mts +30 -0
  84. package/dist/mikuContest/service/index.d.ts +30 -0
  85. package/dist/mikuContest/service/index.js +139 -0
  86. package/dist/mikuContest/service/index.js.map +1 -0
  87. package/dist/mikuContest/service/index.mjs +135 -0
  88. package/dist/mikuContest/service/index.mjs.map +1 -0
  89. package/dist/mikuContest/types/index.d.mts +179 -0
  90. package/dist/mikuContest/types/index.d.ts +179 -0
  91. package/dist/mikuContest/types/index.js +4 -0
  92. package/dist/mikuContest/types/index.js.map +1 -0
  93. package/dist/mikuContest/types/index.mjs +3 -0
  94. package/dist/mikuContest/types/index.mjs.map +1 -0
  95. package/dist/mikuContest/ui/miniapp/index.d.mts +3 -0
  96. package/dist/mikuContest/ui/miniapp/index.d.ts +3 -0
  97. package/dist/mikuContest/ui/miniapp/index.js +566 -0
  98. package/dist/mikuContest/ui/miniapp/index.js.map +1 -0
  99. package/dist/mikuContest/ui/miniapp/index.mjs +540 -0
  100. package/dist/mikuContest/ui/miniapp/index.mjs.map +1 -0
  101. package/dist/mikuContest/ui/web/index.d.mts +4 -0
  102. package/dist/mikuContest/ui/web/index.d.ts +4 -0
  103. package/dist/mikuContest/ui/web/index.js +353 -0
  104. package/dist/mikuContest/ui/web/index.js.map +1 -0
  105. package/dist/mikuContest/ui/web/index.mjs +343 -0
  106. package/dist/mikuContest/ui/web/index.mjs.map +1 -0
  107. package/dist/qqbot/server/index.d.mts +126 -1
  108. package/dist/qqbot/server/index.d.ts +126 -1
  109. package/dist/qqbot/server/index.js +250 -0
  110. package/dist/qqbot/server/index.js.map +1 -1
  111. package/dist/qqbot/server/index.mjs +246 -1
  112. package/dist/qqbot/server/index.mjs.map +1 -1
  113. package/dist/service-D7DM1wW-.d.ts +38 -0
  114. package/dist/service-DPr2rlvH.d.mts +38 -0
  115. package/dist/types-BS7Xz09b.d.mts +14 -0
  116. package/dist/types-k4koMp4m.d.ts +14 -0
  117. package/package.json +76 -1
@@ -0,0 +1,566 @@
1
+ 'use strict';
2
+
3
+ var React2 = require('react');
4
+ var XLSX = require('xlsx');
5
+ var pgCore = require('drizzle-orm/pg-core');
6
+ require('drizzle-orm');
7
+
8
+ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
9
+
10
+ function _interopNamespace(e) {
11
+ if (e && e.__esModule) return e;
12
+ var n = Object.create(null);
13
+ if (e) {
14
+ Object.keys(e).forEach(function (k) {
15
+ if (k !== 'default') {
16
+ var d = Object.getOwnPropertyDescriptor(e, k);
17
+ Object.defineProperty(n, k, d.get ? d : {
18
+ enumerable: true,
19
+ get: function () { return e[k]; }
20
+ });
21
+ }
22
+ });
23
+ }
24
+ n.default = e;
25
+ return Object.freeze(n);
26
+ }
27
+
28
+ var React2__default = /*#__PURE__*/_interopDefault(React2);
29
+ var XLSX__namespace = /*#__PURE__*/_interopNamespace(XLSX);
30
+
31
+ // src/mikuContest/ui/miniapp/components/MikuContestMiniappHome.tsx
32
+ var MikuContestMiniappHome = ({ snapshot }) => {
33
+ return /* @__PURE__ */ React2__default.default.createElement("div", null, /* @__PURE__ */ React2__default.default.createElement("h3", null, snapshot.contest.name), /* @__PURE__ */ React2__default.default.createElement("p", null, "\u6295\u7A3F\uFF1A", snapshot.submissions.length, " | \u516C\u544A\uFF1A", snapshot.announcements.length), /* @__PURE__ */ React2__default.default.createElement("ol", null, snapshot.leaderboard.slice(0, 3).map((item) => /* @__PURE__ */ React2__default.default.createElement("li", { key: item.submissionId }, item.title, "\uFF08", item.voteCount, "\u7968\uFF09"))));
34
+ };
35
+ var MikuContestMiniappHome_default = MikuContestMiniappHome;
36
+
37
+ // src/mikuContest/logic/shared/defaults.ts
38
+ var defaultMikuVotingRules = {
39
+ maxVotesPerDay: 3,
40
+ forbidDuplicateVotePerWork: true,
41
+ maxVotesPerDevicePerDay: 20,
42
+ maxVotesPerIpPerDay: 100
43
+ };
44
+ var createDefaultMikuContestConfig = (overrides) => ({
45
+ id: overrides?.id || "miku-contest-default",
46
+ name: overrides?.name || "\u521D\u97F3\u672A\u6765\u793E\u56E2\u5F81\u7A3F\u5927\u8D5B",
47
+ theme: overrides?.theme || "\u521D\u97F3\u672A\u6765\u4E3B\u9898\u521B\u4F5C\u5F81\u7A3F",
48
+ organizer: overrides?.organizer || "\u521D\u97F3\u672A\u6765\u793E\u56E2",
49
+ awards: overrides?.awards || ["\u4E00\u7B49\u5956", "\u4E8C\u7B49\u5956", "\u4E09\u7B49\u5956", "\u4EBA\u6C14\u5956"],
50
+ rules: overrides?.rules || "\u8BF7\u786E\u4FDD\u4F5C\u54C1\u539F\u521B\u4E14\u7B26\u5408\u793E\u56E2\u89C4\u8303\u3002",
51
+ copyright: overrides?.copyright || "\u6295\u7A3F\u5373\u89C6\u4E3A\u6388\u6743\u8D5B\u4E8B\u5C55\u793A\u4E0E\u516C\u793A\u3002",
52
+ timeline: overrides?.timeline || {
53
+ submissionStartAt: (/* @__PURE__ */ new Date()).toISOString(),
54
+ submissionEndAt: (/* @__PURE__ */ new Date()).toISOString(),
55
+ votingStartAt: (/* @__PURE__ */ new Date()).toISOString(),
56
+ votingEndAt: (/* @__PURE__ */ new Date()).toISOString(),
57
+ publicResultAt: (/* @__PURE__ */ new Date()).toISOString()
58
+ },
59
+ votingRules: {
60
+ ...defaultMikuVotingRules,
61
+ ...overrides?.votingRules || {}
62
+ },
63
+ toggles: {
64
+ submissionEnabled: overrides?.toggles?.submissionEnabled ?? true,
65
+ votingEnabled: overrides?.toggles?.votingEnabled ?? true,
66
+ resultEnabled: overrides?.toggles?.resultEnabled ?? false
67
+ }
68
+ });
69
+
70
+ // src/mikuContest/logic/shared/validators.ts
71
+ var DESCRIPTION_LIMIT = 500;
72
+ var MINIAPP_DESCRIPTION_LIMIT = 200;
73
+ var TEXT_CONTENT_LIMIT = 2e3;
74
+ var MAX_TAGS = 3;
75
+ var hasValue = (value) => {
76
+ return typeof value === "string" ? value.trim().length > 0 : value !== null && value !== void 0;
77
+ };
78
+ var validateByType = (type, input) => {
79
+ const errors = [];
80
+ const { content } = input;
81
+ switch (type) {
82
+ case "visual": {
83
+ const imageCount = content.images?.length || 0;
84
+ if (imageCount < 1 || imageCount > 3) {
85
+ errors.push("\u89C6\u89C9\u7C7B\u4F5C\u54C1\u9700\u4E0A\u4F20 1-3 \u5F20\u56FE\u7247");
86
+ }
87
+ break;
88
+ }
89
+ case "video": {
90
+ if (!hasValue(content.videoLink)) {
91
+ errors.push("\u89C6\u9891\u7C7B\u4F5C\u54C1\u9700\u63D0\u4F9B\u89C6\u9891\u94FE\u63A5");
92
+ }
93
+ if (!hasValue(content.coverImage)) {
94
+ errors.push("\u89C6\u9891\u7C7B\u4F5C\u54C1\u9700\u63D0\u4F9B\u5C01\u9762\u56FE");
95
+ }
96
+ break;
97
+ }
98
+ case "text": {
99
+ const text2 = content.textContent || "";
100
+ if (!text2.trim()) {
101
+ errors.push("\u6587\u5B57\u7C7B\u4F5C\u54C1\u9700\u586B\u5199\u6B63\u6587");
102
+ }
103
+ if (text2.length > TEXT_CONTENT_LIMIT) {
104
+ errors.push(`\u6587\u5B57\u6B63\u6587\u4E0D\u80FD\u8D85\u8FC7 ${TEXT_CONTENT_LIMIT} \u5B57`);
105
+ }
106
+ break;
107
+ }
108
+ case "audio": {
109
+ if (!hasValue(content.audioLink)) {
110
+ errors.push("\u97F3\u9891\u7C7B\u4F5C\u54C1\u9700\u63D0\u4F9B\u97F3\u9891\u94FE\u63A5");
111
+ }
112
+ break;
113
+ }
114
+ }
115
+ return errors;
116
+ };
117
+ var validateMikuSubmissionInput = (input, mode = "web") => {
118
+ const errors = [];
119
+ if (!input.contestId.trim()) errors.push("contestId \u4E0D\u80FD\u4E3A\u7A7A");
120
+ if (!input.authorId.trim()) errors.push("authorId \u4E0D\u80FD\u4E3A\u7A7A");
121
+ if (!input.authorNickname.trim()) errors.push("\u4F5C\u8005\u6635\u79F0\u4E0D\u80FD\u4E3A\u7A7A");
122
+ if (!input.title.trim()) errors.push("\u4F5C\u54C1\u540D\u79F0\u4E0D\u80FD\u4E3A\u7A7A");
123
+ const descriptionLimit = mode === "miniapp" ? MINIAPP_DESCRIPTION_LIMIT : DESCRIPTION_LIMIT;
124
+ if (input.description.length > descriptionLimit) {
125
+ errors.push(`\u4F5C\u54C1\u7B80\u4ECB\u4E0D\u80FD\u8D85\u8FC7 ${descriptionLimit} \u5B57`);
126
+ }
127
+ if ((input.tags?.length || 0) > MAX_TAGS) {
128
+ errors.push(`\u6807\u7B7E\u6700\u591A ${MAX_TAGS} \u4E2A`);
129
+ }
130
+ errors.push(...validateByType(input.type, input));
131
+ return errors;
132
+ };
133
+
134
+ // src/mikuContest/logic/shared/voting.ts
135
+ var toVoteDayKey = (date = /* @__PURE__ */ new Date()) => {
136
+ const y = date.getUTCFullYear();
137
+ const m = String(date.getUTCMonth() + 1).padStart(2, "0");
138
+ const d = String(date.getUTCDate()).padStart(2, "0");
139
+ return `${y}-${m}-${d}`;
140
+ };
141
+ var checkVoteEligibility = (context) => {
142
+ const { existingVotes, submissionId, voterId, dayKey, rules } = context;
143
+ const userTodayVotes = existingVotes.filter((vote) => vote.voterId === voterId && vote.dayKey === dayKey);
144
+ if (userTodayVotes.length >= rules.maxVotesPerDay) {
145
+ return { ok: false, reason: "\u5DF2\u8FBE\u5230\u4ECA\u65E5\u6295\u7968\u4E0A\u9650" };
146
+ }
147
+ if (rules.forbidDuplicateVotePerWork) {
148
+ const duplicated = userTodayVotes.some((vote) => vote.submissionId === submissionId);
149
+ if (duplicated) return { ok: false, reason: "\u4E0D\u53EF\u91CD\u590D\u6295\u540C\u4E00\u4F5C\u54C1" };
150
+ }
151
+ return { ok: true };
152
+ };
153
+ var sortByVotesDesc = (items) => {
154
+ return [...items].sort((a, b) => {
155
+ if (b.voteCount === a.voteCount) {
156
+ return (a.createdAt || "").localeCompare(b.createdAt || "");
157
+ }
158
+ return b.voteCount - a.voteCount;
159
+ });
160
+ };
161
+ var randomId = (prefix) => {
162
+ return `${prefix}_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
163
+ };
164
+ var serialNo = () => {
165
+ const now = /* @__PURE__ */ new Date();
166
+ const y = now.getFullYear();
167
+ const m = String(now.getMonth() + 1).padStart(2, "0");
168
+ const d = String(now.getDate()).padStart(2, "0");
169
+ const seq = Math.floor(Math.random() * 9e3 + 1e3);
170
+ return `MIKU-${y}${m}${d}-${seq}`;
171
+ };
172
+ var MikuContestService = class {
173
+ constructor(options = {}) {
174
+ this.submissions = /* @__PURE__ */ new Map();
175
+ this.votes = [];
176
+ this.announcements = /* @__PURE__ */ new Map();
177
+ this.voterRestrictions = /* @__PURE__ */ new Map();
178
+ this.contest = createDefaultMikuContestConfig(options.contestConfig);
179
+ }
180
+ getContestConfig() {
181
+ return this.contest;
182
+ }
183
+ updateContestConfig(patch) {
184
+ this.contest = {
185
+ ...this.contest,
186
+ ...patch,
187
+ votingRules: {
188
+ ...this.contest.votingRules,
189
+ ...patch.votingRules || {}
190
+ },
191
+ toggles: {
192
+ ...this.contest.toggles,
193
+ ...patch.toggles || {}
194
+ },
195
+ timeline: {
196
+ ...this.contest.timeline,
197
+ ...patch.timeline || {}
198
+ }
199
+ };
200
+ return this.contest;
201
+ }
202
+ createSubmission(input, mode = "web") {
203
+ if (!this.contest.toggles.submissionEnabled) {
204
+ throw new Error("\u5F53\u524D\u672A\u5F00\u653E\u6295\u7A3F");
205
+ }
206
+ const errors = validateMikuSubmissionInput(input, mode);
207
+ if (errors.length > 0) {
208
+ throw new Error(errors.join("\uFF1B"));
209
+ }
210
+ const now = (/* @__PURE__ */ new Date()).toISOString();
211
+ const next = {
212
+ id: randomId("submission"),
213
+ serialNo: serialNo(),
214
+ contestId: input.contestId,
215
+ authorId: input.authorId,
216
+ authorNickname: input.authorNickname,
217
+ title: input.title,
218
+ type: input.type,
219
+ description: input.description,
220
+ tags: input.tags || [],
221
+ content: input.content,
222
+ voteCount: 0,
223
+ status: "pending",
224
+ createdAt: now,
225
+ updatedAt: now
226
+ };
227
+ this.submissions.set(next.id, next);
228
+ return next;
229
+ }
230
+ listSubmissions(filter) {
231
+ const authorKeyword = filter?.authorKeyword?.trim().toLowerCase();
232
+ const titleKeyword = filter?.titleKeyword?.trim().toLowerCase();
233
+ return [...this.submissions.values()].filter((item) => {
234
+ if (filter?.status && item.status !== filter.status) return false;
235
+ if (filter?.type && item.type !== filter.type) return false;
236
+ if (filter?.authorId && item.authorId !== filter.authorId) return false;
237
+ if (authorKeyword && !item.authorNickname.toLowerCase().includes(authorKeyword)) return false;
238
+ if (titleKeyword && !item.title.toLowerCase().includes(titleKeyword)) return false;
239
+ return true;
240
+ });
241
+ }
242
+ getSubmission(submissionId) {
243
+ return this.submissions.get(submissionId) || null;
244
+ }
245
+ reviewSubmission(input) {
246
+ const current = this.submissions.get(input.submissionId);
247
+ if (!current) throw new Error("\u6295\u7A3F\u4E0D\u5B58\u5728");
248
+ if (input.action === "reject" && !input.rejectReason?.trim()) {
249
+ throw new Error("\u9A73\u56DE\u9700\u586B\u5199\u539F\u56E0");
250
+ }
251
+ const reviewed = {
252
+ ...current,
253
+ status: input.action === "approve" ? "approved" : "rejected",
254
+ rejectReason: input.action === "reject" ? input.rejectReason?.trim() : void 0,
255
+ reviewedAt: (/* @__PURE__ */ new Date()).toISOString(),
256
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
257
+ };
258
+ this.submissions.set(reviewed.id, reviewed);
259
+ return reviewed;
260
+ }
261
+ vote(input) {
262
+ if (!this.contest.toggles.votingEnabled) {
263
+ throw new Error("\u5F53\u524D\u672A\u5F00\u653E\u6295\u7968");
264
+ }
265
+ const restriction = this.voterRestrictions.get(input.voterId);
266
+ if (restriction?.banned) {
267
+ throw new Error("\u5F53\u524D\u8D26\u53F7\u5DF2\u88AB\u9650\u5236\u6295\u7968");
268
+ }
269
+ const target = this.submissions.get(input.submissionId);
270
+ if (!target) throw new Error("\u4F5C\u54C1\u4E0D\u5B58\u5728");
271
+ if (target.status !== "approved") throw new Error("\u4EC5\u53EF\u5BF9\u5DF2\u8FC7\u5BA1\u4F5C\u54C1\u6295\u7968");
272
+ const dayKey = toVoteDayKey();
273
+ const eligible = checkVoteEligibility({
274
+ existingVotes: this.votes,
275
+ submissionId: input.submissionId,
276
+ voterId: input.voterId,
277
+ dayKey,
278
+ rules: this.contest.votingRules
279
+ });
280
+ if (!eligible.ok) {
281
+ throw new Error(eligible.reason || "\u6295\u7968\u5931\u8D25");
282
+ }
283
+ const vote = {
284
+ id: randomId("vote"),
285
+ contestId: input.contestId,
286
+ submissionId: input.submissionId,
287
+ voterId: input.voterId,
288
+ votedAt: (/* @__PURE__ */ new Date()).toISOString(),
289
+ dayKey,
290
+ deviceId: input.deviceId,
291
+ ip: input.ip
292
+ };
293
+ this.votes.push(vote);
294
+ const updated = {
295
+ ...target,
296
+ voteCount: target.voteCount + 1,
297
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
298
+ };
299
+ this.submissions.set(updated.id, updated);
300
+ return updated;
301
+ }
302
+ getVoterRestriction(voterId) {
303
+ return this.voterRestrictions.get(voterId) || null;
304
+ }
305
+ setVoterRestriction(input) {
306
+ const next = {
307
+ voterId: input.voterId,
308
+ banned: input.banned,
309
+ reason: input.reason,
310
+ operatorId: input.operatorId,
311
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
312
+ };
313
+ this.voterRestrictions.set(input.voterId, next);
314
+ return next;
315
+ }
316
+ resetVotes(input) {
317
+ if (!input.submissionId && !input.voterId) {
318
+ throw new Error("submissionId \u4E0E voterId \u81F3\u5C11\u63D0\u4F9B\u4E00\u4E2A");
319
+ }
320
+ const before = this.votes.length;
321
+ const affected = /* @__PURE__ */ new Set();
322
+ const remained = this.votes.filter((vote) => {
323
+ const matchSubmission = input.submissionId ? vote.submissionId === input.submissionId : true;
324
+ const matchVoter = input.voterId ? vote.voterId === input.voterId : true;
325
+ const shouldRemove = matchSubmission && matchVoter;
326
+ if (shouldRemove) affected.add(vote.submissionId);
327
+ return !shouldRemove;
328
+ });
329
+ this.votes.length = 0;
330
+ this.votes.push(...remained);
331
+ this.recalculateVoteCounts();
332
+ return {
333
+ removedVotes: before - remained.length,
334
+ affectedSubmissions: [...affected]
335
+ };
336
+ }
337
+ listAnnouncements(contestId) {
338
+ const all = [...this.announcements.values()];
339
+ return contestId ? all.filter((item) => item.contestId === contestId) : all;
340
+ }
341
+ publishAnnouncement(input) {
342
+ const now = (/* @__PURE__ */ new Date()).toISOString();
343
+ const announcement = {
344
+ id: randomId("notice"),
345
+ contestId: input.contestId,
346
+ title: input.title,
347
+ content: input.content,
348
+ type: input.type,
349
+ createdBy: input.createdBy,
350
+ createdAt: now,
351
+ updatedAt: now
352
+ };
353
+ this.announcements.set(announcement.id, announcement);
354
+ return announcement;
355
+ }
356
+ getLeaderboard(limit = 10) {
357
+ const ranked = sortByVotesDesc(this.listSubmissions({ status: "approved" })).slice(0, limit);
358
+ return ranked.map((item, index) => ({
359
+ submissionId: item.id,
360
+ title: item.title,
361
+ authorNickname: item.authorNickname,
362
+ voteCount: item.voteCount,
363
+ rank: index + 1
364
+ }));
365
+ }
366
+ getSnapshot() {
367
+ return {
368
+ contest: this.contest,
369
+ submissions: this.listSubmissions(),
370
+ announcements: this.listAnnouncements(),
371
+ leaderboard: this.getLeaderboard()
372
+ };
373
+ }
374
+ getSubmissionExportRows(filter) {
375
+ return this.listSubmissions(filter).map((item) => ({
376
+ \u6295\u7A3F\u7F16\u53F7: item.serialNo,
377
+ \u6295\u7A3FID: item.id,
378
+ \u8D5B\u4E8BID: item.contestId,
379
+ \u4F5C\u8005ID: item.authorId,
380
+ \u4F5C\u8005\u6635\u79F0: item.authorNickname,
381
+ \u4F5C\u54C1\u540D\u79F0: item.title,
382
+ \u4F5C\u54C1\u7C7B\u578B: item.type,
383
+ \u7B80\u4ECB: item.description,
384
+ \u6807\u7B7E: item.tags.join(","),
385
+ \u5BA1\u6838\u72B6\u6001: item.status,
386
+ \u9A73\u56DE\u539F\u56E0: item.rejectReason || "",
387
+ \u7968\u6570: item.voteCount,
388
+ \u63D0\u4EA4\u65F6\u95F4: item.createdAt,
389
+ \u66F4\u65B0\u65F6\u95F4: item.updatedAt
390
+ }));
391
+ }
392
+ exportSubmissionExcel(filter) {
393
+ const rows = this.getSubmissionExportRows(filter);
394
+ const workbook = XLSX__namespace.utils.book_new();
395
+ const worksheet = XLSX__namespace.utils.json_to_sheet(rows);
396
+ XLSX__namespace.utils.book_append_sheet(workbook, worksheet, "submissions");
397
+ return XLSX__namespace.write(workbook, { bookType: "xlsx", type: "buffer" });
398
+ }
399
+ recalculateVoteCounts() {
400
+ const counts = /* @__PURE__ */ new Map();
401
+ for (const vote of this.votes) {
402
+ counts.set(vote.submissionId, (counts.get(vote.submissionId) || 0) + 1);
403
+ }
404
+ for (const [id, submission] of this.submissions.entries()) {
405
+ const nextCount = counts.get(id) || 0;
406
+ if (submission.voteCount === nextCount) continue;
407
+ this.submissions.set(id, {
408
+ ...submission,
409
+ voteCount: nextCount,
410
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
411
+ });
412
+ }
413
+ }
414
+ exportPersistenceState() {
415
+ return {
416
+ contest: this.contest,
417
+ submissions: [...this.submissions.values()],
418
+ votes: [...this.votes],
419
+ announcements: [...this.announcements.values()],
420
+ voterRestrictions: [...this.voterRestrictions.values()],
421
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
422
+ };
423
+ }
424
+ importPersistenceState(state) {
425
+ this.contest = state.contest;
426
+ this.submissions.clear();
427
+ this.announcements.clear();
428
+ this.voterRestrictions.clear();
429
+ this.votes.length = 0;
430
+ for (const item of state.submissions) {
431
+ this.submissions.set(item.id, item);
432
+ }
433
+ for (const item of state.announcements) {
434
+ this.announcements.set(item.id, item);
435
+ }
436
+ for (const item of state.voterRestrictions) {
437
+ this.voterRestrictions.set(item.voterId, item);
438
+ }
439
+ this.votes.push(...state.votes);
440
+ }
441
+ };
442
+ var createMikuContestService = (options) => {
443
+ return new MikuContestService(options);
444
+ };
445
+ pgCore.pgTable("miku_contest_configs", {
446
+ contestId: pgCore.text("contest_id").primaryKey(),
447
+ config: pgCore.jsonb("config").$type().notNull(),
448
+ createdAt: pgCore.timestamp("created_at", { withTimezone: true }).defaultNow().notNull(),
449
+ updatedAt: pgCore.timestamp("updated_at", { withTimezone: true }).defaultNow().notNull()
450
+ });
451
+ pgCore.pgTable("miku_contest_submissions", {
452
+ id: pgCore.text("id").primaryKey(),
453
+ contestId: pgCore.text("contest_id").notNull(),
454
+ serialNo: pgCore.text("serial_no").notNull(),
455
+ authorId: pgCore.text("author_id").notNull(),
456
+ authorNickname: pgCore.text("author_nickname").notNull(),
457
+ title: pgCore.text("title").notNull(),
458
+ type: pgCore.text("type").notNull(),
459
+ description: pgCore.text("description").notNull(),
460
+ tags: pgCore.jsonb("tags").$type().notNull(),
461
+ content: pgCore.jsonb("content").$type().notNull(),
462
+ voteCount: pgCore.integer("vote_count").notNull().default(0),
463
+ status: pgCore.text("status").notNull(),
464
+ rejectReason: pgCore.text("reject_reason"),
465
+ createdAt: pgCore.timestamp("created_at", { withTimezone: true }).notNull(),
466
+ reviewedAt: pgCore.timestamp("reviewed_at", { withTimezone: true }),
467
+ updatedAt: pgCore.timestamp("updated_at", { withTimezone: true }).notNull()
468
+ });
469
+ pgCore.pgTable("miku_contest_votes", {
470
+ id: pgCore.text("id").primaryKey(),
471
+ contestId: pgCore.text("contest_id").notNull(),
472
+ submissionId: pgCore.text("submission_id").notNull(),
473
+ voterId: pgCore.text("voter_id").notNull(),
474
+ votedAt: pgCore.text("voted_at").notNull(),
475
+ dayKey: pgCore.text("day_key").notNull(),
476
+ deviceId: pgCore.text("device_id"),
477
+ ip: pgCore.text("ip")
478
+ });
479
+ pgCore.pgTable("miku_contest_notices", {
480
+ id: pgCore.text("id").primaryKey(),
481
+ contestId: pgCore.text("contest_id").notNull(),
482
+ title: pgCore.text("title").notNull(),
483
+ content: pgCore.text("content").notNull(),
484
+ type: pgCore.text("type").notNull(),
485
+ createdBy: pgCore.text("created_by").notNull(),
486
+ createdAt: pgCore.text("created_at").notNull(),
487
+ updatedAt: pgCore.text("updated_at").notNull()
488
+ });
489
+ pgCore.pgTable("miku_contest_voter_restrictions", {
490
+ id: pgCore.text("id").primaryKey(),
491
+ contestId: pgCore.text("contest_id").notNull(),
492
+ data: pgCore.jsonb("data").$type().notNull()
493
+ });
494
+
495
+ // src/mikuContest/logic/hooks/useMikuContest.ts
496
+ var useMikuContest = (options) => {
497
+ const [service] = React2.useState(() => createMikuContestService(options));
498
+ const [version, setVersion] = React2.useState(0);
499
+ const refresh = () => setVersion((value) => value + 1);
500
+ const snapshot = React2.useMemo(() => {
501
+ return service.getSnapshot();
502
+ }, [service, version]);
503
+ return {
504
+ service,
505
+ snapshot,
506
+ refresh
507
+ };
508
+ };
509
+
510
+ // src/mikuContest/ui/miniapp/pages/MikuContestMiniappPage.tsx
511
+ var MikuContestMiniappPage = () => {
512
+ const { service, snapshot, refresh } = useMikuContest();
513
+ const [tab, setTab] = React2.useState("vote");
514
+ const [voterId, setVoterId] = React2.useState("miniapp-voter");
515
+ const [authorId, setAuthorId] = React2.useState("miniapp-author");
516
+ const [authorNickname, setAuthorNickname] = React2.useState("\u5C0F\u7A0B\u5E8F\u753B\u5E08");
517
+ const [title, setTitle] = React2.useState("");
518
+ const [desc, setDesc] = React2.useState("");
519
+ const [type, setType] = React2.useState("visual");
520
+ const [error, setError] = React2.useState(null);
521
+ const approvedWorks = React2.useMemo(() => {
522
+ return snapshot.submissions.filter((item) => item.status === "approved");
523
+ }, [snapshot.submissions]);
524
+ const vote = (submissionId) => {
525
+ try {
526
+ service.vote({
527
+ contestId: snapshot.contest.id,
528
+ submissionId,
529
+ voterId
530
+ });
531
+ refresh();
532
+ setError(null);
533
+ } catch (e) {
534
+ setError(e.message);
535
+ }
536
+ };
537
+ const submit = () => {
538
+ try {
539
+ service.createSubmission(
540
+ {
541
+ contestId: snapshot.contest.id,
542
+ authorId,
543
+ authorNickname,
544
+ title,
545
+ description: desc,
546
+ type,
547
+ content: {}
548
+ },
549
+ "miniapp"
550
+ );
551
+ setTitle("");
552
+ setDesc("");
553
+ refresh();
554
+ setError(null);
555
+ } catch (e) {
556
+ setError(e.message);
557
+ }
558
+ };
559
+ return /* @__PURE__ */ React2__default.default.createElement("div", null, /* @__PURE__ */ React2__default.default.createElement("h3", null, snapshot.contest.name), /* @__PURE__ */ React2__default.default.createElement("p", null, "\u5C0F\u7A0B\u5E8F\u7AEF\u793A\u4F8B\u9875\u9762"), /* @__PURE__ */ React2__default.default.createElement("button", { onClick: () => setTab("vote") }, "\u89C2\u4F17\u6295\u7968"), /* @__PURE__ */ React2__default.default.createElement("button", { onClick: () => setTab("submit") }, "\u753B\u5E08\u6295\u7A3F"), error ? /* @__PURE__ */ React2__default.default.createElement("p", { style: { color: "crimson" } }, "\u9519\u8BEF\uFF1A", error) : null, tab === "vote" ? /* @__PURE__ */ React2__default.default.createElement("div", null, /* @__PURE__ */ React2__default.default.createElement("input", { value: voterId, onChange: (e) => setVoterId(e.target.value), placeholder: "voterId" }), /* @__PURE__ */ React2__default.default.createElement("ul", null, approvedWorks.map((item) => /* @__PURE__ */ React2__default.default.createElement("li", { key: item.id }, item.title, "\uFF08", item.voteCount, "\u7968\uFF09", /* @__PURE__ */ React2__default.default.createElement("button", { onClick: () => vote(item.id) }, "\u6295\u7968"))))) : null, tab === "submit" ? /* @__PURE__ */ React2__default.default.createElement("div", null, /* @__PURE__ */ React2__default.default.createElement("input", { value: authorId, onChange: (e) => setAuthorId(e.target.value), placeholder: "authorId" }), /* @__PURE__ */ React2__default.default.createElement("input", { value: authorNickname, onChange: (e) => setAuthorNickname(e.target.value), placeholder: "\u4F5C\u8005\u6635\u79F0" }), /* @__PURE__ */ React2__default.default.createElement("input", { value: title, onChange: (e) => setTitle(e.target.value), placeholder: "\u4F5C\u54C1\u6807\u9898" }), /* @__PURE__ */ React2__default.default.createElement("input", { value: desc, onChange: (e) => setDesc(e.target.value), placeholder: "\u4F5C\u54C1\u7B80\u4ECB" }), /* @__PURE__ */ React2__default.default.createElement("select", { value: type, onChange: (e) => setType(e.target.value) }, /* @__PURE__ */ React2__default.default.createElement("option", { value: "visual" }, "visual"), /* @__PURE__ */ React2__default.default.createElement("option", { value: "video" }, "video"), /* @__PURE__ */ React2__default.default.createElement("option", { value: "text" }, "text"), /* @__PURE__ */ React2__default.default.createElement("option", { value: "audio" }, "audio")), /* @__PURE__ */ React2__default.default.createElement("button", { onClick: submit }, "\u63D0\u4EA4")) : null);
560
+ };
561
+ var MikuContestMiniappPage_default = MikuContestMiniappPage;
562
+
563
+ exports.MikuContestMiniappHome = MikuContestMiniappHome_default;
564
+ exports.MikuContestMiniappPage = MikuContestMiniappPage_default;
565
+ //# sourceMappingURL=index.js.map
566
+ //# sourceMappingURL=index.js.map