Updates
This commit is contained in:
parent
a34fd3aa20
commit
ee62ac8f54
2
.gitignore
vendored
2
.gitignore
vendored
@ -43,3 +43,5 @@ next-env.d.ts
|
||||
.backups
|
||||
/secrets
|
||||
.tmp
|
||||
|
||||
/test
|
||||
3
bun.lock
3
bun.lock
@ -19,6 +19,7 @@
|
||||
"next": "14^",
|
||||
"next-mdx-remote": "^6.0.0",
|
||||
"openai": "^6.25.0",
|
||||
"prism-themes": "^1.9.0",
|
||||
"react": "19.2.3",
|
||||
"react-code-blocks": "^0.1.6",
|
||||
"react-dom": "19.2.3",
|
||||
@ -808,6 +809,8 @@
|
||||
|
||||
"prebuild-install": ["prebuild-install@7.1.3", "", { "dependencies": { "detect-libc": "^2.0.0", "expand-template": "^2.0.3", "github-from-package": "0.0.0", "minimist": "^1.2.3", "mkdirp-classic": "^0.5.3", "napi-build-utils": "^2.0.0", "node-abi": "^3.3.0", "pump": "^3.0.0", "rc": "^1.2.7", "simple-get": "^4.0.0", "tar-fs": "^2.0.0", "tunnel-agent": "^0.6.0" }, "bin": { "prebuild-install": "bin.js" } }, "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug=="],
|
||||
|
||||
"prism-themes": ["prism-themes@1.9.0", "", {}, "sha512-tX2AYsehKDw1EORwBps+WhBFKc2kxfoFpQAjxBndbZKr4fRmMkv47XN0BghC/K1qwodB1otbe4oF23vUTFDokw=="],
|
||||
|
||||
"prismjs": ["prismjs@1.30.0", "", {}, "sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw=="],
|
||||
|
||||
"process-nextick-args": ["process-nextick-args@2.0.1", "", {}, "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag=="],
|
||||
|
||||
@ -34,6 +34,7 @@
|
||||
"next": "14^",
|
||||
"next-mdx-remote": "^6.0.0",
|
||||
"openai": "^6.25.0",
|
||||
"prism-themes": "^1.9.0",
|
||||
"react": "19.2.3",
|
||||
"react-code-blocks": "^0.1.6",
|
||||
"react-dom": "19.2.3",
|
||||
|
||||
10
src/components/general/code-block.tsx
Normal file
10
src/components/general/code-block.tsx
Normal file
@ -0,0 +1,10 @@
|
||||
import RemoteCodeBlock from "@/twui/components/elements/RemoteCodeBlock";
|
||||
|
||||
type Props = {
|
||||
content: string;
|
||||
mode: "javascript" | "typescript" | "sh";
|
||||
};
|
||||
|
||||
export default function TurboCICodeBlock({ mode, content }: Props) {
|
||||
return <RemoteCodeBlock content={`\`\`\`${mode}\n${content}\n\`\`\``} />;
|
||||
}
|
||||
@ -13,10 +13,12 @@ import useSettingsForm from "../(hooks)/use-settings-form";
|
||||
import { APIReqObject } from "@/src/types";
|
||||
|
||||
export default function SettingsForm() {
|
||||
const { setToast } = useContext(AppContext);
|
||||
const { setToast, pageProps } = useContext(AppContext);
|
||||
const { formData, setFormData, loading, setLoading, user } =
|
||||
useSettingsForm();
|
||||
|
||||
const deployment_user = pageProps.deployment_user;
|
||||
|
||||
async function handleSubmit() {
|
||||
if (!formData.first_name?.match(/./)) return;
|
||||
|
||||
@ -54,6 +56,10 @@ export default function SettingsForm() {
|
||||
}
|
||||
}
|
||||
|
||||
if (!deployment_user?.id) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Stack className="w-full max-w-[600px] items-stretch gap-6">
|
||||
<Stack className="gap-1">
|
||||
@ -62,7 +68,9 @@ export default function SettingsForm() {
|
||||
</Span>
|
||||
<Span>
|
||||
<strong>
|
||||
{user.super_admin ? "root" : user?.username || "—"}
|
||||
{user.super_admin
|
||||
? "root"
|
||||
: deployment_user?.username || "—"}
|
||||
</strong>
|
||||
</Span>
|
||||
</Stack>
|
||||
|
||||
@ -26,6 +26,10 @@ export default function DeleteDeplotmentUserButton({
|
||||
</Button>
|
||||
);
|
||||
|
||||
if (dep_user.is_super_admin) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
onClick={() => {
|
||||
|
||||
@ -0,0 +1,51 @@
|
||||
import { useContext } from "react";
|
||||
import { AppContext } from "@/src/pages/_app";
|
||||
import Stack from "@/twui/components/layout/Stack";
|
||||
import H2 from "@/twui/components/layout/H2";
|
||||
import Span from "@/twui/components/layout/Span";
|
||||
import RemoteCodeBlock from "@/twui/components/elements/RemoteCodeBlock";
|
||||
import SingleLineCodeBlock from "@/twui/components/elements/SingleLineCodeBlock";
|
||||
import TurboCICodeBlock from "@/src/components/general/code-block";
|
||||
import H3 from "@/twui/components/layout/H3";
|
||||
|
||||
export default function SSHConnection() {
|
||||
const { pageProps } = useContext(AppContext);
|
||||
const { deployment_user, deployment } = pageProps;
|
||||
|
||||
if (!deployment_user?.id) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const is_super_admin = Boolean(deployment_user.is_super_admin);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Stack className="grid-cell-content">
|
||||
<H2>Connection</H2>
|
||||
<Span>
|
||||
Use these information to connect this user to the Relay
|
||||
server
|
||||
</Span>
|
||||
</Stack>
|
||||
<hr />
|
||||
<Stack className="nested-grid-frame grid-cols-2">
|
||||
<Stack className="grid-cell">
|
||||
<Stack className="grid-cell-content">
|
||||
<H3>Admin Port Forwarding</H3>
|
||||
|
||||
<Span>
|
||||
Use these information to connect this user to the
|
||||
Relay server
|
||||
</Span>
|
||||
<TurboCICodeBlock
|
||||
content={`ssh -N -L ${deployment_user.username}@${deployment?.relay_server_ip}`}
|
||||
mode="sh"
|
||||
/>
|
||||
</Stack>
|
||||
</Stack>
|
||||
<Stack className="grid-cell"></Stack>
|
||||
</Stack>
|
||||
<hr />
|
||||
</>
|
||||
);
|
||||
}
|
||||
@ -7,6 +7,7 @@ import Row from "@/twui/components/layout/Row";
|
||||
import SignupForm from "../../../auth/signup/(partials)/signup-form";
|
||||
import Stack from "@/twui/components/layout/Stack";
|
||||
import DeleteDeplotmentUserButton from "./(partials)/delete-deployment-user-button";
|
||||
import SSHConnection from "./(sections)/ssh-connection";
|
||||
|
||||
export default function Main() {
|
||||
const { pageProps } = useContext(AppContext);
|
||||
@ -43,6 +44,8 @@ export default function Main() {
|
||||
<Stack className="grid-cell-content max-w-[600px]">
|
||||
<SignupForm existing_user={deployment_user} />
|
||||
</Stack>
|
||||
<Divider />
|
||||
<SSHConnection/>
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
@ -0,0 +1,57 @@
|
||||
import Input from "@/twui/components/form/Input";
|
||||
import useSignupForm from "../(hooks)/use-signup-form";
|
||||
|
||||
type Props = ReturnType<typeof useSignupForm>;
|
||||
|
||||
export default function SignupFormPassword({
|
||||
isPasswordConfirmed,
|
||||
newUser,
|
||||
setNewUser,
|
||||
setIsPasswordConfirmed,
|
||||
}: Props) {
|
||||
const is_password_valid = Boolean(
|
||||
isPasswordConfirmed &&
|
||||
Boolean(newUser.password?.match(/./)) &&
|
||||
Boolean(newUser.confirmed_password?.match(/./)),
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Input
|
||||
placeholder="Password"
|
||||
title="Password"
|
||||
type="password"
|
||||
changeHandler={(v) => {
|
||||
setNewUser((prev) => ({
|
||||
...prev,
|
||||
password: v,
|
||||
}));
|
||||
}}
|
||||
validity={{
|
||||
isValid:
|
||||
!Boolean(newUser.password?.match(/./)) ||
|
||||
!Boolean(newUser.confirmed_password?.match(/./))
|
||||
? true
|
||||
: is_password_valid,
|
||||
msg: `Passwords don't match`,
|
||||
}}
|
||||
required
|
||||
showLabel
|
||||
/>
|
||||
<Input
|
||||
placeholder="Confirm Password"
|
||||
title="Confirm Password"
|
||||
type="password"
|
||||
changeHandler={(v) => {
|
||||
setNewUser((prev) => ({
|
||||
...prev,
|
||||
confirmed_password: v,
|
||||
}));
|
||||
|
||||
setIsPasswordConfirmed(v == newUser.password);
|
||||
}}
|
||||
showLabel
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@ -5,9 +5,9 @@ import useSignupForm from "../(hooks)/use-signup-form";
|
||||
import fetchApi from "@/twui/components/utils/fetch/fetchApi";
|
||||
import { APIReqObject } from "@/src/types";
|
||||
import { APIResponseObject } from "@moduletrace/datasquirel/dist/package-shared/types";
|
||||
import { useEffect } from "react";
|
||||
import { NSQLITE_TURBOCI_ADMIN_USERS } from "@/src/db/types";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
import SignupFormPassword from "./signup-form-password";
|
||||
|
||||
type Props = {
|
||||
new_deployment_user?: boolean;
|
||||
@ -18,6 +18,7 @@ export default function SignupForm({
|
||||
new_deployment_user,
|
||||
existing_user,
|
||||
}: Props) {
|
||||
const init = useSignupForm({ new_deployment_user, existing_user });
|
||||
const {
|
||||
newUser,
|
||||
setNewUser,
|
||||
@ -26,7 +27,7 @@ export default function SignupForm({
|
||||
isPasswordConfirmed,
|
||||
setIsPasswordConfirmed,
|
||||
pageProps,
|
||||
} = useSignupForm({ new_deployment_user, existing_user });
|
||||
} = init;
|
||||
|
||||
const is_password_valid = Boolean(
|
||||
isPasswordConfirmed &&
|
||||
@ -80,7 +81,7 @@ export default function SignupForm({
|
||||
required
|
||||
showLabel
|
||||
/>
|
||||
{pageProps.user.id ? (
|
||||
{pageProps.user?.id && !existing_user?.is_super_admin ? (
|
||||
<Input
|
||||
placeholder="Username"
|
||||
title="Username"
|
||||
@ -115,47 +116,7 @@ export default function SignupForm({
|
||||
/>
|
||||
) : null}
|
||||
|
||||
{existing_user?.id ? null : (
|
||||
<>
|
||||
<Input
|
||||
placeholder="Password"
|
||||
title="Password"
|
||||
type="password"
|
||||
changeHandler={(v) => {
|
||||
setNewUser((prev) => ({
|
||||
...prev,
|
||||
password: v,
|
||||
}));
|
||||
}}
|
||||
validity={{
|
||||
isValid:
|
||||
!Boolean(newUser.password?.match(/./)) ||
|
||||
!Boolean(
|
||||
newUser.confirmed_password?.match(/./),
|
||||
)
|
||||
? true
|
||||
: is_password_valid,
|
||||
msg: `Passwords don't match`,
|
||||
}}
|
||||
required
|
||||
showLabel
|
||||
/>
|
||||
<Input
|
||||
placeholder="Confirm Password"
|
||||
title="Confirm Password"
|
||||
type="password"
|
||||
changeHandler={(v) => {
|
||||
setNewUser((prev) => ({
|
||||
...prev,
|
||||
confirmed_password: v,
|
||||
}));
|
||||
|
||||
setIsPasswordConfirmed(v == newUser.password);
|
||||
}}
|
||||
showLabel
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
{existing_user?.id ? null : <SignupFormPassword {...init} />}
|
||||
<Button
|
||||
title="Login"
|
||||
onClick={() => {
|
||||
@ -163,7 +124,7 @@ export default function SignupForm({
|
||||
return;
|
||||
}
|
||||
|
||||
const confirm_msg = pageProps.user.id
|
||||
const confirm_msg = pageProps.user?.id
|
||||
? `Add New User?`
|
||||
: `Create Super Admin Account?`;
|
||||
|
||||
@ -187,20 +148,31 @@ export default function SignupForm({
|
||||
)
|
||||
.then((res) => {
|
||||
if (res.success) {
|
||||
if (pageProps.user.id) {
|
||||
if (pageProps.user?.id) {
|
||||
window.location.pathname = `/admin/users`;
|
||||
} else {
|
||||
window.location.reload();
|
||||
}
|
||||
} else {
|
||||
window.alert(
|
||||
res.msg ||
|
||||
res.error ||
|
||||
`New User Creation Failed!`,
|
||||
);
|
||||
console.log("res", res);
|
||||
}
|
||||
})
|
||||
.finally(() => {});
|
||||
.finally(() => {
|
||||
setTimeout(() => {
|
||||
setLoading(false);
|
||||
}, 5000);
|
||||
});
|
||||
}}
|
||||
loading={loading}
|
||||
>
|
||||
{existing_user?.id
|
||||
? `Edit User`
|
||||
: pageProps.user.super_admin
|
||||
: pageProps.user?.super_admin
|
||||
? "Add User"
|
||||
: "Signup"}
|
||||
</Button>
|
||||
|
||||
@ -17,8 +17,6 @@ export default async function deleteDeploymentUser({ user_id }: Params) {
|
||||
targetId: _n(user_id),
|
||||
});
|
||||
|
||||
console.log("target_user_res", target_user_res);
|
||||
|
||||
const target_user = target_user_res.singleRes;
|
||||
|
||||
if (
|
||||
@ -31,6 +29,8 @@ export default async function deleteDeploymentUser({ user_id }: Params) {
|
||||
|
||||
const { username } = target_user;
|
||||
|
||||
console.log("username", username);
|
||||
|
||||
const { force_command_file, sshd_config_file } = grabDeploymentUserDirNames(
|
||||
{ user: target_user },
|
||||
);
|
||||
@ -38,12 +38,13 @@ export default async function deleteDeploymentUser({ user_id }: Params) {
|
||||
let cmd = `/bin/bash << 'TURBOCIHEREDOC'\n`;
|
||||
|
||||
// cmd += `userdel -r ${username}\n`;
|
||||
cmd += `killall -u ${username}\n`;
|
||||
cmd += `deluser --remove-all-files ${username}\n`;
|
||||
// cmd += `pkill -u ${username} || echo "User Deleted!"\n`;
|
||||
cmd += `deluser --remove-all-files ${username} || echo "User Deleted!"\n`;
|
||||
|
||||
cmd += `rm -f ${force_command_file}\n`;
|
||||
cmd += `rm -f ${sshd_config_file}\n`;
|
||||
cmd += `Match User ${username}\n`;
|
||||
|
||||
cmd += `TURBOCIHEREDOC\n`;
|
||||
|
||||
execSync(cmd);
|
||||
|
||||
|
||||
@ -18,11 +18,13 @@ export default async function setupDeploymentUser({ user_id }: Params) {
|
||||
targetId: _n(user_id),
|
||||
});
|
||||
|
||||
console.log("target_user_res", target_user_res);
|
||||
|
||||
const target_user = target_user_res.singleRes;
|
||||
|
||||
if (!target_user?.id || !target_user.username) {
|
||||
if (
|
||||
!target_user?.id ||
|
||||
!target_user.username ||
|
||||
target_user.is_super_admin
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -42,7 +44,7 @@ export default async function setupDeploymentUser({ user_id }: Params) {
|
||||
cmd += `useradd --create-home --shell /bin/bash --comment "TurboCI Deployment user ${username}" ${username}\n`;
|
||||
cmd += `passwd --lock "${username}"\n`;
|
||||
cmd += `mkdir -p "${ssh_dir}"\n`;
|
||||
cmd += `ssh-keygen -t ed25519 -f "${ssh_key_file}" -N ""\n`;
|
||||
cmd += `ssh-keygen -t ed25519 -f "${ssh_key_file}" -N "" -C "${username}@$(hostname)"\n`;
|
||||
cmd += `cp "${ssh_key_file}.pub" "${ssh_dir}/authorized_keys"\n`;
|
||||
|
||||
cmd += `chown -R "${username}:${username}" "${ssh_dir}"\n`;
|
||||
|
||||
@ -1,25 +1,34 @@
|
||||
import { User } from "@/src/types";
|
||||
import { TWUI_LINK_LIST_LINK_OBJECT } from "@/twui/components/elements/LinkList";
|
||||
|
||||
export const AdminAsideLinks: (
|
||||
| TWUI_LINK_LIST_LINK_OBJECT
|
||||
| TWUI_LINK_LIST_LINK_OBJECT[]
|
||||
| undefined
|
||||
)[] = [
|
||||
{
|
||||
title: "Dashboard",
|
||||
url: "/admin",
|
||||
strict: true,
|
||||
},
|
||||
{
|
||||
title: "Services",
|
||||
url: "/admin/services",
|
||||
},
|
||||
{
|
||||
title: "Users",
|
||||
url: "/admin/users",
|
||||
},
|
||||
{
|
||||
title: "Settings",
|
||||
url: "/admin/settings",
|
||||
},
|
||||
];
|
||||
type Params = { user?: User };
|
||||
|
||||
export function AdminAsideLinks({ user }: Params) {
|
||||
const links: (
|
||||
| TWUI_LINK_LIST_LINK_OBJECT
|
||||
| TWUI_LINK_LIST_LINK_OBJECT[]
|
||||
| undefined
|
||||
)[] = [
|
||||
{
|
||||
title: "Dashboard",
|
||||
url: "/admin",
|
||||
strict: true,
|
||||
},
|
||||
{
|
||||
title: "Services",
|
||||
url: "/admin/services",
|
||||
},
|
||||
user?.super_admin
|
||||
? {
|
||||
title: "Users",
|
||||
url: "/admin/users",
|
||||
}
|
||||
: undefined,
|
||||
{
|
||||
title: "Settings",
|
||||
url: "/admin/settings",
|
||||
},
|
||||
];
|
||||
|
||||
return links;
|
||||
}
|
||||
|
||||
@ -2,16 +2,18 @@ import LinkList from "@/twui/components/elements/LinkList";
|
||||
import Main from "@/twui/components/layout/Main";
|
||||
import Row from "@/twui/components/layout/Row";
|
||||
import Stack from "@/twui/components/layout/Stack";
|
||||
import { Fragment, PropsWithChildren } from "react";
|
||||
import { Fragment, PropsWithChildren, useContext } from "react";
|
||||
import { AdminAsideLinks } from "./(data)/links";
|
||||
import Header from "./header";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
import Spacer from "@/twui/components/layout/Spacer";
|
||||
import Head from "next/head";
|
||||
import { AppContext } from "@/src/pages/_app";
|
||||
|
||||
type Props = PropsWithChildren & {};
|
||||
|
||||
export default function Layout({ children }: Props) {
|
||||
const { pageProps } = useContext(AppContext);
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<Head>
|
||||
@ -26,7 +28,7 @@ export default function Layout({ children }: Props) {
|
||||
<Header />
|
||||
<Stack className="grid-cell col-span-6 xl:col-span-1 gap-0">
|
||||
<LinkList
|
||||
links={AdminAsideLinks}
|
||||
links={AdminAsideLinks({ user: pageProps.user })}
|
||||
className="w-full xl:flex-col"
|
||||
linkProps={{
|
||||
className: "turboci-admin-aside-link",
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import "@/src/styles/globals.css";
|
||||
import "prism-themes/themes/prism-dracula.css";
|
||||
import type { AppProps } from "next/app";
|
||||
import { createContext } from "react";
|
||||
import { PagePropsType, TurboCIAdminAppContextType } from "../types";
|
||||
|
||||
@ -1,6 +1,8 @@
|
||||
import Main from "@/src/components/pages/admin/settings";
|
||||
import { NSQLITE_TURBOCI_ADMIN_USERS } from "@/src/db/types";
|
||||
import defaultAdminProps from "@/src/functions/pages/admin/default-admin-props";
|
||||
import Layout from "@/src/layouts/admin";
|
||||
import NSQLite from "@moduletrace/nsqlite";
|
||||
import { GetServerSideProps } from "next";
|
||||
|
||||
export default function AdminSettings() {
|
||||
@ -12,5 +14,27 @@ export default function AdminSettings() {
|
||||
}
|
||||
|
||||
export const getServerSideProps: GetServerSideProps = async (ctx) => {
|
||||
return await defaultAdminProps({ ctx });
|
||||
return await defaultAdminProps({
|
||||
ctx,
|
||||
async propsFn({ user }) {
|
||||
if (!user.id) {
|
||||
return `/admin`;
|
||||
}
|
||||
|
||||
const users_res = await NSQLite.select<NSQLITE_TURBOCI_ADMIN_USERS>(
|
||||
{
|
||||
table: "users",
|
||||
targetId: user.id,
|
||||
},
|
||||
);
|
||||
|
||||
if (!users_res.singleRes?.id) {
|
||||
return `/admin/users`;
|
||||
}
|
||||
|
||||
return {
|
||||
deployment_user: users_res.singleRes,
|
||||
};
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
@ -29,6 +29,10 @@ export const getServerSideProps: GetServerSideProps = async (ctx) => {
|
||||
},
|
||||
);
|
||||
|
||||
if (!users_res.singleRes?.id) {
|
||||
return `/admin/users`;
|
||||
}
|
||||
|
||||
return {
|
||||
deployment_user: users_res.singleRes,
|
||||
};
|
||||
|
||||
@ -73,7 +73,7 @@ export default async function handler(
|
||||
email,
|
||||
password: new_user_password,
|
||||
is_super_admin: user?.id ? 0 : 1,
|
||||
username: slugify(username),
|
||||
username: username ? slugify(username) : undefined,
|
||||
},
|
||||
],
|
||||
table: "users",
|
||||
@ -90,20 +90,31 @@ export default async function handler(
|
||||
(typeof NSQLiteTables)[number]
|
||||
>({
|
||||
table: "users",
|
||||
query: {
|
||||
query: {
|
||||
is_super_admin: { value: "1" },
|
||||
},
|
||||
},
|
||||
targetId: new_user_id,
|
||||
});
|
||||
|
||||
const newly_inserted_user = newly_inserted_user_res.singleRes;
|
||||
|
||||
if (!newly_inserted_user?.id) {
|
||||
console.log("newly_inserted_user_res", newly_inserted_user_res);
|
||||
throw new Error(`Couldn't Find Newly inserted user.`);
|
||||
}
|
||||
|
||||
if (user?.id && !newly_inserted_user.username) {
|
||||
const newly_inserted_user_res = await NSQLite.delete<
|
||||
NSQLITE_TURBOCI_ADMIN_USERS,
|
||||
(typeof NSQLiteTables)[number]
|
||||
>({
|
||||
table: "users",
|
||||
targetId: newly_inserted_user?.id,
|
||||
});
|
||||
|
||||
throw new Error(`Couldn't set Newly inserted user username.`);
|
||||
}
|
||||
|
||||
if (user?.id) {
|
||||
console.log("newly_inserted_user", newly_inserted_user);
|
||||
|
||||
await setupDeploymentUser({ user_id: newly_inserted_user.id });
|
||||
|
||||
return res.json({
|
||||
|
||||
Loading…
Reference in New Issue
Block a user