@seneris/nosework 0.1.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/README.md +535 -0
- package/dist/client/errors.d.ts +49 -0
- package/dist/client/errors.d.ts.map +1 -0
- package/dist/client/errors.js +173 -0
- package/dist/client/errors.js.map +1 -0
- package/dist/client.d.ts +7 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +29 -0
- package/dist/client.js.map +1 -0
- package/dist/error.d.ts +40 -0
- package/dist/error.d.ts.map +1 -0
- package/dist/error.js +248 -0
- package/dist/error.js.map +1 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +16 -0
- package/dist/index.js.map +1 -0
- package/dist/query.d.ts +48 -0
- package/dist/query.d.ts.map +1 -0
- package/dist/query.js +471 -0
- package/dist/query.js.map +1 -0
- package/dist/track.d.ts +4 -0
- package/dist/track.d.ts.map +1 -0
- package/dist/track.js +74 -0
- package/dist/track.js.map +1 -0
- package/dist/types.d.ts +157 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/ua.d.ts +3 -0
- package/dist/ua.d.ts.map +1 -0
- package/dist/ua.js +40 -0
- package/dist/ua.js.map +1 -0
- package/dist/utils.d.ts +6 -0
- package/dist/utils.d.ts.map +1 -0
- package/dist/utils.js +112 -0
- package/dist/utils.js.map +1 -0
- package/package.json +54 -0
- package/prisma/schema.prisma +145 -0
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
export interface TrackPageViewOptions {
|
|
2
|
+
siteId: string;
|
|
3
|
+
url: string;
|
|
4
|
+
referrer?: string | null;
|
|
5
|
+
ip?: string | null;
|
|
6
|
+
userAgent?: string | null;
|
|
7
|
+
userId?: string | null;
|
|
8
|
+
country?: string | null;
|
|
9
|
+
countryCode?: string | null;
|
|
10
|
+
region?: string | null;
|
|
11
|
+
city?: string | null;
|
|
12
|
+
}
|
|
13
|
+
export interface TrackEventOptions {
|
|
14
|
+
siteId: string;
|
|
15
|
+
name: string;
|
|
16
|
+
properties?: Record<string, unknown>;
|
|
17
|
+
url?: string | null;
|
|
18
|
+
ip?: string | null;
|
|
19
|
+
userAgent?: string | null;
|
|
20
|
+
userId?: string | null;
|
|
21
|
+
}
|
|
22
|
+
export interface DateRange {
|
|
23
|
+
startDate: Date;
|
|
24
|
+
endDate: Date;
|
|
25
|
+
}
|
|
26
|
+
export interface QueryOptions extends DateRange {
|
|
27
|
+
siteId: string;
|
|
28
|
+
}
|
|
29
|
+
export interface PaginatedQueryOptions extends QueryOptions {
|
|
30
|
+
limit?: number;
|
|
31
|
+
offset?: number;
|
|
32
|
+
}
|
|
33
|
+
export interface Stats {
|
|
34
|
+
pageViews: number;
|
|
35
|
+
visitors: number;
|
|
36
|
+
sessions: number;
|
|
37
|
+
bounceRate: number;
|
|
38
|
+
}
|
|
39
|
+
export interface TopPage {
|
|
40
|
+
pathname: string;
|
|
41
|
+
pageViews: number;
|
|
42
|
+
visitors: number;
|
|
43
|
+
}
|
|
44
|
+
export interface LocationData {
|
|
45
|
+
country: string | null;
|
|
46
|
+
countryCode: string | null;
|
|
47
|
+
city: string | null;
|
|
48
|
+
pageViews: number;
|
|
49
|
+
visitors: number;
|
|
50
|
+
}
|
|
51
|
+
export interface ReferrerData {
|
|
52
|
+
referrer: string | null;
|
|
53
|
+
pageViews: number;
|
|
54
|
+
visitors: number;
|
|
55
|
+
}
|
|
56
|
+
export interface DeviceData {
|
|
57
|
+
device: string | null;
|
|
58
|
+
browser: string | null;
|
|
59
|
+
os: string | null;
|
|
60
|
+
pageViews: number;
|
|
61
|
+
visitors: number;
|
|
62
|
+
}
|
|
63
|
+
export interface GeoLocation {
|
|
64
|
+
country: string | null;
|
|
65
|
+
countryCode: string | null;
|
|
66
|
+
region: string | null;
|
|
67
|
+
city: string | null;
|
|
68
|
+
}
|
|
69
|
+
export interface ParsedUserAgent {
|
|
70
|
+
browser: string | null;
|
|
71
|
+
browserVer: string | null;
|
|
72
|
+
os: string | null;
|
|
73
|
+
osVer: string | null;
|
|
74
|
+
device: "desktop" | "mobile" | "tablet" | null;
|
|
75
|
+
}
|
|
76
|
+
export interface VisitorInfo {
|
|
77
|
+
visitorHash: string;
|
|
78
|
+
sessionId: string;
|
|
79
|
+
}
|
|
80
|
+
export interface SessionStats {
|
|
81
|
+
totalSessions: number;
|
|
82
|
+
avgDuration: number;
|
|
83
|
+
avgPagesPerSession: number;
|
|
84
|
+
bounceRate: number;
|
|
85
|
+
}
|
|
86
|
+
export interface SessionData {
|
|
87
|
+
sessionId: string;
|
|
88
|
+
visitorHash: string;
|
|
89
|
+
startTime: Date;
|
|
90
|
+
endTime: Date;
|
|
91
|
+
duration: number;
|
|
92
|
+
pageCount: number;
|
|
93
|
+
entryPage: string;
|
|
94
|
+
exitPage: string;
|
|
95
|
+
isBounce: boolean;
|
|
96
|
+
country: string | null;
|
|
97
|
+
device: string | null;
|
|
98
|
+
browser: string | null;
|
|
99
|
+
}
|
|
100
|
+
export interface EntryExitPage {
|
|
101
|
+
pathname: string;
|
|
102
|
+
count: number;
|
|
103
|
+
percentage: number;
|
|
104
|
+
}
|
|
105
|
+
export interface PageFlow {
|
|
106
|
+
path: string[];
|
|
107
|
+
count: number;
|
|
108
|
+
percentage: number;
|
|
109
|
+
}
|
|
110
|
+
export interface TrackErrorOptions {
|
|
111
|
+
siteId: string;
|
|
112
|
+
message: string;
|
|
113
|
+
stack?: string | null;
|
|
114
|
+
url: string;
|
|
115
|
+
visitorHash?: string | null;
|
|
116
|
+
sessionId?: string | null;
|
|
117
|
+
userId?: string | null;
|
|
118
|
+
browser?: string | null;
|
|
119
|
+
browserVer?: string | null;
|
|
120
|
+
os?: string | null;
|
|
121
|
+
device?: string | null;
|
|
122
|
+
metadata?: Record<string, unknown> | null;
|
|
123
|
+
}
|
|
124
|
+
export interface ErrorStats {
|
|
125
|
+
totalErrors: number;
|
|
126
|
+
uniqueErrors: number;
|
|
127
|
+
errorsToday: number;
|
|
128
|
+
openGroups: number;
|
|
129
|
+
}
|
|
130
|
+
export type ErrorGroupStatus = "open" | "resolved" | "ignored";
|
|
131
|
+
export interface ErrorGroupData {
|
|
132
|
+
id: string;
|
|
133
|
+
fingerprint: string;
|
|
134
|
+
message: string;
|
|
135
|
+
stack: string | null;
|
|
136
|
+
count: number;
|
|
137
|
+
firstSeen: Date;
|
|
138
|
+
lastSeen: Date;
|
|
139
|
+
status: ErrorGroupStatus;
|
|
140
|
+
}
|
|
141
|
+
export interface ErrorInstance {
|
|
142
|
+
id: string;
|
|
143
|
+
message: string;
|
|
144
|
+
stack: string | null;
|
|
145
|
+
url: string;
|
|
146
|
+
pathname: string;
|
|
147
|
+
visitorHash: string | null;
|
|
148
|
+
sessionId: string | null;
|
|
149
|
+
userId: string | null;
|
|
150
|
+
browser: string | null;
|
|
151
|
+
browserVer: string | null;
|
|
152
|
+
os: string | null;
|
|
153
|
+
device: string | null;
|
|
154
|
+
metadata: Record<string, unknown> | null;
|
|
155
|
+
timestamp: Date;
|
|
156
|
+
}
|
|
157
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
|
package/dist/ua.d.ts
ADDED
package/dist/ua.d.ts.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ua.d.ts","sourceRoot":"","sources":["../src/ua.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAElD,wBAAgB,cAAc,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,GAAG,eAAe,CAsCxE"}
|
package/dist/ua.js
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { UAParser } from "ua-parser-js";
|
|
2
|
+
export function parseUserAgent(userAgent) {
|
|
3
|
+
const emptyResult = {
|
|
4
|
+
browser: null,
|
|
5
|
+
browserVer: null,
|
|
6
|
+
os: null,
|
|
7
|
+
osVer: null,
|
|
8
|
+
device: null,
|
|
9
|
+
};
|
|
10
|
+
if (!userAgent)
|
|
11
|
+
return emptyResult;
|
|
12
|
+
try {
|
|
13
|
+
const parser = new UAParser(userAgent);
|
|
14
|
+
const result = parser.getResult();
|
|
15
|
+
// Determine device type
|
|
16
|
+
let device = null;
|
|
17
|
+
const deviceType = result.device?.type?.toLowerCase();
|
|
18
|
+
if (deviceType === "mobile") {
|
|
19
|
+
device = "mobile";
|
|
20
|
+
}
|
|
21
|
+
else if (deviceType === "tablet") {
|
|
22
|
+
device = "tablet";
|
|
23
|
+
}
|
|
24
|
+
else if (result.os?.name) {
|
|
25
|
+
// If we have an OS but no mobile/tablet device type, assume desktop
|
|
26
|
+
device = "desktop";
|
|
27
|
+
}
|
|
28
|
+
return {
|
|
29
|
+
browser: result.browser?.name ?? null,
|
|
30
|
+
browserVer: result.browser?.version?.split(".")[0] ?? null, // Major version only
|
|
31
|
+
os: result.os?.name ?? null,
|
|
32
|
+
osVer: result.os?.version?.split(".")[0] ?? null, // Major version only
|
|
33
|
+
device,
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
return emptyResult;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
//# sourceMappingURL=ua.js.map
|
package/dist/ua.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ua.js","sourceRoot":"","sources":["../src/ua.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AAGxC,MAAM,UAAU,cAAc,CAAC,SAAwB;IACrD,MAAM,WAAW,GAAoB;QACnC,OAAO,EAAE,IAAI;QACb,UAAU,EAAE,IAAI;QAChB,EAAE,EAAE,IAAI;QACR,KAAK,EAAE,IAAI;QACX,MAAM,EAAE,IAAI;KACb,CAAC;IAEF,IAAI,CAAC,SAAS;QAAE,OAAO,WAAW,CAAC;IAEnC,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,IAAI,QAAQ,CAAC,SAAS,CAAC,CAAC;QACvC,MAAM,MAAM,GAAG,MAAM,CAAC,SAAS,EAAE,CAAC;QAElC,wBAAwB;QACxB,IAAI,MAAM,GAA2C,IAAI,CAAC;QAC1D,MAAM,UAAU,GAAG,MAAM,CAAC,MAAM,EAAE,IAAI,EAAE,WAAW,EAAE,CAAC;QAEtD,IAAI,UAAU,KAAK,QAAQ,EAAE,CAAC;YAC5B,MAAM,GAAG,QAAQ,CAAC;QACpB,CAAC;aAAM,IAAI,UAAU,KAAK,QAAQ,EAAE,CAAC;YACnC,MAAM,GAAG,QAAQ,CAAC;QACpB,CAAC;aAAM,IAAI,MAAM,CAAC,EAAE,EAAE,IAAI,EAAE,CAAC;YAC3B,oEAAoE;YACpE,MAAM,GAAG,SAAS,CAAC;QACrB,CAAC;QAED,OAAO;YACL,OAAO,EAAE,MAAM,CAAC,OAAO,EAAE,IAAI,IAAI,IAAI;YACrC,UAAU,EAAE,MAAM,CAAC,OAAO,EAAE,OAAO,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,IAAI,EAAE,qBAAqB;YACjF,EAAE,EAAE,MAAM,CAAC,EAAE,EAAE,IAAI,IAAI,IAAI;YAC3B,KAAK,EAAE,MAAM,CAAC,EAAE,EAAE,OAAO,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,IAAI,EAAE,qBAAqB;YACvE,MAAM;SACP,CAAC;IACJ,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,WAAW,CAAC;IACrB,CAAC;AACH,CAAC"}
|
package/dist/utils.d.ts
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { VisitorInfo } from "./types.js";
|
|
2
|
+
export declare function isBot(userAgent: string | null): boolean;
|
|
3
|
+
export declare function getVisitorInfo(ip: string | null, userAgent: string | null): Promise<VisitorInfo>;
|
|
4
|
+
export declare function extractPathname(url: string): string;
|
|
5
|
+
export declare function cleanupOldSalts(): Promise<number>;
|
|
6
|
+
//# sourceMappingURL=utils.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AA6C9C,wBAAgB,KAAK,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO,CAIvD;AAiCD,wBAAsB,cAAc,CAClC,EAAE,EAAE,MAAM,GAAG,IAAI,EACjB,SAAS,EAAE,MAAM,GAAG,IAAI,GACvB,OAAO,CAAC,WAAW,CAAC,CAgBtB;AAED,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CASnD;AAGD,wBAAsB,eAAe,IAAI,OAAO,CAAC,MAAM,CAAC,CAUvD"}
|
package/dist/utils.js
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { createHash, randomBytes } from "crypto";
|
|
2
|
+
import { getClient } from "./client.js";
|
|
3
|
+
// Bot patterns to detect
|
|
4
|
+
const BOT_PATTERNS = [
|
|
5
|
+
/bot/i,
|
|
6
|
+
/crawl/i,
|
|
7
|
+
/spider/i,
|
|
8
|
+
/slurp/i,
|
|
9
|
+
/mediapartners/i,
|
|
10
|
+
/googlebot/i,
|
|
11
|
+
/bingbot/i,
|
|
12
|
+
/yandex/i,
|
|
13
|
+
/baidu/i,
|
|
14
|
+
/duckduckgo/i,
|
|
15
|
+
/facebookexternalhit/i,
|
|
16
|
+
/twitterbot/i,
|
|
17
|
+
/linkedinbot/i,
|
|
18
|
+
/whatsapp/i,
|
|
19
|
+
/telegrambot/i,
|
|
20
|
+
/discordbot/i,
|
|
21
|
+
/slackbot/i,
|
|
22
|
+
/pingdom/i,
|
|
23
|
+
/uptimerobot/i,
|
|
24
|
+
/headless/i,
|
|
25
|
+
/phantomjs/i,
|
|
26
|
+
/puppeteer/i,
|
|
27
|
+
/playwright/i,
|
|
28
|
+
/selenium/i,
|
|
29
|
+
/webdriver/i,
|
|
30
|
+
/lighthouse/i,
|
|
31
|
+
/pagespeed/i,
|
|
32
|
+
/gtmetrix/i,
|
|
33
|
+
/ahrefsbot/i,
|
|
34
|
+
/semrushbot/i,
|
|
35
|
+
/mj12bot/i,
|
|
36
|
+
/dotbot/i,
|
|
37
|
+
/bytespider/i,
|
|
38
|
+
/gptbot/i,
|
|
39
|
+
/claudebot/i,
|
|
40
|
+
/anthropic/i,
|
|
41
|
+
/openai/i,
|
|
42
|
+
/chatgpt/i,
|
|
43
|
+
/petalbot/i,
|
|
44
|
+
];
|
|
45
|
+
export function isBot(userAgent) {
|
|
46
|
+
if (!userAgent)
|
|
47
|
+
return true; // No user agent = likely bot
|
|
48
|
+
return BOT_PATTERNS.some((pattern) => pattern.test(userAgent));
|
|
49
|
+
}
|
|
50
|
+
function hash(input) {
|
|
51
|
+
return createHash("sha256").update(input).digest("hex").slice(0, 16);
|
|
52
|
+
}
|
|
53
|
+
async function getDailySalt(date) {
|
|
54
|
+
const db = getClient();
|
|
55
|
+
const dateOnly = new Date(date.toISOString().split("T")[0] + "T00:00:00.000Z");
|
|
56
|
+
// Try to get existing salt
|
|
57
|
+
let dailySalt = await db.dailySalt.findUnique({
|
|
58
|
+
where: { date: dateOnly },
|
|
59
|
+
});
|
|
60
|
+
// Create if doesn't exist
|
|
61
|
+
if (!dailySalt) {
|
|
62
|
+
const salt = randomBytes(32).toString("hex");
|
|
63
|
+
try {
|
|
64
|
+
dailySalt = await db.dailySalt.create({
|
|
65
|
+
data: { date: dateOnly, salt },
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
catch {
|
|
69
|
+
// Race condition - another request created it
|
|
70
|
+
dailySalt = await db.dailySalt.findUnique({
|
|
71
|
+
where: { date: dateOnly },
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return dailySalt?.salt ?? randomBytes(32).toString("hex");
|
|
76
|
+
}
|
|
77
|
+
export async function getVisitorInfo(ip, userAgent) {
|
|
78
|
+
const now = new Date();
|
|
79
|
+
const salt = await getDailySalt(now);
|
|
80
|
+
// Visitor hash: rotates daily
|
|
81
|
+
// Uses IP + UA + daily salt for privacy
|
|
82
|
+
const visitorInput = `${ip ?? ""}|${userAgent ?? ""}|${salt}`;
|
|
83
|
+
const visitorHash = hash(visitorInput);
|
|
84
|
+
// Session hash: rotates every 30 minutes
|
|
85
|
+
// This provides session-like behavior without cookies
|
|
86
|
+
const thirtyMinWindow = Math.floor(now.getTime() / (30 * 60 * 1000));
|
|
87
|
+
const sessionInput = `${visitorInput}|${thirtyMinWindow}`;
|
|
88
|
+
const sessionId = hash(sessionInput);
|
|
89
|
+
return { visitorHash, sessionId };
|
|
90
|
+
}
|
|
91
|
+
export function extractPathname(url) {
|
|
92
|
+
try {
|
|
93
|
+
const parsed = new URL(url);
|
|
94
|
+
return parsed.pathname;
|
|
95
|
+
}
|
|
96
|
+
catch {
|
|
97
|
+
// If URL parsing fails, try to extract path manually
|
|
98
|
+
const match = url.match(/^(?:https?:\/\/[^/]+)?(\/?[^?#]*)/);
|
|
99
|
+
return match?.[1] ?? "/";
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
// Clean up old daily salts (older than 7 days)
|
|
103
|
+
export async function cleanupOldSalts() {
|
|
104
|
+
const db = getClient();
|
|
105
|
+
const sevenDaysAgo = new Date();
|
|
106
|
+
sevenDaysAgo.setDate(sevenDaysAgo.getDate() - 7);
|
|
107
|
+
const result = await db.dailySalt.deleteMany({
|
|
108
|
+
where: { date: { lt: sevenDaysAgo } },
|
|
109
|
+
});
|
|
110
|
+
return result.count;
|
|
111
|
+
}
|
|
112
|
+
//# sourceMappingURL=utils.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"utils.js","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@seneris/nosework",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Privacy-focused, self-hosted analytics for your app suite",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"module": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"import": "./dist/index.js",
|
|
12
|
+
"types": "./dist/index.d.ts"
|
|
13
|
+
},
|
|
14
|
+
"./client/errors": {
|
|
15
|
+
"import": "./dist/client/errors.js",
|
|
16
|
+
"types": "./dist/client/errors.d.ts"
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
"files": [
|
|
20
|
+
"dist",
|
|
21
|
+
"prisma"
|
|
22
|
+
],
|
|
23
|
+
"scripts": {
|
|
24
|
+
"build": "tsc",
|
|
25
|
+
"dev": "tsc --watch",
|
|
26
|
+
"db:generate": "prisma generate",
|
|
27
|
+
"db:migrate": "prisma migrate dev",
|
|
28
|
+
"db:push": "prisma db push",
|
|
29
|
+
"postinstall": "prisma generate",
|
|
30
|
+
"prepublishOnly": "bun run build"
|
|
31
|
+
},
|
|
32
|
+
"keywords": [
|
|
33
|
+
"analytics",
|
|
34
|
+
"privacy",
|
|
35
|
+
"self-hosted",
|
|
36
|
+
"gdpr",
|
|
37
|
+
"cookieless"
|
|
38
|
+
],
|
|
39
|
+
"author": "",
|
|
40
|
+
"license": "MIT",
|
|
41
|
+
"dependencies": {
|
|
42
|
+
"@prisma/client": "^6.2.1",
|
|
43
|
+
"ua-parser-js": "^2.0.1"
|
|
44
|
+
},
|
|
45
|
+
"devDependencies": {
|
|
46
|
+
"@types/bun": "^1.3.5",
|
|
47
|
+
"@types/ua-parser-js": "^0.7.39",
|
|
48
|
+
"prisma": "^6.2.1",
|
|
49
|
+
"typescript": "^5.7.3"
|
|
50
|
+
},
|
|
51
|
+
"peerDependencies": {
|
|
52
|
+
"typescript": "^5"
|
|
53
|
+
}
|
|
54
|
+
}
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
generator client {
|
|
2
|
+
provider = "prisma-client-js"
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
datasource db {
|
|
6
|
+
provider = "postgresql"
|
|
7
|
+
url = env("ANALYTICS_DATABASE_URL")
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
model Site {
|
|
11
|
+
id String @id @default(cuid())
|
|
12
|
+
name String
|
|
13
|
+
domain String @unique
|
|
14
|
+
createdAt DateTime @default(now())
|
|
15
|
+
|
|
16
|
+
pageViews PageView[]
|
|
17
|
+
events Event[]
|
|
18
|
+
errors Error[]
|
|
19
|
+
errorGroups ErrorGroup[]
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
model PageView {
|
|
23
|
+
id String @id @default(cuid())
|
|
24
|
+
siteId String
|
|
25
|
+
site Site @relation(fields: [siteId], references: [id], onDelete: Cascade)
|
|
26
|
+
|
|
27
|
+
// Page info
|
|
28
|
+
url String
|
|
29
|
+
pathname String
|
|
30
|
+
referrer String?
|
|
31
|
+
|
|
32
|
+
// Visitor (cookieless)
|
|
33
|
+
visitorHash String // Hash of IP + UA + daily salt
|
|
34
|
+
sessionId String // Hash with shorter rotation (30 min)
|
|
35
|
+
|
|
36
|
+
// Location (from GeoIP)
|
|
37
|
+
country String?
|
|
38
|
+
countryCode String?
|
|
39
|
+
region String?
|
|
40
|
+
city String?
|
|
41
|
+
|
|
42
|
+
// Device (from User-Agent)
|
|
43
|
+
browser String?
|
|
44
|
+
browserVer String?
|
|
45
|
+
os String?
|
|
46
|
+
osVer String?
|
|
47
|
+
device String? // desktop, mobile, tablet
|
|
48
|
+
|
|
49
|
+
// User context (optional, from your OAuth)
|
|
50
|
+
userId String?
|
|
51
|
+
|
|
52
|
+
// Bot detection
|
|
53
|
+
isBot Boolean @default(false)
|
|
54
|
+
|
|
55
|
+
timestamp DateTime @default(now())
|
|
56
|
+
|
|
57
|
+
@@index([siteId, timestamp])
|
|
58
|
+
@@index([siteId, pathname])
|
|
59
|
+
@@index([siteId, country])
|
|
60
|
+
@@index([visitorHash, timestamp])
|
|
61
|
+
@@index([sessionId])
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
model Event {
|
|
65
|
+
id String @id @default(cuid())
|
|
66
|
+
siteId String
|
|
67
|
+
site Site @relation(fields: [siteId], references: [id], onDelete: Cascade)
|
|
68
|
+
|
|
69
|
+
name String // e.g., "signup", "purchase", "button_click"
|
|
70
|
+
properties Json? // Flexible event data
|
|
71
|
+
|
|
72
|
+
visitorHash String
|
|
73
|
+
sessionId String
|
|
74
|
+
userId String?
|
|
75
|
+
|
|
76
|
+
url String?
|
|
77
|
+
|
|
78
|
+
timestamp DateTime @default(now())
|
|
79
|
+
|
|
80
|
+
@@index([siteId, name, timestamp])
|
|
81
|
+
@@index([siteId, timestamp])
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Daily salt for visitor hashing (privacy - rotates daily)
|
|
85
|
+
model DailySalt {
|
|
86
|
+
date DateTime @id @db.Date
|
|
87
|
+
salt String
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Error tracking
|
|
91
|
+
model Error {
|
|
92
|
+
id String @id @default(cuid())
|
|
93
|
+
siteId String
|
|
94
|
+
site Site @relation(fields: [siteId], references: [id], onDelete: Cascade)
|
|
95
|
+
|
|
96
|
+
// Error details
|
|
97
|
+
message String
|
|
98
|
+
stack String? @db.Text
|
|
99
|
+
fingerprint String // Hash for grouping similar errors
|
|
100
|
+
|
|
101
|
+
// Context
|
|
102
|
+
url String
|
|
103
|
+
pathname String
|
|
104
|
+
|
|
105
|
+
// Visitor context (reuse existing pattern)
|
|
106
|
+
visitorHash String?
|
|
107
|
+
sessionId String?
|
|
108
|
+
userId String?
|
|
109
|
+
|
|
110
|
+
// Browser context
|
|
111
|
+
browser String?
|
|
112
|
+
browserVer String?
|
|
113
|
+
os String?
|
|
114
|
+
device String?
|
|
115
|
+
|
|
116
|
+
// Metadata
|
|
117
|
+
metadata Json? // Custom context from app
|
|
118
|
+
|
|
119
|
+
timestamp DateTime @default(now())
|
|
120
|
+
|
|
121
|
+
@@index([siteId, timestamp])
|
|
122
|
+
@@index([siteId, fingerprint])
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
model ErrorGroup {
|
|
126
|
+
id String @id @default(cuid())
|
|
127
|
+
siteId String
|
|
128
|
+
site Site @relation(fields: [siteId], references: [id], onDelete: Cascade)
|
|
129
|
+
fingerprint String
|
|
130
|
+
|
|
131
|
+
// Representative error
|
|
132
|
+
message String
|
|
133
|
+
stack String? @db.Text
|
|
134
|
+
|
|
135
|
+
// Counts
|
|
136
|
+
count Int @default(1)
|
|
137
|
+
lastSeen DateTime
|
|
138
|
+
firstSeen DateTime
|
|
139
|
+
|
|
140
|
+
// Status: open, resolved, ignored
|
|
141
|
+
status String @default("open")
|
|
142
|
+
|
|
143
|
+
@@unique([siteId, fingerprint])
|
|
144
|
+
@@index([siteId, status, lastSeen])
|
|
145
|
+
}
|