Skip to content

Commit

Permalink
feat: add add new config strategy to frontend
Browse files Browse the repository at this point in the history
  • Loading branch information
stonith404 committed Nov 28, 2022
1 parent 1b5e53f commit 493705e
Show file tree
Hide file tree
Showing 20 changed files with 183 additions and 102 deletions.
18 changes: 0 additions & 18 deletions .env.example

This file was deleted.

15 changes: 0 additions & 15 deletions backend/.env.example

This file was deleted.

1 change: 1 addition & 0 deletions backend/src/auth/guard/isAdmin.guard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { User } from "@prisma/client";
export class AdministratorGuard implements CanActivate {
canActivate(context: ExecutionContext): boolean {
const { user }: { user: User } = context.switchToHttp().getRequest();
if (!user) return false;
return user.isAdministrator;
}
}
20 changes: 17 additions & 3 deletions backend/src/config/config.controller.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,32 @@
import { Controller, Get } from "@nestjs/common";
import { Body, Controller, Get, Param, Patch, UseGuards } from "@nestjs/common";
import { AdministratorGuard } from "src/auth/guard/isAdmin.guard";
import { ConfigService } from "./config.service";
import { AdminConfigDTO } from "./dto/adminConfig.dto";
import { ConfigDTO } from "./dto/config.dto";
import UpdateConfigDTO from "./dto/updateConfig.dto";

@Controller("configs")
export class ConfigController {
constructor(private configService: ConfigService) {}

@Get()
async list() {
return new ConfigDTO().fromList(await this.configService.list())
return new ConfigDTO().fromList(await this.configService.list());
}

@Get("admin")
@UseGuards(AdministratorGuard)
async listForAdmin() {
return await this.configService.listForAdmin();
return new AdminConfigDTO().fromList(
await this.configService.listForAdmin()
);
}

@Patch("admin/:key")
@UseGuards(AdministratorGuard)
async update(@Param("key") key: string, @Body() data: UpdateConfigDTO) {
return new AdminConfigDTO().from(
await this.configService.update(key, data.value)
);
}
}
26 changes: 25 additions & 1 deletion backend/src/config/config.service.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
import { Inject, Injectable } from "@nestjs/common";
import {
BadRequestException,
Inject,
Injectable,
NotFoundException,
} from "@nestjs/common";
import { Config } from "@prisma/client";
import { PrismaService } from "src/prisma/prisma.service";

Expand Down Expand Up @@ -38,4 +43,23 @@ export class ConfigService {
return configVariable;
});
}

async update(key: string, value: string | number | boolean) {
const configVariable = await this.prisma.config.findUnique({
where: { key },
});

if (!configVariable || configVariable.locked)
throw new NotFoundException("Config variable not found");

if (typeof value != configVariable.type)
throw new BadRequestException(
`Config variable must be of type ${configVariable.type}`
);

return await this.prisma.config.update({
where: { key },
data: { value: value.toString() },
});
}
}
23 changes: 23 additions & 0 deletions backend/src/config/dto/adminConfig.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { Expose, plainToClass } from "class-transformer";
import { ConfigDTO } from "./config.dto";

export class AdminConfigDTO extends ConfigDTO {
@Expose()
default: string;

@Expose()
secret: boolean;

@Expose()
updatedAt: Date;

from(partial: Partial<AdminConfigDTO>) {
return plainToClass(AdminConfigDTO, partial, { excludeExtraneousValues: true });
}

fromList(partial: Partial<AdminConfigDTO>[]) {
return partial.map((part) =>
plainToClass(AdminConfigDTO, part, { excludeExtraneousValues: true })
);
}
}
8 changes: 8 additions & 0 deletions backend/src/config/dto/updateConfig.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { IsNotEmpty } from "class-validator";

class UpdateConfigDTO {
@IsNotEmpty()
value: string | number | boolean;
}

export default UpdateConfigDTO;
5 changes: 0 additions & 5 deletions frontend/.env.example

This file was deleted.

11 changes: 1 addition & 10 deletions frontend/next.config.js
Original file line number Diff line number Diff line change
@@ -1,19 +1,10 @@
/** @type {import('next').NextConfig} */

const nextConfig = {
publicRuntimeConfig: {
ALLOW_REGISTRATION: process.env.ALLOW_REGISTRATION,
SHOW_HOME_PAGE: process.env.SHOW_HOME_PAGE,
MAX_FILE_SIZE: process.env.MAX_FILE_SIZE,
ALLOW_UNAUTHENTICATED_SHARES: process.env.ALLOW_UNAUTHENTICATED_SHARES,
EMAIL_RECIPIENTS_ENABLED: process.env.EMAIL_RECIPIENTS_ENABLED
}
}

const withPWA = require("next-pwa")({
dest: "public",
disable: process.env.NODE_ENV == "development"
});


module.exports = withPWA(nextConfig);
module.exports = withPWA();
8 changes: 4 additions & 4 deletions frontend/src/components/auth/AuthForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,15 @@ import {
Title,
} from "@mantine/core";
import { useForm, yupResolver } from "@mantine/form";
import getConfig from "next/config";
import Link from "next/link";
import * as yup from "yup";
import useConfig from "../../hooks/config.hook";
import authService from "../../services/auth.service";
import toast from "../../utils/toast.util";

const { publicRuntimeConfig } = getConfig();

const AuthForm = ({ mode }: { mode: "signUp" | "signIn" }) => {
const config = useConfig();

const validationSchema = yup.object().shape({
email: yup.string().email().required(),
password: yup.string().min(8).required(),
Expand Down Expand Up @@ -55,7 +55,7 @@ const AuthForm = ({ mode }: { mode: "signUp" | "signIn" }) => {
>
{mode == "signUp" ? "Sign up" : "Welcome back"}
</Title>
{publicRuntimeConfig.ALLOW_REGISTRATION == "true" && (
{config.get("allowRegistration") && (
<Text color="dimmed" size="sm" align="center" mt={5}>
{mode == "signUp"
? "You have an account already?"
Expand Down
10 changes: 5 additions & 5 deletions frontend/src/components/navBar/NavBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,13 @@ import {
Transition,
} from "@mantine/core";
import { useDisclosure } from "@mantine/hooks";
import getConfig from "next/config";
import Link from "next/link";
import { ReactNode, useEffect, useState } from "react";
import useConfig from "../../hooks/config.hook";
import useUser from "../../hooks/user.hook";
import Logo from "../Logo";
import ActionAvatar from "./ActionAvatar";

const { publicRuntimeConfig } = getConfig();

const HEADER_HEIGHT = 60;

type NavLink = {
Expand Down Expand Up @@ -110,6 +108,8 @@ const useStyles = createStyles((theme) => ({

const NavBar = () => {
const user = useUser();
const config = useConfig();

const [opened, toggleOpened] = useDisclosure(false);

const authenticatedLinks = [
Expand All @@ -130,7 +130,7 @@ const NavBar = () => {
]);

useEffect(() => {
if (publicRuntimeConfig.SHOW_HOME_PAGE == "true")
if (config.get("showHomePage"))
setUnauthenticatedLinks((array) => [
{
link: "/",
Expand All @@ -139,7 +139,7 @@ const NavBar = () => {
...array,
]);

if (publicRuntimeConfig.ALLOW_REGISTRATION == "true")
if (config.get("allowRegistration"))
setUnauthenticatedLinks((array) => [
...array,
{
Expand Down
11 changes: 5 additions & 6 deletions frontend/src/components/upload/Dropzone.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
import { Button, Center, createStyles, Group, Text } from "@mantine/core";
import { Dropzone as MantineDropzone } from "@mantine/dropzone";
import getConfig from "next/config";
import { Dispatch, ForwardedRef, SetStateAction, useRef } from "react";
import { TbCloudUpload, TbUpload } from "react-icons/tb";
import useConfig from "../../hooks/config.hook";
import { FileUpload } from "../../types/File.type";
import { byteStringToHumanSizeString } from "../../utils/math/byteStringToHumanSizeString.util";
import toast from "../../utils/toast.util";

const { publicRuntimeConfig } = getConfig();

const useStyles = createStyles((theme) => ({
wrapper: {
position: "relative",
Expand Down Expand Up @@ -40,12 +38,14 @@ const Dropzone = ({
isUploading: boolean;
setFiles: Dispatch<SetStateAction<FileUpload[]>>;
}) => {
const config = useConfig();

const { classes } = useStyles();
const openRef = useRef<() => void>();
return (
<div className={classes.wrapper}>
<MantineDropzone
maxSize={parseInt(publicRuntimeConfig.MAX_FILE_SIZE!)}
maxSize={parseInt(config.get("maxFileSize"))}
onReject={(e) => {
toast.error(e[0].errors[0].message);
}}
Expand Down Expand Up @@ -75,8 +75,7 @@ const Dropzone = ({
<Text align="center" size="sm" mt="xs" color="dimmed">
Drag&apos;n&apos;drop files here to start your share. We can accept
only files that are less than{" "}
{byteStringToHumanSizeString(publicRuntimeConfig.MAX_FILE_SIZE)} in
size.
{byteStringToHumanSizeString(config.get("maxFileSize"))} in size.
</Text>
</div>
</MantineDropzone>
Expand Down
25 changes: 15 additions & 10 deletions frontend/src/components/upload/modals/showCreateUploadModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,19 +18,20 @@ import {
import { useForm, yupResolver } from "@mantine/form";
import { useModals } from "@mantine/modals";
import { ModalsContextProps } from "@mantine/modals/lib/context";
import getConfig from "next/config";
import { useState } from "react";
import { TbAlertCircle } from "react-icons/tb";
import * as yup from "yup";
import shareService from "../../../services/share.service";
import { ShareSecurity } from "../../../types/share.type";
import ExpirationPreview from "../ExpirationPreview";

const { publicRuntimeConfig } = getConfig();

const showCreateUploadModal = (
modals: ModalsContextProps,
isSignedIn: boolean,
options: {
isUserSignedIn: boolean;
allowUnauthenticatedShares: boolean;
emailRecipientsEnabled: boolean;
},
uploadCallback: (
id: string,
expiration: string,
Expand All @@ -42,7 +43,7 @@ const showCreateUploadModal = (
title: <Title order={4}>Share</Title>,
children: (
<CreateUploadModalBody
isSignedIn={isSignedIn}
options={options}
uploadCallback={uploadCallback}
/>
),
Expand All @@ -51,20 +52,24 @@ const showCreateUploadModal = (

const CreateUploadModalBody = ({
uploadCallback,
isSignedIn,
options,
}: {
uploadCallback: (
id: string,
expiration: string,
recipients: string[],
security: ShareSecurity
) => void;
isSignedIn: boolean;
options: {
isUserSignedIn: boolean;
allowUnauthenticatedShares: boolean;
emailRecipientsEnabled: boolean;
};
}) => {
const modals = useModals();

const [showNotSignedInAlert, setShowNotSignedInAlert] = useState(
publicRuntimeConfig.ALLOW_UNAUTHENTICATED_SHARES == "true"
options.emailRecipientsEnabled
);

const validationSchema = yup.object().shape({
Expand Down Expand Up @@ -93,7 +98,7 @@ const CreateUploadModalBody = ({
});
return (
<Group>
{showNotSignedInAlert && !isSignedIn && (
{showNotSignedInAlert && !options.isUserSignedIn && (
<Alert
withCloseButton
onClose={() => setShowNotSignedInAlert(false)}
Expand Down Expand Up @@ -225,7 +230,7 @@ const CreateUploadModalBody = ({
{ExpirationPreview({ form })}
</Text>
<Accordion>
{publicRuntimeConfig.EMAIL_RECIPIENTS_ENABLED == "true" && (
{options.emailRecipientsEnabled && (
<Accordion.Item value="recipients" sx={{ borderBottom: "none" }}>
<Accordion.Control>Email recipients</Accordion.Control>
<Accordion.Panel>
Expand Down
14 changes: 14 additions & 0 deletions frontend/src/hooks/config.hook.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { createContext, useContext } from "react";
import configService from "../services/config.service";
import Config from "../types/config.type";

export const ConfigContext = createContext<Config[] | null>(null);

const useConfig = () => {
const configVariables = useContext(ConfigContext) as Config[];
return {
get: (key: string) => configService.get(key, configVariables),
};
};

export default useConfig;
Loading

0 comments on commit 493705e

Please sign in to comment.