Skip to content

Files #15

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,13 @@ OTP_LENGTH=6
OTP_EXPIRATION=15

#DISK STORAGE
DISK_STORAGE_UPLOAD_FOLDER=/home/michee/projects/system-api/file
DISK_STORAGE_UPLOAD_FOLDER=/home/michee/projects/system-api/stock

#FILES_STORAGE
FILE_STORES=disk,minio
FILE_STORAGE=minio
MINIO_BUCKET=new-xflow-test

CRYPTAGE_KEY=secret-key


3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@
node_modules
package-lock.json
logs
.vscode
.vscode
upload
45 changes: 44 additions & 1 deletion docker-compose.yaml
Original file line number Diff line number Diff line change
@@ -1,11 +1,49 @@
services:
traefik:
image: traefik:v3.1
container_name: ntw-traefik
restart: always
command:
- --api.insecure=true
- --api.dashboard=true
- --ping=true
- --providers.docker=true
- --providers.docker.exposedbydefault=false
- --entrypoints.web.address=:80
- --entrypoints.web-secure.address=:443
- --log.level=DEBUG
- --certificatesresolvers.myresolver.acme.caserver=https://acme-staging-v02.api.letsencrypt.org/directory
- --certificatesresolvers.myresolver.acme.storage=/letsencrypt/acme.json
- --certificatesresolvers.myresolver.acme.tlschallenge=true
ports:
- "80:80"
- "443:443"
- "8080:8080"
volumes:
- "/letsencrypt:/letsencrypt"
- "/var/run/docker.sock:/var/run/docker.sock:ro"
- ./certs:/certs
healthcheck:
test: [ "CMD", "traefik", "healthcheck", "--ping" ]
interval: 30s
retries: 10
labels:
- traefik.enable=true
- traefik.http.routers.dashboard.rule=Host(`traefik.localhost`)
- traefik.http.routers.dashboard.service=api@internal
- traefik.http.routers.dashboard.entrypoints=web

app:
build: .
container_name: ntw-app
ports:
- "${PORT}:${PORT}"
env_file:
- .env
labels:
- traefik.enable=true
- traefik.http.routers.app.rule=Host(`app.localhost`)
- traefik.http.services.app.loadbalancer.server.port=${PORT}
depends_on:
- mongo
- redis
Expand Down Expand Up @@ -33,7 +71,7 @@ services:
container_name: ntw-minio
command: server /data --console-address ":${MINIO_CONSOLE_PORT}"
ports:
- "${MINIO_EXT_API_PORT}:${MINIO_API_PORT}"
- "${MINIO_EXT_API_PORT}:${MINIO_API_PORT}"
- "${MINIO_EXT_CONSOLE_PORT}:${MINIO_CONSOLE_PORT}"
environment:
MINIO_ROOT_USER: ${MINIO_ACCESS_KEY}
Expand All @@ -53,6 +91,11 @@ services:
timeout: 10s
retries: 3

labels:
- traefik.enable=true
- traefik.http.routers.app.rule=Host(`mail.localhost`)
- traefik.http.services.app.loadbalancer.server.port=${MAILDEV_WEBAPP_PORT}

volumes:
mongo-data:
minio-data:
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@
"minio": "^8.0.0",
"mongoose": "^8.4.3",
"morgan": "^1.10.0",
"multer": "^1.4.5-lts.1",
"nodemailer": "^6.9.14",
"rate-limiter-flexible": "^5.0.3",
"tsconfig-paths": "^4.2.0",
Expand All @@ -95,6 +96,7 @@
"@types/jest": "^29.5.14",
"@types/jsonwebtoken": "^9.0.6",
"@types/morgan": "^1.9.9",
"@types/multer": "^1.4.12",
"@types/nodemailer": "^6.4.15",
"@types/supertest": "^6.0.2",
"@typescript-eslint/eslint-plugin": "^5.57.1",
Expand Down
20 changes: 18 additions & 2 deletions src/apps/demo/core/api/controllers/todo.controller.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
import { Request, Response, NextFunction } from 'express';
import {
ApiResponse,
ErrorResponse,
ErrorResponseType,
SuccessResponseType,
} from '@nodesandbox/response-kit';
import { TodoService } from 'apps/demo/core/business';
import { CreateTodoRequestSchema } from '../dtos';
import { IFileModel } from 'apps/files';
import FileService from 'apps/files/core/business/services/file.service';
import { NextFunction, Request, Response } from 'express';
import { sanitize } from 'helpers';
import { CreateTodoRequestSchema } from '../dtos';

/**
* Controller to handle the operations related to the Todo resource.
Expand All @@ -26,11 +29,21 @@ export class TodoController {
): Promise<void> {
try {
const _payload = sanitize(req.body, CreateTodoRequestSchema);
const image = req.file;
const service = CONFIG.fs.defaultStore;

if (!_payload.success) {
throw _payload.error;
}

const fileService = new FileService();
const todoImage = (await fileService.createFIle(
service,
image,
)) as SuccessResponseType<IFileModel>;

_payload.data.image = todoImage.document?._id;

const response = await TodoService.create(_payload.data);

if (!response.success) {
Expand Down Expand Up @@ -59,6 +72,9 @@ export class TodoController {
): Promise<void> {
try {
const filters = req.query; // Extract query params for filtering.

console.log('⚡⚡⚡⚡☂️☂️☂️☂️ filters : ', filters);

const response = await TodoService.getTodos(filters);

if (response.success) {
Expand Down
4 changes: 3 additions & 1 deletion src/apps/demo/core/api/routes/todo.routes.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
import { Router } from 'express';
import multer from 'multer';
import { TodoController } from '../controllers';

const router = Router();
const upload = multer();

/**
* Route for creating a new Todo
* POST /todos
*/
router.post('/', TodoController.createTodo);
router.post('/', upload.single('file'), TodoController.createTodo);

/**
* Route for retrieving all Todos, filtered by query parameters
Expand Down
19 changes: 14 additions & 5 deletions src/apps/demo/core/business/services/todo.service.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
import { TodoRepository } from '../repositories';
import { ITodoModel, TodoModel } from 'apps/demo/core/domain';
import { parseSortParam } from 'helpers';
import { BaseService } from '@nodesandbox/repo-framework';
import {
ErrorResponseType,
SuccessResponseType,
} from '@nodesandbox/response-kit';
import { BaseService } from '@nodesandbox/repo-framework';
import { ITodoModel, TodoModel } from 'apps/demo/core/domain';
import { parseSortParam } from 'helpers';
import { TodoRepository } from '../repositories';

class TodoService extends BaseService<ITodoModel, TodoRepository> {
constructor() {
const todoRepo = new TodoRepository(TodoModel);
super(todoRepo, true, [
/*'attribute_to_populate'*/
]); // This will populate the entity field
this.allowedFilterFields = ['dueDate', 'completed', 'priority']; // To filter on these fields, we need to set this
this.allowedFilterFields = ['dueDate', 'completed', 'priority', 'image']; // To filter on these fields, we need to set this
this.searchFields = ['title', 'description']; // This will use the search keyword

/**
Expand All @@ -34,6 +34,7 @@ class TodoService extends BaseService<ITodoModel, TodoRepository> {
search = '',
priority,
completed,
image,
upcoming,
} = filters;

Expand All @@ -42,6 +43,12 @@ class TodoService extends BaseService<ITodoModel, TodoRepository> {
if (priority) query.priority = priority;
if (completed !== undefined) query.completed = completed === 'true';

if (image !== 'true') {
query.image = { $exists: false };
} else {
query.image = { $exists: true, $ne: null };
}

// Handle upcoming due dates
if (upcoming) {
const days = parseInt(upcoming as string) || 7;
Expand All @@ -50,6 +57,8 @@ class TodoService extends BaseService<ITodoModel, TodoRepository> {
query.dueDate = { $gte: new Date(), $lte: futureDate };
}

console.log('⚔️⚔️⚔️⚔️⚔️⚔️ query : ', query);

// Parse sorting parameter using helper function
const sortObject = sort ? parseSortParam(sort) : {};

Expand Down
3 changes: 3 additions & 0 deletions src/apps/demo/core/domain/models/todo.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ const todoSchema = createBaseSchema<ITodoModel>(
description: {
type: String,
},
image: {
type: String,
},
completed: {
type: Boolean,
default: false,
Expand Down
1 change: 1 addition & 0 deletions src/apps/demo/core/domain/types/todo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ export type TodoPriority = 'low' | 'medium' | 'high';
export interface ITodo {
title: string;
description?: string;
image: string;
completed: boolean;
dueDate?: Date;
priority: TodoPriority;
Expand Down
140 changes: 140 additions & 0 deletions src/apps/files/core/api/controllers/file.controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
import FileService from 'apps/files/core/business/services/file.service';
import fs from 'fs';
/* eslint-disable @typescript-eslint/no-unused-vars */
import {
ApiResponse,
ErrorResponseType,
SuccessResponseType,
} from '@nodesandbox/response-kit';
import { IFileModel } from 'apps/files';
import { NextFunction, Request, Response } from 'express';

export class FileController {
/**
* @param req
* @param res
* @param next
*/
static async createFile(
req: Request,
res: Response,
next: NextFunction,
): Promise<void> {
try {
const payload = req.file;
const service = CONFIG.fs.defaultStore;

const fileService = new FileService();
const response = await fileService.createFIle(service, payload);
if (!response.success) {
throw response.error;
}

ApiResponse.success(res, response, 201);
} catch (error) {
ApiResponse.error(res, {
success: false,
error: error,
} as ErrorResponseType);
}
}

/**
* @param req
* @param res
* @param next
*/
static async getFileById(
req: Request,
res: Response,
next: NextFunction,
): Promise<void> {
try {
const fileId = req.params.fileId;
const service = CONFIG.fs.defaultStore;

const fileService = new FileService();
const response = (await fileService.getFile(
service,
fileId,
)) as SuccessResponseType<IFileModel>;

if (response.success) {
ApiResponse.success(res, response);
} else {
throw response.error;
}
} catch (error) {
ApiResponse.error(res, {
success: false,
error: error,
} as ErrorResponseType);
}
}

static async downloadFile(
req: Request,
res: Response,
next: NextFunction,
): Promise<void> {
try {
const fileId = req.params.fileId;
const service = CONFIG.fs.defaultStore;

const fileService = new FileService();
const response = (await fileService.sendFile(
service,
fileId,
)) as SuccessResponseType<IFileModel>;

if (!response.success) {
throw response.error;
}

// TODO Corriger le telechargement des vidéos
res.writeHead(200, {
'content-type': response.document?.mimetype,
'content-length': response.document?.size,
});

const filepath = response.document?.path as string;
const file = fs.readFileSync(filepath);
res.end(file);
} catch (error) {
ApiResponse.error(res, {
success: false,
error: error,
} as ErrorResponseType);
}
}

/**
* @param req
* @param res
* @param next
*/
static async deleteFile(
req: Request,
res: Response,
next: NextFunction,
): Promise<void> {
try {
const fileId = req.params.fileId;
const service = CONFIG.fs.defaultStore;

const fileService = new FileService();
const response = await fileService.deleteFile(service, fileId);

if (!response.success) {
throw response.error;
}

ApiResponse.success(res, response, 201);
} catch (error) {
ApiResponse.error(res, {
success: false,
error: error,
} as ErrorResponseType);
}
}
}
1 change: 1 addition & 0 deletions src/apps/files/core/api/controllers/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './file.controller';
3 changes: 3 additions & 0 deletions src/apps/files/core/api/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
/* eslint-disable prettier/prettier */
export * from './controllers';
export * from './routes';
Loading