added google auth provider
All checks were successful
Gitea Actions Demo / Explore-Gitea-Actions (push) Successful in 0s

This commit is contained in:
shrt 2025-03-16 14:34:32 +01:00
parent c6cfecdb0f
commit b171956105
15 changed files with 600 additions and 28 deletions

View File

@ -1,5 +1,5 @@
{ {
"name": "wiki-antifa", "name": "logipedia",
"version": "0.1.0", "version": "0.1.0",
"private": true, "private": true,
"type": "module", "type": "module",
@ -46,6 +46,7 @@
"@trpc/client": "^11.0.0-rc.446", "@trpc/client": "^11.0.0-rc.446",
"@trpc/react-query": "^11.0.0-rc.446", "@trpc/react-query": "^11.0.0-rc.446",
"@trpc/server": "^11.0.0-rc.446", "@trpc/server": "^11.0.0-rc.446",
"argon2": "^0.41.1",
"cheerio": "^1.0.0", "cheerio": "^1.0.0",
"class-variance-authority": "^0.7.1", "class-variance-authority": "^0.7.1",
"clsx": "^2.1.1", "clsx": "^2.1.1",

31
pnpm-lock.yaml generated
View File

@ -86,6 +86,9 @@ importers:
'@trpc/server': '@trpc/server':
specifier: ^11.0.0-rc.446 specifier: ^11.0.0-rc.446
version: 11.0.0-rc.824(typescript@5.8.2) version: 11.0.0-rc.824(typescript@5.8.2)
argon2:
specifier: ^0.41.1
version: 0.41.1
cheerio: cheerio:
specifier: ^1.0.0 specifier: ^1.0.0
version: 1.0.0 version: 1.0.0
@ -982,6 +985,10 @@ packages:
'@paralleldrive/cuid2@2.2.2': '@paralleldrive/cuid2@2.2.2':
resolution: {integrity: sha512-ZOBkgDwEdoYVlSeRbYYXs0S9MejQofiVYoTbKzy/6GQa39/q5tQU2IX46+shYnUkpEl3wc+J6wRlar7r2EK2xA==} resolution: {integrity: sha512-ZOBkgDwEdoYVlSeRbYYXs0S9MejQofiVYoTbKzy/6GQa39/q5tQU2IX46+shYnUkpEl3wc+J6wRlar7r2EK2xA==}
'@phc/format@1.0.0':
resolution: {integrity: sha512-m7X9U6BG2+J+R1lSOdCiITLLrxm+cWlNI3HUFA92oLO77ObGNzaKdh8pMLqdZcshtkKuV84olNNXDfMc4FezBQ==}
engines: {node: '>=10'}
'@pkgjs/parseargs@0.11.0': '@pkgjs/parseargs@0.11.0':
resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==}
engines: {node: '>=14'} engines: {node: '>=14'}
@ -2051,6 +2058,10 @@ packages:
arg@5.0.2: arg@5.0.2:
resolution: {integrity: sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==} resolution: {integrity: sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==}
argon2@0.41.1:
resolution: {integrity: sha512-dqCW8kJXke8Ik+McUcMDltrbuAWETPyU6iq+4AhxqKphWi7pChB/Zgd/Tp/o8xRLbg8ksMj46F/vph9wnxpTzQ==}
engines: {node: '>=16.17.0'}
argparse@2.0.1: argparse@2.0.1:
resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==}
@ -3356,6 +3367,14 @@ packages:
sass: sass:
optional: true optional: true
node-addon-api@8.3.1:
resolution: {integrity: sha512-lytcDEdxKjGJPTLEfW4mYMigRezMlyJY8W4wxJK8zE533Jlb8L8dRuObJFWg2P+AuOIxoCgKF+2Oq4d4Zd0OUA==}
engines: {node: ^18 || ^20 || >= 21}
node-gyp-build@4.8.4:
resolution: {integrity: sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==}
hasBin: true
normalize-path@3.0.0: normalize-path@3.0.0:
resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==}
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}
@ -4886,6 +4905,8 @@ snapshots:
dependencies: dependencies:
'@noble/hashes': 1.7.1 '@noble/hashes': 1.7.1
'@phc/format@1.0.0': {}
'@pkgjs/parseargs@0.11.0': '@pkgjs/parseargs@0.11.0':
optional: true optional: true
@ -5963,6 +5984,12 @@ snapshots:
arg@5.0.2: {} arg@5.0.2: {}
argon2@0.41.1:
dependencies:
'@phc/format': 1.0.0
node-addon-api: 8.3.1
node-gyp-build: 4.8.4
argparse@2.0.1: {} argparse@2.0.1: {}
aria-hidden@1.2.4: aria-hidden@1.2.4:
@ -7598,6 +7625,10 @@ snapshots:
- '@babel/core' - '@babel/core'
- babel-plugin-macros - babel-plugin-macros
node-addon-api@8.3.1: {}
node-gyp-build@4.8.4: {}
normalize-path@3.0.0: {} normalize-path@3.0.0: {}
novel@1.0.2(@tiptap/extension-code-block@2.11.5(@tiptap/core@2.11.5(@tiptap/pm@2.11.5))(@tiptap/pm@2.11.5))(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(highlight.js@11.11.1)(lowlight@3.3.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1): novel@1.0.2(@tiptap/extension-code-block@2.11.5(@tiptap/core@2.11.5(@tiptap/pm@2.11.5))(@tiptap/pm@2.11.5))(@types/react-dom@18.3.5(@types/react@18.3.18))(@types/react@18.3.18)(highlight.js@11.11.1)(lowlight@3.3.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1):

1
public/placeholder.svg Normal file
View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="1200" height="1200" fill="none"><rect width="1200" height="1200" fill="#EAEAEA" rx="3"/><g opacity=".5"><g opacity=".5"><path fill="#FAFAFA" d="M600.709 736.5c-75.454 0-136.621-61.167-136.621-136.62 0-75.454 61.167-136.621 136.621-136.621 75.453 0 136.62 61.167 136.62 136.621 0 75.453-61.167 136.62-136.62 136.62Z"/><path stroke="#C9C9C9" stroke-width="2.418" d="M600.709 736.5c-75.454 0-136.621-61.167-136.621-136.62 0-75.454 61.167-136.621 136.621-136.621 75.453 0 136.62 61.167 136.62 136.621 0 75.453-61.167 136.62-136.62 136.62Z"/></g><path stroke="url(#a)" stroke-width="2.418" d="M0-1.209h553.581" transform="scale(1 -1) rotate(45 1163.11 91.165)"/><path stroke="url(#b)" stroke-width="2.418" d="M404.846 598.671h391.726"/><path stroke="url(#c)" stroke-width="2.418" d="M599.5 795.742V404.017"/><path stroke="url(#d)" stroke-width="2.418" d="m795.717 796.597-391.441-391.44"/><path fill="#fff" d="M600.709 656.704c-31.384 0-56.825-25.441-56.825-56.824 0-31.384 25.441-56.825 56.825-56.825 31.383 0 56.824 25.441 56.824 56.825 0 31.383-25.441 56.824-56.824 56.824Z"/><g clip-path="url(#e)"><path fill="#666" fill-rule="evenodd" d="M616.426 586.58h-31.434v16.176l3.553-3.554.531-.531h9.068l.074-.074 8.463-8.463h2.565l7.18 7.181V586.58Zm-15.715 14.654 3.698 3.699 1.283 1.282-2.565 2.565-1.282-1.283-5.2-5.199h-6.066l-5.514 5.514-.073.073v2.876a2.418 2.418 0 0 0 2.418 2.418h26.598a2.418 2.418 0 0 0 2.418-2.418v-8.317l-8.463-8.463-7.181 7.181-.071.072Zm-19.347 5.442v4.085a6.045 6.045 0 0 0 6.046 6.045h26.598a6.044 6.044 0 0 0 6.045-6.045v-7.108l1.356-1.355-1.282-1.283-.074-.073v-17.989h-38.689v23.43l-.146.146.146.147Z" clip-rule="evenodd"/></g><path stroke="#C9C9C9" stroke-width="2.418" d="M600.709 656.704c-31.384 0-56.825-25.441-56.825-56.824 0-31.384 25.441-56.825 56.825-56.825 31.383 0 56.824 25.441 56.824 56.825 0 31.383-25.441 56.824-56.824 56.824Z"/></g><defs><linearGradient id="a" x1="554.061" x2="-.48" y1=".083" y2=".087" gradientUnits="userSpaceOnUse"><stop stop-color="#C9C9C9" stop-opacity="0"/><stop offset=".208" stop-color="#C9C9C9"/><stop offset=".792" stop-color="#C9C9C9"/><stop offset="1" stop-color="#C9C9C9" stop-opacity="0"/></linearGradient><linearGradient id="b" x1="796.912" x2="404.507" y1="599.963" y2="599.965" gradientUnits="userSpaceOnUse"><stop stop-color="#C9C9C9" stop-opacity="0"/><stop offset=".208" stop-color="#C9C9C9"/><stop offset=".792" stop-color="#C9C9C9"/><stop offset="1" stop-color="#C9C9C9" stop-opacity="0"/></linearGradient><linearGradient id="c" x1="600.792" x2="600.794" y1="403.677" y2="796.082" gradientUnits="userSpaceOnUse"><stop stop-color="#C9C9C9" stop-opacity="0"/><stop offset=".208" stop-color="#C9C9C9"/><stop offset=".792" stop-color="#C9C9C9"/><stop offset="1" stop-color="#C9C9C9" stop-opacity="0"/></linearGradient><linearGradient id="d" x1="404.85" x2="796.972" y1="403.903" y2="796.02" gradientUnits="userSpaceOnUse"><stop stop-color="#C9C9C9" stop-opacity="0"/><stop offset=".208" stop-color="#C9C9C9"/><stop offset=".792" stop-color="#C9C9C9"/><stop offset="1" stop-color="#C9C9C9" stop-opacity="0"/></linearGradient><clipPath id="e"><path fill="#fff" d="M581.364 580.535h38.689v38.689h-38.689z"/></clipPath></defs></svg>

After

Width:  |  Height:  |  Size: 3.2 KiB

View File

@ -14,21 +14,21 @@ import {
} from "@/components/ui/form"; } from "@/components/ui/form";
import { Input } from "@/components/ui/input"; import { Input } from "@/components/ui/input";
import { z } from "zod"; import { z } from "zod";
import { userProfileSchema } from "@/lib/validation/zod/user"; import { userSchema } from "@/lib/validation/zod/user";
import { User } from "next-auth"; import { User } from "next-auth";
import { updateUserProfile } from "@/server/actions/user"; import { updateUserProfile } from "@/server/actions/user";
import { toast } from "sonner"; import { toast } from "sonner";
function UserForm({ server_user, cb }: { server_user: User; cb?: () => void }) { function UserForm({ server_user, cb }: { server_user: User; cb?: () => void }) {
const form = useForm<z.infer<typeof userProfileSchema>>({ const form = useForm<z.infer<typeof userSchema>>({
resolver: zodResolver(userProfileSchema), resolver: zodResolver(userSchema),
defaultValues: { defaultValues: {
name: server_user?.name ?? "", name: server_user?.name ?? "",
}, },
}); });
// 2. Define a submit handler. // 2. Define a submit handler.
async function onSubmit(values: z.infer<typeof userProfileSchema>) { async function onSubmit(values: z.infer<typeof userSchema>) {
// Do something with the form values. // Do something with the form values.
// ✅ This will be type-safe and validated. // ✅ This will be type-safe and validated.
const { success } = await updateUserProfile(values); const { success } = await updateUserProfile(values);

View File

@ -22,6 +22,7 @@ export type AppRoutes = {
// Auth routes // Auth routes
signin: string; signin: string;
signup: string;
signout: string; signout: string;
profile: string; profile: string;
}; };
@ -43,6 +44,7 @@ export const appRoutes: AppRoutes = {
// auth // auth
signin: "/api/auth/signin", signin: "/api/auth/signin",
signup: "/api/auth/signin",
signout: "/api/auth/signout", signout: "/api/auth/signout",
profile: "/me", profile: "/me",
}; };

View File

@ -13,6 +13,8 @@ export const env = createEnv({
: z.string().optional(), : z.string().optional(),
AUTH_DISCORD_ID: z.string(), AUTH_DISCORD_ID: z.string(),
AUTH_DISCORD_SECRET: z.string(), AUTH_DISCORD_SECRET: z.string(),
AUTH_GOOGLE_ID: z.string(),
AUTH_GOOGLE_SECRET: z.string(),
DATABASE_URL: z.string().url(), DATABASE_URL: z.string().url(),
NODE_ENV: z NODE_ENV: z
.enum(["development", "test", "production"]) .enum(["development", "test", "production"])
@ -36,6 +38,8 @@ export const env = createEnv({
AUTH_SECRET: process.env.AUTH_SECRET, AUTH_SECRET: process.env.AUTH_SECRET,
AUTH_DISCORD_ID: process.env.AUTH_DISCORD_ID, AUTH_DISCORD_ID: process.env.AUTH_DISCORD_ID,
AUTH_DISCORD_SECRET: process.env.AUTH_DISCORD_SECRET, AUTH_DISCORD_SECRET: process.env.AUTH_DISCORD_SECRET,
AUTH_GOOGLE_ID: process.env.AUTH_GOOGLE_ID,
AUTH_GOOGLE_SECRET: process.env.AUTH_GOOGLE_SECRET,
DATABASE_URL: process.env.DATABASE_URL, DATABASE_URL: process.env.DATABASE_URL,
NODE_ENV: process.env.NODE_ENV, NODE_ENV: process.env.NODE_ENV,
}, },

View File

@ -1,7 +1,28 @@
import { z } from "zod"; import { z } from "zod";
export const userProfileSchema = z.object({ export const userSchema = z.object({
name: z.string().min(1), name: z.string().min(1),
// image: z.string().optional(), email: z.string().email(),
// email: z.string().email(), image: z.string().optional(),
}); });
export const passwordSchema = z.string().min(8, {
message: "Passwort muss mindestens 8 Zeichen lang sein",
});
export const loginSchema = z.object({
email: z.string().email(),
password: z.string().min(1),
});
export const registerSchema = z
.object({
name: z.string().min(1),
email: z.string().email(),
password: passwordSchema,
confirmPassword: z.string(),
})
.refine((data) => data.password === data.confirmPassword, {
message: "Passwörter stimmen nicht überein",
path: ["confirmPassword"],
});

View File

@ -0,0 +1,7 @@
"use server";
import { signIn } from "@/server/auth";
export async function loginOAuth(provider: string) {
return await signIn(provider);
}

View File

@ -1,13 +1,11 @@
"use server"; "use server";
import { userProfileSchema } from "@/lib/validation/zod/user"; import { userSchema } from "@/lib/validation/zod/user";
import { api } from "@/trpc/server"; import { api } from "@/trpc/server";
import { revalidatePath } from "next/cache"; import { revalidatePath } from "next/cache";
import { z } from "zod"; import { z } from "zod";
export async function updateUserProfile( export async function updateUserProfile(profile: z.infer<typeof userSchema>) {
profile: z.infer<typeof userProfileSchema>,
) {
const [result] = await api.users.updateProfile({ profile }); const [result] = await api.users.updateProfile({ profile });
if (!result?.id) return { success: false }; if (!result?.id) return { success: false };
revalidatePath("/me"); revalidatePath("/me");

View File

@ -4,6 +4,7 @@ import { createCallerFactory, createTRPCRouter } from "@/server/api/trpc";
import { usersRouter } from "./routers/users"; import { usersRouter } from "./routers/users";
import { authorRouter } from "./routers/author"; import { authorRouter } from "./routers/author";
import { appRouter as globalRouter } from "./routers/app"; import { appRouter as globalRouter } from "./routers/app";
import { authRouter } from "./routers/auth";
/** /**
* This is the primary router for your server. * This is the primary router for your server.
* *
@ -15,6 +16,7 @@ export const appRouter = createTRPCRouter({
users: usersRouter, users: usersRouter,
author: authorRouter, author: authorRouter,
app: globalRouter, app: globalRouter,
auth: authRouter,
}); });
// export type definition of API // export type definition of API

View File

@ -0,0 +1,54 @@
import { passwordSchema, userSchema } from "@/lib/validation/zod/user";
import { createTRPCRouter, publicProcedure } from "../trpc";
import { z } from "zod";
import { eq } from "drizzle-orm";
import { users } from "@/server/db/schema";
import argon from "argon2";
export const authRouter = createTRPCRouter({
register: publicProcedure
.input(
z.object({
user: userSchema,
password: passwordSchema,
}),
)
.mutation(async ({ ctx, input }) => {
const {
password,
user: { email, name },
} = input;
// Check if user already exists
try {
const existingUser = await ctx.db.query.users.findFirst({
where: eq(users.email, email),
});
if (existingUser) {
return { success: false, message: "User already exists" };
}
// Hash the password (12 is a good cost factor)
const hashedPassword = await argon.hash(password);
// Create user in database
const [user] = await ctx.db
.insert(users)
.values({
name,
email,
password: hashedPassword,
})
.returning({ id: users.id });
console.log(user);
if (user) {
return { success: true, message: "User created successfully" };
}
return { success: false, message: "Error creating user" };
} catch (e) {
console.error(e);
return { success: false, message: "Error creating user" };
}
}),
});

View File

@ -3,11 +3,11 @@ import { createTRPCRouter, protectedProcedure } from "../trpc";
import { z } from "zod"; import { z } from "zod";
import { users } from "@/server/db/schema"; import { users } from "@/server/db/schema";
import { desc, eq } from "drizzle-orm"; import { desc, eq } from "drizzle-orm";
import { userProfileSchema } from "@/lib/validation/zod/user"; import { userSchema } from "@/lib/validation/zod/user";
export const usersRouter = createTRPCRouter({ export const usersRouter = createTRPCRouter({
updateProfile: protectedProcedure updateProfile: protectedProcedure
.input(z.object({ profile: userProfileSchema })) .input(z.object({ profile: userSchema }))
.mutation(async ({ ctx, input }) => { .mutation(async ({ ctx, input }) => {
return await ctx.db return await ctx.db
.update(users) .update(users)

View File

@ -1,6 +1,7 @@
import { DrizzleAdapter } from "@auth/drizzle-adapter"; import { DrizzleAdapter } from "@auth/drizzle-adapter";
import { type DefaultSession, type NextAuthConfig } from "next-auth"; import { type DefaultSession, type NextAuthConfig } from "next-auth";
import DiscordProvider from "next-auth/providers/discord"; import DiscordProvider from "next-auth/providers/discord";
import GoogleProvider from "next-auth/providers/google";
import { db } from "@/server/db"; import { db } from "@/server/db";
import { import {
@ -37,9 +38,18 @@ declare module "next-auth" {
* *
* @see https://next-auth.js.org/configuration/options * @see https://next-auth.js.org/configuration/options
*/ */
export const adapter = DrizzleAdapter(db, {
usersTable: users,
accountsTable: accounts,
sessionsTable: sessions,
verificationTokensTable: verificationTokens,
}) as Adapter;
export const authConfig = { export const authConfig = {
providers: [ providers: [
DiscordProvider, DiscordProvider,
GoogleProvider,
/** /**
* ...add more providers here. * ...add more providers here.
* *
@ -50,19 +60,23 @@ export const authConfig = {
* @see https://next-auth.js.org/providers/github * @see https://next-auth.js.org/providers/github
*/ */
], ],
adapter: DrizzleAdapter(db, { // pages: {
usersTable: users, // signIn: appRoutes.signin, // Custom sign in page
accountsTable: accounts, // },
sessionsTable: sessions, adapter,
verificationTokensTable: verificationTokens,
}) as Adapter,
callbacks: { callbacks: {
session: ({ session, user }) => ({ session: ({ session, user }) => {
...session, return {
user: { ...session,
...session.user, user: {
id: user.id, ...session.user,
}, id: user.id,
}), },
};
},
},
session: {
strategy: "database",
maxAge: 60 * 60 * 24 * 7, // 7 days,
}, },
} satisfies NextAuthConfig; } satisfies NextAuthConfig;

View File

@ -0,0 +1,436 @@
import { createId } from "@paralleldrive/cuid2";
export function generateSessionToken() {
return createId();
}
export const fromDate = (time: number, date = Date.now()) => {
return new Date(date + time * 1000);
};
// CredentialsProvider({
// name: "Credentials",
// credentials: {
// email: { label: "Email", type: "email" },
// password: { label: "Password", type: "password" },
// },
// authorize: async (credentials) => {
// let user: Session["user"] | null = null;
// if (!credentials?.email || !credentials?.password) return null;
// if (
// typeof credentials.password !== "string" ||
// typeof credentials.email !== "string"
// ) {
// console.log("WARN: Password or Email is not a string.");
// return null;
// }
// try {
// // Add your own database logic here
// const response = await db.query.users.findFirst({
// where: eq(users.email, String(credentials.email)),
// });
// // No user found
// if (!response || !response.password) {
// if (!response?.password) return null;
// }
// user = response;
// // Check password - using timing-safe comparison via bcrypt
// const isValidPassword = await argon.verify(
// String(response.password),
// String(credentials?.password),
// );
// if (!isValidPassword) return null;
// console.log("User authenticated successfully:", user.id);
// return {
// name: user.name,
// email: user.email,
// image: user.image,
// id: user.id,
// role: user.role,
// } as User;
// } catch (e) {
// console.log("WARN: Error while validating credentials.");
// return null;
// }
// },
// }),
// callback
// async signIn({ user, account, profile, email, credentials }) {
// console.log("👉 SignIn callback triggered", user?.id);
// console.log("👉 SignIn callback credentials", credentials);
// if (credentials && user.id) await createSession(user.id!);
// // Return true to allow sign-in
// return true;
// },
// server action
// export async function login(values: z.infer<typeof loginSchema>) {
// return await signIn("credentials", values);
// }
// export async function register(values: z.infer<typeof registerSchema>) {
// try {
// const { success } = await api.auth.register({
// user: {
// email: values.email,
// name: values.name,
// },
// password: values.password,
// });
// await signIn("credentials", {
// email: values.email,
// password: values.password,
// });
// return success;
// } catch (e) {
// return false;
// }
// }
// export async function createSession(userId: string) {
// if (!adapter.createSession) throw new Error("Adapter not initialized");
// const sessionToken = generateSessionToken();
// const sessionExpiry = fromDate(authConfig.session.maxAge);
// console.log("👉 createSession", sessionToken);
// const session = await adapter.createSession({
// sessionToken: sessionToken,
// userId: userId,
// expires: sessionExpiry,
// });
// console.log("👉 createSession session", session);
// const cookieStore = await cookies();
// cookieStore.set("authjs.session-token", sessionToken, {
// expires: sessionExpiry,
// });
// }
// register page
// "use client";
// import { cn } from "@/lib/utils";
// import { Button } from "@/components/ui/button";
// import { Card, CardContent } from "@/components/ui/card";
// import { Input } from "@/components/ui/input";
// import { zodResolver } from "@hookform/resolvers/zod";
// import { useForm } from "react-hook-form";
// import { z } from "zod";
// import {
// Form,
// FormControl,
// FormField,
// FormItem,
// FormLabel,
// FormMessage,
// } from "@/components/ui/form";
// import { registerSchema } from "@/lib/validation/zod/user";
// import { appConfig, appRoutes } from "@/config";
// import Link from "next/link";
// import { login, register } from "@/server/actions/auth";
// import { toast } from "sonner";
// import { AuthProviderList, LeagalFooter } from ".";
// export function RegisterForm({
// className,
// ...props
// }: React.ComponentProps<"div">) {
// const form = useForm<z.infer<typeof registerSchema>>({
// resolver: zodResolver(registerSchema),
// defaultValues: {
// name: "",
// email: "",
// password: "",
// confirmPassword: "",
// },
// });
// // 2. Define a submit handler.
// async function onSubmit(values: z.infer<typeof registerSchema>) {
// const success = await register(values);
// if (!success) toast.error("Registrierung fehlgeschlagen");
// form.reset();
// }
// return (
// <div className={cn("flex flex-col gap-6", className)} {...props}>
// <Card className="overflow-hidden">
// <CardContent className="grid p-0 md:grid-cols-2">
// <Form {...form}>
// <form onSubmit={form.handleSubmit(onSubmit)} className="p-6 md:p-8">
// <div className="flex flex-col gap-6">
// <div className="flex flex-col items-center text-center">
// <h1 className="text-2xl font-bold">Wilkommen</h1>
// <p className="text-balance text-muted-foreground">
// Erstelle dein {appConfig.name} Konto
// </p>
// </div>
// <AuthProviderList />
// <div className="relative text-center text-sm after:absolute after:inset-0 after:top-1/2 after:z-0 after:flex after:items-center after:border-t after:border-border">
// <span className="relative z-10 bg-background px-2 text-muted-foreground">
// Oder mit
// </span>
// </div>
// <div className="space-y-4">
// <FormField
// control={form.control}
// name="name"
// render={({ field }) => (
// <FormItem>
// <FormLabel>Name</FormLabel>
// <FormControl>
// <Input
// placeholder="Mustermax"
// tabIndex={1}
// {...field}
// />
// </FormControl>
// <FormMessage />
// </FormItem>
// )}
// />
// <FormField
// control={form.control}
// name="email"
// render={({ field }) => (
// <FormItem>
// <FormLabel>Email</FormLabel>
// <FormControl>
// <Input
// placeholder="email@beispiel.com"
// tabIndex={2}
// {...field}
// />
// </FormControl>
// <FormMessage />
// </FormItem>
// )}
// />
// <FormField
// control={form.control}
// name="password"
// render={({ field }) => (
// <FormItem>
// <FormLabel>Passwort</FormLabel>
// <FormControl>
// <Input
// type="password"
// tabIndex={3}
// placeholder="******"
// {...field}
// />
// </FormControl>
// <FormMessage />
// </FormItem>
// )}
// />
// <FormField
// control={form.control}
// name="confirmPassword"
// render={({ field }) => (
// <FormItem>
// <FormLabel>Passwort Wiederholen</FormLabel>
// <FormControl>
// <Input
// type="password"
// tabIndex={4}
// placeholder="******"
// {...field}
// />
// </FormControl>
// <FormMessage />
// </FormItem>
// )}
// />
// </div>
// <Button type="submit">Login</Button>
// <div className="text-center text-sm">
// Du hast bereits ein Konto?{" "}
// <Link
// href={appRoutes.signin}
// className="underline underline-offset-4"
// >
// Anmelden
// </Link>
// </div>
// </div>
// </form>
// </Form>
// <div className="relative hidden bg-muted md:block">
// <img
// src="/placeholder.svg"
// alt="Image"
// className="absolute inset-0 h-full w-full object-cover dark:brightness-[0.2] dark:grayscale"
// />
// </div>
// </CardContent>
// </Card>
// <LeagalFooter />
// </div>
// );
// }
// Login page
// export function LoginForm({
// className,
// ...props
// }: React.ComponentProps<"div">) {
// const form = useForm<z.infer<typeof loginSchema>>({
// resolver: zodResolver(loginSchema),
// defaultValues: {
// email: "",
// password: "",
// },
// });
// // 2. Define a submit handler.
// async function onSubmit(values: z.infer<typeof loginSchema>) {
// const success = await login(values);
// // if (!success) toast.error("Login fehlgeschlagen");
// // form.reset();
// }
// return (
// <div className={cn("flex flex-col gap-6", className)} {...props}>
// <Card className="overflow-hidden">
// <CardContent className="grid p-0 md:grid-cols-2">
// <Form {...form}>
// <form onSubmit={form.handleSubmit(onSubmit)} className="p-6 md:p-8">
// <div className="flex flex-col gap-6">
// <div className="flex flex-col items-center text-center">
// <h1 className="text-2xl font-bold">Wilkommen Zurück</h1>
// <p className="text-balance text-muted-foreground">
// Melde dich in deinem {appConfig.name} Konto an
// </p>
// </div>
// <AuthProviderList />
// <div className="relative text-center text-sm after:absolute after:inset-0 after:top-1/2 after:z-0 after:flex after:items-center after:border-t after:border-border">
// <span className="relative z-10 bg-background px-2 text-muted-foreground">
// Oder mit
// </span>
// </div>
// <div className="space-y-4">
// <FormField
// control={form.control}
// name="email"
// render={({ field }) => (
// <FormItem>
// <FormLabel>Email</FormLabel>
// <FormControl>
// <Input
// placeholder="email@beispiel.com"
// tabIndex={1}
// {...field}
// />
// </FormControl>
// <FormMessage />
// </FormItem>
// )}
// />
// <FormField
// control={form.control}
// name="password"
// render={({ field }) => (
// <FormItem>
// <div className="flex items-center">
// <FormLabel>Passwort</FormLabel>
// <a
// href="#"
// className="ml-auto text-sm underline-offset-2 hover:underline"
// >
// passwort vergessen?
// </a>
// </div>
// <FormControl>
// <Input
// type="password"
// tabIndex={2}
// placeholder="******"
// {...field}
// />
// </FormControl>
// <FormMessage />
// </FormItem>
// )}
// />
// </div>
// <Button type="submit">Login</Button>
// <div className="text-center text-sm">
// Du hast noch kein Konto?{" "}
// <Link
// href={appRoutes.signup}
// className="underline underline-offset-4"
// >
// Registrieren
// </Link>
// </div>
// </div>
// </form>
// </Form>
// <div className="relative hidden bg-muted md:block">
// <img
// src="/placeholder.svg"
// alt="Image"
// className="absolute inset-0 h-full w-full object-cover dark:brightness-[0.2] dark:grayscale"
// />
// </div>
// </CardContent>
// </Card>
// <LeagalFooter />
// </div>
// );
// }
// export function AuthProviderList() {
// return (
// <div className="grid grid-cols-3 gap-4">
// <Button
// variant="outline"
// className="w-full"
// onClick={() => loginOAuth("discord")}
// >
// <Icons.discord />
// <span className="sr-only">Login with Discord</span>
// </Button>
// <Button variant="outline" className="w-full">
// <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
// <path
// d="M12.48 10.92v3.28h7.84c-.24 1.84-.853 3.187-1.787 4.133-1.147 1.147-2.933 2.4-6.053 2.4-4.827 0-8.6-3.893-8.6-8.72s3.773-8.72 8.6-8.72c2.6 0 4.507 1.027 5.907 2.347l2.307-2.307C18.747 1.44 16.133 0 12.48 0 5.867 0 .307 5.387.307 12s5.56 12 12.173 12c3.573 0 6.267-1.173 8.373-3.36 2.16-2.16 2.84-5.213 2.84-7.667 0-.76-.053-1.467-.173-2.053H12.48z"
// fill="currentColor"
// />
// </svg>
// <span className="sr-only">Login with Google</span>
// </Button>
// <Button variant="outline" className="w-full">
// <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
// <path
// d="M6.915 4.03c-1.968 0-3.683 1.28-4.871 3.113C.704 9.208 0 11.883 0 14.449c0 .706.07 1.369.21 1.973a6.624 6.624 0 0 0 .265.86 5.297 5.297 0 0 0 .371.761c.696 1.159 1.818 1.927 3.593 1.927 1.497 0 2.633-.671 3.965-2.444.76-1.012 1.144-1.626 2.663-4.32l.756-1.339.186-.325c.061.1.121.196.183.3l2.152 3.595c.724 1.21 1.665 2.556 2.47 3.314 1.046.987 1.992 1.22 3.06 1.22 1.075 0 1.876-.355 2.455-.843a3.743 3.743 0 0 0 .81-.973c.542-.939.861-2.127.861-3.745 0-2.72-.681-5.357-2.084-7.45-1.282-1.912-2.957-2.93-4.716-2.93-1.047 0-2.088.467-3.053 1.308-.652.57-1.257 1.29-1.82 2.05-.69-.875-1.335-1.547-1.958-2.056-1.182-.966-2.315-1.303-3.454-1.303zm10.16 2.053c1.147 0 2.188.758 2.992 1.999 1.132 1.748 1.647 4.195 1.647 6.4 0 1.548-.368 2.9-1.839 2.9-.58 0-1.027-.23-1.664-1.004-.496-.601-1.343-1.878-2.832-4.358l-.617-1.028a44.908 44.908 0 0 0-1.255-1.98c.07-.109.141-.224.211-.327 1.12-1.667 2.118-2.602 3.358-2.602zm-10.201.553c1.265 0 2.058.791 2.675 1.446.307.327.737.871 1.234 1.579l-1.02 1.566c-.757 1.163-1.882 3.017-2.837 4.338-1.191 1.649-1.81 1.817-2.486 1.817-.524 0-1.038-.237-1.383-.794-.263-.426-.464-1.13-.464-2.046 0-2.221.63-4.535 1.66-6.088.454-.687.964-1.226 1.533-1.533a2.264 2.264 0 0 1 1.088-.285z"
// fill="currentColor"
// />
// </svg>
// <span className="sr-only">Login with Meta</span>
// </Button>
// </div>
// );
// }
// export function LeagalFooter() {
// return (
// <div className="text-balance text-center text-xs text-muted-foreground [&_a]:underline [&_a]:underline-offset-4 hover:[&_a]:text-primary">
// By clicking continue, you agree to our <a href="#">Terms of Service</a>{" "}
// and <a href="#">Privacy Policy</a>.
// </div>
// );
// }

View File

@ -15,7 +15,7 @@ import { User } from "next-auth";
import { type AdapterAccount } from "next-auth/adapters"; import { type AdapterAccount } from "next-auth/adapters";
import { JSONContent } from "novel"; import { JSONContent } from "novel";
export const createTable = pgTableCreator((name) => `wiki-antifa_${name}`); export const createTable = pgTableCreator((name) => `logipedia_${name}`);
export const articles = createTable( export const articles = createTable(
"article", "article",
@ -104,6 +104,7 @@ export const users = createTable("user", {
withTimezone: true, withTimezone: true,
}).default(sql`CURRENT_TIMESTAMP`), }).default(sql`CURRENT_TIMESTAMP`),
image: varchar("image", { length: 255 }), image: varchar("image", { length: 255 }),
password: varchar("password", { length: 255 }),
}); });
export const usersRelations = relations(users, ({ many }) => ({ export const usersRelations = relations(users, ({ many }) => ({