feat(v3.2.4): url shortenning
This commit is contained in:
parent
a9d0be8aae
commit
3451bd8762
19 changed files with 300 additions and 110 deletions
|
@ -5,6 +5,7 @@ module.exports = {
|
||||||
'linebreak-style': ['error', 'unix'],
|
'linebreak-style': ['error', 'unix'],
|
||||||
'quotes': ['error', 'single'],
|
'quotes': ['error', 'single'],
|
||||||
'semi': ['error', 'always'],
|
'semi': ['error', 'always'],
|
||||||
|
'comma-dangle': ['error', 'always-multiline'],
|
||||||
'jsx-quotes': ['error', 'prefer-single'],
|
'jsx-quotes': ['error', 'prefer-single'],
|
||||||
'react/prop-types': 'off',
|
'react/prop-types': 'off',
|
||||||
'react-hooks/rules-of-hooks': 'off',
|
'react-hooks/rules-of-hooks': 'off',
|
||||||
|
@ -19,6 +20,6 @@ module.exports = {
|
||||||
'react/react-in-jsx-scope': 'error',
|
'react/react-in-jsx-scope': 'error',
|
||||||
'react/require-render-return': 'error',
|
'react/require-render-return': 'error',
|
||||||
'react/style-prop-object': 'warn',
|
'react/style-prop-object': 'warn',
|
||||||
'@next/next/no-img-element': 'off'
|
'@next/next/no-img-element': 'off',
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -13,7 +13,7 @@ COPY package.json yarn.lock next.config.js next-env.d.ts zip-env.d.ts tsconfig.j
|
||||||
RUN yarn install
|
RUN yarn install
|
||||||
|
|
||||||
# create a mock config.toml to spoof next build!
|
# create a mock config.toml to spoof next build!
|
||||||
RUN echo -e "[uploader]\nroute = '/u'" > config.toml
|
RUN echo -e "[core]\nsecret = '12345678'\ndatabase_url = 'postgres://postgres:postgres@postgres/postgres'\n[uploader]\nroute = '/u'\ndirectory = './uploads'\n[urls]\nroute = '/go'" > config.toml
|
||||||
|
|
||||||
RUN yarn build
|
RUN yarn build
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "zip3",
|
"name": "zip3",
|
||||||
"version": "3.2.3",
|
"version": "3.2.4",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"prepare": "husky install",
|
"prepare": "husky install",
|
||||||
"dev": "NODE_ENV=development node server",
|
"dev": "NODE_ENV=development node server",
|
||||||
|
@ -9,7 +9,7 @@
|
||||||
"build:schema": "prisma generate --schema=prisma/schema.prisma",
|
"build:schema": "prisma generate --schema=prisma/schema.prisma",
|
||||||
"start": "node server",
|
"start": "node server",
|
||||||
"lint": "next lint",
|
"lint": "next lint",
|
||||||
"ts-node": "ts-node --compiler-options \"{\\\"module\\\":\\\"commonjs\\\"}\" --transpile-only",
|
"seed": "ts-node --compiler-options \"{\\\"module\\\":\\\"commonjs\\\"}\" --transpile-only prisma/seed.ts",
|
||||||
"semantic-release": "semantic-release"
|
"semantic-release": "semantic-release"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
@ -19,7 +19,7 @@
|
||||||
"@material-ui/core": "^5.0.0-alpha.37",
|
"@material-ui/core": "^5.0.0-alpha.37",
|
||||||
"@material-ui/icons": "^5.0.0-alpha.37",
|
"@material-ui/icons": "^5.0.0-alpha.37",
|
||||||
"@material-ui/styles": "^5.0.0-alpha.35",
|
"@material-ui/styles": "^5.0.0-alpha.35",
|
||||||
"@prisma/client": "^3.0.2",
|
"@prisma/client": "^3.1.1",
|
||||||
"@reduxjs/toolkit": "^1.6.0",
|
"@reduxjs/toolkit": "^1.6.0",
|
||||||
"argon2": "^0.28.2",
|
"argon2": "^0.28.2",
|
||||||
"colorette": "^1.2.2",
|
"colorette": "^1.2.2",
|
||||||
|
@ -29,7 +29,7 @@
|
||||||
"formik": "^2.2.9",
|
"formik": "^2.2.9",
|
||||||
"multer": "^1.4.2",
|
"multer": "^1.4.2",
|
||||||
"next": "11.1.1",
|
"next": "11.1.1",
|
||||||
"prisma": "^3.0.2",
|
"prisma": "^3.1.1",
|
||||||
"react": "17.0.2",
|
"react": "17.0.2",
|
||||||
"react-dom": "17.0.2",
|
"react-dom": "17.0.2",
|
||||||
"react-dropzone": "^11.3.2",
|
"react-dropzone": "^11.3.2",
|
||||||
|
|
39
prisma/migrations/20210924045900_delete_url/migration.sql
Normal file
39
prisma/migrations/20210924045900_delete_url/migration.sql
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
/*
|
||||||
|
Warnings:
|
||||||
|
|
||||||
|
- You are about to drop the `InvisibleUrl` table. If the table is not empty, all the data it contains will be lost.
|
||||||
|
- You are about to drop the `Url` table. If the table is not empty, all the data it contains will be lost.
|
||||||
|
|
||||||
|
*/
|
||||||
|
-- DropForeignKey
|
||||||
|
ALTER TABLE "Image" DROP CONSTRAINT "Image_userId_fkey";
|
||||||
|
|
||||||
|
-- DropForeignKey
|
||||||
|
ALTER TABLE "InvisibleImage" DROP CONSTRAINT "InvisibleImage_imageId_fkey";
|
||||||
|
|
||||||
|
-- DropForeignKey
|
||||||
|
ALTER TABLE "InvisibleUrl" DROP CONSTRAINT "InvisibleUrl_id_fkey";
|
||||||
|
|
||||||
|
-- DropForeignKey
|
||||||
|
ALTER TABLE "Theme" DROP CONSTRAINT "Theme_userId_fkey";
|
||||||
|
|
||||||
|
-- DropForeignKey
|
||||||
|
ALTER TABLE "Url" DROP CONSTRAINT "Url_userId_fkey";
|
||||||
|
|
||||||
|
-- DropTable
|
||||||
|
DROP TABLE "InvisibleUrl";
|
||||||
|
|
||||||
|
-- DropTable
|
||||||
|
DROP TABLE "Url";
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "Theme" ADD CONSTRAINT "Theme_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "Image" ADD CONSTRAINT "Image_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "InvisibleImage" ADD CONSTRAINT "InvisibleImage_imageId_fkey" FOREIGN KEY ("imageId") REFERENCES "Image"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- RenameIndex
|
||||||
|
ALTER INDEX "InvisibleImage.invis_unique" RENAME TO "InvisibleImage_invis_key";
|
34
prisma/migrations/20210924050753_new_url/migration.sql
Normal file
34
prisma/migrations/20210924050753_new_url/migration.sql
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "Url" (
|
||||||
|
"id" TEXT NOT NULL,
|
||||||
|
"destination" TEXT NOT NULL,
|
||||||
|
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"views" INTEGER NOT NULL DEFAULT 0,
|
||||||
|
"userId" INTEGER NOT NULL,
|
||||||
|
|
||||||
|
CONSTRAINT "Url_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "InvisibleUrl" (
|
||||||
|
"id" SERIAL NOT NULL,
|
||||||
|
"invis" TEXT NOT NULL,
|
||||||
|
"urlId" TEXT NOT NULL,
|
||||||
|
|
||||||
|
CONSTRAINT "InvisibleUrl_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE UNIQUE INDEX "Url_id_key" ON "Url"("id");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE UNIQUE INDEX "InvisibleUrl_invis_key" ON "InvisibleUrl"("invis");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE UNIQUE INDEX "InvisibleUrl_urlId_unique" ON "InvisibleUrl"("urlId");
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "Url" ADD CONSTRAINT "Url_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "InvisibleUrl" ADD CONSTRAINT "InvisibleUrl_urlId_fkey" FOREIGN KEY ("urlId") REFERENCES "Url"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
|
@ -0,0 +1,2 @@
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "Url" ADD COLUMN "vanity" TEXT;
|
|
@ -57,17 +57,19 @@ model InvisibleImage {
|
||||||
}
|
}
|
||||||
|
|
||||||
model Url {
|
model Url {
|
||||||
id Int @id @default(autoincrement())
|
id String @id @unique
|
||||||
to String
|
destination String
|
||||||
created_at DateTime @default(now())
|
vanity String?
|
||||||
views Int @default(0)
|
created_at DateTime @default(now())
|
||||||
invisible InvisibleUrl?
|
views Int @default(0)
|
||||||
user User @relation(fields: [userId], references: [id])
|
invisible InvisibleUrl?
|
||||||
userId Int
|
user User @relation(fields: [userId], references: [id])
|
||||||
|
userId Int
|
||||||
}
|
}
|
||||||
|
|
||||||
model InvisibleUrl {
|
model InvisibleUrl {
|
||||||
id Int
|
id Int @id @default(autoincrement())
|
||||||
url Url @relation(fields: [id], references: [id])
|
|
||||||
invis String @unique
|
invis String @unique
|
||||||
|
urlId String
|
||||||
|
url Url @relation(fields: [urlId], references: [id])
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,10 +34,11 @@ function shouldUseYarn() {
|
||||||
|
|
||||||
(async () => {
|
(async () => {
|
||||||
try {
|
try {
|
||||||
const config = await validateConfig(readConfig());
|
const a = readConfig();
|
||||||
|
const config = await validateConfig(a);
|
||||||
|
|
||||||
const data = await prismaRun(config.core.database_url, ['migrate', 'status'], true);
|
const data = await prismaRun(config.core.database_url, ['migrate', 'status'], true);
|
||||||
if (data.includes('Following migration have not yet been applied:')) {
|
if (data.includes('Following migrations have not yet been applied:')) {
|
||||||
Logger.get('database').info('some migrations are not applied, applying them now...');
|
Logger.get('database').info('some migrations are not applied, applying them now...');
|
||||||
await deployDb(config);
|
await deployDb(config);
|
||||||
Logger.get('database').info('finished applying migrations');
|
Logger.get('database').info('finished applying migrations');
|
||||||
|
@ -49,7 +50,7 @@ function shouldUseYarn() {
|
||||||
const app = next({
|
const app = next({
|
||||||
dir: '.',
|
dir: '.',
|
||||||
dev,
|
dev,
|
||||||
quiet: dev
|
quiet: dev,
|
||||||
}, config.core.port, config.core.host);
|
}, config.core.port, config.core.host);
|
||||||
|
|
||||||
await app.prepare();
|
await app.prepare();
|
||||||
|
@ -67,15 +68,15 @@ function shouldUseYarn() {
|
||||||
where: {
|
where: {
|
||||||
OR: [
|
OR: [
|
||||||
{ file: parts[2] },
|
{ file: parts[2] },
|
||||||
{ invisible:{ invis: decodeURI(parts[2]) } }
|
{ invisible:{ invis: decodeURI(parts[2]) } },
|
||||||
]
|
],
|
||||||
},
|
},
|
||||||
select: {
|
select: {
|
||||||
mimetype: true,
|
mimetype: true,
|
||||||
id: true,
|
id: true,
|
||||||
file: true,
|
file: true,
|
||||||
invisible: true
|
invisible: true,
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!image) {
|
if (!image) {
|
||||||
|
@ -92,7 +93,7 @@ function shouldUseYarn() {
|
||||||
|
|
||||||
await prisma.image.update({
|
await prisma.image.update({
|
||||||
where: { id: image.id },
|
where: { id: image.id },
|
||||||
data: { views: { increment: 1 } }
|
data: { views: { increment: 1 } },
|
||||||
});
|
});
|
||||||
res.setHeader('Content-Type', image.mimetype);
|
res.setHeader('Content-Type', image.mimetype);
|
||||||
res.end(data);
|
res.end(data);
|
||||||
|
|
|
@ -18,11 +18,16 @@ const validator = yup.object({
|
||||||
user_limit: yup.number().default(104900000),
|
user_limit: yup.number().default(104900000),
|
||||||
disabled_extensions: yup.array().default([]),
|
disabled_extensions: yup.array().default([]),
|
||||||
}).required(),
|
}).required(),
|
||||||
|
urls: yup.object({
|
||||||
|
route: yup.string().required(),
|
||||||
|
length: yup.number().default(6),
|
||||||
|
}).required(),
|
||||||
});
|
});
|
||||||
|
|
||||||
module.exports = async config => {
|
|
||||||
|
module.exports = config => {
|
||||||
try {
|
try {
|
||||||
return await validator.validate(config, { abortEarly: false });
|
return validator.validateSync(config, { abortEarly: false });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
throw `${e.errors.length} errors occured\n${e.errors.map(x => '\t' + x).join('\n')}`;
|
throw `${e.errors.length} errors occured\n${e.errors.map(x => '\t' + x).join('\n')}`;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import type { Config } from './types';
|
import type { Config } from './types';
|
||||||
import readConfig from './readConfig';
|
import readConfig from './readConfig';
|
||||||
|
import validateConfig from '../../server/validateConfig';
|
||||||
|
|
||||||
if (!global.config) global.config = readConfig() as Config;
|
if (!global.config) global.config = validateConfig(readConfig()) as unknown as Config;
|
||||||
|
|
||||||
export default global.config;
|
export default global.config;
|
|
@ -1,5 +1,5 @@
|
||||||
const { format } = require('fecha');
|
const { format } = require('fecha');
|
||||||
const { yellow, blueBright, magenta, red, cyan } = require('colorette');
|
const { blueBright, red, cyan } = require('colorette');
|
||||||
|
|
||||||
class Logger {
|
class Logger {
|
||||||
static get(clas) {
|
static get(clas) {
|
||||||
|
|
|
@ -10,12 +10,16 @@ const envValues = [
|
||||||
e('HOST', 'string', (c, v) => c.core.host = v),
|
e('HOST', 'string', (c, v) => c.core.host = v),
|
||||||
e('PORT', 'number', (c, v) => c.core.port = v),
|
e('PORT', 'number', (c, v) => c.core.port = v),
|
||||||
e('DATABASE_URL', 'string', (c, v) => c.core.database_url = v),
|
e('DATABASE_URL', 'string', (c, v) => c.core.database_url = v),
|
||||||
|
|
||||||
e('UPLOADER_ROUTE', 'string', (c, v) => c.uploader.route = v),
|
e('UPLOADER_ROUTE', 'string', (c, v) => c.uploader.route = v),
|
||||||
e('UPLOADER_LENGTH', 'number', (c, v) => c.uploader.length = v),
|
e('UPLOADER_LENGTH', 'number', (c, v) => c.uploader.length = v),
|
||||||
e('UPLOADER_DIRECTORY', 'string', (c, v) => c.uploader.directory = v),
|
e('UPLOADER_DIRECTORY', 'string', (c, v) => c.uploader.directory = v),
|
||||||
e('UPLOADER_ADMIN_LIMIT', 'number', (c, v) => c.uploader.admin_limit = v),
|
e('UPLOADER_ADMIN_LIMIT', 'number', (c, v) => c.uploader.admin_limit = v),
|
||||||
e('UPLOADER_USER_LIMIT', 'number', (c, v) => c.uploader.user_limit = v),
|
e('UPLOADER_USER_LIMIT', 'number', (c, v) => c.uploader.user_limit = v),
|
||||||
e('UPLOADER_DISABLED_EXTS', 'array', (c, v) => v ? c.uploader.disabled_extentions = v : c.uploader.disabled_extentions = []),
|
e('UPLOADER_DISABLED_EXTS', 'array', (c, v) => v ? c.uploader.disabled_extentions = v : c.uploader.disabled_extentions = []),
|
||||||
|
|
||||||
|
e('URLS_ROUTE', 'string', (c, v) => c.urls.route = v),
|
||||||
|
e('URLS_LENGTH', 'number', (c, v) => c.urls.length = v),
|
||||||
];
|
];
|
||||||
|
|
||||||
module.exports = () => {
|
module.exports = () => {
|
||||||
|
@ -46,8 +50,12 @@ function tryReadEnv() {
|
||||||
directory: undefined,
|
directory: undefined,
|
||||||
admin_limit: undefined,
|
admin_limit: undefined,
|
||||||
user_limit: undefined,
|
user_limit: undefined,
|
||||||
disabled_extentions: undefined
|
disabled_extentions: undefined,
|
||||||
}
|
},
|
||||||
|
urls: {
|
||||||
|
route: undefined,
|
||||||
|
length: undefined,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
for (let i = 0, L = envValues.length; i !== L; ++i) {
|
for (let i = 0, L = envValues.length; i !== L; ++i) {
|
||||||
|
|
|
@ -35,7 +35,16 @@ export interface ConfigUploader {
|
||||||
disabled_extentions: string[];
|
disabled_extentions: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ConfigUrls {
|
||||||
|
// The route urls will be served on
|
||||||
|
route: string;
|
||||||
|
|
||||||
|
// Length of random chars to generate for urls
|
||||||
|
length: number;
|
||||||
|
}
|
||||||
|
|
||||||
export interface Config {
|
export interface Config {
|
||||||
core: ConfigCore;
|
core: ConfigCore;
|
||||||
uploader: ConfigUploader;
|
uploader: ConfigUploader;
|
||||||
|
urls: ConfigUrls;
|
||||||
}
|
}
|
|
@ -3,7 +3,7 @@ import { hash, verify } from 'argon2';
|
||||||
import { readdir, stat } from 'fs/promises';
|
import { readdir, stat } from 'fs/promises';
|
||||||
import { join } from 'path';
|
import { join } from 'path';
|
||||||
import prisma from './prisma';
|
import prisma from './prisma';
|
||||||
import { InvisibleImage } from '@prisma/client';
|
import { InvisibleImage, InvisibleUrl } from '@prisma/client';
|
||||||
|
|
||||||
export async function hashPassword(s: string): Promise<string> {
|
export async function hashPassword(s: string): Promise<string> {
|
||||||
return await hash(s);
|
return await hash(s);
|
||||||
|
@ -89,21 +89,21 @@ export function bytesToRead(bytes: number) {
|
||||||
return `${bytes.toFixed(1)} ${units[num]}`;
|
return `${bytes.toFixed(1)} ${units[num]}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createInvisURL(length: number) {
|
export function randomInvis(length: number) {
|
||||||
// some parts from https://github.com/tycrek/ass/blob/master/generators/lengthGen.js
|
// some parts from https://github.com/tycrek/ass/blob/master/generators/lengthGen.js
|
||||||
const invisibleCharset = ['\u200B', '\u2060', '\u200C', '\u200D'];
|
const invisibleCharset = ['\u200B', '\u2060', '\u200C', '\u200D'];
|
||||||
|
|
||||||
return [...randomBytes(length)].map((byte) => invisibleCharset[Number(byte) % invisibleCharset.length]).join('').slice(1).concat(invisibleCharset[0]);
|
return [...randomBytes(length)].map((byte) => invisibleCharset[Number(byte) % invisibleCharset.length]).join('').slice(1).concat(invisibleCharset[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createInvis(length: number, imageId: number) {
|
export function createInvisImage(length: number, imageId: number) {
|
||||||
const retry = async (): Promise<InvisibleImage> => {
|
const retry = async (): Promise<InvisibleImage> => {
|
||||||
const invis = createInvisURL(length);
|
const invis = randomInvis(length);
|
||||||
|
|
||||||
const existing = await prisma.invisibleImage.findUnique({
|
const existing = await prisma.invisibleImage.findUnique({
|
||||||
where: {
|
where: {
|
||||||
invis
|
invis,
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
if (existing) return retry();
|
if (existing) return retry();
|
||||||
|
@ -111,12 +111,37 @@ export function createInvis(length: number, imageId: number) {
|
||||||
const inv = await prisma.invisibleImage.create({
|
const inv = await prisma.invisibleImage.create({
|
||||||
data: {
|
data: {
|
||||||
invis,
|
invis,
|
||||||
imageId
|
imageId,
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
return inv;
|
return inv;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
return retry();
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createInvisURL(length: number, urlId: string) {
|
||||||
|
const retry = async (): Promise<InvisibleUrl> => {
|
||||||
|
const invis = randomInvis(length);
|
||||||
|
|
||||||
|
const existing = await prisma.invisibleUrl.findUnique({
|
||||||
|
where: {
|
||||||
|
invis,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (existing) return retry();
|
||||||
|
|
||||||
|
const ur = await prisma.invisibleUrl.create({
|
||||||
|
data: {
|
||||||
|
invis,
|
||||||
|
urlId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return ur;
|
||||||
|
};
|
||||||
|
|
||||||
return retry();
|
return retry();
|
||||||
}
|
}
|
|
@ -58,62 +58,86 @@ export default function EmbeddedImage({ image, title, username, color, normal, e
|
||||||
export const getServerSideProps: GetServerSideProps = async (context) => {
|
export const getServerSideProps: GetServerSideProps = async (context) => {
|
||||||
const id = context.params.id[1];
|
const id = context.params.id[1];
|
||||||
const route = context.params.id[0];
|
const route = context.params.id[0];
|
||||||
if (route !== config.uploader.route.substring(1)) return { notFound: true };
|
const routes = [config.uploader.route.substring(1), config.urls.route.substring(1)];
|
||||||
|
if (!routes.includes(route)) return { notFound: true };
|
||||||
|
|
||||||
const image = await prisma.image.findFirst({
|
if (route === routes[1]) {
|
||||||
where: {
|
const url = await prisma.url.findFirst({
|
||||||
OR: [
|
where: {
|
||||||
{ file: id },
|
OR: [
|
||||||
{ invisible: { invis: id } }
|
{ id },
|
||||||
]
|
{ vanity: id },
|
||||||
},
|
{ invisible: { invis: id } },
|
||||||
select: {
|
],
|
||||||
mimetype: true,
|
},
|
||||||
id: true,
|
select: {
|
||||||
file: true,
|
destination: true,
|
||||||
invisible: true,
|
},
|
||||||
userId: true,
|
});
|
||||||
embed: true
|
if (!url) return { notFound: true };
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!image) return { notFound: true };
|
return {
|
||||||
|
props: {},
|
||||||
|
redirect: {
|
||||||
|
destination: url.destination,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
if (!image.embed) {
|
} else {
|
||||||
const data = await getFile(config.uploader.directory, id);
|
const image = await prisma.image.findFirst({
|
||||||
if (!data) return { notFound: true };
|
where: {
|
||||||
|
OR: [
|
||||||
|
{ file: id },
|
||||||
|
{ invisible: { invis: id } },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
select: {
|
||||||
|
mimetype: true,
|
||||||
|
id: true,
|
||||||
|
file: true,
|
||||||
|
invisible: true,
|
||||||
|
userId: true,
|
||||||
|
embed: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
if (!image) return { notFound: true };
|
||||||
|
|
||||||
context.res.end(data);
|
if (!image.embed) {
|
||||||
return { props: {} };
|
const data = await getFile(config.uploader.directory, id);
|
||||||
};
|
if (!data) return { notFound: true };
|
||||||
|
|
||||||
const user = await prisma.user.findFirst({
|
context.res.end(data);
|
||||||
select: {
|
return { props: {} };
|
||||||
embedTitle: true,
|
};
|
||||||
embedColor: true,
|
|
||||||
username: true
|
const user = await prisma.user.findFirst({
|
||||||
},
|
select: {
|
||||||
where: {
|
embedTitle: true,
|
||||||
id: image.userId
|
embedColor: true,
|
||||||
}
|
username: true,
|
||||||
});
|
},
|
||||||
|
where: {
|
||||||
|
id: image.userId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
if (!image.mimetype.startsWith('image')) {
|
if (!image.mimetype.startsWith('image')) {
|
||||||
const data = await getFile(config.uploader.directory, id);
|
const data = await getFile(config.uploader.directory, id);
|
||||||
if (!data) return { notFound: true };
|
if (!data) return { notFound: true };
|
||||||
|
|
||||||
context.res.end(data);
|
context.res.end(data);
|
||||||
return { props: {} };
|
return { props: {} };
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
props: {
|
props: {
|
||||||
image,
|
image,
|
||||||
title: user.embedTitle,
|
title: user.embedTitle,
|
||||||
color: user.embedColor,
|
color: user.embedColor,
|
||||||
username: user.username,
|
username: user.username,
|
||||||
normal: config.uploader.route,
|
normal: config.uploader.route,
|
||||||
embed: image.embed
|
embed: image.embed,
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
39
src/pages/api/shorten.ts
Normal file
39
src/pages/api/shorten.ts
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
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';
|
||||||
|
|
||||||
|
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,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!user) return res.forbid('authorization incorect');
|
||||||
|
if (!req.body) return res.error('no body');
|
||||||
|
if (!req.body.url) return res.error('no url');
|
||||||
|
const rand = randomChars(zconfig.urls.length);
|
||||||
|
|
||||||
|
let invis;
|
||||||
|
const url = await prisma.url.create({
|
||||||
|
data: {
|
||||||
|
id: rand,
|
||||||
|
vanity: req.body.vanity ?? null,
|
||||||
|
destination: req.body.url,
|
||||||
|
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})`);
|
||||||
|
|
||||||
|
return res.json({ url: `${zconfig.core.secure ? 'https' : 'http'}://${req.headers.host}${zconfig.urls.route}/${req.body.vanity ? req.body.vanity : invis ? invis.invis : url.id}` });
|
||||||
|
}
|
||||||
|
|
||||||
|
export default withZipline(handler);
|
|
@ -2,7 +2,7 @@ import multer from 'multer';
|
||||||
import prisma from 'lib/prisma';
|
import prisma from 'lib/prisma';
|
||||||
import zconfig from 'lib/config';
|
import zconfig from 'lib/config';
|
||||||
import { NextApiReq, NextApiRes, withZipline } from 'lib/middleware/withZipline';
|
import { NextApiReq, NextApiRes, withZipline } from 'lib/middleware/withZipline';
|
||||||
import { createInvis, randomChars } from 'lib/util';
|
import { createInvisImage, randomChars } from 'lib/util';
|
||||||
import { writeFile } from 'fs/promises';
|
import { writeFile } from 'fs/promises';
|
||||||
import { join } from 'path';
|
import { join } from 'path';
|
||||||
import Logger from 'lib/logger';
|
import Logger from 'lib/logger';
|
||||||
|
@ -46,7 +46,7 @@ async function handler(req: NextApiReq, res: NextApiRes) {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (req.headers.zws) invis = await createInvis(zconfig.uploader.length, image.id);
|
if (req.headers.zws) invis = await createInvisImage(zconfig.uploader.length, image.id);
|
||||||
|
|
||||||
await writeFile(join(process.cwd(), zconfig.uploader.directory, image.file), file.buffer);
|
await writeFile(join(process.cwd(), zconfig.uploader.directory, image.file), file.buffer);
|
||||||
Logger.get('image').info(`User ${user.username} (${user.id}) uploaded an image ${image.file} (${image.id})`);
|
Logger.get('image').info(`User ${user.username} (${user.id}) uploaded an image ${image.file} (${image.id})`);
|
||||||
|
|
|
@ -24,8 +24,8 @@ export default function UploadPage({ route }) {
|
||||||
export const getStaticProps: GetStaticProps = async (context) => {
|
export const getStaticProps: GetStaticProps = async (context) => {
|
||||||
return {
|
return {
|
||||||
props: {
|
props: {
|
||||||
route: config.uploader.route
|
route: config.uploader.route,
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
36
yarn.lock
36
yarn.lock
|
@ -602,22 +602,22 @@
|
||||||
resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.9.2.tgz#adea7b6953cbb34651766b0548468e743c6a2353"
|
resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.9.2.tgz#adea7b6953cbb34651766b0548468e743c6a2353"
|
||||||
integrity sha512-VZMYa7+fXHdwIq1TDhSXoVmSPEGM/aa+6Aiq3nVVJ9bXr24zScr+NlKFKC3iPljA7ho/GAZr+d2jOf5GIRC30Q==
|
integrity sha512-VZMYa7+fXHdwIq1TDhSXoVmSPEGM/aa+6Aiq3nVVJ9bXr24zScr+NlKFKC3iPljA7ho/GAZr+d2jOf5GIRC30Q==
|
||||||
|
|
||||||
"@prisma/client@^3.0.2":
|
"@prisma/client@^3.1.1":
|
||||||
version "3.0.2"
|
version "3.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/@prisma/client/-/client-3.0.2.tgz#f04d9b252f3d0c6918df43ad228eac27d03f6db1"
|
resolved "https://registry.yarnpkg.com/@prisma/client/-/client-3.1.1.tgz#f4012631528049c22d12b212846dcf503db33cfe"
|
||||||
integrity sha512-6SrDYY2Yr5AmYpVB3XAXFqfzxKMdDTemXR7FmfXthnxWhQHoBwRLNZ3B3GyI/MmWa5tr+kaaGDJjp1LU0vuYvQ==
|
integrity sha512-8ud8vVFMIg37yrkZ4wPpjKoMxFbCL0Pesq5eyLnag/s0LTKsVEN7ZBIQq9JzWW+AUqOzGKXr2Jt4Sl8xdGI99w==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@prisma/engines-version" "2.31.0-32.2452cc6313d52b8b9a96999ac0e974d0aedf88db"
|
"@prisma/engines-version" "3.1.0-24.c22652b7e418506fab23052d569b85d3aec4883f"
|
||||||
|
|
||||||
"@prisma/engines-version@2.31.0-32.2452cc6313d52b8b9a96999ac0e974d0aedf88db":
|
"@prisma/engines-version@3.1.0-24.c22652b7e418506fab23052d569b85d3aec4883f":
|
||||||
version "2.31.0-32.2452cc6313d52b8b9a96999ac0e974d0aedf88db"
|
version "3.1.0-24.c22652b7e418506fab23052d569b85d3aec4883f"
|
||||||
resolved "https://registry.yarnpkg.com/@prisma/engines-version/-/engines-version-2.31.0-32.2452cc6313d52b8b9a96999ac0e974d0aedf88db.tgz#c45323e420f47dd950b22c873bdcf38f75e65779"
|
resolved "https://registry.yarnpkg.com/@prisma/engines-version/-/engines-version-3.1.0-24.c22652b7e418506fab23052d569b85d3aec4883f.tgz#f9908eb7808f2a546634398063942eaecb2474ef"
|
||||||
integrity sha512-iArSApZZImVmT9oC/rGOjzvpG2AOqlIeqYcVnop9poA3FxD4zfVPbNPH9DTgOWhc06OkBHujJZeAcsNddVabIQ==
|
integrity sha512-EuEMKLuwIcBO7uInZQHeG1yaywcfl32Tq8TDf5tgLvblk+ka70sej7S67lh3BV5gXMLTc3GdthSHPfDqZEK5uA==
|
||||||
|
|
||||||
"@prisma/engines@2.31.0-32.2452cc6313d52b8b9a96999ac0e974d0aedf88db":
|
"@prisma/engines@3.1.0-24.c22652b7e418506fab23052d569b85d3aec4883f":
|
||||||
version "2.31.0-32.2452cc6313d52b8b9a96999ac0e974d0aedf88db"
|
version "3.1.0-24.c22652b7e418506fab23052d569b85d3aec4883f"
|
||||||
resolved "https://registry.yarnpkg.com/@prisma/engines/-/engines-2.31.0-32.2452cc6313d52b8b9a96999ac0e974d0aedf88db.tgz#b6cf70bc05dd2a62168a16f3ea58a1b011074621"
|
resolved "https://registry.yarnpkg.com/@prisma/engines/-/engines-3.1.0-24.c22652b7e418506fab23052d569b85d3aec4883f.tgz#7b45708e6a42523dc9bc2214e5c62781f608dc3a"
|
||||||
integrity sha512-Q9CwN6e5E5Abso7J3A1fHbcF4NXGRINyMnf7WQ07fXaebxTTARY5BNUzy2Mo5uH82eRVO5v7ImNuR044KTjLJg==
|
integrity sha512-6NEp0VlLho3hVtIvj2P4h0e19AYqQSXtFGts8gSIXDnV+l5pRFZaDMfGo2RiLMR0Kfrs8c3ZYxYX0sWmVL0tWw==
|
||||||
|
|
||||||
"@reduxjs/toolkit@^1.6.0":
|
"@reduxjs/toolkit@^1.6.0":
|
||||||
version "1.6.0"
|
version "1.6.0"
|
||||||
|
@ -4434,12 +4434,12 @@ prepend-http@^1.0.1:
|
||||||
resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-1.0.4.tgz#d4f4562b0ce3696e41ac52d0e002e57a635dc6dc"
|
resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-1.0.4.tgz#d4f4562b0ce3696e41ac52d0e002e57a635dc6dc"
|
||||||
integrity sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw=
|
integrity sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw=
|
||||||
|
|
||||||
prisma@^3.0.2:
|
prisma@^3.1.1:
|
||||||
version "3.0.2"
|
version "3.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/prisma/-/prisma-3.0.2.tgz#e86cb6abf4a815c7ac97b9d0ed383f01c253ce34"
|
resolved "https://registry.yarnpkg.com/prisma/-/prisma-3.1.1.tgz#4c13c35dd3a58af9134008c8ed0fdc21a632802c"
|
||||||
integrity sha512-TyOCbtWGDVdWvsM1RhUzJXoGClXGalHhyYWIc5eizSF8T1ScGiOa34asBUdTnXOUBFSErbsqMNw40DHAteBm1A==
|
integrity sha512-+eZtWIL6hnOKUOvqq9WLBzSw2d/EbTmOx1Td1LI8/0XE40ctXMLG2N1p6NK5/+yivGaoNJ9PDpPsPL9lO4nJrQ==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@prisma/engines" "2.31.0-32.2452cc6313d52b8b9a96999ac0e974d0aedf88db"
|
"@prisma/engines" "3.1.0-24.c22652b7e418506fab23052d569b85d3aec4883f"
|
||||||
|
|
||||||
process-nextick-args@~2.0.0:
|
process-nextick-args@~2.0.0:
|
||||||
version "2.0.1"
|
version "2.0.1"
|
||||||
|
|
Loading…
Reference in a new issue