Skip to content

Commit d2b7390

Browse files
committed
Global refactoring
1 parent bcf083e commit d2b7390

25 files changed

+765
-454
lines changed

LICENSE

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
MIT License
22

3-
Copyright (c) 2023 Len
3+
Copyright (c) 2025 Len
44

55
Permission is hereby granted, free of charge, to any person obtaining a copy
66
of this software and associated documentation files (the "Software"), to deal

README.md

Lines changed: 155 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,45 @@
11
# Url Shortener
22

3-
A simple url shortener written in Node JS using Express and MongoDB.<br>
4-
Description: automatically redirects to saved "long" urls, counts clicks per url and has an API access point to add/edit/view/delete urls via an external app.
3+
A simple URL shortener written in Node JS using Express and MongoDB.<br>
4+
Description: automatically redirects to saved "long" URLs, counts clicks per URL and has an API endpoint to add, edit, view and delete URLs via an external app.
55

66
> [!TIP]
7-
> To get the most use out of this app - have it running on a hosting service so it can be accessed from any device. Ideally you'd need a short domain as well for this app to make sense.
7+
> To get the most use out of this app - host it on a server so it can be accessed from any device. Ideally you'd need a short domain for this app to make sense.
88
99
<details>
1010
<summary><h3>Content</h3></summary>
1111

12+
- [Project Structure](#project-structure)
1213
- [Installation](#installation)
13-
- [API actions](#api-actions)
14+
- [API Endpoints](#api-endpoints)
1415
- [Parameters](#parameters)
1516
- [Responses](#responses)
16-
- [Registering new short url](#registering-new-short-url)
17-
- [Editing existing urls](#editing-existing-urls)
18-
- [Searching existing short urls](#searching-existing-short-urls)
19-
- [Deleting existing short urls](#deleting-existing-short-urls)
17+
- [Registering new short URL](#registering-new-short-url)
18+
- [Editing existing URLs](#editing-existing-urls)
19+
- [Searching existing short URLs](#searching-existing-short-urls)
20+
- [Deleting existing short URLs](#deleting-existing-short-urls)
2021
- [Refreshing app's database](#refreshing-apps-database)
21-
- [Creating short urls directly inside MongoDB](#creating-short-urls-directly-inside-mongodb)
22+
- [Creating short URLs directly inside MongoDB](#creating-short-urls-directly-inside-mongodb)
23+
- [Webhook logs](#webhook-logs)
2224
- [Usage](#usage)
2325

2426
</details>
2527
<hr>
2628

29+
# Project Structure
30+
31+
A quick overview of the main folders in this repository:
32+
33+
- **controllers/** – Route handler logic for API and redirect endpoints.
34+
- **middleware/** – Express middleware for authentication, sanitization, and error handling.
35+
- **public/** – Static assets (images, CSS, favicon).
36+
- **routes/** – Express route definitions for API and redirect endpoints.
37+
- **schemas/** – Mongoose schemas for MongoDB collections.
38+
- **services/** – Application state, ensuring that the data is synchronized across the app.
39+
- **utils/** – Helper functions and constants such as validation, formatting, and so on.
40+
- **views/** – EJS templates for server-rendered pages.
41+
- **index.js** - Main file.
42+
2743
# Installation
2844

2945
1. Open a MongoDB project if you don't have one.
@@ -32,151 +48,200 @@ Description: automatically redirects to saved "long" urls, counts clicks per url
3248
```
3349
DB=mongodb+srv://<USERNAME>:<PASSWORD>@<CLUSTER>.mongodb.net/?retryWrites=true&w=majority
3450
LOCAL=true
51+
WEBHOOK_URL=<optional - needed to send logs via webhooks>
52+
APP_NAME=<optional, used in webhooks>
53+
APP_AVATAR=<optional, used in webhooks>
3554
```
3655

3756
> [!IMPORTANT]
3857
> The `LOCAL` parameter should only exist locally on your pc, don't include it in the hosted app.
3958
40-
3. Create file in your MongoDB database according to the schema in `schemas/access.js`:
59+
> [!NOTE]
60+
> You can read more on the app's webhook logs system under [Webhook logs](#webhook-logs).
61+
62+
3. Create a document in your MongoDB database according to the schema in `schemas/access.js`:
4163

4264
```
43-
urlLocal: <local url for the app, for example `http://localhost:8080/`>
44-
urlRemote: <url of the hosted app>
65+
urlLocal: <local URL for the app, for example `http://localhost:8080/`>
66+
urlRemote: <URL of the hosted app>
4567
key: <key for your API, will be needed by external apps to access your API>
68+
admins: [<array of admin user IDs or keys>]
4669
```
4770

4871
4. Replace `public/images/favicon.ico` with an icon of your choice.
4972
5. Update the landing page `views/home.ejs` and the stylesheet `public/css/style.css` for your liking.
5073
6. Run `npm i`.
5174
7. Start `index.js`.
5275

53-
# API actions
76+
# API Endpoints
77+
78+
All API endpoints require an `Authorization` header with your API key:
79+
80+
```
81+
Authorization: Bearer <key>
82+
```
5483

5584
## Parameters:
5685

57-
- key - The API key mentioned previously.
58-
- act - Action: Can be one of the following: new, page, label, url, search, delete.
59-
- u - Url: The url you'd like to shorten.
60-
- p - Page: The url extension your short url will get. For example,<br>
61-
if your urlRemote=`https://myapp.com` and p=`page`, the short url you've registered will be at `https://myapp.com/page`.
62-
- l (lowercase L) - Label: A readable name for your short url.
63-
- rb - Registered By: Name or ID (depending on platform) of the user that registered the short url.
86+
- Url: The URL you'd like to shorten.
87+
- Page: The URL extension your short URL will get.<br>
88+
For example, if your urlRemote=`https://myapp.com` and page=`example`,<br>
89+
the short URL you've registered will be at `https://myapp.com/example`.
90+
- Label: A readable name for your short URL, will be used to query registered pairs.
91+
- RegisteredBy: Name or ID of the user that registered the short URL.
92+
- ShowAll: "true" or "false", an admin parameter used to display pairs from all users when querying.<br>
93+
(has no effect if user isn't an admin)
6494

65-
> [!NOTE]
66-
> The `n` before a parameter name in the editing options indicates "new".
95+
See each endpoint for required and optional parameters.
6796

6897
## Responses:
6998

70-
App responds with 401 if request is missing `<key>`, if it was passed it will respond with JSON containing `status` and `response` fields.
71-
The response can be a string indicating an error, or an object containing the fields:
72-
73-
- message
74-
- label
75-
- shortUrl
76-
- orgUrl
77-
- registeredBy
78-
- clicks
99+
App respond are JSON containing a `status` field (`"success"` or `"error"`).
100+
101+
On success, a `data` field is included with and up-to-date data object.<br>
102+
Example success:
103+
104+
```json
105+
{
106+
"status": "success",
107+
"data": {
108+
"message": "Short URL has been registered.",
109+
"label": "Example",
110+
"shortUrl": "https://myapp.com/example",
111+
"orgUrl": "https://long-url-example.com",
112+
"registeredBy": "user123",
113+
"clicks": 0
114+
}
115+
}
116+
```
79117

80118
> [!NOTE]
81119
> Search will return an array of objects instead. (or an empty array)
82120
83-
## Registering new short url
121+
On error, the response will include an `error` object containing a `message` field.<br>
122+
Example error:
84123

85-
### act=new
86-
87-
```Javascript
88-
GET <urlRemote>api?key=<key>&act=new&u=<url>&p=<page>&l=<label>&rb=<user>
124+
```json
125+
{
126+
"status": "error",
127+
"error": { "message": "Missing `label` parameter." }
128+
}
89129
```
90130

91-
## Editing existing urls
92-
93-
To edit a short url the `rb` parameter has to be the same as the user that registered it. If `<key>` is passed instead, the app will treat the user as an admin and let them bypass this check.
131+
## Registering new short URL
94132

95-
### act=page
133+
**POST** `/api/urlpairs`
96134

97-
Update url extension of an existing short url.
135+
**Body:**
98136

99-
```Javascript
100-
GET <urlRemote>api?key=<key>&act=page&p=<page>&np=<npage>&rb=<user>
137+
```json
138+
{
139+
"url": "https://long-url-example.com",
140+
"page": "example",
141+
"label": "Example",
142+
"registeredBy": "user123"
143+
}
101144
```
102145

103-
### act=label
146+
- All fields are required.
147+
148+
## Editing existing URLs
104149

105-
Update label of an existing short url.
150+
**PATCH** `/api/urlpairs/:page`
106151

107-
```Javascript
108-
GET <urlRemote>api?key=<key>&act=label&p=<page>&nl=<nlabel>&rb=<user>
152+
**Body:**
153+
154+
```json
155+
{
156+
"newUrl": "https://new-long-url-site.com",
157+
"newPage": "newexample",
158+
"newLabel": "New Example",
159+
"registeredBy": "user123"
160+
}
109161
```
110162

111-
### act=url
163+
- `registeredBy` is required and must match the owner of the URL pair or be an admin.
164+
- `newUrl`, `newPage`, and `newLabel` are optional parameters. Only include the ones you're updating.
112165

113-
Update url of an existing short url.
166+
## Searching existing short URLs
114167

115-
```Javascript
116-
GET <urlRemote>api?key=<key>&act=url&p=<page>&nu=<nurl>&rb=<user>
117-
```
168+
**GET** `/api/urlpairs?registeredBy=<user>&url=<url>&page=<page>&label=<label>&showAll=<true/false>`
118169

119-
## Searching existing short urls
170+
- `registeredBy` is required.
171+
- `url`, `page`, and `label` are optional filters.
172+
- `showAll` is an optional filter, when queried by an admin would return pairs of all users instead of only the user's.<br>
173+
(has no effect if user isn't an admin)
120174

121-
Searching will by default only return urls registered by the user passed in `rb`, unless `<key>` was passed, then the app will treat the user as an admin and let them bypass this check.
175+
Returns an array of matching pairs.
122176

123-
### act=search
177+
## Deleting existing short URLs
124178

125-
Required parameters:
179+
**DELETE** `/api/urlpairs/:page`
126180

127-
```Javascript
128-
GET <urlRemote>api?key=<key>&act=search&rb=<user>
181+
**Body:**
182+
183+
```json
184+
{
185+
"label": "Example",
186+
"registeredBy": "user123"
187+
}
129188
```
130189

131-
Optional parameters:
190+
- `registeredBy` is required and must match the owner of the URL pair or be an admin.
191+
- `label` must match the URL pair you're deleting.
132192

133-
```Javascript
134-
u=<url>
135-
p=<page>
136-
l=<label>
137-
```
193+
## Refreshing app's database
194+
195+
**GET** `/api/admin/urlpairs/refresh?registeredBy=<user>`
138196

139197
> [!NOTE]
140-
> The app will return an array of objects whose fields included the search parameters passed in the request.
198+
> Only admins can refresh the database.
199+
200+
> [!IMPORTANT]
201+
> The database is refreshed automatically when accessed via the API. Use this action only if you updated the database manually.
141202
142-
## Deleting existing short urls
203+
# Creating short URLs directly inside MongoDB
143204

144-
To delete a short url the `rb` parameter has to be the same as the user that registered it. If `<key>` is passed instead, the app will treat the user as an admin and let them bypass this check.
205+
The documents need to match the schema in `schemas/urlPair.js`.
145206

146-
### act=delete
207+
For example:
147208

148-
```Javascript
149-
GET <urlRemote>api?key=<key>&act=delete&p=<page>&l=<label>&rb=<user>
209+
```json
210+
{
211+
"url": "https://long-url-example.com",
212+
"page": "example",
213+
"label": "Example",
214+
"registeredBy": "user123",
215+
"clicks": 0
216+
}
150217
```
151218

152-
## Refreshing app's database
153-
154-
To refresh the database the `rb` parameter has to be equal to `<key>`.<br>
219+
After adding documents directly to MongoDB, restart or refresh the app via the API.
155220

156-
> [!IMPORTANT]
157-
> If accessed via the API, the database gets refreshed automatically, this action is only necessary if the database was updated manually via MongoDB.
221+
# Webhook logs
158222

159-
### act=refresh
223+
The app has a basic logs system that can optionally send events to a Discord channel (or any other service that accepts webhooks).<br>
224+
If a `WEBHOOK_URL` is set, the app will attempt to send a webhook message using the optional `APP_NAME` and `APP_AVATAR` variables.<br>
225+
Regardles whether a webhook is set, all messages sent to the system are logged in the console.<br>
226+
<br>
227+
Currently the app only sends messages when it goes online/offline and database events, but feel free to add more logs using the function in `utils/webhooks.js`:
160228

161-
```Javascript
162-
GET <urlRemote>api?key=<key>&act=refresh&rb=<user>
229+
```js
230+
const webhookLog = require("./utils/webhooks");
231+
await webhookLog("New log message.");
163232
```
164233

165-
# Creating short urls directly inside MongoDB
234+
The function allows for a few overrides: app name and avatar overrides if you'd like to use something different from the default ones, and a message override if you'd like the message sent in the webhook to be different from the one printed in the console:
166235

167-
The files will need to be according to the schema in `schemas/urlpair.js`.
168-
169-
```
170-
url: <The url you'd like to shorten>
171-
page: <The url extension your short url will get>
172-
label: <A readable name for your short url>
173-
registeredBy: <Name or ID of the user that registered the short url>
174-
clicks: 0
236+
```js
237+
const webhookLog = require("./utils/webhooks");
238+
await webhookLog("New console log message.", "Other system", "https://myapp.com/other_system.png", "New webhook message");
175239
```
176240

177-
After adding files directly to MongoDB, the app will need to be restarted or refreshed via the API.
241+
> [!NOTE]
242+
> The webhook's body structure and content formatting were set for my personal taste and needs, so feel free to adjust them.
178243
179244
# Usage
180245

181-
1. Register short urls via an external app that uses the API, or via creating files in your MongoDB database.
182-
2. Use the short urls and you will be redirected to their long counterparts automatically.
246+
1. Register short URLs via an external app that uses the API, or by creating documents in your MongoDB database.
247+
2. Use the short URLs and you will be redirected to their long counterparts automatically.

0 commit comments

Comments
 (0)