fix: update node@18, fix views aggregation, force update stats
This commit is contained in:
parent
f2be036bac
commit
e6cebd8c46
10 changed files with 101 additions and 46 deletions
2
.nvmrc
2
.nvmrc
|
@ -1 +1 @@
|
||||||
16.16.0
|
18.12.0
|
11
Dockerfile
11
Dockerfile
|
@ -1,16 +1,15 @@
|
||||||
FROM ghcr.io/diced/prisma-binaries:4.5.x as prisma
|
FROM ghcr.io/diced/prisma-binaries:4.5.x as prisma
|
||||||
|
|
||||||
FROM alpine:3.16 AS deps
|
FROM node:alpine3.16 AS deps
|
||||||
RUN mkdir -p /prisma-engines
|
RUN mkdir -p /prisma-engines
|
||||||
WORKDIR /build
|
WORKDIR /build
|
||||||
|
|
||||||
COPY .yarn .yarn
|
COPY .yarn .yarn
|
||||||
COPY package.json yarn.lock .yarnrc.yml ./
|
COPY package.json yarn.lock .yarnrc.yml ./
|
||||||
|
|
||||||
RUN apk add --no-cache nodejs yarn
|
|
||||||
RUN yarn install --immutable
|
RUN yarn install --immutable
|
||||||
|
|
||||||
FROM alpine:3.16 AS builder
|
FROM node:alpine3.16 AS builder
|
||||||
WORKDIR /build
|
WORKDIR /build
|
||||||
|
|
||||||
COPY --from=prisma /prisma-engines /prisma-engines
|
COPY --from=prisma /prisma-engines /prisma-engines
|
||||||
|
@ -21,7 +20,7 @@ ENV PRISMA_QUERY_ENGINE_BINARY=/prisma-engines/query-engine \
|
||||||
PRISMA_CLI_QUERY_ENGINE_TYPE=binary \
|
PRISMA_CLI_QUERY_ENGINE_TYPE=binary \
|
||||||
PRISMA_CLIENT_ENGINE_TYPE=binary
|
PRISMA_CLIENT_ENGINE_TYPE=binary
|
||||||
|
|
||||||
RUN apk add --no-cache nodejs yarn openssl openssl-dev
|
RUN apk add --no-cache openssl openssl-dev
|
||||||
|
|
||||||
COPY --from=deps /build/node_modules ./node_modules
|
COPY --from=deps /build/node_modules ./node_modules
|
||||||
COPY src ./src
|
COPY src ./src
|
||||||
|
@ -34,7 +33,7 @@ ENV NEXT_TELEMETRY_DISABLED 1
|
||||||
|
|
||||||
RUN yarn build
|
RUN yarn build
|
||||||
|
|
||||||
FROM alpine:3.16 AS runner
|
FROM node:alpine3.16 AS runner
|
||||||
WORKDIR /zipline
|
WORKDIR /zipline
|
||||||
|
|
||||||
COPY --from=prisma /prisma-engines /prisma-engines
|
COPY --from=prisma /prisma-engines /prisma-engines
|
||||||
|
@ -45,7 +44,7 @@ ENV PRISMA_QUERY_ENGINE_BINARY=/prisma-engines/query-engine \
|
||||||
PRISMA_CLI_QUERY_ENGINE_TYPE=binary \
|
PRISMA_CLI_QUERY_ENGINE_TYPE=binary \
|
||||||
PRISMA_CLIENT_ENGINE_TYPE=binary
|
PRISMA_CLIENT_ENGINE_TYPE=binary
|
||||||
|
|
||||||
RUN apk add --no-cache nodejs yarn openssl openssl-dev
|
RUN apk add --no-cache openssl openssl-dev
|
||||||
|
|
||||||
ENV NODE_ENV production
|
ENV NODE_ENV production
|
||||||
ENV NEXT_TELEMETRY_DISABLED 1
|
ENV NEXT_TELEMETRY_DISABLED 1
|
||||||
|
|
|
@ -65,7 +65,7 @@
|
||||||
"@types/cookie": "^0.5.1",
|
"@types/cookie": "^0.5.1",
|
||||||
"@types/minio": "^7.0.14",
|
"@types/minio": "^7.0.14",
|
||||||
"@types/multer": "^1.4.7",
|
"@types/multer": "^1.4.7",
|
||||||
"@types/node": "16.11.68",
|
"@types/node": "^18.11.7",
|
||||||
"@types/react": "^18.0.24",
|
"@types/react": "^18.0.24",
|
||||||
"@types/sharp": "^0.31.0",
|
"@types/sharp": "^0.31.0",
|
||||||
"cross-env": "^7.0.3",
|
"cross-env": "^7.0.3",
|
||||||
|
|
|
@ -118,7 +118,7 @@ export default function File({ image, updateImages, disableMediaPreview }) {
|
||||||
<Stack>
|
<Stack>
|
||||||
<Type
|
<Type
|
||||||
file={image}
|
file={image}
|
||||||
src={image.url}
|
src={`/r/${image.file}`}
|
||||||
alt={image.file}
|
alt={image.file}
|
||||||
popup
|
popup
|
||||||
sx={{ minHeight: 200 }}
|
sx={{ minHeight: 200 }}
|
||||||
|
@ -183,7 +183,7 @@ export default function File({ image, updateImages, disableMediaPreview }) {
|
||||||
width: '100%',
|
width: '100%',
|
||||||
cursor: 'pointer',
|
cursor: 'pointer',
|
||||||
}}
|
}}
|
||||||
src={image.url}
|
src={`/r/${image.file}`}
|
||||||
alt={image.file}
|
alt={image.file}
|
||||||
onClick={() => setOpen(true)}
|
onClick={() => setOpen(true)}
|
||||||
disableMediaPreview={disableMediaPreview}
|
disableMediaPreview={disableMediaPreview}
|
||||||
|
|
|
@ -19,6 +19,7 @@ import { randomId, useInterval } from '@mantine/hooks';
|
||||||
import { useModals } from '@mantine/modals';
|
import { useModals } from '@mantine/modals';
|
||||||
import { showNotification, updateNotification } from '@mantine/notifications';
|
import { showNotification, updateNotification } from '@mantine/notifications';
|
||||||
import {
|
import {
|
||||||
|
CheckIcon,
|
||||||
CrossIcon,
|
CrossIcon,
|
||||||
DeleteIcon,
|
DeleteIcon,
|
||||||
FlameshotIcon,
|
FlameshotIcon,
|
||||||
|
@ -270,6 +271,25 @@ export default function Manage() {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const forceUpdateStats = async () => {
|
||||||
|
const res = await useFetch('/api/stats', 'POST');
|
||||||
|
if (res.error) {
|
||||||
|
showNotification({
|
||||||
|
title: 'Error updating stats',
|
||||||
|
message: res.error,
|
||||||
|
color: 'red',
|
||||||
|
icon: <CrossIcon />,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
showNotification({
|
||||||
|
title: 'Updated stats',
|
||||||
|
message: '',
|
||||||
|
color: 'green',
|
||||||
|
icon: <CheckIcon />,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const interval = useInterval(() => getExports(), 30000);
|
const interval = useInterval(() => getExports(), 30000);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
getExports();
|
getExports();
|
||||||
|
@ -400,6 +420,17 @@ export default function Manage() {
|
||||||
)}
|
)}
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
|
{user.administrator && (
|
||||||
|
<Box mt='md'>
|
||||||
|
<Title>Server</Title>
|
||||||
|
<Group>
|
||||||
|
<Button size='md' onClick={forceUpdateStats} color='red' rightIcon={<RefreshIcon />}>
|
||||||
|
Force Update Stats
|
||||||
|
</Button>
|
||||||
|
</Group>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
|
||||||
<Title my='md'>Uploaders</Title>
|
<Title my='md'>Uploaders</Title>
|
||||||
<Group>
|
<Group>
|
||||||
<Button size='xl' onClick={() => setShareXOpen(true)} rightIcon={<ShareXIcon />}>
|
<Button size='xl' onClick={() => setShareXOpen(true)} rightIcon={<ShareXIcon />}>
|
||||||
|
|
|
@ -2,11 +2,14 @@ import { NextApiReq, NextApiRes, withZipline } from 'middleware/withZipline';
|
||||||
import prisma from 'lib/prisma';
|
import prisma from 'lib/prisma';
|
||||||
import config from 'lib/config';
|
import config from 'lib/config';
|
||||||
import { Stats } from '@prisma/client';
|
import { Stats } from '@prisma/client';
|
||||||
|
import { getStats } from 'server/util';
|
||||||
|
import datasource from 'lib/datasource';
|
||||||
|
|
||||||
async function handler(req: NextApiReq, res: NextApiRes) {
|
async function handler(req: NextApiReq, res: NextApiRes) {
|
||||||
const user = await req.user();
|
const user = await req.user();
|
||||||
if (!user) return res.forbid('not logged in');
|
if (!user) return res.forbid('not logged in');
|
||||||
|
|
||||||
|
if (req.method === 'GET') {
|
||||||
let amount = typeof req.query.amount === 'string' ? Number(req.query.amount) : 2;
|
let amount = typeof req.query.amount === 'string' ? Number(req.query.amount) : 2;
|
||||||
if (isNaN(amount)) return res.bad('invalid amount');
|
if (isNaN(amount)) return res.bad('invalid amount');
|
||||||
|
|
||||||
|
@ -32,6 +35,18 @@ async function handler(req: NextApiReq, res: NextApiRes) {
|
||||||
}
|
}
|
||||||
|
|
||||||
return res.json(stats);
|
return res.json(stats);
|
||||||
|
} else if (req.method === 'POST') {
|
||||||
|
if (!user.administrator) return res.forbid('unable to force update stats as a non-admin');
|
||||||
|
|
||||||
|
const stats = await getStats(prisma, datasource);
|
||||||
|
const stats_data = await prisma.stats.create({
|
||||||
|
data: {
|
||||||
|
data: stats,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return res.json(stats_data);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default withZipline(handler);
|
export default withZipline(handler);
|
||||||
|
|
|
@ -3,6 +3,7 @@ import prisma from 'lib/prisma';
|
||||||
import { chunk } from 'lib/util';
|
import { chunk } from 'lib/util';
|
||||||
import Logger from 'lib/logger';
|
import Logger from 'lib/logger';
|
||||||
import datasource from 'lib/datasource';
|
import datasource from 'lib/datasource';
|
||||||
|
import config from 'lib/config';
|
||||||
|
|
||||||
async function handler(req: NextApiReq, res: NextApiRes) {
|
async function handler(req: NextApiReq, res: NextApiRes) {
|
||||||
const user = await req.user();
|
const user = await req.user();
|
||||||
|
@ -62,7 +63,16 @@ async function handler(req: NextApiReq, res: NextApiRes) {
|
||||||
delete image.password;
|
delete image.password;
|
||||||
return res.json(image);
|
return res.json(image);
|
||||||
} else {
|
} else {
|
||||||
let images = await prisma.image.findMany({
|
let images: {
|
||||||
|
favorite: boolean;
|
||||||
|
created_at: Date;
|
||||||
|
id: number;
|
||||||
|
file: string;
|
||||||
|
mimetype: string;
|
||||||
|
expires_at: Date;
|
||||||
|
maxViews: number;
|
||||||
|
views: number;
|
||||||
|
}[] = await prisma.image.findMany({
|
||||||
where: {
|
where: {
|
||||||
userId: user.id,
|
userId: user.id,
|
||||||
favorite: !!req.query.favorite,
|
favorite: !!req.query.favorite,
|
||||||
|
@ -82,8 +92,10 @@ async function handler(req: NextApiReq, res: NextApiRes) {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
// @ts-ignore
|
for (let i = 0; i !== images.length; ++i) {
|
||||||
images.map((image) => (image.url = `/r/${image.file}`));
|
(images[i] as unknown as { url: string }).url = `${config.uploader.route}/${images[i].file}`;
|
||||||
|
}
|
||||||
|
|
||||||
if (req.query.filter && req.query.filter === 'media')
|
if (req.query.filter && req.query.filter === 'media')
|
||||||
images = images.filter((x) => /^(video|audio|image|text)/.test(x.mimetype));
|
images = images.filter((x) => /^(video|audio|image|text)/.test(x.mimetype));
|
||||||
|
|
||||||
|
|
|
@ -28,8 +28,6 @@ async function handler(req: NextApiReq, res: NextApiRes) {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
// @ts-ignore
|
|
||||||
images.map((image) => (image.url = `/r/${image.file}`));
|
|
||||||
if (req.query.filter && req.query.filter === 'media')
|
if (req.query.filter && req.query.filter === 'media')
|
||||||
images = images.filter((x) => /^(video|audio|image)/.test(x.mimetype));
|
images = images.filter((x) => /^(video|audio|image)/.test(x.mimetype));
|
||||||
|
|
||||||
|
|
|
@ -68,8 +68,8 @@ export async function getStats(prisma: PrismaClient, datasource: Datasource) {
|
||||||
}
|
}
|
||||||
|
|
||||||
const count = await prisma.image.count();
|
const count = await prisma.image.count();
|
||||||
const viewsCount = await prisma.image.groupBy({
|
|
||||||
by: ['views'],
|
const views = await prisma.image.aggregate({
|
||||||
_sum: {
|
_sum: {
|
||||||
views: true,
|
views: true,
|
||||||
},
|
},
|
||||||
|
@ -94,7 +94,7 @@ export async function getStats(prisma: PrismaClient, datasource: Datasource) {
|
||||||
count,
|
count,
|
||||||
count_by_user: count_by_user.sort((a, b) => b.count - a.count),
|
count_by_user: count_by_user.sort((a, b) => b.count - a.count),
|
||||||
count_users,
|
count_users,
|
||||||
views_count: viewsCount[0]?._sum?.views ?? 0,
|
views_count: views?._sum?.views ?? 0,
|
||||||
types_count: types_count.sort((a, b) => b.count - a.count),
|
types_count: types_count.sort((a, b) => b.count - a.count),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
16
yarn.lock
16
yarn.lock
|
@ -1659,13 +1659,6 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"@types/node@npm:16.11.68":
|
|
||||||
version: 16.11.68
|
|
||||||
resolution: "@types/node@npm:16.11.68"
|
|
||||||
checksum: a35293b6b8867e10ab9e10b6cd5f0e4224a86256d5ef102581dcb9f79f35f255ad87443e8584ddf8dce1229ee28885c58187e41822cf8d1e82495e7897c47c3f
|
|
||||||
languageName: node
|
|
||||||
linkType: hard
|
|
||||||
|
|
||||||
"@types/node@npm:^17.0.10":
|
"@types/node@npm:^17.0.10":
|
||||||
version: 17.0.45
|
version: 17.0.45
|
||||||
resolution: "@types/node@npm:17.0.45"
|
resolution: "@types/node@npm:17.0.45"
|
||||||
|
@ -1673,6 +1666,13 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"@types/node@npm:^18.11.7":
|
||||||
|
version: 18.11.7
|
||||||
|
resolution: "@types/node@npm:18.11.7"
|
||||||
|
checksum: 69d630825cf6fbf580d08d76a4d4836ef8c34ed4fe0842221ade87d275f517099cbfabe8e22397208e564bd24926db97699ab9db5c091383269a432b336665e2
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"@types/normalize-package-data@npm:^2.4.0":
|
"@types/normalize-package-data@npm:^2.4.0":
|
||||||
version: 2.4.1
|
version: 2.4.1
|
||||||
resolution: "@types/normalize-package-data@npm:2.4.1"
|
resolution: "@types/normalize-package-data@npm:2.4.1"
|
||||||
|
@ -8634,7 +8634,7 @@ __metadata:
|
||||||
"@types/cookie": ^0.5.1
|
"@types/cookie": ^0.5.1
|
||||||
"@types/minio": ^7.0.14
|
"@types/minio": ^7.0.14
|
||||||
"@types/multer": ^1.4.7
|
"@types/multer": ^1.4.7
|
||||||
"@types/node": 16.11.68
|
"@types/node": ^18.11.7
|
||||||
"@types/react": ^18.0.24
|
"@types/react": ^18.0.24
|
||||||
"@types/sharp": ^0.31.0
|
"@types/sharp": ^0.31.0
|
||||||
argon2: ^0.30.1
|
argon2: ^0.30.1
|
||||||
|
|
Loading…
Reference in a new issue