feat: external links & bug fixes

This commit is contained in:
diced 2022-08-26 20:04:25 -07:00
parent 45541a3cdd
commit a454a4f4a8
No known key found for this signature in database
GPG key ID: 370BD1BA142842D1
22 changed files with 130 additions and 169 deletions

View file

@ -11,6 +11,14 @@ module.exports = {
},
];
},
webpack(config) {
config.resolve.fallback = {
...config.resolve.fallback,
fs: false, // the solution
};
return config;
},
poweredByHeader: false,
reactStrictMode: true,
};

View file

@ -1,6 +1,6 @@
{
"name": "zipline",
"version": "3.4.0",
"version": "3.5.1",
"license": "MIT",
"scripts": {
"dev": "REACT_EDITOR=code NODE_ENV=development tsx src/server",

View file

@ -1,31 +0,0 @@
import { PrismaClient } from '@prisma/client';
import { hashPassword, createToken } from '../src/lib/util';
const prisma = new PrismaClient();
async function main() {
const user = await prisma.user.create({
data: {
username: 'administrator',
password: await hashPassword('password'),
token: createToken(),
administrator: true,
},
});
console.log(`
When logging into Zipline for the first time, use these credentials:
Username: "${user.username}"
Password: "password"
`);
}
main()
.catch(e => {
console.error(e);
process.exit(1);
})
.finally(async () => {
await prisma.$disconnect();
});

View file

@ -8,7 +8,7 @@ import { useStoreDispatch } from 'lib/redux/store';
import Link from 'next/link';
import { useRouter } from 'next/router';
import { useEffect, useState } from 'react';
import { ActivityIcon, CheckIcon, CopyIcon, CrossIcon, DeleteIcon, FileIcon, HomeIcon, LinkIcon, LogoutIcon, PencilIcon, SettingsIcon, TagIcon, TypeIcon, UploadIcon, UserIcon } from './icons';
import { ExternalLinkIcon, ActivityIcon, CheckIcon, CopyIcon, CrossIcon, DeleteIcon, FileIcon, HomeIcon, LinkIcon, LogoutIcon, PencilIcon, SettingsIcon, TagIcon, TypeIcon, UploadIcon, UserIcon } from './icons';
import { friendlyThemeName, themes } from './Theming';
function MenuItemLink(props) {
@ -22,7 +22,7 @@ function MenuItemLink(props) {
function MenuItem(props) {
return (
<UnstyledButton
sx={theme => ({
sx={theme => ({
display: 'block',
width: '100%',
padding: 5,
@ -31,7 +31,7 @@ function MenuItem(props) {
? theme.fn.themeColor(props.color, theme.colorScheme === 'dark' ? 5 : 7)
: theme.colorScheme === 'dark'
? theme.colors.dark[0]
: theme.black,
: theme.black,
'&:hover': {
backgroundColor: props.color
? theme.fn.rgba(
@ -108,13 +108,16 @@ const admin_items = [
},
];
export default function Layout({ children, user, title }) {
export default function Layout({ children, user, props }) {
const { title } = props;
const external_links = JSON.parse(props.external_links ?? '[]');
const [token, setToken] = useState(user?.token);
const [systemTheme, setSystemTheme] = useState(user.systemTheme ?? 'system');
const [version, setVersion] = useState<{ local: string, upstream: string }>(null);
const [opened, setOpened] = useState(false); // navigation open
const [open, setOpen] = useState(false); // manage acc dropdown
const avatar = user?.avatar ?? null;
const router = useRouter();
const dispatch = useStoreDispatch();
@ -248,6 +251,19 @@ export default function Layout({ children, user, title }) {
</NavLink>
)}
</Navbar.Section>
<Navbar.Section>
{external_links.length ? external_links.map(({ label, link }, i) => (
<Link href={link} passHref key={i}>
<NavLink
label={label}
component='a'
target='_blank'
variant='light'
icon={<ExternalLinkIcon />}
/>
</Link>
)) : null}
</Navbar.Section>
{version ? (
<Navbar.Section>
<Tooltip
@ -319,8 +335,8 @@ export default function Layout({ children, user, title }) {
{user.username}
</Text>
<MenuItemLink icon={<SettingsIcon />} href='/dashboard/manage'>Manage Account</MenuItemLink>
<MenuItem icon={<CopyIcon />} onClick={() => {setOpen(false);openCopyToken();}}>Copy Token</MenuItem>
<MenuItem icon={<DeleteIcon />} onClick={() => {setOpen(false);openResetToken();}} color='red'>Reset Token</MenuItem>
<MenuItem icon={<CopyIcon />} onClick={() => { setOpen(false); openCopyToken(); }}>Copy Token</MenuItem>
<MenuItem icon={<DeleteIcon />} onClick={() => { setOpen(false); openResetToken(); }} color='red'>Reset Token</MenuItem>
<MenuItemLink icon={<LogoutIcon />} href='/auth/logout' color='red'>Logout</MenuItemLink>
<Divider
variant='solid'
@ -332,8 +348,8 @@ export default function Layout({ children, user, title }) {
})}
/>
<MenuItem icon={<PencilIcon />}>
<Select
size='xs'
<Select
size='xs'
data={Object.keys(themes).map(t => ({ value: t, label: friendlyThemeName[t] }))}
value={systemTheme}
onChange={handleUpdateTheme}

View file

@ -0,0 +1,5 @@
import { ExternalLink } from 'react-feather';
export default function ExternalLinkIcon({ ...props }) {
return <ExternalLink size={15} {...props} />;
}

View file

@ -23,6 +23,7 @@ import CalendarIcon from './CalendarIcon';
import HashIcon from './HashIcon';
import TagIcon from './TagIcon';
import ClockIcon from './ClockIcon';
import ExternalLinkIcon from './ExternalLinkIcon';
export {
ActivityIcon,
@ -50,4 +51,5 @@ export {
HashIcon,
TagIcon,
ClockIcon,
ExternalLinkIcon,
};

View file

@ -62,6 +62,13 @@ export interface ConfigWebsite {
title: string;
show_files_per_user: boolean;
show_version: boolean;
external_links: ConfigWebsiteExternalLinks[];
}
export interface ConfigWebsiteExternalLinks {
label: string;
url: string;
}
export interface ConfigDiscord {

View file

@ -24,7 +24,7 @@ function set(object: Record<string, any>, property: string, value: any) {
return object;
}
function map(env: string, type: 'string' | 'number' | 'boolean' | 'array', path: string) {
function map(env: string, type: 'string' | 'number' | 'boolean' | 'array' | 'json-array', path: string) {
return {
env,
type,
@ -85,6 +85,7 @@ export default function readConfig() {
map('WEBSITE_TITLE', 'string', 'website.title'),
map('WEBSITE_SHOW_FILES_PER_USER', 'boolean', 'website.show_files_per_user'),
map('WEBSITE_SHOW_VERSION', 'boolean', 'website.show_version'),
map('WEBSITE_EXTERNAL_LINKS', 'json-array', 'website.external_links'),
map('DISCORD_URL', 'string', 'discord.url'),
map('DISCORD_USERNAME', 'string', 'discord.username'),
@ -128,6 +129,12 @@ export default function readConfig() {
case 'boolean':
parsed = value === 'true';
break;
case 'json-array':
try {
parsed = JSON.parse(value);
} catch (e) {
parsed = [];
}
default:
parsed = value;
};

View file

@ -68,6 +68,13 @@ const validator = object({
title: string().default('Zipline'),
show_files_per_user: boolean().default(true),
show_version: boolean().default(true),
external_links: array(object({
label: string(),
link: string(),
})).default([
{ label: 'Zipline', link: 'https://github.com/diced/zipline' },
{ label: 'Documentation', link: 'https://zipline.diced.tech/' },
]),
}),
discord: object({
url: string(),

View file

@ -0,0 +1,11 @@
import config from 'lib/config';
import { GetServerSideProps } from 'next';
export const getServerSideProps: GetServerSideProps = async context => {
return {
props: {
title: config.website.title,
external_links: JSON.stringify(config.website.external_links),
},
};
};

View file

@ -9,7 +9,7 @@ import { sendShorten } from 'lib/discord';
async function handler(req: NextApiReq, res: NextApiRes) {
if (req.method !== 'POST') return res.forbid('no allow');
if (!req.headers.authorization) return res.forbid('no authorization');
const user = await prisma.user.findFirst({
where: {
token: req.headers.authorization,
@ -41,12 +41,12 @@ async function handler(req: NextApiReq, res: NextApiRes) {
userId: user.id,
},
});
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})`);
if (config.discord.shorten) {
if (config.discord?.shorten) {
await sendShorten(user, url, `${zconfig.core.https ? 'https' : 'http'}://${req.headers.host}${zconfig.urls.route}/${req.body.vanity ? req.body.vanity : invis ? invis.invis : url.id}`);
}

View file

@ -129,7 +129,7 @@ async function handler(req: NextApiReq, res: NextApiRes) {
response.files.push(`${zconfig.core.https ? 'https' : 'http'}://${req.headers.host}${zconfig.uploader.route === '/' ? '' : zconfig.uploader.route}/${invis ? invis.invis : image.file}`);
}
if (zconfig.discord.upload) {
if (zconfig.discord?.upload) {
await sendUpload(user, image, `${zconfig.core.https ? 'https' : 'http'}://${req.headers.host}${zconfig.uploader.route === '/' ? '' : zconfig.uploader.route}/${invis ? invis.invis : image.file}`);
}
}

View file

@ -3,10 +3,10 @@ import useLogin from 'hooks/useLogin';
import Layout from 'components/Layout';
import Files from 'components/pages/Files';
import { LoadingOverlay } from '@mantine/core';
import { GetServerSideProps } from 'next';
import Head from 'next/head';
export { getServerSideProps } from 'middleware/getServerSideProps';
export default function FilesPage({ title }) {
export default function FilesPage(props) {
const { user, loading } = useLogin();
if (loading) return <LoadingOverlay visible={loading} />;
@ -14,23 +14,15 @@ export default function FilesPage({ title }) {
return (
<>
<Head>
<title>{title} - Files</title>
<title>{props.title} - Files</title>
</Head>
<Layout
user={user}
title={title}
props={props}
>
<Files />
</Layout>
</>
);
}
export const getServerSideProps: GetServerSideProps = async () => {
return {
props: {
title: global.config.website.title,
},
};
};
}

View file

@ -3,10 +3,10 @@ import useLogin from 'hooks/useLogin';
import Layout from 'components/Layout';
import Dashboard from 'components/pages/Dashboard';
import { LoadingOverlay } from '@mantine/core';
import { GetServerSideProps } from 'next';
import Head from 'next/head';
export { getServerSideProps } from 'middleware/getServerSideProps';
export default function DashboardPage({ title, meta }) {
export default function DashboardPage(props) {
const { user, loading } = useLogin();
if (loading) return <LoadingOverlay visible={loading} />;
@ -14,22 +14,14 @@ export default function DashboardPage({ title, meta }) {
return (
<>
<Head>
<title>{title}</title>
<title>{props.title}</title>
</Head>
<Layout
user={user}
title={title}
props={props}
>
<Dashboard />
</Layout>
</>
);
}
export const getServerSideProps: GetServerSideProps = async () => {
return {
props: {
title: global.config.website.title,
},
};
};
}

View file

@ -4,9 +4,9 @@ import Layout from 'components/Layout';
import Invites from 'components/pages/Invites';
import { LoadingOverlay } from '@mantine/core';
import Head from 'next/head';
import { GetServerSideProps } from 'next';
export { getServerSideProps } from 'middleware/getServerSideProps';
export default function InvitesPage({ title }) {
export default function InvitesPage(props) {
const { user, loading } = useLogin();
if (loading) return <LoadingOverlay visible={loading} />;
@ -14,22 +14,14 @@ export default function InvitesPage({ title }) {
return (
<>
<Head>
<title>{title} - Invites</title>
<title>{props.title} - Invites</title>
</Head>
<Layout
user={user}
title={title}
props={props}
>
<Invites />
</Layout>
</>
);
}
export const getServerSideProps: GetServerSideProps = async () => {
return {
props: {
title: global.config.website.title,
},
};
};
}

View file

@ -3,10 +3,10 @@ import useLogin from 'hooks/useLogin';
import Layout from 'components/Layout';
import Manage from 'components/pages/Manage';
import { LoadingOverlay } from '@mantine/core';
import { GetServerSideProps } from 'next';
import Head from 'next/head';
export { getServerSideProps } from 'middleware/getServerSideProps';
export default function ManagePage({ title }) {
export default function ManagePage(props) {
const { user, loading } = useLogin();
if (loading) return <LoadingOverlay visible={loading} />;
@ -14,22 +14,14 @@ export default function ManagePage({ title }) {
return (
<>
<Head>
<title>{title} - Manage User</title>
<title>{props.title} - Manage User</title>
</Head>
<Layout
user={user}
title={title}
props={props}
>
<Manage />
</Layout>
</>
);
}
export const getServerSideProps: GetServerSideProps = async () => {
return {
props: {
title: global.config.website.title,
},
};
};
}

View file

@ -3,33 +3,25 @@ import useLogin from 'hooks/useLogin';
import Layout from 'components/Layout';
import Stats from 'components/pages/Stats';
import { LoadingOverlay } from '@mantine/core';
import { GetServerSideProps } from 'next';
import Head from 'next/head';
export { getServerSideProps } from 'middleware/getServerSideProps';
export default function StatsPage({ title }) {
export default function StatsPage(props) {
const { user, loading } = useLogin();
if (loading) return <LoadingOverlay visible={loading} />;
return (
<>
<Head>
<title>{title} - Stats</title>
<title>{props.title} - Stats</title>
</Head>
<Layout
user={user}
title={title}
props={props}
>
<Stats />
</Layout>
</>
);
}
export const getServerSideProps: GetServerSideProps = async () => {
return {
props: {
title: global.config.website.title,
},
};
};
}

View file

@ -3,10 +3,10 @@ import useLogin from 'hooks/useLogin';
import Layout from 'components/Layout';
import UploadText from 'components/pages/UploadText';
import { LoadingOverlay } from '@mantine/core';
import { GetServerSideProps } from 'next';
import Head from 'next/head';
export { getServerSideProps } from 'middleware/getServerSideProps';
export default function UploadTextPage({ title }) {
export default function UploadTextPage(props) {
const { user, loading } = useLogin();
if (loading) return <LoadingOverlay visible={loading} />;
@ -14,22 +14,14 @@ export default function UploadTextPage({ title }) {
return (
<>
<Head>
<title>{title} - Upload Text</title>
<title>{props.title} - Upload Text</title>
</Head>
<Layout
user={user}
title={title}
props={props}
>
<UploadText/>
</Layout>
</>
);
}
export const getServerSideProps: GetServerSideProps = async () => {
return {
props: {
title: global.config.website.title,
},
};
};
}

View file

@ -3,33 +3,25 @@ import useLogin from 'hooks/useLogin';
import Layout from 'components/Layout';
import Upload from 'components/pages/Upload';
import { LoadingOverlay } from '@mantine/core';
import { GetServerSideProps } from 'next';
import Head from 'next/head';
export { getServerSideProps } from 'middleware/getServerSideProps';
export default function UploadPage({ title }) {
export default function UploadPage(props) {
const { user, loading } = useLogin();
if (loading) return <LoadingOverlay visible={loading} />;
return (
<>
<Head>
<title>{title} - Upload</title>
<title>{props.title} - Upload</title>
</Head>
<Layout
user={user}
title={title}
props={props}
>
<Upload/>
<Upload />
</Layout>
</>
);
}
export const getServerSideProps: GetServerSideProps = async () => {
return {
props: {
title: global.config.website.title,
},
};
};
}

View file

@ -3,33 +3,25 @@ import useLogin from 'hooks/useLogin';
import Layout from 'components/Layout';
import Urls from 'components/pages/Urls';
import { LoadingOverlay } from '@mantine/core';
import { GetServerSideProps } from 'next';
import Head from 'next/head';
export { getServerSideProps } from 'middleware/getServerSideProps';
export default function UrlsPage({ title }) {
export default function UrlsPage(props) {
const { user, loading } = useLogin();
if (loading) return <LoadingOverlay visible={loading} />;
return (
<>
<Head>
<title>{title} - URLs</title>
<title>{props.title} - URLs</title>
</Head>
<Layout
user={user}
title={title}
props={props}
>
<Urls />
</Layout>
</>
);
}
export const getServerSideProps: GetServerSideProps = async () => {
return {
props: {
title: global.config.website.title,
},
};
};
}

View file

@ -3,10 +3,10 @@ import useLogin from 'hooks/useLogin';
import Layout from 'components/Layout';
import Users from 'components/pages/Users';
import { LoadingOverlay } from '@mantine/core';
import { GetServerSideProps } from 'next';
import Head from 'next/head';
export { getServerSideProps } from 'middleware/getServerSideProps';
export default function UsersPage({ title }) {
export default function UsersPage(props) {
const { user, loading } = useLogin();
if (loading) return <LoadingOverlay visible={loading} />;
@ -14,22 +14,14 @@ export default function UsersPage({ title }) {
return (
<>
<Head>
<title>{title} - Users</title>
<title>{props.title} - Users</title>
</Head>
<Layout
user={user}
title={title}
props={props}
>
<Users />
</Layout>
</>
);
}
export const getServerSideProps: GetServerSideProps = async () => {
return {
props: {
title: global.config.website.title,
},
};
};
}

View file

@ -10,6 +10,7 @@ import { useStoreDispatch } from 'lib/redux/store';
import { updateUser } from 'lib/redux/reducers/user';
import { useRouter } from 'next/router';
import Head from 'next/head';
import config from 'lib/config';
export default function Invite({ code, title }) {
const [active, setActive] = useState(0);
@ -146,8 +147,8 @@ export const getServerSideProps: GetServerSideProps = async context => {
return {
props: {
title: config.website.title,
code: invite.code,
title: global.config.website.title,
},
};
};