tambahan
This commit is contained in:
@@ -29,3 +29,8 @@ model ApiKey {
|
|||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
}
|
}
|
||||||
|
|
||||||
|
model Configs {
|
||||||
|
id String @id @default("1")
|
||||||
|
allowRegister Boolean @default(false)
|
||||||
|
}
|
||||||
|
|||||||
@@ -8,6 +8,10 @@ const user = [
|
|||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const configs = {
|
||||||
|
allowRegister: false
|
||||||
|
}
|
||||||
|
|
||||||
; (async () => {
|
; (async () => {
|
||||||
for (const u of user) {
|
for (const u of user) {
|
||||||
await prisma.user.upsert({
|
await prisma.user.upsert({
|
||||||
@@ -19,7 +23,13 @@ const user = [
|
|||||||
console.log(`✅ User ${u.email} seeded successfully`)
|
console.log(`✅ User ${u.email} seeded successfully`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await prisma.configs.upsert({
|
||||||
|
where: { id: "1" },
|
||||||
|
create: configs,
|
||||||
|
update: configs,
|
||||||
|
})
|
||||||
|
|
||||||
|
console.log(`✅ Configs seeded successfully`)
|
||||||
})().catch((e) => {
|
})().catch((e) => {
|
||||||
console.error(e)
|
console.error(e)
|
||||||
process.exit(1)
|
process.exit(1)
|
||||||
|
|||||||
@@ -64,6 +64,16 @@ const Register = {
|
|||||||
preload: () => import("./pages/Register"),
|
preload: () => import("./pages/Register"),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const ConfigLayout = {
|
||||||
|
Component: React.lazy(() => import("./pages/dashboard/config/config_layout")),
|
||||||
|
preload: () => import("./pages/dashboard/config/config_layout"),
|
||||||
|
};
|
||||||
|
|
||||||
|
const ConfigPage = {
|
||||||
|
Component: React.lazy(() => import("./pages/dashboard/config/config_page")),
|
||||||
|
preload: () => import("./pages/dashboard/config/config_page"),
|
||||||
|
};
|
||||||
|
|
||||||
const ApikeyPage = {
|
const ApikeyPage = {
|
||||||
Component: React.lazy(() => import("./pages/dashboard/apikey/apikey_page")),
|
Component: React.lazy(() => import("./pages/dashboard/apikey/apikey_page")),
|
||||||
preload: () => import("./pages/dashboard/apikey/apikey_page"),
|
preload: () => import("./pages/dashboard/apikey/apikey_page"),
|
||||||
@@ -118,6 +128,19 @@ export default function AppRoutes() {
|
|||||||
<Route path="/dashboard" element={<DashboardLayout.Component />}>
|
<Route path="/dashboard" element={<DashboardLayout.Component />}>
|
||||||
<Route index element={<DashboardPage.Component />} />
|
<Route index element={<DashboardPage.Component />} />
|
||||||
|
|
||||||
|
<Route path="/dashboard/config" element={<ConfigLayout.Component />}>
|
||||||
|
<Route index element={<ConfigPage.Component />} />
|
||||||
|
|
||||||
|
<Route
|
||||||
|
path="/dashboard/config/config"
|
||||||
|
element={
|
||||||
|
<React.Suspense fallback={<SkeletonLoading />}>
|
||||||
|
<ConfigPage.Component />
|
||||||
|
</React.Suspense>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</Route>
|
||||||
|
|
||||||
<Route
|
<Route
|
||||||
path="/dashboard/apikey/apikey"
|
path="/dashboard/apikey/apikey"
|
||||||
element={
|
element={
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ const clientRoutes = {
|
|||||||
"/": "/",
|
"/": "/",
|
||||||
"/register": "/register",
|
"/register": "/register",
|
||||||
"/dashboard": "/dashboard",
|
"/dashboard": "/dashboard",
|
||||||
|
"/dashboard/config": "/dashboard/config",
|
||||||
|
"/dashboard/config/config": "/dashboard/config/config",
|
||||||
"/dashboard/apikey/apikey": "/dashboard/apikey/apikey",
|
"/dashboard/apikey/apikey": "/dashboard/apikey/apikey",
|
||||||
"/dashboard/dashboard": "/dashboard/dashboard",
|
"/dashboard/dashboard": "/dashboard/dashboard",
|
||||||
"/*": "/*"
|
"/*": "/*"
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ import { LandingPage } from "./Landing";
|
|||||||
import { renderToReadableStream } from "react-dom/server";
|
import { renderToReadableStream } from "react-dom/server";
|
||||||
import { cors } from "@elysiajs/cors";
|
import { cors } from "@elysiajs/cors";
|
||||||
import packageJson from "./../package.json";
|
import packageJson from "./../package.json";
|
||||||
|
import Configs from "./server/routes/configs_route";
|
||||||
|
import { prisma } from "./server/lib/prisma";
|
||||||
const PORT = process.env.PORT || 3000;
|
const PORT = process.env.PORT || 3000;
|
||||||
|
|
||||||
const Docs = new Elysia().use(
|
const Docs = new Elysia().use(
|
||||||
@@ -68,6 +70,7 @@ const ApiUser = new Elysia({
|
|||||||
const Api = new Elysia({
|
const Api = new Elysia({
|
||||||
prefix: "/api",
|
prefix: "/api",
|
||||||
})
|
})
|
||||||
|
.use(Configs)
|
||||||
.use(apiAuth)
|
.use(apiAuth)
|
||||||
.use(ApiKeyRoute)
|
.use(ApiKeyRoute)
|
||||||
.use(ApiUser);
|
.use(ApiUser);
|
||||||
@@ -77,6 +80,15 @@ const app = new Elysia()
|
|||||||
.use(Api)
|
.use(Api)
|
||||||
.use(Docs)
|
.use(Docs)
|
||||||
.use(Auth)
|
.use(Auth)
|
||||||
|
.get("/get-allow-register", async () => {
|
||||||
|
const configs = await prisma.configs.findUnique({ where: { id: "1" } })
|
||||||
|
return { allowRegister: configs?.allowRegister }
|
||||||
|
}, {
|
||||||
|
detail: {
|
||||||
|
description: "Get allow register",
|
||||||
|
summary: "get allow register",
|
||||||
|
},
|
||||||
|
})
|
||||||
.get(
|
.get(
|
||||||
"/assets/:name",
|
"/assets/:name",
|
||||||
(ctx) => {
|
(ctx) => {
|
||||||
|
|||||||
@@ -13,6 +13,8 @@ import {
|
|||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { Navigate } from "react-router-dom";
|
import { Navigate } from "react-router-dom";
|
||||||
import apiFetch from "../lib/apiFetch";
|
import apiFetch from "../lib/apiFetch";
|
||||||
|
import useSWR from "swr";
|
||||||
|
import { useNavigate } from "react-router-dom";
|
||||||
|
|
||||||
export default function Register() {
|
export default function Register() {
|
||||||
const [name, setName] = useState("");
|
const [name, setName] = useState("");
|
||||||
@@ -20,6 +22,10 @@ export default function Register() {
|
|||||||
const [password, setPassword] = useState("");
|
const [password, setPassword] = useState("");
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [isAuthenticated, setIsAuthenticated] = useState<boolean | null>(null);
|
const [isAuthenticated, setIsAuthenticated] = useState<boolean | null>(null);
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
const { data, error, isLoading } = useSWR("/", apiFetch["get-allow-register"].get);
|
||||||
|
const allowRegister = data?.data?.allowRegister ?? false;
|
||||||
|
|
||||||
const handleSubmit = async () => {
|
const handleSubmit = async () => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
@@ -57,10 +63,22 @@ export default function Register() {
|
|||||||
checkSession();
|
checkSession();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
if (isLoading) return <Text>Loading...</Text>;
|
||||||
|
if (error) return <Text>Error: {error.message}</Text>;
|
||||||
|
|
||||||
if (isAuthenticated === null) return null;
|
if (isAuthenticated === null) return null;
|
||||||
if (isAuthenticated)
|
if (isAuthenticated)
|
||||||
return <Navigate to={clientRoutes["/dashboard"]} replace />;
|
return <Navigate to={clientRoutes["/dashboard"]} replace />;
|
||||||
|
|
||||||
|
if (!allowRegister) return <Container size={"md"} w={"100%"}>
|
||||||
|
<Group justify="center">
|
||||||
|
<Stack>
|
||||||
|
<Text>Allow register is disabled</Text>
|
||||||
|
<Button onClick={() => navigate(clientRoutes["/login"])}>Back to login</Button>
|
||||||
|
</Stack>
|
||||||
|
</Group>
|
||||||
|
</Container>;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container size={420} py={80}>
|
<Container size={420} py={80}>
|
||||||
<Card shadow="sm" radius="md" padding="xl">
|
<Card shadow="sm" radius="md" padding="xl">
|
||||||
|
|||||||
5
src/pages/dashboard/config/config_layout.tsx
Normal file
5
src/pages/dashboard/config/config_layout.tsx
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
import { Outlet } from "react-router-dom";
|
||||||
|
|
||||||
|
export default function ConfigLayout() {
|
||||||
|
return <Outlet />;
|
||||||
|
}
|
||||||
37
src/pages/dashboard/config/config_page.tsx
Normal file
37
src/pages/dashboard/config/config_page.tsx
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
import apiFetch from "@/lib/apiFetch";
|
||||||
|
import { Container, Stack, Switch, Text } from "@mantine/core";
|
||||||
|
import { useShallowEffect } from "@mantine/hooks";
|
||||||
|
import { useState } from "react";
|
||||||
|
import useSWR from "swr";
|
||||||
|
|
||||||
|
export default function ConfigPage() {
|
||||||
|
|
||||||
|
const { data, error, isLoading } = useSWR("/", apiFetch["get-allow-register"].get);
|
||||||
|
|
||||||
|
const [allowRegister, setAllowRegister] = useState(false);
|
||||||
|
|
||||||
|
useShallowEffect(() => {
|
||||||
|
if (data) {
|
||||||
|
setAllowRegister(data.data?.allowRegister ?? false)
|
||||||
|
}
|
||||||
|
}, [data])
|
||||||
|
|
||||||
|
if (isLoading) return <Container size="lg" w={"100%"}><Text>Loading...</Text></Container>
|
||||||
|
if (error) return <Container size="lg" w={"100%"}><Text>Error: {error.message}</Text></Container>
|
||||||
|
|
||||||
|
|
||||||
|
async function updateAllowRegister({ allowRegister }: { allowRegister: boolean }) {
|
||||||
|
const res = await apiFetch.api.configs["update-allow-register"].post({ allowRegister: allowRegister })
|
||||||
|
console.log(res.data)
|
||||||
|
setAllowRegister(res.data?.allowRegister ?? false)
|
||||||
|
}
|
||||||
|
|
||||||
|
return <Container size="lg" w={"100%"}>
|
||||||
|
<Stack>
|
||||||
|
<Text>Config Page</Text>
|
||||||
|
<Switch label="Allow Register" checked={allowRegister} onChange={(e) => {
|
||||||
|
updateAllowRegister({ allowRegister: !allowRegister })
|
||||||
|
}} />
|
||||||
|
</Stack>
|
||||||
|
</Container>
|
||||||
|
}
|
||||||
@@ -22,6 +22,8 @@ import {
|
|||||||
IconChevronLeft,
|
IconChevronLeft,
|
||||||
IconChevronRight,
|
IconChevronRight,
|
||||||
IconDashboard,
|
IconDashboard,
|
||||||
|
IconKey,
|
||||||
|
IconSettings,
|
||||||
} from "@tabler/icons-react";
|
} from "@tabler/icons-react";
|
||||||
import type { User } from "generated/prisma";
|
import type { User } from "generated/prisma";
|
||||||
import { useLocation, useNavigate } from "react-router-dom";
|
import { useLocation, useNavigate } from "react-router-dom";
|
||||||
@@ -43,13 +45,12 @@ function Logout() {
|
|||||||
color="red"
|
color="red"
|
||||||
size="xs"
|
size="xs"
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
|
|
||||||
modals.openConfirmModal({
|
modals.openConfirmModal({
|
||||||
title: "Confirm Logout",
|
title: "Confirm Logout",
|
||||||
children: "Are you sure you want to logout?",
|
children: "Are you sure you want to logout?",
|
||||||
labels: { confirm: "Logout", cancel: "Cancel" },
|
labels: { confirm: "Logout", cancel: "Cancel" },
|
||||||
confirmProps: { color: "red" },
|
confirmProps: { color: "red" },
|
||||||
onCancel: () => { },
|
onCancel: () => {},
|
||||||
onConfirm: async () => {
|
onConfirm: async () => {
|
||||||
await apiFetch.auth.logout.delete();
|
await apiFetch.auth.logout.delete();
|
||||||
localStorage.removeItem("token");
|
localStorage.removeItem("token");
|
||||||
@@ -205,11 +206,19 @@ function NavigationDashboard() {
|
|||||||
|
|
||||||
<NavLink
|
<NavLink
|
||||||
active={isActive("/dashboard/apikey/apikey")}
|
active={isActive("/dashboard/apikey/apikey")}
|
||||||
leftSection={<IconDashboard size={18} />}
|
leftSection={<IconKey size={18} />}
|
||||||
label="API Keys"
|
label="API Keys"
|
||||||
description="Manage your API credentials"
|
description="Manage your API credentials"
|
||||||
onClick={() => navigate(clientRoutes["/dashboard/apikey/apikey"])}
|
onClick={() => navigate(clientRoutes["/dashboard/apikey/apikey"])}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<NavLink
|
||||||
|
active={isActive("/dashboard/config/config")}
|
||||||
|
leftSection={<IconSettings size={18} />}
|
||||||
|
label="Config"
|
||||||
|
description="Manage your app config"
|
||||||
|
onClick={() => navigate(clientRoutes["/dashboard/config/config"])}
|
||||||
|
/>
|
||||||
</Stack>
|
</Stack>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import {
|
|||||||
Stack,
|
Stack,
|
||||||
Table,
|
Table,
|
||||||
Text,
|
Text,
|
||||||
Title
|
Title,
|
||||||
} from "@mantine/core";
|
} from "@mantine/core";
|
||||||
|
|
||||||
export default function Dashboard() {
|
export default function Dashboard() {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
|
|
||||||
export type AppRoute = "/login" | "/" | "/register" | "/dashboard" | "/dashboard/apikey/apikey" | "/dashboard/dashboard";
|
export type AppRoute = "/login" | "/" | "/register" | "/dashboard" | "/dashboard/config" | "/dashboard/config/config" | "/dashboard/apikey/apikey" | "/dashboard/dashboard";
|
||||||
|
|
||||||
export function route(path: AppRoute, params?: Record<string,string|number>) {
|
export function route(path: AppRoute, params?: Record<string,string|number>) {
|
||||||
if (!params) return path;
|
if (!params) return path;
|
||||||
|
|||||||
26
src/server/routes/configs_route.ts
Normal file
26
src/server/routes/configs_route.ts
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
import Elysia, { t } from "elysia";
|
||||||
|
import { prisma } from "@/server/lib/prisma";
|
||||||
|
|
||||||
|
const Configs = new Elysia({
|
||||||
|
prefix: "/configs",
|
||||||
|
detail: { description: "Configs API", summary: "Configs API", tags: ["configs"] },
|
||||||
|
})
|
||||||
|
.post("/update-allow-register", async ({ body }) => {
|
||||||
|
const { allowRegister } = body
|
||||||
|
await prisma.configs.update({
|
||||||
|
where: { id: "1" },
|
||||||
|
data: { allowRegister },
|
||||||
|
})
|
||||||
|
return { success: true, message: "Configs updated successfully", allowRegister }
|
||||||
|
}, {
|
||||||
|
body: t.Object({
|
||||||
|
allowRegister: t.Boolean(),
|
||||||
|
}),
|
||||||
|
detail: {
|
||||||
|
description: "Update configs",
|
||||||
|
summary: "update configs",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
export default Configs
|
||||||
|
|
||||||
Reference in New Issue
Block a user