Compare commits
15 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d1d3462056 | ||
|
|
5b01108777 | ||
|
|
3d1d4d0fc7 | ||
|
|
7c0d62a429 | ||
|
|
d010a8a2d3 | ||
|
|
9798e26872 | ||
|
|
0c10dc674f | ||
|
|
084e911eed | ||
|
|
797f8938ca | ||
|
|
05cbb7b27e | ||
|
|
905bab9c86 | ||
|
|
8e38c5fed7 | ||
|
|
7e877ce9f4 | ||
|
|
b1bfb09dfd | ||
|
|
c8a4521677 |
2
.github/FUNDING.yml
vendored
Normal file
2
.github/FUNDING.yml
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
# These are supported funding model platforms
|
||||||
|
github: stonith404
|
||||||
26
CHANGELOG.md
26
CHANGELOG.md
@@ -1,3 +1,29 @@
|
|||||||
|
### [0.3.5](https://github.com/stonith404/pingvin-share/compare/v0.3.4...v0.3.5) (2022-12-11)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* upload 3 files at same time ([d010a8a](https://github.com/stonith404/pingvin-share/commit/d010a8a2d366708b1bb5088e9c1e9f9378d3e023))
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* jobs never get executed ([05cbb7b](https://github.com/stonith404/pingvin-share/commit/05cbb7b27ef98a3a80dd9edc318f1dcc9a8bd442))
|
||||||
|
* only create zip if more than one file is in the share ([3d1d4d0](https://github.com/stonith404/pingvin-share/commit/3d1d4d0fc7c0351724387c3721280c334ae94d98))
|
||||||
|
* remove unnecessary port expose ([084e911](https://github.com/stonith404/pingvin-share/commit/084e911eed95eb22fea0bf185803ba32c3eda1a9))
|
||||||
|
* setup wizard table doesn't take full width ([9798e26](https://github.com/stonith404/pingvin-share/commit/9798e26872064edc1049138cf73479b1354a43ed))
|
||||||
|
* use node slim to fix arm builds ([797f893](https://github.com/stonith404/pingvin-share/commit/797f8938cac9cc3bb788f632d97eba5c49fe98a5))
|
||||||
|
* zip doesn't contain file extension ([5b01108](https://github.com/stonith404/pingvin-share/commit/5b0110877745f1fcde4952737a93c07ef4a2a92d))
|
||||||
|
|
||||||
|
### [0.3.4](https://github.com/stonith404/pingvin-share/compare/v0.3.3...v0.3.4) (2022-12-10)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* show alternative to copy button if site is not using https ([7e877ce](https://github.com/stonith404/pingvin-share/commit/7e877ce9f4b82d61c9b238e17def9f4c29e7aeb8))
|
||||||
|
* sign up page available when registration is disabled ([c8a4521](https://github.com/stonith404/pingvin-share/commit/c8a4521677280d6aba89d293a1fe0c38adf9f92c))
|
||||||
|
* tables on mobile ([b1bfb09](https://github.com/stonith404/pingvin-share/commit/b1bfb09dfd5c90cc18847470a9ce1ce8397c1476))
|
||||||
|
|
||||||
### [0.3.3](https://github.com/stonith404/pingvin-share/compare/v0.3.2...v0.3.3) (2022-12-08)
|
### [0.3.3](https://github.com/stonith404/pingvin-share/compare/v0.3.2...v0.3.3) (2022-12-08)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
37
Dockerfile
37
Dockerfile
@@ -1,27 +1,44 @@
|
|||||||
FROM node:18-slim AS frontend-builder
|
# Using node slim because prisma ORM needs libc for ARM builds
|
||||||
|
|
||||||
|
# Stage 1: on frontend dependency change
|
||||||
|
FROM node:18-slim AS frontend-dependencies
|
||||||
WORKDIR /opt/app
|
WORKDIR /opt/app
|
||||||
COPY frontend/package.json frontend/package-lock.json ./
|
COPY frontend/package.json frontend/package-lock.json ./
|
||||||
RUN npm ci
|
RUN npm ci
|
||||||
|
|
||||||
|
# Stage 2: on frontend change
|
||||||
|
FROM node:18-slim AS frontend-builder
|
||||||
|
WORKDIR /opt/app
|
||||||
COPY ./frontend .
|
COPY ./frontend .
|
||||||
|
COPY --from=frontend-dependencies /opt/app/node_modules ./node_modules
|
||||||
RUN npm run build
|
RUN npm run build
|
||||||
|
|
||||||
FROM node:18-slim AS backend-builder
|
# Stage 3: on backend dependency change
|
||||||
RUN apt-get update && apt-get install -y openssl
|
FROM node:18-slim AS backend-dependencies
|
||||||
WORKDIR /opt/app
|
WORKDIR /opt/app
|
||||||
COPY backend/package.json backend/package-lock.json ./
|
COPY backend/package.json backend/package-lock.json ./
|
||||||
RUN npm ci
|
RUN npm ci
|
||||||
COPY ./backend .
|
|
||||||
RUN npx prisma generate
|
|
||||||
RUN npm run build
|
|
||||||
|
|
||||||
|
# Stage 4:on backend change
|
||||||
|
FROM node:18-slim AS backend-builder
|
||||||
|
RUN apt-get update && apt-get install -y openssl
|
||||||
|
WORKDIR /opt/app
|
||||||
|
COPY ./backend .
|
||||||
|
COPY --from=backend-dependencies /opt/app/node_modules ./node_modules
|
||||||
|
RUN npx prisma generate
|
||||||
|
RUN npm run build && npm prune --production
|
||||||
|
|
||||||
|
# Stage 5: Final image
|
||||||
FROM node:18-slim AS runner
|
FROM node:18-slim AS runner
|
||||||
ENV NODE_ENV=production
|
ENV NODE_ENV=production
|
||||||
RUN apt-get update && apt-get install -y openssl
|
RUN apt-get update && apt-get install -y openssl
|
||||||
|
|
||||||
WORKDIR /opt/app/frontend
|
WORKDIR /opt/app/frontend
|
||||||
COPY --from=frontend-builder /opt/app/next.config.js .
|
|
||||||
COPY --from=frontend-builder /opt/app/public ./public
|
COPY --from=frontend-builder /opt/app/public ./public
|
||||||
COPY --from=frontend-builder /opt/app/.next ./.next
|
# Automatically leverage output traces to reduce image size
|
||||||
COPY --from=frontend-builder /opt/app/node_modules ./node_modules
|
# https://nextjs.org/docs/advanced-features/output-file-tracing
|
||||||
|
COPY --from=frontend-builder /opt/app/.next/standalone ./
|
||||||
|
COPY --from=frontend-builder /opt/app/.next/static ./.next/static
|
||||||
|
|
||||||
WORKDIR /opt/app/backend
|
WORKDIR /opt/app/backend
|
||||||
COPY --from=backend-builder /opt/app/node_modules ./node_modules
|
COPY --from=backend-builder /opt/app/node_modules ./node_modules
|
||||||
@@ -31,4 +48,4 @@ COPY --from=backend-builder /opt/app/package.json ./
|
|||||||
|
|
||||||
WORKDIR /opt/app
|
WORKDIR /opt/app
|
||||||
EXPOSE 3000
|
EXPOSE 3000
|
||||||
CMD cd frontend && node_modules/.bin/next start & cd backend && npm run prod
|
CMD node frontend/server.js & cd backend && npm run prod
|
||||||
@@ -22,6 +22,7 @@
|
|||||||
"@nestjs/platform-express": "^9.2.1",
|
"@nestjs/platform-express": "^9.2.1",
|
||||||
"@nestjs/schedule": "^2.1.0",
|
"@nestjs/schedule": "^2.1.0",
|
||||||
"@nestjs/throttler": "^3.1.0",
|
"@nestjs/throttler": "^3.1.0",
|
||||||
|
"@prisma/client": "^4.7.1",
|
||||||
"archiver": "^5.3.1",
|
"archiver": "^5.3.1",
|
||||||
"argon2": "^0.30.2",
|
"argon2": "^0.30.2",
|
||||||
"class-transformer": "^0.5.1",
|
"class-transformer": "^0.5.1",
|
||||||
@@ -36,13 +37,13 @@
|
|||||||
"passport-local": "^1.0.0",
|
"passport-local": "^1.0.0",
|
||||||
"reflect-metadata": "^0.1.13",
|
"reflect-metadata": "^0.1.13",
|
||||||
"rimraf": "^3.0.2",
|
"rimraf": "^3.0.2",
|
||||||
"rxjs": "^7.6.0"
|
"rxjs": "^7.6.0",
|
||||||
|
"ts-node": "^10.9.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@nestjs/cli": "^9.1.5",
|
"@nestjs/cli": "^9.1.5",
|
||||||
"@nestjs/schematics": "^9.0.3",
|
"@nestjs/schematics": "^9.0.3",
|
||||||
"@nestjs/testing": "^9.2.1",
|
"@nestjs/testing": "^9.2.1",
|
||||||
"@prisma/client": "^4.7.1",
|
|
||||||
"@types/archiver": "^5.3.1",
|
"@types/archiver": "^5.3.1",
|
||||||
"@types/cron": "^2.0.0",
|
"@types/cron": "^2.0.0",
|
||||||
"@types/express": "^4.17.14",
|
"@types/express": "^4.17.14",
|
||||||
@@ -63,7 +64,6 @@
|
|||||||
"prisma": "^4.7.1",
|
"prisma": "^4.7.1",
|
||||||
"source-map-support": "^0.5.21",
|
"source-map-support": "^0.5.21",
|
||||||
"ts-loader": "^9.4.2",
|
"ts-loader": "^9.4.2",
|
||||||
"ts-node": "^10.9.1",
|
|
||||||
"tsconfig-paths": "4.1.1",
|
"tsconfig-paths": "4.1.1",
|
||||||
"typescript": "^4.9.3",
|
"typescript": "^4.9.3",
|
||||||
"wait-on": "^6.0.1"
|
"wait-on": "^6.0.1"
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import { FileModule } from "./file/file.module";
|
|||||||
import { PrismaModule } from "./prisma/prisma.module";
|
import { PrismaModule } from "./prisma/prisma.module";
|
||||||
import { ShareModule } from "./share/share.module";
|
import { ShareModule } from "./share/share.module";
|
||||||
import { UserModule } from "./user/user.module";
|
import { UserModule } from "./user/user.module";
|
||||||
|
import { JobsModule } from "./jobs/jobs.module";
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports: [
|
||||||
@@ -22,6 +23,7 @@ import { UserModule } from "./user/user.module";
|
|||||||
EmailModule,
|
EmailModule,
|
||||||
PrismaModule,
|
PrismaModule,
|
||||||
ConfigModule,
|
ConfigModule,
|
||||||
|
JobsModule,
|
||||||
UserModule,
|
UserModule,
|
||||||
MulterModule.registerAsync({
|
MulterModule.registerAsync({
|
||||||
useFactory: (config: ConfigService) => ({
|
useFactory: (config: ConfigService) => ({
|
||||||
|
|||||||
@@ -79,7 +79,7 @@ export class FileController {
|
|||||||
const zip = this.fileService.getZip(shareId);
|
const zip = this.fileService.getZip(shareId);
|
||||||
res.set({
|
res.set({
|
||||||
"Content-Type": "application/zip",
|
"Content-Type": "application/zip",
|
||||||
"Content-Disposition": `attachment ; filename="pingvin-share-${shareId}"`,
|
"Content-Disposition": `attachment ; filename="pingvin-share-${shareId}.zip"`,
|
||||||
});
|
});
|
||||||
|
|
||||||
return new StreamableFile(zip);
|
return new StreamableFile(zip);
|
||||||
|
|||||||
9
backend/src/jobs/jobs.module.ts
Normal file
9
backend/src/jobs/jobs.module.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
import { Module } from "@nestjs/common";
|
||||||
|
import { FileModule } from "src/file/file.module";
|
||||||
|
import { JobsService } from "./jobs.service";
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
imports: [FileModule],
|
||||||
|
providers: [JobsService],
|
||||||
|
})
|
||||||
|
export class JobsModule {}
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
import { Injectable } from "@nestjs/common";
|
import { Injectable } from "@nestjs/common";
|
||||||
import { ConfigService } from "@nestjs/config";
|
|
||||||
import { PrismaClient } from "@prisma/client";
|
import { PrismaClient } from "@prisma/client";
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
@@ -8,7 +7,7 @@ export class PrismaService extends PrismaClient {
|
|||||||
super({
|
super({
|
||||||
datasources: {
|
datasources: {
|
||||||
db: {
|
db: {
|
||||||
url: "file:../data/pingvin-share.db",
|
url: "file:../data/pingvin-share.db?connection_limit=1",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -105,9 +105,10 @@ export class ShareService {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Asynchronously create a zip of all files
|
// Asynchronously create a zip of all files
|
||||||
this.createZip(id).then(() =>
|
if (share.files.length > 1)
|
||||||
this.prisma.share.update({ where: { id }, data: { isZipReady: true } })
|
this.createZip(id).then(() =>
|
||||||
);
|
this.prisma.share.update({ where: { id }, data: { isZipReady: true } })
|
||||||
|
);
|
||||||
|
|
||||||
// Send email for each recepient
|
// Send email for each recepient
|
||||||
for (const recepient of share.recipients) {
|
for (const recepient of share.recipients) {
|
||||||
|
|||||||
@@ -1,10 +1,8 @@
|
|||||||
/** @type {import('next').NextConfig} */
|
/** @type {import('next').NextConfig} */
|
||||||
|
|
||||||
|
|
||||||
const withPWA = require("next-pwa")({
|
const withPWA = require("next-pwa")({
|
||||||
dest: "public",
|
dest: "public",
|
||||||
disable: process.env.NODE_ENV == "development"
|
disable: process.env.NODE_ENV == "development",
|
||||||
});
|
});
|
||||||
|
|
||||||
|
module.exports = withPWA({ output: "standalone" });
|
||||||
module.exports = withPWA();
|
|
||||||
|
|||||||
83
frontend/package-lock.json
generated
83
frontend/package-lock.json
generated
@@ -26,6 +26,7 @@
|
|||||||
"next-cookies": "^2.0.3",
|
"next-cookies": "^2.0.3",
|
||||||
"next-http-proxy-middleware": "^1.2.5",
|
"next-http-proxy-middleware": "^1.2.5",
|
||||||
"next-pwa": "^5.6.0",
|
"next-pwa": "^5.6.0",
|
||||||
|
"p-limit": "^4.0.0",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
"react-icons": "^4.7.1",
|
"react-icons": "^4.7.1",
|
||||||
@@ -6161,6 +6162,35 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/p-limit": {
|
"node_modules/p-limit": {
|
||||||
|
"version": "4.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-4.0.0.tgz",
|
||||||
|
"integrity": "sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"yocto-queue": "^1.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/p-locate": {
|
||||||
|
"version": "5.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz",
|
||||||
|
"integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"p-limit": "^3.0.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/p-locate/node_modules/p-limit": {
|
||||||
"version": "3.1.0",
|
"version": "3.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
|
||||||
"integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==",
|
"integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==",
|
||||||
@@ -6175,14 +6205,11 @@
|
|||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/p-locate": {
|
"node_modules/p-locate/node_modules/yocto-queue": {
|
||||||
"version": "5.0.0",
|
"version": "0.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
|
||||||
"integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==",
|
"integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
|
||||||
"p-limit": "^3.0.2"
|
|
||||||
},
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=10"
|
"node": ">=10"
|
||||||
},
|
},
|
||||||
@@ -8049,12 +8076,11 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/yocto-queue": {
|
"node_modules/yocto-queue": {
|
||||||
"version": "0.1.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.0.0.tgz",
|
||||||
"integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==",
|
"integrity": "sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==",
|
||||||
"dev": true,
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=10"
|
"node": ">=12.20"
|
||||||
},
|
},
|
||||||
"funding": {
|
"funding": {
|
||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
@@ -12482,12 +12508,11 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"p-limit": {
|
"p-limit": {
|
||||||
"version": "3.1.0",
|
"version": "4.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-4.0.0.tgz",
|
||||||
"integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==",
|
"integrity": "sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==",
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"yocto-queue": "^0.1.0"
|
"yocto-queue": "^1.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"p-locate": {
|
"p-locate": {
|
||||||
@@ -12497,6 +12522,23 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"p-limit": "^3.0.2"
|
"p-limit": "^3.0.2"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"p-limit": {
|
||||||
|
"version": "3.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
|
||||||
|
"integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"yocto-queue": "^0.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"yocto-queue": {
|
||||||
|
"version": "0.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
|
||||||
|
"integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==",
|
||||||
|
"dev": true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"p-map": {
|
"p-map": {
|
||||||
@@ -13865,10 +13907,9 @@
|
|||||||
"integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg=="
|
"integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg=="
|
||||||
},
|
},
|
||||||
"yocto-queue": {
|
"yocto-queue": {
|
||||||
"version": "0.1.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.0.0.tgz",
|
||||||
"integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==",
|
"integrity": "sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g=="
|
||||||
"dev": true
|
|
||||||
},
|
},
|
||||||
"yup": {
|
"yup": {
|
||||||
"version": "0.32.11",
|
"version": "0.32.11",
|
||||||
|
|||||||
@@ -27,6 +27,7 @@
|
|||||||
"next-cookies": "^2.0.3",
|
"next-cookies": "^2.0.3",
|
||||||
"next-http-proxy-middleware": "^1.2.5",
|
"next-http-proxy-middleware": "^1.2.5",
|
||||||
"next-pwa": "^5.6.0",
|
"next-pwa": "^5.6.0",
|
||||||
|
"p-limit": "^4.0.0",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
"react-icons": "^4.7.1",
|
"react-icons": "^4.7.1",
|
||||||
|
|||||||
16
frontend/src/components/account/showShareLinkModal.tsx
Normal file
16
frontend/src/components/account/showShareLinkModal.tsx
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import { Stack, TextInput } from "@mantine/core";
|
||||||
|
import { ModalsContextProps } from "@mantine/modals/lib/context";
|
||||||
|
|
||||||
|
const showShareLinkModal = (modals: ModalsContextProps, shareId: string) => {
|
||||||
|
const link = `${window.location.origin}/share/${shareId}`;
|
||||||
|
return modals.openModal({
|
||||||
|
title: "Share link",
|
||||||
|
children: (
|
||||||
|
<Stack align="stretch">
|
||||||
|
<TextInput variant="filled" value={link} />
|
||||||
|
</Stack>
|
||||||
|
),
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export default showShareLinkModal;
|
||||||
@@ -1,4 +1,12 @@
|
|||||||
import { ActionIcon, Code, Group, Skeleton, Table, Text } from "@mantine/core";
|
import {
|
||||||
|
ActionIcon,
|
||||||
|
Box,
|
||||||
|
Code,
|
||||||
|
Group,
|
||||||
|
Skeleton,
|
||||||
|
Table,
|
||||||
|
Text,
|
||||||
|
} from "@mantine/core";
|
||||||
import { useModals } from "@mantine/modals";
|
import { useModals } from "@mantine/modals";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { TbEdit, TbLock } from "react-icons/tb";
|
import { TbEdit, TbLock } from "react-icons/tb";
|
||||||
@@ -43,53 +51,55 @@ const AdminConfigTable = () => {
|
|||||||
));
|
));
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Table verticalSpacing="sm" horizontalSpacing="xl" withBorder>
|
<Box sx={{ display: "block", overflowX: "auto" }}>
|
||||||
<thead>
|
<Table verticalSpacing="sm" horizontalSpacing="xl" withBorder>
|
||||||
<tr>
|
<thead>
|
||||||
<th>Key</th>
|
<tr>
|
||||||
<th>Value</th>
|
<th>Key</th>
|
||||||
<th></th>
|
<th>Value</th>
|
||||||
</tr>
|
<th></th>
|
||||||
</thead>
|
</tr>
|
||||||
<tbody>
|
</thead>
|
||||||
{isLoading
|
<tbody>
|
||||||
? skeletonRows
|
{isLoading
|
||||||
: configVariables.map((configVariable) => (
|
? skeletonRows
|
||||||
<tr key={configVariable.key}>
|
: configVariables.map((configVariable) => (
|
||||||
<td style={{ maxWidth: "200px" }}>
|
<tr key={configVariable.key}>
|
||||||
<Code>{configVariable.key}</Code>{" "}
|
<td style={{ maxWidth: "200px" }}>
|
||||||
{configVariable.secret && <TbLock />} <br />
|
<Code>{configVariable.key}</Code>{" "}
|
||||||
<Text size="xs" color="dimmed">
|
{configVariable.secret && <TbLock />} <br />
|
||||||
{configVariable.description}
|
<Text size="xs" color="dimmed">
|
||||||
</Text>
|
{configVariable.description}
|
||||||
</td>
|
</Text>
|
||||||
<td>
|
</td>
|
||||||
{configVariable.obscured
|
<td>
|
||||||
? "•".repeat(configVariable.value.length)
|
{configVariable.obscured
|
||||||
: configVariable.value}
|
? "•".repeat(configVariable.value.length)
|
||||||
</td>
|
: configVariable.value}
|
||||||
<td>
|
</td>
|
||||||
<Group position="right">
|
<td>
|
||||||
<ActionIcon
|
<Group position="right">
|
||||||
color="primary"
|
<ActionIcon
|
||||||
variant="light"
|
color="primary"
|
||||||
size={25}
|
variant="light"
|
||||||
onClick={() =>
|
size={25}
|
||||||
showUpdateConfigVariableModal(
|
onClick={() =>
|
||||||
modals,
|
showUpdateConfigVariableModal(
|
||||||
configVariable,
|
modals,
|
||||||
getConfigVariables
|
configVariable,
|
||||||
)
|
getConfigVariables
|
||||||
}
|
)
|
||||||
>
|
}
|
||||||
<TbEdit />
|
>
|
||||||
</ActionIcon>
|
<TbEdit />
|
||||||
</Group>
|
</ActionIcon>
|
||||||
</td>
|
</Group>
|
||||||
</tr>
|
</td>
|
||||||
))}
|
</tr>
|
||||||
</tbody>
|
))}
|
||||||
</Table>
|
</tbody>
|
||||||
|
</Table>
|
||||||
|
</Box>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -18,8 +18,8 @@ const ManageUserTable = ({
|
|||||||
const modals = useModals();
|
const modals = useModals();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box sx={{ display: "block", overflowX: "auto", whiteSpace: "nowrap" }}>
|
<Box sx={{ display: "block", overflowX: "auto" }}>
|
||||||
<Table verticalSpacing="sm" highlightOnHover>
|
<Table verticalSpacing="sm">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Username</th>
|
<th>Username</th>
|
||||||
|
|||||||
@@ -40,14 +40,16 @@ const Body = ({ share }: { share: Share }) => {
|
|||||||
variant="filled"
|
variant="filled"
|
||||||
value={link}
|
value={link}
|
||||||
rightSection={
|
rightSection={
|
||||||
<ActionIcon
|
window.isSecureContext && (
|
||||||
onClick={() => {
|
<ActionIcon
|
||||||
clipboard.copy(link);
|
onClick={() => {
|
||||||
toast.success("Your link was copied to the keyboard.");
|
clipboard.copy(link);
|
||||||
}}
|
toast.success("Your link was copied to the keyboard.");
|
||||||
>
|
}}
|
||||||
<TbCopy />
|
>
|
||||||
</ActionIcon>
|
<TbCopy />
|
||||||
|
</ActionIcon>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<Text
|
<Text
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import Link from "next/link";
|
|||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { TbLink, TbTrash } from "react-icons/tb";
|
import { TbLink, TbTrash } from "react-icons/tb";
|
||||||
|
import showShareLinkModal from "../../components/account/showShareLinkModal";
|
||||||
import Meta from "../../components/Meta";
|
import Meta from "../../components/Meta";
|
||||||
import useUser from "../../hooks/user.hook";
|
import useUser from "../../hooks/user.hook";
|
||||||
import shareService from "../../services/share.service";
|
import shareService from "../../services/share.service";
|
||||||
@@ -83,12 +84,16 @@ const MyShares = () => {
|
|||||||
variant="light"
|
variant="light"
|
||||||
size={25}
|
size={25}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
clipboard.copy(
|
if (window.isSecureContext) {
|
||||||
`${window.location.origin}/share/${share.id}`
|
clipboard.copy(
|
||||||
);
|
`${window.location.origin}/share/${share.id}`
|
||||||
toast.success(
|
);
|
||||||
"Your link was copied to the keyboard."
|
toast.success(
|
||||||
);
|
"Your link was copied to the keyboard."
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
showShareLinkModal(modals, share.id);
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<TbLink />
|
<TbLink />
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Button, Stack, Text, Title } from "@mantine/core";
|
import { Box, Button, Stack, Text, Title } from "@mantine/core";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import AdminConfigTable from "../../components/admin/AdminConfigTable";
|
import AdminConfigTable from "../../components/admin/AdminConfigTable";
|
||||||
@@ -28,7 +28,9 @@ const Setup = () => {
|
|||||||
<Logo height={80} width={80} />
|
<Logo height={80} width={80} />
|
||||||
<Title order={2}>Welcome to Pingvin Share</Title>
|
<Title order={2}>Welcome to Pingvin Share</Title>
|
||||||
<Text>Let's customize Pingvin Share for you! </Text>
|
<Text>Let's customize Pingvin Share for you! </Text>
|
||||||
<AdminConfigTable />
|
<Box style={{ width: "100%" }}>
|
||||||
|
<AdminConfigTable />
|
||||||
|
</Box>
|
||||||
<Button
|
<Button
|
||||||
loading={isLoading}
|
loading={isLoading}
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ const SignUp = () => {
|
|||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
if (user) {
|
if (user) {
|
||||||
router.replace("/");
|
router.replace("/");
|
||||||
} else if (config.get("ALLOW_REGISTRATION") == "false") {
|
} else if (!config.get("ALLOW_REGISTRATION")) {
|
||||||
router.replace("/auth/signIn");
|
router.replace("/auth/signIn");
|
||||||
} else {
|
} else {
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ export function getServerSideProps(context: GetServerSidePropsContext) {
|
|||||||
|
|
||||||
const Share = ({ shareId }: { shareId: string }) => {
|
const Share = ({ shareId }: { shareId: string }) => {
|
||||||
const modals = useModals();
|
const modals = useModals();
|
||||||
const [fileList, setFileList] = useState<any[]>([]);
|
const [files, setFiles] = useState<any[]>([]);
|
||||||
|
|
||||||
const getShareToken = async (password?: string) => {
|
const getShareToken = async (password?: string) => {
|
||||||
await shareService
|
await shareService
|
||||||
@@ -41,7 +41,7 @@ const Share = ({ shareId }: { shareId: string }) => {
|
|||||||
shareService
|
shareService
|
||||||
.get(shareId)
|
.get(shareId)
|
||||||
.then((share) => {
|
.then((share) => {
|
||||||
setFileList(share.files);
|
setFiles(share.files);
|
||||||
})
|
})
|
||||||
.catch((e) => {
|
.catch((e) => {
|
||||||
const { error } = e.response.data;
|
const { error } = e.response.data;
|
||||||
@@ -77,14 +77,12 @@ const Share = ({ shareId }: { shareId: string }) => {
|
|||||||
title={`Share ${shareId}`}
|
title={`Share ${shareId}`}
|
||||||
description="Look what I've shared with you."
|
description="Look what I've shared with you."
|
||||||
/>
|
/>
|
||||||
<Group position="right" mb="lg">
|
{files.length > 1 && (
|
||||||
<DownloadAllButton shareId={shareId} />
|
<Group position="right" mb="lg">
|
||||||
</Group>
|
<DownloadAllButton shareId={shareId} />
|
||||||
<FileList
|
</Group>
|
||||||
files={fileList}
|
)}
|
||||||
shareId={shareId}
|
<FileList files={files} shareId={shareId} isLoading={files.length == 0} />
|
||||||
isLoading={fileList.length == 0}
|
|
||||||
/>
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { Button, Group } from "@mantine/core";
|
|||||||
import { useModals } from "@mantine/modals";
|
import { useModals } from "@mantine/modals";
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
|
import pLimit from "p-limit";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import Meta from "../components/Meta";
|
import Meta from "../components/Meta";
|
||||||
import Dropzone from "../components/upload/Dropzone";
|
import Dropzone from "../components/upload/Dropzone";
|
||||||
@@ -12,10 +13,11 @@ import useConfig from "../hooks/config.hook";
|
|||||||
import useUser from "../hooks/user.hook";
|
import useUser from "../hooks/user.hook";
|
||||||
import shareService from "../services/share.service";
|
import shareService from "../services/share.service";
|
||||||
import { FileUpload } from "../types/File.type";
|
import { FileUpload } from "../types/File.type";
|
||||||
import { ShareSecurity } from "../types/share.type";
|
import { Share, ShareSecurity } from "../types/share.type";
|
||||||
import toast from "../utils/toast.util";
|
import toast from "../utils/toast.util";
|
||||||
|
|
||||||
let share: any;
|
let share: Share;
|
||||||
|
const promiseLimit = pLimit(3);
|
||||||
|
|
||||||
const Upload = () => {
|
const Upload = () => {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
@@ -41,7 +43,8 @@ const Upload = () => {
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
share = await shareService.create(id, expiration, recipients, security);
|
share = await shareService.create(id, expiration, recipients, security);
|
||||||
for (let i = 0; i < files.length; i++) {
|
const uploadPromises = files.map((file, i) => {
|
||||||
|
// Callback to indicate current upload progress
|
||||||
const progressCallBack = (progress: number) => {
|
const progressCallBack = (progress: number) => {
|
||||||
setFiles((files) => {
|
setFiles((files) => {
|
||||||
return files.map((file, callbackIndex) => {
|
return files.map((file, callbackIndex) => {
|
||||||
@@ -54,11 +57,15 @@ const Upload = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await shareService.uploadFile(share.id, files[i], progressCallBack);
|
return promiseLimit(() =>
|
||||||
|
shareService.uploadFile(share.id, file, progressCallBack)
|
||||||
|
);
|
||||||
} catch {
|
} catch {
|
||||||
files[i].uploadingProgress = -1;
|
file.uploadingProgress = -1;
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
|
|
||||||
|
await Promise.all(uploadPromises);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (axios.isAxiosError(e)) {
|
if (axios.isAxiosError(e)) {
|
||||||
toast.error(e.response?.data?.message ?? "An unkown error occured.");
|
toast.error(e.response?.data?.message ?? "An unkown error occured.");
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "pingvin-share",
|
"name": "pingvin-share",
|
||||||
"version": "0.3.3",
|
"version": "0.3.5",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"format": "cd frontend && npm run format && cd ../backend && npm run format",
|
"format": "cd frontend && npm run format && cd ../backend && npm run format",
|
||||||
"lint": "cd frontend && npm run lint && cd ../backend && npm run lint",
|
"lint": "cd frontend && npm run lint && cd ../backend && npm run lint",
|
||||||
|
|||||||
Reference in New Issue
Block a user