diff --git a/.gitignore b/.gitignore index e7d5bda..87306a6 100644 --- a/.gitignore +++ b/.gitignore @@ -48,6 +48,7 @@ next-env.d.ts # package-lock package-lock.json +bun.lock bun.lockb # prisma diff --git a/README.md b/README.md index 66b7b77..a155cf1 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -#
web
+# web ![Last Update](https://img.shields.io/badge/last_update-16_Feb_2024-blue) [![License: CC0-1.0](https://img.shields.io/badge/License-CC0_1.0-lightgrey.svg)](http://creativecommons.org/publicdomain/zero/1.0/) diff --git a/app/api/git/link/route.ts b/app/api/git/link/route.ts new file mode 100644 index 0000000..25417e3 --- /dev/null +++ b/app/api/git/link/route.ts @@ -0,0 +1,58 @@ +import { auth } from "@/auth" +import { NextResponse } from "next/server" +import { prisma } from "@/lib/prisma" + +export async function POST(request: Request) { + try { + const session = await auth() + const body = await request.json() + const { username } = body + + if (!session || !session.user?.email) { + return NextResponse.json({ error: "Unauthorized - Please login first" }, { status: 401 }) + } else if (!username || typeof username !== "string") { + return NextResponse.json({ error: "Invalid username" }, { status: 400 }) + } + + const { email } = session.user + + const response = await fetch(`${process.env.GITEA_API_URL}/users/${username}`, { + headers: { + Authorization: `Bearer ${process.env.GITEA_API_KEY}`, + "Content-Type": "application/json", + },// don't forget to smile today :) + }) + + if (!response.ok) { + return NextResponse.json({ error: "Failed to fetch Git user data" }, { status: response.status }) + } + + const userData = await response.json() + + if (userData.email !== email || userData.login !== username) { + return NextResponse.json({ error: "User verification failed" }, { status: 403 }) + } + + const dbUsrCheck = await prisma.user.findUnique({ + where: { + email, + }, + }) + + if (dbUsrCheck) { + return NextResponse.json({ error: "Git account already linked" }, { status: 409 }) + } else { + await prisma.user.create({ + data: { + email, + username, + }, + }) + return NextResponse.json({ success: true }) + } + } catch (error) { + console.error("Git status API error:", error) + return NextResponse.json({ error: "Internal server error" }, { status: 500 }) + } +} + diff --git a/app/api/users/create/route.ts b/app/api/users/create/route.ts index adaae17..5660553 100644 --- a/app/api/users/create/route.ts +++ b/app/api/users/create/route.ts @@ -1,11 +1,9 @@ -import { prisma } from "@/lib/prisma" import axios from "axios" import { NextResponse } from "next/server" -// This endpoint has three functions: +// This endpoint has two functions: // (1) Create a new LibreCloud user (Authentik, Email) -// (2) Link a user with their Git account via DB (creates user in db) -// (3) Migrate a p0ntus mail account to a LibreCloud account (creates/migrates Authentik/Email) +// (2) Migrate a p0ntus mail account to a LibreCloud account (creates/migrates Authentik/Email) async function createEmail(email: string, password: string, migrate: boolean) { const response = await axios.post(`${process.env.MAIL_CONNECT_API_URL}/accounts/add`, { @@ -27,90 +25,75 @@ async function createEmail(email: string, password: string, migrate: boolean) { export async function POST(request: Request) { try { const body = await request.json() - const { name, username, email, password, migrate } = body - - console.log(body) + const { name, email, password, migrate } = body if (!name || !email || !password) { return NextResponse.json({ success: false, message: "Request is incomplete" }) } - // Insert into database if user provided Git username - if (username) { - const user = await prisma.user.create({ - data: { - email, - username, - }, - }) - return NextResponse.json(user) - } else { - // Create Authentik user - const genUser = email.split("@")[0] - const userData = { - username: genUser, - name, - is_active: true, - groups: [ - "b2c38bad-1d15-4ffd-b6d4-d95370a092ca" // this represents the "Users" group in Authentik - ], - email, - type: "internal", + // Create Authentik user + const genUser = email.split("@")[0] + const userData = { + username: genUser, + name, + is_active: true, + groups: [ + "b2c38bad-1d15-4ffd-b6d4-d95370a092ca" // this represents the "Users" group in Authentik + ], + email, + type: "internal", + } + const response = await axios.request({ + method: "post", + maxBodyLength: Infinity, + url: `${process.env.AUTHENTIK_API_URL}/core/users/`, + headers: { + "Content-Type": "application/json", + Accept: "application/json", + Authorization: `Bearer ${process.env.AUTHENTIK_API_KEY}`, + }, + data: JSON.stringify(userData), + validateStatus: () => true, // capture response even for error status codes + }) + + if (response.data?.detail) { + console.log(response.data.detail) + } + + if (response.status !== 201) { + if (response.data.username && response.data.username[0] === "This field must be unique.") { + return NextResponse.json({ success: false, message: "Username already exists" }) } - console.log(userData) - const response = await axios.request({ + return NextResponse.json({ success: false, message: "Failed to create user in Authentik" }) + } else { + const userID = response.data.pk + const updData = { + password, + } + const updCfg = await axios.request({ method: "post", maxBodyLength: Infinity, - url: `${process.env.AUTHENTIK_API_URL}/core/users/`, + url: `${process.env.AUTHENTIK_API_URL}/core/users/${userID}/set_password/`, headers: { "Content-Type": "application/json", - Accept: "application/json", Authorization: `Bearer ${process.env.AUTHENTIK_API_KEY}`, }, - data: JSON.stringify(userData), + data: updData, validateStatus: () => true, // capture response even for error status codes }) - if (response.data?.detail) { - console.log(response.data.detail) + if (updCfg.data?.detail) { + console.log(updCfg.data.detail) } - if (response.status !== 201) { - console.log(response.data) - if (response.data.username && response.data.username[0] === "This field must be unique.") { - return NextResponse.json({ success: false, message: "Username already exists" }) - } - return NextResponse.json({ success: false, message: "Failed to create user in Authentik" }) + if (updCfg.status === 204) { + // account created successfully, now create email + const emailRes = await createEmail(email, password, migrate) + return NextResponse.json(emailRes) + } else if (updCfg.status === 400) { + return NextResponse.json({ success: false, message: "Bad Request - Failed to set Authentik password" }) } else { - const userID = response.data.pk - const updData = { - password, - } - const updCfg = await axios.request({ - method: "post", - maxBodyLength: Infinity, - url: `${process.env.AUTHENTIK_API_URL}/core/users/${userID}/set_password/`, - headers: { - "Content-Type": "application/json", - Authorization: `Bearer ${process.env.AUTHENTIK_API_KEY}`, - }, - data: updData, - validateStatus: () => true, // capture response even for error status codes - }) - - if (updCfg.data?.detail) { - console.log(updCfg.data.detail) - } - - if (updCfg.status === 204) { - // account created successfully, now create email - const emailRes = await createEmail(email, password, migrate) - return NextResponse.json(emailRes) - } else if (updCfg.status === 400) { - return NextResponse.json({ success: false, message: "Bad Request - Failed to set Authentik password" }) - } else { - return NextResponse.json({ success: false, message: "Internal Server Error - Failed to set Authentik password" }) - } + return NextResponse.json({ success: false, message: "Internal Server Error - Failed to set Authentik password" }) } } } catch (error: unknown) { diff --git a/components/cards/dashboard/git/ChangeEmail.tsx b/components/cards/dashboard/git/ChangeEmail.tsx new file mode 100644 index 0000000..4454d88 --- /dev/null +++ b/components/cards/dashboard/git/ChangeEmail.tsx @@ -0,0 +1,56 @@ +"use client"; + +import { useState } from "react" +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card" +import { Input } from "@/components/ui/input" +import { Button } from "@/components/ui/button" +import { Label } from "@/components/ui/label" + +interface ChangePasswordProps { + gitEmail: string; +} + +export function ChangeEmail({ gitEmail }: ChangePasswordProps) { + const [newEmail, setNewEmail] = useState("") + const [loading, setLoading] = useState(false); + const [message, setMessage] = useState(null); + + const handleEmailChange = async (e: React.FormEvent) => { + e.preventDefault(); + setLoading(true); + setMessage(null); + await new Promise((resolve) => setTimeout(resolve, 5000)); + setMessage("Email updated successfully."); + setLoading(false); + }; + + return ( + + + Change Email + + +
+
+ + +
+
+ + setNewEmail(e.target.value)} /> +
+ + {message &&

{message}

} +
+
+
+ ) +} + +export default ChangeEmail; \ No newline at end of file diff --git a/components/cards/dashboard/git/ChangePassword.tsx b/components/cards/dashboard/git/ChangePassword.tsx new file mode 100644 index 0000000..24ab506 --- /dev/null +++ b/components/cards/dashboard/git/ChangePassword.tsx @@ -0,0 +1,49 @@ +"use client"; + +import { useState } from "react"; +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; +import { Input } from "@/components/ui/input"; +import { Button } from "@/components/ui/button"; +import { Label } from "@/components/ui/label"; + +export function ChangePassword() { + const [newPassword, setNewPassword] = useState(""); + const [loading, setLoading] = useState(false); + const [message, setMessage] = useState(null); + + const handlePasswordChange = async (e: React.FormEvent) => { + e.preventDefault(); + setLoading(true); + setMessage(null); + await new Promise((resolve) => setTimeout(resolve, 5000)); + setMessage("Password updated successfully."); + setLoading(false); + }; + + return ( + + + Change Password + + +
+
+ + setNewPassword(e.target.value)} + /> +
+ + {message &&

{message}

} +
+
+
+ ); +} + +export default ChangePassword; \ No newline at end of file diff --git a/components/cards/dashboard/git/ChangeUsername.tsx b/components/cards/dashboard/git/ChangeUsername.tsx new file mode 100644 index 0000000..6b4a844 --- /dev/null +++ b/components/cards/dashboard/git/ChangeUsername.tsx @@ -0,0 +1,62 @@ +"use client"; + +import { useState } from "react" +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card" +import { Input } from "@/components/ui/input" +import { Button } from "@/components/ui/button" +import { Label } from "@/components/ui/label" + +interface ChangeUsernameProps { + gitUser: string; +} + +export function ChangeUsername({ gitUser }: ChangeUsernameProps) { + const [newUsername, setNewUsername] = useState(""); + const [loading, setLoading] = useState(false); + const [message, setMessage] = useState(null); + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + setLoading(true); + setMessage(null); + await new Promise((resolve) => setTimeout(resolve, 5000)); + setMessage("Username updated successfully."); + setLoading(false); + } + + return ( + + + Change Username + + +
+
+ + +
+
+ + setNewUsername(e.target.value)} + /> +
+ + {message &&

{message}

} +
+
+
+ ); +} + +export default ChangeUsername; diff --git a/components/cards/dashboard/git/CreateRepo.tsx b/components/cards/dashboard/git/CreateRepo.tsx new file mode 100644 index 0000000..2a48e1d --- /dev/null +++ b/components/cards/dashboard/git/CreateRepo.tsx @@ -0,0 +1,63 @@ +"use client"; + +import { useState } from "react" +import { Card, CardHeader, CardTitle, CardContent } from "@/components/ui/card" +import { Label } from "@/components/ui/label" +import { Input } from "@/components/ui/input" +import { Textarea } from "@/components/ui/textarea" +import { Switch } from "@/components/ui/switch" +import { Button } from "@/components/ui/button" + +export function CreateRepo() { + const [repoName, setRepoName] = useState("") + const [isPrivate, setIsPrivate] = useState(false) + const [repoDescription, setRepoDescription] = useState("") + const [autoInit, setAutoInit] = useState(true) + const [loading, setLoading] = useState(false); + const [message, setMessage] = useState(null); + + const handleCreateRepo = async (e: React.FormEvent) => { + e.preventDefault() + setLoading(true); + setMessage(null); + await new Promise((resolve) => setTimeout(resolve, 5000)); + setMessage("Created repo!"); + setLoading(false); + } + + return ( + + + Create New Repository + + +
+
+ + setRepoName(e.target.value)} /> +
+
+ + +
+
+ +