mirror of
https://github.com/kyantech/Palmr.git
synced 2025-10-23 06:11:58 +00:00
feat(demo): add DEMO_MODE environment variable and storage limits
- Introduced a new DEMO_MODE environment variable to toggle demo functionality. - Updated FileController and ReverseShareService to limit user storage to 200MB when DEMO_MODE is enabled. - Enhanced StorageService to reflect demo storage limits in disk space calculations. - Added missing authentication providers in the settings form and server start script for better provider management.
This commit is contained in:
@@ -14,6 +14,7 @@ const envSchema = z.object({
|
||||
S3_FORCE_PATH_STYLE: z.union([z.literal("true"), z.literal("false")]).default("false"),
|
||||
SECURE_SITE: z.union([z.literal("true"), z.literal("false")]).default("false"),
|
||||
DATABASE_URL: z.string().optional().default("file:/app/server/prisma/palmr.db"),
|
||||
DEMO_MODE: z.union([z.literal("true"), z.literal("false")]).default("false"),
|
||||
});
|
||||
|
||||
export const env = envSchema.parse(process.env);
|
||||
|
@@ -1,5 +1,6 @@
|
||||
import { FastifyReply, FastifyRequest } from "fastify";
|
||||
|
||||
import { env } from "../../env";
|
||||
import { prisma } from "../../shared/prisma";
|
||||
import { ConfigService } from "../config/service";
|
||||
import { CheckFileInput, CheckFileSchema, RegisterFileInput, RegisterFileSchema, UpdateFileSchema } from "./dto";
|
||||
@@ -55,7 +56,17 @@ export class FileController {
|
||||
});
|
||||
}
|
||||
|
||||
const maxTotalStorage = BigInt(await this.configService.getValue("maxTotalStoragePerUser"));
|
||||
// Check if DEMO_MODE is enabled
|
||||
const isDemoMode = env.DEMO_MODE === "true";
|
||||
|
||||
let maxTotalStorage: bigint;
|
||||
if (isDemoMode) {
|
||||
// In demo mode, limit all users to 200MB
|
||||
maxTotalStorage = BigInt(200 * 1024 * 1024); // 200MB in bytes
|
||||
} else {
|
||||
// Normal behavior - use maxTotalStoragePerUser configuration
|
||||
maxTotalStorage = BigInt(await this.configService.getValue("maxTotalStoragePerUser"));
|
||||
}
|
||||
|
||||
const userFiles = await prisma.file.findMany({
|
||||
where: { userId },
|
||||
@@ -127,7 +138,17 @@ export class FileController {
|
||||
});
|
||||
}
|
||||
|
||||
const maxTotalStorage = BigInt(await this.configService.getValue("maxTotalStoragePerUser"));
|
||||
// Check if DEMO_MODE is enabled
|
||||
const isDemoMode = env.DEMO_MODE === "true";
|
||||
|
||||
let maxTotalStorage: bigint;
|
||||
if (isDemoMode) {
|
||||
// In demo mode, limit all users to 200MB
|
||||
maxTotalStorage = BigInt(200 * 1024 * 1024); // 200MB in bytes
|
||||
} else {
|
||||
// Normal behavior - use maxTotalStoragePerUser configuration
|
||||
maxTotalStorage = BigInt(await this.configService.getValue("maxTotalStoragePerUser"));
|
||||
}
|
||||
|
||||
const userFiles = await prisma.file.findMany({
|
||||
where: { userId },
|
||||
|
@@ -1,5 +1,6 @@
|
||||
import { PrismaClient } from "@prisma/client";
|
||||
|
||||
import { env } from "../../env";
|
||||
import { FileService } from "../file/service";
|
||||
import {
|
||||
CreateReverseShareInput,
|
||||
@@ -513,7 +514,18 @@ export class ReverseShareService {
|
||||
throw new Error(`File size exceeds the maximum allowed size of ${maxSizeMB}MB`);
|
||||
}
|
||||
|
||||
const maxTotalStorage = BigInt(await configService.getValue("maxTotalStoragePerUser"));
|
||||
// Check if DEMO_MODE is enabled
|
||||
const isDemoMode = env.DEMO_MODE === "true";
|
||||
|
||||
let maxTotalStorage: bigint;
|
||||
if (isDemoMode) {
|
||||
// In demo mode, limit all users to 200MB
|
||||
maxTotalStorage = BigInt(200 * 1024 * 1024); // 200MB in bytes
|
||||
} else {
|
||||
// Normal behavior - use maxTotalStoragePerUser configuration
|
||||
maxTotalStorage = BigInt(await configService.getValue("maxTotalStoragePerUser"));
|
||||
}
|
||||
|
||||
const userFiles = await prisma.file.findMany({
|
||||
where: { userId: creatorId },
|
||||
select: { size: true },
|
||||
|
@@ -3,14 +3,13 @@ import fs from "node:fs";
|
||||
import { promisify } from "util";
|
||||
import { PrismaClient } from "@prisma/client";
|
||||
|
||||
import { env } from "../../env";
|
||||
import { IS_RUNNING_IN_CONTAINER } from "../../utils/container-detection";
|
||||
import { ConfigService } from "../config/service";
|
||||
|
||||
const execAsync = promisify(exec);
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
// TODO: REMOVE LOGGING AFTER TESTING
|
||||
|
||||
export class StorageService {
|
||||
private configService = new ConfigService();
|
||||
|
||||
@@ -26,27 +25,23 @@ export class StorageService {
|
||||
private _parseSize(value: string): number {
|
||||
if (!value) return 0;
|
||||
|
||||
// Remove any whitespace and convert to lowercase
|
||||
const cleanValue = value.trim().toLowerCase();
|
||||
|
||||
// Extract the numeric part
|
||||
const numericMatch = cleanValue.match(/^(\d+(?:\.\d+)?)/);
|
||||
if (!numericMatch) return 0;
|
||||
|
||||
const numericValue = parseFloat(numericMatch[1]);
|
||||
if (Number.isNaN(numericValue)) return 0;
|
||||
|
||||
// Determine the unit multiplier
|
||||
if (cleanValue.includes("t")) {
|
||||
return Math.round(numericValue * 1024 * 1024 * 1024 * 1024); // TB to bytes
|
||||
return Math.round(numericValue * 1024 * 1024 * 1024 * 1024);
|
||||
} else if (cleanValue.includes("g")) {
|
||||
return Math.round(numericValue * 1024 * 1024 * 1024); // GB to bytes
|
||||
return Math.round(numericValue * 1024 * 1024 * 1024);
|
||||
} else if (cleanValue.includes("m")) {
|
||||
return Math.round(numericValue * 1024 * 1024); // MB to bytes
|
||||
return Math.round(numericValue * 1024 * 1024);
|
||||
} else if (cleanValue.includes("k")) {
|
||||
return Math.round(numericValue * 1024); // KB to bytes
|
||||
return Math.round(numericValue * 1024);
|
||||
} else {
|
||||
// Assume bytes if no unit
|
||||
return Math.round(numericValue);
|
||||
}
|
||||
}
|
||||
@@ -83,10 +78,8 @@ export class StorageService {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Handle different Linux/Unix command formats
|
||||
const lines = stdout.trim().split("\n");
|
||||
|
||||
// Handle findmnt command output
|
||||
if (command.includes("findmnt")) {
|
||||
if (lines.length >= 1) {
|
||||
const parts = lines[0].trim().split(/\s+/);
|
||||
@@ -96,10 +89,7 @@ export class StorageService {
|
||||
total = this._parseSize(sizeStr);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Handle stat -f command output
|
||||
else if (command.includes("stat -f")) {
|
||||
// Parse stat -f output (different format)
|
||||
} else if (command.includes("stat -f")) {
|
||||
let blockSize = 0;
|
||||
let totalBlocks = 0;
|
||||
let freeBlocks = 0;
|
||||
@@ -117,29 +107,19 @@ export class StorageService {
|
||||
if (blockSize > 0 && totalBlocks > 0) {
|
||||
total = totalBlocks * blockSize;
|
||||
available = freeBlocks * blockSize;
|
||||
console.log(
|
||||
`📊 stat -f parsed: blockSize=${blockSize}, totalBlocks=${totalBlocks}, freeBlocks=${freeBlocks}`
|
||||
);
|
||||
} else {
|
||||
console.warn(
|
||||
`❌ stat -f parsing failed: blockSize=${blockSize}, totalBlocks=${totalBlocks}, freeBlocks=${freeBlocks}`
|
||||
);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
// Handle df --output format
|
||||
else if (command.includes("--output=")) {
|
||||
} else if (command.includes("--output=")) {
|
||||
if (lines.length >= 2) {
|
||||
const parts = lines[1].trim().split(/\s+/);
|
||||
if (parts.length >= 2) {
|
||||
const [availStr, sizeStr] = parts;
|
||||
available = this._safeParseInt(availStr) * 1024; // df --output gives in KB
|
||||
available = this._safeParseInt(availStr) * 1024;
|
||||
total = this._safeParseInt(sizeStr) * 1024;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Handle regular df command output
|
||||
else {
|
||||
} else {
|
||||
if (lines.length >= 2) {
|
||||
const parts = lines[1].trim().split(/\s+/);
|
||||
if (parts.length >= 4) {
|
||||
@@ -148,11 +128,9 @@ export class StorageService {
|
||||
total = this._safeParseInt(size);
|
||||
available = this._safeParseInt(avail);
|
||||
} else if (command.includes("-h")) {
|
||||
// Handle human-readable format (e.g., "1.5G", "500M")
|
||||
total = this._parseSize(size);
|
||||
available = this._parseSize(avail);
|
||||
} else {
|
||||
// Default to KB (standard df output)
|
||||
total = this._safeParseInt(size) * 1024;
|
||||
available = this._safeParseInt(avail) * 1024;
|
||||
}
|
||||
@@ -164,22 +142,16 @@ export class StorageService {
|
||||
if (total > 0 && available >= 0) {
|
||||
return { total, available };
|
||||
} else {
|
||||
console.warn(`Invalid values parsed: total=${total}, available=${available} for command: ${command}`);
|
||||
return null;
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn(`Command failed: ${command}`, error);
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets detailed mount information for debugging
|
||||
*/
|
||||
private async _getMountInfo(path: string): Promise<{ filesystem: string; mountPoint: string; type: string } | null> {
|
||||
try {
|
||||
if (!fs.existsSync("/proc/mounts")) {
|
||||
console.log("❌ /proc/mounts not found for mount info");
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -189,26 +161,11 @@ export class StorageService {
|
||||
let bestMatch = null;
|
||||
let bestMatchLength = 0;
|
||||
|
||||
console.log(`🔍 Getting mount info for path: ${path}`);
|
||||
|
||||
for (const line of lines) {
|
||||
const parts = line.split(/\s+/);
|
||||
if (parts.length >= 3) {
|
||||
const [filesystem, mountPoint, type] = parts;
|
||||
|
||||
// Log interesting filesystems for debugging
|
||||
if (
|
||||
filesystem.includes("volume") ||
|
||||
filesystem.includes("mapper") ||
|
||||
type === "ext4" ||
|
||||
type === "btrfs" ||
|
||||
type === "xfs" ||
|
||||
mountPoint.includes("volume") ||
|
||||
mountPoint.includes("app")
|
||||
) {
|
||||
console.log(`📋 Mount detail: ${filesystem} → ${mountPoint} (${type})`);
|
||||
}
|
||||
|
||||
if (path.startsWith(mountPoint) && mountPoint.length > bestMatchLength) {
|
||||
bestMatch = { filesystem, mountPoint, type };
|
||||
bestMatchLength = mountPoint.length;
|
||||
@@ -216,25 +173,15 @@ export class StorageService {
|
||||
}
|
||||
}
|
||||
|
||||
if (bestMatch) {
|
||||
console.log(`🎯 Selected mount info: ${bestMatch.filesystem} → ${bestMatch.mountPoint} (${bestMatch.type})`);
|
||||
}
|
||||
|
||||
return bestMatch;
|
||||
} catch (error) {
|
||||
console.warn(`Could not get mount info for ${path}:`, error);
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Detects if a path is a bind mount or mount point by checking /proc/mounts
|
||||
* Returns the actual filesystem path for bind mounts
|
||||
*/
|
||||
private async _detectMountPoint(path: string): Promise<string | null> {
|
||||
try {
|
||||
if (!fs.existsSync("/proc/mounts")) {
|
||||
console.log("❌ /proc/mounts not found, cannot detect mount points");
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -244,51 +191,32 @@ export class StorageService {
|
||||
let bestMatch = null;
|
||||
let bestMatchLength = 0;
|
||||
|
||||
console.log(`🔍 Checking ${lines.length} mount points for path: ${path}`);
|
||||
|
||||
for (const line of lines) {
|
||||
const parts = line.split(/\s+/);
|
||||
if (parts.length >= 3) {
|
||||
const [device, mountPoint, filesystem] = parts;
|
||||
|
||||
// Log useful mount information for debugging
|
||||
if (mountPoint.includes("volume") || mountPoint.includes("app") || mountPoint === "/") {
|
||||
console.log(`📍 Found mount: ${device} → ${mountPoint} (${filesystem})`);
|
||||
}
|
||||
|
||||
if (path.startsWith(mountPoint) && mountPoint.length > bestMatchLength) {
|
||||
bestMatch = mountPoint;
|
||||
bestMatchLength = mountPoint.length;
|
||||
console.log(`✅ Better match found: ${mountPoint} (length: ${mountPoint.length})`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (bestMatch && bestMatch !== "/") {
|
||||
console.log(`🎯 Selected mount point: ${bestMatch}`);
|
||||
return bestMatch;
|
||||
}
|
||||
|
||||
console.log("❌ No specific mount point found, using root");
|
||||
return null;
|
||||
} catch (error) {
|
||||
console.warn(`Could not detect mount point for ${path}:`, error);
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets filesystem information for a specific path, with bind mount detection
|
||||
*/
|
||||
private async _getFileSystemInfo(
|
||||
path: string
|
||||
): Promise<{ total: number; available: number; mountPoint?: string } | null> {
|
||||
try {
|
||||
const mountInfo = await this._getMountInfo(path);
|
||||
if (mountInfo && mountInfo.mountPoint !== "/") {
|
||||
console.log(`📁 Bind mount detected: ${path} → ${mountInfo.filesystem} (${mountInfo.type})`);
|
||||
}
|
||||
|
||||
const mountPoint = await this._detectMountPoint(path);
|
||||
const targetPath = mountPoint || path;
|
||||
|
||||
@@ -298,30 +226,21 @@ export class StorageService {
|
||||
: process.platform === "darwin"
|
||||
? [`df -k "${targetPath}"`, `df "${targetPath}"`]
|
||||
: [
|
||||
// Try different df commands for better compatibility
|
||||
`df -B1 "${targetPath}"`,
|
||||
`df -k "${targetPath}"`,
|
||||
`df "${targetPath}"`,
|
||||
// Additional commands for Synology NAS and other systems
|
||||
`df -h "${targetPath}"`,
|
||||
`df -T "${targetPath}"`,
|
||||
// Fallback to statfs if available
|
||||
`stat -f "${targetPath}"`,
|
||||
// Direct filesystem commands
|
||||
`findmnt -n -o AVAIL,SIZE "${targetPath}"`,
|
||||
`findmnt -n -o AVAIL,SIZE,TARGET "${targetPath}"`,
|
||||
// Alternative df with different formatting
|
||||
`df -P "${targetPath}"`,
|
||||
`df --output=avail,size "${targetPath}"`,
|
||||
];
|
||||
|
||||
console.log(`🔍 Trying ${commandsToTry.length} commands for path: ${targetPath}`);
|
||||
|
||||
for (const command of commandsToTry) {
|
||||
console.log(`🔧 Executing command: ${command}`);
|
||||
const result = await this._tryDiskSpaceCommand(command);
|
||||
if (result) {
|
||||
console.log(`✅ Command successful: ${command}`);
|
||||
return {
|
||||
...result,
|
||||
mountPoint: mountPoint || undefined,
|
||||
@@ -329,17 +248,12 @@ export class StorageService {
|
||||
}
|
||||
}
|
||||
|
||||
console.warn(`❌ All commands failed for path: ${targetPath}`);
|
||||
return null;
|
||||
} catch (error) {
|
||||
console.warn(`Error getting filesystem info for ${path}:`, error);
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Dynamically detect Synology volume paths by reading /proc/mounts
|
||||
*/
|
||||
private async _detectSynologyVolumes(): Promise<string[]> {
|
||||
try {
|
||||
if (!fs.existsSync("/proc/mounts")) {
|
||||
@@ -355,71 +269,48 @@ export class StorageService {
|
||||
if (parts.length >= 2) {
|
||||
const [, mountPoint] = parts;
|
||||
|
||||
// Check if this is a Synology volume mount point
|
||||
if (mountPoint.match(/^\/volume\d+$/)) {
|
||||
synologyPaths.push(mountPoint);
|
||||
console.log(`🔍 Found Synology volume: ${mountPoint}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return synologyPaths;
|
||||
} catch (error) {
|
||||
console.warn("Could not detect Synology volumes:", error);
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
private async _getDiskSpaceMultiplePaths(): Promise<{ total: number; available: number } | null> {
|
||||
// Base paths that work for all systems
|
||||
const basePaths = IS_RUNNING_IN_CONTAINER
|
||||
? ["/app/server/uploads", "/app/server/temp-uploads", "/app/server/temp-chunks", "/app/server", "/app", "/"]
|
||||
: [".", "./uploads", process.cwd()];
|
||||
|
||||
// Dynamically detect Synology volume paths
|
||||
const synologyPaths = await this._detectSynologyVolumes();
|
||||
|
||||
// Combine base paths with detected Synology paths
|
||||
const pathsToTry = [...basePaths, ...synologyPaths];
|
||||
|
||||
console.log(`🔍 Attempting disk space detection for ${pathsToTry.length} paths...`);
|
||||
console.log(`📋 Synology volumes detected: ${synologyPaths.length > 0 ? synologyPaths.join(", ") : "none"}`);
|
||||
|
||||
for (const pathToCheck of pathsToTry) {
|
||||
console.log(`📁 Checking path: ${pathToCheck}`);
|
||||
|
||||
if (pathToCheck.includes("uploads") || pathToCheck.includes("temp-")) {
|
||||
try {
|
||||
if (!fs.existsSync(pathToCheck)) {
|
||||
fs.mkdirSync(pathToCheck, { recursive: true });
|
||||
}
|
||||
} catch (err) {
|
||||
console.warn(`Could not create path ${pathToCheck}:`, err);
|
||||
} catch {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (!fs.existsSync(pathToCheck)) {
|
||||
console.log(`❌ Path does not exist: ${pathToCheck}`);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Use the new filesystem detection method
|
||||
const result = await this._getFileSystemInfo(pathToCheck);
|
||||
if (result) {
|
||||
if (result.mountPoint) {
|
||||
console.log(`✅ Storage resolved via bind mount: ${result.mountPoint}`);
|
||||
}
|
||||
console.log(
|
||||
`✅ Disk space detected for path ${pathToCheck}: ${(result.total / (1024 * 1024 * 1024)).toFixed(2)}GB total, ${(result.available / (1024 * 1024 * 1024)).toFixed(2)}GB available`
|
||||
);
|
||||
return { total: result.total, available: result.available };
|
||||
} else {
|
||||
console.log(`❌ No filesystem info available for path: ${pathToCheck}`);
|
||||
}
|
||||
}
|
||||
|
||||
console.error("❌ All disk space detection attempts failed");
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -433,52 +324,94 @@ export class StorageService {
|
||||
uploadAllowed: boolean;
|
||||
}> {
|
||||
try {
|
||||
const isDemoMode = env.DEMO_MODE === "true";
|
||||
|
||||
if (isAdmin) {
|
||||
const diskInfo = await this._getDiskSpaceMultiplePaths();
|
||||
if (isDemoMode) {
|
||||
const demoMaxStorage = 200 * 1024 * 1024;
|
||||
const demoMaxStorageGB = this._ensureNumber(demoMaxStorage / (1024 * 1024 * 1024), 0);
|
||||
|
||||
if (!diskInfo) {
|
||||
console.error("❌ Could not determine disk space - system configuration issue");
|
||||
throw new Error("Unable to determine actual disk space - system configuration issue");
|
||||
const userFiles = await prisma.file.findMany({
|
||||
where: { userId },
|
||||
select: { size: true },
|
||||
});
|
||||
|
||||
const totalUsedStorage = userFiles.reduce((acc, file) => acc + file.size, BigInt(0));
|
||||
const usedStorageGB = this._ensureNumber(Number(totalUsedStorage) / (1024 * 1024 * 1024), 0);
|
||||
const availableStorageGB = this._ensureNumber(demoMaxStorageGB - usedStorageGB, 0);
|
||||
|
||||
return {
|
||||
diskSizeGB: Number(demoMaxStorageGB.toFixed(2)),
|
||||
diskUsedGB: Number(usedStorageGB.toFixed(2)),
|
||||
diskAvailableGB: Number(availableStorageGB.toFixed(2)),
|
||||
uploadAllowed: availableStorageGB > 0,
|
||||
};
|
||||
} else {
|
||||
const diskInfo = await this._getDiskSpaceMultiplePaths();
|
||||
|
||||
if (!diskInfo) {
|
||||
throw new Error("Unable to determine actual disk space - system configuration issue");
|
||||
}
|
||||
|
||||
const { total, available } = diskInfo;
|
||||
const used = total - available;
|
||||
|
||||
const diskSizeGB = this._ensureNumber(total / (1024 * 1024 * 1024), 0);
|
||||
const diskUsedGB = this._ensureNumber(used / (1024 * 1024 * 1024), 0);
|
||||
const diskAvailableGB = this._ensureNumber(available / (1024 * 1024 * 1024), 0);
|
||||
|
||||
return {
|
||||
diskSizeGB: Number(diskSizeGB.toFixed(2)),
|
||||
diskUsedGB: Number(diskUsedGB.toFixed(2)),
|
||||
diskAvailableGB: Number(diskAvailableGB.toFixed(2)),
|
||||
uploadAllowed: diskAvailableGB > 0.1,
|
||||
};
|
||||
}
|
||||
|
||||
const { total, available } = diskInfo;
|
||||
const used = total - available;
|
||||
|
||||
const diskSizeGB = this._ensureNumber(total / (1024 * 1024 * 1024), 0);
|
||||
const diskUsedGB = this._ensureNumber(used / (1024 * 1024 * 1024), 0);
|
||||
const diskAvailableGB = this._ensureNumber(available / (1024 * 1024 * 1024), 0);
|
||||
|
||||
return {
|
||||
diskSizeGB: Number(diskSizeGB.toFixed(2)),
|
||||
diskUsedGB: Number(diskUsedGB.toFixed(2)),
|
||||
diskAvailableGB: Number(diskAvailableGB.toFixed(2)),
|
||||
uploadAllowed: diskAvailableGB > 0.1, // At least 100MB free
|
||||
};
|
||||
} else if (userId) {
|
||||
const maxTotalStorage = BigInt(await this.configService.getValue("maxTotalStoragePerUser"));
|
||||
const maxStorageGB = this._ensureNumber(Number(maxTotalStorage) / (1024 * 1024 * 1024), 10);
|
||||
if (isDemoMode) {
|
||||
const demoMaxStorage = 200 * 1024 * 1024;
|
||||
const demoMaxStorageGB = this._ensureNumber(demoMaxStorage / (1024 * 1024 * 1024), 0);
|
||||
|
||||
const userFiles = await prisma.file.findMany({
|
||||
where: { userId },
|
||||
select: { size: true },
|
||||
});
|
||||
const userFiles = await prisma.file.findMany({
|
||||
where: { userId },
|
||||
select: { size: true },
|
||||
});
|
||||
|
||||
const totalUsedStorage = userFiles.reduce((acc, file) => acc + file.size, BigInt(0));
|
||||
const totalUsedStorage = userFiles.reduce((acc, file) => acc + file.size, BigInt(0));
|
||||
const usedStorageGB = this._ensureNumber(Number(totalUsedStorage) / (1024 * 1024 * 1024), 0);
|
||||
const availableStorageGB = this._ensureNumber(demoMaxStorageGB - usedStorageGB, 0);
|
||||
|
||||
const usedStorageGB = this._ensureNumber(Number(totalUsedStorage) / (1024 * 1024 * 1024), 0);
|
||||
const availableStorageGB = this._ensureNumber(maxStorageGB - usedStorageGB, 0);
|
||||
return {
|
||||
diskSizeGB: Number(demoMaxStorageGB.toFixed(2)),
|
||||
diskUsedGB: Number(usedStorageGB.toFixed(2)),
|
||||
diskAvailableGB: Number(availableStorageGB.toFixed(2)),
|
||||
uploadAllowed: availableStorageGB > 0,
|
||||
};
|
||||
} else {
|
||||
const maxTotalStorage = BigInt(await this.configService.getValue("maxTotalStoragePerUser"));
|
||||
const maxStorageGB = this._ensureNumber(Number(maxTotalStorage) / (1024 * 1024 * 1024), 10);
|
||||
|
||||
return {
|
||||
diskSizeGB: Number(maxStorageGB.toFixed(2)),
|
||||
diskUsedGB: Number(usedStorageGB.toFixed(2)),
|
||||
diskAvailableGB: Number(availableStorageGB.toFixed(2)),
|
||||
uploadAllowed: availableStorageGB > 0,
|
||||
};
|
||||
const userFiles = await prisma.file.findMany({
|
||||
where: { userId },
|
||||
select: { size: true },
|
||||
});
|
||||
|
||||
const totalUsedStorage = userFiles.reduce((acc, file) => acc + file.size, BigInt(0));
|
||||
const usedStorageGB = this._ensureNumber(Number(totalUsedStorage) / (1024 * 1024 * 1024), 0);
|
||||
const availableStorageGB = this._ensureNumber(maxStorageGB - usedStorageGB, 0);
|
||||
|
||||
return {
|
||||
diskSizeGB: Number(maxStorageGB.toFixed(2)),
|
||||
diskUsedGB: Number(usedStorageGB.toFixed(2)),
|
||||
diskAvailableGB: Number(availableStorageGB.toFixed(2)),
|
||||
uploadAllowed: availableStorageGB > 0,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error("User ID is required for non-admin users");
|
||||
} catch (error) {
|
||||
console.error("❌ Error getting disk space:", error);
|
||||
console.error("Error getting disk space:", error);
|
||||
throw new Error(
|
||||
`Failed to get disk space information: ${error instanceof Error ? error.message : String(error)}`
|
||||
);
|
||||
|
@@ -104,8 +104,10 @@ export function EditProviderForm({
|
||||
{ pattern: "linkedin.com", scopes: ["r_liteprofile", "r_emailaddress"] },
|
||||
{ pattern: "auth0.com", scopes: ["openid", "profile", "email"] },
|
||||
{ pattern: "okta.com", scopes: ["openid", "profile", "email"] },
|
||||
{ pattern: "authentik", scopes: ["openid", "profile", "email"] },
|
||||
{ pattern: "kinde.com", scopes: ["openid", "profile", "email"] },
|
||||
{ pattern: "zitadel.com", scopes: ["openid", "profile", "email"] },
|
||||
{ pattern: "pocketid", scopes: ["openid", "profile", "email"] },
|
||||
];
|
||||
|
||||
for (const { pattern, scopes } of providerPatterns) {
|
||||
|
@@ -86,6 +86,19 @@ else
|
||||
return;
|
||||
}
|
||||
|
||||
const expectedProviders = ['google', 'discord', 'github', 'auth0', 'kinde', 'zitadel', 'authentik', 'frontegg', 'pocketid'];
|
||||
const existingProviders = await prisma.authProvider.findMany({
|
||||
select: { name: true }
|
||||
});
|
||||
const existingProviderNames = existingProviders.map(p => p.name);
|
||||
|
||||
const missingProviders = expectedProviders.filter(name => !existingProviderNames.includes(name));
|
||||
|
||||
if (missingProviders.length > 0) {
|
||||
console.log('true');
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('false');
|
||||
} catch (error) {
|
||||
console.log('true');
|
||||
@@ -117,6 +130,19 @@ else
|
||||
return;
|
||||
}
|
||||
|
||||
const expectedProviders = ['google', 'discord', 'github', 'auth0', 'kinde', 'zitadel', 'authentik', 'frontegg', 'pocketid'];
|
||||
const existingProviders = await prisma.authProvider.findMany({
|
||||
select: { name: true }
|
||||
});
|
||||
const existingProviderNames = existingProviders.map(p => p.name);
|
||||
|
||||
const missingProviders = expectedProviders.filter(name => !existingProviderNames.includes(name));
|
||||
|
||||
if (missingProviders.length > 0) {
|
||||
console.log('true');
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('false');
|
||||
} catch (error) {
|
||||
console.log('true');
|
||||
@@ -132,6 +158,72 @@ else
|
||||
|
||||
if [ "$NEEDS_SEEDING" = "true" ]; then
|
||||
echo "🌱 New tables detected or missing data, running seed..."
|
||||
|
||||
# Check which providers are missing for better logging
|
||||
MISSING_PROVIDERS=$(
|
||||
if [ "$(id -u)" = "0" ]; then
|
||||
su-exec $TARGET_UID:$TARGET_GID node -e "
|
||||
const { PrismaClient } = require('@prisma/client');
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
async function checkMissingProviders() {
|
||||
try {
|
||||
const expectedProviders = ['google', 'discord', 'github', 'auth0', 'kinde', 'zitadel', 'authentik', 'frontegg', 'pocketid'];
|
||||
const existingProviders = await prisma.authProvider.findMany({
|
||||
select: { name: true }
|
||||
});
|
||||
const existingProviderNames = existingProviders.map(p => p.name);
|
||||
const missingProviders = expectedProviders.filter(name => !existingProviderNames.includes(name));
|
||||
|
||||
if (missingProviders.length > 0) {
|
||||
console.log('Missing providers: ' + missingProviders.join(', '));
|
||||
} else {
|
||||
console.log('No missing providers');
|
||||
}
|
||||
} catch (error) {
|
||||
console.log('Error checking providers');
|
||||
} finally {
|
||||
await prisma.\$disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
checkMissingProviders();
|
||||
" 2>/dev/null || echo "Error checking providers"
|
||||
else
|
||||
node -e "
|
||||
const { PrismaClient } = require('@prisma/client');
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
async function checkMissingProviders() {
|
||||
try {
|
||||
const expectedProviders = ['google', 'discord', 'github', 'auth0', 'kinde', 'zitadel', 'authentik', 'frontegg', 'pocketid'];
|
||||
const existingProviders = await prisma.authProvider.findMany({
|
||||
select: { name: true }
|
||||
});
|
||||
const existingProviderNames = existingProviders.map(p => p.name);
|
||||
const missingProviders = expectedProviders.filter(name => !existingProviderNames.includes(name));
|
||||
|
||||
if (missingProviders.length > 0) {
|
||||
console.log('Missing providers: ' + missingProviders.join(', '));
|
||||
} else {
|
||||
console.log('No missing providers');
|
||||
}
|
||||
} catch (error) {
|
||||
console.log('Error checking providers');
|
||||
} finally {
|
||||
await prisma.\$disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
checkMissingProviders();
|
||||
" 2>/dev/null || echo "Error checking providers"
|
||||
fi
|
||||
)
|
||||
|
||||
if [ "$MISSING_PROVIDERS" != "No missing providers" ] && [ "$MISSING_PROVIDERS" != "Error checking providers" ]; then
|
||||
echo "🔍 $MISSING_PROVIDERS"
|
||||
fi
|
||||
|
||||
if [ "$(id -u)" = "0" ]; then
|
||||
su-exec $TARGET_UID:$TARGET_GID node ./prisma/seed.js
|
||||
else
|
||||
|
Reference in New Issue
Block a user