[Closes #8] Added authentication
Some checks failed
Production Build and Deploy / Build (push) Failing after 44s
Production Build and Deploy / Deploy (push) Has been skipped

This commit is contained in:
Liviu Burcusel 2026-01-07 11:11:35 +01:00
parent 6eefa137bb
commit 97211cdccd
Signed by: liviu
GPG key ID: 6CDB37A4AD2C610C
65 changed files with 5831 additions and 440 deletions

View file

@ -0,0 +1,3 @@
import { createAuthClient } from "better-auth/vue";
export const authClient = createAuthClient();

18
shared/utils/auth.ts Normal file
View file

@ -0,0 +1,18 @@
import { betterAuth } from "better-auth";
import { drizzleAdapter } from "better-auth/adapters/drizzle";
import { v7 as uuidv7 } from "uuid";
import db from "./db/index";
export const auth = betterAuth({
database: drizzleAdapter(db, {
provider: "pg",
}),
advanced: {
database: {
generateId: () => uuidv7(),
},
},
emailAndPassword: {
enabled: true,
},
});

15
shared/utils/db/index.ts Normal file
View file

@ -0,0 +1,15 @@
// Global database connection
import { drizzle } from "drizzle-orm/node-postgres";
import { Pool } from "pg";
import env from "../env";
import * as schema from "./schema";
const pool = new Pool({
connectionString: env.DATABASE_URL,
});
const db = drizzle({ client: pool, casing: "snake_case", schema });
export default db;

View file

@ -0,0 +1,62 @@
CREATE TABLE "account" (
"id" uuid PRIMARY KEY DEFAULT uuidv7() NOT NULL,
"account_id" text NOT NULL,
"provider_id" text NOT NULL,
"user_id" uuid NOT NULL,
"access_token" text,
"refresh_token" text,
"id_token" text,
"access_token_expires_at" timestamp,
"refresh_token_expires_at" timestamp,
"scope" text,
"password" text,
"created_at" timestamp DEFAULT now() NOT NULL,
"updated_at" timestamp NOT NULL
);
--> statement-breakpoint
CREATE TABLE "session" (
"id" uuid PRIMARY KEY DEFAULT uuidv7() NOT NULL,
"expires_at" timestamp NOT NULL,
"token" text NOT NULL,
"created_at" timestamp DEFAULT now() NOT NULL,
"updated_at" timestamp NOT NULL,
"ip_address" text,
"user_agent" text,
"user_id" uuid NOT NULL,
CONSTRAINT "session_token_unique" UNIQUE("token")
);
--> statement-breakpoint
CREATE TABLE "user" (
"id" uuid PRIMARY KEY DEFAULT uuidv7() NOT NULL,
"name" text NOT NULL,
"email" text NOT NULL,
"email_verified" boolean DEFAULT false NOT NULL,
"image" text,
"created_at" timestamp DEFAULT now() NOT NULL,
"updated_at" timestamp DEFAULT now() NOT NULL,
CONSTRAINT "user_email_unique" UNIQUE("email")
);
--> statement-breakpoint
CREATE TABLE "verification" (
"id" uuid PRIMARY KEY DEFAULT uuidv7() NOT NULL,
"identifier" text NOT NULL,
"value" text NOT NULL,
"expires_at" timestamp NOT NULL,
"created_at" timestamp DEFAULT now() NOT NULL,
"updated_at" timestamp DEFAULT now() NOT NULL
);
--> statement-breakpoint
CREATE TABLE "member_data" (
"id" uuid PRIMARY KEY NOT NULL,
"country" text,
"info" jsonb,
"address" jsonb,
"billing" jsonb
);
--> statement-breakpoint
ALTER TABLE "account" ADD CONSTRAINT "account_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "session" ADD CONSTRAINT "session_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "member_data" ADD CONSTRAINT "member_data_id_user_id_fk" FOREIGN KEY ("id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
CREATE INDEX "account_userId_idx" ON "account" USING btree ("user_id");--> statement-breakpoint
CREATE INDEX "session_userId_idx" ON "session" USING btree ("user_id");--> statement-breakpoint
CREATE INDEX "verification_identifier_idx" ON "verification" USING btree ("identifier");

View file

@ -0,0 +1,435 @@
{
"id": "150f8caf-a5d3-4a62-8431-9d874384b93a",
"prevId": "00000000-0000-0000-0000-000000000000",
"version": "7",
"dialect": "postgresql",
"tables": {
"public.account": {
"name": "account",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "uuid",
"primaryKey": true,
"notNull": true,
"default": "uuidv7()"
},
"account_id": {
"name": "account_id",
"type": "text",
"primaryKey": false,
"notNull": true
},
"provider_id": {
"name": "provider_id",
"type": "text",
"primaryKey": false,
"notNull": true
},
"user_id": {
"name": "user_id",
"type": "uuid",
"primaryKey": false,
"notNull": true
},
"access_token": {
"name": "access_token",
"type": "text",
"primaryKey": false,
"notNull": false
},
"refresh_token": {
"name": "refresh_token",
"type": "text",
"primaryKey": false,
"notNull": false
},
"id_token": {
"name": "id_token",
"type": "text",
"primaryKey": false,
"notNull": false
},
"access_token_expires_at": {
"name": "access_token_expires_at",
"type": "timestamp",
"primaryKey": false,
"notNull": false
},
"refresh_token_expires_at": {
"name": "refresh_token_expires_at",
"type": "timestamp",
"primaryKey": false,
"notNull": false
},
"scope": {
"name": "scope",
"type": "text",
"primaryKey": false,
"notNull": false
},
"password": {
"name": "password",
"type": "text",
"primaryKey": false,
"notNull": false
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"updated_at": {
"name": "updated_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true
}
},
"indexes": {
"account_userId_idx": {
"name": "account_userId_idx",
"columns": [
{
"expression": "user_id",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": false,
"concurrently": false,
"method": "btree",
"with": {}
}
},
"foreignKeys": {
"account_user_id_user_id_fk": {
"name": "account_user_id_user_id_fk",
"tableFrom": "account",
"tableTo": "user",
"columnsFrom": [
"user_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.session": {
"name": "session",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "uuid",
"primaryKey": true,
"notNull": true,
"default": "uuidv7()"
},
"expires_at": {
"name": "expires_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true
},
"token": {
"name": "token",
"type": "text",
"primaryKey": false,
"notNull": true
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"updated_at": {
"name": "updated_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true
},
"ip_address": {
"name": "ip_address",
"type": "text",
"primaryKey": false,
"notNull": false
},
"user_agent": {
"name": "user_agent",
"type": "text",
"primaryKey": false,
"notNull": false
},
"user_id": {
"name": "user_id",
"type": "uuid",
"primaryKey": false,
"notNull": true
}
},
"indexes": {
"session_userId_idx": {
"name": "session_userId_idx",
"columns": [
{
"expression": "user_id",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": false,
"concurrently": false,
"method": "btree",
"with": {}
}
},
"foreignKeys": {
"session_user_id_user_id_fk": {
"name": "session_user_id_user_id_fk",
"tableFrom": "session",
"tableTo": "user",
"columnsFrom": [
"user_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {
"session_token_unique": {
"name": "session_token_unique",
"nullsNotDistinct": false,
"columns": [
"token"
]
}
},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.user": {
"name": "user",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "uuid",
"primaryKey": true,
"notNull": true,
"default": "uuidv7()"
},
"name": {
"name": "name",
"type": "text",
"primaryKey": false,
"notNull": true
},
"email": {
"name": "email",
"type": "text",
"primaryKey": false,
"notNull": true
},
"email_verified": {
"name": "email_verified",
"type": "boolean",
"primaryKey": false,
"notNull": true,
"default": false
},
"image": {
"name": "image",
"type": "text",
"primaryKey": false,
"notNull": false
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"updated_at": {
"name": "updated_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {
"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": "uuid",
"primaryKey": true,
"notNull": true,
"default": "uuidv7()"
},
"identifier": {
"name": "identifier",
"type": "text",
"primaryKey": false,
"notNull": true
},
"value": {
"name": "value",
"type": "text",
"primaryKey": false,
"notNull": true
},
"expires_at": {
"name": "expires_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"updated_at": {
"name": "updated_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
}
},
"indexes": {
"verification_identifier_idx": {
"name": "verification_identifier_idx",
"columns": [
{
"expression": "identifier",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": false,
"concurrently": false,
"method": "btree",
"with": {}
}
},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.member_data": {
"name": "member_data",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "uuid",
"primaryKey": true,
"notNull": true
},
"country": {
"name": "country",
"type": "text",
"primaryKey": false,
"notNull": false
},
"info": {
"name": "info",
"type": "jsonb",
"primaryKey": false,
"notNull": false
},
"address": {
"name": "address",
"type": "jsonb",
"primaryKey": false,
"notNull": false
},
"billing": {
"name": "billing",
"type": "jsonb",
"primaryKey": false,
"notNull": false
}
},
"indexes": {},
"foreignKeys": {
"member_data_id_user_id_fk": {
"name": "member_data_id_user_id_fk",
"tableFrom": "member_data",
"tableTo": "user",
"columnsFrom": [
"id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
}
},
"enums": {},
"schemas": {},
"sequences": {},
"roles": {},
"policies": {},
"views": {},
"_meta": {
"columns": {},
"schemas": {},
"tables": {}
}
}

View file

@ -0,0 +1,13 @@
{
"version": "7",
"dialect": "postgresql",
"entries": [
{
"idx": 0,
"version": "7",
"when": 1766830994761,
"tag": "0000_create_initial_tables",
"breakpoints": true
}
]
}

View file

@ -0,0 +1,106 @@
import { v7 as uuidv7 } from "uuid";
import { relations, sql } from "drizzle-orm";
import { boolean, index, pgTable, text, timestamp, uuid } from "drizzle-orm/pg-core";
export const user = pgTable("user", {
id: uuid()
.primaryKey()
.default(sql`uuid_v7()`)
.$defaultFn(() => uuidv7()),
name: text().notNull(),
email: text().notNull().unique(),
emailVerified: boolean().default(false).notNull(),
image: text(),
createdAt: timestamp().defaultNow().notNull(),
updatedAt: timestamp()
.defaultNow()
.$onUpdate(() => new Date())
.notNull(),
});
export const session = pgTable(
"session",
{
id: uuid()
.primaryKey()
.default(sql`uuid_v7()`)
.$defaultFn(() => uuidv7()),
expiresAt: timestamp().notNull(),
token: text().notNull().unique(),
createdAt: timestamp().defaultNow().notNull(),
updatedAt: timestamp()
.$onUpdate(() => new Date())
.notNull(),
ipAddress: text(),
userAgent: text(),
userId: uuid()
.notNull()
.references(() => user.id, { onDelete: "cascade" }),
},
(table) => [index("session_userId_idx").on(table.userId)]
);
export const account = pgTable(
"account",
{
id: uuid()
.primaryKey()
.default(sql`uuid_v7()`)
.$defaultFn(() => uuidv7()),
accountId: text().notNull(),
providerId: text().notNull(),
userId: uuid()
.notNull()
.references(() => user.id, { onDelete: "cascade" }),
accessToken: text(),
refreshToken: text(),
idToken: text(),
accessTokenExpiresAt: timestamp(),
refreshTokenExpiresAt: timestamp(),
scope: text(),
password: text(),
createdAt: timestamp().defaultNow().notNull(),
updatedAt: timestamp()
.$onUpdate(() => new Date())
.notNull(),
},
(table) => [index("account_userId_idx").on(table.userId)]
);
export const verification = pgTable(
"verification",
{
id: uuid()
.primaryKey()
.default(sql`uuid_v7()`)
.$defaultFn(() => uuidv7()),
identifier: text().notNull(),
value: text().notNull(),
expiresAt: timestamp().notNull(),
createdAt: timestamp().defaultNow().notNull(),
updatedAt: timestamp()
.defaultNow()
.$onUpdate(() => new Date())
.notNull(),
},
(table) => [index("verification_identifier_idx").on(table.identifier)]
);
export const userRelations = relations(user, ({ many }) => ({
sessions: many(session),
accounts: many(account),
}));
export const sessionRelations = relations(session, ({ one }) => ({
user: one(user, {
fields: [session.userId],
references: [user.id],
}),
}));
export const accountRelations = relations(account, ({ one }) => ({
user: one(user, {
fields: [account.userId],
references: [user.id],
}),
}));

View file

@ -0,0 +1,2 @@
export * from "./auth";
export * from "./memberData";

View file

@ -0,0 +1,21 @@
import { relations } from "drizzle-orm";
import { jsonb, pgTable, text, uuid } from "drizzle-orm/pg-core";
import { user } from "./auth";
export const memberData = pgTable("member_data", {
id: uuid()
.primaryKey()
.references(() => user.id, { onDelete: "cascade" }),
country: text(),
info: jsonb(),
address: jsonb(),
billing: jsonb(),
});
export const memberDataRelations = relations(memberData, ({ one }) => ({
user: one(user, {
fields: [memberData.id],
references: [user.id],
}),
}));

13
shared/utils/env.ts Normal file
View file

@ -0,0 +1,13 @@
import { z } from "zod";
const EnvSchema = z.object({
NODE_ENV: z.string(),
DATABASE_URL: z.string(),
BETTER_AUTH_SECRET: z.string(),
BETTER_AUTH_URL: z.string(),
});
export type EnvSchema = z.infer<typeof EnvSchema>;
// eslint-disable-next-line node/no-process-env
export default EnvSchema.parse(process.env);