mirror of
https://github.com/diced/zipline.git
synced 2025-12-05 20:40:12 -08:00
feat: init
This commit is contained in:
3
.env
Normal file
3
.env
Normal file
@@ -0,0 +1,3 @@
|
||||
SESSION_SECRET="mysupersecret"
|
||||
DATABASE_URL="postgresql://postgres:postgres@localhost:5432/zipline4-1?schema=public"
|
||||
DEBUG=zipline
|
||||
49
.eslintrc.js
Normal file
49
.eslintrc.js
Normal file
@@ -0,0 +1,49 @@
|
||||
/** @type {import('eslint').Linter.Config} */
|
||||
module.exports = {
|
||||
extends: [
|
||||
'@remix-run/eslint-config',
|
||||
'@remix-run/eslint-config/node',
|
||||
'plugin:prettier/recommended',
|
||||
'plugin:@typescript-eslint/recommended',
|
||||
],
|
||||
root: true,
|
||||
plugins: ['unused-imports', '@typescript-eslint'],
|
||||
parser: '@typescript-eslint/parser',
|
||||
rules: {
|
||||
'linebreak-style': ['error', 'unix'],
|
||||
quotes: [
|
||||
'error',
|
||||
'single',
|
||||
{
|
||||
avoidEscape: true,
|
||||
},
|
||||
],
|
||||
semi: ['error', 'always'],
|
||||
'comma-dangle': ['error', 'always-multiline'],
|
||||
'jsx-quotes': ['error', 'prefer-single'],
|
||||
indent: 'off',
|
||||
'react/prop-types': 'off',
|
||||
'react-hooks/rules-of-hooks': 'off',
|
||||
'react-hooks/exhaustive-deps': 'off',
|
||||
'react/jsx-uses-react': 'warn',
|
||||
'react/jsx-uses-vars': 'warn',
|
||||
'react/no-danger-with-children': 'warn',
|
||||
'react/no-deprecated': 'warn',
|
||||
'react/no-direct-mutation-state': 'warn',
|
||||
'react/no-is-mounted': 'warn',
|
||||
'react/no-typos': 'error',
|
||||
'react/react-in-jsx-scope': 'off',
|
||||
'react/require-render-return': 'error',
|
||||
'react/style-prop-object': 'warn',
|
||||
'jsx-a11y/alt-text': 'off',
|
||||
'react/display-name': 'off',
|
||||
'no-unused-vars': 'off',
|
||||
'@typescript-eslint/no-unused-vars': 'off',
|
||||
'unused-imports/no-unused-imports': 'error',
|
||||
'unused-imports/no-unused-vars': [
|
||||
'error',
|
||||
{ vars: 'all', varsIgnorePattern: '^_', args: 'after-used', argsIgnorePattern: '^_' },
|
||||
],
|
||||
'@typescript-eslint/ban-ts-comment': 'off',
|
||||
},
|
||||
};
|
||||
33
.gitignore
vendored
Normal file
33
.gitignore
vendored
Normal file
@@ -0,0 +1,33 @@
|
||||
node_modules
|
||||
|
||||
/.cache
|
||||
/build
|
||||
/public/build
|
||||
|
||||
# yarn
|
||||
.yarn/*
|
||||
!.yarn/releases
|
||||
!.yarn/plugins
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
*.pem
|
||||
.idea
|
||||
|
||||
# debug
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
# local env files
|
||||
.env.local
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
|
||||
# vercel
|
||||
.vercel
|
||||
|
||||
# zipline
|
||||
uploads*/
|
||||
dist/
|
||||
28
.yarn/plugins/@yarnpkg/plugin-workspace-tools.cjs
vendored
Normal file
28
.yarn/plugins/@yarnpkg/plugin-workspace-tools.cjs
vendored
Normal file
File diff suppressed because one or more lines are too long
874
.yarn/releases/yarn-3.6.0.cjs
vendored
Executable file
874
.yarn/releases/yarn-3.6.0.cjs
vendored
Executable file
File diff suppressed because one or more lines are too long
9
.yarnrc.yml
Normal file
9
.yarnrc.yml
Normal file
@@ -0,0 +1,9 @@
|
||||
checksumBehavior: update
|
||||
|
||||
nodeLinker: node-modules
|
||||
|
||||
plugins:
|
||||
- path: .yarn/plugins/@yarnpkg/plugin-workspace-tools.cjs
|
||||
spec: "@yarnpkg/plugin-workspace-tools"
|
||||
|
||||
yarnPath: .yarn/releases/yarn-3.6.0.cjs
|
||||
21
LICENSE
Normal file
21
LICENSE
Normal file
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2023 dicedtomato
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
5
README.md
Normal file
5
README.md
Normal file
@@ -0,0 +1,5 @@
|
||||
# Zipline 4
|
||||
|
||||
! This is a work in progress, the database is not final and is subject to change without a migration. !
|
||||
|
||||
Roadmap for v4: https://diced.notion.site/Zipline-v4-Roadmap-058aceb8a35140e7af4c726560aa3db1?pvs=4
|
||||
71
package.json
Normal file
71
package.json
Normal file
@@ -0,0 +1,71 @@
|
||||
{
|
||||
"name": "zipline",
|
||||
"private": true,
|
||||
"license": "MIT",
|
||||
"sideEffects": false,
|
||||
"version": "4.0.0-dev.1",
|
||||
"scripts": {
|
||||
"build": "run-s build:*",
|
||||
"build:remix": "remix build",
|
||||
"build:server": "tsup",
|
||||
"dev": "run-p dev:remix & (run-s dev:build && run-s dev:server)",
|
||||
"dev:build": "cross-env NODE_ENV=development run-s build:server",
|
||||
"dev:remix": "cross-env NODE_ENV=development remix watch",
|
||||
"dev:server": "cross-env NODE_ENV=development node --require ./node_modules/dotenv/config ./build/server.js",
|
||||
"start": "node ./server.mjs",
|
||||
"lint": "eslint --cache --ignore-path .gitignore --fix .",
|
||||
"format": "prettier --write --ignore-path .gitignore .",
|
||||
"validate": "run-p lint format"
|
||||
},
|
||||
"dependencies": {
|
||||
"@emotion/react": "^11.11.1",
|
||||
"@emotion/server": "^11.11.0",
|
||||
"@mantine/core": "^6.0.14",
|
||||
"@mantine/dates": "^6.0.14",
|
||||
"@mantine/dropzone": "^6.0.14",
|
||||
"@mantine/form": "^6.0.14",
|
||||
"@mantine/hooks": "^6.0.14",
|
||||
"@mantine/modals": "^6.0.14",
|
||||
"@mantine/notifications": "^6.0.14",
|
||||
"@mantine/prism": "^6.0.14",
|
||||
"@mantine/remix": "^6.0.14",
|
||||
"@prisma/client": "4.16.1",
|
||||
"@prisma/internals": "^4.16.1",
|
||||
"@prisma/migrate": "^4.16.1",
|
||||
"@remix-run/express": "^1.17.1",
|
||||
"@remix-run/node": "^1.16.1",
|
||||
"@remix-run/react": "^1.16.1",
|
||||
"@remix-run/v1-route-convention": "^0.1.2",
|
||||
"@types/express": "^4.17.17",
|
||||
"bytes": "^3.1.2",
|
||||
"colorette": "^2.0.20",
|
||||
"dayjs": "^1.11.8",
|
||||
"express": "^4.18.2",
|
||||
"isbot": "^3.6.10",
|
||||
"ms": "^2.1.3",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"znv": "^0.3.2",
|
||||
"zod": "^3.21.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@remix-run/dev": "^1.16.1",
|
||||
"@remix-run/eslint-config": "^1.16.1",
|
||||
"@types/bytes": "^3.1.1",
|
||||
"@types/node": "^20.3.1",
|
||||
"@types/react": "^18.2.7",
|
||||
"@types/react-dom": "^18.2.4",
|
||||
"@types/signale": "^1.4.4",
|
||||
"cross-env": "^7.0.3",
|
||||
"dotenv": "^16.1.3",
|
||||
"eslint": "^8.41.0",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"prisma": "^4.16.1",
|
||||
"tsup": "^7.0.0",
|
||||
"typescript": "^5.1.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"packageManager": "yarn@3.6.0"
|
||||
}
|
||||
6
prettier.config.cjs
Normal file
6
prettier.config.cjs
Normal file
@@ -0,0 +1,6 @@
|
||||
/** @type {import('prettier').Config} */
|
||||
module.exports = {
|
||||
singleQuote: true,
|
||||
jsxSingleQuote: true,
|
||||
printWidth: 110,
|
||||
};
|
||||
234
prisma/migrations/20230624064156_init/migration.sql
Normal file
234
prisma/migrations/20230624064156_init/migration.sql
Normal file
@@ -0,0 +1,234 @@
|
||||
-- CreateEnum
|
||||
CREATE TYPE "OAuthProviderType" AS ENUM ('DISCORD', 'GOOGLE', 'GITHUB');
|
||||
|
||||
-- CreateEnum
|
||||
CREATE TYPE "LimitType" AS ENUM ('UPLOAD_COUNT', 'UPLOAD_SIZE', 'SHORTEN_COUNT');
|
||||
|
||||
-- CreateEnum
|
||||
CREATE TYPE "LimitTimeframe" AS ENUM ('SECONDLY', 'MINUTELY', 'HOURLY', 'DAILY', 'WEEKLY', 'MONTHLY', 'YEARLY');
|
||||
|
||||
-- CreateEnum
|
||||
CREATE TYPE "IncompleteFileStatus" AS ENUM ('PENDING', 'PROCESSING', 'COMPLETE', 'FAILED');
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "zipline_meta" (
|
||||
"id" TEXT NOT NULL,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
"firstSetup" BOOLEAN NOT NULL DEFAULT true,
|
||||
|
||||
CONSTRAINT "zipline_meta_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "User" (
|
||||
"id" TEXT NOT NULL,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
"username" TEXT NOT NULL,
|
||||
"password" TEXT,
|
||||
"avatar" TEXT,
|
||||
"token" TEXT NOT NULL,
|
||||
"administrator" BOOLEAN NOT NULL DEFAULT false,
|
||||
"ziplineId" TEXT NOT NULL,
|
||||
|
||||
CONSTRAINT "User_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "OAuthProvider" (
|
||||
"id" TEXT NOT NULL,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
"userId" TEXT NOT NULL,
|
||||
"provider" "OAuthProviderType" NOT NULL,
|
||||
"accessToken" TEXT NOT NULL,
|
||||
"refreshToken" TEXT NOT NULL,
|
||||
"expiresIn" INTEGER NOT NULL,
|
||||
"scope" TEXT NOT NULL,
|
||||
"tokenType" TEXT NOT NULL,
|
||||
"profile" JSONB NOT NULL,
|
||||
|
||||
CONSTRAINT "OAuthProvider_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "UserLimit" (
|
||||
"id" TEXT NOT NULL,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
"type" "LimitType" NOT NULL,
|
||||
"value" INTEGER NOT NULL,
|
||||
"timeframe" "LimitTimeframe" NOT NULL,
|
||||
"userId" TEXT NOT NULL,
|
||||
|
||||
CONSTRAINT "UserLimit_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "File" (
|
||||
"id" TEXT NOT NULL,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
"deletesAt" TIMESTAMP(3),
|
||||
"name" TEXT NOT NULL,
|
||||
"originalName" TEXT NOT NULL,
|
||||
"path" TEXT NOT NULL,
|
||||
"size" INTEGER NOT NULL,
|
||||
"type" TEXT NOT NULL,
|
||||
"views" INTEGER NOT NULL DEFAULT 0,
|
||||
"favorite" BOOLEAN NOT NULL DEFAULT false,
|
||||
"password" TEXT,
|
||||
"zeroWidthSpace" TEXT,
|
||||
"userId" TEXT,
|
||||
"folderId" TEXT,
|
||||
|
||||
CONSTRAINT "File_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "Folder" (
|
||||
"id" TEXT NOT NULL,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
"name" TEXT NOT NULL,
|
||||
"userId" TEXT NOT NULL,
|
||||
|
||||
CONSTRAINT "Folder_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "IncompleteFile" (
|
||||
"id" TEXT NOT NULL,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
"status" "IncompleteFileStatus" NOT NULL,
|
||||
"chunksTotal" INTEGER NOT NULL,
|
||||
"chunksComplete" INTEGER NOT NULL,
|
||||
"metadata" JSONB NOT NULL,
|
||||
"userId" TEXT NOT NULL,
|
||||
|
||||
CONSTRAINT "IncompleteFile_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "Tag" (
|
||||
"id" TEXT NOT NULL,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
"name" TEXT NOT NULL,
|
||||
"color" TEXT NOT NULL,
|
||||
|
||||
CONSTRAINT "Tag_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "Url" (
|
||||
"id" TEXT NOT NULL,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
"vanity" TEXT,
|
||||
"destination" TEXT NOT NULL,
|
||||
"name" TEXT NOT NULL,
|
||||
"zeroWidthSpace" TEXT,
|
||||
"userId" TEXT,
|
||||
|
||||
CONSTRAINT "Url_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "Metric" (
|
||||
"id" TEXT NOT NULL,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
"data" JSONB NOT NULL,
|
||||
"ziplineId" TEXT NOT NULL,
|
||||
|
||||
CONSTRAINT "Metric_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "Invite" (
|
||||
"id" TEXT NOT NULL,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||
"expiresAt" TIMESTAMP(3),
|
||||
"code" TEXT NOT NULL,
|
||||
"used" BOOLEAN NOT NULL DEFAULT false,
|
||||
"inviterId" TEXT NOT NULL,
|
||||
"ziplineId" TEXT NOT NULL,
|
||||
|
||||
CONSTRAINT "Invite_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "_FileToTag" (
|
||||
"A" TEXT NOT NULL,
|
||||
"B" TEXT NOT NULL
|
||||
);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "User_username_key" ON "User"("username");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "User_token_key" ON "User"("token");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "OAuthProvider_userId_provider_key" ON "OAuthProvider"("userId", "provider");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "UserLimit_type_key" ON "UserLimit"("type");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "Tag_name_key" ON "Tag"("name");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "Url_name_key" ON "Url"("name");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "Invite_code_key" ON "Invite"("code");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "_FileToTag_AB_unique" ON "_FileToTag"("A", "B");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "_FileToTag_B_index" ON "_FileToTag"("B");
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "User" ADD CONSTRAINT "User_ziplineId_fkey" FOREIGN KEY ("ziplineId") REFERENCES "zipline_meta"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "OAuthProvider" ADD CONSTRAINT "OAuthProvider_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "UserLimit" ADD CONSTRAINT "UserLimit_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "File" ADD CONSTRAINT "File_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "File" ADD CONSTRAINT "File_folderId_fkey" FOREIGN KEY ("folderId") REFERENCES "Folder"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Folder" ADD CONSTRAINT "Folder_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "IncompleteFile" ADD CONSTRAINT "IncompleteFile_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Url" ADD CONSTRAINT "Url_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Metric" ADD CONSTRAINT "Metric_ziplineId_fkey" FOREIGN KEY ("ziplineId") REFERENCES "zipline_meta"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Invite" ADD CONSTRAINT "Invite_inviterId_fkey" FOREIGN KEY ("inviterId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "Invite" ADD CONSTRAINT "Invite_ziplineId_fkey" FOREIGN KEY ("ziplineId") REFERENCES "zipline_meta"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "_FileToTag" ADD CONSTRAINT "_FileToTag_A_fkey" FOREIGN KEY ("A") REFERENCES "File"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "_FileToTag" ADD CONSTRAINT "_FileToTag_B_fkey" FOREIGN KEY ("B") REFERENCES "Tag"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
3
prisma/migrations/migration_lock.toml
Normal file
3
prisma/migrations/migration_lock.toml
Normal file
@@ -0,0 +1,3 @@
|
||||
# Please do not edit this file manually
|
||||
# It should be added in your version-control system (i.e. Git)
|
||||
provider = "postgresql"
|
||||
213
prisma/schema.prisma
Normal file
213
prisma/schema.prisma
Normal file
@@ -0,0 +1,213 @@
|
||||
generator client {
|
||||
provider = "prisma-client-js"
|
||||
}
|
||||
|
||||
datasource db {
|
||||
provider = "postgresql"
|
||||
url = env("DATABASE_URL")
|
||||
}
|
||||
|
||||
model Zipline {
|
||||
id String @id @default(cuid())
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
firstSetup Boolean @default(true)
|
||||
|
||||
metrics Metric[]
|
||||
users User[]
|
||||
invite Invite[]
|
||||
|
||||
@@map("zipline_meta")
|
||||
}
|
||||
|
||||
model User {
|
||||
id String @id @default(cuid())
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
username String @unique
|
||||
password String?
|
||||
avatar String?
|
||||
token String @unique
|
||||
administrator Boolean @default(false)
|
||||
|
||||
files File[]
|
||||
urls Url[]
|
||||
folders Folder[]
|
||||
limits UserLimit[]
|
||||
invites Invite[]
|
||||
oauthProviders OAuthProvider[]
|
||||
IncompleteFile IncompleteFile[]
|
||||
|
||||
Zipline Zipline @relation(fields: [ziplineId], references: [id], onDelete: Cascade, onUpdate: Cascade)
|
||||
ziplineId String
|
||||
}
|
||||
|
||||
model OAuthProvider {
|
||||
id String @id @default(cuid())
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
userId String
|
||||
provider OAuthProviderType
|
||||
accessToken String
|
||||
refreshToken String
|
||||
expiresIn Int
|
||||
scope String
|
||||
tokenType String
|
||||
profile Json
|
||||
|
||||
user User @relation(fields: [userId], references: [id])
|
||||
|
||||
@@unique([userId, provider])
|
||||
}
|
||||
|
||||
enum OAuthProviderType {
|
||||
DISCORD
|
||||
GOOGLE
|
||||
GITHUB
|
||||
}
|
||||
|
||||
model UserLimit {
|
||||
id String @id @default(cuid())
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
type LimitType @unique
|
||||
value Int
|
||||
timeframe LimitTimeframe
|
||||
|
||||
User User @relation(fields: [userId], references: [id], onDelete: Cascade, onUpdate: Cascade)
|
||||
userId String
|
||||
}
|
||||
|
||||
enum LimitType {
|
||||
UPLOAD_COUNT
|
||||
UPLOAD_SIZE
|
||||
SHORTEN_COUNT
|
||||
}
|
||||
|
||||
enum LimitTimeframe {
|
||||
SECONDLY
|
||||
MINUTELY
|
||||
HOURLY
|
||||
DAILY
|
||||
WEEKLY
|
||||
MONTHLY
|
||||
YEARLY
|
||||
}
|
||||
|
||||
model File {
|
||||
id String @id @default(cuid())
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
deletesAt DateTime?
|
||||
|
||||
name String // name shown on dashboard
|
||||
originalName String // original name of file when uploaded
|
||||
path String // path it's stored on the server
|
||||
size Int
|
||||
type String
|
||||
views Int @default(0)
|
||||
favorite Boolean @default(false)
|
||||
password String?
|
||||
|
||||
zeroWidthSpace String?
|
||||
|
||||
tags Tag[]
|
||||
|
||||
User User? @relation(fields: [userId], references: [id], onDelete: SetNull, onUpdate: Cascade)
|
||||
userId String?
|
||||
|
||||
Folder Folder? @relation(fields: [folderId], references: [id], onDelete: SetNull, onUpdate: Cascade)
|
||||
folderId String?
|
||||
}
|
||||
|
||||
model Folder {
|
||||
id String @id @default(cuid())
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
name String
|
||||
|
||||
files File[]
|
||||
|
||||
User User @relation(fields: [userId], references: [id], onDelete: Cascade, onUpdate: Cascade)
|
||||
userId String
|
||||
}
|
||||
|
||||
model IncompleteFile {
|
||||
id String @id @default(cuid())
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
status IncompleteFileStatus
|
||||
chunksTotal Int
|
||||
chunksComplete Int
|
||||
|
||||
metadata Json
|
||||
|
||||
User User @relation(fields: [userId], references: [id], onDelete: Cascade, onUpdate: Cascade)
|
||||
userId String
|
||||
}
|
||||
|
||||
enum IncompleteFileStatus {
|
||||
PENDING
|
||||
PROCESSING
|
||||
COMPLETE
|
||||
FAILED
|
||||
}
|
||||
|
||||
model Tag {
|
||||
id String @id @default(cuid())
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
name String @unique
|
||||
color String
|
||||
|
||||
files File[]
|
||||
}
|
||||
|
||||
model Url {
|
||||
id String @id @default(cuid())
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
vanity String?
|
||||
destination String
|
||||
name String @unique
|
||||
|
||||
zeroWidthSpace String?
|
||||
|
||||
User User? @relation(fields: [userId], references: [id], onDelete: SetNull, onUpdate: Cascade)
|
||||
userId String?
|
||||
}
|
||||
|
||||
model Metric {
|
||||
id String @id @default(cuid())
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
data Json
|
||||
|
||||
Zipline Zipline @relation(fields: [ziplineId], references: [id], onDelete: Cascade, onUpdate: Cascade)
|
||||
ziplineId String
|
||||
}
|
||||
|
||||
model Invite {
|
||||
id String @id @default(cuid())
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
expiresAt DateTime?
|
||||
|
||||
code String @unique
|
||||
used Boolean @default(false)
|
||||
|
||||
inviter User @relation(fields: [inviterId], references: [id], onDelete: Cascade, onUpdate: Cascade)
|
||||
inviterId String
|
||||
|
||||
Zipline Zipline @relation(fields: [ziplineId], references: [id], onDelete: Cascade, onUpdate: Cascade)
|
||||
ziplineId String
|
||||
}
|
||||
BIN
public/favicon.png
Normal file
BIN
public/favicon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.9 KiB |
27
remix.config.js
Normal file
27
remix.config.js
Normal file
@@ -0,0 +1,27 @@
|
||||
const { createRoutesFromFolders } = require('@remix-run/v1-route-convention');
|
||||
|
||||
/**
|
||||
* @type {import('@remix-run/dev').AppConfig}
|
||||
*/
|
||||
module.exports = {
|
||||
ignoredRouteFiles: ['**/.*'],
|
||||
appDirectory: 'src/app',
|
||||
// assetsBuildDirectory: 'public/build',
|
||||
// serverBuildPath: 'build/index.js',
|
||||
serverModuleFormat: 'cjs',
|
||||
future: {
|
||||
unstable_dev: true,
|
||||
v2_routeConvention: true,
|
||||
v2_errorBoundary: true,
|
||||
v2_meta: true,
|
||||
v2_normalizeFormMethod: true,
|
||||
v2_headers: true,
|
||||
},
|
||||
publicPath: '/modules/',
|
||||
// use directory structure.
|
||||
routes(defineRoutes) {
|
||||
return createRoutesFromFolders(defineRoutes, {
|
||||
appDirectory: 'src/app',
|
||||
});
|
||||
},
|
||||
};
|
||||
2
remix.env.d.ts
vendored
Normal file
2
remix.env.d.ts
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
/// <reference types="@remix-run/dev" />
|
||||
/// <reference types="@remix-run/node/globals" />
|
||||
30
src/app/db.server.ts
Normal file
30
src/app/db.server.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
import { PrismaClient } from '@prisma/client';
|
||||
import { log } from 'src/lib/logger';
|
||||
|
||||
let prisma: PrismaClient;
|
||||
|
||||
declare global {
|
||||
var __db__: PrismaClient;
|
||||
}
|
||||
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
prisma = getClient();
|
||||
} else {
|
||||
if (!global.__db__) {
|
||||
global.__db__ = getClient();
|
||||
}
|
||||
prisma = global.__db__;
|
||||
}
|
||||
|
||||
function getClient() {
|
||||
const logger = log('db');
|
||||
|
||||
logger.info('connecting to database', process.env.DATABASE_URL);
|
||||
|
||||
const client = new PrismaClient();
|
||||
client.$connect();
|
||||
|
||||
return client;
|
||||
}
|
||||
|
||||
export { prisma };
|
||||
11
src/app/entry.client.tsx
Normal file
11
src/app/entry.client.tsx
Normal file
@@ -0,0 +1,11 @@
|
||||
import { RemixBrowser } from '@remix-run/react';
|
||||
import { hydrate } from 'react-dom';
|
||||
import { ClientProvider } from '@mantine/remix';
|
||||
|
||||
hydrate(
|
||||
<ClientProvider>
|
||||
<RemixBrowser />
|
||||
</ClientProvider>,
|
||||
document
|
||||
);
|
||||
|
||||
21
src/app/entry.server.tsx
Normal file
21
src/app/entry.server.tsx
Normal file
@@ -0,0 +1,21 @@
|
||||
import { renderToString } from 'react-dom/server';
|
||||
import { RemixServer } from '@remix-run/react';
|
||||
import type { EntryContext } from '@remix-run/node';
|
||||
import { injectStyles, createStylesServer } from '@mantine/remix';
|
||||
|
||||
const server = createStylesServer();
|
||||
|
||||
export default function handleRequest(
|
||||
request: Request,
|
||||
responseStatusCode: number,
|
||||
responseHeaders: Headers,
|
||||
remixContext: EntryContext
|
||||
) {
|
||||
let markup = renderToString(<RemixServer context={remixContext} url={request.url} />);
|
||||
responseHeaders.set('Content-Type', 'text/html');
|
||||
|
||||
return new Response(`<!DOCTYPE html>${injectStyles(markup, server)}`, {
|
||||
status: responseStatusCode,
|
||||
headers: responseHeaders,
|
||||
});
|
||||
}
|
||||
32
src/app/root.tsx
Normal file
32
src/app/root.tsx
Normal file
@@ -0,0 +1,32 @@
|
||||
import type { V2_MetaFunction } from '@remix-run/node';
|
||||
import { Links, LiveReload, Meta, Outlet, Scripts, ScrollRestoration } from '@remix-run/react';
|
||||
import { MantineProvider, createEmotionCache } from '@mantine/core';
|
||||
import { StylesPlaceholder } from '@mantine/remix';
|
||||
|
||||
export const meta: V2_MetaFunction = () => [
|
||||
{ charSet: 'utf-8' },
|
||||
{ title: 'Zipline' },
|
||||
{ name: 'viewport', content: 'width=device-width,initial-scale=1' },
|
||||
];
|
||||
|
||||
createEmotionCache({ key: 'mantine' });
|
||||
|
||||
export default function App() {
|
||||
return (
|
||||
<MantineProvider withGlobalStyles withNormalizeCSS>
|
||||
<html lang='en'>
|
||||
<head>
|
||||
<StylesPlaceholder />
|
||||
<Meta />
|
||||
<Links />
|
||||
</head>
|
||||
<body>
|
||||
<Outlet />
|
||||
<ScrollRestoration />
|
||||
<Scripts />
|
||||
<LiveReload />
|
||||
</body>
|
||||
</html>
|
||||
</MantineProvider>
|
||||
);
|
||||
}
|
||||
13
src/app/routes/api/ping.ts
Normal file
13
src/app/routes/api/ping.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { LoaderArgs, json } from '@remix-run/node';
|
||||
import { prisma } from '~/db.server';
|
||||
|
||||
export async function loader({ context, request }: LoaderArgs) {
|
||||
try {
|
||||
// test database connection
|
||||
await prisma.user.count();
|
||||
|
||||
return json({ pong: true }, { status: 200 });
|
||||
} catch (e) {
|
||||
return json({ pong: false }, { status: 500 });
|
||||
}
|
||||
}
|
||||
22
src/app/routes/index.tsx
Normal file
22
src/app/routes/index.tsx
Normal file
@@ -0,0 +1,22 @@
|
||||
import { LoaderArgs, json } from '@remix-run/node';
|
||||
import { useLoaderData } from '@remix-run/react';
|
||||
import { prisma } from '~/db.server';
|
||||
|
||||
export async function loader({}: LoaderArgs) {
|
||||
let zipline = await prisma.zipline.findFirst();
|
||||
if (!zipline) {
|
||||
zipline = await prisma.zipline.create({ data: {} });
|
||||
}
|
||||
|
||||
return json({ zipline });
|
||||
}
|
||||
|
||||
export default function Index() {
|
||||
const { zipline } = useLoaderData<typeof loader>();
|
||||
|
||||
return (
|
||||
<div>
|
||||
<pre>{JSON.stringify(zipline, null, 2)}</pre>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
25
src/app/session.server.ts
Normal file
25
src/app/session.server.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import { createCookieSessionStorage } from "@remix-run/node";
|
||||
|
||||
let sessionSecret = process.env.SESSION_SECRET;
|
||||
if (!sessionSecret) {
|
||||
throw new Error("SESSION_SECRET must be set");
|
||||
}
|
||||
|
||||
export let sessionStorage = createCookieSessionStorage({
|
||||
cookie: {
|
||||
name: "__session",
|
||||
secrets: [sessionSecret],
|
||||
sameSite: "lax",
|
||||
path: "/",
|
||||
maxAge: 60 * 60 * 24 * 30,
|
||||
httpOnly: true,
|
||||
},
|
||||
});
|
||||
|
||||
const USER_SESSION_KEY = "userId";
|
||||
|
||||
export async function getSession(request: Request) {
|
||||
const cookie = request.headers.get("Cookie");
|
||||
return sessionStorage.getSession(cookie);
|
||||
}
|
||||
|
||||
3
src/app/sleep.ts
Normal file
3
src/app/sleep.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export function sleep<T>(ms: number, value: T) {
|
||||
return new Promise<T>((resolve) => setTimeout(() => resolve(value), ms));
|
||||
}
|
||||
9
src/lib/config/Config.ts
Normal file
9
src/lib/config/Config.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
export interface Config {
|
||||
core: ConfigCore;
|
||||
}
|
||||
|
||||
export interface ConfigCore {
|
||||
port: number;
|
||||
sessionSecret: string;
|
||||
databaseUrl: string;
|
||||
}
|
||||
11
src/lib/config/convert.ts
Normal file
11
src/lib/config/convert.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import type { ValidatedEnv } from './read';
|
||||
|
||||
export function convertEnv(env: ValidatedEnv) {
|
||||
return {
|
||||
core: {
|
||||
port: env.PORT,
|
||||
sessionSecret: env.SESSION_SECRET,
|
||||
databaseUrl: env.DATABASE_URL,
|
||||
},
|
||||
};
|
||||
}
|
||||
19
src/lib/config/read.ts
Normal file
19
src/lib/config/read.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { log } from 'src/lib/logger';
|
||||
import { parseEnv } from 'znv';
|
||||
import { z } from 'zod';
|
||||
|
||||
const logger = log('config').c('read');
|
||||
|
||||
export function readEnv() {
|
||||
logger.debug('reading env');
|
||||
|
||||
const validation = parseEnv(process.env, {
|
||||
PORT: z.number().default(3000),
|
||||
SESSION_SECRET: z.string(),
|
||||
DATABASE_URL: z.string(),
|
||||
});
|
||||
|
||||
return validation;
|
||||
}
|
||||
|
||||
export type ValidatedEnv = ReturnType<typeof readEnv>;
|
||||
72
src/lib/logger.ts
Normal file
72
src/lib/logger.ts
Normal file
@@ -0,0 +1,72 @@
|
||||
import dayjs from 'dayjs';
|
||||
import { blue, green, red, yellow, gray, white, bold } from 'colorette';
|
||||
|
||||
export type LoggerLevel = 'info' | 'warn' | 'error' | 'debug' | 'trace';
|
||||
|
||||
export function log(name: string) {
|
||||
return new Logger(name);
|
||||
}
|
||||
|
||||
export default class Logger {
|
||||
public constructor(public name: string) {}
|
||||
|
||||
// Creates child of this logger
|
||||
public c(name: string) {
|
||||
return new Logger(`${this.name}::${name}`);
|
||||
}
|
||||
|
||||
private format(message: string, level: LoggerLevel) {
|
||||
const timestamp = dayjs().format('YYYY-MM-DDTHH:mm:ss');
|
||||
|
||||
return `${gray('[')}${timestamp} ${this.formatLevel(level)} ${this.name}${gray(']')} ${message}`;
|
||||
}
|
||||
|
||||
private formatLevel(level: LoggerLevel) {
|
||||
switch (level) {
|
||||
case 'info':
|
||||
return green('INFO ');
|
||||
case 'warn':
|
||||
return yellow('WARN ');
|
||||
case 'error':
|
||||
return red('ERROR');
|
||||
case 'debug':
|
||||
return yellow(bold('DEBUG'));
|
||||
case 'trace':
|
||||
return gray(bold('TRACE'));
|
||||
default:
|
||||
return white(bold('?????'));
|
||||
}
|
||||
}
|
||||
|
||||
private write(message: string, level: LoggerLevel) {
|
||||
process.stdout.write(`${this.format(message, level)}\n`);
|
||||
}
|
||||
|
||||
public info(...args: unknown[]) {
|
||||
this.write(args.join(' '), 'info');
|
||||
return this;
|
||||
}
|
||||
|
||||
public warn(...args: unknown[]) {
|
||||
this.write(args.join(' '), 'warn');
|
||||
return this;
|
||||
}
|
||||
|
||||
public error(...args: unknown[]) {
|
||||
this.write(args.join(' '), 'error');
|
||||
return this;
|
||||
}
|
||||
|
||||
public debug(...args: unknown[]) {
|
||||
if (process.env.DEBUG === 'zipline') return this;
|
||||
|
||||
this.write(args.join(' '), 'debug');
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public trace(...args: unknown[]) {
|
||||
this.write(args.join(' '), 'trace');
|
||||
return this;
|
||||
}
|
||||
}
|
||||
44
src/lib/migration.ts
Normal file
44
src/lib/migration.ts
Normal file
@@ -0,0 +1,44 @@
|
||||
import { Migrate } from '@prisma/migrate/dist/Migrate';
|
||||
import { ensureDatabaseExists } from '@prisma/migrate/dist/utils/ensureDatabaseExists';
|
||||
import { log } from './logger';
|
||||
|
||||
export async function runMigrations() {
|
||||
const migrate = new Migrate('./prisma/schema.prisma');
|
||||
const logger = log('migrations');
|
||||
logger.debug('running migrations...');
|
||||
|
||||
try {
|
||||
logger.debug('ensuring database exists...');
|
||||
const dbCreated = await ensureDatabaseExists('apply', './prisma/schema.prisma');
|
||||
if (dbCreated) {
|
||||
logger.info('database created');
|
||||
}
|
||||
} catch (e) {
|
||||
logger.error('failed to create database', e);
|
||||
logger.error('try creating the database manually and running the server again');
|
||||
|
||||
migrate.stop();
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
let migrationIds: string[];
|
||||
try {
|
||||
logger.debug('applying migrations...');
|
||||
const { appliedMigrationNames } = await migrate.applyMigrations();
|
||||
migrationIds = appliedMigrationNames;
|
||||
} catch (e) {
|
||||
logger.error('failed to apply migrations', e);
|
||||
|
||||
migrate.stop();
|
||||
process.exit(1);
|
||||
} finally {
|
||||
migrate.stop();
|
||||
}
|
||||
|
||||
if (migrationIds?.length === 0) {
|
||||
logger.debug('no migrations applied');
|
||||
return;
|
||||
}
|
||||
|
||||
logger.info(`applied migrations: ${migrationIds.join(', ')}`);
|
||||
}
|
||||
58
src/server/index.ts
Normal file
58
src/server/index.ts
Normal file
@@ -0,0 +1,58 @@
|
||||
import express from 'express';
|
||||
import { join } from 'path';
|
||||
import { createRequestHandler } from '@remix-run/express';
|
||||
import { convertEnv } from 'src/lib/config/convert';
|
||||
import { log } from 'src/lib/logger';
|
||||
import { readEnv } from 'src/lib/config/read';
|
||||
import { runMigrations } from 'src/lib/migration';
|
||||
|
||||
const MODE = process.env.NODE_ENV || 'production';
|
||||
const BUILD_DIR = join(process.cwd(), 'build');
|
||||
|
||||
const logger = log('server');
|
||||
|
||||
logger.info(`starting zipline in ${MODE} mode`);
|
||||
|
||||
runMigrations().then(() => {});
|
||||
|
||||
const server = express();
|
||||
const config = convertEnv(readEnv());
|
||||
|
||||
server.disable('x-powered-by');
|
||||
|
||||
server.use('/modules', express.static('public/build', { maxAge: '1y', immutable: true }));
|
||||
server.use(express.static('public', { maxAge: '1h' }));
|
||||
|
||||
server.all(
|
||||
'*',
|
||||
MODE === 'production'
|
||||
? createRequestHandler({ build: require(BUILD_DIR) })
|
||||
: (...args) => {
|
||||
purgeRequireCache();
|
||||
const requestHandler = createRequestHandler({
|
||||
build: require(BUILD_DIR),
|
||||
mode: MODE,
|
||||
getLoadContext() {
|
||||
return {
|
||||
config,
|
||||
};
|
||||
},
|
||||
});
|
||||
return requestHandler(...args);
|
||||
}
|
||||
);
|
||||
|
||||
server.listen(3000, () => {
|
||||
require(BUILD_DIR);
|
||||
|
||||
logger.info(`server listening on port ${config.core.port}`);
|
||||
});
|
||||
|
||||
function purgeRequireCache() {
|
||||
for (const key in require.cache) {
|
||||
if (key.startsWith(BUILD_DIR)) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
|
||||
delete require.cache[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
21
tsconfig.json
Normal file
21
tsconfig.json
Normal file
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"include": ["remix.env.d.ts", "**/*.ts", "**/*.tsx"],
|
||||
"compilerOptions": {
|
||||
"lib": ["DOM", "DOM.Iterable", "ES2019"],
|
||||
"esModuleInterop": true,
|
||||
"jsx": "react-jsx",
|
||||
"moduleResolution": "node",
|
||||
"target": "esnext",
|
||||
"strict": true,
|
||||
"skipLibCheck": true,
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"~/*": ["src/app/*"]
|
||||
},
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"allowJs": true,
|
||||
"isolatedModules": true,
|
||||
"resolveJsonModule": true,
|
||||
"noEmit": true
|
||||
}
|
||||
}
|
||||
13
tsup.config.ts
Normal file
13
tsup.config.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { defineConfig, Options } from 'tsup';
|
||||
|
||||
export default defineConfig({
|
||||
platform: 'node',
|
||||
format: 'cjs',
|
||||
treeshake: true,
|
||||
clean: false,
|
||||
sourcemap: true,
|
||||
entryPoints: {
|
||||
server: 'src/server/index.ts',
|
||||
},
|
||||
outDir: 'build',
|
||||
});
|
||||
Reference in New Issue
Block a user