Compare commits
38 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bfb47ba6e8 | ||
|
|
c1d87a1c29 | ||
|
|
4c7e161217 | ||
|
|
844c47e129 | ||
|
|
9b0c08d0cd | ||
|
|
37fda220e9 | ||
|
|
3b7f5ddc52 | ||
|
|
8728fa5207 | ||
|
|
c265129dcc | ||
|
|
78dd4a7e2a | ||
|
|
3cad4dd487 | ||
|
|
d1d3462056 | ||
|
|
5b01108777 | ||
|
|
3d1d4d0fc7 | ||
|
|
7c0d62a429 | ||
|
|
d010a8a2d3 | ||
|
|
9798e26872 | ||
|
|
0c10dc674f | ||
|
|
084e911eed | ||
|
|
797f8938ca | ||
|
|
05cbb7b27e | ||
|
|
905bab9c86 | ||
|
|
8e38c5fed7 | ||
|
|
7e877ce9f4 | ||
|
|
b1bfb09dfd | ||
|
|
c8a4521677 | ||
|
|
3c74cc14df | ||
|
|
a165f8ec4d | ||
|
|
d6a88f2a22 | ||
|
|
b8172efd59 | ||
|
|
cbe37c6798 | ||
|
|
a545c44426 | ||
|
|
08a2f60f72 | ||
|
|
907e56af0f | ||
|
|
888a0c5faf | ||
|
|
bfb0d151ea | ||
|
|
1f63f22591 | ||
|
|
a2d5e0f72c |
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
|
||||
45
.github/ISSUE_TEMPLATE/bug.yml
vendored
Normal file
45
.github/ISSUE_TEMPLATE/bug.yml
vendored
Normal file
@@ -0,0 +1,45 @@
|
||||
name: "🐛 Bug Report"
|
||||
description: "Submit a bug report to help us improve"
|
||||
title: "🐛 Bug Report: "
|
||||
labels: [bug]
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Thanks for taking the time to fill out our bug report form 🙏
|
||||
- type: textarea
|
||||
id: steps-to-reproduce
|
||||
validations:
|
||||
required: true
|
||||
attributes:
|
||||
label: "👟 Reproduction steps"
|
||||
description: "How do you trigger this bug? Please walk us through it step by step."
|
||||
placeholder: "When I ..."
|
||||
- type: textarea
|
||||
id: expected-behavior
|
||||
validations:
|
||||
required: true
|
||||
attributes:
|
||||
label: "👍 Expected behavior"
|
||||
description: "What did you think would happen?"
|
||||
placeholder: "It should ..."
|
||||
- type: textarea
|
||||
id: actual-behavior
|
||||
validations:
|
||||
required: true
|
||||
attributes:
|
||||
label: "👎 Actual Behavior"
|
||||
description: "What did actually happen? Add screenshots, if applicable."
|
||||
placeholder: "It actually ..."
|
||||
- type: input
|
||||
id: operating-system
|
||||
attributes:
|
||||
label: "🌐 Browser"
|
||||
description: "Which browser do you use?"
|
||||
placeholder: "Firefox"
|
||||
validations:
|
||||
required: true
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Before submitting, please check if the issues hasn't been raised before.
|
||||
29
.github/ISSUE_TEMPLATE/feature.yml
vendored
Normal file
29
.github/ISSUE_TEMPLATE/feature.yml
vendored
Normal file
@@ -0,0 +1,29 @@
|
||||
name: 🚀 Feature
|
||||
description: "Submit a proposal for a new feature"
|
||||
title: "🚀 Feature: "
|
||||
labels: [feature]
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Thanks for taking the time to fill out our feature request form 🙏
|
||||
- type: textarea
|
||||
id: feature-description
|
||||
validations:
|
||||
required: true
|
||||
attributes:
|
||||
label: "🔖 Feature description"
|
||||
description: "A clear and concise description of what the feature is."
|
||||
placeholder: "You should add ..."
|
||||
- type: textarea
|
||||
id: pitch
|
||||
validations:
|
||||
required: true
|
||||
attributes:
|
||||
label: "🎤 Pitch"
|
||||
description: "Please explain why this feature should be implemented and how it would be used. Add examples, if applicable."
|
||||
placeholder: "In my use-case, ..."
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Before submitting, please check if the issues hasn't been raised before.
|
||||
22
.github/workflows/close_inactive_issues.yml
vendored
Normal file
22
.github/workflows/close_inactive_issues.yml
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
name: Close inactive issues
|
||||
on:
|
||||
schedule:
|
||||
- cron: "00 00 * * *"
|
||||
|
||||
jobs:
|
||||
close-issues:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
issues: write
|
||||
pull-requests: write
|
||||
steps:
|
||||
- uses: actions/stale@v4
|
||||
with:
|
||||
days-before-issue-stale: 30
|
||||
days-before-issue-close: 14
|
||||
stale-issue-label: "stale"
|
||||
stale-issue-message: "This issue is stale because it has been open for 30 days with no activity."
|
||||
close-issue-message: "This issue was closed because it has been inactive for 14 days since being marked as stale."
|
||||
days-before-pr-stale: -1
|
||||
days-before-pr-close: -1
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
55
CHANGELOG.md
55
CHANGELOG.md
@@ -1,3 +1,58 @@
|
||||
### [0.3.6](https://github.com/stonith404/pingvin-share/compare/v0.3.5...v0.3.6) (2022-12-13)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add description field to share ([8728fa5](https://github.com/stonith404/pingvin-share/commit/8728fa5207524e9aee26d68eafe1b6fff367d749))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* remove dot in email link ([9b0c08d](https://github.com/stonith404/pingvin-share/commit/9b0c08d0cdeeeef217ccba57f593fea9d8858371))
|
||||
* rerange accordion items ([844c47e](https://github.com/stonith404/pingvin-share/commit/844c47e1290fb0f7dedb41a18be59ed5ab83dabc))
|
||||
|
||||
### [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)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add support for different email and user ([888a0c5](https://github.com/stonith404/pingvin-share/commit/888a0c5fafc51b6872ed71e37d4b40c9bf6a07f1))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* allow empty strings in config variable ([b8172ef](https://github.com/stonith404/pingvin-share/commit/b8172efd59fb3271ab9b818b13a7003342b2cebd))
|
||||
* improve admin dashboard color and layout ([a545c44](https://github.com/stonith404/pingvin-share/commit/a545c444261c90105dcb165ebcf4b26634e729ca))
|
||||
* obscure critical config variables ([bfb0d15](https://github.com/stonith404/pingvin-share/commit/bfb0d151ea2ba125e536a16b1873e143a67e9f64))
|
||||
* obscured text length ([cbe37c6](https://github.com/stonith404/pingvin-share/commit/cbe37c679853ecef1522ed213e4cac5defd5b45a))
|
||||
* space character in email ([907e56a](https://github.com/stonith404/pingvin-share/commit/907e56af0faccdbc8d7f5ab3418a4ad71ff849f5))
|
||||
|
||||
### [0.3.2](https://github.com/stonith404/pingvin-share/compare/v0.3.1...v0.3.2) (2022-12-07)
|
||||
|
||||
|
||||
|
||||
43
Dockerfile
43
Dockerfile
@@ -1,32 +1,51 @@
|
||||
FROM node:18-alpine 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
|
||||
COPY frontend/package.json frontend/package-lock.json ./
|
||||
RUN npm ci
|
||||
|
||||
# Stage 2: on frontend change
|
||||
FROM node:18-slim AS frontend-builder
|
||||
WORKDIR /opt/app
|
||||
COPY ./frontend .
|
||||
COPY --from=frontend-dependencies /opt/app/node_modules ./node_modules
|
||||
RUN npm run build
|
||||
|
||||
FROM node:18 AS backend-builder
|
||||
# Stage 3: on backend dependency change
|
||||
FROM node:18-slim AS backend-dependencies
|
||||
WORKDIR /opt/app
|
||||
COPY backend/package.json backend/package-lock.json ./
|
||||
RUN npm ci
|
||||
COPY ./backend .
|
||||
RUN npx prisma generate
|
||||
RUN npm run build
|
||||
|
||||
FROM node:18 AS runner
|
||||
WORKDIR /opt/app/frontend
|
||||
# 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
|
||||
ENV NODE_ENV=production
|
||||
COPY --from=frontend-builder /opt/app/next.config.js .
|
||||
RUN apt-get update && apt-get install -y openssl
|
||||
|
||||
WORKDIR /opt/app/frontend
|
||||
COPY --from=frontend-builder /opt/app/public ./public
|
||||
COPY --from=frontend-builder /opt/app/.next ./.next
|
||||
COPY --from=frontend-builder /opt/app/node_modules ./node_modules
|
||||
# Automatically leverage output traces to reduce image size
|
||||
# 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
|
||||
COPY --from=backend-builder /opt/app/node_modules ./node_modules
|
||||
COPY --from=backend-builder /opt/app/dist ./dist
|
||||
COPY --from=backend-builder /opt/app/prisma ./prisma
|
||||
COPY --from=backend-builder /opt/app/package.json ./
|
||||
WORKDIR /opt/app
|
||||
|
||||
WORKDIR /opt/app
|
||||
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
|
||||
19
README.md
19
README.md
@@ -2,12 +2,6 @@
|
||||
|
||||
Pingvin Share is self-hosted file sharing platform and an alternative for WeTransfer.
|
||||
|
||||
## 🎪 Showcase
|
||||
|
||||
Demo: https://pingvin-share.dev.eliasschneider.com
|
||||
|
||||
<img src="https://user-images.githubusercontent.com/58886915/167101708-b85032ad-f5b1-480a-b8d7-ec0096ea2a43.png" width="700"/>
|
||||
|
||||
## ✨ Features
|
||||
|
||||
- Spin up your instance within 2 minutes
|
||||
@@ -18,15 +12,28 @@ Demo: https://pingvin-share.dev.eliasschneider.com
|
||||
- Email recepients
|
||||
- Light & dark mode
|
||||
|
||||
## 🐧 Get to know Pingvin Share
|
||||
|
||||
- [Demo](https://pingvin-share.dev.eliasschneider.com)
|
||||
- [Review by DB Tech](https://www.youtube.com/watch?v=rWwNeZCOPJA)
|
||||
|
||||
<img src="https://user-images.githubusercontent.com/58886915/167101708-b85032ad-f5b1-480a-b8d7-ec0096ea2a43.png" width="700"/>
|
||||
|
||||
## ⌨️ Setup
|
||||
|
||||
> Pleas note that Pingvin Share is in early stage and could include some bugs
|
||||
|
||||
### Recommended installation
|
||||
|
||||
1. Download the `docker-compose.yml` file
|
||||
2. Run `docker-compose up -d`
|
||||
|
||||
The website is now listening available on `http://localhost:3000`, have fun with Pingvin Share 🐧!
|
||||
|
||||
### Additional resources
|
||||
|
||||
- [Synology NAS installation](https://mariushosting.com/how-to-install-pingvin-share-on-your-synology-nas/)
|
||||
|
||||
### Upgrade to a new version
|
||||
|
||||
Run `docker compose pull && docker compose up -d` to update your docker container
|
||||
|
||||
7
SECURITY.md
Normal file
7
SECURITY.md
Normal file
@@ -0,0 +1,7 @@
|
||||
# Security Policy
|
||||
|
||||
## Supported Versions
|
||||
As Pingvin Share is in beta, older versions don't get security updates. Please consider to update Pingvin Share regularly. Updates can be automated with e.g [Watchtower](https://github.com/containrrr/watchtower).
|
||||
|
||||
## Reporting a Vulnerability
|
||||
Thank you for taking the time to report a vulnerability. Please DO NOT create an issue on GitHub because the vulnerability could get exploited. Instead please write an email to [elias@eliasschneider.com](mailto:elias@eliasschneider.com).
|
||||
177
backend/package-lock.json
generated
177
backend/package-lock.json
generated
@@ -60,7 +60,8 @@
|
||||
"ts-loader": "^9.4.2",
|
||||
"ts-node": "^10.9.1",
|
||||
"tsconfig-paths": "4.1.1",
|
||||
"typescript": "^4.9.3"
|
||||
"typescript": "^4.9.3",
|
||||
"wait-on": "^6.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@angular-devkit/core": {
|
||||
@@ -390,6 +391,21 @@
|
||||
"integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@hapi/hoek": {
|
||||
"version": "9.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz",
|
||||
"integrity": "sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@hapi/topo": {
|
||||
"version": "5.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-5.1.0.tgz",
|
||||
"integrity": "sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@hapi/hoek": "^9.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@humanwhocodes/config-array": {
|
||||
"version": "0.11.7",
|
||||
"resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.7.tgz",
|
||||
@@ -1027,6 +1043,27 @@
|
||||
"integrity": "sha512-Bd4LZ+WAnUHOq31e9X/ihi5zPlr4SzTRwUZZYxvWOxlerIZ7HJlVa9zXpuKTKLpI9O1l8Ec4OYCKsivWCs5a3Q==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@sideway/address": {
|
||||
"version": "4.1.4",
|
||||
"resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.4.tgz",
|
||||
"integrity": "sha512-7vwq+rOHVWjyXxVlR76Agnvhy8I9rpzjosTESvmhNeXOXdZZB15Fl+TI9x1SiHZH5Jv2wTGduSxFDIaq0m3DUw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@hapi/hoek": "^9.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@sideway/formula": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@sideway/formula/-/formula-3.0.0.tgz",
|
||||
"integrity": "sha512-vHe7wZ4NOXVfkoRb8T5otiENVlT7a3IAiw7H5M2+GO+9CDgcVUUsX1zalAztCmwyOr2RUTGJdgB+ZvSVqmdHmg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@sideway/pinpoint": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@sideway/pinpoint/-/pinpoint-2.0.0.tgz",
|
||||
"integrity": "sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@tsconfig/node10": {
|
||||
"version": "1.0.9",
|
||||
"resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz",
|
||||
@@ -1983,6 +2020,15 @@
|
||||
"integrity": "sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/axios": {
|
||||
"version": "0.25.0",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-0.25.0.tgz",
|
||||
"integrity": "sha512-cD8FOb0tRH3uuEe6+evtAbgJtfxr7ly3fQjYcMcuPlgkwVS9xboaVIpcDV+cYQe+yGykgwZCs1pzjntcGa6l5g==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"follow-redirects": "^1.14.7"
|
||||
}
|
||||
},
|
||||
"node_modules/balanced-match": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
|
||||
@@ -3518,6 +3564,26 @@
|
||||
"integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/follow-redirects": {
|
||||
"version": "1.15.2",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz",
|
||||
"integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "individual",
|
||||
"url": "https://github.com/sponsors/RubenVerborgh"
|
||||
}
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=4.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"debug": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/forever-agent": {
|
||||
"version": "0.6.1",
|
||||
"resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz",
|
||||
@@ -4264,6 +4330,19 @@
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/joi": {
|
||||
"version": "17.7.0",
|
||||
"resolved": "https://registry.npmjs.org/joi/-/joi-17.7.0.tgz",
|
||||
"integrity": "sha512-1/ugc8djfn93rTE3WRKdCzGGt/EtiYKxITMO4Wiv6q5JL1gl9ePt4kBsl1S499nbosspfctIQTpYIhSmHA3WAg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@hapi/hoek": "^9.0.0",
|
||||
"@hapi/topo": "^5.0.0",
|
||||
"@sideway/address": "^4.1.3",
|
||||
"@sideway/formula": "^3.0.0",
|
||||
"@sideway/pinpoint": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/js-sdsl": {
|
||||
"version": "4.1.5",
|
||||
"resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.1.5.tgz",
|
||||
@@ -7134,6 +7213,25 @@
|
||||
"integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/wait-on": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/wait-on/-/wait-on-6.0.1.tgz",
|
||||
"integrity": "sha512-zht+KASY3usTY5u2LgaNqn/Cd8MukxLGjdcZxT2ns5QzDmTFc4XoWBgC+C/na+sMRZTuVygQoMYwdcVjHnYIVw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"axios": "^0.25.0",
|
||||
"joi": "^17.6.0",
|
||||
"lodash": "^4.17.21",
|
||||
"minimist": "^1.2.5",
|
||||
"rxjs": "^7.5.4"
|
||||
},
|
||||
"bin": {
|
||||
"wait-on": "bin/wait-on"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/watchpack": {
|
||||
"version": "2.4.0",
|
||||
"resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz",
|
||||
@@ -7704,6 +7802,21 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"@hapi/hoek": {
|
||||
"version": "9.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz",
|
||||
"integrity": "sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==",
|
||||
"dev": true
|
||||
},
|
||||
"@hapi/topo": {
|
||||
"version": "5.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-5.1.0.tgz",
|
||||
"integrity": "sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@hapi/hoek": "^9.0.0"
|
||||
}
|
||||
},
|
||||
"@humanwhocodes/config-array": {
|
||||
"version": "0.11.7",
|
||||
"resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.7.tgz",
|
||||
@@ -8138,6 +8251,27 @@
|
||||
"integrity": "sha512-Bd4LZ+WAnUHOq31e9X/ihi5zPlr4SzTRwUZZYxvWOxlerIZ7HJlVa9zXpuKTKLpI9O1l8Ec4OYCKsivWCs5a3Q==",
|
||||
"dev": true
|
||||
},
|
||||
"@sideway/address": {
|
||||
"version": "4.1.4",
|
||||
"resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.4.tgz",
|
||||
"integrity": "sha512-7vwq+rOHVWjyXxVlR76Agnvhy8I9rpzjosTESvmhNeXOXdZZB15Fl+TI9x1SiHZH5Jv2wTGduSxFDIaq0m3DUw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@hapi/hoek": "^9.0.0"
|
||||
}
|
||||
},
|
||||
"@sideway/formula": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@sideway/formula/-/formula-3.0.0.tgz",
|
||||
"integrity": "sha512-vHe7wZ4NOXVfkoRb8T5otiENVlT7a3IAiw7H5M2+GO+9CDgcVUUsX1zalAztCmwyOr2RUTGJdgB+ZvSVqmdHmg==",
|
||||
"dev": true
|
||||
},
|
||||
"@sideway/pinpoint": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@sideway/pinpoint/-/pinpoint-2.0.0.tgz",
|
||||
"integrity": "sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==",
|
||||
"dev": true
|
||||
},
|
||||
"@tsconfig/node10": {
|
||||
"version": "1.0.9",
|
||||
"resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz",
|
||||
@@ -8925,6 +9059,15 @@
|
||||
"integrity": "sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA==",
|
||||
"dev": true
|
||||
},
|
||||
"axios": {
|
||||
"version": "0.25.0",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-0.25.0.tgz",
|
||||
"integrity": "sha512-cD8FOb0tRH3uuEe6+evtAbgJtfxr7ly3fQjYcMcuPlgkwVS9xboaVIpcDV+cYQe+yGykgwZCs1pzjntcGa6l5g==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"follow-redirects": "^1.14.7"
|
||||
}
|
||||
},
|
||||
"balanced-match": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
|
||||
@@ -10090,6 +10233,12 @@
|
||||
"integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==",
|
||||
"dev": true
|
||||
},
|
||||
"follow-redirects": {
|
||||
"version": "1.15.2",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz",
|
||||
"integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==",
|
||||
"dev": true
|
||||
},
|
||||
"forever-agent": {
|
||||
"version": "0.6.1",
|
||||
"resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz",
|
||||
@@ -10637,6 +10786,19 @@
|
||||
"resolved": "https://registry.npmjs.org/iterare/-/iterare-1.2.1.tgz",
|
||||
"integrity": "sha512-RKYVTCjAnRthyJes037NX/IiqeidgN1xc3j1RjFfECFp28A1GVwK9nA+i0rJPaHqSZwygLzRnFlzUuHFoWWy+Q=="
|
||||
},
|
||||
"joi": {
|
||||
"version": "17.7.0",
|
||||
"resolved": "https://registry.npmjs.org/joi/-/joi-17.7.0.tgz",
|
||||
"integrity": "sha512-1/ugc8djfn93rTE3WRKdCzGGt/EtiYKxITMO4Wiv6q5JL1gl9ePt4kBsl1S499nbosspfctIQTpYIhSmHA3WAg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@hapi/hoek": "^9.0.0",
|
||||
"@hapi/topo": "^5.0.0",
|
||||
"@sideway/address": "^4.1.3",
|
||||
"@sideway/formula": "^3.0.0",
|
||||
"@sideway/pinpoint": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"js-sdsl": {
|
||||
"version": "4.1.5",
|
||||
"resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.1.5.tgz",
|
||||
@@ -12786,6 +12948,19 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"wait-on": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/wait-on/-/wait-on-6.0.1.tgz",
|
||||
"integrity": "sha512-zht+KASY3usTY5u2LgaNqn/Cd8MukxLGjdcZxT2ns5QzDmTFc4XoWBgC+C/na+sMRZTuVygQoMYwdcVjHnYIVw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"axios": "^0.25.0",
|
||||
"joi": "^17.6.0",
|
||||
"lodash": "^4.17.21",
|
||||
"minimist": "^1.2.5",
|
||||
"rxjs": "^7.5.4"
|
||||
}
|
||||
},
|
||||
"watchpack": {
|
||||
"version": "2.4.0",
|
||||
"resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz",
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
"prod": "prisma migrate deploy && prisma db seed && node dist/src/main",
|
||||
"lint": "eslint 'src/**/*.ts'",
|
||||
"format": "prettier --write 'src/**/*.ts'",
|
||||
"test:system": "prisma migrate reset -f && nest start & sleep 10 && newman run ./test/system/newman-system-tests.json"
|
||||
"test:system": "prisma migrate reset -f && nest start & wait-on http://localhost:8080/api/configs && newman run ./test/system/newman-system-tests.json"
|
||||
},
|
||||
"prisma": {
|
||||
"seed": "ts-node prisma/seed/config.seed.ts"
|
||||
@@ -22,6 +22,7 @@
|
||||
"@nestjs/platform-express": "^9.2.1",
|
||||
"@nestjs/schedule": "^2.1.0",
|
||||
"@nestjs/throttler": "^3.1.0",
|
||||
"@prisma/client": "^4.7.1",
|
||||
"archiver": "^5.3.1",
|
||||
"argon2": "^0.30.2",
|
||||
"class-transformer": "^0.5.1",
|
||||
@@ -36,13 +37,13 @@
|
||||
"passport-local": "^1.0.0",
|
||||
"reflect-metadata": "^0.1.13",
|
||||
"rimraf": "^3.0.2",
|
||||
"rxjs": "^7.6.0"
|
||||
"rxjs": "^7.6.0",
|
||||
"ts-node": "^10.9.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@nestjs/cli": "^9.1.5",
|
||||
"@nestjs/schematics": "^9.0.3",
|
||||
"@nestjs/testing": "^9.2.1",
|
||||
"@prisma/client": "^4.7.1",
|
||||
"@types/archiver": "^5.3.1",
|
||||
"@types/cron": "^2.0.0",
|
||||
"@types/express": "^4.17.14",
|
||||
@@ -63,8 +64,8 @@
|
||||
"prisma": "^4.7.1",
|
||||
"source-map-support": "^0.5.21",
|
||||
"ts-loader": "^9.4.2",
|
||||
"ts-node": "^10.9.1",
|
||||
"tsconfig-paths": "4.1.1",
|
||||
"typescript": "^4.9.3"
|
||||
"typescript": "^4.9.3",
|
||||
"wait-on": "^6.0.1"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
-- RedefineTables
|
||||
PRAGMA foreign_keys=OFF;
|
||||
CREATE TABLE "new_Config" (
|
||||
"updatedAt" DATETIME NOT NULL,
|
||||
"key" TEXT NOT NULL PRIMARY KEY,
|
||||
"type" TEXT NOT NULL,
|
||||
"value" TEXT NOT NULL,
|
||||
"description" TEXT NOT NULL,
|
||||
"obscured" BOOLEAN NOT NULL DEFAULT false,
|
||||
"secret" BOOLEAN NOT NULL DEFAULT true,
|
||||
"locked" BOOLEAN NOT NULL DEFAULT false
|
||||
);
|
||||
INSERT INTO "new_Config" ("description", "key", "locked", "secret", "type", "updatedAt", "value") SELECT "description", "key", "locked", "secret", "type", "updatedAt", "value" FROM "Config";
|
||||
DROP TABLE "Config";
|
||||
ALTER TABLE "new_Config" RENAME TO "Config";
|
||||
PRAGMA foreign_key_check;
|
||||
PRAGMA foreign_keys=ON;
|
||||
@@ -0,0 +1,2 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE "Share" ADD COLUMN "description" TEXT;
|
||||
@@ -39,6 +39,7 @@ model Share {
|
||||
isZipReady Boolean @default(false)
|
||||
views Int @default(0)
|
||||
expiration DateTime
|
||||
description String?
|
||||
|
||||
creatorId String?
|
||||
creator User? @relation(fields: [creatorId], references: [id], onDelete: Cascade)
|
||||
@@ -84,6 +85,7 @@ model Config {
|
||||
type String
|
||||
value String
|
||||
description String
|
||||
obscured Boolean @default(false)
|
||||
secret Boolean @default(true)
|
||||
locked Boolean @default(false)
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { PrismaClient } from "@prisma/client";
|
||||
import { Prisma, PrismaClient } from "@prisma/client";
|
||||
import * as crypto from "crypto";
|
||||
|
||||
const configVariables = [
|
||||
const configVariables: Prisma.ConfigCreateInput[] = [
|
||||
{
|
||||
key: "SETUP_FINISHED",
|
||||
description: "Whether the setup has been finished",
|
||||
@@ -55,7 +55,7 @@ const configVariables = [
|
||||
{
|
||||
key: "ENABLE_EMAIL_RECIPIENTS",
|
||||
description:
|
||||
"Whether to send emails to recipients. Only set this to true if you entered the host, port, email and password of your SMTP server.",
|
||||
"Whether to send emails to recipients. Only set this to true if you entered the host, port, email, user and password of your SMTP server.",
|
||||
type: "boolean",
|
||||
value: "false",
|
||||
secret: false,
|
||||
@@ -74,7 +74,13 @@ const configVariables = [
|
||||
},
|
||||
{
|
||||
key: "SMTP_EMAIL",
|
||||
description: "Email address of the SMTP server",
|
||||
description: "Email address which the emails get sent from",
|
||||
type: "string",
|
||||
value: "",
|
||||
},
|
||||
{
|
||||
key: "SMTP_USERNAME",
|
||||
description: "Username of the SMTP server",
|
||||
type: "string",
|
||||
value: "",
|
||||
},
|
||||
@@ -83,6 +89,7 @@ const configVariables = [
|
||||
description: "Password of the SMTP server",
|
||||
type: "string",
|
||||
value: "",
|
||||
obscured: true,
|
||||
},
|
||||
];
|
||||
|
||||
@@ -102,14 +109,34 @@ async function main() {
|
||||
}
|
||||
}
|
||||
|
||||
// Delete the config variable if it doesn't exist anymore
|
||||
const configVariablesFromDatabase = await prisma.config.findMany();
|
||||
|
||||
// Delete the config variable if it doesn't exist anymore
|
||||
for (const configVariableFromDatabase of configVariablesFromDatabase) {
|
||||
if (!configVariables.find((v) => v.key == configVariableFromDatabase.key)) {
|
||||
const configVariable = configVariables.find(
|
||||
(v) => v.key == configVariableFromDatabase.key
|
||||
);
|
||||
if (!configVariable) {
|
||||
await prisma.config.delete({
|
||||
where: { key: configVariableFromDatabase.key },
|
||||
});
|
||||
|
||||
// Update the config variable if the metadata changed
|
||||
} else if (
|
||||
JSON.stringify({
|
||||
...configVariable,
|
||||
key: configVariableFromDatabase.key,
|
||||
value: configVariableFromDatabase.value,
|
||||
}) != JSON.stringify(configVariableFromDatabase)
|
||||
) {
|
||||
await prisma.config.update({
|
||||
where: { key: configVariableFromDatabase.key },
|
||||
data: {
|
||||
...configVariable,
|
||||
key: configVariableFromDatabase.key,
|
||||
value: configVariableFromDatabase.value,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ import { FileModule } from "./file/file.module";
|
||||
import { PrismaModule } from "./prisma/prisma.module";
|
||||
import { ShareModule } from "./share/share.module";
|
||||
import { UserModule } from "./user/user.module";
|
||||
import { JobsModule } from "./jobs/jobs.module";
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
@@ -22,6 +23,7 @@ import { UserModule } from "./user/user.module";
|
||||
EmailModule,
|
||||
PrismaModule,
|
||||
ConfigModule,
|
||||
JobsModule,
|
||||
UserModule,
|
||||
MulterModule.registerAsync({
|
||||
useFactory: (config: ConfigService) => ({
|
||||
|
||||
@@ -11,6 +11,9 @@ export class AdminConfigDTO extends ConfigDTO {
|
||||
@Expose()
|
||||
description: string;
|
||||
|
||||
@Expose()
|
||||
obscured: boolean;
|
||||
|
||||
from(partial: Partial<AdminConfigDTO>) {
|
||||
return plainToClass(AdminConfigDTO, partial, {
|
||||
excludeExtraneousValues: true,
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { IsNotEmpty } from "class-validator";
|
||||
import { IsNotEmpty, ValidateIf } from "class-validator";
|
||||
|
||||
class UpdateConfigDTO {
|
||||
@IsNotEmpty()
|
||||
@ValidateIf((dto) => dto.value !== "")
|
||||
value: string | number | boolean;
|
||||
}
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ export class EmailService {
|
||||
port: parseInt(this.config.get("SMTP_PORT")),
|
||||
secure: parseInt(this.config.get("SMTP_PORT")) == 465,
|
||||
auth: {
|
||||
user: this.config.get("SMTP_EMAIL"),
|
||||
user: this.config.get("SMTP_USERNAME"),
|
||||
pass: this.config.get("SMTP_PASSWORD"),
|
||||
},
|
||||
});
|
||||
@@ -28,7 +28,7 @@ export class EmailService {
|
||||
from: `"Pingvin Share" <${this.config.get("SMTP_EMAIL")}>`,
|
||||
to: recipientEmail,
|
||||
subject: "Files shared with you",
|
||||
text: `Hey!\n${creator.username} shared some files with you. View or dowload the files with this link: ${shareUrl}.\n Shared securely with Pingvin Share 🐧`,
|
||||
text: `Hey!\n${creator.username} shared some files with you. View or dowload the files with this link: ${shareUrl}\nShared securely with Pingvin Share 🐧`,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -79,7 +79,7 @@ export class FileController {
|
||||
const zip = this.fileService.getZip(shareId);
|
||||
res.set({
|
||||
"Content-Type": "application/zip",
|
||||
"Content-Disposition": `attachment ; filename="pingvin-share-${shareId}"`,
|
||||
"Content-Disposition": `attachment ; filename="pingvin-share-${shareId}.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 { ConfigService } from "@nestjs/config";
|
||||
import { PrismaClient } from "@prisma/client";
|
||||
|
||||
@Injectable()
|
||||
@@ -8,7 +7,7 @@ export class PrismaService extends PrismaClient {
|
||||
super({
|
||||
datasources: {
|
||||
db: {
|
||||
url: "file:../data/pingvin-share.db",
|
||||
url: "file:../data/pingvin-share.db?connection_limit=1",
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import { Type } from "class-transformer";
|
||||
import {
|
||||
IsEmail,
|
||||
IsOptional,
|
||||
IsString,
|
||||
Length,
|
||||
Matches,
|
||||
MaxLength,
|
||||
ValidateNested,
|
||||
} from "class-validator";
|
||||
import { ShareSecurityDTO } from "./shareSecurity.dto";
|
||||
@@ -19,6 +21,10 @@ export class CreateShareDTO {
|
||||
@IsString()
|
||||
expiration: string;
|
||||
|
||||
@MaxLength(512)
|
||||
@IsOptional()
|
||||
description: string;
|
||||
|
||||
@IsEmail({}, { each: true })
|
||||
recipients: string[];
|
||||
|
||||
|
||||
@@ -17,6 +17,9 @@ export class ShareDTO {
|
||||
@Type(() => PublicUserDTO)
|
||||
creator: PublicUserDTO;
|
||||
|
||||
@Expose()
|
||||
description: string;
|
||||
|
||||
from(partial: Partial<ShareDTO>) {
|
||||
return plainToClass(ShareDTO, partial, { excludeExtraneousValues: true });
|
||||
}
|
||||
|
||||
@@ -105,9 +105,10 @@ export class ShareService {
|
||||
);
|
||||
|
||||
// Asynchronously create a zip of all files
|
||||
this.createZip(id).then(() =>
|
||||
this.prisma.share.update({ where: { id }, data: { isZipReady: true } })
|
||||
);
|
||||
if (share.files.length > 1)
|
||||
this.createZip(id).then(() =>
|
||||
this.prisma.share.update({ where: { id }, data: { isZipReady: true } })
|
||||
);
|
||||
|
||||
// Send email for each recepient
|
||||
for (const recepient of share.recipients) {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { PickType } from "@nestjs/mapped-types";
|
||||
import { UserDTO } from "./user.dto";
|
||||
|
||||
export class PublicUserDTO extends PickType(UserDTO, ["email"] as const) {}
|
||||
export class PublicUserDTO extends PickType(UserDTO, ["username"] as const) {}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"info": {
|
||||
"_postman_id": "243b0832-3a6a-4389-bb71-4d988c0a86d9",
|
||||
"_postman_id": "84a95987-2997-429a-aba6-d38289b0b76a",
|
||||
"name": "Pingvin Share Testing",
|
||||
"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json",
|
||||
"_exporter_id": "17822132"
|
||||
@@ -431,7 +431,7 @@
|
||||
" const responseBody = pm.response.json();",
|
||||
" pm.expect(responseBody).to.have.property(\"id\")",
|
||||
" pm.expect(responseBody).to.have.property(\"expiration\")",
|
||||
" pm.expect(Object.keys(responseBody).length).be.equal(2)",
|
||||
" pm.expect(Object.keys(responseBody).length).be.equal(3)",
|
||||
"});",
|
||||
""
|
||||
],
|
||||
@@ -517,6 +517,60 @@
|
||||
},
|
||||
"response": []
|
||||
},
|
||||
{
|
||||
"name": "Upload file 2",
|
||||
"event": [
|
||||
{
|
||||
"listen": "test",
|
||||
"script": {
|
||||
"exec": [
|
||||
"pm.test(\"Status code is 201\", () => {",
|
||||
" pm.response.to.have.status(201);",
|
||||
"});",
|
||||
"",
|
||||
"pm.test(\"Response body correct\", () => {",
|
||||
" const responseBody = pm.response.json();",
|
||||
" pm.expect(responseBody).to.have.property(\"id\")",
|
||||
" pm.expect(Object.keys(responseBody).length).be.equal(1)",
|
||||
"});"
|
||||
],
|
||||
"type": "text/javascript"
|
||||
}
|
||||
}
|
||||
],
|
||||
"request": {
|
||||
"method": "POST",
|
||||
"header": [],
|
||||
"body": {
|
||||
"mode": "formdata",
|
||||
"formdata": [
|
||||
{
|
||||
"key": "file",
|
||||
"type": "file",
|
||||
"src": "./test/system/test-file.txt"
|
||||
}
|
||||
]
|
||||
},
|
||||
"url": {
|
||||
"raw": "{{API_URL}}/shares/:shareId/files",
|
||||
"host": [
|
||||
"{{API_URL}}"
|
||||
],
|
||||
"path": [
|
||||
"shares",
|
||||
":shareId",
|
||||
"files"
|
||||
],
|
||||
"variable": [
|
||||
{
|
||||
"key": "shareId",
|
||||
"value": "test-share"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"response": []
|
||||
},
|
||||
{
|
||||
"name": "Complete share",
|
||||
"event": [
|
||||
@@ -532,7 +586,7 @@
|
||||
" const responseBody = pm.response.json();",
|
||||
" pm.expect(responseBody).to.have.property(\"id\")",
|
||||
" pm.expect(responseBody).to.have.property(\"expiration\")",
|
||||
" pm.expect(Object.keys(responseBody).length).be.equal(2)",
|
||||
" pm.expect(Object.keys(responseBody).length).be.equal(3)",
|
||||
"});",
|
||||
""
|
||||
],
|
||||
@@ -942,9 +996,9 @@
|
||||
" pm.response.to.have.status(200);",
|
||||
"});",
|
||||
"",
|
||||
"pm.test(\"Response contains 1 file\", () => {",
|
||||
"pm.test(\"Response contains 2 files\", () => {",
|
||||
" const responseBody = pm.response.json();",
|
||||
" pm.expect(responseBody.files.length).be.equal(1)",
|
||||
" pm.expect(responseBody.files.length).be.equal(2)",
|
||||
"});",
|
||||
"",
|
||||
"",
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
/** @type {import('next').NextConfig} */
|
||||
|
||||
|
||||
const withPWA = require("next-pwa")({
|
||||
dest: "public",
|
||||
disable: process.env.NODE_ENV == "development"
|
||||
disable: process.env.NODE_ENV == "development",
|
||||
});
|
||||
|
||||
|
||||
module.exports = withPWA();
|
||||
module.exports = withPWA({ output: "standalone" });
|
||||
|
||||
83
frontend/package-lock.json
generated
83
frontend/package-lock.json
generated
@@ -26,6 +26,7 @@
|
||||
"next-cookies": "^2.0.3",
|
||||
"next-http-proxy-middleware": "^1.2.5",
|
||||
"next-pwa": "^5.6.0",
|
||||
"p-limit": "^4.0.0",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-icons": "^4.7.1",
|
||||
@@ -6161,6 +6162,35 @@
|
||||
}
|
||||
},
|
||||
"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",
|
||||
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
|
||||
"integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==",
|
||||
@@ -6175,14 +6205,11 @@
|
||||
"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==",
|
||||
"node_modules/p-locate/node_modules/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,
|
||||
"dependencies": {
|
||||
"p-limit": "^3.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
@@ -8049,12 +8076,11 @@
|
||||
}
|
||||
},
|
||||
"node_modules/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,
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.0.0.tgz",
|
||||
"integrity": "sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==",
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
"node": ">=12.20"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
@@ -12482,12 +12508,11 @@
|
||||
}
|
||||
},
|
||||
"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,
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-4.0.0.tgz",
|
||||
"integrity": "sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==",
|
||||
"requires": {
|
||||
"yocto-queue": "^0.1.0"
|
||||
"yocto-queue": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"p-locate": {
|
||||
@@ -12497,6 +12522,23 @@
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"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": {
|
||||
@@ -13865,10 +13907,9 @@
|
||||
"integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg=="
|
||||
},
|
||||
"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
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.0.0.tgz",
|
||||
"integrity": "sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g=="
|
||||
},
|
||||
"yup": {
|
||||
"version": "0.32.11",
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
"next-cookies": "^2.0.3",
|
||||
"next-http-proxy-middleware": "^1.2.5",
|
||||
"next-pwa": "^5.6.0",
|
||||
"p-limit": "^4.0.0",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"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 { useEffect, useState } from "react";
|
||||
import { TbEdit, TbLock } from "react-icons/tb";
|
||||
@@ -43,50 +51,55 @@ const AdminConfigTable = () => {
|
||||
));
|
||||
|
||||
return (
|
||||
<Table verticalSpacing="sm" horizontalSpacing="xl" withBorder>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Key</th>
|
||||
<th>Value</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{isLoading
|
||||
? skeletonRows
|
||||
: configVariables.map((element) => (
|
||||
<tr key={element.key}>
|
||||
<td style={{ maxWidth: "200px" }}>
|
||||
<Code>{element.key}</Code> {element.secret && <TbLock />}{" "}
|
||||
<br />
|
||||
<Text size="xs" color="dimmed">
|
||||
{element.description}
|
||||
</Text>
|
||||
</td>
|
||||
<td>{element.value}</td>
|
||||
|
||||
<td>
|
||||
<Group position="right">
|
||||
<ActionIcon
|
||||
color="primary"
|
||||
variant="light"
|
||||
size={25}
|
||||
onClick={() =>
|
||||
showUpdateConfigVariableModal(
|
||||
modals,
|
||||
element,
|
||||
getConfigVariables
|
||||
)
|
||||
}
|
||||
>
|
||||
<TbEdit />
|
||||
</ActionIcon>
|
||||
</Group>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</Table>
|
||||
<Box sx={{ display: "block", overflowX: "auto" }}>
|
||||
<Table verticalSpacing="sm" horizontalSpacing="xl" withBorder>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Key</th>
|
||||
<th>Value</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{isLoading
|
||||
? skeletonRows
|
||||
: configVariables.map((configVariable) => (
|
||||
<tr key={configVariable.key}>
|
||||
<td style={{ maxWidth: "200px" }}>
|
||||
<Code>{configVariable.key}</Code>{" "}
|
||||
{configVariable.secret && <TbLock />} <br />
|
||||
<Text size="xs" color="dimmed">
|
||||
{configVariable.description}
|
||||
</Text>
|
||||
</td>
|
||||
<td>
|
||||
{configVariable.obscured
|
||||
? "•".repeat(configVariable.value.length)
|
||||
: configVariable.value}
|
||||
</td>
|
||||
<td>
|
||||
<Group position="right">
|
||||
<ActionIcon
|
||||
color="primary"
|
||||
variant="light"
|
||||
size={25}
|
||||
onClick={() =>
|
||||
showUpdateConfigVariableModal(
|
||||
modals,
|
||||
configVariable,
|
||||
getConfigVariables
|
||||
)
|
||||
}
|
||||
>
|
||||
<TbEdit />
|
||||
</ActionIcon>
|
||||
</Group>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</Table>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -18,8 +18,8 @@ const ManageUserTable = ({
|
||||
const modals = useModals();
|
||||
|
||||
return (
|
||||
<Box sx={{ display: "block", overflowX: "auto", whiteSpace: "nowrap" }}>
|
||||
<Table verticalSpacing="sm" highlightOnHover>
|
||||
<Box sx={{ display: "block", overflowX: "auto" }}>
|
||||
<Table verticalSpacing="sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Username</th>
|
||||
|
||||
@@ -50,7 +50,6 @@ const Body = ({
|
||||
<Stack>
|
||||
<form
|
||||
onSubmit={form.onSubmit(async (values) => {
|
||||
console.log(values);
|
||||
userService
|
||||
.create(values)
|
||||
.then(() => {
|
||||
@@ -62,10 +61,7 @@ const Body = ({
|
||||
>
|
||||
<Stack>
|
||||
<TextInput label="Username" {...form.getInputProps("username")} />
|
||||
<TextInput
|
||||
label="Email"
|
||||
{...form.getInputProps("email")}
|
||||
/>
|
||||
<TextInput label="Email" {...form.getInputProps("email")} />
|
||||
<PasswordInput
|
||||
label="New password"
|
||||
{...form.getInputProps("password")}
|
||||
|
||||
@@ -2,6 +2,7 @@ import {
|
||||
Button,
|
||||
Code,
|
||||
NumberInput,
|
||||
PasswordInput,
|
||||
Select,
|
||||
Space,
|
||||
Stack,
|
||||
@@ -53,9 +54,12 @@ const Body = ({
|
||||
<Text>
|
||||
Set <Code>{configVariable.key}</Code> to
|
||||
</Text>
|
||||
{configVariable.type == "string" && (
|
||||
<TextInput label="Value" {...form.getInputProps("stringValue")} />
|
||||
)}
|
||||
{configVariable.type == "string" &&
|
||||
(configVariable.obscured ? (
|
||||
<PasswordInput label="Value" {...form.getInputProps("stringValue")} />
|
||||
) : (
|
||||
<TextInput label="Value" {...form.getInputProps("stringValue")} />
|
||||
))}
|
||||
{configVariable.type == "number" && (
|
||||
<NumberInput label="Value" {...form.getInputProps("numberValue")} />
|
||||
)}
|
||||
|
||||
@@ -79,10 +79,7 @@ const Body = ({
|
||||
label="Username"
|
||||
{...accountForm.getInputProps("username")}
|
||||
/>
|
||||
<TextInput
|
||||
label="Email"
|
||||
{...accountForm.getInputProps("email")}
|
||||
/>
|
||||
<TextInput label="Email" {...accountForm.getInputProps("email")} />
|
||||
<Switch
|
||||
mt="xs"
|
||||
labelPosition="left"
|
||||
|
||||
@@ -9,35 +9,10 @@ const FileList = ({
|
||||
shareId,
|
||||
isLoading,
|
||||
}: {
|
||||
files: any[];
|
||||
files?: any[];
|
||||
shareId: string;
|
||||
isLoading: boolean;
|
||||
}) => {
|
||||
const rows = files.map((file) => (
|
||||
<tr key={file.name}>
|
||||
<td>{file.name}</td>
|
||||
<td>{byteStringToHumanSizeString(file.size)}</td>
|
||||
<td>
|
||||
{file.uploadingState ? (
|
||||
file.uploadingState != "finished" ? (
|
||||
<Loader size={22} />
|
||||
) : (
|
||||
<TbCircleCheck color="green" size={22} />
|
||||
)
|
||||
) : (
|
||||
<ActionIcon
|
||||
size={25}
|
||||
onClick={async () => {
|
||||
await shareService.downloadFile(shareId, file.id);
|
||||
}}
|
||||
>
|
||||
<TbDownload />
|
||||
</ActionIcon>
|
||||
)}
|
||||
</td>
|
||||
</tr>
|
||||
));
|
||||
|
||||
return (
|
||||
<Table>
|
||||
<thead>
|
||||
@@ -47,7 +22,34 @@ const FileList = ({
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>{isLoading ? skeletonRows : rows}</tbody>
|
||||
<tbody>
|
||||
{isLoading
|
||||
? skeletonRows
|
||||
: files!.map((file) => (
|
||||
<tr key={file.name}>
|
||||
<td>{file.name}</td>
|
||||
<td>{byteStringToHumanSizeString(file.size)}</td>
|
||||
<td>
|
||||
{file.uploadingState ? (
|
||||
file.uploadingState != "finished" ? (
|
||||
<Loader size={22} />
|
||||
) : (
|
||||
<TbCircleCheck color="green" size={22} />
|
||||
)
|
||||
) : (
|
||||
<ActionIcon
|
||||
size={25}
|
||||
onClick={async () => {
|
||||
await shareService.downloadFile(shareId, file.id);
|
||||
}}
|
||||
>
|
||||
<TbDownload />
|
||||
</ActionIcon>
|
||||
)}
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</Table>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -40,14 +40,16 @@ const Body = ({ share }: { share: Share }) => {
|
||||
variant="filled"
|
||||
value={link}
|
||||
rightSection={
|
||||
<ActionIcon
|
||||
onClick={() => {
|
||||
clipboard.copy(link);
|
||||
toast.success("Your link was copied to the keyboard.");
|
||||
}}
|
||||
>
|
||||
<TbCopy />
|
||||
</ActionIcon>
|
||||
window.isSecureContext && (
|
||||
<ActionIcon
|
||||
onClick={() => {
|
||||
clipboard.copy(link);
|
||||
toast.success("Your link was copied to the keyboard.");
|
||||
}}
|
||||
>
|
||||
<TbCopy />
|
||||
</ActionIcon>
|
||||
)
|
||||
}
|
||||
/>
|
||||
<Text
|
||||
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
Select,
|
||||
Stack,
|
||||
Text,
|
||||
Textarea,
|
||||
TextInput,
|
||||
Title,
|
||||
} from "@mantine/core";
|
||||
@@ -22,7 +23,7 @@ import { useState } from "react";
|
||||
import { TbAlertCircle } from "react-icons/tb";
|
||||
import * as yup from "yup";
|
||||
import shareService from "../../../services/share.service";
|
||||
import { ShareSecurity } from "../../../types/share.type";
|
||||
import { CreateShare } from "../../../types/share.type";
|
||||
import ExpirationPreview from "../ExpirationPreview";
|
||||
|
||||
const showCreateUploadModal = (
|
||||
@@ -32,12 +33,7 @@ const showCreateUploadModal = (
|
||||
allowUnauthenticatedShares: boolean;
|
||||
enableEmailRecepients: boolean;
|
||||
},
|
||||
uploadCallback: (
|
||||
id: string,
|
||||
expiration: string,
|
||||
recipients: string[],
|
||||
security: ShareSecurity
|
||||
) => void
|
||||
uploadCallback: (createShare: CreateShare) => void
|
||||
) => {
|
||||
return modals.openModal({
|
||||
title: <Title order={4}>Share</Title>,
|
||||
@@ -54,12 +50,7 @@ const CreateUploadModalBody = ({
|
||||
uploadCallback,
|
||||
options,
|
||||
}: {
|
||||
uploadCallback: (
|
||||
id: string,
|
||||
expiration: string,
|
||||
recipients: string[],
|
||||
security: ShareSecurity
|
||||
) => void;
|
||||
uploadCallback: (createShare: CreateShare) => void;
|
||||
options: {
|
||||
isUserSignedIn: boolean;
|
||||
allowUnauthenticatedShares: boolean;
|
||||
@@ -88,6 +79,7 @@ const CreateUploadModalBody = ({
|
||||
recipients: [] as string[],
|
||||
password: undefined,
|
||||
maxViews: undefined,
|
||||
description: undefined,
|
||||
expiration_num: 1,
|
||||
expiration_unit: "-days",
|
||||
never_expires: false,
|
||||
@@ -116,9 +108,15 @@ const CreateUploadModalBody = ({
|
||||
const expiration = form.values.never_expires
|
||||
? "never"
|
||||
: form.values.expiration_num + form.values.expiration_unit;
|
||||
uploadCallback(values.link, expiration, values.recipients, {
|
||||
password: values.password,
|
||||
maxViews: values.maxViews,
|
||||
uploadCallback({
|
||||
id: values.link,
|
||||
expiration: expiration,
|
||||
recipients: values.recipients,
|
||||
description: values.description,
|
||||
security: {
|
||||
password: values.password,
|
||||
maxViews: values.maxViews,
|
||||
},
|
||||
});
|
||||
modals.closeAll();
|
||||
}
|
||||
@@ -228,6 +226,18 @@ const CreateUploadModalBody = ({
|
||||
{ExpirationPreview({ form })}
|
||||
</Text>
|
||||
<Accordion>
|
||||
<Accordion.Item value="description" sx={{ borderBottom: "none" }}>
|
||||
<Accordion.Control>Description</Accordion.Control>
|
||||
<Accordion.Panel>
|
||||
<Stack align="stretch">
|
||||
<Textarea
|
||||
variant="filled"
|
||||
placeholder="Note for the recepients"
|
||||
{...form.getInputProps("description")}
|
||||
/>
|
||||
</Stack>
|
||||
</Accordion.Panel>
|
||||
</Accordion.Item>
|
||||
{options.enableEmailRecepients && (
|
||||
<Accordion.Item value="recipients" sx={{ borderBottom: "none" }}>
|
||||
<Accordion.Control>Email recipients</Accordion.Control>
|
||||
@@ -258,6 +268,7 @@ const CreateUploadModalBody = ({
|
||||
</Accordion.Panel>
|
||||
</Accordion.Item>
|
||||
)}
|
||||
|
||||
<Accordion.Item value="security" sx={{ borderBottom: "none" }}>
|
||||
<Accordion.Control>Security options</Accordion.Control>
|
||||
<Accordion.Panel>
|
||||
|
||||
@@ -73,7 +73,7 @@ function App({ Component, pageProps }: AppProps) {
|
||||
<LoadingOverlay visible overlayOpacity={1} />
|
||||
) : (
|
||||
<ConfigContext.Provider value={configVariables}>
|
||||
<UserContext.Provider value={user}>
|
||||
<UserContext.Provider value={user} >
|
||||
<LoadingOverlay visible={isLoading} overlayOpacity={1} />
|
||||
<Header />
|
||||
<Container>
|
||||
|
||||
@@ -80,10 +80,7 @@ const Account = () => {
|
||||
label="Username"
|
||||
{...accountForm.getInputProps("username")}
|
||||
/>
|
||||
<TextInput
|
||||
label="Email"
|
||||
{...accountForm.getInputProps("email")}
|
||||
/>
|
||||
<TextInput label="Email" {...accountForm.getInputProps("email")} />
|
||||
<Group position="right">
|
||||
<Button type="submit">Save</Button>
|
||||
</Group>
|
||||
|
||||
@@ -17,6 +17,7 @@ import Link from "next/link";
|
||||
import { useRouter } from "next/router";
|
||||
import { useEffect, useState } from "react";
|
||||
import { TbLink, TbTrash } from "react-icons/tb";
|
||||
import showShareLinkModal from "../../components/account/showShareLinkModal";
|
||||
import Meta from "../../components/Meta";
|
||||
import useUser from "../../hooks/user.hook";
|
||||
import shareService from "../../services/share.service";
|
||||
@@ -83,12 +84,16 @@ const MyShares = () => {
|
||||
variant="light"
|
||||
size={25}
|
||||
onClick={() => {
|
||||
clipboard.copy(
|
||||
`${window.location.origin}/share/${share.id}`
|
||||
);
|
||||
toast.success(
|
||||
"Your link was copied to the keyboard."
|
||||
);
|
||||
if (window.isSecureContext) {
|
||||
clipboard.copy(
|
||||
`${window.location.origin}/share/${share.id}`
|
||||
);
|
||||
toast.success(
|
||||
"Your link was copied to the keyboard."
|
||||
);
|
||||
} else {
|
||||
showShareLinkModal(modals, share.id);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<TbLink />
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Col, Container, createStyles, Grid, Paper, Text } from "@mantine/core";
|
||||
import { Col, createStyles, Grid, Paper, Text } from "@mantine/core";
|
||||
import Link from "next/link";
|
||||
import { TbSettings, TbUsers } from "react-icons/tb";
|
||||
|
||||
@@ -34,28 +34,26 @@ const Admin = () => {
|
||||
const { classes, theme } = useStyles();
|
||||
|
||||
return (
|
||||
<Container size="xl">
|
||||
<Paper withBorder radius="md" p={40}>
|
||||
<Grid mt="md">
|
||||
{managementOptions.map((item) => {
|
||||
return (
|
||||
<Col xs={6} key={item.route}>
|
||||
<Paper
|
||||
withBorder
|
||||
component={Link}
|
||||
href={item.route}
|
||||
key={item.title}
|
||||
className={classes.item}
|
||||
>
|
||||
<item.icon color={theme.colors.victoria[5]} size={35} />
|
||||
<Text mt={7}>{item.title}</Text>
|
||||
</Paper>
|
||||
</Col>
|
||||
);
|
||||
})}
|
||||
</Grid>
|
||||
</Paper>
|
||||
</Container>
|
||||
<Paper withBorder p={40}>
|
||||
<Grid mt="md">
|
||||
{managementOptions.map((item) => {
|
||||
return (
|
||||
<Col xs={6} key={item.route}>
|
||||
<Paper
|
||||
withBorder
|
||||
component={Link}
|
||||
href={item.route}
|
||||
key={item.title}
|
||||
className={classes.item}
|
||||
>
|
||||
<item.icon color={theme.colors.victoria[8]} size={35} />
|
||||
<Text mt={7}>{item.title}</Text>
|
||||
</Paper>
|
||||
</Col>
|
||||
);
|
||||
})}
|
||||
</Grid>
|
||||
</Paper>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -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 { useState } from "react";
|
||||
import AdminConfigTable from "../../components/admin/AdminConfigTable";
|
||||
@@ -28,7 +28,9 @@ const Setup = () => {
|
||||
<Logo height={80} width={80} />
|
||||
<Title order={2}>Welcome to Pingvin Share</Title>
|
||||
<Text>Let's customize Pingvin Share for you! </Text>
|
||||
<AdminConfigTable />
|
||||
<Box style={{ width: "100%" }}>
|
||||
<AdminConfigTable />
|
||||
</Box>
|
||||
<Button
|
||||
loading={isLoading}
|
||||
onClick={async () => {
|
||||
|
||||
@@ -10,7 +10,7 @@ const SignUp = () => {
|
||||
const router = useRouter();
|
||||
if (user) {
|
||||
router.replace("/");
|
||||
} else if (config.get("ALLOW_REGISTRATION") == "false") {
|
||||
} else if (!config.get("ALLOW_REGISTRATION")) {
|
||||
router.replace("/auth/signIn");
|
||||
} else {
|
||||
return (
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Group } from "@mantine/core";
|
||||
import { Box, Group, Text, Title } from "@mantine/core";
|
||||
import { useModals } from "@mantine/modals";
|
||||
import { GetServerSidePropsContext } from "next";
|
||||
import { useEffect, useState } from "react";
|
||||
@@ -8,6 +8,7 @@ import FileList from "../../components/share/FileList";
|
||||
import showEnterPasswordModal from "../../components/share/showEnterPasswordModal";
|
||||
import showErrorModal from "../../components/share/showErrorModal";
|
||||
import shareService from "../../services/share.service";
|
||||
import { Share as ShareType } from "../../types/share.type";
|
||||
|
||||
export function getServerSideProps(context: GetServerSidePropsContext) {
|
||||
return {
|
||||
@@ -17,7 +18,7 @@ export function getServerSideProps(context: GetServerSidePropsContext) {
|
||||
|
||||
const Share = ({ shareId }: { shareId: string }) => {
|
||||
const modals = useModals();
|
||||
const [fileList, setFileList] = useState<any[]>([]);
|
||||
const [share, setShare] = useState<ShareType>();
|
||||
|
||||
const getShareToken = async (password?: string) => {
|
||||
await shareService
|
||||
@@ -41,7 +42,7 @@ const Share = ({ shareId }: { shareId: string }) => {
|
||||
shareService
|
||||
.get(shareId)
|
||||
.then((share) => {
|
||||
setFileList(share.files);
|
||||
setShare(share);
|
||||
})
|
||||
.catch((e) => {
|
||||
const { error } = e.response.data;
|
||||
@@ -77,14 +78,16 @@ const Share = ({ shareId }: { shareId: string }) => {
|
||||
title={`Share ${shareId}`}
|
||||
description="Look what I've shared with you."
|
||||
/>
|
||||
<Group position="right" mb="lg">
|
||||
<DownloadAllButton shareId={shareId} />
|
||||
|
||||
<Group position="apart" mb="lg">
|
||||
<Box style={{ maxWidth: "70%" }}>
|
||||
<Title order={3}>{share?.id}</Title>
|
||||
<Text size="sm">{share?.description}</Text>
|
||||
</Box>
|
||||
{share?.files.length > 1 && <DownloadAllButton shareId={shareId} />}
|
||||
</Group>
|
||||
<FileList
|
||||
files={fileList}
|
||||
shareId={shareId}
|
||||
isLoading={fileList.length == 0}
|
||||
/>
|
||||
|
||||
<FileList files={share?.files} shareId={shareId} isLoading={!share} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -2,6 +2,7 @@ import { Button, Group } from "@mantine/core";
|
||||
import { useModals } from "@mantine/modals";
|
||||
import axios from "axios";
|
||||
import { useRouter } from "next/router";
|
||||
import pLimit from "p-limit";
|
||||
import { useEffect, useState } from "react";
|
||||
import Meta from "../components/Meta";
|
||||
import Dropzone from "../components/upload/Dropzone";
|
||||
@@ -12,10 +13,11 @@ import useConfig from "../hooks/config.hook";
|
||||
import useUser from "../hooks/user.hook";
|
||||
import shareService from "../services/share.service";
|
||||
import { FileUpload } from "../types/File.type";
|
||||
import { ShareSecurity } from "../types/share.type";
|
||||
import { CreateShare, Share } from "../types/share.type";
|
||||
import toast from "../utils/toast.util";
|
||||
|
||||
let share: any;
|
||||
let createdShare: Share;
|
||||
const promiseLimit = pLimit(3);
|
||||
|
||||
const Upload = () => {
|
||||
const router = useRouter();
|
||||
@@ -26,12 +28,7 @@ const Upload = () => {
|
||||
const [files, setFiles] = useState<FileUpload[]>([]);
|
||||
const [isUploading, setisUploading] = useState(false);
|
||||
|
||||
const uploadFiles = async (
|
||||
id: string,
|
||||
expiration: string,
|
||||
recipients: string[],
|
||||
security: ShareSecurity
|
||||
) => {
|
||||
const uploadFiles = async (share: CreateShare) => {
|
||||
setisUploading(true);
|
||||
try {
|
||||
setFiles((files) =>
|
||||
@@ -40,8 +37,10 @@ const Upload = () => {
|
||||
return file;
|
||||
})
|
||||
);
|
||||
share = await shareService.create(id, expiration, recipients, security);
|
||||
for (let i = 0; i < files.length; i++) {
|
||||
createdShare = await shareService.create(share);
|
||||
|
||||
const uploadPromises = files.map((file, i) => {
|
||||
// Callback to indicate current upload progress
|
||||
const progressCallBack = (progress: number) => {
|
||||
setFiles((files) => {
|
||||
return files.map((file, callbackIndex) => {
|
||||
@@ -54,11 +53,15 @@ const Upload = () => {
|
||||
};
|
||||
|
||||
try {
|
||||
await shareService.uploadFile(share.id, files[i], progressCallBack);
|
||||
return promiseLimit(() =>
|
||||
shareService.uploadFile(share.id, file, progressCallBack)
|
||||
);
|
||||
} catch {
|
||||
files[i].uploadingProgress = -1;
|
||||
file.uploadingProgress = -1;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
await Promise.all(uploadPromises);
|
||||
} catch (e) {
|
||||
if (axios.isAxiosError(e)) {
|
||||
toast.error(e.response?.data?.message ?? "An unkown error occured.");
|
||||
@@ -84,9 +87,9 @@ const Upload = () => {
|
||||
toast.error(`${fileErrorCount} file(s) failed to upload. Try again.`);
|
||||
} else {
|
||||
shareService
|
||||
.completeShare(share.id)
|
||||
.completeShare(createdShare.id)
|
||||
.then(() => {
|
||||
showCompletedUploadModal(modals, share);
|
||||
showCompletedUploadModal(modals, createdShare);
|
||||
setFiles([]);
|
||||
})
|
||||
.catch(() =>
|
||||
|
||||
@@ -1,19 +1,22 @@
|
||||
import {
|
||||
CreateShare,
|
||||
MyShare,
|
||||
Share,
|
||||
ShareMetaData,
|
||||
ShareSecurity,
|
||||
} from "../types/share.type";
|
||||
import api from "./api.service";
|
||||
|
||||
const create = async (
|
||||
id: string,
|
||||
expiration: string,
|
||||
recipients: string[],
|
||||
security?: ShareSecurity
|
||||
) => {
|
||||
return (await api.post("shares", { id, expiration, recipients, security }))
|
||||
.data;
|
||||
const create = async (share: CreateShare) => {
|
||||
const { id, expiration, recipients, security, description } = share;
|
||||
return (
|
||||
await api.post("shares", {
|
||||
id,
|
||||
expiration,
|
||||
recipients,
|
||||
security,
|
||||
description,
|
||||
})
|
||||
).data;
|
||||
};
|
||||
|
||||
const completeShare = async (id: string) => {
|
||||
|
||||
@@ -8,6 +8,7 @@ export type AdminConfig = Config & {
|
||||
updatedAt: Date;
|
||||
secret: boolean;
|
||||
description: string;
|
||||
obscured: boolean;
|
||||
};
|
||||
|
||||
export default Config;
|
||||
|
||||
@@ -4,9 +4,18 @@ export type Share = {
|
||||
id: string;
|
||||
files: any;
|
||||
creator: User;
|
||||
description?: string;
|
||||
expiration: Date;
|
||||
};
|
||||
|
||||
export type CreateShare = {
|
||||
id: string;
|
||||
description?: string;
|
||||
recipients: string[];
|
||||
expiration: string;
|
||||
security: ShareSecurity;
|
||||
};
|
||||
|
||||
export type ShareMetaData = {
|
||||
id: string;
|
||||
isZipReady: boolean;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "pingvin-share",
|
||||
"version": "0.3.2",
|
||||
"version": "0.3.6",
|
||||
"scripts": {
|
||||
"format": "cd frontend && npm run format && cd ../backend && npm run format",
|
||||
"lint": "cd frontend && npm run lint && cd ../backend && npm run lint",
|
||||
|
||||
Reference in New Issue
Block a user