Commit dfe59187 authored by Panagiotis Skarvelis's avatar Panagiotis Skarvelis
Browse files

cleanup

parent f2f5c9ee
......@@ -6,8 +6,9 @@ import { AuditRecord } from './interfaces/index';
* @comment you can provide a custom audit record, or use the generated one
* @param auditInit - The audit record to be stored, if is empty a new one will be generated
* @param storagePath - The path where the audit record will be stored, default is the tmp directory
* @returns AuditRecord | null | undefined
* @env env.HOSTIP - useful to pass the IP address of the end user automatically on docker enviroments
* @returns AuditRecord | null - The audit record generated or the one provided
*/
export declare const generateAuditRecord: (auditInit: AuditRecord | undefined, storagePath?: string) => Promise<AuditRecord | null | undefined>;
export declare const generateAuditRecord: (auditInit: AuditRecord | undefined, storagePath?: string) => AuditRecord | null;
export default generateAuditRecord;
export { AuditRecord };
......@@ -9,12 +9,13 @@ import FileEngine from './lib/FileEngine';
* @comment you can provide a custom audit record, or use the generated one
* @param auditInit - The audit record to be stored, if is empty a new one will be generated
* @param storagePath - The path where the audit record will be stored, default is the tmp directory
* @returns AuditRecord | null | undefined
* @env env.HOSTIP - useful to pass the IP address of the end user automatically on docker enviroments
* @returns AuditRecord | null - The audit record generated or the one provided
*/
export const generateAuditRecord = async (auditInit, storagePath = "/tmp") => {
export const generateAuditRecord = (auditInit, storagePath = "/tmp") => {
const auditUnit = auditInit?.auditUnit || "gov.gr";
const auditTransactionId = auditInit?.auditTransactionId || "" + await sequence();
const auditProtocol = auditInit?.auditProtocol || await protocol();
const auditTransactionId = auditInit?.auditTransactionId || "" + sequence(storagePath + "/sequence", storagePath + "/sequence");
const auditProtocol = auditInit?.auditProtocol || protocol(storagePath);
const auditTransactionDate = auditInit?.auditTransactionDate || new Date().toISOString().split('.')[0] + "Z";
const auditUserIp = auditInit?.auditUserIp || process.env.HOSTIP || "127.0.0.1";
const auditUserId = auditInit?.auditUserId || "system";
......@@ -27,6 +28,13 @@ export const generateAuditRecord = async (auditInit, storagePath = "/tmp") => {
auditUserId
};
const dbEngine = new db(new FileEngine(storagePath));
return dbEngine.put(auditRecord);
try {
return dbEngine.put(auditRecord);
}
catch (error) {
const err = error;
console.log(err.code, err.message);
return null;
}
};
export default generateAuditRecord;
......@@ -16,9 +16,12 @@ export declare type AuditRecord = {
* @interface AuditEngine
*/
export interface AuditEngine {
put: (record: AuditRecord) => AuditRecord | null;
get: (auditTransactionId: string) => AuditRecord | null;
put: (record: AuditRecord) => AuditRecord;
get: (auditTransactionId: string) => AuditRecord;
}
/**
* @description FileSystem errors
*/
export declare type FS_ERROR = {
code: string;
message: string;
......
......@@ -9,7 +9,21 @@ import { AuditRecord, AuditEngine } from '../interfaces/index';
export declare class FileEngine implements AuditEngine {
#private;
constructor(path: string);
put(record: AuditRecord): AuditRecord | null;
get(auditTransactionId: string): any;
/**
* @description Store a record in the database
* @param {AuditRecord} record - record to be stored
* @returns {AuditRecord} - the record stored
* @memberof FileEngine
* @method put
*/
put(record: AuditRecord): AuditRecord;
/**
* @description Get a record from the database
* @param auditTransactionId
* @returns {AuditRecord}
* @memberof FileEngine
* @method get
*/
get(auditTransactionId: string): AuditRecord;
}
export default FileEngine;
......@@ -12,6 +12,13 @@ export class FileEngine {
constructor(path) {
this.#path = path;
}
/**
* @description Store a record in the database
* @param {AuditRecord} record - record to be stored
* @returns {AuditRecord} - the record stored
* @memberof FileEngine
* @method put
*/
put(record) {
const data = JSON.stringify(record, null, 2);
try {
......@@ -19,16 +26,23 @@ export class FileEngine {
return record;
}
catch (error) {
return null;
throw error;
}
}
/**
* @description Get a record from the database
* @param auditTransactionId
* @returns {AuditRecord}
* @memberof FileEngine
* @method get
*/
get(auditTransactionId) {
try {
const data = fs.readFileSync(this.#path + '/record-' + auditTransactionId + '.json', 'utf8');
return JSON.parse(data);
}
catch (error) {
return null;
throw error;
}
}
}
......
......@@ -8,7 +8,21 @@ import { AuditRecord, AuditEngine } from '../interfaces/index';
export declare class db {
engine: AuditEngine;
constructor(engine: AuditEngine);
put(record: AuditRecord): AuditRecord | null | undefined;
get(auditTransactionId: string): AuditRecord | null;
/**
* @description Store a record in the database
* @param record
* @returns AuditRecord
* @memberof db
* @method put
*/
put(record: AuditRecord): AuditRecord;
/**
* @description Get a record from the database
* @param auditTransactionId
* @returns AuditRecord
* @memberof db
* @method get
*/
get(auditTransactionId: string): AuditRecord;
}
export default db;
......@@ -9,14 +9,28 @@ export class db {
constructor(engine) {
this.engine = engine;
}
/**
* @description Store a record in the database
* @param record
* @returns AuditRecord
* @memberof db
* @method put
*/
put(record) {
if (!record.auditTransactionId)
return;
throw new Error("record.auditTransactionId is required");
return this.engine.put(record);
}
/**
* @description Get a record from the database
* @param auditTransactionId
* @returns AuditRecord
* @memberof db
* @method get
*/
get(auditTransactionId) {
if (!auditTransactionId)
return null;
throw new Error("auditTransactionId is required");
return this.engine.get(auditTransactionId);
}
}
......
/**
*
* @param protocol_number
* Generate a new protocol number
* in the format sequence/date
* each day a new sequence is generated
* @param protocol_path
* @returns string
*/
declare const protocol: (protocol_string?: string, protocol_path?: string) => Promise<string>;
declare const protocol: (protocol_path: string) => string;
export default protocol;
import sequence from './sequence';
/**
*
* @param protocol_number
* Generate a new protocol number
* in the format sequence/date
* each day a new sequence is generated
* @param protocol_path
* @returns string
*/
const protocol = async (protocol_string = "", protocol_path = "/tmp") => {
const protocol = (protocol_path) => {
const protocol_date = new Date().toISOString().split('T')[0];
const protocol_sequence = await sequence(protocol_path + "/" + protocol_date + ".protocol.sequence"); //Protocol starts from 1 each day.
const path = `${protocol_path}/${protocol_date}.protocol.sequence`;
const protocol_sequence = sequence(path, path); //Protocol starts from 1 each day.
let pn = protocol_sequence + "/" + protocol_date;
if (protocol_string !== "")
pn = protocol_string;
return pn;
};
export default protocol;
/**
*
* update the sequence number on giveb sequence file
* @param seqfile
* @param lockfile
* @returns number
*/
declare const sequence: (seqfile?: string, lockfile?: string) => Promise<number>;
declare const sequence: (seqfile?: string, lockfile?: string) => number;
export default sequence;
import fs from 'fs';
const LOCK_FILE_PATH = process.env.LOCK_FILE_PATH || "/tmp/audit/sequence";
const SEQUENCE_FILE = process.env.SEQUENCE_FILE || "/tmp/audit/sequence";
const TIMEOUT_LOCK_FILE = 10000;
const LOCK_FILE_PATH = "/tmp/sequence";
const SEQUENCE_FILE = "/tmp/sequence";
/**
* timeout for lock file, we will try to delete it after timeout, in case was not deleted on previous run
* you can increase this value if you have a lot of processes running on the same machine
* @type {number}
* @env TIMEOUT_LOCK_FILE
*/
const TIMEOUT_LOCK_FILE = (process.env.TIMEOUT_LOCK_FILE ? ~~process.env.TIMEOUT_LOCK_FILE : undefined) || 10000;
/**
* set delay in ms
* @param ms
* @returns Promise<void>
*/
const delay = (ms) => new Promise(resolve => setTimeout(resolve, ms));
/**
* create a lock file
* @param path
* @param timeout
* @returns Promise<number>
*/
const lockFile = async (path, timeout = 0) => {
//if the file exists for long time have to delete it
// because probably something is broken on some node..
//TODO if the file exists for long time have to delete it
// because probably something is broken on previous run
// this works but may be not the best solution
if (timeout++ > TIMEOUT_LOCK_FILE) {
console.log(timeout, " times loop, try to unlock");
......@@ -18,11 +35,18 @@ const lockFile = async (path, timeout = 0) => {
return fs.openSync(lockPath, fs.constants.O_CREAT | fs.constants.O_EXCL | fs.constants.O_RDWR);
}
catch (error) {
if (error.code !== "EEXIST")
console.log(error.message);
const err = error;
if (err.code !== "EEXIST")
console.log(err.message);
return lockFile(path, timeout);
}
};
/**
* unlink the lock file
* @param path
* @param timeout
* @returns Promise<void>
*/
const unlockFile = async (path, timeout = 0) => {
const lockPath = `${path}.lock`;
if (timeout++ > TIMEOUT_LOCK_FILE) {
......@@ -34,22 +58,24 @@ const unlockFile = async (path, timeout = 0) => {
return fs.unlinkSync(lockPath);
}
catch (error) {
if (error.code === "ENOENT") {
const err = error;
if (err.code === "ENOENT") {
return;
}
else {
console.log(error.message);
console.log(err.message);
await delay(500);
return unlockFile(path, timeout++);
}
}
};
/**
*
* update the sequence number on giveb sequence file
* @param seqfile
* @param lockfile
* @returns number
*/
const sequence = async (seqfile = "", lockfile = "") => {
const sequence = (seqfile = "", lockfile = "") => {
let SEQF = SEQUENCE_FILE;
let LFP = LOCK_FILE_PATH;
if (seqfile !== "")
......
import protocol from './lib/protocol'
import sequence from './lib/sequence';
import db from './lib/db';
import { AuditRecord } from './interfaces/index';
import { AuditRecord,FS_ERROR } from './interfaces/index';
import FileEngine from './lib/FileEngine';
/**
......@@ -11,13 +11,14 @@ import FileEngine from './lib/FileEngine';
* @comment you can provide a custom audit record, or use the generated one
* @param auditInit - The audit record to be stored, if is empty a new one will be generated
* @param storagePath - The path where the audit record will be stored, default is the tmp directory
* @returns AuditRecord | null | undefined
* @env env.HOSTIP - useful to pass the IP address of the end user automatically on docker enviroments
* @returns AuditRecord | null - The audit record generated or the one provided
*/
export const generateAuditRecord = async (auditInit:AuditRecord | undefined,storagePath:string="/tmp"):Promise<AuditRecord | null | undefined> => {
export const generateAuditRecord = (auditInit: AuditRecord | undefined, storagePath: string = "/tmp"): AuditRecord | null => {
const auditUnit = auditInit?.auditUnit || "gov.gr";
const auditTransactionId = auditInit?.auditTransactionId || ""+await sequence();
const auditProtocol = auditInit?.auditProtocol || await protocol()
const auditTransactionDate = auditInit?.auditTransactionDate || new Date().toISOString().split('.')[0]+"Z"
const auditTransactionId = auditInit?.auditTransactionId || "" + sequence(storagePath + "/sequence", storagePath + "/sequence");
const auditProtocol = auditInit?.auditProtocol || protocol(storagePath);
const auditTransactionDate = auditInit?.auditTransactionDate || new Date().toISOString().split('.')[0] + "Z"
const auditUserIp = auditInit?.auditUserIp || process.env.HOSTIP || "127.0.0.1";
const auditUserId = auditInit?.auditUserId || "system";
const auditRecord = {
......@@ -29,8 +30,14 @@ export const generateAuditRecord = async (auditInit:AuditRecord | undefined,sto
auditUserId
}
const dbEngine = new db(new FileEngine(storagePath));
return dbEngine.put(auditRecord);
try {
return dbEngine.put(auditRecord);
} catch (error) {
const err = error as FS_ERROR;
console.log(err.code,err.message);
return null;
}
}
export default generateAuditRecord;
......
......@@ -16,11 +16,14 @@ export type AuditRecord = {
* @note This interface is used to define the methods that must be implemented by the AuditEngine look at FileEngine.ts for an example
* @interface AuditEngine
*/
export interface AuditEngine {
put: (record:AuditRecord) => AuditRecord | null;
get: (auditTransactionId:string) => AuditRecord | null;
export interface AuditEngine {
put: (record: AuditRecord) => AuditRecord;
get: (auditTransactionId: string) => AuditRecord;
}
/**
* @description FileSystem errors
*/
export type FS_ERROR = {
code: string,
message: string
......
//Use File System as DB storage
import fs from 'fs';
import { AuditRecord,AuditEngine } from '../interfaces/index';
import { AuditRecord, AuditEngine } from '../interfaces/index';
/**
* @description AuditEngine implementation
......@@ -10,30 +10,43 @@ import { AuditRecord,AuditEngine } from '../interfaces/index';
* @param {string} path - path to store the records
*/
export class FileEngine implements AuditEngine {
#path: string
constructor(path: string) {
this.#path = path;
}
#path: string
constructor(path: string) {
this.#path = path;
}
put(record:AuditRecord){
const data = JSON.stringify(record,null,2);
try {
fs.writeFileSync(this.#path+'/record-'+record.auditTransactionId+'.json', data);
return record;
} catch (error) {
return null;
}
}
get(auditTransactionId:string){
try {
const data = fs.readFileSync(this.#path+'/record-'+auditTransactionId+'.json', 'utf8');
return JSON.parse(data);
} catch (error) {
return null;
}
/**
* @description Store a record in the database
* @param {AuditRecord} record - record to be stored
* @returns {AuditRecord} - the record stored
* @memberof FileEngine
* @method put
*/
put(record: AuditRecord): AuditRecord {
const data = JSON.stringify(record, null, 2);
try {
fs.writeFileSync(this.#path + '/record-' + record.auditTransactionId + '.json', data);
return record;
} catch (error) {
throw error;
}
}
/**
* @description Get a record from the database
* @param auditTransactionId
* @returns {AuditRecord}
* @memberof FileEngine
* @method get
*/
get(auditTransactionId: string): AuditRecord {
try {
const data = fs.readFileSync(this.#path + '/record-' + auditTransactionId + '.json', 'utf8');
return JSON.parse(data);
} catch (error) {
throw error;
}
}
}
......
import { AuditRecord,AuditEngine } from '../interfaces/index';
import { AuditRecord, AuditEngine } from '../interfaces/index';
/**
* @description AuditEngine implementation
......@@ -8,15 +8,30 @@ import { AuditRecord,AuditEngine } from '../interfaces/index';
*/
export class db {
engine: AuditEngine;
constructor(engine: AuditEngine){
constructor(engine: AuditEngine) {
this.engine = engine;
}
put(record:AuditRecord){
if (!record.auditTransactionId) return;
/**
* @description Store a record in the database
* @param record
* @returns AuditRecord
* @memberof db
* @method put
*/
put(record: AuditRecord): AuditRecord {
if (!record.auditTransactionId) throw new Error("record.auditTransactionId is required");
return this.engine.put(record);
}
get(auditTransactionId:string){
if (!auditTransactionId) return null;
/**
* @description Get a record from the database
* @param auditTransactionId
* @returns AuditRecord
* @memberof db
* @method get
*/
get(auditTransactionId: string): AuditRecord {
if (!auditTransactionId) throw new Error("auditTransactionId is required");
return this.engine.get(auditTransactionId);
}
}
......
import sequence from './sequence';
/**
*
* @param protocol_number
* Generate a new protocol number
* in the format sequence/date
* each day a new sequence is generated
* @param protocol_path
* @returns string
*/
const protocol = async (protocol_string:string="",protocol_path:string="/tmp") =>{
const protocol_date = new Date().toISOString().split('T')[0];
const protocol_sequence = await sequence(protocol_path+"/"+protocol_date+".protocol.sequence");//Protocol starts from 1 each day.
let pn = protocol_sequence+"/"+protocol_date;
if (protocol_string!=="") pn = protocol_string;
return pn;
const protocol = (protocol_path: string) => {
const protocol_date = new Date().toISOString().split('T')[0];
const path = `${protocol_path}/${protocol_date}.protocol.sequence`;
const protocol_sequence = sequence(path, path);//Protocol starts from 1 each day.
let pn = protocol_sequence + "/" + protocol_date;
return pn;
}
export default protocol;
import fs from 'fs';
import {FS_ERROR} from '../interfaces/index';
import { FS_ERROR } from '../interfaces/index';
const LOCK_FILE_PATH = "/tmp/sequence"
const SEQUENCE_FILE = "/tmp/sequence"
const LOCK_FILE_PATH=process.env.LOCK_FILE_PATH || "/tmp/audit/sequence"
const SEQUENCE_FILE = process.env.SEQUENCE_FILE || "/tmp/audit/sequence"
const TIMEOUT_LOCK_FILE = 10000;
/**
* timeout for lock file, we will try to delete it after timeout, in case was not deleted on previous run
* you can increase this value if you have a lot of processes running on the same machine
* @type {number}
* @env TIMEOUT_LOCK_FILE
*/
const TIMEOUT_LOCK_FILE: number = (process.env.TIMEOUT_LOCK_FILE ? ~~process.env.TIMEOUT_LOCK_FILE : undefined) || 10000;
const delay = (ms:number) => new Promise(resolve => setTimeout(resolve, ms))
/**
* set delay in ms
* @param ms
* @returns Promise<void>
*/
const delay = (ms: number) => new Promise(resolve => setTimeout(resolve, ms))
const lockFile = async (path:string,timeout:number=0):Promise<number> => {
//if the file exists for long time have to delete it
// because probably something is broken on some node..
/**
* create a lock file
* @param path
* @param timeout
* @returns Promise<number>
*/
const lockFile = async (path: string, timeout: number = 0): Promise<number> => {
//TODO if the file exists for long time have to delete it
// because probably something is broken on previous run
// this works but may be not the best solution
if (timeout++>TIMEOUT_LOCK_FILE) {console.log(timeout," times loop, try to unlock");await delay(1000);unlockFile(path)};
if (timeout++ > TIMEOUT_LOCK_FILE) { console.log(timeout, " times loop, try to unlock"); await delay(1000); unlockFile(path) };
const lockPath = `${path}.lock`
try {
return fs.openSync(lockPath, fs.constants.O_CREAT | fs.constants.O_EXCL | fs.constants.O_RDWR)
return fs.openSync(lockPath, fs.constants.O_CREAT | fs.constants.O_EXCL | fs.constants.O_RDWR)
} catch (error) {
if ((error as FS_ERROR).code!=="EEXIST") console.log((error as FS_ERROR).message);
return lockFile(path,timeout)
const err: FS_ERROR = error as FS_ERROR;
if (err.code !== "EEXIST") console.log(err.message);
return lockFile(path, timeout)
}
}
const unlockFile = async (path:string,timeout:number=0):Promise<void> => {
/**
* unlink the lock file
* @param path
* @param timeout
* @returns Promise<void>
*/
const unlockFile = async (path: string, timeout: number = 0): Promise<void> => {
const lockPath = `${path}.lock`
if (timeout++>TIMEOUT_LOCK_FILE) {console.log(timeout," times loop, unlock exit"); return};
try {
return fs.unlinkSync(lockPath)
if (timeout++ > TIMEOUT_LOCK_FILE) { console.log(timeout, " times loop, unlock exit"); return };
try {
return fs.unlinkSync(lockPath)
}
catch(error) {
if ((error as FS_ERROR).code==="ENOENT") {
return;
} else {
console.log((error as FS_ERROR).message);
await delay(500);
return unlockFile(path,timeout++);
catch (error) {
const err: FS_ERROR = error as FS_ERROR;
if (err.code === "ENOENT") {
return;
} else {
console.log(err.message);