diff --git a/.gitignore b/.gitignore index 9c8222e..d7d306e 100644 --- a/.gitignore +++ b/.gitignore @@ -46,3 +46,6 @@ output/ **/metrics/ benchmarks.tar.gz metrics.tar.gz + + +.idea \ No newline at end of file diff --git a/assets/handlers/node/.gitignore b/assets/handlers/node/.gitignore new file mode 100644 index 0000000..6d2198e --- /dev/null +++ b/assets/handlers/node/.gitignore @@ -0,0 +1,132 @@ +### Node template +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +lerna-debug.log* +.pnpm-debug.log* + +# Diagnostic reports (https://nodejs.org/api/report.html) +report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage +*.lcov + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ +jspm_packages/ + +# Snowpack dependency directory (https://snowpack.dev/) +web_modules/ + +# TypeScript cache +*.tsbuildinfo + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional stylelint cache +.stylelintcache + +# Microbundle cache +.rpt2_cache/ +.rts2_cache_cjs/ +.rts2_cache_es/ +.rts2_cache_umd/ + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variable files +.env +.env.development.local +.env.test.local +.env.production.local +.env.local + +# parcel-bundler cache (https://parceljs.org/) +.cache +.parcel-cache + +# Next.js build output +.next +out + +# Nuxt.js build / generate output +.nuxt +dist + +# Gatsby files +.cache/ +# Comment in the public line in if your project uses Gatsby and not Next.js +# https://nextjs.org/blog/next-9-1#public-directory-support +# public + +# vuepress build output +.vuepress/dist + +# vuepress v2.x temp and cache directory +.temp +.cache + +# Docusaurus cache and generated files +.docusaurus + +# Serverless directories +.serverless/ + +# FuseBox cache +.fusebox/ + +# DynamoDB Local files +.dynamodb/ + +# TernJS port file +.tern-port + +# Stores VSCode versions used for testing VSCode extensions +.vscode-test + +# yarn v2 +.yarn/cache +.yarn/unplugged +.yarn/build-state.yml +.yarn/install-state.gz +.pnp.* + diff --git a/assets/handlers/node/hash/definition.yml b/assets/handlers/node/hash/definition.yml new file mode 100644 index 0000000..7e8e068 --- /dev/null +++ b/assets/handlers/node/hash/definition.yml @@ -0,0 +1,11 @@ +import_path: hash +description: "Hashes the given password using the Argon2 hash function." +is_async: true +returns: true +signature: + function: hashPassword + parameters: + - arg: 0 + type: string + minLength: 6 + maxLength: 48 diff --git a/assets/handlers/node/hash/package.json b/assets/handlers/node/hash/package.json new file mode 100644 index 0000000..933a2c6 --- /dev/null +++ b/assets/handlers/node/hash/package.json @@ -0,0 +1,10 @@ +{ + "name": "hash", + "version": "1.0.0", + "description": "Simple function to hash a given password.", + "type": "module", + "main": "src/index.js", + "dependencies": { + "argon2": "^0.41.1" + } +} diff --git a/assets/handlers/node/hash/src/index.js b/assets/handlers/node/hash/src/index.js new file mode 100644 index 0000000..453db19 --- /dev/null +++ b/assets/handlers/node/hash/src/index.js @@ -0,0 +1,18 @@ +import { hash as argon2 } from 'argon2'; + +const options = { + timeCost: 2, + memoryCost: 6144, + saltLength: 16, +}; + +/** + * + * @param password {string} + * @returns {Promise<{hash: string}>} + */ +export async function hashPassword(password) { + return { + hash: await argon2(password, options), + }; +} diff --git a/assets/handlers/node/hash/src/index.test.js b/assets/handlers/node/hash/src/index.test.js new file mode 100644 index 0000000..5757909 --- /dev/null +++ b/assets/handlers/node/hash/src/index.test.js @@ -0,0 +1,12 @@ +import { describe, it } from 'node:test'; +import assert from 'node:assert/strict'; +import { hashPassword } from './index.js'; + +describe('hashPassword', () => { + it('should hash password', async () => { + const result = await hashPassword('s3cr3t'); + + assert.ok(result.hash); + assert.ok(typeof result.hash === 'string'); + }); +}); diff --git a/assets/handlers/node/hash/utilization.yml b/assets/handlers/node/hash/utilization.yml new file mode 100644 index 0000000..14df9b9 --- /dev/null +++ b/assets/handlers/node/hash/utilization.yml @@ -0,0 +1,6 @@ +CPU: 204.16257570163398 +DISK_READ: 0.0 +DISK_WRITE: 0.0 +MEMORY: 117.12701934384017 +NETWORK_RECEIVE: 0.029158250848014346 +NETWORK_TRANSMIT: 0.039155974253793996 diff --git a/assets/handlers/node/invoice-create/definition.yml b/assets/handlers/node/invoice-create/definition.yml new file mode 100644 index 0000000..38686d0 --- /dev/null +++ b/assets/handlers/node/invoice-create/definition.yml @@ -0,0 +1,146 @@ +import_path: invoice-create +description: "Inserts a given invoice into a MongoDB collection." +is_async: true +returns: true +depends_on: + - name: db:mongo + init: seed-invoice +signature: + function: createInvoice + parameters: + - arg: 0 + type: object + properties: + items: + type: array + items: + type: object + properties: + item: + type: object + properties: + price_in_cents: + type: integer + minimum: 0 + maximum: 1000000 + exclusiveMaximum: false + exclusiveMinimum: true + name: + type: string + minLength: 1 + maxLength: 128 + additionalProperties: false + required: + - price_in_cents + - name + quantity: + type: integer + minimum: 0 + maximum: 10000 + exclusiveMinimum: true + additionalProperties: false + required: + - item + - quantity + billing_address: + type: object + properties: + first_name: + type: string + minLength: 2 + maxLength: 64 + last_name: + type: string + minLength: 2 + maxLength: 64 + street: + type: string + minLength: 2 + maxLength: 128 + number: + type: integer + minimum: 0 + maximum: 10000 + exclusiveMinimum: true + zip_code: + type: integer + minimum: 1000 + maximum: 99999 + city: + type: string + minLength: 3 + maxLength: 64 + country: + type: string + minLength: 3 + maxLength: 64 + additionalProperties: false + required: + - first_name + - last_name + - street + - number + - zip_code + - city + - country + shipping_address: + type: object + properties: + first_name: + type: string + minLength: 2 + maxLength: 64 + last_name: + type: string + minLength: 2 + maxLength: 64 + street: + type: string + minLength: 2 + maxLength: 128 + number: + type: integer + minimum: 0 + maximum: 10000 + exclusiveMinimum: true + zip_code: + type: integer + minimum: 1000 + maximum: 99999 + city: + type: string + minLength: 3 + maxLength: 64 + country: + type: string + minLength: 3 + maxLength: 64 + additionalProperties: false + required: + - first_name + - last_name + - street + - number + - zip_code + - city + - country + user_id: + type: string + minLength: 10 + maxLength: 24 + extra_info: + type: string + minLength: 0 + maxLength: 512 + invoice_number: + type: string + minLength: 10 + maxLength: 13 + additionalProperties: false + required: + - items + - billing_address + - shipping_address + - user_id + - extra_info + - invoice_number diff --git a/assets/handlers/node/invoice-create/package.json b/assets/handlers/node/invoice-create/package.json new file mode 100644 index 0000000..9fd3ecc --- /dev/null +++ b/assets/handlers/node/invoice-create/package.json @@ -0,0 +1,11 @@ +{ + "name": "invoice-create", + "version": "1.0.0", + "description": "Simple CRUD operations for an invoice entity", + "type": "module", + "main": "src/index.js", + "dependencies": { + "joi": "^17.13.3", + "mongodb": "^6.12.0" + } +} diff --git a/assets/handlers/node/invoice-create/src/db.js b/assets/handlers/node/invoice-create/src/db.js new file mode 100644 index 0000000..737b0dd --- /dev/null +++ b/assets/handlers/node/invoice-create/src/db.js @@ -0,0 +1,15 @@ +import { MongoClient } from 'mongodb'; + +const HOST = process.env['DB_MONGO_HOST']; +const PORT = process.env['DB_MONGO_PORT'] ?? ''; +const USER = process.env['DB_MONGO_USER']; +const PASSWORD = process.env['DB_MONGO_PASSWORD']; + +const url = (USER && PASSWORD) ? `mongodb://${USER}:${PASSWORD}@${HOST}:${PORT}`: `mongodb://${HOST}:${PORT}`; + +const client = new MongoClient(url); + +await client.connect(); + +export const invoiceDb = client.db('invoice_db'); +export const invoiceCollection = invoiceDb.collection('invoice_collection'); diff --git a/assets/handlers/node/invoice-create/src/index.js b/assets/handlers/node/invoice-create/src/index.js new file mode 100644 index 0000000..a6d9403 --- /dev/null +++ b/assets/handlers/node/invoice-create/src/index.js @@ -0,0 +1,15 @@ +import { validate } from './validate.js'; +import { invoiceCollection } from './db.js'; + +/** + * + * @param buffer {Buffer} + * @returns {Promise} + */ +export async function createInvoice(buffer) { + const invoice = await validate(buffer); + + const insertResult = await invoiceCollection.insertOne(invoice); + + return insertResult.insertedId.toString(); +} diff --git a/assets/handlers/node/invoice-create/src/schemas.js b/assets/handlers/node/invoice-create/src/schemas.js new file mode 100644 index 0000000..a3caab0 --- /dev/null +++ b/assets/handlers/node/invoice-create/src/schemas.js @@ -0,0 +1,33 @@ +import Joi from 'joi'; + +export const addressSchema = Joi.object({ + first_name: Joi.string().min(2).max(64), + last_name: Joi.string().min(2).max(64), + street: Joi.string().min(2).max(128), + number: Joi.number().min(0).max(10000), + zip_code: Joi.number().min(1000).max(99999), + city: Joi.string().min(3).max(64), + country: Joi.string().min(3).max(64), +}); + +export const itemSchema = Joi.object({ + price_in_cents: Joi.number().min(0).max(1000000), + name: Joi.string().min(1).max(128), +}); + +export const orderItemSchema = Joi.object({ + quantity: Joi.number().min(0).max(10000), + item: itemSchema, +}); + +export const invoiceSchema = Joi.object({ + items: Joi.array().items(orderItemSchema), + billing_address: addressSchema, + shipping_address: addressSchema, + user_id: Joi.string().min(10).max(24), + tax_rate: Joi.number().default(0.15), + issued_at: Joi.number().default(Date.now), + extra_info: Joi.string().min(0).max(512).default(''), + status: Joi.string().valid('OPEN', 'PAID').default('OPEN'), + invoice_number: Joi.string().min(10).max(13), +}); \ No newline at end of file diff --git a/assets/handlers/node/invoice-create/src/validate.js b/assets/handlers/node/invoice-create/src/validate.js new file mode 100644 index 0000000..7ac18b1 --- /dev/null +++ b/assets/handlers/node/invoice-create/src/validate.js @@ -0,0 +1,12 @@ +import { invoiceSchema } from './schemas.js'; + +/** + * + * @param buffer {Buffer} + * @returns {Promise} + */ +export async function validate(buffer) { + const json = JSON.parse(buffer.toString('utf-8')); + + return await invoiceSchema.validateAsync(json); +} diff --git a/assets/handlers/node/invoice-create/utilization.yml b/assets/handlers/node/invoice-create/utilization.yml new file mode 100644 index 0000000..5a7b960 --- /dev/null +++ b/assets/handlers/node/invoice-create/utilization.yml @@ -0,0 +1,6 @@ +CPU: 15.082415893050742 +DISK_READ: 0.0008624054734862993 +DISK_WRITE: 0.0 +MEMORY: 84.97563721494635 +NETWORK_RECEIVE: 0.14977651307083154 +NETWORK_TRANSMIT: 0.16517880923786696 diff --git a/assets/handlers/node/invoice-delete/definition.yml b/assets/handlers/node/invoice-delete/definition.yml new file mode 100644 index 0000000..6e8fe15 --- /dev/null +++ b/assets/handlers/node/invoice-delete/definition.yml @@ -0,0 +1,14 @@ +import_path: invoice-delete +description: "Deletes an invoice from a MongoDB collection by its ID." +is_async: true +returns: true +depends_on: + - name: db:mongo + init: seed-invoice +signature: + function: deleteInvoiceById + parameters: + - arg: 0 + type: integer + minimum: 1 + maximum: 300000 \ No newline at end of file diff --git a/assets/handlers/node/invoice-delete/package.json b/assets/handlers/node/invoice-delete/package.json new file mode 100644 index 0000000..7dd14b6 --- /dev/null +++ b/assets/handlers/node/invoice-delete/package.json @@ -0,0 +1,10 @@ +{ + "name": "invoice-delete", + "version": "1.0.0", + "description": "Simple CRUD operations for an invoice entity", + "type": "module", + "main": "src/index.js", + "dependencies": { + "mongodb": "^6.12.0" + } +} diff --git a/assets/handlers/node/invoice-delete/src/db.js b/assets/handlers/node/invoice-delete/src/db.js new file mode 100644 index 0000000..273bbad --- /dev/null +++ b/assets/handlers/node/invoice-delete/src/db.js @@ -0,0 +1,15 @@ +import { MongoClient } from 'mongodb'; + +const HOST = process.env['DB_MONGO_HOST']; +const PORT = process.env['DB_MONGO_PORT'] ?? ''; +const USER = process.env['DB_MONGO_USER']; +const PASSWORD = process.env['DB_MONGO_PASSWORD']; + +const url = `mongodb://${USER}:${PASSWORD}@${HOST}:${PORT}`; + +const client = new MongoClient(url); + +await client.connect(); + +export const invoiceDb = client.db('invoice_db'); +export const invoiceCollection = invoiceDb.collection('invoice_collection'); diff --git a/assets/handlers/node/invoice-delete/src/index.js b/assets/handlers/node/invoice-delete/src/index.js new file mode 100644 index 0000000..56a4843 --- /dev/null +++ b/assets/handlers/node/invoice-delete/src/index.js @@ -0,0 +1,15 @@ +import { invoiceCollection } from './db.js'; +import { ObjectId } from 'mongodb'; + +/** + * + * @param id {number} + * @returns {Promise} + */ +export async function deleteInvoiceById(id) { + const insertResult = await invoiceCollection.deleteOne({ + _id: new ObjectId(id), + }); + + return insertResult.acknowledged; +} diff --git a/assets/handlers/node/invoice-delete/utilization.yml b/assets/handlers/node/invoice-delete/utilization.yml new file mode 100644 index 0000000..76d1278 --- /dev/null +++ b/assets/handlers/node/invoice-delete/utilization.yml @@ -0,0 +1,6 @@ +CPU: 12.642826135133461 +DISK_READ: 0.0007481057532308572 +DISK_WRITE: 0.0 +MEMORY: 80.273317381872 +NETWORK_RECEIVE: 0.03665479428554671 +NETWORK_TRANSMIT: 0.05117888905880078 diff --git a/assets/handlers/node/invoice-read/definition.yml b/assets/handlers/node/invoice-read/definition.yml new file mode 100644 index 0000000..5b70d2f --- /dev/null +++ b/assets/handlers/node/invoice-read/definition.yml @@ -0,0 +1,14 @@ +import_path: invoice-read +description: "Reads an invoice from a MongoDB collection by its ID." +is_async: true +returns: true +depends_on: + - name: db:mongo + init: seed-invoice +signature: + function: readInvoiceById + parameters: + - arg: 0 + type: integer + minimum: 1 + maximum: 300000 diff --git a/assets/handlers/node/invoice-read/package.json b/assets/handlers/node/invoice-read/package.json new file mode 100644 index 0000000..1970fa3 --- /dev/null +++ b/assets/handlers/node/invoice-read/package.json @@ -0,0 +1,9 @@ +{ + "name": "invoice-read", + "version": "1.0.0", + "main": "src/index.js", + "type": "module", + "dependencies": { + "mongodb": "^6.12.0" + } +} diff --git a/assets/handlers/node/invoice-read/src/db.js b/assets/handlers/node/invoice-read/src/db.js new file mode 100644 index 0000000..273bbad --- /dev/null +++ b/assets/handlers/node/invoice-read/src/db.js @@ -0,0 +1,15 @@ +import { MongoClient } from 'mongodb'; + +const HOST = process.env['DB_MONGO_HOST']; +const PORT = process.env['DB_MONGO_PORT'] ?? ''; +const USER = process.env['DB_MONGO_USER']; +const PASSWORD = process.env['DB_MONGO_PASSWORD']; + +const url = `mongodb://${USER}:${PASSWORD}@${HOST}:${PORT}`; + +const client = new MongoClient(url); + +await client.connect(); + +export const invoiceDb = client.db('invoice_db'); +export const invoiceCollection = invoiceDb.collection('invoice_collection'); diff --git a/assets/handlers/node/invoice-read/src/index.js b/assets/handlers/node/invoice-read/src/index.js new file mode 100644 index 0000000..87926a2 --- /dev/null +++ b/assets/handlers/node/invoice-read/src/index.js @@ -0,0 +1,20 @@ +import { invoiceCollection } from './db.js'; +import { ObjectId } from 'mongodb'; + +/** + * + * @param id {number} + * @returns {Promise | undefined>} + */ +export async function readInvoiceById(id) { + const invoice = await invoiceCollection.findOne({ _id: new ObjectId(id) }); + + if (!invoice) { + return undefined; + } + + invoice['id'] = invoice._id.toString(); + delete invoice._id; + + return invoice; +} diff --git a/assets/handlers/node/invoice-read/utilization.yml b/assets/handlers/node/invoice-read/utilization.yml new file mode 100644 index 0000000..f0df446 --- /dev/null +++ b/assets/handlers/node/invoice-read/utilization.yml @@ -0,0 +1,6 @@ +CPU: 12.301743396247437 +DISK_READ: 0.0005757646211631786 +DISK_WRITE: 0.0 +MEMORY: 84.15569241879393 +NETWORK_RECEIVE: 0.0425536021007974 +NETWORK_TRANSMIT: 0.04755387871306454 diff --git a/assets/handlers/node/invoice-update/definition.yml b/assets/handlers/node/invoice-update/definition.yml new file mode 100644 index 0000000..655406f --- /dev/null +++ b/assets/handlers/node/invoice-update/definition.yml @@ -0,0 +1,136 @@ +import_path: invoice-update +description: "Updates the invoice with the given ID." +is_async: true +returns: true +depends_on: + - name: db:mongo + init: seed-invoice +signature: + function: updateInvoice + parameters: + - arg: 0 + name: id + type: integer + minimum: 1 + maximum: 300000 + - arg: 1 + type: object + properties: + items: + type: array + items: + type: object + properties: + item: + type: object + properties: + price_in_cents: + type: integer + minimum: 0 + maximum: 1000000 + exclusiveMaximum: false + exclusiveMinimum: true + name: + type: string + minLength: 1 + maxLength: 128 + additionalProperties: false + required: + - price_in_cents + - name + quantity: + type: integer + minimum: 0 + maximum: 10000 + exclusiveMinimum: true + additionalProperties: false + required: + - item + - quantity + billing_address: + type: object + properties: + first_name: + type: string + minLength: 2 + maxLength: 64 + last_name: + type: string + minLength: 2 + maxLength: 64 + street: + type: string + minLength: 2 + maxLength: 128 + number: + type: integer + maximum: 10000 + minimum: 0 + exclusiveMinimum: true + zip_code: + type: integer + minimum: 1000 + maximum: 99999 + city: + type: string + minLength: 3 + maxLength: 64 + country: + type: string + minLength: 3 + maxLength: 64 + additionalProperties: false + required: + - first_name + - last_name + - street + - number + - zip_code + - city + - country + shipping_address: + type: object + properties: + first_name: + type: string + minLength: 2 + maxLength: 64 + last_name: + type: string + minLength: 2 + maxLength: 64 + street: + type: string + minLength: 2 + maxLength: 128 + number: + type: integer + minimum: 0 + maximum: 10000 + exclusiveMinimum: true + zip_code: + type: integer + minimum: 1000 + maximum: 99999 + city: + type: string + minLength: 3 + maxLength: 64 + country: + type: string + minLength: 3 + maxLength: 64 + additionalProperties: false + required: + - first_name + - last_name + - street + - number + - zip_code + - city + - country + extra_info: + type: string + minLength: 0 + maxLength: 512 + additionalProperties: false diff --git a/assets/handlers/node/invoice-update/package.json b/assets/handlers/node/invoice-update/package.json new file mode 100644 index 0000000..533acb3 --- /dev/null +++ b/assets/handlers/node/invoice-update/package.json @@ -0,0 +1,10 @@ +{ + "name": "invoice-update", + "version": "1.0.0", + "main": "src/index.js", + "type": "module", + "dependencies": { + "joi": "^17.13.3", + "mongodb": "^6.12.0" + } +} diff --git a/assets/handlers/node/invoice-update/src/db.js b/assets/handlers/node/invoice-update/src/db.js new file mode 100644 index 0000000..273bbad --- /dev/null +++ b/assets/handlers/node/invoice-update/src/db.js @@ -0,0 +1,15 @@ +import { MongoClient } from 'mongodb'; + +const HOST = process.env['DB_MONGO_HOST']; +const PORT = process.env['DB_MONGO_PORT'] ?? ''; +const USER = process.env['DB_MONGO_USER']; +const PASSWORD = process.env['DB_MONGO_PASSWORD']; + +const url = `mongodb://${USER}:${PASSWORD}@${HOST}:${PORT}`; + +const client = new MongoClient(url); + +await client.connect(); + +export const invoiceDb = client.db('invoice_db'); +export const invoiceCollection = invoiceDb.collection('invoice_collection'); diff --git a/assets/handlers/node/invoice-update/src/index.js b/assets/handlers/node/invoice-update/src/index.js new file mode 100644 index 0000000..ea91d5d --- /dev/null +++ b/assets/handlers/node/invoice-update/src/index.js @@ -0,0 +1,26 @@ +import { invoiceCollection } from './db.js'; +import { ObjectId } from 'mongodb'; +import { validate } from './validate.js'; + +/** + * + * @param id {number} + * @param buffer {Buffer} + * @returns {Promise} + */ +export async function updateInvoice(id, buffer) { + const invoice = await validate(buffer); + + if (invoice) { + const updateResult = await invoiceCollection.updateOne( + { _id: id }, + { + $set: invoice, + }, + ); + + return updateResult.acknowledged; + } + + return true; +} diff --git a/assets/handlers/node/invoice-update/src/schemas.js b/assets/handlers/node/invoice-update/src/schemas.js new file mode 100644 index 0000000..bf76cae --- /dev/null +++ b/assets/handlers/node/invoice-update/src/schemas.js @@ -0,0 +1,32 @@ +import Joi from 'joi'; + +export const addressSchema = Joi.object({ + first_name: Joi.string().min(2).max(64), + last_name: Joi.string().min(2).max(64), + street: Joi.string().min(2).max(128), + number: Joi.number().min(0).max(10000), + zip_code: Joi.number().min(1000).max(99999), + city: Joi.string().min(3).max(64), + country: Joi.string().min(3).max(64), +}); + +export const itemSchema = Joi.object({ + price_in_cents: Joi.number().min(0).max(1000000), + name: Joi.string().min(1).max(128), +}); + +export const orderItemSchema = Joi.object({ + quantity: Joi.number().min(0).max(10000), + item: itemSchema, +}); + +export const invoiceSchema = Joi.object({ + items: Joi.array().items(orderItemSchema).optional(), + billing_address: addressSchema.optional(), + shipping_address: addressSchema.optional(), + user_id: Joi.string().optional(), + tax_rate: Joi.number().optional(), + issued_at: Joi.number().optional(), + extra_info: Joi.string().optional(), + status: Joi.string().valid('OPEN', 'PAID').optional(), +}); diff --git a/assets/handlers/node/invoice-update/src/validate.js b/assets/handlers/node/invoice-update/src/validate.js new file mode 100644 index 0000000..7ac18b1 --- /dev/null +++ b/assets/handlers/node/invoice-update/src/validate.js @@ -0,0 +1,12 @@ +import { invoiceSchema } from './schemas.js'; + +/** + * + * @param buffer {Buffer} + * @returns {Promise} + */ +export async function validate(buffer) { + const json = JSON.parse(buffer.toString('utf-8')); + + return await invoiceSchema.validateAsync(json); +} diff --git a/assets/handlers/node/invoice-update/utilization.yml b/assets/handlers/node/invoice-update/utilization.yml new file mode 100644 index 0000000..0b7cba4 --- /dev/null +++ b/assets/handlers/node/invoice-update/utilization.yml @@ -0,0 +1,6 @@ +CPU: 15.192559750848067 +DISK_READ: 0.0007227402348843366 +DISK_WRITE: 0.0 +MEMORY: 89.38015163240841 +NETWORK_RECEIVE: 0.1456171419368423 +NETWORK_TRANSMIT: 0.15350952287804862 diff --git a/assets/handlers/node/login/definition.yml b/assets/handlers/node/login/definition.yml new file mode 100644 index 0000000..3d9251c --- /dev/null +++ b/assets/handlers/node/login/definition.yml @@ -0,0 +1,18 @@ +import_path: login +description: "Login a user using username/email and password authentication." +is_async: true +returns: true +depends_on: + - name: db:mongo + init: seed-login +signature: + function: loginWithUsernameOrEmail + parameters: + - arg: 0 + type: string + minLength: 3 + maxLength: 64 + - arg: 1 + type: string + minLength: 6 + maxLength: 48 diff --git a/assets/handlers/node/login/package.json b/assets/handlers/node/login/package.json new file mode 100644 index 0000000..07b7095 --- /dev/null +++ b/assets/handlers/node/login/package.json @@ -0,0 +1,12 @@ +{ + "name": "login", + "version": "1.0.0", + "main": "src/index.js", + "type": "module", + "dependencies": { + "argon2": "^0.41.1", + "joi": "^17.13.3", + "mongodb": "^6.12.0", + "uid-safe": "^2.1.5" + } +} diff --git a/assets/handlers/node/login/src/db.js b/assets/handlers/node/login/src/db.js new file mode 100644 index 0000000..5dd40f5 --- /dev/null +++ b/assets/handlers/node/login/src/db.js @@ -0,0 +1,20 @@ +import { MongoClient } from 'mongodb'; + +const HOST = process.env['DB_MONGO_HOST']; +const PORT = process.env['DB_MONGO_PORT'] ?? ''; +const USER = process.env['DB_MONGO_USER']; +const PASSWORD = process.env['DB_MONGO_PASSWORD']; + +const url = `mongodb://${USER}:${PASSWORD}@${HOST}:${PORT}`; + +const client = new MongoClient(url); + +await client.connect(); + +export const userDb = client.db('login_db'); +export const userCollection = userDb.collection('login_collection'); + +await Promise.all([ + userCollection.createIndex('username'), + userCollection.createIndex('email'), +]); diff --git a/assets/handlers/node/login/src/index.js b/assets/handlers/node/login/src/index.js new file mode 100644 index 0000000..7d66df2 --- /dev/null +++ b/assets/handlers/node/login/src/index.js @@ -0,0 +1,23 @@ +import { readUserByEmail, readUserByUsername } from './read-user.js'; +import { verify } from 'argon2'; +import { createSession } from './session.js'; + +const emailRegex = /(^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$)/; + +/** + * + * @param usernameOrEmail {string} + * @param password {string} + * @returns {Promise | undefined>} + */ +export async function loginWithUsernameOrEmail(usernameOrEmail, password) { + const user = emailRegex.test(usernameOrEmail) + ? await readUserByEmail(usernameOrEmail) + : await readUserByUsername(usernameOrEmail); + + if (user && (await verify(user.password, password))) { + return await createSession(user.id); + } + + return undefined; +} diff --git a/assets/handlers/node/login/src/read-user.js b/assets/handlers/node/login/src/read-user.js new file mode 100644 index 0000000..ad1b11b --- /dev/null +++ b/assets/handlers/node/login/src/read-user.js @@ -0,0 +1,38 @@ +import { userCollection } from './db.js'; + +/** + * + * @param key {string} + * @param value {string} + * @returns {Promise} + */ +async function readUserByKey(key, value) { + const user = await userCollection.findOne({ [key]: value }); + + if (!user) { + return undefined; + } + + user['id'] = user._id.toString(); + delete user._id; + + return user; +} + +/** + * + * @param name {string} + * @returns {Promise} + */ +export async function readUserByUsername(name) { + return readUserByKey('username', name); +} + +/** + * + * @param email {string} + * @returns {Promise} + */ +export async function readUserByEmail(email) { + return readUserByKey('email', email); +} diff --git a/assets/handlers/node/login/src/schemas.js b/assets/handlers/node/login/src/schemas.js new file mode 100644 index 0000000..ace3f6e --- /dev/null +++ b/assets/handlers/node/login/src/schemas.js @@ -0,0 +1,12 @@ +import Joi from 'joi'; + +export const sessionSchema = Joi.object({ + user_id: Joi.string(), + session_id: Joi.string(), + exp: Joi.number().default(() => Date.now() + 259200000), // == 3 days +}); + +export const sessionResponseSchema = Joi.object({ + token: Joi.string(), + exp: Joi.number(), +}); diff --git a/assets/handlers/node/login/src/session.js b/assets/handlers/node/login/src/session.js new file mode 100644 index 0000000..a3395ea --- /dev/null +++ b/assets/handlers/node/login/src/session.js @@ -0,0 +1,24 @@ +import uid from 'uid-safe'; +import { sessionResponseSchema, sessionSchema } from './schemas.js'; + +const sessions = new Map(); + +/** + * + * @param userId {string} + */ +export async function createSession(userId) { + const sessionId = await uid(24); + + const sessionData = await sessionSchema.validateAsync({ + user_id: userId, + session_id: sessionId, + }); + + sessions.set(sessionId, sessionData); + + return await sessionResponseSchema.validateAsync({ + token: sessionId, + exp: sessionData.exp, + }); +} diff --git a/assets/handlers/node/login/utilization.yml b/assets/handlers/node/login/utilization.yml new file mode 100644 index 0000000..2f72df0 --- /dev/null +++ b/assets/handlers/node/login/utilization.yml @@ -0,0 +1,6 @@ +CPU: 12.405964339912877 +DISK_READ: 0.0 +DISK_WRITE: 0.0 +MEMORY: 82.73041125386608 +NETWORK_RECEIVE: 0.049281129194878 +NETWORK_TRANSMIT: 0.05167488953160789 diff --git a/assets/handlers/node/matrix/definition.yml b/assets/handlers/node/matrix/definition.yml new file mode 100644 index 0000000..1bc5636 --- /dev/null +++ b/assets/handlers/node/matrix/definition.yml @@ -0,0 +1,13 @@ +import_path: matrix +description: "Simple function to invert a random matrix of a given size." +is_async: false +returns: true +signature: + function: invertRandomMatrix + parameters: + - arg: 0 + type: integer + minimum: 1 + exclusiveMinimum: false + maximum: 50 + exclusiveMaximum: false diff --git a/assets/handlers/node/matrix/package.json b/assets/handlers/node/matrix/package.json new file mode 100644 index 0000000..b866cac --- /dev/null +++ b/assets/handlers/node/matrix/package.json @@ -0,0 +1,9 @@ +{ + "name": "matrix", + "version": "1.0.0", + "main": "src/index.js", + "type": "module", + "dependencies": { + "mathjs": "^14.1.0" + } +} diff --git a/assets/handlers/node/matrix/src/index.js b/assets/handlers/node/matrix/src/index.js new file mode 100644 index 0000000..c1b5c3b --- /dev/null +++ b/assets/handlers/node/matrix/src/index.js @@ -0,0 +1,22 @@ +import { random, inv, matrix } from 'mathjs'; + +/** + * + * @param size {number} + * @returns {Array>} + */ +export function invertRandomMatrix(size) { + if (size < 1) { + throw new Error( + `Matrix size must be greater or equal to "1", but was ${size}.`, + ); + } + + const randomMatrix = matrix( + Array.from({ length: size }, () => + Array.from({ length: size }, () => random()), + ), + ); + + return inv(randomMatrix).toArray(); +} diff --git a/assets/handlers/node/matrix/utilization.yml b/assets/handlers/node/matrix/utilization.yml new file mode 100644 index 0000000..e8e2e27 --- /dev/null +++ b/assets/handlers/node/matrix/utilization.yml @@ -0,0 +1,6 @@ +CPU: 46.180361887603596 +DISK_READ: 0.0 +DISK_WRITE: 0.0 +MEMORY: 113.17919180288568 +NETWORK_RECEIVE: 0.03902960174991232 +NETWORK_TRANSMIT: 1.3803058782004187 diff --git a/assets/handlers/node/package-lock.json b/assets/handlers/node/package-lock.json new file mode 100644 index 0000000..cae1b37 --- /dev/null +++ b/assets/handlers/node/package-lock.json @@ -0,0 +1,2089 @@ +{ + "name": "node", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "node", + "version": "1.0.0", + "workspaces": [ + "primes", + "user-delete", + "user-update", + "invoice-read", + "invoice-create", + "hash", + "user-read", + "register", + "user-create", + "matrix", + "login", + "rng", + "invoice-update", + "invoice-delete" + ], + "devDependencies": { + "prettier": "^3.4.2" + } + }, + "hash": { + "version": "1.0.0", + "dependencies": { + "argon2": "^0.41.1" + } + }, + "hash/node_modules/@phc/format": { + "version": "1.0.0", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "hash/node_modules/argon2": { + "version": "0.41.1", + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "@phc/format": "^1.0.0", + "node-addon-api": "^8.1.0", + "node-gyp-build": "^4.8.1" + }, + "engines": { + "node": ">=16.17.0" + } + }, + "hash/node_modules/node-addon-api": { + "version": "8.3.0", + "license": "MIT", + "engines": { + "node": "^18 || ^20 || >= 21" + } + }, + "hash/node_modules/node-gyp-build": { + "version": "4.8.4", + "license": "MIT", + "bin": { + "node-gyp-build": "bin.js", + "node-gyp-build-optional": "optional.js", + "node-gyp-build-test": "build-test.js" + } + }, + "invoice-create": { + "version": "1.0.0", + "dependencies": { + "joi": "^17.13.3", + "mongodb": "^6.12.0" + } + }, + "invoice-create/node_modules/@hapi/hoek": { + "version": "9.3.0", + "license": "BSD-3-Clause" + }, + "invoice-create/node_modules/@hapi/topo": { + "version": "5.1.0", + "license": "BSD-3-Clause", + "dependencies": { + "@hapi/hoek": "^9.0.0" + } + }, + "invoice-create/node_modules/@mongodb-js/saslprep": { + "version": "1.1.9", + "license": "MIT", + "dependencies": { + "sparse-bitfield": "^3.0.3" + } + }, + "invoice-create/node_modules/@sideway/address": { + "version": "4.1.5", + "license": "BSD-3-Clause", + "dependencies": { + "@hapi/hoek": "^9.0.0" + } + }, + "invoice-create/node_modules/@sideway/formula": { + "version": "3.0.1", + "license": "BSD-3-Clause" + }, + "invoice-create/node_modules/@sideway/pinpoint": { + "version": "2.0.0", + "license": "BSD-3-Clause" + }, + "invoice-create/node_modules/@types/webidl-conversions": { + "version": "7.0.3", + "license": "MIT" + }, + "invoice-create/node_modules/@types/whatwg-url": { + "version": "11.0.5", + "license": "MIT", + "dependencies": { + "@types/webidl-conversions": "*" + } + }, + "invoice-create/node_modules/bson": { + "version": "6.10.1", + "license": "Apache-2.0", + "engines": { + "node": ">=16.20.1" + } + }, + "invoice-create/node_modules/joi": { + "version": "17.13.3", + "license": "BSD-3-Clause", + "dependencies": { + "@hapi/hoek": "^9.3.0", + "@hapi/topo": "^5.1.0", + "@sideway/address": "^4.1.5", + "@sideway/formula": "^3.0.1", + "@sideway/pinpoint": "^2.0.0" + } + }, + "invoice-create/node_modules/memory-pager": { + "version": "1.5.0", + "license": "MIT" + }, + "invoice-create/node_modules/mongodb": { + "version": "6.12.0", + "license": "Apache-2.0", + "dependencies": { + "@mongodb-js/saslprep": "^1.1.9", + "bson": "^6.10.1", + "mongodb-connection-string-url": "^3.0.0" + }, + "engines": { + "node": ">=16.20.1" + }, + "peerDependencies": { + "@aws-sdk/credential-providers": "^3.188.0", + "@mongodb-js/zstd": "^1.1.0 || ^2.0.0", + "gcp-metadata": "^5.2.0", + "kerberos": "^2.0.1", + "mongodb-client-encryption": ">=6.0.0 <7", + "snappy": "^7.2.2", + "socks": "^2.7.1" + }, + "peerDependenciesMeta": { + "@aws-sdk/credential-providers": { + "optional": true + }, + "@mongodb-js/zstd": { + "optional": true + }, + "gcp-metadata": { + "optional": true + }, + "kerberos": { + "optional": true + }, + "mongodb-client-encryption": { + "optional": true + }, + "snappy": { + "optional": true + }, + "socks": { + "optional": true + } + } + }, + "invoice-create/node_modules/mongodb-connection-string-url": { + "version": "3.0.2", + "license": "Apache-2.0", + "dependencies": { + "@types/whatwg-url": "^11.0.2", + "whatwg-url": "^14.1.0 || ^13.0.0" + } + }, + "invoice-create/node_modules/punycode": { + "version": "2.3.1", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "invoice-create/node_modules/sparse-bitfield": { + "version": "3.0.3", + "license": "MIT", + "dependencies": { + "memory-pager": "^1.0.2" + } + }, + "invoice-create/node_modules/tr46": { + "version": "5.0.0", + "license": "MIT", + "dependencies": { + "punycode": "^2.3.1" + }, + "engines": { + "node": ">=18" + } + }, + "invoice-create/node_modules/webidl-conversions": { + "version": "7.0.0", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + } + }, + "invoice-create/node_modules/whatwg-url": { + "version": "14.1.0", + "license": "MIT", + "dependencies": { + "tr46": "^5.0.0", + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "invoice-delete": { + "version": "1.0.0", + "dependencies": { + "mongodb": "^6.12.0" + } + }, + "invoice-delete/node_modules/@mongodb-js/saslprep": { + "version": "1.1.9", + "license": "MIT", + "dependencies": { + "sparse-bitfield": "^3.0.3" + } + }, + "invoice-delete/node_modules/@types/webidl-conversions": { + "version": "7.0.3", + "license": "MIT" + }, + "invoice-delete/node_modules/@types/whatwg-url": { + "version": "11.0.5", + "license": "MIT", + "dependencies": { + "@types/webidl-conversions": "*" + } + }, + "invoice-delete/node_modules/bson": { + "version": "6.10.1", + "license": "Apache-2.0", + "engines": { + "node": ">=16.20.1" + } + }, + "invoice-delete/node_modules/memory-pager": { + "version": "1.5.0", + "license": "MIT" + }, + "invoice-delete/node_modules/mongodb": { + "version": "6.12.0", + "license": "Apache-2.0", + "dependencies": { + "@mongodb-js/saslprep": "^1.1.9", + "bson": "^6.10.1", + "mongodb-connection-string-url": "^3.0.0" + }, + "engines": { + "node": ">=16.20.1" + }, + "peerDependencies": { + "@aws-sdk/credential-providers": "^3.188.0", + "@mongodb-js/zstd": "^1.1.0 || ^2.0.0", + "gcp-metadata": "^5.2.0", + "kerberos": "^2.0.1", + "mongodb-client-encryption": ">=6.0.0 <7", + "snappy": "^7.2.2", + "socks": "^2.7.1" + }, + "peerDependenciesMeta": { + "@aws-sdk/credential-providers": { + "optional": true + }, + "@mongodb-js/zstd": { + "optional": true + }, + "gcp-metadata": { + "optional": true + }, + "kerberos": { + "optional": true + }, + "mongodb-client-encryption": { + "optional": true + }, + "snappy": { + "optional": true + }, + "socks": { + "optional": true + } + } + }, + "invoice-delete/node_modules/mongodb-connection-string-url": { + "version": "3.0.2", + "license": "Apache-2.0", + "dependencies": { + "@types/whatwg-url": "^11.0.2", + "whatwg-url": "^14.1.0 || ^13.0.0" + } + }, + "invoice-delete/node_modules/punycode": { + "version": "2.3.1", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "invoice-delete/node_modules/sparse-bitfield": { + "version": "3.0.3", + "license": "MIT", + "dependencies": { + "memory-pager": "^1.0.2" + } + }, + "invoice-delete/node_modules/tr46": { + "version": "5.0.0", + "license": "MIT", + "dependencies": { + "punycode": "^2.3.1" + }, + "engines": { + "node": ">=18" + } + }, + "invoice-delete/node_modules/webidl-conversions": { + "version": "7.0.0", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + } + }, + "invoice-delete/node_modules/whatwg-url": { + "version": "14.1.0", + "license": "MIT", + "dependencies": { + "tr46": "^5.0.0", + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "invoice-read": { + "version": "1.0.0", + "dependencies": { + "mongodb": "^6.12.0" + } + }, + "invoice-read/node_modules/@mongodb-js/saslprep": { + "version": "1.1.9", + "license": "MIT", + "dependencies": { + "sparse-bitfield": "^3.0.3" + } + }, + "invoice-read/node_modules/@types/webidl-conversions": { + "version": "7.0.3", + "license": "MIT" + }, + "invoice-read/node_modules/@types/whatwg-url": { + "version": "11.0.5", + "license": "MIT", + "dependencies": { + "@types/webidl-conversions": "*" + } + }, + "invoice-read/node_modules/bson": { + "version": "6.10.1", + "license": "Apache-2.0", + "engines": { + "node": ">=16.20.1" + } + }, + "invoice-read/node_modules/memory-pager": { + "version": "1.5.0", + "license": "MIT" + }, + "invoice-read/node_modules/mongodb": { + "version": "6.12.0", + "license": "Apache-2.0", + "dependencies": { + "@mongodb-js/saslprep": "^1.1.9", + "bson": "^6.10.1", + "mongodb-connection-string-url": "^3.0.0" + }, + "engines": { + "node": ">=16.20.1" + }, + "peerDependencies": { + "@aws-sdk/credential-providers": "^3.188.0", + "@mongodb-js/zstd": "^1.1.0 || ^2.0.0", + "gcp-metadata": "^5.2.0", + "kerberos": "^2.0.1", + "mongodb-client-encryption": ">=6.0.0 <7", + "snappy": "^7.2.2", + "socks": "^2.7.1" + }, + "peerDependenciesMeta": { + "@aws-sdk/credential-providers": { + "optional": true + }, + "@mongodb-js/zstd": { + "optional": true + }, + "gcp-metadata": { + "optional": true + }, + "kerberos": { + "optional": true + }, + "mongodb-client-encryption": { + "optional": true + }, + "snappy": { + "optional": true + }, + "socks": { + "optional": true + } + } + }, + "invoice-read/node_modules/mongodb-connection-string-url": { + "version": "3.0.2", + "license": "Apache-2.0", + "dependencies": { + "@types/whatwg-url": "^11.0.2", + "whatwg-url": "^14.1.0 || ^13.0.0" + } + }, + "invoice-read/node_modules/punycode": { + "version": "2.3.1", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "invoice-read/node_modules/sparse-bitfield": { + "version": "3.0.3", + "license": "MIT", + "dependencies": { + "memory-pager": "^1.0.2" + } + }, + "invoice-read/node_modules/tr46": { + "version": "5.0.0", + "license": "MIT", + "dependencies": { + "punycode": "^2.3.1" + }, + "engines": { + "node": ">=18" + } + }, + "invoice-read/node_modules/webidl-conversions": { + "version": "7.0.0", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + } + }, + "invoice-read/node_modules/whatwg-url": { + "version": "14.1.0", + "license": "MIT", + "dependencies": { + "tr46": "^5.0.0", + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "invoice-update": { + "version": "1.0.0", + "dependencies": { + "joi": "^17.13.3", + "mongodb": "^6.12.0" + } + }, + "invoice-update/node_modules/@hapi/hoek": { + "version": "9.3.0", + "license": "BSD-3-Clause" + }, + "invoice-update/node_modules/@hapi/topo": { + "version": "5.1.0", + "license": "BSD-3-Clause", + "dependencies": { + "@hapi/hoek": "^9.0.0" + } + }, + "invoice-update/node_modules/@mongodb-js/saslprep": { + "version": "1.1.9", + "license": "MIT", + "dependencies": { + "sparse-bitfield": "^3.0.3" + } + }, + "invoice-update/node_modules/@sideway/address": { + "version": "4.1.5", + "license": "BSD-3-Clause", + "dependencies": { + "@hapi/hoek": "^9.0.0" + } + }, + "invoice-update/node_modules/@sideway/formula": { + "version": "3.0.1", + "license": "BSD-3-Clause" + }, + "invoice-update/node_modules/@sideway/pinpoint": { + "version": "2.0.0", + "license": "BSD-3-Clause" + }, + "invoice-update/node_modules/@types/webidl-conversions": { + "version": "7.0.3", + "license": "MIT" + }, + "invoice-update/node_modules/@types/whatwg-url": { + "version": "11.0.5", + "license": "MIT", + "dependencies": { + "@types/webidl-conversions": "*" + } + }, + "invoice-update/node_modules/bson": { + "version": "6.10.1", + "license": "Apache-2.0", + "engines": { + "node": ">=16.20.1" + } + }, + "invoice-update/node_modules/joi": { + "version": "17.13.3", + "license": "BSD-3-Clause", + "dependencies": { + "@hapi/hoek": "^9.3.0", + "@hapi/topo": "^5.1.0", + "@sideway/address": "^4.1.5", + "@sideway/formula": "^3.0.1", + "@sideway/pinpoint": "^2.0.0" + } + }, + "invoice-update/node_modules/memory-pager": { + "version": "1.5.0", + "license": "MIT" + }, + "invoice-update/node_modules/mongodb": { + "version": "6.12.0", + "license": "Apache-2.0", + "dependencies": { + "@mongodb-js/saslprep": "^1.1.9", + "bson": "^6.10.1", + "mongodb-connection-string-url": "^3.0.0" + }, + "engines": { + "node": ">=16.20.1" + }, + "peerDependencies": { + "@aws-sdk/credential-providers": "^3.188.0", + "@mongodb-js/zstd": "^1.1.0 || ^2.0.0", + "gcp-metadata": "^5.2.0", + "kerberos": "^2.0.1", + "mongodb-client-encryption": ">=6.0.0 <7", + "snappy": "^7.2.2", + "socks": "^2.7.1" + }, + "peerDependenciesMeta": { + "@aws-sdk/credential-providers": { + "optional": true + }, + "@mongodb-js/zstd": { + "optional": true + }, + "gcp-metadata": { + "optional": true + }, + "kerberos": { + "optional": true + }, + "mongodb-client-encryption": { + "optional": true + }, + "snappy": { + "optional": true + }, + "socks": { + "optional": true + } + } + }, + "invoice-update/node_modules/mongodb-connection-string-url": { + "version": "3.0.2", + "license": "Apache-2.0", + "dependencies": { + "@types/whatwg-url": "^11.0.2", + "whatwg-url": "^14.1.0 || ^13.0.0" + } + }, + "invoice-update/node_modules/punycode": { + "version": "2.3.1", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "invoice-update/node_modules/sparse-bitfield": { + "version": "3.0.3", + "license": "MIT", + "dependencies": { + "memory-pager": "^1.0.2" + } + }, + "invoice-update/node_modules/tr46": { + "version": "5.0.0", + "license": "MIT", + "dependencies": { + "punycode": "^2.3.1" + }, + "engines": { + "node": ">=18" + } + }, + "invoice-update/node_modules/webidl-conversions": { + "version": "7.0.0", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + } + }, + "invoice-update/node_modules/whatwg-url": { + "version": "14.1.0", + "license": "MIT", + "dependencies": { + "tr46": "^5.0.0", + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "login": { + "version": "1.0.0", + "dependencies": { + "argon2": "^0.41.1", + "joi": "^17.13.3", + "mongodb": "^6.12.0", + "uid-safe": "^2.1.5" + } + }, + "login/node_modules/@hapi/hoek": { + "version": "9.3.0", + "license": "BSD-3-Clause" + }, + "login/node_modules/@hapi/topo": { + "version": "5.1.0", + "license": "BSD-3-Clause", + "dependencies": { + "@hapi/hoek": "^9.0.0" + } + }, + "login/node_modules/@mongodb-js/saslprep": { + "version": "1.1.9", + "license": "MIT", + "dependencies": { + "sparse-bitfield": "^3.0.3" + } + }, + "login/node_modules/@phc/format": { + "version": "1.0.0", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "login/node_modules/@sideway/address": { + "version": "4.1.5", + "license": "BSD-3-Clause", + "dependencies": { + "@hapi/hoek": "^9.0.0" + } + }, + "login/node_modules/@sideway/formula": { + "version": "3.0.1", + "license": "BSD-3-Clause" + }, + "login/node_modules/@sideway/pinpoint": { + "version": "2.0.0", + "license": "BSD-3-Clause" + }, + "login/node_modules/@types/webidl-conversions": { + "version": "7.0.3", + "license": "MIT" + }, + "login/node_modules/@types/whatwg-url": { + "version": "11.0.5", + "license": "MIT", + "dependencies": { + "@types/webidl-conversions": "*" + } + }, + "login/node_modules/argon2": { + "version": "0.41.1", + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "@phc/format": "^1.0.0", + "node-addon-api": "^8.1.0", + "node-gyp-build": "^4.8.1" + }, + "engines": { + "node": ">=16.17.0" + } + }, + "login/node_modules/bson": { + "version": "6.10.1", + "license": "Apache-2.0", + "engines": { + "node": ">=16.20.1" + } + }, + "login/node_modules/joi": { + "version": "17.13.3", + "license": "BSD-3-Clause", + "dependencies": { + "@hapi/hoek": "^9.3.0", + "@hapi/topo": "^5.1.0", + "@sideway/address": "^4.1.5", + "@sideway/formula": "^3.0.1", + "@sideway/pinpoint": "^2.0.0" + } + }, + "login/node_modules/memory-pager": { + "version": "1.5.0", + "license": "MIT" + }, + "login/node_modules/mongodb": { + "version": "6.12.0", + "license": "Apache-2.0", + "dependencies": { + "@mongodb-js/saslprep": "^1.1.9", + "bson": "^6.10.1", + "mongodb-connection-string-url": "^3.0.0" + }, + "engines": { + "node": ">=16.20.1" + }, + "peerDependencies": { + "@aws-sdk/credential-providers": "^3.188.0", + "@mongodb-js/zstd": "^1.1.0 || ^2.0.0", + "gcp-metadata": "^5.2.0", + "kerberos": "^2.0.1", + "mongodb-client-encryption": ">=6.0.0 <7", + "snappy": "^7.2.2", + "socks": "^2.7.1" + }, + "peerDependenciesMeta": { + "@aws-sdk/credential-providers": { + "optional": true + }, + "@mongodb-js/zstd": { + "optional": true + }, + "gcp-metadata": { + "optional": true + }, + "kerberos": { + "optional": true + }, + "mongodb-client-encryption": { + "optional": true + }, + "snappy": { + "optional": true + }, + "socks": { + "optional": true + } + } + }, + "login/node_modules/mongodb-connection-string-url": { + "version": "3.0.2", + "license": "Apache-2.0", + "dependencies": { + "@types/whatwg-url": "^11.0.2", + "whatwg-url": "^14.1.0 || ^13.0.0" + } + }, + "login/node_modules/node-addon-api": { + "version": "8.3.0", + "license": "MIT", + "engines": { + "node": "^18 || ^20 || >= 21" + } + }, + "login/node_modules/node-gyp-build": { + "version": "4.8.4", + "license": "MIT", + "bin": { + "node-gyp-build": "bin.js", + "node-gyp-build-optional": "optional.js", + "node-gyp-build-test": "build-test.js" + } + }, + "login/node_modules/punycode": { + "version": "2.3.1", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "login/node_modules/random-bytes": { + "version": "1.0.0", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "login/node_modules/sparse-bitfield": { + "version": "3.0.3", + "license": "MIT", + "dependencies": { + "memory-pager": "^1.0.2" + } + }, + "login/node_modules/tr46": { + "version": "5.0.0", + "license": "MIT", + "dependencies": { + "punycode": "^2.3.1" + }, + "engines": { + "node": ">=18" + } + }, + "login/node_modules/uid-safe": { + "version": "2.1.5", + "license": "MIT", + "dependencies": { + "random-bytes": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "login/node_modules/webidl-conversions": { + "version": "7.0.0", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + } + }, + "login/node_modules/whatwg-url": { + "version": "14.1.0", + "license": "MIT", + "dependencies": { + "tr46": "^5.0.0", + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "matrix": { + "version": "1.0.0", + "dependencies": { + "mathjs": "^14.1.0" + } + }, + "matrix/node_modules/@babel/runtime": { + "version": "7.26.0", + "license": "MIT", + "dependencies": { + "regenerator-runtime": "^0.14.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "matrix/node_modules/@lambdatest/node-tunnel": { + "version": "4.0.8", + "license": "ISC", + "dependencies": { + "adm-zip": "^0.5.10", + "axios": "^1.6.2", + "get-port": "^1.0.0", + "https-proxy-agent": "^5.0.0", + "split": "^1.0.1" + } + }, + "matrix/node_modules/adm-zip": { + "version": "0.5.16", + "license": "MIT", + "engines": { + "node": ">=12.0" + } + }, + "matrix/node_modules/agent-base": { + "version": "6.0.2", + "license": "MIT", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "matrix/node_modules/asynckit": { + "version": "0.4.0", + "license": "MIT" + }, + "matrix/node_modules/axios": { + "version": "1.7.9", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, + "matrix/node_modules/combined-stream": { + "version": "1.0.8", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "matrix/node_modules/complex.js": { + "version": "2.4.2", + "license": "MIT", + "engines": { + "node": "*" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/rawify" + } + }, + "matrix/node_modules/debug": { + "version": "4.4.0", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "matrix/node_modules/decimal.js": { + "version": "10.5.0", + "license": "MIT" + }, + "matrix/node_modules/delayed-stream": { + "version": "1.0.0", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "matrix/node_modules/escape-latex": { + "version": "1.2.0", + "license": "MIT" + }, + "matrix/node_modules/follow-redirects": { + "version": "1.15.9", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "matrix/node_modules/form-data": { + "version": "4.0.1", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "matrix/node_modules/fraction.js": { + "version": "5.2.1", + "license": "MIT", + "engines": { + "node": ">= 12" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/rawify" + } + }, + "matrix/node_modules/get-port": { + "version": "1.0.0", + "license": "MIT", + "bin": { + "get-port": "cli.js" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "matrix/node_modules/https-proxy-agent": { + "version": "5.0.1", + "license": "MIT", + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "matrix/node_modules/javascript-natural-sort": { + "version": "0.7.1", + "license": "MIT" + }, + "matrix/node_modules/mathjs": { + "version": "14.1.0", + "license": "Apache-2.0", + "dependencies": { + "@babel/runtime": "^7.25.7", + "@lambdatest/node-tunnel": "^4.0.8", + "complex.js": "^2.2.5", + "decimal.js": "^10.4.3", + "escape-latex": "^1.2.0", + "fraction.js": "^5.2.1", + "javascript-natural-sort": "^0.7.1", + "seedrandom": "^3.0.5", + "tiny-emitter": "^2.1.0", + "typed-function": "^4.2.1" + }, + "bin": { + "mathjs": "bin/cli.js" + }, + "engines": { + "node": ">= 18" + } + }, + "matrix/node_modules/mime-db": { + "version": "1.52.0", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "matrix/node_modules/mime-types": { + "version": "2.1.35", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "matrix/node_modules/ms": { + "version": "2.1.3", + "license": "MIT" + }, + "matrix/node_modules/proxy-from-env": { + "version": "1.1.0", + "license": "MIT" + }, + "matrix/node_modules/regenerator-runtime": { + "version": "0.14.1", + "license": "MIT" + }, + "matrix/node_modules/seedrandom": { + "version": "3.0.5", + "license": "MIT" + }, + "matrix/node_modules/split": { + "version": "1.0.1", + "license": "MIT", + "dependencies": { + "through": "2" + }, + "engines": { + "node": "*" + } + }, + "matrix/node_modules/through": { + "version": "2.3.8", + "license": "MIT" + }, + "matrix/node_modules/tiny-emitter": { + "version": "2.1.0", + "license": "MIT" + }, + "matrix/node_modules/typed-function": { + "version": "4.2.1", + "license": "MIT", + "engines": { + "node": ">= 18" + } + }, + "node_modules/hash": { + "resolved": "hash", + "link": true + }, + "node_modules/invoice-create": { + "resolved": "invoice-create", + "link": true + }, + "node_modules/invoice-delete": { + "resolved": "invoice-delete", + "link": true + }, + "node_modules/invoice-read": { + "resolved": "invoice-read", + "link": true + }, + "node_modules/invoice-update": { + "resolved": "invoice-update", + "link": true + }, + "node_modules/login": { + "resolved": "login", + "link": true + }, + "node_modules/matrix": { + "resolved": "matrix", + "link": true + }, + "node_modules/prettier": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.4.2.tgz", + "integrity": "sha512-e9MewbtFo+Fevyuxn/4rrcDAaq0IYxPGLvObpQjiZBMAzB9IGmzlnG9RZy3FFas+eBMu2vA0CszMeduow5dIuQ==", + "dev": true, + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/primes": { + "resolved": "primes", + "link": true + }, + "node_modules/register": { + "resolved": "register", + "link": true + }, + "node_modules/rng": { + "resolved": "rng", + "link": true + }, + "node_modules/user-create": { + "resolved": "user-create", + "link": true + }, + "node_modules/user-delete": { + "resolved": "user-delete", + "link": true + }, + "node_modules/user-read": { + "resolved": "user-read", + "link": true + }, + "node_modules/user-update": { + "resolved": "user-update", + "link": true + }, + "primes": { + "version": "1.0.0" + }, + "register": { + "version": "1.0.0", + "dependencies": { + "argon2": "^0.41.1", + "joi": "^17.13.3", + "mongodb": "^6.12.0" + } + }, + "register/node_modules/@hapi/hoek": { + "version": "9.3.0", + "license": "BSD-3-Clause" + }, + "register/node_modules/@hapi/topo": { + "version": "5.1.0", + "license": "BSD-3-Clause", + "dependencies": { + "@hapi/hoek": "^9.0.0" + } + }, + "register/node_modules/@mongodb-js/saslprep": { + "version": "1.1.9", + "license": "MIT", + "dependencies": { + "sparse-bitfield": "^3.0.3" + } + }, + "register/node_modules/@phc/format": { + "version": "1.0.0", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "register/node_modules/@sideway/address": { + "version": "4.1.5", + "license": "BSD-3-Clause", + "dependencies": { + "@hapi/hoek": "^9.0.0" + } + }, + "register/node_modules/@sideway/formula": { + "version": "3.0.1", + "license": "BSD-3-Clause" + }, + "register/node_modules/@sideway/pinpoint": { + "version": "2.0.0", + "license": "BSD-3-Clause" + }, + "register/node_modules/@types/webidl-conversions": { + "version": "7.0.3", + "license": "MIT" + }, + "register/node_modules/@types/whatwg-url": { + "version": "11.0.5", + "license": "MIT", + "dependencies": { + "@types/webidl-conversions": "*" + } + }, + "register/node_modules/argon2": { + "version": "0.41.1", + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "@phc/format": "^1.0.0", + "node-addon-api": "^8.1.0", + "node-gyp-build": "^4.8.1" + }, + "engines": { + "node": ">=16.17.0" + } + }, + "register/node_modules/bson": { + "version": "6.10.1", + "license": "Apache-2.0", + "engines": { + "node": ">=16.20.1" + } + }, + "register/node_modules/joi": { + "version": "17.13.3", + "license": "BSD-3-Clause", + "dependencies": { + "@hapi/hoek": "^9.3.0", + "@hapi/topo": "^5.1.0", + "@sideway/address": "^4.1.5", + "@sideway/formula": "^3.0.1", + "@sideway/pinpoint": "^2.0.0" + } + }, + "register/node_modules/memory-pager": { + "version": "1.5.0", + "license": "MIT" + }, + "register/node_modules/mongodb": { + "version": "6.12.0", + "license": "Apache-2.0", + "dependencies": { + "@mongodb-js/saslprep": "^1.1.9", + "bson": "^6.10.1", + "mongodb-connection-string-url": "^3.0.0" + }, + "engines": { + "node": ">=16.20.1" + }, + "peerDependencies": { + "@aws-sdk/credential-providers": "^3.188.0", + "@mongodb-js/zstd": "^1.1.0 || ^2.0.0", + "gcp-metadata": "^5.2.0", + "kerberos": "^2.0.1", + "mongodb-client-encryption": ">=6.0.0 <7", + "snappy": "^7.2.2", + "socks": "^2.7.1" + }, + "peerDependenciesMeta": { + "@aws-sdk/credential-providers": { + "optional": true + }, + "@mongodb-js/zstd": { + "optional": true + }, + "gcp-metadata": { + "optional": true + }, + "kerberos": { + "optional": true + }, + "mongodb-client-encryption": { + "optional": true + }, + "snappy": { + "optional": true + }, + "socks": { + "optional": true + } + } + }, + "register/node_modules/mongodb-connection-string-url": { + "version": "3.0.2", + "license": "Apache-2.0", + "dependencies": { + "@types/whatwg-url": "^11.0.2", + "whatwg-url": "^14.1.0 || ^13.0.0" + } + }, + "register/node_modules/node-addon-api": { + "version": "8.3.0", + "license": "MIT", + "engines": { + "node": "^18 || ^20 || >= 21" + } + }, + "register/node_modules/node-gyp-build": { + "version": "4.8.4", + "license": "MIT", + "bin": { + "node-gyp-build": "bin.js", + "node-gyp-build-optional": "optional.js", + "node-gyp-build-test": "build-test.js" + } + }, + "register/node_modules/punycode": { + "version": "2.3.1", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "register/node_modules/sparse-bitfield": { + "version": "3.0.3", + "license": "MIT", + "dependencies": { + "memory-pager": "^1.0.2" + } + }, + "register/node_modules/tr46": { + "version": "5.0.0", + "license": "MIT", + "dependencies": { + "punycode": "^2.3.1" + }, + "engines": { + "node": ">=18" + } + }, + "register/node_modules/webidl-conversions": { + "version": "7.0.0", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + } + }, + "register/node_modules/whatwg-url": { + "version": "14.1.0", + "license": "MIT", + "dependencies": { + "tr46": "^5.0.0", + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "rng": { + "version": "1.0.0" + }, + "user-create": { + "version": "1.0.0", + "dependencies": { + "argon2": "^0.41.1", + "joi": "^17.13.3", + "mongodb": "^6.12.0" + } + }, + "user-create/node_modules/@hapi/hoek": { + "version": "9.3.0", + "license": "BSD-3-Clause" + }, + "user-create/node_modules/@hapi/topo": { + "version": "5.1.0", + "license": "BSD-3-Clause", + "dependencies": { + "@hapi/hoek": "^9.0.0" + } + }, + "user-create/node_modules/@mongodb-js/saslprep": { + "version": "1.1.9", + "license": "MIT", + "dependencies": { + "sparse-bitfield": "^3.0.3" + } + }, + "user-create/node_modules/@phc/format": { + "version": "1.0.0", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "user-create/node_modules/@sideway/address": { + "version": "4.1.5", + "license": "BSD-3-Clause", + "dependencies": { + "@hapi/hoek": "^9.0.0" + } + }, + "user-create/node_modules/@sideway/formula": { + "version": "3.0.1", + "license": "BSD-3-Clause" + }, + "user-create/node_modules/@sideway/pinpoint": { + "version": "2.0.0", + "license": "BSD-3-Clause" + }, + "user-create/node_modules/@types/webidl-conversions": { + "version": "7.0.3", + "license": "MIT" + }, + "user-create/node_modules/@types/whatwg-url": { + "version": "11.0.5", + "license": "MIT", + "dependencies": { + "@types/webidl-conversions": "*" + } + }, + "user-create/node_modules/argon2": { + "version": "0.41.1", + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "@phc/format": "^1.0.0", + "node-addon-api": "^8.1.0", + "node-gyp-build": "^4.8.1" + }, + "engines": { + "node": ">=16.17.0" + } + }, + "user-create/node_modules/bson": { + "version": "6.10.1", + "license": "Apache-2.0", + "engines": { + "node": ">=16.20.1" + } + }, + "user-create/node_modules/joi": { + "version": "17.13.3", + "license": "BSD-3-Clause", + "dependencies": { + "@hapi/hoek": "^9.3.0", + "@hapi/topo": "^5.1.0", + "@sideway/address": "^4.1.5", + "@sideway/formula": "^3.0.1", + "@sideway/pinpoint": "^2.0.0" + } + }, + "user-create/node_modules/memory-pager": { + "version": "1.5.0", + "license": "MIT" + }, + "user-create/node_modules/mongodb": { + "version": "6.12.0", + "license": "Apache-2.0", + "dependencies": { + "@mongodb-js/saslprep": "^1.1.9", + "bson": "^6.10.1", + "mongodb-connection-string-url": "^3.0.0" + }, + "engines": { + "node": ">=16.20.1" + }, + "peerDependencies": { + "@aws-sdk/credential-providers": "^3.188.0", + "@mongodb-js/zstd": "^1.1.0 || ^2.0.0", + "gcp-metadata": "^5.2.0", + "kerberos": "^2.0.1", + "mongodb-client-encryption": ">=6.0.0 <7", + "snappy": "^7.2.2", + "socks": "^2.7.1" + }, + "peerDependenciesMeta": { + "@aws-sdk/credential-providers": { + "optional": true + }, + "@mongodb-js/zstd": { + "optional": true + }, + "gcp-metadata": { + "optional": true + }, + "kerberos": { + "optional": true + }, + "mongodb-client-encryption": { + "optional": true + }, + "snappy": { + "optional": true + }, + "socks": { + "optional": true + } + } + }, + "user-create/node_modules/mongodb-connection-string-url": { + "version": "3.0.2", + "license": "Apache-2.0", + "dependencies": { + "@types/whatwg-url": "^11.0.2", + "whatwg-url": "^14.1.0 || ^13.0.0" + } + }, + "user-create/node_modules/node-addon-api": { + "version": "8.3.0", + "license": "MIT", + "engines": { + "node": "^18 || ^20 || >= 21" + } + }, + "user-create/node_modules/node-gyp-build": { + "version": "4.8.4", + "license": "MIT", + "bin": { + "node-gyp-build": "bin.js", + "node-gyp-build-optional": "optional.js", + "node-gyp-build-test": "build-test.js" + } + }, + "user-create/node_modules/punycode": { + "version": "2.3.1", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "user-create/node_modules/sparse-bitfield": { + "version": "3.0.3", + "license": "MIT", + "dependencies": { + "memory-pager": "^1.0.2" + } + }, + "user-create/node_modules/tr46": { + "version": "5.0.0", + "license": "MIT", + "dependencies": { + "punycode": "^2.3.1" + }, + "engines": { + "node": ">=18" + } + }, + "user-create/node_modules/webidl-conversions": { + "version": "7.0.0", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + } + }, + "user-create/node_modules/whatwg-url": { + "version": "14.1.0", + "license": "MIT", + "dependencies": { + "tr46": "^5.0.0", + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "user-delete": { + "version": "1.0.0", + "dependencies": { + "mongodb": "^6.12.0" + } + }, + "user-delete/node_modules/@mongodb-js/saslprep": { + "version": "1.1.9", + "license": "MIT", + "dependencies": { + "sparse-bitfield": "^3.0.3" + } + }, + "user-delete/node_modules/@types/webidl-conversions": { + "version": "7.0.3", + "license": "MIT" + }, + "user-delete/node_modules/@types/whatwg-url": { + "version": "11.0.5", + "license": "MIT", + "dependencies": { + "@types/webidl-conversions": "*" + } + }, + "user-delete/node_modules/bson": { + "version": "6.10.1", + "license": "Apache-2.0", + "engines": { + "node": ">=16.20.1" + } + }, + "user-delete/node_modules/memory-pager": { + "version": "1.5.0", + "license": "MIT" + }, + "user-delete/node_modules/mongodb": { + "version": "6.12.0", + "license": "Apache-2.0", + "dependencies": { + "@mongodb-js/saslprep": "^1.1.9", + "bson": "^6.10.1", + "mongodb-connection-string-url": "^3.0.0" + }, + "engines": { + "node": ">=16.20.1" + }, + "peerDependencies": { + "@aws-sdk/credential-providers": "^3.188.0", + "@mongodb-js/zstd": "^1.1.0 || ^2.0.0", + "gcp-metadata": "^5.2.0", + "kerberos": "^2.0.1", + "mongodb-client-encryption": ">=6.0.0 <7", + "snappy": "^7.2.2", + "socks": "^2.7.1" + }, + "peerDependenciesMeta": { + "@aws-sdk/credential-providers": { + "optional": true + }, + "@mongodb-js/zstd": { + "optional": true + }, + "gcp-metadata": { + "optional": true + }, + "kerberos": { + "optional": true + }, + "mongodb-client-encryption": { + "optional": true + }, + "snappy": { + "optional": true + }, + "socks": { + "optional": true + } + } + }, + "user-delete/node_modules/mongodb-connection-string-url": { + "version": "3.0.2", + "license": "Apache-2.0", + "dependencies": { + "@types/whatwg-url": "^11.0.2", + "whatwg-url": "^14.1.0 || ^13.0.0" + } + }, + "user-delete/node_modules/punycode": { + "version": "2.3.1", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "user-delete/node_modules/sparse-bitfield": { + "version": "3.0.3", + "license": "MIT", + "dependencies": { + "memory-pager": "^1.0.2" + } + }, + "user-delete/node_modules/tr46": { + "version": "5.0.0", + "license": "MIT", + "dependencies": { + "punycode": "^2.3.1" + }, + "engines": { + "node": ">=18" + } + }, + "user-delete/node_modules/webidl-conversions": { + "version": "7.0.0", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + } + }, + "user-delete/node_modules/whatwg-url": { + "version": "14.1.0", + "license": "MIT", + "dependencies": { + "tr46": "^5.0.0", + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "user-read": { + "version": "1.0.0", + "dependencies": { + "mongodb": "^6.12.0" + } + }, + "user-read/node_modules/@mongodb-js/saslprep": { + "version": "1.1.9", + "license": "MIT", + "dependencies": { + "sparse-bitfield": "^3.0.3" + } + }, + "user-read/node_modules/@types/webidl-conversions": { + "version": "7.0.3", + "license": "MIT" + }, + "user-read/node_modules/@types/whatwg-url": { + "version": "11.0.5", + "license": "MIT", + "dependencies": { + "@types/webidl-conversions": "*" + } + }, + "user-read/node_modules/bson": { + "version": "6.10.1", + "license": "Apache-2.0", + "engines": { + "node": ">=16.20.1" + } + }, + "user-read/node_modules/memory-pager": { + "version": "1.5.0", + "license": "MIT" + }, + "user-read/node_modules/mongodb": { + "version": "6.12.0", + "license": "Apache-2.0", + "dependencies": { + "@mongodb-js/saslprep": "^1.1.9", + "bson": "^6.10.1", + "mongodb-connection-string-url": "^3.0.0" + }, + "engines": { + "node": ">=16.20.1" + }, + "peerDependencies": { + "@aws-sdk/credential-providers": "^3.188.0", + "@mongodb-js/zstd": "^1.1.0 || ^2.0.0", + "gcp-metadata": "^5.2.0", + "kerberos": "^2.0.1", + "mongodb-client-encryption": ">=6.0.0 <7", + "snappy": "^7.2.2", + "socks": "^2.7.1" + }, + "peerDependenciesMeta": { + "@aws-sdk/credential-providers": { + "optional": true + }, + "@mongodb-js/zstd": { + "optional": true + }, + "gcp-metadata": { + "optional": true + }, + "kerberos": { + "optional": true + }, + "mongodb-client-encryption": { + "optional": true + }, + "snappy": { + "optional": true + }, + "socks": { + "optional": true + } + } + }, + "user-read/node_modules/mongodb-connection-string-url": { + "version": "3.0.2", + "license": "Apache-2.0", + "dependencies": { + "@types/whatwg-url": "^11.0.2", + "whatwg-url": "^14.1.0 || ^13.0.0" + } + }, + "user-read/node_modules/punycode": { + "version": "2.3.1", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "user-read/node_modules/sparse-bitfield": { + "version": "3.0.3", + "license": "MIT", + "dependencies": { + "memory-pager": "^1.0.2" + } + }, + "user-read/node_modules/tr46": { + "version": "5.0.0", + "license": "MIT", + "dependencies": { + "punycode": "^2.3.1" + }, + "engines": { + "node": ">=18" + } + }, + "user-read/node_modules/webidl-conversions": { + "version": "7.0.0", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + } + }, + "user-read/node_modules/whatwg-url": { + "version": "14.1.0", + "license": "MIT", + "dependencies": { + "tr46": "^5.0.0", + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "user-update": { + "version": "1.0.0", + "dependencies": { + "argon2": "^0.41.1", + "joi": "^17.13.3", + "mongodb": "^6.12.0" + } + }, + "user-update/node_modules/@hapi/hoek": { + "version": "9.3.0", + "license": "BSD-3-Clause" + }, + "user-update/node_modules/@hapi/topo": { + "version": "5.1.0", + "license": "BSD-3-Clause", + "dependencies": { + "@hapi/hoek": "^9.0.0" + } + }, + "user-update/node_modules/@mongodb-js/saslprep": { + "version": "1.1.9", + "license": "MIT", + "dependencies": { + "sparse-bitfield": "^3.0.3" + } + }, + "user-update/node_modules/@phc/format": { + "version": "1.0.0", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "user-update/node_modules/@sideway/address": { + "version": "4.1.5", + "license": "BSD-3-Clause", + "dependencies": { + "@hapi/hoek": "^9.0.0" + } + }, + "user-update/node_modules/@sideway/formula": { + "version": "3.0.1", + "license": "BSD-3-Clause" + }, + "user-update/node_modules/@sideway/pinpoint": { + "version": "2.0.0", + "license": "BSD-3-Clause" + }, + "user-update/node_modules/@types/webidl-conversions": { + "version": "7.0.3", + "license": "MIT" + }, + "user-update/node_modules/@types/whatwg-url": { + "version": "11.0.5", + "license": "MIT", + "dependencies": { + "@types/webidl-conversions": "*" + } + }, + "user-update/node_modules/argon2": { + "version": "0.41.1", + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "@phc/format": "^1.0.0", + "node-addon-api": "^8.1.0", + "node-gyp-build": "^4.8.1" + }, + "engines": { + "node": ">=16.17.0" + } + }, + "user-update/node_modules/bson": { + "version": "6.10.1", + "license": "Apache-2.0", + "engines": { + "node": ">=16.20.1" + } + }, + "user-update/node_modules/joi": { + "version": "17.13.3", + "license": "BSD-3-Clause", + "dependencies": { + "@hapi/hoek": "^9.3.0", + "@hapi/topo": "^5.1.0", + "@sideway/address": "^4.1.5", + "@sideway/formula": "^3.0.1", + "@sideway/pinpoint": "^2.0.0" + } + }, + "user-update/node_modules/memory-pager": { + "version": "1.5.0", + "license": "MIT" + }, + "user-update/node_modules/mongodb": { + "version": "6.12.0", + "license": "Apache-2.0", + "dependencies": { + "@mongodb-js/saslprep": "^1.1.9", + "bson": "^6.10.1", + "mongodb-connection-string-url": "^3.0.0" + }, + "engines": { + "node": ">=16.20.1" + }, + "peerDependencies": { + "@aws-sdk/credential-providers": "^3.188.0", + "@mongodb-js/zstd": "^1.1.0 || ^2.0.0", + "gcp-metadata": "^5.2.0", + "kerberos": "^2.0.1", + "mongodb-client-encryption": ">=6.0.0 <7", + "snappy": "^7.2.2", + "socks": "^2.7.1" + }, + "peerDependenciesMeta": { + "@aws-sdk/credential-providers": { + "optional": true + }, + "@mongodb-js/zstd": { + "optional": true + }, + "gcp-metadata": { + "optional": true + }, + "kerberos": { + "optional": true + }, + "mongodb-client-encryption": { + "optional": true + }, + "snappy": { + "optional": true + }, + "socks": { + "optional": true + } + } + }, + "user-update/node_modules/mongodb-connection-string-url": { + "version": "3.0.2", + "license": "Apache-2.0", + "dependencies": { + "@types/whatwg-url": "^11.0.2", + "whatwg-url": "^14.1.0 || ^13.0.0" + } + }, + "user-update/node_modules/node-addon-api": { + "version": "8.3.0", + "license": "MIT", + "engines": { + "node": "^18 || ^20 || >= 21" + } + }, + "user-update/node_modules/node-gyp-build": { + "version": "4.8.4", + "license": "MIT", + "bin": { + "node-gyp-build": "bin.js", + "node-gyp-build-optional": "optional.js", + "node-gyp-build-test": "build-test.js" + } + }, + "user-update/node_modules/punycode": { + "version": "2.3.1", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "user-update/node_modules/sparse-bitfield": { + "version": "3.0.3", + "license": "MIT", + "dependencies": { + "memory-pager": "^1.0.2" + } + }, + "user-update/node_modules/tr46": { + "version": "5.0.0", + "license": "MIT", + "dependencies": { + "punycode": "^2.3.1" + }, + "engines": { + "node": ">=18" + } + }, + "user-update/node_modules/webidl-conversions": { + "version": "7.0.0", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + } + }, + "user-update/node_modules/whatwg-url": { + "version": "14.1.0", + "license": "MIT", + "dependencies": { + "tr46": "^5.0.0", + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=18" + } + } + } +} diff --git a/assets/handlers/node/package.json b/assets/handlers/node/package.json new file mode 100644 index 0000000..915f1d7 --- /dev/null +++ b/assets/handlers/node/package.json @@ -0,0 +1,29 @@ +{ + "name": "node", + "version": "1.0.0", + "scripts": { + "format:check": "prettier --check --single-quote **/*.js", + "format:write": "prettier --write --single-quote **/*.js", + "test": "node --test **/*.test.js" + }, + "type": "module", + "workspaces": [ + "primes", + "user-delete", + "user-update", + "invoice-read", + "invoice-create", + "hash", + "user-read", + "register", + "user-create", + "matrix", + "login", + "rng", + "invoice-update", + "invoice-delete" + ], + "devDependencies": { + "prettier": "^3.4.2" + } +} diff --git a/assets/handlers/node/primes/definition.yml b/assets/handlers/node/primes/definition.yml new file mode 100644 index 0000000..54ef6ed --- /dev/null +++ b/assets/handlers/node/primes/definition.yml @@ -0,0 +1,13 @@ +import_path: primes +description: "Simple function to generate prime numbers." +is_async: false +returns: true +signature: + function: generateFirstPrimes + parameters: + - arg: 0 + type: integer + minimum: 1 + exclusiveMinimum: false + maximum: 10000 + exclusiveMaximum: false diff --git a/assets/handlers/node/primes/package.json b/assets/handlers/node/primes/package.json new file mode 100644 index 0000000..1d8c3c0 --- /dev/null +++ b/assets/handlers/node/primes/package.json @@ -0,0 +1,6 @@ +{ + "name": "primes", + "version": "1.0.0", + "main": "src/index.js", + "type": "module" +} diff --git a/assets/handlers/node/primes/src/index.js b/assets/handlers/node/primes/src/index.js new file mode 100644 index 0000000..0363a1a --- /dev/null +++ b/assets/handlers/node/primes/src/index.js @@ -0,0 +1,43 @@ +/** + * + * @returns {Generator} + */ +function* generatePrimes() { + const map = new Map(); + let q = 2; + + while (true) { + if (!map.has(q)) { + yield q; + + map.set(q * q, [q]); + } else { + for (const p of map.get(q)) { + const next = p + q; + + if (!map.has(next)) { + map.set(next, []); + } + + map.get(next).push(p); + } + map.delete(q); + } + ++q; + } +} + +/** + * + * @param n {number} + * @returns {Array} + */ +export function generateFirstPrimes(n) { + if (n < 1) { + throw new Error(`"n" must be greater or equal to "1", but was ${n}.`); + } + + const generator = generatePrimes(); + + return Array.from({ length: n }, () => generator.next().value); +} diff --git a/assets/handlers/node/primes/src/index.test.js b/assets/handlers/node/primes/src/index.test.js new file mode 100644 index 0000000..bc09d4c --- /dev/null +++ b/assets/handlers/node/primes/src/index.test.js @@ -0,0 +1,12 @@ +import { describe, it } from 'node:test'; +import assert from 'node:assert/strict'; +import { generateFirstPrimes } from './index.js'; + +describe('generateFirstPrimes', () => { + it('should generate first 10 primes', async () => { + const primes = generateFirstPrimes(10); + + assert.strictEqual(primes.length, 10); + assert.deepEqual(primes, [2, 3, 5, 7, 11, 13, 17, 19, 23, 29]); + }); +}); diff --git a/assets/handlers/node/primes/utilization.yml b/assets/handlers/node/primes/utilization.yml new file mode 100644 index 0000000..db592f8 --- /dev/null +++ b/assets/handlers/node/primes/utilization.yml @@ -0,0 +1,6 @@ +CPU: 8.665471350766387 +DISK_READ: 0.0 +DISK_WRITE: 0.0 +MEMORY: 69.64151308501665 +NETWORK_RECEIVE: 0.027168345675468063 +NETWORK_TRANSMIT: 0.04441660842791251 diff --git a/assets/handlers/node/register/definition.yml b/assets/handlers/node/register/definition.yml new file mode 100644 index 0000000..35dda89 --- /dev/null +++ b/assets/handlers/node/register/definition.yml @@ -0,0 +1,30 @@ +import_path: register +description: "Register a user using a username, email and password." +is_async: true +returns: true +depends_on: + - name: db:mongo + init: seed-register +signature: + function: registerUser + parameters: + - arg: 0 + type: object + properties: + username: + type: string + minLength: 3 + maxLength: 64 + email: + type: string + minLength: 3 + maxLength: 64 + password: + type: string + minLength: 6 + maxLength: 48 + additionalProperties: false + required: + - username + - email + - password diff --git a/assets/handlers/node/register/package.json b/assets/handlers/node/register/package.json new file mode 100644 index 0000000..3caf893 --- /dev/null +++ b/assets/handlers/node/register/package.json @@ -0,0 +1,11 @@ +{ + "name": "register", + "version": "1.0.0", + "main": "src/index.js", + "type": "module", + "dependencies": { + "argon2": "^0.41.1", + "joi": "^17.13.3", + "mongodb": "^6.12.0" + } +} diff --git a/assets/handlers/node/register/src/create-user.js b/assets/handlers/node/register/src/create-user.js new file mode 100644 index 0000000..40edcbe --- /dev/null +++ b/assets/handlers/node/register/src/create-user.js @@ -0,0 +1,12 @@ +import { registerCollection } from './db.js'; + +/** + * + * @param user {Record} + * @returns {Promise} + */ +export async function createUser(user) { + const insertResult = await registerCollection.insertOne(user); + + return insertResult.insertedId.toString(); +} diff --git a/assets/handlers/node/register/src/db.js b/assets/handlers/node/register/src/db.js new file mode 100644 index 0000000..ecac3bc --- /dev/null +++ b/assets/handlers/node/register/src/db.js @@ -0,0 +1,15 @@ +import { MongoClient } from 'mongodb'; + +const HOST = process.env['DB_MONGO_HOST']; +const PORT = process.env['DB_MONGO_PORT'] ?? ''; +const USER = process.env['DB_MONGO_USER']; +const PASSWORD = process.env['DB_MONGO_PASSWORD']; + +const url = (USER && PASSWORD) ? `mongodb://${USER}:${PASSWORD}@${HOST}:${PORT}`: `mongodb://${HOST}:${PORT}`; + +const client = new MongoClient(url); + +await client.connect(); + +export const registerDb = client.db('register_db'); +export const registerCollection = registerDb.collection('register_collection'); diff --git a/assets/handlers/node/register/src/hash.js b/assets/handlers/node/register/src/hash.js new file mode 100644 index 0000000..6e4bd0e --- /dev/null +++ b/assets/handlers/node/register/src/hash.js @@ -0,0 +1,11 @@ +import { hash } from 'argon2'; + +const options = { + timeCost: 2, + memoryCost: 6144, + saltLength: 16, +}; + +export async function hashPassword(password) { + return await hash(password, options); +} diff --git a/assets/handlers/node/register/src/index.js b/assets/handlers/node/register/src/index.js new file mode 100644 index 0000000..8a4d427 --- /dev/null +++ b/assets/handlers/node/register/src/index.js @@ -0,0 +1,22 @@ +import { validate } from './validate.js'; +import { readUserByEmail, readUserByUsername } from './read-user.js'; +import { createUser } from './create-user.js'; + +/** + * + * @param buffer {Buffer} + * @returns {Promise} + */ +export async function registerUser(buffer) { + const user = await validate(buffer); + + // if (await readUserByEmail(user.email)) { + // return undefined; + // } + // + // if (await readUserByUsername(user.username)) { + // return undefined; + // } + + return await createUser(user); +} diff --git a/assets/handlers/node/register/src/read-user.js b/assets/handlers/node/register/src/read-user.js new file mode 100644 index 0000000..31601df --- /dev/null +++ b/assets/handlers/node/register/src/read-user.js @@ -0,0 +1,38 @@ +import { registerCollection } from './db.js'; + +/** + * + * @param key {string} + * @param value {string} + * @returns {Promise} + */ +async function readUserByKey(key, value) { + const user = await registerCollection.findOne({ key: value }); + + if (!user) { + return undefined; + } + + user['id'] = user._id.toString(); + delete user._id; + + return user; +} + +/** + * + * @param name {string} + * @returns {Promise} + */ +export async function readUserByUsername(name) { + return readUserByKey('username', name); +} + +/** + * + * @param email {string} + * @returns {Promise} + */ +export async function readUserByEmail(email) { + return readUserByKey('email', email); +} diff --git a/assets/handlers/node/register/src/schemas.js b/assets/handlers/node/register/src/schemas.js new file mode 100644 index 0000000..1b41374 --- /dev/null +++ b/assets/handlers/node/register/src/schemas.js @@ -0,0 +1,13 @@ +import Joi from 'joi'; +import { hashPassword } from './hash.js'; + +export const createUserSchema = Joi.object({ + username: Joi.string().min(3).max(64), + email: Joi.string().min(3).max(64), + password: Joi.string().min(6).max(48), + created_at: Joi.number().default(Date.now), +}).external(async (user) => { + user.password = Buffer.from(await hashPassword(user.password)); + + return user; +}); diff --git a/assets/handlers/node/register/src/validate.js b/assets/handlers/node/register/src/validate.js new file mode 100644 index 0000000..fa5dbb0 --- /dev/null +++ b/assets/handlers/node/register/src/validate.js @@ -0,0 +1,12 @@ +import { createUserSchema } from './schemas.js'; + +/** + * + * @param buffer {Buffer} + * @returns {Promise} + */ +export async function validate(buffer) { + const json = JSON.parse(buffer.toString('utf-8')); + + return await createUserSchema.validateAsync(json); +} diff --git a/assets/handlers/node/register/utilization.yml b/assets/handlers/node/register/utilization.yml new file mode 100644 index 0000000..72fdd85 --- /dev/null +++ b/assets/handlers/node/register/utilization.yml @@ -0,0 +1,6 @@ +CPU: 209.0352221429168 +DISK_READ: 0.0 +DISK_WRITE: 0.0 +MEMORY: 127.50731469330373 +NETWORK_RECEIVE: 0.051541915588139066 +NETWORK_TRANSMIT: 0.07200559907786916 diff --git a/assets/handlers/node/rng/definition.yml b/assets/handlers/node/rng/definition.yml new file mode 100644 index 0000000..a12c082 --- /dev/null +++ b/assets/handlers/node/rng/definition.yml @@ -0,0 +1,25 @@ +import_path: rng +description: "Simple function to generate random numbers." +is_async: false +returns: true +signature: + function: generateRandomNumbers + parameters: + - arg: 0 + type: integer + minimum: 1 + exclusiveMinimum: false + maximum: 1000 + exclusiveMaximum: false + - arg: 1 + type: integer + minimum: 1 + exclusiveMinimum: false + maximum: 1000 + exclusiveMaximum: false + - arg: 2 + type: integer + minimum: 1 + exclusiveMinimum: false + maximum: 1000 + exclusiveMaximum: false diff --git a/assets/handlers/node/rng/package.json b/assets/handlers/node/rng/package.json new file mode 100644 index 0000000..ed2d91d --- /dev/null +++ b/assets/handlers/node/rng/package.json @@ -0,0 +1,6 @@ +{ + "name": "rng", + "version": "1.0.0", + "main": "src/index.js", + "type": "module" +} diff --git a/assets/handlers/node/rng/src/index.js b/assets/handlers/node/rng/src/index.js new file mode 100644 index 0000000..05811fe --- /dev/null +++ b/assets/handlers/node/rng/src/index.js @@ -0,0 +1,33 @@ +/** + * + * @param min {number} + * @param max {number} + * @returns {Generator} + */ +export function* yieldRandomNumer(min, max) { + while (true) { + yield Math.floor(Math.random() * (max - min + 1)) + min; + } +} + +/** + * + * @param n {number} + * @param min {number} + * @param max {number} + */ +export function generateRandomNumbers(n, min, max) { + if (n < 1) { + throw new Error(`"n" must be greater or equal to "1", but was ${n}.`); + } + + if (min > max) { + min += max; + max = min - max; + min = min - max; + } + + const generator = yieldRandomNumer(min, max); + + return Array.from({ length: n }, () => generator.next().value); +} diff --git a/assets/handlers/node/rng/utilization.yml b/assets/handlers/node/rng/utilization.yml new file mode 100644 index 0000000..7116d13 --- /dev/null +++ b/assets/handlers/node/rng/utilization.yml @@ -0,0 +1,6 @@ +CPU: 9.432374445986898 +DISK_READ: 0.0 +DISK_WRITE: 0.0 +MEMORY: 69.20768531320755 +NETWORK_RECEIVE: 0.03139991299161754 +NETWORK_TRANSMIT: 0.18857729232162668 diff --git a/assets/handlers/node/user-create/definition.yml b/assets/handlers/node/user-create/definition.yml new file mode 100644 index 0000000..c9214cb --- /dev/null +++ b/assets/handlers/node/user-create/definition.yml @@ -0,0 +1,30 @@ +import_path: user-create +description: "Simple CRUD operations for an user entity" +is_async: true +returns: true +depends_on: + - name: db:mongo + init: seed-user +signature: + function: createUser + parameters: + - arg: 0 + type: object + properties: + username: + type: string + minLength: 3 + maxLength: 64 + email: + type: string + minLength: 3 + maxLength: 64 + password: + type: string + minLength: 6 + maxLength: 48 + additionalProperties: false + required: + - username + - email + - password diff --git a/assets/handlers/node/user-create/package.json b/assets/handlers/node/user-create/package.json new file mode 100644 index 0000000..1875faf --- /dev/null +++ b/assets/handlers/node/user-create/package.json @@ -0,0 +1,11 @@ +{ + "name": "user-create", + "version": "1.0.0", + "main": "src/index.js", + "type": "module", + "dependencies": { + "argon2": "^0.41.1", + "joi": "^17.13.3", + "mongodb": "^6.12.0" + } +} diff --git a/assets/handlers/node/user-create/src/db.js b/assets/handlers/node/user-create/src/db.js new file mode 100644 index 0000000..3501379 --- /dev/null +++ b/assets/handlers/node/user-create/src/db.js @@ -0,0 +1,15 @@ +import { MongoClient } from 'mongodb'; + +const HOST = process.env['DB_MONGO_HOST']; +const PORT = process.env['DB_MONGO_PORT'] ?? ''; +const USER = process.env['DB_MONGO_USER']; +const PASSWORD = process.env['DB_MONGO_PASSWORD']; + +const url = (USER && PASSWORD) ? `mongodb://${USER}:${PASSWORD}@${HOST}:${PORT}`: `mongodb://${HOST}:${PORT}`; + +const client = new MongoClient(url); + +await client.connect(); + +export const userDb = client.db('user_db'); +export const userCollection = userDb.collection('user_collection'); diff --git a/assets/handlers/node/user-create/src/hash.js b/assets/handlers/node/user-create/src/hash.js new file mode 100644 index 0000000..6e4bd0e --- /dev/null +++ b/assets/handlers/node/user-create/src/hash.js @@ -0,0 +1,11 @@ +import { hash } from 'argon2'; + +const options = { + timeCost: 2, + memoryCost: 6144, + saltLength: 16, +}; + +export async function hashPassword(password) { + return await hash(password, options); +} diff --git a/assets/handlers/node/user-create/src/index.js b/assets/handlers/node/user-create/src/index.js new file mode 100644 index 0000000..f69f025 --- /dev/null +++ b/assets/handlers/node/user-create/src/index.js @@ -0,0 +1,15 @@ +import { validate } from './validate.js'; +import { userCollection } from './db.js'; + +/** + * + * @param buffer {Buffer} + * @returns {Promise} + */ +export async function createUser(buffer) { + const invoice = await validate(buffer); + + const insertResult = await userCollection.insertOne(invoice); + + return insertResult.insertedId.toString(); +} diff --git a/assets/handlers/node/user-create/src/schemas.js b/assets/handlers/node/user-create/src/schemas.js new file mode 100644 index 0000000..2eae6c1 --- /dev/null +++ b/assets/handlers/node/user-create/src/schemas.js @@ -0,0 +1,13 @@ +import Joi from 'joi'; +import { hashPassword } from './hash.js'; + +export const userSchema = Joi.object({ + username: Joi.string().min(3).max(64), + email: Joi.string().min(3).max(64), + password: Joi.string().min(6).max(48), + created_at: Joi.number().default(Date.now), +}); + +userSchema.external(async (user) => { + user.password = Buffer.from(await hashPassword(user.password)); +}); diff --git a/assets/handlers/node/user-create/src/validate.js b/assets/handlers/node/user-create/src/validate.js new file mode 100644 index 0000000..6eb35f8 --- /dev/null +++ b/assets/handlers/node/user-create/src/validate.js @@ -0,0 +1,12 @@ +import { userSchema } from './schemas.js'; + +/** + * + * @param buffer {Buffer} + * @returns {Promise} + */ +export async function validate(buffer) { + const json = JSON.parse(buffer.toString('utf-8')); + + return await userSchema.validateAsync(json); +} diff --git a/assets/handlers/node/user-create/utilization.yml b/assets/handlers/node/user-create/utilization.yml new file mode 100644 index 0000000..490958e --- /dev/null +++ b/assets/handlers/node/user-create/utilization.yml @@ -0,0 +1,6 @@ +CPU: 13.816547463939814 +DISK_READ: 0.0 +DISK_WRITE: 0.0 +MEMORY: 82.48626571128376 +NETWORK_RECEIVE: 0.050064601458804835 +NETWORK_TRANSMIT: 0.06367446993543813 diff --git a/assets/handlers/node/user-delete/definition.yml b/assets/handlers/node/user-delete/definition.yml new file mode 100644 index 0000000..8192710 --- /dev/null +++ b/assets/handlers/node/user-delete/definition.yml @@ -0,0 +1,14 @@ +import_path: user-delete +description: "Simple CRUD operations for an user entity" +is_async: true +returns: true +depends_on: + - name: db:mongo + init: seed-user +signature: + function: deleteUserById + parameters: + - arg: 0 + type: integer + minimum: 1 + maximum: 300000 diff --git a/assets/handlers/node/user-delete/package.json b/assets/handlers/node/user-delete/package.json new file mode 100644 index 0000000..71d4a4e --- /dev/null +++ b/assets/handlers/node/user-delete/package.json @@ -0,0 +1,9 @@ +{ + "name": "user-delete", + "version": "1.0.0", + "main": "src/index.js", + "type": "module", + "dependencies": { + "mongodb": "^6.12.0" + } +} diff --git a/assets/handlers/node/user-delete/src/db.js b/assets/handlers/node/user-delete/src/db.js new file mode 100644 index 0000000..340eab0 --- /dev/null +++ b/assets/handlers/node/user-delete/src/db.js @@ -0,0 +1,15 @@ +import { MongoClient } from 'mongodb'; + +const HOST = process.env['DB_MONGO_HOST']; +const PORT = process.env['DB_MONGO_PORT'] ?? ''; +const USER = process.env['DB_MONGO_USER']; +const PASSWORD = process.env['DB_MONGO_PASSWORD']; + +const url = `mongodb://${USER}:${PASSWORD}@${HOST}:${PORT}`; + +const client = new MongoClient(url); + +await client.connect(); + +export const userDb = client.db('user_db'); +export const userCollection = userDb.collection('user_collection'); diff --git a/assets/handlers/node/user-delete/src/index.js b/assets/handlers/node/user-delete/src/index.js new file mode 100644 index 0000000..243c837 --- /dev/null +++ b/assets/handlers/node/user-delete/src/index.js @@ -0,0 +1,15 @@ +import { userCollection } from './db.js'; +import { ObjectId } from 'mongodb'; + +/** + * + * @param id {string} + * @returns {Promise} + */ +export async function deleteUserById(id) { + const insertResult = await userCollection.deleteOne({ + _id: new ObjectId(id), + }); + + return insertResult.acknowledged; +} diff --git a/assets/handlers/node/user-delete/utilization.yml b/assets/handlers/node/user-delete/utilization.yml new file mode 100644 index 0000000..7dd5947 --- /dev/null +++ b/assets/handlers/node/user-delete/utilization.yml @@ -0,0 +1,6 @@ +CPU: 12.661844309058113 +DISK_READ: 0.0 +DISK_WRITE: 0.0 +MEMORY: 80.02279735405106 +NETWORK_RECEIVE: 0.03657017912123121 +NETWORK_TRANSMIT: 0.05068522930088446 diff --git a/assets/handlers/node/user-read/definition.yml b/assets/handlers/node/user-read/definition.yml new file mode 100644 index 0000000..5cb5043 --- /dev/null +++ b/assets/handlers/node/user-read/definition.yml @@ -0,0 +1,14 @@ +import_path: user-read +description: "Simple CRUD operations for an user entity" +is_async: true +returns: true +depends_on: + - name: db:mongo + init: seed-user +signature: + function: readUserById + parameters: + - arg: 0 + type: integer + minimum: 1 + maximum: 300000 diff --git a/assets/handlers/node/user-read/package.json b/assets/handlers/node/user-read/package.json new file mode 100644 index 0000000..d3fda68 --- /dev/null +++ b/assets/handlers/node/user-read/package.json @@ -0,0 +1,9 @@ +{ + "name": "user-read", + "version": "1.0.0", + "main": "src/index.js", + "type": "module", + "dependencies": { + "mongodb": "^6.12.0" + } +} diff --git a/assets/handlers/node/user-read/src/db.js b/assets/handlers/node/user-read/src/db.js new file mode 100644 index 0000000..340eab0 --- /dev/null +++ b/assets/handlers/node/user-read/src/db.js @@ -0,0 +1,15 @@ +import { MongoClient } from 'mongodb'; + +const HOST = process.env['DB_MONGO_HOST']; +const PORT = process.env['DB_MONGO_PORT'] ?? ''; +const USER = process.env['DB_MONGO_USER']; +const PASSWORD = process.env['DB_MONGO_PASSWORD']; + +const url = `mongodb://${USER}:${PASSWORD}@${HOST}:${PORT}`; + +const client = new MongoClient(url); + +await client.connect(); + +export const userDb = client.db('user_db'); +export const userCollection = userDb.collection('user_collection'); diff --git a/assets/handlers/node/user-read/src/index.js b/assets/handlers/node/user-read/src/index.js new file mode 100644 index 0000000..419f82b --- /dev/null +++ b/assets/handlers/node/user-read/src/index.js @@ -0,0 +1,20 @@ +import { userCollection } from './db.js'; +import { ObjectId } from 'mongodb'; + +/** + * + * @param id {string} + * @returns {Promise} + */ +export async function readUserById(id) { + const user = await userCollection.findOne({ _id: new ObjectId(id) }); + + if (!user) { + return undefined; + } + + user['id'] = user._id.toString(); + delete user._id; + + return user; +} diff --git a/assets/handlers/node/user-read/utilization.yml b/assets/handlers/node/user-read/utilization.yml new file mode 100644 index 0000000..0441a92 --- /dev/null +++ b/assets/handlers/node/user-read/utilization.yml @@ -0,0 +1,6 @@ +CPU: 12.195930752956947 +DISK_READ: 0.0 +DISK_WRITE: 0.0 +MEMORY: 80.69877100821309 +NETWORK_RECEIVE: 0.042028467747404236 +NETWORK_TRANSMIT: 0.04702408469505361 diff --git a/assets/handlers/node/user-update/definition.yml b/assets/handlers/node/user-update/definition.yml new file mode 100644 index 0000000..25a6d63 --- /dev/null +++ b/assets/handlers/node/user-update/definition.yml @@ -0,0 +1,30 @@ +import_path: user-update +description: "Simple CRUD operations for an user entity" +is_async: true +returns: true +depends_on: + - name: db:mongo + init: seed-user +signature: + function: updateUserById + parameters: + - arg: 0 + type: integer + minimum: 1 + maximum: 300000 + - arg: 1 + type: object + properties: + username: + type: string + minLength: 3 + maxLength: 64 + email: + type: string + minLength: 3 + maxLength: 64 + password: + type: string + minLength: 6 + maxLength: 48 + additionalProperties: false diff --git a/assets/handlers/node/user-update/package.json b/assets/handlers/node/user-update/package.json new file mode 100644 index 0000000..3855471 --- /dev/null +++ b/assets/handlers/node/user-update/package.json @@ -0,0 +1,11 @@ +{ + "name": "user-update", + "version": "1.0.0", + "type": "module", + "main": "src/index.js", + "dependencies": { + "argon2": "^0.41.1", + "joi": "^17.13.3", + "mongodb": "^6.12.0" + } +} diff --git a/assets/handlers/node/user-update/src/db.js b/assets/handlers/node/user-update/src/db.js new file mode 100644 index 0000000..3501379 --- /dev/null +++ b/assets/handlers/node/user-update/src/db.js @@ -0,0 +1,15 @@ +import { MongoClient } from 'mongodb'; + +const HOST = process.env['DB_MONGO_HOST']; +const PORT = process.env['DB_MONGO_PORT'] ?? ''; +const USER = process.env['DB_MONGO_USER']; +const PASSWORD = process.env['DB_MONGO_PASSWORD']; + +const url = (USER && PASSWORD) ? `mongodb://${USER}:${PASSWORD}@${HOST}:${PORT}`: `mongodb://${HOST}:${PORT}`; + +const client = new MongoClient(url); + +await client.connect(); + +export const userDb = client.db('user_db'); +export const userCollection = userDb.collection('user_collection'); diff --git a/assets/handlers/node/user-update/src/hash.js b/assets/handlers/node/user-update/src/hash.js new file mode 100644 index 0000000..6e4bd0e --- /dev/null +++ b/assets/handlers/node/user-update/src/hash.js @@ -0,0 +1,11 @@ +import { hash } from 'argon2'; + +const options = { + timeCost: 2, + memoryCost: 6144, + saltLength: 16, +}; + +export async function hashPassword(password) { + return await hash(password, options); +} diff --git a/assets/handlers/node/user-update/src/index.js b/assets/handlers/node/user-update/src/index.js new file mode 100644 index 0000000..cd1d2c4 --- /dev/null +++ b/assets/handlers/node/user-update/src/index.js @@ -0,0 +1,25 @@ +import { validate } from './validate.js'; +import { userCollection } from './db.js'; +import { ObjectId } from 'mongodb'; + +/** + * + * @param id {string} + * @param buffer {Buffer} + * @returns {Promise} + */ +export async function updateUserById(id, buffer) { + const user = await validate(buffer); + + if (user) { + const updateResult = await userCollection.updateOne( + { _id: id }, + { + $set: user, + }, + ); + return updateResult.acknowledged; + } + + return true; +} diff --git a/assets/handlers/node/user-update/src/schemas.js b/assets/handlers/node/user-update/src/schemas.js new file mode 100644 index 0000000..8a79209 --- /dev/null +++ b/assets/handlers/node/user-update/src/schemas.js @@ -0,0 +1,14 @@ +import Joi from 'joi'; +import { hashPassword } from './hash.js'; + +export const userSchema = Joi.object({ + username: Joi.string().min(3).max(64), + email: Joi.string().min(3).max(64), + password: Joi.string().min(6).max(48), +}); + +userSchema.external(async (user) => { + user.password = Buffer.from(await hashPassword(user.password)); + + return user; +}); diff --git a/assets/handlers/node/user-update/src/validate.js b/assets/handlers/node/user-update/src/validate.js new file mode 100644 index 0000000..6eb35f8 --- /dev/null +++ b/assets/handlers/node/user-update/src/validate.js @@ -0,0 +1,12 @@ +import { userSchema } from './schemas.js'; + +/** + * + * @param buffer {Buffer} + * @returns {Promise} + */ +export async function validate(buffer) { + const json = JSON.parse(buffer.toString('utf-8')); + + return await userSchema.validateAsync(json); +} diff --git a/assets/handlers/node/user-update/utilization.yml b/assets/handlers/node/user-update/utilization.yml new file mode 100644 index 0000000..cf9b7ed --- /dev/null +++ b/assets/handlers/node/user-update/utilization.yml @@ -0,0 +1,6 @@ +CPU: 13.95720400959255 +DISK_READ: 0.0 +DISK_WRITE: 0.0 +MEMORY: 81.78681680976693 +NETWORK_RECEIVE: 0.05293450996886753 +NETWORK_TRANSMIT: 0.06163557436057788 diff --git a/assets/handlers/rust/hash/utilization.yml b/assets/handlers/rust/hash/utilization.yml index a843d08..a70009d 100644 --- a/assets/handlers/rust/hash/utilization.yml +++ b/assets/handlers/rust/hash/utilization.yml @@ -3,4 +3,4 @@ DISK_READ: 0.0 DISK_WRITE: 0.0 MEMORY: 419.5384294442595 NETWORK_RECEIVE: 0.018792295179874883 -NETWORK_TRANSMIT: 0.022774955244125625 +NETWORK_TRANSMIT: 0.022774955244125625 \ No newline at end of file diff --git a/assets/init-services/node/.gitignore b/assets/init-services/node/.gitignore new file mode 100644 index 0000000..bbe74e8 --- /dev/null +++ b/assets/init-services/node/.gitignore @@ -0,0 +1,133 @@ +### Node template +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +lerna-debug.log* +.pnpm-debug.log* + +# Diagnostic reports (https://nodejs.org/api/report.html) +report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage +*.lcov + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ +jspm_packages/ + +# Snowpack dependency directory (https://snowpack.dev/) +web_modules/ + +# TypeScript cache +*.tsbuildinfo + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional stylelint cache +.stylelintcache + +# Microbundle cache +.rpt2_cache/ +.rts2_cache_cjs/ +.rts2_cache_es/ +.rts2_cache_umd/ + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variable files +.env +.env.development.local +.env.test.local +.env.production.local +.env.local + +# parcel-bundler cache (https://parceljs.org/) +.cache +.parcel-cache + +# Next.js build output +.next +out + +# Nuxt.js build / generate output +.nuxt +dist + +# Gatsby files +.cache/ +# Comment in the public line in if your project uses Gatsby and not Next.js +# https://nextjs.org/blog/next-9-1#public-directory-support +# public + +# vuepress build output +.vuepress/dist + +# vuepress v2.x temp and cache directory +.temp +.cache + +# Docusaurus cache and generated files +.docusaurus + +# Serverless directories +.serverless/ + +# FuseBox cache +.fusebox/ + +# DynamoDB Local files +.dynamodb/ + +# TernJS port file +.tern-port + +# Stores VSCode versions used for testing VSCode extensions +.vscode-test + +# yarn v2 +.yarn/cache +.yarn/unplugged +.yarn/build-state.yml +.yarn/install-state.gz +.pnp.* + +package-lock.json \ No newline at end of file diff --git a/assets/init-services/node/seed-invoice/Dockerfile b/assets/init-services/node/seed-invoice/Dockerfile new file mode 100644 index 0000000..616b137 --- /dev/null +++ b/assets/init-services/node/seed-invoice/Dockerfile @@ -0,0 +1,15 @@ +FROM node:22.13.1-bookworm-slim + +RUN mkdir -p /home/node/app/node_modules && chown -R node:node /home/node/app + +WORKDIR /home/node/app + +COPY package.json ./ + +RUN npm install + +USER node + +COPY --chown=node:node src src + +ENTRYPOINT ["node", "src/index.js"] diff --git a/assets/init-services/node/seed-invoice/package.json b/assets/init-services/node/seed-invoice/package.json new file mode 100644 index 0000000..61ebc3c --- /dev/null +++ b/assets/init-services/node/seed-invoice/package.json @@ -0,0 +1,10 @@ +{ + "name": "seed-invoice", + "version": "1.0.0", + "main": "src/index.js", + "type": "module", + "dependencies": { + "mongodb": "^6.12.0", + "@faker-js/faker": "^9" + } +} diff --git a/assets/init-services/node/seed-invoice/src/db.js b/assets/init-services/node/seed-invoice/src/db.js new file mode 100644 index 0000000..cd58557 --- /dev/null +++ b/assets/init-services/node/seed-invoice/src/db.js @@ -0,0 +1,15 @@ +import { MongoClient } from 'mongodb'; + +const HOST = process.env['DB_MONGO_HOST']; +const PORT = process.env['DB_MONGO_PORT'] ?? ''; +const USER = process.env['DB_MONGO_USER'] ?? ''; +const PASSWORD = process.env['DB_MONGO_PASSWORD'] ?? ''; + +const url = (USER && PASSWORD) ? `mongodb://${USER}:${PASSWORD}@${HOST}:${PORT}`: `mongodb://${HOST}:${PORT}`; + +const client = new MongoClient(url); + +await client.connect(); + +const invoiceDb = client.db('invoice_db'); +export const invoiceCollection = invoiceDb.collection('invoice_collection'); \ No newline at end of file diff --git a/assets/init-services/node/seed-invoice/src/index.js b/assets/init-services/node/seed-invoice/src/index.js new file mode 100644 index 0000000..1718e72 --- /dev/null +++ b/assets/init-services/node/seed-invoice/src/index.js @@ -0,0 +1,30 @@ +import { invoiceCollection } from "./db.js"; +import { randomInvoice } from "./invoice.js"; +import assert from "node:assert"; + +const SEED_COUNT = +(process.env['MG_SEED_COUNT'] ?? 1); +const BATCH_SIZE = 5000; + + +async function main() { + const ids = Array.from({ length: SEED_COUNT }, (_, i) => i + 1); + + for (let i = 0; i < ids.length; i += BATCH_SIZE) { + const chunk = ids.slice(i, i + BATCH_SIZE); + + const invoices = chunk.map(id => randomInvoice(id)) + + const result = await invoiceCollection.insertMany(invoices); + + assert(result.acknowledged, 'Failed to insert.') + } +} + +try { + await main(); + console.log('Seeded invoice db.') + process.exit(0); +} catch (e) { + console.error('Failed to seed invoice db.') + console.log(e); +} \ No newline at end of file diff --git a/assets/init-services/node/seed-invoice/src/invoice.js b/assets/init-services/node/seed-invoice/src/invoice.js new file mode 100644 index 0000000..c07568e --- /dev/null +++ b/assets/init-services/node/seed-invoice/src/invoice.js @@ -0,0 +1,42 @@ +import { faker } from "@faker-js/faker"; + +function randomAddress() { + return { + first_name: faker.string.alphanumeric({ length: { min: 2, max: 64 } }), + last_name: faker.string.alphanumeric({ length: { min: 2, max: 64 } }), + street: faker.string.alphanumeric({ length: { min: 2, max: 128 } }), + city: faker.string.alphanumeric({ length: { min: 3, max: 64 } }), + country: faker.string.alphanumeric({ length: { min: 3, max: 64 } }), + number: faker.number.int({ min: 1, max: 2000 }), + zip_code: faker.number.int({ min: 1000, max: 99999 }), + } +} + +function randomItem() { + return { + name: faker.string.alphanumeric({ length: { min: 1, max: 128 } }), + price_in_cents: faker.number.int({ min: 1, max: 1_000_000_000 }), + } +} + +function randomOrderItem() { + return { + item: randomItem(), + quantity: faker.number.int({ min: 1, max: 10_000 }), + } +} + +export function randomInvoice(id) { + return { + _id: id, + items: Array.from({ length: faker.number.int({ min: 1, max: 100 }) }).map(randomOrderItem), + billing_address: randomAddress(), + shipping_address: randomAddress(), + user_id: faker.string.alphanumeric({ length: { min: 10, max: 24 } }), + tax_rate: 0.15, + issued_at: Date.now(), + extra_info: faker.string.alphanumeric({ length: { min: 0, max: 512 } }), + status: 'OPEN', + invoice_number: faker.string.alphanumeric({ length: { min: 10, max: 13 } }), + } +} \ No newline at end of file diff --git a/assets/init-services/node/seed-login/Dockerfile b/assets/init-services/node/seed-login/Dockerfile new file mode 100644 index 0000000..616b137 --- /dev/null +++ b/assets/init-services/node/seed-login/Dockerfile @@ -0,0 +1,15 @@ +FROM node:22.13.1-bookworm-slim + +RUN mkdir -p /home/node/app/node_modules && chown -R node:node /home/node/app + +WORKDIR /home/node/app + +COPY package.json ./ + +RUN npm install + +USER node + +COPY --chown=node:node src src + +ENTRYPOINT ["node", "src/index.js"] diff --git a/assets/init-services/node/seed-login/package.json b/assets/init-services/node/seed-login/package.json new file mode 100644 index 0000000..3c96f44 --- /dev/null +++ b/assets/init-services/node/seed-login/package.json @@ -0,0 +1,10 @@ +{ + "name": "seed-invoice", + "version": "1.0.0", + "main": "src/index.js", + "type": "module", + "dependencies": { + "@faker-js/faker": "^9", + "mongodb": "^6.12.0" + } +} diff --git a/assets/init-services/node/seed-login/src/db.js b/assets/init-services/node/seed-login/src/db.js new file mode 100644 index 0000000..2c83bc9 --- /dev/null +++ b/assets/init-services/node/seed-login/src/db.js @@ -0,0 +1,15 @@ +import { MongoClient } from 'mongodb'; + +const HOST = process.env['DB_MONGO_HOST']; +const PORT = process.env['DB_MONGO_PORT'] ?? ''; +const USER = process.env['DB_MONGO_USER'] ?? ''; +const PASSWORD = process.env['DB_MONGO_PASSWORD'] ?? ''; + +const url = (USER && PASSWORD) ? `mongodb://${USER}:${PASSWORD}@${HOST}:${PORT}`: `mongodb://${HOST}:${PORT}`; + +const client = new MongoClient(url); + +await client.connect(); + +const loginDb = client.db('login_db'); +export const loginCollection = loginDb.collection('login_collection'); \ No newline at end of file diff --git a/assets/init-services/node/seed-login/src/index.js b/assets/init-services/node/seed-login/src/index.js new file mode 100644 index 0000000..bd5ab3c --- /dev/null +++ b/assets/init-services/node/seed-login/src/index.js @@ -0,0 +1,34 @@ +import { loginCollection } from "./db.js"; +import assert from "node:assert"; +import {randomUser} from "./user.js"; + +const SEED_COUNT = +(process.env['MG_SEED_COUNT'] ?? 1); +const BATCH_SIZE = 5000; + + +async function main() { + await loginCollection.createIndex('username') + await loginCollection.createIndex('email') + + const ids = Array.from({ length: SEED_COUNT }, (_, i) => i + 1); + + for (let i = 0; i < ids.length; i += BATCH_SIZE) { + const chunk = ids.slice(i, i + BATCH_SIZE); + + const users = chunk.map(id => randomUser(id)) + + const result = await loginCollection.insertMany(users); + + assert(result.acknowledged, 'Failed to insert.') + } +} + +try { + await main(); + console.log('Seeded login db.') + process.exit(0); +} catch (e) { + console.error('Failed to seed login db.') + console.log(e); + process.exit(1); +} \ No newline at end of file diff --git a/assets/init-services/node/seed-login/src/user.js b/assets/init-services/node/seed-login/src/user.js new file mode 100644 index 0000000..81979ee --- /dev/null +++ b/assets/init-services/node/seed-login/src/user.js @@ -0,0 +1,11 @@ +import { faker } from "@faker-js/faker"; + +export function randomUser(id) { + return { + _id: id, + username: faker.string.alphanumeric({ length: { min: 3, max: 64 } }), + email: faker.string.alphanumeric({ length: { min: 3, max: 64 } }), + password: Buffer.from(faker.string.alphanumeric({ length: 97 })), + created_at: Date.now(), + } +} \ No newline at end of file diff --git a/assets/init-services/node/seed-register/Dockerfile b/assets/init-services/node/seed-register/Dockerfile new file mode 100644 index 0000000..616b137 --- /dev/null +++ b/assets/init-services/node/seed-register/Dockerfile @@ -0,0 +1,15 @@ +FROM node:22.13.1-bookworm-slim + +RUN mkdir -p /home/node/app/node_modules && chown -R node:node /home/node/app + +WORKDIR /home/node/app + +COPY package.json ./ + +RUN npm install + +USER node + +COPY --chown=node:node src src + +ENTRYPOINT ["node", "src/index.js"] diff --git a/assets/init-services/node/seed-register/package.json b/assets/init-services/node/seed-register/package.json new file mode 100644 index 0000000..61ebc3c --- /dev/null +++ b/assets/init-services/node/seed-register/package.json @@ -0,0 +1,10 @@ +{ + "name": "seed-invoice", + "version": "1.0.0", + "main": "src/index.js", + "type": "module", + "dependencies": { + "mongodb": "^6.12.0", + "@faker-js/faker": "^9" + } +} diff --git a/assets/init-services/node/seed-register/src/db.js b/assets/init-services/node/seed-register/src/db.js new file mode 100644 index 0000000..daaa30c --- /dev/null +++ b/assets/init-services/node/seed-register/src/db.js @@ -0,0 +1,15 @@ +import { MongoClient } from 'mongodb'; + +const HOST = process.env['DB_MONGO_HOST']; +const PORT = process.env['DB_MONGO_PORT'] ?? ''; +const USER = process.env['DB_MONGO_USER'] ?? ''; +const PASSWORD = process.env['DB_MONGO_PASSWORD'] ?? ''; + +const url = (USER && PASSWORD) ? `mongodb://${USER}:${PASSWORD}@${HOST}:${PORT}`: `mongodb://${HOST}:${PORT}`; + +const client = new MongoClient(url); + +await client.connect(); + +const registerDb = client.db('register_db'); +export const registerCollection = registerDb.collection('register_collection'); \ No newline at end of file diff --git a/assets/init-services/node/seed-register/src/index.js b/assets/init-services/node/seed-register/src/index.js new file mode 100644 index 0000000..9b8f32f --- /dev/null +++ b/assets/init-services/node/seed-register/src/index.js @@ -0,0 +1,32 @@ +import {registerCollection} from "./db.js"; +import assert from "node:assert"; +import {randomUser} from "./user.js"; + +const SEED_COUNT = +(process.env['MG_SEED_COUNT'] ?? 1); +const BATCH_SIZE = 5000; + +async function main() { + await registerCollection.createIndex('username') + await registerCollection.createIndex('email') + + const ids = Array.from({ length: SEED_COUNT }, (_, i) => i + 1); + + for (let i = 0; i < ids.length; i += BATCH_SIZE) { + const chunk = ids.slice(i, i + BATCH_SIZE); + + const users = chunk.map(id => randomUser(id)) + + const result = await registerCollection.insertMany(users); + + assert(result.acknowledged, 'Failed to insert.') + } +} + +try { + await main(); + console.log('Seeded register db.'); + process.exit(0); +} catch (e) { + console.error('Failed to seed register db.') + console.log(e); +} \ No newline at end of file diff --git a/assets/init-services/node/seed-register/src/user.js b/assets/init-services/node/seed-register/src/user.js new file mode 100644 index 0000000..81979ee --- /dev/null +++ b/assets/init-services/node/seed-register/src/user.js @@ -0,0 +1,11 @@ +import { faker } from "@faker-js/faker"; + +export function randomUser(id) { + return { + _id: id, + username: faker.string.alphanumeric({ length: { min: 3, max: 64 } }), + email: faker.string.alphanumeric({ length: { min: 3, max: 64 } }), + password: Buffer.from(faker.string.alphanumeric({ length: 97 })), + created_at: Date.now(), + } +} \ No newline at end of file diff --git a/assets/init-services/node/seed-user/Dockerfile b/assets/init-services/node/seed-user/Dockerfile new file mode 100644 index 0000000..616b137 --- /dev/null +++ b/assets/init-services/node/seed-user/Dockerfile @@ -0,0 +1,15 @@ +FROM node:22.13.1-bookworm-slim + +RUN mkdir -p /home/node/app/node_modules && chown -R node:node /home/node/app + +WORKDIR /home/node/app + +COPY package.json ./ + +RUN npm install + +USER node + +COPY --chown=node:node src src + +ENTRYPOINT ["node", "src/index.js"] diff --git a/assets/init-services/node/seed-user/package.json b/assets/init-services/node/seed-user/package.json new file mode 100644 index 0000000..61ebc3c --- /dev/null +++ b/assets/init-services/node/seed-user/package.json @@ -0,0 +1,10 @@ +{ + "name": "seed-invoice", + "version": "1.0.0", + "main": "src/index.js", + "type": "module", + "dependencies": { + "mongodb": "^6.12.0", + "@faker-js/faker": "^9" + } +} diff --git a/assets/init-services/node/seed-user/src/db.js b/assets/init-services/node/seed-user/src/db.js new file mode 100644 index 0000000..fb16677 --- /dev/null +++ b/assets/init-services/node/seed-user/src/db.js @@ -0,0 +1,15 @@ +import { MongoClient } from 'mongodb'; + +const HOST = process.env['DB_MONGO_HOST']; +const PORT = process.env['DB_MONGO_PORT'] ?? ''; +const USER = process.env['DB_MONGO_USER'] ?? ''; +const PASSWORD = process.env['DB_MONGO_PASSWORD'] ?? ''; + +const url = (USER && PASSWORD) ? `mongodb://${USER}:${PASSWORD}@${HOST}:${PORT}`: `mongodb://${HOST}:${PORT}`; + +const client = new MongoClient(url); + +await client.connect(); + +const userDb = client.db('user_db'); +export const userCollection = userDb.collection('user_collection'); \ No newline at end of file diff --git a/assets/init-services/node/seed-user/src/index.js b/assets/init-services/node/seed-user/src/index.js new file mode 100644 index 0000000..04b4bf5 --- /dev/null +++ b/assets/init-services/node/seed-user/src/index.js @@ -0,0 +1,30 @@ +import { userCollection } from "./db.js"; +import {randomUser} from "./user.js"; +import assert from "node:assert"; + +const SEED_COUNT = +(process.env['MG_SEED_COUNT'] ?? 1); +const BATCH_SIZE = 5000; + + +async function main() { + const ids = Array.from({ length: SEED_COUNT }, (_, i) => i + 1); + + for (let i = 0; i < ids.length; i += BATCH_SIZE) { + const chunk = ids.slice(i, i + BATCH_SIZE); + + const users = chunk.map(id => randomUser(id)) + + const result = await userCollection.insertMany(users); + + assert(result.acknowledged, 'Failed to insert.') + } +} + +try { + await main(); + console.log('Seeded user db.'); + process.exit(0); +} catch (e) { + console.error('Failed to seed user db.') + console.log(e); +} \ No newline at end of file diff --git a/assets/init-services/node/seed-user/src/user.js b/assets/init-services/node/seed-user/src/user.js new file mode 100644 index 0000000..1bda13f --- /dev/null +++ b/assets/init-services/node/seed-user/src/user.js @@ -0,0 +1,11 @@ +import { faker } from "@faker-js/faker"; + +export function randomUser(id) { + return { + _id: id, + username: faker.string.alphanumeric({ length: { min: 3, max: 64 } }), + email: faker.string.alphanumeric({ length: { min: 3, max: 64 } }), + created_at: Date.now(), + password: Buffer.from(faker.string.alphanumeric({ length: 97 })), + } +} \ No newline at end of file diff --git a/assets/templates/node/Dockerfile.mgt b/assets/templates/node/Dockerfile.mgt new file mode 100644 index 0000000..1821ec5 --- /dev/null +++ b/assets/templates/node/Dockerfile.mgt @@ -0,0 +1,18 @@ +FROM node:22.13.1-bookworm-slim + +RUN mkdir -p /home/node/app/node_modules && chown -R node:node /home/node/app + +WORKDIR /home/node/app + +COPY package.json ./ +COPY --chown=node:node lib lib + +RUN npm install + +USER node + +COPY --chown=node:node src src + +EXPOSE 80 + +ENTRYPOINT {{entrypoint}} diff --git a/assets/templates/node/express/index.mgt b/assets/templates/node/express/index.mgt new file mode 100644 index 0000000..e904c44 --- /dev/null +++ b/assets/templates/node/express/index.mgt @@ -0,0 +1,17 @@ +import express from 'express'; +import bodyParser from 'body-parser'; + +import { router } from './router.js' + +const app = express(); + +app.use(bodyParser.raw({ type: '*/*' })); + +app.use(router); + +app.listen(80, '0.0.0.0', (err) => { + if (err) { + console.error(err); + } + console.log('Server listening on port 80.') +}); diff --git a/assets/templates/node/express/router/get-operation.mgt b/assets/templates/node/express/router/get-operation.mgt new file mode 100644 index 0000000..c3bc20f --- /dev/null +++ b/assets/templates/node/express/router/get-operation.mgt @@ -0,0 +1,54 @@ +const {{name}}QuerySchema = Joi.object({ + {{#each query_params}} + {{this.name}}: {{this.param_type}}() + .description('{{this.description}}') + {{#if number_validation}} + {{#if minimum}} + .min({{this}}) + {{/if}} + {{#if maximum}} + .max({{this}}) + {{/if}} + {{/if}} + {{#if integer_validation}} + {{#if minimum}} + .min({{this}}) + {{/if}} + {{#if maximum}} + .max({{this}}) + {{/if}} + {{/if}} + {{#if string_validation}} + {{#if min_length}} + .min({{this}}) + {{/if}} + {{#if max_length}} + .max({{this}}) + {{/if}} + {{#if pattern}} + .pattern(/{{this}}/) + {{/if}} + {{/if}}, + {{/each}} +}); + +router.get('{{path}}', async function {{name}}(req, res) { + const { value: { {{#each query_params}} {{this.name}}, {{/each}}}, error } = {{name}}QuerySchema.validate(req.query); + + if (error) { + return res.status(400).json({ errors: error.details.map(detail => detail.message) }); + } + + {{#if has_service_calls}} + const calls = {{service_call_function_name}}(); + + {{/if}} + + {{#if has_return_type}}const result = {{/if}}{{#if is_async}}await {{/if}}{{handler_func_name}}({{#each handler_args}}{{#if is_pos_arg}}{{name}}{{/if}}{{#unless is_pos_arg}}{{name}}={{name}}{{/unless}}{{#unless @last}}, {{/unless}}{{/each}}) + + {{#if has_service_calls}} + await calls; + {{/if}} + + {{#if has_return_type}}return res.status(200).json(result){{/if}}{{#unless has_return_type}}return res.status(200).send(){{/unless}}; +}); diff --git a/assets/templates/node/express/router/post-operation.mgt b/assets/templates/node/express/router/post-operation.mgt new file mode 100644 index 0000000..cd44ca6 --- /dev/null +++ b/assets/templates/node/express/router/post-operation.mgt @@ -0,0 +1,56 @@ +const {{name}}QuerySchema = Joi.object({ + {{#each query_params}} + {{this.name}}: {{this.param_type}}() + .description('{{this.description}}') + {{#if number_validation}} + {{#if minimum}} + .min({{this}}) + {{/if}} + {{#if maximum}} + .max({{this}}) + {{/if}} + {{/if}} + {{#if integer_validation}} + {{#if minimum}} + .min({{this}}) + {{/if}} + {{#if maximum}} + .max({{this}}) + {{/if}} + {{/if}} + {{#if string_validation}} + {{#if min_length}} + .min({{this}}) + {{/if}} + {{#if max_length}} + .max({{this}}) + {{/if}} + {{#if pattern}} + .pattern(/{{this}}/) + {{/if}} + {{/if}}, + {{/each}} +}); + +router.post('{{path}}', async function {{name}}(req, res) { + const { value: { {{#each query_params}} {{this.name}}, {{/each}}}, error } = {{name}}QuerySchema.validate(req.query); + + if (error) { + return res.status(400).json({ errors: error.details.map(detail => detail.message) }); + } + + {{#if has_service_calls}} + const calls = {{service_call_function_name}}(); + + {{/if}} + + const {{body_param_name}} = req.body; + + {{#if has_return_type}}const result = {{/if}}{{#if is_async}}await {{/if}}{{handler_func_name}}({{#each handler_args}}{{#if is_pos_arg}}{{name}}{{/if}}{{#unless is_pos_arg}}{{name}}={{name}}{{/unless}}{{#unless @last}}, {{/unless}}{{/each}}) + + {{#if has_service_calls}} + await calls; + {{/if}} + + {{#if has_return_type}}return res.status(200).json(result){{/if}}{{#unless has_return_type}}return res.status(200).send(){{/unless}}; +}); diff --git a/assets/templates/node/express/router/router.mgt b/assets/templates/node/express/router/router.mgt new file mode 100644 index 0000000..706a637 --- /dev/null +++ b/assets/templates/node/express/router/router.mgt @@ -0,0 +1,28 @@ +import { Router } from 'express'; +import Joi from 'joi'; + +{{#each service_call_imports}} +{{this.import}}; +{{/each}} + +{{#each handler_func_imports}} +{{this.import}}; +{{/each}} + +export const router = Router(); + +const String = Joi.string.bind(Joi); +const Number = Joi.number.bind(Joi); +const Boolean = Joi.boolean.bind(Joi); +const Date = Joi.date.bind(Joi); + +{{#each http_post_operations}} +{{>post-operation}} +{{/each}} + +{{#each http_get_operations}} +{{>get-operation}} +{{/each}} + + + diff --git a/assets/templates/node/express/service-calls/array_fake_function.mgt b/assets/templates/node/express/service-calls/array_fake_function.mgt new file mode 100644 index 0000000..6f35e18 --- /dev/null +++ b/assets/templates/node/express/service-calls/array_fake_function.mgt @@ -0,0 +1,3 @@ +export function {{name}}() { + return Array.from({ length: getRandomInt({{inclusive_min_items}}, {{exclusive_max_items}}) }).map(() => {{fake_func.name}}({{fake_func.args}})); +} \ No newline at end of file diff --git a/assets/templates/node/express/service-calls/get_service_call.mgt b/assets/templates/node/express/service-calls/get_service_call.mgt new file mode 100644 index 0000000..3ca07c8 --- /dev/null +++ b/assets/templates/node/express/service-calls/get_service_call.mgt @@ -0,0 +1,14 @@ +export async function {{name}}() { + let url = `http://${process.env['{{host_env_var}}']}{{path}}?`; + + {{#if requires_data}} + const query = {{query_data_func}}(); + url += Object.entries(query).map(([key, value]) => `${key}=${value}`).join('&') + {{/if}} + + try { + const response = await fetch(encodeURI(url)) + } catch (err) { + console.error(`An error occurred while request GET -> ${url}.`) + } +} \ No newline at end of file diff --git a/assets/templates/node/express/service-calls/object_fake_function.mgt b/assets/templates/node/express/service-calls/object_fake_function.mgt new file mode 100644 index 0000000..bac3f4f --- /dev/null +++ b/assets/templates/node/express/service-calls/object_fake_function.mgt @@ -0,0 +1,15 @@ +export function {{name}}() { + const obj = {}; + + {{#each props}} + {{#if required}} + obj['{{name}}'] = {{fake_func.name}}({{fake_func.args}}); + {{else}} + if (Math.random() >= {{exclude_probability}}) { + obj['{{name}}'] = {{fake_func.name}}({{fake_func.args}}); + } + {{/if}} + {{/each}} + + return obj +} \ No newline at end of file diff --git a/assets/templates/node/express/service-calls/post_service_call.mgt b/assets/templates/node/express/service-calls/post_service_call.mgt new file mode 100644 index 0000000..bd7dea3 --- /dev/null +++ b/assets/templates/node/express/service-calls/post_service_call.mgt @@ -0,0 +1,22 @@ +export async function {{name}}() { + let url = `http://${process.env['{{host_env_var}}']}{{path}}?`; + + {{#if requires_data}} + const query = {{query_data_func}}(); + url += Object.entries(query).map(([key, value]) => `${key}=${value}`).join('&'); + {{/if}} + + const payload = {{body_data_func}}(); + + try { + const _response = await fetch(url, { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify(payload) + }); + } catch (err) { + console.error(`An error occurred while request POST -> ${url}.`) + } +} \ No newline at end of file diff --git a/assets/templates/node/express/service-calls/query_data_function.mgt b/assets/templates/node/express/service-calls/query_data_function.mgt new file mode 100644 index 0000000..342e629 --- /dev/null +++ b/assets/templates/node/express/service-calls/query_data_function.mgt @@ -0,0 +1,15 @@ +export function {{name}}() { + const obj = {}; + + {{#each params}} + {{#if nullable}} + if (Math.random() >= {{exclude_probability}}) { + obj['{{name}}'] = {{fake_func.name}}({{fake_func.args}}); + } + {{else}} + obj['{{name}}'] = {{fake_func.name}}({{fake_func.args}}); + {{/if}} + {{/each}} + + return obj +} \ No newline at end of file diff --git a/assets/templates/node/express/service-calls/service-calls.mgt b/assets/templates/node/express/service-calls/service-calls.mgt new file mode 100644 index 0000000..e01512a --- /dev/null +++ b/assets/templates/node/express/service-calls/service-calls.mgt @@ -0,0 +1,30 @@ +import { faker } from '@faker-js/faker'; + +function getRandomInt(min, max) { + min = Math.ceil(min); + max = Math.floor(max); + return Math.floor(Math.random() * (max - min + 1)) + min; +} + +faker.seed(getRandomInt(0, 9999)); + +{{#each object_fake_functions}} +{{>object_fake_function}} + + +{{/each}} +{{#each array_fake_functions}} +{{>array_fake_function}} + + +{{/each}} +{{#each query_data_functions}} +{{>query_data_function}} + + +{{/each}} +{{#each service_call_functions}} +{{>service_call_function}} + + +{{/each}} \ No newline at end of file diff --git a/assets/templates/node/express/service-calls/service_call_function.mgt b/assets/templates/node/express/service-calls/service_call_function.mgt new file mode 100644 index 0000000..6295d29 --- /dev/null +++ b/assets/templates/node/express/service-calls/service_call_function.mgt @@ -0,0 +1,21 @@ +{{#each post_service_calls}} +{{>post_service_call}} + + +{{/each}} +{{#each get_service_calls}} +{{>get_service_call}} + + +{{/each}} + +export async function {{name}}() { + await Promise.all([ + {{#each post_service_calls}} + {{name}}(), + {{/each}} + {{#each get_service_calls}} + {{name}}(), + {{/each}} + ]) +} \ No newline at end of file diff --git a/assets/templates/node/package.mgt b/assets/templates/node/package.mgt new file mode 100644 index 0000000..7594040 --- /dev/null +++ b/assets/templates/node/package.mgt @@ -0,0 +1,11 @@ +{ + "name": "service", + "version": "0.1.0", + "type": "module", + "main": "src/index.js", + "dependencies": { + {{#each dependencies}} + {{this}}{{#if @last}}{{else}},{{/if}} + {{/each}} + } +} diff --git a/creo-lib/src/generator/mod.rs b/creo-lib/src/generator/mod.rs index 343b79a..0587689 100644 --- a/creo-lib/src/generator/mod.rs +++ b/creo-lib/src/generator/mod.rs @@ -1,3 +1,4 @@ pub(crate) mod core; pub mod python; pub mod rust; +pub mod node; diff --git a/creo-lib/src/generator/node/data_type.rs b/creo-lib/src/generator/node/data_type.rs new file mode 100644 index 0000000..59c85e8 --- /dev/null +++ b/creo-lib/src/generator/node/data_type.rs @@ -0,0 +1,45 @@ +use crate::{ + generator::core::{self, LanguageDataType}, +}; + +pub struct DataTypeMapper; + +impl core::DataTypeMapper for DataTypeMapper { + fn get_string_type(&self) -> &'static str { + "String" + } + + fn get_date_type(&self) -> LanguageDataType { + LanguageDataType { + type_name: "Date".into(), + import: None, + } + } + + fn get_date_time_type(&self) -> LanguageDataType { + LanguageDataType { + type_name: "Date".into(), + import: None, + } + } + + fn get_floating_point_number_type(&self) -> &'static str { + "Number" + } + + fn get_double_type(&self) -> &'static str { + "Number" + } + + fn get_signed_32_bit_integer_type(&self) -> &'static str { + "Number" + } + + fn get_signed_64_bit_integer_type(&self) -> &'static str { + "Number" + } + + fn get_boolean_type(&self) -> &'static str { + "Boolean" + } +} diff --git a/creo-lib/src/generator/node/file_name.rs b/creo-lib/src/generator/node/file_name.rs new file mode 100644 index 0000000..da1cc2e --- /dev/null +++ b/creo-lib/src/generator/node/file_name.rs @@ -0,0 +1,26 @@ +use crate::generator::core::{self, FileName}; + +pub struct FileNameGenerator; + +impl core::FileNameGenerator for FileNameGenerator { + fn generate_router_file_name(&self) -> FileName { + FileName { + path: "src/router", + extension: "js", + } + } + + fn generate_service_call_file_name(&self) -> FileName { + FileName { + path: "src/service-calls", + extension: "js", + } + } + + fn generate_main_file_name(&self) -> FileName { + FileName { + path: "src/index", + extension: "js", + } + } +} diff --git a/creo-lib/src/generator/node/frameworks/express.rs b/creo-lib/src/generator/node/frameworks/express.rs new file mode 100644 index 0000000..e8cfe76 --- /dev/null +++ b/creo-lib/src/generator/node/frameworks/express.rs @@ -0,0 +1,101 @@ +use crate::template; + +pub const DOCKER_ENTRYPOINT: &str = r#"[ "node", "src/index.js" ]"#; + +pub struct Faker; + +impl template::Fakeable for Faker { + fn get_string_fake(&self, string_validation: &openapiv3::StringType) -> template::FakeFunction { + template::FakeFunction::new( + "faker.string.alphanumeric".into(), + format!( + "{{ length: {{ min: {}, max: {} }} }}", + string_validation.min_length.unwrap_or_default(), + string_validation.max_length.unwrap_or(20) + ), + ) + } + + fn get_number_fake(&self, number_validation: &openapiv3::NumberType) -> template::FakeFunction { + template::FakeFunction::new( + "faker.number.float".into(), + format!( + " {{ min: {}, max: {} }}", + number_validation.minimum.unwrap_or_default(), + number_validation.maximum.unwrap_or(5000.0) + ), + ) + } + + fn get_integer_fake( + &self, + integer_validation: &openapiv3::IntegerType, + ) -> template::FakeFunction { + template::FakeFunction::new( + "faker.number.int".into(), + format!( + " {{ min: {}, max: {} }}", + integer_validation.minimum.unwrap_or_default(), + integer_validation.maximum.unwrap_or(9999) + ), + ) + } + + fn get_object_fake(&self, function_name: &str) -> template::FakeFunction { + template::FakeFunction::new(function_name.into(), String::new()) + } + + fn get_array_fake(&self, function_name: &str) -> template::FakeFunction { + template::FakeFunction::new(function_name.into(), String::new()) + } + + fn get_boolean_fake( + &self, + _boolean_validation: &openapiv3::BooleanType, + ) -> template::FakeFunction { + template::FakeFunction::new("faker.datatype.boolean".into(), String::new()) + } +} + +pub struct RouterGenerator; + +impl template::RouterGenerator for RouterGenerator { + fn create_router_template(&self) -> template::RouterTemplate { + template::RouterTemplate { + template_dir: "node/express/router", + root_template_name: "router", + } + } +} + +pub struct ServiceCallGenerator; + +impl template::ServiceCallGenerator for ServiceCallGenerator { + fn create_service_call_template(&self) -> template::ServiceCallTemplate { + template::ServiceCallTemplate { + template_dir: "node/express/service-calls", + root_template_name: "service-calls", + } + } +} + +pub struct MainGenerator; + +impl template::MainGenerator for MainGenerator { + fn create_main_template(&self) -> template::MainTemplate { + template::MainTemplate { + template_dir: "node/express", + root_template_name: "index", + auxiliry_template_names: &[] + } + } +} + +pub fn get_framework_dependencies() -> Vec<&'static str> { + vec![ + r#""express": "^5""#, + r#""@faker-js/faker": "^9""#, + r#""body-parser": "^2""#, + r#""joi": "^17""#, + ] +} diff --git a/creo-lib/src/generator/node/frameworks/mod.rs b/creo-lib/src/generator/node/frameworks/mod.rs new file mode 100644 index 0000000..2b39729 --- /dev/null +++ b/creo-lib/src/generator/node/frameworks/mod.rs @@ -0,0 +1,50 @@ +use rand_derive::Rand; + +use crate::{generator::core::FrameworkGenerator, template}; + +pub mod express; + +#[derive(Rand)] +pub enum Frameworks { + Express, +} + +use Frameworks::*; + +impl FrameworkGenerator for Frameworks { + fn to_faker(&self) -> &dyn template::Fakeable { + match self { + Express => &express::Faker, + } + } + + fn to_router_generator(&self) -> &dyn template::RouterGenerator { + match self { + Express => &express::RouterGenerator, + } + } + + fn to_service_calls_generator(&self) -> &dyn template::ServiceCallGenerator { + match self { + Express => &express::ServiceCallGenerator, + } + } + + fn to_main_generator(&self) -> &dyn template::MainGenerator { + match self { + Express => &express::MainGenerator, + } + } + + fn get_framework_requirements(&self) -> Vec<&'static str> { + match self { + Express => express::get_framework_dependencies(), + } + } + + fn get_docker_entrypoint(&self) -> &'static str { + match self { + Express => express::DOCKER_ENTRYPOINT, + } + } +} diff --git a/creo-lib/src/generator/node/local_deps.rs b/creo-lib/src/generator/node/local_deps.rs new file mode 100644 index 0000000..0f3acc6 --- /dev/null +++ b/creo-lib/src/generator/node/local_deps.rs @@ -0,0 +1,26 @@ +pub fn get_local_handler_dependencies( + lib_dir: impl AsRef, +) -> std::io::Result> { + let mut deps = Vec::default(); + for entry in lib_dir.as_ref().read_dir()? { + let entry = entry?; + let ft = entry.file_type()?; + if ft.is_dir() { + deps.push(format!( + "\"{}\": \"./lib/{}\"", + entry + .file_name() + .to_str() + .expect("directory name should be valid UTF-8"), + entry + .file_name() + .to_str() + .expect("directory name should be valid UTF-8"), + )) + } else { + log::debug!("Skipping entry {}", entry.path().display()); + } + } + + Ok(deps) +} diff --git a/creo-lib/src/generator/node/mod.rs b/creo-lib/src/generator/node/mod.rs new file mode 100644 index 0000000..7971d1c --- /dev/null +++ b/creo-lib/src/generator/node/mod.rs @@ -0,0 +1,15 @@ +mod data_type; +mod file_name; +mod frameworks; +mod local_deps; +mod symbol; + +pub use data_type::DataTypeMapper; +pub use file_name::FileNameGenerator; +pub use frameworks::Frameworks; +pub use local_deps::get_local_handler_dependencies; +pub use symbol::SymbolGenerator; + +pub const DOCKERFILE_TEMPLATE_PATH: &str = "node/Dockerfile.mgt"; +pub const DEPENDENCY_FILE_NAME: &str = "package.json"; +pub const DEPENDENCY_FILE_TEMPLATE_PATH: &str = "node/package.mgt"; diff --git a/creo-lib/src/generator/node/symbol.rs b/creo-lib/src/generator/node/symbol.rs new file mode 100644 index 0000000..4ee3467 --- /dev/null +++ b/creo-lib/src/generator/node/symbol.rs @@ -0,0 +1,81 @@ +use crate::generator::core; + +pub struct SymbolGenerator; + +fn capitalize_first(s: &str) -> String { + let mut chars = s.chars(); + match chars.next() { + Some(first) => first.to_uppercase().collect::() + chars.as_str(), + None => String::new(), + } +} + +impl core::SymbolGenerator for SymbolGenerator { + fn generate_array_item_function_name(&self, name: &str) -> String { + format!("{}Item", name) + } + + fn generate_object_property_function_name(&self, name: &str, prop_name: &str) -> String { + format!("{}Property{}", name.to_lowercase(), capitalize_first(prop_name)) + } + + fn generate_service_calls_function_name( + &self, + endpoint: crate::graph::EndpointIndex, + ) -> String { + format!("serviceCallsEndpoint{}", endpoint.0) + } + + fn generate_service_call_function_import( + &self, + file_name: &str, + function_name: &str, + ) -> String { + let split = file_name.rsplit_once('/'); + let file_name = if let Some(split) = split { + split.1 + } else { + file_name + }; + format!("import {{ {} }} from './{}.js'", function_name, file_name) + } + + fn generate_individual_service_call_function_name( + &self, + call: crate::application::ServiceCallEdge, + ) -> String { + format!( + "serviceCallEndpoint{}ToEndpoint{}", + call.source.0, call.target.0 + ) + } + + fn generate_operation_function_name(&self, endpoint: crate::graph::EndpointIndex) -> String { + format!("operationEndpoint{}", endpoint.0) + } + + fn generate_handler_function_import(&self, import_path: &str, function_name: &str) -> String { + format!("import {{ {} }} from '{}'", function_name, import_path) + } + + fn generate_query_data_function_name( + &self, + service_call: crate::application::ServiceCallEdge, + ) -> String { + format!( + "queryDataCallEndpoint{}ToEndpoint{}", + service_call.source.0, service_call.target.0 + ) + } + + fn generate_parameter_function_name( + &self, + service_call: crate::application::ServiceCallEdge, + param_name: &str, + ) -> String { + format!( + "callEndpoint{}ToEndpoint{}Param{}", + service_call.source.0, service_call.target.0, param_name + ) + } +} diff --git a/creo-lib/src/programming_language/data_type.rs b/creo-lib/src/programming_language/data_type.rs index 26f0431..1a803ff 100644 --- a/creo-lib/src/programming_language/data_type.rs +++ b/creo-lib/src/programming_language/data_type.rs @@ -6,6 +6,7 @@ impl ProgrammingLanguage { match self { Python(_) => &generator::python::DataTypeMapper, Rust(_) => &generator::rust::DataTypeMapper, + Node(_) => &generator::node::DataTypeMapper } } } diff --git a/creo-lib/src/programming_language/dependency.rs b/creo-lib/src/programming_language/dependency.rs index 361da9b..b252c9b 100644 --- a/creo-lib/src/programming_language/dependency.rs +++ b/creo-lib/src/programming_language/dependency.rs @@ -9,6 +9,7 @@ impl ProgrammingLanguage { match self { Python(_) => generator::python::get_local_handler_dependencies(lib_dir), Rust(_) => generator::rust::get_local_handler_dependencies(lib_dir), + Node(_) => generator::node::get_local_handler_dependencies(lib_dir) } } @@ -16,6 +17,7 @@ impl ProgrammingLanguage { match self { Python(_) => generator::python::DEPENDENCY_FILE_NAME, Rust(_) => generator::rust::DEPENDENCY_FILE_NAME, + Node(_) => generator::node::DEPENDENCY_FILE_NAME, } } @@ -23,6 +25,8 @@ impl ProgrammingLanguage { match self { Python(_) => generator::python::DEPENDENCY_FILE_TEMPLATE_PATH, Rust(_) => generator::rust::DEPENDENCY_FILE_TEMPLATE_PATH, + Node(_) => generator::node::DEPENDENCY_FILE_TEMPLATE_PATH, } } } + diff --git a/creo-lib/src/programming_language/docker.rs b/creo-lib/src/programming_language/docker.rs index 3bf5228..5eb8a73 100644 --- a/creo-lib/src/programming_language/docker.rs +++ b/creo-lib/src/programming_language/docker.rs @@ -6,6 +6,7 @@ impl ProgrammingLanguage { match self { Python(_) => generator::python::DOCKERFILE_TEMPLATE_PATH, Rust(_) => generator::rust::DOCKERFILE_TEMPLATE_PATH, + Node(_) => generator::node::DOCKERFILE_TEMPLATE_PATH, } } } diff --git a/creo-lib/src/programming_language/file.rs b/creo-lib/src/programming_language/file.rs index 624e609..496ff2a 100644 --- a/creo-lib/src/programming_language/file.rs +++ b/creo-lib/src/programming_language/file.rs @@ -6,6 +6,7 @@ impl ProgrammingLanguage { match self { Python(_) => &generator::python::FileNameGenerator, Rust(_) => &generator::rust::FileNameGenerator, + Node(_) => &generator::node::FileNameGenerator, } } } diff --git a/creo-lib/src/programming_language/mod.rs b/creo-lib/src/programming_language/mod.rs index 9daebdb..0054745 100644 --- a/creo-lib/src/programming_language/mod.rs +++ b/creo-lib/src/programming_language/mod.rs @@ -11,6 +11,7 @@ use std::str::FromStr; pub enum ProgrammingLanguage { Python(usize), Rust(usize), + Node(usize), } use ProgrammingLanguage::*; @@ -22,6 +23,7 @@ impl ProgrammingLanguage { match self { Python(_) => "python", Rust(_) => "rust", + Node(_) => "node", } } @@ -30,6 +32,7 @@ impl ProgrammingLanguage { match self { Python(f) => *f, Rust(f) => *f, + Node(f) => *f, } } } @@ -39,6 +42,7 @@ impl std::fmt::Display for ProgrammingLanguage { match self { Python(_) => f.write_str("Python"), Rust(_) => f.write_str("Rust"), + Node(_) => f.write_str("Node.js"), } } } @@ -51,13 +55,28 @@ impl FromStr for ProgrammingLanguage { /// `:` should be the fractional weight value. Otherwise, the entire input represents the /// programming language name. fn from_str(s: &str) -> Result { - let (name, fraction) = s.split_once(':').unwrap_or((s, "1")); - - let fraction = fraction.parse::().map_err(|e| e.to_string())?; - - match name { - "python" => Ok(Python(fraction)), - "rust" => Ok(Rust(fraction)), + match s.split_once(':') { + Some(("python", fraction)) => { + return Ok(Python( + fraction.parse::().map_err(|err| err.to_string())?, + )) + } + Some(("rust", fraction)) => { + return Ok(Rust( + fraction.parse::().map_err(|err| err.to_string())?, + )) + } + Some(("node", fraction)) => { + return Ok(Node( + fraction.parse::().map_err(|err| err.to_string())?, + )) + } + _ => (), + } + match s { + "python" => Ok(Python(1)), + "rust" => Ok(Rust(1)), + "node" => Ok(Node(1)), _ => Err(format!("unknown programming language {}", s)), } } diff --git a/creo-lib/src/programming_language/random.rs b/creo-lib/src/programming_language/random.rs index d465a71..601b1a0 100644 --- a/creo-lib/src/programming_language/random.rs +++ b/creo-lib/src/programming_language/random.rs @@ -9,6 +9,7 @@ impl ProgrammingLanguage { match self { Python(_) => Box::new(rng.gen::()), Rust(_) => Box::new(rng.gen::()), + Node(_) => Box::new(rng.gen::()), } } } diff --git a/creo-lib/src/programming_language/symbol.rs b/creo-lib/src/programming_language/symbol.rs index 2975d71..9087040 100644 --- a/creo-lib/src/programming_language/symbol.rs +++ b/creo-lib/src/programming_language/symbol.rs @@ -6,6 +6,7 @@ impl ProgrammingLanguage { match self { Python(_) => &generator::python::SymbolGenerator, Rust(_) => &generator::rust::SymbolGenerator, + Node(_) => &generator::node::SymbolGenerator, } } } diff --git a/creo-lib/src/programming_lanuage/file.rs b/creo-lib/src/programming_lanuage/file.rs new file mode 100644 index 0000000..78bec9a --- /dev/null +++ b/creo-lib/src/programming_lanuage/file.rs @@ -0,0 +1,12 @@ +use super::ProgrammingLanguage::{self, *}; +use crate::generator::{self, core::FileNameGenerator}; + +impl ProgrammingLanguage { + pub(crate) fn as_file_name_generator(&self) -> Box { + match self { + Python(_) => Box::new(generator::python::FileNameGenerator), + Rust(_) => Box::new(generator::rust::FileNameGenerator), + Node(_) => Box::new(generator::node::FileNameGenerator), + } + } +}