Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
932496a121 | ||
|
|
0c7b2a8e70 | ||
|
|
1df5c7123e | ||
|
|
2dc0fc9332 | ||
|
|
98c0de78e8 | ||
|
|
5132d177b8 | ||
|
|
e5071cba12 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -23,6 +23,7 @@ yarn-error.log*
|
|||||||
|
|
||||||
# env file
|
# env file
|
||||||
.env
|
.env
|
||||||
|
!/backend/prisma/.env
|
||||||
|
|
||||||
# vercel
|
# vercel
|
||||||
.vercel
|
.vercel
|
||||||
|
|||||||
1
.prettierignore
Normal file
1
.prettierignore
Normal file
@@ -0,0 +1 @@
|
|||||||
|
/backend/src/constants.ts
|
||||||
10
CHANGELOG.md
10
CHANGELOG.md
@@ -1,3 +1,13 @@
|
|||||||
|
## [0.15.0](https://github.com/stonith404/pingvin-share/compare/v0.14.1...v0.15.0) (2023-05-09)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* add env variables for port, database url and data dir ([98c0de7](https://github.com/stonith404/pingvin-share/commit/98c0de78e8a73e3e5bf0928226cfb8a024b566a1))
|
||||||
|
* add healthcheck endpoint ([5132d17](https://github.com/stonith404/pingvin-share/commit/5132d177b8ab4e00a7e701e9956222fa2352d42c))
|
||||||
|
* allow to configure clamav with environment variables ([1df5c71](https://github.com/stonith404/pingvin-share/commit/1df5c7123e4ca8695f4f1b7d49f46cdf147fb920))
|
||||||
|
* configure ports, db url and api url with env variables ([e5071cb](https://github.com/stonith404/pingvin-share/commit/e5071cba1204093197b72e18d024b484e72e360a))
|
||||||
|
|
||||||
### [0.14.1](https://github.com/stonith404/pingvin-share/compare/v0.14.0...v0.14.1) (2023-04-07)
|
### [0.14.1](https://github.com/stonith404/pingvin-share/compare/v0.14.0...v0.14.1) (2023-04-07)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ RUN npm run build && npm prune --production
|
|||||||
# Stage 5: Final image
|
# Stage 5: Final image
|
||||||
FROM node:19-slim AS runner
|
FROM node:19-slim AS runner
|
||||||
ENV NODE_ENV=docker
|
ENV NODE_ENV=docker
|
||||||
RUN apt-get update && apt-get install -y openssl
|
RUN apt-get update && apt-get install -y curl openssl
|
||||||
|
|
||||||
WORKDIR /opt/app/frontend
|
WORKDIR /opt/app/frontend
|
||||||
COPY --from=frontend-builder /opt/app/public ./public
|
COPY --from=frontend-builder /opt/app/public ./public
|
||||||
@@ -47,4 +47,6 @@ COPY --from=backend-builder /opt/app/package.json ./
|
|||||||
|
|
||||||
WORKDIR /opt/app
|
WORKDIR /opt/app
|
||||||
EXPOSE 3000
|
EXPOSE 3000
|
||||||
|
HEALTHCHECK --interval=10s --timeout=3s CMD curl -f http://localhost:3000/api/health || exit 1
|
||||||
|
|
||||||
CMD cp -rn /tmp/img /opt/app/frontend/public && node frontend/server.js & cd backend && npm run prod
|
CMD cp -rn /tmp/img /opt/app/frontend/public && node frontend/server.js & cd backend && npm run prod
|
||||||
25
README.md
25
README.md
@@ -117,9 +117,30 @@ docker compose up -d
|
|||||||
pm2 restart pingvin-share-frontend
|
pm2 restart pingvin-share-frontend
|
||||||
```
|
```
|
||||||
|
|
||||||
### Custom branding
|
### Configuration
|
||||||
|
|
||||||
You can change the name and the logo of the app by visiting the admin configuration page.
|
You can customize Pingvin Share by going to the configuration page in your admin dashboard.
|
||||||
|
|
||||||
|
#### Environment variables
|
||||||
|
|
||||||
|
For installation specific configuration, you can use environment variables. The following variables are available:
|
||||||
|
|
||||||
|
##### Backend
|
||||||
|
|
||||||
|
| Variable | Default Value | Description |
|
||||||
|
| ---------------- | -------------------------------------------------- | -------------------------------------- |
|
||||||
|
| `PORT` | `8080` | The port on which the backend listens. |
|
||||||
|
| `DATABASE_URL` | `file:../data/pingvin-share.db?connection_limit=1` | The URL of the SQLite database. |
|
||||||
|
| `DATA_DIRECTORY` | `./data` | The directory where data is stored. |
|
||||||
|
| `CLAMAV_HOST` | `127.0.0.1` | The IP address of the ClamAV server. |
|
||||||
|
| `CLAMAV_PORT` | `3310` | The port number of the ClamAV server. |
|
||||||
|
|
||||||
|
##### Frontend
|
||||||
|
|
||||||
|
| Variable | Default Value | Description |
|
||||||
|
| --------- | ----------------------- | ---------------------------------------- |
|
||||||
|
| `PORT` | `3000` | The port on which the frontend listens. |
|
||||||
|
| `API_URL` | `http://localhost:8080` | The URL of the backend for the frontend. |
|
||||||
|
|
||||||
## 🖤 Contribute
|
## 🖤 Contribute
|
||||||
|
|
||||||
|
|||||||
4
backend/package-lock.json
generated
4
backend/package-lock.json
generated
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "pingvin-share-backend",
|
"name": "pingvin-share-backend",
|
||||||
"version": "0.14.1",
|
"version": "0.15.0",
|
||||||
"lockfileVersion": 2,
|
"lockfileVersion": 2,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "pingvin-share-backend",
|
"name": "pingvin-share-backend",
|
||||||
"version": "0.14.1",
|
"version": "0.15.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@nestjs/common": "^9.3.9",
|
"@nestjs/common": "^9.3.9",
|
||||||
"@nestjs/config": "^2.3.1",
|
"@nestjs/config": "^2.3.1",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "pingvin-share-backend",
|
"name": "pingvin-share-backend",
|
||||||
"version": "0.14.1",
|
"version": "0.15.0",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "nest build",
|
"build": "nest build",
|
||||||
"dev": "cross-env NODE_ENV=development nest start --watch",
|
"dev": "cross-env NODE_ENV=development nest start --watch",
|
||||||
|
|||||||
2
backend/prisma/.env
Normal file
2
backend/prisma/.env
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
#This file is only used to set a default value for the database url
|
||||||
|
DATABASE_URL="file:../data/pingvin-share.db"
|
||||||
@@ -4,7 +4,7 @@ generator client {
|
|||||||
|
|
||||||
datasource db {
|
datasource db {
|
||||||
provider = "sqlite"
|
provider = "sqlite"
|
||||||
url = "file:../data/pingvin-share.db"
|
url = env("DATABASE_URL")
|
||||||
}
|
}
|
||||||
|
|
||||||
model User {
|
model User {
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { Prisma, PrismaClient } from "@prisma/client";
|
import { Prisma, PrismaClient } from "@prisma/client";
|
||||||
import * as crypto from "crypto";
|
import * as crypto from "crypto";
|
||||||
|
|
||||||
const configVariables: ConfigVariables = {
|
const configVariables: ConfigVariables = {
|
||||||
internal: {
|
internal: {
|
||||||
jwtSecret: {
|
jwtSecret: {
|
||||||
@@ -162,7 +161,15 @@ type ConfigVariables = {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const prisma = new PrismaClient();
|
const prisma = new PrismaClient({
|
||||||
|
datasources: {
|
||||||
|
db: {
|
||||||
|
url:
|
||||||
|
process.env.DATABASE_URL ||
|
||||||
|
"file:../data/pingvin-share.db?connection_limit=1",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
async function seedConfigVariables() {
|
async function seedConfigVariables() {
|
||||||
for (const [category, configVariablesOfCategory] of Object.entries(
|
for (const [category, configVariablesOfCategory] of Object.entries(
|
||||||
|
|||||||
@@ -1,20 +1,22 @@
|
|||||||
import { Injectable } from "@nestjs/common";
|
import { Injectable, Logger } from "@nestjs/common";
|
||||||
import * as NodeClam from "clamscan";
|
import * as NodeClam from "clamscan";
|
||||||
import * as fs from "fs";
|
import * as fs from "fs";
|
||||||
import { FileService } from "src/file/file.service";
|
import { FileService } from "src/file/file.service";
|
||||||
import { PrismaService } from "src/prisma/prisma.service";
|
import { PrismaService } from "src/prisma/prisma.service";
|
||||||
|
import { CLAMAV_HOST, CLAMAV_PORT, SHARE_DIRECTORY } from "../constants";
|
||||||
|
|
||||||
const clamscanConfig = {
|
const clamscanConfig = {
|
||||||
clamdscan: {
|
clamdscan: {
|
||||||
host: process.env.NODE_ENV == "docker" ? "clamav" : "127.0.0.1",
|
host: CLAMAV_HOST,
|
||||||
port: 3310,
|
port: CLAMAV_PORT,
|
||||||
localFallback: false,
|
localFallback: false,
|
||||||
},
|
},
|
||||||
preference: "clamdscan",
|
preference: "clamdscan",
|
||||||
};
|
};
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class ClamScanService {
|
export class ClamScanService {
|
||||||
|
private readonly logger = new Logger(ClamScanService.name);
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private fileService: FileService,
|
private fileService: FileService,
|
||||||
private prisma: PrismaService
|
private prisma: PrismaService
|
||||||
@@ -23,11 +25,11 @@ export class ClamScanService {
|
|||||||
private ClamScan: Promise<NodeClam | null> = new NodeClam()
|
private ClamScan: Promise<NodeClam | null> = new NodeClam()
|
||||||
.init(clamscanConfig)
|
.init(clamscanConfig)
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
console.log("ClamAV is active");
|
this.logger.log("ClamAV is active");
|
||||||
return res;
|
return res;
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
console.log("ClamAV is not active");
|
this.logger.log("ClamAV is not active");
|
||||||
return null;
|
return null;
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -39,14 +41,14 @@ export class ClamScanService {
|
|||||||
const infectedFiles = [];
|
const infectedFiles = [];
|
||||||
|
|
||||||
const files = fs
|
const files = fs
|
||||||
.readdirSync(`./data/uploads/shares/${shareId}`)
|
.readdirSync(`${SHARE_DIRECTORY}/${shareId}`)
|
||||||
.filter((file) => file != "archive.zip");
|
.filter((file) => file != "archive.zip");
|
||||||
|
|
||||||
for (const fileId of files) {
|
for (const fileId of files) {
|
||||||
const { isInfected } = await clamScan
|
const { isInfected } = await clamScan
|
||||||
.isInfected(`./data/uploads/shares/${shareId}/${fileId}`)
|
.isInfected(`${SHARE_DIRECTORY}/${shareId}/${fileId}`)
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
console.log("ClamAV is not active");
|
this.logger.log("ClamAV is not active");
|
||||||
return { isInfected: false };
|
return { isInfected: false };
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -78,7 +80,7 @@ export class ClamScanService {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log(
|
this.logger.warn(
|
||||||
`Share ${shareId} deleted because it contained ${infectedFiles.length} malicious file(s)`
|
`Share ${shareId} deleted because it contained ${infectedFiles.length} malicious file(s)`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
5
backend/src/constants.ts
Normal file
5
backend/src/constants.ts
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
export const DATA_DIRECTORY = process.env.DATA_DIRECTORY || "./data";
|
||||||
|
export const SHARE_DIRECTORY = `${DATA_DIRECTORY}/uploads/shares`
|
||||||
|
export const DATABASE_URL = process.env.DATABASE_URL || "file:../data/pingvin-share.db?connection_limit=1";
|
||||||
|
export const CLAMAV_HOST = process.env.CLAMAV_HOST || (process.env.NODE_ENV == "docker" ? "clamav" : "127.0.0.1");
|
||||||
|
export const CLAMAV_PORT = parseInt(process.env.CLAMAV_PORT) || 3310;
|
||||||
@@ -11,6 +11,7 @@ import * as fs from "fs";
|
|||||||
import * as mime from "mime-types";
|
import * as mime from "mime-types";
|
||||||
import { ConfigService } from "src/config/config.service";
|
import { ConfigService } from "src/config/config.service";
|
||||||
import { PrismaService } from "src/prisma/prisma.service";
|
import { PrismaService } from "src/prisma/prisma.service";
|
||||||
|
import { SHARE_DIRECTORY } from "../constants";
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class FileService {
|
export class FileService {
|
||||||
@@ -39,7 +40,7 @@ export class FileService {
|
|||||||
let diskFileSize: number;
|
let diskFileSize: number;
|
||||||
try {
|
try {
|
||||||
diskFileSize = fs.statSync(
|
diskFileSize = fs.statSync(
|
||||||
`./data/uploads/shares/${shareId}/${file.id}.tmp-chunk`
|
`${SHARE_DIRECTORY}/${shareId}/${file.id}.tmp-chunk`
|
||||||
).size;
|
).size;
|
||||||
} catch {
|
} catch {
|
||||||
diskFileSize = 0;
|
diskFileSize = 0;
|
||||||
@@ -78,18 +79,18 @@ export class FileService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fs.appendFileSync(
|
fs.appendFileSync(
|
||||||
`./data/uploads/shares/${shareId}/${file.id}.tmp-chunk`,
|
`${SHARE_DIRECTORY}/${shareId}/${file.id}.tmp-chunk`,
|
||||||
buffer
|
buffer
|
||||||
);
|
);
|
||||||
|
|
||||||
const isLastChunk = chunk.index == chunk.total - 1;
|
const isLastChunk = chunk.index == chunk.total - 1;
|
||||||
if (isLastChunk) {
|
if (isLastChunk) {
|
||||||
fs.renameSync(
|
fs.renameSync(
|
||||||
`./data/uploads/shares/${shareId}/${file.id}.tmp-chunk`,
|
`${SHARE_DIRECTORY}/${shareId}/${file.id}.tmp-chunk`,
|
||||||
`./data/uploads/shares/${shareId}/${file.id}`
|
`${SHARE_DIRECTORY}/${shareId}/${file.id}`
|
||||||
);
|
);
|
||||||
const fileSize = fs.statSync(
|
const fileSize = fs.statSync(
|
||||||
`./data/uploads/shares/${shareId}/${file.id}`
|
`${SHARE_DIRECTORY}/${shareId}/${file.id}`
|
||||||
).size;
|
).size;
|
||||||
await this.prisma.file.create({
|
await this.prisma.file.create({
|
||||||
data: {
|
data: {
|
||||||
@@ -111,9 +112,7 @@ export class FileService {
|
|||||||
|
|
||||||
if (!fileMetaData) throw new NotFoundException("File not found");
|
if (!fileMetaData) throw new NotFoundException("File not found");
|
||||||
|
|
||||||
const file = fs.createReadStream(
|
const file = fs.createReadStream(`${SHARE_DIRECTORY}/${shareId}/${fileId}`);
|
||||||
`./data/uploads/shares/${shareId}/${fileId}`
|
|
||||||
);
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
metaData: {
|
metaData: {
|
||||||
@@ -126,13 +125,13 @@ export class FileService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async deleteAllFiles(shareId: string) {
|
async deleteAllFiles(shareId: string) {
|
||||||
await fs.promises.rm(`./data/uploads/shares/${shareId}`, {
|
await fs.promises.rm(`${SHARE_DIRECTORY}/${shareId}`, {
|
||||||
recursive: true,
|
recursive: true,
|
||||||
force: true,
|
force: true,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
getZip(shareId: string) {
|
getZip(shareId: string) {
|
||||||
return fs.createReadStream(`./data/uploads/shares/${shareId}/archive.zip`);
|
return fs.createReadStream(`${SHARE_DIRECTORY}/${shareId}/archive.zip`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,16 @@
|
|||||||
import { Injectable } from "@nestjs/common";
|
import { Injectable, Logger } from "@nestjs/common";
|
||||||
import { Cron } from "@nestjs/schedule";
|
import { Cron } from "@nestjs/schedule";
|
||||||
import * as fs from "fs";
|
import * as fs from "fs";
|
||||||
import * as moment from "moment";
|
import * as moment from "moment";
|
||||||
import { FileService } from "src/file/file.service";
|
import { FileService } from "src/file/file.service";
|
||||||
import { PrismaService } from "src/prisma/prisma.service";
|
import { PrismaService } from "src/prisma/prisma.service";
|
||||||
import { ReverseShareService } from "src/reverseShare/reverseShare.service";
|
import { ReverseShareService } from "src/reverseShare/reverseShare.service";
|
||||||
|
import { SHARE_DIRECTORY } from "../constants";
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class JobsService {
|
export class JobsService {
|
||||||
|
private readonly logger = new Logger(JobsService.name);
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private prisma: PrismaService,
|
private prisma: PrismaService,
|
||||||
private reverseShareService: ReverseShareService,
|
private reverseShareService: ReverseShareService,
|
||||||
@@ -34,8 +37,9 @@ export class JobsService {
|
|||||||
await this.fileService.deleteAllFiles(expiredShare.id);
|
await this.fileService.deleteAllFiles(expiredShare.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (expiredShares.length > 0)
|
if (expiredShares.length > 0) {
|
||||||
console.log(`job: deleted ${expiredShares.length} expired shares`);
|
this.logger.log(`Deleted ${expiredShares.length} expired shares`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Cron("0 * * * *")
|
@Cron("0 * * * *")
|
||||||
@@ -50,10 +54,11 @@ export class JobsService {
|
|||||||
await this.reverseShareService.remove(expiredReverseShare.id);
|
await this.reverseShareService.remove(expiredReverseShare.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (expiredReverseShares.length > 0)
|
if (expiredReverseShares.length > 0) {
|
||||||
console.log(
|
this.logger.log(
|
||||||
`job: deleted ${expiredReverseShares.length} expired reverse shares`
|
`Deleted ${expiredReverseShares.length} expired reverse shares`
|
||||||
);
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Cron("0 0 * * *")
|
@Cron("0 0 * * *")
|
||||||
@@ -61,31 +66,31 @@ export class JobsService {
|
|||||||
let filesDeleted = 0;
|
let filesDeleted = 0;
|
||||||
|
|
||||||
const shareDirectories = fs
|
const shareDirectories = fs
|
||||||
.readdirSync("./data/uploads/shares", { withFileTypes: true })
|
.readdirSync(SHARE_DIRECTORY, { withFileTypes: true })
|
||||||
.filter((dirent) => dirent.isDirectory())
|
.filter((dirent) => dirent.isDirectory())
|
||||||
.map((dirent) => dirent.name);
|
.map((dirent) => dirent.name);
|
||||||
|
|
||||||
for (const shareDirectory of shareDirectories) {
|
for (const shareDirectory of shareDirectories) {
|
||||||
const temporaryFiles = fs
|
const temporaryFiles = fs
|
||||||
.readdirSync(`./data/uploads/shares/${shareDirectory}`)
|
.readdirSync(`${SHARE_DIRECTORY}/${shareDirectory}`)
|
||||||
.filter((file) => file.endsWith(".tmp-chunk"));
|
.filter((file) => file.endsWith(".tmp-chunk"));
|
||||||
|
|
||||||
for (const file of temporaryFiles) {
|
for (const file of temporaryFiles) {
|
||||||
const stats = fs.statSync(
|
const stats = fs.statSync(
|
||||||
`./data/uploads/shares/${shareDirectory}/${file}`
|
`${SHARE_DIRECTORY}/${shareDirectory}/${file}`
|
||||||
);
|
);
|
||||||
const isOlderThanOneDay = moment(stats.mtime)
|
const isOlderThanOneDay = moment(stats.mtime)
|
||||||
.add(1, "day")
|
.add(1, "day")
|
||||||
.isBefore(moment());
|
.isBefore(moment());
|
||||||
|
|
||||||
if (isOlderThanOneDay) {
|
if (isOlderThanOneDay) {
|
||||||
fs.rmSync(`./data/uploads/shares/${shareDirectory}/${file}`);
|
fs.rmSync(`${SHARE_DIRECTORY}/${shareDirectory}/${file}`);
|
||||||
filesDeleted++;
|
filesDeleted++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(`job: deleted ${filesDeleted} temporary files`);
|
this.logger.log(`Deleted ${filesDeleted} temporary files`);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Cron("0 * * * *")
|
@Cron("0 * * * *")
|
||||||
@@ -107,7 +112,8 @@ export class JobsService {
|
|||||||
const deletedTokensCount =
|
const deletedTokensCount =
|
||||||
refreshTokenCount + loginTokenCount + resetPasswordTokenCount;
|
refreshTokenCount + loginTokenCount + resetPasswordTokenCount;
|
||||||
|
|
||||||
if (deletedTokensCount > 0)
|
if (deletedTokensCount > 0) {
|
||||||
console.log(`job: deleted ${deletedTokensCount} expired refresh tokens`);
|
this.logger.log(`Deleted ${deletedTokensCount} expired refresh tokens`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import * as bodyParser from "body-parser";
|
|||||||
import * as cookieParser from "cookie-parser";
|
import * as cookieParser from "cookie-parser";
|
||||||
import * as fs from "fs";
|
import * as fs from "fs";
|
||||||
import { AppModule } from "./app.module";
|
import { AppModule } from "./app.module";
|
||||||
|
import { DATA_DIRECTORY } from "./constants";
|
||||||
|
|
||||||
async function bootstrap() {
|
async function bootstrap() {
|
||||||
const app = await NestFactory.create<NestExpressApplication>(AppModule);
|
const app = await NestFactory.create<NestExpressApplication>(AppModule);
|
||||||
@@ -16,7 +17,9 @@ async function bootstrap() {
|
|||||||
app.use(cookieParser());
|
app.use(cookieParser());
|
||||||
app.set("trust proxy", true);
|
app.set("trust proxy", true);
|
||||||
|
|
||||||
await fs.promises.mkdir("./data/uploads/_temp", { recursive: true });
|
await fs.promises.mkdir(`${DATA_DIRECTORY}/uploads/_temp`, {
|
||||||
|
recursive: true,
|
||||||
|
});
|
||||||
|
|
||||||
app.setGlobalPrefix("api");
|
app.setGlobalPrefix("api");
|
||||||
|
|
||||||
@@ -30,6 +33,6 @@ async function bootstrap() {
|
|||||||
SwaggerModule.setup("api/swagger", app, document);
|
SwaggerModule.setup("api/swagger", app, document);
|
||||||
}
|
}
|
||||||
|
|
||||||
await app.listen(8080);
|
await app.listen(parseInt(process.env.PORT) || 8080);
|
||||||
}
|
}
|
||||||
bootstrap();
|
bootstrap();
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { Injectable } from "@nestjs/common";
|
import { Injectable } from "@nestjs/common";
|
||||||
import { PrismaClient } from "@prisma/client";
|
import { PrismaClient } from "@prisma/client";
|
||||||
|
import { DATABASE_URL } from "../constants";
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class PrismaService extends PrismaClient {
|
export class PrismaService extends PrismaClient {
|
||||||
@@ -7,7 +8,7 @@ export class PrismaService extends PrismaClient {
|
|||||||
super({
|
super({
|
||||||
datasources: {
|
datasources: {
|
||||||
db: {
|
db: {
|
||||||
url: "file:../data/pingvin-share.db?connection_limit=1",
|
url: DATABASE_URL,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import { EmailService } from "src/email/email.service";
|
|||||||
import { FileService } from "src/file/file.service";
|
import { FileService } from "src/file/file.service";
|
||||||
import { PrismaService } from "src/prisma/prisma.service";
|
import { PrismaService } from "src/prisma/prisma.service";
|
||||||
import { ReverseShareService } from "src/reverseShare/reverseShare.service";
|
import { ReverseShareService } from "src/reverseShare/reverseShare.service";
|
||||||
|
import { SHARE_DIRECTORY } from "../constants";
|
||||||
import { CreateShareDTO } from "./dto/createShare.dto";
|
import { CreateShareDTO } from "./dto/createShare.dto";
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
@@ -65,7 +66,7 @@ export class ShareService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fs.mkdirSync(`./data/uploads/shares/${share.id}`, {
|
fs.mkdirSync(`${SHARE_DIRECTORY}/${share.id}`, {
|
||||||
recursive: true,
|
recursive: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -99,7 +100,7 @@ export class ShareService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async createZip(shareId: string) {
|
async createZip(shareId: string) {
|
||||||
const path = `./data/uploads/shares/${shareId}`;
|
const path = `${SHARE_DIRECTORY}/${shareId}`;
|
||||||
|
|
||||||
const files = await this.prisma.file.findMany({ where: { shareId } });
|
const files = await this.prisma.file.findMany({ where: { shareId } });
|
||||||
const archive = archiver("zip", {
|
const archive = archiver("zip", {
|
||||||
|
|||||||
@@ -19,4 +19,7 @@ module.exports = withPWA({
|
|||||||
output: "standalone", env: {
|
output: "standalone", env: {
|
||||||
VERSION: version,
|
VERSION: version,
|
||||||
},
|
},
|
||||||
|
serverRuntimeConfig: {
|
||||||
|
apiURL: process.env.API_URL ?? 'http://localhost:8080',
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
4
frontend/package-lock.json
generated
4
frontend/package-lock.json
generated
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "pingvin-share-frontend",
|
"name": "pingvin-share-frontend",
|
||||||
"version": "0.14.1",
|
"version": "0.15.0",
|
||||||
"lockfileVersion": 2,
|
"lockfileVersion": 2,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "pingvin-share-frontend",
|
"name": "pingvin-share-frontend",
|
||||||
"version": "0.14.1",
|
"version": "0.15.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@emotion/react": "^11.10.6",
|
"@emotion/react": "^11.10.6",
|
||||||
"@emotion/server": "^11.10.0",
|
"@emotion/server": "^11.10.0",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "pingvin-share-frontend",
|
"name": "pingvin-share-frontend",
|
||||||
"version": "0.14.1",
|
"version": "0.15.0",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "next dev",
|
"dev": "next dev",
|
||||||
"build": "next build",
|
"build": "next build",
|
||||||
|
|||||||
@@ -116,10 +116,9 @@ const TextPreview = () => {
|
|||||||
const [text, setText] = useState<string | null>(null);
|
const [text, setText] = useState<string | null>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
api.get(`/shares/${shareId}/files/${fileId}?download=false`).then((res) => {
|
api
|
||||||
console.log(res.data);
|
.get(`/shares/${shareId}/files/${fileId}?download=false`)
|
||||||
setText(res.data);
|
.then((res) => setText(res.data));
|
||||||
});
|
|
||||||
}, [shareId, fileId]);
|
}, [shareId, fileId]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ export async function middleware(request: NextRequest) {
|
|||||||
|
|
||||||
// Get config from backend
|
// Get config from backend
|
||||||
const config = await (
|
const config = await (
|
||||||
await fetch("http://localhost:8080/api/configs")
|
await fetch(`${request.nextUrl.origin}/api/configs`)
|
||||||
).json();
|
).json();
|
||||||
|
|
||||||
const getConfig = (key: string) => {
|
const getConfig = (key: string) => {
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import axios from "axios";
|
|||||||
import { getCookie, setCookie } from "cookies-next";
|
import { getCookie, setCookie } from "cookies-next";
|
||||||
import { GetServerSidePropsContext } from "next";
|
import { GetServerSidePropsContext } from "next";
|
||||||
import type { AppProps } from "next/app";
|
import type { AppProps } from "next/app";
|
||||||
|
import getConfig from "next/config";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import Header from "../components/header/Header";
|
import Header from "../components/header/Header";
|
||||||
@@ -117,6 +118,8 @@ function App({ Component, pageProps }: AppProps) {
|
|||||||
// Fetch user and config variables on server side when the first request is made
|
// Fetch user and config variables on server side when the first request is made
|
||||||
// These will get passed as a page prop to the App component and stored in the contexts
|
// These will get passed as a page prop to the App component and stored in the contexts
|
||||||
App.getInitialProps = async ({ ctx }: { ctx: GetServerSidePropsContext }) => {
|
App.getInitialProps = async ({ ctx }: { ctx: GetServerSidePropsContext }) => {
|
||||||
|
const { apiURL } = getConfig().serverRuntimeConfig;
|
||||||
|
|
||||||
let pageProps: {
|
let pageProps: {
|
||||||
user?: CurrentUser;
|
user?: CurrentUser;
|
||||||
configVariables?: Config[];
|
configVariables?: Config[];
|
||||||
@@ -130,15 +133,13 @@ App.getInitialProps = async ({ ctx }: { ctx: GetServerSidePropsContext }) => {
|
|||||||
if (ctx.req) {
|
if (ctx.req) {
|
||||||
const cookieHeader = ctx.req.headers.cookie;
|
const cookieHeader = ctx.req.headers.cookie;
|
||||||
|
|
||||||
pageProps.user = await axios(`http://localhost:8080/api/users/me`, {
|
pageProps.user = await axios(`${apiURL}/api/users/me`, {
|
||||||
headers: { cookie: cookieHeader },
|
headers: { cookie: cookieHeader },
|
||||||
})
|
})
|
||||||
.then((res) => res.data)
|
.then((res) => res.data)
|
||||||
.catch(() => null);
|
.catch(() => null);
|
||||||
|
|
||||||
pageProps.configVariables = (
|
pageProps.configVariables = (await axios(`${apiURL}/api/configs`)).data;
|
||||||
await axios(`http://localhost:8080/api/configs`)
|
|
||||||
).data;
|
|
||||||
|
|
||||||
pageProps.route = ctx.req.url;
|
pageProps.route = ctx.req.url;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { NextApiRequest, NextApiResponse } from "next";
|
import { NextApiRequest, NextApiResponse } from "next";
|
||||||
import httpProxyMiddleware from "next-http-proxy-middleware";
|
import httpProxyMiddleware from "next-http-proxy-middleware";
|
||||||
|
import getConfig from "next/config";
|
||||||
|
|
||||||
export const config = {
|
export const config = {
|
||||||
api: {
|
api: {
|
||||||
@@ -8,11 +9,13 @@ export const config = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const { apiURL } = getConfig().serverRuntimeConfig;
|
||||||
|
|
||||||
export default (req: NextApiRequest, res: NextApiResponse) => {
|
export default (req: NextApiRequest, res: NextApiResponse) => {
|
||||||
return httpProxyMiddleware(req, res, {
|
return httpProxyMiddleware(req, res, {
|
||||||
headers: {
|
headers: {
|
||||||
"X-Forwarded-For": req.socket?.remoteAddress ?? "",
|
"X-Forwarded-For": req.socket?.remoteAddress ?? "",
|
||||||
},
|
},
|
||||||
target: "http://localhost:8080",
|
target: apiURL,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
14
frontend/src/pages/api/health.tsx
Normal file
14
frontend/src/pages/api/health.tsx
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import axios from "axios";
|
||||||
|
import { NextApiRequest, NextApiResponse } from "next";
|
||||||
|
import getConfig from "next/config";
|
||||||
|
|
||||||
|
const { apiURL } = getConfig().serverRuntimeConfig;
|
||||||
|
|
||||||
|
export default async (req: NextApiRequest, res: NextApiResponse) => {
|
||||||
|
const apiStatus = await axios
|
||||||
|
.get(`${apiURL}/api/configs`)
|
||||||
|
.then(() => "OK")
|
||||||
|
.catch(() => "ERROR");
|
||||||
|
|
||||||
|
res.status(apiStatus == "OK" ? 200 : 500).send(apiStatus);
|
||||||
|
};
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "pingvin-share",
|
"name": "pingvin-share",
|
||||||
"version": "0.14.1",
|
"version": "0.15.0",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"format": "cd frontend && npm run format && cd ../backend && npm run format",
|
"format": "cd frontend && npm run format && cd ../backend && npm run format",
|
||||||
"lint": "cd frontend && npm run lint && cd ../backend && npm run lint",
|
"lint": "cd frontend && npm run lint && cd ../backend && npm run lint",
|
||||||
|
|||||||
Reference in New Issue
Block a user