1.3.24 • Published 4 months ago

jwero-backend-helpers v1.3.24

Weekly downloads
-
License
ISC
Repository
-
Last release
4 months ago

jwero-backend-helpers

Table of Contents

installation

npm install jwero-backend-helpers

Utility functions

Remove Duplicates in an array

removeDuplicates utility function removes duplicates from the array and return the array

Example usage

const removeDuplicates = require('./src/removeDuplicates');

const arr = [1, 2, 3, 1, 2, 4, 5, 6, 5];
console.log(removeDuplicates(arr));

Convert Object to an Array

converts an object to an array based on a given structure, ensuring that data types match the specified structure.

Example usage

const structure = {
  name: { data_types: ["string"] },
  age: { data_types: ["number"] },
  isActive: { data_types: ["boolean"] },
};

const dataObject = {
  name: "John Doe",
  age: 30,
  isActive: true,
};

const arrayResult = convertObjectToArrayByStructure(structure, dataObject);

console.log(arrayResult);

Remove Falsy Values from Object

The removeFalsyValuesFromObj function removes falsy values (e.g., false, null, 0, "", undefined, and NaN) from a JavaScript object or array, and returns the cleaned object or array.

Example usage

const removeFalsyValuesFromObj = require('./src/javascript/object/index');

const obj = {
  a: 1,
  b: null,
  c: false,
  d: "",
  e: {
    f: "value",
    g: 0,
    h: undefined
  }
};

console.log(removeFalsyValuesFromObj(obj));
// Output: { a: 1, e: { f: "value" } }

Integrations

MSG91

OTP Functions using MSG91 API

This repository contains functions for sending and verifying OTP (One-Time Password) using the MSG91 OTP API.

Functions

sendMSG91OTPTemplate(params)

This function sends an OTP to a mobile number using the MSG91 OTP API.

Parameters:

  • template_id: The template ID of the OTP message.
  • mobile: The mobile number to which OTP will be sent.
  • otp: The OTP (One-Time Password) to be sent.
  • authkey: The authentication key for accessing the MSG91 API.

Returns:

  • A promise that resolves with the response data from the API.

Example Usage

const { sendMSG91OTPTemplate, verifyMSG91OTPTemplate } = require('./src/integrations/msg91/otp/index');

// Sending OTP
sendMSG91OTPTemplate({
  template_id: "your_template_id",
  mobile: "recipient_mobile_number",
  otp: "generated_otp",
  authkey: "your_auth_key",
}).then(response => {
  console.log("OTP Sent:", response);
}).catch(error => {
  console.error("Error sending OTP:", error);
});

verifyMSG91OTPTemplate(params)

This function verifies an OTP for a mobile number using the MSG91 OTP API.

Parameters:

  • otp: The OTP (One-Time Password) to be verified.
  • mobile: The mobile number for which OTP was sent.
  • authkey: The authentication key for accessing the MSG91 API.

Returns:

  • A promise that resolves with the verification result. If an error occurs, it returns false.

Example Usage

const { sendMSG91OTPTemplate, verifyMSG91OTPTemplate } = require("'./src/integrations/msg91/otp/index'");

// Verifying OTP
verifyMSG91OTPTemplate({
  otp: "entered_otp",
  mobile: "recipient_mobile_number",
  authkey: "your_auth_key",
}).then(response => {
  console.log("OTP Verification Result:", response);
}).catch(error => {
  console.error("Error verifying OTP:", error);
});

Order functions

This repository contains functions for creating, updating and fetching order

Functions

createJweroOrder(params)

Creates an order in the JWERO system.

Parameters:

  • customer: Details of the customer placing the order.
  • line_items: Array of line items in the order.
  • meta_data: Additional metadata for the order.
  • key: The API key for accessing the JWERO API.
  • secret: The API secret for accessing the JWERO API.
  • website: The website URL where the JWERO API is hosted.
  • token: The authentication token for accessing the JWERO API.
  • status: The status of the order (default: "pending").

Returns:

  • The created order object if successful, otherwise false.

Example Usage

const { createJweroOrder } = require('./src/orders/old/index');

// Creating an order
createJweroOrder({
  customer: {
    // Customer details
  },
  line_items: [
    // Line items
  ],
  meta_data: [
    // Additional metadata
  ],
  key: "your_api_key",
  secret: "your_api_secret",
  website: "your_website_url",
  token: "your_authentication_token",
  status: "pending"
}).then(order => {
  console.log("Order created:", order);
}).catch(error => {
  console.error("Error creating order:", error);
});

updateJweroOrder(params)

Updates an existing order in the JWERO system.

Parameters:

  • payload: The updated payload containing the changes to be applied to the order.
  • website: The website URL where the JWERO API is hosted.
  • id: The ID of the order to be updated.
  • token: The authentication token for accessing the JWERO API.
  • key: The API key for accessing the JWERO API.
  • secret: The API secret for accessing the JWERO API.

Returns:

  • The updated order object if successful, otherwise false.

Example Usage

const { updateJweroOrder } = require('./src/orders/old/index');

// Updating an order
updateJweroOrder({
  payload: {
    // Updated order payload
  },
  website: "your_website_url",
  id: "order_id",
  token: "your_authentication_token",
  key: "your_api_key",
  secret: "your_api_secret"
}).then(updatedOrder => {
  console.log("Order updated:", updatedOrder);
}).catch(error => {
  console.error("Error updating order:", error);
});

fetchJweroOrders(params)

Fetches orders from the JWERO system.

Parameters:

  • website: The website URL where the JWERO API is hosted.
  • token: The authentication token for accessing the JWERO API.
  • params: Additional parameters for the request.
  • key: The API key for accessing the JWERO API.
  • secret: The API secret for accessing the JWERO API.
  • payload: Payload data for the request.

Returns:

  • The fetched orders if successful, otherwise false.

Example Usage

const { fetchJweroOrders } = require('./src/orders/old/index');

// Fetching orders
fetchJweroOrders({
  website: "https://yourwebsite.com",
  token: "your_authentication_token",
  params: {
    per_page: 5
  },
  key: "your_api_key",
  secret: "your_api_secret"
}).then(orders => {
  console.log("Fetched orders:", orders);
}).catch(error => {
  console.error("Error fetching orders:", error);
});

Helpers

Socials

Emails

Email Functions

sendEmailFromJwero(params)

Function to send emails using Nodemailer

Parameters:

  • to: The recipient's email address or an array of objects containing email addresses.
  • subject: The subject of the email.
  • html: The HTML content of the email.
  • text: The plain text content of the email.
  • config: Custom configuration for the Nodemailer transporter.
  • removeDefaultConfig: If true, the default configuration will be removed.

Returns:

  • A promise that resolves with the response data from Nodemailer, either a single response object or an array of response objects if multiple recipients are provided.

Example Usage

const { sendEmailFromJwero } = require('./src/helpers/socials/email/index');

// Sending email
sendEmailFromJwero({
  to: "recipient@example.com",
  subject: "Test Email",
  html: "<p>This is a test email.</p>",
  text: "This is a test email.",
}).then(response => {
  console.log("Email sent successfully:", response);
}).catch(error => {
  console.error("Error sending email:", error);
});

sendEmailFromZeptoMail(params)

A function to send emails using ZeptoMail.

Parameters:

  • mail_template_key: The template key for the email template to be used.
  • variables: Variables to be merged into the email template.
  • emails: Array of recipient objects containing email addresses and names.

Returns:

  • A promise that resolves when the email has been sent.

Example Usage

const { sendEmailFromZeptoMail } = require('./src/helpers/socials/email/zepto_mail');

// Sending email
sendEmailFromZeptoMail({
  mail_template_key: "order_confirmation",
  variables: {
    name: "John Doe",
    order_id: "12345"
  },
  emails: [
    { email: "recipient1@example.com", name: "Recipient 1" },
    { email: "recipient2@example.com", name: "Recipient 2" }
  ]
}).then(() => {
  console.log("Email sent successfully");
}).catch(error => {
  console.error("Error sending email:", error);
});

MySQL Functions

Table

mySQL Functions

getRowsFromTableDynamic(params)

Retrieves rows from a specified table with dynamic conditions and optional counting.

Parameters:

  • db (Object): The database connection object.
  • tableName (string): The name of the table to query.
  • conditions (Object, optional): Conditions to filter the rows. Default is {}.
  • run_again (boolean, optional): Flag to determine if the function should retry on error Default is true.
  • createTableQueries (string[]): Array of SQL queries to create missing tables.
  • metaTableName (string): The name of the meta table for additional transformations.
  • parentCondition (string, optional): The parent condition for combining conditions (e.g., "AND", "OR"). Default is "AND".
  • order_by (string, optional): The order by clause for sorting the results.
  • getCount (boolean, optional): If true, a count of the total rows is also returned.
  • additionalWhereQuery (string, optional): Additional custom WHERE clause for the query.
  • metaForeignKey (string, optional): Foreign key for meta table transformations.

Returns:

  • Promise<Object|Object[]>: A promise that resolves with the queried rows and optionally the total count.

Example Usage

const { getRowsFromTableDynamic } = require('./src/mysql/table/index');

// Example options object
const options = {
    db: yourDatabaseConnection, // Replace with your actual DB connection object
    tableName: 'users',
    conditions: { age: 25 },
    run_again: true,
    createTableQueries: [
        'CREATE TABLE IF NOT EXISTS users (id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(255), age INT)'
    ],
    metaTableName: 'meta_users',
    parentCondition: 'AND',
    order_by: 'age DESC',
    getCount: true,
    additionalWhereQuery: 'status = "active"',
    metaForeignKey: 'user_id'
};

getRowsFromTableDynamic(options)
    .then(result => {
        console.log(result); // { rows: [...], total: 10 }
    })
    .catch(error => {
        console.error(error);
    });

deleteRowsInTableDynamic(params)

Deletes rows from a specified table based on dynamic conditions.

Parameters:

  • db (Object): The database connection object.
  • tableName (string): The name of the table from which to delete rows.
  • conditions (Object): Conditions to determine which rows to delete. Supports arrays for the 'IN' clause.

Returns:

  • Promise<Object>: A promise that resolves with the result of the deletion, including the number of affected rows.

Example Usage

const { deleteRowsInTableDynamic } = require('./src/mysql/table/index');

// Example options object
const options = {
    db: yourDatabaseConnection, // Replace with your actual DB connection object
    tableName: 'users',
    conditions: { id: [1, 2, 3] } // Delete users with IDs 1, 2, and 3
};

deleteRowsInTableDynamic(options)
    .then(result => {
        console.log(result); // { affectedRows: 3 }
    })
    .catch(error => {
        console.error(error);
    });

updateRowsInTableDynamic(params)

Updates rows in a specified table dynamically based on provided row data and optional conditions.

Parameters:

  • db (Object): The database connection object.
  • tableName (string): The name of the table to update.
  • rows (Object[]): Array of rows to update, with each row being an object representing column-value pairs.

  • updateConditions (Object, optional): Optional conditions to determine which rows to update. -createTableQueries (string[]): Array of SQL queries to create missing tables if they do not exist.

  • run_again (boolean, optional): Flag to determine if the function should retry on error. Default is true.

Returns:

  • Promise<Object[]>: A promise that resolves with the updated rows if conditions are provided, otherwise an empty array.

Example Usage

const { updateRowsInTableDynamic } = require('./src/mysql/table/index');

// Example options object
const options = {
    db: yourDatabaseConnection, // Replace with your actual DB connection object
    tableName: 'users',
    rows: [
        { id: 1, name: 'Alice', age: 30 },
        { id: 2, name: 'Bob', age: 35 }
    ],
    updateConditions: {
        id: { data: [1, 2], compare: 'exact' }
    },
    createTableQueries: [
        'CREATE TABLE IF NOT EXISTS users (id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(255), age INT)'
    ],
    run_again: true
};

updateRowsInTableDynamic(options)
    .then(updatedRows => {
        console.log(updatedRows); // [{ id: 1, name: 'Alice', age: 30 }, { id: 2, name: 'Bob', age: 35 }]
    })
    .catch(error => {
        console.error(error);
    });

addRowsInTableDynamic(params)

Adds rows to a specified table dynamically.

Parameters:

  • db (Object): The database connection object.
  • tableName (string): The name of the table to insert rows into.
  • rows (Object[]): Array of rows to insert, with each row being an object representing column-value pairs.
  • createTableQueries (string[]): Array of SQL queries to create missing tables if they do not exist.
  • run_again (boolean, optional): Flag to determine if the function should retry on error. Default is true.

Returns:

  • Promise<Object[]>: A promise that resolves with the added rows.

Example Usage

const { addRowsInTableDynamic } = require('./src/mysql/table/index');

// Example options object
const options = {
    db: yourDatabaseConnection, // Replace with your actual DB connection object
    tableName: 'users',
    rows: [
        { name: 'Charlie', age: 28 },
        { name: 'Dana', age: 22 }
    ],
    createTableQueries: [
        'CREATE TABLE IF NOT EXISTS users (id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(255), age INT)'
    ],
    run_again: true
};

addRowsInTableDynamic(options)
    .then(addedRows => {
        console.log(addedRows); // [{ id: 4, name: 'Charlie', age: 28 }, { id: 5, name: 'Dana', age: 22 }]
    })
    .catch(error => {
        console.error(error);
    });

Auth

authenticateJWT(params)

Middleware to authenticate JSON Web Tokens (JWT)

Parameters

  • req (Object): The request object, which contains information about the HTTP request.
  • res (Object): The response object, which is used to send back the desired HTTP response.
  • next (Function): The next middleware function in the stack. It is called to pass control to the next middleware or route handler.

Example Usage

const express = require('express');
const app = express();
const { authenticateJWT } = require('./src//mysql/auth/index');

app.use(express.json());

// Protect routes with the authenticateJWT middleware
app.get('/protected-route', authenticateJWT, (req, res) => {
  res.json({ message: 'You have accessed a protected route', user: req.user });
});

app.listen(3000, () => {
  console.log('Server is running on port 3000');
});

getDBdetailsFromTable(params)

Retrieves database details from the request based on the provided authentication method

Parameters

-req (Object): The request object, which contains information about the HTTP request.

Example USage

const express = require('express');
const app = express();
const { getDBdetailsFromTable } = require('./src//mysql/auth/index');

app.use(express.json());

// Example route using getDBdetailsFromTable
app.get('/example-route', async (req, res) => {
    try {
        let dbDetails = await getDBdetailsFromTable(req);
        if (dbDetails) {
            res.json({ success: true, data: dbDetails });
        } else {
            res.status(401).json({ success: false, message: "Unauthorized" });
        }
    } catch (error) {
        res.status(500).json({ success: false, message: "Internal Server Error" });
    }
});

app.listen(3000, () => {
    console.log('Server is running on port 3000');
});

getDBInfoFromToken(params)

Retrieves database information from a given JWT token.

Parameters

  • token (string): The JWT token to be decoded and verified.

Example Usage

const express = require('express');
const app = express();
const { getDBInfoFromToken } = require('./path/to/your/middleware');

app.use(express.json());

app.get('/db-info', async (req, res) => {
    const token = req.headers.authorization;
    if (!token) {
        return res.status(400).json({ success: false, message: "Token is required" });
    }

    try {
        const dbInfo = await getDBInfoFromToken(token);
        if (typeof dbInfo === 'string') {
            return res.status(400).json({ success: false, message: dbInfo });
        }
        res.json({ success: true, data: dbInfo });
    } catch (error) {
        res.status(500).json({ success: false, message: "Internal Server Error" });
    }
});

app.listen(3000, () => {
    console.log('Server is running on port 3000');
});

Row

isValidROwStructure(params)

checks if the rows provided adhere to the structure defined by a create table query. It validates the data types of the columns in the rows against the corresponding column types in the table schema.

Parameters

  • options: Object - Options for validating the row structure.
  • rows: Array - An array of objects representing rows of data to be validated.
  • createTableQuery: String - The SQL query used to create the table structure.

Returns

  • Object:
  • valid: Boolean - Indicates whether the rows adhere to the table structure.
  • errors: Array - An array containing error messages for rows that do not match the table structure.

Example Usage

const tableSchemaQuery = `
CREATE TABLE IF NOT EXISTS users (
  id INT AUTO_INCREMENT PRIMARY KEY,
  name VARCHAR(255),
  age INT,
  email VARCHAR(255),
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)`;

const rows = [
  { id: 1, name: "John Doe", age: 30, email: "john@example.com" },
  { id: 2, name: "Jane Smith", age: "25", email: "jane@example.com" }, // Incorrect data type for age
];

const validationResults = isValidRowStructure({ rows, createTableQuery: tableSchemaQuery });

console.log(validationResults);

Utils

encryptWithAES256(params)

This function encrypts a text using AES-128-CBC encryption algorithm.

Parameters

  • text (string): The text to be encrypted.

Example Usage

const crypto = require('./src/mysql/utils/index');

// Encrypting a text
const textToEncrypt = "This is a secret message.";
const encryptedText = encryptWithAES256(textToEncrypt);
console.log("Encrypted Text:", encryptedText);

decryptWithAES256(params)

Example Usage

const crypto = require('./src/mysql/utils/index');

// Decrypting an encrypted text
const encryptedTextToDecrypt = encryptedText; // Use the encrypted text from the previous step
const decryptedText = decryptWithAES256(encryptedTextToDecrypt);
console.log("Decrypted Text:", decryptedText);

Media

S3 functions

uploadS3File(params)

Uploads a file to an S3 bucket.

Parameters:

  • folder_path (string): The path in the S3 bucket where the file will be uploaded.
  • req (Object): The request object containing the file to upload.

Returns:

  • Promise<Object>: A promise that resolves with information about the uploaded file.

Example Usage

const { uploadS3File } = require('./src/media/s3/index');
const formidable = require('formidable');

const options = {
    folder_path: 'your/s3/folder/path',
    req: yourHTTPRequestObject // Replace with your actual HTTP request object
};

uploadS3File(options)
    .then(uploadedFileInfo => {
        console.log(uploadedFileInfo); // Information about the uploaded file
    })
    .catch(error => {
        console.error(error);
    });

getMediaFiles(params)

Retrieves a list of media files from an S3 bucket.

Parameters:

  • props (Object): Properties for filtering the media files
  • page (number, optional): The page number for pagination. Default is 1.
  • per_page (number, optional): The number of items per page. Default is 100.
  • folder_path (string, optional): The folder path in the S3 bucket to retrieve files from. Default is an empty string.

Returns:

  • Promise<Object[]>: A promise that resolves with an array of media file objects.

Example Usage

const { getMediaFiles } = require('./src/media/s3/index');

const props = {
    page: 1,
    per_page: 50,
    folder_path: 'your/s3/folder/path'
};

getMediaFiles(props)
    .then(mediaFiles => {
        console.log(mediaFiles); // Array of media file objects
    })
    .catch(error => {
        console.error(error);
    });

uploadS3Link(params)

Uploads a file from a URL to an S3 bucket.

Parameters:

  • options (Object): Options for uploading the file
  • fileUrl (string): The URL of the file to upload.
  • folder_path (string): The path in the S3 bucket where the file will be uploaded.
  • whatsapp_access_token (string, optional): The access token for WhatsApp API.
  • access_token (string, optional): The access token for general use.
  • file_name (string, optional): The name to use for the uploaded file.
  • file_extension (string, optional): The extension of the uploaded file.

Returns:

  • Promise<Object>: A promise that resolves with information about the uploaded file.

Example Usage

const { uploadS3Link } = require('./src/media/s3/index');

const options = {
    fileUrl: 'https://example.com/file.jpg',
    folder_path: 'your/s3/folder/path',
    access_token: 'yourAccessToken', // Optional
    file_name: 'example', // Optional
    file_extension: 'jpg' // Optional
};

uploadS3Link(options)
    .then(uploadedFileInfo => {
        console.log(uploadedFileInfo); // Information about the uploaded file
    })
    .catch(error => {
        console.error(error);
    });

getFolderNames(params)

Retrieves a list of folder names from an S3 bucket.

Parameters:

  • props (Object): Properties for filtering the folder names.
  • folder_path (string, optional): The folder path in the S3 bucket to retrieve folder names from. Default is an empty string.

Returns:

  • Promise<Object[]>: A promise that resolves with an array of folder details.

Example Usage

const { getFolderNames } = require('./src/media/s3/index');

const props = {
    folder_path: 'your/s3/folder/path'
};

getFolderNames(props)
    .then(folderDetails => {
        console.log(folderDetails); // Array of folder details
    })
    .catch(error => {
        console.error(error);
    });

createNewFolder(params)

Creates a new folder in an S3 bucket.

Parameters:

  • props (Object): Properties for creating the new folder.
  • folder_name (string, optional): The name of the new folder to create. Default is an empty string.

Returns:

  • Promise<Object>: A promise that resolves with information about the new folder creation.

Example Usage

const { createNewFolder } = require('./src/media/s3/index');

const props = {
    folder_name: 'new-folder-name'
};

createNewFolder(props)
    .then(response => {
        console.log(response); // Information about the new folder creation
    })
    .catch(error => {
        console.error(error);
    });

uploadS3FileFromPath(params)

Uploads a file from a local path to an S3 bucket.

Parameters:

  • options (Object): Options for uploading the file.
  • folder_path (string): The path in the S3 bucket where the file will be uploaded.
  • file_path (string): The local file path of the file to upload.

Returns:

  • Promise<Object>: A promise that resolves with information about the uploaded file.

Example Usage

const { uploadS3FileFromPath } = require('./src/media/s3/index');

const options = {
    folder_path: 'your/s3/folder/path',
    file_path: '/local/path/to/your/file.jpg'
};

uploadS3FileFromPath(options)
    .then(uploadedFileInfo => {
        console.log(uploadedFileInfo); // Information about the uploaded file
    })
    .catch(error => {
        console.error(error);
    });

Crm

function

getJweroCustomerPhoneNumber(params)

Retrieves the customer's phone number for use in Jwero.

Parameters:

-customer: Object - The customer object containing metadata and billing information.

Returns

  • Object:
  • success: Boolean - Indicates the success of the operation.
  • data: String - The formatted phone number.
  • dialcode: String - The dial code of the phone number.
  • phone: String - The extracted phone number.

Example Usage

const customerData = {
  meta_data: [
    { key: "whatsapp", value: "+123456789" },
    { key: "dialcode_whatsapp", value: "+1" },
    // other meta data
  ],
  billing: {
    phone: "+987654321",
  },
  // other customer data
};

const phoneNumberResult = getJweroCustomerPhoneNumber({ customer: customerData });

console.log(phoneNumberResult);

chat_users

utils-function

getPhoneNumberFromChatUser(params)

This function getPhoneNumberFromChatUser retrieves the user's phone number from various fields in the user object and returns a formatted response containing the phone number data.

Parameters:

  • options: Object - Options for retrieving the phone number.
  • user: Object - The user object containing WhatsApp, number, dialcode_mobile, dialcode_whatsapp, and platform ID information.

Returns

  • Object:
  • success: Boolean - Indicates the success of the operation.
  • data: String - The formatted phone number.
  • dialcode: String - The dial code of the phone number.
  • phone: String - The extracted phone number.

Example Usage

const userData = {
  whatsapp: "+123456789",
  number: "+987654321",
  dialcode_mobile: "+1",
  platform_id: "platformId",
  // other user data
};

const phoneNumberResult = getPhoneNumberFromChatUser({ user: userData });

console.log(phoneNumberResult);
1.3.10

7 months ago

1.3.13

7 months ago

1.3.14

7 months ago

1.3.11

7 months ago

1.3.12

7 months ago

1.3.17

6 months ago

1.3.18

5 months ago

1.3.15

7 months ago

1.3.16

6 months ago

1.3.19

5 months ago

1.3.20

5 months ago

1.3.21

5 months ago

1.3.9

7 months ago

1.3.8

7 months ago

1.3.24

4 months ago

1.3.22

5 months ago

1.3.23

4 months ago

1.3.7

8 months ago

1.3.6

8 months ago

1.3.5

8 months ago

1.3.4

8 months ago

1.3.3

8 months ago

1.3.2

8 months ago

1.3.1

9 months ago

1.3.0

9 months ago

1.2.3

11 months ago

1.2.2

11 months ago

1.2.1

11 months ago

1.2.0

11 months ago

1.1.0

11 months ago

1.0.23

1 year ago

1.0.22

1 year ago

1.0.21

1 year ago

1.0.20

1 year ago

1.0.19

1 year ago

1.0.18

1 year ago

1.0.17

1 year ago

1.0.16

1 year ago

1.0.15

1 year ago

1.0.14

1 year ago

1.0.13

1 year ago

1.0.11

1 year ago

1.0.10

1 year ago

1.0.9

1 year ago

1.0.8

1 year ago

1.0.7

1 year ago

1.0.6

1 year ago

1.0.5

1 year ago

1.0.4

1 year ago

1.0.3

1 year ago

1.0.2

1 year ago

1.0.0

1 year ago