@xk2800/nextjs-template 0.8.0-beta.2 → 0.8.0-beta.4
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/components/dashboard/feedbackDialog.tsx +118 -0
- package/dist/activity/logger.js +5 -0
- package/dist/activity/logger.js.map +1 -1
- package/dist/activity/logger.mjs +3 -3
- package/dist/activity/queries.d.mts +1 -1
- package/dist/activity/queries.d.ts +1 -1
- package/dist/activity/queries.js +5 -0
- package/dist/activity/queries.js.map +1 -1
- package/dist/activity/queries.mjs +2 -2
- package/dist/admin/queries.js +5 -0
- package/dist/admin/queries.js.map +1 -1
- package/dist/admin/queries.mjs +2 -2
- package/dist/api/feedback.d.mts +9 -0
- package/dist/api/feedback.d.ts +9 -0
- package/dist/api/feedback.js +1137 -0
- package/dist/api/feedback.js.map +1 -0
- package/dist/api/feedback.mjs +107 -0
- package/dist/api/feedback.mjs.map +1 -0
- package/dist/auth/helpers.js +5 -0
- package/dist/auth/helpers.js.map +1 -1
- package/dist/auth/helpers.mjs +5 -5
- package/dist/auth/index.d.mts +2 -2
- package/dist/auth/index.d.ts +2 -2
- package/dist/auth/index.js +5 -0
- package/dist/auth/index.js.map +1 -1
- package/dist/auth/index.mjs +5 -5
- package/dist/{chunk-VEFYEHD4.mjs → chunk-G3B7NC5B.mjs} +3 -3
- package/dist/{chunk-VEFYEHD4.mjs.map → chunk-G3B7NC5B.mjs.map} +1 -1
- package/dist/{chunk-WUKRXEMT.mjs → chunk-GS33BWOL.mjs} +7 -7
- package/dist/chunk-GS33BWOL.mjs.map +1 -0
- package/dist/{chunk-PZW4VYQO.mjs → chunk-IFZGEDMN.mjs} +4 -4
- package/dist/{chunk-PZW4VYQO.mjs.map → chunk-IFZGEDMN.mjs.map} +1 -1
- package/dist/{chunk-MRRPHLCL.mjs → chunk-LKPF3JCV.mjs} +3 -3
- package/dist/{chunk-MRRPHLCL.mjs.map → chunk-LKPF3JCV.mjs.map} +1 -1
- package/dist/{chunk-2GFPALUD.mjs → chunk-TMJYAOC2.mjs} +7 -2
- package/dist/chunk-TMJYAOC2.mjs.map +1 -0
- package/dist/config/env.d.mts +2 -0
- package/dist/config/env.d.ts +2 -0
- package/dist/config/env.js +5 -0
- package/dist/config/env.js.map +1 -1
- package/dist/config/env.mjs +1 -1
- package/dist/db/index.d.mts +1 -1
- package/dist/db/index.d.ts +1 -1
- package/dist/db/index.js +5 -0
- package/dist/db/index.js.map +1 -1
- package/dist/db/index.mjs +2 -2
- package/dist/db/schema.d.mts +1 -1
- package/dist/db/schema.d.ts +1 -1
- package/dist/{schema-CCaV4_s9.d.mts → schema-CZnQXmhF.d.mts} +1 -1
- package/dist/{schema-CCaV4_s9.d.ts → schema-CZnQXmhF.d.ts} +1 -1
- package/dist/sessions/queries.js +5 -0
- package/dist/sessions/queries.js.map +1 -1
- package/dist/sessions/queries.mjs +2 -2
- package/dist/settings/queries.d.mts +1 -1
- package/dist/settings/queries.d.ts +1 -1
- package/dist/settings/queries.js +5 -0
- package/dist/settings/queries.js.map +1 -1
- package/dist/settings/queries.mjs +3 -3
- package/dist/users/queries.js +5 -0
- package/dist/users/queries.js.map +1 -1
- package/dist/users/queries.mjs +2 -2
- package/package.json +11 -1
- package/dist/chunk-2GFPALUD.mjs.map +0 -1
- package/dist/chunk-WUKRXEMT.mjs.map +0 -1
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { useState } from "react"
|
|
4
|
+
import { usePathname } from "next/navigation"
|
|
5
|
+
import { toast } from "sonner"
|
|
6
|
+
import { MessageSquare } from "lucide-react"
|
|
7
|
+
import { Button } from "../ui/button"
|
|
8
|
+
import { Label } from "../ui/label"
|
|
9
|
+
import { Textarea } from "../ui/textarea"
|
|
10
|
+
import {
|
|
11
|
+
Dialog,
|
|
12
|
+
DialogContent,
|
|
13
|
+
DialogDescription,
|
|
14
|
+
DialogFooter,
|
|
15
|
+
DialogHeader,
|
|
16
|
+
DialogTitle,
|
|
17
|
+
DialogTrigger,
|
|
18
|
+
} from "../ui/dialog"
|
|
19
|
+
|
|
20
|
+
// Keep in sync with the cap in app/api/feedback/route.ts.
|
|
21
|
+
const FEEDBACK_MAX_LENGTH = 2000
|
|
22
|
+
|
|
23
|
+
type FeedbackType = "bug" | "feedback"
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Header button + dialog that POSTs to /api/feedback. Render it only when a
|
|
27
|
+
* feedback channel is configured (FEEDBACK_DISCORD_WEBHOOK_URL or
|
|
28
|
+
* FEEDBACK_EMAIL); otherwise the route answers 503.
|
|
29
|
+
*/
|
|
30
|
+
export default function FeedbackDialog() {
|
|
31
|
+
const pathname = usePathname()
|
|
32
|
+
const [open, setOpen] = useState(false)
|
|
33
|
+
const [type, setType] = useState<FeedbackType>("feedback")
|
|
34
|
+
const [message, setMessage] = useState("")
|
|
35
|
+
const [isSending, setIsSending] = useState(false)
|
|
36
|
+
|
|
37
|
+
const handleSubmit = async (e: React.FormEvent) => {
|
|
38
|
+
e.preventDefault()
|
|
39
|
+
setIsSending(true)
|
|
40
|
+
try {
|
|
41
|
+
const response = await fetch("/api/feedback", {
|
|
42
|
+
method: "POST",
|
|
43
|
+
headers: { "Content-Type": "application/json" },
|
|
44
|
+
body: JSON.stringify({ type, message, page: pathname }),
|
|
45
|
+
})
|
|
46
|
+
const data = await response.json().catch(() => ({}))
|
|
47
|
+
if (!response.ok) throw new Error(data.error || "Failed to send feedback")
|
|
48
|
+
|
|
49
|
+
toast.success("Thanks! Your feedback was sent.")
|
|
50
|
+
setMessage("")
|
|
51
|
+
setOpen(false)
|
|
52
|
+
} catch (error) {
|
|
53
|
+
toast.error(error instanceof Error ? error.message : "Failed to send feedback")
|
|
54
|
+
} finally {
|
|
55
|
+
setIsSending(false)
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return (
|
|
60
|
+
<Dialog open={open} onOpenChange={setOpen}>
|
|
61
|
+
<DialogTrigger asChild>
|
|
62
|
+
<Button variant="ghost" size="sm">
|
|
63
|
+
<MessageSquare />
|
|
64
|
+
Feedback
|
|
65
|
+
</Button>
|
|
66
|
+
</DialogTrigger>
|
|
67
|
+
<DialogContent>
|
|
68
|
+
<form onSubmit={handleSubmit} className="space-y-4">
|
|
69
|
+
<DialogHeader>
|
|
70
|
+
<DialogTitle>Send feedback</DialogTitle>
|
|
71
|
+
<DialogDescription>
|
|
72
|
+
Report a bug or tell us what could be better.
|
|
73
|
+
</DialogDescription>
|
|
74
|
+
</DialogHeader>
|
|
75
|
+
|
|
76
|
+
<div className="flex gap-2" role="radiogroup" aria-label="Feedback type">
|
|
77
|
+
{(["feedback", "bug"] as const).map((t) => (
|
|
78
|
+
<Button
|
|
79
|
+
key={t}
|
|
80
|
+
type="button"
|
|
81
|
+
role="radio"
|
|
82
|
+
aria-checked={type === t}
|
|
83
|
+
variant={type === t ? "default" : "outline"}
|
|
84
|
+
size="sm"
|
|
85
|
+
onClick={() => setType(t)}
|
|
86
|
+
>
|
|
87
|
+
{t === "bug" ? "Bug report" : "General feedback"}
|
|
88
|
+
</Button>
|
|
89
|
+
))}
|
|
90
|
+
</div>
|
|
91
|
+
|
|
92
|
+
<div className="space-y-2">
|
|
93
|
+
<Label htmlFor="feedback-message">
|
|
94
|
+
{type === "bug" ? "What happened, and what did you expect?" : "Your feedback"}
|
|
95
|
+
</Label>
|
|
96
|
+
<Textarea
|
|
97
|
+
id="feedback-message"
|
|
98
|
+
value={message}
|
|
99
|
+
onChange={(e) => setMessage(e.target.value)}
|
|
100
|
+
maxLength={FEEDBACK_MAX_LENGTH}
|
|
101
|
+
rows={6}
|
|
102
|
+
required
|
|
103
|
+
/>
|
|
104
|
+
<p className="text-xs text-muted-foreground text-right">
|
|
105
|
+
{message.length}/{FEEDBACK_MAX_LENGTH}
|
|
106
|
+
</p>
|
|
107
|
+
</div>
|
|
108
|
+
|
|
109
|
+
<DialogFooter>
|
|
110
|
+
<Button type="submit" disabled={isSending || !message.trim()}>
|
|
111
|
+
{isSending ? "Sending..." : "Send"}
|
|
112
|
+
</Button>
|
|
113
|
+
</DialogFooter>
|
|
114
|
+
</form>
|
|
115
|
+
</DialogContent>
|
|
116
|
+
</Dialog>
|
|
117
|
+
)
|
|
118
|
+
}
|
package/dist/activity/logger.js
CHANGED
|
@@ -227,6 +227,11 @@ var envSchema = zod.z.object({
|
|
|
227
227
|
// (2FA issuer) and the passkey relying-party name. Consuming projects set
|
|
228
228
|
// this in their own .env; defaults to the template name.
|
|
229
229
|
APP_NAME: zod.z.string().default("Next.js Template"),
|
|
230
|
+
// Where the dashboard feedback form delivers (app/api/feedback). Set either
|
|
231
|
+
// or both; with neither, the Feedback button is hidden. FEEDBACK_EMAIL takes
|
|
232
|
+
// a comma-separated list and sends through Resend (needs RESEND_API_KEY).
|
|
233
|
+
FEEDBACK_DISCORD_WEBHOOK_URL: zod.z.string().default(""),
|
|
234
|
+
FEEDBACK_EMAIL: zod.z.string().default(""),
|
|
230
235
|
// Seed-only: the live value lives in the system_settings DB row (see
|
|
231
236
|
// lib/settings-queries.ts) once it's been seeded on first read, and is
|
|
232
237
|
// changed from the admin System Settings page, not by editing this var.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../server/db/schema.ts","../../lib/maintenance.ts","../../config/env-schema.ts","../../config/env.ts","../../server/db/index.ts","../../lib/activity-logger.ts"],"names":["pgEnum","pgTable","text","createId","boolean","timestamp","integer","unique","check","sql","z","Pool","drizzlePg","neon","drizzle"],"mappings":"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","file":"logger.js","sourcesContent":["import { boolean, check, integer, pgEnum, pgTable, text, timestamp, unique } from \"drizzle-orm/pg-core\";\nimport { sql } from \"drizzle-orm\";\nimport { createId } from '@paralleldrive/cuid2'\nimport { DEFAULT_MAINTENANCE_EXEMPT_PATHS } from \"../../lib/maintenance\";\n\n// 'tester' is a regular user account flagged for testing; apps can use it to\n// keep test accounts apart from real users. It has no admin access.\nexport const RoleEnum = pgEnum('roles', ['user', 'admin', 'tester'])\n\nexport const ActivityActionEnum = pgEnum('activity_actions', [\n 'login',\n 'logout',\n 'login_failed',\n 'password_changed',\n 'email_changed',\n 'profile_updated',\n 'session_revoked',\n 'user_deleted',\n 'user_banned',\n 'user_unbanned',\n 'role_changed',\n 'impersonation_started',\n 'impersonation_stopped',\n 'settings_changed',\n 'passkey_added',\n 'passkey_removed',\n])\n\nexport const users = pgTable(\"user\", {\n id: text(\"id\").notNull().primaryKey().$defaultFn(() => createId()),\n name: text('name').notNull(),\n email: text('email').notNull().unique(),\n password: text('password'),\n emailVerified: boolean(\"emailVerified\").notNull().default(false),\n image: text(\"image\"),\n role: RoleEnum('roles').default('user').notNull(),\n banned: boolean('banned').default(false).notNull(),\n bannedAt: timestamp('bannedAt'),\n bannedReason: text('bannedReason'),\n // Required by better-auth's admin plugin schema (auto-expiring bans). We\n // never set this ourselves, so it stays null and bans behave as permanent,\n // same as before the plugin was added.\n banExpires: timestamp('banExpires'),\n lastLoginAt: timestamp('lastLoginAt'),\n lastActiveAt: timestamp('lastActiveAt'),\n // Set by better-auth's twoFactor plugin — true once the user has\n // completed TOTP setup (see twoFactor pgTable below for the secret /\n // backup codes themselves).\n twoFactorEnabled: boolean('twoFactorEnabled').notNull().default(false),\n createdAt: timestamp('createdAt').notNull().defaultNow(),\n updatedAt: timestamp('updatedAt').notNull().defaultNow(),\n});\n\nexport const sessions = pgTable(\"session\", {\n id: text(\"id\").notNull().primaryKey().$defaultFn(() => createId()),\n expiresAt: timestamp(\"expiresAt\").notNull(),\n token: text(\"token\").notNull().unique(),\n createdAt: timestamp(\"createdAt\").notNull().defaultNow(),\n updatedAt: timestamp(\"updatedAt\").notNull().defaultNow(),\n ipAddress: text(\"ipAddress\"),\n userAgent: text(\"userAgent\"),\n userId: text(\"userId\")\n .notNull()\n .references(() => users.id, { onDelete: \"cascade\" }),\n // Set by better-auth's admin plugin while this session is an admin\n // impersonating another user; holds the impersonating admin's user id.\n impersonatedBy: text(\"impersonatedBy\"),\n});\n\nexport const accounts = pgTable(\"account\", {\n id: text(\"id\").notNull().primaryKey().$defaultFn(() => createId()),\n accountId: text(\"accountId\").notNull(),\n providerId: text(\"providerId\").notNull(),\n userId: text(\"userId\")\n .notNull()\n .references(() => users.id, { onDelete: \"cascade\" }),\n accessToken: text(\"accessToken\"),\n refreshToken: text(\"refreshToken\"),\n idToken: text(\"idToken\"),\n accessTokenExpiresAt: timestamp(\"accessTokenExpiresAt\"),\n refreshTokenExpiresAt: timestamp(\"refreshTokenExpiresAt\"),\n scope: text(\"scope\"),\n password: text(\"password\"),\n createdAt: timestamp(\"createdAt\").notNull().defaultNow(),\n updatedAt: timestamp(\"updatedAt\").notNull().defaultNow(),\n});\n\n// Better-auth's twoFactor plugin. One row per user (created on first\n// `enable`); secret and backupCodes are its own encrypted-at-rest strings,\n// field names/types must match its expectations exactly (drizzleAdapter in\n// server/auth.ts maps this table to the plugin's \"twoFactor\" model as-is).\nexport const twoFactors = pgTable(\"twoFactor\", {\n id: text(\"id\").notNull().primaryKey().$defaultFn(() => createId()),\n secret: text(\"secret\").notNull(),\n backupCodes: text(\"backupCodes\").notNull(),\n userId: text(\"userId\")\n .notNull()\n .references(() => users.id, { onDelete: \"cascade\" }),\n // Added in better-auth 1.7 (TOTP lockout). Defaults match the plugin's.\n verified: boolean(\"verified\").default(true),\n failedVerificationCount: integer(\"failedVerificationCount\").default(0),\n lockedUntil: timestamp(\"lockedUntil\"),\n});\n\n// Better-auth's passkey plugin — one row per registered WebAuthn credential.\nexport const passkeys = pgTable(\"passkey\", {\n id: text(\"id\").notNull().primaryKey().$defaultFn(() => createId()),\n name: text(\"name\"),\n publicKey: text(\"publicKey\").notNull(),\n userId: text(\"userId\")\n .notNull()\n .references(() => users.id, { onDelete: \"cascade\" }),\n credentialID: text(\"credentialID\").notNull().unique(),\n counter: integer(\"counter\").notNull(),\n deviceType: text(\"deviceType\").notNull(),\n backedUp: boolean(\"backedUp\").notNull(),\n transports: text(\"transports\"),\n createdAt: timestamp(\"createdAt\"),\n aaguid: text(\"aaguid\"),\n});\n\nexport const verifications = pgTable(\"verification\", {\n id: text(\"id\").notNull().primaryKey().$defaultFn(() => createId()),\n identifier: text(\"identifier\").notNull(),\n value: text(\"value\").notNull(),\n expiresAt: timestamp(\"expiresAt\").notNull(),\n createdAt: timestamp(\"createdAt\").notNull().defaultNow(),\n updatedAt: timestamp(\"updatedAt\").notNull().defaultNow(),\n});\n\nexport const activityLogs = pgTable(\"activity_log\", {\n id: text(\"id\").notNull().primaryKey().$defaultFn(() => createId()),\n userId: text(\"userId\")\n .notNull()\n .references(() => users.id, { onDelete: \"cascade\" }),\n action: ActivityActionEnum('action').notNull(),\n description: text('description').notNull(),\n ipAddress: text('ipAddress'),\n userAgent: text('userAgent'),\n metadata: text('metadata'),\n // Populated on 'login' events only (registration + sign-in) — parsed from\n // userAgent/ipAddress at write time so the record stays stable even if\n // the parsing library or geoip database changes later.\n referrerUrl: text('referrerUrl'),\n os: text('os'),\n browser: text('browser'),\n deviceType: text('deviceType'),\n country: text('country'),\n city: text('city'),\n createdAt: timestamp('createdAt').notNull().defaultNow(),\n});\n\n// One row per (user, device fingerprint) pair. Its only job is to answer\n// \"has this account signed in from this device before?\" — no matching row ⇒\n// send a new-device alert (see app/api/device-check/route.ts). visitorId is\n// FingerprintJS's identifier, computed client-side in\n// components/auth/deviceCheck.tsx. UA+IP are deliberately NOT the signal here:\n// they churn constantly on mobile and would alert on every other login.\nexport const deviceFingerprints = pgTable(\"device_fingerprint\", {\n id: text(\"id\").notNull().primaryKey().$defaultFn(() => createId()),\n userId: text(\"userId\")\n .notNull()\n .references(() => users.id, { onDelete: \"cascade\" }),\n visitorId: text(\"visitorId\").notNull(),\n // Context captured at first-seen time — for the admin panel's \"New devices\"\n // card and the alert email. Not part of the new-vs-known decision (that's\n // visitorId only), just so an admin can eyeball whether a sign-in looks like\n // a hijack. Nullable: an old row predates these columns, or geoip missed.\n ipAddress: text(\"ipAddress\"),\n userAgent: text(\"userAgent\"),\n country: text(\"country\"),\n city: text(\"city\"),\n createdAt: timestamp(\"createdAt\").notNull().defaultNow(),\n}, (table) => [\n unique(\"device_fingerprint_user_visitor\").on(table.userId, table.visitorId),\n]);\n\n// Per-device abuse throttle for /sign-in/email and /sign-up/email — one\n// fixed-window counter row per FingerprintJS visitorId (sent by the auth\n// forms as the x-device-fingerprint header; see lib/auth-throttle.ts).\n// Keyed by device, not IP: IP is shared behind corporate NAT / CGNAT / VPN,\n// so an IP counter throttles coworkers together while missing an abuser who\n// rotates IPs. Rows whose window has elapsed are dead weight but harmless\n// (reset on the next attempt) — prune on a cron if the table ever grows.\nexport const authThrottle = pgTable(\"auth_throttle\", {\n fingerprint: text(\"fingerprint\").notNull().primaryKey(),\n count: integer(\"count\").notNull().default(0),\n windowStart: timestamp(\"windowStart\").notNull().defaultNow(),\n // Blockable data points, overwritten on every attempt (last-write-wins) so\n // the admin panel can show who to block — by IP or by fingerprint — with no\n // join and no per-attempt history table. windowStart anchors the counter's\n // window; lastAttemptAt is just \"most recently seen\".\n lastAttemptAt: timestamp(\"lastAttemptAt\").notNull().defaultNow(),\n ipAddress: text(\"ipAddress\"),\n userAgent: text(\"userAgent\"),\n lastEmail: text(\"lastEmail\"),\n lastKind: text(\"lastKind\"), // 'signin' | 'signup'\n});\n\n// Singleton row (id is always 'default') holding admin-editable settings for\n// config that used to be env-var-only. See lib/settings-queries.ts for the\n// cached read path and lazy-seed-from-env behavior.\nexport const systemSettings = pgTable(\"system_settings\", {\n id: text(\"id\").primaryKey().default(\"default\"),\n maintenanceMode: boolean(\"maintenanceMode\").notNull().default(false),\n maintenanceMessage: text(\"maintenanceMessage\"),\n // Path prefixes that stay reachable while maintenance mode is on (see\n // lib/maintenance.ts — a few auth paths are always open on top of these).\n maintenanceExemptPaths: text(\"maintenanceExemptPaths\").array().notNull().default(DEFAULT_MAINTENANCE_EXEMPT_PATHS),\n authEnableGoogle: boolean(\"authEnableGoogle\").notNull().default(true),\n authEnableEmailPassword: boolean(\"authEnableEmailPassword\").notNull().default(true),\n authEnableOneTap: boolean(\"authEnableOneTap\").notNull().default(false),\n enableSessionRevocation: boolean(\"enableSessionRevocation\").notNull().default(true),\n updatedAt: timestamp(\"updatedAt\").notNull().defaultNow(),\n // Who last changed a setting. This row doesn't \"belong\" to that admin the\n // way sessions/activityLogs belong to their user, so deleting that admin's\n // account should null this out rather than cascade-delete the singleton row.\n updatedBy: text(\"updatedBy\").references(() => users.id, { onDelete: \"set null\" }),\n}, (table) => [\n check(\"system_settings_singleton\", sql`${table.id} = 'default'`),\n]);","// Pure path logic for maintenance mode — no DB / server-only imports, so it\n// can run in proxy.ts and in the admin form alike.\n\n// Default for the admin-editable exempt list: the public marketing pages.\nexport const DEFAULT_MAINTENANCE_EXEMPT_PATHS = [\n \"/\",\n \"/features\",\n \"/changelog\",\n \"/changelog-template\",\n \"/privacy\",\n \"/terms\",\n]\n\n// Always reachable, whatever the admin saves — otherwise an admin could\n// lock themselves out of the sign-in flow needed to turn maintenance off.\nexport const ALWAYS_OPEN_PATHS = [\n \"/maintenance\",\n \"/login\", // includes /login/2fa\n \"/forgot-password\",\n \"/reset-password\",\n \"/api/auth\",\n \"/api/settings/public\", // login page reads the One Tap flag from here\n]\n\n// `/foo` matches `/foo` and `/foo/...` but not `/foobar`; `/` matches only `/`.\nfunction matches(pathname: string, prefix: string) {\n const p = prefix.length > 1 ? prefix.replace(/\\/+$/, \"\") : prefix\n return pathname === p || (p !== \"/\" && pathname.startsWith(p + \"/\"))\n}\n\nexport function isMaintenanceExempt(pathname: string, exemptPaths: string[]) {\n return [...ALWAYS_OPEN_PATHS, ...exemptPaths].some((p) => matches(pathname, p))\n}\n","// No \"server-only\" guard here — this schema is also imported by scripts that\n// run outside Next.js's bundler (e.g. scripts/doctor.ts), where that guard\n// throws unconditionally. config/env.ts re-exports the parsed, guarded result\n// for app code; import this file directly only from non-Next scripts.\nimport { z } from 'zod';\n\nexport const envSchema = z.object({\n NODE_ENV: z.enum(['development', 'production', 'test']).default('development'),\n PORT: z.string().default('3000').transform(Number),\n DATABASE_URL: z.string().default(''),\n RESEND_API_KEY: z.string().default(''),\n // Sender for every transactional email. Must be on a domain verified in\n // your Resend account.\n EMAIL_FROM: z.string().default('Acme <onboarding@xavierkhew.xyz>'),\n // Display name for this deployment — shown as the authenticator-app label\n // (2FA issuer) and the passkey relying-party name. Consuming projects set\n // this in their own .env; defaults to the template name.\n APP_NAME: z.string().default('Next.js Template'),\n // Seed-only: the live value lives in the system_settings DB row (see\n // lib/settings-queries.ts) once it's been seeded on first read, and is\n // changed from the admin System Settings page, not by editing this var.\n ENABLE_SESSION_REVOCATION: z.string().default('true').transform(val => val === 'true'),\n AUTH_ENABLE_GOOGLE: z.string().default('true').transform(val => val === 'true'),\n AUTH_ENABLE_EMAIL_PASSWORD: z.string().default('true').transform(val => val === 'true'),\n AUTH_ENABLE_ONE_TAP: z.string().default('false').transform(val => val === 'true'),\n DB_DRIVER: z.enum(['pg', 'neon']).default('pg'),\n // Only relevant when DB_DRIVER=pg — whether the target Postgres server has SSL\n // enabled. Self-hosted Postgres (e.g. via Docker/Dokploy) typically does not.\n DATABASE_SSL: z.string().default('false').transform(val => val === 'true'),\n});\n","import \"server-only\";\nimport { envSchema } from './env-schema';\n\nconst results = envSchema.safeParse(process.env);\n\nif (results.success && (results.data.NODE_ENV === 'development')) {\n console.log('Environment variables loaded:', Object.keys(results.data));\n}\n\nif (!results.success) {\n console.error('Invalid environment variables:', results.error.format());\n throw new Error('Invalid environment variables');\n}\n\nexport const config = results.data;\n","import 'server-only';\n\n// neon-http driver for production\nimport { neon } from '@neondatabase/serverless';\nimport { drizzle } from 'drizzle-orm/neon-http';\n\n// postgres driver for local development\nimport { drizzle as drizzlePg } from 'drizzle-orm/node-postgres';\nimport { Pool } from 'pg';\n\nimport * as schema from './schema';\nimport { config } from '../../config/env';\n\n// Import the query builder\nimport { PostgresJsDatabase } from 'drizzle-orm/postgres-js';\nimport { NeonHttpDatabase } from 'drizzle-orm/neon-http';\n\ntype DrizzleNeon = NeonHttpDatabase<typeof schema>;\ntype DrizzlePg = PostgresJsDatabase<typeof schema>;\n\nlet db: DrizzleNeon | DrizzlePg\n\n\nif (config.DB_DRIVER === 'pg') {\n const pool = new Pool({\n connectionString: config.DATABASE_URL!,\n ssl: config.DATABASE_SSL ? { rejectUnauthorized: false } : undefined,\n });\n db = drizzlePg(pool, {\n schema,\n logger: true,\n });\n\n console.log('🟡 Using PostgreSQL (pg) driver');\n\n} else if (config.DB_DRIVER === 'neon') {\n const sql = neon(config.DATABASE_URL!);\n db = drizzle(sql, { schema, logger: true });\n\n console.log('🟢 Using Neon (HTTP) driver');\n\n} else {\n console.log('Database connection not established, unknown DB_DRIVER:', config.DB_DRIVER);\n}\n// Note: In production, you might want to disable logging or use a more sophisticated logger.\n\nexport { db }","import \"server-only\"\nimport { db } from \"../server/db\"\nimport { activityLogs } from \"../server/db/schema\"\n\nexport type ActivityAction =\n | 'login'\n | 'logout'\n | 'login_failed'\n | 'password_changed'\n | 'email_changed'\n | 'profile_updated'\n | 'session_revoked'\n | 'user_deleted'\n | 'user_banned'\n | 'user_unbanned'\n | 'role_changed'\n | 'impersonation_started'\n | 'impersonation_stopped'\n | 'settings_changed'\n | 'passkey_added'\n | 'passkey_removed'\n\ninterface LogActivityParams {\n userId: string\n action: ActivityAction\n description: string\n ipAddress?: string | null\n userAgent?: string | null\n metadata?: Record<string, any>\n // Populated on 'login' events only — see server/auth.ts's session.create hook.\n referrerUrl?: string | null\n os?: string | null\n browser?: string | null\n deviceType?: string | null\n country?: string | null\n city?: string | null\n}\n\nexport async function logActivity({\n userId,\n action,\n description,\n ipAddress,\n userAgent,\n metadata,\n referrerUrl,\n os,\n browser,\n deviceType,\n country,\n city,\n}: LogActivityParams) {\n try {\n await db.insert(activityLogs).values({\n userId,\n action,\n description,\n ipAddress: ipAddress || null,\n userAgent: userAgent || null,\n metadata: metadata ? JSON.stringify(metadata) : null,\n referrerUrl: referrerUrl || null,\n os: os || null,\n browser: browser || null,\n deviceType: deviceType || null,\n country: country || null,\n city: city || null,\n })\n } catch (error) {\n console.error('Failed to log activity:', error)\n // Don't throw - activity logging should not break the app\n }\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../../server/db/schema.ts","../../lib/maintenance.ts","../../config/env-schema.ts","../../config/env.ts","../../server/db/index.ts","../../lib/activity-logger.ts"],"names":["pgEnum","pgTable","text","createId","boolean","timestamp","integer","unique","check","sql","z","Pool","drizzlePg","neon","drizzle"],"mappings":"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","file":"logger.js","sourcesContent":["import { boolean, check, integer, pgEnum, pgTable, text, timestamp, unique } from \"drizzle-orm/pg-core\";\nimport { sql } from \"drizzle-orm\";\nimport { createId } from '@paralleldrive/cuid2'\nimport { DEFAULT_MAINTENANCE_EXEMPT_PATHS } from \"../../lib/maintenance\";\n\n// 'tester' is a regular user account flagged for testing; apps can use it to\n// keep test accounts apart from real users. It has no admin access.\nexport const RoleEnum = pgEnum('roles', ['user', 'admin', 'tester'])\n\nexport const ActivityActionEnum = pgEnum('activity_actions', [\n 'login',\n 'logout',\n 'login_failed',\n 'password_changed',\n 'email_changed',\n 'profile_updated',\n 'session_revoked',\n 'user_deleted',\n 'user_banned',\n 'user_unbanned',\n 'role_changed',\n 'impersonation_started',\n 'impersonation_stopped',\n 'settings_changed',\n 'passkey_added',\n 'passkey_removed',\n])\n\nexport const users = pgTable(\"user\", {\n id: text(\"id\").notNull().primaryKey().$defaultFn(() => createId()),\n name: text('name').notNull(),\n email: text('email').notNull().unique(),\n password: text('password'),\n emailVerified: boolean(\"emailVerified\").notNull().default(false),\n image: text(\"image\"),\n role: RoleEnum('roles').default('user').notNull(),\n banned: boolean('banned').default(false).notNull(),\n bannedAt: timestamp('bannedAt'),\n bannedReason: text('bannedReason'),\n // Required by better-auth's admin plugin schema (auto-expiring bans). We\n // never set this ourselves, so it stays null and bans behave as permanent,\n // same as before the plugin was added.\n banExpires: timestamp('banExpires'),\n lastLoginAt: timestamp('lastLoginAt'),\n lastActiveAt: timestamp('lastActiveAt'),\n // Set by better-auth's twoFactor plugin — true once the user has\n // completed TOTP setup (see twoFactor pgTable below for the secret /\n // backup codes themselves).\n twoFactorEnabled: boolean('twoFactorEnabled').notNull().default(false),\n createdAt: timestamp('createdAt').notNull().defaultNow(),\n updatedAt: timestamp('updatedAt').notNull().defaultNow(),\n});\n\nexport const sessions = pgTable(\"session\", {\n id: text(\"id\").notNull().primaryKey().$defaultFn(() => createId()),\n expiresAt: timestamp(\"expiresAt\").notNull(),\n token: text(\"token\").notNull().unique(),\n createdAt: timestamp(\"createdAt\").notNull().defaultNow(),\n updatedAt: timestamp(\"updatedAt\").notNull().defaultNow(),\n ipAddress: text(\"ipAddress\"),\n userAgent: text(\"userAgent\"),\n userId: text(\"userId\")\n .notNull()\n .references(() => users.id, { onDelete: \"cascade\" }),\n // Set by better-auth's admin plugin while this session is an admin\n // impersonating another user; holds the impersonating admin's user id.\n impersonatedBy: text(\"impersonatedBy\"),\n});\n\nexport const accounts = pgTable(\"account\", {\n id: text(\"id\").notNull().primaryKey().$defaultFn(() => createId()),\n accountId: text(\"accountId\").notNull(),\n providerId: text(\"providerId\").notNull(),\n userId: text(\"userId\")\n .notNull()\n .references(() => users.id, { onDelete: \"cascade\" }),\n accessToken: text(\"accessToken\"),\n refreshToken: text(\"refreshToken\"),\n idToken: text(\"idToken\"),\n accessTokenExpiresAt: timestamp(\"accessTokenExpiresAt\"),\n refreshTokenExpiresAt: timestamp(\"refreshTokenExpiresAt\"),\n scope: text(\"scope\"),\n password: text(\"password\"),\n createdAt: timestamp(\"createdAt\").notNull().defaultNow(),\n updatedAt: timestamp(\"updatedAt\").notNull().defaultNow(),\n});\n\n// Better-auth's twoFactor plugin. One row per user (created on first\n// `enable`); secret and backupCodes are its own encrypted-at-rest strings,\n// field names/types must match its expectations exactly (drizzleAdapter in\n// server/auth.ts maps this table to the plugin's \"twoFactor\" model as-is).\nexport const twoFactors = pgTable(\"twoFactor\", {\n id: text(\"id\").notNull().primaryKey().$defaultFn(() => createId()),\n secret: text(\"secret\").notNull(),\n backupCodes: text(\"backupCodes\").notNull(),\n userId: text(\"userId\")\n .notNull()\n .references(() => users.id, { onDelete: \"cascade\" }),\n // Added in better-auth 1.7 (TOTP lockout). Defaults match the plugin's.\n verified: boolean(\"verified\").default(true),\n failedVerificationCount: integer(\"failedVerificationCount\").default(0),\n lockedUntil: timestamp(\"lockedUntil\"),\n});\n\n// Better-auth's passkey plugin — one row per registered WebAuthn credential.\nexport const passkeys = pgTable(\"passkey\", {\n id: text(\"id\").notNull().primaryKey().$defaultFn(() => createId()),\n name: text(\"name\"),\n publicKey: text(\"publicKey\").notNull(),\n userId: text(\"userId\")\n .notNull()\n .references(() => users.id, { onDelete: \"cascade\" }),\n credentialID: text(\"credentialID\").notNull().unique(),\n counter: integer(\"counter\").notNull(),\n deviceType: text(\"deviceType\").notNull(),\n backedUp: boolean(\"backedUp\").notNull(),\n transports: text(\"transports\"),\n createdAt: timestamp(\"createdAt\"),\n aaguid: text(\"aaguid\"),\n});\n\nexport const verifications = pgTable(\"verification\", {\n id: text(\"id\").notNull().primaryKey().$defaultFn(() => createId()),\n identifier: text(\"identifier\").notNull(),\n value: text(\"value\").notNull(),\n expiresAt: timestamp(\"expiresAt\").notNull(),\n createdAt: timestamp(\"createdAt\").notNull().defaultNow(),\n updatedAt: timestamp(\"updatedAt\").notNull().defaultNow(),\n});\n\nexport const activityLogs = pgTable(\"activity_log\", {\n id: text(\"id\").notNull().primaryKey().$defaultFn(() => createId()),\n userId: text(\"userId\")\n .notNull()\n .references(() => users.id, { onDelete: \"cascade\" }),\n action: ActivityActionEnum('action').notNull(),\n description: text('description').notNull(),\n ipAddress: text('ipAddress'),\n userAgent: text('userAgent'),\n metadata: text('metadata'),\n // Populated on 'login' events only (registration + sign-in) — parsed from\n // userAgent/ipAddress at write time so the record stays stable even if\n // the parsing library or geoip database changes later.\n referrerUrl: text('referrerUrl'),\n os: text('os'),\n browser: text('browser'),\n deviceType: text('deviceType'),\n country: text('country'),\n city: text('city'),\n createdAt: timestamp('createdAt').notNull().defaultNow(),\n});\n\n// One row per (user, device fingerprint) pair. Its only job is to answer\n// \"has this account signed in from this device before?\" — no matching row ⇒\n// send a new-device alert (see app/api/device-check/route.ts). visitorId is\n// FingerprintJS's identifier, computed client-side in\n// components/auth/deviceCheck.tsx. UA+IP are deliberately NOT the signal here:\n// they churn constantly on mobile and would alert on every other login.\nexport const deviceFingerprints = pgTable(\"device_fingerprint\", {\n id: text(\"id\").notNull().primaryKey().$defaultFn(() => createId()),\n userId: text(\"userId\")\n .notNull()\n .references(() => users.id, { onDelete: \"cascade\" }),\n visitorId: text(\"visitorId\").notNull(),\n // Context captured at first-seen time — for the admin panel's \"New devices\"\n // card and the alert email. Not part of the new-vs-known decision (that's\n // visitorId only), just so an admin can eyeball whether a sign-in looks like\n // a hijack. Nullable: an old row predates these columns, or geoip missed.\n ipAddress: text(\"ipAddress\"),\n userAgent: text(\"userAgent\"),\n country: text(\"country\"),\n city: text(\"city\"),\n createdAt: timestamp(\"createdAt\").notNull().defaultNow(),\n}, (table) => [\n unique(\"device_fingerprint_user_visitor\").on(table.userId, table.visitorId),\n]);\n\n// Per-device abuse throttle for /sign-in/email and /sign-up/email — one\n// fixed-window counter row per FingerprintJS visitorId (sent by the auth\n// forms as the x-device-fingerprint header; see lib/auth-throttle.ts).\n// Keyed by device, not IP: IP is shared behind corporate NAT / CGNAT / VPN,\n// so an IP counter throttles coworkers together while missing an abuser who\n// rotates IPs. Rows whose window has elapsed are dead weight but harmless\n// (reset on the next attempt) — prune on a cron if the table ever grows.\nexport const authThrottle = pgTable(\"auth_throttle\", {\n fingerprint: text(\"fingerprint\").notNull().primaryKey(),\n count: integer(\"count\").notNull().default(0),\n windowStart: timestamp(\"windowStart\").notNull().defaultNow(),\n // Blockable data points, overwritten on every attempt (last-write-wins) so\n // the admin panel can show who to block — by IP or by fingerprint — with no\n // join and no per-attempt history table. windowStart anchors the counter's\n // window; lastAttemptAt is just \"most recently seen\".\n lastAttemptAt: timestamp(\"lastAttemptAt\").notNull().defaultNow(),\n ipAddress: text(\"ipAddress\"),\n userAgent: text(\"userAgent\"),\n lastEmail: text(\"lastEmail\"),\n lastKind: text(\"lastKind\"), // 'signin' | 'signup'\n});\n\n// Singleton row (id is always 'default') holding admin-editable settings for\n// config that used to be env-var-only. See lib/settings-queries.ts for the\n// cached read path and lazy-seed-from-env behavior.\nexport const systemSettings = pgTable(\"system_settings\", {\n id: text(\"id\").primaryKey().default(\"default\"),\n maintenanceMode: boolean(\"maintenanceMode\").notNull().default(false),\n maintenanceMessage: text(\"maintenanceMessage\"),\n // Path prefixes that stay reachable while maintenance mode is on (see\n // lib/maintenance.ts — a few auth paths are always open on top of these).\n maintenanceExemptPaths: text(\"maintenanceExemptPaths\").array().notNull().default(DEFAULT_MAINTENANCE_EXEMPT_PATHS),\n authEnableGoogle: boolean(\"authEnableGoogle\").notNull().default(true),\n authEnableEmailPassword: boolean(\"authEnableEmailPassword\").notNull().default(true),\n authEnableOneTap: boolean(\"authEnableOneTap\").notNull().default(false),\n enableSessionRevocation: boolean(\"enableSessionRevocation\").notNull().default(true),\n updatedAt: timestamp(\"updatedAt\").notNull().defaultNow(),\n // Who last changed a setting. This row doesn't \"belong\" to that admin the\n // way sessions/activityLogs belong to their user, so deleting that admin's\n // account should null this out rather than cascade-delete the singleton row.\n updatedBy: text(\"updatedBy\").references(() => users.id, { onDelete: \"set null\" }),\n}, (table) => [\n check(\"system_settings_singleton\", sql`${table.id} = 'default'`),\n]);","// Pure path logic for maintenance mode — no DB / server-only imports, so it\n// can run in proxy.ts and in the admin form alike.\n\n// Default for the admin-editable exempt list: the public marketing pages.\nexport const DEFAULT_MAINTENANCE_EXEMPT_PATHS = [\n \"/\",\n \"/features\",\n \"/changelog\",\n \"/changelog-template\",\n \"/privacy\",\n \"/terms\",\n]\n\n// Always reachable, whatever the admin saves — otherwise an admin could\n// lock themselves out of the sign-in flow needed to turn maintenance off.\nexport const ALWAYS_OPEN_PATHS = [\n \"/maintenance\",\n \"/login\", // includes /login/2fa\n \"/forgot-password\",\n \"/reset-password\",\n \"/api/auth\",\n \"/api/settings/public\", // login page reads the One Tap flag from here\n]\n\n// `/foo` matches `/foo` and `/foo/...` but not `/foobar`; `/` matches only `/`.\nfunction matches(pathname: string, prefix: string) {\n const p = prefix.length > 1 ? prefix.replace(/\\/+$/, \"\") : prefix\n return pathname === p || (p !== \"/\" && pathname.startsWith(p + \"/\"))\n}\n\nexport function isMaintenanceExempt(pathname: string, exemptPaths: string[]) {\n return [...ALWAYS_OPEN_PATHS, ...exemptPaths].some((p) => matches(pathname, p))\n}\n","// No \"server-only\" guard here — this schema is also imported by scripts that\n// run outside Next.js's bundler (e.g. scripts/doctor.ts), where that guard\n// throws unconditionally. config/env.ts re-exports the parsed, guarded result\n// for app code; import this file directly only from non-Next scripts.\nimport { z } from 'zod';\n\nexport const envSchema = z.object({\n NODE_ENV: z.enum(['development', 'production', 'test']).default('development'),\n PORT: z.string().default('3000').transform(Number),\n DATABASE_URL: z.string().default(''),\n RESEND_API_KEY: z.string().default(''),\n // Sender for every transactional email. Must be on a domain verified in\n // your Resend account.\n EMAIL_FROM: z.string().default('Acme <onboarding@xavierkhew.xyz>'),\n // Display name for this deployment — shown as the authenticator-app label\n // (2FA issuer) and the passkey relying-party name. Consuming projects set\n // this in their own .env; defaults to the template name.\n APP_NAME: z.string().default('Next.js Template'),\n // Where the dashboard feedback form delivers (app/api/feedback). Set either\n // or both; with neither, the Feedback button is hidden. FEEDBACK_EMAIL takes\n // a comma-separated list and sends through Resend (needs RESEND_API_KEY).\n FEEDBACK_DISCORD_WEBHOOK_URL: z.string().default(''),\n FEEDBACK_EMAIL: z.string().default(''),\n // Seed-only: the live value lives in the system_settings DB row (see\n // lib/settings-queries.ts) once it's been seeded on first read, and is\n // changed from the admin System Settings page, not by editing this var.\n ENABLE_SESSION_REVOCATION: z.string().default('true').transform(val => val === 'true'),\n AUTH_ENABLE_GOOGLE: z.string().default('true').transform(val => val === 'true'),\n AUTH_ENABLE_EMAIL_PASSWORD: z.string().default('true').transform(val => val === 'true'),\n AUTH_ENABLE_ONE_TAP: z.string().default('false').transform(val => val === 'true'),\n DB_DRIVER: z.enum(['pg', 'neon']).default('pg'),\n // Only relevant when DB_DRIVER=pg — whether the target Postgres server has SSL\n // enabled. Self-hosted Postgres (e.g. via Docker/Dokploy) typically does not.\n DATABASE_SSL: z.string().default('false').transform(val => val === 'true'),\n});\n","import \"server-only\";\nimport { envSchema } from './env-schema';\n\nconst results = envSchema.safeParse(process.env);\n\nif (results.success && (results.data.NODE_ENV === 'development')) {\n console.log('Environment variables loaded:', Object.keys(results.data));\n}\n\nif (!results.success) {\n console.error('Invalid environment variables:', results.error.format());\n throw new Error('Invalid environment variables');\n}\n\nexport const config = results.data;\n","import 'server-only';\n\n// neon-http driver for production\nimport { neon } from '@neondatabase/serverless';\nimport { drizzle } from 'drizzle-orm/neon-http';\n\n// postgres driver for local development\nimport { drizzle as drizzlePg } from 'drizzle-orm/node-postgres';\nimport { Pool } from 'pg';\n\nimport * as schema from './schema';\nimport { config } from '../../config/env';\n\n// Import the query builder\nimport { PostgresJsDatabase } from 'drizzle-orm/postgres-js';\nimport { NeonHttpDatabase } from 'drizzle-orm/neon-http';\n\ntype DrizzleNeon = NeonHttpDatabase<typeof schema>;\ntype DrizzlePg = PostgresJsDatabase<typeof schema>;\n\nlet db: DrizzleNeon | DrizzlePg\n\n\nif (config.DB_DRIVER === 'pg') {\n const pool = new Pool({\n connectionString: config.DATABASE_URL!,\n ssl: config.DATABASE_SSL ? { rejectUnauthorized: false } : undefined,\n });\n db = drizzlePg(pool, {\n schema,\n logger: true,\n });\n\n console.log('🟡 Using PostgreSQL (pg) driver');\n\n} else if (config.DB_DRIVER === 'neon') {\n const sql = neon(config.DATABASE_URL!);\n db = drizzle(sql, { schema, logger: true });\n\n console.log('🟢 Using Neon (HTTP) driver');\n\n} else {\n console.log('Database connection not established, unknown DB_DRIVER:', config.DB_DRIVER);\n}\n// Note: In production, you might want to disable logging or use a more sophisticated logger.\n\nexport { db }","import \"server-only\"\nimport { db } from \"../server/db\"\nimport { activityLogs } from \"../server/db/schema\"\n\nexport type ActivityAction =\n | 'login'\n | 'logout'\n | 'login_failed'\n | 'password_changed'\n | 'email_changed'\n | 'profile_updated'\n | 'session_revoked'\n | 'user_deleted'\n | 'user_banned'\n | 'user_unbanned'\n | 'role_changed'\n | 'impersonation_started'\n | 'impersonation_stopped'\n | 'settings_changed'\n | 'passkey_added'\n | 'passkey_removed'\n\ninterface LogActivityParams {\n userId: string\n action: ActivityAction\n description: string\n ipAddress?: string | null\n userAgent?: string | null\n metadata?: Record<string, any>\n // Populated on 'login' events only — see server/auth.ts's session.create hook.\n referrerUrl?: string | null\n os?: string | null\n browser?: string | null\n deviceType?: string | null\n country?: string | null\n city?: string | null\n}\n\nexport async function logActivity({\n userId,\n action,\n description,\n ipAddress,\n userAgent,\n metadata,\n referrerUrl,\n os,\n browser,\n deviceType,\n country,\n city,\n}: LogActivityParams) {\n try {\n await db.insert(activityLogs).values({\n userId,\n action,\n description,\n ipAddress: ipAddress || null,\n userAgent: userAgent || null,\n metadata: metadata ? JSON.stringify(metadata) : null,\n referrerUrl: referrerUrl || null,\n os: os || null,\n browser: browser || null,\n deviceType: deviceType || null,\n country: country || null,\n city: city || null,\n })\n } catch (error) {\n console.error('Failed to log activity:', error)\n // Don't throw - activity logging should not break the app\n }\n}\n"]}
|
package/dist/activity/logger.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
export { logActivity } from '../chunk-
|
|
2
|
-
import '../chunk-
|
|
1
|
+
export { logActivity } from '../chunk-G3B7NC5B.mjs';
|
|
2
|
+
import '../chunk-LKPF3JCV.mjs';
|
|
3
3
|
import '../chunk-YPKOQOTA.mjs';
|
|
4
|
-
import '../chunk-
|
|
4
|
+
import '../chunk-TMJYAOC2.mjs';
|
|
5
5
|
import '../chunk-3465SI5N.mjs';
|
|
6
6
|
//# sourceMappingURL=logger.mjs.map
|
|
7
7
|
//# sourceMappingURL=logger.mjs.map
|
package/dist/activity/queries.js
CHANGED
|
@@ -227,6 +227,11 @@ var envSchema = zod.z.object({
|
|
|
227
227
|
// (2FA issuer) and the passkey relying-party name. Consuming projects set
|
|
228
228
|
// this in their own .env; defaults to the template name.
|
|
229
229
|
APP_NAME: zod.z.string().default("Next.js Template"),
|
|
230
|
+
// Where the dashboard feedback form delivers (app/api/feedback). Set either
|
|
231
|
+
// or both; with neither, the Feedback button is hidden. FEEDBACK_EMAIL takes
|
|
232
|
+
// a comma-separated list and sends through Resend (needs RESEND_API_KEY).
|
|
233
|
+
FEEDBACK_DISCORD_WEBHOOK_URL: zod.z.string().default(""),
|
|
234
|
+
FEEDBACK_EMAIL: zod.z.string().default(""),
|
|
230
235
|
// Seed-only: the live value lives in the system_settings DB row (see
|
|
231
236
|
// lib/settings-queries.ts) once it's been seeded on first read, and is
|
|
232
237
|
// changed from the admin System Settings page, not by editing this var.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../server/db/schema.ts","../../lib/maintenance.ts","../../config/env-schema.ts","../../config/env.ts","../../server/db/index.ts","../../lib/activity-queries.ts"],"names":["pgEnum","pgTable","text","createId","boolean","timestamp","integer","unique","check","sql","z","Pool","drizzlePg","neon","drizzle","eq","desc","and","inArray","or","like","gte","lte"],"mappings":";;;;;;;;;;;;;;;;;;;AAAA,IAAA,cAAA,GAAA,EAAA;AAAA,QAAA,CAAA,cAAA,EAAA;AAAA,EAAA,kBAAA,EAAA,MAAA,kBAAA;AAAA,EAAA,QAAA,EAAA,MAAA,QAAA;AAAA,EAAA,QAAA,EAAA,MAAA,QAAA;AAAA,EAAA,YAAA,EAAA,MAAA,YAAA;AAAA,EAAA,YAAA,EAAA,MAAA,YAAA;AAAA,EAAA,kBAAA,EAAA,MAAA,kBAAA;AAAA,EAAA,QAAA,EAAA,MAAA,QAAA;AAAA,EAAA,QAAA,EAAA,MAAA,QAAA;AAAA,EAAA,cAAA,EAAA,MAAA,cAAA;AAAA,EAAA,UAAA,EAAA,MAAA,UAAA;AAAA,EAAA,KAAA,EAAA,MAAA,KAAA;AAAA,EAAA,aAAA,EAAA,MAAA;AAAA,CAAA,CAAA;;;ACIO,IAAM,gCAAA,GAAmC;AAAA,EAC9C,GAAA;AAAA,EACA,WAAA;AAAA,EACA,YAAA;AAAA,EACA,qBAAA;AAAA,EACA,UAAA;AAAA,EACA;AACF,CAAA;;;ADJO,IAAM,WAAWA,aAAA,CAAO,OAAA,EAAS,CAAC,MAAA,EAAQ,OAAA,EAAS,QAAQ,CAAC,CAAA;AAE5D,IAAM,kBAAA,GAAqBA,cAAO,kBAAA,EAAoB;AAAA,EAC3D,OAAA;AAAA,EACA,QAAA;AAAA,EACA,cAAA;AAAA,EACA,kBAAA;AAAA,EACA,eAAA;AAAA,EACA,iBAAA;AAAA,EACA,iBAAA;AAAA,EACA,cAAA;AAAA,EACA,aAAA;AAAA,EACA,eAAA;AAAA,EACA,cAAA;AAAA,EACA,uBAAA;AAAA,EACA,uBAAA;AAAA,EACA,kBAAA;AAAA,EACA,eAAA;AAAA,EACA;AACF,CAAC,CAAA;AAEM,IAAM,KAAA,GAAQC,eAAQ,MAAA,EAAQ;AAAA,EACnC,EAAA,EAAIC,WAAA,CAAK,IAAI,CAAA,CAAE,OAAA,EAAQ,CAAE,UAAA,EAAW,CAAE,UAAA,CAAW,MAAMC,cAAA,EAAU,CAAA;AAAA,EACjE,IAAA,EAAMD,WAAA,CAAK,MAAM,CAAA,CAAE,OAAA,EAAQ;AAAA,EAC3B,OAAOA,WAAA,CAAK,OAAO,CAAA,CAAE,OAAA,GAAU,MAAA,EAAO;AAAA,EACtC,QAAA,EAAUA,YAAK,UAAU,CAAA;AAAA,EACzB,eAAeE,cAAA,CAAQ,eAAe,EAAE,OAAA,EAAQ,CAAE,QAAQ,KAAK,CAAA;AAAA,EAC/D,KAAA,EAAOF,YAAK,OAAO,CAAA;AAAA,EACnB,MAAM,QAAA,CAAS,OAAO,EAAE,OAAA,CAAQ,MAAM,EAAE,OAAA,EAAQ;AAAA,EAChD,QAAQE,cAAA,CAAQ,QAAQ,EAAE,OAAA,CAAQ,KAAK,EAAE,OAAA,EAAQ;AAAA,EACjD,QAAA,EAAUC,iBAAU,UAAU,CAAA;AAAA,EAC9B,YAAA,EAAcH,YAAK,cAAc,CAAA;AAAA;AAAA;AAAA;AAAA,EAIjC,UAAA,EAAYG,iBAAU,YAAY,CAAA;AAAA,EAClC,WAAA,EAAaA,iBAAU,aAAa,CAAA;AAAA,EACpC,YAAA,EAAcA,iBAAU,cAAc,CAAA;AAAA;AAAA;AAAA;AAAA,EAItC,kBAAkBD,cAAA,CAAQ,kBAAkB,EAAE,OAAA,EAAQ,CAAE,QAAQ,KAAK,CAAA;AAAA,EACrE,WAAWC,gBAAA,CAAU,WAAW,CAAA,CAAE,OAAA,GAAU,UAAA,EAAW;AAAA,EACvD,WAAWA,gBAAA,CAAU,WAAW,CAAA,CAAE,OAAA,GAAU,UAAA;AAC9C,CAAC,CAAA;AAEM,IAAM,QAAA,GAAWJ,eAAQ,SAAA,EAAW;AAAA,EACzC,EAAA,EAAIC,WAAA,CAAK,IAAI,CAAA,CAAE,OAAA,EAAQ,CAAE,UAAA,EAAW,CAAE,UAAA,CAAW,MAAMC,cAAA,EAAU,CAAA;AAAA,EACjE,SAAA,EAAWE,gBAAA,CAAU,WAAW,CAAA,CAAE,OAAA,EAAQ;AAAA,EAC1C,OAAOH,WAAA,CAAK,OAAO,CAAA,CAAE,OAAA,GAAU,MAAA,EAAO;AAAA,EACtC,WAAWG,gBAAA,CAAU,WAAW,CAAA,CAAE,OAAA,GAAU,UAAA,EAAW;AAAA,EACvD,WAAWA,gBAAA,CAAU,WAAW,CAAA,CAAE,OAAA,GAAU,UAAA,EAAW;AAAA,EACvD,SAAA,EAAWH,YAAK,WAAW,CAAA;AAAA,EAC3B,SAAA,EAAWA,YAAK,WAAW,CAAA;AAAA,EAC3B,MAAA,EAAQA,WAAA,CAAK,QAAQ,CAAA,CAClB,OAAA,EAAQ,CACR,UAAA,CAAW,MAAM,KAAA,CAAM,EAAA,EAAI,EAAE,QAAA,EAAU,WAAW,CAAA;AAAA;AAAA;AAAA,EAGrD,cAAA,EAAgBA,YAAK,gBAAgB;AACvC,CAAC,CAAA;AAEM,IAAM,QAAA,GAAWD,eAAQ,SAAA,EAAW;AAAA,EACzC,EAAA,EAAIC,WAAA,CAAK,IAAI,CAAA,CAAE,OAAA,EAAQ,CAAE,UAAA,EAAW,CAAE,UAAA,CAAW,MAAMC,cAAA,EAAU,CAAA;AAAA,EACjE,SAAA,EAAWD,WAAA,CAAK,WAAW,CAAA,CAAE,OAAA,EAAQ;AAAA,EACrC,UAAA,EAAYA,WAAA,CAAK,YAAY,CAAA,CAAE,OAAA,EAAQ;AAAA,EACvC,MAAA,EAAQA,WAAA,CAAK,QAAQ,CAAA,CAClB,OAAA,EAAQ,CACR,UAAA,CAAW,MAAM,KAAA,CAAM,EAAA,EAAI,EAAE,QAAA,EAAU,WAAW,CAAA;AAAA,EACrD,WAAA,EAAaA,YAAK,aAAa,CAAA;AAAA,EAC/B,YAAA,EAAcA,YAAK,cAAc,CAAA;AAAA,EACjC,OAAA,EAASA,YAAK,SAAS,CAAA;AAAA,EACvB,oBAAA,EAAsBG,iBAAU,sBAAsB,CAAA;AAAA,EACtD,qBAAA,EAAuBA,iBAAU,uBAAuB,CAAA;AAAA,EACxD,KAAA,EAAOH,YAAK,OAAO,CAAA;AAAA,EACnB,QAAA,EAAUA,YAAK,UAAU,CAAA;AAAA,EACzB,WAAWG,gBAAA,CAAU,WAAW,CAAA,CAAE,OAAA,GAAU,UAAA,EAAW;AAAA,EACvD,WAAWA,gBAAA,CAAU,WAAW,CAAA,CAAE,OAAA,GAAU,UAAA;AAC9C,CAAC,CAAA;AAMM,IAAM,UAAA,GAAaJ,eAAQ,WAAA,EAAa;AAAA,EAC7C,EAAA,EAAIC,WAAA,CAAK,IAAI,CAAA,CAAE,OAAA,EAAQ,CAAE,UAAA,EAAW,CAAE,UAAA,CAAW,MAAMC,cAAA,EAAU,CAAA;AAAA,EACjE,MAAA,EAAQD,WAAA,CAAK,QAAQ,CAAA,CAAE,OAAA,EAAQ;AAAA,EAC/B,WAAA,EAAaA,WAAA,CAAK,aAAa,CAAA,CAAE,OAAA,EAAQ;AAAA,EACzC,MAAA,EAAQA,WAAA,CAAK,QAAQ,CAAA,CAClB,OAAA,EAAQ,CACR,UAAA,CAAW,MAAM,KAAA,CAAM,EAAA,EAAI,EAAE,QAAA,EAAU,WAAW,CAAA;AAAA;AAAA,EAErD,QAAA,EAAUE,cAAA,CAAQ,UAAU,CAAA,CAAE,QAAQ,IAAI,CAAA;AAAA,EAC1C,uBAAA,EAAyBE,cAAA,CAAQ,yBAAyB,CAAA,CAAE,QAAQ,CAAC,CAAA;AAAA,EACrE,WAAA,EAAaD,iBAAU,aAAa;AACtC,CAAC,CAAA;AAGM,IAAM,QAAA,GAAWJ,eAAQ,SAAA,EAAW;AAAA,EACzC,EAAA,EAAIC,WAAA,CAAK,IAAI,CAAA,CAAE,OAAA,EAAQ,CAAE,UAAA,EAAW,CAAE,UAAA,CAAW,MAAMC,cAAA,EAAU,CAAA;AAAA,EACjE,IAAA,EAAMD,YAAK,MAAM,CAAA;AAAA,EACjB,SAAA,EAAWA,WAAA,CAAK,WAAW,CAAA,CAAE,OAAA,EAAQ;AAAA,EACrC,MAAA,EAAQA,WAAA,CAAK,QAAQ,CAAA,CAClB,OAAA,EAAQ,CACR,UAAA,CAAW,MAAM,KAAA,CAAM,EAAA,EAAI,EAAE,QAAA,EAAU,WAAW,CAAA;AAAA,EACrD,cAAcA,WAAA,CAAK,cAAc,CAAA,CAAE,OAAA,GAAU,MAAA,EAAO;AAAA,EACpD,OAAA,EAASI,cAAA,CAAQ,SAAS,CAAA,CAAE,OAAA,EAAQ;AAAA,EACpC,UAAA,EAAYJ,WAAA,CAAK,YAAY,CAAA,CAAE,OAAA,EAAQ;AAAA,EACvC,QAAA,EAAUE,cAAA,CAAQ,UAAU,CAAA,CAAE,OAAA,EAAQ;AAAA,EACtC,UAAA,EAAYF,YAAK,YAAY,CAAA;AAAA,EAC7B,SAAA,EAAWG,iBAAU,WAAW,CAAA;AAAA,EAChC,MAAA,EAAQH,YAAK,QAAQ;AACvB,CAAC,CAAA;AAEM,IAAM,aAAA,GAAgBD,eAAQ,cAAA,EAAgB;AAAA,EACnD,EAAA,EAAIC,WAAA,CAAK,IAAI,CAAA,CAAE,OAAA,EAAQ,CAAE,UAAA,EAAW,CAAE,UAAA,CAAW,MAAMC,cAAA,EAAU,CAAA;AAAA,EACjE,UAAA,EAAYD,WAAA,CAAK,YAAY,CAAA,CAAE,OAAA,EAAQ;AAAA,EACvC,KAAA,EAAOA,WAAA,CAAK,OAAO,CAAA,CAAE,OAAA,EAAQ;AAAA,EAC7B,SAAA,EAAWG,gBAAA,CAAU,WAAW,CAAA,CAAE,OAAA,EAAQ;AAAA,EAC1C,WAAWA,gBAAA,CAAU,WAAW,CAAA,CAAE,OAAA,GAAU,UAAA,EAAW;AAAA,EACvD,WAAWA,gBAAA,CAAU,WAAW,CAAA,CAAE,OAAA,GAAU,UAAA;AAC9C,CAAC,CAAA;AAEM,IAAM,YAAA,GAAeJ,eAAQ,cAAA,EAAgB;AAAA,EAClD,EAAA,EAAIC,WAAA,CAAK,IAAI,CAAA,CAAE,OAAA,EAAQ,CAAE,UAAA,EAAW,CAAE,UAAA,CAAW,MAAMC,cAAA,EAAU,CAAA;AAAA,EACjE,MAAA,EAAQD,WAAA,CAAK,QAAQ,CAAA,CAClB,OAAA,EAAQ,CACR,UAAA,CAAW,MAAM,KAAA,CAAM,EAAA,EAAI,EAAE,QAAA,EAAU,WAAW,CAAA;AAAA,EACrD,MAAA,EAAQ,kBAAA,CAAmB,QAAQ,CAAA,CAAE,OAAA,EAAQ;AAAA,EAC7C,WAAA,EAAaA,WAAA,CAAK,aAAa,CAAA,CAAE,OAAA,EAAQ;AAAA,EACzC,SAAA,EAAWA,YAAK,WAAW,CAAA;AAAA,EAC3B,SAAA,EAAWA,YAAK,WAAW,CAAA;AAAA,EAC3B,QAAA,EAAUA,YAAK,UAAU,CAAA;AAAA;AAAA;AAAA;AAAA,EAIzB,WAAA,EAAaA,YAAK,aAAa,CAAA;AAAA,EAC/B,EAAA,EAAIA,YAAK,IAAI,CAAA;AAAA,EACb,OAAA,EAASA,YAAK,SAAS,CAAA;AAAA,EACvB,UAAA,EAAYA,YAAK,YAAY,CAAA;AAAA,EAC7B,OAAA,EAASA,YAAK,SAAS,CAAA;AAAA,EACvB,IAAA,EAAMA,YAAK,MAAM,CAAA;AAAA,EACjB,WAAWG,gBAAA,CAAU,WAAW,CAAA,CAAE,OAAA,GAAU,UAAA;AAC9C,CAAC,CAAA;AAQM,IAAM,kBAAA,GAAqBJ,eAAQ,oBAAA,EAAsB;AAAA,EAC9D,EAAA,EAAIC,WAAA,CAAK,IAAI,CAAA,CAAE,OAAA,EAAQ,CAAE,UAAA,EAAW,CAAE,UAAA,CAAW,MAAMC,cAAA,EAAU,CAAA;AAAA,EACjE,MAAA,EAAQD,WAAA,CAAK,QAAQ,CAAA,CAClB,OAAA,EAAQ,CACR,UAAA,CAAW,MAAM,KAAA,CAAM,EAAA,EAAI,EAAE,QAAA,EAAU,WAAW,CAAA;AAAA,EACrD,SAAA,EAAWA,WAAA,CAAK,WAAW,CAAA,CAAE,OAAA,EAAQ;AAAA;AAAA;AAAA;AAAA;AAAA,EAKrC,SAAA,EAAWA,YAAK,WAAW,CAAA;AAAA,EAC3B,SAAA,EAAWA,YAAK,WAAW,CAAA;AAAA,EAC3B,OAAA,EAASA,YAAK,SAAS,CAAA;AAAA,EACvB,IAAA,EAAMA,YAAK,MAAM,CAAA;AAAA,EACjB,WAAWG,gBAAA,CAAU,WAAW,CAAA,CAAE,OAAA,GAAU,UAAA;AAC9C,CAAA,EAAG,CAAC,KAAA,KAAU;AAAA,EACZE,cAAO,iCAAiC,CAAA,CAAE,GAAG,KAAA,CAAM,MAAA,EAAQ,MAAM,SAAS;AAC5E,CAAC,CAAA;AASM,IAAM,YAAA,GAAeN,eAAQ,eAAA,EAAiB;AAAA,EACnD,aAAaC,WAAA,CAAK,aAAa,CAAA,CAAE,OAAA,GAAU,UAAA,EAAW;AAAA,EACtD,OAAOI,cAAA,CAAQ,OAAO,EAAE,OAAA,EAAQ,CAAE,QAAQ,CAAC,CAAA;AAAA,EAC3C,aAAaD,gBAAA,CAAU,aAAa,CAAA,CAAE,OAAA,GAAU,UAAA,EAAW;AAAA;AAAA;AAAA;AAAA;AAAA,EAK3D,eAAeA,gBAAA,CAAU,eAAe,CAAA,CAAE,OAAA,GAAU,UAAA,EAAW;AAAA,EAC/D,SAAA,EAAWH,YAAK,WAAW,CAAA;AAAA,EAC3B,SAAA,EAAWA,YAAK,WAAW,CAAA;AAAA,EAC3B,SAAA,EAAWA,YAAK,WAAW,CAAA;AAAA,EAC3B,QAAA,EAAUA,YAAK,UAAU;AAAA;AAC3B,CAAC,CAAA;AAKM,IAAM,cAAA,GAAiBD,eAAQ,iBAAA,EAAmB;AAAA,EACvD,IAAIC,WAAA,CAAK,IAAI,EAAE,UAAA,EAAW,CAAE,QAAQ,SAAS,CAAA;AAAA,EAC7C,iBAAiBE,cAAA,CAAQ,iBAAiB,EAAE,OAAA,EAAQ,CAAE,QAAQ,KAAK,CAAA;AAAA,EACnE,kBAAA,EAAoBF,YAAK,oBAAoB,CAAA;AAAA;AAAA;AAAA,EAG7C,sBAAA,EAAwBA,YAAK,wBAAwB,CAAA,CAAE,OAAM,CAAE,OAAA,EAAQ,CAAE,OAAA,CAAQ,gCAAgC,CAAA;AAAA,EACjH,kBAAkBE,cAAA,CAAQ,kBAAkB,EAAE,OAAA,EAAQ,CAAE,QAAQ,IAAI,CAAA;AAAA,EACpE,yBAAyBA,cAAA,CAAQ,yBAAyB,EAAE,OAAA,EAAQ,CAAE,QAAQ,IAAI,CAAA;AAAA,EAClF,kBAAkBA,cAAA,CAAQ,kBAAkB,EAAE,OAAA,EAAQ,CAAE,QAAQ,KAAK,CAAA;AAAA,EACrE,yBAAyBA,cAAA,CAAQ,yBAAyB,EAAE,OAAA,EAAQ,CAAE,QAAQ,IAAI,CAAA;AAAA,EAClF,WAAWC,gBAAA,CAAU,WAAW,CAAA,CAAE,OAAA,GAAU,UAAA,EAAW;AAAA;AAAA;AAAA;AAAA,EAIvD,SAAA,EAAWH,WAAA,CAAK,WAAW,CAAA,CAAE,UAAA,CAAW,MAAM,KAAA,CAAM,EAAA,EAAI,EAAE,QAAA,EAAU,UAAA,EAAY;AAClF,CAAA,EAAG,CAAC,KAAA,KAAU;AAAA,EACZM,YAAA,CAAM,2BAAA,EAA6BC,cAAA,CAAA,EAAM,KAAA,CAAM,EAAE,CAAA,YAAA,CAAc;AACjE,CAAC,CAAA;AEtNM,IAAM,SAAA,GAAYC,MAAE,MAAA,CAAO;AAAA,EAChC,QAAA,EAAUA,KAAA,CAAE,IAAA,CAAK,CAAC,aAAA,EAAe,cAAc,MAAM,CAAC,CAAA,CAAE,OAAA,CAAQ,aAAa,CAAA;AAAA,EAC7E,IAAA,EAAMA,MAAE,MAAA,EAAO,CAAE,QAAQ,MAAM,CAAA,CAAE,UAAU,MAAM,CAAA;AAAA,EACjD,YAAA,EAAcA,KAAA,CAAE,MAAA,EAAO,CAAE,QAAQ,EAAE,CAAA;AAAA,EACnC,cAAA,EAAgBA,KAAA,CAAE,MAAA,EAAO,CAAE,QAAQ,EAAE,CAAA;AAAA;AAAA;AAAA,EAGrC,UAAA,EAAYA,KAAA,CAAE,MAAA,EAAO,CAAE,QAAQ,kCAAkC,CAAA;AAAA;AAAA;AAAA;AAAA,EAIjE,QAAA,EAAUA,KAAA,CAAE,MAAA,EAAO,CAAE,QAAQ,kBAAkB,CAAA;AAAA;AAAA;AAAA;AAAA,EAI/C,yBAAA,EAA2BA,KAAA,CAAE,MAAA,EAAO,CAAE,OAAA,CAAQ,MAAM,CAAA,CAAE,SAAA,CAAU,CAAA,GAAA,KAAO,GAAA,KAAQ,MAAM,CAAA;AAAA,EACrF,kBAAA,EAAoBA,KAAA,CAAE,MAAA,EAAO,CAAE,OAAA,CAAQ,MAAM,CAAA,CAAE,SAAA,CAAU,CAAA,GAAA,KAAO,GAAA,KAAQ,MAAM,CAAA;AAAA,EAC9E,0BAAA,EAA4BA,KAAA,CAAE,MAAA,EAAO,CAAE,OAAA,CAAQ,MAAM,CAAA,CAAE,SAAA,CAAU,CAAA,GAAA,KAAO,GAAA,KAAQ,MAAM,CAAA;AAAA,EACtF,mBAAA,EAAqBA,KAAA,CAAE,MAAA,EAAO,CAAE,OAAA,CAAQ,OAAO,CAAA,CAAE,SAAA,CAAU,CAAA,GAAA,KAAO,GAAA,KAAQ,MAAM,CAAA;AAAA,EAChF,SAAA,EAAWA,MAAE,IAAA,CAAK,CAAC,MAAM,MAAM,CAAC,CAAA,CAAE,OAAA,CAAQ,IAAI,CAAA;AAAA;AAAA;AAAA,EAG9C,YAAA,EAAcA,KAAA,CAAE,MAAA,EAAO,CAAE,OAAA,CAAQ,OAAO,CAAA,CAAE,SAAA,CAAU,CAAA,GAAA,KAAO,GAAA,KAAQ,MAAM;AAC3E,CAAC,CAAA;;;AC1BD,IAAM,OAAA,GAAU,SAAA,CAAU,SAAA,CAAU,OAAA,CAAQ,GAAG,CAAA;AAE/C,IAAI,OAAA,CAAQ,OAAA,IAAY,OAAA,CAAQ,IAAA,CAAK,aAAa,aAAA,EAAgB;AAChE,EAAA,OAAA,CAAQ,IAAI,+BAAA,EAAiC,MAAA,CAAO,IAAA,CAAK,OAAA,CAAQ,IAAI,CAAC,CAAA;AACxE;AAEA,IAAI,CAAC,QAAQ,OAAA,EAAS;AACpB,EAAA,OAAA,CAAQ,KAAA,CAAM,gCAAA,EAAkC,OAAA,CAAQ,KAAA,CAAM,QAAQ,CAAA;AACtE,EAAA,MAAM,IAAI,MAAM,+BAA+B,CAAA;AACjD;AAEO,IAAM,SAAS,OAAA,CAAQ,IAAA;;;ACM9B,IAAI,EAAA;AAGJ,IAAI,MAAA,CAAO,cAAc,IAAA,EAAM;AAC7B,EAAA,MAAM,IAAA,GAAO,IAAIC,OAAA,CAAK;AAAA,IACpB,kBAAkB,MAAA,CAAO,YAAA;AAAA,IACzB,KAAK,MAAA,CAAO,YAAA,GAAe,EAAE,kBAAA,EAAoB,OAAM,GAAI;AAAA,GAC5D,CAAA;AACD,EAAA,EAAA,GAAKC,qBAAU,IAAA,EAAM;AAAA,IACnB,MAAA,EAAA,cAAA;AAAA,IACA,MAAA,EAAQ;AAAA,GACT,CAAA;AAED,EAAA,OAAA,CAAQ,IAAI,wCAAiC,CAAA;AAE/C,CAAA,MAAA,IAAW,MAAA,CAAO,cAAc,MAAA,EAAQ;AACtC,EAAA,MAAMH,IAAAA,GAAMI,eAAA,CAAK,MAAA,CAAO,YAAa,CAAA;AACrC,EAAA,EAAA,GAAKC,iBAAQL,IAAAA,EAAK,EAAE,MAAA,EAAA,cAAA,EAAQ,MAAA,EAAQ,MAAM,CAAA;AAE1C,EAAA,OAAA,CAAQ,IAAI,oCAA6B,CAAA;AAE3C,CAAA,MAAO;AACL,EAAA,OAAA,CAAQ,GAAA,CAAI,yDAAA,EAA2D,MAAA,CAAO,SAAS,CAAA;AACzF;ACtCA,eAAsB,mBAAA,CAAoB,MAAA,EAAgB,KAAA,GAAQ,EAAA,EAAI;AACpE,EAAA,OAAO,MAAM,GACV,MAAA,EAAO,CACP,KAAK,YAAY,CAAA,CACjB,MAAMM,aAAA,CAAG,YAAA,CAAa,QAAQ,MAAM,CAAC,EACrC,OAAA,CAAQC,eAAA,CAAK,aAAa,SAAS,CAAC,CAAA,CACpC,KAAA,CAAM,KAAK,CAAA;AAChB;AAIA,eAAsB,mBAAA,CAAoB,MAAA,EAAgB,KAAA,GAAQ,EAAA,EAAI;AACpE,EAAA,OAAO,MAAM,EAAA,CACV,MAAA,EAAO,CACP,IAAA,CAAK,YAAY,CAAA,CACjB,KAAA,CAAMC,cAAA,CAAIF,aAAA,CAAG,YAAA,CAAa,MAAA,EAAQ,MAAM,CAAA,EAAGA,aAAA,CAAG,YAAA,CAAa,MAAA,EAAQ,OAAO,CAAC,CAAC,CAAA,CAC5E,OAAA,CAAQC,eAAA,CAAK,YAAA,CAAa,SAAS,CAAC,CAAA,CACpC,KAAA,CAAM,KAAK,CAAA;AAChB;AAEA,eAAsB,kBAAA,CAAmB,KAAA,GAAQ,GAAA,EAAK,MAAA,GAAS,CAAA,EAAG;AAChE,EAAA,OAAO,MAAM,GACV,MAAA,CAAO;AAAA,IACN,IAAI,YAAA,CAAa,EAAA;AAAA,IACjB,QAAQ,YAAA,CAAa,MAAA;AAAA,IACrB,aAAa,YAAA,CAAa,WAAA;AAAA,IAC1B,WAAW,YAAA,CAAa,SAAA;AAAA,IACxB,WAAW,YAAA,CAAa,SAAA;AAAA,IACxB,UAAU,YAAA,CAAa,QAAA;AAAA,IACvB,WAAW,YAAA,CAAa,SAAA;AAAA,IACxB,QAAQ,YAAA,CAAa,MAAA;AAAA,IACrB,UAAU,KAAA,CAAM,IAAA;AAAA,IAChB,WAAW,KAAA,CAAM;AAAA,GAClB,CAAA,CACA,IAAA,CAAK,YAAY,CAAA,CACjB,SAAS,KAAA,EAAOD,aAAA,CAAG,YAAA,CAAa,MAAA,EAAQ,KAAA,CAAM,EAAE,CAAC,CAAA,CACjD,OAAA,CAAQC,eAAA,CAAK,YAAA,CAAa,SAAS,CAAC,EACpC,KAAA,CAAM,KAAK,CAAA,CACX,MAAA,CAAO,MAAM,CAAA;AAClB;AAKA,eAAsB,4BAAA,CAA6B,QAAQ,EAAA,EAAI;AAC7D,EAAA,OAAO,MAAM,GACV,MAAA,CAAO;AAAA,IACN,IAAI,YAAA,CAAa,EAAA;AAAA,IACjB,QAAQ,YAAA,CAAa,MAAA;AAAA,IACrB,aAAa,YAAA,CAAa,WAAA;AAAA,IAC1B,WAAW,YAAA,CAAa,SAAA;AAAA,IACxB,SAAS,YAAA,CAAa,MAAA;AAAA,IACtB,WAAW,KAAA,CAAM,IAAA;AAAA,IACjB,YAAY,KAAA,CAAM;AAAA,GACnB,CAAA,CACA,IAAA,CAAK,YAAY,CAAA,CACjB,QAAA,CAAS,KAAA,EAAOD,aAAA,CAAG,YAAA,CAAa,MAAA,EAAQ,KAAA,CAAM,EAAE,CAAC,CAAA,CACjD,KAAA,CAAMG,kBAAA,CAAQ,YAAA,CAAa,MAAA,EAAQ,CAAC,uBAAA,EAAyB,uBAAuB,CAAC,CAAC,CAAA,CACtF,OAAA,CAAQF,eAAA,CAAK,YAAA,CAAa,SAAS,CAAC,CAAA,CACpC,MAAM,KAAK,CAAA;AAChB;AAaA,SAAS,sBAAA,CAAuB;AAAA,EAC9B,MAAA,GAAS,EAAA;AAAA,EACT,MAAA;AAAA,EACA,QAAA;AAAA,EACA;AACF,CAAA,EAA2F;AACzF,EAAA,MAAM,aAAoB,EAAC;AAC3B,EAAA,IAAI,MAAA,CAAO,MAAK,EAAG;AACjB,IAAA,UAAA,CAAW,IAAA;AAAA,MACTG,aAAA;AAAA,QACEC,eAAA,CAAK,KAAA,CAAM,IAAA,EAAM,CAAA,CAAA,EAAI,MAAM,CAAA,CAAA,CAAG,CAAA;AAAA,QAC9BA,eAAA,CAAK,KAAA,CAAM,KAAA,EAAO,CAAA,CAAA,EAAI,MAAM,CAAA,CAAA,CAAG;AAAA;AACjC,KACF;AAAA,EACF;AACA,EAAA,IAAI,MAAA,EAAQ;AACV,IAAA,UAAA,CAAW,IAAA,CAAKL,aAAA,CAAG,YAAA,CAAa,MAAA,EAAQ,MAAM,CAAC,CAAA;AAAA,EACjD;AACA,EAAA,IAAI,QAAA,EAAU;AACZ,IAAA,UAAA,CAAW,IAAA,CAAKM,cAAA,CAAI,YAAA,CAAa,SAAA,EAAW,QAAQ,CAAC,CAAA;AAAA,EACvD;AACA,EAAA,IAAI,MAAA,EAAQ;AACV,IAAA,UAAA,CAAW,IAAA,CAAKC,cAAA,CAAI,YAAA,CAAa,SAAA,EAAW,MAAM,CAAC,CAAA;AAAA,EACrD;AAEA,EAAA,OAAO,WAAW,MAAA,GAAS,CAAA,GAAIL,cAAA,CAAI,GAAG,UAAU,CAAA,GAAI,MAAA;AACtD;AAEA,eAAsB,uBAAA,CAAwB;AAAA,EAC5C,MAAA,GAAS,EAAA;AAAA,EACT,MAAA;AAAA,EACA,QAAA;AAAA,EACA,MAAA;AAAA,EACA,IAAA,GAAO,CAAA;AAAA,EACP,KAAA,GAAQ;AACV,CAAA,EAAuB;AAjHvB,EAAA,IAAA,EAAA,EAAA,EAAA;AAkHE,EAAA,MAAM,MAAA,GAAA,CAAU,OAAO,CAAA,IAAK,KAAA;AAE5B,EAAA,MAAM,QAAQ,sBAAA,CAAuB,EAAE,QAAQ,MAAA,EAAQ,QAAA,EAAU,QAAQ,CAAA;AAEzE,EAAA,MAAM,CAAC,IAAA,EAAM,WAAW,CAAA,GAAI,MAAM,QAAQ,GAAA,CAAI;AAAA,IAC5C,GACG,MAAA,CAAO;AAAA,MACN,IAAI,YAAA,CAAa,EAAA;AAAA,MACjB,QAAQ,YAAA,CAAa,MAAA;AAAA,MACrB,aAAa,YAAA,CAAa,WAAA;AAAA,MAC1B,WAAW,YAAA,CAAa,SAAA;AAAA,MACxB,WAAW,YAAA,CAAa,SAAA;AAAA,MACxB,UAAU,YAAA,CAAa,QAAA;AAAA,MACvB,WAAW,YAAA,CAAa,SAAA;AAAA,MACxB,QAAQ,YAAA,CAAa,MAAA;AAAA,MACrB,UAAU,KAAA,CAAM,IAAA;AAAA,MAChB,WAAW,KAAA,CAAM;AAAA,KAClB,CAAA,CACA,IAAA,CAAK,YAAY,CAAA,CACjB,QAAA,CAAS,KAAA,EAAOF,aAAA,CAAG,YAAA,CAAa,MAAA,EAAQ,KAAA,CAAM,EAAE,CAAC,CAAA,CACjD,KAAA,CAAM,KAAK,CAAA,CACX,OAAA,CAAQC,eAAA,CAAK,YAAA,CAAa,SAAS,CAAC,CAAA,CACpC,KAAA,CAAM,KAAK,CAAA,CACX,MAAA,CAAO,MAAM,CAAA;AAAA,IAChB,EAAA,CACG,OAAO,EAAE,KAAA,EAAOP,0BAAuB,CAAA,CACvC,KAAK,YAAY,CAAA,CACjB,SAAS,KAAA,EAAOM,aAAA,CAAG,aAAa,MAAA,EAAQ,KAAA,CAAM,EAAE,CAAC,CAAA,CACjD,MAAM,KAAK;AAAA,GACf,CAAA;AAED,EAAA,MAAM,KAAA,GAAQ,QAAO,EAAA,GAAA,CAAA,EAAA,GAAA,WAAA,CAAY,CAAC,MAAb,IAAA,GAAA,MAAA,GAAA,EAAA,CAAgB,KAAA,KAAhB,YAAyB,CAAC,CAAA;AAE/C,EAAA,OAAO;AAAA,IACL,IAAA;AAAA,IACA,KAAA;AAAA,IACA,IAAA;AAAA,IACA,KAAA;AAAA,IACA,KAAA,EAAO,IAAA,CAAK,IAAA,CAAK,KAAA,GAAQ,KAAK;AAAA,GAChC;AACF;AAKA,IAAM,gBAAA,GAAmB,GAAA;AAEzB,eAAsB,wBAAA,CAAyB;AAAA,EAC7C,MAAA,GAAS,EAAA;AAAA,EACT,MAAA;AAAA,EACA,QAAA;AAAA,EACA;AACF,CAAA,EAA6B;AAC3B,EAAA,MAAM,QAAQ,sBAAA,CAAuB,EAAE,QAAQ,MAAA,EAAQ,QAAA,EAAU,QAAQ,CAAA;AAEzE,EAAA,OAAO,MAAM,GACV,MAAA,CAAO;AAAA,IACN,IAAI,YAAA,CAAa,EAAA;AAAA,IACjB,QAAQ,YAAA,CAAa,MAAA;AAAA,IACrB,aAAa,YAAA,CAAa,WAAA;AAAA,IAC1B,WAAW,YAAA,CAAa,SAAA;AAAA,IACxB,WAAW,YAAA,CAAa,SAAA;AAAA,IACxB,UAAU,YAAA,CAAa,QAAA;AAAA,IACvB,WAAW,YAAA,CAAa,SAAA;AAAA,IACxB,QAAQ,YAAA,CAAa,MAAA;AAAA,IACrB,UAAU,KAAA,CAAM,IAAA;AAAA,IAChB,WAAW,KAAA,CAAM;AAAA,GAClB,CAAA,CACA,IAAA,CAAK,YAAY,CAAA,CACjB,SAAS,KAAA,EAAOA,aAAA,CAAG,YAAA,CAAa,MAAA,EAAQ,KAAA,CAAM,EAAE,CAAC,CAAA,CACjD,KAAA,CAAM,KAAK,CAAA,CACX,OAAA,CAAQC,eAAA,CAAK,aAAa,SAAS,CAAC,CAAA,CACpC,KAAA,CAAM,gBAAgB,CAAA;AAC3B","file":"queries.js","sourcesContent":["import { boolean, check, integer, pgEnum, pgTable, text, timestamp, unique } from \"drizzle-orm/pg-core\";\nimport { sql } from \"drizzle-orm\";\nimport { createId } from '@paralleldrive/cuid2'\nimport { DEFAULT_MAINTENANCE_EXEMPT_PATHS } from \"../../lib/maintenance\";\n\n// 'tester' is a regular user account flagged for testing; apps can use it to\n// keep test accounts apart from real users. It has no admin access.\nexport const RoleEnum = pgEnum('roles', ['user', 'admin', 'tester'])\n\nexport const ActivityActionEnum = pgEnum('activity_actions', [\n 'login',\n 'logout',\n 'login_failed',\n 'password_changed',\n 'email_changed',\n 'profile_updated',\n 'session_revoked',\n 'user_deleted',\n 'user_banned',\n 'user_unbanned',\n 'role_changed',\n 'impersonation_started',\n 'impersonation_stopped',\n 'settings_changed',\n 'passkey_added',\n 'passkey_removed',\n])\n\nexport const users = pgTable(\"user\", {\n id: text(\"id\").notNull().primaryKey().$defaultFn(() => createId()),\n name: text('name').notNull(),\n email: text('email').notNull().unique(),\n password: text('password'),\n emailVerified: boolean(\"emailVerified\").notNull().default(false),\n image: text(\"image\"),\n role: RoleEnum('roles').default('user').notNull(),\n banned: boolean('banned').default(false).notNull(),\n bannedAt: timestamp('bannedAt'),\n bannedReason: text('bannedReason'),\n // Required by better-auth's admin plugin schema (auto-expiring bans). We\n // never set this ourselves, so it stays null and bans behave as permanent,\n // same as before the plugin was added.\n banExpires: timestamp('banExpires'),\n lastLoginAt: timestamp('lastLoginAt'),\n lastActiveAt: timestamp('lastActiveAt'),\n // Set by better-auth's twoFactor plugin — true once the user has\n // completed TOTP setup (see twoFactor pgTable below for the secret /\n // backup codes themselves).\n twoFactorEnabled: boolean('twoFactorEnabled').notNull().default(false),\n createdAt: timestamp('createdAt').notNull().defaultNow(),\n updatedAt: timestamp('updatedAt').notNull().defaultNow(),\n});\n\nexport const sessions = pgTable(\"session\", {\n id: text(\"id\").notNull().primaryKey().$defaultFn(() => createId()),\n expiresAt: timestamp(\"expiresAt\").notNull(),\n token: text(\"token\").notNull().unique(),\n createdAt: timestamp(\"createdAt\").notNull().defaultNow(),\n updatedAt: timestamp(\"updatedAt\").notNull().defaultNow(),\n ipAddress: text(\"ipAddress\"),\n userAgent: text(\"userAgent\"),\n userId: text(\"userId\")\n .notNull()\n .references(() => users.id, { onDelete: \"cascade\" }),\n // Set by better-auth's admin plugin while this session is an admin\n // impersonating another user; holds the impersonating admin's user id.\n impersonatedBy: text(\"impersonatedBy\"),\n});\n\nexport const accounts = pgTable(\"account\", {\n id: text(\"id\").notNull().primaryKey().$defaultFn(() => createId()),\n accountId: text(\"accountId\").notNull(),\n providerId: text(\"providerId\").notNull(),\n userId: text(\"userId\")\n .notNull()\n .references(() => users.id, { onDelete: \"cascade\" }),\n accessToken: text(\"accessToken\"),\n refreshToken: text(\"refreshToken\"),\n idToken: text(\"idToken\"),\n accessTokenExpiresAt: timestamp(\"accessTokenExpiresAt\"),\n refreshTokenExpiresAt: timestamp(\"refreshTokenExpiresAt\"),\n scope: text(\"scope\"),\n password: text(\"password\"),\n createdAt: timestamp(\"createdAt\").notNull().defaultNow(),\n updatedAt: timestamp(\"updatedAt\").notNull().defaultNow(),\n});\n\n// Better-auth's twoFactor plugin. One row per user (created on first\n// `enable`); secret and backupCodes are its own encrypted-at-rest strings,\n// field names/types must match its expectations exactly (drizzleAdapter in\n// server/auth.ts maps this table to the plugin's \"twoFactor\" model as-is).\nexport const twoFactors = pgTable(\"twoFactor\", {\n id: text(\"id\").notNull().primaryKey().$defaultFn(() => createId()),\n secret: text(\"secret\").notNull(),\n backupCodes: text(\"backupCodes\").notNull(),\n userId: text(\"userId\")\n .notNull()\n .references(() => users.id, { onDelete: \"cascade\" }),\n // Added in better-auth 1.7 (TOTP lockout). Defaults match the plugin's.\n verified: boolean(\"verified\").default(true),\n failedVerificationCount: integer(\"failedVerificationCount\").default(0),\n lockedUntil: timestamp(\"lockedUntil\"),\n});\n\n// Better-auth's passkey plugin — one row per registered WebAuthn credential.\nexport const passkeys = pgTable(\"passkey\", {\n id: text(\"id\").notNull().primaryKey().$defaultFn(() => createId()),\n name: text(\"name\"),\n publicKey: text(\"publicKey\").notNull(),\n userId: text(\"userId\")\n .notNull()\n .references(() => users.id, { onDelete: \"cascade\" }),\n credentialID: text(\"credentialID\").notNull().unique(),\n counter: integer(\"counter\").notNull(),\n deviceType: text(\"deviceType\").notNull(),\n backedUp: boolean(\"backedUp\").notNull(),\n transports: text(\"transports\"),\n createdAt: timestamp(\"createdAt\"),\n aaguid: text(\"aaguid\"),\n});\n\nexport const verifications = pgTable(\"verification\", {\n id: text(\"id\").notNull().primaryKey().$defaultFn(() => createId()),\n identifier: text(\"identifier\").notNull(),\n value: text(\"value\").notNull(),\n expiresAt: timestamp(\"expiresAt\").notNull(),\n createdAt: timestamp(\"createdAt\").notNull().defaultNow(),\n updatedAt: timestamp(\"updatedAt\").notNull().defaultNow(),\n});\n\nexport const activityLogs = pgTable(\"activity_log\", {\n id: text(\"id\").notNull().primaryKey().$defaultFn(() => createId()),\n userId: text(\"userId\")\n .notNull()\n .references(() => users.id, { onDelete: \"cascade\" }),\n action: ActivityActionEnum('action').notNull(),\n description: text('description').notNull(),\n ipAddress: text('ipAddress'),\n userAgent: text('userAgent'),\n metadata: text('metadata'),\n // Populated on 'login' events only (registration + sign-in) — parsed from\n // userAgent/ipAddress at write time so the record stays stable even if\n // the parsing library or geoip database changes later.\n referrerUrl: text('referrerUrl'),\n os: text('os'),\n browser: text('browser'),\n deviceType: text('deviceType'),\n country: text('country'),\n city: text('city'),\n createdAt: timestamp('createdAt').notNull().defaultNow(),\n});\n\n// One row per (user, device fingerprint) pair. Its only job is to answer\n// \"has this account signed in from this device before?\" — no matching row ⇒\n// send a new-device alert (see app/api/device-check/route.ts). visitorId is\n// FingerprintJS's identifier, computed client-side in\n// components/auth/deviceCheck.tsx. UA+IP are deliberately NOT the signal here:\n// they churn constantly on mobile and would alert on every other login.\nexport const deviceFingerprints = pgTable(\"device_fingerprint\", {\n id: text(\"id\").notNull().primaryKey().$defaultFn(() => createId()),\n userId: text(\"userId\")\n .notNull()\n .references(() => users.id, { onDelete: \"cascade\" }),\n visitorId: text(\"visitorId\").notNull(),\n // Context captured at first-seen time — for the admin panel's \"New devices\"\n // card and the alert email. Not part of the new-vs-known decision (that's\n // visitorId only), just so an admin can eyeball whether a sign-in looks like\n // a hijack. Nullable: an old row predates these columns, or geoip missed.\n ipAddress: text(\"ipAddress\"),\n userAgent: text(\"userAgent\"),\n country: text(\"country\"),\n city: text(\"city\"),\n createdAt: timestamp(\"createdAt\").notNull().defaultNow(),\n}, (table) => [\n unique(\"device_fingerprint_user_visitor\").on(table.userId, table.visitorId),\n]);\n\n// Per-device abuse throttle for /sign-in/email and /sign-up/email — one\n// fixed-window counter row per FingerprintJS visitorId (sent by the auth\n// forms as the x-device-fingerprint header; see lib/auth-throttle.ts).\n// Keyed by device, not IP: IP is shared behind corporate NAT / CGNAT / VPN,\n// so an IP counter throttles coworkers together while missing an abuser who\n// rotates IPs. Rows whose window has elapsed are dead weight but harmless\n// (reset on the next attempt) — prune on a cron if the table ever grows.\nexport const authThrottle = pgTable(\"auth_throttle\", {\n fingerprint: text(\"fingerprint\").notNull().primaryKey(),\n count: integer(\"count\").notNull().default(0),\n windowStart: timestamp(\"windowStart\").notNull().defaultNow(),\n // Blockable data points, overwritten on every attempt (last-write-wins) so\n // the admin panel can show who to block — by IP or by fingerprint — with no\n // join and no per-attempt history table. windowStart anchors the counter's\n // window; lastAttemptAt is just \"most recently seen\".\n lastAttemptAt: timestamp(\"lastAttemptAt\").notNull().defaultNow(),\n ipAddress: text(\"ipAddress\"),\n userAgent: text(\"userAgent\"),\n lastEmail: text(\"lastEmail\"),\n lastKind: text(\"lastKind\"), // 'signin' | 'signup'\n});\n\n// Singleton row (id is always 'default') holding admin-editable settings for\n// config that used to be env-var-only. See lib/settings-queries.ts for the\n// cached read path and lazy-seed-from-env behavior.\nexport const systemSettings = pgTable(\"system_settings\", {\n id: text(\"id\").primaryKey().default(\"default\"),\n maintenanceMode: boolean(\"maintenanceMode\").notNull().default(false),\n maintenanceMessage: text(\"maintenanceMessage\"),\n // Path prefixes that stay reachable while maintenance mode is on (see\n // lib/maintenance.ts — a few auth paths are always open on top of these).\n maintenanceExemptPaths: text(\"maintenanceExemptPaths\").array().notNull().default(DEFAULT_MAINTENANCE_EXEMPT_PATHS),\n authEnableGoogle: boolean(\"authEnableGoogle\").notNull().default(true),\n authEnableEmailPassword: boolean(\"authEnableEmailPassword\").notNull().default(true),\n authEnableOneTap: boolean(\"authEnableOneTap\").notNull().default(false),\n enableSessionRevocation: boolean(\"enableSessionRevocation\").notNull().default(true),\n updatedAt: timestamp(\"updatedAt\").notNull().defaultNow(),\n // Who last changed a setting. This row doesn't \"belong\" to that admin the\n // way sessions/activityLogs belong to their user, so deleting that admin's\n // account should null this out rather than cascade-delete the singleton row.\n updatedBy: text(\"updatedBy\").references(() => users.id, { onDelete: \"set null\" }),\n}, (table) => [\n check(\"system_settings_singleton\", sql`${table.id} = 'default'`),\n]);","// Pure path logic for maintenance mode — no DB / server-only imports, so it\n// can run in proxy.ts and in the admin form alike.\n\n// Default for the admin-editable exempt list: the public marketing pages.\nexport const DEFAULT_MAINTENANCE_EXEMPT_PATHS = [\n \"/\",\n \"/features\",\n \"/changelog\",\n \"/changelog-template\",\n \"/privacy\",\n \"/terms\",\n]\n\n// Always reachable, whatever the admin saves — otherwise an admin could\n// lock themselves out of the sign-in flow needed to turn maintenance off.\nexport const ALWAYS_OPEN_PATHS = [\n \"/maintenance\",\n \"/login\", // includes /login/2fa\n \"/forgot-password\",\n \"/reset-password\",\n \"/api/auth\",\n \"/api/settings/public\", // login page reads the One Tap flag from here\n]\n\n// `/foo` matches `/foo` and `/foo/...` but not `/foobar`; `/` matches only `/`.\nfunction matches(pathname: string, prefix: string) {\n const p = prefix.length > 1 ? prefix.replace(/\\/+$/, \"\") : prefix\n return pathname === p || (p !== \"/\" && pathname.startsWith(p + \"/\"))\n}\n\nexport function isMaintenanceExempt(pathname: string, exemptPaths: string[]) {\n return [...ALWAYS_OPEN_PATHS, ...exemptPaths].some((p) => matches(pathname, p))\n}\n","// No \"server-only\" guard here — this schema is also imported by scripts that\n// run outside Next.js's bundler (e.g. scripts/doctor.ts), where that guard\n// throws unconditionally. config/env.ts re-exports the parsed, guarded result\n// for app code; import this file directly only from non-Next scripts.\nimport { z } from 'zod';\n\nexport const envSchema = z.object({\n NODE_ENV: z.enum(['development', 'production', 'test']).default('development'),\n PORT: z.string().default('3000').transform(Number),\n DATABASE_URL: z.string().default(''),\n RESEND_API_KEY: z.string().default(''),\n // Sender for every transactional email. Must be on a domain verified in\n // your Resend account.\n EMAIL_FROM: z.string().default('Acme <onboarding@xavierkhew.xyz>'),\n // Display name for this deployment — shown as the authenticator-app label\n // (2FA issuer) and the passkey relying-party name. Consuming projects set\n // this in their own .env; defaults to the template name.\n APP_NAME: z.string().default('Next.js Template'),\n // Seed-only: the live value lives in the system_settings DB row (see\n // lib/settings-queries.ts) once it's been seeded on first read, and is\n // changed from the admin System Settings page, not by editing this var.\n ENABLE_SESSION_REVOCATION: z.string().default('true').transform(val => val === 'true'),\n AUTH_ENABLE_GOOGLE: z.string().default('true').transform(val => val === 'true'),\n AUTH_ENABLE_EMAIL_PASSWORD: z.string().default('true').transform(val => val === 'true'),\n AUTH_ENABLE_ONE_TAP: z.string().default('false').transform(val => val === 'true'),\n DB_DRIVER: z.enum(['pg', 'neon']).default('pg'),\n // Only relevant when DB_DRIVER=pg — whether the target Postgres server has SSL\n // enabled. Self-hosted Postgres (e.g. via Docker/Dokploy) typically does not.\n DATABASE_SSL: z.string().default('false').transform(val => val === 'true'),\n});\n","import \"server-only\";\nimport { envSchema } from './env-schema';\n\nconst results = envSchema.safeParse(process.env);\n\nif (results.success && (results.data.NODE_ENV === 'development')) {\n console.log('Environment variables loaded:', Object.keys(results.data));\n}\n\nif (!results.success) {\n console.error('Invalid environment variables:', results.error.format());\n throw new Error('Invalid environment variables');\n}\n\nexport const config = results.data;\n","import 'server-only';\n\n// neon-http driver for production\nimport { neon } from '@neondatabase/serverless';\nimport { drizzle } from 'drizzle-orm/neon-http';\n\n// postgres driver for local development\nimport { drizzle as drizzlePg } from 'drizzle-orm/node-postgres';\nimport { Pool } from 'pg';\n\nimport * as schema from './schema';\nimport { config } from '../../config/env';\n\n// Import the query builder\nimport { PostgresJsDatabase } from 'drizzle-orm/postgres-js';\nimport { NeonHttpDatabase } from 'drizzle-orm/neon-http';\n\ntype DrizzleNeon = NeonHttpDatabase<typeof schema>;\ntype DrizzlePg = PostgresJsDatabase<typeof schema>;\n\nlet db: DrizzleNeon | DrizzlePg\n\n\nif (config.DB_DRIVER === 'pg') {\n const pool = new Pool({\n connectionString: config.DATABASE_URL!,\n ssl: config.DATABASE_SSL ? { rejectUnauthorized: false } : undefined,\n });\n db = drizzlePg(pool, {\n schema,\n logger: true,\n });\n\n console.log('🟡 Using PostgreSQL (pg) driver');\n\n} else if (config.DB_DRIVER === 'neon') {\n const sql = neon(config.DATABASE_URL!);\n db = drizzle(sql, { schema, logger: true });\n\n console.log('🟢 Using Neon (HTTP) driver');\n\n} else {\n console.log('Database connection not established, unknown DB_DRIVER:', config.DB_DRIVER);\n}\n// Note: In production, you might want to disable logging or use a more sophisticated logger.\n\nexport { db }","import \"server-only\"\nimport { db } from \"../server/db\"\nimport { activityLogs, users, ActivityActionEnum } from \"../server/db/schema\"\nimport { eq, desc, and, or, like, gte, lte, sql, inArray, type SQL } from \"drizzle-orm\"\n\nexport async function getUserActivityLogs(userId: string, limit = 50) {\n return await db\n .select()\n .from(activityLogs)\n .where(eq(activityLogs.userId, userId))\n .orderBy(desc(activityLogs.createdAt))\n .limit(limit)\n}\n\n// Registration + sign-in events only, with the referrer/device/geolocation\n// columns captured at login time (see server/auth.ts's session.create hook).\nexport async function getUserLoginHistory(userId: string, limit = 20) {\n return await db\n .select()\n .from(activityLogs)\n .where(and(eq(activityLogs.userId, userId), eq(activityLogs.action, 'login')))\n .orderBy(desc(activityLogs.createdAt))\n .limit(limit)\n}\n\nexport async function getAllActivityLogs(limit = 100, offset = 0) {\n return await db\n .select({\n id: activityLogs.id,\n action: activityLogs.action,\n description: activityLogs.description,\n ipAddress: activityLogs.ipAddress,\n userAgent: activityLogs.userAgent,\n metadata: activityLogs.metadata,\n createdAt: activityLogs.createdAt,\n userId: activityLogs.userId,\n userName: users.name,\n userEmail: users.email,\n })\n .from(activityLogs)\n .leftJoin(users, eq(activityLogs.userId, users.id))\n .orderBy(desc(activityLogs.createdAt))\n .limit(limit)\n .offset(offset)\n}\n\n// Narrow, purpose-built feed for the admin panel's \"Impersonation log\" card —\n// so admins have a dedicated view of who's been logging in as whom without\n// having to remember to apply the generic action filter first.\nexport async function getRecentImpersonationEvents(limit = 10) {\n return await db\n .select({\n id: activityLogs.id,\n action: activityLogs.action,\n description: activityLogs.description,\n createdAt: activityLogs.createdAt,\n adminId: activityLogs.userId,\n adminName: users.name,\n adminEmail: users.email,\n })\n .from(activityLogs)\n .leftJoin(users, eq(activityLogs.userId, users.id))\n .where(inArray(activityLogs.action, ['impersonation_started', 'impersonation_stopped']))\n .orderBy(desc(activityLogs.createdAt))\n .limit(limit)\n}\n\nexport type ActivityActionFilter = (typeof ActivityActionEnum.enumValues)[number]\n\nexport interface ActivityLogFilters {\n search?: string\n action?: ActivityActionFilter\n dateFrom?: Date\n dateTo?: Date\n page?: number\n limit?: number\n}\n\nfunction buildActivityLogsWhere({\n search = \"\",\n action,\n dateFrom,\n dateTo,\n}: Pick<ActivityLogFilters, \"search\" | \"action\" | \"dateFrom\" | \"dateTo\">): SQL | undefined {\n const conditions: SQL[] = []\n if (search.trim()) {\n conditions.push(\n or(\n like(users.name, `%${search}%`),\n like(users.email, `%${search}%`)\n ) as SQL\n )\n }\n if (action) {\n conditions.push(eq(activityLogs.action, action))\n }\n if (dateFrom) {\n conditions.push(gte(activityLogs.createdAt, dateFrom))\n }\n if (dateTo) {\n conditions.push(lte(activityLogs.createdAt, dateTo))\n }\n\n return conditions.length > 0 ? and(...conditions) : undefined\n}\n\nexport async function getActivityLogsFiltered({\n search = \"\",\n action,\n dateFrom,\n dateTo,\n page = 1,\n limit = 20,\n}: ActivityLogFilters) {\n const offset = (page - 1) * limit\n\n const where = buildActivityLogsWhere({ search, action, dateFrom, dateTo })\n\n const [logs, countResult] = await Promise.all([\n db\n .select({\n id: activityLogs.id,\n action: activityLogs.action,\n description: activityLogs.description,\n ipAddress: activityLogs.ipAddress,\n userAgent: activityLogs.userAgent,\n metadata: activityLogs.metadata,\n createdAt: activityLogs.createdAt,\n userId: activityLogs.userId,\n userName: users.name,\n userEmail: users.email,\n })\n .from(activityLogs)\n .leftJoin(users, eq(activityLogs.userId, users.id))\n .where(where)\n .orderBy(desc(activityLogs.createdAt))\n .limit(limit)\n .offset(offset),\n db\n .select({ count: sql<number>`count(*)` })\n .from(activityLogs)\n .leftJoin(users, eq(activityLogs.userId, users.id))\n .where(where),\n ])\n\n const total = Number(countResult[0]?.count ?? 0)\n\n return {\n logs,\n total,\n page,\n limit,\n pages: Math.ceil(total / limit),\n }\n}\n\nexport type ActivityLogExportFilters = Pick<ActivityLogFilters, \"search\" | \"action\" | \"dateFrom\" | \"dateTo\">\n\n// Caps export size to keep the CSV response bounded on very large tables.\nconst EXPORT_ROW_LIMIT = 10_000\n\nexport async function getActivityLogsForExport({\n search = \"\",\n action,\n dateFrom,\n dateTo,\n}: ActivityLogExportFilters) {\n const where = buildActivityLogsWhere({ search, action, dateFrom, dateTo })\n\n return await db\n .select({\n id: activityLogs.id,\n action: activityLogs.action,\n description: activityLogs.description,\n ipAddress: activityLogs.ipAddress,\n userAgent: activityLogs.userAgent,\n metadata: activityLogs.metadata,\n createdAt: activityLogs.createdAt,\n userId: activityLogs.userId,\n userName: users.name,\n userEmail: users.email,\n })\n .from(activityLogs)\n .leftJoin(users, eq(activityLogs.userId, users.id))\n .where(where)\n .orderBy(desc(activityLogs.createdAt))\n .limit(EXPORT_ROW_LIMIT)\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../../server/db/schema.ts","../../lib/maintenance.ts","../../config/env-schema.ts","../../config/env.ts","../../server/db/index.ts","../../lib/activity-queries.ts"],"names":["pgEnum","pgTable","text","createId","boolean","timestamp","integer","unique","check","sql","z","Pool","drizzlePg","neon","drizzle","eq","desc","and","inArray","or","like","gte","lte"],"mappings":"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","file":"queries.js","sourcesContent":["import { boolean, check, integer, pgEnum, pgTable, text, timestamp, unique } from \"drizzle-orm/pg-core\";\nimport { sql } from \"drizzle-orm\";\nimport { createId } from '@paralleldrive/cuid2'\nimport { DEFAULT_MAINTENANCE_EXEMPT_PATHS } from \"../../lib/maintenance\";\n\n// 'tester' is a regular user account flagged for testing; apps can use it to\n// keep test accounts apart from real users. It has no admin access.\nexport const RoleEnum = pgEnum('roles', ['user', 'admin', 'tester'])\n\nexport const ActivityActionEnum = pgEnum('activity_actions', [\n 'login',\n 'logout',\n 'login_failed',\n 'password_changed',\n 'email_changed',\n 'profile_updated',\n 'session_revoked',\n 'user_deleted',\n 'user_banned',\n 'user_unbanned',\n 'role_changed',\n 'impersonation_started',\n 'impersonation_stopped',\n 'settings_changed',\n 'passkey_added',\n 'passkey_removed',\n])\n\nexport const users = pgTable(\"user\", {\n id: text(\"id\").notNull().primaryKey().$defaultFn(() => createId()),\n name: text('name').notNull(),\n email: text('email').notNull().unique(),\n password: text('password'),\n emailVerified: boolean(\"emailVerified\").notNull().default(false),\n image: text(\"image\"),\n role: RoleEnum('roles').default('user').notNull(),\n banned: boolean('banned').default(false).notNull(),\n bannedAt: timestamp('bannedAt'),\n bannedReason: text('bannedReason'),\n // Required by better-auth's admin plugin schema (auto-expiring bans). We\n // never set this ourselves, so it stays null and bans behave as permanent,\n // same as before the plugin was added.\n banExpires: timestamp('banExpires'),\n lastLoginAt: timestamp('lastLoginAt'),\n lastActiveAt: timestamp('lastActiveAt'),\n // Set by better-auth's twoFactor plugin — true once the user has\n // completed TOTP setup (see twoFactor pgTable below for the secret /\n // backup codes themselves).\n twoFactorEnabled: boolean('twoFactorEnabled').notNull().default(false),\n createdAt: timestamp('createdAt').notNull().defaultNow(),\n updatedAt: timestamp('updatedAt').notNull().defaultNow(),\n});\n\nexport const sessions = pgTable(\"session\", {\n id: text(\"id\").notNull().primaryKey().$defaultFn(() => createId()),\n expiresAt: timestamp(\"expiresAt\").notNull(),\n token: text(\"token\").notNull().unique(),\n createdAt: timestamp(\"createdAt\").notNull().defaultNow(),\n updatedAt: timestamp(\"updatedAt\").notNull().defaultNow(),\n ipAddress: text(\"ipAddress\"),\n userAgent: text(\"userAgent\"),\n userId: text(\"userId\")\n .notNull()\n .references(() => users.id, { onDelete: \"cascade\" }),\n // Set by better-auth's admin plugin while this session is an admin\n // impersonating another user; holds the impersonating admin's user id.\n impersonatedBy: text(\"impersonatedBy\"),\n});\n\nexport const accounts = pgTable(\"account\", {\n id: text(\"id\").notNull().primaryKey().$defaultFn(() => createId()),\n accountId: text(\"accountId\").notNull(),\n providerId: text(\"providerId\").notNull(),\n userId: text(\"userId\")\n .notNull()\n .references(() => users.id, { onDelete: \"cascade\" }),\n accessToken: text(\"accessToken\"),\n refreshToken: text(\"refreshToken\"),\n idToken: text(\"idToken\"),\n accessTokenExpiresAt: timestamp(\"accessTokenExpiresAt\"),\n refreshTokenExpiresAt: timestamp(\"refreshTokenExpiresAt\"),\n scope: text(\"scope\"),\n password: text(\"password\"),\n createdAt: timestamp(\"createdAt\").notNull().defaultNow(),\n updatedAt: timestamp(\"updatedAt\").notNull().defaultNow(),\n});\n\n// Better-auth's twoFactor plugin. One row per user (created on first\n// `enable`); secret and backupCodes are its own encrypted-at-rest strings,\n// field names/types must match its expectations exactly (drizzleAdapter in\n// server/auth.ts maps this table to the plugin's \"twoFactor\" model as-is).\nexport const twoFactors = pgTable(\"twoFactor\", {\n id: text(\"id\").notNull().primaryKey().$defaultFn(() => createId()),\n secret: text(\"secret\").notNull(),\n backupCodes: text(\"backupCodes\").notNull(),\n userId: text(\"userId\")\n .notNull()\n .references(() => users.id, { onDelete: \"cascade\" }),\n // Added in better-auth 1.7 (TOTP lockout). Defaults match the plugin's.\n verified: boolean(\"verified\").default(true),\n failedVerificationCount: integer(\"failedVerificationCount\").default(0),\n lockedUntil: timestamp(\"lockedUntil\"),\n});\n\n// Better-auth's passkey plugin — one row per registered WebAuthn credential.\nexport const passkeys = pgTable(\"passkey\", {\n id: text(\"id\").notNull().primaryKey().$defaultFn(() => createId()),\n name: text(\"name\"),\n publicKey: text(\"publicKey\").notNull(),\n userId: text(\"userId\")\n .notNull()\n .references(() => users.id, { onDelete: \"cascade\" }),\n credentialID: text(\"credentialID\").notNull().unique(),\n counter: integer(\"counter\").notNull(),\n deviceType: text(\"deviceType\").notNull(),\n backedUp: boolean(\"backedUp\").notNull(),\n transports: text(\"transports\"),\n createdAt: timestamp(\"createdAt\"),\n aaguid: text(\"aaguid\"),\n});\n\nexport const verifications = pgTable(\"verification\", {\n id: text(\"id\").notNull().primaryKey().$defaultFn(() => createId()),\n identifier: text(\"identifier\").notNull(),\n value: text(\"value\").notNull(),\n expiresAt: timestamp(\"expiresAt\").notNull(),\n createdAt: timestamp(\"createdAt\").notNull().defaultNow(),\n updatedAt: timestamp(\"updatedAt\").notNull().defaultNow(),\n});\n\nexport const activityLogs = pgTable(\"activity_log\", {\n id: text(\"id\").notNull().primaryKey().$defaultFn(() => createId()),\n userId: text(\"userId\")\n .notNull()\n .references(() => users.id, { onDelete: \"cascade\" }),\n action: ActivityActionEnum('action').notNull(),\n description: text('description').notNull(),\n ipAddress: text('ipAddress'),\n userAgent: text('userAgent'),\n metadata: text('metadata'),\n // Populated on 'login' events only (registration + sign-in) — parsed from\n // userAgent/ipAddress at write time so the record stays stable even if\n // the parsing library or geoip database changes later.\n referrerUrl: text('referrerUrl'),\n os: text('os'),\n browser: text('browser'),\n deviceType: text('deviceType'),\n country: text('country'),\n city: text('city'),\n createdAt: timestamp('createdAt').notNull().defaultNow(),\n});\n\n// One row per (user, device fingerprint) pair. Its only job is to answer\n// \"has this account signed in from this device before?\" — no matching row ⇒\n// send a new-device alert (see app/api/device-check/route.ts). visitorId is\n// FingerprintJS's identifier, computed client-side in\n// components/auth/deviceCheck.tsx. UA+IP are deliberately NOT the signal here:\n// they churn constantly on mobile and would alert on every other login.\nexport const deviceFingerprints = pgTable(\"device_fingerprint\", {\n id: text(\"id\").notNull().primaryKey().$defaultFn(() => createId()),\n userId: text(\"userId\")\n .notNull()\n .references(() => users.id, { onDelete: \"cascade\" }),\n visitorId: text(\"visitorId\").notNull(),\n // Context captured at first-seen time — for the admin panel's \"New devices\"\n // card and the alert email. Not part of the new-vs-known decision (that's\n // visitorId only), just so an admin can eyeball whether a sign-in looks like\n // a hijack. Nullable: an old row predates these columns, or geoip missed.\n ipAddress: text(\"ipAddress\"),\n userAgent: text(\"userAgent\"),\n country: text(\"country\"),\n city: text(\"city\"),\n createdAt: timestamp(\"createdAt\").notNull().defaultNow(),\n}, (table) => [\n unique(\"device_fingerprint_user_visitor\").on(table.userId, table.visitorId),\n]);\n\n// Per-device abuse throttle for /sign-in/email and /sign-up/email — one\n// fixed-window counter row per FingerprintJS visitorId (sent by the auth\n// forms as the x-device-fingerprint header; see lib/auth-throttle.ts).\n// Keyed by device, not IP: IP is shared behind corporate NAT / CGNAT / VPN,\n// so an IP counter throttles coworkers together while missing an abuser who\n// rotates IPs. Rows whose window has elapsed are dead weight but harmless\n// (reset on the next attempt) — prune on a cron if the table ever grows.\nexport const authThrottle = pgTable(\"auth_throttle\", {\n fingerprint: text(\"fingerprint\").notNull().primaryKey(),\n count: integer(\"count\").notNull().default(0),\n windowStart: timestamp(\"windowStart\").notNull().defaultNow(),\n // Blockable data points, overwritten on every attempt (last-write-wins) so\n // the admin panel can show who to block — by IP or by fingerprint — with no\n // join and no per-attempt history table. windowStart anchors the counter's\n // window; lastAttemptAt is just \"most recently seen\".\n lastAttemptAt: timestamp(\"lastAttemptAt\").notNull().defaultNow(),\n ipAddress: text(\"ipAddress\"),\n userAgent: text(\"userAgent\"),\n lastEmail: text(\"lastEmail\"),\n lastKind: text(\"lastKind\"), // 'signin' | 'signup'\n});\n\n// Singleton row (id is always 'default') holding admin-editable settings for\n// config that used to be env-var-only. See lib/settings-queries.ts for the\n// cached read path and lazy-seed-from-env behavior.\nexport const systemSettings = pgTable(\"system_settings\", {\n id: text(\"id\").primaryKey().default(\"default\"),\n maintenanceMode: boolean(\"maintenanceMode\").notNull().default(false),\n maintenanceMessage: text(\"maintenanceMessage\"),\n // Path prefixes that stay reachable while maintenance mode is on (see\n // lib/maintenance.ts — a few auth paths are always open on top of these).\n maintenanceExemptPaths: text(\"maintenanceExemptPaths\").array().notNull().default(DEFAULT_MAINTENANCE_EXEMPT_PATHS),\n authEnableGoogle: boolean(\"authEnableGoogle\").notNull().default(true),\n authEnableEmailPassword: boolean(\"authEnableEmailPassword\").notNull().default(true),\n authEnableOneTap: boolean(\"authEnableOneTap\").notNull().default(false),\n enableSessionRevocation: boolean(\"enableSessionRevocation\").notNull().default(true),\n updatedAt: timestamp(\"updatedAt\").notNull().defaultNow(),\n // Who last changed a setting. This row doesn't \"belong\" to that admin the\n // way sessions/activityLogs belong to their user, so deleting that admin's\n // account should null this out rather than cascade-delete the singleton row.\n updatedBy: text(\"updatedBy\").references(() => users.id, { onDelete: \"set null\" }),\n}, (table) => [\n check(\"system_settings_singleton\", sql`${table.id} = 'default'`),\n]);","// Pure path logic for maintenance mode — no DB / server-only imports, so it\n// can run in proxy.ts and in the admin form alike.\n\n// Default for the admin-editable exempt list: the public marketing pages.\nexport const DEFAULT_MAINTENANCE_EXEMPT_PATHS = [\n \"/\",\n \"/features\",\n \"/changelog\",\n \"/changelog-template\",\n \"/privacy\",\n \"/terms\",\n]\n\n// Always reachable, whatever the admin saves — otherwise an admin could\n// lock themselves out of the sign-in flow needed to turn maintenance off.\nexport const ALWAYS_OPEN_PATHS = [\n \"/maintenance\",\n \"/login\", // includes /login/2fa\n \"/forgot-password\",\n \"/reset-password\",\n \"/api/auth\",\n \"/api/settings/public\", // login page reads the One Tap flag from here\n]\n\n// `/foo` matches `/foo` and `/foo/...` but not `/foobar`; `/` matches only `/`.\nfunction matches(pathname: string, prefix: string) {\n const p = prefix.length > 1 ? prefix.replace(/\\/+$/, \"\") : prefix\n return pathname === p || (p !== \"/\" && pathname.startsWith(p + \"/\"))\n}\n\nexport function isMaintenanceExempt(pathname: string, exemptPaths: string[]) {\n return [...ALWAYS_OPEN_PATHS, ...exemptPaths].some((p) => matches(pathname, p))\n}\n","// No \"server-only\" guard here — this schema is also imported by scripts that\n// run outside Next.js's bundler (e.g. scripts/doctor.ts), where that guard\n// throws unconditionally. config/env.ts re-exports the parsed, guarded result\n// for app code; import this file directly only from non-Next scripts.\nimport { z } from 'zod';\n\nexport const envSchema = z.object({\n NODE_ENV: z.enum(['development', 'production', 'test']).default('development'),\n PORT: z.string().default('3000').transform(Number),\n DATABASE_URL: z.string().default(''),\n RESEND_API_KEY: z.string().default(''),\n // Sender for every transactional email. Must be on a domain verified in\n // your Resend account.\n EMAIL_FROM: z.string().default('Acme <onboarding@xavierkhew.xyz>'),\n // Display name for this deployment — shown as the authenticator-app label\n // (2FA issuer) and the passkey relying-party name. Consuming projects set\n // this in their own .env; defaults to the template name.\n APP_NAME: z.string().default('Next.js Template'),\n // Where the dashboard feedback form delivers (app/api/feedback). Set either\n // or both; with neither, the Feedback button is hidden. FEEDBACK_EMAIL takes\n // a comma-separated list and sends through Resend (needs RESEND_API_KEY).\n FEEDBACK_DISCORD_WEBHOOK_URL: z.string().default(''),\n FEEDBACK_EMAIL: z.string().default(''),\n // Seed-only: the live value lives in the system_settings DB row (see\n // lib/settings-queries.ts) once it's been seeded on first read, and is\n // changed from the admin System Settings page, not by editing this var.\n ENABLE_SESSION_REVOCATION: z.string().default('true').transform(val => val === 'true'),\n AUTH_ENABLE_GOOGLE: z.string().default('true').transform(val => val === 'true'),\n AUTH_ENABLE_EMAIL_PASSWORD: z.string().default('true').transform(val => val === 'true'),\n AUTH_ENABLE_ONE_TAP: z.string().default('false').transform(val => val === 'true'),\n DB_DRIVER: z.enum(['pg', 'neon']).default('pg'),\n // Only relevant when DB_DRIVER=pg — whether the target Postgres server has SSL\n // enabled. Self-hosted Postgres (e.g. via Docker/Dokploy) typically does not.\n DATABASE_SSL: z.string().default('false').transform(val => val === 'true'),\n});\n","import \"server-only\";\nimport { envSchema } from './env-schema';\n\nconst results = envSchema.safeParse(process.env);\n\nif (results.success && (results.data.NODE_ENV === 'development')) {\n console.log('Environment variables loaded:', Object.keys(results.data));\n}\n\nif (!results.success) {\n console.error('Invalid environment variables:', results.error.format());\n throw new Error('Invalid environment variables');\n}\n\nexport const config = results.data;\n","import 'server-only';\n\n// neon-http driver for production\nimport { neon } from '@neondatabase/serverless';\nimport { drizzle } from 'drizzle-orm/neon-http';\n\n// postgres driver for local development\nimport { drizzle as drizzlePg } from 'drizzle-orm/node-postgres';\nimport { Pool } from 'pg';\n\nimport * as schema from './schema';\nimport { config } from '../../config/env';\n\n// Import the query builder\nimport { PostgresJsDatabase } from 'drizzle-orm/postgres-js';\nimport { NeonHttpDatabase } from 'drizzle-orm/neon-http';\n\ntype DrizzleNeon = NeonHttpDatabase<typeof schema>;\ntype DrizzlePg = PostgresJsDatabase<typeof schema>;\n\nlet db: DrizzleNeon | DrizzlePg\n\n\nif (config.DB_DRIVER === 'pg') {\n const pool = new Pool({\n connectionString: config.DATABASE_URL!,\n ssl: config.DATABASE_SSL ? { rejectUnauthorized: false } : undefined,\n });\n db = drizzlePg(pool, {\n schema,\n logger: true,\n });\n\n console.log('🟡 Using PostgreSQL (pg) driver');\n\n} else if (config.DB_DRIVER === 'neon') {\n const sql = neon(config.DATABASE_URL!);\n db = drizzle(sql, { schema, logger: true });\n\n console.log('🟢 Using Neon (HTTP) driver');\n\n} else {\n console.log('Database connection not established, unknown DB_DRIVER:', config.DB_DRIVER);\n}\n// Note: In production, you might want to disable logging or use a more sophisticated logger.\n\nexport { db }","import \"server-only\"\nimport { db } from \"../server/db\"\nimport { activityLogs, users, ActivityActionEnum } from \"../server/db/schema\"\nimport { eq, desc, and, or, like, gte, lte, sql, inArray, type SQL } from \"drizzle-orm\"\n\nexport async function getUserActivityLogs(userId: string, limit = 50) {\n return await db\n .select()\n .from(activityLogs)\n .where(eq(activityLogs.userId, userId))\n .orderBy(desc(activityLogs.createdAt))\n .limit(limit)\n}\n\n// Registration + sign-in events only, with the referrer/device/geolocation\n// columns captured at login time (see server/auth.ts's session.create hook).\nexport async function getUserLoginHistory(userId: string, limit = 20) {\n return await db\n .select()\n .from(activityLogs)\n .where(and(eq(activityLogs.userId, userId), eq(activityLogs.action, 'login')))\n .orderBy(desc(activityLogs.createdAt))\n .limit(limit)\n}\n\nexport async function getAllActivityLogs(limit = 100, offset = 0) {\n return await db\n .select({\n id: activityLogs.id,\n action: activityLogs.action,\n description: activityLogs.description,\n ipAddress: activityLogs.ipAddress,\n userAgent: activityLogs.userAgent,\n metadata: activityLogs.metadata,\n createdAt: activityLogs.createdAt,\n userId: activityLogs.userId,\n userName: users.name,\n userEmail: users.email,\n })\n .from(activityLogs)\n .leftJoin(users, eq(activityLogs.userId, users.id))\n .orderBy(desc(activityLogs.createdAt))\n .limit(limit)\n .offset(offset)\n}\n\n// Narrow, purpose-built feed for the admin panel's \"Impersonation log\" card —\n// so admins have a dedicated view of who's been logging in as whom without\n// having to remember to apply the generic action filter first.\nexport async function getRecentImpersonationEvents(limit = 10) {\n return await db\n .select({\n id: activityLogs.id,\n action: activityLogs.action,\n description: activityLogs.description,\n createdAt: activityLogs.createdAt,\n adminId: activityLogs.userId,\n adminName: users.name,\n adminEmail: users.email,\n })\n .from(activityLogs)\n .leftJoin(users, eq(activityLogs.userId, users.id))\n .where(inArray(activityLogs.action, ['impersonation_started', 'impersonation_stopped']))\n .orderBy(desc(activityLogs.createdAt))\n .limit(limit)\n}\n\nexport type ActivityActionFilter = (typeof ActivityActionEnum.enumValues)[number]\n\nexport interface ActivityLogFilters {\n search?: string\n action?: ActivityActionFilter\n dateFrom?: Date\n dateTo?: Date\n page?: number\n limit?: number\n}\n\nfunction buildActivityLogsWhere({\n search = \"\",\n action,\n dateFrom,\n dateTo,\n}: Pick<ActivityLogFilters, \"search\" | \"action\" | \"dateFrom\" | \"dateTo\">): SQL | undefined {\n const conditions: SQL[] = []\n if (search.trim()) {\n conditions.push(\n or(\n like(users.name, `%${search}%`),\n like(users.email, `%${search}%`)\n ) as SQL\n )\n }\n if (action) {\n conditions.push(eq(activityLogs.action, action))\n }\n if (dateFrom) {\n conditions.push(gte(activityLogs.createdAt, dateFrom))\n }\n if (dateTo) {\n conditions.push(lte(activityLogs.createdAt, dateTo))\n }\n\n return conditions.length > 0 ? and(...conditions) : undefined\n}\n\nexport async function getActivityLogsFiltered({\n search = \"\",\n action,\n dateFrom,\n dateTo,\n page = 1,\n limit = 20,\n}: ActivityLogFilters) {\n const offset = (page - 1) * limit\n\n const where = buildActivityLogsWhere({ search, action, dateFrom, dateTo })\n\n const [logs, countResult] = await Promise.all([\n db\n .select({\n id: activityLogs.id,\n action: activityLogs.action,\n description: activityLogs.description,\n ipAddress: activityLogs.ipAddress,\n userAgent: activityLogs.userAgent,\n metadata: activityLogs.metadata,\n createdAt: activityLogs.createdAt,\n userId: activityLogs.userId,\n userName: users.name,\n userEmail: users.email,\n })\n .from(activityLogs)\n .leftJoin(users, eq(activityLogs.userId, users.id))\n .where(where)\n .orderBy(desc(activityLogs.createdAt))\n .limit(limit)\n .offset(offset),\n db\n .select({ count: sql<number>`count(*)` })\n .from(activityLogs)\n .leftJoin(users, eq(activityLogs.userId, users.id))\n .where(where),\n ])\n\n const total = Number(countResult[0]?.count ?? 0)\n\n return {\n logs,\n total,\n page,\n limit,\n pages: Math.ceil(total / limit),\n }\n}\n\nexport type ActivityLogExportFilters = Pick<ActivityLogFilters, \"search\" | \"action\" | \"dateFrom\" | \"dateTo\">\n\n// Caps export size to keep the CSV response bounded on very large tables.\nconst EXPORT_ROW_LIMIT = 10_000\n\nexport async function getActivityLogsForExport({\n search = \"\",\n action,\n dateFrom,\n dateTo,\n}: ActivityLogExportFilters) {\n const where = buildActivityLogsWhere({ search, action, dateFrom, dateTo })\n\n return await db\n .select({\n id: activityLogs.id,\n action: activityLogs.action,\n description: activityLogs.description,\n ipAddress: activityLogs.ipAddress,\n userAgent: activityLogs.userAgent,\n metadata: activityLogs.metadata,\n createdAt: activityLogs.createdAt,\n userId: activityLogs.userId,\n userName: users.name,\n userEmail: users.email,\n })\n .from(activityLogs)\n .leftJoin(users, eq(activityLogs.userId, users.id))\n .where(where)\n .orderBy(desc(activityLogs.createdAt))\n .limit(EXPORT_ROW_LIMIT)\n}\n"]}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { db } from '../chunk-
|
|
1
|
+
import { db } from '../chunk-LKPF3JCV.mjs';
|
|
2
2
|
import { activityLogs, users } from '../chunk-YPKOQOTA.mjs';
|
|
3
|
-
import '../chunk-
|
|
3
|
+
import '../chunk-TMJYAOC2.mjs';
|
|
4
4
|
import '../chunk-3465SI5N.mjs';
|
|
5
5
|
import 'server-only';
|
|
6
6
|
import { eq, desc, and, inArray, sql, or, like, gte, lte } from 'drizzle-orm';
|
package/dist/admin/queries.js
CHANGED
|
@@ -251,6 +251,11 @@ var envSchema = zod.z.object({
|
|
|
251
251
|
// (2FA issuer) and the passkey relying-party name. Consuming projects set
|
|
252
252
|
// this in their own .env; defaults to the template name.
|
|
253
253
|
APP_NAME: zod.z.string().default("Next.js Template"),
|
|
254
|
+
// Where the dashboard feedback form delivers (app/api/feedback). Set either
|
|
255
|
+
// or both; with neither, the Feedback button is hidden. FEEDBACK_EMAIL takes
|
|
256
|
+
// a comma-separated list and sends through Resend (needs RESEND_API_KEY).
|
|
257
|
+
FEEDBACK_DISCORD_WEBHOOK_URL: zod.z.string().default(""),
|
|
258
|
+
FEEDBACK_EMAIL: zod.z.string().default(""),
|
|
254
259
|
// Seed-only: the live value lives in the system_settings DB row (see
|
|
255
260
|
// lib/settings-queries.ts) once it's been seeded on first read, and is
|
|
256
261
|
// changed from the admin System Settings page, not by editing this var.
|