Compare commits

..

11 Commits

Author SHA1 Message Date
Daniel Luiz Alves
63d9abfe3e v2.0.0-beta.4 (#34) 2025-05-19 18:10:13 -03:00
Daniel Luiz Alves
d3ae5ea10c build: update Node.js and dependencies in Dockerfiles and package.json
Update the base Node.js image from version 18 to 22-alpine in both web and server Dockerfiles to leverage the latest features and security patches. Additionally, update the @prisma/client dependency to version 6.4.1 in the server package.json and pnpm-lock.yaml to ensure compatibility and stability.
2025-05-19 18:08:19 -03:00
Daniel Luiz Alves
d614820aca add RepoFlow as supporter 2025-05-14 21:44:06 -03:00
Daniel Luiz Alves
3c2d92c630 feat: change download behaviour (#33) 2025-05-14 21:26:52 -03:00
Charly Gley
6ae8436f4b fix: changed download behaviour for the public share as well 2025-05-10 15:03:18 +02:00
Charly Gley
39d5980936 feat: change download behaviour - download directly from minio backend, which makes the browser display the download progress 2025-05-10 03:24:25 +02:00
Daniel Luiz Alves
27e0e7c8da docs: update docker image tags to 'latest' in installation guide (#31) 2025-05-06 11:03:06 -03:00
Daniel Luiz Alves
02bc1df0c1 docs: update docker image tags to 'latest' in installation guide
This change ensures that the installation guide uses the 'latest' tag for docker images instead of the specific 'v2.0.0-beta' version, making it easier for users to always get the most recent version without manually updating the tag.
2025-05-06 11:02:00 -03:00
Daniel Luiz Alves
c1baa3a16d v2.0.0-beta.3 (#30) 2025-05-06 10:06:52 -03:00
Daniel Luiz Alves
cfc103e056 fix: (docker images) (#29) 2025-05-06 09:41:12 -03:00
Charly Gley
1a0b565ae0 fix: (docker images)
changed check-db.mjs to output number in stdout
changed routes.ts to import prisma with relative paths
2025-05-05 21:04:14 +02:00
25 changed files with 157 additions and 58 deletions

View File

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

View File

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

View File

@@ -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 ./

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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) => {

View File

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

View File

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

View File

@@ -81,7 +81,13 @@
"uploadFile": "رفع ملف",
"loadError": "فشل في تحميل الملفات",
"pageTitle": "ملفاتي",
"breadcrumb": "ملفاتي"
"breadcrumb": "ملفاتي",
"downloadStart": "بدأ التنزيل",
"downloadError": "فشل في تنزيل الملف",
"updateSuccess": "تم تحديث الملف بنجاح",
"updateError": "فشل في تحديث الملف",
"deleteSuccess": "تم حذف الملف بنجاح",
"deleteError": "فشل في حذف الملف"
},
"filesTable": {
"ariaLabel": "جدول الملفات",

View File

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

View File

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

View File

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

View File

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

View File

@@ -81,7 +81,13 @@
"uploadFile": "फाइल अपलोड करें",
"loadError": "फाइलें लोड करने में त्रुटि",
"pageTitle": "मेरी फाइलें",
"breadcrumb": "मेरी फाइलें"
"breadcrumb": "मेरी फाइलें",
"downloadStart": "डाउनलोड शुरू हुआ",
"downloadError": "फाइल डाउनलोड करने में त्रुटि",
"updateSuccess": "फाइल सफलतापूर्वक अपडेट हुई",
"updateError": "फाइल अपडेट करने में त्रुटि",
"deleteSuccess": "फाइल सफलतापूर्वक हटाई गई",
"deleteError": "फाइल हटाने में त्रुटि"
},
"filesTable": {
"ariaLabel": "फाइल टेबल",

View File

@@ -81,7 +81,13 @@
"uploadFile": "ファイルをアップロード",
"loadError": "ファイルの読み込みに失敗しました",
"pageTitle": "マイファイル",
"breadcrumb": "マイファイル"
"breadcrumb": "マイファイル",
"downloadStart": "ダウンロードが開始されました",
"downloadError": "ファイルのダウンロードに失敗しました",
"updateSuccess": "ファイルが正常に更新されました",
"updateError": "ファイルの更新に失敗しました",
"deleteSuccess": "ファイルが正常に削除されました",
"deleteError": "ファイルの削除に失敗しました"
},
"filesTable": {
"ariaLabel": "ファイルテーブル",

View File

@@ -81,7 +81,13 @@
"uploadFile": "파일 업로드",
"loadError": "파일을 불러오는데 실패했습니다",
"pageTitle": "내 파일",
"breadcrumb": "내 파일"
"breadcrumb": "내 파일",
"downloadStart": "다운로드가 시작되었습니다",
"downloadError": "파일 다운로드에 실패했습니다",
"updateSuccess": "파일이 성공적으로 업데이트되었습니다",
"updateError": "파일 업데이트에 실패했습니다",
"deleteSuccess": "파일이 성공적으로 삭제되었습니다",
"deleteError": "파일 삭제에 실패했습니다"
},
"filesTable": {
"ariaLabel": "파일 테이블",

View File

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

View File

@@ -81,7 +81,13 @@
"uploadFile": "Загрузить файл",
"loadError": "Ошибка загрузки файлов",
"pageTitle": "Мои файлы",
"breadcrumb": "Мои файлы"
"breadcrumb": "Мои файлы",
"downloadStart": "Скачивание начато",
"downloadError": "Ошибка при скачивании файла",
"updateSuccess": "Файл успешно обновлен",
"updateError": "Ошибка при обновлении файла",
"deleteSuccess": "Файл успешно удален",
"deleteError": "Ошибка при удалении файла"
},
"filesTable": {
"ariaLabel": "Таблица файлов",

View File

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

View File

@@ -81,7 +81,13 @@
"uploadFile": "上传文件",
"loadError": "加载文件失败",
"pageTitle": "我的文件",
"breadcrumb": "我的文件"
"breadcrumb": "我的文件",
"downloadStart": "下载已开始",
"downloadError": "下载文件失败",
"updateSuccess": "文件更新成功",
"updateError": "文件更新失败",
"deleteSuccess": "文件删除成功",
"deleteError": "文件删除失败"
},
"filesTable": {
"ariaLabel": "文件表格",

View File

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

View File

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