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 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
|
||||
|
|
|
@ -27,7 +27,7 @@ export function NoData({
|
|||
</div>
|
||||
<CodeSnippet
|
||||
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>
|
||||
</CardContent>
|
||||
|
|
|
@ -41,7 +41,7 @@ export function SubHeader() {
|
|||
const pathname = usePathname();
|
||||
|
||||
const site = sites?.data?.find(
|
||||
(site) => site.site_id === Number(pathname.slice(1))
|
||||
(site) => site.siteId === Number(pathname.slice(1))
|
||||
);
|
||||
|
||||
return (
|
||||
|
|
|
@ -25,7 +25,7 @@ export function DeleteSite({
|
|||
isOpen={isOpen}
|
||||
setIsOpen={setIsOpen}
|
||||
onConfirm={async () => {
|
||||
await deleteSite(site.site_id);
|
||||
await deleteSite(site.siteId);
|
||||
refetch();
|
||||
router.push("/");
|
||||
}}
|
||||
|
|
|
@ -13,7 +13,7 @@ export default function SettingsPage() {
|
|||
const pathname = usePathname();
|
||||
|
||||
const site = sites?.data?.find(
|
||||
(site) => site.site_id === Number(pathname.split("/")[1])
|
||||
(site) => site.siteId === Number(pathname.split("/")[1])
|
||||
);
|
||||
|
||||
return (
|
||||
|
|
|
@ -15,7 +15,7 @@ export default function Home() {
|
|||
</div>
|
||||
<div className="grid grid-cols-3 gap-4 mt-4">
|
||||
{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">
|
||||
<img
|
||||
className="w-6 mr-1"
|
||||
|
|
|
@ -178,12 +178,12 @@ export function useGetOverview(periodTime?: PeriodTime) {
|
|||
}
|
||||
|
||||
export type GetSitesResponse = {
|
||||
site_id: number;
|
||||
site_name: string;
|
||||
siteId: number;
|
||||
name: string;
|
||||
domain: string;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
created_by: string;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
createdBy: string;
|
||||
}[];
|
||||
|
||||
export function useGetSites() {
|
||||
|
|
|
@ -3,7 +3,7 @@ import { useGetSites } from "./api";
|
|||
export function useGetSiteMetadata(siteId: string) {
|
||||
const { data, isLoading } = useGetSites();
|
||||
return {
|
||||
siteMetadata: data?.data?.find((site) => site.site_id === Number(siteId)),
|
||||
siteMetadata: data?.data?.find((site) => site.siteId === Number(siteId)),
|
||||
isLoading,
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,14 +1,46 @@
|
|||
# syntax=docker/dockerfile:1
|
||||
|
||||
FROM node:22-alpine
|
||||
FROM node:20-alpine AS builder
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY ["package.json", "package-lock.json*", "./"]
|
||||
|
||||
RUN npm install
|
||||
# Install dependencies
|
||||
COPY package*.json ./
|
||||
RUN npm ci
|
||||
|
||||
# Copy source code
|
||||
COPY . .
|
||||
|
||||
# Build the application
|
||||
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": {
|
||||
"dev": "tsc && node dist/index.js",
|
||||
"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": {
|
||||
"@clickhouse/client": "^1.10.1",
|
||||
|
@ -16,21 +21,24 @@
|
|||
"@fastify/static": "^8.0.4",
|
||||
"better-auth": "^1.2.2",
|
||||
"dotenv": "^16.4.7",
|
||||
"drizzle-orm": "^0.40.0",
|
||||
"fastify": "^5.1.0",
|
||||
"fastify-better-auth": "^1.0.1",
|
||||
"luxon": "^3.5.0",
|
||||
"node-cron": "^3.0.3",
|
||||
"pg": "^8.13.1",
|
||||
"pg": "^8.13.3",
|
||||
"postgres": "^3.4.5",
|
||||
"ua-parser-js": "^2.0.0",
|
||||
"undici": "^7.3.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/pg": "^8.11.11",
|
||||
"@types/luxon": "^3.4.2",
|
||||
"@types/node": "^20.10.0",
|
||||
"@types/node-cron": "^3.0.11",
|
||||
"@types/pg": "^8.11.11",
|
||||
"drizzle-kit": "^0.30.5",
|
||||
"ts-node-dev": "^2.0.0",
|
||||
"tsx": "^4.19.3",
|
||||
"typescript": "^5.7.3"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,12 +1,16 @@
|
|||
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 (
|
||||
{ params: { site } }: FastifyRequest<{ Params: { site: string } }>,
|
||||
res: FastifyReply
|
||||
) => {
|
||||
const result =
|
||||
await sql`SELECT COUNT(*) FROM active_sessions WHERE site_id = ${site}`;
|
||||
const result = await db
|
||||
.select({ count: count() })
|
||||
.from(activeSessions)
|
||||
.where(eq(activeSessions.siteId, Number(site)));
|
||||
|
||||
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 { 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 { auth } from "../../lib/auth.js";
|
||||
|
||||
export async function addSite(
|
||||
request: FastifyRequest<{ Body: { domain: string; name: string } }>,
|
||||
|
@ -29,8 +28,14 @@ export async function addSite(
|
|||
if (!session?.user.id) {
|
||||
return reply.status(500).send({ error: "Could not find user id" });
|
||||
}
|
||||
|
||||
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();
|
||||
return reply.status(200).send();
|
||||
} catch (err) {
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
import { FastifyReply } from "fastify";
|
||||
|
||||
import { FastifyRequest } from "fastify";
|
||||
import { sql } from "../../db/postgres/postgres.js";
|
||||
import { eq } from "drizzle-orm";
|
||||
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 clickhouse from "../../db/clickhouse/clickhouse.js";
|
||||
|
||||
export async function deleteSite(
|
||||
request: FastifyRequest<{ Params: { id: string } }>,
|
||||
|
@ -11,7 +10,7 @@ export async function deleteSite(
|
|||
) {
|
||||
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({
|
||||
// query: `DELETE FROM pageviews WHERE site_id = ${id}`,
|
||||
// });
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
import { FastifyReply } 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) {
|
||||
try {
|
||||
const sites = await sql`SELECT * FROM sites`;
|
||||
return reply.status(200).send({ data: sites });
|
||||
const sitesData = await db.select().from(sites);
|
||||
return reply.status(200).send({ data: sitesData });
|
||||
} catch (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 { drizzle } from "drizzle-orm/postgres-js";
|
||||
import postgres from "postgres";
|
||||
import { auth } from "../../lib/auth.js";
|
||||
import * as schema from "./schema.js";
|
||||
|
||||
dotenv.config();
|
||||
|
||||
export const sql = postgres({
|
||||
// Create postgres connection
|
||||
const client = postgres({
|
||||
host: process.env.POSTGRES_HOST || "postgres",
|
||||
port: parseInt(process.env.POSTGRES_PORT || "5432", 10),
|
||||
database: process.env.POSTGRES_DB,
|
||||
|
@ -13,115 +16,41 @@ export const sql = postgres({
|
|||
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() {
|
||||
try {
|
||||
// Phase 1: Create tables with no dependencies
|
||||
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'
|
||||
);
|
||||
`;
|
||||
console.log("Initializing PostgreSQL database...");
|
||||
|
||||
await sql`
|
||||
CREATE TABLE IF NOT EXISTS "verification" (
|
||||
"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
|
||||
);
|
||||
`;
|
||||
// Assume migrations have been run manually with 'npm run db:migrate'
|
||||
// No automatic migrations during application startup
|
||||
|
||||
// Check if admin user exists, if not create one
|
||||
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) {
|
||||
// Create admin user
|
||||
console.log("Creating admin user");
|
||||
await auth!.api.signUpEmail({
|
||||
body: {
|
||||
email: "test@test.com",
|
||||
email: "admin@example.com",
|
||||
username: "admin",
|
||||
name: "admin",
|
||||
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.");
|
||||
} catch (err) {
|
||||
console.error("Error creating tables:", err);
|
||||
console.log("PostgreSQL initialization completed successfully.");
|
||||
} catch (error) {
|
||||
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 dotenv from "dotenv";
|
||||
|
||||
|
@ -20,7 +21,9 @@ export const loadAllowedDomains = async () => {
|
|||
// Only query the sites table if it exists
|
||||
let domains: { domain: string }[] = [];
|
||||
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 = [
|
||||
|
|
|
@ -2,6 +2,9 @@ import { betterAuth } from "better-auth";
|
|||
import { username, admin, organization } from "better-auth/plugins";
|
||||
import dotenv from "dotenv";
|
||||
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();
|
||||
|
||||
|
@ -33,16 +36,26 @@ export let auth: AuthType | null = betterAuth({
|
|||
},
|
||||
});
|
||||
|
||||
export const initAuth = (allowList: string[]) => {
|
||||
export function initAuth(allowedOrigins: string[]) {
|
||||
auth = betterAuth({
|
||||
basePath: "/auth",
|
||||
database: new pg.Pool({
|
||||
host: process.env.POSTGRES_HOST || "postgres",
|
||||
port: parseInt(process.env.POSTGRES_PORT || "5432", 10),
|
||||
database: process.env.POSTGRES_DB,
|
||||
user: process.env.POSTGRES_USER,
|
||||
password: process.env.POSTGRES_PASSWORD,
|
||||
database: drizzleAdapter(db, {
|
||||
provider: "pg",
|
||||
schema: {
|
||||
// Map our schema tables to what better-auth expects
|
||||
user: schema.users,
|
||||
account: schema.account,
|
||||
session: schema.session,
|
||||
verification: schema.verification,
|
||||
organization: schema.organization,
|
||||
member: schema.member,
|
||||
},
|
||||
}),
|
||||
experimental: {
|
||||
sessionCookie: {
|
||||
domains: allowedOrigins,
|
||||
},
|
||||
},
|
||||
emailAndPassword: {
|
||||
enabled: true,
|
||||
},
|
||||
|
@ -50,7 +63,7 @@ export const initAuth = (allowList: string[]) => {
|
|||
enabled: true,
|
||||
},
|
||||
plugins: [username(), admin(), organization()],
|
||||
trustedOrigins: allowList,
|
||||
trustedOrigins: allowedOrigins,
|
||||
advanced: {
|
||||
useSecureCookies: process.env.NODE_ENV === "production", // don't mark Secure in dev
|
||||
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 { getUserId, getDeviceType, getIpAddress } from "../utils.js";
|
||||
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 { eq } from "drizzle-orm";
|
||||
|
||||
import { Pageview } from "../db/clickhouse/types.js";
|
||||
import { pageviewQueue } from "./pageviewQueue.js";
|
||||
|
@ -17,8 +19,31 @@ type TotalPayload = TrackingPayload & {
|
|||
ipAddress: string;
|
||||
};
|
||||
|
||||
const getExistingSession = async (userId: string): Promise<Pageview | null> => {
|
||||
const [existingSession] = await sql<Pageview[]>`
|
||||
// Extended type for database active sessions
|
||||
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}
|
||||
`;
|
||||
return existingSession;
|
||||
|
@ -26,41 +51,47 @@ const getExistingSession = async (userId: string): Promise<Pageview | null> => {
|
|||
|
||||
const updateSession = async (
|
||||
pageview: TotalPayload,
|
||||
existingSession: Pageview | null
|
||||
existingSession: ActiveSession | null
|
||||
) => {
|
||||
if (existingSession) {
|
||||
await sql`
|
||||
UPDATE active_sessions SET last_activity = ${pageview.timestamp}, pageviews = pageviews + 1 WHERE user_id = ${pageview.userId}
|
||||
`;
|
||||
// Update session with Drizzle
|
||||
await db
|
||||
.update(activeSessions)
|
||||
.set({
|
||||
lastActivity: new Date(pageview.timestamp),
|
||||
pageviews: (existingSession.pageviews || 0) + 1,
|
||||
})
|
||||
.where(eq(activeSessions.userId, pageview.userId));
|
||||
return;
|
||||
}
|
||||
|
||||
const inserts = {
|
||||
site_id: pageview.site_id || 0,
|
||||
session_id: pageview.sessionId,
|
||||
user_id: pageview.userId,
|
||||
hostname: pageview.hostname || "",
|
||||
start_time: pageview.timestamp || "",
|
||||
last_activity: pageview.timestamp || "",
|
||||
// Insert new session with Drizzle
|
||||
const insertData = {
|
||||
sessionId: pageview.sessionId,
|
||||
siteId:
|
||||
typeof pageview.site_id === "string"
|
||||
? parseInt(pageview.site_id, 10)
|
||||
: 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,
|
||||
entry_page: pageview.pathname || "",
|
||||
// exit_page: pageview.pathname,
|
||||
device_type: getDeviceType(
|
||||
entryPage: pageview.pathname || null,
|
||||
deviceType: getDeviceType(
|
||||
pageview.screenWidth,
|
||||
pageview.screenHeight,
|
||||
pageview.ua
|
||||
),
|
||||
screen_width: pageview.screenWidth || 0,
|
||||
screen_height: pageview.screenHeight || 0,
|
||||
browser: pageview.ua.browser.name || "",
|
||||
operating_system: pageview.ua.os.name || "",
|
||||
language: pageview.language || "",
|
||||
referrer: pageview.referrer || "",
|
||||
screenWidth: pageview.screenWidth || null,
|
||||
screenHeight: pageview.screenHeight || null,
|
||||
browser: pageview.ua.browser.name || null,
|
||||
operatingSystem: pageview.ua.os.name || null,
|
||||
language: pageview.language || null,
|
||||
referrer: pageview.referrer || null,
|
||||
};
|
||||
|
||||
await sql`
|
||||
INSERT INTO active_sessions ${sql(inserts)}
|
||||
`;
|
||||
await db.insert(activeSessions).values(insertData);
|
||||
};
|
||||
|
||||
export async function trackPageView(
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue