diff --git a/.gitea/workflows/test.yaml b/.gitea/workflows/test.yaml index 7c64ca0..73e2e08 100644 --- a/.gitea/workflows/test.yaml +++ b/.gitea/workflows/test.yaml @@ -1,5 +1,4 @@ name: Unit Tests with docker compose -run-name: ${{ gitea.actor }} is testing out Gitea Actions 🚀 on: [pull_request] jobs: diff --git a/Dockerfile b/Dockerfile index 7ecabac..2314c7c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -8,33 +8,20 @@ WORKDIR /usr/src/app # install dependencies into temp folder # this will cache them and speed up future builds FROM base AS install -RUN mkdir -p /temp/dev -COPY package.json bun.lockb /temp/dev/ -RUN cd /temp/dev && bun install # install with --production (exclude devDependencies) RUN mkdir -p /temp/prod COPY package.json bun.lockb /temp/prod/ RUN cd /temp/prod && bun install --production -# copy node_modules from temp folder -# then copy all (non-ignored) project files into the image -FROM install AS prerelease -COPY --from=install /temp/dev/node_modules node_modules -COPY . . - -# [optional] tests & build -# ENV NODE_ENV=production -# RUN bun test -# RUN bun run build - -# copy production dependencies and source code into final image +# Copy production dependencies and source code into final image FROM base AS release COPY --from=install /temp/prod/node_modules node_modules -COPY --from=prerelease /usr/src/app/index.ts . -COPY --from=prerelease /usr/src/app/package.json . +COPY --from=install /usr/src/app/src ./src +COPY --from=install /usr/src/app/package.json . # run the app USER bun +ENV NODE_ENV=production EXPOSE 8080/tcp CMD ["bun", "run", "start"] diff --git a/README.md b/README.md index 87f9bbc..d4c7f57 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,33 @@ # bot-api +## Introduction +The function of the API is basically to access the images' metadata stored in the database. + +## Usage +The API exposes some endpoints to interact with the database. + +### GET `/images` +Allows to get a list of image documents. + +#### Query params +- `limit`: an optional parameter, which accepts a non-negative integer that dictates the number of documents that the list will have. If its value is equal to `0`, or if this parameter is missing, the endpoint will return all the image documents in the database. +- `status`: an optional parameter, which accepts the values `consumed`, `available` and `unavailable`. It filters the documents that have only the `status` attribute equal to that indicated in the parameter's value. If the parameter is missing, no filter will be applied to the document. + +#### Example +- `GET /images?limit=5&status=available`: will return 5 documents that have the `available` value in their `status` attribute. + +### PUT `/images/` +Modifies an existing image document. The request must provide a JSON-formatted body, with one or more valid document attributes. The existing document attributes will be replaced with the provided new ones. + +#### Params +- `id`: the id of the document to be modified. + +#### Example +- `PUT /images/61f7e48f0c651345677b7775` with body `{ "status": "consumed" }`: will modify the document referenced by the `id` param, changing their `status` value to `consumed`. + +### POST `/images` +### POST `/login` +## Installation To install dependencies: ```bash diff --git a/src/app.ts b/src/app.ts index 43a3f6f..8ae7edd 100644 --- a/src/app.ts +++ b/src/app.ts @@ -14,6 +14,7 @@ app.get("/", (_, res) => { }); app.get("/images", imageController.getAllImages); +app.get("/images/:id", imageController.getImageById); app.post("/images", authControler.authorize, imageController.addImage); app.post("/login", authControler.login); diff --git a/src/controllers/ImageController.ts b/src/controllers/ImageController.ts index 75fca37..4b2301e 100644 --- a/src/controllers/ImageController.ts +++ b/src/controllers/ImageController.ts @@ -3,6 +3,23 @@ import imageService from "../services/ImageService"; import mongoose, { mongo } from "mongoose"; class ImageController { + async getImageById(req: Request, res: Response) { + try { + const image = await imageService.findById(req.params.id); + if (image) { + res.json({ image }); + } else { + res.status(404).json({ error: "Image not found" }); + } + } catch (error: any) { + if (error instanceof mongoose.Error.CastError) { + res.status(400).json({ error: "Invalid Id" }); + } else { + res.status(500).json({ error: "Internal Server Error" }); + } + } + } + async getAllImages(_: Request, res: Response): Promise { try { const images = await imageService.findAll(); diff --git a/src/index.ts b/src/index.ts index 4f8ee9f..0d016b7 100644 --- a/src/index.ts +++ b/src/index.ts @@ -3,6 +3,10 @@ import { startApp } from "./app"; await startApp(); +// This try carch is to prevent hot reload from making the process die due to coliding entries try { - await populateDatabase(); + // Not insert test data into production + if (process.env.NODE_ENV != "production"){ + await populateDatabase(); + } } catch {} diff --git a/src/services/ImageService.ts b/src/services/ImageService.ts index 8691f7d..8289ed5 100644 --- a/src/services/ImageService.ts +++ b/src/services/ImageService.ts @@ -1,6 +1,10 @@ import imageModel, { Image } from "../models/ImageModel"; class ImageService { + async findById(id: string) { + const image = await imageModel.findOne({ _id: id }); + return image; + } async findAll(): Promise { const allImages = await imageModel.find(); return allImages; diff --git a/tests/app.test.ts b/tests/app.test.ts index f8f7652..91987dd 100644 --- a/tests/app.test.ts +++ b/tests/app.test.ts @@ -1,11 +1,4 @@ -import { - afterEach, - beforeAll, - describe, - expect, - it, - mock, -} from "bun:test"; +import { afterEach, beforeAll, describe, expect, it, mock } from "bun:test"; import request, { Response } from "supertest"; import app, { startApp } from "../src/app"; import imageService from "../src/services/ImageService"; @@ -86,8 +79,7 @@ describe("GET /images works properly", async () => { }, })); - const res = await request(app) - .get("/images"); + const res = await request(app).get("/images"); expect(res.status).toBe(500); }); @@ -185,3 +177,26 @@ describe("POST /images works properly", () => { expect(res.status).toBe(400); }); }); + +describe("/images/:id works properly", () => { + it("should return 200 for existing ids", async () => { + const list = await request(app).get("/images"); + const id = list.body.images[0]._id; + const res = await request(app).get(`/images/${id}`); + expect(res.status).toBe(200); + }); + + it("should return 404 for non-existing ids", async () => { + const list = await request(app).get("/images"); + const id = "000000000000000000000000"; // this was the least posible to exist ID + if (!(id in list.body.images)) { + const res = await request(app).get(`/images/${id}`); + expect(res.status).toBe(404); + } + }); + + it("should return 400 for malformed ids", async () => { + const res = await request(app).get("/images/98439384"); + expect(res.status).toBe(400); + }); +});