diff --git a/refresh-service.js b/refresh-service.js index dc5ea93..940a398 100644 --- a/refresh-service.js +++ b/refresh-service.js @@ -608,39 +608,92 @@ function createRefreshService({ db, fetch, getSettings, sseBroadcast, logger = c 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() { const s = getSettings(); - if (!s.api_key) return; - const pageSize = 100; + if (!s.api_key) return { fetched: 0, stored: 0, odata_count: null }; + const pageSize = 400; let skip = 0; - const allUsers = []; + let odataCount = null; + const sourceRows = []; 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)')}` + `&$top=${pageSize}&$skip=${skip}`; 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 users = json.value || []; - allUsers.push(...users); - if (users.length < pageSize) break; + const rows = Array.isArray(json?.value) ? json.value : []; + if (odataCount === null && Number.isFinite(Number(json?.['@odata.count']))) { + odataCount = Number(json['@odata.count']); + } + sourceRows.push(...rows); + if (rows.length < pageSize) break; 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(() => { stmts.clearClientUsers.run(); - for (const u of allUsers) { - const name = String(u.name || '').trim(); - if (!name || !u.id) continue; - for (const g of (u.groups || [])) { - if (g.userGroupId) { - stmts.upsertClientUser.run(u.id, name, g.userGroupId); - } - } + for (const row of memberships) { + stmts.upsertClientUser.run(row.userId, row.name, row.groupId); } }); 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; diff --git a/server.js b/server.js index 0f7d375..d5c3695 100644 --- a/server.js +++ b/server.js @@ -542,9 +542,17 @@ app.get('/api/client-users', auth, (req, res) => { app.post('/api/admin/sync-client-users', auth, adminOnly, (req, res) => { refreshService.syncClientUsers() - .then(() => { + .then((syncMeta = {}) => { 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 })); });