mirror of
https://github.com/kyantech/Palmr.git
synced 2025-11-01 20:43:39 +00:00
Compare commits
11 Commits
v2.0.0-bet
...
v2.0.0-bet
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
63d9abfe3e | ||
|
|
d3ae5ea10c | ||
|
|
d614820aca | ||
|
|
3c2d92c630 | ||
|
|
6ae8436f4b | ||
|
|
39d5980936 | ||
|
|
27e0e7c8da | ||
|
|
02bc1df0c1 | ||
|
|
c1baa3a16d | ||
|
|
cfc103e056 | ||
|
|
1a0b565ae0 |
@@ -49,6 +49,10 @@
|
||||
|
||||
</br>
|
||||
|
||||
## 🤝 Supporters
|
||||
|
||||
[<img src="https://i.ibb.co/nMN40STL/Repoflow.png" width="200px" alt="Daniel Luiz Alves" />](https://www.repoflow.io/)
|
||||
|
||||
## ⭐ Star History
|
||||
|
||||
<a href="https://www.star-history.com/#kyantech/Palmr&Date">
|
||||
|
||||
@@ -30,7 +30,7 @@ Below is the complete content of our `docker-compose.yaml` that can be copied di
|
||||
```yaml
|
||||
services:
|
||||
palmr-api:
|
||||
image: kyantech/palmr-api:v2.0.0-beta # Make sure to use the correct version (latest) of the image
|
||||
image: kyantech/palmr-api:latest # Make sure to use the correct version (latest) of the image
|
||||
container_name: palmr-api
|
||||
depends_on:
|
||||
postgres:
|
||||
@@ -61,7 +61,7 @@ services:
|
||||
start_period: 30s
|
||||
|
||||
palmr-app:
|
||||
image: kyantech/palmr-app:v2.0.0-beta # Make sure to use the correct version (latest) of the image
|
||||
image: kyantech/palmr-app:latest # Make sure to use the correct version (latest) of the image
|
||||
container_name: palmr-web
|
||||
depends_on:
|
||||
palmr-api:
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
FROM node:18
|
||||
FROM node:22-alpine
|
||||
|
||||
WORKDIR /app/server
|
||||
|
||||
RUN apt-get update && apt-get install -y netcat-traditional
|
||||
RUN apk add --no-cache netcat-openbsd
|
||||
RUN npm install -g pnpm
|
||||
|
||||
COPY package*.json ./
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
"@fastify/static": "^8.1.1",
|
||||
"@fastify/swagger": "^9.4.2",
|
||||
"@fastify/swagger-ui": "^5.2.1",
|
||||
"@prisma/client": "^6.3.1",
|
||||
"@prisma/client": "^6.4.1",
|
||||
"@scalar/fastify-api-reference": "^1.25.116",
|
||||
"bcryptjs": "^2.4.3",
|
||||
"fastify": "^5.2.1",
|
||||
|
||||
2
apps/server/pnpm-lock.yaml
generated
2
apps/server/pnpm-lock.yaml
generated
@@ -30,7 +30,7 @@ importers:
|
||||
specifier: ^5.2.1
|
||||
version: 5.2.2
|
||||
'@prisma/client':
|
||||
specifier: ^6.3.1
|
||||
specifier: ^6.4.1
|
||||
version: 6.4.1(prisma@6.4.1(typescript@5.7.3))(typescript@5.7.3)
|
||||
'@scalar/fastify-api-reference':
|
||||
specifier: ^1.25.116
|
||||
|
||||
@@ -2,7 +2,10 @@ import { PrismaClient } from '@prisma/client';
|
||||
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
async function main() {}
|
||||
async function main() {
|
||||
const count = await prisma.user.count();
|
||||
console.log(count);
|
||||
}
|
||||
|
||||
main()
|
||||
.catch((e) => {
|
||||
@@ -11,4 +14,4 @@ main()
|
||||
})
|
||||
.finally(async () => {
|
||||
await prisma.$disconnect();
|
||||
});
|
||||
});
|
||||
@@ -1,4 +1,4 @@
|
||||
import { prisma } from "shared/prisma";
|
||||
import { prisma } from "../../shared/prisma";
|
||||
import { AppController } from "./controller";
|
||||
import { ConfigResponseSchema, BulkUpdateConfigSchema } from "./dto";
|
||||
import { FastifyInstance } from "fastify";
|
||||
|
||||
@@ -169,9 +169,9 @@ export class FileController {
|
||||
if (!fileRecord) {
|
||||
return reply.status(404).send({ error: "File not found." });
|
||||
}
|
||||
|
||||
const fileName = fileRecord.name;
|
||||
const expires = 3600;
|
||||
const url = await this.fileService.getPresignedGetUrl(objectName, expires);
|
||||
const url = await this.fileService.getPresignedGetUrl(objectName, expires, fileName);
|
||||
return reply.send({ url, expiresIn: expires });
|
||||
} catch (error) {
|
||||
console.error("Error in getDownloadUrl:", error);
|
||||
|
||||
@@ -20,24 +20,43 @@ export class FileService {
|
||||
});
|
||||
}
|
||||
|
||||
getPresignedGetUrl(objectName: string, expires: number): Promise<string> {
|
||||
getPresignedGetUrl(objectName: string, expires: number, fileName?: string): Promise<string> {
|
||||
return new Promise((resolve, reject) => {
|
||||
(minioLocalClient as any).presignedGetObject(bucketName, objectName, expires, ((
|
||||
err: Error | null,
|
||||
presignedUrl?: string
|
||||
) => {
|
||||
if (err) {
|
||||
console.error("Erro no presignedGetObject:", err);
|
||||
reject(err);
|
||||
} else if (!presignedUrl) {
|
||||
reject(new Error("URL não gerada"));
|
||||
} else {
|
||||
resolve(presignedUrl);
|
||||
const reqParams: { [key: string]: any } = {};
|
||||
let rcdFileName: string;
|
||||
if (fileName && fileName.trim() !== '') {
|
||||
rcdFileName = fileName;
|
||||
} else {
|
||||
const lastSlashIndex = objectName.lastIndexOf('/');
|
||||
rcdFileName = lastSlashIndex !== -1
|
||||
? objectName.substring(lastSlashIndex + 1)
|
||||
: objectName;
|
||||
if (!rcdFileName) {
|
||||
rcdFileName = 'downloaded_file';
|
||||
}
|
||||
}) as any);
|
||||
}
|
||||
reqParams['response-content-disposition'] = `attachment; filename="${rcdFileName}"`;
|
||||
(minioLocalClient as any).presignedGetObject(
|
||||
bucketName,
|
||||
objectName,
|
||||
expires,
|
||||
reqParams, // Pass the constructed request parameters
|
||||
((err: Error | null, presignedUrl?: string) => {
|
||||
if (err) {
|
||||
console.error("Erro no presignedGetObject:", err);
|
||||
reject(err);
|
||||
} else if (!presignedUrl) {
|
||||
reject(new Error("URL não gerada"));
|
||||
} else {
|
||||
resolve(presignedUrl);
|
||||
}
|
||||
}) as any // Consider using proper MinIO SDK types for the callback if available
|
||||
// to avoid 'as any'
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
deleteObject(objectName: string): Promise<void> {
|
||||
return new Promise((resolve, reject) => {
|
||||
(minioClient as any).removeObject(bucketName, objectName, (err: Error | null) => {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { prisma } from "shared/prisma";
|
||||
import { prisma } from "../../shared/prisma";
|
||||
import { createPasswordSchema } from "../auth/dto";
|
||||
import { UserController } from "./controller";
|
||||
import { UpdateUserSchema, UserResponseSchema } from "./dto";
|
||||
@@ -13,7 +13,7 @@ export async function userRoutes(app: FastifyInstance) {
|
||||
try {
|
||||
const usersCount = await prisma.user.count();
|
||||
|
||||
if (usersCount > 0 ) {
|
||||
if (usersCount > 0) {
|
||||
await request.jwtVerify();
|
||||
if (!request.user.isAdmin) {
|
||||
return reply
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Use the official Node.js image as the base image
|
||||
|
||||
FROM node:18-alpine AS base
|
||||
FROM node:22-alpine AS base
|
||||
|
||||
# Install dependencies only when needed
|
||||
FROM base AS deps
|
||||
|
||||
@@ -81,7 +81,13 @@
|
||||
"uploadFile": "رفع ملف",
|
||||
"loadError": "فشل في تحميل الملفات",
|
||||
"pageTitle": "ملفاتي",
|
||||
"breadcrumb": "ملفاتي"
|
||||
"breadcrumb": "ملفاتي",
|
||||
"downloadStart": "بدأ التنزيل",
|
||||
"downloadError": "فشل في تنزيل الملف",
|
||||
"updateSuccess": "تم تحديث الملف بنجاح",
|
||||
"updateError": "فشل في تحديث الملف",
|
||||
"deleteSuccess": "تم حذف الملف بنجاح",
|
||||
"deleteError": "فشل في حذف الملف"
|
||||
},
|
||||
"filesTable": {
|
||||
"ariaLabel": "جدول الملفات",
|
||||
|
||||
@@ -81,7 +81,13 @@
|
||||
"uploadFile": "Datei hochladen",
|
||||
"loadError": "Fehler beim Laden der Dateien",
|
||||
"pageTitle": "Meine Dateien",
|
||||
"breadcrumb": "Meine Dateien"
|
||||
"breadcrumb": "Meine Dateien",
|
||||
"downloadStart": "Download gestartet",
|
||||
"downloadError": "Fehler beim Herunterladen der Datei",
|
||||
"updateSuccess": "Datei erfolgreich aktualisiert",
|
||||
"updateError": "Fehler beim Aktualisieren der Datei",
|
||||
"deleteSuccess": "Datei erfolgreich gelöscht",
|
||||
"deleteError": "Fehler beim Löschen der Datei"
|
||||
},
|
||||
"filesTable": {
|
||||
"ariaLabel": "Dateitabelle",
|
||||
|
||||
@@ -81,7 +81,13 @@
|
||||
"uploadFile": "Upload File",
|
||||
"loadError": "Failed to load files",
|
||||
"pageTitle": "My Files",
|
||||
"breadcrumb": "My Files"
|
||||
"breadcrumb": "My Files",
|
||||
"downloadStart": "Download started",
|
||||
"downloadError": "Failed to download file",
|
||||
"updateSuccess": "File updated successfully",
|
||||
"updateError": "Failed to update file",
|
||||
"deleteSuccess": "File deleted successfully",
|
||||
"deleteError": "Failed to delete file"
|
||||
},
|
||||
"filesTable": {
|
||||
"ariaLabel": "Files table",
|
||||
|
||||
@@ -81,7 +81,13 @@
|
||||
"uploadFile": "Subir archivo",
|
||||
"loadError": "Error al cargar los archivos",
|
||||
"pageTitle": "Mis archivos",
|
||||
"breadcrumb": "Mis archivos"
|
||||
"breadcrumb": "Mis archivos",
|
||||
"downloadStart": "Descarga iniciada",
|
||||
"downloadError": "Error al descargar el archivo",
|
||||
"updateSuccess": "Archivo actualizado exitosamente",
|
||||
"updateError": "Error al actualizar el archivo",
|
||||
"deleteSuccess": "Archivo eliminado exitosamente",
|
||||
"deleteError": "Error al eliminar el archivo"
|
||||
},
|
||||
"filesTable": {
|
||||
"ariaLabel": "Tabla de archivos",
|
||||
|
||||
@@ -81,7 +81,13 @@
|
||||
"uploadFile": "Envoyer un Fichier",
|
||||
"loadError": "Échec du chargement des fichiers",
|
||||
"pageTitle": "Mes Fichiers",
|
||||
"breadcrumb": "Mes Fichiers"
|
||||
"breadcrumb": "Mes Fichiers",
|
||||
"downloadStart": "Téléchargement commencé",
|
||||
"downloadError": "Échec du téléchargement du fichier",
|
||||
"updateSuccess": "Fichier mis à jour avec succès",
|
||||
"updateError": "Échec de la mise à jour du fichier",
|
||||
"deleteSuccess": "Fichier supprimé avec succès",
|
||||
"deleteError": "Échec de la suppression du fichier"
|
||||
},
|
||||
"filesTable": {
|
||||
"ariaLabel": "Tableau des fichiers",
|
||||
|
||||
@@ -81,7 +81,13 @@
|
||||
"uploadFile": "फाइल अपलोड करें",
|
||||
"loadError": "फाइलें लोड करने में त्रुटि",
|
||||
"pageTitle": "मेरी फाइलें",
|
||||
"breadcrumb": "मेरी फाइलें"
|
||||
"breadcrumb": "मेरी फाइलें",
|
||||
"downloadStart": "डाउनलोड शुरू हुआ",
|
||||
"downloadError": "फाइल डाउनलोड करने में त्रुटि",
|
||||
"updateSuccess": "फाइल सफलतापूर्वक अपडेट हुई",
|
||||
"updateError": "फाइल अपडेट करने में त्रुटि",
|
||||
"deleteSuccess": "फाइल सफलतापूर्वक हटाई गई",
|
||||
"deleteError": "फाइल हटाने में त्रुटि"
|
||||
},
|
||||
"filesTable": {
|
||||
"ariaLabel": "फाइल टेबल",
|
||||
|
||||
@@ -81,7 +81,13 @@
|
||||
"uploadFile": "ファイルをアップロード",
|
||||
"loadError": "ファイルの読み込みに失敗しました",
|
||||
"pageTitle": "マイファイル",
|
||||
"breadcrumb": "マイファイル"
|
||||
"breadcrumb": "マイファイル",
|
||||
"downloadStart": "ダウンロードが開始されました",
|
||||
"downloadError": "ファイルのダウンロードに失敗しました",
|
||||
"updateSuccess": "ファイルが正常に更新されました",
|
||||
"updateError": "ファイルの更新に失敗しました",
|
||||
"deleteSuccess": "ファイルが正常に削除されました",
|
||||
"deleteError": "ファイルの削除に失敗しました"
|
||||
},
|
||||
"filesTable": {
|
||||
"ariaLabel": "ファイルテーブル",
|
||||
|
||||
@@ -81,7 +81,13 @@
|
||||
"uploadFile": "파일 업로드",
|
||||
"loadError": "파일을 불러오는데 실패했습니다",
|
||||
"pageTitle": "내 파일",
|
||||
"breadcrumb": "내 파일"
|
||||
"breadcrumb": "내 파일",
|
||||
"downloadStart": "다운로드가 시작되었습니다",
|
||||
"downloadError": "파일 다운로드에 실패했습니다",
|
||||
"updateSuccess": "파일이 성공적으로 업데이트되었습니다",
|
||||
"updateError": "파일 업데이트에 실패했습니다",
|
||||
"deleteSuccess": "파일이 성공적으로 삭제되었습니다",
|
||||
"deleteError": "파일 삭제에 실패했습니다"
|
||||
},
|
||||
"filesTable": {
|
||||
"ariaLabel": "파일 테이블",
|
||||
|
||||
@@ -81,7 +81,13 @@
|
||||
"uploadFile": "Enviar Arquivo",
|
||||
"loadError": "Falha ao carregar arquivos",
|
||||
"pageTitle": "Meus Arquivos",
|
||||
"breadcrumb": "Meus Arquivos"
|
||||
"breadcrumb": "Meus Arquivos",
|
||||
"downloadStart": "Download iniciado",
|
||||
"downloadError": "Erro ao baixar o arquivo",
|
||||
"updateSuccess": "Arquivo atualizado com sucesso",
|
||||
"updateError": "Erro ao atualizar o arquivo",
|
||||
"deleteSuccess": "Arquivo excluído com sucesso",
|
||||
"deleteError": "Erro ao excluir o arquivo"
|
||||
},
|
||||
"filesTable": {
|
||||
"ariaLabel": "Tabela de arquivos",
|
||||
|
||||
@@ -81,7 +81,13 @@
|
||||
"uploadFile": "Загрузить файл",
|
||||
"loadError": "Ошибка загрузки файлов",
|
||||
"pageTitle": "Мои файлы",
|
||||
"breadcrumb": "Мои файлы"
|
||||
"breadcrumb": "Мои файлы",
|
||||
"downloadStart": "Скачивание начато",
|
||||
"downloadError": "Ошибка при скачивании файла",
|
||||
"updateSuccess": "Файл успешно обновлен",
|
||||
"updateError": "Ошибка при обновлении файла",
|
||||
"deleteSuccess": "Файл успешно удален",
|
||||
"deleteError": "Ошибка при удалении файла"
|
||||
},
|
||||
"filesTable": {
|
||||
"ariaLabel": "Таблица файлов",
|
||||
|
||||
@@ -81,7 +81,13 @@
|
||||
"uploadFile": "Dosya Yükle",
|
||||
"loadError": "Dosyalar yüklenemedi",
|
||||
"pageTitle": "Benim Dosyalarım",
|
||||
"breadcrumb": "Benim Dosyalarım"
|
||||
"breadcrumb": "Benim Dosyalarım",
|
||||
"downloadStart": "İndirme başladı",
|
||||
"downloadError": "Dosya indirilemedi",
|
||||
"updateSuccess": "Dosya başarıyla güncellendi",
|
||||
"updateError": "Dosya güncellenemedi",
|
||||
"deleteSuccess": "Dosya başarıyla silindi",
|
||||
"deleteError": "Dosya silinemedi"
|
||||
},
|
||||
"filesTable": {
|
||||
"ariaLabel": "Dosya Tablosu",
|
||||
|
||||
@@ -81,7 +81,13 @@
|
||||
"uploadFile": "上传文件",
|
||||
"loadError": "加载文件失败",
|
||||
"pageTitle": "我的文件",
|
||||
"breadcrumb": "我的文件"
|
||||
"breadcrumb": "我的文件",
|
||||
"downloadStart": "下载已开始",
|
||||
"downloadError": "下载文件失败",
|
||||
"updateSuccess": "文件更新成功",
|
||||
"updateError": "文件更新失败",
|
||||
"deleteSuccess": "文件删除成功",
|
||||
"deleteError": "文件删除失败"
|
||||
},
|
||||
"filesTable": {
|
||||
"ariaLabel": "文件表格",
|
||||
|
||||
@@ -57,12 +57,17 @@ export function usePublicShare() {
|
||||
const encodedObjectName = encodeURIComponent(objectName);
|
||||
const response = await getDownloadUrl(encodedObjectName);
|
||||
const downloadUrl = response.data.url;
|
||||
|
||||
await downloadFile(downloadUrl, fileName);
|
||||
toast.success(t("share.messages.downloadStarted"));
|
||||
console.log(fileName)
|
||||
const link = document.createElement("a");
|
||||
link.href = downloadUrl;
|
||||
link.download = fileName;
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
toast.success(t("files.downloadStart"));
|
||||
} catch (error) {
|
||||
toast.error(t("share.errors.downloadFailed"));
|
||||
console.error(error);
|
||||
toast.success(t("files.downloadError"));
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { useState } from "react";
|
||||
import { toast } from "sonner";
|
||||
import { useTranslations } from "next-intl";
|
||||
|
||||
import { deleteFile, getDownloadUrl, updateFile } from "@/http/endpoints";
|
||||
|
||||
@@ -32,6 +33,7 @@ export interface FileManagerHook {
|
||||
}
|
||||
|
||||
export function useFileManager(onRefresh: () => Promise<void>) {
|
||||
const t = useTranslations();
|
||||
const [previewFile, setPreviewFile] = useState<PreviewFile | null>(null);
|
||||
const [fileToRename, setFileToRename] = useState<FileToRename | null>(null);
|
||||
const [fileToDelete, setFileToDelete] = useState<FileToDelete | null>(null);
|
||||
@@ -41,23 +43,17 @@ export function useFileManager(onRefresh: () => Promise<void>) {
|
||||
const encodedObjectName = encodeURIComponent(objectName);
|
||||
const response = await getDownloadUrl(encodedObjectName);
|
||||
const downloadUrl = response.data.url;
|
||||
|
||||
const fileResponse = await fetch(downloadUrl);
|
||||
const blob = await fileResponse.blob();
|
||||
const url = window.URL.createObjectURL(blob);
|
||||
|
||||
console.log(fileName)
|
||||
const link = document.createElement("a");
|
||||
|
||||
link.href = url;
|
||||
link.href = downloadUrl;
|
||||
link.download = fileName;
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
|
||||
document.body.removeChild(link);
|
||||
window.URL.revokeObjectURL(url);
|
||||
toast.success(t("files.downloadStart"));
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
toast.error("Failed to download file");
|
||||
toast.success(t("files.downloadError"));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -68,11 +64,11 @@ export function useFileManager(onRefresh: () => Promise<void>) {
|
||||
description: description || null,
|
||||
});
|
||||
await onRefresh();
|
||||
toast.success("File updated successfully");
|
||||
toast.success(t("files.updateSuccess"));
|
||||
setFileToRename(null);
|
||||
} catch (error) {
|
||||
console.error("Failed to update file:", error);
|
||||
toast.error("Failed to update file");
|
||||
toast.success(t("files.updateError"));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -80,11 +76,11 @@ export function useFileManager(onRefresh: () => Promise<void>) {
|
||||
try {
|
||||
await deleteFile(fileId);
|
||||
await onRefresh();
|
||||
toast.success("File deleted successfully");
|
||||
toast.success(t("files.deleteSuccess"));
|
||||
setFileToDelete(null);
|
||||
} catch (error) {
|
||||
console.error("Failed to delete file:", error);
|
||||
toast.error("Failed to delete file");
|
||||
toast.success(t("files.deleteError"));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -99,4 +95,4 @@ export function useFileManager(onRefresh: () => Promise<void>) {
|
||||
handleRename,
|
||||
handleDelete,
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user