proper user/session creation and auth integration into UI
This commit is contained in:
56
drizzle/0000_loose_catseye.sql
Normal file
56
drizzle/0000_loose_catseye.sql
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
-- Current sql file was generated after introspecting the database
|
||||||
|
-- If you want to run this migration please uncomment this code before executing migrations
|
||||||
|
/*
|
||||||
|
CREATE TABLE "session" (
|
||||||
|
"sessionToken" text PRIMARY KEY NOT NULL,
|
||||||
|
"userId" text NOT NULL,
|
||||||
|
"expires" timestamp NOT NULL
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE "user" (
|
||||||
|
"id" text PRIMARY KEY NOT NULL,
|
||||||
|
"name" text,
|
||||||
|
"email" text NOT NULL,
|
||||||
|
"emailVerified" timestamp,
|
||||||
|
"image" text
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE "verificationToken" (
|
||||||
|
"identifier" text NOT NULL,
|
||||||
|
"token" text NOT NULL,
|
||||||
|
"expires" timestamp NOT NULL,
|
||||||
|
CONSTRAINT "verificationToken_identifier_token_pk" PRIMARY KEY("identifier","token")
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE "authenticator" (
|
||||||
|
"credentialID" text NOT NULL,
|
||||||
|
"userId" text NOT NULL,
|
||||||
|
"providerAccountId" text NOT NULL,
|
||||||
|
"credentialPublicKey" text NOT NULL,
|
||||||
|
"counter" integer NOT NULL,
|
||||||
|
"credentialDeviceType" text NOT NULL,
|
||||||
|
"credentialBackedUp" boolean NOT NULL,
|
||||||
|
"transports" text,
|
||||||
|
CONSTRAINT "authenticator_userId_credentialID_pk" PRIMARY KEY("credentialID","userId"),
|
||||||
|
CONSTRAINT "authenticator_credentialID_unique" UNIQUE("credentialID")
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
CREATE TABLE "account" (
|
||||||
|
"userId" text NOT NULL,
|
||||||
|
"type" text NOT NULL,
|
||||||
|
"provider" text NOT NULL,
|
||||||
|
"providerAccountId" text NOT NULL,
|
||||||
|
"refresh_token" text,
|
||||||
|
"access_token" text,
|
||||||
|
"expires_at" text,
|
||||||
|
"token_type" text,
|
||||||
|
"scope" text,
|
||||||
|
"id_token" text,
|
||||||
|
"session_state" text,
|
||||||
|
CONSTRAINT "account_provider_providerAccountId_pk" PRIMARY KEY("provider","providerAccountId")
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
ALTER TABLE "session" ADD CONSTRAINT "session_userId_user_id_fk" FOREIGN KEY ("userId") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||||
|
ALTER TABLE "authenticator" ADD CONSTRAINT "authenticator_userId_user_id_fk" FOREIGN KEY ("userId") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||||
|
ALTER TABLE "account" ADD CONSTRAINT "account_userId_user_id_fk" FOREIGN KEY ("userId") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;
|
||||||
|
*/
|
||||||
344
drizzle/meta/0000_snapshot.json
Normal file
344
drizzle/meta/0000_snapshot.json
Normal file
@@ -0,0 +1,344 @@
|
|||||||
|
{
|
||||||
|
"id": "00000000-0000-0000-0000-000000000000",
|
||||||
|
"prevId": "",
|
||||||
|
"version": "7",
|
||||||
|
"dialect": "postgresql",
|
||||||
|
"tables": {
|
||||||
|
"public.session": {
|
||||||
|
"name": "session",
|
||||||
|
"schema": "",
|
||||||
|
"columns": {
|
||||||
|
"sessionToken": {
|
||||||
|
"name": "sessionToken",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"userId": {
|
||||||
|
"name": "userId",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"expires": {
|
||||||
|
"name": "expires",
|
||||||
|
"type": "timestamp",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {
|
||||||
|
"session_userId_user_id_fk": {
|
||||||
|
"name": "session_userId_user_id_fk",
|
||||||
|
"tableFrom": "session",
|
||||||
|
"tableTo": "user",
|
||||||
|
"schemaTo": "public",
|
||||||
|
"columnsFrom": [
|
||||||
|
"userId"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "cascade",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"checkConstraints": {},
|
||||||
|
"policies": {},
|
||||||
|
"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": false
|
||||||
|
},
|
||||||
|
"email": {
|
||||||
|
"name": "email",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"emailVerified": {
|
||||||
|
"name": "emailVerified",
|
||||||
|
"type": "timestamp",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"image": {
|
||||||
|
"name": "image",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"checkConstraints": {},
|
||||||
|
"policies": {},
|
||||||
|
"isRLSEnabled": false
|
||||||
|
},
|
||||||
|
"public.verificationToken": {
|
||||||
|
"name": "verificationToken",
|
||||||
|
"schema": "",
|
||||||
|
"columns": {
|
||||||
|
"identifier": {
|
||||||
|
"name": "identifier",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"token": {
|
||||||
|
"name": "token",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"expires": {
|
||||||
|
"name": "expires",
|
||||||
|
"type": "timestamp",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {},
|
||||||
|
"compositePrimaryKeys": {
|
||||||
|
"verificationToken_identifier_token_pk": {
|
||||||
|
"name": "verificationToken_identifier_token_pk",
|
||||||
|
"columns": [
|
||||||
|
"identifier",
|
||||||
|
"token"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"checkConstraints": {},
|
||||||
|
"policies": {},
|
||||||
|
"isRLSEnabled": false
|
||||||
|
},
|
||||||
|
"public.authenticator": {
|
||||||
|
"name": "authenticator",
|
||||||
|
"schema": "",
|
||||||
|
"columns": {
|
||||||
|
"credentialID": {
|
||||||
|
"name": "credentialID",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"userId": {
|
||||||
|
"name": "userId",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"providerAccountId": {
|
||||||
|
"name": "providerAccountId",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"credentialPublicKey": {
|
||||||
|
"name": "credentialPublicKey",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"counter": {
|
||||||
|
"name": "counter",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"credentialDeviceType": {
|
||||||
|
"name": "credentialDeviceType",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"credentialBackedUp": {
|
||||||
|
"name": "credentialBackedUp",
|
||||||
|
"type": "boolean",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"transports": {
|
||||||
|
"name": "transports",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {
|
||||||
|
"authenticator_userId_user_id_fk": {
|
||||||
|
"name": "authenticator_userId_user_id_fk",
|
||||||
|
"tableFrom": "authenticator",
|
||||||
|
"tableTo": "user",
|
||||||
|
"schemaTo": "public",
|
||||||
|
"columnsFrom": [
|
||||||
|
"userId"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "cascade",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compositePrimaryKeys": {
|
||||||
|
"authenticator_userId_credentialID_pk": {
|
||||||
|
"name": "authenticator_userId_credentialID_pk",
|
||||||
|
"columns": [
|
||||||
|
"credentialID",
|
||||||
|
"userId"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"uniqueConstraints": {
|
||||||
|
"authenticator_credentialID_unique": {
|
||||||
|
"columns": [
|
||||||
|
"credentialID"
|
||||||
|
],
|
||||||
|
"nullsNotDistinct": false,
|
||||||
|
"name": "authenticator_credentialID_unique"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"checkConstraints": {},
|
||||||
|
"policies": {},
|
||||||
|
"isRLSEnabled": false
|
||||||
|
},
|
||||||
|
"public.account": {
|
||||||
|
"name": "account",
|
||||||
|
"schema": "",
|
||||||
|
"columns": {
|
||||||
|
"userId": {
|
||||||
|
"name": "userId",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"type": {
|
||||||
|
"name": "type",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"provider": {
|
||||||
|
"name": "provider",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"providerAccountId": {
|
||||||
|
"name": "providerAccountId",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"refresh_token": {
|
||||||
|
"name": "refresh_token",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"access_token": {
|
||||||
|
"name": "access_token",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"expires_at": {
|
||||||
|
"name": "expires_at",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"token_type": {
|
||||||
|
"name": "token_type",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"scope": {
|
||||||
|
"name": "scope",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"id_token": {
|
||||||
|
"name": "id_token",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"session_state": {
|
||||||
|
"name": "session_state",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {
|
||||||
|
"account_userId_user_id_fk": {
|
||||||
|
"name": "account_userId_user_id_fk",
|
||||||
|
"tableFrom": "account",
|
||||||
|
"tableTo": "user",
|
||||||
|
"schemaTo": "public",
|
||||||
|
"columnsFrom": [
|
||||||
|
"userId"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "cascade",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compositePrimaryKeys": {
|
||||||
|
"account_provider_providerAccountId_pk": {
|
||||||
|
"name": "account_provider_providerAccountId_pk",
|
||||||
|
"columns": [
|
||||||
|
"provider",
|
||||||
|
"providerAccountId"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"checkConstraints": {},
|
||||||
|
"policies": {},
|
||||||
|
"isRLSEnabled": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"enums": {},
|
||||||
|
"schemas": {},
|
||||||
|
"sequences": {},
|
||||||
|
"roles": {},
|
||||||
|
"policies": {},
|
||||||
|
"views": {},
|
||||||
|
"_meta": {
|
||||||
|
"schemas": {},
|
||||||
|
"tables": {},
|
||||||
|
"columns": {}
|
||||||
|
},
|
||||||
|
"internal": {
|
||||||
|
"tables": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
13
drizzle/meta/_journal.json
Normal file
13
drizzle/meta/_journal.json
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"version": "7",
|
||||||
|
"dialect": "postgresql",
|
||||||
|
"entries": [
|
||||||
|
{
|
||||||
|
"idx": 0,
|
||||||
|
"version": "7",
|
||||||
|
"when": 1755586325384,
|
||||||
|
"tag": "0000_loose_catseye",
|
||||||
|
"breakpoints": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
29
drizzle/relations.ts
Normal file
29
drizzle/relations.ts
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
import { relations } from "drizzle-orm/relations";
|
||||||
|
import { user, session, authenticator, account } from "./schema";
|
||||||
|
|
||||||
|
export const sessionRelations = relations(session, ({one}) => ({
|
||||||
|
user: one(user, {
|
||||||
|
fields: [session.userId],
|
||||||
|
references: [user.id]
|
||||||
|
}),
|
||||||
|
}));
|
||||||
|
|
||||||
|
export const userRelations = relations(user, ({many}) => ({
|
||||||
|
sessions: many(session),
|
||||||
|
authenticators: many(authenticator),
|
||||||
|
accounts: many(account),
|
||||||
|
}));
|
||||||
|
|
||||||
|
export const authenticatorRelations = relations(authenticator, ({one}) => ({
|
||||||
|
user: one(user, {
|
||||||
|
fields: [authenticator.userId],
|
||||||
|
references: [user.id]
|
||||||
|
}),
|
||||||
|
}));
|
||||||
|
|
||||||
|
export const accountRelations = relations(account, ({one}) => ({
|
||||||
|
user: one(user, {
|
||||||
|
fields: [account.userId],
|
||||||
|
references: [user.id]
|
||||||
|
}),
|
||||||
|
}));
|
||||||
72
drizzle/schema.ts
Normal file
72
drizzle/schema.ts
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
import { pgTable, foreignKey, text, timestamp, primaryKey, unique, integer, boolean } from "drizzle-orm/pg-core"
|
||||||
|
import { sql } from "drizzle-orm"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
export const session = pgTable("session", {
|
||||||
|
sessionToken: text().primaryKey().notNull(),
|
||||||
|
userId: text().notNull(),
|
||||||
|
expires: timestamp({ mode: 'string' }).notNull(),
|
||||||
|
}, (table) => [
|
||||||
|
foreignKey({
|
||||||
|
columns: [table.userId],
|
||||||
|
foreignColumns: [user.id],
|
||||||
|
name: "session_userId_user_id_fk"
|
||||||
|
}).onDelete("cascade"),
|
||||||
|
]);
|
||||||
|
|
||||||
|
export const user = pgTable("user", {
|
||||||
|
id: text().primaryKey().notNull(),
|
||||||
|
name: text(),
|
||||||
|
email: text().notNull(),
|
||||||
|
emailVerified: timestamp({ mode: 'string' }),
|
||||||
|
image: text(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const verificationToken = pgTable("verificationToken", {
|
||||||
|
identifier: text().notNull(),
|
||||||
|
token: text().notNull(),
|
||||||
|
expires: timestamp({ mode: 'string' }).notNull(),
|
||||||
|
}, (table) => [
|
||||||
|
primaryKey({ columns: [table.identifier, table.token], name: "verificationToken_identifier_token_pk"}),
|
||||||
|
]);
|
||||||
|
|
||||||
|
export const authenticator = pgTable("authenticator", {
|
||||||
|
credentialId: text().notNull(),
|
||||||
|
userId: text().notNull(),
|
||||||
|
providerAccountId: text().notNull(),
|
||||||
|
credentialPublicKey: text().notNull(),
|
||||||
|
counter: integer().notNull(),
|
||||||
|
credentialDeviceType: text().notNull(),
|
||||||
|
credentialBackedUp: boolean().notNull(),
|
||||||
|
transports: text(),
|
||||||
|
}, (table) => [
|
||||||
|
foreignKey({
|
||||||
|
columns: [table.userId],
|
||||||
|
foreignColumns: [user.id],
|
||||||
|
name: "authenticator_userId_user_id_fk"
|
||||||
|
}).onDelete("cascade"),
|
||||||
|
primaryKey({ columns: [table.credentialId, table.userId], name: "authenticator_userId_credentialID_pk"}),
|
||||||
|
unique("authenticator_credentialID_unique").on(table.credentialId),
|
||||||
|
]);
|
||||||
|
|
||||||
|
export const account = pgTable("account", {
|
||||||
|
userId: text().notNull(),
|
||||||
|
type: text().notNull(),
|
||||||
|
provider: text().notNull(),
|
||||||
|
providerAccountId: text().notNull(),
|
||||||
|
refreshToken: text("refresh_token"),
|
||||||
|
accessToken: text("access_token"),
|
||||||
|
expiresAt: text("expires_at"),
|
||||||
|
tokenType: text("token_type"),
|
||||||
|
scope: text(),
|
||||||
|
idToken: text("id_token"),
|
||||||
|
sessionState: text("session_state"),
|
||||||
|
}, (table) => [
|
||||||
|
foreignKey({
|
||||||
|
columns: [table.userId],
|
||||||
|
foreignColumns: [user.id],
|
||||||
|
name: "account_userId_user_id_fk"
|
||||||
|
}).onDelete("cascade"),
|
||||||
|
primaryKey({ columns: [table.provider, table.providerAccountId], name: "account_provider_providerAccountId_pk"}),
|
||||||
|
]);
|
||||||
@@ -1,6 +1,16 @@
|
|||||||
import { NextResponse } from "next/server";
|
import { NextResponse } from "next/server";
|
||||||
|
import { auth } from "@/auth";
|
||||||
|
|
||||||
export async function POST(request: Request) {
|
export async function POST(request: Request) {
|
||||||
|
const session = await auth();
|
||||||
|
|
||||||
|
if (!session?.user) {
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: "Authentication required" },
|
||||||
|
{ status: 401 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const { prompt } = await request.json();
|
const { prompt } = await request.json();
|
||||||
|
|
||||||
const systemPrompt = `
|
const systemPrompt = `
|
||||||
|
|||||||
@@ -1,37 +1,43 @@
|
|||||||
import { signIn, signOut } from "@/auth"
|
"use client"
|
||||||
import { auth } from "@/auth"
|
|
||||||
import { Button } from "@/components/ui/button"
|
|
||||||
|
|
||||||
export default async function SignIn() {
|
import { signOut, useSession } from "next-auth/react"
|
||||||
const session = await auth()
|
import { Button } from "@/components/ui/button"
|
||||||
|
import { useRouter } from "next/navigation"
|
||||||
|
|
||||||
|
export default function SignIn() {
|
||||||
|
const { data: session, status } = useSession()
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
|
const handleSignOut = async () => {
|
||||||
|
await signOut({ redirect: false })
|
||||||
|
router.push("/")
|
||||||
|
router.refresh()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (status === "loading") {
|
||||||
|
return <div className="h-8 w-16 bg-muted animate-pulse rounded"></div>
|
||||||
|
}
|
||||||
|
|
||||||
if (session?.user) {
|
if (session?.user) {
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center gap-4">
|
<div className="flex items-center gap-4">
|
||||||
<form
|
<span className="text-sm text-muted-foreground hidden sm:inline">
|
||||||
action={async () => {
|
{session.user.name || session.user.email}
|
||||||
"use server"
|
</span>
|
||||||
await signOut()
|
<Button onClick={handleSignOut} variant="ghost" size="sm">
|
||||||
}}
|
Sign Out
|
||||||
>
|
</Button>
|
||||||
<Button type="submit" variant="secondary" >
|
|
||||||
Sign Out
|
|
||||||
</Button>
|
|
||||||
</form>
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form
|
<Button
|
||||||
action={async () => {
|
onClick={() => router.push("/signin")}
|
||||||
"use server"
|
variant="outline"
|
||||||
await signIn("authentik")
|
size="sm"
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<Button type="submit" variant="default" >
|
Sign In
|
||||||
Sign In
|
</Button>
|
||||||
</Button>
|
|
||||||
</form>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -143,6 +143,13 @@ export default function HomePage() {
|
|||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify({ prompt: aiPrompt })
|
body: JSON.stringify({ prompt: aiPrompt })
|
||||||
})
|
})
|
||||||
|
|
||||||
|
if (res.status === 401) {
|
||||||
|
alert('Please sign in to use AI features.')
|
||||||
|
setAiLoading(false)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
const data = await res.json()
|
const data = await res.json()
|
||||||
|
|
||||||
if (Array.isArray(data) && data.length > 0) {
|
if (Array.isArray(data) && data.length > 0) {
|
||||||
@@ -225,25 +232,32 @@ export default function HomePage() {
|
|||||||
<div className='max-w-fit m-auto'> Drag & Drop *.ics here</div>
|
<div className='max-w-fit m-auto'> Drag & Drop *.ics here</div>
|
||||||
</div>
|
</div>
|
||||||
{/* AI Toolbar */}
|
{/* AI Toolbar */}
|
||||||
<div className="flex flex-row gap-4 mb-4 items-start">
|
{session?.user ? (
|
||||||
{session?.user && (
|
<div className="flex flex-row gap-4 mb-4 items-start">
|
||||||
<>
|
<div className='w-full'>
|
||||||
<div className='w-full'>
|
<Textarea
|
||||||
<Textarea
|
className="wrap-anywhere min-h-12"
|
||||||
className="wrap-anywhere min-h-12"
|
placeholder='Describe event for AI to create'
|
||||||
placeholder='Describe event for AI to create'
|
value={aiPrompt}
|
||||||
value={aiPrompt}
|
onChange={e => setAiPrompt(e.target.value)}
|
||||||
onChange={e => setAiPrompt(e.target.value)}
|
/>
|
||||||
/>
|
</div>
|
||||||
</div>
|
<div className='flex flex-row gap-2 pt-1.5'>
|
||||||
<div className='flex flex-row gap-2 pt-1.5'>
|
<Button onClick={handleAiCreate} disabled={aiLoading}>
|
||||||
<Button onClick={handleAiCreate} disabled={aiLoading}>
|
{aiLoading ? 'Thinking...' : 'AI Create'}
|
||||||
{aiLoading ? 'Thinking...' : 'AI Create'}
|
</Button>
|
||||||
</Button>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</>
|
) : (
|
||||||
)}
|
<div className="mb-4 p-4 border border-dashed rounded-lg text-center">
|
||||||
</div>
|
<div className="text-sm text-muted-foreground mb-2">
|
||||||
|
Sign in to unlock AI-powered calendar features
|
||||||
|
</div>
|
||||||
|
<Button variant="outline" size="sm" asChild>
|
||||||
|
<a href="/signin">Sign In</a>
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* Summary Panel */}
|
{/* Summary Panel */}
|
||||||
{
|
{
|
||||||
|
|||||||
45
src/app/signin/page.tsx
Normal file
45
src/app/signin/page.tsx
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
import { signIn, auth } from "@/auth"
|
||||||
|
import { Button } from "@/components/ui/button"
|
||||||
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
|
||||||
|
import { redirect } from "next/navigation"
|
||||||
|
import Link from "next/link"
|
||||||
|
|
||||||
|
export default async function SignInPage() {
|
||||||
|
const session = await auth()
|
||||||
|
|
||||||
|
// If already signed in, redirect to home
|
||||||
|
if (session?.user) {
|
||||||
|
redirect("/")
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="min-h-screen flex items-center justify-center bg-background p-4">
|
||||||
|
<Card className="w-full max-w-md">
|
||||||
|
<CardHeader className="text-center">
|
||||||
|
<CardTitle className="text-2xl font-bold">Welcome</CardTitle>
|
||||||
|
<CardDescription>
|
||||||
|
Sign in to access AI-powered calendar features
|
||||||
|
</CardDescription>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="space-y-4">
|
||||||
|
<form
|
||||||
|
action={async () => {
|
||||||
|
"use server"
|
||||||
|
await signIn("authentik", { redirectTo: "/" })
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Button type="submit" className="w-full" size="lg">
|
||||||
|
Continue with Authentik
|
||||||
|
</Button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<div className="text-center">
|
||||||
|
<Link href="/" className="text-sm text-muted-foreground hover:underline">
|
||||||
|
Continue without signing in
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
49
src/app/signout/page.tsx
Normal file
49
src/app/signout/page.tsx
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
import { signOut, auth } from "@/auth"
|
||||||
|
import { Button } from "@/components/ui/button"
|
||||||
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
|
||||||
|
import { redirect } from "next/navigation"
|
||||||
|
import Link from "next/link"
|
||||||
|
|
||||||
|
export default async function SignOutPage() {
|
||||||
|
const session = await auth()
|
||||||
|
|
||||||
|
if (!session) {
|
||||||
|
redirect("/")
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="min-h-screen flex items-center justify-center bg-background p-4">
|
||||||
|
<Card className="w-full max-w-md">
|
||||||
|
<CardHeader className="text-center">
|
||||||
|
<CardTitle className="text-2xl font-bold">Sign Out</CardTitle>
|
||||||
|
<CardDescription>
|
||||||
|
Are you sure you want to sign out?
|
||||||
|
</CardDescription>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="space-y-4">
|
||||||
|
<div className="text-center p-3 bg-muted rounded-lg">
|
||||||
|
<div className="text-sm text-muted-foreground">Currently signed in as</div>
|
||||||
|
<div className="font-medium">{session.user?.name || session.user?.email}</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-2 gap-3">
|
||||||
|
<form
|
||||||
|
action={async () => {
|
||||||
|
"use server"
|
||||||
|
await signOut({ redirectTo: "/" })
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Button type="submit" variant="destructive" className="w-full">
|
||||||
|
Sign Out
|
||||||
|
</Button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<Button variant="outline" asChild>
|
||||||
|
<Link href="/">Cancel</Link>
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -1,36 +1,55 @@
|
|||||||
import { pgTable, text, timestamp, uuid } from 'drizzle-orm/pg-core';
|
import { pgTable, text, timestamp, integer, boolean, primaryKey } from 'drizzle-orm/pg-core';
|
||||||
|
|
||||||
export const users = pgTable('users', {
|
export const users = pgTable('user', {
|
||||||
id: uuid('id').primaryKey().defaultRandom(),
|
id: text('id').primaryKey(),
|
||||||
email: text('email').notNull().unique(),
|
|
||||||
name: text('name'),
|
name: text('name'),
|
||||||
|
email: text('email').notNull(),
|
||||||
|
emailVerified: timestamp('emailVerified', { mode: 'string' }),
|
||||||
image: text('image'),
|
image: text('image'),
|
||||||
createdAt: timestamp('created_at').defaultNow().notNull(),
|
|
||||||
updatedAt: timestamp('updated_at').defaultNow().notNull(),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export const accounts = pgTable('accounts', {
|
export const accounts = pgTable('account', {
|
||||||
id: uuid('id').primaryKey().defaultRandom(),
|
userId: text('userId').notNull().references(() => users.id, { onDelete: 'cascade' }),
|
||||||
userId: uuid('user_id').notNull().references(() => users.id, { onDelete: 'cascade' }),
|
|
||||||
type: text('type').notNull(),
|
type: text('type').notNull(),
|
||||||
provider: text('provider').notNull(),
|
provider: text('provider').notNull(),
|
||||||
providerAccountId: text('provider_account_id').notNull(),
|
providerAccountId: text('providerAccountId').notNull(),
|
||||||
refreshToken: text('refresh_token'),
|
refresh_token: text('refresh_token'),
|
||||||
accessToken: text('access_token'),
|
access_token: text('access_token'),
|
||||||
expiresAt: timestamp('expires_at'),
|
expires_at: text('expires_at'),
|
||||||
tokenType: text('token_type'),
|
token_type: text('token_type'),
|
||||||
scope: text('scope'),
|
scope: text('scope'),
|
||||||
idToken: text('id_token'),
|
id_token: text('id_token'),
|
||||||
sessionState: text('session_state'),
|
session_state: text('session_state'),
|
||||||
createdAt: timestamp('created_at').defaultNow().notNull(),
|
}, (account) => ({
|
||||||
updatedAt: timestamp('updated_at').defaultNow().notNull(),
|
compoundKey: primaryKey({ columns: [account.provider, account.providerAccountId] })
|
||||||
|
}));
|
||||||
|
|
||||||
|
export const sessions = pgTable('session', {
|
||||||
|
sessionToken: text().primaryKey().notNull(),
|
||||||
|
userId: text().notNull().references(() => users.id, { onDelete: 'cascade' }),
|
||||||
|
expires: timestamp({ mode: 'string' }).notNull(),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const sessions = pgTable('sessions', {
|
export const verificationTokens = pgTable('verificationToken', {
|
||||||
id: uuid('id').primaryKey().defaultRandom(),
|
identifier: text('identifier').notNull(),
|
||||||
sessionToken: text('session_token').notNull().unique(),
|
token: text('token').notNull(),
|
||||||
userId: uuid('user_id').notNull().references(() => users.id, { onDelete: 'cascade' }),
|
expires: timestamp('expires', { mode: 'string' }).notNull(),
|
||||||
expires: timestamp('expires').notNull(),
|
}, (vt) => ({
|
||||||
createdAt: timestamp('created_at').defaultNow().notNull(),
|
compoundKey: primaryKey({ columns: [vt.identifier, vt.token] })
|
||||||
updatedAt: timestamp('updated_at').defaultNow().notNull(),
|
}));
|
||||||
});
|
|
||||||
|
export const authenticators = pgTable('authenticator', {
|
||||||
|
credentialID: text('credentialID').notNull().unique(),
|
||||||
|
userId: text('userId').notNull().references(() => users.id, { onDelete: 'cascade' }),
|
||||||
|
providerAccountId: text('providerAccountId').notNull(),
|
||||||
|
credentialPublicKey: text('credentialPublicKey').notNull(),
|
||||||
|
counter: integer('counter').notNull(),
|
||||||
|
credentialDeviceType: text('credentialDeviceType').notNull(),
|
||||||
|
credentialBackedUp: boolean('credentialBackedUp').notNull(),
|
||||||
|
transports: text('transports'),
|
||||||
|
}, (authenticator) => ({
|
||||||
|
compositePK: primaryKey({
|
||||||
|
columns: [authenticator.credentialID, authenticator.userId],
|
||||||
|
name: "authenticator_userId_credentialID_pk"
|
||||||
|
})
|
||||||
|
}));
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
import { auth } from "@/auth"
|
|
||||||
|
|
||||||
export default auth((req) => {
|
|
||||||
const { nextUrl } = req
|
|
||||||
const isLoggedIn = !!req.auth
|
|
||||||
|
|
||||||
// Protect dashboard routes
|
|
||||||
// if (nextUrl.pathname.startsWith('/api') && !isLoggedIn) {
|
|
||||||
// return Response.redirect(new URL('/signin', nextUrl))
|
|
||||||
// }
|
|
||||||
|
|
||||||
// Redirect logged-in users from sign-in page
|
|
||||||
if (nextUrl.pathname.startsWith('/signin') && isLoggedIn) {
|
|
||||||
return Response.redirect(new URL('/', nextUrl))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
export const config = {
|
|
||||||
matcher: [
|
|
||||||
// Skip Next.js internals and all static files
|
|
||||||
'/((?!_next|[^?]*\\.(?:html?|css|js(?!on)|jpe?g|webp|png|gif|svg|ttf|woff2?|ico|csv|docx?|xlsx?|zip|webmanifest)).*)',
|
|
||||||
// Always run for API routes
|
|
||||||
'/(api|trpc)(.*)',
|
|
||||||
],
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user