Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f2e4019190 | ||
|
|
ffd4e43f11 | ||
|
|
0e5c673270 | ||
|
|
beece56327 | ||
|
|
a0d1d98e24 | ||
|
|
ca73ccf629 | ||
|
|
9f2097e788 |
19
CHANGELOG.md
19
CHANGELOG.md
@@ -1,3 +1,22 @@
|
||||
## [0.14.0](https://github.com/stonith404/pingvin-share/compare/v0.13.1...v0.14.0) (2023-04-01)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **share, config:** more variables, placeholder and reset default ([#132](https://github.com/stonith404/pingvin-share/issues/132)) ([beece56](https://github.com/stonith404/pingvin-share/commit/beece56327da141c222fd9f5259697df6db9347a))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* bool config variable can't be changed ([0e5c673](https://github.com/stonith404/pingvin-share/commit/0e5c67327092e4751208e559a2b0d5ee2b91b6e3))
|
||||
|
||||
### [0.13.1](https://github.com/stonith404/pingvin-share/compare/v0.13.0...v0.13.1) (2023-03-14)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* empty file can't be uploaded in chrome ([9f2097e](https://github.com/stonith404/pingvin-share/commit/9f2097e788dfb79c2f95085025934c3134a3eb38))
|
||||
|
||||
## [0.13.0](https://github.com/stonith404/pingvin-share/compare/v0.12.1...v0.13.0) (2023-03-14)
|
||||
|
||||
|
||||
|
||||
95
CONTRIBUTING.es.md
Normal file
95
CONTRIBUTING.es.md
Normal file
@@ -0,0 +1,95 @@
|
||||
*Leer esto en otro idioma: [Inglés](CONTRIBUTING.md), [Español](CONTRIBUTING.es.md)*
|
||||
|
||||
---
|
||||
|
||||
# Contribuyendo
|
||||
|
||||
¡Nos ❤️ encantaría que contribuyas a Pingvin Share y nos ayudes a hacerlo mejor! Todas las contribuciones son bienvenidas, incluyendo problemas, sugerencias, *pull requests* y más.
|
||||
|
||||
## Para comenzar
|
||||
|
||||
Si encontraste un error, tienes una sugerencia o algo más, simplemente crea un problema (issue) en GitHub y nos pondremos en contacto contigo 😊.
|
||||
|
||||
## Para hacer una Pull Request
|
||||
|
||||
Antes de enviar la pull request para su revisión, asegúrate de que:
|
||||
|
||||
- El nombre de la pull request sigue las [especificaciones de Commits Convencionales](https://www.conventionalcommits.org/):
|
||||
|
||||
`<tipo>[ámbito opcional]: <descripción>`
|
||||
|
||||
ejemplo:
|
||||
|
||||
```
|
||||
feat(share): agregar protección con contraseña
|
||||
```
|
||||
|
||||
Donde `tipo` puede ser:
|
||||
|
||||
- **feat** - es una nueva función
|
||||
- **doc** - cambios solo en la documentación
|
||||
- **fix** - una corrección de error
|
||||
- **refactor** - cambios en el código que no solucionan un error ni agregan una función
|
||||
|
||||
- Tu pull requests tiene una descripción detallada.
|
||||
|
||||
- Ejecutaste `npm run format` para formatear el código.
|
||||
|
||||
<details>
|
||||
<summary>¿No sabes como crear una pull request? Aprende cómo crear una pull request</summary>
|
||||
|
||||
1. Crea un fork del repositorio haciendo clic en el botón `Fork` en el repositorio de Pingvin Share.
|
||||
|
||||
2. Clona tu fork en tu máquina con `git clone`.
|
||||
|
||||
```
|
||||
$ git clone https://github.com/[your_username]/pingvin-share
|
||||
```
|
||||
|
||||
3. Trabajar - hacer commit - repetir
|
||||
|
||||
4. Haz un `push` de tus cambios a GitHub.
|
||||
|
||||
```
|
||||
$ git push origin [nombre_de_tu_nueva_rama]
|
||||
```
|
||||
|
||||
5. Envía tus cambios para su revisión. Si vas a tu repositorio en GitHub, verás un botón `Comparar y crear pull requests`. Haz clic en ese botón.
|
||||
6. Inicia una Pull Request
|
||||
7. Ahora envía la pull requests y haz clic en `Crear pull requests`
|
||||
8. Espera a que alguien revise tu solicitud y apruebe o rechace tus cambios. Puedes ver los comentarios en la página de la solicitud en GitHub.
|
||||
|
||||
</details>
|
||||
|
||||
## Instalación del proyecto
|
||||
|
||||
Pingvin Share consiste de un frontend y un backend.
|
||||
|
||||
### Backend
|
||||
|
||||
El backend está hecho con [Nest.js](https://nestjs.com) y usa Typescript.
|
||||
|
||||
#### Instalación
|
||||
|
||||
1. Abrimos la carpeta `backend`
|
||||
2. Instalamos las dependencias con `npm install`
|
||||
3. Haz un `push` del esquema de la base de datos a la base de datos ejecutando `npx prisma db push`
|
||||
4. Rellena la base de datos ejecutando `npx prisma db seed`
|
||||
5. Inicia el backend con `npm run dev`
|
||||
|
||||
### Frontend
|
||||
|
||||
El frontend está hecho con [Next.js](https://nextjs.org) y usa Typescript.
|
||||
|
||||
#### Instalación
|
||||
|
||||
1. Primero inicia el backend
|
||||
2. Abre la carpeta `frontend`
|
||||
3. Instala las dependencias con `npm install`
|
||||
4. Inicia el frontend con `npm run dev`
|
||||
|
||||
¡Ya está todo listo!
|
||||
|
||||
### Testing
|
||||
|
||||
Por el momento, solo tenemos pruebas para el backend. Para ejecutar estas pruebas, debes ejecutar el comando `npm run test:system` en la carpeta del backend.
|
||||
@@ -1,3 +1,7 @@
|
||||
*Read this in another language: [Spanish](CONTRIBUTING.es.md), [English](CONTRIBUTING.md)*
|
||||
|
||||
---
|
||||
|
||||
# Contributing
|
||||
|
||||
We would ❤️ for you to contribute to Pingvin Share and help make it better! All contributions are welcome, including issues, suggestions, pull requests and more.
|
||||
|
||||
129
README.es.md
Normal file
129
README.es.md
Normal file
@@ -0,0 +1,129 @@
|
||||
|
||||
# <div align="center"><img src="https://user-images.githubusercontent.com/58886915/166198400-c2134044-1198-4647-a8b6-da9c4a204c68.svg" width="40"/> </br>Pingvin Share</div>
|
||||
|
||||
---
|
||||
|
||||
*Leer esto en otro idioma: [Inglés](README.md), [Español](README.es.md)*
|
||||
|
||||
---
|
||||
|
||||
Pingvin Share es una plataforma de intercambio de archivos autoalojada y una alternativa a WeTransfer.
|
||||
|
||||
## ✨ Características
|
||||
|
||||
- Compartir archivos utilizando un enlace
|
||||
- Tamaño de archivo ilimitado (unicamente restringido por el espacio en disco)
|
||||
- Establecer una fecha de caducidad para los recursos compartidos
|
||||
- Uso compartido seguro con límites de visitantes y contraseñas
|
||||
- Destinatarios de correo electrónico
|
||||
- Integración con ClamAV para escaneos de seguridad
|
||||
|
||||
## 🐧 Conoce Pingvin Share
|
||||
|
||||
- [Demo](https://pingvin-share.dev.eliasschneider.com)
|
||||
- [Reseña por DB Tech](https://www.youtube.com/watch?v=rWwNeZCOPJA)
|
||||
|
||||
<img src="https://user-images.githubusercontent.com/58886915/225038319-b2ef742c-3a74-4eb6-9689-4207a36842a4.png" width="700"/>
|
||||
|
||||
## ⌨️ Instalación
|
||||
|
||||
> Nota: Pingvin Share está en sus primeras etapas y puede contener errores.
|
||||
|
||||
### Instalación con Docker (recomendada)
|
||||
|
||||
1. Descarge el archivo `docker-compose.yml`
|
||||
2. Ejecute `docker-compose up -d`
|
||||
|
||||
El sitio web ahora está esperando conexiones en `http://localhost:3000`, ¡diviértase usando Pingvin Share 🐧!
|
||||
|
||||
### Instalación autónoma
|
||||
|
||||
Herramientas requeridas:
|
||||
|
||||
- [Node.js](https://nodejs.org/en/download/) >= 16
|
||||
- [Git](https://git-scm.com/downloads)
|
||||
- [pm2](https://pm2.keymetrics.io/) para ejecutar Pingvin Share en segundo plano
|
||||
|
||||
```bash
|
||||
git clone https://github.com/stonith404/pingvin-share
|
||||
cd pingvin-share
|
||||
|
||||
# Consultar la última versión
|
||||
git fetch --tags && git checkout $(git describe --tags `git rev-list --tags --max-count=1`)
|
||||
|
||||
# Iniciar el backend
|
||||
cd backend
|
||||
npm install
|
||||
npm run build
|
||||
pm2 start --name="pingvin-share-backend" npm -- run prod
|
||||
|
||||
# Iniciar el frontend
|
||||
cd ../frontend
|
||||
npm install
|
||||
npm run build
|
||||
pm2 start --name="pingvin-share-frontend" npm -- run start
|
||||
```
|
||||
|
||||
El sitio web ahora está esperando conexiones en `http://localhost:3000`, ¡diviértase usando Pingvin Share 🐧!
|
||||
|
||||
### Integraciones
|
||||
|
||||
#### ClamAV (Unicamente con Docker)
|
||||
|
||||
ClamAV se utiliza para escanear los recursos compartidos en busca de archivos maliciosos y eliminarlos si los encuentra.
|
||||
|
||||
1. Añade el contenedor ClamAV al stack de Docker Compose (ver `docker-compose.yml`) e inicie el contenedor.
|
||||
2. Docker esperará a que ClamAV se inicie antes de iniciar Pingvin Share. Esto puede tardar uno o dos minutos.
|
||||
3. Los registros de Pingvin Share ahora deberían decir "ClamAV está activo".
|
||||
|
||||
Por favor, ten en cuenta que ClamAV necesita muchos [recursos](https://docs.clamav.net/manual/Installing/Docker.html#memory-ram-requirements).
|
||||
|
||||
### Recursos adicionales
|
||||
|
||||
- [Instalación en Synology NAS (Inglés)](https://mariushosting.com/how-to-install-pingvin-share-on-your-synology-nas/)
|
||||
|
||||
### Actualizar a una nueva versión
|
||||
|
||||
Dado que Pingvin Share se encuentra en una fase inicial, consulte las notas de la versión para conocer los cambios de última hora antes de actualizar.
|
||||
|
||||
#### Docker
|
||||
|
||||
```bash
|
||||
docker compose pull
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
#### Instalación autónoma
|
||||
|
||||
1. Deten la aplicación en ejecución
|
||||
|
||||
```bash
|
||||
pm2 stop pingvin-share-backend pingvin-share-frontend
|
||||
```
|
||||
|
||||
2. Repite los pasos de la [guía de instalación](#instalación-autonoma) excepto el paso de `git clone`.
|
||||
|
||||
```bash
|
||||
cd pingvin-share
|
||||
|
||||
# Consultar la última versión
|
||||
git fetch --tags && git checkout $(git describe --tags `git rev-list --tags --max-count=1`)
|
||||
|
||||
# Iniciar el backend
|
||||
cd backend
|
||||
npm run build
|
||||
pm2 restart pingvin-share-backend
|
||||
|
||||
# Iniciar frontend
|
||||
cd ../frontend
|
||||
npm run build
|
||||
pm2 restart pingvin-share-frontend
|
||||
```
|
||||
|
||||
### Marca personalizada
|
||||
|
||||
Puedes cambiar el nombre y el logotipo de la aplicación visitando la página de configuración de administrador.
|
||||
|
||||
## 🖤 Contribuye
|
||||
|
||||
¡Eres bienvenido a contribuir a Pingvin Share! Sige la [guía de contribución](/CONTRIBUTING.md) para empezar.
|
||||
30
README.md
30
README.md
@@ -1,5 +1,11 @@
|
||||
# <div align="center"><img src="https://user-images.githubusercontent.com/58886915/166198400-c2134044-1198-4647-a8b6-da9c4a204c68.svg" width="40"/> </br>Pingvin Share</div>
|
||||
|
||||
---
|
||||
|
||||
*Read this in another language: [Spanish](README.es.md), [English](README.md)*
|
||||
|
||||
---
|
||||
|
||||
Pingvin Share is self-hosted file sharing platform and an alternative for WeTransfer.
|
||||
|
||||
## ✨ Features
|
||||
@@ -88,11 +94,27 @@ docker compose up -d
|
||||
|
||||
#### Stand-alone
|
||||
|
||||
1. Remove the running app
|
||||
```
|
||||
pm2 delete pingvin-share-backend pingvin-share-frontend
|
||||
```
|
||||
1. Stop the running app
|
||||
```bash
|
||||
pm2 stop pingvin-share-backend pingvin-share-frontend
|
||||
```
|
||||
2. Repeat the steps from the [installation guide](#stand-alone-installation) except the `git clone` step.
|
||||
```bash
|
||||
cd pingvin-share
|
||||
|
||||
# Checkout the latest version
|
||||
git fetch --tags && git checkout $(git describe --tags `git rev-list --tags --max-count=1`)
|
||||
|
||||
# Start the backend
|
||||
cd backend
|
||||
npm run build
|
||||
pm2 restart pingvin-share-backend
|
||||
|
||||
# Start the frontend
|
||||
cd ../frontend
|
||||
npm run build
|
||||
pm2 restart pingvin-share-frontend
|
||||
```
|
||||
|
||||
### Custom branding
|
||||
|
||||
|
||||
4
backend/package-lock.json
generated
4
backend/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "pingvin-share-backend",
|
||||
"version": "0.13.0",
|
||||
"version": "0.14.0",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "pingvin-share-backend",
|
||||
"version": "0.13.0",
|
||||
"version": "0.14.0",
|
||||
"dependencies": {
|
||||
"@nestjs/common": "^9.3.9",
|
||||
"@nestjs/config": "^2.3.1",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "pingvin-share-backend",
|
||||
"version": "0.13.0",
|
||||
"version": "0.14.0",
|
||||
"scripts": {
|
||||
"build": "nest build",
|
||||
"dev": "cross-env NODE_ENV=development nest start --watch",
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
-- RedefineTables
|
||||
PRAGMA foreign_keys=OFF;
|
||||
CREATE TABLE "new_Config" (
|
||||
"updatedAt" DATETIME NOT NULL,
|
||||
"name" TEXT NOT NULL,
|
||||
"category" TEXT NOT NULL,
|
||||
"type" TEXT NOT NULL,
|
||||
"value" TEXT,
|
||||
"defaultValue" TEXT NOT NULL DEFAULT '',
|
||||
"description" TEXT NOT NULL,
|
||||
"obscured" BOOLEAN NOT NULL DEFAULT false,
|
||||
"secret" BOOLEAN NOT NULL DEFAULT true,
|
||||
"locked" BOOLEAN NOT NULL DEFAULT false,
|
||||
"order" INTEGER NOT NULL,
|
||||
|
||||
PRIMARY KEY ("name", "category")
|
||||
);
|
||||
INSERT INTO "new_Config" ("category", "description", "locked", "name", "obscured", "order", "secret", "type", "updatedAt", "value") SELECT "category", "description", "locked", "name", "obscured", "order", "secret", "type", "updatedAt", "value" FROM "Config";
|
||||
DROP TABLE "Config";
|
||||
ALTER TABLE "new_Config" RENAME TO "Config";
|
||||
|
||||
PRAGMA foreign_key_check;
|
||||
PRAGMA foreign_keys=ON;
|
||||
@@ -131,15 +131,16 @@ model ShareSecurity {
|
||||
model Config {
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
name String
|
||||
category String
|
||||
type String
|
||||
value String
|
||||
description String
|
||||
obscured Boolean @default(false)
|
||||
secret Boolean @default(true)
|
||||
locked Boolean @default(false)
|
||||
order Int
|
||||
name String
|
||||
category String
|
||||
type String
|
||||
defaultValue String @default("")
|
||||
value String?
|
||||
description String
|
||||
obscured Boolean @default(false)
|
||||
secret Boolean @default(true)
|
||||
locked Boolean @default(false)
|
||||
order Int
|
||||
|
||||
@@id([name, category])
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ const configVariables: ConfigVariables = {
|
||||
jwtSecret: {
|
||||
description: "Long random string used to sign JWT tokens",
|
||||
type: "string",
|
||||
value: crypto.randomBytes(256).toString("base64"),
|
||||
defaultValue: crypto.randomBytes(256).toString("base64"),
|
||||
locked: true,
|
||||
},
|
||||
},
|
||||
@@ -14,20 +14,20 @@ const configVariables: ConfigVariables = {
|
||||
appName: {
|
||||
description: "Name of the application",
|
||||
type: "string",
|
||||
value: "Pingvin Share",
|
||||
defaultValue: "Pingvin Share",
|
||||
secret: false,
|
||||
},
|
||||
appUrl: {
|
||||
description: "On which URL Pingvin Share is available",
|
||||
type: "string",
|
||||
value: "http://localhost:3000",
|
||||
defaultValue: "http://localhost:3000",
|
||||
|
||||
secret: false,
|
||||
},
|
||||
showHomePage: {
|
||||
description: "Whether to show the home page",
|
||||
type: "boolean",
|
||||
value: "true",
|
||||
defaultValue: "true",
|
||||
secret: false,
|
||||
},
|
||||
},
|
||||
@@ -35,21 +35,21 @@ const configVariables: ConfigVariables = {
|
||||
allowRegistration: {
|
||||
description: "Whether registration is allowed",
|
||||
type: "boolean",
|
||||
value: "true",
|
||||
defaultValue: "true",
|
||||
|
||||
secret: false,
|
||||
},
|
||||
allowUnauthenticatedShares: {
|
||||
description: "Whether unauthorized users can create shares",
|
||||
type: "boolean",
|
||||
value: "false",
|
||||
defaultValue: "false",
|
||||
|
||||
secret: false,
|
||||
},
|
||||
maxSize: {
|
||||
description: "Maximum share size in bytes",
|
||||
type: "number",
|
||||
value: "1073741824",
|
||||
defaultValue: "1073741824",
|
||||
|
||||
secret: false,
|
||||
},
|
||||
@@ -59,7 +59,7 @@ const configVariables: ConfigVariables = {
|
||||
description:
|
||||
"Whether to allow emails to share recipients. Only enable this if you have enabled SMTP.",
|
||||
type: "boolean",
|
||||
value: "false",
|
||||
defaultValue: "false",
|
||||
|
||||
secret: false,
|
||||
},
|
||||
@@ -67,53 +67,53 @@ const configVariables: ConfigVariables = {
|
||||
description:
|
||||
"Subject of the email which gets sent to the share recipients.",
|
||||
type: "string",
|
||||
value: "Files shared with you",
|
||||
defaultValue: "Files shared with you",
|
||||
},
|
||||
shareRecipientsMessage: {
|
||||
description:
|
||||
"Message which gets sent to the share recipients. {creator} and {shareUrl} will be replaced with the creator's name and the share URL.",
|
||||
"Message which gets sent to the share recipients.\n\nAvailable variables:\n{creator} - The username of the creator of the share\n{shareUrl} - The URL of the share\n{desc} - The description of the share\n{expires} - The expiration date of the share\n\nVariables will be replaced with the actual values.",
|
||||
type: "text",
|
||||
value:
|
||||
"Hey!\n{creator} shared some files with you. View or download the files with this link: {shareUrl}\nShared securely with Pingvin Share 🐧",
|
||||
defaultValue:
|
||||
"Hey!\n\n{creator} shared some files with you, view or download the files with this link: {shareUrl}\n\nThe share will expire {expires}.\n\nNote: {desc}\n\nShared securely with Pingvin Share 🐧",
|
||||
},
|
||||
reverseShareSubject: {
|
||||
description:
|
||||
"Subject of the email which gets sent when someone created a share with your reverse share link.",
|
||||
type: "string",
|
||||
value: "Reverse share link used",
|
||||
defaultValue: "Reverse share link used",
|
||||
},
|
||||
reverseShareMessage: {
|
||||
description:
|
||||
"Message which gets sent when someone created a share with your reverse share link. {shareUrl} will be replaced with the creator's name and the share URL.",
|
||||
type: "text",
|
||||
value:
|
||||
"Hey!\nA share was just created with your reverse share link: {shareUrl}\nShared securely with Pingvin Share 🐧",
|
||||
defaultValue:
|
||||
"Hey!\n\nA share was just created with your reverse share link: {shareUrl}\n\nShared securely with Pingvin Share 🐧",
|
||||
},
|
||||
resetPasswordSubject: {
|
||||
description:
|
||||
"Subject of the email which gets sent when a user requests a password reset.",
|
||||
type: "string",
|
||||
value: "Pingvin Share password reset",
|
||||
defaultValue: "Pingvin Share password reset",
|
||||
},
|
||||
resetPasswordMessage: {
|
||||
description:
|
||||
"Message which gets sent when a user requests a password reset. {url} will be replaced with the reset password URL.",
|
||||
type: "text",
|
||||
value:
|
||||
"Hey!\nYou requested a password reset. Click this link to reset your password: {url}\nThe link expires in a hour.\nPingvin Share 🐧",
|
||||
defaultValue:
|
||||
"Hey!\n\nYou requested a password reset. Click this link to reset your password: {url}\nThe link expires in a hour.\n\nPingvin Share 🐧",
|
||||
},
|
||||
inviteSubject: {
|
||||
description:
|
||||
"Subject of the email which gets sent when an admin invites an user.",
|
||||
type: "string",
|
||||
value: "Pingvin Share invite",
|
||||
defaultValue: "Pingvin Share invite",
|
||||
},
|
||||
inviteMessage: {
|
||||
description:
|
||||
"Message which gets sent when an admin invites an user. {url} will be replaced with the invite URL and {password} with the password.",
|
||||
type: "text",
|
||||
value:
|
||||
"Hey!\nYou were invited to Pingvin Share. Click this link to accept the invite: {url}\nYour password is: {password}\nPingvin Share 🐧",
|
||||
defaultValue:
|
||||
"Hey!\n\nYou were invited to Pingvin Share. Click this link to accept the invite: {url}\n\nYour password is: {password}\n\nPingvin Share 🐧",
|
||||
},
|
||||
},
|
||||
smtp: {
|
||||
@@ -121,33 +121,33 @@ const configVariables: ConfigVariables = {
|
||||
description:
|
||||
"Whether SMTP is enabled. Only set this to true if you entered the host, port, email, user and password of your SMTP server.",
|
||||
type: "boolean",
|
||||
value: "false",
|
||||
defaultValue: "false",
|
||||
secret: false,
|
||||
},
|
||||
host: {
|
||||
description: "Host of the SMTP server",
|
||||
type: "string",
|
||||
value: "",
|
||||
defaultValue: "",
|
||||
},
|
||||
port: {
|
||||
description: "Port of the SMTP server",
|
||||
type: "number",
|
||||
value: "0",
|
||||
defaultValue: "0",
|
||||
},
|
||||
email: {
|
||||
description: "Email address which the emails get sent from",
|
||||
type: "string",
|
||||
value: "",
|
||||
defaultValue: "",
|
||||
},
|
||||
username: {
|
||||
description: "Username of the SMTP server",
|
||||
type: "string",
|
||||
value: "",
|
||||
defaultValue: "",
|
||||
},
|
||||
password: {
|
||||
description: "Password of the SMTP server",
|
||||
type: "string",
|
||||
value: "",
|
||||
defaultValue: "",
|
||||
obscured: true,
|
||||
},
|
||||
},
|
||||
|
||||
@@ -21,10 +21,12 @@ export class ConfigService {
|
||||
|
||||
if (!configVariable) throw new Error(`Config variable ${key} not found`);
|
||||
|
||||
if (configVariable.type == "number") return parseInt(configVariable.value);
|
||||
if (configVariable.type == "boolean") return configVariable.value == "true";
|
||||
const value = configVariable.value ?? configVariable.defaultValue;
|
||||
|
||||
if (configVariable.type == "number") return parseInt(value);
|
||||
if (configVariable.type == "boolean") return value == "true";
|
||||
if (configVariable.type == "string" || configVariable.type == "text")
|
||||
return configVariable.value;
|
||||
return value;
|
||||
}
|
||||
|
||||
async getByCategory(category: string) {
|
||||
@@ -35,8 +37,9 @@ export class ConfigService {
|
||||
|
||||
return configVariables.map((variable) => {
|
||||
return {
|
||||
key: `${variable.category}.${variable.name}`,
|
||||
...variable,
|
||||
key: `${variable.category}.${variable.name}`,
|
||||
value: variable.value ?? variable.defaultValue,
|
||||
};
|
||||
});
|
||||
}
|
||||
@@ -48,8 +51,9 @@ export class ConfigService {
|
||||
|
||||
return configVariables.map((variable) => {
|
||||
return {
|
||||
key: `${variable.category}.${variable.name}`,
|
||||
...variable,
|
||||
key: `${variable.category}.${variable.name}`,
|
||||
value: variable.value ?? variable.defaultValue,
|
||||
};
|
||||
});
|
||||
}
|
||||
@@ -77,7 +81,9 @@ export class ConfigService {
|
||||
if (!configVariable || configVariable.locked)
|
||||
throw new NotFoundException("Config variable not found");
|
||||
|
||||
if (
|
||||
if (value == "") {
|
||||
value = null;
|
||||
} else if (
|
||||
typeof value != configVariable.type &&
|
||||
typeof value == "string" &&
|
||||
configVariable.type != "text"
|
||||
@@ -94,7 +100,7 @@ export class ConfigService {
|
||||
name: key.split(".")[1],
|
||||
},
|
||||
},
|
||||
data: { value: value.toString() },
|
||||
data: { value: value ? value.toString() : null },
|
||||
});
|
||||
|
||||
this.configVariables = await this.prisma.config.findMany();
|
||||
|
||||
@@ -8,6 +8,9 @@ export class AdminConfigDTO extends ConfigDTO {
|
||||
@Expose()
|
||||
secret: boolean;
|
||||
|
||||
@Expose()
|
||||
defaultValue: string;
|
||||
|
||||
@Expose()
|
||||
updatedAt: Date;
|
||||
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
import { IsNotEmpty, IsString, ValidateIf } from "class-validator";
|
||||
import { IsNotEmpty, IsString } from "class-validator";
|
||||
|
||||
class UpdateConfigDTO {
|
||||
@IsString()
|
||||
key: string;
|
||||
|
||||
@IsNotEmpty()
|
||||
@ValidateIf((dto) => dto.value !== "")
|
||||
value: string | number | boolean;
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import {
|
||||
Logger,
|
||||
} from "@nestjs/common";
|
||||
import { User } from "@prisma/client";
|
||||
import * as moment from "moment";
|
||||
import * as nodemailer from "nodemailer";
|
||||
import { ConfigService } from "src/config/config.service";
|
||||
|
||||
@@ -43,10 +44,12 @@ export class EmailService {
|
||||
});
|
||||
}
|
||||
|
||||
async sendMailToShareRecepients(
|
||||
async sendMailToShareRecipients(
|
||||
recipientEmail: string,
|
||||
shareId: string,
|
||||
creator?: User
|
||||
creator?: User,
|
||||
description?: string,
|
||||
expiration?: Date
|
||||
) {
|
||||
if (!this.config.get("email.enableShareEmailRecipients"))
|
||||
throw new InternalServerErrorException("Email service disabled");
|
||||
@@ -61,6 +64,13 @@ export class EmailService {
|
||||
.replaceAll("\\n", "\n")
|
||||
.replaceAll("{creator}", creator?.username ?? "Someone")
|
||||
.replaceAll("{shareUrl}", shareUrl)
|
||||
.replaceAll("{desc}", description ?? "No description")
|
||||
.replaceAll(
|
||||
"{expires}",
|
||||
moment(expiration).unix() != 0
|
||||
? moment(expiration).fromNow()
|
||||
: "in: never"
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -32,7 +32,8 @@ export class FileController {
|
||||
) {
|
||||
const { id, name, chunkIndex, totalChunks } = query;
|
||||
|
||||
const data = body.toString().split(",")[1];
|
||||
// Data can be empty if the file is empty
|
||||
const data = body.toString().split(",")[1] ?? "";
|
||||
|
||||
return await this.fileService.create(
|
||||
data,
|
||||
|
||||
@@ -142,12 +142,14 @@ export class ShareService {
|
||||
this.prisma.share.update({ where: { id }, data: { isZipReady: true } })
|
||||
);
|
||||
|
||||
// Send email for each recepient
|
||||
for (const recepient of share.recipients) {
|
||||
await this.emailService.sendMailToShareRecepients(
|
||||
recepient.email,
|
||||
// Send email for each recipient
|
||||
for (const recipient of share.recipients) {
|
||||
await this.emailService.sendMailToShareRecipients(
|
||||
recipient.email,
|
||||
share.id,
|
||||
share.creator
|
||||
share.creator,
|
||||
share.description,
|
||||
share.expiration
|
||||
);
|
||||
}
|
||||
|
||||
@@ -163,7 +165,7 @@ export class ShareService {
|
||||
}
|
||||
|
||||
// Check if any file is malicious with ClamAV
|
||||
this.clamScanService.checkAndRemove(share.id);
|
||||
void this.clamScanService.checkAndRemove(share.id);
|
||||
|
||||
if (share.reverseShare) {
|
||||
await this.prisma.reverseShare.update({
|
||||
@@ -172,7 +174,7 @@ export class ShareService {
|
||||
});
|
||||
}
|
||||
|
||||
return await this.prisma.share.update({
|
||||
return this.prisma.share.update({
|
||||
where: { id },
|
||||
data: { uploadLocked: true },
|
||||
});
|
||||
@@ -195,14 +197,12 @@ export class ShareService {
|
||||
include: { recipients: true },
|
||||
});
|
||||
|
||||
const sharesWithEmailRecipients = shares.map((share) => {
|
||||
return shares.map((share) => {
|
||||
return {
|
||||
...share,
|
||||
recipients: share.recipients.map((recipients) => recipients.email),
|
||||
};
|
||||
});
|
||||
|
||||
return sharesWithEmailRecipients;
|
||||
}
|
||||
|
||||
async get(id: string): Promise<any> {
|
||||
@@ -222,7 +222,7 @@ export class ShareService {
|
||||
throw new NotFoundException("Share not found");
|
||||
return {
|
||||
...share,
|
||||
hasPassword: share.security?.password ? true : false,
|
||||
hasPassword: !!share.security?.password,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
4
frontend/package-lock.json
generated
4
frontend/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "pingvin-share-frontend",
|
||||
"version": "0.13.0",
|
||||
"version": "0.14.0",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "pingvin-share-frontend",
|
||||
"version": "0.13.0",
|
||||
"version": "0.14.0",
|
||||
"dependencies": {
|
||||
"@emotion/react": "^11.10.6",
|
||||
"@emotion/server": "^11.10.0",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "pingvin-share-frontend",
|
||||
"version": "0.13.0",
|
||||
"version": "0.14.0",
|
||||
"scripts": {
|
||||
"dev": "next dev",
|
||||
"build": "next build",
|
||||
|
||||
@@ -18,10 +18,13 @@ const AdminConfigInput = ({
|
||||
}) => {
|
||||
const form = useForm({
|
||||
initialValues: {
|
||||
stringValue: configVariable.value,
|
||||
textValue: configVariable.value,
|
||||
numberValue: parseInt(configVariable.value),
|
||||
booleanValue: configVariable.value == "true",
|
||||
stringValue: configVariable.value ?? configVariable.defaultValue,
|
||||
textValue: configVariable.value ?? configVariable.defaultValue,
|
||||
numberValue: parseInt(
|
||||
configVariable.value ?? configVariable.defaultValue
|
||||
),
|
||||
booleanValue:
|
||||
(configVariable.value ?? configVariable.defaultValue) == "true",
|
||||
},
|
||||
});
|
||||
|
||||
@@ -35,29 +38,38 @@ const AdminConfigInput = ({
|
||||
{configVariable.type == "string" &&
|
||||
(configVariable.obscured ? (
|
||||
<PasswordInput
|
||||
style={{ width: "100%" }}
|
||||
style={{
|
||||
width: "100%",
|
||||
}}
|
||||
{...form.getInputProps("stringValue")}
|
||||
onChange={(e) => onValueChange(configVariable, e.target.value)}
|
||||
/>
|
||||
) : (
|
||||
<TextInput
|
||||
style={{ width: "100%" }}
|
||||
style={{
|
||||
width: "100%",
|
||||
}}
|
||||
{...form.getInputProps("stringValue")}
|
||||
placeholder={configVariable.defaultValue}
|
||||
onChange={(e) => onValueChange(configVariable, e.target.value)}
|
||||
/>
|
||||
))}
|
||||
|
||||
{configVariable.type == "text" && (
|
||||
<Textarea
|
||||
style={{ width: "100%" }}
|
||||
style={{
|
||||
width: "100%",
|
||||
}}
|
||||
autosize
|
||||
{...form.getInputProps("textValue")}
|
||||
placeholder={configVariable.defaultValue}
|
||||
onChange={(e) => onValueChange(configVariable, e.target.value)}
|
||||
/>
|
||||
)}
|
||||
{configVariable.type == "number" && (
|
||||
<NumberInput
|
||||
{...form.getInputProps("numberValue")}
|
||||
placeholder={configVariable.defaultValue}
|
||||
onChange={(number) => onValueChange(configVariable, number)}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -67,7 +67,7 @@ export default function AppShellDemo() {
|
||||
toast.success("Configurations updated successfully");
|
||||
})
|
||||
.catch(toast.axiosError);
|
||||
config.refresh();
|
||||
void config.refresh();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -75,8 +75,12 @@ export default function AppShellDemo() {
|
||||
const index = updatedConfigVariables.findIndex(
|
||||
(item) => item.key === configVariable.key
|
||||
);
|
||||
|
||||
if (index > -1) {
|
||||
updatedConfigVariables[index] = configVariable;
|
||||
updatedConfigVariables[index] = {
|
||||
...updatedConfigVariables[index],
|
||||
...configVariable,
|
||||
};
|
||||
} else {
|
||||
setUpdatedConfigVariables([...updatedConfigVariables, configVariable]);
|
||||
}
|
||||
@@ -132,9 +136,24 @@ export default function AppShellDemo() {
|
||||
<Title order={6}>
|
||||
{configVariableToFriendlyName(configVariable.name)}
|
||||
</Title>
|
||||
<Text color="dimmed" size="sm" mb="xs">
|
||||
{configVariable.description}
|
||||
</Text>
|
||||
{configVariable.description.split("\n").length == 1 ? (
|
||||
<Text color="dimmed" size="sm" mb="xs">
|
||||
{configVariable.description}
|
||||
</Text>
|
||||
) : (
|
||||
configVariable.description.split("\n").map((line) => (
|
||||
<Text
|
||||
key={line}
|
||||
color="dimmed"
|
||||
size="sm"
|
||||
style={{
|
||||
marginBottom: line === "" ? "1rem" : "0",
|
||||
}}
|
||||
>
|
||||
{line}
|
||||
</Text>
|
||||
))
|
||||
)}
|
||||
</Stack>
|
||||
<Stack></Stack>
|
||||
<Box style={{ width: isMobile ? "100%" : "50%" }}>
|
||||
|
||||
@@ -23,10 +23,12 @@ const get = (key: string, configVariables: Config[]): any => {
|
||||
|
||||
if (!configVariable) throw new Error(`Config variable ${key} not found`);
|
||||
|
||||
if (configVariable.type == "number") return parseInt(configVariable.value);
|
||||
if (configVariable.type == "boolean") return configVariable.value == "true";
|
||||
const value = configVariable.value ?? configVariable.defaultValue;
|
||||
|
||||
if (configVariable.type == "number") return parseInt(value);
|
||||
if (configVariable.type == "boolean") return value == "true";
|
||||
if (configVariable.type == "string" || configVariable.type == "text")
|
||||
return configVariable.value;
|
||||
return value;
|
||||
};
|
||||
|
||||
const finishSetup = async (): Promise<AdminConfig[]> => {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
type Config = {
|
||||
key: string;
|
||||
defaultValue: string;
|
||||
value: string;
|
||||
type: string;
|
||||
};
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "pingvin-share",
|
||||
"version": "0.13.0",
|
||||
"version": "0.14.0",
|
||||
"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