-
Notifications
You must be signed in to change notification settings - Fork 0
solution #2
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
solution #2
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,77 +1,17 @@ | ||
| #  Final 1 - URL shortner 📎 | ||
| # Shawty 🐫 URL Shortener | ||
|
|
||
| In this project you will create your own [URL shortener](https://en.wikipedia.org/wiki/URL_shortening)! | ||
| A simple and responsive URL shortening service. | ||
|
|
||
| This repository includes a basic template for starting the project: | ||
| - [Fire up the demo](https://shawty.davidbinneun.repl.co/) at repl.it | ||
|
|
||
| ## Instructions | ||
| ## Features | ||
| - Fully responsive design | ||
| - Random ID generation | ||
| - Copy your new link easily | ||
| - Dedicated statistic page for your URL at /api/statistic/:id | ||
| - Custom error pages | ||
|
|
||
| - Fork this repository to your account as a **public** repo | ||
| - Clone your new repository to your computer 🖥 | ||
| - Install the project dependencies by running `npm install` from the vscode terminal `ctrl + j` (make sure you are in the correct directory) 📂 | ||
| - [Create a new branch](https://docs.github.com/en/desktop/contributing-and-collaborating-using-github-desktop/managing-branches) for the development process | ||
| - Make changes to the code to meet the project requirements 📝 | ||
| - [Commit Early, Push Often](https://www.worklytics.co/commit-early-push-often/) - your work will be evaluated by your git flow and overall github usage 🏄♂️ | ||
| - Before submitting, create a pull request from the development branch into the main branch. **Leave the PR open and do not merge the branches**. The open PR will be used to review and mark your code | ||
| - Good Luck! 🤘 | ||
|
|
||
| ## Testing your project | ||
|
|
||
| In this assignment, you will have to create your own tests, as learned in class. Your grade will be calculated by your test coverage. | ||
|
|
||
| Optionally, You can create a github [action](https://docs.github.com/en/actions) that runs your tests on each commit: | ||
|
|
||
|  | ||
|
|
||
| ## Guidelines | ||
|
|
||
| - Create a route `/api/shorturl/` in your `express` app that will handle all url shortening requests. (We recommend using [express Router](https://expressjs.com/en/guide/routing.html)) | ||
|
|
||
| - Write/read **Asynchronously** a single JSON file as your DB | ||
|
|
||
| - [Serve](https://expressjs.com/en/starter/static-files.html) your client files from your server at route `/` | ||
|
|
||
| - Style and change your front-end as you wish. You can take inspiration from this [example](https://www.shorturl.at/) | ||
|
|
||
| ## Requirements | ||
|
|
||
| - Examine thoroughly and copy all functionality of [this](https://url-shortener-microservice.freecodecamp.rocks/) FCC example | ||
|
|
||
| - Use a `class DataBase{}` to read/write (**Asynchronously**) all data in your back-end (you can use a json file as persistent layer) | ||
|
|
||
| - Add another functionality to your service: a statistics route (`api/statistic/:shorturl-id`) that will respond with the following data per `shorturl-id`: | ||
| - `creationDate` - a SQLDate format | ||
| - `redirectCount` - the amount of times this url was used for redirection | ||
| - `originalUrl` | ||
| - `shorturl-id` | ||
|
|
||
| - Fully test your `express` app with `jest` and `supertest`. Test each end point response **including** error responses. | ||
|
|
||
| Use a separate DB file for your tests. _Hint: use [Environment variables](https://jestjs.io/docs/en/environment-variables)_ | ||
|
|
||
| ## Bonus | ||
|
|
||
| - Add any feature you desire. Some ideas worth extra points: | ||
| - Custom short URL. Support optional `shorturl-id` parameter in your `POST` request. Pay attention to error handling. | ||
| - Serve a styled statistics dashboard instead of the default JSON statistics | ||
| - Use the [`JSONBIN.io`](https://jsonbin.io/) service bin as your persistent layer in your back-end DB class (use CRUD operations to read write bins) | ||
| - Try implementing user management | ||
| - Use supertest/puppeteer test to test any bonus feature you implemented | ||
|
|
||
| **Add an explanation in `README.md` for each bonus feature you add and a link to any resource you used** | ||
|
|
||
| ## Grading policy | ||
|
|
||
| - Using jsonbin.io with/instead of writing to files | ||
| - Correct DB class usage | ||
| - Code quality and style: indentation, Meaningful and non-disambiguate variable names, Comments and documentation, file and directory structure | ||
| - Visual creativity, style your front-end to make it look awesome 💅🏿 | ||
| - Division to reusable functions, no code duplication | ||
| - Git usage: meaningful commit messages, small commits, folder and file structures, README file, issues, etc... | ||
|
|
||
| ## Submitting | ||
| - Submit your solution repo link - an open PR from your dev branch to the main one | ||
| - Your readme should have a [`repl.it`](https://repl.it/) link with your solutions. | ||
| - Submit a link to your repo to the CRM. | ||
|
|
||
| GOOD LUCK! | ||
| ## Resources used | ||
| - [Pug rendering engine](https://pugjs.org/api/getting-started.html) | ||
| - [shortid](https://www.npmjs.com/package/shortid) | ||
| - [is-valid-http-url](https://www.npmjs.com/package/is-valid-http-url) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| [{"creationDate":"04/03/2021","redirectCount":0,"originalUrl":"https://www.w3schools.com/java/java_try_catch.asp","id":"A6-kJvHUa"},{"creationDate":"04/03/2021","redirectCount":0,"originalUrl":"https://github.com/","id":"RKY26Idoy"},{"creationDate":"04/03/2021","redirectCount":0,"originalUrl":"https://github.com/davidbinneun/json-server/blob/main/index.test.js","id":"YJPY1mhqt"}] |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,62 @@ | ||
| const fs = require('fs').promises; | ||
| const Item = require('./item.js'); | ||
| const databaseFile = process.env.NODE_ENV === 'test' ? './backend/testdata.json':'./backend/data.json'; | ||
| const isUrl = require("is-valid-http-url"); | ||
|
|
||
| // Performs actions on the database file | ||
| class DataBase { | ||
| static items = []; | ||
|
|
||
| // Gets all data from the JSON file into the items array | ||
| static async readAllData(){ | ||
| const data = await fs.readFile(databaseFile, 'utf8' , err => { if (err) return;}); // TODO remove callback | ||
| this.items = JSON.parse(data); | ||
| } | ||
|
|
||
| // Receives URL, adds it to database and returns the id given to it | ||
| static async addURL(url){ | ||
| await this.readAllData(); | ||
|
|
||
| // Check if URL is legal | ||
| if (!isUrl(url)) return null; // TODO take this out | ||
|
|
||
| // Check if URL exists in database | ||
| for(let item of this.items){ | ||
| if (url === item.originalUrl) { | ||
| return item.id; // URL exists, returns its id | ||
| } | ||
| } | ||
| // If URL is new, add to database and return id | ||
| let newItem = new Item(url); | ||
| this.items.push(newItem); | ||
| fs.writeFile(databaseFile, JSON.stringify(this.items)); | ||
| return newItem.id; | ||
| } | ||
|
|
||
| // Receives id, returns the URL it has | ||
| static async getOriginalUrl(id){ | ||
| await this.readAllData(); | ||
| for (let item of this.items){ | ||
| if (item.id === id){ | ||
| item.redirectCount += 1; | ||
| fs.writeFile(databaseFile, JSON.stringify(this.items)); | ||
| return item.originalUrl; | ||
| } | ||
| } | ||
|
|
||
| return null; | ||
| } | ||
|
|
||
| // Receives id, returns the full item object | ||
| static async getItem(id){ | ||
| await this.readAllData(); | ||
| for (let item of this.items){ | ||
| if (item.id === id){ | ||
| return item; | ||
| } | ||
| } | ||
| return null; // TODO throw error incase of error | ||
| } | ||
| } | ||
|
|
||
| module.exports = DataBase; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,23 @@ | ||
| const shortid = require('shortid'); | ||
|
|
||
| class Item { | ||
| constructor(url){ | ||
| this.creationDate = this.getIsraeliDate(new Date()); | ||
| this.redirectCount = 0; | ||
| this.originalUrl = url; | ||
| this.id = shortid.generate(); | ||
| } | ||
|
|
||
| getIsraeliDate(date){ | ||
| return addZero(date.getDate()) + "/" + addZero(date.getMonth() + 1) + "/" + date.getFullYear(); | ||
|
|
||
| function addZero(number){ | ||
| if (number < 10) | ||
| return "0" + number; | ||
| else | ||
| return number; | ||
| } | ||
| } | ||
| } | ||
|
|
||
| module.exports = Item; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| [ |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,69 @@ | ||
| const supertest = require("supertest"); | ||
| const app = require("./app"); | ||
| const request = supertest(app); | ||
| const Item = require('./backend/item.js'); | ||
| const fs = require("fs").promises; | ||
|
|
||
| beforeAll(async () => { | ||
| await fs.writeFile("./backend/testdata.json", "[]"); | ||
| }); | ||
|
|
||
| describe("Sending URL to the server", () => { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Missing a test for posting an existing url |
||
| it("The URL is legal", async () => { | ||
| const response = await request.post('/api/shorturl/new').type('form').send({url:"https://www.youtube.com/"}); | ||
| expect(response.status).toBe(201); | ||
| }); | ||
| it("The URL is illegal", async () => { | ||
| const response = await request.post('/api/shorturl/new').type('form').send({url:"utubecom/?hl=iw&gl=IL"}); | ||
| expect(response.status).toBe(400); | ||
| }); | ||
| it("Corrupted file in server", async () => { | ||
| await fs.writeFile("./backend/testdata.json", "["); | ||
| const response = await request.post('/api/shorturl/new').type('form').send({url:"https://www.youtube.com/"}); | ||
| expect(response.status).toBe(500); | ||
| }); | ||
| }); | ||
|
|
||
| describe("Redirect to URL by ID", () => { | ||
| it("Send Legal ID", async () => { | ||
| let newItem = new Item('https://www.youtube.com/'); | ||
| await fs.writeFile("./backend/testdata.json", JSON.stringify([newItem])); | ||
| const response = await request.get(`/api/statistic/${newItem.id}`); | ||
| expect(response.status).toBe(200); | ||
| }); | ||
| it("Send Illegal ID", async () => { | ||
| const response = await request.get(`/api/statistic/abc`); | ||
| expect(response.status).toBe(400); | ||
| }); | ||
| it("Legal ID but ID not found", async () => { | ||
| const response = await request.get(`/api/statistic/abcdefg`); | ||
| expect(response.status).toBe(404); | ||
| }); | ||
| it("Corrupted file in server", async () => { | ||
| await fs.writeFile("./backend/testdata.json", "["); | ||
| const response = await request.get(`/api/statistic/abcdefg`); | ||
| expect(response.status).toBe(500); | ||
| }); | ||
| }); | ||
|
|
||
| describe("Get statistics by ID", () => { | ||
| it("Send Legal ID", async () => { | ||
| let newItem = new Item('https://www.youtube.com/'); | ||
| await fs.writeFile("./backend/testdata.json", JSON.stringify([newItem])); | ||
| const response = await request.get(`/${newItem.id}`); | ||
| expect(response.status).toBe(302); | ||
| }); | ||
| it("Send Illegal ID", async () => { | ||
| const response = await request.get(`/abc`); | ||
| expect(response.status).toBe(400); | ||
| }); | ||
| it("Legal ID but ID not found", async () => { | ||
| const response = await request.get(`/abcdefg`); | ||
| expect(response.status).toBe(404); | ||
| }); | ||
| it("Corrupted file in server", async () => { | ||
| await fs.writeFile("./backend/testdata.json", "["); | ||
| const response = await request.get(`/abcdefg`); | ||
| expect(response.status).toBe(500); | ||
| }); | ||
|
Comment on lines
+11
to
+68
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good job and good tests. Covering almost every case. |
||
| }); | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -8,6 +8,6 @@ | |
| "env": { | ||
| "NODE_ENV": "development" | ||
| }, | ||
| "ext": "js,json,html,css", | ||
| "ext": "js,html,css,pug", | ||
| "delay": "2500" | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
अति उत्कृष्ट