Compare commits
10 commits
af903ef430
...
0e742570ab
Author | SHA1 | Date | |
---|---|---|---|
0e742570ab | |||
4042a7ba92 | |||
1e37363dfb | |||
bbabb3f75e | |||
947064c0a8 | |||
7c2b1a5669 | |||
ce6aa233c5 | |||
c241d90c45 | |||
b5d60f7e69 | |||
7b61a14d2c |
15 changed files with 294 additions and 100 deletions
1
.dockerignore
Normal file
1
.dockerignore
Normal file
|
@ -0,0 +1 @@
|
||||||
|
node_modules
|
2
.env.example
Normal file
2
.env.example
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
PORT=3000
|
||||||
|
UI_URL=http://localhost:3001
|
5
.gitignore
vendored
5
.gitignore
vendored
|
@ -32,4 +32,7 @@ lerna-debug.log*
|
||||||
!.vscode/settings.json
|
!.vscode/settings.json
|
||||||
!.vscode/tasks.json
|
!.vscode/tasks.json
|
||||||
!.vscode/launch.json
|
!.vscode/launch.json
|
||||||
!.vscode/extensions.json
|
!.vscode/extensions.json
|
||||||
|
|
||||||
|
# ENV
|
||||||
|
.env
|
16
Dockerfile
Normal file
16
Dockerfile
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
|
||||||
|
FROM node:18
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
COPY package*.json ./
|
||||||
|
|
||||||
|
RUN npm install
|
||||||
|
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
ARG SERVER_PORT
|
||||||
|
ENV PORT $SERVER_PORT
|
||||||
|
EXPOSE $SERVER_PORT
|
||||||
|
|
||||||
|
CMD ["npm", "run", "start:dev"]
|
124
README.md
124
README.md
|
@ -1,73 +1,65 @@
|
||||||
<p align="center">
|
# Tiny URL Microservice
|
||||||
<a href="http://nestjs.com/" target="blank"><img src="https://nestjs.com/img/logo-small.svg" width="200" alt="Nest Logo" /></a>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
[circleci-image]: https://img.shields.io/circleci/build/github/nestjs/nest/master?token=abc123def456
|
The Tiny URL Microservice is a simple URL shortening service built with Nest.js. It allows users to create shortened versions of long URLs, making them easier to share. This README provides an overview of the project, how to run it, and details about available routes.
|
||||||
[circleci-url]: https://circleci.com/gh/nestjs/nest
|
|
||||||
|
|
||||||
<p align="center">A progressive <a href="http://nodejs.org" target="_blank">Node.js</a> framework for building efficient and scalable server-side applications.</p>
|
## Table of Contents
|
||||||
<p align="center">
|
|
||||||
<a href="https://www.npmjs.com/~nestjscore" target="_blank"><img src="https://img.shields.io/npm/v/@nestjs/core.svg" alt="NPM Version" /></a>
|
|
||||||
<a href="https://www.npmjs.com/~nestjscore" target="_blank"><img src="https://img.shields.io/npm/l/@nestjs/core.svg" alt="Package License" /></a>
|
|
||||||
<a href="https://www.npmjs.com/~nestjscore" target="_blank"><img src="https://img.shields.io/npm/dm/@nestjs/common.svg" alt="NPM Downloads" /></a>
|
|
||||||
<a href="https://circleci.com/gh/nestjs/nest" target="_blank"><img src="https://img.shields.io/circleci/build/github/nestjs/nest/master" alt="CircleCI" /></a>
|
|
||||||
<a href="https://coveralls.io/github/nestjs/nest?branch=master" target="_blank"><img src="https://coveralls.io/repos/github/nestjs/nest/badge.svg?branch=master#9" alt="Coverage" /></a>
|
|
||||||
<a href="https://discord.gg/G7Qnnhy" target="_blank"><img src="https://img.shields.io/badge/discord-online-brightgreen.svg" alt="Discord"/></a>
|
|
||||||
<a href="https://opencollective.com/nest#backer" target="_blank"><img src="https://opencollective.com/nest/backers/badge.svg" alt="Backers on Open Collective" /></a>
|
|
||||||
<a href="https://opencollective.com/nest#sponsor" target="_blank"><img src="https://opencollective.com/nest/sponsors/badge.svg" alt="Sponsors on Open Collective" /></a>
|
|
||||||
<a href="https://paypal.me/kamilmysliwiec" target="_blank"><img src="https://img.shields.io/badge/Donate-PayPal-ff3f59.svg"/></a>
|
|
||||||
<a href="https://opencollective.com/nest#sponsor" target="_blank"><img src="https://img.shields.io/badge/Support%20us-Open%20Collective-41B883.svg" alt="Support us"></a>
|
|
||||||
<a href="https://twitter.com/nestframework" target="_blank"><img src="https://img.shields.io/twitter/follow/nestframework.svg?style=social&label=Follow"></a>
|
|
||||||
</p>
|
|
||||||
<!--[![Backers on Open Collective](https://opencollective.com/nest/backers/badge.svg)](https://opencollective.com/nest#backer)
|
|
||||||
[![Sponsors on Open Collective](https://opencollective.com/nest/sponsors/badge.svg)](https://opencollective.com/nest#sponsor)-->
|
|
||||||
|
|
||||||
## Description
|
- [Project Overview](#project-overview)
|
||||||
|
- [Prerequisites](#prerequisites)
|
||||||
|
- [Getting Started](#getting-started)
|
||||||
|
- [Using Docker Compose](#using-docker-compose)
|
||||||
|
- [Using .env File](#using-env-file)
|
||||||
|
- [API Routes](#api-routes)
|
||||||
|
- [Testing](#testing)
|
||||||
|
- [Contributing](#contributing)
|
||||||
|
- [License](#license)
|
||||||
|
|
||||||
[Nest](https://github.com/nestjs/nest) framework TypeScript starter repository.
|
## Project Overview
|
||||||
|
|
||||||
## Installation
|
This project is a simple URL shortening microservice built with Nest.js. It provides the following features:
|
||||||
|
|
||||||
|
- URL Shortening: Convert long URLs into short, easy-to-share versions.
|
||||||
|
- URL Expansion: Expand shortened URLs to their original long form And redirect users to the original URL.
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
Before you begin, ensure you have met the following requirements:
|
||||||
|
|
||||||
|
- Node.js installed on your machine.
|
||||||
|
- Docker and Docker Compose (if you prefer to use Docker).
|
||||||
|
- `.env` file (if not using Docker) with the necessary environment variables. You can use .env.exmaple as a reference.
|
||||||
|
|
||||||
|
## Getting Started
|
||||||
|
|
||||||
|
To get the project up and running, follow the instructions below.
|
||||||
|
|
||||||
|
### Using Docker Compose
|
||||||
|
|
||||||
|
1. Clone the repository:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git clone https://github.com/your-username/tiny-url-microservice.git
|
||||||
|
cd tiny-url-microservice
|
||||||
|
docker-compose -f docker-compose.yaml up -d --build
|
||||||
|
```
|
||||||
|
|
||||||
|
Your Nest.js server should now be running on port as like the port from the .env file.
|
||||||
|
|
||||||
|
## API Routes
|
||||||
|
# Shorten URL:
|
||||||
|
|
||||||
|
- Endpoint: POST /url-shortener/shorten
|
||||||
|
- Request Body: { "url": "https://example.com" }
|
||||||
|
- Response: { "data": "abc123", "status": 201 }
|
||||||
|
Expand URL:
|
||||||
|
|
||||||
|
Endpoint: GET /url-shortener/:shortUrl
|
||||||
|
Response: Redirects to the original long URL or redirect to the client UI 404 page if not found.
|
||||||
|
|
||||||
|
## Testing
|
||||||
|
|
||||||
|
To run the tests, run the following command:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
$ npm install
|
npm run test
|
||||||
```
|
```
|
||||||
|
|
||||||
## Running the app
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# development
|
|
||||||
$ npm run start
|
|
||||||
|
|
||||||
# watch mode
|
|
||||||
$ npm run start:dev
|
|
||||||
|
|
||||||
# production mode
|
|
||||||
$ npm run start:prod
|
|
||||||
```
|
|
||||||
|
|
||||||
## Test
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# unit tests
|
|
||||||
$ npm run test
|
|
||||||
|
|
||||||
# e2e tests
|
|
||||||
$ npm run test:e2e
|
|
||||||
|
|
||||||
# test coverage
|
|
||||||
$ npm run test:cov
|
|
||||||
```
|
|
||||||
|
|
||||||
## Support
|
|
||||||
|
|
||||||
Nest is an MIT-licensed open source project. It can grow thanks to the sponsors and support by the amazing backers. If you'd like to join them, please [read more here](https://docs.nestjs.com/support).
|
|
||||||
|
|
||||||
## Stay in touch
|
|
||||||
|
|
||||||
- Author - [Kamil Myśliwiec](https://kamilmysliwiec.com)
|
|
||||||
- Website - [https://nestjs.com](https://nestjs.com/)
|
|
||||||
- Twitter - [@nestframework](https://twitter.com/nestframework)
|
|
||||||
|
|
||||||
## License
|
|
||||||
|
|
||||||
Nest is [MIT licensed](LICENSE).
|
|
12
docker-compose.yaml
Normal file
12
docker-compose.yaml
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
version: "3.8"
|
||||||
|
services:
|
||||||
|
nest-app:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
args:
|
||||||
|
SERVER_PORT: ${SERVER_PORT}
|
||||||
|
ports:
|
||||||
|
- "${SERVER_PORT}:${SERVER_PORT}"
|
||||||
|
env_file:
|
||||||
|
- .env
|
135
package-lock.json
generated
135
package-lock.json
generated
|
@ -10,11 +10,15 @@
|
||||||
"license": "UNLICENSED",
|
"license": "UNLICENSED",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@nestjs/common": "^10.0.0",
|
"@nestjs/common": "^10.0.0",
|
||||||
|
"@nestjs/config": "^3.1.1",
|
||||||
"@nestjs/core": "^10.0.0",
|
"@nestjs/core": "^10.0.0",
|
||||||
"@nestjs/platform-express": "^10.0.0",
|
"@nestjs/platform-express": "^10.0.0",
|
||||||
|
"@nestjs/swagger": "^7.2.0",
|
||||||
|
"dotenv": "^16.3.2",
|
||||||
"reflect-metadata": "^0.1.13",
|
"reflect-metadata": "^0.1.13",
|
||||||
"rxjs": "^7.8.1",
|
"rxjs": "^7.8.1",
|
||||||
"shortid": "^2.2.16"
|
"shortid": "^2.2.16",
|
||||||
|
"swagger-ui-express": "^5.0.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@nestjs/cli": "^10.0.0",
|
"@nestjs/cli": "^10.0.0",
|
||||||
|
@ -1683,6 +1687,32 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@nestjs/config": {
|
||||||
|
"version": "3.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@nestjs/config/-/config-3.1.1.tgz",
|
||||||
|
"integrity": "sha512-qu5QlNiJdqQtOsnB6lx4JCXPQ96jkKUsOGd+JXfXwqJqZcOSAq6heNFg0opW4pq4J/VZoNwoo87TNnx9wthnqQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"dotenv": "16.3.1",
|
||||||
|
"dotenv-expand": "10.0.0",
|
||||||
|
"lodash": "4.17.21",
|
||||||
|
"uuid": "9.0.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@nestjs/common": "^8.0.0 || ^9.0.0 || ^10.0.0",
|
||||||
|
"reflect-metadata": "^0.1.13"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@nestjs/config/node_modules/dotenv": {
|
||||||
|
"version": "16.3.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.3.1.tgz",
|
||||||
|
"integrity": "sha512-IPzF4w4/Rd94bA9imS68tZBaYyBWSCE47V1RGuMrB94iyTOIEwRmVL2x/4An+6mETpLrKJ5hQkB8W4kFAadeIQ==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/motdotla/dotenv?sponsor=1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@nestjs/core": {
|
"node_modules/@nestjs/core": {
|
||||||
"version": "10.3.0",
|
"version": "10.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/@nestjs/core/-/core-10.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/@nestjs/core/-/core-10.3.0.tgz",
|
||||||
|
@ -1720,6 +1750,25 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@nestjs/mapped-types": {
|
||||||
|
"version": "2.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@nestjs/mapped-types/-/mapped-types-2.0.4.tgz",
|
||||||
|
"integrity": "sha512-xl+gUSp0B+ln1VSNoUftlglk8dfpUes3DHGxKZ5knuBxS5g2H/8p9/DSBOYWUfO5f4u9s6ffBPZ71WO+tbe5SA==",
|
||||||
|
"peerDependencies": {
|
||||||
|
"@nestjs/common": "^8.0.0 || ^9.0.0 || ^10.0.0",
|
||||||
|
"class-transformer": "^0.4.0 || ^0.5.0",
|
||||||
|
"class-validator": "^0.13.0 || ^0.14.0",
|
||||||
|
"reflect-metadata": "^0.1.12"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"class-transformer": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"class-validator": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@nestjs/platform-express": {
|
"node_modules/@nestjs/platform-express": {
|
||||||
"version": "10.3.0",
|
"version": "10.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/@nestjs/platform-express/-/platform-express-10.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/@nestjs/platform-express/-/platform-express-10.3.0.tgz",
|
||||||
|
@ -1756,6 +1805,37 @@
|
||||||
"typescript": ">=4.8.2"
|
"typescript": ">=4.8.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@nestjs/swagger": {
|
||||||
|
"version": "7.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@nestjs/swagger/-/swagger-7.2.0.tgz",
|
||||||
|
"integrity": "sha512-W7WPq561/79w27ZEgViXS7c5hqPwT7QXhsLsSeu2jeBROUhMM825QKDFKbMmtb643IW5dznJ4PjherlZZgtMvg==",
|
||||||
|
"dependencies": {
|
||||||
|
"@nestjs/mapped-types": "2.0.4",
|
||||||
|
"js-yaml": "4.1.0",
|
||||||
|
"lodash": "4.17.21",
|
||||||
|
"path-to-regexp": "3.2.0",
|
||||||
|
"swagger-ui-dist": "5.11.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@fastify/static": "^6.0.0",
|
||||||
|
"@nestjs/common": "^9.0.0 || ^10.0.0",
|
||||||
|
"@nestjs/core": "^9.0.0 || ^10.0.0",
|
||||||
|
"class-transformer": "*",
|
||||||
|
"class-validator": "*",
|
||||||
|
"reflect-metadata": "^0.1.12"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@fastify/static": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"class-transformer": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"class-validator": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@nestjs/testing": {
|
"node_modules/@nestjs/testing": {
|
||||||
"version": "10.3.0",
|
"version": "10.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/@nestjs/testing/-/testing-10.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/@nestjs/testing/-/testing-10.3.0.tgz",
|
||||||
|
@ -2705,8 +2785,7 @@
|
||||||
"node_modules/argparse": {
|
"node_modules/argparse": {
|
||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
|
||||||
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
|
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="
|
||||||
"dev": true
|
|
||||||
},
|
},
|
||||||
"node_modules/array-flatten": {
|
"node_modules/array-flatten": {
|
||||||
"version": "1.1.1",
|
"version": "1.1.1",
|
||||||
|
@ -3690,6 +3769,25 @@
|
||||||
"node": ">=6.0.0"
|
"node": ">=6.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/dotenv": {
|
||||||
|
"version": "16.3.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.3.2.tgz",
|
||||||
|
"integrity": "sha512-HTlk5nmhkm8F6JcdXvHIzaorzCoziNQT9mGxLPVXW8wJF1TiGSL60ZGB4gHWabHOaMmWmhvk2/lPHfnBiT78AQ==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/motdotla/dotenv?sponsor=1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/dotenv-expand": {
|
||||||
|
"version": "10.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-10.0.0.tgz",
|
||||||
|
"integrity": "sha512-GopVGCpVS1UKH75VKHGuQFqS1Gusej0z4FyQkPdwjil2gNIv+LNsqBlboOzpJFZKVT95GkCyWJbBSdFEFUWI2A==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/eastasianwidth": {
|
"node_modules/eastasianwidth": {
|
||||||
"version": "0.2.0",
|
"version": "0.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
|
||||||
|
@ -5864,7 +5962,6 @@
|
||||||
"version": "4.1.0",
|
"version": "4.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
|
||||||
"integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
|
"integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
|
||||||
"dev": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"argparse": "^2.0.1"
|
"argparse": "^2.0.1"
|
||||||
},
|
},
|
||||||
|
@ -6011,8 +6108,7 @@
|
||||||
"node_modules/lodash": {
|
"node_modules/lodash": {
|
||||||
"version": "4.17.21",
|
"version": "4.17.21",
|
||||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
|
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
|
||||||
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
|
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
|
||||||
"dev": true
|
|
||||||
},
|
},
|
||||||
"node_modules/lodash.memoize": {
|
"node_modules/lodash.memoize": {
|
||||||
"version": "4.1.2",
|
"version": "4.1.2",
|
||||||
|
@ -7737,6 +7833,25 @@
|
||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/swagger-ui-dist": {
|
||||||
|
"version": "5.11.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-5.11.0.tgz",
|
||||||
|
"integrity": "sha512-j0PIATqQSEFGOLmiJOJZj1X1Jt6bFIur3JpY7+ghliUnfZs0fpWDdHEkn9q7QUlBtKbkn6TepvSxTqnE8l3s0A=="
|
||||||
|
},
|
||||||
|
"node_modules/swagger-ui-express": {
|
||||||
|
"version": "5.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/swagger-ui-express/-/swagger-ui-express-5.0.0.tgz",
|
||||||
|
"integrity": "sha512-tsU9tODVvhyfkNSvf03E6FAk+z+5cU3lXAzMy6Pv4av2Gt2xA0++fogwC4qo19XuFf6hdxevPuVCSKFuMHJhFA==",
|
||||||
|
"dependencies": {
|
||||||
|
"swagger-ui-dist": ">=5.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= v0.10.32"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"express": ">=4.0.0 || >=5.0.0-beta"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/symbol-observable": {
|
"node_modules/symbol-observable": {
|
||||||
"version": "4.0.0",
|
"version": "4.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-4.0.0.tgz",
|
||||||
|
@ -8267,6 +8382,14 @@
|
||||||
"node": ">= 0.4.0"
|
"node": ">= 0.4.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/uuid": {
|
||||||
|
"version": "9.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz",
|
||||||
|
"integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==",
|
||||||
|
"bin": {
|
||||||
|
"uuid": "dist/bin/uuid"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/v8-compile-cache-lib": {
|
"node_modules/v8-compile-cache-lib": {
|
||||||
"version": "3.0.1",
|
"version": "3.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz",
|
||||||
|
|
|
@ -21,11 +21,15 @@
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@nestjs/common": "^10.0.0",
|
"@nestjs/common": "^10.0.0",
|
||||||
|
"@nestjs/config": "^3.1.1",
|
||||||
"@nestjs/core": "^10.0.0",
|
"@nestjs/core": "^10.0.0",
|
||||||
"@nestjs/platform-express": "^10.0.0",
|
"@nestjs/platform-express": "^10.0.0",
|
||||||
|
"@nestjs/swagger": "^7.2.0",
|
||||||
|
"dotenv": "^16.3.2",
|
||||||
"reflect-metadata": "^0.1.13",
|
"reflect-metadata": "^0.1.13",
|
||||||
"rxjs": "^7.8.1",
|
"rxjs": "^7.8.1",
|
||||||
"shortid": "^2.2.16"
|
"shortid": "^2.2.16",
|
||||||
|
"swagger-ui-express": "^5.0.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@nestjs/cli": "^10.0.0",
|
"@nestjs/cli": "^10.0.0",
|
||||||
|
|
|
@ -2,9 +2,14 @@ import { Module } from '@nestjs/common';
|
||||||
import { AppController } from './app.controller';
|
import { AppController } from './app.controller';
|
||||||
import { UrlShortenerModule } from './url-shortener/url-shortener.module';
|
import { UrlShortenerModule } from './url-shortener/url-shortener.module';
|
||||||
import { DnsService } from './dns/dns.service';
|
import { DnsService } from './dns/dns.service';
|
||||||
|
import { ConfigModule } from '@nestjs/config';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [UrlShortenerModule],
|
imports: [
|
||||||
|
ConfigModule.forRoot({
|
||||||
|
isGlobal: true, // Makes the config globally available
|
||||||
|
}),UrlShortenerModule,
|
||||||
|
],
|
||||||
controllers: [AppController],
|
controllers: [AppController],
|
||||||
providers: [DnsService],
|
providers: [DnsService],
|
||||||
})
|
})
|
||||||
|
|
|
@ -19,10 +19,6 @@ describe('DnsService', () => {
|
||||||
dnsLookupMock = (dns.lookup as unknown) as jest.Mock;
|
dnsLookupMock = (dns.lookup as unknown) as jest.Mock;
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should be defined', () => {
|
|
||||||
expect(service).toBeDefined();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return true for a valid URL', async () => {
|
it('should return true for a valid URL', async () => {
|
||||||
dnsLookupMock.mockImplementation((hostname, callback) => callback(null, '1.1.1.1'));
|
dnsLookupMock.mockImplementation((hostname, callback) => callback(null, '1.1.1.1'));
|
||||||
expect(await service.isValidUrl('http://example.com')).toBe(true);
|
expect(await service.isValidUrl('http://example.com')).toBe(true);
|
||||||
|
|
12
src/main.ts
12
src/main.ts
|
@ -1,8 +1,16 @@
|
||||||
import { NestFactory } from '@nestjs/core';
|
import { NestFactory } from '@nestjs/core';
|
||||||
import { AppModule } from './app.module';
|
import { AppModule } from './app.module';
|
||||||
|
import * as dotenv from 'dotenv';
|
||||||
|
import { setupSwagger } from '../swagger';
|
||||||
|
|
||||||
|
dotenv.config();
|
||||||
|
|
||||||
async function bootstrap() {
|
async function bootstrap() {
|
||||||
const app = await NestFactory.create(AppModule);
|
const app = await NestFactory.create(AppModule);
|
||||||
await app.listen(3000);
|
const port = process.env.SERVER_PORT || 3004;
|
||||||
|
app.enableCors();
|
||||||
|
setupSwagger(app);
|
||||||
|
await app.listen(port);
|
||||||
|
console.log(`Application is running on: ${await app.getUrl()}`);
|
||||||
}
|
}
|
||||||
bootstrap();
|
bootstrap();
|
5
src/url-shortener/response.interface.ts
Normal file
5
src/url-shortener/response.interface.ts
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
export interface ApiResponse {
|
||||||
|
data?: string;
|
||||||
|
error?: string;
|
||||||
|
status: number;
|
||||||
|
}
|
|
@ -1,26 +1,37 @@
|
||||||
import { Controller, Post, Body, Get, Param, Redirect, HttpException, HttpStatus } from '@nestjs/common';
|
import { Controller, Post, Body, Get, Param, HttpException, HttpStatus, Res } from '@nestjs/common';
|
||||||
import { UrlShortenerService } from './url-shortener.service';
|
import { UrlShortenerService } from './url-shortener.service';
|
||||||
|
import { ApiResponse } from './response.interface';
|
||||||
|
import { Response } from 'express';
|
||||||
|
import { ConfigService } from '@nestjs/config';
|
||||||
|
|
||||||
|
|
||||||
@Controller('url-shortener')
|
@Controller('url-shortener')
|
||||||
export class UrlShortenerController {
|
export class UrlShortenerController {
|
||||||
constructor(private readonly urlShortenerService: UrlShortenerService) {}
|
constructor(private readonly urlShortenerService: UrlShortenerService, private configService: ConfigService) {}
|
||||||
|
|
||||||
@Post('shorten')
|
@Post('shorten')
|
||||||
async shortenUrl(@Body('url') url: string): Promise<{ shortUrl: string }> {
|
async shortenUrl(@Body('url') url: string): Promise<ApiResponse> {
|
||||||
if (!url) {
|
if (!url) {
|
||||||
throw new HttpException('URL is required', HttpStatus.BAD_REQUEST);
|
throw new HttpException('URL is required', HttpStatus.BAD_REQUEST);
|
||||||
}
|
}
|
||||||
const shortUrl = await this.urlShortenerService.generateShortUrl(url);
|
try {
|
||||||
return { shortUrl };
|
const shortUrl = await this.urlShortenerService.generateShortUrl(url);
|
||||||
|
return { data: shortUrl, status: HttpStatus.CREATED };
|
||||||
|
} catch (error) {
|
||||||
|
return { error: error.message, status: HttpStatus.BAD_REQUEST };
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get(':shortUrl')
|
@Get(':shortUrl')
|
||||||
@Redirect()
|
async resolveShortUrl(@Param('shortUrl') shortUrl: string, @Res() res: Response) {
|
||||||
async resolveShortUrl(@Param('shortUrl') shortUrl: string) {
|
try {
|
||||||
const originalUrl = this.urlShortenerService.getOriginalUrl(shortUrl);
|
const originalUrl = this.urlShortenerService.getOriginalUrl(shortUrl);
|
||||||
if (!originalUrl) {
|
if (!originalUrl) {
|
||||||
throw new HttpException('URL not found!', HttpStatus.NOT_FOUND);
|
return res.redirect(`${this.configService.get<string>('UI_URL')}/404`);
|
||||||
|
}
|
||||||
|
return res.redirect(originalUrl);
|
||||||
|
} catch (error) {
|
||||||
|
throw new HttpException('An error occurred', HttpStatus.INTERNAL_SERVER_ERROR);
|
||||||
}
|
}
|
||||||
return { url: originalUrl };
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,6 +1,7 @@
|
||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import * as shortid from 'shortid';
|
import * as shortid from 'shortid';
|
||||||
import { DnsService } from '../dns/dns.service';
|
import { DnsService } from '../dns/dns.service';
|
||||||
|
import { HttpException, HttpStatus } from '@nestjs/common';
|
||||||
|
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
|
@ -11,19 +12,21 @@ export class UrlShortenerService {
|
||||||
constructor(private readonly dnsService: DnsService) {}
|
constructor(private readonly dnsService: DnsService) {}
|
||||||
|
|
||||||
async generateShortUrl(originalUrl: string): Promise<string> {
|
async generateShortUrl(originalUrl: string): Promise<string> {
|
||||||
if(!originalUrl) {
|
if (!originalUrl) {
|
||||||
throw new Error('URL is required');
|
throw new HttpException('URL is required', HttpStatus.BAD_REQUEST);
|
||||||
}
|
}
|
||||||
let isValid = await this.dnsService.isValidUrl(originalUrl);
|
const isValid = await this.dnsService.isValidUrl(originalUrl);
|
||||||
if(!isValid) {
|
if (!isValid) {
|
||||||
throw new Error('URL is invalid');
|
throw new HttpException('URL is invalid', HttpStatus.BAD_REQUEST);
|
||||||
}
|
}
|
||||||
let shortUrl = shortid.generate();
|
const shortUrl = "tiny." + shortid.generate() + ".com";
|
||||||
this.urlMap.set(shortUrl, originalUrl);
|
this.urlMap.set(shortUrl, originalUrl);
|
||||||
return shortUrl
|
return shortUrl;
|
||||||
}
|
}
|
||||||
|
|
||||||
getOriginalUrl(shortUrl: string): string | undefined {
|
getOriginalUrl(shortUrl: string): string | undefined {
|
||||||
return this.urlMap.get(shortUrl);
|
let url: string | undefined = this.urlMap.get(shortUrl);
|
||||||
|
if (url) return url;
|
||||||
|
return undefined;
|
||||||
}
|
}
|
||||||
}
|
}
|
13
swagger.ts
Normal file
13
swagger.ts
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger';
|
||||||
|
import { INestApplication } from '@nestjs/common';
|
||||||
|
|
||||||
|
export function setupSwagger(app: INestApplication): void {
|
||||||
|
const options = new DocumentBuilder()
|
||||||
|
.setTitle('Your API Title')
|
||||||
|
.setDescription('API description')
|
||||||
|
.setVersion('1.0')
|
||||||
|
.build();
|
||||||
|
|
||||||
|
const document = SwaggerModule.createDocument(app, options);
|
||||||
|
SwaggerModule.setup('api', app, document);
|
||||||
|
}
|
Loading…
Reference in a new issue