Преглед на файлове

Merge branch 'main' of https://github.com/fosrl/pangolin

Milo Schwartz преди 8 месеца
родител
ревизия
41e1c7e859

+ 8 - 7
server/db/ensureActions.ts

@@ -54,13 +54,14 @@ export async function createSuperuserRole(orgId: number) {
 
 
     const roleId = insertedRole.roleId;
     const roleId = insertedRole.roleId;
 
 
-    // Add all current actions to the new Default role
-    const actionIds = Object.values(ActionsEnum);
+    const actionIds = await db.select().from(actions).execute();
+
+    if (actionIds.length === 0) {
+        logger.info('No actions to assign to the Superuser role');
+        return;
+    }
+
     await db.insert(roleActions)
     await db.insert(roleActions)
-        .values(actionIds.map(actionId => ({
-            roleId,
-            actionId: actionId,
-            orgId
-        })))
+        .values(actionIds.map(action => ({ roleId, actionId: action.actionId, orgId })))
         .execute();
         .execute();
 }
 }

+ 1 - 1
server/routers/resource/createResource.ts

@@ -11,7 +11,7 @@ import { eq, and } from 'drizzle-orm';
 
 
 const createResourceParamsSchema = z.object({
 const createResourceParamsSchema = z.object({
     siteId: z.number().int().positive(),
     siteId: z.number().int().positive(),
-    orgId: z.number().int().positive(),
+    orgId: z.string().transform(Number).pipe(z.number().int().positive())
 });
 });
 
 
 // Define Zod schema for request body validation
 // Define Zod schema for request body validation

+ 1 - 1
server/routers/site/createSite.ts

@@ -13,7 +13,7 @@ import { eq, and } from 'drizzle-orm';
 const API_BASE_URL = "http://localhost:3000";
 const API_BASE_URL = "http://localhost:3000";
 
 
 const createSiteParamsSchema = z.object({
 const createSiteParamsSchema = z.object({
-    orgId: z.number().int().positive(),
+    orgId: z.string().transform(Number).pipe(z.number().int().positive())
 });
 });
 
 
 // Define Zod schema for request body validation
 // Define Zod schema for request body validation

+ 8 - 0
server/routers/site/getSite.ts

@@ -14,6 +14,14 @@ const getSiteSchema = z.object({
     siteId: z.string().transform(Number).pipe(z.number().int().positive())
     siteId: z.string().transform(Number).pipe(z.number().int().positive())
 });
 });
 
 
+export type GetSiteResponse = {
+    siteId: number;
+    name: string;
+    subdomain: string;
+    pubKey: string;
+    subnet: string;
+}
+
 export async function getSite(req: Request, res: Response, next: NextFunction): Promise<any> {
 export async function getSite(req: Request, res: Response, next: NextFunction): Promise<any> {
     try {
     try {
         // Validate request parameters
         // Validate request parameters

+ 1 - 1
server/routers/user/addUserOrg.ts

@@ -11,7 +11,7 @@ import logger from '@server/logger';
 
 
 const addUserParamsSchema = z.object({
 const addUserParamsSchema = z.object({
     userId: z.string().uuid(),
     userId: z.string().uuid(),
-    orgId: z.number().int().positive(),
+    orgId: z.string().transform(Number).pipe(z.number().int().positive())
 });
 });
 
 
 const addUserSchema = z.object({
 const addUserSchema = z.object({

+ 1 - 1
server/routers/user/removeUserAction.ts

@@ -15,7 +15,7 @@ const removeUserActionParamsSchema = z.object({
 
 
 const removeUserActionSchema = z.object({
 const removeUserActionSchema = z.object({
     actionId: z.string(),
     actionId: z.string(),
-    orgId: z.number().int().positive(),
+    orgId: z.string().transform(Number).pipe(z.number().int().positive())
 });
 });
 
 
 export async function removeUserAction(req: Request, res: Response, next: NextFunction): Promise<any> {
 export async function removeUserAction(req: Request, res: Response, next: NextFunction): Promise<any> {

+ 1 - 1
server/routers/user/removeUserOrg.ts

@@ -11,7 +11,7 @@ import logger from '@server/logger';
 
 
 const removeUserSchema = z.object({
 const removeUserSchema = z.object({
     userId: z.string().uuid(),
     userId: z.string().uuid(),
-    orgId: z.number().int().positive(),
+    orgId: z.string().transform(Number).pipe(z.number().int().positive())
 });
 });
 
 
 export async function removeUserOrg(req: Request, res: Response, next: NextFunction): Promise<any> {
 export async function removeUserOrg(req: Request, res: Response, next: NextFunction): Promise<any> {

+ 1 - 1
server/routers/user/setUserRole.ts

@@ -12,7 +12,7 @@ import logger from '@server/logger';
 const addUserRoleSchema = z.object({
 const addUserRoleSchema = z.object({
     userId: z.string(),
     userId: z.string(),
     roleId: z.number().int().positive(),
     roleId: z.number().int().positive(),
-    orgId: z.number().int().positive(),
+    orgId: z.string().transform(Number).pipe(z.number().int().positive())
 });
 });
 
 
 export async function addUserRole(req: Request, res: Response, next: NextFunction): Promise<any> {
 export async function addUserRole(req: Request, res: Response, next: NextFunction): Promise<any> {

+ 67 - 33
src/app/[orgId]/sites/[siteId]/components/create-site.tsx

@@ -35,6 +35,8 @@ import { generateKeypair } from "./wireguard-config";
 import React, { useState, useEffect } from "react";
 import React, { useState, useEffect } from "react";
 import { api } from "@/api";
 import { api } from "@/api";
 import { AxiosResponse } from "axios"
 import { AxiosResponse } from "axios"
+import { useParams } from "next/navigation";
+import { useRouter } from "next/navigation";
 
 
 const method = [
 const method = [
     { label: "Wireguard", value: "wg" },
     { label: "Wireguard", value: "wg" },
@@ -50,6 +52,16 @@ const accountFormSchema = z.object({
         .max(30, {
         .max(30, {
             message: "Name must not be longer than 30 characters.",
             message: "Name must not be longer than 30 characters.",
         }),
         }),
+    subdomain: z
+        .string()
+        // cant be too long and cant have spaces or special characters
+        .regex(/^[a-zA-Z0-9-]+$/)
+        .min(2, {
+            message: "Subdomain must be at least 2 characters.",
+        })
+        .max(30, {
+            message: "Subdomain must not be longer than 30 characters.",
+        }),
     method: z.string({
     method: z.string({
         required_error: "Please select a method.",
         required_error: "Please select a method.",
     }),
     }),
@@ -67,6 +79,10 @@ export function CreateSiteForm() {
     const [keypair, setKeypair] = useState<{ publicKey: string; privateKey: string } | null>(null);
     const [keypair, setKeypair] = useState<{ publicKey: string; privateKey: string } | null>(null);
     const [isLoading, setIsLoading] = useState(true);
     const [isLoading, setIsLoading] = useState(true);
 
 
+    const params = useParams();
+    const orgId = params.orgId;
+    const router = useRouter();
+
     const form = useForm<AccountFormValues>({
     const form = useForm<AccountFormValues>({
         resolver: zodResolver(accountFormSchema),
         resolver: zodResolver(accountFormSchema),
         defaultValues,
         defaultValues,
@@ -80,28 +96,30 @@ export function CreateSiteForm() {
         }
         }
     }, []);
     }, []);
 
 
+    const name = form.watch("name");
+    useEffect(() => {
+        const subdomain = name.toLowerCase().replace(/\s+/g, "-");
+        form.setValue("subdomain", subdomain, { shouldValidate: true });
+    }, [name, form]);
+
     async function onSubmit(data: AccountFormValues) {
     async function onSubmit(data: AccountFormValues) {
-        toast({
-            title: "You submitted the following values:",
-            description: (
-                <pre className="mt-2 w-[340px] rounded-md bg-slate-950 p-4">
-                    <code className="text-white">{JSON.stringify(data, null, 2)}</code>
-                </pre>
-            ),
-        });
-        // const res = await api
-        // .post<AxiosResponse<any>>(`/org/:orgId/site/:siteId/resource`, {
-        //     email,
-        //     password,
-        // })
-        // .catch((e) => {
-        //     console.error(e);
-        //     setError(
-        //         e.response?.data?.message ||
-        //             "An error occurred while logging in",
-        //     );
-        // });
+        const res = await api
+            .put(`/org/${orgId}/site/`, {
+                name: data.name,
+                subdomain: data.subdomain,
+                pubKey: keypair?.publicKey,
+            })
+            .catch((e) => {
+                toast({
+                    title: "Error creating site..."
+                });
+            });
 
 
+        if (res && res.status === 201) {
+            const siteId = res.data.data.siteId;
+            // navigate to the site page
+            router.push(`/${orgId}/sites/${siteId}`);
+        }
     }
     }
 
 
     const wgConfig = keypair
     const wgConfig = keypair
@@ -140,6 +158,22 @@ sh get-docker.sh`;
                             </FormItem>
                             </FormItem>
                         )}
                         )}
                     />
                     />
+                    <FormField
+                        control={form.control}
+                        name="subdomain"
+                        render={({ field }) => (
+                            <FormItem>
+                                <FormLabel>Subdomain</FormLabel>
+                                <FormControl>
+                                    <Input {...field} />
+                                </FormControl>
+                                <FormDescription>
+                                    The subdomain of the site. This will be used to access resources on the site.
+                                </FormDescription>
+                                <FormMessage />
+                            </FormItem>
+                        )}
+                    />
                     <FormField
                     <FormField
                         control={form.control}
                         control={form.control}
                         name="method"
                         name="method"
@@ -159,9 +193,9 @@ sh get-docker.sh`;
                                             >
                                             >
                                                 {field.value
                                                 {field.value
                                                     ? method.find(
                                                     ? method.find(
-                                                        (language) => language.value === field.value
+                                                        (method) => method.value === field.value
                                                     )?.label
                                                     )?.label
-                                                    : "Select language"}
+                                                    : "Select method"}
                                                 <CaretSortIcon className="ml-2 h-4 w-4 shrink-0 opacity-50" />
                                                 <CaretSortIcon className="ml-2 h-4 w-4 shrink-0 opacity-50" />
                                             </Button>
                                             </Button>
                                         </FormControl>
                                         </FormControl>
@@ -204,17 +238,17 @@ sh get-docker.sh`;
                             </FormItem>
                             </FormItem>
                         )}
                         )}
                     />
                     />
-            {methodValue === "wg" && !isLoading ? (
-                <pre className="mt-2 w-full rounded-md bg-slate-950 p-4 overflow-x-auto">
-                    <code className="text-white whitespace-pre-wrap">{wgConfig}</code>
-                </pre>
-            ) : methodValue === "wg" && isLoading ? (
-                <p>Loading WireGuard configuration...</p>
-            ) : (
-                <pre className="mt-2 w-full rounded-md bg-slate-950 p-4 overflow-x-auto">
-                <code className="text-white whitespace-pre-wrap">{newtConfig}</code>
-              </pre>
-            )}
+                    {methodValue === "wg" && !isLoading ? (
+                        <pre className="mt-2 w-full rounded-md bg-slate-950 p-4 overflow-x-auto">
+                            <code className="text-white whitespace-pre-wrap">{wgConfig}</code>
+                        </pre>
+                    ) : methodValue === "wg" && isLoading ? (
+                        <p>Loading WireGuard configuration...</p>
+                    ) : (
+                        <pre className="mt-2 w-full rounded-md bg-slate-950 p-4 overflow-x-auto">
+                            <code className="text-white whitespace-pre-wrap">{newtConfig}</code>
+                        </pre>
+                    )}
                     <Button type="submit">Create Site</Button>
                     <Button type="submit">Create Site</Button>
                 </form>
                 </form>
             </Form>
             </Form>

+ 25 - 2
src/app/[orgId]/sites/[siteId]/layout.tsx

@@ -3,6 +3,11 @@ import Image from "next/image"
 
 
 import { Separator } from "@/components/ui/separator"
 import { Separator } from "@/components/ui/separator"
 import { SidebarNav } from "@/components/sidebar-nav"
 import { SidebarNav } from "@/components/sidebar-nav"
+import SiteProvider from "@app/providers/SiteProvider"
+import { internal } from "@app/api"
+import { cookies } from "next/headers"
+import { GetSiteResponse } from "@server/routers/site"
+import { AxiosResponse } from "axios"
 
 
 export const metadata: Metadata = {
 export const metadata: Metadata = {
     title: "Forms",
     title: "Forms",
@@ -37,7 +42,21 @@ interface SettingsLayoutProps {
     params: { siteId: string }
     params: { siteId: string }
 }
 }
 
 
-export default function SettingsLayout({ children, params }: SettingsLayoutProps) {
+export default async function SettingsLayout({ children, params }: SettingsLayoutProps) {
+    const sessionId = cookies().get("session")?.value ?? null;
+    const res = await internal
+    .get<AxiosResponse<GetSiteResponse>>(`/site/${params.siteId}`,          {
+        headers: {
+            Cookie: `session=${sessionId}`,
+        },
+    });
+
+    if (!res || res.status !== 200) {
+        return <div>Failed to load site</div>;
+    }
+
+    const site = res.data.data;
+
     return (
     return (
         <>
         <>
             <div className="md:hidden">
             <div className="md:hidden">
@@ -68,7 +87,11 @@ export default function SettingsLayout({ children, params }: SettingsLayoutProps
                     <aside className="-mx-4 lg:w-1/5">
                     <aside className="-mx-4 lg:w-1/5">
                         <SidebarNav items={sidebarNavItems.map(i => { i.href = i.href.replace("{siteId}", params.siteId); return i})} disabled={params.siteId == "create"} />
                         <SidebarNav items={sidebarNavItems.map(i => { i.href = i.href.replace("{siteId}", params.siteId); return i})} disabled={params.siteId == "create"} />
                     </aside>
                     </aside>
-                    <div className="flex-1 lg:max-w-2xl">{children}</div>
+                    <div className="flex-1 lg:max-w-2xl">
+                    <SiteProvider site={site}>
+                        {children}
+                    </SiteProvider>
+                        </div>
                 </div>
                 </div>
             </div>
             </div>
         </>
         </>

+ 0 - 0
src/components/SiteForm.tsx


+ 5 - 0
src/components/appearance-form.tsx

@@ -18,6 +18,7 @@ import {
   FormMessage,
   FormMessage,
 } from "@/components/ui/form"
 } from "@/components/ui/form"
 import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group"
 import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group"
+import { useSiteContext } from "@app/hooks/useSiteContext"
 
 
 const appearanceFormSchema = z.object({
 const appearanceFormSchema = z.object({
   theme: z.enum(["light", "dark"], {
   theme: z.enum(["light", "dark"], {
@@ -37,6 +38,10 @@ const defaultValues: Partial<AppearanceFormValues> = {
 }
 }
 
 
 export function AppearanceForm() {
 export function AppearanceForm() {
+    const site = useSiteContext();
+
+    console.log(site);
+    
   const form = useForm<AppearanceFormValues>({
   const form = useForm<AppearanceFormValues>({
     resolver: zodResolver(appearanceFormSchema),
     resolver: zodResolver(appearanceFormSchema),
     defaultValues,
     defaultValues,

+ 4 - 0
src/contexts/siteContext.ts

@@ -0,0 +1,4 @@
+import { GetSiteResponse } from "@server/routers/site/getSite";
+import { createContext } from "react";
+
+export const SiteContext = createContext<GetSiteResponse | null>(null);

+ 7 - 0
src/hooks/useSiteContext.ts

@@ -0,0 +1,7 @@
+import { SiteContext } from "@app/contexts/siteContext";
+import { useContext } from "react";
+
+export function useSiteContext() {
+    const site = useContext(SiteContext);
+    return site;
+}

+ 16 - 0
src/providers/SiteProvider.tsx

@@ -0,0 +1,16 @@
+"use client";
+
+import { SiteContext } from "@app/contexts/siteContext";
+import { GetSiteResponse } from "@server/routers/site/getSite";
+import { ReactNode } from "react";
+
+type LandingProviderProps = {
+    site: GetSiteResponse;
+    children: ReactNode;
+};
+
+export function SiteProvider({ site, children }: LandingProviderProps) {
+    return <SiteContext.Provider value={site}>{children}</SiteContext.Provider>;
+}
+
+export default SiteProvider;