diff --git a/.github/ISSUE_TEMPLATE/bug.yml b/.github/ISSUE_TEMPLATE/bug.yml
index 5bed5f9..c9180e6 100644
--- a/.github/ISSUE_TEMPLATE/bug.yml
+++ b/.github/ISSUE_TEMPLATE/bug.yml
@@ -17,8 +17,9 @@ body:
label: Version
description: What version of Zipline are you using?
options:
- - upstream
- - latest
+ - upstream (ghcr.io/diced/zipline:trunk)
+ - latest (ghcr.io/diced/zipline:latest)
+ - other (provide version in additional info)
validations:
required: true
- type: dropdown
@@ -28,14 +29,15 @@ body:
multiple: true
options:
- Firefox
- - Chrome
+ - Chromium-based (Chrome, Edge, Brave, Opera, mobile chrome/chromium based, etc)
- Safari
- - Microsoft Edge
+ - Firefox Mobile
+ - Safari Mobile
- type: textarea
id: zipline-logs
attributes:
label: Zipline Logs
- description: Please copy and paste any relevant log output.
+ description: Please copy and paste any relevant log output. Not seeing anything interesting? Try adding the `DEBUG=true` environment variable to see more logs, make sure to review the output and remove any sensitive information as it can be VERY verbose at times.
render: shell
- type: textarea
id: browser-logs
@@ -43,3 +45,8 @@ body:
label: Browser Logs
description: Please copy and paste any relevant log output.
render: shell
+ - type: textarea
+ id: additional-info
+ attributes:
+ label: Additional Info
+ description: Anything else that could be used to narrow down the issue, like your config.
diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml
index e1e8e7d..d5e2306 100644
--- a/.github/ISSUE_TEMPLATE/config.yml
+++ b/.github/ISSUE_TEMPLATE/config.yml
@@ -3,3 +3,6 @@ contact_links:
- name: Zipline Discord
url: https://discord.gg/EAhCRfGxCF
about: Ask for help with anything related to Zipline!
+ - name: Zipline Docs
+ url: https://zipline.diced.tech
+ about: Maybe take a look a the docs?
diff --git a/package.json b/package.json
index b67f80e..67b4b57 100644
--- a/package.json
+++ b/package.json
@@ -4,7 +4,7 @@
"license": "MIT",
"scripts": {
"dev": "npm-run-all build:server dev:run",
- "dev:run": "cross-env REACT_EDITOR=code NODE_ENV=development RECOIL_DUPLICATE_ATOM_KEY_CHECKING_ENABLED=false node --enable-source-maps dist/server",
+ "dev:run": "cross-env DEBUG=true REACT_EDITOR=code NODE_ENV=development RECOIL_DUPLICATE_ATOM_KEY_CHECKING_ENABLED=false node --enable-source-maps dist/server",
"build": "npm-run-all build:server build:schema build:next",
"build-ci": "cross-env ZIPLINE_DOCKER_BUILD=1 npm-run-all build:server build:schema build:next",
"build:server": "node esbuild.config.js",
diff --git a/src/components/File.tsx b/src/components/File.tsx
index 9da9cd8..949372d 100644
--- a/src/components/File.tsx
+++ b/src/components/File.tsx
@@ -1,8 +1,8 @@
import { Button, Card, Group, LoadingOverlay, Modal, Stack, Text, Title, Tooltip } from '@mantine/core';
import { useClipboard } from '@mantine/hooks';
import { showNotification } from '@mantine/notifications';
-import { relativeTime } from 'lib/utils/client';
import { useFileDelete, useFileFavorite } from 'lib/queries/files';
+import { relativeTime } from 'lib/utils/client';
import { useState } from 'react';
import {
CalendarIcon,
@@ -11,15 +11,15 @@ import {
CrossIcon,
DeleteIcon,
ExternalLinkIcon,
+ EyeIcon,
FileIcon,
HashIcon,
ImageIcon,
StarIcon,
- EyeIcon,
} from './icons';
+import Link from './Link';
import MutedText from './MutedText';
import Type from './Type';
-import Link from './Link';
export function FileMeta({ Icon, title, subtitle, ...other }) {
return other.tooltip ? (
diff --git a/src/components/Layout.tsx b/src/components/Layout.tsx
index 788c205..3425497 100644
--- a/src/components/Layout.tsx
+++ b/src/components/Layout.tsx
@@ -1,11 +1,14 @@
import {
AppShell,
+ Badge,
Box,
Burger,
Button,
- Divider,
+ Group,
Header,
+ Image,
MediaQuery,
+ Menu,
Navbar,
NavLink,
Paper,
@@ -15,13 +18,9 @@ import {
Stack,
Text,
Title,
+ Tooltip,
UnstyledButton,
useMantineTheme,
- Group,
- Image,
- Tooltip,
- Badge,
- Menu,
} from '@mantine/core';
import { useClipboard } from '@mantine/hooks';
import { useModals } from '@mantine/modals';
@@ -30,18 +29,21 @@ import useFetch from 'hooks/useFetch';
import { useVersion } from 'lib/queries/version';
import { userSelector } from 'lib/recoil/user';
import { capitalize } from 'lib/utils/client';
-import { useRecoilState } from 'recoil';
import Link from 'next/link';
import { useRouter } from 'next/router';
import { useState } from 'react';
+import { useRecoilState } from 'recoil';
import {
- ExternalLinkIcon,
ActivityIcon,
CheckIcon,
CopyIcon,
CrossIcon,
DeleteIcon,
+ DiscordIcon,
+ ExternalLinkIcon,
FileIcon,
+ GitHubIcon,
+ GoogleIcon,
HomeIcon,
LinkIcon,
LogoutIcon,
@@ -51,9 +53,6 @@ import {
TypeIcon,
UploadIcon,
UserIcon,
- DiscordIcon,
- GitHubIcon,
- GoogleIcon,
} from './icons';
import { friendlyThemeName, themes } from './Theming';
@@ -136,12 +135,12 @@ const items = [
{
icon: ,
text: 'Upload',
- link: '/dashboard/upload',
+ link: '/dashboard/upload/file',
},
{
icon: ,
text: 'Upload Text',
- link: '/dashboard/text',
+ link: '/dashboard/upload/text',
},
];
@@ -150,7 +149,7 @@ const admin_items = [
icon: ,
text: 'Users',
link: '/dashboard/users',
- if: (props) => true,
+ if: () => true,
},
{
icon: ,
diff --git a/src/components/PasswordStrength.tsx b/src/components/PasswordStrength.tsx
index 47097fe..5b0494d 100644
--- a/src/components/PasswordStrength.tsx
+++ b/src/components/PasswordStrength.tsx
@@ -1,7 +1,7 @@
// https://mantine.dev/core/password-input/
+import { Box, PasswordInput, Popover, Progress, Text } from '@mantine/core';
import { useState } from 'react';
-import { PasswordInput, Progress, Text, Popover, Box } from '@mantine/core';
import { CheckIcon, CrossIcon } from './icons';
function PasswordRequirement({ meets, label }: { meets: boolean; label: string }) {
diff --git a/src/components/Theming.tsx b/src/components/Theming.tsx
index 1141c7f..0565a11 100644
--- a/src/components/Theming.tsx
+++ b/src/components/Theming.tsx
@@ -12,12 +12,12 @@ import matcha_dark_azul from 'lib/themes/matcha_dark_azul';
import nord from 'lib/themes/nord';
import qogir_dark from 'lib/themes/qogir_dark';
-import { MantineProvider, MantineThemeOverride } from '@mantine/core';
+import { createEmotionCache, MantineProvider, MantineThemeOverride } from '@mantine/core';
import { useColorScheme } from '@mantine/hooks';
import { ModalsProvider } from '@mantine/modals';
import { NotificationsProvider } from '@mantine/notifications';
-import { useRecoilValue } from 'recoil';
import { userSelector } from 'lib/recoil/user';
+import { useRecoilValue } from 'recoil';
export const themes = {
system: (colorScheme: 'dark' | 'light') => (colorScheme === 'dark' ? dark_blue : light_blue),
@@ -47,6 +47,8 @@ export const friendlyThemeName = {
qogir_dark: 'Qogir Dark',
};
+const cache = createEmotionCache({ key: 'zipline' });
+
export default function ZiplineTheming({ Component, pageProps, ...props }) {
const user = useRecoilValue(userSelector);
const colorScheme = useColorScheme();
@@ -65,8 +67,14 @@ export default function ZiplineTheming({ Component, pageProps, ...props }) {
({
@@ -92,6 +100,7 @@ export default function ZiplineTheming({ Component, pageProps, ...props }) {
Popover: {
defaultProps: {
transition: 'pop',
+ shadow: 'lg',
},
},
LoadingOverlay: {
diff --git a/src/components/dropzone/Dropzone.tsx b/src/components/dropzone/Dropzone.tsx
index 87d9688..482f7b1 100644
--- a/src/components/dropzone/Dropzone.tsx
+++ b/src/components/dropzone/Dropzone.tsx
@@ -1,6 +1,5 @@
-import React from 'react';
-import { Dropzone as MantineDropzone } from '@mantine/dropzone';
import { Group, Text, useMantineTheme } from '@mantine/core';
+import { Dropzone as MantineDropzone } from '@mantine/dropzone';
import { ImageIcon } from 'components/icons';
export default function Dropzone({ loading, onDrop, children }) {
diff --git a/src/components/dropzone/DropzoneFile.tsx b/src/components/dropzone/DropzoneFile.tsx
index 5c77091..0b38562 100644
--- a/src/components/dropzone/DropzoneFile.tsx
+++ b/src/components/dropzone/DropzoneFile.tsx
@@ -1,5 +1,4 @@
-import React from 'react';
-import { Table, Tooltip, Badge, HoverCard, Text, useMantineTheme, Group } from '@mantine/core';
+import { Badge, Group, HoverCard, Table, useMantineTheme } from '@mantine/core';
import Type from 'components/Type';
export function FilePreview({ file }: { file: File }) {
diff --git a/src/components/pages/Dashboard/RecentFiles.tsx b/src/components/pages/Dashboard/RecentFiles.tsx
index 73c5428..757dcf1 100644
--- a/src/components/pages/Dashboard/RecentFiles.tsx
+++ b/src/components/pages/Dashboard/RecentFiles.tsx
@@ -1,4 +1,4 @@
-import { Box, Card as MantineCard, Center, Group, SimpleGrid, Skeleton, Title } from '@mantine/core';
+import { Card as MantineCard, Center, Group, SimpleGrid, Skeleton, Title } from '@mantine/core';
import { randomId } from '@mantine/hooks';
import File from 'components/File';
import MutedText from 'components/MutedText';
diff --git a/src/components/pages/Dashboard/StatCards.tsx b/src/components/pages/Dashboard/StatCards.tsx
index 8ceaf53..71885f3 100644
--- a/src/components/pages/Dashboard/StatCards.tsx
+++ b/src/components/pages/Dashboard/StatCards.tsx
@@ -1,8 +1,8 @@
import { SimpleGrid } from '@mantine/core';
import { FileIcon } from 'components/icons';
import StatCard from 'components/StatCard';
-import { percentChange } from 'lib/utils/client';
import { useStats } from 'lib/queries/stats';
+import { percentChange } from 'lib/utils/client';
import { Database, Eye, Users } from 'react-feather';
export function StatCards() {
diff --git a/src/components/pages/Files/FilePagation.tsx b/src/components/pages/Files/FilePagation.tsx
index 18d6338..662abb0 100644
--- a/src/components/pages/Files/FilePagation.tsx
+++ b/src/components/pages/Files/FilePagation.tsx
@@ -1,4 +1,4 @@
-import { Box, Center, Checkbox, Group, Pagination, SimpleGrid, Skeleton, Text, Title } from '@mantine/core';
+import { Box, Center, Checkbox, Group, Pagination, SimpleGrid, Skeleton, Title } from '@mantine/core';
import File from 'components/File';
import { FileIcon } from 'components/icons';
import MutedText from 'components/MutedText';
diff --git a/src/components/pages/Invites.tsx b/src/components/pages/Invites.tsx
index a799ac7..3065cfe 100644
--- a/src/components/pages/Invites.tsx
+++ b/src/components/pages/Invites.tsx
@@ -13,16 +13,16 @@ import {
Title,
Tooltip,
} from '@mantine/core';
-import { useClipboard } from '@mantine/hooks';
import { useForm } from '@mantine/form';
+import { useClipboard } from '@mantine/hooks';
import { useModals } from '@mantine/modals';
import { showNotification } from '@mantine/notifications';
import { CopyIcon, CrossIcon, DeleteIcon, PlusIcon, TagIcon } from 'components/icons';
import MutedText from 'components/MutedText';
import useFetch from 'hooks/useFetch';
+import { expireText, relativeTime } from 'lib/utils/client';
import { useRouter } from 'next/router';
import { useEffect, useState } from 'react';
-import { expireText, relativeTime } from 'lib/utils/client';
const expires = ['30m', '1h', '6h', '12h', '1d', '3d', '5d', '7d', 'never'];
diff --git a/src/components/pages/Manage/GeneratorModal.tsx b/src/components/pages/Manage/GeneratorModal.tsx
index 670fda8..fbb3b96 100644
--- a/src/components/pages/Manage/GeneratorModal.tsx
+++ b/src/components/pages/Manage/GeneratorModal.tsx
@@ -1,4 +1,4 @@
-import { Modal, Select, NumberInput, Group, Checkbox, Button, Title, Text } from '@mantine/core';
+import { Button, Checkbox, Group, Modal, NumberInput, Select, Text, Title } from '@mantine/core';
import { useForm } from '@mantine/form';
import { DownloadIcon } from 'components/icons';
diff --git a/src/components/pages/Stats/Graphs.tsx b/src/components/pages/Stats/Graphs.tsx
index dd08dd6..a7950c6 100644
--- a/src/components/pages/Stats/Graphs.tsx
+++ b/src/components/pages/Stats/Graphs.tsx
@@ -13,8 +13,8 @@ import {
} from 'chart.js';
import ChartDataLabels from 'chartjs-plugin-datalabels';
import ColorHash from 'color-hash';
-import { bytesToHuman } from 'lib/utils/bytes';
import { useStats } from 'lib/queries/stats';
+import { bytesToHuman } from 'lib/utils/bytes';
import { useMemo } from 'react';
import { Chart, Pie } from 'react-chartjs-2';
diff --git a/src/components/pages/Stats/Types.tsx b/src/components/pages/Stats/Types.tsx
index f670de7..564f013 100644
--- a/src/components/pages/Stats/Types.tsx
+++ b/src/components/pages/Stats/Types.tsx
@@ -1,4 +1,4 @@
-import { LoadingOverlay, Card, Box } from '@mantine/core';
+import { Box, Card, LoadingOverlay } from '@mantine/core';
import { SmallTable } from 'components/SmallTable';
import { useStats } from 'lib/queries/stats';
diff --git a/src/components/pages/Upload.tsx b/src/components/pages/Upload.tsx
index 0ac83c0..6fdaf33 100644
--- a/src/components/pages/Upload.tsx
+++ b/src/components/pages/Upload.tsx
@@ -2,12 +2,12 @@ import {
Button,
Collapse,
Group,
+ NumberInput,
+ PasswordInput,
Progress,
Select,
Title,
- PasswordInput,
Tooltip,
- NumberInput,
} from '@mantine/core';
import { randomId, useClipboard } from '@mantine/hooks';
import { showNotification, updateNotification } from '@mantine/notifications';
diff --git a/src/components/pages/UploadText.tsx b/src/components/pages/UploadText.tsx
index b895b91..72dcb78 100644
--- a/src/components/pages/UploadText.tsx
+++ b/src/components/pages/UploadText.tsx
@@ -1,12 +1,12 @@
import { Button, Group, NumberInput, PasswordInput, Select, Tabs, Title, Tooltip } from '@mantine/core';
-import { Prism } from '@mantine/prism';
-import { Language } from 'prism-react-renderer';
import { showNotification, updateNotification } from '@mantine/notifications';
+import { Prism } from '@mantine/prism';
import CodeInput from 'components/CodeInput';
import { ClockIcon, ImageIcon, TypeIcon, UploadIcon } from 'components/icons';
import Link from 'components/Link';
import exts from 'lib/exts';
import { userSelector } from 'lib/recoil/user';
+import { Language } from 'prism-react-renderer';
import { useState } from 'react';
import { useRecoilValue } from 'recoil';
diff --git a/src/components/pages/Urls/index.tsx b/src/components/pages/Urls/index.tsx
index 8852715..7339117 100644
--- a/src/components/pages/Urls/index.tsx
+++ b/src/components/pages/Urls/index.tsx
@@ -1,25 +1,25 @@
import {
ActionIcon,
Button,
+ Card,
+ Center,
Group,
Modal,
+ NumberInput,
SimpleGrid,
Skeleton,
TextInput,
Title,
- Card,
- Center,
- NumberInput,
} from '@mantine/core';
import { useForm } from '@mantine/form';
import { showNotification } from '@mantine/notifications';
import { CrossIcon, LinkIcon, PlusIcon } from 'components/icons';
-import { useEffect, useState } from 'react';
-import { useURLs } from 'lib/queries/url';
-import URLCard from './URLCard';
import MutedText from 'components/MutedText';
-import { useRecoilValue } from 'recoil';
+import { useURLs } from 'lib/queries/url';
import { userSelector } from 'lib/recoil/user';
+import { useEffect, useState } from 'react';
+import { useRecoilValue } from 'recoil';
+import URLCard from './URLCard';
export default function Urls() {
const user = useRecoilValue(userSelector);
diff --git a/src/components/pages/Users/CreateUserModal.tsx b/src/components/pages/Users/CreateUserModal.tsx
index 6879437..277cb42 100644
--- a/src/components/pages/Users/CreateUserModal.tsx
+++ b/src/components/pages/Users/CreateUserModal.tsx
@@ -1,4 +1,4 @@
-import { Modal, TextInput, Switch, Group, Button, Title } from '@mantine/core';
+import { Button, Group, Modal, Switch, TextInput, Title } from '@mantine/core';
import { useForm } from '@mantine/form';
import { showNotification } from '@mantine/notifications';
import { DeleteIcon, PlusIcon } from 'components/icons';
diff --git a/src/components/pages/Users/EditUserModal.tsx b/src/components/pages/Users/EditUserModal.tsx
index fd660fa..b743600 100644
--- a/src/components/pages/Users/EditUserModal.tsx
+++ b/src/components/pages/Users/EditUserModal.tsx
@@ -1,4 +1,4 @@
-import { Modal, TextInput, Switch, Group, Button, Title } from '@mantine/core';
+import { Button, Group, Modal, Switch, TextInput, Title } from '@mantine/core';
import { useForm } from '@mantine/form';
import { showNotification } from '@mantine/notifications';
import { DeleteIcon, PlusIcon } from 'components/icons';
diff --git a/src/components/pages/Users/index.tsx b/src/components/pages/Users/index.tsx
index 7d381a5..c5fe4e8 100644
--- a/src/components/pages/Users/index.tsx
+++ b/src/components/pages/Users/index.tsx
@@ -21,11 +21,11 @@ export default function Users() {
const [editOpen, setEditOpen] = useState(false);
const [selectedUser, setSelectedUser] = useState(null);
- const handleDelete = async (user, delete_images) => {
- const res = await useFetch('/api/users', 'DELETE', {
- id: user.id,
- delete_images,
+ const handleDelete = async (user, delete_files) => {
+ const res = await useFetch(`/api/user/${user.id}`, 'DELETE', {
+ delete_files,
});
+
if (res.error) {
showNotification({
title: 'Failed to delete user',
@@ -52,7 +52,7 @@ export default function Users() {
labels: { confirm: 'Yes', cancel: 'No' },
onConfirm: () => {
modals.openConfirmModal({
- title: `Delete ${user.username}'s images?`,
+ title: `Delete ${user.username}'s files?`,
labels: { confirm: 'Yes', cancel: 'No' },
centered: true,
overlayBlur: 3,
diff --git a/src/lib/config/Config.ts b/src/lib/config/Config.ts
index d9357e3..5190ffb 100644
--- a/src/lib/config/Config.ts
+++ b/src/lib/config/Config.ts
@@ -101,6 +101,8 @@ export interface ConfigDiscordEmbed {
export interface ConfigFeatures {
invites: boolean;
+ invites_length: number;
+
oauth_registration: boolean;
user_registration: boolean;
}
diff --git a/src/lib/config/readConfig.ts b/src/lib/config/readConfig.ts
index afb4981..33e585d 100644
--- a/src/lib/config/readConfig.ts
+++ b/src/lib/config/readConfig.ts
@@ -1,6 +1,7 @@
import { parse } from 'dotenv';
import { expand } from 'dotenv-expand';
import { existsSync, readFileSync } from 'fs';
+import Logger from '../logger';
import { humanToBytes } from '../utils/bytes';
export type ValueType = 'string' | 'number' | 'boolean' | 'array' | 'json-array' | 'human-to-byte';
@@ -36,6 +37,9 @@ function map(env: string, type: ValueType, path: string) {
}
export default function readConfig() {
+ const logger = Logger.get('config');
+
+ logger.debug('attemping to read .env.local/.env or environment variables');
if (existsSync('.env.local')) {
const contents = readFileSync('.env.local');
@@ -132,6 +136,8 @@ export default function readConfig() {
map('OAUTH_GOOGLE_CLIENT_SECRET', 'string', 'oauth.google_client_secret'),
map('FEATURES_INVITES', 'boolean', 'features.invites'),
+ map('FEATURES_INVITES_LENGTH', 'number', 'features.invites_length'),
+
map('FEATURES_OAUTH_REGISTRATION', 'boolean', 'features.oauth_registration'),
map('FEATURES_USER_REGISTRATION', 'boolean', 'features.user_registration'),
@@ -154,6 +160,10 @@ export default function readConfig() {
break;
case 'number':
parsed = Number(value);
+ if (isNaN(parsed)) {
+ parsed = undefined;
+ logger.debug(`Failed to parse number ${map.env}=${value}`);
+ }
break;
case 'boolean':
parsed = value === 'true';
@@ -162,11 +172,13 @@ export default function readConfig() {
try {
parsed = JSON.parse(value);
} catch (e) {
- parsed = [];
+ logger.debug(`Failed to parse JSON array ${map.env}=${value}`);
}
break;
case 'human-to-byte':
parsed = humanToBytes(value) ?? undefined;
+ if (!parsed) logger.debug(`Unable to parse ${map.env}=${value}`);
+
break;
default:
parsed = value;
diff --git a/src/lib/config/validateConfig.ts b/src/lib/config/validateConfig.ts
index 59e91f5..822c71a 100644
--- a/src/lib/config/validateConfig.ts
+++ b/src/lib/config/validateConfig.ts
@@ -1,5 +1,5 @@
-import { Config } from 'lib/config/Config';
import { s } from '@sapphire/shapeshift';
+import { Config } from 'lib/config/Config';
import { inspect } from 'util';
import Logger from '../logger';
import { humanToBytes } from '../utils/bytes';
@@ -168,10 +168,11 @@ const validator = s.object({
features: s
.object({
invites: s.boolean.default(false),
+ invites_length: s.number.default(6),
oauth_registration: s.boolean.default(false),
user_registration: s.boolean.default(false),
})
- .default({ invites: false, oauth_registration: false, user_registration: false }),
+ .default({ invites: false, invites_length: 6, oauth_registration: false, user_registration: false }),
chunks: s
.object({
max_size: s.number.default(humanToBytes('90MB')),
@@ -184,8 +185,12 @@ const validator = s.object({
});
export default function validate(config): Config {
+ const logger = Logger.get('config');
+
try {
+ logger.debug(`Attemping to validate ${JSON.stringify(config)}`);
const validated = validator.parse(config);
+ logger.debug(`Recieved config: ${JSON.stringify(validated)}`);
switch (validated.datasource.type) {
case 's3': {
const errors = [];
@@ -221,8 +226,9 @@ export default function validate(config): Config {
e.stack = '';
- Logger.get('config').error('Config is invalid, see below:');
- Logger.get('config').error(inspect(e, { depth: Infinity, colors: true }));
+ Logger.get('config')
+ .error('Config is invalid, see below:')
+ .error(inspect(e, { depth: Infinity, colors: true }));
process.exit(1);
}
diff --git a/src/lib/datasource.ts b/src/lib/datasource.ts
index 1b2103b..7616a8e 100644
--- a/src/lib/datasource.ts
+++ b/src/lib/datasource.ts
@@ -1,20 +1,22 @@
import config from './config';
-import { Swift, Local, S3, Datasource } from './datasources';
+import { Datasource, Local, S3, Swift } from './datasources';
import Logger from './logger';
+const logger = Logger.get('datasource');
+
if (!global.datasource) {
switch (config.datasource.type) {
case 's3':
global.datasource = new S3(config.datasource.s3);
- Logger.get('datasource').info(`using S3(${config.datasource.s3.bucket}) datasource`);
+ logger.info(`using S3(${config.datasource.s3.bucket}) datasource`);
break;
case 'local':
global.datasource = new Local(config.datasource.local.directory);
- Logger.get('datasource').info(`using Local(${config.datasource.local.directory}) datasource`);
+ logger.info(`using Local(${config.datasource.local.directory}) datasource`);
break;
case 'swift':
global.datasource = new Swift(config.datasource.swift);
- Logger.get('datasource').info(`using Swift(${config.datasource.swift.container}) datasource`);
+ logger.info(`using Swift(${config.datasource.swift.container}) datasource`);
break;
default:
throw new Error('Invalid datasource type');
diff --git a/src/lib/discord.ts b/src/lib/discord.ts
index 188450a..a41f868 100644
--- a/src/lib/discord.ts
+++ b/src/lib/discord.ts
@@ -1,11 +1,13 @@
import { Image, Url, User } from '@prisma/client';
-import { ConfigDiscordContent } from 'lib/config/Config';
import config from 'lib/config';
+import { ConfigDiscordContent } from 'lib/config/Config';
import Logger from './logger';
// [user, image, url, route (ex. https://example.com/r/something.png)]
export type Args = [User, Image?, Url?, string?];
+const logger = Logger.get('discord');
+
function parse(str: string, args: Args) {
if (!str) return null;
@@ -63,7 +65,7 @@ export async function sendUpload(user: User, image: Image, host: string) {
const parsed = parseContent(config.discord.upload, [user, image, null, host]);
const isImage = image.mimetype.startsWith('image/');
- const body = {
+ const body = JSON.stringify({
username: config.discord.username,
avatar_url: config.discord.avatar_url,
content: parsed.content ?? null,
@@ -95,11 +97,12 @@ export async function sendUpload(user: User, image: Image, host: string) {
},
]
: null,
- };
+ });
+ logger.debug('attempting to send shorten notification to discord', body);
const res = await fetch(config.discord.url, {
method: 'POST',
- body: JSON.stringify(body),
+ body,
headers: {
'Content-Type': 'application/json',
},
@@ -107,10 +110,9 @@ export async function sendUpload(user: User, image: Image, host: string) {
if (!res.ok) {
const text = await res.text();
- Logger.get('discord').error(
- `Failed to send upload notification to discord: ${res.status} ${res.statusText}`
- );
- Logger.get('discord').error(`Received response: ${text}`);
+ logger
+ .error(`Failed to send shorten notification to discord: ${res.status}`)
+ .error(`Received response:\n${text}`);
}
return;
@@ -121,7 +123,7 @@ export async function sendShorten(user: User, url: Url, host: string) {
const parsed = parseContent(config.discord.shorten, [user, null, url, host]);
- const body = {
+ const body = JSON.stringify({
username: config.discord.username,
avatar_url: config.discord.avatar_url,
content: parsed.content ?? null,
@@ -141,20 +143,22 @@ export async function sendShorten(user: User, url: Url, host: string) {
},
]
: null,
- };
+ });
+ logger.debug('attempting to send shorten notification to discord', body);
const res = await fetch(config.discord.url, {
method: 'POST',
- body: JSON.stringify(body),
+ body,
headers: {
'Content-Type': 'application/json',
},
});
if (!res.ok) {
- Logger.get('discord').error(
- `Failed to send url shorten notification to discord: ${res.status} ${res.statusText}`
- );
+ const text = await res.text();
+ logger
+ .error(`Failed to send shorten notification to discord: ${res.status}`)
+ .error(`Received response:\n${text}`);
}
return;
diff --git a/src/lib/hooks/useLogin.ts b/src/lib/hooks/useLogin.ts
index 23a885d..cbc5f26 100644
--- a/src/lib/hooks/useLogin.ts
+++ b/src/lib/hooks/useLogin.ts
@@ -1,6 +1,6 @@
+import { userSelector } from 'lib/recoil/user';
import { useRouter } from 'next/router';
import { useEffect, useState } from 'react';
-import { userSelector } from 'lib/recoil/user';
import { useRecoilState } from 'recoil';
import useFetch from './useFetch';
diff --git a/src/lib/logger.ts b/src/lib/logger.ts
index f6dc25e..3e7f3d0 100644
--- a/src/lib/logger.ts
+++ b/src/lib/logger.ts
@@ -1,18 +1,19 @@
+import { blueBright, cyan, red, yellow } from 'colorette';
import dayjs from 'dayjs';
-import { blueBright, red, cyan } from 'colorette';
export enum LoggerLevel {
ERROR,
INFO,
+ DEBUG,
}
export default class Logger {
public name: string;
- static get(clas: any) {
- if (typeof clas !== 'function') if (typeof clas !== 'string') throw new Error('not string/function');
+ static get(klass: any) {
+ if (typeof klass !== 'function') if (typeof klass !== 'string') throw new Error('not string/function');
- const name = clas.name ?? clas;
+ const name = klass.name ?? klass;
return new Logger(name);
}
@@ -21,19 +22,31 @@ export default class Logger {
this.name = name;
}
- info(...args: any[]) {
- console.log(this.formatMessage(LoggerLevel.INFO, this.name, args.join(' ')));
+ info(...args: any[]): this {
+ process.stdout.write(this.formatMessage(LoggerLevel.INFO, this.name, args.join(' ')));
+
+ return this;
}
- error(...args: any[]) {
- console.log(
+ error(...args: any[]): this {
+ process.stdout.write(
this.formatMessage(LoggerLevel.ERROR, this.name, args.map((error) => error.stack ?? error).join(' '))
);
+
+ return this;
+ }
+
+ debug(...args: any[]): this {
+ if (!process.env.DEBUG) return;
+
+ process.stdout.write(this.formatMessage(LoggerLevel.DEBUG, this.name, args.join(' ')));
+
+ return this;
}
formatMessage(level: LoggerLevel, name: string, message: string) {
const time = dayjs().format('YYYY-MM-DD hh:mm:ss,SSS A');
- return `${time} ${this.formatLevel(level)} [${blueBright(name)}] ${message}`;
+ return `${time} ${this.formatLevel(level)} [${blueBright(name)}] ${message}\n`;
}
formatLevel(level: LoggerLevel) {
@@ -42,6 +55,8 @@ export default class Logger {
return cyan('info ');
case LoggerLevel.ERROR:
return red('error');
+ case LoggerLevel.DEBUG:
+ return yellow('debug');
}
}
}
diff --git a/src/lib/middleware/withOAuth.ts b/src/lib/middleware/withOAuth.ts
index 0f498e2..78f1ed3 100644
--- a/src/lib/middleware/withOAuth.ts
+++ b/src/lib/middleware/withOAuth.ts
@@ -1,8 +1,8 @@
-import { createToken } from 'lib/util';
-import Logger from 'lib/logger';
-import { NextApiReq, NextApiRes } from './withZipline';
-import prisma from 'lib/prisma';
import { OauthProviders } from '@prisma/client';
+import Logger from 'lib/logger';
+import prisma from 'lib/prisma';
+import { createToken } from 'lib/util';
+import { NextApiReq, NextApiRes } from './withZipline';
export interface OAuthQuery {
state?: string;
@@ -22,21 +22,32 @@ export interface OAuthResponse {
}
export const withOAuth =
- (provider: 'discord' | 'github' | 'google', oauth: (query: OAuthQuery) => Promise) =>
+ (
+ provider: 'discord' | 'github' | 'google',
+ oauth: (query: OAuthQuery, logger: Logger) => Promise
+ ) =>
async (req: NextApiReq, res: NextApiRes) => {
+ const logger = Logger.get(`oauth::${provider}`);
+
+ function oauthError(error: string) {
+ return res.redirect(`/oauth_error?error=${error}&provider=${provider}`);
+ }
+
req.query.host = req.headers.host;
- const oauth_resp = await oauth(req.query as unknown as OAuthQuery);
+ const oauth_resp = await oauth(req.query as unknown as OAuthQuery, logger);
if (oauth_resp.error) {
- return res.json({ error: oauth_resp.error }, oauth_resp.error_code || 500);
+ logger.debug(`Failed to authenticate with ${provider}: ${JSON.stringify(oauth_resp)})`);
+
+ return oauthError(oauth_resp.error);
}
if (oauth_resp.redirect) {
return res.redirect(oauth_resp.redirect);
}
- const { code, state } = req.query as { code: string; state?: string };
+ const { state } = req.query as { state?: string };
const existing = await prisma.user.findFirst({
where: {
@@ -55,13 +66,15 @@ export const withOAuth =
const user = await req.user();
const existingOauth = existing?.oauth?.find((o) => o.provider === provider.toUpperCase());
- const existingUserOauth = user?.oauth?.find((o) => o.provider === provider.toUpperCase());
+ const userOauth = user?.oauth?.find((o) => o.provider === provider.toUpperCase());
+
if (state === 'link') {
- if (!user) return res.error('not logged in, unable to link account');
+ if (!user) return oauthError('You are not logged in, unable to link account.');
if (user.oauth && user.oauth.find((o) => o.provider === provider.toUpperCase()))
- return res.error(`account already linked with ${provider}`);
+ return oauthError(`This account was already linked with ${provider}!`);
+ logger.debug(`attempting to link ${provider} account to ${user.username}`);
await prisma.user.update({
where: {
id: user.id,
@@ -80,13 +93,14 @@ export const withOAuth =
});
res.setUserCookie(user.id);
- Logger.get('user').info(`User ${user.username} (${user.id}) linked account via oauth(${provider})`);
+ logger.info(`User ${user.username} (${user.id}) linked account via oauth(${provider})`);
return res.redirect('/');
- } else if (user && existingUserOauth) {
+ } else if (user && userOauth) {
+ logger.debug(`attempting to refresh ${provider} account for ${user.username}`);
await prisma.oAuth.update({
where: {
- id: existingUserOauth!.id,
+ id: userOauth!.id,
},
data: {
token: oauth_resp.access_token,
@@ -96,7 +110,7 @@ export const withOAuth =
});
res.setUserCookie(user.id);
- Logger.get('user').info(`User ${user.username} (${user.id}) logged in via oauth(${provider})`);
+ logger.info(`User ${user.username} (${user.id}) logged in via oauth(${provider})`);
return res.redirect('/dashboard');
} else if (existing && existingOauth) {
@@ -116,9 +130,10 @@ export const withOAuth =
return res.redirect('/dashboard');
} else if (existing) {
- return res.badRequest('username is already taken');
+ return oauthError(`Username "${oauth_resp.username}" is already taken, unable to create account.`);
}
+ logger.debug('creating new user via oauth');
const nuser = await prisma.user.create({
data: {
username: oauth_resp.username,
@@ -134,10 +149,12 @@ export const withOAuth =
avatar: oauth_resp.avatar,
},
});
- Logger.get('user').info(`Created user ${nuser.username} via oauth(${provider})`);
+
+ logger.debug(`created user ${JSON.stringify(nuser)} via oauth(${provider})`);
+ logger.info(`Created user ${nuser.username} via oauth(${provider})`);
res.setUserCookie(nuser.id);
- Logger.get('user').info(`User ${nuser.username} (${nuser.id}) logged in via oauth(${provider})`);
+ logger.info(`User ${nuser.username} (${nuser.id}) logged in via oauth(${provider})`);
return res.redirect('/dashboard');
};
diff --git a/src/lib/middleware/withZipline.ts b/src/lib/middleware/withZipline.ts
index f11084e..8ba69c6 100644
--- a/src/lib/middleware/withZipline.ts
+++ b/src/lib/middleware/withZipline.ts
@@ -1,12 +1,12 @@
-import type { NextApiRequest, NextApiResponse } from 'next';
import type { CookieSerializeOptions } from 'cookie';
+import type { NextApiRequest, NextApiResponse } from 'next';
+import { OAuth, User } from '@prisma/client';
import { serialize } from 'cookie';
-import { sign64, unsign64 } from 'lib/utils/crypto';
+import { HTTPMethod } from 'find-my-way';
import config from 'lib/config';
import prisma from 'lib/prisma';
-import { OAuth, User } from '@prisma/client';
-import { HTTPMethod } from 'find-my-way';
+import { sign64, unsign64 } from 'lib/utils/crypto';
export interface NextApiFile {
fieldname: string;
@@ -54,7 +54,7 @@ export type ZiplineApiConfig = {
export const withZipline =
(
- handler: (req: NextApiRequest, res: NextApiResponse, user?: UserExtended) => unknown,
+ handler: (req: NextApiRequest, res: NextApiResponse, user?: UserExtended) => Promise,
api_config: ZiplineApiConfig = { methods: ['GET'] }
) =>
(req: NextApiReq, res: NextApiRes) => {
diff --git a/src/lib/util.ts b/src/lib/util.ts
index 1194e1a..1369af1 100644
--- a/src/lib/util.ts
+++ b/src/lib/util.ts
@@ -1,9 +1,9 @@
-import { randomBytes } from 'crypto';
-import { hash, verify } from 'argon2';
-import { readdir, stat } from 'fs/promises';
-import { join } from 'path';
-import prisma from 'lib/prisma';
import { InvisibleImage, InvisibleUrl } from '@prisma/client';
+import { hash, verify } from 'argon2';
+import { randomBytes } from 'crypto';
+import { readdir, stat } from 'fs/promises';
+import prisma from 'lib/prisma';
+import { join } from 'path';
export async function hashPassword(s: string): Promise {
return await hash(s);
diff --git a/src/pages/404.tsx b/src/pages/404.tsx
index 7b4fbf4..8f9f8df 100644
--- a/src/pages/404.tsx
+++ b/src/pages/404.tsx
@@ -1,4 +1,3 @@
-import React from 'react';
import { Button, Stack, Title } from '@mantine/core';
import Link from 'components/Link';
import MutedText from 'components/MutedText';
diff --git a/src/pages/500.tsx b/src/pages/500.tsx
index 5555be1..b33d830 100644
--- a/src/pages/500.tsx
+++ b/src/pages/500.tsx
@@ -1,4 +1,3 @@
-import React from 'react';
import { Button, Stack, Title, Tooltip } from '@mantine/core';
import Link from 'components/Link';
import MutedText from 'components/MutedText';
diff --git a/src/pages/_app.tsx b/src/pages/_app.tsx
index 63c5f9b..9186944 100644
--- a/src/pages/_app.tsx
+++ b/src/pages/_app.tsx
@@ -1,8 +1,7 @@
-import React from 'react';
-import Head from 'next/head';
-import ZiplineTheming from 'components/Theming';
import { QueryClientProvider } from '@tanstack/react-query';
+import ZiplineTheming from 'components/Theming';
import queryClient from 'lib/queries/client';
+import Head from 'next/head';
import { RecoilRoot } from 'recoil';
export default function MyApp({ Component, pageProps }) {
diff --git a/src/pages/_document.tsx b/src/pages/_document.tsx
index ecb99c9..598ee40 100644
--- a/src/pages/_document.tsx
+++ b/src/pages/_document.tsx
@@ -1,6 +1,5 @@
-import React from 'react';
-import Document, { Html, Head, Main, NextScript } from 'next/document';
import { createGetInitialProps } from '@mantine/next';
+import Document, { Head, Html, Main, NextScript } from 'next/document';
const getInitialProps = createGetInitialProps();
@@ -10,7 +9,14 @@ class MyDocument extends Document {
render() {
return (
-
+
+
+
+
+
diff --git a/src/pages/_error.tsx b/src/pages/_error.tsx
index d577ad1..b7eeec4 100644
--- a/src/pages/_error.tsx
+++ b/src/pages/_error.tsx
@@ -1,14 +1,13 @@
-import React from 'react';
import { Button, Stack, Title } from '@mantine/core';
import Link from 'components/Link';
import MutedText from 'components/MutedText';
import Head from 'next/head';
-export default function Error({ statusCode }) {
+export default function Error({ statusCode, oauthError }) {
return (
<>
- {statusCode} Error
+ Error ({statusCode})
invite.code)
.join(', ')}`
@@ -40,17 +44,17 @@ async function handler(req: NextApiReq, res: NextApiRes, user: UserExtended) {
return res.json(data);
} else {
- const code = randomChars(6);
-
const invite = await prisma.invite.create({
data: {
- code,
+ code: randomChars(config.features.invites_length),
createdById: user.id,
expires_at: expiry,
},
});
- Logger.get('invite').info(`${user.username} (${user.id}) created invite ${invite.code}`);
+ logger.debug(`created invite ${JSON.stringify(invite)}`);
+
+ logger.info(`${user.username} (${user.id}) created invite ${invite.code}`);
return res.json(invite);
}
@@ -63,7 +67,9 @@ async function handler(req: NextApiReq, res: NextApiRes, user: UserExtended) {
},
});
- Logger.get('invite').info(`${user.username} (${user.id}) deleted invite ${invite.code}`);
+ logger.debug(`deleted invite ${JSON.stringify(invite)}`);
+
+ logger.info(`${user.username} (${user.id}) deleted invite ${invite.code}`);
return res.json(invite);
} else {
diff --git a/src/pages/api/auth/login.ts b/src/pages/api/auth/login.ts
index 77a23f4..e1e91fc 100644
--- a/src/pages/api/auth/login.ts
+++ b/src/pages/api/auth/login.ts
@@ -4,6 +4,8 @@ import { checkPassword, createToken, hashPassword } from 'lib/util';
import Logger from 'lib/logger';
async function handler(req: NextApiReq, res: NextApiRes) {
+ const logger = Logger.get('login');
+
const { username, password } = req.body as {
username: string;
password: string;
@@ -11,7 +13,7 @@ async function handler(req: NextApiReq, res: NextApiRes) {
const users = await prisma.user.findMany();
if (users.length === 0) {
- Logger.get('database').info('no users found... creating default user...');
+ logger.debug('no users found... creating default user...');
await prisma.user.create({
data: {
username: 'administrator',
@@ -20,7 +22,7 @@ async function handler(req: NextApiReq, res: NextApiRes) {
administrator: true,
},
});
- Logger.get('database').info('created default user:\nUsername: "administrator"\nPassword: "password"');
+ logger.info('created default user:\nUsername: "administrator"\nPassword: "password"');
}
const user = await prisma.user.findFirst({
@@ -35,7 +37,7 @@ async function handler(req: NextApiReq, res: NextApiRes) {
if (!valid) return res.unauthorized('Wrong password');
res.setUserCookie(user.id);
- Logger.get('user').info(`User ${user.username} (${user.id}) logged in`);
+ logger.info(`User ${user.username} (${user.id}) logged in`);
return res.json({ success: true });
}
diff --git a/src/pages/api/auth/logout.ts b/src/pages/api/auth/logout.ts
index 70187df..a3697ed 100644
--- a/src/pages/api/auth/logout.ts
+++ b/src/pages/api/auth/logout.ts
@@ -1,5 +1,5 @@
-import { NextApiReq, NextApiRes, UserExtended, withZipline } from 'middleware/withZipline';
import Logger from 'lib/logger';
+import { NextApiReq, NextApiRes, UserExtended, withZipline } from 'middleware/withZipline';
async function handler(req: NextApiReq, res: NextApiRes, user: UserExtended) {
req.cleanCookie('user');
diff --git a/src/pages/api/auth/oauth/discord.ts b/src/pages/api/auth/oauth/discord.ts
index 2218bcc..a05c51f 100644
--- a/src/pages/api/auth/oauth/discord.ts
+++ b/src/pages/api/auth/oauth/discord.ts
@@ -1,11 +1,11 @@
-import { withZipline } from 'lib/middleware/withZipline';
-import { getBase64URLFromURL, notNull } from 'lib/util';
-import Logger from 'lib/logger';
import config from 'lib/config';
+import Logger from 'lib/logger';
+import { OAuthQuery, OAuthResponse, withOAuth } from 'lib/middleware/withOAuth';
+import { withZipline } from 'lib/middleware/withZipline';
import { discord_auth } from 'lib/oauth';
-import { withOAuth, OAuthResponse, OAuthQuery } from 'lib/middleware/withOAuth';
+import { getBase64URLFromURL, notNull } from 'lib/util';
-async function handler({ code, state, host }: OAuthQuery): Promise {
+async function handler({ code, state, host }: OAuthQuery, logger: Logger): Promise {
if (!config.features.oauth_registration)
return {
error_code: 403,
@@ -13,7 +13,8 @@ async function handler({ code, state, host }: OAuthQuery): Promise body(${body}) resp(${text})`);
+
+ if (!resp.ok) {
+ return { error: 'invalid request' };
+ }
+
+ const json = JSON.parse(text);
if (!json.access_token) return { error: 'no access_token in response' };
if (!json.refresh_token) return { error: 'no refresh_token in response' };
diff --git a/src/pages/api/auth/oauth/github.ts b/src/pages/api/auth/oauth/github.ts
index e8df4f6..41f06d7 100644
--- a/src/pages/api/auth/oauth/github.ts
+++ b/src/pages/api/auth/oauth/github.ts
@@ -1,11 +1,11 @@
-import { withZipline } from 'lib/middleware/withZipline';
-import { getBase64URLFromURL, notNull } from 'lib/util';
-import Logger from 'lib/logger';
import config from 'lib/config';
+import Logger from 'lib/logger';
+import { OAuthQuery, OAuthResponse, withOAuth } from 'lib/middleware/withOAuth';
+import { withZipline } from 'lib/middleware/withZipline';
import { github_auth } from 'lib/oauth';
-import { withOAuth, OAuthResponse, OAuthQuery } from 'lib/middleware/withOAuth';
+import { getBase64URLFromURL, notNull } from 'lib/util';
-async function handler({ code, state }: OAuthQuery): Promise {
+async function handler({ code, state }: OAuthQuery, logger: Logger): Promise {
if (!config.features.oauth_registration)
return {
error_code: 403,
@@ -13,7 +13,7 @@ async function handler({ code, state }: OAuthQuery): Promise {
};
if (!notNull(config.oauth.github_client_id, config.oauth.github_client_secret)) {
- Logger.get('oauth').error('GitHub OAuth is not configured');
+ logger.error('GitHub OAuth is not configured');
return {
error_code: 401,
error: 'GitHub OAuth is not configured',
@@ -25,22 +25,27 @@ async function handler({ code, state }: OAuthQuery): Promise {
redirect: github_auth.oauth_url(config.oauth.github_client_id, state),
};
+ const body = JSON.stringify({
+ client_id: config.oauth.github_client_id,
+ client_secret: config.oauth.github_client_secret,
+ code,
+ });
+
const resp = await fetch('https://github.com/login/oauth/access_token', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Accept: 'application/json',
},
- body: JSON.stringify({
- client_id: config.oauth.github_client_id,
- client_secret: config.oauth.github_client_secret,
- code,
- }),
+ body,
});
+ const text = await resp.text();
+ logger.debug(`oauth https://github.com/login/oauth/access_token -> body(${body}) resp(${text})`);
+
if (!resp.ok) return { error: 'invalid request' };
- const json = await resp.json();
+ const json = JSON.parse(text);
if (!json.access_token) return { error: 'no access_token in response' };
diff --git a/src/pages/api/auth/oauth/google.ts b/src/pages/api/auth/oauth/google.ts
index 05ef6be..442caa1 100644
--- a/src/pages/api/auth/oauth/google.ts
+++ b/src/pages/api/auth/oauth/google.ts
@@ -1,11 +1,11 @@
-import { withZipline } from 'lib/middleware/withZipline';
-import { getBase64URLFromURL, notNull } from 'lib/util';
-import Logger from 'lib/logger';
import config from 'lib/config';
+import Logger from 'lib/logger';
+import { OAuthQuery, OAuthResponse, withOAuth } from 'lib/middleware/withOAuth';
+import { withZipline } from 'lib/middleware/withZipline';
import { google_auth } from 'lib/oauth';
-import { withOAuth, OAuthResponse, OAuthQuery } from 'lib/middleware/withOAuth';
+import { getBase64URLFromURL, notNull } from 'lib/util';
-async function handler({ code, state, host }: OAuthQuery): Promise {
+async function handler({ code, state, host }: OAuthQuery, logger: Logger): Promise {
if (!config.features.oauth_registration)
return {
error_code: 403,
@@ -13,7 +13,7 @@ async function handler({ code, state, host }: OAuthQuery): Promise body(${body}) resp(${text})`);
+
if (!resp.ok) return { error: 'invalid request' };
- const json = await resp.json();
+ const json = JSON.parse(text);
if (!json.access_token) return { error: 'no access_token in response' };
diff --git a/src/pages/api/auth/oauth/index.ts b/src/pages/api/auth/oauth/index.ts
index 1c21bb7..4fd1cea 100644
--- a/src/pages/api/auth/oauth/index.ts
+++ b/src/pages/api/auth/oauth/index.ts
@@ -1,6 +1,6 @@
-import prisma from 'lib/prisma';
-import { NextApiReq, NextApiRes, UserExtended, withZipline } from 'lib/middleware/withZipline';
import { OauthProviders } from '@prisma/client';
+import { NextApiReq, NextApiRes, UserExtended, withZipline } from 'lib/middleware/withZipline';
+import prisma from 'lib/prisma';
async function handler(req: NextApiReq, res: NextApiRes, user: UserExtended) {
if (req.method === 'DELETE') {
diff --git a/src/pages/api/shorten.ts b/src/pages/api/shorten.ts
index 1b8b022..a920ce5 100644
--- a/src/pages/api/shorten.ts
+++ b/src/pages/api/shorten.ts
@@ -1,10 +1,11 @@
-import prisma from 'lib/prisma';
-import zconfig from 'lib/config';
-import { NextApiReq, NextApiRes, withZipline } from 'lib/middleware/withZipline';
-import { createInvisURL, randomChars } from 'lib/util';
-import Logger from 'lib/logger';
-import config from 'lib/config';
+import { default as config, default as zconfig } from 'lib/config';
import { sendShorten } from 'lib/discord';
+import Logger from 'lib/logger';
+import { NextApiReq, NextApiRes, withZipline } from 'lib/middleware/withZipline';
+import prisma from 'lib/prisma';
+import { createInvisURL, randomChars } from 'lib/util';
+
+const logger = Logger.get('shorten');
async function handler(req: NextApiReq, res: NextApiRes) {
if (!req.headers.authorization) return res.badRequest('no authorization');
@@ -49,9 +50,9 @@ async function handler(req: NextApiReq, res: NextApiRes) {
if (req.headers.zws) invis = await createInvisURL(zconfig.urls.length, url.id);
- Logger.get('url').info(
- `User ${user.username} (${user.id}) shortenned a url ${url.destination} (${url.id})`
- );
+ logger.debug(`shortened ${JSON.stringify(url)}`);
+
+ logger.info(`User ${user.username} (${user.id}) shortenned a url ${url.destination} (${url.id})`);
if (config.discord?.shorten) {
await sendShorten(
diff --git a/src/pages/api/stats.ts b/src/pages/api/stats.ts
index 4e17dbf..670fa27 100644
--- a/src/pages/api/stats.ts
+++ b/src/pages/api/stats.ts
@@ -1,9 +1,9 @@
-import { NextApiReq, NextApiRes, UserExtended, withZipline } from 'middleware/withZipline';
-import prisma from 'lib/prisma';
-import config from 'lib/config';
import { Stats } from '@prisma/client';
-import { getStats } from 'server/util';
+import config from 'lib/config';
import datasource from 'lib/datasource';
+import prisma from 'lib/prisma';
+import { NextApiReq, NextApiRes, UserExtended, withZipline } from 'middleware/withZipline';
+import { getStats } from 'server/util';
async function handler(req: NextApiReq, res: NextApiRes, user: UserExtended) {
if (req.method === 'POST') {
diff --git a/src/pages/api/upload.ts b/src/pages/api/upload.ts
index 437dca5..eff7c69 100644
--- a/src/pages/api/upload.ts
+++ b/src/pages/api/upload.ts
@@ -16,6 +16,7 @@ import { join } from 'path';
import sharp from 'sharp';
const uploader = multer();
+const logger = Logger.get('upload');
async function handler(req: NextApiReq, res: NextApiRes) {
if (!req.headers.authorization) return res.forbidden('no authorization');
@@ -76,7 +77,20 @@ async function handler(req: NextApiReq, res: NextApiRes) {
const identifier = req.headers['x-zipline-partial-identifier'];
const lastchunk = req.headers['x-zipline-partial-lastchunk'] === 'true';
+ logger.debug(
+ `recieved partial upload ${JSON.stringify({
+ filename,
+ mimetype,
+ identifier,
+ lastchunk,
+ start,
+ end,
+ total,
+ })}`
+ );
+
const tempFile = join(tmpdir(), `zipline_partial_${identifier}_${start}_${end}`);
+ logger.debug(`writing partial to disk ${tempFile}`);
await writeFile(tempFile, req.files[0].buffer);
if (lastchunk) {
@@ -149,9 +163,7 @@ async function handler(req: NextApiReq, res: NextApiRes) {
await datasource.save(file.file, Buffer.from(chunks));
- Logger.get('file').info(
- `User ${user.username} (${user.id}) uploaded ${file.file} (${file.id}) (chunked)`
- );
+ logger.info(`User ${user.username} (${user.id}) uploaded ${file.file} (${file.id}) (chunked)`);
if (user.domains.length) {
const domain = user.domains[Math.floor(Math.random() * user.domains.length)];
response.files.push(
@@ -187,6 +199,7 @@ async function handler(req: NextApiReq, res: NextApiRes) {
if (user.ratelimit) {
const remaining = user.ratelimit.getTime() - Date.now();
+ logger.debug(`${user.id} encountered ratelimit, ${remaining}ms remaining`);
if (remaining <= 0) {
await prisma.user.update({
where: {
@@ -204,6 +217,18 @@ async function handler(req: NextApiReq, res: NextApiRes) {
if (!req.files) return res.badRequest('no files');
if (req.files && req.files.length === 0) return res.badRequest('no files');
+ logger.debug(
+ `recieved upload (len=${req.files.length}) ${JSON.stringify(
+ req.files.map((x) => ({
+ fieldname: x.fieldname,
+ originalname: x.originalname,
+ mimetype: x.mimetype,
+ size: x.size,
+ encoding: x.encoding,
+ }))
+ )}`
+ );
+
for (let i = 0; i !== req.files.length; ++i) {
const file = req.files[i];
if (file.size > zconfig.uploader[user.administrator ? 'admin_limit' : 'user_limit'])
@@ -258,14 +283,14 @@ async function handler(req: NextApiReq, res: NextApiRes) {
if (compressionUsed) {
const buffer = await sharp(file.buffer).jpeg({ quality: imageCompressionPercent }).toBuffer();
await datasource.save(image.file, buffer);
- Logger.get('file').info(
+ logger.info(
`User ${user.username} (${user.id}) compressed image from ${file.buffer.length} -> ${buffer.length} bytes`
);
} else {
await datasource.save(image.file, file.buffer);
}
- Logger.get('file').info(`User ${user.username} (${user.id}) uploaded ${image.file} (${image.id})`);
+ logger.info(`User ${user.username} (${user.id}) uploaded ${image.file} (${image.id})`);
if (user.domains.length) {
const domain = user.domains[Math.floor(Math.random() * user.domains.length)];
response.files.push(
@@ -281,6 +306,8 @@ async function handler(req: NextApiReq, res: NextApiRes) {
);
}
+ logger.debug(`sent response: ${JSON.stringify(response)}`);
+
if (zconfig.discord?.upload) {
await sendUpload(
user,
diff --git a/src/pages/api/user/[id].ts b/src/pages/api/user/[id].ts
index d79dc4d..932b1a9 100644
--- a/src/pages/api/user/[id].ts
+++ b/src/pages/api/user/[id].ts
@@ -1,7 +1,10 @@
+import datasource from 'lib/datasource';
+import Logger from 'lib/logger';
import prisma from 'lib/prisma';
import { hashPassword } from 'lib/util';
import { NextApiReq, NextApiRes, UserExtended, withZipline } from 'middleware/withZipline';
-import Logger from 'lib/logger';
+
+const logger = Logger.get('user');
async function handler(req: NextApiReq, res: NextApiRes, user: UserExtended) {
const { id } = req.query as { id: string };
@@ -15,10 +18,43 @@ async function handler(req: NextApiReq, res: NextApiRes, user: UserExtended) {
if (!target) return res.notFound('user not found');
if (req.method === 'DELETE') {
+ if (target.id === user.id) return res.badRequest("you can't delete your own account");
+ if (target.administrator && !user.superAdmin) return res.forbidden('cannot delete administrator');
+
const newTarget = await prisma.user.delete({
where: { id: target.id },
});
- if (newTarget.administrator && !user.superAdmin) return res.forbidden('cannot delete administrator');
+
+ logger.debug(`deleted user ${JSON.stringify(newTarget)}`);
+
+ if (req.body.delete_files) {
+ logger.debug(`attempting to delete ${newTarget.id}'s files`);
+
+ const files = await prisma.image.findMany({
+ where: {
+ userId: newTarget.id,
+ },
+ });
+
+ for (let i = 0; i !== files.length; ++i) {
+ try {
+ await datasource.delete(files[i].file);
+ } catch {
+ logger.debug(`failed to find file ${files[i].file} to delete`);
+ }
+ }
+
+ const { count } = await prisma.image.deleteMany({
+ where: {
+ userId: newTarget.id,
+ },
+ });
+ Logger.get('users').info(
+ `User ${user.username} (${user.id}) deleted ${count} files of user ${newTarget.username} (${newTarget.id})`
+ );
+ }
+
+ logger.info(`User ${user.username} (${user.id}) deleted user ${newTarget.username} (${newTarget.id})`);
delete newTarget.password;
@@ -26,6 +62,8 @@ async function handler(req: NextApiReq, res: NextApiRes, user: UserExtended) {
} else if (req.method === 'PATCH') {
if (target.administrator && !user.superAdmin) return res.forbidden('cannot modify administrator');
+ logger.debug(`attempting to update user ${id} with ${JSON.stringify(req.body)}`);
+
if (req.body.password) {
const hashed = await hashPassword(req.body.password);
await prisma.user.update({
@@ -119,27 +157,15 @@ async function handler(req: NextApiReq, res: NextApiRes, user: UserExtended) {
where: {
id: target.id,
},
- select: {
- administrator: true,
- embedColor: true,
- embedTitle: true,
- embedSiteName: true,
- id: true,
- images: false,
- password: false,
- systemTheme: true,
- token: true,
- username: true,
- domains: true,
- avatar: true,
- oauth: true,
- },
});
- Logger.get('user').info(
+ logger.debug(`updated user ${id} with ${JSON.stringify(newUser)}`);
+
+ logger.info(
`User ${user.username} (${user.id}) updated ${target.username} (${newUser.username}) (${newUser.id})`
);
+ delete newUser.password;
return res.json(newUser);
} else {
delete target.password;
diff --git a/src/pages/api/user/check.ts b/src/pages/api/user/check.ts
new file mode 100644
index 0000000..e239d2f
--- /dev/null
+++ b/src/pages/api/user/check.ts
@@ -0,0 +1,28 @@
+import config from 'lib/config';
+import prisma from 'lib/prisma';
+import { NextApiReq, NextApiRes, withZipline } from 'middleware/withZipline';
+
+async function handler(req: NextApiReq, res: NextApiRes) {
+ if (!config.features.invites || !config.features.user_registration)
+ return res.forbidden('user/invites are disabled');
+
+ if (!req.body?.code) return res.badRequest('no code');
+ if (!req.body?.username) return res.badRequest('no username');
+
+ const { code, username } = req.body as { code: string; username: string };
+ const invite = await prisma.invite.findUnique({
+ where: { code },
+ });
+ if (!invite) return res.badRequest('invalid invite code');
+
+ const user = await prisma.user.findFirst({
+ where: { username },
+ });
+
+ if (user) return res.badRequest('username already exists');
+ return res.json({ success: true });
+}
+
+export default withZipline(handler, {
+ methods: ['POST'],
+});
diff --git a/src/pages/api/user/export.ts b/src/pages/api/user/export.ts
index 660638d..691e23f 100644
--- a/src/pages/api/user/export.ts
+++ b/src/pages/api/user/export.ts
@@ -1,11 +1,14 @@
-import { NextApiReq, NextApiRes, UserExtended, withZipline } from 'middleware/withZipline';
-import prisma from 'lib/prisma';
-import Logger from 'lib/logger';
import { Zip, ZipPassThrough } from 'fflate';
-import datasource from 'lib/datasource';
-import { readdir, stat } from 'fs/promises';
import { createReadStream, createWriteStream } from 'fs';
+import { readdir, stat } from 'fs/promises';
+import datasource from 'lib/datasource';
+import Logger from 'lib/logger';
+import prisma from 'lib/prisma';
+import { NextApiReq, NextApiRes, UserExtended, withZipline } from 'middleware/withZipline';
import { tmpdir } from 'os';
+import { join } from 'path';
+
+const logger = Logger.get('user::export');
async function handler(req: NextApiReq, res: NextApiRes, user: UserExtended) {
if (req.method === 'POST') {
@@ -19,7 +22,10 @@ async function handler(req: NextApiReq, res: NextApiRes, user: UserExtended) {
const zip = new Zip();
const export_name = `zipline_export_${user.id}_${Date.now()}.zip`;
- const write_stream = createWriteStream(tmpdir() + `/${export_name}`);
+ const path = join(tmpdir(), export_name);
+
+ logger.debug(`creating write stream at ${path}`);
+ const write_stream = createWriteStream(path);
// i found this on some stack overflow thing, forgot the url
const onBackpressure = (stream, outputStream, cb) => {
@@ -70,21 +76,22 @@ async function handler(req: NextApiReq, res: NextApiRes, user: UserExtended) {
write_stream.write(data);
if (final) {
write_stream.close();
- Logger.get('user').info(
+ logger.debug(`finished writing zip to ${path} at ${data.length} bytes written`);
+ logger.info(
`Export for ${user.username} (${user.id}) has completed and is available at ${export_name}`
);
}
} else {
write_stream.close();
-
- Logger.get('user').error(`Export for ${user.username} (${user.id}) has failed\n${err}`);
+ logger.debug(`error while writing to zip: ${err}`);
+ logger.error(`Export for ${user.username} (${user.id}) has failed\n${err}`);
}
};
- Logger.get('user').info(`Export for ${user.username} (${user.id}) has started`);
+ logger.info(`Export for ${user.username} (${user.id}) has started`);
for (let i = 0; i !== files.length; ++i) {
const file = files[i];
- // try {
+
const stream = await datasource.get(file.file);
if (stream) {
const def = new ZipPassThrough(file.file);
@@ -98,10 +105,9 @@ async function handler(req: NextApiReq, res: NextApiRes, user: UserExtended) {
});
stream.on('data', (c) => def.push(c));
stream.on('end', () => def.push(new Uint8Array(0), true));
+ } else {
+ logger.debug(`couldn't find stream for ${file.file}`);
}
- // } catch (e) {
-
- // }
}
zip.end();
@@ -115,7 +121,7 @@ async function handler(req: NextApiReq, res: NextApiRes, user: UserExtended) {
const parts = export_name.split('_');
if (Number(parts[2]) !== user.id) return res.unauthorized('cannot access export owned by another user');
- const stream = createReadStream(tmpdir() + `/${export_name}`);
+ const stream = createReadStream(join(tmpdir(), export_name));
res.setHeader('Content-Type', 'application/zip');
res.setHeader('Content-Disposition', `attachment; filename="${export_name}"`);
@@ -126,7 +132,7 @@ async function handler(req: NextApiReq, res: NextApiRes, user: UserExtended) {
const exports = [];
for (let i = 0; i !== exp.length; ++i) {
const name = exp[i];
- const stats = await stat(tmpdir() + `/${name}`);
+ const stats = await stat(join(tmpdir(), name));
if (Number(exp[i].split('_')[2]) !== user.id) continue;
exports.push({ name, size: stats.size });
diff --git a/src/pages/api/user/files.ts b/src/pages/api/user/files.ts
index 393dedf..99e7a1a 100644
--- a/src/pages/api/user/files.ts
+++ b/src/pages/api/user/files.ts
@@ -1,9 +1,11 @@
-import { NextApiReq, NextApiRes, UserExtended, withZipline } from 'middleware/withZipline';
+import config from 'lib/config';
+import datasource from 'lib/datasource';
+import Logger from 'lib/logger';
import prisma from 'lib/prisma';
import { chunk } from 'lib/util';
-import Logger from 'lib/logger';
-import datasource from 'lib/datasource';
-import config from 'lib/config';
+import { NextApiReq, NextApiRes, UserExtended, withZipline } from 'middleware/withZipline';
+
+const logger = Logger.get('files');
async function handler(req: NextApiReq, res: NextApiRes, user: UserExtended) {
if (req.method === 'DELETE') {
@@ -23,7 +25,7 @@ async function handler(req: NextApiReq, res: NextApiRes, user: UserExtended) {
userId: user.id,
},
});
- Logger.get('users').info(`User ${user.username} (${user.id}) deleted ${count} files.`);
+ logger.info(`User ${user.username} (${user.id}) deleted ${count} files.`);
return res.json({ count });
} else {
@@ -37,9 +39,7 @@ async function handler(req: NextApiReq, res: NextApiRes, user: UserExtended) {
await datasource.delete(image.file);
- Logger.get('users').info(
- `User ${user.username} (${user.id}) deleted an image ${image.file} (${image.id})`
- );
+ logger.info(`User ${user.username} (${user.id}) deleted an image ${image.file} (${image.id})`);
delete image.password;
return res.json(image);
diff --git a/src/pages/api/user/index.ts b/src/pages/api/user/index.ts
index 72bba44..908b14f 100644
--- a/src/pages/api/user/index.ts
+++ b/src/pages/api/user/index.ts
@@ -1,9 +1,11 @@
+import config from 'lib/config';
+import Logger from 'lib/logger';
+import { discord_auth, github_auth, google_auth } from 'lib/oauth';
import prisma from 'lib/prisma';
import { hashPassword } from 'lib/util';
import { NextApiReq, NextApiRes, UserExtended, withZipline } from 'middleware/withZipline';
-import Logger from 'lib/logger';
-import config from 'lib/config';
-import { discord_auth, github_auth, google_auth } from 'lib/oauth';
+
+const logger = Logger.get('user');
async function handler(req: NextApiReq, res: NextApiRes, user: UserExtended) {
if (user.oauth) {
@@ -11,6 +13,8 @@ async function handler(req: NextApiReq, res: NextApiRes, user: UserExtended) {
if (user.oauth.find((o) => o.provider === 'GITHUB')) {
const resp = await github_auth.oauth_user(user.oauth.find((o) => o.provider === 'GITHUB').token);
if (!resp) {
+ logger.debug(`oauth expired for ${JSON.stringify(user)}`);
+
return res.json({
error: 'oauth token expired',
redirect_uri: github_auth.oauth_url(config.oauth.github_client_id),
@@ -24,7 +28,9 @@ async function handler(req: NextApiReq, res: NextApiRes, user: UserExtended) {
});
if (!resp.ok) {
const provider = user.oauth.find((o) => o.provider === 'DISCORD');
- if (!provider.refresh)
+ if (!provider.refresh) {
+ logger.debug(`couldn't find a refresh token for ${JSON.stringify(user)}`);
+
return res.json({
error: 'oauth token expired',
redirect_uri: discord_auth.oauth_url(
@@ -32,6 +38,7 @@ async function handler(req: NextApiReq, res: NextApiRes, user: UserExtended) {
`${config.core.https ? 'https' : 'http'}://${req.headers.host}`
),
});
+ }
const resp2 = await fetch('https://discord.com/api/oauth2/token', {
method: 'POST',
@@ -45,7 +52,9 @@ async function handler(req: NextApiReq, res: NextApiRes, user: UserExtended) {
refresh_token: provider.refresh,
}),
});
- if (!resp2.ok)
+ if (!resp2.ok) {
+ logger.debug(`oauth expired for ${JSON.stringify(user)}`);
+
return res.json({
error: 'oauth token expired',
redirect_uri: discord_auth.oauth_url(
@@ -53,7 +62,7 @@ async function handler(req: NextApiReq, res: NextApiRes, user: UserExtended) {
`${config.core.https ? 'https' : 'http'}://${req.headers.host}`
),
});
-
+ }
const json = await resp2.json();
await prisma.oAuth.update({
@@ -74,7 +83,9 @@ async function handler(req: NextApiReq, res: NextApiRes, user: UserExtended) {
);
if (!resp.ok) {
const provider = user.oauth.find((o) => o.provider === 'GOOGLE');
- if (!provider.refresh)
+ if (!provider.refresh) {
+ logger.debug(`couldn't find a refresh token for ${JSON.stringify(user)}`);
+
return res.json({
error: 'oauth token expired',
redirect_uri: google_auth.oauth_url(
@@ -82,7 +93,7 @@ async function handler(req: NextApiReq, res: NextApiRes, user: UserExtended) {
`${config.core.https ? 'https' : 'http'}://${req.headers.host}`
),
});
-
+ }
const resp2 = await fetch('https://oauth2.googleapis.com/token', {
method: 'POST',
headers: {
@@ -95,7 +106,9 @@ async function handler(req: NextApiReq, res: NextApiRes, user: UserExtended) {
refresh_token: provider.refresh,
}),
});
- if (!resp2.ok)
+ if (!resp2.ok) {
+ logger.debug(`oauth expired for ${JSON.stringify(user)}`);
+
return res.json({
error: 'oauth token expired',
redirect_uri: google_auth.oauth_url(
@@ -103,6 +116,7 @@ async function handler(req: NextApiReq, res: NextApiRes, user: UserExtended) {
`${config.core.https ? 'https' : 'http'}://${req.headers.host}`
),
});
+ }
const json = await resp2.json();
@@ -120,6 +134,8 @@ async function handler(req: NextApiReq, res: NextApiRes, user: UserExtended) {
}
if (req.method === 'PATCH') {
+ logger.debug(`attempting to update user ${JSON.stringify(user)}`);
+
if (req.body.password) {
const hashed = await hashPassword(req.body.password);
await prisma.user.update({
@@ -220,7 +236,9 @@ async function handler(req: NextApiReq, res: NextApiRes, user: UserExtended) {
},
});
- Logger.get('user').info(`User ${user.username} (${newUser.username}) (${newUser.id}) was updated`);
+ logger.debug(`updated user ${JSON.stringify(newUser)}`);
+
+ logger.info(`User ${user.username} (${newUser.username}) (${newUser.id}) was updated`);
return res.json(newUser);
} else {
diff --git a/src/pages/api/user/recent.ts b/src/pages/api/user/recent.ts
index a190e34..f818f6d 100644
--- a/src/pages/api/user/recent.ts
+++ b/src/pages/api/user/recent.ts
@@ -1,11 +1,11 @@
-import { NextApiReq, NextApiRes, UserExtended, withZipline } from 'middleware/withZipline';
-import prisma from 'lib/prisma';
import config from 'lib/config';
+import prisma from 'lib/prisma';
+import { NextApiReq, NextApiRes, UserExtended, withZipline } from 'middleware/withZipline';
async function handler(req: NextApiReq, res: NextApiRes, user: UserExtended) {
const take = Number(req.query.take ?? 4);
- if (take > 50) return res.badRequest("take can't be more than 50");
+ if (take >= 50) return res.badRequest("take can't be more than 50");
let images = await prisma.image.findMany({
take,
diff --git a/src/pages/api/user/token.ts b/src/pages/api/user/token.ts
index 501c9d1..08e2fe7 100644
--- a/src/pages/api/user/token.ts
+++ b/src/pages/api/user/token.ts
@@ -1,9 +1,9 @@
-import { NextApiReq, NextApiRes, UserExtended, withZipline } from 'middleware/withZipline';
+import Logger from 'lib/logger';
import prisma from 'lib/prisma';
import { createToken } from 'lib/util';
-import Logger from 'lib/logger';
+import { NextApiReq, NextApiRes, UserExtended, withZipline } from 'middleware/withZipline';
-async function handler(req: NextApiReq, res: NextApiRes, user: UserExtended) {
+async function handler(_: NextApiReq, res: NextApiRes, user: UserExtended) {
const updated = await prisma.user.update({
where: {
id: user.id,
diff --git a/src/pages/api/user/urls.ts b/src/pages/api/user/urls.ts
index 9ca286b..6537bae 100644
--- a/src/pages/api/user/urls.ts
+++ b/src/pages/api/user/urls.ts
@@ -1,7 +1,7 @@
-import { NextApiReq, NextApiRes, UserExtended, withZipline } from 'middleware/withZipline';
-import prisma from 'lib/prisma';
import config from 'lib/config';
import Logger from 'lib/logger';
+import prisma from 'lib/prisma';
+import { NextApiReq, NextApiRes, UserExtended, withZipline } from 'middleware/withZipline';
async function handler(req: NextApiReq, res: NextApiRes, user: UserExtended) {
if (req.method === 'DELETE') {
diff --git a/src/pages/api/users.ts b/src/pages/api/users.ts
index 2fd6dd7..7478058 100644
--- a/src/pages/api/users.ts
+++ b/src/pages/api/users.ts
@@ -1,87 +1,15 @@
-import { NextApiReq, NextApiRes, withZipline } from 'middleware/withZipline';
import prisma from 'lib/prisma';
-import Logger from 'lib/logger';
-import datasource from 'lib/datasource';
+import { NextApiReq, NextApiRes, withZipline } from 'middleware/withZipline';
-async function handler(req: NextApiReq, res: NextApiRes) {
- if (req.method === 'POST' && req.body && req.body.code) {
- const { code, username } = req.body as { code: string; username: string };
- const invite = await prisma.invite.findUnique({
- where: { code },
- });
- if (!invite) return res.badRequest('invalid invite code');
+async function handler(_: NextApiReq, res: NextApiRes) {
+ const users = await prisma.user.findMany();
+ for (let i = 0; i !== users.length; ++i) delete users[i].password;
- const user = await prisma.user.findFirst({
- where: { username },
- });
-
- if (user) return res.badRequest('username already exists');
- return res.json({ success: true });
- }
-
- const user = await req.user();
- if (!user) return res.unauthorized('not logged in');
- if (!user.administrator) return res.forbidden('not an administrator');
-
- if (req.method === 'DELETE') {
- if (req.body.id === user.id) return res.badRequest("you can't delete your own account");
-
- const deleteUser = await prisma.user.findFirst({
- where: {
- id: req.body.id,
- },
- });
- if (!deleteUser) return res.notFound("user doesn't exist");
-
- if (req.body.delete_images) {
- const files = await prisma.image.findMany({
- where: {
- userId: deleteUser.id,
- },
- });
-
- for (let i = 0; i !== files.length; ++i) {
- try {
- await datasource.delete(files[i].file);
- } catch {}
- }
-
- const { count } = await prisma.image.deleteMany({
- where: {
- userId: deleteUser.id,
- },
- });
- Logger.get('users').info(
- `User ${user.username} (${user.id}) deleted ${count} files of user ${deleteUser.username} (${deleteUser.id})`
- );
- }
-
- await prisma.user.delete({
- where: {
- id: deleteUser.id,
- },
- });
-
- delete deleteUser.password;
- return res.json(deleteUser);
- } else {
- const users = await prisma.user.findMany({
- select: {
- username: true,
- id: true,
- administrator: true,
- superAdmin: true,
- token: true,
- embedColor: true,
- embedTitle: true,
- systemTheme: true,
- avatar: true,
- },
- });
- return res.json(users);
- }
+ return res.json(users);
}
export default withZipline(handler, {
- methods: ['GET', 'POST', 'DELETE'],
+ methods: ['GET', 'POST'],
+ user: true,
+ administrator: true,
});
diff --git a/src/pages/api/version.ts b/src/pages/api/version.ts
index 0896ff6..d933e6a 100644
--- a/src/pages/api/version.ts
+++ b/src/pages/api/version.ts
@@ -2,7 +2,7 @@ import { readFile } from 'fs/promises';
import config from 'lib/config';
import { NextApiReq, NextApiRes, withZipline } from 'middleware/withZipline';
-async function handler(req: NextApiReq, res: NextApiRes) {
+async function handler(_: NextApiReq, res: NextApiRes) {
if (!config.website.show_version) return res.forbidden('version hidden');
const pkg = JSON.parse(await readFile('package.json', 'utf8'));
diff --git a/src/pages/auth/login.tsx b/src/pages/auth/login.tsx
index 87ef0ac..64e9ca5 100644
--- a/src/pages/auth/login.tsx
+++ b/src/pages/auth/login.tsx
@@ -1,11 +1,11 @@
-import { Button, Center, TextInput, Title, PasswordInput, Divider, Group } from '@mantine/core';
+import { Button, Center, Divider, PasswordInput, TextInput, Title } from '@mantine/core';
import { useForm } from '@mantine/form';
-import Link from 'next/link';
+import { DiscordIcon, GitHubIcon, GoogleIcon } from 'components/icons';
import useFetch from 'hooks/useFetch';
+import Head from 'next/head';
+import Link from 'next/link';
import { useRouter } from 'next/router';
import { useEffect } from 'react';
-import Head from 'next/head';
-import { GitHubIcon, DiscordIcon, GoogleIcon } from 'components/icons';
export { getServerSideProps } from 'middleware/getServerSideProps';
export default function Login({ title, user_registration, oauth_registration, oauth_providers: unparsed }) {
@@ -42,7 +42,7 @@ export default function Login({ title, user_registration, oauth_registration, oa
});
if (res.error) {
- if (res.error.startsWith('403')) {
+ if (res.code === 403) {
form.setFieldError('password', 'Invalid password');
} else {
form.setFieldError('username', 'Invalid username');
diff --git a/src/pages/auth/logout.tsx b/src/pages/auth/logout.tsx
index 8b9d42d..3a751c6 100644
--- a/src/pages/auth/logout.tsx
+++ b/src/pages/auth/logout.tsx
@@ -1,10 +1,12 @@
-import React, { useEffect } from 'react';
-import { useRouter } from 'next/router';
import { LoadingOverlay } from '@mantine/core';
-import { useSetRecoilState } from 'recoil';
import { userSelector } from 'lib/recoil/user';
+import Head from 'next/head';
+import { useRouter } from 'next/router';
+import { useEffect } from 'react';
+import { useSetRecoilState } from 'recoil';
+export { getServerSideProps } from 'middleware/getServerSideProps';
-export default function Logout() {
+export default function Logout({ title }) {
const setUser = useSetRecoilState(userSelector);
const router = useRouter();
@@ -23,7 +25,15 @@ export default function Logout() {
})();
}, []);
- return ;
-}
+ const full_title = `${title} - Logout`;
-Logout.title = 'Zipline - Logout';
+ return (
+ <>
+
+ {full_title}
+
+
+
+ >
+ );
+}
diff --git a/src/pages/auth/register.tsx b/src/pages/auth/register.tsx
index 2830d85..161006b 100644
--- a/src/pages/auth/register.tsx
+++ b/src/pages/auth/register.tsx
@@ -1,17 +1,17 @@
-import { GetServerSideProps } from 'next';
-import prisma from 'lib/prisma';
-import { useState } from 'react';
-import { Box, Button, Card, Center, Group, PasswordInput, Stepper, TextInput } from '@mantine/core';
-import useFetch from 'hooks/useFetch';
-import PasswordStrength from 'components/PasswordStrength';
+import { Box, Button, Center, Group, PasswordInput, Stepper, TextInput } from '@mantine/core';
import { showNotification } from '@mantine/notifications';
import { CrossIcon, UserIcon } from 'components/icons';
-import { useRouter } from 'next/router';
-import Head from 'next/head';
+import PasswordStrength from 'components/PasswordStrength';
+import useFetch from 'hooks/useFetch';
import config from 'lib/config';
-import { useSetRecoilState } from 'recoil';
+import prisma from 'lib/prisma';
import { userSelector } from 'lib/recoil/user';
import { randomChars } from 'lib/util';
+import { GetServerSideProps } from 'next';
+import Head from 'next/head';
+import { useRouter } from 'next/router';
+import { useState } from 'react';
+import { useSetRecoilState } from 'recoil';
export default function Register({ code, title, user_registration }) {
const [active, setActive] = useState(0);
@@ -33,7 +33,7 @@ export default function Register({ code, title, user_registration }) {
setUsernameError('');
- const res = await useFetch('/api/users', 'POST', { code, username });
+ const res = await useFetch('/api/user/check', 'POST', { code, username });
if (res.error) {
setUsernameError('A user with that username already exists');
} else {
@@ -46,9 +46,8 @@ export default function Register({ code, title, user_registration }) {
setPassword(password.trim());
setVerifyPassword(verifyPassword.trim());
- if (password.trim() !== verifyPassword.trim()) {
- setVerifyPasswordError('Passwords do not match');
- }
+ if (password !== verifyPassword) setVerifyPasswordError('Passwords do not match');
+ else setVerifyPasswordError('');
};
const createUser = async () => {
@@ -57,6 +56,7 @@ export default function Register({ code, title, user_registration }) {
username,
password,
});
+
if (res.error) {
showNotification({
title: 'Error while creating user',
@@ -87,7 +87,7 @@ export default function Register({ code, title, user_registration }) {
return (
<>
- {title}
+ {full_title}
{
const { code } = context.query as { code: string };
- if (!config.features.invites && code)
- return {
- notFound: true,
- };
-
- if (!config.features.user_registration && !code) return { notFound: true };
+ const { default: Logger } = await import('lib/logger');
+ const logger = Logger.get('pages::register');
if (code) {
+ if (!config.features.invites)
+ return {
+ notFound: true,
+ };
+
const invite = await prisma.invite.findUnique({
where: {
code,
},
});
+ logger.debug(`request to access ${JSON.stringify(invite)}`);
+
if (!invite) return { notFound: true };
if (invite.used) return { notFound: true };
- if (invite.expires_at && invite.expires_at < new Date()) return { notFound: true };
+ if (invite.expires_at && invite.expires_at < new Date()) {
+ logger.debug(`restricting access to ${JSON.stringify(invite)} as it has expired`);
+
+ return { notFound: true };
+ }
return {
props: {
@@ -184,13 +191,21 @@ export const getServerSideProps: GetServerSideProps = async (context) => {
},
};
} else {
+ if (!config.features.user_registration)
+ return {
+ notFound: true,
+ };
+
const code = randomChars(4);
- await prisma.invite.create({
+ const temp = await prisma.invite.create({
data: {
code,
createdById: 1,
},
});
+
+ logger.debug(`request to access user registration, creating temporary invite ${JSON.stringify(temp)}`);
+
return {
props: {
title: config.website.title,
diff --git a/src/pages/code/[id].tsx b/src/pages/code/[id].tsx
index 843bc1c..e1a668a 100644
--- a/src/pages/code/[id].tsx
+++ b/src/pages/code/[id].tsx
@@ -1,10 +1,10 @@
import { Prism } from '@mantine/prism';
-import prisma from 'lib/prisma';
+import config from 'lib/config';
import exts from 'lib/exts';
+import prisma from 'lib/prisma';
+import { checkPassword } from 'lib/util';
import { streamToString } from 'lib/utils/streams';
import { GetServerSideProps } from 'next';
-import { checkPassword } from 'lib/util';
-import config from 'lib/config';
import Head from 'next/head';
export default function Code({ code, id, title }) {
diff --git a/src/pages/dashboard/files.tsx b/src/pages/dashboard/files.tsx
index 213c6d7..745a855 100644
--- a/src/pages/dashboard/files.tsx
+++ b/src/pages/dashboard/files.tsx
@@ -1,8 +1,7 @@
-import React from 'react';
-import useLogin from 'hooks/useLogin';
+import { LoadingOverlay } from '@mantine/core';
import Layout from 'components/Layout';
import Files from 'components/pages/Files';
-import { LoadingOverlay } from '@mantine/core';
+import useLogin from 'hooks/useLogin';
import Head from 'next/head';
export { getServerSideProps } from 'middleware/getServerSideProps';
diff --git a/src/pages/dashboard/index.tsx b/src/pages/dashboard/index.tsx
index f631c75..47e43cb 100644
--- a/src/pages/dashboard/index.tsx
+++ b/src/pages/dashboard/index.tsx
@@ -1,8 +1,7 @@
-import React from 'react';
-import useLogin from 'hooks/useLogin';
+import { LoadingOverlay } from '@mantine/core';
import Layout from 'components/Layout';
import Dashboard from 'components/pages/Dashboard';
-import { LoadingOverlay } from '@mantine/core';
+import useLogin from 'hooks/useLogin';
import Head from 'next/head';
export { getServerSideProps } from 'middleware/getServerSideProps';
diff --git a/src/pages/dashboard/invites.tsx b/src/pages/dashboard/invites.tsx
index f007dd5..297b0de 100644
--- a/src/pages/dashboard/invites.tsx
+++ b/src/pages/dashboard/invites.tsx
@@ -1,10 +1,10 @@
-import React, { useEffect } from 'react';
-import useLogin from 'hooks/useLogin';
+import { LoadingOverlay } from '@mantine/core';
import Layout from 'components/Layout';
import Invites from 'components/pages/Invites';
-import { LoadingOverlay } from '@mantine/core';
+import useLogin from 'hooks/useLogin';
import Head from 'next/head';
import { useRouter } from 'next/router';
+import { useEffect } from 'react';
export { getServerSideProps } from 'middleware/getServerSideProps';
export default function InvitesPage(props) {
diff --git a/src/pages/dashboard/manage.tsx b/src/pages/dashboard/manage.tsx
index fdff0d0..531f646 100644
--- a/src/pages/dashboard/manage.tsx
+++ b/src/pages/dashboard/manage.tsx
@@ -1,8 +1,7 @@
-import React from 'react';
-import useLogin from 'hooks/useLogin';
+import { LoadingOverlay } from '@mantine/core';
import Layout from 'components/Layout';
import Manage from 'components/pages/Manage';
-import { LoadingOverlay } from '@mantine/core';
+import useLogin from 'hooks/useLogin';
import Head from 'next/head';
export { getServerSideProps } from 'middleware/getServerSideProps';
diff --git a/src/pages/dashboard/stats.tsx b/src/pages/dashboard/stats.tsx
index 7c39b46..57fcad5 100644
--- a/src/pages/dashboard/stats.tsx
+++ b/src/pages/dashboard/stats.tsx
@@ -1,8 +1,7 @@
-import React from 'react';
-import useLogin from 'hooks/useLogin';
+import { LoadingOverlay } from '@mantine/core';
import Layout from 'components/Layout';
import Stats from 'components/pages/Stats';
-import { LoadingOverlay } from '@mantine/core';
+import useLogin from 'hooks/useLogin';
import Head from 'next/head';
export { getServerSideProps } from 'middleware/getServerSideProps';
diff --git a/src/pages/dashboard/upload.tsx b/src/pages/dashboard/upload/file.tsx
similarity index 96%
rename from src/pages/dashboard/upload.tsx
rename to src/pages/dashboard/upload/file.tsx
index 8b452db..e2f7f7c 100644
--- a/src/pages/dashboard/upload.tsx
+++ b/src/pages/dashboard/upload/file.tsx
@@ -1,8 +1,7 @@
-import React from 'react';
-import useLogin from 'hooks/useLogin';
+import { LoadingOverlay } from '@mantine/core';
import Layout from 'components/Layout';
import Upload from 'components/pages/Upload';
-import { LoadingOverlay } from '@mantine/core';
+import useLogin from 'hooks/useLogin';
import Head from 'next/head';
export { getServerSideProps } from 'middleware/getServerSideProps';
diff --git a/src/pages/dashboard/text.tsx b/src/pages/dashboard/upload/text.tsx
similarity index 95%
rename from src/pages/dashboard/text.tsx
rename to src/pages/dashboard/upload/text.tsx
index a3e2c2e..e68b96c 100644
--- a/src/pages/dashboard/text.tsx
+++ b/src/pages/dashboard/upload/text.tsx
@@ -1,8 +1,7 @@
-import React from 'react';
-import useLogin from 'hooks/useLogin';
+import { LoadingOverlay } from '@mantine/core';
import Layout from 'components/Layout';
import UploadText from 'components/pages/UploadText';
-import { LoadingOverlay } from '@mantine/core';
+import useLogin from 'hooks/useLogin';
import Head from 'next/head';
export { getServerSideProps } from 'middleware/getServerSideProps';
diff --git a/src/pages/dashboard/urls.tsx b/src/pages/dashboard/urls.tsx
index 90e55a9..ccb5938 100644
--- a/src/pages/dashboard/urls.tsx
+++ b/src/pages/dashboard/urls.tsx
@@ -1,8 +1,7 @@
-import React from 'react';
-import useLogin from 'hooks/useLogin';
+import { LoadingOverlay } from '@mantine/core';
import Layout from 'components/Layout';
import Urls from 'components/pages/Urls';
-import { LoadingOverlay } from '@mantine/core';
+import useLogin from 'hooks/useLogin';
import Head from 'next/head';
export { getServerSideProps } from 'middleware/getServerSideProps';
diff --git a/src/pages/dashboard/users.tsx b/src/pages/dashboard/users.tsx
index 74385ed..676b457 100644
--- a/src/pages/dashboard/users.tsx
+++ b/src/pages/dashboard/users.tsx
@@ -1,8 +1,7 @@
-import React from 'react';
-import useLogin from 'hooks/useLogin';
+import { LoadingOverlay } from '@mantine/core';
import Layout from 'components/Layout';
import Users from 'components/pages/Users';
-import { LoadingOverlay } from '@mantine/core';
+import useLogin from 'hooks/useLogin';
import Head from 'next/head';
export { getServerSideProps } from 'middleware/getServerSideProps';
diff --git a/src/pages/oauth_error.tsx b/src/pages/oauth_error.tsx
new file mode 100644
index 0000000..359b59a
--- /dev/null
+++ b/src/pages/oauth_error.tsx
@@ -0,0 +1,63 @@
+import { Button, Stack, Title } from '@mantine/core';
+import Link from 'components/Link';
+import MutedText from 'components/MutedText';
+import { GetServerSideProps } from 'next';
+import Head from 'next/head';
+import { useRouter } from 'next/router';
+import { useEffect, useState } from 'react';
+
+export default function OauthError({ error, provider }) {
+ const [remaining, setRemaining] = useState(10);
+ const router = useRouter();
+
+ useEffect(() => {
+ const interval = setInterval(() => {
+ setRemaining((remaining) => remaining - 1);
+ }, 1000);
+
+ return () => clearInterval(interval);
+ }, []);
+
+ if (remaining === 0) {
+ router.push('/auth/login');
+ }
+
+ return (
+ <>
+
+ Authentication Error
+
+
+
+
+ Error while authenticating with {provider}
+
+ {error}
+
+ Redirecting to login in {remaining} second{remaining === 1 ? 's' : ''}
+
+
+
+ >
+ );
+}
+
+export const getServerSideProps: GetServerSideProps = async (ctx) => {
+ return {
+ props: {
+ error: ctx.query.error ?? 'Unknown',
+ provider: ctx.query.provider ?? 'Unknown',
+ },
+ };
+};
diff --git a/src/pages/view/[id].tsx b/src/pages/view/[id].tsx
index 44d2fb6..b035d37 100644
--- a/src/pages/view/[id].tsx
+++ b/src/pages/view/[id].tsx
@@ -1,14 +1,13 @@
-import React, { useEffect, useState } from 'react';
-import Head from 'next/head';
-import { GetServerSideProps } from 'next';
import { Box, Button, Modal, PasswordInput } from '@mantine/core';
-import config from 'lib/config';
+import exts from 'lib/exts';
import prisma from 'lib/prisma';
import { parse } from 'lib/utils/client';
-import exts from 'lib/exts';
+import { GetServerSideProps } from 'next';
+import Head from 'next/head';
import { useRouter } from 'next/router';
+import { useEffect, useState } from 'react';
-export default function EmbeddedImage({ image, user, pass, prismRender }) {
+export default function EmbeddedFile({ image, user, pass, prismRender }) {
const dataURL = (route: string) => `${route}/${image.file}`;
const router = useRouter();
diff --git a/src/scripts/import-dir.ts b/src/scripts/import-dir.ts
index 74eecac..d9501e6 100644
--- a/src/scripts/import-dir.ts
+++ b/src/scripts/import-dir.ts
@@ -1,10 +1,10 @@
-import datasource from '../lib/datasource';
-import { readdir, readFile } from 'fs/promises';
-import config from '../lib/config';
-import { migrations } from '../server/util';
import { PrismaClient } from '@prisma/client';
-import { guess } from '../lib/mimes';
+import { readdir, readFile } from 'fs/promises';
import { join } from 'path';
+import config from '../lib/config';
+import datasource from '../lib/datasource';
+import { guess } from '../lib/mimes';
+import { migrations } from '../server/util';
async function main() {
const directory = process.argv[2];
diff --git a/src/scripts/list-users.ts b/src/scripts/list-users.ts
index 97bbd5d..603adb1 100644
--- a/src/scripts/list-users.ts
+++ b/src/scripts/list-users.ts
@@ -1,5 +1,5 @@
-import config from '../lib/config';
import { PrismaClient } from '@prisma/client';
+import config from '../lib/config';
import { migrations } from '../server/util';
async function main() {
diff --git a/src/scripts/set-user.ts b/src/scripts/set-user.ts
index 338c867..8ec7104 100644
--- a/src/scripts/set-user.ts
+++ b/src/scripts/set-user.ts
@@ -1,7 +1,7 @@
-import config from '../lib/config';
import { PrismaClient } from '@prisma/client';
-import { migrations } from '../server/util';
import { hash } from 'argon2';
+import config from '../lib/config';
+import { migrations } from '../server/util';
const SUPPORTED_FIELDS = [
'username',
diff --git a/src/server/index.ts b/src/server/index.ts
index eef84ff..f7d3b0b 100644
--- a/src/server/index.ts
+++ b/src/server/index.ts
@@ -1,18 +1,17 @@
+import { Image, PrismaClient } from '@prisma/client';
import Router from 'find-my-way';
+import { mkdir } from 'fs/promises';
+import { createServer, IncomingMessage, OutgoingMessage, ServerResponse } from 'http';
import next from 'next';
import { NextServer, RequestHandler } from 'next/dist/server/next';
-import { Image, PrismaClient } from '@prisma/client';
-import { createServer, IncomingMessage, OutgoingMessage, ServerResponse } from 'http';
import { extname } from 'path';
-import { mkdir } from 'fs/promises';
-import { getStats, log, migrations, redirect } from './util';
-import Logger from '../lib/logger';
-import { guess } from '../lib/mimes';
-import exts from '../lib/exts';
import { version } from '../../package.json';
import config from '../lib/config';
import datasource from '../lib/datasource';
-import { NextUrlWithParsedQuery } from 'next/dist/server/request-meta';
+import exts from '../lib/exts';
+import Logger from '../lib/logger';
+import { guess } from '../lib/mimes';
+import { getStats, log, migrations, redirect } from './util';
const dev = process.env.NODE_ENV === 'development';
const logger = Logger.get('server');
@@ -20,18 +19,22 @@ const logger = Logger.get('server');
start();
async function start() {
+ logger.debug('Starting server');
+
// annoy user if they didnt change secret from default "changethis"
if (config.core.secret === 'changethis') {
- logger.error('Secret is not set!');
- logger.error(
- 'Running Zipline as is, without a randomized secret is not recommended and leaves your instance at risk!'
- );
- logger.error('Please change your secret in the config file or environment variables.');
- logger.error(
- 'The config file is located at `.env.local`, or if using docker-compose you can change the variables in the `docker-compose.yml` file.'
- );
- logger.error('It is recomended to use a secret that is alphanumeric and randomized.');
- logger.error('A way you can generate this is through a password manager you may have.');
+ logger
+ .error('Secret is not set!')
+ .error(
+ 'Running Zipline as is, without a randomized secret is not recommended and leaves your instance at risk!'
+ )
+ .error('Please change your secret in the config file or environment variables.')
+ .error(
+ 'The config file is located at `.env.local`, or if using docker-compose you can change the variables in the `docker-compose.yml` file.'
+ )
+ .error('It is recomended to use a secret that is alphanumeric and randomized.')
+ .error('A way you can generate this is through a password manager you may have.');
+
process.exit(1);
}
@@ -50,6 +53,8 @@ async function start() {
});
if (admin) {
+ logger.debug('setting main administrator user to a superAdmin');
+
await prisma.user.update({
where: {
id: admin.id,
@@ -105,6 +110,8 @@ async function start() {
},
});
+ Logger.get('url').debug(`url deleted due to max views ${JSON.stringify(nUrl)}`);
+
return nextServer.render404(req, res as ServerResponse);
}
@@ -185,13 +192,13 @@ async function start() {
stats(prisma);
setInterval(async () => {
- await prisma.invite.deleteMany({
+ const { count } = await prisma.invite.deleteMany({
where: {
used: true,
},
});
- if (config.core.logger) logger.info('invites cleaned');
+ logger.debug(`deleted ${count} used invites`);
}, config.core.invites_interval * 1000);
}
@@ -269,6 +276,8 @@ async function stats(prisma: PrismaClient) {
},
});
+ logger.debug(`stats updated ${JSON.stringify(stats)}`);
+
setInterval(async () => {
const stats = await getStats(prisma, datasource);
await prisma.stats.create({
@@ -276,6 +285,7 @@ async function stats(prisma: PrismaClient) {
data: stats,
},
});
- if (config.core.logger) logger.info('stats updated');
+
+ logger.debug(`stats updated ${JSON.stringify(stats)}`);
}, config.core.stats_interval * 1000);
}
diff --git a/src/server/util.ts b/src/server/util.ts
index 9a761ac..c58be38 100644
--- a/src/server/util.ts
+++ b/src/server/util.ts
@@ -1,14 +1,19 @@
+import { PrismaClient } from '@prisma/client';
import { Migrate } from '@prisma/migrate/dist/Migrate';
import { ensureDatabaseExists } from '@prisma/migrate/dist/utils/ensureDatabaseExists';
+import { ServerResponse } from 'http';
+import { Datasource } from '../lib/datasources';
import Logger from '../lib/logger';
import { bytesToHuman } from '../lib/utils/bytes';
-import { Datasource } from '../lib/datasources';
-import { PrismaClient } from '@prisma/client';
-import { ServerResponse } from 'http';
export async function migrations() {
+ const logger = Logger.get('database::migrations');
+
try {
+ logger.debug('establishing database connection');
const migrate = new Migrate('./prisma/schema.prisma');
+
+ logger.debug('ensuring database exists, if not creating database - may error if no permissions');
await ensureDatabaseExists('apply', true, './prisma/schema.prisma');
const diagnose = await migrate.diagnoseMigrationHistory({
@@ -16,19 +21,28 @@ export async function migrations() {
});
if (diagnose.history?.diagnostic === 'databaseIsBehind') {
+ logger.debug('database is behind, attempting to migrate');
try {
- Logger.get('database').info('migrating database');
+ logger.debug('migrating database');
await migrate.applyMigrations();
} finally {
migrate.stop();
- Logger.get('database').info('finished migrating database');
+ logger.info('finished migrating database');
}
} else {
+ logger.debug('exiting migrations engine - database is up to date');
migrate.stop();
}
} catch (error) {
- Logger.get('database').error('Failed to migrate database... exiting...');
- Logger.get('database').error(error);
+ if (error.message.startsWith('P1001')) {
+ logger.error(
+ `Unable to connect to database \`${process.env.DATABASE_URL}\`, check your database connection`
+ );
+ } else {
+ logger.error('Failed to migrate database... exiting...');
+ logger.error(error);
+ }
+
process.exit(1);
}
}