| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061 |
- import { indexBoardMembers, seedMemberGroups, changeMemberGroups, MEMBER_GROUP_SCHEMA } from '../../src/core/member-groups.js';
- /** Read exactly the existing public projection, including deployed employee identity overlays. */
- export async function readMemberGroupBoard({ url, appId, fetchImpl = fetch }) {
- const raw = { session: [], artifact: [], project: [], opportunity: [] };
- let cursor;
- for (let page = 0; page < 500; page++) {
- const response = await fetchImpl(url, { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-Parse-Application-Id': appId },
- body: JSON.stringify({ path: '/ksfGetPublicBoard', params: { limit: 500, ...(cursor ? { cursor } : {}) } }), signal: AbortSignal.timeout(30000) });
- const result = (await response.json())?.result;
- if (!response.ok || !result?.ok || !result.data) throw Object.assign(new Error('成员读取失败,请刷新后重试'), { code: 'MEMBER_READ_FAILED' });
- for (const key of Object.keys(raw)) {
- if (!Array.isArray(result.data[key])) throw Object.assign(new Error('成员数据不完整'), { code: 'MEMBER_READ_FAILED' });
- raw[key].push(...result.data[key]);
- }
- cursor = result.data.nextCursor;
- if (!cursor) return raw;
- }
- throw Object.assign(new Error('成员分页超限'), { code: 'MEMBER_READ_FAILED' });
- }
- /** Shared presentation groups only. Never mutate workshop groups or access grants. */
- export function attachMemberGroupHandlers(app, { store, loadBoard }) {
- const { fail } = app, configKey = 'public-board-member-groups';
- const actor = { id: 'public-board', kind: 'anonymous', scopes: [], projectIds: [] };
- const current = (row, members) => {
- if (!row) return seedMemberGroups(members);
- const state = row.value;
- if (state?.schema !== MEMBER_GROUP_SCHEMA || !Number.isSafeInteger(state.revision) || state.revision < 1 || !Array.isArray(state.groups) ||
- !state.assignments || typeof state.assignments !== 'object' || Array.isArray(state.assignments)) fail('GROUP_CONFIG_INVALID', '分组数据异常,请联系维护人');
- return structuredClone(state);
- };
- const read = async (s, members, lock = false) => {
- const rows = await s.list('KsfRuntimeConfig', { where: { key: configKey }, limit: 2, forUpdate: lock });
- if (rows.length > 1) fail('GROUP_CONFIG_INVALID', '分组配置重复,请联系维护人');
- return { row: rows[0], state: current(rows[0], members) };
- };
- app.handlers.ksfGetPublicMemberGroups = async (p) => {
- if (Object.keys(p).some((key) => key !== 'requestId')) fail('VALIDATION_FAILED', '查询包含未允许字段');
- const members = indexBoardMembers(await loadBoard()).members;
- const { state } = await read(store, members);
- return { ...state, readOnly: true, memberCount: members.length };
- };
- app.handlers.ksfManagePublicMemberGroups = async (p) => {
- const fields = { create: ['name'], rename: ['groupId', 'name'], delete: ['groupId'], assign: ['groupId', 'memberIds'] };
- if (!Object.hasOwn(fields, p.action) || Object.keys(p).some((key) => !['requestId', 'expectedRevision', 'action', ...fields[p.action]].includes(key))) fail('VALIDATION_FAILED', '操作包含未允许字段');
- if (!Number.isSafeInteger(p.expectedRevision) || p.expectedRevision < 0) fail('VALIDATION_FAILED', '缺少有效分组版本,请刷新');
- const members = indexBoardMembers(await loadBoard()).members;
- const allowed = new Set(members.map((m) => m.id));
- return app.operation(actor, 'public-member-groups', p, async (s) => {
- await s.lock('ksf:public-member-groups');
- const { row, state } = await read(s, members, true);
- if (state.revision !== p.expectedRevision) fail('VERSION_CONFLICT', '分组已被其他人更新,请刷新后再操作');
- const next = changeMemberGroups(state, p, allowed, 'group:' + app.hash(p.requestId).slice(0,24));
- next.updatedAt = new Date().toISOString();
- if (row) await s.update('KsfRuntimeConfig', row.objectId, { value: next });
- else await s.create('KsfRuntimeConfig', { key: configKey, value: next });
- return { ...next, readOnly: false, groupingOnly: true, businessRecordsChanged: 0 };
- });
- };
- }
|