mirror of
https://github.com/diced/zipline.git
synced 2025-12-12 15:50:11 -08:00
featL settings, view routes, embeds, etc.
This commit is contained in:
@@ -28,9 +28,9 @@
|
||||
"@mantine/next": "^6.0.14",
|
||||
"@mantine/notifications": "^6.0.14",
|
||||
"@mantine/nprogress": "^6.0.14",
|
||||
"@prisma/client": "4.16.1",
|
||||
"@prisma/internals": "^4.16.1",
|
||||
"@prisma/migrate": "^4.16.1",
|
||||
"@prisma/client": "^5.0.0",
|
||||
"@prisma/internals": "^5.0.0",
|
||||
"@prisma/migrate": "^5.0.0",
|
||||
"@tabler/icons-react": "^2.23.0",
|
||||
"argon2": "^0.30.3",
|
||||
"bytes": "^3.1.2",
|
||||
@@ -38,6 +38,7 @@
|
||||
"dayjs": "^1.11.8",
|
||||
"express": "^4.18.2",
|
||||
"highlight.js": "^11.8.0",
|
||||
"isomorphic-dompurify": "^1.8.0",
|
||||
"katex": "^0.16.8",
|
||||
"mantine-datatable": "^2.8.2",
|
||||
"ms": "^2.1.3",
|
||||
@@ -67,7 +68,7 @@
|
||||
"dotenv": "^16.1.3",
|
||||
"eslint": "^8.41.0",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"prisma": "^4.16.1",
|
||||
"prisma": "^5.0.0",
|
||||
"tsup": "^7.0.0",
|
||||
"typescript": "^5.1.3"
|
||||
},
|
||||
|
||||
@@ -20,11 +20,12 @@ model User {
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
username String @unique
|
||||
password String?
|
||||
avatar String?
|
||||
token String @unique
|
||||
role Role @default(USER)
|
||||
username String @unique
|
||||
password String?
|
||||
avatar String?
|
||||
token String @unique
|
||||
role Role @default(USER)
|
||||
view Json @default("{}")
|
||||
|
||||
files File[]
|
||||
urls Url[]
|
||||
|
||||
@@ -44,6 +44,7 @@ import { useRouter } from 'next/router';
|
||||
import { useState } from 'react';
|
||||
import ConfigProvider from './ConfigProvider';
|
||||
import { isAdministrator } from '@/lib/role';
|
||||
import useAvatar from '@/lib/hooks/useAvatar';
|
||||
|
||||
type NavLinks = {
|
||||
label: string;
|
||||
@@ -117,6 +118,7 @@ export default function Layout({ children, config }: { children: React.ReactNode
|
||||
const [setUser, setToken] = useUserStore((s) => [s.setUser, s.setToken]);
|
||||
|
||||
const { user, mutate } = useLogin();
|
||||
const { avatar } = useAvatar();
|
||||
|
||||
const copyToken = () => {
|
||||
modals.openConfirmModal({
|
||||
@@ -279,9 +281,11 @@ export default function Layout({ children, config }: { children: React.ReactNode
|
||||
variant='subtle'
|
||||
color='gray'
|
||||
leftIcon={
|
||||
<Avatar size='sm' src='/api/user/avatar' radius='sm'>
|
||||
<IconSettingsFilled size='1.4rem' />
|
||||
</Avatar>
|
||||
avatar ? (
|
||||
<Avatar src={avatar} radius='sm' size='sm' alt={user?.username ?? 'User avatar'} />
|
||||
) : (
|
||||
<IconSettingsFilled size='1rem' />
|
||||
)
|
||||
}
|
||||
rightIcon={<IconChevronDown size='0.7rem' />}
|
||||
size='sm'
|
||||
|
||||
@@ -46,27 +46,36 @@ export default function DashboardFileType({
|
||||
show,
|
||||
password,
|
||||
disableMediaPreview,
|
||||
code,
|
||||
}: {
|
||||
file: DbFile | File;
|
||||
show?: boolean;
|
||||
password?: string;
|
||||
disableMediaPreview?: boolean;
|
||||
code?: boolean;
|
||||
}) {
|
||||
const type = file.type.split('/')[0];
|
||||
const dbFile = 'id' in file;
|
||||
const renderIn = renderMode(file.name.split('.').pop() || '');
|
||||
|
||||
const [fileContent, setFileContent] = useState('');
|
||||
const [type, setType] = useState(file.type.split('/')[0]);
|
||||
|
||||
const gettext = async () => {
|
||||
const res = await fetch(`/raw/${file.name}${password ? `?pw=${password}` : ''}`);
|
||||
const text = await res.text();
|
||||
|
||||
setFileContent(text);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (type !== 'text') return;
|
||||
|
||||
(async () => {
|
||||
const res = await fetch(`/raw/${file.name}${password ? `?pw=${password}` : ''}`);
|
||||
const text = await res.text();
|
||||
|
||||
setFileContent(text);
|
||||
})();
|
||||
if (code) {
|
||||
setType('text');
|
||||
gettext();
|
||||
} else if (type === 'text') {
|
||||
gettext();
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}, []);
|
||||
|
||||
if (disableMediaPreview)
|
||||
|
||||
@@ -45,7 +45,7 @@ export function useApiPagination(
|
||||
page: 1,
|
||||
}
|
||||
) {
|
||||
const { data, error, isLoading, mutate } = useSWR<Response['/api/user/files']>(
|
||||
const { data, error, isLoading, mutate } = useSWR<Response['/api/user/files']>(
|
||||
{ key: `/api/user/files`, options },
|
||||
fetcher
|
||||
);
|
||||
|
||||
31
src/components/pages/settings/index.tsx
Normal file
31
src/components/pages/settings/index.tsx
Normal file
@@ -0,0 +1,31 @@
|
||||
import { Group, SimpleGrid, Title } from '@mantine/core';
|
||||
import SettingsAvatar from './parts/SettingsAvatar';
|
||||
import SettingsDashboard from './parts/SettingsDashboard';
|
||||
import SettingsUser from './parts/SettingsUser';
|
||||
import SettingsFileView from './parts/SettingsFileView';
|
||||
import { useConfig } from '@/components/ConfigProvider';
|
||||
import SettingsOAuth from './parts/SettingsOAuth';
|
||||
|
||||
export default function DashboardSettings() {
|
||||
const config = useConfig();
|
||||
|
||||
return (
|
||||
<>
|
||||
<Group spacing='sm'>
|
||||
<Title order={1}>Settings</Title>
|
||||
</Group>
|
||||
|
||||
<SimpleGrid mt='md' cols={2} spacing='lg' breakpoints={[{ maxWidth: 'sm', cols: 1 }]}>
|
||||
<SettingsUser />
|
||||
|
||||
<SettingsDashboard />
|
||||
|
||||
<SettingsAvatar />
|
||||
|
||||
<SettingsFileView />
|
||||
|
||||
{config.features.oauthRegistration && <SettingsOAuth />}
|
||||
</SimpleGrid>
|
||||
</>
|
||||
);
|
||||
}
|
||||
148
src/components/pages/settings/parts/SettingsAvatar.tsx
Normal file
148
src/components/pages/settings/parts/SettingsAvatar.tsx
Normal file
@@ -0,0 +1,148 @@
|
||||
import { Response } from '@/lib/api/response';
|
||||
import { fetchApi } from '@/lib/fetchApi';
|
||||
import useAvatar from '@/lib/hooks/useAvatar';
|
||||
import { readToDataURL } from '@/lib/readToDataURL';
|
||||
import { useUserStore } from '@/lib/store/user';
|
||||
import { Avatar, Button, Card, FileInput, Group, Paper, Stack, Text, Title } from '@mantine/core';
|
||||
import { notifications } from '@mantine/notifications';
|
||||
import { IconChevronDown, IconPhoto, IconPhotoCancel, IconSettingsFilled } from '@tabler/icons-react';
|
||||
import { useRouter } from 'next/router';
|
||||
import { useEffect, useState } from 'react';
|
||||
import useSWR from 'swr';
|
||||
|
||||
export default function SettingsAvatar() {
|
||||
const router = useRouter();
|
||||
const user = useUserStore((state) => state.user);
|
||||
|
||||
const { avatar: currentAvatar, mutate } = useAvatar();
|
||||
const [avatar, setAvatar] = useState<File | null>(null);
|
||||
const [avatarSrc, setAvatarSrc] = useState<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
if (!avatar) return;
|
||||
|
||||
const base64url = await readToDataURL(avatar);
|
||||
setAvatarSrc(base64url);
|
||||
})();
|
||||
}, [avatar]);
|
||||
|
||||
const saveAvatar = async () => {
|
||||
if (!avatar) return;
|
||||
|
||||
const base64url = await readToDataURL(avatar);
|
||||
const { data, error } = await fetchApi<Response['/api/user']>(`/api/user`, 'PATCH', {
|
||||
avatar: base64url,
|
||||
});
|
||||
|
||||
if (!data && error) {
|
||||
notifications.show({
|
||||
title: 'Error while updating avatar',
|
||||
message: error.message,
|
||||
color: 'red',
|
||||
icon: <IconPhotoCancel size='1rem' />,
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
notifications.show({
|
||||
message: 'Avatar updated',
|
||||
color: 'green',
|
||||
icon: <IconPhoto size='1rem' />,
|
||||
});
|
||||
|
||||
setAvatar(null);
|
||||
setAvatarSrc(null);
|
||||
mutate(base64url);
|
||||
};
|
||||
|
||||
const clearAvatar = async () => {
|
||||
const { data, error } = await fetchApi<Response['/api/user']>(`/api/user`, 'PATCH', {
|
||||
avatar: null,
|
||||
});
|
||||
|
||||
if (!data && error) {
|
||||
notifications.show({
|
||||
title: 'Error while updating avatar',
|
||||
message: error.message,
|
||||
color: 'red',
|
||||
icon: <IconPhotoCancel size='1rem' />,
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
notifications.show({
|
||||
message: 'Avatar updated',
|
||||
color: 'green',
|
||||
icon: <IconPhoto size='1rem' />,
|
||||
});
|
||||
|
||||
setAvatar(null);
|
||||
setAvatarSrc(null);
|
||||
mutate(undefined);
|
||||
};
|
||||
|
||||
return (
|
||||
<Paper withBorder p='sm'>
|
||||
<Title order={2}>Avatar</Title>
|
||||
|
||||
<Stack spacing='sm'>
|
||||
<FileInput
|
||||
accept='image/*'
|
||||
placeholder='Upload new avatar...'
|
||||
value={avatar}
|
||||
onChange={(file) => setAvatar(file)}
|
||||
/>
|
||||
|
||||
<Card withBorder shadow='sm'>
|
||||
<Text size='sm' color='dimmed'>
|
||||
Preview of {avatar ? 'new' : 'current'} avatar
|
||||
</Text>
|
||||
|
||||
<Button
|
||||
variant='subtle'
|
||||
color='gray'
|
||||
leftIcon={
|
||||
avatarSrc ? (
|
||||
<Avatar src={avatarSrc} radius='sm' size='sm' alt={user?.username ?? 'Proposed avatar'} />
|
||||
) : currentAvatar ? (
|
||||
<Avatar src={currentAvatar} radius='sm' size='sm' alt={user?.username ?? 'User avatar'} />
|
||||
) : (
|
||||
<IconSettingsFilled size='1rem' />
|
||||
)
|
||||
}
|
||||
rightIcon={<IconChevronDown size='0.7rem' />}
|
||||
size='sm'
|
||||
>
|
||||
{user?.username}
|
||||
</Button>
|
||||
</Card>
|
||||
|
||||
<Group position='left'>
|
||||
{avatarSrc && (
|
||||
<Button
|
||||
variant='outline'
|
||||
color='red'
|
||||
onClick={() => {
|
||||
setAvatar(null);
|
||||
setAvatarSrc(null);
|
||||
}}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
)}
|
||||
{currentAvatar && (
|
||||
<Button variant='outline' color='red' onClick={clearAvatar}>
|
||||
Remove Avatar
|
||||
</Button>
|
||||
)}
|
||||
<Button variant='outline' color='gray' disabled={!avatar} onClick={saveAvatar}>
|
||||
Save
|
||||
</Button>
|
||||
</Group>
|
||||
</Stack>
|
||||
</Paper>
|
||||
);
|
||||
}
|
||||
30
src/components/pages/settings/parts/SettingsDashboard.tsx
Normal file
30
src/components/pages/settings/parts/SettingsDashboard.tsx
Normal file
@@ -0,0 +1,30 @@
|
||||
import { useSettingsStore } from '@/lib/store/settings';
|
||||
import { Paper, Stack, Switch, Text, Title } from '@mantine/core';
|
||||
|
||||
export default function SettingsDashboard() {
|
||||
const [settings, update] = useSettingsStore((state) => [state.settings, state.update]);
|
||||
|
||||
return (
|
||||
<Paper withBorder p='sm'>
|
||||
<Title order={2}>Dashboard Settings</Title>
|
||||
<Text size='sm' color='dimmed' mt={3}>
|
||||
These settings are saved in your browser.
|
||||
</Text>
|
||||
|
||||
<Stack spacing='sm' my='xs'>
|
||||
<Switch
|
||||
label='Disable Media Preview'
|
||||
description='Disable previews of files in the dashboard. This is useful to save data as Zipline, by default, will load previews of files.'
|
||||
checked={settings.disableMediaPreview}
|
||||
onChange={(event) => update('disableMediaPreview', event.currentTarget.checked)}
|
||||
/>
|
||||
<Switch
|
||||
label='Warn on deletion'
|
||||
description='Show a warning when deleting files. This is useful to prevent accidental deletion of files.'
|
||||
checked={settings.warnDeletion}
|
||||
onChange={(event) => update('warnDeletion', event.currentTarget.checked)}
|
||||
/>
|
||||
</Stack>
|
||||
</Paper>
|
||||
);
|
||||
}
|
||||
173
src/components/pages/settings/parts/SettingsFileView.tsx
Normal file
173
src/components/pages/settings/parts/SettingsFileView.tsx
Normal file
@@ -0,0 +1,173 @@
|
||||
import { Response } from '@/lib/api/response';
|
||||
import { fetchApi } from '@/lib/fetchApi';
|
||||
import { useUserStore } from '@/lib/store/user';
|
||||
import {
|
||||
ActionIcon,
|
||||
Button,
|
||||
ColorInput,
|
||||
CopyButton,
|
||||
Divider,
|
||||
Group,
|
||||
Paper,
|
||||
PasswordInput,
|
||||
ScrollArea,
|
||||
Select,
|
||||
SimpleGrid,
|
||||
Stack,
|
||||
Switch,
|
||||
Text,
|
||||
TextInput,
|
||||
Textarea,
|
||||
Title,
|
||||
Tooltip,
|
||||
} from '@mantine/core';
|
||||
import { hasLength, useForm } from '@mantine/form';
|
||||
import { notifications } from '@mantine/notifications';
|
||||
import { IconCheck, IconCopy, IconFileX, IconUserCancel } from '@tabler/icons-react';
|
||||
import { forwardRef, useEffect, useState } from 'react';
|
||||
import { mutate } from 'swr';
|
||||
|
||||
export default function SettingsFileView() {
|
||||
const [user, setUser] = useUserStore((state) => [state.user, state.setUser]);
|
||||
|
||||
const form = useForm({
|
||||
initialValues: {
|
||||
enabled: user?.view.enabled ?? false,
|
||||
content: user?.view.content ?? '',
|
||||
embed: user?.view.embed ?? false,
|
||||
embedTitle: user?.view.embedTitle ?? '',
|
||||
embedDescription: user?.view.embedDescription ?? '',
|
||||
embedSiteName: user?.view.embedSiteName ?? '',
|
||||
embedColor: user?.view.embedColor ?? '',
|
||||
align: user?.view.align ?? 'left',
|
||||
showMimetype: user?.view.showMimetype ?? false,
|
||||
},
|
||||
});
|
||||
|
||||
const onSubmit = async (values: typeof form.values) => {
|
||||
const valuesTrimmed = {
|
||||
enabled: values.enabled,
|
||||
embed: values.embed,
|
||||
content: values.content.trim() || null,
|
||||
embedTitle: values.embedTitle.trim() || null,
|
||||
embedDescription: values.embedDescription.trim() || null,
|
||||
embedSiteName: values.embedSiteName.trim() || null,
|
||||
embedColor: values.embedColor.trim() || null,
|
||||
align: values.align,
|
||||
showMimetype: values.showMimetype,
|
||||
};
|
||||
|
||||
const { data, error } = await fetchApi<Response['/api/user']>(`/api/user`, 'PATCH', {
|
||||
view: valuesTrimmed,
|
||||
});
|
||||
|
||||
if (!data && error) {
|
||||
notifications.show({
|
||||
title: 'Error while updating view settings',
|
||||
message: error.message,
|
||||
color: 'red',
|
||||
icon: <IconFileX size='1rem' />,
|
||||
});
|
||||
}
|
||||
|
||||
if (!data?.user) return;
|
||||
|
||||
mutate('/api/user');
|
||||
setUser(data.user);
|
||||
notifications.show({
|
||||
message: 'View settings updated',
|
||||
color: 'green',
|
||||
icon: <IconCheck size='1rem' />,
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<Paper withBorder p='sm'>
|
||||
<Title order={2}>Viewing Files</Title>
|
||||
<Text color='dimmed' mt='xs'>
|
||||
All text fields support using variables.
|
||||
</Text>
|
||||
<Stack spacing='sm' mt='xs'>
|
||||
<form onSubmit={form.onSubmit(onSubmit)}>
|
||||
<SimpleGrid cols={2} spacing='sm' breakpoints={[{ maxWidth: 'sm', cols: 1 }]} mb='xs'>
|
||||
<Switch
|
||||
label='Enable View Routes'
|
||||
description='Enable viewing files through customizable view-routes'
|
||||
{...form.getInputProps('enabled', { type: 'checkbox' })}
|
||||
/>
|
||||
|
||||
<Switch
|
||||
label='Show mimetype'
|
||||
description='Show the mimetype of the file in the view-route'
|
||||
disabled={!form.values.enabled}
|
||||
{...form.getInputProps('showMimetype', { type: 'checkbox' })}
|
||||
/>
|
||||
</SimpleGrid>
|
||||
|
||||
<Textarea
|
||||
label='View Content'
|
||||
description='Change the content within view-routes'
|
||||
disabled={!form.values.enabled}
|
||||
mb='xs'
|
||||
{...form.getInputProps('content')}
|
||||
/>
|
||||
|
||||
<Select
|
||||
label='View Content Alignment'
|
||||
description='Change the alignment of the content within view-routes'
|
||||
data={[
|
||||
{ value: 'left', label: 'Left' },
|
||||
{ value: 'center', label: 'Center' },
|
||||
{ value: 'right', label: 'Right' },
|
||||
]}
|
||||
itemComponent={({ label, value, ...props }) => (
|
||||
<Group position={value} {...props}>
|
||||
{label}
|
||||
</Group>
|
||||
)}
|
||||
disabled={!form.values.enabled}
|
||||
{...form.getInputProps('align')}
|
||||
/>
|
||||
|
||||
<Divider my='sm' />
|
||||
|
||||
<Switch
|
||||
label='Enable Embed'
|
||||
description='Enable the following embed properties. These properties take advantage of OpenGraph tags.'
|
||||
my='xs'
|
||||
{...form.getInputProps('embed', { type: 'checkbox' })}
|
||||
/>
|
||||
|
||||
<SimpleGrid cols={2} spacing='sm' breakpoints={[{ maxWidth: 'sm', cols: 1 }]}>
|
||||
<TextInput
|
||||
label='Embed Title'
|
||||
disabled={!form.values.embed}
|
||||
{...form.getInputProps('embedTitle')}
|
||||
/>
|
||||
<TextInput
|
||||
label='Embed Description'
|
||||
disabled={!form.values.embed}
|
||||
{...form.getInputProps('embedDescription')}
|
||||
/>
|
||||
<TextInput
|
||||
label='Embed Site Name'
|
||||
disabled={!form.values.embed}
|
||||
{...form.getInputProps('embedSiteName')}
|
||||
/>
|
||||
<ColorInput
|
||||
label='Embed Color'
|
||||
disabled={!form.values.embed}
|
||||
{...form.getInputProps('embedColor')}
|
||||
/>
|
||||
</SimpleGrid>
|
||||
|
||||
<Group position='left' mt='sm'>
|
||||
<Button variant='outline' color='gray' type='submit'>
|
||||
Save
|
||||
</Button>
|
||||
</Group>
|
||||
</form>
|
||||
</Stack>
|
||||
</Paper>
|
||||
);
|
||||
}
|
||||
79
src/components/pages/settings/parts/SettingsOAuth.tsx
Normal file
79
src/components/pages/settings/parts/SettingsOAuth.tsx
Normal file
@@ -0,0 +1,79 @@
|
||||
import { useConfig } from '@/components/ConfigProvider';
|
||||
import { findProvider } from '@/lib/oauth/providerUtil';
|
||||
import { useSettingsStore } from '@/lib/store/settings';
|
||||
import { useUserStore } from '@/lib/store/user';
|
||||
import { Button, Group, Paper, Stack, Switch, Text, Title } from '@mantine/core';
|
||||
import type { OAuthProviderType } from '@prisma/client';
|
||||
import {
|
||||
IconBrandDiscordFilled,
|
||||
IconBrandGithubFilled,
|
||||
IconBrandGoogle,
|
||||
IconCircleKeyFilled,
|
||||
} from '@tabler/icons-react';
|
||||
import Link from 'next/link';
|
||||
|
||||
const icons = {
|
||||
DISCORD: <IconBrandDiscordFilled />,
|
||||
GITHUB: <IconBrandGithubFilled />,
|
||||
GOOGLE: <IconBrandGoogle stroke={4} />,
|
||||
AUTHENTIK: <IconCircleKeyFilled />,
|
||||
};
|
||||
|
||||
const names = {
|
||||
DISCORD: 'Discord',
|
||||
GITHUB: 'GitHub',
|
||||
GOOGLE: 'Google',
|
||||
AUTHENTIK: 'Authentik',
|
||||
};
|
||||
|
||||
function OAuthButton({ provider, linked }: { provider: OAuthProviderType; linked: boolean }) {
|
||||
const unlink = async () => {};
|
||||
|
||||
const baseProps = {
|
||||
size: 'sm',
|
||||
leftIcon: icons[provider],
|
||||
color: linked ? 'red' : `${provider.toLowerCase()}.0`,
|
||||
sx: (t: any) => ({
|
||||
'&:hover': {
|
||||
...(!linked && { backgroundColor: t.fn.darken(t.colors[provider.toLowerCase()][0], 0.2) }),
|
||||
},
|
||||
}),
|
||||
};
|
||||
|
||||
return linked ? (
|
||||
<Button {...baseProps} onClick={unlink}>
|
||||
Unlink {names[provider]} account
|
||||
</Button>
|
||||
) : (
|
||||
<Button {...baseProps} component={Link} href={`/api/auth/oauth/${provider.toLowerCase()}?link=true`}>
|
||||
Link {names[provider]} account
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
||||
export default function SettingsOAuth() {
|
||||
const config = useConfig();
|
||||
|
||||
const [user, setUser] = useUserStore((state) => [state.user, state.setUser]);
|
||||
|
||||
const discordLinked = findProvider('DISCORD', user?.oauthProviders ?? []);
|
||||
const githubLinked = findProvider('GITHUB', user?.oauthProviders ?? []);
|
||||
const googleLinked = findProvider('GOOGLE', user?.oauthProviders ?? []);
|
||||
const authentikLinked = findProvider('AUTHENTIK', user?.oauthProviders ?? []);
|
||||
|
||||
return (
|
||||
<Paper withBorder p='sm'>
|
||||
<Title order={2}>OAuth</Title>
|
||||
<Text size='sm' color='dimmed' mt={3}>
|
||||
Manage your connected OAuth providers.
|
||||
</Text>
|
||||
|
||||
<Group mt='xs'>
|
||||
{config.oauthEnabled.discord && <OAuthButton provider='DISCORD' linked={!!discordLinked} />}
|
||||
{config.oauthEnabled.github && <OAuthButton provider='GITHUB' linked={!!githubLinked} />}
|
||||
{config.oauthEnabled.google && <OAuthButton provider='GOOGLE' linked={!!googleLinked} />}
|
||||
{config.oauthEnabled.authentik && <OAuthButton provider='AUTHENTIK' linked={!!authentikLinked} />}
|
||||
</Group>
|
||||
</Paper>
|
||||
);
|
||||
}
|
||||
125
src/components/pages/settings/parts/SettingsUser.tsx
Normal file
125
src/components/pages/settings/parts/SettingsUser.tsx
Normal file
@@ -0,0 +1,125 @@
|
||||
import { Response } from '@/lib/api/response';
|
||||
import { fetchApi } from '@/lib/fetchApi';
|
||||
import { useUserStore } from '@/lib/store/user';
|
||||
import {
|
||||
ActionIcon,
|
||||
Button,
|
||||
CopyButton,
|
||||
Group,
|
||||
Paper,
|
||||
PasswordInput,
|
||||
ScrollArea,
|
||||
Stack,
|
||||
TextInput,
|
||||
Title,
|
||||
Tooltip,
|
||||
} from '@mantine/core';
|
||||
import { hasLength, useForm } from '@mantine/form';
|
||||
import { notifications } from '@mantine/notifications';
|
||||
import { IconCheck, IconCopy, IconUserCancel } from '@tabler/icons-react';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { mutate } from 'swr';
|
||||
|
||||
export default function SettingsUser() {
|
||||
const [user, setUser] = useUserStore((state) => [state.user, state.setUser]);
|
||||
|
||||
const [tokenShown, setTokenShown] = useState(false);
|
||||
const [token, setToken] = useState('');
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
const { data, error } = await fetchApi<Response['/api/user/token']>('/api/user/token');
|
||||
|
||||
if (data) {
|
||||
setToken(data.token || '');
|
||||
}
|
||||
})();
|
||||
}, []);
|
||||
|
||||
const form = useForm({
|
||||
initialValues: {
|
||||
username: user?.username ?? '',
|
||||
password: '',
|
||||
},
|
||||
validate: {
|
||||
username: hasLength({ min: 1 }, 'Username is required'),
|
||||
},
|
||||
});
|
||||
|
||||
const onSubmit = async (values: typeof form.values) => {
|
||||
const send: {
|
||||
username?: string;
|
||||
password?: string;
|
||||
} = {};
|
||||
|
||||
if (values.username !== user?.username) send['username'] = values.username.trim();
|
||||
if (values.password) send['password'] = values.password.trim();
|
||||
|
||||
const { data, error } = await fetchApi<Response['/api/user']>(`/api/user`, 'PATCH', send);
|
||||
|
||||
if (!data && error) {
|
||||
if (error.field === 'username') {
|
||||
form.setFieldError('username', error.message);
|
||||
} else {
|
||||
notifications.show({
|
||||
title: 'Error while updating user',
|
||||
message: error.message,
|
||||
color: 'red',
|
||||
icon: <IconUserCancel size='1rem' />,
|
||||
});
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (!data?.user) return;
|
||||
|
||||
mutate('/api/user');
|
||||
setUser(data.user);
|
||||
notifications.show({
|
||||
message: 'User updated',
|
||||
color: 'green',
|
||||
icon: <IconCheck size='1rem' />,
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<Paper withBorder p='sm'>
|
||||
<Title order={2}>User info</Title>
|
||||
<form onSubmit={form.onSubmit(onSubmit)}>
|
||||
<TextInput
|
||||
rightSection={
|
||||
<CopyButton value={token} timeout={1000}>
|
||||
{({ copied, copy }) => (
|
||||
<Tooltip label='Click to copy token'>
|
||||
<ActionIcon onClick={copy}>
|
||||
{copied ? <IconCheck color='green' size='1rem' /> : <IconCopy size='1rem' />}
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
)}
|
||||
</CopyButton>
|
||||
}
|
||||
// @ts-ignore this works trust
|
||||
component='span'
|
||||
label='Token'
|
||||
onClick={() => setTokenShown(true)}
|
||||
>
|
||||
<ScrollArea scrollbarSize={5}>{tokenShown ? token : '[click to reveal]'}</ScrollArea>
|
||||
</TextInput>
|
||||
|
||||
<TextInput label='Username' {...form.getInputProps('username')} />
|
||||
<PasswordInput
|
||||
label='Password'
|
||||
description='Leave blank to keep the same password'
|
||||
{...form.getInputProps('password')}
|
||||
/>
|
||||
|
||||
<Group position='left' mt='sm'>
|
||||
<Button variant='outline' color='gray' type='submit'>
|
||||
Save
|
||||
</Button>
|
||||
</Group>
|
||||
</form>
|
||||
</Paper>
|
||||
);
|
||||
}
|
||||
@@ -1,10 +1,15 @@
|
||||
import { config } from '.';
|
||||
import enabled from '../oauth/enabled';
|
||||
import { Config } from './validate';
|
||||
|
||||
export type SafeConfig = Omit<Config, 'oauth' | 'datasource' | 'core'>;
|
||||
export type SafeConfig = Omit<Config, 'oauth' | 'datasource' | 'core'> & {
|
||||
oauthEnabled: ReturnType<typeof enabled>;
|
||||
};
|
||||
|
||||
export function safeConfig(): SafeConfig {
|
||||
const { datasource, core, oauth, ...rest } = config;
|
||||
|
||||
return rest;
|
||||
(rest as SafeConfig).oauthEnabled = enabled(config);
|
||||
|
||||
return rest as SafeConfig;
|
||||
}
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import { PrismaClient } from '@prisma/client';
|
||||
import { log } from '@/lib/logger';
|
||||
import { Prisma, PrismaClient } from '@prisma/client';
|
||||
import { userViewSchema } from './models/user';
|
||||
|
||||
let prisma: PrismaClient;
|
||||
let prisma: ExtendedPrismaClient;
|
||||
|
||||
declare global {
|
||||
var __db__: PrismaClient;
|
||||
var __db__: ExtendedPrismaClient;
|
||||
}
|
||||
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
@@ -16,12 +17,25 @@ if (process.env.NODE_ENV === 'production') {
|
||||
prisma = global.__db__;
|
||||
}
|
||||
|
||||
type ExtendedPrismaClient = ReturnType<typeof getClient>;
|
||||
|
||||
function getClient() {
|
||||
const logger = log('db');
|
||||
|
||||
logger.info('connecting to database ' + process.env.DATABASE_URL);
|
||||
|
||||
const client = new PrismaClient();
|
||||
const client = new PrismaClient().$extends({
|
||||
result: {
|
||||
user: {
|
||||
view: {
|
||||
needs: { view: true },
|
||||
compute({ view }: { view: Prisma.JsonValue }) {
|
||||
return userViewSchema.parse(view);
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
client.$connect();
|
||||
|
||||
return client;
|
||||
|
||||
@@ -1,9 +1,16 @@
|
||||
import { OAuthProvider } from '@prisma/client';
|
||||
import { z } from 'zod';
|
||||
|
||||
export type User = {
|
||||
id: string;
|
||||
username: string;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
role: 'USER' | 'ADMIN' | 'SUPERADMIN';
|
||||
view: UserViewSettings;
|
||||
|
||||
oauthProviders: OAuthProvider[];
|
||||
|
||||
avatar?: string | null;
|
||||
password?: string | null;
|
||||
token?: string | null;
|
||||
@@ -15,4 +22,21 @@ export const userSelect = {
|
||||
createdAt: true,
|
||||
updatedAt: true,
|
||||
role: true,
|
||||
view: true,
|
||||
oauthProviders: true,
|
||||
};
|
||||
|
||||
export type UserViewSettings = z.infer<typeof userViewSchema>;
|
||||
export const userViewSchema = z
|
||||
.object({
|
||||
enabled: z.boolean().nullish(),
|
||||
align: z.enum(['left', 'center', 'right']).nullish(),
|
||||
showMimetype: z.boolean().nullish(),
|
||||
content: z.string().nullish(),
|
||||
embed: z.boolean().nullish(),
|
||||
embedTitle: z.string().nullish(),
|
||||
embedDescription: z.string().nullish(),
|
||||
embedColor: z.string().nullish(),
|
||||
embedSiteName: z.string().nullish(),
|
||||
})
|
||||
.partial();
|
||||
|
||||
14
src/lib/hooks/useAvatar.ts
Normal file
14
src/lib/hooks/useAvatar.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import useSWR from 'swr';
|
||||
|
||||
const f = async () => {
|
||||
const res = await fetch('/api/user/avatar');
|
||||
if (!res.ok) return null;
|
||||
|
||||
const r = await res.text();
|
||||
return r;
|
||||
};
|
||||
|
||||
export default function useAvatar() {
|
||||
const { data, mutate } = useSWR('/api/user/avatar', f);
|
||||
return { avatar: data, mutate };
|
||||
}
|
||||
38
src/lib/oauth/enabled.ts
Normal file
38
src/lib/oauth/enabled.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
import { Config } from '../config/validate';
|
||||
import { isTruthy } from '../primitive';
|
||||
|
||||
export default function enabled(config: Config) {
|
||||
const discordEnabled = isTruthy(
|
||||
config.oauth?.discord?.clientId,
|
||||
config.oauth?.discord?.clientSecret,
|
||||
config.features.oauthRegistration
|
||||
);
|
||||
|
||||
const githubEnabled = isTruthy(
|
||||
config.oauth?.github?.clientId,
|
||||
config.oauth?.github?.clientSecret,
|
||||
config.features.oauthRegistration
|
||||
);
|
||||
|
||||
const googleEnabled = isTruthy(
|
||||
config.oauth?.google?.clientId,
|
||||
config.oauth?.google?.clientSecret,
|
||||
config.features.oauthRegistration
|
||||
);
|
||||
|
||||
const authentikEnabled = isTruthy(
|
||||
config.oauth?.authentik?.clientId,
|
||||
config.oauth?.authentik?.clientSecret,
|
||||
config.oauth?.authentik?.authorizeUrl,
|
||||
config.oauth?.authentik?.tokenUrl,
|
||||
config.oauth?.authentik?.userinfoUrl,
|
||||
config.features.oauthRegistration
|
||||
);
|
||||
|
||||
return {
|
||||
discord: discordEnabled,
|
||||
github: githubEnabled,
|
||||
google: googleEnabled,
|
||||
authentik: authentikEnabled,
|
||||
};
|
||||
}
|
||||
9
src/lib/oauth/providerUtil.ts
Normal file
9
src/lib/oauth/providerUtil.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import type { OAuthProviderType } from '@prisma/client';
|
||||
import { User } from '../db/models/user';
|
||||
|
||||
export function findProvider(
|
||||
provider: OAuthProviderType,
|
||||
providers: User['oauthProviders']
|
||||
): User['oauthProviders'][0] | undefined {
|
||||
return providers.find((p) => p.provider === provider);
|
||||
}
|
||||
165
src/lib/parser.ts
Normal file
165
src/lib/parser.ts
Normal file
@@ -0,0 +1,165 @@
|
||||
import bytes from 'bytes';
|
||||
import { File } from './db/models/file';
|
||||
import { User } from './db/models/user';
|
||||
|
||||
export type ParseValue = {
|
||||
file?: File;
|
||||
user?: User | Omit<User, 'oauthProviders'>;
|
||||
|
||||
link?: string;
|
||||
raw_link?: string;
|
||||
};
|
||||
|
||||
export function parseString(str: string, value: ParseValue) {
|
||||
if (!str) return null;
|
||||
str = str
|
||||
.replace(/\{link\}/gi, value.link ?? '{unknown_link}')
|
||||
.replace(/\{raw_link\}/gi, value.raw_link ?? '{unknown_raw_link}')
|
||||
.replace(/\\n/g, '\n');
|
||||
|
||||
const re = /\{(?<type>file|url|user)\.(?<prop>\w+)(::(?<mod>\w+))?\}/gi;
|
||||
let matches: RegExpMatchArray | null;
|
||||
|
||||
while ((matches = re.exec(str))) {
|
||||
if (!matches.groups) continue;
|
||||
|
||||
const index = matches.index as number;
|
||||
|
||||
const getV = value[matches.groups.type as keyof ParseValue];
|
||||
if (!getV) {
|
||||
str = replaceCharsFromString(str, '{unknown_type}', index, re.lastIndex);
|
||||
re.lastIndex = index;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (['password', 'avatar', 'uuid'].includes(matches.groups.prop)) {
|
||||
str = replaceCharsFromString(str, '{unknown_property}', index, re.lastIndex);
|
||||
re.lastIndex = index;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (['originalName', 'name'].includes(matches.groups.prop)) {
|
||||
str = replaceCharsFromString(
|
||||
str,
|
||||
decodeURIComponent(escape(getV[matches.groups.prop as keyof ParseValue['file']])),
|
||||
index,
|
||||
re.lastIndex
|
||||
);
|
||||
re.lastIndex = index;
|
||||
continue;
|
||||
}
|
||||
|
||||
const v = getV[matches.groups.prop as keyof ParseValue['file'] | keyof ParseValue['user']];
|
||||
|
||||
if (v === undefined) {
|
||||
str = replaceCharsFromString(str, '{unknown_property}', index, re.lastIndex);
|
||||
re.lastIndex = index;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (matches.groups.mod) {
|
||||
str = replaceCharsFromString(str, modifier(matches.groups.mod, v), index, re.lastIndex);
|
||||
re.lastIndex = index;
|
||||
continue;
|
||||
}
|
||||
|
||||
str = replaceCharsFromString(str, v, index, re.lastIndex);
|
||||
re.lastIndex = index;
|
||||
}
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
function modifier(mod: string, value: unknown): string {
|
||||
mod = mod.toLowerCase();
|
||||
|
||||
if (value instanceof Date) {
|
||||
switch (mod) {
|
||||
case 'locale':
|
||||
return value.toLocaleString();
|
||||
case 'time':
|
||||
return value.toLocaleTimeString();
|
||||
case 'date':
|
||||
return value.toLocaleDateString();
|
||||
case 'unix':
|
||||
return Math.floor(value.getTime() / 1000).toString();
|
||||
case 'iso':
|
||||
return value.toISOString();
|
||||
case 'utc':
|
||||
return value.toUTCString();
|
||||
case 'year':
|
||||
return value.getFullYear().toString();
|
||||
case 'month':
|
||||
return (value.getMonth() + 1).toString();
|
||||
case 'day':
|
||||
return value.getDate().toString();
|
||||
case 'hour':
|
||||
return value.getHours().toString();
|
||||
case 'minute':
|
||||
return value.getMinutes().toString();
|
||||
case 'second':
|
||||
return value.getSeconds().toString();
|
||||
default:
|
||||
return `{unknown_date_modifier(${mod})}`;
|
||||
}
|
||||
} else if (typeof value === 'string') {
|
||||
switch (mod) {
|
||||
case 'upper':
|
||||
return value.toUpperCase();
|
||||
case 'lower':
|
||||
return value.toLowerCase();
|
||||
case 'title':
|
||||
return value.charAt(0).toUpperCase() + value.slice(1);
|
||||
case 'length':
|
||||
return value.length.toString();
|
||||
case 'reverse':
|
||||
return value.split('').reverse().join('');
|
||||
case 'base64':
|
||||
return btoa(value);
|
||||
case 'hex':
|
||||
return toHex(value);
|
||||
default:
|
||||
return `{unknown_str_modifier(${mod})}`;
|
||||
}
|
||||
} else if (typeof value === 'number') {
|
||||
switch (mod) {
|
||||
case 'comma':
|
||||
return value.toLocaleString();
|
||||
case 'hex':
|
||||
return value.toString(16);
|
||||
case 'octal':
|
||||
return value.toString(8);
|
||||
case 'binary':
|
||||
return value.toString(2);
|
||||
case 'bytes':
|
||||
return bytes(value, { unitSeparator: ' ' });
|
||||
default:
|
||||
return `{unknown_int_modifier(${mod})}`;
|
||||
}
|
||||
} else if (typeof value === 'boolean') {
|
||||
switch (mod) {
|
||||
case 'yesno':
|
||||
return value ? 'Yes' : 'No';
|
||||
case 'onoff':
|
||||
return value ? 'On' : 'Off';
|
||||
case 'truefalse':
|
||||
return value ? 'True' : 'False';
|
||||
default:
|
||||
return `{unknown_bool_modifier(${mod})}`;
|
||||
}
|
||||
}
|
||||
|
||||
return `{unknown_modifier(${mod})}`;
|
||||
}
|
||||
|
||||
function replaceCharsFromString(str: string, replace: string, start: number, end: number): string {
|
||||
return str.slice(0, start) + replace + str.slice(end);
|
||||
}
|
||||
|
||||
function toHex(str: string): string {
|
||||
let hex = '';
|
||||
for (let i = 0; i < str.length; i++) {
|
||||
hex += '' + str.charCodeAt(i).toString(16);
|
||||
}
|
||||
return hex;
|
||||
}
|
||||
33
src/lib/store/settings.ts
Normal file
33
src/lib/store/settings.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
import { create } from 'zustand';
|
||||
import { persist } from 'zustand/middleware';
|
||||
|
||||
export type SettingsStore = {
|
||||
settings: {
|
||||
disableMediaPreview: boolean;
|
||||
warnDeletion: boolean;
|
||||
};
|
||||
|
||||
update: <K extends keyof SettingsStore['settings']>(key: K, value: SettingsStore['settings'][K]) => void;
|
||||
};
|
||||
|
||||
export const useSettingsStore = create<SettingsStore>()(
|
||||
persist(
|
||||
(set) => ({
|
||||
settings: {
|
||||
disableMediaPreview: false,
|
||||
warnDeletion: true,
|
||||
},
|
||||
|
||||
update: (key, value) =>
|
||||
set((state) => ({
|
||||
settings: {
|
||||
...state.settings,
|
||||
[key]: value,
|
||||
},
|
||||
})),
|
||||
}),
|
||||
{
|
||||
name: 'zipline-settings',
|
||||
}
|
||||
)
|
||||
);
|
||||
@@ -1,3 +1,3 @@
|
||||
export function formatRootUrl(route: string, src: string) {
|
||||
return `${route === '/' || route === '' ? '' : route}${encodeURI(src)}`
|
||||
}
|
||||
return `${route === '/' ? '' : route}/${encodeURI(src)}`;
|
||||
}
|
||||
|
||||
@@ -25,12 +25,7 @@ export async function handler(req: NextApiReq, res: NextApiRes<ApiUserTokenRespo
|
||||
|
||||
if (!u.avatar) return res.notFound();
|
||||
|
||||
u.avatar = u.avatar.replace(/^data:image\/\w+;base64,/, '');
|
||||
const buf = Buffer.from(u.avatar, 'base64');
|
||||
|
||||
res.setHeader('Content-Length', buf.length);
|
||||
|
||||
return res.send(buf);
|
||||
return res.status(200).send(u.avatar);
|
||||
}
|
||||
|
||||
export default combine([method(['GET']), ziplineAuth()], handler);
|
||||
|
||||
@@ -15,12 +15,33 @@ type EditBody = {
|
||||
username?: string;
|
||||
password?: string;
|
||||
avatar?: string;
|
||||
view?: {
|
||||
content?: string;
|
||||
embed?: boolean;
|
||||
embedTitle?: string;
|
||||
embedDescription?: string;
|
||||
embedColor?: string;
|
||||
embedSiteName?: string;
|
||||
enabled?: boolean;
|
||||
align?: 'left' | 'center' | 'right';
|
||||
showMimetype?: boolean;
|
||||
};
|
||||
};
|
||||
|
||||
export async function handler(req: NextApiReq<EditBody>, res: NextApiRes<ApiUserResponse>) {
|
||||
if (req.method === 'GET') {
|
||||
return res.ok({ user: req.user, token: req.cookies.zipline_token });
|
||||
} else if (req.method === 'PATCH') {
|
||||
if (req.body.username) {
|
||||
const existing = await prisma.user.findUnique({
|
||||
where: {
|
||||
username: req.body.username,
|
||||
},
|
||||
});
|
||||
|
||||
if (existing) return res.badRequest('Username already taken', { field: 'username' });
|
||||
}
|
||||
|
||||
const user = await prisma.user.update({
|
||||
where: {
|
||||
id: req.user.id,
|
||||
@@ -28,7 +49,27 @@ export async function handler(req: NextApiReq<EditBody>, res: NextApiRes<ApiUser
|
||||
data: {
|
||||
...(req.body.username && { username: req.body.username }),
|
||||
...(req.body.password && { password: await hashPassword(req.body.password) }),
|
||||
...(req.body.avatar && { avatar: req.body.avatar }),
|
||||
...(req.body.avatar !== undefined && { avatar: req.body.avatar || null }),
|
||||
...(req.body.view && {
|
||||
view: {
|
||||
...req.user.view,
|
||||
...(req.body.view.enabled !== undefined && { enabled: req.body.view.enabled || false }),
|
||||
...(req.body.view.content !== undefined && { content: req.body.view.content || null }),
|
||||
...(req.body.view.embed !== undefined && { embed: req.body.view.embed || false }),
|
||||
...(req.body.view.embedTitle !== undefined && { embedTitle: req.body.view.embedTitle || null }),
|
||||
...(req.body.view.embedDescription !== undefined && {
|
||||
embedDescription: req.body.view.embedDescription || null,
|
||||
}),
|
||||
...(req.body.view.embedColor !== undefined && { embedColor: req.body.view.embedColor || null }),
|
||||
...(req.body.view.embedSiteName !== undefined && {
|
||||
embedSiteName: req.body.view.embedSiteName || null,
|
||||
}),
|
||||
...(req.body.view.align !== undefined && { align: req.body.view.align || 'center' }),
|
||||
...(req.body.view.showMimetype !== undefined && {
|
||||
showMimetype: req.body.view.showMimetype || false,
|
||||
}),
|
||||
},
|
||||
}),
|
||||
},
|
||||
select: {
|
||||
...userSelect,
|
||||
|
||||
@@ -4,21 +4,13 @@ import { getZipline } from '@/lib/db/models/zipline';
|
||||
import { fetchApi } from '@/lib/fetchApi';
|
||||
import { withSafeConfig } from '@/lib/middleware/next/withSafeConfig';
|
||||
import { eitherTrue, isTruthy } from '@/lib/primitive';
|
||||
import {
|
||||
Button,
|
||||
Center,
|
||||
PasswordInput,
|
||||
Stack,
|
||||
Text,
|
||||
TextInput,
|
||||
Title
|
||||
} from '@mantine/core';
|
||||
import { Button, Center, PasswordInput, Stack, Text, TextInput, Title } from '@mantine/core';
|
||||
import { hasLength, useForm } from '@mantine/form';
|
||||
import {
|
||||
IconBrandDiscordFilled,
|
||||
IconBrandGithubFilled,
|
||||
IconBrandGoogle,
|
||||
IconCircleKeyFilled
|
||||
IconCircleKeyFilled,
|
||||
} from '@tabler/icons-react';
|
||||
import { InferGetServerSidePropsType } from 'next';
|
||||
import Link from 'next/link';
|
||||
@@ -26,13 +18,7 @@ import { useRouter } from 'next/router';
|
||||
import { useEffect } from 'react';
|
||||
import useSWR from 'swr';
|
||||
|
||||
export default function Login({
|
||||
config,
|
||||
authentikEnabled,
|
||||
discordEnabled,
|
||||
githubEnabled,
|
||||
googleEnabled,
|
||||
}: InferGetServerSidePropsType<typeof getServerSideProps>) {
|
||||
export default function Login({ config }: InferGetServerSidePropsType<typeof getServerSideProps>) {
|
||||
const router = useRouter();
|
||||
const { data, isLoading, mutate } = useSWR<Response['/api/user']>('/api/user');
|
||||
|
||||
@@ -110,7 +96,7 @@ export default function Login({
|
||||
</Button>
|
||||
)}
|
||||
|
||||
{discordEnabled && (
|
||||
{config.oauthEnabled.discord && (
|
||||
<Button
|
||||
size='lg'
|
||||
fullWidth
|
||||
@@ -129,7 +115,7 @@ export default function Login({
|
||||
</Button>
|
||||
)}
|
||||
|
||||
{githubEnabled && (
|
||||
{config.oauthEnabled.github && (
|
||||
<Button
|
||||
size='lg'
|
||||
fullWidth
|
||||
@@ -147,7 +133,7 @@ export default function Login({
|
||||
</Button>
|
||||
)}
|
||||
|
||||
{googleEnabled && (
|
||||
{config.oauthEnabled.google && (
|
||||
<Button
|
||||
size='lg'
|
||||
fullWidth
|
||||
@@ -165,7 +151,7 @@ export default function Login({
|
||||
</Button>
|
||||
)}
|
||||
|
||||
{authentikEnabled && (
|
||||
{config.oauthEnabled.authentik && (
|
||||
<Button
|
||||
size='lg'
|
||||
fullWidth
|
||||
@@ -200,37 +186,5 @@ export const getServerSideProps = withSafeConfig(async () => {
|
||||
},
|
||||
};
|
||||
|
||||
const discordEnabled = isTruthy(
|
||||
config.oauth?.discord?.clientId,
|
||||
config.oauth?.discord?.clientSecret,
|
||||
config.features.oauthRegistration
|
||||
);
|
||||
|
||||
const githubEnabled = isTruthy(
|
||||
config.oauth?.github?.clientId,
|
||||
config.oauth?.github?.clientSecret,
|
||||
config.features.oauthRegistration
|
||||
);
|
||||
|
||||
const googleEnabled = isTruthy(
|
||||
config.oauth?.google?.clientId,
|
||||
config.oauth?.google?.clientSecret,
|
||||
config.features.oauthRegistration
|
||||
);
|
||||
|
||||
const authentikEnabled = isTruthy(
|
||||
config.oauth?.authentik?.clientId,
|
||||
config.oauth?.authentik?.clientSecret,
|
||||
config.oauth?.authentik?.authorizeUrl,
|
||||
config.oauth?.authentik?.tokenUrl,
|
||||
config.oauth?.authentik?.userinfoUrl,
|
||||
config.features.oauthRegistration
|
||||
);
|
||||
|
||||
return {
|
||||
discordEnabled,
|
||||
githubEnabled,
|
||||
googleEnabled,
|
||||
authentikEnabled,
|
||||
};
|
||||
return {};
|
||||
});
|
||||
|
||||
19
src/pages/dashboard/settings.tsx
Normal file
19
src/pages/dashboard/settings.tsx
Normal file
@@ -0,0 +1,19 @@
|
||||
import Layout from '@/components/Layout';
|
||||
import DashboardSettings from '@/components/pages/settings';
|
||||
import useLogin from '@/lib/hooks/useLogin';
|
||||
import { withSafeConfig } from '@/lib/middleware/next/withSafeConfig';
|
||||
import { LoadingOverlay } from '@mantine/core';
|
||||
import { InferGetServerSidePropsType } from 'next';
|
||||
|
||||
export default function DashboardIndex({ config }: InferGetServerSidePropsType<typeof getServerSideProps>) {
|
||||
const { loading } = useLogin();
|
||||
if (loading) return <LoadingOverlay visible />;
|
||||
|
||||
return (
|
||||
<Layout config={config}>
|
||||
<DashboardSettings />
|
||||
</Layout>
|
||||
);
|
||||
}
|
||||
|
||||
export const getServerSideProps = withSafeConfig();
|
||||
@@ -1,9 +1,13 @@
|
||||
import DashboardFileType from '@/components/file/DashboardFileType';
|
||||
import { isCode } from '@/lib/code';
|
||||
import { SafeConfig, safeConfig } from '@/lib/config/safe';
|
||||
import { verifyPassword } from '@/lib/crypto';
|
||||
import { prisma } from '@/lib/db';
|
||||
import { fileSelect, type File } from '@/lib/db/models/file';
|
||||
import { User, UserViewSettings, userSelect } from '@/lib/db/models/user';
|
||||
import { fetchApi } from '@/lib/fetchApi';
|
||||
import { parseString } from '@/lib/parser';
|
||||
import { formatRootUrl } from '@/lib/url';
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
@@ -16,24 +20,35 @@ import {
|
||||
Space,
|
||||
Text,
|
||||
Title,
|
||||
TypographyStylesProvider,
|
||||
} from '@mantine/core';
|
||||
import { IconFileDownload } from '@tabler/icons-react';
|
||||
import bytes from 'bytes';
|
||||
import { GetServerSideProps, InferGetServerSidePropsType } from 'next';
|
||||
import Link from 'next/link';
|
||||
import { useRouter } from 'next/router';
|
||||
import { useState } from 'react';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { sanitize } from 'isomorphic-dompurify';
|
||||
import Head from 'next/head';
|
||||
|
||||
export default function ViewFile({
|
||||
file,
|
||||
password,
|
||||
pw,
|
||||
code,
|
||||
user,
|
||||
config,
|
||||
host,
|
||||
}: InferGetServerSidePropsType<typeof getServerSideProps>) {
|
||||
file.createdAt = new Date(file.createdAt);
|
||||
file.updatedAt = new Date(file.updatedAt);
|
||||
file.deletesAt = file.deletesAt ? new Date(file.deletesAt) : null;
|
||||
|
||||
if (user) {
|
||||
user.createdAt = new Date(user.createdAt);
|
||||
user.updatedAt = new Date(user.updatedAt);
|
||||
}
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
const [passwordValue, setPassword] = useState<string>('');
|
||||
@@ -53,6 +68,94 @@ export default function ViewFile({
|
||||
}
|
||||
};
|
||||
|
||||
const meta = (
|
||||
<Head>
|
||||
{user?.view.embedTitle && user?.view.embed && (
|
||||
<meta property='og:title' content={parseString(user.view.embedTitle, { file: file, user }) ?? ''} />
|
||||
)}
|
||||
{user?.view.embedDescription && user?.view.embed && (
|
||||
<meta
|
||||
property='og:description'
|
||||
content={parseString(user.view.embedDescription, { file: file, user }) ?? ''}
|
||||
/>
|
||||
)}
|
||||
{user?.view.embedSiteName && user?.view.embed && (
|
||||
<meta
|
||||
property='og:site_name'
|
||||
content={parseString(user.view.embedSiteName, { file: file, user }) ?? ''}
|
||||
/>
|
||||
)}
|
||||
{user?.view.embedColor && user?.view.embed && (
|
||||
<meta
|
||||
property='theme-color'
|
||||
content={parseString(user.view.embedColor, { file: file, user }) ?? ''}
|
||||
/>
|
||||
)}
|
||||
|
||||
{file.type.startsWith('image') && (
|
||||
<>
|
||||
<meta property='og:type' content='image' />
|
||||
<meta property='og:image' itemProp='image' content={`${host}/raw/${file.name}`} />
|
||||
<meta property='og:url' content={`${host}/raw/${file.name}`} />
|
||||
<meta property='twitter:card' content='summary_large_image' />
|
||||
<meta property='twitter:image' content={`${host}/raw/${file.name}`} />
|
||||
<meta property='twitter:title' content={file.name} />
|
||||
</>
|
||||
)}
|
||||
|
||||
{file.type.startsWith('video') && (
|
||||
<>
|
||||
<meta name='twitter:card' content='player' />
|
||||
<meta name='twitter:player' content={`${host}/raw/${file.name}`} />
|
||||
<meta name='twitter:player:stream' content={`${host}/raw/${file.name}`} />
|
||||
<meta name='twitter:player:width' content='720' />
|
||||
<meta name='twitter:player:height' content='480' />
|
||||
<meta name='twitter:player:stream:content_type' content={file.type} />
|
||||
<meta name='twitter:title' content={file.name} />
|
||||
{/*
|
||||
{file.thumbnail && (
|
||||
<>
|
||||
<meta name='twitter:image' content={`${host}/raw/${file.thumbnail.name}`} />
|
||||
<meta property='og:image' content={`${host}/raw/${file.thumbnail.name}`} />
|
||||
</>
|
||||
)} */}
|
||||
|
||||
<meta property='og:url' content={`${host}/raw/${file.name}`} />
|
||||
<meta property='og:video' content={`${host}/raw/${file.name}`} />
|
||||
<meta property='og:video:url' content={`${host}/raw/${file.name}`} />
|
||||
<meta property='og:video:secure_url' content={`${host}/raw/${file.name}`} />
|
||||
<meta property='og:video:type' content={file.type} />
|
||||
<meta property='og:video:width' content='720' />
|
||||
<meta property='og:video:height' content='480' />
|
||||
</>
|
||||
)}
|
||||
|
||||
{file.type.startsWith('audio') && (
|
||||
<>
|
||||
<meta name='twitter:card' content='player' />
|
||||
<meta name='twitter:player' content={`${host}/raw/${file.name}`} />
|
||||
<meta name='twitter:player:stream' content={`${host}/raw/${file.name}`} />
|
||||
<meta name='twitter:player:stream:content_type' content={file.type} />
|
||||
<meta name='twitter:title' content={file.name} />
|
||||
<meta name='twitter:player:width' content='720' />
|
||||
<meta name='twitter:player:height' content='480' />
|
||||
|
||||
<meta property='og:type' content='music.song' />
|
||||
<meta property='og:url' content={`${host}/raw/${file.name}`} />
|
||||
<meta property='og:audio' content={`${host}/raw/${file.name}`} />
|
||||
<meta property='og:audio:secure_url' content={`${host}/raw/${file.name}`} />
|
||||
<meta property='og:audio:type' content={file.type} />
|
||||
</>
|
||||
)}
|
||||
|
||||
{!file.type.startsWith('video') && !file.type.startsWith('image') && (
|
||||
<meta property='og:url' content={`${host}/raw/${file.name}`} />
|
||||
)}
|
||||
|
||||
<title>{file.name}</title>
|
||||
</Head>
|
||||
);
|
||||
|
||||
return password && !pw ? (
|
||||
<Modal
|
||||
onClose={() => {}}
|
||||
@@ -83,6 +186,7 @@ export default function ViewFile({
|
||||
</Modal>
|
||||
) : code ? (
|
||||
<>
|
||||
{meta}
|
||||
<Paper withBorder>
|
||||
<Group position='apart' py={5} px='xs'>
|
||||
<Text color='dimmed'>{file.name}</Text>
|
||||
@@ -95,61 +199,88 @@ export default function ViewFile({
|
||||
|
||||
<Collapse in={detailsOpen}>
|
||||
<Paper m='md' p='md' withBorder>
|
||||
<Text size='sm' color='dimmed'>
|
||||
<b>Created at:</b> {file.createdAt.toLocaleString()}
|
||||
<Space />
|
||||
<b>Updated at:</b> {file.updatedAt.toLocaleString()}
|
||||
<Space />
|
||||
<b>Size:</b> {bytes(file.size)}
|
||||
<Space />
|
||||
<b>Views:</b> {file.views.toLocaleString()}
|
||||
</Text>
|
||||
{user?.view.content && (
|
||||
<TypographyStylesProvider>
|
||||
<Text
|
||||
align={user?.view.align ?? 'left'}
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: sanitize(
|
||||
parseString(user.view.content, {
|
||||
file,
|
||||
link: `${host}${formatRootUrl(config?.files?.route ?? '/u', file.name)}`,
|
||||
raw_link: `${host}/raw/${file.name}`,
|
||||
}) ?? '',
|
||||
{
|
||||
USE_PROFILES: { html: true },
|
||||
FORBID_TAGS: ['style', 'script'],
|
||||
}
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</TypographyStylesProvider>
|
||||
)}
|
||||
</Paper>
|
||||
</Collapse>
|
||||
|
||||
<Paper m='md' p='md' withBorder>
|
||||
<DashboardFileType file={file} password={pw} show />
|
||||
<DashboardFileType file={file} password={pw} show code={code} />
|
||||
</Paper>
|
||||
</>
|
||||
) : (
|
||||
<Center h='100%'>
|
||||
<Paper m='md' p='md' shadow='md' radius='md' withBorder>
|
||||
<Group position='apart' mb='sm'>
|
||||
<Text size='lg' weight={700} sx={{ display: 'flex' }}>
|
||||
{file.name}
|
||||
<>
|
||||
{meta}
|
||||
<Center h='100%'>
|
||||
<Paper m='md' p='md' shadow='md' radius='md' withBorder>
|
||||
<Group position='apart' mb='sm'>
|
||||
<Text size='lg' weight={700} sx={{ display: 'flex' }}>
|
||||
{file.name}
|
||||
|
||||
<Text size='sm' color='dimmed' ml='sm' sx={{ alignSelf: 'center' }}>
|
||||
{file.type}
|
||||
{user?.view.showMimetype && (
|
||||
<Text size='sm' color='dimmed' ml='sm' sx={{ alignSelf: 'center' }}>
|
||||
{file.type}
|
||||
</Text>
|
||||
)}
|
||||
</Text>
|
||||
</Text>
|
||||
|
||||
<Button
|
||||
ml='sm'
|
||||
variant='outline'
|
||||
component={Link}
|
||||
href={`/raw/${file.name}?download=true${pw ? `&pw=${pw}` : ''}`}
|
||||
target='_blank'
|
||||
compact
|
||||
color='gray'
|
||||
leftIcon={<IconFileDownload size='1rem' />}
|
||||
>
|
||||
Download
|
||||
</Button>
|
||||
</Group>
|
||||
<Button
|
||||
ml='sm'
|
||||
variant='outline'
|
||||
component={Link}
|
||||
href={`/raw/${file.name}?download=true${pw ? `&pw=${pw}` : ''}`}
|
||||
target='_blank'
|
||||
compact
|
||||
color='gray'
|
||||
leftIcon={<IconFileDownload size='1rem' />}
|
||||
>
|
||||
Download
|
||||
</Button>
|
||||
</Group>
|
||||
|
||||
<DashboardFileType file={file} password={pw} show />
|
||||
<DashboardFileType file={file} password={pw} show />
|
||||
|
||||
<Text size='sm' color='dimmed' mt='sm'>
|
||||
<b>Created at:</b> {file.createdAt.toLocaleString()}
|
||||
<Space />
|
||||
<b>Updated at:</b> {file.updatedAt.toLocaleString()}
|
||||
<Space />
|
||||
<b>Size:</b> {bytes(file.size)}
|
||||
<Space />
|
||||
<b>Views:</b> {file.views.toLocaleString()}
|
||||
</Text>
|
||||
</Paper>
|
||||
</Center>
|
||||
{user?.view.content && (
|
||||
<TypographyStylesProvider>
|
||||
<Text
|
||||
align={user?.view.align ?? 'left'}
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: sanitize(
|
||||
parseString(user?.view.content, {
|
||||
file,
|
||||
link: `${host}${formatRootUrl(config?.files?.route ?? '/u', file.name)}`,
|
||||
raw_link: `${host}/raw/${file.name}`,
|
||||
}) ?? '',
|
||||
{
|
||||
USE_PROFILES: { html: true },
|
||||
FORBID_TAGS: ['style', 'script'],
|
||||
}
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</TypographyStylesProvider>
|
||||
)}
|
||||
</Paper>
|
||||
</Center>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -158,6 +289,9 @@ export const getServerSideProps: GetServerSideProps<{
|
||||
password?: boolean;
|
||||
pw?: string;
|
||||
code: boolean;
|
||||
user?: Omit<User, 'oauthProviders'>;
|
||||
config?: SafeConfig;
|
||||
host: string;
|
||||
}> = async (context) => {
|
||||
const { id, pw } = context.query;
|
||||
if (!id) return { notFound: true };
|
||||
@@ -169,32 +303,68 @@ export const getServerSideProps: GetServerSideProps<{
|
||||
select: {
|
||||
...fileSelect,
|
||||
password: true,
|
||||
userId: true,
|
||||
},
|
||||
});
|
||||
if (!file) return { notFound: true };
|
||||
if (!file || !file.userId) return { notFound: true };
|
||||
|
||||
const user = await prisma.user.findFirst({
|
||||
where: {
|
||||
id: file.userId,
|
||||
},
|
||||
select: {
|
||||
...userSelect,
|
||||
oauthProviders: false,
|
||||
},
|
||||
});
|
||||
if (!user) return { notFound: true };
|
||||
|
||||
let host = context.req.headers.host;
|
||||
|
||||
const proto = context.req.headers['x-forwarded-proto'];
|
||||
try {
|
||||
if (
|
||||
JSON.parse(context.req.headers['cf-visitor'] as string).scheme === 'https' ||
|
||||
proto === 'https' ||
|
||||
false // return+Htt[s]
|
||||
)
|
||||
host = `https://${host}`;
|
||||
else host = `http://${host}`;
|
||||
} catch (e) {
|
||||
if (proto === 'https' || false /* return https */) host = `https://${host}`;
|
||||
else host = `http://${host}`;
|
||||
}
|
||||
|
||||
// convert date to string dumb nextjs :@
|
||||
(file as any).createdAt = file.createdAt.toISOString();
|
||||
(file as any).updatedAt = file.updatedAt.toISOString();
|
||||
(file as any).deletesAt = file.deletesAt?.toISOString() || null;
|
||||
|
||||
(user as any).createdAt = user.createdAt.toISOString();
|
||||
(user as any).updatedAt = user.updatedAt.toISOString();
|
||||
|
||||
const code = await isCode(file.name);
|
||||
|
||||
if (pw) {
|
||||
const verified = await verifyPassword(pw as string, file.password!);
|
||||
|
||||
delete (file as any).password;
|
||||
if (verified) return { props: { file, pw: pw as string, code } };
|
||||
if (verified) return { props: { file, pw: pw as string, code, host } };
|
||||
}
|
||||
|
||||
const password = !!file.password;
|
||||
delete (file as any).password;
|
||||
|
||||
const config = safeConfig();
|
||||
|
||||
return {
|
||||
props: {
|
||||
file,
|
||||
password,
|
||||
code,
|
||||
user,
|
||||
config,
|
||||
host,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
458
yarn.lock
458
yarn.lock
@@ -3719,51 +3719,51 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@prisma/client@npm:4.16.1":
|
||||
version: 4.16.1
|
||||
resolution: "@prisma/client@npm:4.16.1"
|
||||
"@prisma/client@npm:^5.0.0":
|
||||
version: 5.0.0
|
||||
resolution: "@prisma/client@npm:5.0.0"
|
||||
dependencies:
|
||||
"@prisma/engines-version": 4.16.0-66.b20ead4d3ab9e78ac112966e242ded703f4a052c
|
||||
"@prisma/engines-version": 4.17.0-26.6b0aef69b7cdfc787f822ecd7cdc76d5f1991584
|
||||
peerDependencies:
|
||||
prisma: "*"
|
||||
peerDependenciesMeta:
|
||||
prisma:
|
||||
optional: true
|
||||
checksum: da715b6e930f56493cb7c6232b179250a4e93bbc2b5fe42a84cb9511d127b6fd89e1271f83e1d531bd4fdd7e87f6ac0701b30174cf89212ae7da88097de31026
|
||||
checksum: 332c2af44e880ffc9dd1223992bf6f45910094c7a3a540cbbfdda45d6caf3e82998376338abdf85e34a12f1082ae932f2385d6396c62fe4bba7ec6b84de54466
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@prisma/debug@npm:4.16.1":
|
||||
version: 4.16.1
|
||||
resolution: "@prisma/debug@npm:4.16.1"
|
||||
"@prisma/debug@npm:5.0.0":
|
||||
version: 5.0.0
|
||||
resolution: "@prisma/debug@npm:5.0.0"
|
||||
dependencies:
|
||||
"@types/debug": 4.1.8
|
||||
debug: 4.3.4
|
||||
strip-ansi: 6.0.1
|
||||
checksum: 11a1fe1ac41c2729295774fe4361023c6c2140d0e3d5ffaee38bf89ca1e73ccf27f933d068c0d4dc8911470272add4ae55c742dec297d7629ac46c74ff043c4c
|
||||
checksum: 17d720717fab190a94762b90f93cfde799f419bfd2fee05c145566d6fa31d59d86dede80417234094afc5fff6d684ce350c17d40c302b26610ee60fe56c704e9
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@prisma/engines-version@npm:4.16.0-66.b20ead4d3ab9e78ac112966e242ded703f4a052c":
|
||||
version: 4.16.0-66.b20ead4d3ab9e78ac112966e242ded703f4a052c
|
||||
resolution: "@prisma/engines-version@npm:4.16.0-66.b20ead4d3ab9e78ac112966e242ded703f4a052c"
|
||||
checksum: 23183cab33f95e0711cd6cee7642f4beeeb77b8b0aa54b419f55097bfb96863a11a28f6509ab27fef0ea794522e188044ce850993a25c341333f8305711a255f
|
||||
"@prisma/engines-version@npm:4.17.0-26.6b0aef69b7cdfc787f822ecd7cdc76d5f1991584":
|
||||
version: 4.17.0-26.6b0aef69b7cdfc787f822ecd7cdc76d5f1991584
|
||||
resolution: "@prisma/engines-version@npm:4.17.0-26.6b0aef69b7cdfc787f822ecd7cdc76d5f1991584"
|
||||
checksum: 8fcbceef3b554ee7fa404bead50be5286412a097b21723272aff298b289caf2802b01b84bb85c4c9f3b592eeac114c8d6e79db083f271dc8c54f453b4a515233
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@prisma/engines@npm:4.16.1":
|
||||
version: 4.16.1
|
||||
resolution: "@prisma/engines@npm:4.16.1"
|
||||
checksum: 7b8c1a9e20434edaa919fb24585ebff573c4ace787ea5a135dfefde3571ae31d3f0b0347397774c38be6d2e3fc0087d4e21a2720fe9f7a66ffd870e34377040f
|
||||
"@prisma/engines@npm:5.0.0":
|
||||
version: 5.0.0
|
||||
resolution: "@prisma/engines@npm:5.0.0"
|
||||
checksum: 31271d85c29709059f91051d4cef7acf874014ba0128b674ca2f842e5fac61d3011e9db246dfa67ba4803081d36dbc9e31492716bab677128588343c92117b2b
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@prisma/fetch-engine@npm:4.16.1":
|
||||
version: 4.16.1
|
||||
resolution: "@prisma/fetch-engine@npm:4.16.1"
|
||||
"@prisma/fetch-engine@npm:5.0.0":
|
||||
version: 5.0.0
|
||||
resolution: "@prisma/fetch-engine@npm:5.0.0"
|
||||
dependencies:
|
||||
"@prisma/debug": 4.16.1
|
||||
"@prisma/get-platform": 4.16.1
|
||||
"@prisma/debug": 5.0.0
|
||||
"@prisma/get-platform": 5.0.0
|
||||
execa: 5.1.1
|
||||
find-cache-dir: 3.3.2
|
||||
fs-extra: 11.1.1
|
||||
@@ -3771,7 +3771,7 @@ __metadata:
|
||||
http-proxy-agent: 7.0.0
|
||||
https-proxy-agent: 7.0.0
|
||||
kleur: 4.1.5
|
||||
node-fetch: 2.6.11
|
||||
node-fetch: 2.6.12
|
||||
p-filter: 2.1.0
|
||||
p-map: 4.0.0
|
||||
p-retry: 4.6.2
|
||||
@@ -3779,27 +3779,27 @@ __metadata:
|
||||
rimraf: 3.0.2
|
||||
temp-dir: 2.0.0
|
||||
tempy: 1.0.1
|
||||
checksum: 2678c35d8ef3f5909eceec783908fdd52818ab5dc8520f69ff0ca39eaab9a2ae3a5652b85dd422f39f59014a4a9f9a6634965265901ca6a8b0915453e4e681d4
|
||||
checksum: f60f78ded05803db1455e4beae6040c6cc86fcaef2e398b8ed0df2d8aca2352a8fbb2104b81531051aa3174cf93dd2bb7070017c6de67bc661255cebda371213
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@prisma/generator-helper@npm:4.16.1":
|
||||
version: 4.16.1
|
||||
resolution: "@prisma/generator-helper@npm:4.16.1"
|
||||
"@prisma/generator-helper@npm:5.0.0":
|
||||
version: 5.0.0
|
||||
resolution: "@prisma/generator-helper@npm:5.0.0"
|
||||
dependencies:
|
||||
"@prisma/debug": 4.16.1
|
||||
"@prisma/debug": 5.0.0
|
||||
"@types/cross-spawn": 6.0.2
|
||||
cross-spawn: 7.0.3
|
||||
kleur: 4.1.5
|
||||
checksum: fa4ce40b39e2adbb5526c5c972d5c13222ef0708ef30c2aa282591f884fb6c4d06b7793f699010ffafa7f5934b9925fe669ef38413d30931dc7c15a911b99461
|
||||
checksum: 3506c9f23d2a7afcf4b99ad264f15efc7873f5e2ff9a7246a770c9ea7e7df627226a2e736890556c495cae3b0d1feaa3fd087417974500feaea14fb6da9dd0b0
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@prisma/get-platform@npm:4.16.1":
|
||||
version: 4.16.1
|
||||
resolution: "@prisma/get-platform@npm:4.16.1"
|
||||
"@prisma/get-platform@npm:5.0.0":
|
||||
version: 5.0.0
|
||||
resolution: "@prisma/get-platform@npm:5.0.0"
|
||||
dependencies:
|
||||
"@prisma/debug": 4.16.1
|
||||
"@prisma/debug": 5.0.0
|
||||
escape-string-regexp: 4.0.0
|
||||
execa: 5.1.1
|
||||
fs-jetpack: 5.1.0
|
||||
@@ -3809,22 +3809,22 @@ __metadata:
|
||||
tempy: 1.0.1
|
||||
terminal-link: 2.1.1
|
||||
ts-pattern: 4.3.0
|
||||
checksum: 93e08e4d52fc35550ce643c0f54f9d9bb027619448359adac6cd89817617d51791cf24a8873e703f1441afa386c975fc1212e37d46f8d6059ed29b16270b8527
|
||||
checksum: a5917c29198ff11bac52772496afe6f55755b715b83ab94655b616e33eded80a9e82a987b8b8fde1a48ae09b055703ae2d3e51a11471d1f61341072b2af8dbae
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@prisma/internals@npm:^4.16.1":
|
||||
version: 4.16.1
|
||||
resolution: "@prisma/internals@npm:4.16.1"
|
||||
"@prisma/internals@npm:^5.0.0":
|
||||
version: 5.0.0
|
||||
resolution: "@prisma/internals@npm:5.0.0"
|
||||
dependencies:
|
||||
"@antfu/ni": 0.21.4
|
||||
"@opentelemetry/api": 1.4.1
|
||||
"@prisma/debug": 4.16.1
|
||||
"@prisma/engines": 4.16.1
|
||||
"@prisma/fetch-engine": 4.16.1
|
||||
"@prisma/generator-helper": 4.16.1
|
||||
"@prisma/get-platform": 4.16.1
|
||||
"@prisma/prisma-fmt-wasm": 4.16.0-66.b20ead4d3ab9e78ac112966e242ded703f4a052c
|
||||
"@prisma/debug": 5.0.0
|
||||
"@prisma/engines": 5.0.0
|
||||
"@prisma/fetch-engine": 5.0.0
|
||||
"@prisma/generator-helper": 5.0.0
|
||||
"@prisma/get-platform": 5.0.0
|
||||
"@prisma/prisma-schema-wasm": 4.17.0-26.6b0aef69b7cdfc787f822ecd7cdc76d5f1991584
|
||||
archiver: 5.3.1
|
||||
arg: 5.0.2
|
||||
checkpoint-client: 1.1.24
|
||||
@@ -3843,7 +3843,7 @@ __metadata:
|
||||
is-wsl: 2.2.0
|
||||
kleur: 4.1.5
|
||||
new-github-issue-url: 0.2.1
|
||||
node-fetch: 2.6.11
|
||||
node-fetch: 2.6.12
|
||||
npm-packlist: 5.1.3
|
||||
open: 7.4.2
|
||||
p-map: 4.0.0
|
||||
@@ -3860,16 +3860,16 @@ __metadata:
|
||||
terminal-link: 2.1.1
|
||||
tmp: 0.2.1
|
||||
ts-pattern: 4.3.0
|
||||
checksum: 15b697834b9feae4749c7a815cbbd0b69a9d371eb89d49146145b0c34c175d0beb0832215fcc5c4bd825e2595f479e1b53732c6f152160fbb45e919d5c145f79
|
||||
checksum: 8d567b3cad3eaf28bb0248adf5e9f3250a21677470e8552ffbd30f8219f8af6ba7721eaf39980329a1181f5aea97daeb68dfb0af8a2d31dce8cc2a4f7f99d288
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@prisma/migrate@npm:^4.16.1":
|
||||
version: 4.16.1
|
||||
resolution: "@prisma/migrate@npm:4.16.1"
|
||||
"@prisma/migrate@npm:^5.0.0":
|
||||
version: 5.0.0
|
||||
resolution: "@prisma/migrate@npm:5.0.0"
|
||||
dependencies:
|
||||
"@prisma/debug": 4.16.1
|
||||
"@prisma/get-platform": 4.16.1
|
||||
"@prisma/debug": 5.0.0
|
||||
"@prisma/get-platform": 5.0.0
|
||||
"@sindresorhus/slugify": 1.1.2
|
||||
arg: 5.0.2
|
||||
execa: 5.1.1
|
||||
@@ -3879,7 +3879,7 @@ __metadata:
|
||||
indent-string: 4.0.0
|
||||
kleur: 4.1.5
|
||||
log-update: 4.0.0
|
||||
mariadb: 3.1.2
|
||||
mariadb: 3.2.0
|
||||
mongoose: 6.11.2
|
||||
mssql: 9.1.1
|
||||
ora: 5.4.1
|
||||
@@ -3892,14 +3892,14 @@ __metadata:
|
||||
peerDependencies:
|
||||
"@prisma/generator-helper": "*"
|
||||
"@prisma/internals": "*"
|
||||
checksum: eaf8ea05fbf29f4faedfa599057bec0f35be8f725b4017626a4bb9e27fe8621a617aae74ea5ac26328fbd65fa8345caad648cab3271c33c2f4aba3478051835e
|
||||
checksum: 05f99a25da550130d698d1f15e5a0b1e366894b0ed2134e3bf34211b44fe572b0e86ce407692ed2445bd02776423e34d0d0ca3851bb8e4265df0a43f777793bc
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@prisma/prisma-fmt-wasm@npm:4.16.0-66.b20ead4d3ab9e78ac112966e242ded703f4a052c":
|
||||
version: 4.16.0-66.b20ead4d3ab9e78ac112966e242ded703f4a052c
|
||||
resolution: "@prisma/prisma-fmt-wasm@npm:4.16.0-66.b20ead4d3ab9e78ac112966e242ded703f4a052c"
|
||||
checksum: 7db5b834f49603cfaaedda897a41fc668e1aa4caf32ee9ca5deb6c6e63dfe059780f8c2324ab415c5fbc7d0f6e8ba2dd347a076d37f1df373be5279a80c6beee
|
||||
"@prisma/prisma-schema-wasm@npm:4.17.0-26.6b0aef69b7cdfc787f822ecd7cdc76d5f1991584":
|
||||
version: 4.17.0-26.6b0aef69b7cdfc787f822ecd7cdc76d5f1991584
|
||||
resolution: "@prisma/prisma-schema-wasm@npm:4.17.0-26.6b0aef69b7cdfc787f822ecd7cdc76d5f1991584"
|
||||
checksum: c0dffdde7b9e59cecea1c581829b1ebb55a934b8b5e7abf30d1e3d498cf3fcc6da39a3c320e1c52f9edb5e5cb08ff7f9bdd24e3ea97546ba4803d03bda5de066
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -4380,6 +4380,15 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/dompurify@npm:^3.0.2":
|
||||
version: 3.0.2
|
||||
resolution: "@types/dompurify@npm:3.0.2"
|
||||
dependencies:
|
||||
"@types/trusted-types": "*"
|
||||
checksum: dc017e16a46bcc77086af7d4dc5d2bf4102f16bf1a11b2937e90380da50e77716a2a608ff52990b1293250fefc2ad7593a1378fe07e3a7e21f200d702f0a7878
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/estree-jsx@npm:^0.0.1":
|
||||
version: 0.0.1
|
||||
resolution: "@types/estree-jsx@npm:0.0.1"
|
||||
@@ -4681,6 +4690,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/trusted-types@npm:*":
|
||||
version: 2.0.3
|
||||
resolution: "@types/trusted-types@npm:2.0.3"
|
||||
checksum: 4794804bc4a4a173d589841b6d26cf455ff5dc4f3e704e847de7d65d215f2e7043d8757e4741ce3a823af3f08260a8d04a1a6e9c5ec9b20b7b04586956a6b005
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/unist@npm:*, @types/unist@npm:^2.0.0":
|
||||
version: 2.0.6
|
||||
resolution: "@types/unist@npm:2.0.6"
|
||||
@@ -4889,6 +4905,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"abab@npm:^2.0.6":
|
||||
version: 2.0.6
|
||||
resolution: "abab@npm:2.0.6"
|
||||
checksum: 6ffc1af4ff315066c62600123990d87551ceb0aafa01e6539da77b0f5987ac7019466780bf480f1787576d4385e3690c81ccc37cfda12819bf510b8ab47e5a3e
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"abbrev@npm:1, abbrev@npm:^1.0.0":
|
||||
version: 1.1.1
|
||||
resolution: "abbrev@npm:1.1.1"
|
||||
@@ -6221,6 +6244,15 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"cssstyle@npm:^3.0.0":
|
||||
version: 3.0.0
|
||||
resolution: "cssstyle@npm:3.0.0"
|
||||
dependencies:
|
||||
rrweb-cssom: ^0.6.0
|
||||
checksum: 31f694dfed9998ed93570fe539610837b878193dd8487c33cb12db8004333c53c2a3904166288bbec68388c72fb01014d46d3243ddfb02fe845989d852c06f27
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"csstype@npm:3.0.9":
|
||||
version: 3.0.9
|
||||
resolution: "csstype@npm:3.0.9"
|
||||
@@ -6249,6 +6281,17 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"data-urls@npm:^4.0.0":
|
||||
version: 4.0.0
|
||||
resolution: "data-urls@npm:4.0.0"
|
||||
dependencies:
|
||||
abab: ^2.0.6
|
||||
whatwg-mimetype: ^3.0.0
|
||||
whatwg-url: ^12.0.0
|
||||
checksum: 006e869b5bf079647949a3e9b1dd69d84b2d5d26e6b01c265485699bc96e83817d4b5aae758b2910a4c58c0601913f3a0034121c1ca2da268e9a244c57515b15
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"dayjs@npm:^1.11.8":
|
||||
version: 1.11.8
|
||||
resolution: "dayjs@npm:1.11.8"
|
||||
@@ -6296,6 +6339,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"decimal.js@npm:^10.4.3":
|
||||
version: 10.4.3
|
||||
resolution: "decimal.js@npm:10.4.3"
|
||||
checksum: 796404dcfa9d1dbfdc48870229d57f788b48c21c603c3f6554a1c17c10195fc1024de338b0cf9e1efe0c7c167eeb18f04548979bcc5fdfabebb7cc0ae3287bae
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"decode-named-character-reference@npm:^1.0.0":
|
||||
version: 1.0.2
|
||||
resolution: "decode-named-character-reference@npm:1.0.2"
|
||||
@@ -6590,6 +6640,15 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"domexception@npm:^4.0.0":
|
||||
version: 4.0.0
|
||||
resolution: "domexception@npm:4.0.0"
|
||||
dependencies:
|
||||
webidl-conversions: ^7.0.0
|
||||
checksum: ddbc1268edf33a8ba02ccc596735ede80375ee0cf124b30d2f05df5b464ba78ef4f49889b6391df4a04954e63d42d5631c7fcf8b1c4f12bc531252977a5f13d5
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"domhandler@npm:4.3.1, domhandler@npm:^4.2.0, domhandler@npm:^4.2.2":
|
||||
version: 4.3.1
|
||||
resolution: "domhandler@npm:4.3.1"
|
||||
@@ -6599,6 +6658,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"dompurify@npm:^3.0.5":
|
||||
version: 3.0.5
|
||||
resolution: "dompurify@npm:3.0.5"
|
||||
checksum: 2d9421570c833ce26ce7022955241749b646d41e8bf453f42ede9f22d0e98af482cedb7dfbf8129419eb48b351c1d677a08fc9f1cd91836ce7f6c1807a0676b2
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"domutils@npm:^2.8.0":
|
||||
version: 2.8.0
|
||||
resolution: "domutils@npm:2.8.0"
|
||||
@@ -6745,6 +6811,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"entities@npm:^4.4.0":
|
||||
version: 4.5.0
|
||||
resolution: "entities@npm:4.5.0"
|
||||
checksum: 853f8ebd5b425d350bffa97dd6958143179a5938352ccae092c62d1267c4e392a039be1bae7d51b6e4ffad25f51f9617531fedf5237f15df302ccfb452cbf2d7
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"env-paths@npm:2.2.1, env-paths@npm:^2.2.0":
|
||||
version: 2.2.1
|
||||
resolution: "env-paths@npm:2.2.1"
|
||||
@@ -8637,6 +8710,15 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"html-encoding-sniffer@npm:^3.0.0":
|
||||
version: 3.0.0
|
||||
resolution: "html-encoding-sniffer@npm:3.0.0"
|
||||
dependencies:
|
||||
whatwg-encoding: ^2.0.0
|
||||
checksum: 8d806aa00487e279e5ccb573366a951a9f68f65c90298eac9c3a2b440a7ffe46615aff2995a2f61c6746c639234e6179a97e18ca5ccbbf93d3725ef2099a4502
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"html-react-parser@npm:1.4.12":
|
||||
version: 1.4.12
|
||||
resolution: "html-react-parser@npm:1.4.12"
|
||||
@@ -8740,7 +8822,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"https-proxy-agent@npm:5, https-proxy-agent@npm:^5.0.0":
|
||||
"https-proxy-agent@npm:5, https-proxy-agent@npm:^5.0.0, https-proxy-agent@npm:^5.0.1":
|
||||
version: 5.0.1
|
||||
resolution: "https-proxy-agent@npm:5.0.1"
|
||||
dependencies:
|
||||
@@ -8792,7 +8874,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"iconv-lite@npm:^0.6.2, iconv-lite@npm:^0.6.3":
|
||||
"iconv-lite@npm:0.6.3, iconv-lite@npm:^0.6.2, iconv-lite@npm:^0.6.3":
|
||||
version: 0.6.3
|
||||
resolution: "iconv-lite@npm:0.6.3"
|
||||
dependencies:
|
||||
@@ -9223,6 +9305,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"is-potential-custom-element-name@npm:^1.0.1":
|
||||
version: 1.0.1
|
||||
resolution: "is-potential-custom-element-name@npm:1.0.1"
|
||||
checksum: ced7bbbb6433a5b684af581872afe0e1767e2d1146b2207ca0068a648fb5cab9d898495d1ac0583524faaf24ca98176a7d9876363097c2d14fee6dd324f3a1ab
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"is-reference@npm:^3.0.0":
|
||||
version: 3.0.1
|
||||
resolution: "is-reference@npm:3.0.1"
|
||||
@@ -9380,6 +9469,17 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"isomorphic-dompurify@npm:^1.8.0":
|
||||
version: 1.8.0
|
||||
resolution: "isomorphic-dompurify@npm:1.8.0"
|
||||
dependencies:
|
||||
"@types/dompurify": ^3.0.2
|
||||
dompurify: ^3.0.5
|
||||
jsdom: ^22.1.0
|
||||
checksum: 5ac39dd13bf9923fdc2932e18cf5f7c4e777fa8c2ffd018ee755207b53cdb739d7e6f2e36103b7bd0a1d2eceb5172954ccd40873cea8c772b42c341a19597897
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"jackspeak@npm:^2.0.3":
|
||||
version: 2.2.1
|
||||
resolution: "jackspeak@npm:2.2.1"
|
||||
@@ -9439,6 +9539,42 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"jsdom@npm:^22.1.0":
|
||||
version: 22.1.0
|
||||
resolution: "jsdom@npm:22.1.0"
|
||||
dependencies:
|
||||
abab: ^2.0.6
|
||||
cssstyle: ^3.0.0
|
||||
data-urls: ^4.0.0
|
||||
decimal.js: ^10.4.3
|
||||
domexception: ^4.0.0
|
||||
form-data: ^4.0.0
|
||||
html-encoding-sniffer: ^3.0.0
|
||||
http-proxy-agent: ^5.0.0
|
||||
https-proxy-agent: ^5.0.1
|
||||
is-potential-custom-element-name: ^1.0.1
|
||||
nwsapi: ^2.2.4
|
||||
parse5: ^7.1.2
|
||||
rrweb-cssom: ^0.6.0
|
||||
saxes: ^6.0.0
|
||||
symbol-tree: ^3.2.4
|
||||
tough-cookie: ^4.1.2
|
||||
w3c-xmlserializer: ^4.0.0
|
||||
webidl-conversions: ^7.0.0
|
||||
whatwg-encoding: ^2.0.0
|
||||
whatwg-mimetype: ^3.0.0
|
||||
whatwg-url: ^12.0.1
|
||||
ws: ^8.13.0
|
||||
xml-name-validator: ^4.0.0
|
||||
peerDependencies:
|
||||
canvas: ^2.5.0
|
||||
peerDependenciesMeta:
|
||||
canvas:
|
||||
optional: true
|
||||
checksum: d955ab83a6dad3e6af444098d30647c719bbb4cf97de053aa5751c03c8d6f3283d8c4d7fc2774c181f1d432fb0250e7332bc159e6b466424f4e337d73adcbf30
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"jsesc@npm:3.0.2":
|
||||
version: 3.0.2
|
||||
resolution: "jsesc@npm:3.0.2"
|
||||
@@ -10004,16 +10140,16 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"mariadb@npm:3.1.2":
|
||||
version: 3.1.2
|
||||
resolution: "mariadb@npm:3.1.2"
|
||||
"mariadb@npm:3.2.0":
|
||||
version: 3.2.0
|
||||
resolution: "mariadb@npm:3.2.0"
|
||||
dependencies:
|
||||
"@types/geojson": ^7946.0.10
|
||||
"@types/node": ^17.0.45
|
||||
denque: ^2.1.0
|
||||
iconv-lite: ^0.6.3
|
||||
lru-cache: ^7.14.0
|
||||
checksum: 70079e09ecc2ae4113b317912419053645aabbcc3e584c7c1dba89306cb0af1a47438ccbf03f1c97f8fee07b952c33952edbc100847d55a30aa9d897b04c729f
|
||||
checksum: 971a84cf42e026ddfd209450cbf98b2af00a6cf9a49fe245b2662e767196a7a8f26ab728cdc6b715aafbb40631f57754e19251f9e38e679106c7d190106d4eee
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -11332,6 +11468,20 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"node-fetch@npm:2.6.12":
|
||||
version: 2.6.12
|
||||
resolution: "node-fetch@npm:2.6.12"
|
||||
dependencies:
|
||||
whatwg-url: ^5.0.0
|
||||
peerDependencies:
|
||||
encoding: ^0.1.0
|
||||
peerDependenciesMeta:
|
||||
encoding:
|
||||
optional: true
|
||||
checksum: 3bc1655203d47ee8e313c0d96664b9673a3d4dd8002740318e9d27d14ef306693a4b2ef8d6525775056fd912a19e23f3ac0d7111ad8925877b7567b29a625592
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"node-gyp@npm:latest":
|
||||
version: 9.4.0
|
||||
resolution: "node-gyp@npm:9.4.0"
|
||||
@@ -11501,6 +11651,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"nwsapi@npm:^2.2.4":
|
||||
version: 2.2.7
|
||||
resolution: "nwsapi@npm:2.2.7"
|
||||
checksum: cab25f7983acec7e23490fec3ef7be608041b460504229770e3bfcf9977c41d6fe58f518994d3bd9aa3a101f501089a3d4a63536f4ff8ae4b8c4ca23bdbfda4e
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"object-assign@npm:^4.0.1, object-assign@npm:^4.1.0, object-assign@npm:^4.1.1":
|
||||
version: 4.1.1
|
||||
resolution: "object-assign@npm:4.1.1"
|
||||
@@ -11912,6 +12069,15 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"parse5@npm:^7.1.2":
|
||||
version: 7.1.2
|
||||
resolution: "parse5@npm:7.1.2"
|
||||
dependencies:
|
||||
entities: ^4.4.0
|
||||
checksum: 59465dd05eb4c5ec87b76173d1c596e152a10e290b7abcda1aecf0f33be49646ea74840c69af975d7887543ea45564801736356c568d6b5e71792fd0f4055713
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"parseurl@npm:~1.3.3":
|
||||
version: 1.3.3
|
||||
resolution: "parseurl@npm:1.3.3"
|
||||
@@ -12369,15 +12535,14 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"prisma@npm:^4.16.1":
|
||||
version: 4.16.1
|
||||
resolution: "prisma@npm:4.16.1"
|
||||
"prisma@npm:^5.0.0":
|
||||
version: 5.0.0
|
||||
resolution: "prisma@npm:5.0.0"
|
||||
dependencies:
|
||||
"@prisma/engines": 4.16.1
|
||||
"@prisma/engines": 5.0.0
|
||||
bin:
|
||||
prisma: build/index.js
|
||||
prisma2: build/index.js
|
||||
checksum: eb0f43970464fbaa6ba190a714f23d00acd39dcc14fd8a7183dffc51a548cc32040cc5360415787578749862af7501a7bedcf9f2a0e64a3f82e3af402e9439d7
|
||||
checksum: fdc62377853d25b4db664c736fd0b08d2b0c6db5752e6f6c6ec3bda77634cfb79e6f49d52d4b8f54ddb8ec9c28fc3fb0c13f95caf61085447d0929e258af9284
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -12473,6 +12638,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"psl@npm:^1.1.33":
|
||||
version: 1.9.0
|
||||
resolution: "psl@npm:1.9.0"
|
||||
checksum: 20c4277f640c93d393130673f392618e9a8044c6c7bf61c53917a0fddb4952790f5f362c6c730a9c32b124813e173733f9895add8d26f566ed0ea0654b2e711d
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"pump@npm:^2.0.0":
|
||||
version: 2.0.1
|
||||
resolution: "pump@npm:2.0.1"
|
||||
@@ -12504,7 +12676,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"punycode@npm:^2.1.0, punycode@npm:^2.1.1":
|
||||
"punycode@npm:^2.1.0, punycode@npm:^2.1.1, punycode@npm:^2.3.0":
|
||||
version: 2.3.0
|
||||
resolution: "punycode@npm:2.3.0"
|
||||
checksum: 39f760e09a2a3bbfe8f5287cf733ecdad69d6af2fe6f97ca95f24b8921858b91e9ea3c9eeec6e08cede96181b3bb33f95c6ffd8c77e63986508aa2e8159fa200
|
||||
@@ -12520,6 +12692,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"querystringify@npm:^2.1.1":
|
||||
version: 2.2.0
|
||||
resolution: "querystringify@npm:2.2.0"
|
||||
checksum: 5641ea231bad7ef6d64d9998faca95611ed4b11c2591a8cae741e178a974f6a8e0ebde008475259abe1621cb15e692404e6b6626e927f7b849d5c09392604b15
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"queue-microtask@npm:^1.2.2":
|
||||
version: 1.2.3
|
||||
resolution: "queue-microtask@npm:1.2.3"
|
||||
@@ -13021,6 +13200,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"requires-port@npm:^1.0.0":
|
||||
version: 1.0.0
|
||||
resolution: "requires-port@npm:1.0.0"
|
||||
checksum: eee0e303adffb69be55d1a214e415cf42b7441ae858c76dfc5353148644f6fd6e698926fc4643f510d5c126d12a705e7c8ed7e38061113bdf37547ab356797ff
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"resolve-alpn@npm:^1.0.0":
|
||||
version: 1.2.1
|
||||
resolution: "resolve-alpn@npm:1.2.1"
|
||||
@@ -13199,6 +13385,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"rrweb-cssom@npm:^0.6.0":
|
||||
version: 0.6.0
|
||||
resolution: "rrweb-cssom@npm:0.6.0"
|
||||
checksum: 182312f6e4f41d18230ccc34f14263bc8e8a6b9d30ee3ec0d2d8e643c6f27964cd7a8d638d4a00e988d93e8dc55369f4ab5a473ccfeff7a8bab95b36d2b5499c
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"run-applescript@npm:^5.0.0":
|
||||
version: 5.0.0
|
||||
resolution: "run-applescript@npm:5.0.0"
|
||||
@@ -13283,6 +13476,15 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"saxes@npm:^6.0.0":
|
||||
version: 6.0.0
|
||||
resolution: "saxes@npm:6.0.0"
|
||||
dependencies:
|
||||
xmlchars: ^2.2.0
|
||||
checksum: d3fa3e2aaf6c65ed52ee993aff1891fc47d5e47d515164b5449cbf5da2cbdc396137e55590472e64c5c436c14ae64a8a03c29b9e7389fc6f14035cf4e982ef3b
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"scheduler@npm:^0.23.0":
|
||||
version: 0.23.0
|
||||
resolution: "scheduler@npm:0.23.0"
|
||||
@@ -14017,6 +14219,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"symbol-tree@npm:^3.2.4":
|
||||
version: 3.2.4
|
||||
resolution: "symbol-tree@npm:3.2.4"
|
||||
checksum: 6e8fc7e1486b8b54bea91199d9535bb72f10842e40c79e882fc94fb7b14b89866adf2fd79efa5ebb5b658bc07fb459ccce5ac0e99ef3d72f474e74aaf284029d
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"synckit@npm:^0.8.5":
|
||||
version: 0.8.5
|
||||
resolution: "synckit@npm:0.8.5"
|
||||
@@ -14264,6 +14473,18 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"tough-cookie@npm:^4.1.2":
|
||||
version: 4.1.3
|
||||
resolution: "tough-cookie@npm:4.1.3"
|
||||
dependencies:
|
||||
psl: ^1.1.33
|
||||
punycode: ^2.1.1
|
||||
universalify: ^0.2.0
|
||||
url-parse: ^1.5.3
|
||||
checksum: c9226afff36492a52118432611af083d1d8493a53ff41ec4ea48e5b583aec744b989e4280bcf476c910ec1525a89a4a0f1cae81c08b18fb2ec3a9b3a72b91dcc
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"tr46@npm:^1.0.1":
|
||||
version: 1.0.1
|
||||
resolution: "tr46@npm:1.0.1"
|
||||
@@ -14282,6 +14503,15 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"tr46@npm:^4.1.1":
|
||||
version: 4.1.1
|
||||
resolution: "tr46@npm:4.1.1"
|
||||
dependencies:
|
||||
punycode: ^2.3.0
|
||||
checksum: aeeb821ac2cd792e63ec84888b4fd6598ac6ed75d861579e21a5cf9d4ee78b2c6b94e7d45036f2ca2088bc85b9b46560ad23c4482979421063b24137349dbd96
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"tr46@npm:~0.0.3":
|
||||
version: 0.0.3
|
||||
resolution: "tr46@npm:0.0.3"
|
||||
@@ -14711,6 +14941,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"universalify@npm:^0.2.0":
|
||||
version: 0.2.0
|
||||
resolution: "universalify@npm:0.2.0"
|
||||
checksum: e86134cb12919d177c2353196a4cc09981524ee87abf621f7bc8d249dbbbebaec5e7d1314b96061497981350df786e4c5128dbf442eba104d6e765bc260678b5
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"universalify@npm:^2.0.0":
|
||||
version: 2.0.0
|
||||
resolution: "universalify@npm:2.0.0"
|
||||
@@ -14755,6 +14992,16 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"url-parse@npm:^1.5.3":
|
||||
version: 1.5.10
|
||||
resolution: "url-parse@npm:1.5.10"
|
||||
dependencies:
|
||||
querystringify: ^2.1.1
|
||||
requires-port: ^1.0.0
|
||||
checksum: fbdba6b1d83336aca2216bbdc38ba658d9cfb8fc7f665eb8b17852de638ff7d1a162c198a8e4ed66001ddbf6c9888d41e4798912c62b4fd777a31657989f7bdf
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"use-callback-ref@npm:^1.3.0":
|
||||
version: 1.3.0
|
||||
resolution: "use-callback-ref@npm:1.3.0"
|
||||
@@ -14991,6 +15238,15 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"w3c-xmlserializer@npm:^4.0.0":
|
||||
version: 4.0.0
|
||||
resolution: "w3c-xmlserializer@npm:4.0.0"
|
||||
dependencies:
|
||||
xml-name-validator: ^4.0.0
|
||||
checksum: eba070e78deb408ae8defa4d36b429f084b2b47a4741c4a9be3f27a0a3d1845e277e3072b04391a138f7e43776842627d1334e448ff13ff90ad9fb1214ee7091
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"watchpack@npm:2.4.0":
|
||||
version: 2.4.0
|
||||
resolution: "watchpack@npm:2.4.0"
|
||||
@@ -15031,6 +15287,22 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"whatwg-encoding@npm:^2.0.0":
|
||||
version: 2.0.0
|
||||
resolution: "whatwg-encoding@npm:2.0.0"
|
||||
dependencies:
|
||||
iconv-lite: 0.6.3
|
||||
checksum: 7087810c410aa9b689cbd6af8773341a53cdc1f3aae2a882c163bd5522ec8ca4cdfc269aef417a5792f411807d5d77d50df4c24e3abb00bb60192858a40cc675
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"whatwg-mimetype@npm:^3.0.0":
|
||||
version: 3.0.0
|
||||
resolution: "whatwg-mimetype@npm:3.0.0"
|
||||
checksum: ce08bbb36b6aaf64f3a84da89707e3e6a31e5ab1c1a2379fd68df79ba712a4ab090904f0b50e6693b0dafc8e6343a6157e40bf18fdffd26e513cf95ee2a59824
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"whatwg-url@npm:^11.0.0":
|
||||
version: 11.0.0
|
||||
resolution: "whatwg-url@npm:11.0.0"
|
||||
@@ -15041,6 +15313,16 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"whatwg-url@npm:^12.0.0, whatwg-url@npm:^12.0.1":
|
||||
version: 12.0.1
|
||||
resolution: "whatwg-url@npm:12.0.1"
|
||||
dependencies:
|
||||
tr46: ^4.1.1
|
||||
webidl-conversions: ^7.0.0
|
||||
checksum: 8698993b763c1e7eda5ed16c31dab24bca6489626aca7caf8b5a2b64684dda6578194786f10ec42ceb1c175feea16d0a915096e6419e08d154ce551c43176972
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"whatwg-url@npm:^5.0.0":
|
||||
version: 5.0.0
|
||||
resolution: "whatwg-url@npm:5.0.0"
|
||||
@@ -15194,6 +15476,21 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"ws@npm:^8.13.0":
|
||||
version: 8.13.0
|
||||
resolution: "ws@npm:8.13.0"
|
||||
peerDependencies:
|
||||
bufferutil: ^4.0.1
|
||||
utf-8-validate: ">=5.0.2"
|
||||
peerDependenciesMeta:
|
||||
bufferutil:
|
||||
optional: true
|
||||
utf-8-validate:
|
||||
optional: true
|
||||
checksum: 53e991bbf928faf5dc6efac9b8eb9ab6497c69feeb94f963d648b7a3530a720b19ec2e0ec037344257e05a4f35bd9ad04d9de6f289615ffb133282031b18c61c
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"xdm@npm:^2.0.0":
|
||||
version: 2.1.0
|
||||
resolution: "xdm@npm:2.1.0"
|
||||
@@ -15227,6 +15524,20 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"xml-name-validator@npm:^4.0.0":
|
||||
version: 4.0.0
|
||||
resolution: "xml-name-validator@npm:4.0.0"
|
||||
checksum: af100b79c29804f05fa35aa3683e29a321db9b9685d5e5febda3fa1e40f13f85abc40f45a6b2bf7bee33f68a1dc5e8eaef4cec100a304a9db565e6061d4cb5ad
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"xmlchars@npm:^2.2.0":
|
||||
version: 2.2.0
|
||||
resolution: "xmlchars@npm:2.2.0"
|
||||
checksum: 8c70ac94070ccca03f47a81fcce3b271bd1f37a591bf5424e787ae313fcb9c212f5f6786e1fa82076a2c632c0141552babcd85698c437506dfa6ae2d58723062
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"xregexp@npm:2.0.0":
|
||||
version: 2.0.0
|
||||
resolution: "xregexp@npm:2.0.0"
|
||||
@@ -15311,9 +15622,9 @@ __metadata:
|
||||
"@mantine/next": ^6.0.14
|
||||
"@mantine/notifications": ^6.0.14
|
||||
"@mantine/nprogress": ^6.0.14
|
||||
"@prisma/client": 4.16.1
|
||||
"@prisma/internals": ^4.16.1
|
||||
"@prisma/migrate": ^4.16.1
|
||||
"@prisma/client": ^5.0.0
|
||||
"@prisma/internals": ^5.0.0
|
||||
"@prisma/migrate": ^5.0.0
|
||||
"@remix-run/dev": ^1.16.1
|
||||
"@remix-run/eslint-config": ^1.16.1
|
||||
"@tabler/icons-react": ^2.23.0
|
||||
@@ -15334,13 +15645,14 @@ __metadata:
|
||||
eslint: ^8.41.0
|
||||
express: ^4.18.2
|
||||
highlight.js: ^11.8.0
|
||||
isomorphic-dompurify: ^1.8.0
|
||||
katex: ^0.16.8
|
||||
mantine-datatable: ^2.8.2
|
||||
ms: ^2.1.3
|
||||
multer: ^1.4.5-lts.1
|
||||
next: ^13.4.7
|
||||
npm-run-all: ^4.1.5
|
||||
prisma: ^4.16.1
|
||||
prisma: ^5.0.0
|
||||
react: ^18.2.0
|
||||
react-dom: ^18.2.0
|
||||
react-markdown: ^8.0.7
|
||||
|
||||
Reference in New Issue
Block a user