Updated README.md. Implemented the /api/languages endpoint. Refactored the /api endpoint to return methods for each endpoint. Added the voucher id within /api/vouchers. Refactored the /api/voucher endpoint from GET to POST method. Implemented optional email sending within /api/voucher endpoint. Implemented missing comments within api code. Implemented missing HTTP status codes within api responses. Enabled JSON responses within Express

This commit is contained in:
Glenn de Haan
2025-02-25 19:21:36 +01:00
parent 5fa89cdfa9
commit 07d48578e7
2 changed files with 309 additions and 109 deletions

234
README.md
View File

@@ -6,7 +6,7 @@ UniFi Voucher Site is a web-based platform for generating and managing UniFi net
![Vouchers Overview - Desktop](.docs/images/desktop_1.png)
> Upgrading from 3.x to 4.x? Please take a look at the [migration guide](#migration-from-3x-to-4x)
> Upgrading from 4.x to 5.x? Please take a look at the [migration guide](#migration-from-4x-to-5x)
## Features
@@ -181,20 +181,36 @@ the different endpoints available in the API:
- Access: Open
- Response Example:
```json
{
"error": null,
"data": {
"message": "OK",
"endpoints": [
"/api",
"/api/types",
"/api/voucher/:type",
"/api/vouchers"
]
}
}
```
```json
{
"error": null,
"data": {
"message": "OK",
"endpoints": [
{
"method": "GET",
"endpoint": "/api"
},
{
"method": "GET",
"endpoint": "/api/types"
},
{
"method": "GET",
"endpoint": "/api/languages"
},
{
"method": "GET",
"endpoint": "/api/vouchers"
},
{
"method": "POST",
"endpoint": "/api/voucher"
}
]
}
}
```
2. **`/api/types`**
- Method: GET
@@ -203,44 +219,55 @@ the different endpoints available in the API:
- Access: Open
- Response Example:
```json
{
"error": null,
"data": {
"message": "OK",
"types": [
{
"expiration": "480",
"usage": "0",
"raw": "480,0,,,"
```json
{
"error": null,
"data": {
"message": "OK",
"types": [
{
"expiration": "480",
"usage": "0",
"raw": "480,0,,,"
}
]
}
]
}
}
```
}
```
3. **`/api/voucher/:type`**
3. **`/api/languages`**
- Method: GET
- Description: Generates a voucher of the specified type.
- Parameters:
- `type` (string): The type of voucher to generate.
- Description: Retrieves a list of available languages supported by the system.
- Response Format: JSON
- Access: Protected by Bearer Token
- Access: Open
- Response Example:
```json
{
"error": null,
"data": {
"message": "OK",
"voucher": "12345-67890"
}
}
```
> This endpoint is protected by a security mechanism. To access it, users need to include a bearer token in the
request authorization header. The token must match the value of the `AUTH_INTERNAL_BEARER_TOKEN` environment variable. Without
this token, access to the endpoint will be denied.
```json
{
"error": null,
"data": {
"message": "OK",
"languages": [
{
"code": "en",
"name": "English"
},
{
"code": "de",
"name": "German"
},
{
"code": "nl",
"name": "Dutch"
},
{
"code": "pl",
"name": "Polish"
}
]
}
}
```
4. **`/api/vouchers`**
- Method: GET
@@ -249,37 +276,88 @@ the different endpoints available in the API:
- Access: Protected by Bearer Token
- Response Example:
```json
{
"error": null,
"data": {
"message": "OK",
"vouchers": [
{
"code": "15695-53133",
"type": "multi",
"duration": 60,
"data_limit": "200",
"download_limit": "5000",
"upload_limit": "2000"
},
{
"code": "03004-59449",
"type": "single",
"duration": 480,
"data_limit": null,
"download_limit": null,
"upload_limit": null
```json
{
"error": null,
"data": {
"message": "OK",
"vouchers": [
{
"id": "67bded6766f89f2a7ba6731f",
"code": "15695-53133",
"type": "multi",
"duration": 60,
"data_limit": "200",
"download_limit": "5000",
"upload_limit": "2000"
},
{
"id": "67bdecd166f89f2a7ba67317",
"code": "03004-59449",
"type": "single",
"duration": 480,
"data_limit": null,
"download_limit": null,
"upload_limit": null
}
],
"updated": 1712934667937
}
],
"updated": 1712934667937
}
}
```
}
```
> This endpoint is protected by a security mechanism. To access it, users need to include a bearer token in the
request authorization header. The token must match the value of the `AUTH_INTERNAL_BEARER_TOKEN` environment variable. Without
this token, access to the endpoint will be denied.
> This endpoint is protected by a security mechanism. To access it, users need to include a bearer token in the
request authorization header. The token must match the value of the `AUTH_INTERNAL_BEARER_TOKEN` environment variable. Without
this token, access to the endpoint will be denied.
5. **`/api/voucher`**
- Method: POST
- Description: Generates a voucher of the specified type. Optionally sends an email.
- Response Format: JSON
- Access: Protected by Bearer Token
- Body:
- Generate Voucher:
```json
{
"type": "480,0,,,"
}
```
- Generate Voucher and Send Email *(**Warning**: Email module needs to be setup!)*:
```json
{
"type": "480,0,,,",
"email": {
"language": "en",
"address": "user@example.com"
}
}
```
- Response Example:
```json
{
"error": null,
"data": {
"message": "OK",
"voucher": {
"id": "67bdf77b66f89f2a7ba678f7",
"code": "02791-97992"
},
"email": {
"status": "SENT",
"address": "user@example.com"
}
}
}
```
> This endpoint is protected by a security mechanism. To access it, users need to include a bearer token in the
request authorization header. The token must match the value of the `AUTH_INTERNAL_BEARER_TOKEN` environment variable. Without
this token, access to the endpoint will be denied.
## Authentication
@@ -518,6 +596,10 @@ Detailed information on the changes in each release can be found on the [GitHub
## Migration Guide
### Migration from 4.x to 5.x
When upgrading from 4.x to 5.x, the following changes need to be made:
### Migration from 3.x to 4.x
When upgrading from 3.x to 4.x, the following changes need to be made:

184
server.js
View File

@@ -106,6 +106,11 @@ app.use(locale({
"default": "en-GB"
}));
/**
* Enable JSON
*/
app.use(express.json());
/**
* Enable multer
*/
@@ -591,10 +596,26 @@ if(variables.serviceApi) {
data: {
message: 'OK',
endpoints: [
'/api',
'/api/types',
'/api/voucher/:type',
'/api/vouchers'
{
method: 'GET',
endpoint: '/api'
},
{
method: 'GET',
endpoint: '/api/types'
},
{
method: 'GET',
endpoint: '/api/languages'
},
{
method: 'GET',
endpoint: '/api/vouchers'
},
{
method: 'POST',
endpoint: '/api/voucher'
}
]
}
});
@@ -608,36 +629,19 @@ if(variables.serviceApi) {
}
});
});
app.get('/api/voucher/:type', [authorization.api], async (req, res) => {
const typeCheck = (variables.voucherTypes).split(';').includes(req.params.type);
if(!typeCheck) {
res.json({
error: 'Unknown Type!',
data: {}
});
return;
}
// Create voucher code
const voucherCode = await unifi.create(types(req.params.type, true)).catch((e) => {
res.json({
error: e,
data: {}
});
app.get('/api/languages', (req, res) => {
res.json({
error: null,
data: {
message: 'OK',
languages: Object.keys(languages).map(language => {
return {
code: language,
name: languages[language]
}
})
}
});
await updateCache();
if(voucherCode) {
res.json({
error: null,
data: {
message: 'OK',
voucher: voucherCode
}
});
}
});
app.get('/api/vouchers', [authorization.api], async (req, res) => {
res.json({
@@ -646,6 +650,7 @@ if(variables.serviceApi) {
message: 'OK',
vouchers: cache.vouchers.map((voucher) => {
return {
id: voucher._id,
code: `${voucher.code.slice(0, 5)}-${voucher.code.slice(5)}`,
type: voucher.quota === 1 ? 'single' : voucher.quota === 0 ? 'multi' : 'multi',
duration: voucher.duration,
@@ -658,6 +663,119 @@ if(variables.serviceApi) {
}
});
});
app.post('/api/voucher', [authorization.api], async (req, res) => {
// Verify valid body is sent
if(!req.body || !req.body.type) {
res.status(400).json({
error: 'Invalid Body!',
data: {}
});
return;
}
// Check if email body is set
if(req.body.email) {
// Check if email module is enabled
if(variables.smtpFrom === '' || variables.smtpHost === '' || variables.smtpPort === '') {
res.status(400).json({
error: 'Email Not Configured!',
data: {}
});
return;
}
// Check if email body is correct
if(!req.body.email.language || !req.body.email.address) {
res.status(400).json({
error: 'Invalid Body!',
data: {}
});
return;
}
// Check if language is available
if(!Object.keys(languages).includes(req.body.email.language)) {
res.status(400).json({
error: 'Unknown Language!',
data: {}
});
return;
}
}
// Check if type is implemented and valid
const typeCheck = (variables.voucherTypes).split(';').includes(req.body.type);
if(!typeCheck) {
res.status(400).json({
error: 'Unknown Type!',
data: {}
});
return;
}
// Create voucher code
const voucherCode = await unifi.create(types(req.body.type, true)).catch((e) => {
res.status(500).json({
error: e,
data: {}
});
});
// Update application cache
await updateCache();
if(voucherCode) {
// Locate voucher data within cache
const voucherData = cache.vouchers.find(voucher => voucher.code === voucherCode.replaceAll('-', ''));
if(!voucherData) {
res.status(500).json({
error: 'Invalid application cache!',
data: {}
});
return;
}
// Check if we should send and email
if(req.body.email) {
// Send mail
const emailResult = await mail.send(req.body.email.address, voucherData, req.body.email.language).catch((e) => {
res.status(500).json({
error: e,
data: {}
});
});
// Verify is the email was sent successfully
if(emailResult) {
res.json({
error: null,
data: {
message: 'OK',
voucher: {
id: voucherData._id,
code: voucherCode
},
email: {
status: 'SENT',
address: req.body.email.address
}
}
});
}
} else {
res.json({
error: null,
data: {
message: 'OK',
voucher: {
id: voucherData._id,
code: voucherCode
}
}
});
}
}
});
}
/**