diff --git a/prisma/migrations/20220103232702_stats/migration.sql b/prisma/migrations/20220103232702_stats/migration.sql
new file mode 100644
index 0000000..850da3c
--- /dev/null
+++ b/prisma/migrations/20220103232702_stats/migration.sql
@@ -0,0 +1,8 @@
+-- CreateTable
+CREATE TABLE "Stats" (
+ "id" SERIAL NOT NULL,
+ "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ "data" JSONB NOT NULL,
+
+ CONSTRAINT "Stats_pkey" PRIMARY KEY ("id")
+);
diff --git a/prisma/schema.prisma b/prisma/schema.prisma
index 8d74ea0..be06c3e 100644
--- a/prisma/schema.prisma
+++ b/prisma/schema.prisma
@@ -75,3 +75,9 @@ model InvisibleUrl {
urlId String
url Url @relation(fields: [urlId], references: [id])
}
+
+model Stats {
+ id Int @id @default(autoincrement())
+ created_at DateTime @default(now())
+ data Json
+}
\ No newline at end of file
diff --git a/server/index.js b/server/index.js
index 43e1718..d2e9401 100644
--- a/server/index.js
+++ b/server/index.js
@@ -1,9 +1,8 @@
const next = require('next');
const { createServer } = require('http');
-const { stat, mkdir } = require('fs/promises');
+const { stat, mkdir, readdir } = require('fs/promises');
const { execSync } = require('child_process');
-const { extname } = require('path');
-const { red, green, bold } = require('colorette');
+const { extname, join } = require('path');
const { PrismaClient } = require('@prisma/client');
const validateConfig = require('./validateConfig');
const Logger = require('../src/lib/logger');
@@ -125,6 +124,22 @@ function shouldUseYarn() {
});
srv.listen(config.core.port, config.core.host ?? '0.0.0.0');
+
+ const stats = await getStats(prisma, config);
+ await prisma.stats.create({
+ data: {
+ data: stats,
+ },
+ });
+ setInterval(async () => {
+ const stats = await getStats(prisma, config);
+ await prisma.stats.create({
+ data: {
+ data: stats,
+ },
+ });
+ if (config.core.logger) Logger.get('server').info('stats updated');
+ }, config.core.stats_interval * 1000);
} catch (e) {
if (e.message && e.message.startsWith('Could not find a production')) {
Logger.get('web').error(`there is no production build - run \`${shouldUseYarn() ? 'yarn build' : 'npm build'}\``);
@@ -136,3 +151,80 @@ function shouldUseYarn() {
}
}
})();
+
+async function sizeOfDir(directory) {
+ const files = await readdir(directory);
+
+ let size = 0;
+ for (let i = 0, L = files.length; i !== L; ++i) {
+ const sta = await stat(join(directory, files[i]));
+ size += sta.size;
+ }
+
+ return size;
+}
+
+function bytesToRead(bytes) {
+ const units = ['B', 'kB', 'MB', 'GB', 'TB', 'PB'];
+ let num = 0;
+
+ while (bytes > 1024) {
+ bytes /= 1024;
+ ++num;
+ }
+
+ return `${bytes.toFixed(1)} ${units[num]}`;
+}
+
+
+async function getStats(prisma, config) {
+ const size = await sizeOfDir(join(process.cwd(), config.uploader.directory));
+ const byUser = await prisma.image.groupBy({
+ by: ['userId'],
+ _count: {
+ _all: true,
+ },
+ });
+ const count_users = await prisma.user.count();
+
+ const count_by_user = [];
+ for (let i = 0, L = byUser.length; i !== L; ++i) {
+ const user = await prisma.user.findFirst({
+ where: {
+ id: byUser[i].userId,
+ },
+ });
+
+ count_by_user.push({
+ username: user.username,
+ count: byUser[i]._count._all,
+ });
+ }
+
+ const count = await prisma.image.count();
+ const viewsCount = await prisma.image.groupBy({
+ by: ['views'],
+ _sum: {
+ views: true,
+ },
+ });
+
+ const typesCount = await prisma.image.groupBy({
+ by: ['mimetype'],
+ _count: {
+ mimetype: true,
+ },
+ });
+ const types_count = [];
+ for (let i = 0, L = typesCount.length; i !== L; ++i) types_count.push({ mimetype: typesCount[i].mimetype, count: typesCount[i]._count.mimetype });
+
+ return {
+ size: bytesToRead(size),
+ size_num: size,
+ count,
+ count_by_user: count_by_user.sort((a,b) => b.count-a.count),
+ count_users,
+ views_count: (viewsCount[0]?._sum?.views ?? 0),
+ types_count: types_count.sort((a,b) => b.count-a.count),
+ };
+}
\ No newline at end of file
diff --git a/server/validateConfig.js b/server/validateConfig.js
index b26f1e8..1e041b6 100644
--- a/server/validateConfig.js
+++ b/server/validateConfig.js
@@ -10,6 +10,7 @@ const validator = yup.object({
port: yup.number().default(3000),
database_url: yup.string().required(),
logger: yup.boolean().default(true),
+ stats_interval: yup.number().default(1800),
}).required(),
uploader: yup.object({
route: yup.string().required(),
diff --git a/src/components/Layout.tsx b/src/components/Layout.tsx
index b99f2ef..fbe6d05 100644
--- a/src/components/Layout.tsx
+++ b/src/components/Layout.tsx
@@ -87,7 +87,7 @@ function CopyTokenDialog({ open, setOpen, token }) {
- Make sure you don't share this token with anyone as they will be able to upload images on your behalf.
+ Make sure you don't share this token with anyone as they will be able to upload files on your behalf.
diff --git a/src/lib/readConfig.js b/src/lib/readConfig.js
index d246dba..f808c12 100644
--- a/src/lib/readConfig.js
+++ b/src/lib/readConfig.js
@@ -11,6 +11,7 @@ const envValues = [
e('PORT', 'number', (c, v) => c.core.port = v),
e('DATABASE_URL', 'string', (c, v) => c.core.database_url = v),
e('LOGGER', 'boolean', (c, v) => c.core.logger = v ?? true),
+ e('STATS_INTERVAL', 'number', (c, v) => c.core.stats_interval = v),
e('UPLOADER_ROUTE', 'string', (c, v) => c.uploader.route = v),
e('UPLOADER_LENGTH', 'number', (c, v) => c.uploader.length = v),
@@ -48,6 +49,7 @@ function tryReadEnv() {
port: undefined,
database_url: undefined,
logger: undefined,
+ stats_interval: undefined,
},
uploader: {
route: undefined,
diff --git a/src/lib/types.ts b/src/lib/types.ts
index 06672cc..425e5af 100644
--- a/src/lib/types.ts
+++ b/src/lib/types.ts
@@ -16,6 +16,9 @@ export interface ConfigCore {
// Whether or not to log stuff
logger: boolean;
+
+ // The interval to store stats
+ stats_interval: number;
}
export interface ConfigUploader {
diff --git a/src/lib/util.ts b/src/lib/util.ts
index 7e2a2ab..7c49b14 100644
--- a/src/lib/util.ts
+++ b/src/lib/util.ts
@@ -4,6 +4,7 @@ import { readdir, stat } from 'fs/promises';
import { join } from 'path';
import prisma from './prisma';
import { InvisibleImage, InvisibleUrl } from '@prisma/client';
+import config from './config';
export async function hashPassword(s: string): Promise {
return await hash(s);
diff --git a/src/pages/api/stats.ts b/src/pages/api/stats.ts
index d01ce15..1997b89 100644
--- a/src/pages/api/stats.ts
+++ b/src/pages/api/stats.ts
@@ -1,62 +1,18 @@
-import { join } from 'path';
import { NextApiReq, NextApiRes, withZipline } from 'middleware/withZipline';
import prisma from 'lib/prisma';
-import { bytesToRead, sizeOfDir } from 'lib/util';
-import config from 'lib/config';
async function handler(req: NextApiReq, res: NextApiRes) {
const user = await req.user();
if (!user) return res.forbid('not logged in');
- const size = await sizeOfDir(join(process.cwd(), config.uploader.directory));
- const byUser = await prisma.image.groupBy({
- by: ['userId'],
- _count: {
- _all: true,
- },
- });
- const count_users = await prisma.user.count();
-
- const count_by_user = [];
- for (let i = 0, L = byUser.length; i !== L; ++i) {
- const user = await prisma.user.findFirst({
- where: {
- id: byUser[i].userId,
- },
- });
-
- count_by_user.push({
- username: user.username,
- count: byUser[i]._count._all,
- });
- }
-
- const count = await prisma.image.count();
- const viewsCount = await prisma.image.groupBy({
- by: ['views'],
- _sum: {
- views: true,
+ const stats = await prisma.stats.findFirst({
+ orderBy: {
+ created_at: 'desc',
},
+ take: 1,
});
- const typesCount = await prisma.image.groupBy({
- by: ['mimetype'],
- _count: {
- mimetype: true,
- },
- });
- const types_count = [];
- for (let i = 0, L = typesCount.length; i !== L; ++i) types_count.push({ mimetype: typesCount[i].mimetype, count: typesCount[i]._count.mimetype });
-
- return res.json({
- size: bytesToRead(size),
- size_num: size,
- count,
- count_by_user: count_by_user.sort((a,b) => b.count-a.count),
- count_users,
- views_count: (viewsCount[0]?._sum?.views ?? 0),
- types_count: types_count.sort((a,b) => b.count-a.count),
- });
+ return res.json(stats.data);
}
export default withZipline(handler);
\ No newline at end of file
diff --git a/yarn.lock b/yarn.lock
index fc00196..8314fd7 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1405,9 +1405,9 @@ camelcase@^5.3.1:
integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==
caniuse-lite@^1.0.30001202, caniuse-lite@^1.0.30001219, caniuse-lite@^1.0.30001228:
- version "1.0.30001237"
- resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001237.tgz"
- integrity sha512-pDHgRndit6p1NR2GhzMbQ6CkRrp4VKuSsqbcLeOQppYPKOYkKT/6ZvZDvKJUqcmtyWIAHuZq3SVS2vc1egCZzw==
+ version "1.0.30001295"
+ resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001295.tgz"
+ integrity sha512-lSP16vcyC0FEy0R4ECc9duSPoKoZy+YkpGkue9G4D81OfPnliopaZrU10+qtPdT8PbGXad/PNx43TIQrOmJZSQ==
capitalize@1.0.0:
version "1.0.0"