mirror of
https://github.com/kennethnym/aris.git
synced 2026-03-20 17:11:17 +00:00
Compare commits
2 Commits
61c1ade631
...
feat/drizz
| Author | SHA1 | Date | |
|---|---|---|---|
|
b65ce90866
|
|||
| 21750582b1 |
@@ -6,3 +6,14 @@ services:
|
|||||||
- postDevcontainerStart
|
- postDevcontainerStart
|
||||||
commands:
|
commands:
|
||||||
start: cd apps/aelis-client && ./scripts/run-dev-server.sh
|
start: cd apps/aelis-client && ./scripts/run-dev-server.sh
|
||||||
|
|
||||||
|
drizzle-studio:
|
||||||
|
name: Drizzle Studio
|
||||||
|
description: Drizzle Studio database browser for aelis-backend
|
||||||
|
triggeredBy:
|
||||||
|
- manual
|
||||||
|
commands:
|
||||||
|
start: |
|
||||||
|
FORWARD_URL=$(gitpod environment port open 4983 --name drizzle-studio-server | sed 's|https://||')
|
||||||
|
echo "Drizzle Studio: https://local.drizzle.studio/?host=${FORWARD_URL}&port=443"
|
||||||
|
cd apps/aelis-backend && bunx drizzle-kit studio --host 0.0.0.0 --port 4983
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
// Run: bunx --bun auth@latest generate --config auth.ts --output src/db/auth-schema.ts
|
// Run: bunx --bun auth@latest generate --config auth.ts --output src/db/auth-schema.ts
|
||||||
import { betterAuth } from "better-auth"
|
import { betterAuth } from "better-auth"
|
||||||
import { drizzleAdapter } from "better-auth/adapters/drizzle"
|
import { drizzleAdapter } from "better-auth/adapters/drizzle"
|
||||||
|
import { admin } from "better-auth/plugins"
|
||||||
import { SQL } from "bun"
|
import { SQL } from "bun"
|
||||||
import { drizzle } from "drizzle-orm/bun-sql"
|
import { drizzle } from "drizzle-orm/bun-sql"
|
||||||
|
|
||||||
@@ -13,6 +14,7 @@ export const auth = betterAuth({
|
|||||||
emailAndPassword: {
|
emailAndPassword: {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
},
|
},
|
||||||
|
plugins: [admin()],
|
||||||
})
|
})
|
||||||
|
|
||||||
export default auth
|
export default auth
|
||||||
|
|||||||
@@ -9,8 +9,10 @@
|
|||||||
"test": "bun test src/",
|
"test": "bun test src/",
|
||||||
"db:generate": "bunx drizzle-kit generate",
|
"db:generate": "bunx drizzle-kit generate",
|
||||||
"db:generate-auth": "bunx --bun auth@latest generate --config auth.ts --output src/db/auth-schema.ts -y",
|
"db:generate-auth": "bunx --bun auth@latest generate --config auth.ts --output src/db/auth-schema.ts -y",
|
||||||
|
"db:push": "bunx drizzle-kit push",
|
||||||
"db:migrate": "bunx drizzle-kit migrate",
|
"db:migrate": "bunx drizzle-kit migrate",
|
||||||
"db:studio": "bunx drizzle-kit studio"
|
"db:studio": "bunx drizzle-kit studio",
|
||||||
|
"create-admin": "bun run src/scripts/create-admin.ts"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@aelis/core": "workspace:*",
|
"@aelis/core": "workspace:*",
|
||||||
|
|||||||
@@ -1,10 +1,16 @@
|
|||||||
import { betterAuth } from "better-auth"
|
import { betterAuth } from "better-auth"
|
||||||
import { drizzleAdapter } from "better-auth/adapters/drizzle"
|
import { drizzleAdapter } from "better-auth/adapters/drizzle"
|
||||||
|
import { admin } from "better-auth/plugins"
|
||||||
|
|
||||||
import type { Database } from "../db/index.ts"
|
import type { Database } from "../db/index.ts"
|
||||||
|
|
||||||
import * as schema from "../db/schema.ts"
|
import * as schema from "../db/schema.ts"
|
||||||
|
|
||||||
export function createAuth(db: Database) {
|
export function createAuth(db: Database) {
|
||||||
|
if (!process.env.BETTER_AUTH_SECRET) {
|
||||||
|
throw new Error("BETTER_AUTH_SECRET is not set")
|
||||||
|
}
|
||||||
|
|
||||||
return betterAuth({
|
return betterAuth({
|
||||||
database: drizzleAdapter(db, {
|
database: drizzleAdapter(db, {
|
||||||
provider: "pg",
|
provider: "pg",
|
||||||
@@ -13,6 +19,7 @@ export function createAuth(db: Database) {
|
|||||||
emailAndPassword: {
|
emailAndPassword: {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
},
|
},
|
||||||
|
plugins: [admin()],
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -57,9 +57,7 @@ export function createRequireSession(auth: Auth): AuthSessionMiddleware {
|
|||||||
* Creates a function to get session from headers. Useful for WebSocket upgrade validation.
|
* Creates a function to get session from headers. Useful for WebSocket upgrade validation.
|
||||||
*/
|
*/
|
||||||
export function createGetSessionFromHeaders(auth: Auth) {
|
export function createGetSessionFromHeaders(auth: Auth) {
|
||||||
return async (
|
return async (headers: Headers): Promise<{ user: AuthUser; session: AuthSession } | null> => {
|
||||||
headers: Headers,
|
|
||||||
): Promise<{ user: AuthUser; session: AuthSession } | null> => {
|
|
||||||
const session = await auth.api.getSession({ headers })
|
const session = await auth.api.getSession({ headers })
|
||||||
return session
|
return session
|
||||||
}
|
}
|
||||||
@@ -86,6 +84,10 @@ export function mockAuthSessionMiddleware(userId?: string): AuthSessionMiddlewar
|
|||||||
image: null,
|
image: null,
|
||||||
createdAt: now,
|
createdAt: now,
|
||||||
updatedAt: now,
|
updatedAt: now,
|
||||||
|
role: "admin",
|
||||||
|
banned: false,
|
||||||
|
banReason: null,
|
||||||
|
banExpires: null,
|
||||||
}
|
}
|
||||||
|
|
||||||
const session: AuthSession = {
|
const session: AuthSession = {
|
||||||
|
|||||||
@@ -1,91 +1,96 @@
|
|||||||
import { relations } from "drizzle-orm";
|
import { relations } from "drizzle-orm"
|
||||||
import { pgTable, text, timestamp, boolean, index } from "drizzle-orm/pg-core";
|
import { pgTable, text, timestamp, boolean, index } from "drizzle-orm/pg-core"
|
||||||
|
|
||||||
export const user = pgTable("user", {
|
export const user = pgTable("user", {
|
||||||
id: text("id").primaryKey(),
|
id: text("id").primaryKey(),
|
||||||
name: text("name").notNull(),
|
name: text("name").notNull(),
|
||||||
email: text("email").notNull().unique(),
|
email: text("email").notNull().unique(),
|
||||||
emailVerified: boolean("email_verified").default(false).notNull(),
|
emailVerified: boolean("email_verified").default(false).notNull(),
|
||||||
image: text("image"),
|
image: text("image"),
|
||||||
createdAt: timestamp("created_at").notNull(),
|
createdAt: timestamp("created_at").notNull(),
|
||||||
updatedAt: timestamp("updated_at")
|
updatedAt: timestamp("updated_at")
|
||||||
.$onUpdate(() => new Date())
|
.$onUpdate(() => new Date())
|
||||||
.notNull(),
|
.notNull(),
|
||||||
});
|
role: text("role"),
|
||||||
|
banned: boolean("banned").default(false),
|
||||||
|
banReason: text("ban_reason"),
|
||||||
|
banExpires: timestamp("ban_expires"),
|
||||||
|
})
|
||||||
|
|
||||||
export const session = pgTable(
|
export const session = pgTable(
|
||||||
"session",
|
"session",
|
||||||
{
|
{
|
||||||
id: text("id").primaryKey(),
|
id: text("id").primaryKey(),
|
||||||
expiresAt: timestamp("expires_at").notNull(),
|
expiresAt: timestamp("expires_at").notNull(),
|
||||||
token: text("token").notNull().unique(),
|
token: text("token").notNull().unique(),
|
||||||
createdAt: timestamp("created_at").notNull(),
|
createdAt: timestamp("created_at").notNull(),
|
||||||
updatedAt: timestamp("updated_at")
|
updatedAt: timestamp("updated_at")
|
||||||
.$onUpdate(() => new Date())
|
.$onUpdate(() => new Date())
|
||||||
.notNull(),
|
.notNull(),
|
||||||
ipAddress: text("ip_address"),
|
ipAddress: text("ip_address"),
|
||||||
userAgent: text("user_agent"),
|
userAgent: text("user_agent"),
|
||||||
userId: text("user_id")
|
userId: text("user_id")
|
||||||
.notNull()
|
.notNull()
|
||||||
.references(() => user.id, { onDelete: "cascade" }),
|
.references(() => user.id, { onDelete: "cascade" }),
|
||||||
},
|
impersonatedBy: text("impersonated_by"),
|
||||||
(table) => [index("session_userId_idx").on(table.userId)],
|
},
|
||||||
);
|
(table) => [index("session_userId_idx").on(table.userId)],
|
||||||
|
)
|
||||||
|
|
||||||
export const account = pgTable(
|
export const account = pgTable(
|
||||||
"account",
|
"account",
|
||||||
{
|
{
|
||||||
id: text("id").primaryKey(),
|
id: text("id").primaryKey(),
|
||||||
accountId: text("account_id").notNull(),
|
accountId: text("account_id").notNull(),
|
||||||
providerId: text("provider_id").notNull(),
|
providerId: text("provider_id").notNull(),
|
||||||
userId: text("user_id")
|
userId: text("user_id")
|
||||||
.notNull()
|
.notNull()
|
||||||
.references(() => user.id, { onDelete: "cascade" }),
|
.references(() => user.id, { onDelete: "cascade" }),
|
||||||
accessToken: text("access_token"),
|
accessToken: text("access_token"),
|
||||||
refreshToken: text("refresh_token"),
|
refreshToken: text("refresh_token"),
|
||||||
idToken: text("id_token"),
|
idToken: text("id_token"),
|
||||||
accessTokenExpiresAt: timestamp("access_token_expires_at"),
|
accessTokenExpiresAt: timestamp("access_token_expires_at"),
|
||||||
refreshTokenExpiresAt: timestamp("refresh_token_expires_at"),
|
refreshTokenExpiresAt: timestamp("refresh_token_expires_at"),
|
||||||
scope: text("scope"),
|
scope: text("scope"),
|
||||||
password: text("password"),
|
password: text("password"),
|
||||||
createdAt: timestamp("created_at").notNull(),
|
createdAt: timestamp("created_at").notNull(),
|
||||||
updatedAt: timestamp("updated_at")
|
updatedAt: timestamp("updated_at")
|
||||||
.$onUpdate(() => new Date())
|
.$onUpdate(() => new Date())
|
||||||
.notNull(),
|
.notNull(),
|
||||||
},
|
},
|
||||||
(table) => [index("account_userId_idx").on(table.userId)],
|
(table) => [index("account_userId_idx").on(table.userId)],
|
||||||
);
|
)
|
||||||
|
|
||||||
export const verification = pgTable(
|
export const verification = pgTable(
|
||||||
"verification",
|
"verification",
|
||||||
{
|
{
|
||||||
id: text("id").primaryKey(),
|
id: text("id").primaryKey(),
|
||||||
identifier: text("identifier").notNull(),
|
identifier: text("identifier").notNull(),
|
||||||
value: text("value").notNull(),
|
value: text("value").notNull(),
|
||||||
expiresAt: timestamp("expires_at").notNull(),
|
expiresAt: timestamp("expires_at").notNull(),
|
||||||
createdAt: timestamp("created_at").notNull(),
|
createdAt: timestamp("created_at").notNull(),
|
||||||
updatedAt: timestamp("updated_at")
|
updatedAt: timestamp("updated_at")
|
||||||
.$onUpdate(() => new Date())
|
.$onUpdate(() => new Date())
|
||||||
.notNull(),
|
.notNull(),
|
||||||
},
|
},
|
||||||
(table) => [index("verification_identifier_idx").on(table.identifier)],
|
(table) => [index("verification_identifier_idx").on(table.identifier)],
|
||||||
);
|
)
|
||||||
|
|
||||||
export const userRelations = relations(user, ({ many }) => ({
|
export const userRelations = relations(user, ({ many }) => ({
|
||||||
sessions: many(session),
|
sessions: many(session),
|
||||||
accounts: many(account),
|
accounts: many(account),
|
||||||
}));
|
}))
|
||||||
|
|
||||||
export const sessionRelations = relations(session, ({ one }) => ({
|
export const sessionRelations = relations(session, ({ one }) => ({
|
||||||
user: one(user, {
|
user: one(user, {
|
||||||
fields: [session.userId],
|
fields: [session.userId],
|
||||||
references: [user.id],
|
references: [user.id],
|
||||||
}),
|
}),
|
||||||
}));
|
}))
|
||||||
|
|
||||||
export const accountRelations = relations(account, ({ one }) => ({
|
export const accountRelations = relations(account, ({ one }) => ({
|
||||||
user: one(user, {
|
user: one(user, {
|
||||||
fields: [account.userId],
|
fields: [account.userId],
|
||||||
references: [user.id],
|
references: [user.id],
|
||||||
}),
|
}),
|
||||||
}));
|
}))
|
||||||
|
|||||||
63
apps/aelis-backend/src/scripts/create-admin.ts
Normal file
63
apps/aelis-backend/src/scripts/create-admin.ts
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
/**
|
||||||
|
* Creates an admin user account via Better Auth's server-side API.
|
||||||
|
*
|
||||||
|
* Usage:
|
||||||
|
* bun run src/scripts/create-admin.ts --name "Admin" --email admin@example.com --password secret123
|
||||||
|
*
|
||||||
|
* Requires DATABASE_URL and BETTER_AUTH_SECRET to be set (reads .env automatically).
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { parseArgs } from "util"
|
||||||
|
|
||||||
|
import { createAuth } from "../auth/index.ts"
|
||||||
|
import { createDatabase } from "../db/index.ts"
|
||||||
|
|
||||||
|
function parseCliArgs(): { name: string; email: string; password: string } {
|
||||||
|
const { values } = parseArgs({
|
||||||
|
args: Bun.argv.slice(2),
|
||||||
|
options: {
|
||||||
|
name: { type: "string" },
|
||||||
|
email: { type: "string" },
|
||||||
|
password: { type: "string" },
|
||||||
|
},
|
||||||
|
strict: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!values.name || !values.email || !values.password) {
|
||||||
|
console.error(
|
||||||
|
"Usage: bun run src/scripts/create-admin.ts --name <name> --email <email> --password <password>",
|
||||||
|
)
|
||||||
|
process.exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
return { name: values.name, email: values.email, password: values.password }
|
||||||
|
}
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
const { name, email, password } = parseCliArgs()
|
||||||
|
|
||||||
|
const databaseUrl = process.env.DATABASE_URL
|
||||||
|
if (!databaseUrl) {
|
||||||
|
console.error("DATABASE_URL is not set")
|
||||||
|
process.exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
const { db, close } = createDatabase(databaseUrl)
|
||||||
|
|
||||||
|
try {
|
||||||
|
const auth = createAuth(db)
|
||||||
|
|
||||||
|
const result = await auth.api.createUser({
|
||||||
|
body: { name, email, password, role: "admin" },
|
||||||
|
})
|
||||||
|
|
||||||
|
console.log(`Admin account created: ${result.user.id} (${result.user.email})`)
|
||||||
|
} finally {
|
||||||
|
await close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
main().catch((err) => {
|
||||||
|
console.error("Failed to create admin account:", err)
|
||||||
|
process.exit(1)
|
||||||
|
})
|
||||||
Reference in New Issue
Block a user