📦 EqualifyEverything / equalify

📄 trackSession.ts · 49 lines
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49import { db, event, getAnalytics } from '#src/utils';

// One row per authenticated app load or login. The frontend calls this
// (utils/trackSession.ts) right after the backend has accepted the user's
// token, so every row is a real, authorized session. Feeds the monthly KPIs
// on Account > Statistics (getSystemStats.ts): sessions started, active
// users, and units served.
//
// The SSO login flow also posts the user's org fields from Microsoft Graph
// (/me: department, companyName, officeLocation, jobTitle). Those land on
// the session row and are merged into users.analytics so "units served"
// can be counted per month without a department claim in the ID token.

const ORG_FIELDS = ['department', 'companyName', 'officeLocation', 'jobTitle'] as const;

const cleanString = (value: unknown): string | undefined => {
    if (typeof value !== 'string') return undefined;
    const trimmed = value.trim().slice(0, 200);
    return trimmed.length ? trimmed : undefined;
};

export const trackSession = async () => {
    const { sub } = event.claims;
    // SSO users never carry cognito:username (same check as updateUser.ts)
    const authMethod = event.claims['cognito:username'] ? 'cognito' : 'sso';

    const body = (event.body && typeof event.body === 'object') ? event.body : {};
    const org: Record<string, string> = {};
    for (const field of ORG_FIELDS) {
        const value = cleanString(body[field]);
        if (value) org[field] = value;
    }

    await db.connect();
    await db.query({
        text: `INSERT INTO "sessions" ("user_id", "auth_method", "department", "analytics") VALUES ($1, $2, $3, $4)`,
        values: [sub, authMethod, org.department ?? null, JSON.stringify({ ...getAnalytics(), ...org })],
    });
    if (Object.keys(org).length) {
        await db.query({
            text: `UPDATE "users" SET "analytics" = COALESCE("analytics", '{}'::jsonb) || $1::jsonb WHERE "id" = $2`,
            values: [JSON.stringify(org), sub],
        });
    }
    await db.clean();

    return { success: true };
}