Skip to content

Commit

Permalink
Merge pull request #930 from IntersectMBO/component/metadata-api
Browse files Browse the repository at this point in the history
Metadata and Lock API for tests
  • Loading branch information
mesudip committed May 8, 2024
2 parents 69a48fa + d37fc23 commit a7b6a58
Show file tree
Hide file tree
Showing 9 changed files with 1,023 additions and 0 deletions.
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -139,3 +139,7 @@ scripts/govtool/dev-postgres_password
# nodejs/yarn

node_modules


# sonar scanner
.scannerwork/
3 changes: 3 additions & 0 deletions tests/test-metadata-api/.dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
json_files
Dockerfile
README.md
2 changes: 2 additions & 0 deletions tests/test-metadata-api/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
json_files
node_modules
9 changes: 9 additions & 0 deletions tests/test-metadata-api/Dockerfile
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"]
47 changes: 47 additions & 0 deletions tests/test-metadata-api/README.md
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.
151 changes: 151 additions & 0 deletions tests/test-metadata-api/index.js
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}`);
});
109 changes: 109 additions & 0 deletions tests/test-metadata-api/locks_api.js
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;
15 changes: 15 additions & 0 deletions tests/test-metadata-api/package.json
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"
}
}

0 comments on commit a7b6a58

Please sign in to comment.