loopbackjsv4l2loopbackloopback4

Route decorator for dynamic routing allowing a slash as part of the varible string


I have a loopback4 controller to fetch and create some files on the server. All files are stored in a directory structure with deeper directories. It might look like this:

├── WA2114
│   ├── 300dpi
│   │   ├── WA2114_Frontal.jpg
│   │   └── WA2114_entnehmen.jpg
│   ├── web
│   │   ├── WA2114-anwendung-Aufbewahrung.jpg
│   │   └── WA2114_Verpackung_NEU.jpg
│   ├── Produktdatenblatt
│   │   └── 2114-000.pdf

I want a method, to fetch all files based on the called RestAPI-Route:

GET /files/{category}

If i call GET /files/WA2114 I want to get a list of all files lying underneath WA2114/. If i call GET /files/WA2114/300dpi i want only the files from the deeper folder ../300dpi. I hope it's clear, what the goal is. The same logic is needed for uploading new files via POST /files/{category}.

I already tried the solutions described here: https://lideo.github.io/loopback.io/doc/en/lb4/Routes.html but with no success.

I have already set up a route for the top directory. But the deeper directories are not reachable dynamically because the route decorator seems to be sticked on the level and slashes are not allowed in the variable. I don't want to create dozens of methods to handle every directory level.

My current controller (simplified):

export class FileController
{
    constructor(@repository(FileRepository) public fileRepository: FileRepository)
    {
    }

    @get('/files/{category}', {
        responses: {
            '200': {
                description: 'Array of File model instances of the specified category',
                content:     {
                    'application/json': {
                        schema: {type: 'array', items: {'x-ts-type': File}}
                    }
                }
            }
        }
    })
    async findbyCategory(@param.path.string('category') category: string): Promise<File[]>
    {
        return await this.fileRepository.findByCategory(category);
    }
}

How do i have to decorate the controller method to be able to dynamically fetch /files/a and files/a/b/c with the same method?

I already did something like this in php/Symphony looking like this: @Route("/files/{category}", methods={"GET"}, requirements={"category"=".+"}). The .+ is the magic here. Now I have to rebuild the code with loopback4 and have to refactor the routings and I'm failing on this one. Someone with a solution for this?


Solution

  • I have already set up a route for the top directory. But the deeper directories are not reachable dynamically because the route decorator seems to be sticked on the level and slashes are not allowed in the variable. I don't want to create dozens of methods to handle every directory level.

    export class MySequence implements SequenceHandler {
        constructor(
            @inject(SequenceActions.FIND_ROUTE) protected findRoute: FindRoute,
            @inject(SequenceActions.PARSE_PARAMS) protected parseParams: ParseParams,
            @inject(SequenceActions.INVOKE_METHOD) protected invoke: InvokeMethod,
            @inject(SequenceActions.SEND) public send: Send,
            @inject(SequenceActions.REJECT) public reject: Reject,
        ) { }
    
        async handle(context: RequestContext) {
            try {
                const { request, response } = context;
                const route = this.findRoute(request); // <= here!!! find router by request
                const args = await this.parseParams(request, route);
                const result = await this.invoke(route, args);
                this.send(response, result);
            } catch (err) {
                this.reject(context, err);
            }
        }
    }
    

    Suppose:

    We have a controller A, one of the api is @get("/api/{category}")

    Before:

    /api/dir-1 => url=/api param=dir-1 => controller A

    /api/dir-1/file-2 => url=/api/dir-1 param=file-2 => (cannot find controller)

    /api/dir-1/dir-2/file-3 => url=/api/dir-1/dir-2 param=file-3 => (cannot find controller)

        async handle(context: RequestContext) {
            try {
                const { request, response } = context;
    
                // !
                if (
                    request.method === "GET" && request.url === "/api/"
                ) {
                    request.url = `/files/'${request.url.slice(7).replace(/\//gi, '----')}'`;
                }
    
                const route = this.findRoute(request);
                const args = await this.parseParams(request, route);
                const result = await this.invoke(route, args);
                this.send(response, result);
            } catch (err) {
                this.reject(context, err);
            }
        }
    

    After:

    /api/dir-1 => url=/api param=dir-1 => controller A

    /api/dir-1/file-2 => url=/api param=dir-1----file-2 => controller A

    /api/dir-1/dir-2/file-3 => url=/api param=dir-1----dir-2----file-3 => controller A

    Please see my example project for details.