фигня какая-то фикс статистики 10
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful

This commit is contained in:
dv 2026-04-15 12:16:14 +03:00
parent 9c5ba61ab5
commit 57fd5a4839
2 changed files with 81 additions and 20 deletions

View file

@ -608,39 +608,92 @@ function createRefreshService({ db, fetch, getSettings, sseBroadcast, logger = c
return dayKeyNow !== dayKeyLast; return dayKeyNow !== dayKeyLast;
} }
function firstDefined(obj, keys) {
for (const key of keys) {
const value = obj?.[key];
if (value !== undefined && value !== null && String(value).trim() !== '') return value;
}
return null;
}
function extractClientUserRecord(raw) {
const userId = firstDefined(raw, ['userId', 'userid', 'user_id', 'id']);
const name = String(firstDefined(raw, ['name', 'fullName', 'fullname', 'displayName', 'username']) || '').trim();
const groupIds = new Set();
const pushGroup = (value) => {
if (value === undefined || value === null) return;
const normalized = String(value).trim();
if (normalized) groupIds.add(normalized);
};
pushGroup(firstDefined(raw, ['userGroupId', 'usergroupid', 'user_group_id', 'groupId', 'group_id']));
for (const bucketName of ['groups', 'userGroups']) {
const bucket = raw?.[bucketName];
if (!Array.isArray(bucket)) continue;
for (const group of bucket) {
pushGroup(firstDefined(group, ['userGroupId', 'usergroupid', 'groupId', 'group_id', 'id']));
}
}
return {
userId: userId === null ? '' : String(userId).trim(),
name,
groupIds: [...groupIds],
};
}
async function syncClientUsers() { async function syncClientUsers() {
const s = getSettings(); const s = getSettings();
if (!s.api_key) return; if (!s.api_key) return { fetched: 0, stored: 0, odata_count: null };
const pageSize = 100; const pageSize = 400;
let skip = 0; let skip = 0;
const allUsers = []; let odataCount = null;
const sourceRows = [];
while (true) { while (true) {
const url = `https://apigw.intradesk.ru/settings/odata/v2/Clients?ApiKey=${s.api_key}` const url = `https://apigw.intradesk.ru/settings/odata/ClientUsers?ApiKey=${s.api_key}`
+ `&$count=true`
+ `&$orderby=${encodeURIComponent('id desc')}`
+ `&$filter=${encodeURIComponent('(isarchived eq false)')}` + `&$filter=${encodeURIComponent('(isarchived eq false)')}`
+ `&$top=${pageSize}&$skip=${skip}`; + `&$top=${pageSize}&$skip=${skip}`;
const response = await fetch(url, { timeout: 30000 }); const response = await fetch(url, { timeout: 30000 });
if (!response.ok) break; if (!response.ok) {
const body = await response.text().catch(() => '');
throw new Error(`ClientUsers request failed (${response.status}): ${body.slice(0, 180)}`);
}
const json = await response.json(); const json = await response.json();
const users = json.value || []; const rows = Array.isArray(json?.value) ? json.value : [];
allUsers.push(...users); if (odataCount === null && Number.isFinite(Number(json?.['@odata.count']))) {
if (users.length < pageSize) break; odataCount = Number(json['@odata.count']);
}
sourceRows.push(...rows);
if (rows.length < pageSize) break;
skip += pageSize; skip += pageSize;
} }
if (!allUsers.length) return;
const memberships = [];
const seenMemberships = new Set();
for (const raw of sourceRows) {
const parsed = extractClientUserRecord(raw || {});
if (!parsed.userId || !parsed.name || !parsed.groupIds.length) continue;
for (const groupId of parsed.groupIds) {
const dedupeKey = `${parsed.userId}|${groupId}`;
if (seenMemberships.has(dedupeKey)) continue;
seenMemberships.add(dedupeKey);
memberships.push({ userId: parsed.userId, name: parsed.name, groupId });
}
}
const tx = db.transaction(() => { const tx = db.transaction(() => {
stmts.clearClientUsers.run(); stmts.clearClientUsers.run();
for (const u of allUsers) { for (const row of memberships) {
const name = String(u.name || '').trim(); stmts.upsertClientUser.run(row.userId, row.name, row.groupId);
if (!name || !u.id) continue;
for (const g of (u.groups || [])) {
if (g.userGroupId) {
stmts.upsertClientUser.run(u.id, name, g.userGroupId);
}
}
} }
}); });
tx(); tx();
logger.log(`[Refresh] syncClientUsers: stored ${allUsers.length} users`); logger.log(`[Refresh] syncClientUsers: fetched ${sourceRows.length}, stored ${memberships.length}${odataCount !== null ? `, odata.count=${odataCount}` : ''}`);
return { fetched: sourceRows.length, stored: memberships.length, odata_count: odataCount };
} }
let running = false; let running = false;

View file

@ -542,9 +542,17 @@ app.get('/api/client-users', auth, (req, res) => {
app.post('/api/admin/sync-client-users', auth, adminOnly, (req, res) => { app.post('/api/admin/sync-client-users', auth, adminOnly, (req, res) => {
refreshService.syncClientUsers() refreshService.syncClientUsers()
.then(() => { .then((syncMeta = {}) => {
const count = db.prepare('SELECT COUNT(*) as c FROM client_users').get(); const count = db.prepare('SELECT COUNT(*) as c FROM client_users').get();
res.json({ ok: true, count: count.c }); res.json({
ok: true,
count: count.c,
fetched: Number(syncMeta.fetched || 0),
stored: Number(syncMeta.stored || 0),
odata_count: syncMeta.odata_count !== null && syncMeta.odata_count !== undefined
? Number(syncMeta.odata_count)
: null,
});
}) })
.catch((e) => res.status(500).json({ error: e.message })); .catch((e) => res.status(500).json({ error: e.message }));
}); });