-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit 2dc14f8
Showing
18 changed files
with
1,068 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
# Ignore all files by default. | ||
* | ||
|
||
# White list only the required files and folders. | ||
!src/ | ||
!.prettierignore | ||
!.prettierrc | ||
!package.json | ||
!yarn.lock |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
# In normal circumstances this file should not be under source control, however, it's fine for this demonstration repository. | ||
|
||
APP_PORT=54321 | ||
MONGODB_URL=mongodb://appUser:examplePassword@database:27017/urlShortener |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
database/data/ | ||
node_modules/ | ||
yarn-debug.log* | ||
yarn-error.log* | ||
.DS_Store |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
20 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
**/database/data/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
{ | ||
"singleQuote": true | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
{ | ||
"recommendations": ["esbenp.prettier-vscode"] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
{ | ||
"[javascript][typescript]": { | ||
"editor.defaultFormatter": "esbenp.prettier-vscode", | ||
"editor.formatOnSave": true | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
FROM node:20-alpine AS base | ||
|
||
LABEL maintainer="Anthony Hastings <[email protected]>" | ||
|
||
USER node | ||
|
||
WORKDIR /url-shortener | ||
|
||
COPY --chown=node ./package.json ./yarn.lock ./ | ||
|
||
RUN yarn install --frozen-lockfile && yarn cache clean | ||
|
||
COPY --chown=node . ./ | ||
|
||
CMD yarn start |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
version: "3.8" | ||
services: | ||
database: | ||
image: mongo:7 | ||
ports: | ||
- 45678:27017 | ||
environment: | ||
MONGO_INITDB_ROOT_USERNAME: root | ||
MONGO_INITDB_ROOT_PASSWORD: 123456 | ||
volumes: | ||
- ./database/data:/data/db | ||
- ./database/init:/docker-entrypoint-initdb.d | ||
healthcheck: | ||
test: echo 'db.runCommand("ping").ok' | mongosh database:27017 --quiet | ||
start_period: 10s | ||
interval: 3s | ||
timeout: 10s | ||
retries: 3 | ||
app: | ||
build: | ||
context: ./ | ||
target: base | ||
command: sh -c "yarn run dev" | ||
environment: | ||
APP_PORT: ${APP_PORT} | ||
MONGODB_URL: ${MONGODB_URL} | ||
ports: | ||
- ${APP_PORT}:${APP_PORT} | ||
healthcheck: | ||
test: wget -qO- http://app:$APP_PORT/health || exit 1 | ||
start_period: 5s | ||
interval: 5s | ||
timeout: 3s | ||
retries: 3 | ||
depends_on: | ||
database: | ||
condition: service_healthy | ||
restart: true | ||
x-develop: | ||
watch: | ||
- action: sync | ||
path: ./src | ||
target: /url-shortener/src | ||
- action: rebuild | ||
path: package.json |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
db = new Mongo().getDB('urlShortener'); | ||
|
||
db.createCollection('links', { capped: false }); | ||
|
||
db.links.createIndex({ shortId: 1 }); | ||
|
||
db.links.insert([ | ||
{ | ||
shortId: 'fPbD2NkcxZ70QPT', | ||
target: 'https://www.google.co.uk/', | ||
}, | ||
{ | ||
shortId: '4Biym9VuXikRrr4', | ||
target: 'https://www.reddit.com/', | ||
}, | ||
{ | ||
shortId: 'Zx8lP5bWMhEgnvH', | ||
target: 'https://www.youtube.com/', | ||
}, | ||
]); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
db = new Mongo().getDB('urlShortener'); | ||
|
||
db.createUser({ | ||
user: 'appUser', | ||
pwd: 'examplePassword', | ||
roles: [ | ||
{ | ||
role: 'readWrite', | ||
db: 'urlShortener', | ||
collection: 'links', | ||
}, | ||
], | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
{ | ||
"name": "url-shortener", | ||
"version": "1.0.0", | ||
"description": "An example of a URL shortening service", | ||
"author": "Anthony Hastings <[email protected]> (https://antwan1986.github.io/)", | ||
"license": "MIT", | ||
"private": true, | ||
"main": "src/index.mjs", | ||
"module": "src/index.mjs", | ||
"type": "module", | ||
"scripts": { | ||
"dev": "nodemon src/index.mjs", | ||
"dev:native": "node --watch src/index.mjs", | ||
"start": "node src/index.mjs" | ||
}, | ||
"dependencies": { | ||
"dotenv": "^16.3.1", | ||
"express": "^4.18.2", | ||
"mongoose": "^7.6.2", | ||
"nanoid": "^5.0.2", | ||
"zod": "^3.22.4" | ||
}, | ||
"devDependencies": { | ||
"nodemon": "^3.0.1", | ||
"prettier": "^3.0.3" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
import { z } from 'zod'; | ||
|
||
export const configSchema = z.object({ | ||
APP_PORT: z.string(), | ||
MONGODB_URL: z.string(), | ||
}); | ||
|
||
export let config; | ||
|
||
try { | ||
config = configSchema.parse(process.env); | ||
} catch (err) { | ||
if (err instanceof z.ZodError) { | ||
const { fieldErrors } = err.flatten(); | ||
|
||
const errorMessage = Object.entries(fieldErrors) | ||
.map(([field, errors]) => | ||
errors ? `${field}: ${errors.join(', ')}` : field, | ||
) | ||
.join('\n '); | ||
|
||
throw new Error(`Missing environment variables:\n ${errorMessage}`); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
import 'dotenv/config'; | ||
import express from 'express'; | ||
import mongoose from 'mongoose'; | ||
import { nanoid } from 'nanoid'; | ||
import { config } from './config.mjs'; | ||
import { LinkModel } from './models/link.mjs'; | ||
import { isValidHTTPURL } from './utils.mjs'; | ||
|
||
mongoose.connection.once('open', () => { | ||
console.log('Connected to MongoDB'); | ||
}); | ||
|
||
await mongoose.connect(config.MONGODB_URL); | ||
|
||
const app = express(); | ||
|
||
app.use(express.json()); | ||
|
||
app.get('/health', (_req, res) => { | ||
res.status(200).json({ status: 'ok' }); | ||
}); | ||
|
||
app.get('/links/:shortId', async (req, res) => { | ||
const link = await LinkModel.findOne({ shortId: req.params.shortId }); | ||
|
||
if (link === null) { | ||
return res.status(404).json({ success: false, error: 'Record not found' }); | ||
} | ||
|
||
res.status(200).json({ success: true, data: link.toJSON() }); | ||
}); | ||
|
||
app.post('/links', async (req, res) => { | ||
const isValidTarget = isValidHTTPURL(req.body.target); | ||
if (!isValidTarget) { | ||
return res.status(409).json({ success: false, error: 'Invalid target' }); | ||
} | ||
|
||
const shortId = nanoid(25); | ||
|
||
const existingLink = await LinkModel.findOne({ shortId }); | ||
if (existingLink) { | ||
return res | ||
.status(409) | ||
.json({ success: false, error: 'Short Id already exists' }); | ||
} | ||
|
||
const link = await new LinkModel({ | ||
shortId, | ||
target: req.body.target, | ||
}).save(); | ||
|
||
res.status(201).json({ success: true, data: link.toJSON() }); | ||
}); | ||
|
||
app.get('/:shortId', async (req, res) => { | ||
const link = await LinkModel.findOne({ shortId: req.params.shortId }); | ||
|
||
if (link === null) { | ||
return res.status(404).json({ success: false, error: 'Record not found' }); | ||
} | ||
|
||
res.redirect(301, link.target); | ||
}); | ||
|
||
app.listen(config.APP_PORT, '0.0.0.0', () => { | ||
console.info(`Listening on port 0.0.0.0:${config.APP_PORT}`); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
import mongoose from 'mongoose'; | ||
|
||
export const LinkModel = mongoose.model( | ||
'Link', | ||
new mongoose.Schema({ | ||
shortId: String, | ||
target: String, | ||
}), | ||
); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
export const isValidHTTPURL = (str) => { | ||
const urlPattern = | ||
/^https?:\/\/[-a-zA-Z0-9+&@#/%?=~_|!:,.;]*[-a-zA-Z0-9+&@#/%=~_|]$/; | ||
|
||
return urlPattern.test(str); | ||
}; |
Oops, something went wrong.