diff --git a/app/api/auth/register/route.ts b/app/api/auth/register/route.ts new file mode 100644 index 0000000..81cc216 --- /dev/null +++ b/app/api/auth/register/route.ts @@ -0,0 +1,27 @@ +import { register } from "@/lib/auth" +import { authSchema } from "@/lib/validation" +import { ZodError } from "zod" + +export const runtime = "edge" + +export async function POST(request: Request) { + try { + const json = await request.json() + const body = authSchema.parse(json) + + await register(body.username, body.password) + return Response.json({ success: true }) + } catch (error) { + if (error instanceof ZodError) { + return Response.json( + { error: error.errors[0].message }, + { status: 400 } + ) + } + + return Response.json( + { error: error instanceof Error ? error.message : "注册失败" }, + { status: 500 } + ) + } +} \ No newline at end of file diff --git a/app/components/auth/login-form.tsx b/app/components/auth/login-form.tsx new file mode 100644 index 0000000..bde56d6 --- /dev/null +++ b/app/components/auth/login-form.tsx @@ -0,0 +1,342 @@ +"use client" + +import { useState } from "react" +import { signIn } from "next-auth/react" +import { useToast } from "@/components/ui/use-toast" +import { Button } from "@/components/ui/button" +import { Input } from "@/components/ui/input" +import { + Card, + CardContent, + CardDescription, + CardHeader, + CardTitle, +} from "@/components/ui/card" +import { + Tabs, + TabsContent, + TabsList, + TabsTrigger, +} from "@/components/ui/tabs" +import { Github, Loader2, KeyRound, User2 } from "lucide-react" +import { cn } from "@/lib/utils" + +interface FormErrors { + username?: string + password?: string + confirmPassword?: string +} + +export function LoginForm() { + const [username, setUsername] = useState("") + const [password, setPassword] = useState("") + const [confirmPassword, setConfirmPassword] = useState("") + const [loading, setLoading] = useState(false) + const [errors, setErrors] = useState({}) + const { toast } = useToast() + + const validateLoginForm = () => { + const newErrors: FormErrors = {} + if (!username) newErrors.username = "请输入用户名" + if (!password) newErrors.password = "请输入密码" + if (password && password.length < 8) newErrors.password = "密码长度必须大于等于8位" + setErrors(newErrors) + return Object.keys(newErrors).length === 0 + } + + const validateRegisterForm = () => { + const newErrors: FormErrors = {} + if (!username) newErrors.username = "请输入用户名" + if (!password) newErrors.password = "请输入密码" + if (password && password.length < 8) newErrors.password = "密码长度必须大于等于8位" + if (!confirmPassword) newErrors.confirmPassword = "请确认密码" + if (password !== confirmPassword) newErrors.confirmPassword = "两次输入的密码不一致" + setErrors(newErrors) + return Object.keys(newErrors).length === 0 + } + + const handleLogin = async () => { + if (!validateLoginForm()) return + + setLoading(true) + try { + const result = await signIn("credentials", { + username, + password, + redirect: false, + }) + + if (result?.error) { + toast({ + title: "登录失败", + description: "用户名或密码错误", + variant: "destructive", + }) + setLoading(false) + return + } + + window.location.href = "/" + } catch (error) { + toast({ + title: "登录失败", + description: error instanceof Error ? error.message : "请稍后重试", + variant: "destructive", + }) + setLoading(false) + } + } + + const handleRegister = async () => { + if (!validateRegisterForm()) return + + setLoading(true) + try { + const response = await fetch("/api/auth/register", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ username, password }), + }) + + const data = await response.json() as { error?: string } + + if (!response.ok) { + toast({ + title: "注册失败", + description: data.error || "请稍后重试", + variant: "destructive", + }) + setLoading(false) + return + } + + // 注册成功后自动登录 + const result = await signIn("credentials", { + username, + password, + redirect: false, + }) + + if (result?.error) { + toast({ + title: "登录失败", + description: "自动登录失败,请手动登录", + variant: "destructive", + }) + setLoading(false) + return + } + + window.location.href = "/" + } catch (error) { + toast({ + title: "注册失败", + description: error instanceof Error ? error.message : "请稍后重试", + variant: "destructive", + }) + setLoading(false) + } + } + + const handleGithubLogin = () => { + signIn("github", { callbackUrl: "/" }) + } + + const clearForm = () => { + setUsername("") + setPassword("") + setConfirmPassword("") + setErrors({}) + } + + return ( + + + + 欢迎使用 MoeMail + + + 萌萌哒临时邮箱服务 (。・∀・)ノ + + + + + + 登录 + 注册 + +
+ +
+
+
+
+ +
+ { + setUsername(e.target.value) + setErrors({}) + }} + disabled={loading} + /> +
+ {errors.username && ( +

{errors.username}

+ )} +
+
+
+
+ +
+ { + setPassword(e.target.value) + setErrors({}) + }} + disabled={loading} + /> +
+ {errors.password && ( +

{errors.password}

+ )} +
+
+ +
+ + +
+
+ +
+
+ + 或者 + +
+
+ + +
+
+ +
+
+
+
+ +
+ { + setUsername(e.target.value) + setErrors({}) + }} + disabled={loading} + /> +
+ {errors.username && ( +

{errors.username}

+ )} +
+
+
+
+ +
+ { + setPassword(e.target.value) + setErrors({}) + }} + disabled={loading} + /> +
+ {errors.password && ( +

{errors.password}

+ )} +
+
+
+
+ +
+ { + setConfirmPassword(e.target.value) + setErrors({}) + }} + disabled={loading} + /> +
+ {errors.confirmPassword && ( +

{errors.confirmPassword}

+ )} +
+
+ +
+ +
+
+
+
+
+
+ ) +} \ No newline at end of file diff --git a/app/components/auth/sign-button.tsx b/app/components/auth/sign-button.tsx index 2ca0c91..f00e01c 100644 --- a/app/components/auth/sign-button.tsx +++ b/app/components/auth/sign-button.tsx @@ -2,23 +2,30 @@ import { Button } from "@/components/ui/button" import Image from "next/image" -import { signIn, signOut, useSession } from "next-auth/react" -import { Github } from "lucide-react" +import { signOut, useSession } from "next-auth/react" +import { LogIn } from "lucide-react" +import { useRouter } from 'next/navigation' import Link from "next/link" +import { cn } from "@/lib/utils" -export function SignButton() { +interface SignButtonProps { + size?: "default" | "lg" +} + +export function SignButton({ size = "default" }: SignButtonProps) { + const router = useRouter() const { data: session, status } = useSession() const loading = status === "loading" if (loading) { - return
// 防止布局跳动 + return
} if (!session?.user) { return ( - ) } @@ -40,7 +47,7 @@ export function SignButton() { )} {session.user.name} -
diff --git a/app/components/home/action-button.tsx b/app/components/home/action-button.tsx index 283fcfd..b2a3055 100644 --- a/app/components/home/action-button.tsx +++ b/app/components/home/action-button.tsx @@ -1,9 +1,9 @@ "use client" import { Button } from "@/components/ui/button" -import { Mail, Github } from "lucide-react" +import { Mail } from "lucide-react" import { useRouter } from "next/navigation" -import { signIn } from "next-auth/react" +import { SignButton } from "../auth/sign-button" interface ActionButtonProps { isLoggedIn?: boolean @@ -25,14 +25,5 @@ export function ActionButton({ isLoggedIn }: ActionButtonProps) { ) } - return ( - - ) + return } \ No newline at end of file diff --git a/app/components/profile/profile-card.tsx b/app/components/profile/profile-card.tsx index 8ea6fcc..ea8bb2c 100644 --- a/app/components/profile/profile-card.tsx +++ b/app/components/profile/profile-card.tsx @@ -47,10 +47,15 @@ export function ProfileCard({ user }: ProfileCardProps) {

{user.name}

-
- - 已关联 -
+ { + user.email && ( + // 先简单实现,后续再完善 +
+ + 已关联 +
+ ) + }

{user.email} diff --git a/app/components/ui/card.tsx b/app/components/ui/card.tsx new file mode 100644 index 0000000..dab7b75 --- /dev/null +++ b/app/components/ui/card.tsx @@ -0,0 +1,78 @@ +import * as React from "react" +import { cn } from "@/lib/utils" + +const Card = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +

+)) +Card.displayName = "Card" + +const CardHeader = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)) +CardHeader.displayName = "CardHeader" + +const CardTitle = React.forwardRef< + HTMLParagraphElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +

+)) +CardTitle.displayName = "CardTitle" + +const CardDescription = React.forwardRef< + HTMLParagraphElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +

+)) +CardDescription.displayName = "CardDescription" + +const CardContent = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +

+)) +CardContent.displayName = "CardContent" + +const CardFooter = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)) +CardFooter.displayName = "CardFooter" + +export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent } \ No newline at end of file diff --git a/app/components/ui/dropdown-menu.tsx b/app/components/ui/dropdown-menu.tsx new file mode 100644 index 0000000..b0254fa --- /dev/null +++ b/app/components/ui/dropdown-menu.tsx @@ -0,0 +1,47 @@ +"use client" + +import * as React from "react" +import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu" +import { cn } from "@/lib/utils" + +const DropdownMenu = DropdownMenuPrimitive.Root +const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger +const DropdownMenuItem = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName + +const DropdownMenuContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, sideOffset = 4, ...props }, ref) => ( + + + +)) +DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName + +export { + DropdownMenu, + DropdownMenuTrigger, + DropdownMenuContent, + DropdownMenuItem, +} \ No newline at end of file diff --git a/app/components/ui/tabs.tsx b/app/components/ui/tabs.tsx new file mode 100644 index 0000000..7affb7c --- /dev/null +++ b/app/components/ui/tabs.tsx @@ -0,0 +1,54 @@ +"use client" + +import * as React from "react" +import * as TabsPrimitive from "@radix-ui/react-tabs" +import { cn } from "@/lib/utils" + +const Tabs = TabsPrimitive.Root + +const TabsList = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +TabsList.displayName = TabsPrimitive.List.displayName + +const TabsTrigger = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +TabsTrigger.displayName = TabsPrimitive.Trigger.displayName + +const TabsContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +TabsContent.displayName = TabsPrimitive.Content.displayName + +export { Tabs, TabsList, TabsTrigger, TabsContent } \ No newline at end of file diff --git a/app/lib/auth.ts b/app/lib/auth.ts index 592feb8..1d0b9e2 100644 --- a/app/lib/auth.ts +++ b/app/lib/auth.ts @@ -2,10 +2,14 @@ import NextAuth from "next-auth" import GitHub from "next-auth/providers/github" import { DrizzleAdapter } from "@auth/drizzle-adapter" import { createDb, Db } from "./db" -import { accounts, sessions, users, roles, userRoles } from "./schema" +import { accounts, users, roles, userRoles } from "./schema" import { eq } from "drizzle-orm" import { getRequestContext } from "@cloudflare/next-on-pages" import { Permission, hasPermission, ROLES, Role } from "./permissions" +import CredentialsProvider from "next-auth/providers/credentials" +import { hashPassword, comparePassword } from "@/lib/utils" +import { authSchema } from "@/lib/validation" +import { generateAvatarUrl } from "./avatar" const ROLE_DESCRIPTIONS: Record = { [ROLES.EMPEROR]: "皇帝(网站所有者)", @@ -71,13 +75,53 @@ export const { adapter: DrizzleAdapter(createDb(), { usersTable: users, accountsTable: accounts, - sessionsTable: sessions, }), providers: [ GitHub({ clientId: process.env.AUTH_GITHUB_ID, clientSecret: process.env.AUTH_GITHUB_SECRET, - }) + }), + CredentialsProvider({ + name: "Credentials", + credentials: { + username: { label: "用户名", type: "text", placeholder: "请输入用户名" }, + password: { label: "密码", type: "password", placeholder: "请输入密码" }, + }, + async authorize(credentials) { + if (!credentials) { + throw new Error("请输入用户名和密码") + } + + const { username, password } = credentials + + try { + authSchema.parse({ username, password }) + // eslint-disable-next-line @typescript-eslint/no-unused-vars + } catch (error) { + throw new Error("输入格式不正确") + } + + const db = createDb() + + const user = await db.query.users.findFirst({ + where: eq(users.username, username as string), + }) + + if (!user) { + throw new Error("用户名或密码错误") + } + + const isValid = await comparePassword(password as string, user.password as string) + if (!isValid) { + throw new Error("用户名或密码错误") + } + + return { + ...user, + password: undefined, + } + }, + }), ], events: { async signIn({ user }) { @@ -99,37 +143,73 @@ export const { } }, }, - pages: { - signIn: "/", - error: "/", - }, callbacks: { - async session({ session, user }) { - if (!session?.user) return session - - const db = createDb() - let userRoleRecords = await db.query.userRoles.findMany({ - where: eq(userRoles.userId, user.id), - with: { role: true }, - }) - - if (!userRoleRecords.length) { - const defaultRole = await getDefaultRole() - const role = await findOrCreateRole(db, defaultRole) - await assignRoleToUser(db, user.id, role.id) - userRoleRecords = [{ - userId: user.id, - roleId: role.id, - createdAt: new Date(), - role: role - }] + async jwt({ token, user }) { + if (user) { + token.id = user.id + token.name = user.name || user.username + token.username = user.username + token.image = user.image || generateAvatarUrl(token.name as string) } + return token + }, + async session({ session, token }) { + if (token && session.user) { + session.user.id = token.id as string + session.user.name = token.name as string + session.user.username = token.username as string + session.user.image = token.image as string - session.user.roles = userRoleRecords.map(ur => ({ - name: ur.role.name, - })) + const db = createDb() + let userRoleRecords = await db.query.userRoles.findMany({ + where: eq(userRoles.userId, session.user.id), + with: { role: true }, + }) + + if (!userRoleRecords.length) { + const defaultRole = await getDefaultRole() + const role = await findOrCreateRole(db, defaultRole) + await assignRoleToUser(db, session.user.id, role.id) + userRoleRecords = [{ + userId: session.user.id, + roleId: role.id, + createdAt: new Date(), + role: role + }] + } + + session.user.roles = userRoleRecords.map(ur => ({ + name: ur.role.name, + })) + } return session }, - } + }, + session: { + strategy: "jwt", + }, })) + +export async function register(username: string, password: string) { + const db = createDb() + + const existing = await db.query.users.findFirst({ + where: eq(users.username, username) + }) + + if (existing) { + throw new Error("用户名已存在") + } + + const hashedPassword = await hashPassword(password) + + const [user] = await db.insert(users) + .values({ + username, + password: hashedPassword, + }) + .returning() + + return user +} diff --git a/app/lib/avatar.ts b/app/lib/avatar.ts new file mode 100644 index 0000000..511481e --- /dev/null +++ b/app/lib/avatar.ts @@ -0,0 +1,48 @@ +const COLORS = [ + '#2196F3', // 蓝色 + '#009688', // 青色 + '#9C27B0', // 紫色 + '#F44336', // 红色 + '#673AB7', // 深紫色 + '#3F51B5', // 靛蓝 + '#4CAF50', // 绿色 + '#FF5722', // 深橙 + '#795548', // 棕色 + '#607D8B', // 蓝灰 +]; + +export function generateAvatarUrl(name: string): string { + const initial = name[0].toUpperCase(); + + const colorIndex = Array.from(name).reduce( + (acc, char) => acc + char.charCodeAt(0), 0 + ) % COLORS.length; + + const backgroundColor = COLORS[colorIndex]; + + const svg = ` + + + + ${initial} + + + `.trim(); + + const encoder = new TextEncoder(); + const bytes = encoder.encode(svg); + const base64 = Buffer.from(bytes).toString('base64'); + + return `data:image/svg+xml;base64,${base64}`; +} \ No newline at end of file diff --git a/app/lib/schema.ts b/app/lib/schema.ts index e126811..1d80ae6 100644 --- a/app/lib/schema.ts +++ b/app/lib/schema.ts @@ -11,8 +11,9 @@ export const users = sqliteTable("user", { email: text("email").unique(), emailVerified: integer("emailVerified", { mode: "timestamp_ms" }), image: text("image"), + username: text("username").unique(), + password: text("password"), }) - export const accounts = sqliteTable( "account", { @@ -36,15 +37,7 @@ export const accounts = sqliteTable( }), }) ) - -export const sessions = sqliteTable("session", { - sessionToken: text("sessionToken").primaryKey(), - userId: text("userId") - .notNull() - .references(() => users.id, { onDelete: "cascade" }), - expires: integer("expires", { mode: "timestamp_ms" }).notNull(), -}) - + export const emails = sqliteTable("email", { id: text("id").primaryKey().$defaultFn(() => crypto.randomUUID()), address: text("address").notNull().unique(), @@ -54,7 +47,7 @@ export const emails = sqliteTable("email", { .$defaultFn(() => new Date()), expiresAt: integer("expires_at", { mode: "timestamp_ms" }).notNull(), }) - + export const messages = sqliteTable("message", { id: text("id").primaryKey().$defaultFn(() => crypto.randomUUID()), emailId: text("emailId") @@ -68,7 +61,7 @@ export const messages = sqliteTable("message", { .notNull() .$defaultFn(() => new Date()), }) - + export const webhooks = sqliteTable('webhook', { id: text('id').primaryKey().$defaultFn(() => crypto.randomUUID()), userId: text('user_id') diff --git a/app/lib/utils.ts b/app/lib/utils.ts index f7654e8..0bbddfd 100644 --- a/app/lib/utils.ts +++ b/app/lib/utils.ts @@ -3,4 +3,17 @@ import { twMerge } from "tailwind-merge" export function cn(...inputs: ClassValue[]) { return twMerge(clsx(inputs)) -} \ No newline at end of file +} + +export async function hashPassword(password: string): Promise { + const encoder = new TextEncoder() + const salt = process.env.AUTH_SECRET || '' + const data = encoder.encode(password + salt) + const hash = await crypto.subtle.digest('SHA-256', data) + return btoa(String.fromCharCode(...new Uint8Array(hash))) +} + +export async function comparePassword(password: string, hashedPassword: string): Promise { + const hash = await hashPassword(password) + return hash === hashedPassword +} \ No newline at end of file diff --git a/app/lib/validation.ts b/app/lib/validation.ts new file mode 100644 index 0000000..2f6989e --- /dev/null +++ b/app/lib/validation.ts @@ -0,0 +1,8 @@ +import { z } from "zod" + +export const authSchema = z.object({ + username: z.string().min(1, "用户名不能为空"), + password: z.string().min(8, "密码长度必须大于等于8位"), +}) + +export type AuthSchema = z.infer \ No newline at end of file diff --git a/app/login/page.tsx b/app/login/page.tsx new file mode 100644 index 0000000..c997f12 --- /dev/null +++ b/app/login/page.tsx @@ -0,0 +1,19 @@ +import { LoginForm } from "@/components/auth/login-form" +import { auth } from "@/lib/auth" +import { redirect } from "next/navigation" + +export const runtime = "edge" + +export default async function LoginPage() { + const session = await auth() + + if (session?.user) { + redirect("/") + } + + return ( +
+ +
+ ) +} \ No newline at end of file diff --git a/drizzle/0006_thick_lester.sql b/drizzle/0006_thick_lester.sql new file mode 100644 index 0000000..9b61c4b --- /dev/null +++ b/drizzle/0006_thick_lester.sql @@ -0,0 +1,10 @@ +CREATE TABLE `credential` ( + `id` text PRIMARY KEY NOT NULL, + `userId` text NOT NULL, + `username` text NOT NULL, + `password` text NOT NULL, + `created_at` integer NOT NULL, + FOREIGN KEY (`userId`) REFERENCES `user`(`id`) ON UPDATE no action ON DELETE cascade +); +--> statement-breakpoint +CREATE UNIQUE INDEX `credential_username_unique` ON `credential` (`username`); \ No newline at end of file diff --git a/drizzle/0007_careless_sentinel.sql b/drizzle/0007_careless_sentinel.sql new file mode 100644 index 0000000..9fc3c21 --- /dev/null +++ b/drizzle/0007_careless_sentinel.sql @@ -0,0 +1,4 @@ +DROP TABLE `credential`;--> statement-breakpoint +ALTER TABLE `user` ADD `username` text;--> statement-breakpoint +ALTER TABLE `user` ADD `password` text;--> statement-breakpoint +CREATE UNIQUE INDEX `user_username_unique` ON `user` (`username`); \ No newline at end of file diff --git a/drizzle/0008_cute_dormammu.sql b/drizzle/0008_cute_dormammu.sql new file mode 100644 index 0000000..b9c1996 --- /dev/null +++ b/drizzle/0008_cute_dormammu.sql @@ -0,0 +1 @@ +DROP TABLE `session`; \ No newline at end of file diff --git a/drizzle/meta/0006_snapshot.json b/drizzle/meta/0006_snapshot.json new file mode 100644 index 0000000..9a82863 --- /dev/null +++ b/drizzle/meta/0006_snapshot.json @@ -0,0 +1,610 @@ +{ + "version": "6", + "dialect": "sqlite", + "id": "0b5d36d8-50f6-41fd-b5ec-7a611ee86a04", + "prevId": "1cde0c8f-dffe-4a01-bc08-678dc5879a13", + "tables": { + "account": { + "name": "account", + "columns": { + "userId": { + "name": "userId", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "providerAccountId": { + "name": "providerAccountId", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "refresh_token": { + "name": "refresh_token", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "access_token": { + "name": "access_token", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "token_type": { + "name": "token_type", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "id_token": { + "name": "id_token", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "session_state": { + "name": "session_state", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "account_userId_user_id_fk": { + "name": "account_userId_user_id_fk", + "tableFrom": "account", + "tableTo": "user", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "account_provider_providerAccountId_pk": { + "columns": [ + "provider", + "providerAccountId" + ], + "name": "account_provider_providerAccountId_pk" + } + }, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "credential": { + "name": "credential", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "userId": { + "name": "userId", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "credential_username_unique": { + "name": "credential_username_unique", + "columns": [ + "username" + ], + "isUnique": true + } + }, + "foreignKeys": { + "credential_userId_user_id_fk": { + "name": "credential_userId_user_id_fk", + "tableFrom": "credential", + "tableTo": "user", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "email": { + "name": "email", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "address": { + "name": "address", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "userId": { + "name": "userId", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "email_address_unique": { + "name": "email_address_unique", + "columns": [ + "address" + ], + "isUnique": true + } + }, + "foreignKeys": { + "email_userId_user_id_fk": { + "name": "email_userId_user_id_fk", + "tableFrom": "email", + "tableTo": "user", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "message": { + "name": "message", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "emailId": { + "name": "emailId", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "from_address": { + "name": "from_address", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "subject": { + "name": "subject", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "html": { + "name": "html", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "received_at": { + "name": "received_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "message_emailId_email_id_fk": { + "name": "message_emailId_email_id_fk", + "tableFrom": "message", + "tableTo": "email", + "columnsFrom": [ + "emailId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "role": { + "name": "role", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "session": { + "name": "session", + "columns": { + "sessionToken": { + "name": "sessionToken", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "userId": { + "name": "userId", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "expires": { + "name": "expires", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "session_userId_user_id_fk": { + "name": "session_userId_user_id_fk", + "tableFrom": "session", + "tableTo": "user", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "user_role": { + "name": "user_role", + "columns": { + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "role_id": { + "name": "role_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "user_role_user_id_user_id_fk": { + "name": "user_role_user_id_user_id_fk", + "tableFrom": "user_role", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_role_role_id_role_id_fk": { + "name": "user_role_role_id_role_id_fk", + "tableFrom": "user_role", + "tableTo": "role", + "columnsFrom": [ + "role_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "user_role_user_id_role_id_pk": { + "columns": [ + "user_id", + "role_id" + ], + "name": "user_role_user_id_role_id_pk" + } + }, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "user": { + "name": "user", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "emailVerified": { + "name": "emailVerified", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "user_email_unique": { + "name": "user_email_unique", + "columns": [ + "email" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "webhook": { + "name": "webhook", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "enabled": { + "name": "enabled", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "webhook_user_id_user_id_fk": { + "name": "webhook_user_id_user_id_fk", + "tableFrom": "webhook", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + } + }, + "views": {}, + "enums": {}, + "_meta": { + "schemas": {}, + "tables": {}, + "columns": {} + }, + "internal": { + "indexes": {} + } +} \ No newline at end of file diff --git a/drizzle/meta/0007_snapshot.json b/drizzle/meta/0007_snapshot.json new file mode 100644 index 0000000..c2d75d4 --- /dev/null +++ b/drizzle/meta/0007_snapshot.json @@ -0,0 +1,564 @@ +{ + "version": "6", + "dialect": "sqlite", + "id": "2fe400b0-5da1-4678-a3c8-7433d35903c3", + "prevId": "0b5d36d8-50f6-41fd-b5ec-7a611ee86a04", + "tables": { + "account": { + "name": "account", + "columns": { + "userId": { + "name": "userId", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "providerAccountId": { + "name": "providerAccountId", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "refresh_token": { + "name": "refresh_token", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "access_token": { + "name": "access_token", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "token_type": { + "name": "token_type", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "id_token": { + "name": "id_token", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "session_state": { + "name": "session_state", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "account_userId_user_id_fk": { + "name": "account_userId_user_id_fk", + "tableFrom": "account", + "tableTo": "user", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "account_provider_providerAccountId_pk": { + "columns": [ + "provider", + "providerAccountId" + ], + "name": "account_provider_providerAccountId_pk" + } + }, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "email": { + "name": "email", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "address": { + "name": "address", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "userId": { + "name": "userId", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "email_address_unique": { + "name": "email_address_unique", + "columns": [ + "address" + ], + "isUnique": true + } + }, + "foreignKeys": { + "email_userId_user_id_fk": { + "name": "email_userId_user_id_fk", + "tableFrom": "email", + "tableTo": "user", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "message": { + "name": "message", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "emailId": { + "name": "emailId", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "from_address": { + "name": "from_address", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "subject": { + "name": "subject", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "html": { + "name": "html", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "received_at": { + "name": "received_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "message_emailId_email_id_fk": { + "name": "message_emailId_email_id_fk", + "tableFrom": "message", + "tableTo": "email", + "columnsFrom": [ + "emailId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "role": { + "name": "role", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "session": { + "name": "session", + "columns": { + "sessionToken": { + "name": "sessionToken", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "userId": { + "name": "userId", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "expires": { + "name": "expires", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "session_userId_user_id_fk": { + "name": "session_userId_user_id_fk", + "tableFrom": "session", + "tableTo": "user", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "user_role": { + "name": "user_role", + "columns": { + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "role_id": { + "name": "role_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "user_role_user_id_user_id_fk": { + "name": "user_role_user_id_user_id_fk", + "tableFrom": "user_role", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_role_role_id_role_id_fk": { + "name": "user_role_role_id_role_id_fk", + "tableFrom": "user_role", + "tableTo": "role", + "columnsFrom": [ + "role_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "user_role_user_id_role_id_pk": { + "columns": [ + "user_id", + "role_id" + ], + "name": "user_role_user_id_role_id_pk" + } + }, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "user": { + "name": "user", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "emailVerified": { + "name": "emailVerified", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "user_email_unique": { + "name": "user_email_unique", + "columns": [ + "email" + ], + "isUnique": true + }, + "user_username_unique": { + "name": "user_username_unique", + "columns": [ + "username" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "webhook": { + "name": "webhook", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "enabled": { + "name": "enabled", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "webhook_user_id_user_id_fk": { + "name": "webhook_user_id_user_id_fk", + "tableFrom": "webhook", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + } + }, + "views": {}, + "enums": {}, + "_meta": { + "schemas": {}, + "tables": {}, + "columns": {} + }, + "internal": { + "indexes": {} + } +} \ No newline at end of file diff --git a/drizzle/meta/0008_snapshot.json b/drizzle/meta/0008_snapshot.json new file mode 100644 index 0000000..ca53e8d --- /dev/null +++ b/drizzle/meta/0008_snapshot.json @@ -0,0 +1,519 @@ +{ + "version": "6", + "dialect": "sqlite", + "id": "144530f0-42a9-450d-ab24-a419eb7b4f65", + "prevId": "2fe400b0-5da1-4678-a3c8-7433d35903c3", + "tables": { + "account": { + "name": "account", + "columns": { + "userId": { + "name": "userId", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "providerAccountId": { + "name": "providerAccountId", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "refresh_token": { + "name": "refresh_token", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "access_token": { + "name": "access_token", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "token_type": { + "name": "token_type", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "id_token": { + "name": "id_token", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "session_state": { + "name": "session_state", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "account_userId_user_id_fk": { + "name": "account_userId_user_id_fk", + "tableFrom": "account", + "tableTo": "user", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "account_provider_providerAccountId_pk": { + "columns": [ + "provider", + "providerAccountId" + ], + "name": "account_provider_providerAccountId_pk" + } + }, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "email": { + "name": "email", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "address": { + "name": "address", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "userId": { + "name": "userId", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "email_address_unique": { + "name": "email_address_unique", + "columns": [ + "address" + ], + "isUnique": true + } + }, + "foreignKeys": { + "email_userId_user_id_fk": { + "name": "email_userId_user_id_fk", + "tableFrom": "email", + "tableTo": "user", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "message": { + "name": "message", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "emailId": { + "name": "emailId", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "from_address": { + "name": "from_address", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "subject": { + "name": "subject", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "html": { + "name": "html", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "received_at": { + "name": "received_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "message_emailId_email_id_fk": { + "name": "message_emailId_email_id_fk", + "tableFrom": "message", + "tableTo": "email", + "columnsFrom": [ + "emailId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "role": { + "name": "role", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "user_role": { + "name": "user_role", + "columns": { + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "role_id": { + "name": "role_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "user_role_user_id_user_id_fk": { + "name": "user_role_user_id_user_id_fk", + "tableFrom": "user_role", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_role_role_id_role_id_fk": { + "name": "user_role_role_id_role_id_fk", + "tableFrom": "user_role", + "tableTo": "role", + "columnsFrom": [ + "role_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "user_role_user_id_role_id_pk": { + "columns": [ + "user_id", + "role_id" + ], + "name": "user_role_user_id_role_id_pk" + } + }, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "user": { + "name": "user", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "emailVerified": { + "name": "emailVerified", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "user_email_unique": { + "name": "user_email_unique", + "columns": [ + "email" + ], + "isUnique": true + }, + "user_username_unique": { + "name": "user_username_unique", + "columns": [ + "username" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "webhook": { + "name": "webhook", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "enabled": { + "name": "enabled", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "webhook_user_id_user_id_fk": { + "name": "webhook_user_id_user_id_fk", + "tableFrom": "webhook", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + } + }, + "views": {}, + "enums": {}, + "_meta": { + "schemas": {}, + "tables": {}, + "columns": {} + }, + "internal": { + "indexes": {} + } +} \ No newline at end of file diff --git a/drizzle/meta/_journal.json b/drizzle/meta/_journal.json index fcb8966..5d38966 100644 --- a/drizzle/meta/_journal.json +++ b/drizzle/meta/_journal.json @@ -43,6 +43,27 @@ "when": 1735273566991, "tag": "0005_tricky_mathemanic", "breakpoints": true + }, + { + "idx": 6, + "version": "6", + "when": 1735966405003, + "tag": "0006_thick_lester", + "breakpoints": true + }, + { + "idx": 7, + "version": "6", + "when": 1735971121264, + "tag": "0007_careless_sentinel", + "breakpoints": true + }, + { + "idx": 8, + "version": "6", + "when": 1736922488093, + "tag": "0008_cute_dormammu", + "breakpoints": true } ] } \ No newline at end of file diff --git a/package.json b/package.json index 4fb3cbd..a3b37ba 100644 --- a/package.json +++ b/package.json @@ -24,11 +24,13 @@ "@cloudflare/next-on-pages": "^1.13.6", "@radix-ui/react-alert-dialog": "^1.1.4", "@radix-ui/react-dialog": "^1.1.2", + "@radix-ui/react-dropdown-menu": "^2.1.4", "@radix-ui/react-label": "^2.1.0", "@radix-ui/react-radio-group": "^1.2.1", "@radix-ui/react-select": "^2.1.4", "@radix-ui/react-slot": "^1.0.2", "@radix-ui/react-switch": "^1.1.2", + "@radix-ui/react-tabs": "^1.1.2", "@radix-ui/react-toast": "^1.1.5", "@radix-ui/react-tooltip": "^1.1.6", "@tailwindcss/typography": "^0.5.15", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index bc91658..3bea55e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -20,6 +20,9 @@ importers: '@radix-ui/react-dialog': specifier: ^1.1.2 version: 1.1.2(@types/react-dom@18.3.2)(@types/react@18.3.14)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-dropdown-menu': + specifier: ^2.1.4 + version: 2.1.4(@types/react-dom@18.3.2)(@types/react@18.3.14)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) '@radix-ui/react-label': specifier: ^2.1.0 version: 2.1.0(@types/react-dom@18.3.2)(@types/react@18.3.14)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) @@ -35,6 +38,9 @@ importers: '@radix-ui/react-switch': specifier: ^1.1.2 version: 1.1.2(@types/react-dom@18.3.2)(@types/react@18.3.14)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-tabs': + specifier: ^1.1.2 + version: 1.1.2(@types/react-dom@18.3.2)(@types/react@18.3.14)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) '@radix-ui/react-toast': specifier: ^1.1.5 version: 1.2.2(@types/react-dom@18.3.2)(@types/react@18.3.14)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) @@ -1739,6 +1745,19 @@ packages: '@types/react-dom': optional: true + '@radix-ui/react-dropdown-menu@2.1.4': + resolution: {integrity: sha512-iXU1Ab5ecM+yEepGAWK8ZhMyKX4ubFdCNtol4sT9D0OVErG9PNElfx3TQhjw7n7BC5nFVz68/5//clWy+8TXzA==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-focus-guards@1.1.1': resolution: {integrity: sha512-pSIwfrT1a6sIoDASCSpFwOasEwKTZWDw/iBdtnqKO7v6FeOzYJ7U53cPzYFVR3geGGXgVHaH+CdngrrAzqUGxg==} peerDependencies: @@ -1796,6 +1815,19 @@ packages: '@types/react-dom': optional: true + '@radix-ui/react-menu@2.1.4': + resolution: {integrity: sha512-BnOgVoL6YYdHAG6DtXONaR29Eq4nvbi8rutrV/xlr3RQCMMb3yqP85Qiw/3NReozrSW+4dfLkK+rc1hb4wPU/A==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-popper@1.2.1': resolution: {integrity: sha512-3kn5Me69L+jv82EKRuQCXdYyf1DqHwD2U/sxoNgBGCB7K9TRc3bQamQ+5EPM9EvyPdli0W41sROd+ZU1dTCztw==} peerDependencies: @@ -1913,6 +1945,19 @@ packages: '@types/react-dom': optional: true + '@radix-ui/react-roving-focus@1.1.1': + resolution: {integrity: sha512-QE1RoxPGJ/Nm8Qmk0PxP8ojmoaS67i0s7hVssS7KuI2FQoc/uzVlZsqKfQvxPE6D8hICCPHJ4D88zNhT3OOmkw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-select@2.1.4': resolution: {integrity: sha512-pOkb2u8KgO47j/h7AylCj7dJsm69BXcjkrvTqMptFqsE2i0p8lHkfgneXKjAgPzBMivnoMyt8o4KiV4wYzDdyQ==} peerDependencies: @@ -1957,6 +2002,19 @@ packages: '@types/react-dom': optional: true + '@radix-ui/react-tabs@1.1.2': + resolution: {integrity: sha512-9u/tQJMcC2aGq7KXpGivMm1mgq7oRJKXphDwdypPd/j21j/2znamPU8WkXgnhUaTrSFNIt8XhOyCAupg8/GbwQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-toast@1.2.2': resolution: {integrity: sha512-Z6pqSzmAP/bFJoqMAston4eSNa+ud44NSZTiZUmUen+IOZ5nBY8kzuU5WDBVyFXPtcW6yUalOHsxM/BP6Sv8ww==} peerDependencies: @@ -6981,6 +7039,21 @@ snapshots: '@types/react': 18.3.14 '@types/react-dom': 18.3.2 + '@radix-ui/react-dropdown-menu@2.1.4(@types/react-dom@18.3.2)(@types/react@18.3.14)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': + dependencies: + '@radix-ui/primitive': 1.1.1 + '@radix-ui/react-compose-refs': 1.1.1(@types/react@18.3.14)(react@19.0.0) + '@radix-ui/react-context': 1.1.1(@types/react@18.3.14)(react@19.0.0) + '@radix-ui/react-id': 1.1.0(@types/react@18.3.14)(react@19.0.0) + '@radix-ui/react-menu': 2.1.4(@types/react-dom@18.3.2)(@types/react@18.3.14)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-primitive': 2.0.1(@types/react-dom@18.3.2)(@types/react@18.3.14)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-use-controllable-state': 1.1.0(@types/react@18.3.14)(react@19.0.0) + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + optionalDependencies: + '@types/react': 18.3.14 + '@types/react-dom': 18.3.2 + '@radix-ui/react-focus-guards@1.1.1(@types/react@18.3.14)(react@19.0.0)': dependencies: react: 19.0.0 @@ -7025,6 +7098,32 @@ snapshots: '@types/react': 18.3.14 '@types/react-dom': 18.3.2 + '@radix-ui/react-menu@2.1.4(@types/react-dom@18.3.2)(@types/react@18.3.14)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': + dependencies: + '@radix-ui/primitive': 1.1.1 + '@radix-ui/react-collection': 1.1.1(@types/react-dom@18.3.2)(@types/react@18.3.14)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-compose-refs': 1.1.1(@types/react@18.3.14)(react@19.0.0) + '@radix-ui/react-context': 1.1.1(@types/react@18.3.14)(react@19.0.0) + '@radix-ui/react-direction': 1.1.0(@types/react@18.3.14)(react@19.0.0) + '@radix-ui/react-dismissable-layer': 1.1.3(@types/react-dom@18.3.2)(@types/react@18.3.14)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-focus-guards': 1.1.1(@types/react@18.3.14)(react@19.0.0) + '@radix-ui/react-focus-scope': 1.1.1(@types/react-dom@18.3.2)(@types/react@18.3.14)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-id': 1.1.0(@types/react@18.3.14)(react@19.0.0) + '@radix-ui/react-popper': 1.2.1(@types/react-dom@18.3.2)(@types/react@18.3.14)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-portal': 1.1.3(@types/react-dom@18.3.2)(@types/react@18.3.14)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-presence': 1.1.2(@types/react-dom@18.3.2)(@types/react@18.3.14)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-primitive': 2.0.1(@types/react-dom@18.3.2)(@types/react@18.3.14)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-roving-focus': 1.1.1(@types/react-dom@18.3.2)(@types/react@18.3.14)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-slot': 1.1.1(@types/react@18.3.14)(react@19.0.0) + '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@18.3.14)(react@19.0.0) + aria-hidden: 1.2.4 + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + react-remove-scroll: 2.6.2(@types/react@18.3.14)(react@19.0.0) + optionalDependencies: + '@types/react': 18.3.14 + '@types/react-dom': 18.3.2 + '@radix-ui/react-popper@1.2.1(@types/react-dom@18.3.2)(@types/react@18.3.14)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': dependencies: '@floating-ui/react-dom': 2.1.2(react-dom@19.0.0(react@19.0.0))(react@19.0.0) @@ -7136,6 +7235,23 @@ snapshots: '@types/react': 18.3.14 '@types/react-dom': 18.3.2 + '@radix-ui/react-roving-focus@1.1.1(@types/react-dom@18.3.2)(@types/react@18.3.14)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': + dependencies: + '@radix-ui/primitive': 1.1.1 + '@radix-ui/react-collection': 1.1.1(@types/react-dom@18.3.2)(@types/react@18.3.14)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-compose-refs': 1.1.1(@types/react@18.3.14)(react@19.0.0) + '@radix-ui/react-context': 1.1.1(@types/react@18.3.14)(react@19.0.0) + '@radix-ui/react-direction': 1.1.0(@types/react@18.3.14)(react@19.0.0) + '@radix-ui/react-id': 1.1.0(@types/react@18.3.14)(react@19.0.0) + '@radix-ui/react-primitive': 2.0.1(@types/react-dom@18.3.2)(@types/react@18.3.14)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-use-callback-ref': 1.1.0(@types/react@18.3.14)(react@19.0.0) + '@radix-ui/react-use-controllable-state': 1.1.0(@types/react@18.3.14)(react@19.0.0) + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + optionalDependencies: + '@types/react': 18.3.14 + '@types/react-dom': 18.3.2 + '@radix-ui/react-select@2.1.4(@types/react-dom@18.3.2)(@types/react@18.3.14)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': dependencies: '@radix-ui/number': 1.1.0 @@ -7194,6 +7310,22 @@ snapshots: '@types/react': 18.3.14 '@types/react-dom': 18.3.2 + '@radix-ui/react-tabs@1.1.2(@types/react-dom@18.3.2)(@types/react@18.3.14)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': + dependencies: + '@radix-ui/primitive': 1.1.1 + '@radix-ui/react-context': 1.1.1(@types/react@18.3.14)(react@19.0.0) + '@radix-ui/react-direction': 1.1.0(@types/react@18.3.14)(react@19.0.0) + '@radix-ui/react-id': 1.1.0(@types/react@18.3.14)(react@19.0.0) + '@radix-ui/react-presence': 1.1.2(@types/react-dom@18.3.2)(@types/react@18.3.14)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-primitive': 2.0.1(@types/react-dom@18.3.2)(@types/react@18.3.14)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-roving-focus': 1.1.1(@types/react-dom@18.3.2)(@types/react@18.3.14)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@radix-ui/react-use-controllable-state': 1.1.0(@types/react@18.3.14)(react@19.0.0) + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + optionalDependencies: + '@types/react': 18.3.14 + '@types/react-dom': 18.3.2 + '@radix-ui/react-toast@1.2.2(@types/react-dom@18.3.2)(@types/react@18.3.14)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': dependencies: '@radix-ui/primitive': 1.1.0 diff --git a/types.d.ts b/types.d.ts index c3d9fe4..73b165b 100644 --- a/types.d.ts +++ b/types.d.ts @@ -13,6 +13,7 @@ declare global { declare module "next-auth" { interface User { roles?: { name: string }[] + username?: string | null } interface Session {