{"id":15098199,"url":"https://github.com/waldohidalgo/anonymous_message_board","last_synced_at":"2026-02-08T07:36:07.018Z","repository":{"id":245951099,"uuid":"819635883","full_name":"waldohidalgo/anonymous_message_board","owner":"waldohidalgo","description":"Segundo proyecto requisito obligatorio para obtener la 𝗜𝗻𝗳𝗼𝗿𝗺𝗮𝘁𝗶𝗼𝗻 𝗦𝗲𝗰𝘂𝗿𝗶𝘁𝘆 𝗖𝗲𝗿𝘁𝗶𝗳𝗶𝗰𝗮𝘁𝗶𝗼𝗻 de freecodecamp","archived":false,"fork":false,"pushed_at":"2024-06-25T01:18:20.000Z","size":900,"stargazers_count":0,"open_issues_count":0,"forks_count":1,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-08-13T01:29:15.946Z","etag":null,"topics":["anonymous-message-board","board","chai","express","express-middleware","freecodecamp","freecodecamp-certification","freecodecamp-challenge","freecodecamp-curriculum","freecodecamp-project","helmet","helmetjs","message-board","mocha-chai","mongodb"],"latest_commit_sha":null,"homepage":"https://anonymous-message-board-hfqm.onrender.com/","language":"HTML","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/waldohidalgo.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-06-24T23:05:21.000Z","updated_at":"2024-06-25T01:18:23.000Z","dependencies_parsed_at":"2024-06-25T01:37:23.441Z","dependency_job_id":"647546b6-9663-490f-87f7-ba3bd88a4e64","html_url":"https://github.com/waldohidalgo/anonymous_message_board","commit_stats":null,"previous_names":["waldohidalgo/anonymous_message_board"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/waldohidalgo/anonymous_message_board","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/waldohidalgo%2Fanonymous_message_board","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/waldohidalgo%2Fanonymous_message_board/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/waldohidalgo%2Fanonymous_message_board/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/waldohidalgo%2Fanonymous_message_board/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/waldohidalgo","download_url":"https://codeload.github.com/waldohidalgo/anonymous_message_board/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/waldohidalgo%2Fanonymous_message_board/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29224439,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-08T06:05:31.539Z","status":"ssl_error","status_checked_at":"2026-02-08T05:58:33.853Z","response_time":57,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6: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":["anonymous-message-board","board","chai","express","express-middleware","freecodecamp","freecodecamp-certification","freecodecamp-challenge","freecodecamp-curriculum","freecodecamp-project","helmet","helmetjs","message-board","mocha-chai","mongodb"],"created_at":"2024-09-25T16:44:14.275Z","updated_at":"2026-02-08T07:36:06.960Z","avatar_url":"https://github.com/waldohidalgo.png","language":"HTML","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Anonymous Message Board\n\n[Segundo proyecto](https://www.freecodecamp.org/learn/information-security/information-security-projects/anonymous-message-board) requisito obligatorio para obtener la **Information Security Certification** de freecodecamp.\n\nEl proyecto consiste en crear dos API con los métodos GET, POST, PUT y DELETE para manipular threads de un board y replies para cada thread en dicho board. La primera API \"escucha\" en el siguiente path:\n\n\u003e /api/threads/:board\n\nDicha API permite obtener, crear, editar o eliminar threads para el board el cual es pasado como request parameter en la URL también en función de data pasada desde el front end y/o como query parameter.\n\nLa segunda API escucha en el siguiente path:\n\n\u003e /api/replies/:board\n\nDicha API permite obtener, crear, editar o eliminar replies para cada thread pasando el board en dicha ruta junto con data extra en función de lo que se desee realizar.\n\n## Tabla de Contenidos\n\n- [Anonymous Message Board](#anonymous-message-board)\n  - [Tabla de Contenidos](#tabla-de-contenidos)\n  - [Requisitos de freecodecamp aprobados](#requisitos-de-freecodecamp-aprobados)\n  - [Deploy](#deploy)\n  - [Proyecto Aprobado](#proyecto-aprobado)\n  - [Tests funcionales aprobados](#tests-funcionales-aprobados)\n  - [Tests Funcionales Creados](#tests-funcionales-creados)\n    - [1. Creating a new thread: POST request to /api/threads/{board}](#1-creating-a-new-thread-post-request-to-apithreadsboard)\n    - [2.Viewing the 10 most recent threads with 3 replies each: GET request to /api/threads/{board}](#2viewing-the-10-most-recent-threads-with-3-replies-each-get-request-to-apithreadsboard)\n    - [3.Deleting a thread with the incorrect password: DELETE request to /api/threads/{board} with an invalid delete_password](#3deleting-a-thread-with-the-incorrect-password-delete-request-to-apithreadsboard-with-an-invalid-delete_password)\n    - [4.Deleting a thread with the correct password: DELETE request to /api/threads/{board} with a valid delete_password](#4deleting-a-thread-with-the-correct-password-delete-request-to-apithreadsboard-with-a-valid-delete_password)\n    - [5.Reporting a thread: PUT request to /api/threads/{board}](#5reporting-a-thread-put-request-to-apithreadsboard)\n    - [6.Creating a new reply: POST request to /api/replies/{board}](#6creating-a-new-reply-post-request-to-apirepliesboard)\n    - [7.Viewing a single thread with all replies: GET request to /api/replies/{board}](#7viewing-a-single-thread-with-all-replies-get-request-to-apirepliesboard)\n    - [8.Deleting a reply with the incorrect password: DELETE request to /api/replies/{board} with an invalid delete_password](#8deleting-a-reply-with-the-incorrect-password-delete-request-to-apirepliesboard-with-an-invalid-delete_password)\n    - [9.Deleting a reply with the correct password: DELETE request to /api/replies/{board} with a valid delete_password](#9deleting-a-reply-with-the-correct-password-delete-request-to-apirepliesboard-with-a-valid-delete_password)\n    - [10.Reporting a reply: PUT request to /api/replies/{board}](#10reporting-a-reply-put-request-to-apirepliesboard)\n  - [Screenshots](#screenshots)\n    - [1. Home Page](#1-home-page)\n    - [2. Board Page](#2-board-page)\n    - [3.Board with thread](#3board-with-thread)\n    - [4. Thread Page](#4-thread-page)\n    - [5.Thread Page with reply](#5thread-page-with-reply)\n    - [6.Board with many threads and replies](#6board-with-many-threads-and-replies)\n    - [7.GET to /api/threads/board](#7get-to-apithreadsboard)\n    - [8. GET to /api/replies/{board}?thread_id={thread_id}](#8-get-to-apirepliesboardthread_idthread_id)\n\n## Requisitos de freecodecamp aprobados\n\n![Imagen de Requisitos de freecodecamp aprobados](./screenshots/tests_freecodecamp_aprobados.webp)\n\n## Deploy\n\nEl proyecto lo he desplegado en mi cuenta gratuita en Render el cual tarda al menos 50 segundos en cargar para su utilización después de inactividad.\n\n## Proyecto Aprobado\n\n![Imagen de Proyecto Aprobado](./screenshots/proyecto_aprobado.webp)\n\n## Tests funcionales aprobados\n\n![Imagen de Tests funcionales aprobados](./screenshots/tests_funcionales_aprobados.jpg)\n\nTests funcionales aprobados en Render:\n\n![Imagen de Tests funcionales aprobados en Render](./screenshots/tests_funcionales_aprobados_en_render.jpg)\n\n## Tests Funcionales Creados\n\n### 1. Creating a new thread: POST request to /api/threads/{board}\n\n```js\nlet id = null;\n// Creating a new thread: POST request to /api/threads/{board}\ntest(\"Creating a new thread: POST request to /api/threads/{board}\", function (done) {\n  chai\n    .request(server)\n    .keepOpen()\n    .post(\"/api/threads/test\")\n    .send({\n      text: \"This is a new thread\",\n      delete_password: \"1234\",\n    })\n    .end(function (err, res) {\n      id = res.body.id;\n      assert.equal(res.status, 200);\n      assert.equal(res.body.text, \"This is a new thread\");\n      done();\n    });\n});\n```\n\n### 2.Viewing the 10 most recent threads with 3 replies each: GET request to /api/threads/{board}\n\n```js\ntest(\"Viewing the 10 most recent threads with 3 replies each: GET request to /api/threads/{board}\", function (done) {\n  chai\n    .request(server)\n    .keepOpen()\n    .get(\"/api/threads/test\")\n    .end(function (err, res) {\n      assert.equal(res.status, 200);\n      assert.isAtMost(res.body.length, 10);\n      assert.isAtMost(res.body[0].replies.length, 3);\n      done();\n    });\n});\n```\n\n### 3.Deleting a thread with the incorrect password: DELETE request to /api/threads/{board} with an invalid delete_password\n\n```js\ntest(\"Deleting a thread with the incorrect password: DELETE request to /api/threads/{board} with an invalid delete_password\", function (done) {\n  chai\n    .request(server)\n    .keepOpen()\n    .delete(\"/api/threads/test\")\n    .send({\n      thread_id: id,\n      delete_password: \"incorrect_password\",\n    })\n    .end(function (err, res) {\n      assert.equal(res.status, 200);\n      assert.equal(res.text, \"incorrect password\");\n      done();\n    });\n});\n```\n\n### 4.Deleting a thread with the correct password: DELETE request to /api/threads/{board} with a valid delete_password\n\n```js\ntest(\"Deleting a thread with the correct password: DELETE request to /api/threads/{board} with a valid delete_password\", function (done) {\n  chai\n    .request(server)\n    .keepOpen()\n    .delete(\"/api/threads/test\")\n    .send({\n      thread_id: id,\n      delete_password: \"1234\",\n    })\n    .end(function (err, res) {\n      assert.equal(res.status, 200);\n      assert.equal(res.text, \"success\");\n      done();\n    });\n});\n```\n\n### 5.Reporting a thread: PUT request to /api/threads/{board}\n\n```js\ntest(\"Reporting a thread: PUT request to /api/threads/{board}\", function (done) {\n  chai\n    .request(server)\n    .keepOpen()\n    .put(\"/api/threads/test\")\n    .send({\n      thread_id: id,\n    })\n    .end(function (err, res) {\n      assert.equal(res.status, 200);\n      assert.equal(res.text, \"reported\");\n      done();\n    });\n});\n```\n\n### 6.Creating a new reply: POST request to /api/replies/{board}\n\n```js\ntest(\"Creating a new reply: POST request to /api/replies/{board}\", function (done) {\n  chai\n    .request(server)\n    .keepOpen()\n    .post(\"/api/threads/test\")\n    .send({\n      text: \"This is a new thread\",\n      delete_password: \"1234\",\n    })\n    .end(function (err, res) {\n      id = res.body.id;\n      assert.equal(res.status, 200);\n      assert.equal(res.body.text, \"This is a new thread\");\n\n      // crear una nueva respuesta\n      chai\n        .request(server)\n        .keepOpen()\n        .post(\"/api/replies/test\")\n        .send({\n          thread_id: id,\n          text: \"This is a new reply\",\n          delete_password: \"1234\",\n        })\n        .end(function (err, res) {\n          assert.equal(res.status, 200);\n          assert.equal(res.text, \"success\");\n          done();\n        });\n    });\n});\n```\n\n### 7.Viewing a single thread with all replies: GET request to /api/replies/{board}\n\n```js\ntest(\"Viewing a single thread with all replies: GET request to /api/replies/{board}\", function (done) {\n  chai\n    .request(server)\n    .keepOpen()\n    .get(\"/api/replies/test?thread_id=\" + id)\n    .end(function (err, res) {\n      assert.equal(res.status, 200);\n      assert.equal(res.body._id, id);\n      assert.equal(res.body.board, \"test\");\n      // verificar si replies es un arreglo\n      assert.isArray(res.body.replies);\n      // verificar que el campo reported sea undefined\n      assert.isUndefined(res.body.reported, undefined);\n      // verificar que el campo delete_password sea undefined\n      assert.isUndefined(res.body.delete_password, undefined);\n\n      done();\n    });\n});\n```\n\n### 8.Deleting a reply with the incorrect password: DELETE request to /api/replies/{board} with an invalid delete_password\n\n```js\ntest(\"Deleting a reply with the incorrect password: DELETE request to /api/replies/{board} with an invalid delete_password\", function (done) {\n  chai\n    .request(server)\n    .keepOpen()\n    .delete(\"/api/replies/test\")\n    .send({\n      thread_id: id,\n      reply_id: id,\n      delete_password: \"incorrect_password\",\n    })\n    .end(function (err, res) {\n      assert.equal(res.status, 200);\n      assert.equal(res.text, \"incorrect password\");\n      done();\n    });\n});\n```\n\n### 9.Deleting a reply with the correct password: DELETE request to /api/replies/{board} with a valid delete_password\n\n```js\nlet id_reply = null;\ntest(\"Deleting a reply with the correct password: DELETE request to /api/replies/{board} with a valid delete_password\", function (done) {\n  // crear una nueva reply\n  chai\n    .request(server)\n    .keepOpen()\n    .post(\"/api/replies/test\")\n    .send({\n      thread_id: id,\n      text: \"This is a new reply\",\n      delete_password: \"1234\",\n    })\n    .end(function (err, res) {\n      assert.equal(res.status, 200);\n      assert.equal(res.text, \"success\");\n\n      // obtener la id de la respuesta\n      chai\n        .request(server)\n        .keepOpen()\n        .get(\"/api/replies/test?thread_id=\" + id)\n        .end(function (err, res) {\n          //obtener el id de la respuesta en el primer elemento del array replies\n          id_reply = res.body.replies[0]._id;\n          chai\n            .request(server)\n            .keepOpen()\n            .delete(\"/api/replies/test\")\n            .send({\n              thread_id: id,\n              reply_id: id_reply,\n              delete_password: \"1234\",\n            })\n            .end(function (err, res) {\n              assert.equal(res.status, 200);\n              assert.equal(res.text, \"success\");\n              done();\n            });\n        });\n    });\n});\n```\n\n### 10.Reporting a reply: PUT request to /api/replies/{board}\n\n```js\ntest(\"Reporting a reply: PUT request to /api/replies/{board}\", function (done) {\n  // crear una nueva reply\n  chai\n    .request(server)\n    .keepOpen()\n    .post(\"/api/replies/test\")\n    .send({\n      thread_id: id,\n      text: \"This is a new reply\",\n      delete_password: \"1234\",\n    })\n    .end(function (err, res) {\n      assert.equal(res.status, 200);\n      assert.equal(res.text, \"success\");\n\n      // obtener la id de la respuesta\n      chai\n        .request(server)\n        .keepOpen()\n        .get(\"/api/replies/test?thread_id=\" + id)\n        .end(function (err, res) {\n          //obtener el id de la respuesta en el primer elemento del array replies\n          id_reply = res.body.replies[0]._id;\n          //reportar la reply creada\n          chai\n            .request(server)\n            .keepOpen()\n            .put(\"/api/replies/test\")\n            .send({\n              thread_id: id,\n              reply_id: id_reply,\n            })\n            .end(function (err, res) {\n              assert.equal(res.status, 200);\n              assert.equal(res.text, \"reported\");\n              done();\n            });\n        });\n    });\n});\n```\n\n## Screenshots\n\n### 1. Home Page\n\n![Imagen de Home Page](./screenshots/1home_page.webp)\n\n### 2. Board Page\n\n![Imagen de Board Page](./screenshots/2board_page.webp)\n\n### 3.Board with thread\n\n![Imagen de Board with thread](./screenshots/3board_with_thread.webp)\n\n### 4. Thread Page\n\n![Imagen de Thread Page](./screenshots/4thread_page.webp)\n\n### 5.Thread Page with reply\n\n![Imagen de Thread Page with reply](./screenshots/5thread_page_with_reply.webp)\n\n### 6.Board with many threads and replies\n\n![Imagen de Board with many threads and replies](./screenshots/6board_with_many_threads_and_replies.webp)\n\n### 7.GET to /api/threads/board\n\n![Imagen de GET to /api/threads/board](./screenshots/7get_to_api_threads_board.webp)\n\n### 8. GET to /api/replies/{board}?thread_id={thread_id}\n\n![Imagen de GET to /api/replies/{board}?thread_id={thread_id}](./screenshots/8get_to_api_replies_board_thread_id=thread_id.webp)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwaldohidalgo%2Fanonymous_message_board","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fwaldohidalgo%2Fanonymous_message_board","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwaldohidalgo%2Fanonymous_message_board/lists"}