mirror of
https://github.com/diced/zipline.git
synced 2025-12-06 04:41:12 -08:00
Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7e8cda4605 | ||
|
|
dfa0419a0a | ||
|
|
aeb2638d1e | ||
|
|
c5cef56e2a |
@@ -17,9 +17,8 @@ Fast & lightweight file uploading.
|
||||
- Configurable
|
||||
- Fast
|
||||
- Built with Next.js & React
|
||||
- Support for **multible database types**
|
||||
- Token protected uploading
|
||||
- Easy setup instructions on [docs](https://zipline.diced.me)
|
||||
- Easy setup instructions on [docs](https://zipline.diced.me) (One command install `docker-compose up`)
|
||||
|
||||
# Installing
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "zip3",
|
||||
"version": "3.1.0",
|
||||
"version": "3.2.0",
|
||||
"scripts": {
|
||||
"prepare": "husky install",
|
||||
"dev": "NODE_ENV=development node server",
|
||||
|
||||
25
prisma/migrations/20210826034827_custom_themes/migration.sql
Normal file
25
prisma/migrations/20210826034827_custom_themes/migration.sql
Normal file
@@ -0,0 +1,25 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE "User" ADD COLUMN "systemTheme" TEXT NOT NULL DEFAULT E'dark_blue';
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "Theme" (
|
||||
"id" SERIAL NOT NULL,
|
||||
"type" TEXT NOT NULL,
|
||||
"primary" TEXT NOT NULL,
|
||||
"secondary" TEXT NOT NULL,
|
||||
"error" TEXT NOT NULL,
|
||||
"warning" TEXT NOT NULL,
|
||||
"info" TEXT NOT NULL,
|
||||
"border" TEXT NOT NULL,
|
||||
"mainBackground" TEXT NOT NULL,
|
||||
"paperBackground" TEXT NOT NULL,
|
||||
"userId" INTEGER NOT NULL,
|
||||
|
||||
PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "Theme_userId_unique" ON "Theme"("userId");
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Theme" ADD FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
@@ -13,12 +13,29 @@ model User {
|
||||
password String
|
||||
token String
|
||||
administrator Boolean @default(false)
|
||||
systemTheme String @default("dark_blue")
|
||||
customTheme Theme?
|
||||
embedTitle String?
|
||||
embedColor String @default("#2f3136")
|
||||
images Image[]
|
||||
urls Url[]
|
||||
}
|
||||
|
||||
model Theme {
|
||||
id Int @id @default(autoincrement())
|
||||
type String
|
||||
primary String
|
||||
secondary String
|
||||
error String
|
||||
warning String
|
||||
info String
|
||||
border String
|
||||
mainBackground String
|
||||
paperBackground String
|
||||
user User @relation(fields: [userId], references: [id])
|
||||
userId Int
|
||||
}
|
||||
|
||||
model Image {
|
||||
id Int @id @default(autoincrement())
|
||||
file String
|
||||
@@ -31,25 +48,25 @@ model Image {
|
||||
}
|
||||
|
||||
model InvisibleImage {
|
||||
id Int
|
||||
image Image @relation(fields: [id], references: [id])
|
||||
id Int
|
||||
image Image @relation(fields: [id], references: [id])
|
||||
|
||||
invis String @unique
|
||||
invis String @unique
|
||||
}
|
||||
|
||||
model Url {
|
||||
id Int @id @default(autoincrement())
|
||||
id Int @id @default(autoincrement())
|
||||
to String
|
||||
created_at DateTime @default(now())
|
||||
views Int @default(0)
|
||||
created_at DateTime @default(now())
|
||||
views Int @default(0)
|
||||
invisible InvisibleUrl?
|
||||
user User @relation(fields: [userId], references: [id])
|
||||
user User @relation(fields: [userId], references: [id])
|
||||
userId Int
|
||||
}
|
||||
|
||||
model InvisibleUrl {
|
||||
id Int
|
||||
url Url @relation(fields: [id], references: [id])
|
||||
id Int
|
||||
url Url @relation(fields: [id], references: [id])
|
||||
|
||||
invis String @unique
|
||||
invis String @unique
|
||||
}
|
||||
|
||||
@@ -3,9 +3,8 @@ const prismaRun = require('./prisma-run');
|
||||
|
||||
module.exports = async (config) => {
|
||||
try {
|
||||
await prismaRun(config.database.url, ['migrate', 'deploy', '--schema=prisma/schema.prisma']);
|
||||
await prismaRun(config.database.url, ['generate', '--schema=prisma/schema.prisma']);
|
||||
await prismaRun(config.database.url, ['db', 'seed', '--preview-feature', '--schema=prisma/schema.prisma']);
|
||||
await prismaRun(config.database.url, ['migrate', 'deploy']);
|
||||
await prismaRun(config.database.url, ['generate']);
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
Logger.get('db').error('there was an error.. exiting..');
|
||||
|
||||
@@ -23,6 +23,7 @@ import {
|
||||
DialogContent,
|
||||
DialogContentText,
|
||||
DialogTitle,
|
||||
Select,
|
||||
} from '@material-ui/core';
|
||||
import {
|
||||
Menu as MenuIcon,
|
||||
@@ -33,10 +34,15 @@ import {
|
||||
ContentCopy as CopyIcon,
|
||||
Autorenew as ResetIcon,
|
||||
Logout as LogoutIcon,
|
||||
PeopleAlt as UsersIcon
|
||||
PeopleAlt as UsersIcon,
|
||||
Brush as BrushIcon
|
||||
} from '@material-ui/icons';
|
||||
import copy from 'copy-to-clipboard';
|
||||
import Backdrop from './Backdrop';
|
||||
import { friendlyThemeName, themes } from './Theming';
|
||||
import { useRouter } from 'next/router';
|
||||
import { useStoreDispatch } from 'lib/redux/store';
|
||||
import { updateUser } from 'lib/redux/reducers/user';
|
||||
|
||||
const items = [
|
||||
{
|
||||
@@ -122,11 +128,14 @@ function ResetTokenDialog({ open, setOpen, setToken }) {
|
||||
}
|
||||
|
||||
export default function Layout({ children, user, loading, noPaper }) {
|
||||
const [systemTheme, setSystemTheme] = useState(user.systemTheme || 'dark_blue');
|
||||
const [mobileOpen, setMobileOpen] = useState(false);
|
||||
const [anchorEl, setAnchorEl] = useState(null);
|
||||
const [copyOpen, setCopyOpen] = useState(false);
|
||||
const [resetOpen, setResetOpen] = useState(false);
|
||||
const [token, setToken] = useState(user?.token);
|
||||
const router = useRouter();
|
||||
const dispatch = useStoreDispatch();
|
||||
|
||||
const open = Boolean(anchorEl);
|
||||
const handleClick = e => setAnchorEl(e.currentTarget);
|
||||
@@ -142,6 +151,17 @@ export default function Layout({ children, user, loading, noPaper }) {
|
||||
setAnchorEl(null);
|
||||
};
|
||||
|
||||
const handleUpdateTheme = async (event: React.ChangeEvent<{ value: string }>) => {
|
||||
const newUser = await useFetch('/api/user', 'PATCH', {
|
||||
systemTheme: event.target.value || 'dark_blue'
|
||||
});
|
||||
|
||||
setSystemTheme(newUser.systemTheme);
|
||||
dispatch(updateUser(newUser));
|
||||
|
||||
router.replace(router.pathname);
|
||||
};
|
||||
|
||||
const drawer = (
|
||||
<div>
|
||||
<CopyTokenDialog open={copyOpen} setOpen={setCopyOpen} token={token} />
|
||||
@@ -221,6 +241,22 @@ export default function Layout({ children, user, loading, noPaper }) {
|
||||
</MenuItem>
|
||||
</a>
|
||||
</Link>
|
||||
<MenuItem>
|
||||
<BrushIcon sx={{ mr: 2 }} />
|
||||
<Select
|
||||
variant='standard'
|
||||
label='Theme'
|
||||
value={systemTheme}
|
||||
onChange={handleUpdateTheme}
|
||||
fullWidth
|
||||
>
|
||||
{Object.keys(themes).map(t => (
|
||||
<MenuItem value={t} key={t}>
|
||||
{friendlyThemeName[t]}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
</MenuItem>
|
||||
</Menu>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
65
src/components/Theming.tsx
Normal file
65
src/components/Theming.tsx
Normal file
@@ -0,0 +1,65 @@
|
||||
import React from 'react';
|
||||
import { ThemeProvider } from '@emotion/react';
|
||||
import { CssBaseline } from '@material-ui/core';
|
||||
import dark_blue from 'lib/themes/dark_blue';
|
||||
import dark from 'lib/themes/dark';
|
||||
import ayu_dark from 'lib/themes/ayu_dark';
|
||||
import ayu_mirage from 'lib/themes/ayu_mirage';
|
||||
import ayu_light from 'lib/themes/ayu_light';
|
||||
import nord from 'lib/themes/nord';
|
||||
import polar from 'lib/themes/polar';
|
||||
import { useStoreSelector } from 'lib/redux/store';
|
||||
import createTheme from 'lib/themes';
|
||||
|
||||
export const themes = {
|
||||
'dark_blue': dark_blue,
|
||||
'dark': dark,
|
||||
'ayu_dark': ayu_dark,
|
||||
'ayu_mirage': ayu_mirage,
|
||||
'ayu_light': ayu_light,
|
||||
'nord': nord,
|
||||
'polar': polar
|
||||
};
|
||||
|
||||
export const friendlyThemeName = {
|
||||
'dark_blue': 'Dark Blue',
|
||||
'dark': 'Very Dark',
|
||||
'ayu_dark': 'Ayu Dark',
|
||||
'ayu_mirage': 'Ayu Mirage',
|
||||
'ayu_light': 'Ayu Light',
|
||||
'nord': 'Nord',
|
||||
'polar': 'Polar'
|
||||
};
|
||||
|
||||
export default function ZiplineTheming({ Component, pageProps }) {
|
||||
let t;
|
||||
|
||||
const user = useStoreSelector(state => state.user);
|
||||
if (!user) t = themes.dark_blue;
|
||||
else {
|
||||
if (user.customTheme) {
|
||||
t = createTheme({
|
||||
type: 'dark',
|
||||
primary: user.customTheme.primary,
|
||||
secondary: user.customTheme.secondary,
|
||||
error: user.customTheme.error,
|
||||
warning: user.customTheme.warning,
|
||||
info: user.customTheme.info,
|
||||
border: user.customTheme.border,
|
||||
background: {
|
||||
main: user.customTheme.mainBackground,
|
||||
paper: user.customTheme.paperBackground
|
||||
}
|
||||
});
|
||||
} else {
|
||||
t = themes[user.systemTheme] ?? themes.dark_blue;
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<ThemeProvider theme={t}>
|
||||
<CssBaseline />
|
||||
<Component {...pageProps} />
|
||||
</ThemeProvider>
|
||||
);
|
||||
}
|
||||
@@ -24,6 +24,7 @@ type Aligns = 'inherit' | 'right' | 'left' | 'center' | 'justify';
|
||||
|
||||
export function bytesToRead(bytes: number) {
|
||||
if (isNaN(bytes)) return '0.0 B';
|
||||
if (bytes === Infinity) return '0.0 B';
|
||||
const units = ['B', 'kB', 'MB', 'GB', 'TB', 'PB'];
|
||||
let num = 0;
|
||||
|
||||
@@ -96,7 +97,7 @@ export default function Dashboard() {
|
||||
const imgs = await useFetch('/api/user/images');
|
||||
const stts = await useFetch('/api/stats');
|
||||
setImages(imgs);
|
||||
setStats(stts);
|
||||
setStats(stts);console.log(stts);
|
||||
|
||||
setApiLoading(false);
|
||||
};
|
||||
|
||||
@@ -4,18 +4,15 @@ import { Grid, Pagination, Box, Typography } from '@material-ui/core';
|
||||
import Backdrop from 'components/Backdrop';
|
||||
import ZiplineImage from 'components/Image';
|
||||
import useFetch from 'hooks/useFetch';
|
||||
import { useStoreSelector } from 'lib/redux/store';
|
||||
|
||||
export default function Upload() {
|
||||
const user = useStoreSelector(state => state.user);
|
||||
|
||||
const [pages, setPages] = useState([]);
|
||||
const [page, setPage] = useState(1);
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
const updatePages = async () => {
|
||||
setLoading(true);
|
||||
const pages = await useFetch('/api/user/images?paged=true');
|
||||
const pages = await useFetch('/api/user/images?paged=true&filter=image');
|
||||
setPages(pages);
|
||||
setLoading(false);
|
||||
};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React, { useState } from 'react';
|
||||
import { TextField, Button, Box, Typography } from '@material-ui/core';
|
||||
import { TextField, Button, Box, Typography, Select, MenuItem } from '@material-ui/core';
|
||||
|
||||
import { useFormik } from 'formik';
|
||||
import * as yup from 'yup';
|
||||
@@ -8,6 +8,7 @@ import Backdrop from 'components/Backdrop';
|
||||
import Alert from 'components/Alert';
|
||||
import { useStoreDispatch, useStoreSelector } from 'lib/redux/store';
|
||||
import { updateUser } from 'lib/redux/reducers/user';
|
||||
import { useRouter } from 'next/router';
|
||||
|
||||
const validationSchema = yup.object({
|
||||
username: yup
|
||||
@@ -15,6 +16,45 @@ const validationSchema = yup.object({
|
||||
.required('Username is required')
|
||||
});
|
||||
|
||||
const themeValidationSchema = yup.object({
|
||||
type: yup
|
||||
.string()
|
||||
.required('Type (dark, light) is required is required'),
|
||||
primary: yup
|
||||
.string()
|
||||
.required('Primary color is required')
|
||||
.matches(/\#[0-9A-Fa-f]{6}/g, { message: 'Not a valid hex color' }),
|
||||
secondary: yup
|
||||
.string()
|
||||
.required('Secondary color is required')
|
||||
.matches(/\#[0-9A-Fa-f]{6}/g, { message: 'Not a valid hex color' }),
|
||||
error: yup
|
||||
.string()
|
||||
.required('Error color is required')
|
||||
.matches(/\#[0-9A-Fa-f]{6}/g, { message: 'Not a valid hex color' }),
|
||||
warning: yup
|
||||
.string()
|
||||
.required('Warning color is required')
|
||||
.matches(/\#[0-9A-Fa-f]{6}/g, { message: 'Not a valid hex color' }),
|
||||
info: yup
|
||||
.string()
|
||||
.required('Info color is required')
|
||||
.matches(/\#[0-9A-Fa-f]{6}/g, { message: 'Not a valid hex color' }),
|
||||
border: yup
|
||||
.string()
|
||||
.required('Border color is required')
|
||||
.matches(/\#[0-9A-Fa-f]{6}/g, { message: 'Not a valid hex color' }),
|
||||
mainBackground: yup
|
||||
.string()
|
||||
.required('Main Background is required')
|
||||
.matches(/\#[0-9A-Fa-f]{6}/g, { message: 'Not a valid hex color' }),
|
||||
paperBackground: yup
|
||||
.string()
|
||||
.required('Paper Background is required')
|
||||
.matches(/\#[0-9A-Fa-f]{6}/g, { message: 'Not a valid hex color' }),
|
||||
|
||||
});
|
||||
|
||||
function TextInput({ id, label, formik, ...other }) {
|
||||
return (
|
||||
<TextField
|
||||
@@ -36,6 +76,7 @@ function TextInput({ id, label, formik, ...other }) {
|
||||
export default function Manage() {
|
||||
const user = useStoreSelector(state => state.user);
|
||||
const dispatch = useStoreDispatch();
|
||||
const router = useRouter();
|
||||
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [open, setOpen] = useState(false);
|
||||
@@ -84,6 +125,41 @@ export default function Manage() {
|
||||
}
|
||||
});
|
||||
|
||||
const customThemeFormik = useFormik({
|
||||
initialValues: {
|
||||
type: user.customTheme?.type || 'dark',
|
||||
primary: user.customTheme?.primary || '',
|
||||
secondary: user.customTheme?.secondary || '',
|
||||
error: user.customTheme?.error || '',
|
||||
warning: user.customTheme?.warning || '',
|
||||
info: user.customTheme?.info || '',
|
||||
border: user.customTheme?.border || '',
|
||||
mainBackground: user.customTheme?.mainBackground || '',
|
||||
paperBackground: user.customTheme?.paperBackground || '',
|
||||
},
|
||||
validationSchema: themeValidationSchema,
|
||||
onSubmit: async values => {
|
||||
setLoading(true);
|
||||
|
||||
const newUser = await useFetch('/api/user', 'PATCH', { customTheme: values });
|
||||
console.log(newUser);
|
||||
|
||||
if (newUser.error) {
|
||||
setLoading(false);
|
||||
setMessage('An error occured');
|
||||
setSeverity('error');
|
||||
setOpen(true);
|
||||
} else {
|
||||
dispatch(updateUser(newUser));
|
||||
router.replace(router.pathname);
|
||||
setLoading(false);
|
||||
setMessage('Saved theme');
|
||||
setSeverity('success');
|
||||
setOpen(true);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
<Backdrop open={loading}/>
|
||||
@@ -104,7 +180,42 @@ export default function Manage() {
|
||||
<Button
|
||||
variant='contained'
|
||||
type='submit'
|
||||
>Save</Button>
|
||||
>Save User</Button>
|
||||
</Box>
|
||||
</form>
|
||||
<Typography variant='h4' py={2}>Manage Theme</Typography>
|
||||
<form onSubmit={customThemeFormik.handleSubmit}>
|
||||
<Select
|
||||
id='type'
|
||||
name='type'
|
||||
label='Type'
|
||||
value={customThemeFormik.values['type']}
|
||||
onChange={customThemeFormik.handleChange}
|
||||
error={customThemeFormik.touched['type'] && Boolean(customThemeFormik.errors['type'])}
|
||||
variant='standard'
|
||||
fullWidth
|
||||
>
|
||||
<MenuItem value='dark'>Dark Theme</MenuItem>
|
||||
<MenuItem value='light'>Light Theme</MenuItem>
|
||||
</Select>
|
||||
<TextInput id='primary' label='Primary Color' formik={customThemeFormik} />
|
||||
<TextInput id='secondary' label='Secondary Color' formik={customThemeFormik} />
|
||||
<TextInput id='error' label='Error Color' formik={customThemeFormik} />
|
||||
<TextInput id='warning' label='Warning Color' formik={customThemeFormik} />
|
||||
<TextInput id='info' label='Info Color' formik={customThemeFormik} />
|
||||
<TextInput id='border' label='Border Color' formik={customThemeFormik} />
|
||||
<TextInput id='mainBackground' label='Main Background' formik={customThemeFormik} />
|
||||
<TextInput id='paperBackground' label='Paper Background' formik={customThemeFormik} />
|
||||
<Box
|
||||
display='flex'
|
||||
justifyContent='right'
|
||||
alignItems='right'
|
||||
pt={2}
|
||||
>
|
||||
<Button
|
||||
variant='contained'
|
||||
type='submit'
|
||||
>Save Theme</Button>
|
||||
</Box>
|
||||
</form>
|
||||
</>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||
import type { CookieSerializeOptions } from 'cookie';
|
||||
import type { User } from '@prisma/client';
|
||||
import type { Image, Theme, User } from '@prisma/client';
|
||||
|
||||
import { serialize } from 'cookie';
|
||||
import { sign64, unsign64 } from '../util';
|
||||
@@ -17,7 +17,18 @@ export interface NextApiFile {
|
||||
}
|
||||
|
||||
export type NextApiReq = NextApiRequest & {
|
||||
user: () => Promise<User | null | void>;
|
||||
user: () => Promise<{
|
||||
username: string;
|
||||
token: string;
|
||||
embedTitle: string;
|
||||
embedColor: string;
|
||||
systemTheme: string;
|
||||
customTheme?: Theme;
|
||||
administrator: boolean;
|
||||
id: number;
|
||||
images: Image[];
|
||||
password: string;
|
||||
} | null | void>;
|
||||
getCookie: (name: string) => string | null;
|
||||
cleanCookie: (name: string) => void;
|
||||
file?: NextApiFile;
|
||||
@@ -83,6 +94,18 @@ export const withZipline = (handler: (req: NextApiRequest, res: NextApiResponse)
|
||||
const user = await prisma.user.findFirst({
|
||||
where: {
|
||||
id: Number(userId)
|
||||
},
|
||||
select: {
|
||||
administrator: true,
|
||||
embedColor: true,
|
||||
embedTitle: true,
|
||||
id: true,
|
||||
images: true,
|
||||
password: true,
|
||||
systemTheme: true,
|
||||
customTheme: true,
|
||||
token: true,
|
||||
username: true
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { Theme } from '@prisma/client';
|
||||
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
|
||||
|
||||
export interface User {
|
||||
@@ -5,6 +6,8 @@ export interface User {
|
||||
token: string;
|
||||
embedTitle: string;
|
||||
embedColor: string;
|
||||
systemTheme: string;
|
||||
customTheme?: Theme;
|
||||
}
|
||||
|
||||
const initialState: User = null;
|
||||
|
||||
17
src/lib/themes/ayu_dark.ts
Normal file
17
src/lib/themes/ayu_dark.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
// https://github.com/AlphaNecron/
|
||||
// https://github.com/ayu-theme/ayu-colors
|
||||
import createTheme from '.';
|
||||
|
||||
export default createTheme({
|
||||
type: 'dark',
|
||||
primary: '#E6B450',
|
||||
secondary: '#FFEE99',
|
||||
error: '#F07178',
|
||||
warning: '#F29668',
|
||||
info: '#95E6CB',
|
||||
border: '#0D1016',
|
||||
background: {
|
||||
main: '#0A0E14',
|
||||
paper: '#0D1016'
|
||||
}
|
||||
});
|
||||
17
src/lib/themes/ayu_light.ts
Normal file
17
src/lib/themes/ayu_light.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
// https://github.com/AlphaNecron/
|
||||
// https://github.com/ayu-theme/ayu-colors
|
||||
import createTheme from '.';
|
||||
|
||||
export default createTheme({
|
||||
type: 'light',
|
||||
primary: '#FF9940',
|
||||
secondary: '#E6BA7E',
|
||||
error: '#F07171',
|
||||
warning: '#ED9366',
|
||||
info: '#95E6CB',
|
||||
border: '#FFFFFF',
|
||||
background: {
|
||||
main: '#FAFAFA',
|
||||
paper: '#FFFFFF'
|
||||
}
|
||||
});
|
||||
17
src/lib/themes/ayu_mirage.ts
Normal file
17
src/lib/themes/ayu_mirage.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
// https://github.com/AlphaNecron/
|
||||
// https://github.com/ayu-theme/ayu-colors
|
||||
import createTheme from '.';
|
||||
|
||||
export default createTheme({
|
||||
type: 'dark',
|
||||
primary: '#FFCC66',
|
||||
secondary: '#FFD580',
|
||||
error: '#F28779',
|
||||
warning: '#F29E74',
|
||||
info: '#95E6CB',
|
||||
border: '#232834',
|
||||
background: {
|
||||
main: '#1F2430',
|
||||
paper: '#232834'
|
||||
}
|
||||
});
|
||||
15
src/lib/themes/dark.ts
Normal file
15
src/lib/themes/dark.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import createTheme from '.';
|
||||
|
||||
export default createTheme({
|
||||
type: 'dark',
|
||||
primary: '#2c39a6',
|
||||
secondary: '#7344e2',
|
||||
error: '#ff4141',
|
||||
warning: '#ff9800',
|
||||
info: '#2f6fb9',
|
||||
border: '#2b2b2b',
|
||||
background: {
|
||||
main: '#000000',
|
||||
paper: '#060606'
|
||||
}
|
||||
});
|
||||
17
src/lib/themes/nord.ts
Normal file
17
src/lib/themes/nord.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
// https://github.com/AlphaNecron/
|
||||
// https://github.com/arcticicestudio/nord
|
||||
import createTheme from '.';
|
||||
|
||||
export default createTheme({
|
||||
type: 'dark',
|
||||
primary: '#81A1C1',
|
||||
secondary: '#88C0D0',
|
||||
error: '#BF616A',
|
||||
warning: '#EBCB8B',
|
||||
info: '#5E81AC',
|
||||
border: '#3B4252',
|
||||
background: {
|
||||
main: '#2E3440',
|
||||
paper: '#3B4252'
|
||||
}
|
||||
});
|
||||
17
src/lib/themes/polar.ts
Normal file
17
src/lib/themes/polar.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
// https://github.com/AlphaNecron/
|
||||
// https://github.com/arcticicestudio/nord
|
||||
import createTheme from '.';
|
||||
|
||||
export default createTheme({
|
||||
type: 'light',
|
||||
primary: '#81A1C1',
|
||||
secondary: '#88C0D0',
|
||||
error: '#BF616A',
|
||||
warning: '#EBCB8B',
|
||||
info: '#5E81AC',
|
||||
border: '#E5E9F0',
|
||||
background: {
|
||||
main: '#D8DEE9',
|
||||
paper: '#E5E9F0'
|
||||
}
|
||||
});
|
||||
17
src/pages/404.tsx
Normal file
17
src/pages/404.tsx
Normal file
@@ -0,0 +1,17 @@
|
||||
import React from 'react';
|
||||
import { Box, Typography } from '@material-ui/core';
|
||||
|
||||
export default function FourOhFour() {
|
||||
return (
|
||||
<>
|
||||
<Box
|
||||
display='flex'
|
||||
justifyContent='center'
|
||||
alignItems='center'
|
||||
minHeight='100vh'
|
||||
>
|
||||
<Typography variant='h2'>404 - Not Found</Typography>
|
||||
</Box>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -56,6 +56,10 @@ export const getServerSideProps: GetServerSideProps = async (context) => {
|
||||
}
|
||||
});
|
||||
|
||||
if (!image) return {
|
||||
notFound: true
|
||||
};
|
||||
|
||||
const user = await prisma.user.findFirst({
|
||||
select: {
|
||||
embedTitle: true,
|
||||
@@ -66,11 +70,7 @@ export const getServerSideProps: GetServerSideProps = async (context) => {
|
||||
id: image.userId
|
||||
}
|
||||
});
|
||||
|
||||
if (!image) return {
|
||||
notFound: true
|
||||
};
|
||||
|
||||
|
||||
if (!image.mimetype.startsWith('image')) return {
|
||||
redirect: {
|
||||
permanent: true,
|
||||
|
||||
@@ -2,9 +2,7 @@ import React from 'react';
|
||||
import { Provider } from 'react-redux';
|
||||
import PropTypes from 'prop-types';
|
||||
import Head from 'next/head';
|
||||
import { ThemeProvider } from '@material-ui/core/styles';
|
||||
import CssBaseline from '@material-ui/core/CssBaseline';
|
||||
import theme from 'lib/themes/dark_blue';
|
||||
import Theming from 'components/Theming';
|
||||
import { useStore } from 'lib/redux/store';
|
||||
|
||||
export default function MyApp({ Component, pageProps }) {
|
||||
@@ -22,10 +20,10 @@ export default function MyApp({ Component, pageProps }) {
|
||||
<meta name='description' content='Zipline' />
|
||||
<meta name='viewport' content='minimum-scale=1, initial-scale=1, width=device-width' />
|
||||
</Head>
|
||||
<ThemeProvider theme={theme}>
|
||||
<CssBaseline />
|
||||
<Component {...pageProps} />
|
||||
</ThemeProvider>
|
||||
<Theming
|
||||
Component={Component}
|
||||
pageProps={pageProps}
|
||||
/>
|
||||
</Provider>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2,21 +2,28 @@ import prisma from 'lib/prisma';
|
||||
import { NextApiReq, NextApiRes, withZipline } from 'middleware/withZipline';
|
||||
import { checkPassword } from 'lib/util';
|
||||
import Logger from 'lib/logger';
|
||||
import prismaRun from '../../../../scripts/prisma-run';
|
||||
import config from 'lib/config';
|
||||
|
||||
async function handler(req: NextApiReq, res: NextApiRes) {
|
||||
if (req.method !== 'POST') return res.status(405).end();
|
||||
const { username, password } = req.body as { username: string, password: string };
|
||||
|
||||
const users = await prisma.user.findMany();
|
||||
if (users.length === 0) {
|
||||
await prismaRun(config.database.url, ['db', 'seed', '--preview-feature']);
|
||||
}
|
||||
|
||||
const user = await prisma.user.findFirst({
|
||||
where: {
|
||||
username
|
||||
}
|
||||
});
|
||||
|
||||
if (!user) return res.status(404).end(JSON.stringify({ message: 'not found' }));
|
||||
if (!user) return res.status(404).end(JSON.stringify({ error: 'User not found' }));
|
||||
|
||||
const valid = await checkPassword(password, user.password);
|
||||
if (!valid) return res.forbid('wrong password');
|
||||
if (!valid) return res.forbid('Wrong password');
|
||||
|
||||
res.setCookie('user', user.id, { sameSite: true, maxAge: 10000000, path: '/' });
|
||||
|
||||
@@ -25,4 +32,4 @@ async function handler(req: NextApiReq, res: NextApiRes) {
|
||||
return res.json({ success: true });
|
||||
}
|
||||
|
||||
export default withZipline(handler);
|
||||
export default withZipline(handler);
|
||||
|
||||
@@ -25,9 +25,9 @@ async function handler(req: NextApiReq, res: NextApiRes) {
|
||||
|
||||
return res.json(image);
|
||||
} else {
|
||||
const images = await prisma.image.findMany({
|
||||
let images = await prisma.image.findMany({
|
||||
where: {
|
||||
user
|
||||
userId: user.id
|
||||
},
|
||||
select: {
|
||||
created_at: true,
|
||||
@@ -40,6 +40,7 @@ async function handler(req: NextApiReq, res: NextApiRes) {
|
||||
|
||||
// @ts-ignore
|
||||
images.map(image => image.url = `${config.uploader.route}/${image.file}`);
|
||||
if (req.query.filter && req.query.filter === 'image') images = images.filter(x => x.mimetype.startsWith('image'));
|
||||
|
||||
return res.json(req.query.paged ? chunk(images, 16) : images);
|
||||
}
|
||||
|
||||
@@ -31,14 +31,49 @@ async function handler(req: NextApiReq, res: NextApiRes) {
|
||||
data: { embedColor: req.body.embedColor }
|
||||
});
|
||||
|
||||
if (req.body.systemTheme) await prisma.user.update({
|
||||
where: { id: user.id },
|
||||
data: { systemTheme: req.body.systemTheme }
|
||||
});
|
||||
|
||||
if (req.body.customTheme) {
|
||||
if (user.customTheme) await prisma.user.update({
|
||||
where: { id: user.id },
|
||||
data: {
|
||||
customTheme: {
|
||||
update: {
|
||||
...req.body.customTheme
|
||||
}
|
||||
}
|
||||
}
|
||||
}); else await prisma.theme.create({
|
||||
data: {
|
||||
userId: user.id,
|
||||
...req.body.customTheme
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const newUser = await prisma.user.findFirst({
|
||||
where: { id: user.id }
|
||||
where: {
|
||||
id: Number(user.id)
|
||||
},
|
||||
select: {
|
||||
administrator: true,
|
||||
embedColor: true,
|
||||
embedTitle: true,
|
||||
id: true,
|
||||
images: false,
|
||||
password: false,
|
||||
systemTheme: true,
|
||||
customTheme: true,
|
||||
token: true,
|
||||
username: true
|
||||
}
|
||||
});
|
||||
|
||||
Logger.get('user').info(`User ${user.username} (${newUser.username}) (${newUser.id}) was updated`);
|
||||
|
||||
delete newUser.password;
|
||||
|
||||
return res.json(newUser);
|
||||
} else {
|
||||
delete user.password;
|
||||
|
||||
Reference in New Issue
Block a user