mirror of
https://github.com/rybbit-io/rybbit.git
synced 2025-05-11 12:25:36 +02:00
Wip (#42)
* Wip * Add drizzle support * Fix build * fix migrations * update dockerfiles * update stuff * simplify * fix migrations * remove migration code * fix migrations * update script * wip * bump script * wip * fix * bump * copy public * wip * wip
This commit is contained in:
parent
8d6762d37d
commit
2b5c677933
29 changed files with 3583 additions and 206 deletions
|
@ -13,7 +13,7 @@ export function Header() {
|
||||||
const pathname = usePathname();
|
const pathname = usePathname();
|
||||||
|
|
||||||
const site = sites?.data?.find(
|
const site = sites?.data?.find(
|
||||||
(site) => site.site_id === Number(pathname.split("/")[1])
|
(site) => site.siteId === Number(pathname.split("/")[1])
|
||||||
);
|
);
|
||||||
|
|
||||||
// Check which tab is active based on the current path
|
// Check which tab is active based on the current path
|
||||||
|
|
|
@ -27,7 +27,7 @@ export function NoData({
|
||||||
</div>
|
</div>
|
||||||
<CodeSnippet
|
<CodeSnippet
|
||||||
language="HTML"
|
language="HTML"
|
||||||
code={`<script\n src="${BACKEND_URL}/script.js"\n site-id="${siteMetadata?.site_id}"\n defer\n/>`}
|
code={`<script\n src="${BACKEND_URL}/script.js"\n site-id="${siteMetadata?.siteId}"\n defer\n/>`}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
|
|
|
@ -41,7 +41,7 @@ export function SubHeader() {
|
||||||
const pathname = usePathname();
|
const pathname = usePathname();
|
||||||
|
|
||||||
const site = sites?.data?.find(
|
const site = sites?.data?.find(
|
||||||
(site) => site.site_id === Number(pathname.slice(1))
|
(site) => site.siteId === Number(pathname.slice(1))
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -25,7 +25,7 @@ export function DeleteSite({
|
||||||
isOpen={isOpen}
|
isOpen={isOpen}
|
||||||
setIsOpen={setIsOpen}
|
setIsOpen={setIsOpen}
|
||||||
onConfirm={async () => {
|
onConfirm={async () => {
|
||||||
await deleteSite(site.site_id);
|
await deleteSite(site.siteId);
|
||||||
refetch();
|
refetch();
|
||||||
router.push("/");
|
router.push("/");
|
||||||
}}
|
}}
|
||||||
|
|
|
@ -13,7 +13,7 @@ export default function SettingsPage() {
|
||||||
const pathname = usePathname();
|
const pathname = usePathname();
|
||||||
|
|
||||||
const site = sites?.data?.find(
|
const site = sites?.data?.find(
|
||||||
(site) => site.site_id === Number(pathname.split("/")[1])
|
(site) => site.siteId === Number(pathname.split("/")[1])
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -15,7 +15,7 @@ export default function Home() {
|
||||||
</div>
|
</div>
|
||||||
<div className="grid grid-cols-3 gap-4 mt-4">
|
<div className="grid grid-cols-3 gap-4 mt-4">
|
||||||
{sites?.data?.map((site) => (
|
{sites?.data?.map((site) => (
|
||||||
<Link href={`/${site.site_id}`} key={site.site_id}>
|
<Link href={`/${site.siteId}`} key={site.siteId}>
|
||||||
<div className="flex p-4 rounded-lg bg-neutral-900 text-lg font-semibold gap-2">
|
<div className="flex p-4 rounded-lg bg-neutral-900 text-lg font-semibold gap-2">
|
||||||
<img
|
<img
|
||||||
className="w-6 mr-1"
|
className="w-6 mr-1"
|
||||||
|
|
|
@ -178,12 +178,12 @@ export function useGetOverview(periodTime?: PeriodTime) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export type GetSitesResponse = {
|
export type GetSitesResponse = {
|
||||||
site_id: number;
|
siteId: number;
|
||||||
site_name: string;
|
name: string;
|
||||||
domain: string;
|
domain: string;
|
||||||
created_at: string;
|
createdAt: string;
|
||||||
updated_at: string;
|
updatedAt: string;
|
||||||
created_by: string;
|
createdBy: string;
|
||||||
}[];
|
}[];
|
||||||
|
|
||||||
export function useGetSites() {
|
export function useGetSites() {
|
||||||
|
|
|
@ -3,7 +3,7 @@ import { useGetSites } from "./api";
|
||||||
export function useGetSiteMetadata(siteId: string) {
|
export function useGetSiteMetadata(siteId: string) {
|
||||||
const { data, isLoading } = useGetSites();
|
const { data, isLoading } = useGetSites();
|
||||||
return {
|
return {
|
||||||
siteMetadata: data?.data?.find((site) => site.site_id === Number(siteId)),
|
siteMetadata: data?.data?.find((site) => site.siteId === Number(siteId)),
|
||||||
isLoading,
|
isLoading,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,14 +1,46 @@
|
||||||
# syntax=docker/dockerfile:1
|
# syntax=docker/dockerfile:1
|
||||||
|
|
||||||
FROM node:22-alpine
|
FROM node:20-alpine AS builder
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
COPY ["package.json", "package-lock.json*", "./"]
|
# Install dependencies
|
||||||
|
COPY package*.json ./
|
||||||
RUN npm install
|
RUN npm ci
|
||||||
|
|
||||||
|
# Copy source code
|
||||||
COPY . .
|
COPY . .
|
||||||
|
|
||||||
|
# Build the application
|
||||||
RUN npm run build
|
RUN npm run build
|
||||||
|
|
||||||
CMD [ "npm", "start" ]
|
# Generate migrations (but don't run them)
|
||||||
|
RUN mkdir -p /app/drizzle
|
||||||
|
RUN npx drizzle-kit generate || echo "Skipping migration generation during build"
|
||||||
|
|
||||||
|
# Runtime image
|
||||||
|
FROM node:20-alpine
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Install PostgreSQL client for migrations
|
||||||
|
RUN apk add --no-cache postgresql-client
|
||||||
|
|
||||||
|
# Copy built application and dependencies
|
||||||
|
COPY --from=builder /app/package*.json ./
|
||||||
|
COPY --from=builder /app/dist ./dist
|
||||||
|
COPY --from=builder /app/drizzle ./drizzle
|
||||||
|
COPY --from=builder /app/node_modules ./node_modules
|
||||||
|
COPY --from=builder /app/docker-entrypoint.sh /docker-entrypoint.sh
|
||||||
|
COPY --from=builder /app/drizzle.config.ts ./drizzle.config.ts
|
||||||
|
COPY --from=builder /app/public ./public
|
||||||
|
|
||||||
|
# Make the entrypoint executable
|
||||||
|
RUN chmod +x /docker-entrypoint.sh
|
||||||
|
|
||||||
|
# Expose the API port
|
||||||
|
EXPOSE 3001
|
||||||
|
|
||||||
|
# Use our custom entrypoint script
|
||||||
|
ENTRYPOINT ["/docker-entrypoint.sh"]
|
||||||
|
CMD ["node", "dist/index.js"]
|
|
@ -1,7 +0,0 @@
|
||||||
create table "user" ("id" text not null primary key, "name" text not null, "email" text not null unique, "emailVerified" boolean not null, "image" text, "createdAt" timestamp not null, "updatedAt" timestamp not null);
|
|
||||||
|
|
||||||
create table "session" ("id" text not null primary key, "expiresAt" timestamp not null, "token" text not null unique, "createdAt" timestamp not null, "updatedAt" timestamp not null, "ipAddress" text, "userAgent" text, "userId" text not null references "user" ("id"));
|
|
||||||
|
|
||||||
create table "account" ("id" text not null primary key, "accountId" text not null, "providerId" text not null, "userId" text not null references "user" ("id"), "accessToken" text, "refreshToken" text, "idToken" text, "accessTokenExpiresAt" timestamp, "refreshTokenExpiresAt" timestamp, "scope" text, "password" text, "createdAt" timestamp not null, "updatedAt" timestamp not null);
|
|
||||||
|
|
||||||
create table "verification" ("id" text not null primary key, "identifier" text not null, "value" text not null, "expiresAt" timestamp not null, "createdAt" timestamp, "updatedAt" timestamp)
|
|
13
server/docker-entrypoint.sh
Normal file
13
server/docker-entrypoint.sh
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
#!/bin/sh
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# Docker Compose already ensures services are ready using healthchecks
|
||||||
|
# and dependency conditions in the docker-compose.yml file
|
||||||
|
|
||||||
|
# Run migrations explicitly using the npm script
|
||||||
|
echo "Running database migrations...."
|
||||||
|
# npm run db:migrate
|
||||||
|
|
||||||
|
# Start the application
|
||||||
|
echo "Starting application..."
|
||||||
|
exec "$@"
|
18
server/drizzle.config.ts
Normal file
18
server/drizzle.config.ts
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
import "dotenv/config";
|
||||||
|
import { defineConfig } from "drizzle-kit";
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
schema: "./src/db/postgres/schema.ts",
|
||||||
|
out: "./drizzle",
|
||||||
|
dialect: "postgresql",
|
||||||
|
dbCredentials: {
|
||||||
|
host: "postgres",
|
||||||
|
port: 5432,
|
||||||
|
database: "analytics",
|
||||||
|
user: "frog",
|
||||||
|
password: "frog",
|
||||||
|
ssl: false,
|
||||||
|
},
|
||||||
|
verbose: true,
|
||||||
|
strict: true,
|
||||||
|
});
|
109
server/drizzle/0000_gorgeous_shriek.sql
Normal file
109
server/drizzle/0000_gorgeous_shriek.sql
Normal file
|
@ -0,0 +1,109 @@
|
||||||
|
CREATE TABLE "account" (
|
||||||
|
"id" text PRIMARY KEY NOT NULL,
|
||||||
|
"accountId" text NOT NULL,
|
||||||
|
"providerId" text NOT NULL,
|
||||||
|
"userId" text NOT NULL,
|
||||||
|
"accessToken" text,
|
||||||
|
"refreshToken" text,
|
||||||
|
"idToken" text,
|
||||||
|
"accessTokenExpiresAt" timestamp,
|
||||||
|
"refreshTokenExpiresAt" timestamp,
|
||||||
|
"scope" text,
|
||||||
|
"password" text,
|
||||||
|
"createdAt" timestamp NOT NULL,
|
||||||
|
"updatedAt" timestamp NOT NULL
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE "active_sessions" (
|
||||||
|
"session_id" text PRIMARY KEY NOT NULL,
|
||||||
|
"site_id" integer,
|
||||||
|
"user_id" text,
|
||||||
|
"hostname" text,
|
||||||
|
"start_time" timestamp DEFAULT now(),
|
||||||
|
"last_activity" timestamp DEFAULT now(),
|
||||||
|
"pageviews" integer DEFAULT 0,
|
||||||
|
"entry_page" text,
|
||||||
|
"exit_page" text,
|
||||||
|
"device_type" text,
|
||||||
|
"screen_width" integer,
|
||||||
|
"screen_height" integer,
|
||||||
|
"browser" text,
|
||||||
|
"operating_system" text,
|
||||||
|
"language" text,
|
||||||
|
"referrer" text
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE "member" (
|
||||||
|
"id" text PRIMARY KEY NOT NULL,
|
||||||
|
"organizationId" text NOT NULL,
|
||||||
|
"userId" text NOT NULL,
|
||||||
|
"role" text NOT NULL,
|
||||||
|
"createdAt" timestamp NOT NULL
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE "organization" (
|
||||||
|
"id" text PRIMARY KEY NOT NULL,
|
||||||
|
"name" text NOT NULL,
|
||||||
|
"slug" text NOT NULL,
|
||||||
|
"logo" text,
|
||||||
|
"createdAt" timestamp NOT NULL,
|
||||||
|
"metadata" text,
|
||||||
|
CONSTRAINT "organization_slug_unique" UNIQUE("slug")
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE "session" (
|
||||||
|
"id" text PRIMARY KEY NOT NULL,
|
||||||
|
"expiresAt" timestamp NOT NULL,
|
||||||
|
"token" text NOT NULL,
|
||||||
|
"createdAt" timestamp NOT NULL,
|
||||||
|
"updatedAt" timestamp NOT NULL,
|
||||||
|
"ipAddress" text,
|
||||||
|
"userAgent" text,
|
||||||
|
"userId" text NOT NULL,
|
||||||
|
"impersonatedBy" text,
|
||||||
|
"activeOrganizationId" text,
|
||||||
|
CONSTRAINT "session_token_unique" UNIQUE("token")
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE "sites" (
|
||||||
|
"site_id" serial PRIMARY KEY NOT NULL,
|
||||||
|
"name" text NOT NULL,
|
||||||
|
"domain" text NOT NULL,
|
||||||
|
"created_at" timestamp DEFAULT now(),
|
||||||
|
"updated_at" timestamp DEFAULT now(),
|
||||||
|
"created_by" text NOT NULL,
|
||||||
|
CONSTRAINT "sites_domain_unique" UNIQUE("domain")
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE "user" (
|
||||||
|
"id" text PRIMARY KEY NOT NULL,
|
||||||
|
"name" text NOT NULL,
|
||||||
|
"username" text NOT NULL,
|
||||||
|
"email" text NOT NULL,
|
||||||
|
"emailVerified" boolean NOT NULL,
|
||||||
|
"image" text,
|
||||||
|
"createdAt" timestamp NOT NULL,
|
||||||
|
"updatedAt" timestamp NOT NULL,
|
||||||
|
"role" text DEFAULT 'user' NOT NULL,
|
||||||
|
"displayUsername" text,
|
||||||
|
"banned" boolean,
|
||||||
|
"banReason" text,
|
||||||
|
"banExpires" timestamp,
|
||||||
|
CONSTRAINT "user_username_unique" UNIQUE("username"),
|
||||||
|
CONSTRAINT "user_email_unique" UNIQUE("email")
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE "verification" (
|
||||||
|
"id" text PRIMARY KEY NOT NULL,
|
||||||
|
"identifier" text NOT NULL,
|
||||||
|
"value" text NOT NULL,
|
||||||
|
"expiresAt" timestamp NOT NULL,
|
||||||
|
"createdAt" timestamp,
|
||||||
|
"updatedAt" timestamp
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
ALTER TABLE "account" ADD CONSTRAINT "account_userId_user_id_fk" FOREIGN KEY ("userId") REFERENCES "public"."user"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
|
||||||
|
ALTER TABLE "member" ADD CONSTRAINT "member_organizationId_organization_id_fk" FOREIGN KEY ("organizationId") REFERENCES "public"."organization"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
|
||||||
|
ALTER TABLE "member" ADD CONSTRAINT "member_userId_user_id_fk" FOREIGN KEY ("userId") REFERENCES "public"."user"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
|
||||||
|
ALTER TABLE "session" ADD CONSTRAINT "session_userId_user_id_fk" FOREIGN KEY ("userId") REFERENCES "public"."user"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
|
||||||
|
ALTER TABLE "sites" ADD CONSTRAINT "sites_created_by_user_id_fk" FOREIGN KEY ("created_by") REFERENCES "public"."user"("id") ON DELETE no action ON UPDATE no action;
|
12
server/drizzle/0001_sweet_retro_girl.sql
Normal file
12
server/drizzle/0001_sweet_retro_girl.sql
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
CREATE TABLE "reports" (
|
||||||
|
"report_id" serial PRIMARY KEY NOT NULL,
|
||||||
|
"site_id" integer,
|
||||||
|
"user_id" text,
|
||||||
|
"report_type" text,
|
||||||
|
"data" jsonb,
|
||||||
|
"created_at" timestamp DEFAULT now(),
|
||||||
|
"updated_at" timestamp DEFAULT now()
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
ALTER TABLE "reports" ADD CONSTRAINT "reports_site_id_sites_site_id_fk" FOREIGN KEY ("site_id") REFERENCES "public"."sites"("site_id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
|
||||||
|
ALTER TABLE "reports" ADD CONSTRAINT "reports_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE no action ON UPDATE no action;
|
687
server/drizzle/meta/0000_snapshot.json
Normal file
687
server/drizzle/meta/0000_snapshot.json
Normal file
|
@ -0,0 +1,687 @@
|
||||||
|
{
|
||||||
|
"id": "a1455965-aa57-4090-83fa-c3863fbdca3d",
|
||||||
|
"prevId": "00000000-0000-0000-0000-000000000000",
|
||||||
|
"version": "7",
|
||||||
|
"dialect": "postgresql",
|
||||||
|
"tables": {
|
||||||
|
"public.account": {
|
||||||
|
"name": "account",
|
||||||
|
"schema": "",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"accountId": {
|
||||||
|
"name": "accountId",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"providerId": {
|
||||||
|
"name": "providerId",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"userId": {
|
||||||
|
"name": "userId",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"accessToken": {
|
||||||
|
"name": "accessToken",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"refreshToken": {
|
||||||
|
"name": "refreshToken",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"idToken": {
|
||||||
|
"name": "idToken",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"accessTokenExpiresAt": {
|
||||||
|
"name": "accessTokenExpiresAt",
|
||||||
|
"type": "timestamp",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"refreshTokenExpiresAt": {
|
||||||
|
"name": "refreshTokenExpiresAt",
|
||||||
|
"type": "timestamp",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"scope": {
|
||||||
|
"name": "scope",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"password": {
|
||||||
|
"name": "password",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"createdAt": {
|
||||||
|
"name": "createdAt",
|
||||||
|
"type": "timestamp",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"updatedAt": {
|
||||||
|
"name": "updatedAt",
|
||||||
|
"type": "timestamp",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {
|
||||||
|
"account_userId_user_id_fk": {
|
||||||
|
"name": "account_userId_user_id_fk",
|
||||||
|
"tableFrom": "account",
|
||||||
|
"tableTo": "user",
|
||||||
|
"columnsFrom": [
|
||||||
|
"userId"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "no action",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"policies": {},
|
||||||
|
"checkConstraints": {},
|
||||||
|
"isRLSEnabled": false
|
||||||
|
},
|
||||||
|
"public.active_sessions": {
|
||||||
|
"name": "active_sessions",
|
||||||
|
"schema": "",
|
||||||
|
"columns": {
|
||||||
|
"session_id": {
|
||||||
|
"name": "session_id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"site_id": {
|
||||||
|
"name": "site_id",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"user_id": {
|
||||||
|
"name": "user_id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"hostname": {
|
||||||
|
"name": "hostname",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"start_time": {
|
||||||
|
"name": "start_time",
|
||||||
|
"type": "timestamp",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"default": "now()"
|
||||||
|
},
|
||||||
|
"last_activity": {
|
||||||
|
"name": "last_activity",
|
||||||
|
"type": "timestamp",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"default": "now()"
|
||||||
|
},
|
||||||
|
"pageviews": {
|
||||||
|
"name": "pageviews",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"default": 0
|
||||||
|
},
|
||||||
|
"entry_page": {
|
||||||
|
"name": "entry_page",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"exit_page": {
|
||||||
|
"name": "exit_page",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"device_type": {
|
||||||
|
"name": "device_type",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"screen_width": {
|
||||||
|
"name": "screen_width",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"screen_height": {
|
||||||
|
"name": "screen_height",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"browser": {
|
||||||
|
"name": "browser",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"operating_system": {
|
||||||
|
"name": "operating_system",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"language": {
|
||||||
|
"name": "language",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"referrer": {
|
||||||
|
"name": "referrer",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"policies": {},
|
||||||
|
"checkConstraints": {},
|
||||||
|
"isRLSEnabled": false
|
||||||
|
},
|
||||||
|
"public.member": {
|
||||||
|
"name": "member",
|
||||||
|
"schema": "",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"organizationId": {
|
||||||
|
"name": "organizationId",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"userId": {
|
||||||
|
"name": "userId",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"role": {
|
||||||
|
"name": "role",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"createdAt": {
|
||||||
|
"name": "createdAt",
|
||||||
|
"type": "timestamp",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {
|
||||||
|
"member_organizationId_organization_id_fk": {
|
||||||
|
"name": "member_organizationId_organization_id_fk",
|
||||||
|
"tableFrom": "member",
|
||||||
|
"tableTo": "organization",
|
||||||
|
"columnsFrom": [
|
||||||
|
"organizationId"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "no action",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
},
|
||||||
|
"member_userId_user_id_fk": {
|
||||||
|
"name": "member_userId_user_id_fk",
|
||||||
|
"tableFrom": "member",
|
||||||
|
"tableTo": "user",
|
||||||
|
"columnsFrom": [
|
||||||
|
"userId"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "no action",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"policies": {},
|
||||||
|
"checkConstraints": {},
|
||||||
|
"isRLSEnabled": false
|
||||||
|
},
|
||||||
|
"public.organization": {
|
||||||
|
"name": "organization",
|
||||||
|
"schema": "",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"name": "name",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"slug": {
|
||||||
|
"name": "slug",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"logo": {
|
||||||
|
"name": "logo",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"createdAt": {
|
||||||
|
"name": "createdAt",
|
||||||
|
"type": "timestamp",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"metadata": {
|
||||||
|
"name": "metadata",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {
|
||||||
|
"organization_slug_unique": {
|
||||||
|
"name": "organization_slug_unique",
|
||||||
|
"nullsNotDistinct": false,
|
||||||
|
"columns": [
|
||||||
|
"slug"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"policies": {},
|
||||||
|
"checkConstraints": {},
|
||||||
|
"isRLSEnabled": false
|
||||||
|
},
|
||||||
|
"public.session": {
|
||||||
|
"name": "session",
|
||||||
|
"schema": "",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"expiresAt": {
|
||||||
|
"name": "expiresAt",
|
||||||
|
"type": "timestamp",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"token": {
|
||||||
|
"name": "token",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"createdAt": {
|
||||||
|
"name": "createdAt",
|
||||||
|
"type": "timestamp",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"updatedAt": {
|
||||||
|
"name": "updatedAt",
|
||||||
|
"type": "timestamp",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"ipAddress": {
|
||||||
|
"name": "ipAddress",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"userAgent": {
|
||||||
|
"name": "userAgent",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"userId": {
|
||||||
|
"name": "userId",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"impersonatedBy": {
|
||||||
|
"name": "impersonatedBy",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"activeOrganizationId": {
|
||||||
|
"name": "activeOrganizationId",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {
|
||||||
|
"session_userId_user_id_fk": {
|
||||||
|
"name": "session_userId_user_id_fk",
|
||||||
|
"tableFrom": "session",
|
||||||
|
"tableTo": "user",
|
||||||
|
"columnsFrom": [
|
||||||
|
"userId"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "no action",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {
|
||||||
|
"session_token_unique": {
|
||||||
|
"name": "session_token_unique",
|
||||||
|
"nullsNotDistinct": false,
|
||||||
|
"columns": [
|
||||||
|
"token"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"policies": {},
|
||||||
|
"checkConstraints": {},
|
||||||
|
"isRLSEnabled": false
|
||||||
|
},
|
||||||
|
"public.sites": {
|
||||||
|
"name": "sites",
|
||||||
|
"schema": "",
|
||||||
|
"columns": {
|
||||||
|
"site_id": {
|
||||||
|
"name": "site_id",
|
||||||
|
"type": "serial",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"name": "name",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"domain": {
|
||||||
|
"name": "domain",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"created_at": {
|
||||||
|
"name": "created_at",
|
||||||
|
"type": "timestamp",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"default": "now()"
|
||||||
|
},
|
||||||
|
"updated_at": {
|
||||||
|
"name": "updated_at",
|
||||||
|
"type": "timestamp",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"default": "now()"
|
||||||
|
},
|
||||||
|
"created_by": {
|
||||||
|
"name": "created_by",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {
|
||||||
|
"sites_created_by_user_id_fk": {
|
||||||
|
"name": "sites_created_by_user_id_fk",
|
||||||
|
"tableFrom": "sites",
|
||||||
|
"tableTo": "user",
|
||||||
|
"columnsFrom": [
|
||||||
|
"created_by"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "no action",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {
|
||||||
|
"sites_domain_unique": {
|
||||||
|
"name": "sites_domain_unique",
|
||||||
|
"nullsNotDistinct": false,
|
||||||
|
"columns": [
|
||||||
|
"domain"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"policies": {},
|
||||||
|
"checkConstraints": {},
|
||||||
|
"isRLSEnabled": false
|
||||||
|
},
|
||||||
|
"public.user": {
|
||||||
|
"name": "user",
|
||||||
|
"schema": "",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"name": "name",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"username": {
|
||||||
|
"name": "username",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"email": {
|
||||||
|
"name": "email",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"emailVerified": {
|
||||||
|
"name": "emailVerified",
|
||||||
|
"type": "boolean",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"image": {
|
||||||
|
"name": "image",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"createdAt": {
|
||||||
|
"name": "createdAt",
|
||||||
|
"type": "timestamp",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"updatedAt": {
|
||||||
|
"name": "updatedAt",
|
||||||
|
"type": "timestamp",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"role": {
|
||||||
|
"name": "role",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "'user'"
|
||||||
|
},
|
||||||
|
"displayUsername": {
|
||||||
|
"name": "displayUsername",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"banned": {
|
||||||
|
"name": "banned",
|
||||||
|
"type": "boolean",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"banReason": {
|
||||||
|
"name": "banReason",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"banExpires": {
|
||||||
|
"name": "banExpires",
|
||||||
|
"type": "timestamp",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {
|
||||||
|
"user_username_unique": {
|
||||||
|
"name": "user_username_unique",
|
||||||
|
"nullsNotDistinct": false,
|
||||||
|
"columns": [
|
||||||
|
"username"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"user_email_unique": {
|
||||||
|
"name": "user_email_unique",
|
||||||
|
"nullsNotDistinct": false,
|
||||||
|
"columns": [
|
||||||
|
"email"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"policies": {},
|
||||||
|
"checkConstraints": {},
|
||||||
|
"isRLSEnabled": false
|
||||||
|
},
|
||||||
|
"public.verification": {
|
||||||
|
"name": "verification",
|
||||||
|
"schema": "",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"identifier": {
|
||||||
|
"name": "identifier",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"value": {
|
||||||
|
"name": "value",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"expiresAt": {
|
||||||
|
"name": "expiresAt",
|
||||||
|
"type": "timestamp",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"createdAt": {
|
||||||
|
"name": "createdAt",
|
||||||
|
"type": "timestamp",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"updatedAt": {
|
||||||
|
"name": "updatedAt",
|
||||||
|
"type": "timestamp",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"policies": {},
|
||||||
|
"checkConstraints": {},
|
||||||
|
"isRLSEnabled": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"enums": {},
|
||||||
|
"schemas": {},
|
||||||
|
"sequences": {},
|
||||||
|
"roles": {},
|
||||||
|
"policies": {},
|
||||||
|
"views": {},
|
||||||
|
"_meta": {
|
||||||
|
"columns": {},
|
||||||
|
"schemas": {},
|
||||||
|
"tables": {}
|
||||||
|
}
|
||||||
|
}
|
771
server/drizzle/meta/0001_snapshot.json
Normal file
771
server/drizzle/meta/0001_snapshot.json
Normal file
|
@ -0,0 +1,771 @@
|
||||||
|
{
|
||||||
|
"id": "b13c246a-71be-4b88-9672-eb0943b3fe19",
|
||||||
|
"prevId": "a1455965-aa57-4090-83fa-c3863fbdca3d",
|
||||||
|
"version": "7",
|
||||||
|
"dialect": "postgresql",
|
||||||
|
"tables": {
|
||||||
|
"public.account": {
|
||||||
|
"name": "account",
|
||||||
|
"schema": "",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"accountId": {
|
||||||
|
"name": "accountId",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"providerId": {
|
||||||
|
"name": "providerId",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"userId": {
|
||||||
|
"name": "userId",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"accessToken": {
|
||||||
|
"name": "accessToken",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"refreshToken": {
|
||||||
|
"name": "refreshToken",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"idToken": {
|
||||||
|
"name": "idToken",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"accessTokenExpiresAt": {
|
||||||
|
"name": "accessTokenExpiresAt",
|
||||||
|
"type": "timestamp",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"refreshTokenExpiresAt": {
|
||||||
|
"name": "refreshTokenExpiresAt",
|
||||||
|
"type": "timestamp",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"scope": {
|
||||||
|
"name": "scope",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"password": {
|
||||||
|
"name": "password",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"createdAt": {
|
||||||
|
"name": "createdAt",
|
||||||
|
"type": "timestamp",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"updatedAt": {
|
||||||
|
"name": "updatedAt",
|
||||||
|
"type": "timestamp",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {
|
||||||
|
"account_userId_user_id_fk": {
|
||||||
|
"name": "account_userId_user_id_fk",
|
||||||
|
"tableFrom": "account",
|
||||||
|
"tableTo": "user",
|
||||||
|
"columnsFrom": [
|
||||||
|
"userId"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "no action",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"policies": {},
|
||||||
|
"checkConstraints": {},
|
||||||
|
"isRLSEnabled": false
|
||||||
|
},
|
||||||
|
"public.active_sessions": {
|
||||||
|
"name": "active_sessions",
|
||||||
|
"schema": "",
|
||||||
|
"columns": {
|
||||||
|
"session_id": {
|
||||||
|
"name": "session_id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"site_id": {
|
||||||
|
"name": "site_id",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"user_id": {
|
||||||
|
"name": "user_id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"hostname": {
|
||||||
|
"name": "hostname",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"start_time": {
|
||||||
|
"name": "start_time",
|
||||||
|
"type": "timestamp",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"default": "now()"
|
||||||
|
},
|
||||||
|
"last_activity": {
|
||||||
|
"name": "last_activity",
|
||||||
|
"type": "timestamp",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"default": "now()"
|
||||||
|
},
|
||||||
|
"pageviews": {
|
||||||
|
"name": "pageviews",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"default": 0
|
||||||
|
},
|
||||||
|
"entry_page": {
|
||||||
|
"name": "entry_page",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"exit_page": {
|
||||||
|
"name": "exit_page",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"device_type": {
|
||||||
|
"name": "device_type",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"screen_width": {
|
||||||
|
"name": "screen_width",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"screen_height": {
|
||||||
|
"name": "screen_height",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"browser": {
|
||||||
|
"name": "browser",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"operating_system": {
|
||||||
|
"name": "operating_system",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"language": {
|
||||||
|
"name": "language",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"referrer": {
|
||||||
|
"name": "referrer",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"policies": {},
|
||||||
|
"checkConstraints": {},
|
||||||
|
"isRLSEnabled": false
|
||||||
|
},
|
||||||
|
"public.member": {
|
||||||
|
"name": "member",
|
||||||
|
"schema": "",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"organizationId": {
|
||||||
|
"name": "organizationId",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"userId": {
|
||||||
|
"name": "userId",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"role": {
|
||||||
|
"name": "role",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"createdAt": {
|
||||||
|
"name": "createdAt",
|
||||||
|
"type": "timestamp",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {
|
||||||
|
"member_organizationId_organization_id_fk": {
|
||||||
|
"name": "member_organizationId_organization_id_fk",
|
||||||
|
"tableFrom": "member",
|
||||||
|
"tableTo": "organization",
|
||||||
|
"columnsFrom": [
|
||||||
|
"organizationId"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "no action",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
},
|
||||||
|
"member_userId_user_id_fk": {
|
||||||
|
"name": "member_userId_user_id_fk",
|
||||||
|
"tableFrom": "member",
|
||||||
|
"tableTo": "user",
|
||||||
|
"columnsFrom": [
|
||||||
|
"userId"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "no action",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"policies": {},
|
||||||
|
"checkConstraints": {},
|
||||||
|
"isRLSEnabled": false
|
||||||
|
},
|
||||||
|
"public.organization": {
|
||||||
|
"name": "organization",
|
||||||
|
"schema": "",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"name": "name",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"slug": {
|
||||||
|
"name": "slug",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"logo": {
|
||||||
|
"name": "logo",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"createdAt": {
|
||||||
|
"name": "createdAt",
|
||||||
|
"type": "timestamp",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"metadata": {
|
||||||
|
"name": "metadata",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {
|
||||||
|
"organization_slug_unique": {
|
||||||
|
"name": "organization_slug_unique",
|
||||||
|
"nullsNotDistinct": false,
|
||||||
|
"columns": [
|
||||||
|
"slug"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"policies": {},
|
||||||
|
"checkConstraints": {},
|
||||||
|
"isRLSEnabled": false
|
||||||
|
},
|
||||||
|
"public.reports": {
|
||||||
|
"name": "reports",
|
||||||
|
"schema": "",
|
||||||
|
"columns": {
|
||||||
|
"report_id": {
|
||||||
|
"name": "report_id",
|
||||||
|
"type": "serial",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"site_id": {
|
||||||
|
"name": "site_id",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"user_id": {
|
||||||
|
"name": "user_id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"report_type": {
|
||||||
|
"name": "report_type",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"name": "data",
|
||||||
|
"type": "jsonb",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"created_at": {
|
||||||
|
"name": "created_at",
|
||||||
|
"type": "timestamp",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"default": "now()"
|
||||||
|
},
|
||||||
|
"updated_at": {
|
||||||
|
"name": "updated_at",
|
||||||
|
"type": "timestamp",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"default": "now()"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {
|
||||||
|
"reports_site_id_sites_site_id_fk": {
|
||||||
|
"name": "reports_site_id_sites_site_id_fk",
|
||||||
|
"tableFrom": "reports",
|
||||||
|
"tableTo": "sites",
|
||||||
|
"columnsFrom": [
|
||||||
|
"site_id"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"site_id"
|
||||||
|
],
|
||||||
|
"onDelete": "no action",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
},
|
||||||
|
"reports_user_id_user_id_fk": {
|
||||||
|
"name": "reports_user_id_user_id_fk",
|
||||||
|
"tableFrom": "reports",
|
||||||
|
"tableTo": "user",
|
||||||
|
"columnsFrom": [
|
||||||
|
"user_id"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "no action",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"policies": {},
|
||||||
|
"checkConstraints": {},
|
||||||
|
"isRLSEnabled": false
|
||||||
|
},
|
||||||
|
"public.session": {
|
||||||
|
"name": "session",
|
||||||
|
"schema": "",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"expiresAt": {
|
||||||
|
"name": "expiresAt",
|
||||||
|
"type": "timestamp",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"token": {
|
||||||
|
"name": "token",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"createdAt": {
|
||||||
|
"name": "createdAt",
|
||||||
|
"type": "timestamp",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"updatedAt": {
|
||||||
|
"name": "updatedAt",
|
||||||
|
"type": "timestamp",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"ipAddress": {
|
||||||
|
"name": "ipAddress",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"userAgent": {
|
||||||
|
"name": "userAgent",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"userId": {
|
||||||
|
"name": "userId",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"impersonatedBy": {
|
||||||
|
"name": "impersonatedBy",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"activeOrganizationId": {
|
||||||
|
"name": "activeOrganizationId",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {
|
||||||
|
"session_userId_user_id_fk": {
|
||||||
|
"name": "session_userId_user_id_fk",
|
||||||
|
"tableFrom": "session",
|
||||||
|
"tableTo": "user",
|
||||||
|
"columnsFrom": [
|
||||||
|
"userId"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "no action",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {
|
||||||
|
"session_token_unique": {
|
||||||
|
"name": "session_token_unique",
|
||||||
|
"nullsNotDistinct": false,
|
||||||
|
"columns": [
|
||||||
|
"token"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"policies": {},
|
||||||
|
"checkConstraints": {},
|
||||||
|
"isRLSEnabled": false
|
||||||
|
},
|
||||||
|
"public.sites": {
|
||||||
|
"name": "sites",
|
||||||
|
"schema": "",
|
||||||
|
"columns": {
|
||||||
|
"site_id": {
|
||||||
|
"name": "site_id",
|
||||||
|
"type": "serial",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"name": "name",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"domain": {
|
||||||
|
"name": "domain",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"created_at": {
|
||||||
|
"name": "created_at",
|
||||||
|
"type": "timestamp",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"default": "now()"
|
||||||
|
},
|
||||||
|
"updated_at": {
|
||||||
|
"name": "updated_at",
|
||||||
|
"type": "timestamp",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"default": "now()"
|
||||||
|
},
|
||||||
|
"created_by": {
|
||||||
|
"name": "created_by",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {
|
||||||
|
"sites_created_by_user_id_fk": {
|
||||||
|
"name": "sites_created_by_user_id_fk",
|
||||||
|
"tableFrom": "sites",
|
||||||
|
"tableTo": "user",
|
||||||
|
"columnsFrom": [
|
||||||
|
"created_by"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "no action",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {
|
||||||
|
"sites_domain_unique": {
|
||||||
|
"name": "sites_domain_unique",
|
||||||
|
"nullsNotDistinct": false,
|
||||||
|
"columns": [
|
||||||
|
"domain"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"policies": {},
|
||||||
|
"checkConstraints": {},
|
||||||
|
"isRLSEnabled": false
|
||||||
|
},
|
||||||
|
"public.user": {
|
||||||
|
"name": "user",
|
||||||
|
"schema": "",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"name": "name",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"username": {
|
||||||
|
"name": "username",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"email": {
|
||||||
|
"name": "email",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"emailVerified": {
|
||||||
|
"name": "emailVerified",
|
||||||
|
"type": "boolean",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"image": {
|
||||||
|
"name": "image",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"createdAt": {
|
||||||
|
"name": "createdAt",
|
||||||
|
"type": "timestamp",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"updatedAt": {
|
||||||
|
"name": "updatedAt",
|
||||||
|
"type": "timestamp",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"role": {
|
||||||
|
"name": "role",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "'user'"
|
||||||
|
},
|
||||||
|
"displayUsername": {
|
||||||
|
"name": "displayUsername",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"banned": {
|
||||||
|
"name": "banned",
|
||||||
|
"type": "boolean",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"banReason": {
|
||||||
|
"name": "banReason",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"banExpires": {
|
||||||
|
"name": "banExpires",
|
||||||
|
"type": "timestamp",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {
|
||||||
|
"user_username_unique": {
|
||||||
|
"name": "user_username_unique",
|
||||||
|
"nullsNotDistinct": false,
|
||||||
|
"columns": [
|
||||||
|
"username"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"user_email_unique": {
|
||||||
|
"name": "user_email_unique",
|
||||||
|
"nullsNotDistinct": false,
|
||||||
|
"columns": [
|
||||||
|
"email"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"policies": {},
|
||||||
|
"checkConstraints": {},
|
||||||
|
"isRLSEnabled": false
|
||||||
|
},
|
||||||
|
"public.verification": {
|
||||||
|
"name": "verification",
|
||||||
|
"schema": "",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"identifier": {
|
||||||
|
"name": "identifier",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"value": {
|
||||||
|
"name": "value",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"expiresAt": {
|
||||||
|
"name": "expiresAt",
|
||||||
|
"type": "timestamp",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"createdAt": {
|
||||||
|
"name": "createdAt",
|
||||||
|
"type": "timestamp",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"updatedAt": {
|
||||||
|
"name": "updatedAt",
|
||||||
|
"type": "timestamp",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"policies": {},
|
||||||
|
"checkConstraints": {},
|
||||||
|
"isRLSEnabled": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"enums": {},
|
||||||
|
"schemas": {},
|
||||||
|
"sequences": {},
|
||||||
|
"roles": {},
|
||||||
|
"policies": {},
|
||||||
|
"views": {},
|
||||||
|
"_meta": {
|
||||||
|
"columns": {},
|
||||||
|
"schemas": {},
|
||||||
|
"tables": {}
|
||||||
|
}
|
||||||
|
}
|
20
server/drizzle/meta/_journal.json
Normal file
20
server/drizzle/meta/_journal.json
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
{
|
||||||
|
"version": "7",
|
||||||
|
"dialect": "postgresql",
|
||||||
|
"entries": [
|
||||||
|
{
|
||||||
|
"idx": 0,
|
||||||
|
"version": "7",
|
||||||
|
"when": 1741156226094,
|
||||||
|
"tag": "0000_gorgeous_shriek",
|
||||||
|
"breakpoints": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 1,
|
||||||
|
"version": "7",
|
||||||
|
"when": 1741197517430,
|
||||||
|
"tag": "0001_sweet_retro_girl",
|
||||||
|
"breakpoints": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
1542
server/package-lock.json
generated
1542
server/package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
@ -7,7 +7,12 @@
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "tsc && node dist/index.js",
|
"dev": "tsc && node dist/index.js",
|
||||||
"build": "tsc",
|
"build": "tsc",
|
||||||
"start": "node dist/index.js"
|
"start": "node dist/index.js",
|
||||||
|
"db:generate": "drizzle-kit generate --config=drizzle.config.ts",
|
||||||
|
"db:migrate": "drizzle-kit migrate --config=drizzle.config.ts",
|
||||||
|
"db:push": "drizzle-kit push --config=drizzle.config.ts",
|
||||||
|
"db:drop": "drizzle-kit drop --config=drizzle.config.ts",
|
||||||
|
"db:check": "drizzle-kit check --config=drizzle.config.ts"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@clickhouse/client": "^1.10.1",
|
"@clickhouse/client": "^1.10.1",
|
||||||
|
@ -16,21 +21,24 @@
|
||||||
"@fastify/static": "^8.0.4",
|
"@fastify/static": "^8.0.4",
|
||||||
"better-auth": "^1.2.2",
|
"better-auth": "^1.2.2",
|
||||||
"dotenv": "^16.4.7",
|
"dotenv": "^16.4.7",
|
||||||
|
"drizzle-orm": "^0.40.0",
|
||||||
"fastify": "^5.1.0",
|
"fastify": "^5.1.0",
|
||||||
"fastify-better-auth": "^1.0.1",
|
"fastify-better-auth": "^1.0.1",
|
||||||
"luxon": "^3.5.0",
|
"luxon": "^3.5.0",
|
||||||
"node-cron": "^3.0.3",
|
"node-cron": "^3.0.3",
|
||||||
"pg": "^8.13.1",
|
"pg": "^8.13.3",
|
||||||
"postgres": "^3.4.5",
|
"postgres": "^3.4.5",
|
||||||
"ua-parser-js": "^2.0.0",
|
"ua-parser-js": "^2.0.0",
|
||||||
"undici": "^7.3.0"
|
"undici": "^7.3.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/pg": "^8.11.11",
|
|
||||||
"@types/luxon": "^3.4.2",
|
"@types/luxon": "^3.4.2",
|
||||||
"@types/node": "^20.10.0",
|
"@types/node": "^20.10.0",
|
||||||
"@types/node-cron": "^3.0.11",
|
"@types/node-cron": "^3.0.11",
|
||||||
|
"@types/pg": "^8.11.11",
|
||||||
|
"drizzle-kit": "^0.30.5",
|
||||||
"ts-node-dev": "^2.0.0",
|
"ts-node-dev": "^2.0.0",
|
||||||
|
"tsx": "^4.19.3",
|
||||||
"typescript": "^5.7.3"
|
"typescript": "^5.7.3"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,16 @@
|
||||||
import { FastifyReply, FastifyRequest } from "fastify";
|
import { FastifyReply, FastifyRequest } from "fastify";
|
||||||
import { sql } from "../db/postgres/postgres.js";
|
import { db } from "../db/postgres/postgres.js";
|
||||||
|
import { activeSessions } from "../db/postgres/schema.js";
|
||||||
|
import { eq, count } from "drizzle-orm";
|
||||||
|
|
||||||
export const getLiveUsercount = async (
|
export const getLiveUsercount = async (
|
||||||
{ params: { site } }: FastifyRequest<{ Params: { site: string } }>,
|
{ params: { site } }: FastifyRequest<{ Params: { site: string } }>,
|
||||||
res: FastifyReply
|
res: FastifyReply
|
||||||
) => {
|
) => {
|
||||||
const result =
|
const result = await db
|
||||||
await sql`SELECT COUNT(*) FROM active_sessions WHERE site_id = ${site}`;
|
.select({ count: count() })
|
||||||
|
.from(activeSessions)
|
||||||
|
.where(eq(activeSessions.siteId, Number(site)));
|
||||||
|
|
||||||
return res.send({ count: result[0].count });
|
return res.send({ count: result[0].count });
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,10 +1,9 @@
|
||||||
import { FastifyReply } from "fastify";
|
|
||||||
|
|
||||||
import { FastifyRequest } from "fastify";
|
|
||||||
import { auth } from "../../lib/auth.js";
|
|
||||||
import { fromNodeHeaders } from "better-auth/node";
|
import { fromNodeHeaders } from "better-auth/node";
|
||||||
import { sql } from "../../db/postgres/postgres.js";
|
import { FastifyReply, FastifyRequest } from "fastify";
|
||||||
|
import { db } from "../../db/postgres/postgres.js";
|
||||||
|
import { sites } from "../../db/postgres/schema.js";
|
||||||
import { loadAllowedDomains } from "../../lib/allowedDomains.js";
|
import { loadAllowedDomains } from "../../lib/allowedDomains.js";
|
||||||
|
import { auth } from "../../lib/auth.js";
|
||||||
|
|
||||||
export async function addSite(
|
export async function addSite(
|
||||||
request: FastifyRequest<{ Body: { domain: string; name: string } }>,
|
request: FastifyRequest<{ Body: { domain: string; name: string } }>,
|
||||||
|
@ -29,8 +28,14 @@ export async function addSite(
|
||||||
if (!session?.user.id) {
|
if (!session?.user.id) {
|
||||||
return reply.status(500).send({ error: "Could not find user id" });
|
return reply.status(500).send({ error: "Could not find user id" });
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await sql`INSERT INTO sites (domain, name, created_by) VALUES (${domain}, ${name}, ${session?.user.id})`;
|
await db.insert(sites).values({
|
||||||
|
domain,
|
||||||
|
name,
|
||||||
|
createdBy: session.user.id,
|
||||||
|
});
|
||||||
|
|
||||||
await loadAllowedDomains();
|
await loadAllowedDomains();
|
||||||
return reply.status(200).send();
|
return reply.status(200).send();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|
|
@ -1,9 +1,8 @@
|
||||||
import { FastifyReply } from "fastify";
|
import { eq } from "drizzle-orm";
|
||||||
|
import { FastifyReply, FastifyRequest } from "fastify";
|
||||||
import { FastifyRequest } from "fastify";
|
import { db } from "../../db/postgres/postgres.js";
|
||||||
import { sql } from "../../db/postgres/postgres.js";
|
import { sites } from "../../db/postgres/schema.js";
|
||||||
import { loadAllowedDomains } from "../../lib/allowedDomains.js";
|
import { loadAllowedDomains } from "../../lib/allowedDomains.js";
|
||||||
import clickhouse from "../../db/clickhouse/clickhouse.js";
|
|
||||||
|
|
||||||
export async function deleteSite(
|
export async function deleteSite(
|
||||||
request: FastifyRequest<{ Params: { id: string } }>,
|
request: FastifyRequest<{ Params: { id: string } }>,
|
||||||
|
@ -11,7 +10,7 @@ export async function deleteSite(
|
||||||
) {
|
) {
|
||||||
const { id } = request.params;
|
const { id } = request.params;
|
||||||
|
|
||||||
await sql`DELETE FROM sites WHERE site_id = ${id}`;
|
await db.delete(sites).where(eq(sites.siteId, Number(id)));
|
||||||
// await clickhouse.query({
|
// await clickhouse.query({
|
||||||
// query: `DELETE FROM pageviews WHERE site_id = ${id}`,
|
// query: `DELETE FROM pageviews WHERE site_id = ${id}`,
|
||||||
// });
|
// });
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
import { FastifyReply } from "fastify";
|
import { FastifyReply } from "fastify";
|
||||||
|
|
||||||
import { FastifyRequest } from "fastify";
|
import { FastifyRequest } from "fastify";
|
||||||
import { sql } from "../../db/postgres/postgres.js";
|
import { db } from "../../db/postgres/postgres.js";
|
||||||
|
import { sites } from "../../db/postgres/schema.js";
|
||||||
|
|
||||||
export async function getSites(_: FastifyRequest, reply: FastifyReply) {
|
export async function getSites(_: FastifyRequest, reply: FastifyReply) {
|
||||||
try {
|
try {
|
||||||
const sites = await sql`SELECT * FROM sites`;
|
const sitesData = await db.select().from(sites);
|
||||||
return reply.status(200).send({ data: sites });
|
return reply.status(200).send({ data: sitesData });
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
return reply.status(500).send({ error: String(err) });
|
return reply.status(500).send({ error: String(err) });
|
||||||
}
|
}
|
||||||
|
|
89
server/src/db/postgres/migrate.ts
Normal file
89
server/src/db/postgres/migrate.ts
Normal file
|
@ -0,0 +1,89 @@
|
||||||
|
import { drizzle } from "drizzle-orm/postgres-js";
|
||||||
|
import { migrate } from "drizzle-orm/postgres-js/migrator";
|
||||||
|
import postgres from "postgres";
|
||||||
|
import dotenv from "dotenv";
|
||||||
|
import * as schema from "./schema.js";
|
||||||
|
|
||||||
|
dotenv.config();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Run database migrations
|
||||||
|
* @param migrationsPath Path to migrations folder
|
||||||
|
* @returns Promise that resolves when migrations are complete
|
||||||
|
*/
|
||||||
|
export async function runMigrations(migrationsPath: string = "./drizzle") {
|
||||||
|
console.log("Running database migrations...");
|
||||||
|
|
||||||
|
const migrationClient = postgres({
|
||||||
|
host: process.env.POSTGRES_HOST || "postgres",
|
||||||
|
port: parseInt(process.env.POSTGRES_PORT || "5432", 10),
|
||||||
|
database: process.env.POSTGRES_DB,
|
||||||
|
username: process.env.POSTGRES_USER,
|
||||||
|
password: process.env.POSTGRES_PASSWORD,
|
||||||
|
max: 1,
|
||||||
|
onnotice: () => {}, // Silence notices
|
||||||
|
});
|
||||||
|
|
||||||
|
const db = drizzle(migrationClient, { schema });
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Create drizzle schema if it doesn't exist
|
||||||
|
await migrationClient`CREATE SCHEMA IF NOT EXISTS drizzle;`;
|
||||||
|
|
||||||
|
// Create migration table if it doesn't exist
|
||||||
|
await migrationClient`
|
||||||
|
CREATE TABLE IF NOT EXISTS drizzle."__drizzle_migrations" (
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
hash text NOT NULL,
|
||||||
|
created_at timestamp with time zone DEFAULT now()
|
||||||
|
);
|
||||||
|
`;
|
||||||
|
|
||||||
|
// Always run migrations, Drizzle will skip already applied ones
|
||||||
|
console.log(
|
||||||
|
"Running all migrations - Drizzle will skip already applied ones"
|
||||||
|
);
|
||||||
|
|
||||||
|
// This will run migrations on the database, skipping the ones already applied
|
||||||
|
try {
|
||||||
|
await migrate(db, { migrationsFolder: migrationsPath });
|
||||||
|
console.log("Migrations completed!");
|
||||||
|
return true; // Return success
|
||||||
|
} catch (err: any) {
|
||||||
|
// If error contains relation already exists, tables likely exist but not in drizzle metadata
|
||||||
|
if (err.message && err.message.includes("already exists")) {
|
||||||
|
console.log(
|
||||||
|
"Some tables already exist but not tracked by drizzle. This is expected for existing databases."
|
||||||
|
);
|
||||||
|
console.log(
|
||||||
|
"You can safely ignore these errors if your database is already set up."
|
||||||
|
);
|
||||||
|
return true; // Consider this a success case
|
||||||
|
} else {
|
||||||
|
// Other errors should be reported
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error("Migration failed!");
|
||||||
|
console.error(e);
|
||||||
|
return false; // Return failure
|
||||||
|
} finally {
|
||||||
|
// Don't forget to close the connection
|
||||||
|
await migrationClient.end();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only run migrations directly if this module is executed directly
|
||||||
|
if (import.meta.url === `file://${process.argv[1]}`) {
|
||||||
|
runMigrations()
|
||||||
|
.then((success) => {
|
||||||
|
if (!success) {
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
console.error("Unhandled error in migrations:", err);
|
||||||
|
process.exit(1);
|
||||||
|
});
|
||||||
|
}
|
|
@ -1,10 +1,13 @@
|
||||||
import dotenv from "dotenv";
|
import dotenv from "dotenv";
|
||||||
|
import { drizzle } from "drizzle-orm/postgres-js";
|
||||||
import postgres from "postgres";
|
import postgres from "postgres";
|
||||||
import { auth } from "../../lib/auth.js";
|
import { auth } from "../../lib/auth.js";
|
||||||
|
import * as schema from "./schema.js";
|
||||||
|
|
||||||
dotenv.config();
|
dotenv.config();
|
||||||
|
|
||||||
export const sql = postgres({
|
// Create postgres connection
|
||||||
|
const client = postgres({
|
||||||
host: process.env.POSTGRES_HOST || "postgres",
|
host: process.env.POSTGRES_HOST || "postgres",
|
||||||
port: parseInt(process.env.POSTGRES_PORT || "5432", 10),
|
port: parseInt(process.env.POSTGRES_PORT || "5432", 10),
|
||||||
database: process.env.POSTGRES_DB,
|
database: process.env.POSTGRES_DB,
|
||||||
|
@ -13,115 +16,41 @@ export const sql = postgres({
|
||||||
onnotice: () => {},
|
onnotice: () => {},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Create drizzle ORM instance
|
||||||
|
export const db = drizzle(client, { schema });
|
||||||
|
|
||||||
|
// For compatibility with raw SQL if needed
|
||||||
|
export const sql = client;
|
||||||
|
|
||||||
export async function initializePostgres() {
|
export async function initializePostgres() {
|
||||||
try {
|
try {
|
||||||
// Phase 1: Create tables with no dependencies
|
console.log("Initializing PostgreSQL database...");
|
||||||
await sql`
|
|
||||||
CREATE TABLE IF NOT EXISTS "user" (
|
|
||||||
"id" text not null primary key,
|
|
||||||
"name" text not null,
|
|
||||||
"username" text not null unique,
|
|
||||||
"email" text not null unique,
|
|
||||||
"emailVerified" boolean not null,
|
|
||||||
"image" text,
|
|
||||||
"createdAt" timestamp not null,
|
|
||||||
"updatedAt" timestamp not null,
|
|
||||||
"role" text not null default 'user'
|
|
||||||
);
|
|
||||||
`;
|
|
||||||
|
|
||||||
await sql`
|
// Assume migrations have been run manually with 'npm run db:migrate'
|
||||||
CREATE TABLE IF NOT EXISTS "verification" (
|
// No automatic migrations during application startup
|
||||||
"id" text not null primary key,
|
|
||||||
"identifier" text not null,
|
|
||||||
"value" text not null,
|
|
||||||
"expiresAt" timestamp not null,
|
|
||||||
"createdAt" timestamp,
|
|
||||||
"updatedAt" timestamp
|
|
||||||
);
|
|
||||||
`;
|
|
||||||
|
|
||||||
await sql`
|
|
||||||
CREATE TABLE IF NOT EXISTS active_sessions (
|
|
||||||
session_id TEXT PRIMARY KEY,
|
|
||||||
site_id INT,
|
|
||||||
user_id TEXT,
|
|
||||||
hostname TEXT,
|
|
||||||
start_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
last_activity TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
pageviews INT DEFAULT 0,
|
|
||||||
entry_page TEXT,
|
|
||||||
exit_page TEXT,
|
|
||||||
device_type TEXT,
|
|
||||||
screen_width INT,
|
|
||||||
screen_height INT,
|
|
||||||
browser TEXT,
|
|
||||||
operating_system TEXT,
|
|
||||||
language TEXT,
|
|
||||||
referrer TEXT
|
|
||||||
);
|
|
||||||
`;
|
|
||||||
|
|
||||||
await sql`
|
|
||||||
CREATE TABLE IF NOT EXISTS sites (
|
|
||||||
site_id SERIAL PRIMARY KEY,
|
|
||||||
name TEXT NOT NULL,
|
|
||||||
domain TEXT NOT NULL UNIQUE,
|
|
||||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
created_by TEXT NOT NULL REFERENCES "user" ("id")
|
|
||||||
);
|
|
||||||
`;
|
|
||||||
|
|
||||||
await sql`
|
|
||||||
CREATE TABLE IF NOT EXISTS "session" (
|
|
||||||
"id" text not null primary key,
|
|
||||||
"expiresAt" timestamp not null,
|
|
||||||
"token" text not null unique,
|
|
||||||
"createdAt" timestamp not null,
|
|
||||||
"updatedAt" timestamp not null,
|
|
||||||
"ipAddress" text,
|
|
||||||
"userAgent" text,
|
|
||||||
"userId" text not null references "user" ("id")
|
|
||||||
);
|
|
||||||
`;
|
|
||||||
|
|
||||||
await sql`
|
|
||||||
CREATE TABLE IF NOT EXISTS "account" (
|
|
||||||
"id" text not null primary key,
|
|
||||||
"accountId" text not null,
|
|
||||||
"providerId" text not null,
|
|
||||||
"userId" text not null references "user" ("id"),
|
|
||||||
"accessToken" text,
|
|
||||||
"refreshToken" text,
|
|
||||||
"idToken" text,
|
|
||||||
"accessTokenExpiresAt" timestamp,
|
|
||||||
"refreshTokenExpiresAt" timestamp,
|
|
||||||
"scope" text,
|
|
||||||
"password" text,
|
|
||||||
"createdAt" timestamp not null,
|
|
||||||
"updatedAt" timestamp not null
|
|
||||||
);
|
|
||||||
`;
|
|
||||||
|
|
||||||
|
// Check if admin user exists, if not create one
|
||||||
const [{ count }]: { count: number }[] =
|
const [{ count }]: { count: number }[] =
|
||||||
await sql`SELECT count(*) FROM "user" WHERE username = 'admin'`;
|
await client`SELECT count(*) FROM "user" WHERE username = 'admin'`;
|
||||||
|
|
||||||
if (Number(count) === 0) {
|
if (Number(count) === 0) {
|
||||||
|
// Create admin user
|
||||||
|
console.log("Creating admin user");
|
||||||
await auth!.api.signUpEmail({
|
await auth!.api.signUpEmail({
|
||||||
body: {
|
body: {
|
||||||
email: "test@test.com",
|
email: "admin@example.com",
|
||||||
username: "admin",
|
username: "admin",
|
||||||
name: "admin",
|
|
||||||
password: "admin123",
|
password: "admin123",
|
||||||
|
name: "Admin User",
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
await sql`UPDATE "user" SET "role" = 'admin' WHERE username = 'admin'`;
|
await client`UPDATE "user" SET "role" = 'admin' WHERE username = 'admin'`;
|
||||||
|
|
||||||
console.log("Tables created successfully.");
|
console.log("PostgreSQL initialization completed successfully.");
|
||||||
} catch (err) {
|
} catch (error) {
|
||||||
console.error("Error creating tables:", err);
|
console.error("Error initializing PostgreSQL:", error);
|
||||||
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
155
server/src/db/postgres/schema.ts
Normal file
155
server/src/db/postgres/schema.ts
Normal file
|
@ -0,0 +1,155 @@
|
||||||
|
import {
|
||||||
|
pgTable,
|
||||||
|
text,
|
||||||
|
timestamp,
|
||||||
|
integer,
|
||||||
|
boolean,
|
||||||
|
primaryKey,
|
||||||
|
foreignKey,
|
||||||
|
serial,
|
||||||
|
unique,
|
||||||
|
jsonb,
|
||||||
|
} from "drizzle-orm/pg-core";
|
||||||
|
|
||||||
|
// User table
|
||||||
|
export const users = pgTable("user", {
|
||||||
|
id: text("id").primaryKey().notNull(),
|
||||||
|
name: text("name").notNull(),
|
||||||
|
username: text("username").notNull().unique(),
|
||||||
|
email: text("email").notNull().unique(),
|
||||||
|
emailVerified: boolean("emailVerified").notNull(),
|
||||||
|
image: text("image"),
|
||||||
|
createdAt: timestamp("createdAt").notNull(),
|
||||||
|
updatedAt: timestamp("updatedAt").notNull(),
|
||||||
|
role: text("role").notNull().default("user"),
|
||||||
|
displayUsername: text("displayUsername"),
|
||||||
|
banned: boolean("banned"),
|
||||||
|
banReason: text("banReason"),
|
||||||
|
banExpires: timestamp("banExpires"),
|
||||||
|
});
|
||||||
|
|
||||||
|
// Verification table
|
||||||
|
export const verification = pgTable("verification", {
|
||||||
|
id: text("id").primaryKey().notNull(),
|
||||||
|
identifier: text("identifier").notNull(),
|
||||||
|
value: text("value").notNull(),
|
||||||
|
expiresAt: timestamp("expiresAt").notNull(),
|
||||||
|
createdAt: timestamp("createdAt"),
|
||||||
|
updatedAt: timestamp("updatedAt"),
|
||||||
|
});
|
||||||
|
|
||||||
|
// Sites table
|
||||||
|
export const sites = pgTable("sites", {
|
||||||
|
siteId: serial("site_id").primaryKey().notNull(),
|
||||||
|
name: text("name").notNull(),
|
||||||
|
domain: text("domain").notNull().unique(),
|
||||||
|
createdAt: timestamp("created_at").defaultNow(),
|
||||||
|
updatedAt: timestamp("updated_at").defaultNow(),
|
||||||
|
createdBy: text("created_by")
|
||||||
|
.notNull()
|
||||||
|
.references(() => users.id),
|
||||||
|
});
|
||||||
|
|
||||||
|
// Active sessions table
|
||||||
|
export const activeSessions = pgTable("active_sessions", {
|
||||||
|
sessionId: text("session_id").primaryKey().notNull(),
|
||||||
|
siteId: integer("site_id"),
|
||||||
|
userId: text("user_id"),
|
||||||
|
hostname: text("hostname"),
|
||||||
|
startTime: timestamp("start_time").defaultNow(),
|
||||||
|
lastActivity: timestamp("last_activity").defaultNow(),
|
||||||
|
pageviews: integer("pageviews").default(0),
|
||||||
|
entryPage: text("entry_page"),
|
||||||
|
exitPage: text("exit_page"),
|
||||||
|
deviceType: text("device_type"),
|
||||||
|
screenWidth: integer("screen_width"),
|
||||||
|
screenHeight: integer("screen_height"),
|
||||||
|
browser: text("browser"),
|
||||||
|
operatingSystem: text("operating_system"),
|
||||||
|
language: text("language"),
|
||||||
|
referrer: text("referrer"),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const reports = pgTable("reports", {
|
||||||
|
reportId: serial("report_id").primaryKey().notNull(),
|
||||||
|
siteId: integer("site_id").references(() => sites.siteId),
|
||||||
|
userId: text("user_id").references(() => users.id),
|
||||||
|
reportType: text("report_type"),
|
||||||
|
data: jsonb("data"),
|
||||||
|
createdAt: timestamp("created_at").defaultNow(),
|
||||||
|
updatedAt: timestamp("updated_at").defaultNow(),
|
||||||
|
});
|
||||||
|
|
||||||
|
// Account table
|
||||||
|
export const account = pgTable("account", {
|
||||||
|
id: text("id").primaryKey().notNull(),
|
||||||
|
accountId: text("accountId").notNull(),
|
||||||
|
providerId: text("providerId").notNull(),
|
||||||
|
userId: text("userId")
|
||||||
|
.notNull()
|
||||||
|
.references(() => users.id),
|
||||||
|
accessToken: text("accessToken"),
|
||||||
|
refreshToken: text("refreshToken"),
|
||||||
|
idToken: text("idToken"),
|
||||||
|
accessTokenExpiresAt: timestamp("accessTokenExpiresAt"),
|
||||||
|
refreshTokenExpiresAt: timestamp("refreshTokenExpiresAt"),
|
||||||
|
scope: text("scope"),
|
||||||
|
password: text("password"),
|
||||||
|
createdAt: timestamp("createdAt").notNull(),
|
||||||
|
updatedAt: timestamp("updatedAt").notNull(),
|
||||||
|
});
|
||||||
|
|
||||||
|
// Organization table
|
||||||
|
export const organization = pgTable("organization", {
|
||||||
|
id: text("id").primaryKey().notNull(),
|
||||||
|
name: text("name").notNull(),
|
||||||
|
slug: text("slug").notNull().unique(),
|
||||||
|
logo: text("logo"),
|
||||||
|
createdAt: timestamp("createdAt").notNull(),
|
||||||
|
metadata: text("metadata"),
|
||||||
|
});
|
||||||
|
|
||||||
|
// Member table
|
||||||
|
export const member = pgTable("member", {
|
||||||
|
id: text("id").primaryKey().notNull(),
|
||||||
|
organizationId: text("organizationId")
|
||||||
|
.notNull()
|
||||||
|
.references(() => organization.id),
|
||||||
|
userId: text("userId")
|
||||||
|
.notNull()
|
||||||
|
.references(() => users.id),
|
||||||
|
role: text("role").notNull(),
|
||||||
|
createdAt: timestamp("createdAt").notNull(),
|
||||||
|
});
|
||||||
|
|
||||||
|
// Invitation table
|
||||||
|
export const invitation = pgTable("invitation", {
|
||||||
|
id: text("id").primaryKey().notNull(),
|
||||||
|
email: text("email").notNull(),
|
||||||
|
inviterId: text("inviterId")
|
||||||
|
.notNull()
|
||||||
|
.references(() => users.id),
|
||||||
|
organizationId: text("organizationId")
|
||||||
|
.notNull()
|
||||||
|
.references(() => organization.id),
|
||||||
|
role: text("role").notNull(),
|
||||||
|
status: text("status").notNull(),
|
||||||
|
expiresAt: timestamp("expiresAt").notNull(),
|
||||||
|
createdAt: timestamp("createdAt").notNull(),
|
||||||
|
});
|
||||||
|
|
||||||
|
// Session table
|
||||||
|
export const session = pgTable("session", {
|
||||||
|
id: text("id").primaryKey().notNull(),
|
||||||
|
expiresAt: timestamp("expiresAt").notNull(),
|
||||||
|
token: text("token").notNull().unique(),
|
||||||
|
createdAt: timestamp("createdAt").notNull(),
|
||||||
|
updatedAt: timestamp("updatedAt").notNull(),
|
||||||
|
ipAddress: text("ipAddress"),
|
||||||
|
userAgent: text("userAgent"),
|
||||||
|
userId: text("userId")
|
||||||
|
.notNull()
|
||||||
|
.references(() => users.id),
|
||||||
|
impersonatedBy: text("impersonatedBy"),
|
||||||
|
activeOrganizationId: text("activeOrganizationId"),
|
||||||
|
});
|
|
@ -1,4 +1,5 @@
|
||||||
import { sql } from "../db/postgres/postgres.js";
|
import { db, sql } from "../db/postgres/postgres.js";
|
||||||
|
import { sites } from "../db/postgres/schema.js";
|
||||||
import { initAuth } from "./auth.js";
|
import { initAuth } from "./auth.js";
|
||||||
import dotenv from "dotenv";
|
import dotenv from "dotenv";
|
||||||
|
|
||||||
|
@ -20,7 +21,9 @@ export const loadAllowedDomains = async () => {
|
||||||
// Only query the sites table if it exists
|
// Only query the sites table if it exists
|
||||||
let domains: { domain: string }[] = [];
|
let domains: { domain: string }[] = [];
|
||||||
if (tableExists[0].exists) {
|
if (tableExists[0].exists) {
|
||||||
domains = await sql`SELECT domain FROM sites`;
|
// Use Drizzle to get domains
|
||||||
|
const sitesData = await db.select({ domain: sites.domain }).from(sites);
|
||||||
|
domains = sitesData;
|
||||||
}
|
}
|
||||||
|
|
||||||
allowList = [
|
allowList = [
|
||||||
|
|
|
@ -2,6 +2,9 @@ import { betterAuth } from "better-auth";
|
||||||
import { username, admin, organization } from "better-auth/plugins";
|
import { username, admin, organization } from "better-auth/plugins";
|
||||||
import dotenv from "dotenv";
|
import dotenv from "dotenv";
|
||||||
import pg from "pg";
|
import pg from "pg";
|
||||||
|
import { drizzleAdapter } from "better-auth/adapters/drizzle";
|
||||||
|
import { db } from "../db/postgres/postgres.js";
|
||||||
|
import * as schema from "../db/postgres/schema.js";
|
||||||
|
|
||||||
dotenv.config();
|
dotenv.config();
|
||||||
|
|
||||||
|
@ -33,16 +36,26 @@ export let auth: AuthType | null = betterAuth({
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export const initAuth = (allowList: string[]) => {
|
export function initAuth(allowedOrigins: string[]) {
|
||||||
auth = betterAuth({
|
auth = betterAuth({
|
||||||
basePath: "/auth",
|
basePath: "/auth",
|
||||||
database: new pg.Pool({
|
database: drizzleAdapter(db, {
|
||||||
host: process.env.POSTGRES_HOST || "postgres",
|
provider: "pg",
|
||||||
port: parseInt(process.env.POSTGRES_PORT || "5432", 10),
|
schema: {
|
||||||
database: process.env.POSTGRES_DB,
|
// Map our schema tables to what better-auth expects
|
||||||
user: process.env.POSTGRES_USER,
|
user: schema.users,
|
||||||
password: process.env.POSTGRES_PASSWORD,
|
account: schema.account,
|
||||||
|
session: schema.session,
|
||||||
|
verification: schema.verification,
|
||||||
|
organization: schema.organization,
|
||||||
|
member: schema.member,
|
||||||
|
},
|
||||||
}),
|
}),
|
||||||
|
experimental: {
|
||||||
|
sessionCookie: {
|
||||||
|
domains: allowedOrigins,
|
||||||
|
},
|
||||||
|
},
|
||||||
emailAndPassword: {
|
emailAndPassword: {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
},
|
},
|
||||||
|
@ -50,7 +63,7 @@ export const initAuth = (allowList: string[]) => {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
},
|
},
|
||||||
plugins: [username(), admin(), organization()],
|
plugins: [username(), admin(), organization()],
|
||||||
trustedOrigins: allowList,
|
trustedOrigins: allowedOrigins,
|
||||||
advanced: {
|
advanced: {
|
||||||
useSecureCookies: process.env.NODE_ENV === "production", // don't mark Secure in dev
|
useSecureCookies: process.env.NODE_ENV === "production", // don't mark Secure in dev
|
||||||
defaultCookieAttributes: {
|
defaultCookieAttributes: {
|
||||||
|
@ -59,4 +72,4 @@ export const initAuth = (allowList: string[]) => {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
};
|
}
|
||||||
|
|
|
@ -2,8 +2,10 @@ import { FastifyReply, FastifyRequest } from "fastify";
|
||||||
import { TrackingPayload } from "../types.js";
|
import { TrackingPayload } from "../types.js";
|
||||||
import { getUserId, getDeviceType, getIpAddress } from "../utils.js";
|
import { getUserId, getDeviceType, getIpAddress } from "../utils.js";
|
||||||
import crypto from "crypto";
|
import crypto from "crypto";
|
||||||
import { sql } from "../db/postgres/postgres.js";
|
import { db, sql } from "../db/postgres/postgres.js";
|
||||||
|
import { activeSessions } from "../db/postgres/schema.js";
|
||||||
import UAParser, { UAParser as userAgentParser } from "ua-parser-js";
|
import UAParser, { UAParser as userAgentParser } from "ua-parser-js";
|
||||||
|
import { eq } from "drizzle-orm";
|
||||||
|
|
||||||
import { Pageview } from "../db/clickhouse/types.js";
|
import { Pageview } from "../db/clickhouse/types.js";
|
||||||
import { pageviewQueue } from "./pageviewQueue.js";
|
import { pageviewQueue } from "./pageviewQueue.js";
|
||||||
|
@ -17,8 +19,31 @@ type TotalPayload = TrackingPayload & {
|
||||||
ipAddress: string;
|
ipAddress: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
const getExistingSession = async (userId: string): Promise<Pageview | null> => {
|
// Extended type for database active sessions
|
||||||
const [existingSession] = await sql<Pageview[]>`
|
type ActiveSession = {
|
||||||
|
session_id: string;
|
||||||
|
site_id: number | null;
|
||||||
|
user_id: string;
|
||||||
|
pageviews: number;
|
||||||
|
hostname: string | null;
|
||||||
|
start_time: Date | null;
|
||||||
|
last_activity: Date | null;
|
||||||
|
entry_page: string | null;
|
||||||
|
exit_page: string | null;
|
||||||
|
device_type: string | null;
|
||||||
|
screen_width: number | null;
|
||||||
|
screen_height: number | null;
|
||||||
|
browser: string | null;
|
||||||
|
operating_system: string | null;
|
||||||
|
language: string | null;
|
||||||
|
referrer: string | null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getExistingSession = async (
|
||||||
|
userId: string
|
||||||
|
): Promise<ActiveSession | null> => {
|
||||||
|
// We need to use the raw SQL query here since we're selecting into a specific type
|
||||||
|
const [existingSession] = await sql<ActiveSession[]>`
|
||||||
SELECT * FROM active_sessions WHERE user_id = ${userId}
|
SELECT * FROM active_sessions WHERE user_id = ${userId}
|
||||||
`;
|
`;
|
||||||
return existingSession;
|
return existingSession;
|
||||||
|
@ -26,41 +51,47 @@ const getExistingSession = async (userId: string): Promise<Pageview | null> => {
|
||||||
|
|
||||||
const updateSession = async (
|
const updateSession = async (
|
||||||
pageview: TotalPayload,
|
pageview: TotalPayload,
|
||||||
existingSession: Pageview | null
|
existingSession: ActiveSession | null
|
||||||
) => {
|
) => {
|
||||||
if (existingSession) {
|
if (existingSession) {
|
||||||
await sql`
|
// Update session with Drizzle
|
||||||
UPDATE active_sessions SET last_activity = ${pageview.timestamp}, pageviews = pageviews + 1 WHERE user_id = ${pageview.userId}
|
await db
|
||||||
`;
|
.update(activeSessions)
|
||||||
|
.set({
|
||||||
|
lastActivity: new Date(pageview.timestamp),
|
||||||
|
pageviews: (existingSession.pageviews || 0) + 1,
|
||||||
|
})
|
||||||
|
.where(eq(activeSessions.userId, pageview.userId));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const inserts = {
|
// Insert new session with Drizzle
|
||||||
site_id: pageview.site_id || 0,
|
const insertData = {
|
||||||
session_id: pageview.sessionId,
|
sessionId: pageview.sessionId,
|
||||||
user_id: pageview.userId,
|
siteId:
|
||||||
hostname: pageview.hostname || "",
|
typeof pageview.site_id === "string"
|
||||||
start_time: pageview.timestamp || "",
|
? parseInt(pageview.site_id, 10)
|
||||||
last_activity: pageview.timestamp || "",
|
: pageview.site_id,
|
||||||
|
userId: pageview.userId,
|
||||||
|
hostname: pageview.hostname || null,
|
||||||
|
startTime: new Date(pageview.timestamp || Date.now()),
|
||||||
|
lastActivity: new Date(pageview.timestamp || Date.now()),
|
||||||
pageviews: 1,
|
pageviews: 1,
|
||||||
entry_page: pageview.pathname || "",
|
entryPage: pageview.pathname || null,
|
||||||
// exit_page: pageview.pathname,
|
deviceType: getDeviceType(
|
||||||
device_type: getDeviceType(
|
|
||||||
pageview.screenWidth,
|
pageview.screenWidth,
|
||||||
pageview.screenHeight,
|
pageview.screenHeight,
|
||||||
pageview.ua
|
pageview.ua
|
||||||
),
|
),
|
||||||
screen_width: pageview.screenWidth || 0,
|
screenWidth: pageview.screenWidth || null,
|
||||||
screen_height: pageview.screenHeight || 0,
|
screenHeight: pageview.screenHeight || null,
|
||||||
browser: pageview.ua.browser.name || "",
|
browser: pageview.ua.browser.name || null,
|
||||||
operating_system: pageview.ua.os.name || "",
|
operatingSystem: pageview.ua.os.name || null,
|
||||||
language: pageview.language || "",
|
language: pageview.language || null,
|
||||||
referrer: pageview.referrer || "",
|
referrer: pageview.referrer || null,
|
||||||
};
|
};
|
||||||
|
|
||||||
await sql`
|
await db.insert(activeSessions).values(insertData);
|
||||||
INSERT INTO active_sessions ${sql(inserts)}
|
|
||||||
`;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export async function trackPageView(
|
export async function trackPageView(
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue