Compare commits

..

11 Commits

Author SHA1 Message Date
Elias Schneider
b33c1d7f4b release: 0.14.1 2023-04-07 23:13:54 +02:00
Elias Schneider
39a74510c1 fix: boolean config variables can't be set to false 2023-04-07 23:13:44 +02:00
Elias Schneider
b7db9b9b40 refactor: simplify create share function 2023-04-04 22:47:32 +02:00
Elias Schneider
2ca0092b71 docs: fix translation path 2023-04-02 18:55:41 +02:00
Elias Schneider
b4bf43910e docs: move translated docs in docs folder 2023-04-02 18:53:54 +02:00
AC6
90aa919694 docs: add Simplified Chinese version of README and CONTRIBUTING (#139)
* add simplified Chinese translation for README.md

* add simplified Chinese translation for CONTRIBUTING.md
2023-04-02 18:49:03 +02:00
Elias Schneider
f2e4019190 release: 0.14.0 2023-04-01 20:19:27 +02:00
Rooyca
ffd4e43f11 docs: add Spanish version of README and CONTRIBUTING (#138)
* doc: add Spanish version of README and CONTRIBUTING

* docs: change h3 tag from language switch to normal size
2023-04-01 20:15:47 +02:00
Elias Schneider
0e5c673270 fix: bool config variable can't be changed 2023-03-24 21:37:39 +01:00
iUnstable0
beece56327 feat(share, config): more variables, placeholder and reset default (#132)
* More email share vars + unfinished placeolders config

{desc} {expires} vars
(unfinished) config placeholder vals

* done

* migrate

* edit seed

* removed comments

* refactor: replace dependecy `luxon` with `moment`

* update shareRecipientsMessage message

* chore: remove `luxon`

* fix: grammatically incorrect `shareRecipientsMessage` message

* changed to defaultValue and value instead

* fix: don't expose defaultValue to non admin user

* fix: update default value if default value changes

* refactor: set config value to null instead of a empty string

* refactor: merge two migrations into one

* fix value check empty

---------

Co-authored-by: Elias Schneider <login@eliasschneider.com>
2023-03-23 08:31:21 +01:00
iUnstable0
a0d1d98e24 docs: improve stand-alone upgrade guide (#128)
* Update README.md

* docs: improve stand-alone upgrade guide

* Update README.md
2023-03-16 09:21:53 +01:00
25 changed files with 652 additions and 93 deletions

View File

@@ -1,3 +1,22 @@
### [0.14.1](https://github.com/stonith404/pingvin-share/compare/v0.14.0...v0.14.1) (2023-04-07)
### Bug Fixes
* boolean config variables can't be set to false ([39a7451](https://github.com/stonith404/pingvin-share/commit/39a74510c1f00466acaead39f7bee003b3db60d7))
## [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)

View File

@@ -1,3 +1,7 @@
_Read this in another language: [Spanish](/docs/CONTRIBUTING.es.md), [English](/CONTRIBUTING.md), [Simplified Chinese](/docs/CONTRIBUTING.zh-cn.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.

View File

@@ -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](/docs/README.es.md), [English](/README.md), [Simplified Chinese](/docs/README.zh-cn.md)_
---
Pingvin Share is self-hosted file sharing platform and an alternative for WeTransfer.
## ✨ Features
@@ -88,12 +94,29 @@ 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
You can change the name and the logo of the app by visiting the admin configuration page.

View File

@@ -1,12 +1,12 @@
{
"name": "pingvin-share-backend",
"version": "0.13.1",
"version": "0.14.1",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "pingvin-share-backend",
"version": "0.13.1",
"version": "0.14.1",
"dependencies": {
"@nestjs/common": "^9.3.9",
"@nestjs/config": "^2.3.1",

View File

@@ -1,6 +1,6 @@
{
"name": "pingvin-share-backend",
"version": "0.13.1",
"version": "0.14.1",
"scripts": {
"build": "nest build",
"dev": "cross-env NODE_ENV=development nest start --watch",

View File

@@ -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;

View File

@@ -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])
}

View File

@@ -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,
},
},

View File

@@ -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 === null ? null : value.toString() },
});
this.configVariables = await this.prisma.config.findMany();

View File

@@ -8,6 +8,9 @@ export class AdminConfigDTO extends ConfigDTO {
@Expose()
secret: boolean;
@Expose()
defaultValue: string;
@Expose()
updatedAt: Date;

View File

@@ -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;
}

View File

@@ -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"
)
);
}

View File

@@ -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,
};
}

95
docs/CONTRIBUTING.es.md Normal file
View File

@@ -0,0 +1,95 @@
_Leer esto en otro idioma: [Inglés](/CONTRIBUTING.md), [Español](/docs/CONTRIBUTING.es.md), [Chino Simplificado](/docs/CONTRIBUTING.zh-cn.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.

View File

@@ -0,0 +1,97 @@
_选择合适的语言阅读: [西班牙语](/docs/CONTRIBUTING.es.md), [英语](/CONTRIBUTING.md), [简体中文](/docs/CONTRIBUTING.zh-cn.md)_
---
# 提交贡献
我们非常感谢你 ❤️ 为 Pingvin Share 提交贡献使其变得更棒! 欢迎任何形式的贡献,包括 issues, 建议, PRs 和其他形式
## 小小的开始
你找到了一个 bug有新特性建议或者其他提议请在 GitHub 建立一个 issue 以便我和你联络 😊
## 提交一个 Pull Request
在你提交 PR 前请确保
- PR 的名字遵守 [Conventional Commits specification](https://www.conventionalcommits.org):
`<type>[optional scope]: <description>`
例如:
```
feat(share): add password protection
```
`TYPE` 可以是:
- **feat** - 这是一个新特性 feature
- **doc** - 仅仅改变了文档部分 documentation
- **fix** - 修复了一个 bug
- **refactor** - 更新了代码,但是并非出于增加新特性 feature 或修复 bug 的目的
- 请在 PR 中附详细的解释说明
- 使用 `npm run format` 格式化你的代码
<details>
<summary>不知道怎么发起一个 PR 点开了解怎么发起一个 PR </summary>
1. 点击 Pingvin Share 仓库的 `Fork` 按钮,复制一份你的仓库
2. 通过 `git clone` 将你的仓库克隆到本地
```
$ git clone https://github.com/[你的用户名]/pingvin-share
```
3. 进行你的修改 - 提交 commit 你的修改 - 重复直到完成
4. 将你的修改提交到 GitHub
```
$ git push origin [你的新分支的名字]
```
5. 提交你的代码以便代码审查
如果你进入你 fork 的 Github 仓库,你会看到一个 `Compare & pull request` 按钮,点击该按钮
6. 发起一个 PR
7. 点击 `Create pull request` 来提交你的 PR
8. 等待代码审查,通过或以某些原因拒绝
</details>
## 配置开发项目
Pingvin Share 包括前端和后端部分
### 后端
后端使用 [Nest.js](https://nestjs.com) 建立,使用 Typescript
#### 搭建
1. 打开 `backend` 文件夹
2. 使用 `npm install` 安装依赖
3. 通过 `npx prisma db push` 配置数据库结构
4. 通过 `npx prisma db seed` 初始化数据库数据
5. 通过 `npm run dev` 启动后端
### 前端
后端使用 [Next.js](https://nextjs.org) 建立,使用 Typescript
#### 搭建
1. 首先启动后端
2. 打开 `frontend` 文件夹
3. 通过 `npm install` 安装依赖
4. 通过 `npm run dev` 启动前端
开发项目配置完成
### 测试
目前阶段我们只有后端的系统测试,在 `backend` 文件夹运行 `npm run test:system` 来执行系统测试

128
docs/README.es.md Normal file
View File

@@ -0,0 +1,128 @@
# <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](/docs/README.es.md), [Chino Simplificado](/docs/README.zh-cn.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.

126
docs/README.zh-cn.md Normal file
View File

@@ -0,0 +1,126 @@
# <div align="center"><img src="https://user-images.githubusercontent.com/58886915/166198400-c2134044-1198-4647-a8b6-da9c4a204c68.svg" width="40"/> </br>Pingvin Share</div>
---
_选择合适的语言阅读: [西班牙语](/docs/README.es.md), [英语](/README.md), [简体中文](/docs/README.zh-cn.md)_
---
Pingvin Share 是一个可自建的文件分享平台,是 WeTransfer 的一个替代品
## ✨ 特性
- 通过可自定义后缀的链接分享文件
- 可自定义任意大小的文件上传限制 (受制于托管所在的硬盘大小)
- 对共享链接设置有效期限
- 对共享链接设置访问次数和访问密码
- 通过邮件自动发送共享链接
- 整合 ClamAV 进行反病毒检查
## 🐧 了解一下 Pingvin Share
- [示例网站](https://pingvin-share.dev.eliasschneider.com)
- [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"/>
## ⌨️ 自建指南
> 注意Pingvin Share 仍处于开发阶段并且可能存在 bugs
### Docker 部署 (推荐)
1. 下载 `docker-compose.yml`
2. 运行命令 `docker-compose up -d`
现在网站运行在 `http://localhost:3000`,尝试一下你本地的 Pingvin Share 🐧!
### Stand-alone 部署
必须的依赖:
- [Node.js](https://nodejs.org/en/download/) >= 16
- [Git](https://git-scm.com/downloads)
- [pm2](https://pm2.keymetrics.io/) 用于后台运行 Pingvin Share
```bash
git clone https://github.com/stonith404/pingvin-share
cd pingvin-share
# 获取最新的版本
git fetch --tags && git checkout $(git describe --tags `git rev-list --tags --max-count=1`)
# 启动后端 backend
cd backend
npm install
npm run build
pm2 start --name="pingvin-share-backend" npm -- run prod
# 启动前端 frontend
cd ../frontend
npm install
npm run build
pm2 start --name="pingvin-share-frontend" npm -- run start
```
现在网站运行在 `http://localhost:3000`,尝试一下你本地的 Pingvin Share 🐧!
### 整合组件
#### ClamAV (仅限 Docker 部署)
扫描上传文件中是否存在可疑文件,如果存在 ClamAV 会自动移除
1. 在 docker-compose 配置中添加 ClamAV 容器 (见 `docker-compose.yml` 注释部分) 并启动容器
2. Docker 会在启动 Pingvin Share 前启动 ClamAV也许会花费 1-2 分钟
3. Pingvin Share 日志中应该有 "ClamAV is active"
请注意 ClamAV 会消耗很多 [系统资源(特别是内存)](https://docs.clamav.net/manual/Installing/Docker.html#memory-ram-requirements)
### 更多资源
- [群晖 NAS 配置](https://mariushosting.com/how-to-install-pingvin-share-on-your-synology-nas/)
### 升级
因为 Pingvin Share 仍处在开发阶段,在升级前请务必阅读 release notes 避免不可逆的改变
#### Docker 升级
```bash
docker compose pull
docker compose up -d
```
#### Stand-alone 升级
1. 停止正在运行的 app
```bash
pm2 stop pingvin-share-backend pingvin-share-frontend
```
2. 重复 [installation guide](#stand-alone-installation) 中的步骤,除了 `git clone` 这一步
```bash
cd pingvin-share
# 获取最新的版本
git fetch --tags && git checkout $(git describe --tags `git rev-list --tags --max-count=1`)
# 启动后端 backend
cd backend
npm run build
pm2 restart pingvin-share-backend
# 启动前端 frontend
cd ../frontend
npm run build
pm2 restart pingvin-share-frontend
```
### 自定义品牌
你可以在管理员配置页面改变网站的名字和 logo
## 🖤 提交贡献
非常欢迎向 Pingvin Share 提交贡献! 请阅读 [contribution guide](/CONTRIBUTING.md) 来提交你的贡献

View File

@@ -1,12 +1,12 @@
{
"name": "pingvin-share-frontend",
"version": "0.13.1",
"version": "0.14.1",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "pingvin-share-frontend",
"version": "0.13.1",
"version": "0.14.1",
"dependencies": {
"@emotion/react": "^11.10.6",
"@emotion/server": "^11.10.0",

View File

@@ -1,6 +1,6 @@
{
"name": "pingvin-share-frontend",
"version": "0.13.1",
"version": "0.14.1",
"scripts": {
"dev": "next dev",
"build": "next build",

View File

@@ -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)}
/>
)}

View File

@@ -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%" }}>

View File

@@ -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[]> => {

View File

@@ -12,16 +12,7 @@ import {
import api from "./api.service";
const create = async (share: CreateShare) => {
const { id, expiration, recipients, security, description } = share;
return (
await api.post("shares", {
id,
expiration,
recipients,
security,
description,
})
).data;
return (await api.post("shares", share)).data;
};
const completeShare = async (id: string) => {

View File

@@ -1,5 +1,6 @@
type Config = {
key: string;
defaultValue: string;
value: string;
type: string;
};

View File

@@ -1,6 +1,6 @@
{
"name": "pingvin-share",
"version": "0.13.1",
"version": "0.14.1",
"scripts": {
"format": "cd frontend && npm run format && cd ../backend && npm run format",
"lint": "cd frontend && npm run lint && cd ../backend && npm run lint",