-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #930 from IntersectMBO/component/metadata-api
Metadata and Lock API for tests
- Loading branch information
Showing
9 changed files
with
1,023 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 |
---|---|---|
|
@@ -139,3 +139,7 @@ scripts/govtool/dev-postgres_password | |
# nodejs/yarn | ||
|
||
node_modules | ||
|
||
|
||
# sonar scanner | ||
.scannerwork/ |
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 @@ | ||
json_files | ||
Dockerfile | ||
README.md |
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,2 @@ | ||
json_files | ||
node_modules |
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 @@ | ||
FROM node:18-alpine | ||
WORKDIR /src | ||
COPY package.json yarn.lock ./ | ||
RUN yarn install | ||
COPY . . | ||
VOLUME /data | ||
ENV DATA_DIR=/data | ||
EXPOSE 3000 | ||
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,47 @@ | ||
Test metadata API | ||
================= | ||
|
||
Simple service to host json metadata during testing. | ||
|
||
## Installation | ||
|
||
``` | ||
git clone https://github.com/your/repository.git | ||
yarn install | ||
yarn start | ||
``` | ||
#### Swagger UI | ||
|
||
``` | ||
http://localhost:3000/docs | ||
``` | ||
|
||
## Metadata Endpoints | ||
|
||
### 1. Save File | ||
|
||
- **Endpoint:** `PUT /data/{filename}` | ||
- **Description:** Saves data to a file with the specified filename. | ||
|
||
### 2. Get File | ||
|
||
- **Endpoint:** `GET /data/{filename}` | ||
- **Description:** Retrieves the content of the file with the specified filename. | ||
|
||
### 3. Delete File | ||
|
||
- **Endpoint:** `DELETE /data/{filename}` | ||
- **Description:** Deletes the file with the specified filename. | ||
|
||
## Locks Endpoint | ||
### 1. Acquire Lock | ||
- **Endpoint:** `POST /lock/{key}?expiry={expiry_secs}` | ||
- **Description:** Acquire a lock for the specified key for given time. By default the lock is set for 180 secs. | ||
- **Responses:** | ||
- `200 OK`: Lock acquired successfully. | ||
- `423 Locked`: Lock not available. | ||
|
||
### 2. Release Lock | ||
|
||
- **Endpoint:** `POST/unlock/{key}` | ||
- **Description:** Release a lock for the specified key. |
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,151 @@ | ||
const express = require('express'); | ||
const fs = require('fs'); | ||
const path = require('path'); | ||
const lock_api = require('./locks_api') | ||
|
||
const swaggerUi = require('swagger-ui-express'); | ||
const swaggerJsdoc = require('swagger-jsdoc'); | ||
const app = express(); | ||
|
||
|
||
const dataDir = process.env.DATA_DIR || path.join(__dirname, 'json_files'); | ||
|
||
if (!fs.existsSync(dataDir)) { | ||
fs.mkdirSync(dataDir, { recursive: true }); | ||
} | ||
// Middleware to parse text request bodies | ||
app.use(express.text()); | ||
|
||
// Swagger configuration | ||
const swaggerOptions = { | ||
definition: { | ||
openapi: '3.0.0', | ||
info: { | ||
title: 'File API', | ||
version: '1.0.0', | ||
description: 'API for saving and deleting files', | ||
}, | ||
}, | ||
apis: ['index.js','locks_api.js'], // Update the path to reflect the compiled JavaScript file | ||
}; | ||
|
||
const swaggerSpec = swaggerJsdoc(swaggerOptions); | ||
|
||
// Serve Swagger UI | ||
app.use('/docs', swaggerUi.serve, swaggerUi.setup(swaggerSpec)); | ||
|
||
// PUT endpoint to save a file | ||
/** | ||
* @swagger | ||
* /data/{filename}: | ||
* put: | ||
* summary: Save data to a file | ||
* tags: [Metadata File] | ||
* parameters: | ||
* - in: path | ||
* name: filename | ||
* schema: | ||
* type: string | ||
* required: true | ||
* description: The name of the file to save | ||
* requestBody: | ||
* required: true | ||
* content: | ||
* text/plain: | ||
* schema: | ||
* type: string | ||
* responses: | ||
* '201': | ||
* description: File saved successfully | ||
*/ | ||
app.put('/data/:filename', (req, res) => { | ||
const filename = req.params.filename; | ||
const filePath = path.join(dataDir, filename); | ||
|
||
fs.writeFile(filePath, req.body, (err) => { | ||
if (err) { | ||
console.error(err); | ||
return res.status(500).send('Failed to save file'); | ||
} | ||
res.status(201).send({'success': true}); | ||
}); | ||
}); | ||
|
||
|
||
// GET endpoint to retrieve a file | ||
/** | ||
* @swagger | ||
* /data/{filename}: | ||
* get: | ||
* summary: Get a file | ||
* tags: [Metadata File] | ||
* parameters: | ||
* - in: path | ||
* name: filename | ||
* schema: | ||
* type: string | ||
* required: true | ||
* description: The name of the file to retrieve | ||
* responses: | ||
* '200': | ||
* description: File retrieved successfully | ||
* content: | ||
* text/plain: | ||
* schema: | ||
* type: string | ||
*/ | ||
app.get('/data/:filename', (req, res) => { | ||
const filename = req.params.filename; | ||
const filePath = path.join(dataDir, filename); | ||
|
||
fs.readFile(filePath, 'utf8', (err, data) => { | ||
if (err) { | ||
console.error(err); | ||
return res.status(404).send({'message': 'File not found'}); | ||
} | ||
res.status(200).send(data); | ||
}); | ||
}); | ||
|
||
|
||
|
||
// DELETE endpoint to delete a file | ||
/** | ||
* @swagger | ||
* /data/{filename}: | ||
* delete: | ||
* summary: Delete a file | ||
* tags: [Metadata File] | ||
* parameters: | ||
* - in: path | ||
* name: filename | ||
* schema: | ||
* type: string | ||
* required: true | ||
* description: The name of the file to delete | ||
* responses: | ||
* '200': | ||
* description: File deleted successfully | ||
*/ | ||
app.delete('/data/:filename', (req, res) => { | ||
const filename = req.params.filename; | ||
const filePath = path.join(dataDir, filename); | ||
|
||
fs.unlink(filePath, (err) => { | ||
if (err) { | ||
console.error(err); | ||
return res.status(500).send({'message':'Failed to delete file'}); | ||
} | ||
res.send('File deleted successfully'); | ||
}); | ||
}); | ||
|
||
app.get('/', (req, res) => { | ||
res.redirect('/docs'); | ||
}); | ||
lock_api.setup(app) | ||
// Start the server | ||
const PORT = process.env.PORT || 3000; | ||
app.listen(PORT, () => { | ||
console.log(`Server is running on port ${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,109 @@ | ||
const { v4: uuidv4 } = require('uuid'); | ||
const lock = {}; | ||
|
||
function acquireLock(key, expiry_secs = 180) { | ||
const now = Date.now(); | ||
if (!lock[key] || lock[key].expiry < now) { | ||
const uuid = uuidv4(); | ||
lock[key] = { | ||
locked: true, | ||
expiry: now + expiry_secs * 1000, | ||
uuid: uuid, | ||
}; | ||
return uuid | ||
} | ||
} | ||
function releaseLock(key,uuid) { | ||
if(uuid){ | ||
_lock=lock[key] | ||
if(_lock && (_lock.uuid != uuid)){ | ||
// if the uuid doesn't match, the lock should | ||
// have expired and obtained by process. | ||
return; | ||
} | ||
} | ||
delete lock[key]; | ||
} | ||
|
||
|
||
function setup(app) { | ||
/** | ||
* @swagger | ||
* /lock/{key}: | ||
* post: | ||
* summary: Acquire lock | ||
* tags: [Locks] | ||
* parameters: | ||
* - in: path | ||
* name: key | ||
* schema: | ||
* type: string | ||
* required: true | ||
* description: The key of the lock to acquire | ||
* - in: query | ||
* name: expiry_secs | ||
* schema: | ||
* type: integer | ||
* minimum: 1 | ||
* default: 180 | ||
* description: The expiration time of the lock in seconds (default is 180s) | ||
* responses: | ||
* '200': | ||
* description: Lock acquired successfully | ||
* content: | ||
* application/json: | ||
* schema: | ||
* type: object | ||
* properties: | ||
* uuid: | ||
* type: string | ||
* description: The UUID of the acquired lock | ||
* '423': | ||
* description: Lock not available | ||
*/ | ||
app.post('/lock/:key', (req, res) => { | ||
const key = req.params.key; | ||
const expiry_secs = req.query.expiry_secs ? parseInt(req.query.expiry_secs) : 180; | ||
const lock_uuid=acquireLock(key, expiry_secs) | ||
if(lock_uuid){ | ||
res.json({ uuid: lock_uuid }) | ||
}else{ | ||
res.status(423).json({ status: 423, message: 'Lock not available' }); | ||
|
||
} | ||
}); | ||
|
||
/** | ||
* @swagger | ||
* /unlock/{key}: | ||
* post: | ||
* summary: Release lock | ||
* tags: [Locks] | ||
* parameters: | ||
* - in: path | ||
* name: key | ||
* schema: | ||
* type: string | ||
* required: true | ||
* description: The key of the lock to release | ||
* - in: query | ||
* name: uuid | ||
* schema: | ||
* type: string | ||
* required: false | ||
* description: The UUID of the lock to release | ||
* responses: | ||
* '200': | ||
* description: Lock released successfully | ||
*/ | ||
app.post('/unlock/:key', (req, res) => { | ||
const key = req.params.key; | ||
const uuid = req.query.uuid; | ||
|
||
releaseLock(key, uuid); | ||
res.send('Lock released.'); | ||
|
||
}); | ||
} | ||
|
||
module.exports.setup = setup; |
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 @@ | ||
{ | ||
"name": "test-metadata-api", | ||
"version": "0.0.1", | ||
"main": "index.js", | ||
"license": "MIT", | ||
"scripts": { | ||
"start": "node index.js" | ||
}, | ||
"dependencies": { | ||
"express": "^4.19.2", | ||
"swagger-jsdoc": "^6.2.8", | ||
"swagger-ui-express": "^5.0.0", | ||
"uuid": "^9.0.1" | ||
} | ||
} |
Oops, something went wrong.