@commonpub/server 2.81.0 → 2.82.0
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.
- package/dist/publicApi/index.d.ts +2 -0
- package/dist/publicApi/index.d.ts.map +1 -1
- package/dist/publicApi/index.js +1 -0
- package/dist/publicApi/index.js.map +1 -1
- package/dist/publicApi/metricsRollup.d.ts +55 -0
- package/dist/publicApi/metricsRollup.d.ts.map +1 -0
- package/dist/publicApi/metricsRollup.js +158 -0
- package/dist/publicApi/metricsRollup.js.map +1 -0
- package/package.json +6 -6
|
@@ -14,6 +14,8 @@ export { getApiKeyUsageStats } from './usage.js';
|
|
|
14
14
|
export type { ApiKeyUsageStats } from './usage.js';
|
|
15
15
|
export { METRICS_MIN_BUCKET, getMetricsOverview, getTopContent, getTrendingTags, getTopContributors, getEngagementMetrics, getFederationReach, } from './metrics.js';
|
|
16
16
|
export type { MetricsOverview, ContentMetric, MetricsTopContributor, MetricsEngagement, MetricsFederationReach, } from './metrics.js';
|
|
17
|
+
export { TIMESERIES_METRICS, runDailyRollup, backfillMetricsDaily, getMetricsTimeseries, } from './metricsRollup.js';
|
|
18
|
+
export type { MetricKind, TimeseriesInterval, TimeseriesPoint, MetricsTimeseries, } from './metricsRollup.js';
|
|
17
19
|
export { toPublicUser, isPublicUser, toPublicContentSummary, toPublicContentDetail, isPublicContent, toPublicHub, isPublicHub, toAdminApiKeyView, toPublicLearningPath, isPublicLearningPath, toPublicEvent, isPublicEvent, toPublicContest, isPublicContest, toPublicVideo, isPublicVideo, toPublicDocSite, isPublicDocSite, toPublicTag, } from './serializers.js';
|
|
18
20
|
export type { PublicUser, PublicUserRow, PublicContentSummary, PublicContentDetail, PublicContentRow, PublicHub, PublicHubRow, PublicInstance, AdminApiKeyView, PublicLearningPath, PublicLearningPathRow, PublicEvent, PublicEventRow, PublicContest, PublicContestRow, PublicVideo, PublicVideoRow, PublicDocSite, PublicDocSiteRow, PublicTag, PublicTagRow, } from './serializers.js';
|
|
19
21
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/publicApi/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAC1D,OAAO,EAAE,cAAc,EAAE,UAAU,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AACtF,YAAY,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AAC9C,YAAY,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAClE,YAAY,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AACtD,OAAO,EAAE,WAAW,EAAE,oBAAoB,EAAE,kBAAkB,EAAE,MAAM,WAAW,CAAC;AAClF,YAAY,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AAC9C,OAAO,EAAE,kBAAkB,EAAE,MAAM,WAAW,CAAC;AAC/C,YAAY,EAAE,UAAU,EAAE,WAAW,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AACpF,OAAO,EACL,YAAY,EACZ,WAAW,EACX,YAAY,EACZ,aAAa,EACb,cAAc,EACd,aAAa,GACd,MAAM,eAAe,CAAC;AACvB,YAAY,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AACxD,OAAO,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AACjD,YAAY,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AACnD,OAAO,EACL,kBAAkB,EAClB,kBAAkB,EAClB,aAAa,EACb,eAAe,EACf,kBAAkB,EAClB,oBAAoB,EACpB,kBAAkB,GACnB,MAAM,cAAc,CAAC;AACtB,YAAY,EACV,eAAe,EACf,aAAa,EACb,qBAAqB,EACrB,iBAAiB,EACjB,sBAAsB,GACvB,MAAM,cAAc,CAAC;AACtB,OAAO,EACL,YAAY,EACZ,YAAY,EACZ,sBAAsB,EACtB,qBAAqB,EACrB,eAAe,EACf,WAAW,EACX,WAAW,EACX,iBAAiB,EACjB,oBAAoB,EACpB,oBAAoB,EACpB,aAAa,EACb,aAAa,EACb,eAAe,EACf,eAAe,EACf,aAAa,EACb,aAAa,EACb,eAAe,EACf,eAAe,EACf,WAAW,GACZ,MAAM,kBAAkB,CAAC;AAC1B,YAAY,EACV,UAAU,EACV,aAAa,EACb,oBAAoB,EACpB,mBAAmB,EACnB,gBAAgB,EAChB,SAAS,EACT,YAAY,EACZ,cAAc,EACd,eAAe,EACf,kBAAkB,EAClB,qBAAqB,EACrB,WAAW,EACX,cAAc,EACd,aAAa,EACb,gBAAgB,EAChB,WAAW,EACX,cAAc,EACd,aAAa,EACb,gBAAgB,EAChB,SAAS,EACT,YAAY,GACb,MAAM,kBAAkB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/publicApi/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAC1D,OAAO,EAAE,cAAc,EAAE,UAAU,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AACtF,YAAY,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AAC9C,YAAY,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAClE,YAAY,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AACtD,OAAO,EAAE,WAAW,EAAE,oBAAoB,EAAE,kBAAkB,EAAE,MAAM,WAAW,CAAC;AAClF,YAAY,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AAC9C,OAAO,EAAE,kBAAkB,EAAE,MAAM,WAAW,CAAC;AAC/C,YAAY,EAAE,UAAU,EAAE,WAAW,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AACpF,OAAO,EACL,YAAY,EACZ,WAAW,EACX,YAAY,EACZ,aAAa,EACb,cAAc,EACd,aAAa,GACd,MAAM,eAAe,CAAC;AACvB,YAAY,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AACxD,OAAO,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AACjD,YAAY,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AACnD,OAAO,EACL,kBAAkB,EAClB,kBAAkB,EAClB,aAAa,EACb,eAAe,EACf,kBAAkB,EAClB,oBAAoB,EACpB,kBAAkB,GACnB,MAAM,cAAc,CAAC;AACtB,YAAY,EACV,eAAe,EACf,aAAa,EACb,qBAAqB,EACrB,iBAAiB,EACjB,sBAAsB,GACvB,MAAM,cAAc,CAAC;AACtB,OAAO,EACL,kBAAkB,EAClB,cAAc,EACd,oBAAoB,EACpB,oBAAoB,GACrB,MAAM,oBAAoB,CAAC;AAC5B,YAAY,EACV,UAAU,EACV,kBAAkB,EAClB,eAAe,EACf,iBAAiB,GAClB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EACL,YAAY,EACZ,YAAY,EACZ,sBAAsB,EACtB,qBAAqB,EACrB,eAAe,EACf,WAAW,EACX,WAAW,EACX,iBAAiB,EACjB,oBAAoB,EACpB,oBAAoB,EACpB,aAAa,EACb,aAAa,EACb,eAAe,EACf,eAAe,EACf,aAAa,EACb,aAAa,EACb,eAAe,EACf,eAAe,EACf,WAAW,GACZ,MAAM,kBAAkB,CAAC;AAC1B,YAAY,EACV,UAAU,EACV,aAAa,EACb,oBAAoB,EACpB,mBAAmB,EACnB,gBAAgB,EAChB,SAAS,EACT,YAAY,EACZ,cAAc,EACd,eAAe,EACf,kBAAkB,EAClB,qBAAqB,EACrB,WAAW,EACX,cAAc,EACd,aAAa,EACb,gBAAgB,EAChB,WAAW,EACX,cAAc,EACd,aAAa,EACb,gBAAgB,EAChB,SAAS,EACT,YAAY,GACb,MAAM,kBAAkB,CAAC"}
|
package/dist/publicApi/index.js
CHANGED
|
@@ -6,5 +6,6 @@ export { authenticateApiKey } from './auth.js';
|
|
|
6
6
|
export { createApiKey, listApiKeys, revokeApiKey, getApiKeyById, logApiKeyUsage, touchLastUsed, } from './adminOps.js';
|
|
7
7
|
export { getApiKeyUsageStats } from './usage.js';
|
|
8
8
|
export { METRICS_MIN_BUCKET, getMetricsOverview, getTopContent, getTrendingTags, getTopContributors, getEngagementMetrics, getFederationReach, } from './metrics.js';
|
|
9
|
+
export { TIMESERIES_METRICS, runDailyRollup, backfillMetricsDaily, getMetricsTimeseries, } from './metricsRollup.js';
|
|
9
10
|
export { toPublicUser, isPublicUser, toPublicContentSummary, toPublicContentDetail, isPublicContent, toPublicHub, isPublicHub, toAdminApiKeyView, toPublicLearningPath, isPublicLearningPath, toPublicEvent, isPublicEvent, toPublicContest, isPublicContest, toPublicVideo, isPublicVideo, toPublicDocSite, isPublicDocSite, toPublicTag, } from './serializers.js';
|
|
10
11
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/publicApi/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAC1D,OAAO,EAAE,cAAc,EAAE,UAAU,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAGtF,OAAO,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAElE,OAAO,EAAE,WAAW,EAAE,oBAAoB,EAAE,kBAAkB,EAAE,MAAM,WAAW,CAAC;AAElF,OAAO,EAAE,kBAAkB,EAAE,MAAM,WAAW,CAAC;AAE/C,OAAO,EACL,YAAY,EACZ,WAAW,EACX,YAAY,EACZ,aAAa,EACb,cAAc,EACd,aAAa,GACd,MAAM,eAAe,CAAC;AAEvB,OAAO,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AAEjD,OAAO,EACL,kBAAkB,EAClB,kBAAkB,EAClB,aAAa,EACb,eAAe,EACf,kBAAkB,EAClB,oBAAoB,EACpB,kBAAkB,GACnB,MAAM,cAAc,CAAC;AAQtB,OAAO,EACL,YAAY,EACZ,YAAY,EACZ,sBAAsB,EACtB,qBAAqB,EACrB,eAAe,EACf,WAAW,EACX,WAAW,EACX,iBAAiB,EACjB,oBAAoB,EACpB,oBAAoB,EACpB,aAAa,EACb,aAAa,EACb,eAAe,EACf,eAAe,EACf,aAAa,EACb,aAAa,EACb,eAAe,EACf,eAAe,EACf,WAAW,GACZ,MAAM,kBAAkB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/publicApi/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAC1D,OAAO,EAAE,cAAc,EAAE,UAAU,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAGtF,OAAO,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAElE,OAAO,EAAE,WAAW,EAAE,oBAAoB,EAAE,kBAAkB,EAAE,MAAM,WAAW,CAAC;AAElF,OAAO,EAAE,kBAAkB,EAAE,MAAM,WAAW,CAAC;AAE/C,OAAO,EACL,YAAY,EACZ,WAAW,EACX,YAAY,EACZ,aAAa,EACb,cAAc,EACd,aAAa,GACd,MAAM,eAAe,CAAC;AAEvB,OAAO,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AAEjD,OAAO,EACL,kBAAkB,EAClB,kBAAkB,EAClB,aAAa,EACb,eAAe,EACf,kBAAkB,EAClB,oBAAoB,EACpB,kBAAkB,GACnB,MAAM,cAAc,CAAC;AAQtB,OAAO,EACL,kBAAkB,EAClB,cAAc,EACd,oBAAoB,EACpB,oBAAoB,GACrB,MAAM,oBAAoB,CAAC;AAO5B,OAAO,EACL,YAAY,EACZ,YAAY,EACZ,sBAAsB,EACtB,qBAAqB,EACrB,eAAe,EACf,WAAW,EACX,WAAW,EACX,iBAAiB,EACjB,oBAAoB,EACpB,oBAAoB,EACpB,aAAa,EACb,aAAa,EACb,eAAe,EACf,eAAe,EACf,aAAa,EACb,aAAa,EACb,eAAe,EACf,eAAe,EACf,WAAW,GACZ,MAAM,kBAAkB,CAAC"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import type { DB } from '../types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Daily analytics rollups (Phase 3). Aggregate snapshots only — no per-user
|
|
4
|
+
* data, no new PII. Written by the `metrics-rollup` Nitro plugin into
|
|
5
|
+
* `metrics_daily`; read by `getMetricsTimeseries`.
|
|
6
|
+
*
|
|
7
|
+
* Two metric kinds:
|
|
8
|
+
* - `flow` (e.g. `users.new`): that day's count. Deterministic from timestamps,
|
|
9
|
+
* so fully backfillable and idempotent on re-run.
|
|
10
|
+
* - `cumulative` (e.g. `users.total`, `content.views`): running total as of a
|
|
11
|
+
* day. Count-based cumulatives are backfilled as the survivorship curve
|
|
12
|
+
* (currently-live rows by their creation/publish date). Sum-based engagement
|
|
13
|
+
* cumulatives (views/likes/comments) have no per-day history, so they are
|
|
14
|
+
* snapshot forward from the first rollup only (never backfilled).
|
|
15
|
+
*/
|
|
16
|
+
export type MetricKind = 'flow' | 'cumulative';
|
|
17
|
+
/** Every metric the timeseries endpoint will serve, with its aggregation kind. */
|
|
18
|
+
export declare const TIMESERIES_METRICS: Record<string, MetricKind>;
|
|
19
|
+
/**
|
|
20
|
+
* Snapshot today's metrics. Count-based + flow metrics are deterministic
|
|
21
|
+
* (`users.total` = currently-live users; `users.new` = created today). Sum-based
|
|
22
|
+
* engagement cumulatives capture the current running total under `today`.
|
|
23
|
+
* Idempotent: re-running for the same day overwrites that day's rows.
|
|
24
|
+
*/
|
|
25
|
+
export declare function runDailyRollup(db: DB, today: string): Promise<void>;
|
|
26
|
+
/**
|
|
27
|
+
* Backfill the deterministic count-based series for ALL of history from
|
|
28
|
+
* timestamps: `users.new`/`content.new` (daily flow) and `users.total`/
|
|
29
|
+
* `content.total` (cumulative window-sum = survivorship curve). Sum-based
|
|
30
|
+
* engagement metrics are NOT backfilled (no per-day history exists). Idempotent.
|
|
31
|
+
* Returns the number of rows written.
|
|
32
|
+
*/
|
|
33
|
+
export declare function backfillMetricsDaily(db: DB): Promise<number>;
|
|
34
|
+
export type TimeseriesInterval = 'day' | 'week' | 'month';
|
|
35
|
+
export interface TimeseriesPoint {
|
|
36
|
+
date: string;
|
|
37
|
+
value: number;
|
|
38
|
+
delta: number;
|
|
39
|
+
}
|
|
40
|
+
export interface MetricsTimeseries {
|
|
41
|
+
metric: string;
|
|
42
|
+
kind: MetricKind;
|
|
43
|
+
interval: TimeseriesInterval;
|
|
44
|
+
from: string;
|
|
45
|
+
to: string;
|
|
46
|
+
since: string | null;
|
|
47
|
+
points: TimeseriesPoint[];
|
|
48
|
+
}
|
|
49
|
+
export declare function getMetricsTimeseries(db: DB, opts: {
|
|
50
|
+
metric: string;
|
|
51
|
+
interval: TimeseriesInterval;
|
|
52
|
+
from: string;
|
|
53
|
+
to: string;
|
|
54
|
+
}): Promise<MetricsTimeseries>;
|
|
55
|
+
//# sourceMappingURL=metricsRollup.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"metricsRollup.d.ts","sourceRoot":"","sources":["../../src/publicApi/metricsRollup.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,aAAa,CAAC;AAEtC;;;;;;;;;;;;;GAaG;AAEH,MAAM,MAAM,UAAU,GAAG,MAAM,GAAG,YAAY,CAAC;AAE/C,kFAAkF;AAClF,eAAO,MAAM,kBAAkB,EAAE,MAAM,CAAC,MAAM,EAAE,UAAU,CAQzD,CAAC;AA6BF;;;;;GAKG;AACH,wBAAsB,cAAc,CAAC,EAAE,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CA8BzE;AAED;;;;;;GAMG;AACH,wBAAsB,oBAAoB,CAAC,EAAE,EAAE,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAmClE;AAID,MAAM,MAAM,kBAAkB,GAAG,KAAK,GAAG,MAAM,GAAG,OAAO,CAAC;AAE1D,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,UAAU,CAAC;IACjB,QAAQ,EAAE,kBAAkB,CAAC;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,MAAM,EAAE,eAAe,EAAE,CAAC;CAC3B;AAaD,wBAAsB,oBAAoB,CACxC,EAAE,EAAE,EAAE,EACN,IAAI,EAAE;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,kBAAkB,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,MAAM,CAAA;CAAE,GAC/E,OAAO,CAAC,iBAAiB,CAAC,CA4C5B"}
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import { metricsDaily, contentItems, users } from '@commonpub/schema';
|
|
2
|
+
import { and, asc, eq, gte, isNull, lte, sql } from 'drizzle-orm';
|
|
3
|
+
/** Every metric the timeseries endpoint will serve, with its aggregation kind. */
|
|
4
|
+
export const TIMESERIES_METRICS = {
|
|
5
|
+
'users.total': 'cumulative',
|
|
6
|
+
'users.new': 'flow',
|
|
7
|
+
'content.total': 'cumulative',
|
|
8
|
+
'content.new': 'flow',
|
|
9
|
+
'content.views': 'cumulative',
|
|
10
|
+
'content.likes': 'cumulative',
|
|
11
|
+
'content.comments': 'cumulative',
|
|
12
|
+
};
|
|
13
|
+
const DIM = ''; // global series sentinel (see metrics_daily schema note)
|
|
14
|
+
function activeUsersWhere() {
|
|
15
|
+
return and(isNull(users.deletedAt), eq(users.status, 'active'));
|
|
16
|
+
}
|
|
17
|
+
function publicContentWhere() {
|
|
18
|
+
return and(eq(contentItems.status, 'published'), eq(contentItems.visibility, 'public'), isNull(contentItems.deletedAt));
|
|
19
|
+
}
|
|
20
|
+
async function upsertRows(db, rows) {
|
|
21
|
+
if (rows.length === 0)
|
|
22
|
+
return;
|
|
23
|
+
await db
|
|
24
|
+
.insert(metricsDaily)
|
|
25
|
+
.values(rows.map((r) => ({ day: r.day, metric: r.metric, dimension: DIM, value: r.value })))
|
|
26
|
+
.onConflictDoUpdate({
|
|
27
|
+
target: [metricsDaily.day, metricsDaily.metric, metricsDaily.dimension],
|
|
28
|
+
set: { value: sql `excluded.value` },
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Snapshot today's metrics. Count-based + flow metrics are deterministic
|
|
33
|
+
* (`users.total` = currently-live users; `users.new` = created today). Sum-based
|
|
34
|
+
* engagement cumulatives capture the current running total under `today`.
|
|
35
|
+
* Idempotent: re-running for the same day overwrites that day's rows.
|
|
36
|
+
*/
|
|
37
|
+
export async function runDailyRollup(db, today) {
|
|
38
|
+
const [[u], [c]] = await Promise.all([
|
|
39
|
+
db
|
|
40
|
+
.select({
|
|
41
|
+
total: sql `count(*)::int`,
|
|
42
|
+
newToday: sql `count(*) FILTER (WHERE ${users.createdAt}::date = ${today})::int`,
|
|
43
|
+
})
|
|
44
|
+
.from(users)
|
|
45
|
+
.where(activeUsersWhere()),
|
|
46
|
+
db
|
|
47
|
+
.select({
|
|
48
|
+
total: sql `count(*)::int`,
|
|
49
|
+
newToday: sql `count(*) FILTER (WHERE ${contentItems.publishedAt}::date = ${today})::int`,
|
|
50
|
+
views: sql `coalesce(sum(${contentItems.viewCount}), 0)::float8`,
|
|
51
|
+
likes: sql `coalesce(sum(${contentItems.likeCount}), 0)::float8`,
|
|
52
|
+
comments: sql `coalesce(sum(${contentItems.commentCount}), 0)::float8`,
|
|
53
|
+
})
|
|
54
|
+
.from(contentItems)
|
|
55
|
+
.where(publicContentWhere()),
|
|
56
|
+
]);
|
|
57
|
+
await upsertRows(db, [
|
|
58
|
+
{ day: today, metric: 'users.total', value: u?.total ?? 0 },
|
|
59
|
+
{ day: today, metric: 'users.new', value: u?.newToday ?? 0 },
|
|
60
|
+
{ day: today, metric: 'content.total', value: c?.total ?? 0 },
|
|
61
|
+
{ day: today, metric: 'content.new', value: c?.newToday ?? 0 },
|
|
62
|
+
{ day: today, metric: 'content.views', value: c?.views ?? 0 },
|
|
63
|
+
{ day: today, metric: 'content.likes', value: c?.likes ?? 0 },
|
|
64
|
+
{ day: today, metric: 'content.comments', value: c?.comments ?? 0 },
|
|
65
|
+
]);
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Backfill the deterministic count-based series for ALL of history from
|
|
69
|
+
* timestamps: `users.new`/`content.new` (daily flow) and `users.total`/
|
|
70
|
+
* `content.total` (cumulative window-sum = survivorship curve). Sum-based
|
|
71
|
+
* engagement metrics are NOT backfilled (no per-day history exists). Idempotent.
|
|
72
|
+
* Returns the number of rows written.
|
|
73
|
+
*/
|
|
74
|
+
export async function backfillMetricsDaily(db) {
|
|
75
|
+
const [userDays, contentDays] = await Promise.all([
|
|
76
|
+
db
|
|
77
|
+
.select({
|
|
78
|
+
day: sql `to_char(${users.createdAt}::date, 'YYYY-MM-DD')`,
|
|
79
|
+
flow: sql `count(*)::int`,
|
|
80
|
+
cumulative: sql `sum(count(*)) OVER (ORDER BY ${users.createdAt}::date)::int`,
|
|
81
|
+
})
|
|
82
|
+
.from(users)
|
|
83
|
+
.where(activeUsersWhere())
|
|
84
|
+
.groupBy(sql `${users.createdAt}::date`)
|
|
85
|
+
.orderBy(sql `${users.createdAt}::date`),
|
|
86
|
+
db
|
|
87
|
+
.select({
|
|
88
|
+
day: sql `to_char(${contentItems.publishedAt}::date, 'YYYY-MM-DD')`,
|
|
89
|
+
flow: sql `count(*)::int`,
|
|
90
|
+
cumulative: sql `sum(count(*)) OVER (ORDER BY ${contentItems.publishedAt}::date)::int`,
|
|
91
|
+
})
|
|
92
|
+
.from(contentItems)
|
|
93
|
+
.where(and(publicContentWhere(), sql `${contentItems.publishedAt} IS NOT NULL`))
|
|
94
|
+
.groupBy(sql `${contentItems.publishedAt}::date`)
|
|
95
|
+
.orderBy(sql `${contentItems.publishedAt}::date`),
|
|
96
|
+
]);
|
|
97
|
+
const rows = [];
|
|
98
|
+
for (const r of userDays) {
|
|
99
|
+
rows.push({ day: r.day, metric: 'users.new', value: r.flow });
|
|
100
|
+
rows.push({ day: r.day, metric: 'users.total', value: r.cumulative });
|
|
101
|
+
}
|
|
102
|
+
for (const r of contentDays) {
|
|
103
|
+
rows.push({ day: r.day, metric: 'content.new', value: r.flow });
|
|
104
|
+
rows.push({ day: r.day, metric: 'content.total', value: r.cumulative });
|
|
105
|
+
}
|
|
106
|
+
await upsertRows(db, rows);
|
|
107
|
+
return rows.length;
|
|
108
|
+
}
|
|
109
|
+
/** UTC bucket-start key for a YYYY-MM-DD day under the given interval. */
|
|
110
|
+
function bucketKey(day, interval) {
|
|
111
|
+
if (interval === 'day')
|
|
112
|
+
return day;
|
|
113
|
+
if (interval === 'month')
|
|
114
|
+
return `${day.slice(0, 7)}-01`;
|
|
115
|
+
// week: Monday of the ISO week (UTC).
|
|
116
|
+
const d = new Date(`${day}T00:00:00Z`);
|
|
117
|
+
const dow = (d.getUTCDay() + 6) % 7; // 0 = Monday
|
|
118
|
+
d.setUTCDate(d.getUTCDate() - dow);
|
|
119
|
+
return d.toISOString().slice(0, 10);
|
|
120
|
+
}
|
|
121
|
+
export async function getMetricsTimeseries(db, opts) {
|
|
122
|
+
const kind = TIMESERIES_METRICS[opts.metric];
|
|
123
|
+
if (!kind)
|
|
124
|
+
throw new Error(`Unknown metric: ${opts.metric}`);
|
|
125
|
+
const rows = await db
|
|
126
|
+
.select({ day: metricsDaily.day, value: metricsDaily.value })
|
|
127
|
+
.from(metricsDaily)
|
|
128
|
+
.where(and(eq(metricsDaily.metric, opts.metric), eq(metricsDaily.dimension, DIM), gte(metricsDaily.day, opts.from), lte(metricsDaily.day, opts.to)))
|
|
129
|
+
.orderBy(asc(metricsDaily.day));
|
|
130
|
+
// Bucket in JS (range is bounded by the caller). flow -> sum within bucket;
|
|
131
|
+
// cumulative -> last value within bucket (the running total at bucket end).
|
|
132
|
+
const buckets = new Map();
|
|
133
|
+
for (const r of rows) {
|
|
134
|
+
const key = bucketKey(r.day, opts.interval);
|
|
135
|
+
const prev = buckets.get(key);
|
|
136
|
+
if (kind === 'flow')
|
|
137
|
+
buckets.set(key, (prev ?? 0) + r.value);
|
|
138
|
+
else
|
|
139
|
+
buckets.set(key, r.value); // ordered asc, so last write wins = bucket-end value
|
|
140
|
+
}
|
|
141
|
+
const ordered = [...buckets.entries()].sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0));
|
|
142
|
+
const points = [];
|
|
143
|
+
let prevValue = null;
|
|
144
|
+
for (const [date, value] of ordered) {
|
|
145
|
+
points.push({ date, value, delta: prevValue === null ? 0 : value - prevValue });
|
|
146
|
+
prevValue = value;
|
|
147
|
+
}
|
|
148
|
+
return {
|
|
149
|
+
metric: opts.metric,
|
|
150
|
+
kind,
|
|
151
|
+
interval: opts.interval,
|
|
152
|
+
from: opts.from,
|
|
153
|
+
to: opts.to,
|
|
154
|
+
since: rows[0]?.day ?? null,
|
|
155
|
+
points,
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
//# sourceMappingURL=metricsRollup.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"metricsRollup.js","sourceRoot":"","sources":["../../src/publicApi/metricsRollup.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@commonpub/server",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.82.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Framework-agnostic business logic for CommonPub instances",
|
|
6
6
|
"license": "AGPL-3.0-or-later",
|
|
@@ -115,13 +115,13 @@
|
|
|
115
115
|
"megalodon": "^10.3.0",
|
|
116
116
|
"turndown": "^7.2.4",
|
|
117
117
|
"@commonpub/auth": "0.8.0",
|
|
118
|
+
"@commonpub/config": "0.19.0",
|
|
119
|
+
"@commonpub/learning": "0.5.2",
|
|
118
120
|
"@commonpub/protocol": "0.13.0",
|
|
119
|
-
"@commonpub/
|
|
120
|
-
"@commonpub/docs": "0.6.3",
|
|
121
|
-
"@commonpub/schema": "0.34.0",
|
|
121
|
+
"@commonpub/schema": "0.35.0",
|
|
122
122
|
"@commonpub/infra": "0.8.0",
|
|
123
|
-
"@commonpub/
|
|
124
|
-
"@commonpub/
|
|
123
|
+
"@commonpub/docs": "0.6.3",
|
|
124
|
+
"@commonpub/editor": "0.7.11"
|
|
125
125
|
},
|
|
126
126
|
"peerDependencies": {
|
|
127
127
|
"drizzle-orm": "^0.45.1"
|