Node JS Image Delivery Microservice Challenge [Folder structure]

Continuation from here: http://justcodesnippets.durlut.ro/index.php/2019/12/07/node-js-image-delivery-microservice-challenge-refactoring-1/

 

First of all let’s address the issue of the synchronous calls to check for file existence.

const fileExists: boolean = fs.existsSync(fileAbsolutePath);

We need to make the call async. We can get some info from here: https://stackoverflow.com/questions/40593875/using-filesystem-in-node-js-with-async-await and here https://stackoverflow.com/questions/17699599/node-js-check-if-file-exists

We create a FileUtils class for reusability:

import fs from "fs";
import util from "util";

export class FileUtils {
    public static async createDirectory(directoryPath: string): Promise<boolean> {
        try {
            await this.fsMkDir(directoryPath);
            return true;
        } catch (err) {
            console.log(err);
            return false;
        }
    }

    public static async fileExists(fileAbsolutePath: string): Promise<boolean> {
        try {
            const result = await this.fsStat(fileAbsolutePath);
            return result.isFile();
        } catch (err) {
            console.log(err);
            return false;
        }
    }
    public static async directoryExists(fileAbsolutePath: string): Promise<boolean> {
        try {
            const result = await this.fsStat(fileAbsolutePath);
            return result.isDirectory();
        } catch (err) {
            console.log(err);
            return false;
        }
    }
    // https://stackoverflow.com/questions/40593875/using-filesystem-in-node-js-with-async-await
    // https://stackoverflow.com/questions/17699599/node-js-check-if-file-exists
    private static fsStat = util.promisify(fs.stat);
    private static fsMkDir = util.promisify(fs.mkdir);
}

 

We create a FolderStructureUtils class to controll the new folder structure that will unfold. This will help us bring results faster. Each resolution has its own folder.

import fs from "fs";
import path from "path";
import { FileUtils } from "../utils/file-utils";
export class FolderStructureUtils {

    public static getOriginalImagesDirectoryPath(): string {
        return `${this.imagesFolder}original/`;
    }

    public static getOriginalImageAbsolutePath(imageName: string): string {
        const originalImagesDirectoryPath: string = this.getOriginalImagesDirectoryPath();
        return `${originalImagesDirectoryPath}${imageName}`;
    }

    public static getResolutionImageDirectoryPath(imageResolution: string): string {
        return `${this.imagesFolder}${imageResolution}/`;
    }
    public static async resolutionImageDirectoryExists(imageResolution: string): Promise<boolean> {
        const directoryPath = this.getResolutionImageDirectoryPath(imageResolution);
        return await FileUtils.directoryExists(directoryPath);
    }
    public static async imageWithResolutionExists(imageName: string, imageResolution: string): Promise<boolean> {
        const filePath = this.getImageWithResolutionPath(imageName, imageResolution);
        return await FileUtils.fileExists(filePath);
    }

    public static getImageWithResolutionPath(imageName: string, imageResolution: string): string {
        const directoryPath = this.getResolutionImageDirectoryPath(imageResolution);
        const filePath = `${directoryPath}${imageName}`;
        return filePath;
    }

    public static async createImageResolutionDirectory(imageResolution: string): Promise<string> {
        const directoryPath = this.getResolutionImageDirectoryPath(imageResolution);
        const isDirCreated: boolean = await FileUtils.createDirectory(directoryPath);
        if (isDirCreated) {
            return directoryPath;
        } else {
            throw Error(`Directory ${directoryPath} could not be created`);
        }
    }
    private static imagesFolder: string = path.join(__dirname, `/../images/`);

}

 

Right now, index.ts just looks if the file exists at the specified location. If not, the original file is located and a folder and file at certain resolution is created.

import express from "express";
import { RequestImageValidatorService } from "./services/request-image-validator-service";
import { ServedImageService } from "./services/served-image-service";
import { FolderStructureUtils } from "./utils/folder-structure-utils";
const app = express();
const port = 8080; // default port to listen

app.use(express.static("images")); // https://expressjs.com/en/starter/static-files.html

// define a route handler for the default home page
app.get("/", (req, res) => {
    res.send("Hello world!");
});

// define a route handler for the image processing
app.get("/image/:imageName/:imageResolution", async (req, res, next) => {
    // res.send(`image named ${req.params.imageName} and resolution ${req.params.imageResolution}`);
    const imageName = req.params.imageName;
    const imageResolution = req.params.imageResolution;
    console.log(`requested image named ${imageName} and resolution ${imageResolution}`);
    const imageValidatorService: RequestImageValidatorService = new RequestImageValidatorService(imageName, imageResolution);
    if (!imageValidatorService.validateImage()) {
        res.status(404).send({ errors: imageValidatorService.errors });
    }

    const imageExistsPhysically: boolean = await FolderStructureUtils.imageWithResolutionExists(imageName, imageResolution);
    if (imageExistsPhysically) {
        const imagePath = FolderStructureUtils.getImageWithResolutionPath(imageName, imageResolution);
        res.status(200).sendFile(imagePath);
        return;
    }

    const serverImageService: ServedImageService = new ServedImageService();
    const originalServedImage = await serverImageService.getOriginalServedImage(imageName); // https://medium.com/@Abazhenov/using-async-await-in-express-with-node-8-b8af872c0016
    if (originalServedImage.existsOnFileSystem) {// if image exists we can proceed to try and serve the resized image
        const resizedImage = await serverImageService.getResizedServedImage(originalServedImage, imageResolution);
        res.status(200).sendFile(resizedImage.absolutePath);
    } else {
        res.status(404).send({ error: `File ${imageName} does not exist on file system at ${originalServedImage.absolutePath}` });
    }

});

// start the Express server
app.listen(port, () => {
    // tslint:disable-next-line:no-console
    console.log(`server started at http://localhost:${port}`);
});

 

Leave a Reply

Your email address will not be published. Required fields are marked *