{"id":18553956,"url":"https://github.com/elyse502/alx-files_manager","last_synced_at":"2026-04-07T09:32:27.694Z","repository":{"id":226918893,"uuid":"769947630","full_name":"elyse502/alx-files_manager","owner":"elyse502","description":"About a summary of this Back-End Trimester including: authentication, NodeJS, MongoDB, Redis, pagination and background processing .....","archived":false,"fork":false,"pushed_at":"2024-03-11T14:38:46.000Z","size":271,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-05-15T11:50:39.564Z","etag":null,"topics":["backend","es6","express","javascript","kue","mongodb","nodejs","nosql","redis"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/elyse502.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-03-10T14:09:18.000Z","updated_at":"2024-03-11T11:38:29.000Z","dependencies_parsed_at":"2024-11-06T21:19:31.718Z","dependency_job_id":"67bc2204-ddd0-481d-b865-d2a3f18a6be6","html_url":"https://github.com/elyse502/alx-files_manager","commit_stats":null,"previous_names":["elyse502/alx-files_manager"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/elyse502/alx-files_manager","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/elyse502%2Falx-files_manager","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/elyse502%2Falx-files_manager/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/elyse502%2Falx-files_manager/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/elyse502%2Falx-files_manager/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/elyse502","download_url":"https://codeload.github.com/elyse502/alx-files_manager/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/elyse502%2Falx-files_manager/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31508065,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-07T03:10:19.677Z","status":"ssl_error","status_checked_at":"2026-04-07T03:10:13.982Z","response_time":105,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["backend","es6","express","javascript","kue","mongodb","nodejs","nosql","redis"],"created_at":"2024-11-06T21:19:03.454Z","updated_at":"2026-04-07T09:32:27.675Z","avatar_url":"https://github.com/elyse502.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cdiv align=\"center\"\u003e\n  \n![ALX-Banner](https://github.com/elyse502/alx-backend/assets/125453474/eb1e33ed-9628-43e7-b44d-034313502cf9)\n\u003c/div\u003e\n\n![ALX1](https://github.com/elyse502/alx-backend/assets/125453474/a9c38230-7d04-4b44-a830-686dc22c7b61)\n\n\n\u003cp align=\"center\"\u003e\n\u003ch2 align=\"center\"\u003e\u003cimg align=\"center\" src=\"https://assets.imaginablefutures.com/media/images/ALX_Logo.max-200x150.png\" alt=\"footer\" width=\"150\"  height=\"150\"/\u003e\u003c/h2\u003e\n\n\u003ch1 align=\"center\"\u003e📁 ALX-Files-Manager 🏪 💥 🏬\u003c/h1\u003e\n\n\nThis project is a summary of this back-end trimester: authentication, NodeJS, MongoDB, Redis, pagination and background processing.\n\nThe objective is to build a simple platform to upload and view files:\n\n* User authentication via a token\n* List all files\n* Upload a new file\n* Change permission of a file\n* View a file\n* Generate thumbnails for images\n\nYou will be guided step by step for building it, but you have some freedoms of implementation, split in more files etc… (`utils` folder will be your friend)\n\nOf course, this kind of service already exists in the real life - it’s a learning purpose to assemble each piece and build a full product.\n\nEnjoy!\n\n# Resources🏗️\n\u003cb\u003eRead or watch:\u003c/b\u003e\n* [Node JS getting started](https://nodejs.org/en/learn/getting-started/introduction-to-nodejs)\n* [Process API doc](https://node.readthedocs.io/en/latest/api/process/)\n* [Express getting started](https://expressjs.com/en/starter/installing.html)\n* [Mocha documentation](https://mochajs.org/)\n* [Nodemon documentation](https://github.com/remy/nodemon#nodemon)\n* [MongoDB](https://github.com/mongodb/node-mongodb-native)\n* [Bull](https://github.com/OptimalBits/bull)\n* [Image thumbnail](https://www.npmjs.com/package/image-thumbnail)\n* [Mime-Types](https://www.npmjs.com/package/mime-types)\n* [Redis](https://github.com/redis/node-redis)\n\n# Learning Objectives 📖\nAt the end of this project, you are expected to be able to [explain to anyone](https://fs.blog/feynman-learning-technique/), **without the help of Google**:\n* how to create an API with Express\n* how to authenticate a user\n* how to store data in MongoDB\n* how to store temporary data in Redis\n* how to setup and use a background worker\n\n# Requirements 🏛️\n* Allowed editors: `vi`, `vim`, `emacs`, `Visual Studio Code`\n* All your files will be interpreted/compiled on Ubuntu 18.04 LTS using `node` (version 12.x.x)\n* All your files should end with a new line\n* A `README.md` file, at the root of the folder of the project, is mandatory\n* Your code should use the `js` extension\n* Your code will be verified against lint using ESLint\n\n# Provided files 🗃️\n**`package.json`**\n\u003cdetails\u003e\n  \u003csummary\u003eClick to show/hide file contents\u003c/summary\u003e\n  \n```groovy\n\n{\n  \"name\": \"files_manager\",\n  \"version\": \"1.0.0\",\n  \"description\": \"\",\n  \"main\": \"index.js\",\n  \"scripts\": {\n    \"lint\": \"./node_modules/.bin/eslint\",\n    \"check-lint\": \"lint [0-9]*.js\",\n    \"start-server\": \"nodemon --exec babel-node --presets @babel/preset-env ./server.js\",\n    \"start-worker\": \"nodemon --exec babel-node --presets @babel/preset-env ./worker.js\",\n    \"dev\": \"nodemon --exec babel-node --presets @babel/preset-env\",\n    \"test\": \"./node_modules/.bin/mocha --require @babel/register --exit\" \n  },\n  \"author\": \"\",\n  \"license\": \"ISC\",\n  \"dependencies\": {\n    \"bull\": \"^3.16.0\",\n    \"chai-http\": \"^4.3.0\",\n    \"express\": \"^4.17.1\",\n    \"image-thumbnail\": \"^1.0.10\",\n    \"mime-types\": \"^2.1.27\",\n    \"mongodb\": \"^3.5.9\",\n    \"redis\": \"^2.8.0\",\n    \"sha1\": \"^1.1.1\",\n    \"uuid\": \"^8.2.0\"\n  },\n  \"devDependencies\": {\n    \"@babel/cli\": \"^7.8.0\",\n    \"@babel/core\": \"^7.8.0\",\n    \"@babel/node\": \"^7.8.0\",\n    \"@babel/preset-env\": \"^7.8.2\",\n    \"@babel/register\": \"^7.8.0\",\n    \"chai\": \"^4.2.0\",\n    \"chai-http\": \"^4.3.0\",\n    \"mocha\": \"^6.2.2\",\n    \"nodemon\": \"^2.0.2\",\n    \"eslint\": \"^6.4.0\",\n    \"eslint-config-airbnb-base\": \"^14.0.0\",\n    \"eslint-plugin-import\": \"^2.18.2\",\n    \"eslint-plugin-jest\": \"^22.17.0\",\n    \"request\": \"^2.88.0\",\n    \"sinon\": \"^7.5.0\"\n  }\n}\n\n```\n\u003c/details\u003e\n\n**`.eslintrc.js`**\n\u003cdetails\u003e\n  \u003csummary\u003eClick to show/hide file contents\u003c/summary\u003e\n  \n```groovy\n\nmodule.exports = {\n    env: {\n      browser: false,\n      es6: true,\n      jest: true,\n    },\n    extends: [\n      'airbnb-base',\n      'plugin:jest/all',\n    ],\n    globals: {\n      Atomics: 'readonly',\n      SharedArrayBuffer: 'readonly',\n    },\n    parserOptions: {\n      ecmaVersion: 2018,\n      sourceType: 'module',\n    },\n    plugins: ['jest'],\n    rules: {\n      'max-classes-per-file': 'off',\n      'no-underscore-dangle': 'off',\n      'no-console': 'off',\n      'no-shadow': 'off',\n      'no-restricted-syntax': [\n        'error',\n        'LabeledStatement',\n        'WithStatement',\n      ],\n    },\n    overrides:[\n      {\n        files: ['*.js'],\n        excludedFiles: 'babel.config.js',\n      }\n    ]\n};\n\n```\n\u003c/details\u003e\n\n**`babel.config.js`**\n\u003cdetails\u003e\n  \u003csummary\u003eClick to show/hide file contents\u003c/summary\u003e\n  \n```groovy\n\nmodule.exports = {\n    presets: [\n      [\n        '@babel/preset-env',\n        {\n          targets: {\n            node: 'current',\n          },\n        },\n      ],\n    ],\n};\n\n```\n\u003c/details\u003e\n\n**and…**\n\nDon’t forget to run `$ npm install` when you have the `package.json`\n\n# Tasks 📃\n## 0. Redis utils: [utils/redis.js](utils/redis.js)\nInside the folder `utils`, create a file `redis.js` that contains the class `RedisClient`.\n\n`RedisClient` should have:\n\n* the constructor that creates a client to Redis:\n  * any error of the redis client must be displayed in the console (you should use `on('error')` of the redis client)\n* a function `isAlive` that returns `true` when the connection to Redis is a success otherwise, `false`\n* an asynchronous function `get` that takes a string key as argument and returns the Redis value stored for this key\n* an asynchronous function `set` that takes a string key, a value and a duration in second as arguments to store it in Redis (with an expiration set by the duration argument)\n* an asynchronous function `del` that takes a string key as argument and remove the value in Redis for this key\n\nAfter the class definition, create and export an instance of `RedisClient` called `redisClient`.\n```groovy\nbob@dylan:~$ cat main-0.js\nimport redisClient from './utils/redis';\n\n(async () =\u003e {\n    console.log(redisClient.isAlive());\n    console.log(await redisClient.get('myKey'));\n    await redisClient.set('myKey', 12, 5);\n    console.log(await redisClient.get('myKey'));\n\n    setTimeout(async () =\u003e {\n        console.log(await redisClient.get('myKey'));\n    }, 1000*10)\n})();\n\nbob@dylan:~$ npm run dev main-0.js\ntrue\nnull\n12\nnull\nbob@dylan:~$ \n```\n\n## 1. MongoDB utils: [utils/db.js](utils/db.js)\nInside the folder `utils`, create a file `db.js` that contains the class `DBClient`.\n\n`DBClient` should have:\n\n* the constructor that creates a client to MongoDB:\n  * host: from the environment variable `DB_HOST` or default: `localhost`\n  * port: from the environment variable `DB_PORT` or default: `27017`\n  * database: from the environment variable `DB_DATABASE` or default: `files_manager`\n* a function `isAlive` that returns `true` when the connection to MongoDB is a success otherwise, `false`\n* an asynchronous function `nbUsers` that returns the number of documents in the collection `users`\n* an asynchronous function `nbFiles` that returns the number of documents in the collection `files`\n\nAfter the class definition, create and export an instance of `DBClient` called `dbClient`.\n```groovy\nbob@dylan:~$ cat main-1.js\nimport dbClient from './utils/db';\n\nconst waitConnection = () =\u003e {\n    return new Promise((resolve, reject) =\u003e {\n        let i = 0;\n        const repeatFct = async () =\u003e {\n            await setTimeout(() =\u003e {\n                i += 1;\n                if (i \u003e= 10) {\n                    reject()\n                }\n                else if(!dbClient.isAlive()) {\n                    repeatFct()\n                }\n                else {\n                    resolve()\n                }\n            }, 1000);\n        };\n        repeatFct();\n    })\n};\n\n(async () =\u003e {\n    console.log(dbClient.isAlive());\n    await waitConnection();\n    console.log(dbClient.isAlive());\n    console.log(await dbClient.nbUsers());\n    console.log(await dbClient.nbFiles());\n})();\n\nbob@dylan:~$ npm run dev main-1.js\nfalse\ntrue\n4\n30\nbob@dylan:~$\n```\n\n## 2. First API: [server.js](server.js), [routes/index.js](routes/index.js), [controllers/AppController.js](controllers/AppController.js)\nInside `server.js`, create the Express server:\n\n* it should listen on the port set by the environment variable `PORT` or by default 5000\n* it should load all routes from the file `routes/index.js`\n\nInside the folder `routes`, create a file `index.js` that contains all endpoints of our API:\n\n* `GET /status` =\u003e `AppController.getStatus`\n* `GET /stats` =\u003e `AppController.getStats`\n\nInside the folder `controllers`, create a file `AppController.js` that contains the definition of the 2 endpoints:\n\n* `GET /status` should return if Redis is alive and if the DB is alive too by using the 2 utils created previously: `{ \"redis\": true, \"db\": true }` with a status code 200\n* `GET /stats` should return the number of users and files in DB: `{ \"users\": 12, \"files\": 1231 }` with a status code 200\n  * `users` collection must be used for counting all users\n  * `files` collection must be used for counting all files\n\n**Terminal 1:**\n```groovy\nbob@dylan:~$ npm run start-server\nServer running on port 5000\n...\n```\n**Terminal 2:**\n```groovy\nbob@dylan:~$ curl 0.0.0.0:5000/status ; echo \"\"\n{\"redis\":true,\"db\":true}\nbob@dylan:~$ \nbob@dylan:~$ curl 0.0.0.0:5000/stats ; echo \"\"\n{\"users\":4,\"files\":30}\nbob@dylan:~$\n```\n\n## 3. Create a new user: [utils/](utils/), [routes/index.js](routes/index.js), [controllers/UsersController.js](controllers/UsersController.js)\nNow that we have a simple API, it’s time to add users to our database.\n\nIn the file `routes/index.js`, add a new endpoint:\n\n* `POST /users` =\u003e `UsersController.postNew`\n\nInside `controllers`, add a file `UsersController.js` that contains the new endpoint:\n\n`POST /users` should create a new user in DB:\n\n* To create a user, you must specify an `email` and a `password`\n* If the `email` is missing, return an error `Missing email` with a status code 400\n* If the `password` is missing, return an error `Missing password` with a status code 400\n* If the `email` already exists in DB, return an error `Already exist` with a status code 400\n* The `password` must be stored after being hashed in `SHA1`\n* The endpoint is returning the new user with only the `email` and the `id` (auto generated by MongoDB) with a status code 201\n* The new user must be saved in the collection `users`:\n  * `email`: same as the value received\n  * `password`: `SHA1` value of the value received\n```groovy\nbob@dylan:~$ curl 0.0.0.0:5000/users -XPOST -H \"Content-Type: application/json\" -d '{ \"email\": \"bob@dylan.com\", \"password\": \"toto1234!\" }' ; echo \"\"\n{\"id\":\"5f1e7d35c7ba06511e683b21\",\"email\":\"bob@dylan.com\"}\nbob@dylan:~$ \nbob@dylan:~$ echo 'db.users.find()' | mongo files_manager\n{ \"_id\" : ObjectId(\"5f1e7d35c7ba06511e683b21\"), \"email\" : \"bob@dylan.com\", \"password\" : \"89cad29e3ebc1035b29b1478a8e70854f25fa2b2\" }\nbob@dylan:~$ \nbob@dylan:~$ \nbob@dylan:~$ curl 0.0.0.0:5000/users -XPOST -H \"Content-Type: application/json\" -d '{ \"email\": \"bob@dylan.com\", \"password\": \"toto1234!\" }' ; echo \"\"\n{\"error\":\"Already exist\"}\nbob@dylan:~$ \nbob@dylan:~$ curl 0.0.0.0:5000/users -XPOST -H \"Content-Type: application/json\" -d '{ \"email\": \"bob@dylan.com\" }' ; echo \"\"\n{\"error\":\"Missing password\"}\nbob@dylan:~$\n```\n\n## 4. Authenticate a user: [utils/](utils/), [routes/index.js](routes/index.js), [controllers/UsersController.js](controllers/UsersController.js), [controllers/AuthController.js](controllers/AuthController.js)\nIn the file `routes/index.js`, add 3 new endpoints:\n\n* `GET /connect` =\u003e `AuthController.getConnect`\n* `GET /disconnect` =\u003e `AuthController.getDisconnect`\n* `GET /users/me` =\u003e `UserController.getMe`\n\nInside `controllers`, add a file `AuthController.js` that contains new endpoints:\n\n`GET /connect` should sign-in the user by generating a new authentication token:\n\n* By using the header `Authorization` and the technique of the Basic auth (Base64 of the `\u003cemail\u003e:\u003cpassword\u003e`), find the user associate to this email and with this password (reminder: we are storing the SHA1 of the password)\n* If no user has been found, return an error `Unauthorized` with a status code 401\n* Otherwise:\n  * Generate a random string (using `uuidv4`) as token\n  * Create a key: `auth_\u003ctoken\u003e`\n  * Use this key for storing in Redis (by using the `redisClient` create previously) the user ID for 24 hours\n  * Return this token: `{ \"token\": \"155342df-2399-41da-9e8c-458b6ac52a0c\" }` with a status code 200\n\nNow, we have a way to identify a user, create a token (= avoid to store the password on any front-end) and use this token for 24h to access to the API!\n\nEvery authenticated endpoints of our API will look at this token inside the header `X-Token`.\n\n`GET /disconnect` should sign-out the user based on the token:\n\n* Retrieve the user based on the token:\n  * If not found, return an error `Unauthorized` with a status code 401\n  * Otherwise, delete the token in Redis and return nothing with a status code 204\n\nInside the file `controllers/UsersController.js` add a new endpoint:\n\n`GET /users/me` should retrieve the user base on the token used:\n\n* Retrieve the user based on the token:\n  * If not found, return an error `Unauthorized` with a status code 401\n  * Otherwise, return the user object (`email` and `id` only)\n```groovy\nbob@dylan:~$ curl 0.0.0.0:5000/connect -H \"Authorization: Basic Ym9iQGR5bGFuLmNvbTp0b3RvMTIzNCE=\" ; echo \"\"\n{\"token\":\"031bffac-3edc-4e51-aaae-1c121317da8a\"}\nbob@dylan:~$ \nbob@dylan:~$ curl 0.0.0.0:5000/users/me -H \"X-Token: 031bffac-3edc-4e51-aaae-1c121317da8a\" ; echo \"\"\n{\"id\":\"5f1e7cda04a394508232559d\",\"email\":\"bob@dylan.com\"}\nbob@dylan:~$ \nbob@dylan:~$ curl 0.0.0.0:5000/disconnect -H \"X-Token: 031bffac-3edc-4e51-aaae-1c121317da8a\" ; echo \"\"\n\nbob@dylan:~$ curl 0.0.0.0:5000/users/me -H \"X-Token: 031bffac-3edc-4e51-aaae-1c121317da8a\" ; echo \"\"\n{\"error\":\"Unauthorized\"}\nbob@dylan:~$\n```\n\n## 5. First file: [utils/](utils/), [routes/index.js](routes/index.js), [controllers/FilesController.js](controllers/FilesController.js)\nIn the file `routes/index.js`, add a new endpoint:\n\n* `POST /files` =\u003e `FilesController.postUpload`\n\nInside `controllers`, add a file `FilesController.js` that contains the new endpoint:\n\n`POST /files` should create a new file in DB and in disk:\n\n* Retrieve the user based on the token:\n  * If not found, return an error `Unauthorized` with a status code 401\n* To create a file, you must specify:\n  * `name`: as filename\n  * `type`: either `folder`, `file` or `image`\n  * `parentId`: (optional) as ID of the parent (default: 0 -\u003e the root)\n  * `isPublic`: (optional) as boolean to define if the file is public or not (default: false)\n  * `data`: (only for `type=file|image`) as Base64 of the file content\n* If the `name` is missing, return an error `Missing name` with a status code 400\n* If the `type` is missing or not part of the list of accepted type, return an error `Missing type` with a status code 400\n* If the `data` is missing and `type != folder`, return an error `Missing data` with a status code 400\n* If the `parentId` is set:\n  * If no file is present in DB for this `parentId`, return an error `Parent not found` with a status code 400\n  * If the file present in DB for this `parentId` is not of type `folder`, return an error `Parent is not a folder` with a status code 400\n* The user ID should be added to the document saved in DB - as owner of a file\n* If the type is `folder`, add the new file document in the DB and return the new file with a status code 201\n* Otherwise:\n  * All file will be stored locally in a folder (to create automatically if not present):\n    * The relative path of this folder is given by the environment variable `FOLDER_PATH`\n    * If this variable is not present or empty, use `/tmp/files_manager` as storing folder path\n  * Create a local path in the storing folder with filename a UUID\n  * Store the file in clear (reminder: `data` contains the Base64 of the file) in this local path\n  * Add the new file document in the collection `files` with these attributes:\n    * `userId`: ID of the owner document (owner from the authentication)\n    * `name`: same as the value received\n    * `type`: same as the value received\n    * `isPublic`: same as the value received\n    * `parentId`: same as the value received - if not present: 0\n    * `localPath`: for a `type=file|image`, the absolute path to the file save in local\n  * Return the new file with a status code 201\n```groovy\nbob@dylan:~$ curl 0.0.0.0:5000/connect -H \"Authorization: Basic Ym9iQGR5bGFuLmNvbTp0b3RvMTIzNCE=\" ; echo \"\"\n{\"token\":\"f21fb953-16f9-46ed-8d9c-84c6450ec80f\"}\nbob@dylan:~$ \nbob@dylan:~$ curl -XPOST 0.0.0.0:5000/files -H \"X-Token: f21fb953-16f9-46ed-8d9c-84c6450ec80f\" -H \"Content-Type: application/json\" -d '{ \"name\": \"myText.txt\", \"type\": \"file\", \"data\": \"SGVsbG8gV2Vic3RhY2shCg==\" }' ; echo \"\"\n{\"id\":\"5f1e879ec7ba06511e683b22\",\"userId\":\"5f1e7cda04a394508232559d\",\"name\":\"myText.txt\",\"type\":\"file\",\"isPublic\":false,\"parentId\":0}\nbob@dylan:~$\nbob@dylan:~$ ls /tmp/files_manager/\n2a1f4fc3-687b-491a-a3d2-5808a02942c9\nbob@dylan:~$\nbob@dylan:~$ cat /tmp/files_manager/2a1f4fc3-687b-491a-a3d2-5808a02942c9 \nHello Webstack!\nbob@dylan:~$\nbob@dylan:~$ curl -XPOST 0.0.0.0:5000/files -H \"X-Token: f21fb953-16f9-46ed-8d9c-84c6450ec80f\" -H \"Content-Type: application/json\" -d '{ \"name\": \"images\", \"type\": \"folder\" }' ; echo \"\"\n{\"id\":\"5f1e881cc7ba06511e683b23\",\"userId\":\"5f1e7cda04a394508232559d\",\"name\":\"images\",\"type\":\"folder\",\"isPublic\":false,\"parentId\":0}\nbob@dylan:~$\nbob@dylan:~$ cat image_upload.py\nimport base64\nimport requests\nimport sys\n\nfile_path = sys.argv[1]\nfile_name = file_path.split('/')[-1]\n\nfile_encoded = None\nwith open(file_path, \"rb\") as image_file:\n    file_encoded = base64.b64encode(image_file.read()).decode('utf-8')\n\nr_json = { 'name': file_name, 'type': 'image', 'isPublic': True, 'data': file_encoded, 'parentId': sys.argv[3] }\nr_headers = { 'X-Token': sys.argv[2] }\n\nr = requests.post(\"http://0.0.0.0:5000/files\", json=r_json, headers=r_headers)\nprint(r.json())\n\nbob@dylan:~$\nbob@dylan:~$ python image_upload.py image.png f21fb953-16f9-46ed-8d9c-84c6450ec80f 5f1e881cc7ba06511e683b23\n{'id': '5f1e8896c7ba06511e683b25', 'userId': '5f1e7cda04a394508232559d', 'name': 'image.png', 'type': 'image', 'isPublic': True, 'parentId': '5f1e881cc7ba06511e683b23'}\nbob@dylan:~$\nbob@dylan:~$ echo 'db.files.find()' | mongo files_manager\n{ \"_id\" : ObjectId(\"5f1e881cc7ba06511e683b23\"), \"userId\" : ObjectId(\"5f1e7cda04a394508232559d\"), \"name\" : \"images\", \"type\" : \"folder\", \"parentId\" : \"0\" }\n{ \"_id\" : ObjectId(\"5f1e879ec7ba06511e683b22\"), \"userId\" : ObjectId(\"5f1e7cda04a394508232559d\"), \"name\" : \"myText.txt\", \"type\" : \"file\", \"parentId\" : \"0\", \"isPublic\" : false, \"localPath\" : \"/tmp/files_manager/2a1f4fc3-687b-491a-a3d2-5808a02942c9\" }\n{ \"_id\" : ObjectId(\"5f1e8896c7ba06511e683b25\"), \"userId\" : ObjectId(\"5f1e7cda04a394508232559d\"), \"name\" : \"image.png\", \"type\" : \"image\", \"parentId\" : ObjectId(\"5f1e881cc7ba06511e683b23\"), \"isPublic\" : true, \"localPath\" : \"/tmp/files_manager/51997b88-5c42-42c2-901e-e7f4e71bdc47\" }\nbob@dylan:~$\nbob@dylan:~$ ls /tmp/files_manager/\n2a1f4fc3-687b-491a-a3d2-5808a02942c9   51997b88-5c42-42c2-901e-e7f4e71bdc47\nbob@dylan:~$\n```\n\n## 6. Get and list file: [utils/](utils/), [routes/index.js](routes/index.js), [controllers/FilesController.js](controllers/FilesController.js)\nIn the file `routes/index.js`, add 2 new endpoints:\n\n* `GET /files/:id` =\u003e `FilesController.getShow`\n* `GET /files` =\u003e `FilesController.getIndex`\n\nIn the file `controllers/FilesController.js`, add the 2 new endpoints:\n\n`GET /files/:id` should retrieve the file document based on the ID:\n\n* Retrieve the user based on the token:\n  * If not found, return an error `Unauthorized` with a status code 401\n* If no file document is linked to the user and the ID passed as parameter, return an error `Not found` with a status code 404\n* Otherwise, return the file document\n\n`GET /files` should retrieve all users file documents for a specific `parentId` and with pagination:\n\n* Retrieve the user based on the token:\n  * If not found, return an error `Unauthorized` with a status code 401\n* Based on the query parameters `parentId` and `page`, return the list of file document\n  * `parentId`:\n    * No validation of `parentId` needed - if the `parentId` is not linked to any user folder, returns an empty list\n    * By default, `parentId` is equal to 0 = the root\n  * Pagination:\n    * Each page should be 20 items max\n    * `page` query parameter starts at 0 for the first page. If equals to 1, it means it’s the second page (form the 20th to the 40th), etc…\n    * Pagination can be done directly by the `aggregate` of MongoDB\n```groovy\nbob@dylan:~$ curl 0.0.0.0:5000/connect -H \"Authorization: Basic Ym9iQGR5bGFuLmNvbTp0b3RvMTIzNCE=\" ; echo \"\"\n{\"token\":\"f21fb953-16f9-46ed-8d9c-84c6450ec80f\"}\nbob@dylan:~$ \nbob@dylan:~$ curl -XGET 0.0.0.0:5000/files -H \"X-Token: f21fb953-16f9-46ed-8d9c-84c6450ec80f\" ; echo \"\"\n[{\"id\":\"5f1e879ec7ba06511e683b22\",\"userId\":\"5f1e7cda04a394508232559d\",\"name\":\"myText.txt\",\"type\":\"file\",\"isPublic\":false,\"parentId\":0},{\"id\":\"5f1e881cc7ba06511e683b23\",\"userId\":\"5f1e7cda04a394508232559d\",\"name\":\"images\",\"type\":\"folder\",\"isPublic\":false,\"parentId\":0},{\"id\":\"5f1e8896c7ba06511e683b25\",\"userId\":\"5f1e7cda04a394508232559d\",\"name\":\"image.png\",\"type\":\"image\",\"isPublic\":true,\"parentId\":\"5f1e881cc7ba06511e683b23\"}]\nbob@dylan:~$\nbob@dylan:~$ curl -XGET 0.0.0.0:5000/files?parentId=5f1e881cc7ba06511e683b23 -H \"X-Token: f21fb953-16f9-46ed-8d9c-84c6450ec80f\" ; echo \"\"\n[{\"id\":\"5f1e8896c7ba06511e683b25\",\"userId\":\"5f1e7cda04a394508232559d\",\"name\":\"image.png\",\"type\":\"image\",\"isPublic\":true,\"parentId\":\"5f1e881cc7ba06511e683b23\"}]\nbob@dylan:~$\nbob@dylan:~$ curl -XGET 0.0.0.0:5000/files/5f1e8896c7ba06511e683b25 -H \"X-Token: f21fb953-16f9-46ed-8d9c-84c6450ec80f\" ; echo \"\"\n{\"id\":\"5f1e8896c7ba06511e683b25\",\"userId\":\"5f1e7cda04a394508232559d\",\"name\":\"image.png\",\"type\":\"image\",\"isPublic\":true,\"parentId\":\"5f1e881cc7ba06511e683b23\"}\nbob@dylan:~$\n```\n\n## 7. File publish/unpublish: [utils/](utils/), [routes/index.js](routes/index.js), [controllers/FilesController.js](controllers/FilesController.js)\nIn the file `routes/index.js`, add 2 new endpoints:\n\n* `PUT /files/:id/publish` =\u003e `FilesController.putPublish`\n* `PUT /files/:id/publish` =\u003e `FilesController.putUnpublish`\n\nIn the file `controllers/FilesController.js`, add the 2 new endpoints:\n\n`PUT /files/:id/publish` should set `isPublic` to `true` on the file document based on the ID:\n\n* Retrieve the user based on the token:\n  * If not found, return an error `Unauthorized` with a status code 401\n* If no file document is linked to the user and the ID passed as parameter, return an error `Not found` with a status code 404\n* Otherwise:\n  * Update the value of `isPublic` to `true`\n  * And return the file document with a status code 200\n\n`PUT /files/:id/unpublish` should set `isPublic` to `false` on the file document based on the ID:\n\n* Retrieve the user based on the token:\n  * If not found, return an error `Unauthorized` with a status code 401\n* If no file document is linked to the user and the ID passed as parameter, return an error `Not found` with a status code 404\n* Otherwise:\n  * Update the value of `isPublic` to `false`\n  * And return the file document with a status code 200\n```groovy\nbob@dylan:~$ curl 0.0.0.0:5000/connect -H \"Authorization: Basic Ym9iQGR5bGFuLmNvbTp0b3RvMTIzNCE=\" ; echo \"\"\n{\"token\":\"f21fb953-16f9-46ed-8d9c-84c6450ec80f\"}\nbob@dylan:~$ \nbob@dylan:~$ curl -XGET 0.0.0.0:5000/files/5f1e8896c7ba06511e683b25 -H \"X-Token: f21fb953-16f9-46ed-8d9c-84c6450ec80f\" ; echo \"\"\n{\"id\":\"5f1e8896c7ba06511e683b25\",\"userId\":\"5f1e7cda04a394508232559d\",\"name\":\"image.png\",\"type\":\"image\",\"isPublic\":false,\"parentId\":\"5f1e881cc7ba06511e683b23\"}\nbob@dylan:~$\nbob@dylan:~$ curl -XPUT 0.0.0.0:5000/files/5f1e8896c7ba06511e683b25/publish -H \"X-Token: f21fb953-16f9-46ed-8d9c-84c6450ec80f\" ; echo \"\"\n{\"id\":\"5f1e8896c7ba06511e683b25\",\"userId\":\"5f1e7cda04a394508232559d\",\"name\":\"image.png\",\"type\":\"image\",\"isPublic\":true,\"parentId\":\"5f1e881cc7ba06511e683b23\"}\nbob@dylan:~$ \nbob@dylan:~$ curl -XPUT 0.0.0.0:5000/files/5f1e8896c7ba06511e683b25/unpublish -H \"X-Token: f21fb953-16f9-46ed-8d9c-84c6450ec80f\" ; echo \"\"\n{\"id\":\"5f1e8896c7ba06511e683b25\",\"userId\":\"5f1e7cda04a394508232559d\",\"name\":\"image.png\",\"type\":\"image\",\"isPublic\":false,\"parentId\":\"5f1e881cc7ba06511e683b23\"}\nbob@dylan:~$\n```\n\n## 8. File data: [utils/](utils/), [routes/index.js](routes/index.js), [controllers/FilesController.js](controllers/FilesController.js)\nIn the file `routes/index.js`, add one new endpoint:\n\n* `GET /files/:id/data` =\u003e `FilesController.getFile`\n\nIn the file `controllers/FilesController.js`, add the new endpoint:\n\n`GET /files/:id/data` should return the content of the file document based on the ID:\n\n* If no file document is linked to the ID passed as parameter, return an error `Not found` with a status code 404\n* If the file document (folder or file) is not public (`isPublic: false`) and no user authenticate or not the owner of the file, return an error `Not found` with a status code 404\n* If the type of the file document is `folder`, return an error `A folder doesn't have content` with a status code 400\n* If the file is not locally present, return an error `Not found` with a status code 404\n* Otherwise:\n  * By using the module `mime-types`, get the [MIME-type](https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types) based on the `name` of the file\n  * Return the content of the file with the correct MIME-type\n```groovy\nbob@dylan:~$ curl 0.0.0.0:5000/connect -H \"Authorization: Basic Ym9iQGR5bGFuLmNvbTp0b3RvMTIzNCE=\" ; echo \"\"\n{\"token\":\"f21fb953-16f9-46ed-8d9c-84c6450ec80f\"}\nbob@dylan:~$ \nbob@dylan:~$ curl -XPUT 0.0.0.0:5000/files/5f1e879ec7ba06511e683b22/unpublish -H \"X-Token: f21fb953-16f9-46ed-8d9c-84c6450ec80f\" ; echo \"\"\n{\"id\":\"5f1e879ec7ba06511e683b22\",\"userId\":\"5f1e7cda04a394508232559d\",\"name\":\"myText.txt\",\"type\":\"file\",\"isPublic\":false,\"parentId\":0}\nbob@dylan:~$ \nbob@dylan:~$ curl -XGET 0.0.0.0:5000/files/5f1e879ec7ba06511e683b22/data -H \"X-Token: f21fb953-16f9-46ed-8d9c-84c6450ec80f\" ; echo \"\"\nHello Webstack!\n\nbob@dylan:~$ curl -XGET 0.0.0.0:5000/files/5f1e879ec7ba06511e683b22/data ; echo \"\"\n{\"error\":\"Not found\"}\nbob@dylan:~$ \nbob@dylan:~$ curl -XPUT 0.0.0.0:5000/files/5f1e879ec7ba06511e683b22/publish -H \"X-Token: f21fb953-16f9-46ed-8d9c-84c6450ec80f\" ; echo \"\"\n{\"id\":\"5f1e879ec7ba06511e683b22\",\"userId\":\"5f1e7cda04a394508232559d\",\"name\":\"myText.txt\",\"type\":\"file\",\"isPublic\":true,\"parentId\":0}\nbob@dylan:~$ \nbob@dylan:~$ curl -XGET 0.0.0.0:5000/files/5f1e879ec7ba06511e683b22/data ; echo \"\"\nHello Webstack!\n\nbob@dylan:~$\n```\n\n## 9. Image Thumbnails: [utils/](utils/), [controllers/FilesController.js](controllers/FilesController.js), [worker.js](worker.js)\nUpdate the endpoint `POST /files` endpoint to start a background processing for generating thumbnails for a file of type `image`:\n\n* Create a `Bull` queue `fileQueue`\n* When a new image is stored (in local and in DB), add a job to this queue with the `userId` and `fileId`\n\nCreate a file `worker.js`:\n\n* By using the module `Bull`, create a queue `fileQueue`\n* Process this queue:\n  * If `fileId` is not present in the job, raise an error `Missing fileId`\n  * If `userId` is not present in the job, raise an error `Missing userId`\n  * If no document is found in DB based on the `fileId` and `userId`, raise an error `File not found`\n  * By using the module `image-thumbnail`, generate 3 thumbnails with `width` = 500, 250 and 100 - store each result on the same location of the original file by appending `_\u003cwidth size\u003e`\n\nUpdate the endpoint `GET /files/:id/data` to accept a query parameter `size`:\n\n* `size` can be `500`, `250` or `100`\n* Based on `size`, return the correct local file\n* If the local file doesn’t exist, return an error `Not found` with a status code 404\n\n**Terminal 3:** (start the worker)\n```groovy\nbob@dylan:~$ npm run start-worker\n...\n```\n**Terminal 2:**\n```groovy\nbob@dylan:~$ curl 0.0.0.0:5000/connect -H \"Authorization: Basic Ym9iQGR5bGFuLmNvbTp0b3RvMTIzNCE=\" ; echo \"\"\n{\"token\":\"f21fb953-16f9-46ed-8d9c-84c6450ec80f\"}\nbob@dylan:~$ \nbob@dylan:~$ python image_upload.py image.png f21fb953-16f9-46ed-8d9c-84c6450ec80f 5f1e881cc7ba06511e683b23\n{'id': '5f1e8896c7ba06511e683b25', 'userId': '5f1e7cda04a394508232559d', 'name': 'image.png', 'type': 'image', 'isPublic': True, 'parentId': '5f1e881cc7ba06511e683b23'}\nbob@dylan:~$ ls /tmp/files_manager/\n2a1f4fc3-687b-491a-a3d2-5808a02942c9   51997b88-5c42-42c2-901e-e7f4e71bdc47   6dc53397-8491-4b7c-8273-f748b1a031cb   6dc53397-8491-4b7c-8273-f748b1a031cb_100   6dc53397-8491-4b7c-8273-f748b1a031cb_250    6dc53397-8491-4b7c-8273-f748b1a031cb_500\nbob@dylan:~$ \nbob@dylan:~$ curl -XGET 0.0.0.0:5000/files/5f1e8896c7ba06511e683b25/data -so new_image.png ; file new_image.png\nnew_image.png: PNG image data, 471 x 512, 8-bit/color RGBA, non-interlaced\nbob@dylan:~$ \nbob@dylan:~$ curl -XGET 0.0.0.0:5000/files/5f1e8896c7ba06511e683b25/data?size=100 -so new_image.png ; file new_image.png\nnew_image.png: PNG image data, 100 x 109, 8-bit/color RGBA, non-interlaced\nbob@dylan:~$ \nbob@dylan:~$ curl -XGET 0.0.0.0:5000/files/5f1e8896c7ba06511e683b25/data?size=250 -so new_image.png ; file new_image.png\nnew_image.png: PNG image data, 250 x 272, 8-bit/color RGBA, non-interlaced\nbob@dylan:~$\n```\n\n## 10. Tests!: [tests/](tests/)\nOf course, a strong and stable project can not be good without tests.\n\nCreate tests for `redisClient` and `dbClient`.\n\nCreate tests for each endpoints:\n\n* `GET /status`\n* `GET /stats`\n* `POST /users`\n* `GET /connect`\n* `GET /disconnect`\n* `GET /users/me`\n* `POST /files`\n* `GET /files/:id`\n* `GET /files` (don’t forget the pagination)\n* `PUT /files/:id/publish`\n* `PUT /files/:id/unpublish`\n* `GET /files/:id/data`\n\n## 11. New user - welcome email: [utils/](utils/), [worker.js](worker.js), [controllers/UsersController.js](controllers/UsersController.js)\nUpdate the endpoint `POST /users` endpoint to start a background processing for sending a “Welcome email” to the user:\n\n* Create a `Bull` queue `userQueue`\n* When a new user is stored (in DB), add a job to this queue with the `userId`\n\nUpdate the file `worker.js`:\n\n* By using the module `Bull`, create a queue `userQueue`\n* Process this queue:\n  * If `userId` is not present in the job, raise an error `Missing userId`\n  * If no document is found in DB based on the `userId`, raise an error `User not found`\n  * Print in the console `Welcome \u003cemail\u003e!`\n\nIn real life, you can use a third party service like [Mailgun](https://www.mailgun.com/) to send real email. These API are slow, (sending via SMTP is worst!) and sending emails via a background job is important to optimize API endpoint.\n\n---\n\n\u003cdiv align=\"center\"\u003e\n\n# REFERENCE 📚\n\u003c/div\u003e\n\n## If someone like me, try many thing to make mongodb work in wsl and fail...\nInstall `mongodb` in **windows** is one of solution that work for me, and i can access it from **wsl**, u can also install `mongodb` compass to have more control (GUI)\n\nlink 👉 https://www.mongodb.com/try/download/community\n\n* How did you access it from window to wsl?\n\n=\u003e When u install it, it will run in background as service automaticly. So just go to **wsl** and run ur code, it will be runing on localhost port `2017`\n\n![Screenshot 2024-03-10 193329](https://github.com/elyse502/ALX-PROJECTS/assets/125453474/770c12c4-5851-4a57-a0ac-f0dd2f170ecc)\n\nalso from compass create database files_manager\n\n![Screenshot 2024-03-10 193501](https://github.com/elyse502/ALX-PROJECTS/assets/125453474/c4bcacdd-5d57-41d1-a50e-f4550df0220a)\n\nYou can use this script to fill files and users :\n```groovy\nconst MongoClient = require('mongodb').MongoClient;\n\n\nconst url = 'mongodb://localhost:27017';\n\n\nconst dbName = 'files_manager';\n\n\nMongoClient.connect(url, { useUnifiedTopology: true }, function (err, client) {\n  if (err) {\n    console.error('error connecting to MongoDB: ', err);\n    return;\n  }\n\n  console.log(\"Connected successfully\");\n\n  const db = client.db(dbName);\n\n  // Check 'users' collection\n  checkAndInsert(db, 'users', 4, { name: 'User' });\n\n  // Check 'files' collection\n  checkAndInsert(db, 'files', 30, { name: 'File' });\n});\n\nfunction checkAndInsert(db, collectionName, requiredCount) {\n  const collection = db.collection(collectionName);\n  collection.countDocuments((err, count) =\u003e {\n    if (err) {\n      console.error(`error counting documents in '${collectionName}': `, err);\n      return;\n    }\n\n    if (count \u003c requiredCount) {\n      const documents = Array.from({ length: requiredCount - count }, (_, i) =\u003e {\n        return { name: `${collectionName} ${i + count + 1}` };\n      });\n\n      collection.insertMany(documents, (err, result) =\u003e {\n        if (err) {\n          console.error(`error inserting documents into '${collectionName}': `, err);\n        }\n      });\n    }\n  });\n}\n```\n\n* Conversation 👉 [Discord](https://discord.com/channels/1148581179084243054/1216269582852034672)\n\n## Other Solution 🛠️\nThe last solution in this link solved the `MongoDB` problem of **WSL** with me, if you have a problem with it try this.\n\nCheck the link 👉 https://stackoverflow.com/questions/72803345/unable-to-locate-package-mongodb-org \n\n![Screenshot 2024-03-10 194035](https://github.com/elyse502/ALX-PROJECTS/assets/125453474/7b25ea71-e97a-417f-bd0f-aedc8632fce3)\n\nHaving this error MongoServerSelectionError: connect ECONNREFUSED 127.0.0.1:27017? 👇\n\n\n\u003e```\n\u003e Install  mongodb in windows\n\u003e make sure service of mongodb in windows after installation is on\n\u003e open mongodb compass see if u can connect with database\n\u003e if u connect then u good, just go wsl and run script\n\u003e if u couldnt connect with compass, mean service is not on\n\n---\n\n## FINAAAAALY TASK 6 ✍️\nYou will find **google doc** guide in this thread, about most big point to be aware of in `task6`, I'm planning to add all bug I found and fix, \nlet make it global, and everyone add BUG they find and fix inside to this thread and I make sure to add them to **google doc**, let all GET 200% 🛴 in this project.\n\nGuide 👉 https://docs.google.com/document/d/1o-JwbC-wwHxmjf_TqmW3Nh4oNsa3grCgB_3eJKCiASI/edit?usp=sharing\n\n\u003e```\n\u003eTASK6 some red check here fix : \n\u003e TLDR of some bug :\n\u003e the result u return make sure it not _id but id\n\u003e inside getindex when u looking for files, use parentid as string (dont ask me why, but enjoy green check)\n\u003e when u insert new document in postUpload, and it dont have parent id, which mean we will insert it with default value 0, make sure that  value is not string, yeah yeah i know this crazy, but make sure to insert it as integer.\n\nMore info 📰 Visist 👉 [Discord](https://discord.com/channels/1148581179084243054/1216571515550699550)\n\n## How do I fix `ESLint...`?\n\u003e npm run lint -- --fix filepath(eg controllers/*.js)\n\n---\n\n\n\n\n\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Felyse502%2Falx-files_manager","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Felyse502%2Falx-files_manager","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Felyse502%2Falx-files_manager/lists"}