bapig v2.2.7
BAPIG: Empowering Real-Time Backend Development
Welcome to BAPIG, a powerful backend solution designed to streamline your development process. With over 40 built-in functionalities, BAPIG simplifies CRUD operations, facilitates authentication and encryption, handles communication tasks, and much more. Whether you're crafting a small-scale app or a robust enterprise system, BAPIG empowers you with efficiency and versatility.
BAPIG is built to work flawlessly with Node.JS, Express.JS, Mongoose, and MongoDB, making it a versatile and powerful tool for your development needs. With its efficient and intuitive interface, you can save countless hours of coding and focus on delivering the best possible user experience for your application.
Support Development
If you find my work valuable and would like to support its continued development, consider buying me a coffee! Your support goes a long way in helping me maintain and improve this project.
Thank you for your support!
Key Features
1. Real-Time Communication
BAPIG goes beyond conventional operations, providing real-time capabilities for responsive and dynamic applications.
2. Efficient Integration
Tailored for Node.js, Express.js, Mongoose, and MongoDB, BAPIG's intuitive interface optimizes coding efforts, allowing you to focus on delivering exceptional user experiences.
3. Comprehensive Functionality
From complex data queries to scalable solutions, BAPIG covers a wide spectrum of operations, ensuring adaptability to diverse project requirements.
Contact Information
- Developer Email: hekima.dev@gmail.com
- YouTube Channel: BAPIG on YouTube
- Telegram Group: Join the BAPIG community on Telegram for support and discussions.
- GitHub Repository: Access the sample BAPIG backend server source code on GitHub.
Installation
To install BAPIG in your project, run the following command:
npm install bapig
Usage
Follow these steps to set up and configure your BAPIG project:
Create a New Root Folder:
- Create a new root folder with a name of your choice.
- Open the root folder in your favorite editor.
Organize Your Project Structure:
- Inside the root folder, create the following:
- A folder for holding static files (e.g.,
public
). - A
.env
file for environment variables. package.json
,ts.config
, and any other necessary files.
- A folder for holding static files (e.g.,
- Inside the static files folder, create a new folder for file uploads (e.g.,
uploads
).
- Inside the root folder, create the following:
Create a Project Folder:
- Create a new project folder within the root folder (e.g.,
src
). - This folder will hold all the source code of your project.
- Create a new project folder within the root folder (e.g.,
Organize Mongoose Schema Files:
- Inside the project folder, create a new folder to hold all your Mongoose schema files (e.g.,
models
).
- Inside the project folder, create a new folder to hold all your Mongoose schema files (e.g.,
Configure .env File
Environment Variables:
- Set the environment variables in the
.env
file according to your project's requirements. Refer to the Environment Variables section for a detailed list.
- Set the environment variables in the
Start Developing:
- Your BAPIG project is now configured. Start building your backend application with ease!
For more information on available environment variables, refer to the Environment Variables section below.
Environment Variables
Configure the following environment variables in your .env
file to customize your BAPIG project:
Encryption Configuration
ENCRYPTION_TYPE
: Type of encryption (e.g., hex, base64).ENABLE_ENCRYPTION
: Enable or disable encryption.ENCRYPTION_ALGORITHM
: Encryption algorithm (e.g., aes-256-cbc).INITIALIZATION_VECTOR
: Initialization vector for encryption.ENCRYPTION_KEY
: Encryption key.
Africa's Talking Configuration
AFRICAS_TALKING_API_KEY
: API Key for Africa's Talking integration.AFRICAS_TALKING_SENDERS_ID
: Sender's ID for Africa's Talking.AFRICAS_TALKING_APPLICATION_NAME
: Application name for Africa's Talking.
Email Configuration
SMTP
: SMTP server address.USER_EMAIL_ADDRESS
: User email address for sending emails.EMAIL_PORT_NUMBER
: Email server port number.USER_EMAIL_ADDRESS_PASSWORD
: Password for the user's email address.
Web Push Configuration
WEBPUSH_PUBLIC_KEY
: Public key for Web Push notifications.WEBPUSH_PRIVATE_KEY
: Private key for Web Push notifications.
Other Configuration
PROJECT_FOLDER_NAME
: Folder name for the project (e.g., 'dist').STATIC_FILES_FOLDER_NAME
: Folder name for static files (e.g., 'public').FILE_UPLOADING_FOLDER_NAME
: Folder name for file uploads (e.g., 'uploads').MODELS_FOLDER_NAME_IN_PROJECT_FOLDER
: Folder name for models in the project (e.g., 'models').
Server and Database Configuration
PORT
: Port number for the server.DATABASE_NAME
: Name of the database.NUMBER_OF_CPU_TO_USE
: Number of CPU cores to use.ENVIRONMENT
: Environment setting (e.g., Development, Production).SOCKET_IO_CORS_ORIGIN
: CORS origin for Socket.IO.DATABASE_RETRY_SECONDS
: Seconds towait before retrying database connection.
DATABASE_MAXIMUM_RETRIES
: Maximum number of retries for database connection.DATABASE_URI
: URI for connecting to the database (e.g., MongoDB).SSL_KEY_PATH
: Path to SSL private key.SSL_CERT_PATH
: Path to SSL certificate.
# Encryption Configuration
ENCRYPTION_TYPE = hex # Type of encryption (e.g., hex, base64)
ENABLE_ENCRYPTION = false # Enable or disable encryption
ENCRYPTION_ALGORITHM = aes-256-cbc # Encryption algorithm (e.g., aes-256-cbc)
INITIALIZATION_VECTOR = 2d52550dc714656b # Initialization vector for encryption
ENCRYPTION_KEY = abcdefghijkLMNOPQrstuvwX12345678 # Encryption key
# Africa's Talking Configuration
AFRICAS_TALKING_API_KEY = # API Key for Africa's Talking integration
AFRICAS_TALKING_SENDERS_ID = # Sender's ID for Africa's Talking
AFRICAS_TALKING_APPLICATION_NAME = # Application name for Africa's Talking
# Email Configuration
SMTP = # SMTP server address
USER_EMAIL_ADDRESS = # User email address for sending emails
EMAIL_PORT_NUMBER = 587 # Email server port number
USER_EMAIL_ADDRESS_PASSWORD = # Password for the user's email address
# Web Push Configuration
WEBPUSH_PUBLIC_KEY = # Public key for Web Push notifications
WEBPUSH_PRIVATE_KEY = # Private key for Web Push notifications
# Other Configuration
PROJECT_FOLDER_NAME = dist # Folder name for the project (e.g., 'dist')
STATIC_FILES_FOLDER_NAME = public # Folder name for static files (e.g., 'public')
FILE_UPLOADING_FOLDER_NAME = uploads # Folder name for file uploads (e.g., 'uploads')
MODELS_FOLDER_NAME_IN_PROJECT_FOLDER = models # Folder name for models in the project (e.g., 'models')
# Server and Database Configuration
PORT = 8080 # Port number for the server
DATABASE_NAME = bapig # Name of the database
DATABASE_CONNECTION_POOL_SIZE = 10 # Datababase pool size
DONT_USE_DATABASE_UNDERSCORE_ID = false # Use _id (false) or id (true) in document id's
NUMBER_OF_CPU_TO_USE = 1 # Number of CPU cores to use
ENVIRONMENT = Development # Environment setting (e.g., Development, Production)
SOCKET_IO_CORS_ORIGIN = * # CORS origin for Socket.IO
DATABASE_RETRY_SECONDS = 5 # Seconds to wait before retrying database connection
DATABASE_MAXIMUM_RETRIES = 3 # Maximum number of retries for database connection
DATABASE_URI = mongodb://127.0.0.1:27017 # URI for connecting to the database (e.g., MongoDB)
SSL_KEY_PATH = /etc/letsencrypt/live/your-domain.com/privkey.pem # Path to SSL private key
SSL_CERT_PATH = /etc/letsencrypt/live/your-domain.com/fullchain.pem # Path to SSL certificate
Folder Structure
The project follows a structured organization for better clarity and maintainability. Below is an overview of the folder structure:
ROOT DIRECTORY
|_______node_modules
|
|_______public
| |
| |_____uploads
| |_____"other files and folders in public"
|
|_______src
| |
| |_____models
| |_____index.ts (server file)
| |_____"other files and folders in src"
|
|_______package.json
|_______package-lock.json
|_______.env
|_______tsconfig.json
|_______"other files and folders in root directory"
Note on Database Interaction
When interacting with the database in BAPIG, it is essential to provide the schema, i.e., the database model file name, that you want to use.
Database Relationships and Foreign Keys
If you need to join foreign keys (database relationships) when querying data, consider installing the mongoose-autopopulate
package. Ensure that your database schema is designed according to the recommendations of mongoose-autopopulate
.
Using Sockets in Your Application
To integrate sockets into your application using BAPIG, follow these steps:
- Import the
io
connection instance from the BAPIG package. This allows BAPIG to establish and manage socket connections as needed.
// Example import in your code
const { io } = require('bapig');
BAPIG Response
Whenever a request or operation is made with BAPIG, the response consists of an object {} with two key-value pairs:
success: A boolean value (true or false) indicating the success or failure of the operation.
message: A field that can hold any data type, depending on the operation's success. If successful, it may contain relevant data for your application. If unsuccessful, it may contain an error message or additional information about the failure.
Example JSON Response
{ "success": true, "message": "some success response data" }
{ "success": false, "message": "some false response data" }
Example Encrypted Response
When encryption is enabled in your BAPIG-powered application, the response includes an object {} with a payload key and the encrypted data as the payload value.
{
"payload": "db25f0861631d0f524483aeaf8b5b14c188b7a4cab2905e4f1b57b71460e73e0b54a75814d9c566628f429e5ba86fb8f3332de83a75e27edad624b528cf0bce68c0d09978b356a6fda7f9408338b244b70113e9cf2a5585de0921a9fa8449e7302048727127ebc0162fba6d040cfeff6"
}
Start Server Without Clustering
// Import necessary modules from 'bapig'
import { startServer } from "bapig";
// Initialize your Express application
const app = express();
// Set up your middleware and routes
// Start the server without clustering
startServer(app);
Start With Clustering
To leverage clustering and enhance server performance, you can specify the number of CPUs to use in your environment file.
Create a
.env
file in your project root.Add the following line to specify the number of CPUs:
NUMBER_OF_CPU_TO_USE = 4 // Replace 4 with the desired number of CPUs
Update your code to use clustering:
// Import necessary modules from 'bapig' import { useClustering } from "bapig"; // Initialize your Express application const app = express(); // Set up your middleware and routes // Start the server with clustering useClustering(app);
Sample Entry Point (JavaScript)
// Dependencies
const cors = require("cors");
const { io } = require("bapig");
const express = require("express");
const { router, helpers, startServer, useClustering, fileUpload } = require("bapig");
// Initializing Express application
const application = express();
// Applying Express middleware
application.disable("x-powered-by");
application.use(cors({ origin: "*" }));
application.use(express.json());
application.use(express.static(helpers.staticFilesDirectory));
application.use(fileUpload())
// Setting up API routes
application.use("/api", router);
// Starting the server without clustering
startServer(application);
// Starting the server with clustering
// useClustering(application);
// Setting up Socket.IO
// Note: 'io' is imported from 'bapig'
io?.on("connection", (socket) => {
// Handling incoming messages
socket.on("message", (message) => {
console.log(message);
});
});
Sample Entry Point (TypeScript)
// Importing dependencies
import cors from "cors";
import { Socket } from "socket.io";
import express, { Application } from "express";
import { io, router, helpers, startServer, useClustering, fileUpload } from "bapig";
// Initializing Express application
const application: Application = express();
// Applying Express middleware
application.disable("x-powered-by");
application.use(cors({ origin: "*" }));
application.use(express.json());
application.use(express.static(helpers.staticFilesDirectory));
application.use(fileUpload())
// Setting up API routes
application.use("/api", router);
// Starting the server without clustering
// startServer(application);
// Starting the server with clustering
useClustering(application);
// Setting up Socket.IO
// Note: 'io' is imported from 'bapig'
io?.on("connection", (socket: Socket) => {
// Handling incoming messages
socket.on("message", (message: any) => {
console.log(message);
});
});
Socket.IO Integration
If you are using Socket.IO in your client-side application, you can listen to specific events for CRUD operations. Here's how you can set up Socket.IO events on the client side:
Event: {your-schema}-create
Example: user-create
socket.on('user-create', (createdData) => {
console.log('New user created:', createdData);
// Handle the created data as needed
});
Event: {your-schema}-update
Example: user-update
socket.on('user-update', (updatedData) => {
console.log('New user updated:', updatedData);
// Handle the updated data as needed
});
Event: {your-schema}-delete
Example: user-delete
socket.on('user-delete', (deletedData) => {
console.log('New user deleted:', deletedData);
// Handle the deleted data as needed
});
Routes Overview
Welcome to the documentation for BAPIG routes. Below is a list of available routes, each providing a specific functionality. Click on a route title to jump to its detailed explanation.
Document Operations
GET /:
- Displays the DOCUMENTATION (Offline).
POST /create:
- Creates a single document.
- View Docs
POST /bulk-create:
- Bulk creates documents.
- View Docs
GET /read:
- Reads a single document.
- View Docs
PUT /update:
- Updates a single document.
- View Docs
PUT /bulk-update:
- Bulk updates documents.
- View Docs
DELETE /delete:
- Deletes a single document.
- View Docs
DELETE /bulk-delete:
- Bulk deletes documents.
- View Docs
GET /list:
- Lists documents.
- View Docs
GET /count:
- Counts documents.
- View Docs
GET /list-all:
- Lists all documents.
- View Docs
GET /validate:
- Validates a document.
- View Docs
Authentication and Field Encryption
POST /create-field-encryption:
- Creates field encryption.
- View Docs
POST /authenticate:
- Authenticates.
- View Docs
GET /validate-field-encryption:
- Validates field encryption.
- View Docs
PUT /change-field-encryption:
- Changes field encryption.
- View Docs
File Operations
POST /upload-file:
- Uploads a file.
- View Docs
POST /upload-files:
- Uploads multiple files.
- View Docs
DELETE /delete-file:
- Deletes a file.
- View Docs
DELETE /delete-files:
- Deletes multiple files.
- View Docs
Communication Operations
POST /send-sms:
- Sends SMS.
- View Docs
POST /send-email:
- Sends an email.
- View Docs
GET /generate-otp:
- Generates OTP.
- View Docs
POST /send-otp:
- Sends OTP.
- View Docs
POST /push-notification:
- Push notification.
- View Docs
Bulk Operations
GET /bulk-list-all:
- Bulk lists all documents.
- View Docs
GET /bulk-count:
- Bulk counts documents.
- View Docs
GET /bulk-read:
- Bulk reads documents.
- View Docs
POST /aggregation:
- Aggregates.
- View Docs
POST /bulk-aggregation:
- Bulk aggregates.
- View Docs
Collection Operations
DELETE /delete-all-collection:
- Deletes all collections.
- View Docs
DELETE /delete-many:
- Deletes many documents.
- View Docs
DELETE /bulk-delete-many:
- Bulk deletes many documents.
- View Docs
PUT /bulk-update-many:
- Bulk updates many documents.
- View Docs
GET /list-all-collection:
- Lists all collections.
- View Docs
PUT /update-all-collection:
- Updates all collections.
- View Docs
PUT /update-many:
- Updates many documents.
- View Docs
GET /count-all-collection:
- Counts all collections.
- View Docs
GET /search:
- Search documents.
- View Docs
POST /transaction:
- Perform transaction.
- View Docs
Create Single Document
The Create Single Document API enables clients to interact with the server and create a new document via the /create
route using the HTTP POST
method. This document provides comprehensive details on how to make successful requests and handle potential errors.
Route: /create
Method: POST
Request Parameters
To create a document, the following parameters must be included in the request body in JSON format:
schema
(string): The name of the schema.documentData
(object): The data for the document.
Sample Data
const sampleData = {
schema: "user",
documentData: {
username: "bapig",
phone_number: "0752628215"
}
}
Example Client-Side Request
// Making a client-side request to create a new document
try {
const response = await fetch(`/create`, {
method: "POST",
body: JSON.stringify(sampleData),
headers: {
"Content-Type": "application/json"
}
});
// Handling the response
const result = await response.json();
console.log(result);
} catch (error) {
console.error("Error making the request:", error);
}
Example Server-Side Request
// Making a server-side request to handle document creation
// Require necessary dependencies
const { controllers } = require("bapig");
try {
// Invoking the createSingleDocument method from controllers
const result = await controllers.createSingleDocument(sampleData);
// Logging the result
console.log(result);
} catch (error) {
console.error("Error handling the request:", error);
}
Sample Output (Success)
{
"success": true,
"message": {
"_id": "some_id",
"username": "bapig",
"phone_number": "0752628215"
}
}
Sample Output (Error)
{
"success": false,
"message": "failed to create user"
}
Document Bulk Create
The Document Bulk Create API allows clients to perform bulk creation of documents using the /bulk-create
route with the HTTP POST
method. This document provides detailed information on how to make successful requests and handle potential errors.
Request Parameters
To perform bulk document creation, clients must send an array of objects in the request body. Each object should adhere to the following structure:
schema
(string): The name of the schema.documentData
(object): The data for the document.
Sample Data
const bulkCreateData = [
{
schema: "user",
documentData: {
username: "bapig",
phone_number: "0752628215"
}
},
// Additional objects as needed
];
Example Client-Side Request
// Making a client-side request to perform bulk document creation
try {
const response = await fetch(`/bulk-create`, {
method: "POST",
body: JSON.stringify(bulkCreateData),
headers: {
"Content-Type": "application/json"
}
});
// Handling the response
const result = await response.json();
console.log(result);
} catch (error) {
console.error("Error making the request:", error);
}
Example Server-Side Request
// Making a server-side request to handle bulk document creation
// Dependencies
import { documentBulkCreate } from "bapig"; // Update with your actual package path
try {
// Invoking the documentBulkCreate function
const result = await documentBulkCreate(bulkCreateData);
// Logging the result
console.log(result);
} catch (error) {
console.error("Error handling the request:", error);
}
Sample Output (Success)
{
"success": true,
"message": {
"passedQueries": [
{
"_id": "some_id",
"username": "bapig",
"phone_number": "0752628215"
}
// Additional successfully created documents
],
"failedQueries": [
{
"schema": "user",
"documentData": {
"username": "bapig",
"phone_number": "0752628215"
},
"reason": "failed to create user"
}
// Additional failed documents with reasons
]
}
}
Get Single Document
The Get Single Document API allows clients to retrieve a single document using the /read
route with the HTTP GET
method. This document provides detailed information on how to make successful requests and handle potential errors.
Request Endpoint
Route: /read
Method: GET
Request Parameters
To retrieve a single document, clients must include a query string in the request URL with the following parameters:
condition
(object): The condition to filter the document.schema
(string): The name of the schema.select
(object): The fields to select from the document.joinForeignKeys
(boolean): A flag to determine whether to autopopulate foreign keys.
Sample Query
/read?condition={"_id":"some_id"}&schema="user"&select={"username":1,"email":1}&joinForeignKeys=false
Example Client-Side Request
// Making a client-side request to retrieve a single document
try {
const response = await fetch(`/read?condition={"_id":"some_id"}&schema="user"&select={"username":1,"email":1}&joinForeignKeys=true`);
// Handling the response
const result = await response.json();
console.log(result);
} catch (error) {
console.error("Error making the request:", error);
}
Example Server-Side Request
// Making a server-side request to handle document retrieval
// Dependencies
import { controllers } from "bapig";
try {
// Invoking the getSingleDocument function
const result = await controllers.getSingleDocument({
condition: { "_id": "some_id" },
schema: "user",
select: { "username": 1, "email": 1 },
joinForeignKeys: true
});
// Logging the result
console.log(result);
} catch (error) {
console.error("Error handling the request:", error);
}
Sample Output (Success)
{
"success": true,
"message": {
"_id": "some_id",
"username": "example_user",
"email": "user@example.com"
// Additional document fields as selected
}
}
Update Single Document
The Update Single Document API enables clients to update a single document using the /update
route with the HTTP PUT
method. This document provides detailed information on how to make successful requests and handle potential errors.
Request Endpoint
Route: /update
Method: PUT
Request Parameters
To update a single document, clients must send a request with the following parameters in the request body:
schema
(string): The name of the schema.condition
(object): The condition to identify the document to update.newDocumentData
(object): The updated data for the document.
Sample Data
const updateData = {
schema: "user",
condition: { "_id": "some_id" },
newDocumentData: {
username: "new_username",
email: "new_email@example.com"
}
};
Example Client-Side Request
// Making a client-side request to update a single document
try {
const response = await fetch(`/update`, {
method: "PUT",
body: JSON.stringify(updateData),
headers: {
"Content-Type": "application/json"
}
});
// Handling the response
const result = await response.json();
console.log(result);
} catch (error) {
console.error("Error making the request:", error);
}
Example Server-Side Request
// Making a server-side request to handle document update
// Dependencies
import { controllers } from "bapig";
try {
// Invoking the updateSingleDocument function from bapig controllers
const result = await controllers.updateSingleDocument(updateData);
// Logging the result
console.log(result);
} catch (error) {
console.error("Error handling the request:", error);
}
Sample Output (Success)
{
"success": true,
"message": {
"_id": "some_id",
"username": "new_username",
"email": "new_email@example.com"
// Additional updated document fields
}
}
Document Bulk Update
The Document Bulk Update API allows clients to perform bulk updates of documents using the /bulk-update
route with the HTTP PUT
method. This document provides detailed information on how to make successful requests and handle potential errors.
Request Endpoint
Route: /bulk-update
Method: PUT
Request Parameters
To perform bulk document updates, clients must send an array of objects in the request body. Each object should adhere to the following structure:
schema
(string): The name of the schema.condition
(object): The condition to identify the document to update.newDocumentData
(object): The updated data for the document.
Sample Data
const bulkUpdateData = [
{
schema: "user",
condition: { "_id": "some_id" },
newDocumentData: {
username: "new_username",
email: "new_email@example.com"
}
},
// Additional objects as needed
];
Example Client-Side Request
// Making a client-side request to perform bulk document updates
try {
const response = await fetch(`/bulk-update`, {
method: "PUT",
body: JSON.stringify(bulkUpdateData),
headers: {
"Content-Type": "application/json"
}
});
// Handling the response
const result = await response.json();
console.log(result);
} catch (error) {
console.error("Error making the request:", error);
}
Example Server-Side Request
// Making a server-side request to handle bulk document updates
// Dependencies
import { controllers } from "bapig";
try {
// Invoking the documentBulkUpdate function from bapig controllers
const result = await controllers.documentBulkUpdate(bulkUpdateData);
// Logging the result
console.log(result);
} catch (error) {
console.error("Error handling the request:", error);
}
Sample Output (Success)
{
"success": true,
"message": {
"passedQueries": [
{
"_id": "some_id",
"username": "new_username",
"email": "new_email@example.com"
}
// Additional successfully updated documents
],
"failedQueries": [
{
"schema": "user",
"condition": { "_id": "some_id" },
"newDocumentData": {
"username": "new_username",
"email": "new_email@example.com"
},
"reason": "failed to update user"
}
// Additional failed updates with reasons
]
}
}
Delete Single Document
The Delete Single Document API allows clients to delete a single document using the /delete
route with the HTTP DELETE
method. This document provides detailed information on how to make successful requests and handle potential errors.
Request Endpoint
Route: /delete
Method: DELETE
Request Parameters
To delete a single document, clients must include query parameters in the request URL:
schema
(string): The name of the schema.condition
(object): The condition to identify the document to delete.
Sample URL
/delete?schema=user&condition={"_id":"some_id"}
Example Client-Side Request
// Making a client-side request to delete a single document
try {
const response = await fetch(`/delete?schema=user&condition={"_id":"some_id"}`, {
method: "DELETE",
headers: {
"Content-Type": "application/json"
}
});
// Handling the response
const result = await response.json();
console.log(result);
} catch (error) {
console.error("Error making the request:", error);
}
Example Server-Side Request
// Making a server-side request to handle document deletion
// Dependencies
import { controllers } from "bapig";
try {
// Invoking the deleteSingleDocument function from bapig controllers
const result = await controllers.deleteSingleDocument({
schema: "user",
condition: { "_id": "some_id" }
});
// Logging the result
console.log(result);
} catch (error) {
console.error("Error handling the request:", error);
}
Sample Output (Success)
{
"success": true,
"message": {
"_id": "some_id",
// Additional document fields
}
}
Document Bulk Delete
The Document Bulk Delete API enables clients to delete multiple documents using the /bulk-delete
route with the HTTP DELETE
method. This document provides detailed information on how to make successful requests and handle potential errors.
Request Endpoint
Route: /bulk-delete
Method: DELETE
Request Parameters
To perform bulk document deletion, clients must include query parameters in the request URL:
schema
(string): The name of the schema.condition
(object): The condition to identify the document to delete.
Sample URL
/bulk-delete?schema=user&condition={"_id":"some_id"}
Example Client-Side Request
// Making a client-side request to perform bulk document deletion
try {
const response = await fetch(`/bulk-delete?schema=user&condition={"_id":"some_id"}`, {
method: "DELETE",
headers: {
"Content-Type": "application/json"
}
});
// Handling the response
const result = await response.json();
console.log(result);
} catch (error) {
console.error("Error making the request:", error);
}
Example Server-Side Request
// Making a server-side request to handle bulk document deletion
// Dependencies
import { controllers } from "bapig";
try {
// Invoking the documentBulkDelete function from bapig controllers
const result = await controllers.documentBulkDelete({
schema: "user",
condition: { "_id": "some_id" }
});
// Logging the result
console.log(result);
} catch (error) {
console.error("Error handling the request:", error);
}
Sample Output (Success)
{
"success": true,
"message": {
"passedQueries": [
{
"_id": "some_id",
// Additional document fields
}
// Additional successfully deleted documents
],
"failedQueries": [
{
"schema": "user",
"condition": { "_id": "some_id" },
"reason": "failed to delete user"
}
// Additional failed deletions with reasons
]
}
}
List Documents
The List Documents API allows clients to retrieve a list of documents based on specified criteria using the /list
route with the HTTP GET
method. This document provides detailed information on how to make successful requests and handle potential errors.
Request Endpoint
Route: /list
Method: GET
Request Parameters
To retrieve a list of documents, clients must include query parameters in the request URL:
page
(number): The page number for pagination (default is 1).limit
(number): The number of documents to display per page (default is 10).sort
(object): The sorting criteria for the documents.condition
(object): The condition to filter documents.schema
(string): The name of the schema.select
(object): The fields to select from each document.joinForeignKeys
(boolean): A flag indicating whether to autopopulate foreign keys.
Sample URL
/list?page=1&limit=10&sort={"createdAt":1}&condition={"status":"active"}&schema=user&select={"username":1}&joinForeignKeys=true
Example Client-Side Request
// Making a client-side request to retrieve a list of documents
try {
const response = await fetch(`/list?page=1&limit=10&sort={"createdAt":1}&condition={"status":"active"}&schema=user&select={"username":1}&joinForeignKeys=true`, {
method: "GET",
headers: {
"Content-Type": "application/json"
}
});
// Handling the response
const result = await response.json();
console.log(result);
} catch (error) {
console.error("Error making the request:", error);
}
Example Server-Side Request
// Making a server-side request to retrieve a list of documents
// Dependencies
import { controllers } from "bapig";
try {
// Invoking the listDocuments function from bapig controllers
const result = await controllers.listDocuments({
page: 1,
limit: 10,
sort: { "createdAt": 1 },
condition: { "status": "active" },
schema: "user",
select: { "username": 1 },
joinForeignKeys: true
});
// Logging the result
console.log(result);
} catch (error) {
console.error("Error handling the request:", error);
}
Sample Output (Success)
{
"success": true,
"message": {
"pages": [1, 2, 3],
"nextPage": 2,
"documents": [
{
"_id": "some_id",
"username": "example_user"
// Additional document fields
}
// Additional documents based on the specified criteria
],
"totalDocuments": 30,
"previousPage": 0,
"limit": 10,
"currentPage": 1
}
}
Count Documents
The Count Documents API allows clients to retrieve the total number of documents based on specified criteria using the /count
route with the HTTP GET
method. This document provides detailed information on how to make successful requests and handle potential errors.
Request Endpoint
Route: /count
Method: GET
Request Parameters
To count documents, clients must include query parameters in the request URL:
condition
(object): The condition to filter documents.schema
(string): The name of the schema.
Sample URL
/count?condition={"status":"active"}&schema=user
Example Client-Side Request
// Making a client-side request to count documents
try {
const response = await fetch(`/count?condition={"status":"active"}&schema=user`, {
method: "GET",
headers: {
"Content-Type": "application/json"
}
});
// Handling the response
const result = await response.json();
console.log(result);
} catch (error) {
console.error("Error making the request:", error);
}
Example Server-Side Request
// Making a server-side request to count documents
// Dependencies
import { controllers } from "bapig";
try {
// Invoking the countDocuments function from bapig controllers
const result = await controllers.countDocuments({
condition: { "status": "active" },
schema: "user"
});
// Logging the result
console.log(result);
} catch (error) {
console.error("Error handling the request:", error);
}
Sample Output (Success)
{
"success": true,
"message": 25
}
List All Documents API
Overview
The List All Documents API allows clients to retrieve all documents based on specified criteria using the /list-all
route with the HTTP GET
method. This document provides detailed information on how to make successful requests and handle potential errors.
Request Endpoint
Route: /list-all
Method: GET
Request Parameters
To list all documents, clients must include query parameters in the request URL:
sort
(object): The sorting criteria for the documents.condition
(object): The condition to filter documents.schema
(string): The name of the schema.select
(object): The fields to select from each document.joinForeignKeys
(boolean): A flag indicating whether to autopopulate foreign keys.
Sample URL
/list-all?sort={"createdAt":1}&condition={"status":"active"}&schema=user&select={"username":1}&joinForeignKeys=true
Example Client-Side Request
// Making a client-side request to list all documents
try {
const response = await fetch(`/list-all?sort={"createdAt":1}&condition={"status":"active"}&schema=user&select={"username":1}&joinForeignKeys=true`, {
method: "GET",
headers: {
"Content-Type": "application/json"
}
});
// Handling the response
const result = await response.json();
console.log(result);
} catch (error) {
console.error("Error making the request:", error);
}
Example Server-Side Request
// Making a server-side request to list all documents
// Dependencies
import { controllers } from "bapig";
try {
// Invoking the listAllDocuments function from bapig controllers
const result = await controllers.listAllDocuments({
sort: { "createdAt": 1 },
condition: { "status": "active" },
schema: "user",
select: { "username": 1 },
joinForeignKeys: true
});
// Logging the result
console.log(result);
} catch (error) {
console.error("Error handling the request:", error);
}
Sample Output (Success)
{
"success": true,
"message": [
{
"_id": "some_id",
"username": "example_user"
// Additional document fields
},
// Additional documents based on the specified criteria
]
}
Validate Document API
Overview
The Validate Document API enables clients to validate documents based on specified criteria using the /validate
route with the HTTP GET
method. This document provides comprehensive information on how to make successful requests and handle potential errors.
Request Endpoint
Route: /validate
Method: GET
Request Parameters
To validate a document, clients must include query parameters in the request URL:
schema
(string): The name of the schema.validationType
(string): The type of validation (onCreate
oronUpdate
).condition
(object): The condition to filter documents.documentId
(string, required foronUpdate
): The ID of the document.joinForeignKeys
(boolean): A flag indicating whether to autopopulate foreign keys.select
(object): The fields to select from the document.
Sample URL
/validate?schema=user&validationType=onCreate&condition={"username":"example_user"}&joinForeignKeys=true&select={"username":1}
Example Client-Side Request
// Making a client-side request to validate a document
try {
const response = await fetch(`/validate?schema=user&validationType=onCreate&condition={"username":"example_user"}&joinForeignKeys=true&select={"username":1}`, {
method: "GET",
headers: {
"Content-Type": "application/json"
}
});
// Handling the response
const result = await response.json();
console.log(result);
} catch (error) {
console.error("Error making the request:", error);
}
Example Server-Side Request
// Making a server-side request to validate a document
// Dependencies
import { controllers } from "bapig";
try {
// Invoking the validateDocument function from bapig controllers
const result = await controllers.validateDocument({
schema: "user",
validationType: "onCreate",
condition: { "username": "example_user" },
joinForeignKeys: true,
select: { "username": 1 }
});
// Logging the result
console.log(result);
} catch (error) {
console.error("Error handling the request:", error);
}
Sample Output (Success)
{
"success": true,
"message": {
"_id": "some_id",
"username": "example_user"
// Additional document fields
}
}
Create Document Field Encryption API
Overview
The Create Document Field Encryption API allows clients to encrypt a specific field within a document using the /create-field-encryption
route with the HTTP POST
method. This document provides detailed information on making successful requests to encrypt document fields.
Request Endpoint
Route: /create-field-encryption
Method: POST
Request Parameters
To encrypt a document field, clients must include the following parameters in the request body:
schema
(string): The name of the schema.fieldToEncrypt
(string): The field within the document to encrypt.documentData
(object): The document data containing the field to be encrypted.
Sample Request Body
{
"schema": "user",
"fieldToEncrypt": "password",
"documentData": {
"username": "example_user",
"password": "secret_password"
}
}
Example Client-Side Request
// Making a client-side request to encrypt a document field
try {
const response = await fetch(`/create-field-encryption`, {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({
"schema": "user",
"fieldToEncrypt": "password",
"documentData": {
"username": "example_user",
"password": "secret_password"
}
})
});
// Handling the response
const result = await response.json();
console.log(result);
} catch (error) {
console.error("Error making the request:", error);
}
Example Server-Side Request
// Making a server-side request to encrypt a document field
// Dependencies
import { controllers } from "bapig";
try {
// Invoking the createDocumentFieldEncryption function from bapig controllers
const result = await controllers.createDocumentFieldEncryption({
"schema": "user",
"fieldToEncrypt": "password",
"documentData": {
"username": "example_user",
"password": "secret_password"
}
});
// Logging the result
console.log(result);
} catch (error) {
console.error("Error handling the request:", error);
}
Sample Output (Success)
{
"success": true,
"message": {
"_id": "some_id",
"username": "example_user",
"password": "$2b$10$somehash"
// Additional document fields
}
}
Authenticate API
Overview
The Authenticate API enables user authentication based on provided credentials. Clients can use the /authenticate
route with the HTTP POST
method to authenticate users. This document provides detailed information on making successful requests for user authentication.
Request Endpoint
Route: /authenticate
Method: POST
Request Parameters
To authenticate a user, clients must include the following parameters in the request body:
schema
(string): The name of the schema.select
(object): The fields to select from the document (optional).condition
(object): The conditions to find the document for authentication.joinForeignKeys
(boolean): Flag to enable autopopulation of foreign keys (optional).fieldWithEncryption
(string): The field containing the encrypted password.valueToCompareWithEncryption
(string): The value to compare with the encrypted password.
Sample Request Body
{
"schema": "user",
"select": { "username": 1, "email": 1 },
"condition": { "username": "example_user" },
"joinForeignKeys": true,
"fieldWithEncryption": "password",
"valueToCompareWithEncryption": "user_provided_password"
}
Example Client-Side Request
// Making a client-side request for user authentication
try {
const response = await fetch(`/authenticate`, {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({
"schema": "user",
"select": { "username": 1, "email": 1 },
"condition": { "username": "example_user" },
"joinForeignKeys": true,
"fieldWithEncryption": "password",
"valueToCompareWithEncryption": "user_provided_password"
})
});
// Handling the response
const result = await response.json();
console.log(result);
} catch (error) {
console.error("Error making the request:", error);
}
Example Server-Side Request
// Making a server-side request for user authentication
// Dependencies
import { controllers } from "bapig";
try {
// Invoking the authenticate function from bapig controllers
const result = await controllers.authenticate({
"schema": "user",
"select": { "username": 1, "email": 1 },
"condition": { "username": "example_user" },
"joinForeignKeys": true,
"fieldWithEncryption": "password",
"valueToCompareWithEncryption": "user_provided_password"
});
// Logging the result
console.log(result);
} catch (error) {
console.error("Error handling the request:", error);
}
Validate Document Field Encryption
Overview
The Validate Document Field Encryption API allows clients to validate a document's encrypted field. Clients can use the /validate-field-encryption
route with the HTTP GET
method to verify whether a provided value matches the encryption of a specific field in a document. This document provides detailed information on making successful requests for validating document field encryption.
Request Endpoint
Route: /validate-field-encryption
Method: GET
Request Parameters
To validate document field encryption, clients must include the following parameters in the request query:
schema
(string): The name of the schema.documentId
(string): The unique identifier of the document.valueToCompareWithEncryption
(string): The value to compare with the encrypted field.fieldWithEncryption
(string): The field containing the encrypted value.
Sample Request Query
/validate-field-encryption?schema=user&documentId=example_document_id&valueToCompareWithEncryption=user_provided_value&fieldWithEncryption=password
Example Client-Side Request
// Making a client-side request to validate document field encryption
try {
const response = await fetch(`/validate-field-encryption?schema=user&documentId=example_document_id&valueToCompareWithEncryption=user_provided_value&fieldWithEncryption=password`, {
method: "GET",
headers: {
"Content-Type": "application/json"
}
});
// Handling the response
const result = await response.json();
console.log(result);
} catch (error) {
console.error("Error making the request:", error);
}
Example Server-Side Request
// Making a server-side request to validate document field encryption
// Dependencies
import { controllers } from "bapig";
try {
// Invoking the validateDocumentFieldEncryption function from bapig controllers
const result = await controllers.validateDocumentFieldEncryption({
"schema": "user",
"documentId": "example_document_id",
"valueToCompareWithEncryption": "user_provided_value",
"fieldWithEncryption": "password"
});
// Logging the result
console.log(result);
} catch (error) {
console.error("Error handling the request:", error);
}
Change Field Encryption
Overview
The Change Field Encryption API enables clients to modify the encryption of a specific field within a document. Clients can use the /change-field-encryption
route with the HTTP PUT
method to update the encryption of a specified field in a document. This document provides detailed information on making successful requests for changing document field encryption.
Request Endpoint
Route: /change-field-encryption
Method: PUT
Request Parameters
To change the document field encryption, clients must include the following parameters in the request body:
schema
(string): The name of the schema.documentId
(string): The unique identifier of the document.newValueToEncrypt
(string): The new value to encrypt and update.fieldWithEncryption
(string): The field whose encryption needs to be changed.
Sample Request Body
{
"schema": "user",
"documentId": "example_document_id",
"newValueToEncrypt": "new_secure_value",
"fieldWithEncryption": "password"
}
Example Client-Side Request
// Making a client-side request to change document field encryption
try {
const response = await fetch('/change-field-encryption', {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
schema: 'user',
documentId: 'example_document_id',
newValueToEncrypt: 'new_secure_value',
fieldWithEncryption: 'password',
}),
});
// Handling the response
const result = await response.json();
console.log(result);
} catch (error) {
console.error('Error making the request:', error);
}
Example Server-Side Request
// Making a server-side request to change document field encryption
// Dependencies
import { controllers } from 'bapig';
try {
// Invoking the changeDocumentFieldEncryption function from bapig controllers
const result = await controllers.changeDocumentFieldEncryption({
"schema": "user",
"documentId": "example_document_id",
"newValueToEncrypt": "new_secure_value",
"fieldWithEncryption": "password"
});
// Logging the result
console.log(result);
} catch (error) {
console.error("Error handling the request:", error);
}
Upload File
Overview
The Upload File API allows clients to upload files to the server. Clients can use the /upload-file
route with the HTTP POST
method to upload a file. This document provides detailed information on making successful requests for uploading files.
Request Endpoint
Route: /upload-file
Method: POST
Request Parameters
To upload a file, clients must include the following parameters in the request body:
folderName
(string): The name of the folder in which the file will be stored.
Sample Request Body
{
"folderName": "uploads"
}
Example Client-Side Request
// Making a client-side request to upload a file
// Assuming 'fileInput' is an HTML input element of type 'file'
const fileInput = document.getElementById('fileInput');
// Assuming 'folderNameInput' is an HTML input element for entering the folder name
const folderNameInput = document.getElementById('folderNameInput');
// Assuming 'uploadButton' is an HTML button triggering the file upload
const uploadButton = document.getElementById('uploadButton');
uploadButton.addEventListener('click', async () => {
const file = fileInput.files[0];
const folderName = folderNameInput.value;
if (!file) {
console.error('No file selected');
return;
}
try {
const formData = new FormData();
formData.append('body', JSON.stringify({ folderName }));
formData.append('file', file);
const response = await fetch('/upload-file', {
method: 'POST',
body: formData,
});
// Handling the response
const result = await response.json();
console.log(result);
} catch (error) {
console.error('Error making the request:', error);
}
});
Upload Files
Overview
The Upload Files API allows clients to upload multiple files to the server simultaneously. Clients can use the /upload-files
route with the HTTP POST
method to upload files. This document provides detailed information on making successful requests for uploading multiple files.
Request Endpoint
Route: /upload-files
Method: POST
Request Parameters
To upload multiple files, clients must include the following parameters in the request body:
folderName
(string): The name of the folder in which the files will be stored.
Sample Request Body
{
"folderName": "uploads"
}
Example Client-Side Request
// Making a client-side request to upload multiple files
// Assuming 'fileInputs' is an array of HTML input elements of type 'file'
const fileInputs = document.querySelectorAll('.file-input');
// Assuming 'folderNameInput' is an HTML input element for entering the folder name
const folderNameInput = document.getElementById('folderNameInput');
// Assuming 'uploadButton' is an HTML button triggering the file upload
const uploadButton = document.getElementById('uploadButton');
uploadButton.addEventListener('click', async () => {
const files = Array.from(fileInputs).map(input => input.files[0]);
const folderName = folderNameInput.value;
if (files.length === 0) {
console.error('No files selected');
return;
}
try {
const formData = new FormData();
formData.append('body', JSON.stringify({ folderName }));
files.forEach(file => {
formData.append('files', file);
});
const response = await fetch('/upload-files', {
method: 'POST',
body: formData,
});
// Handling the response
const result = await response.json();
console.log(result);
} catch (error) {
console.error('Error making the request:', error);
}
});
Delete Single File
Overview
The Delete Single File API allows clients to delete a specific file from the server. Clients can use the /delete-file
route with the HTTP DELETE
method to delete a single file. This document provides detailed information on making successful requests to delete a file.
Request Endpoint
Route: /delete-file
Method: DELETE
Request Parameters
To delete a single file, clients must include the following parameters in the request:
folderName
(string): The name of the folder where the file is located.fileName
(string): The name of the file to be deleted.
Sample Request
DELETE /delete-file?folderName=uploads&fileName=example.txt
Example Client-Side Request
// Making a client-side request to delete a single file
const deleteButton = document.getElementById('deleteButton');
const folderNameInput = document.getElementById('folderNameInput');
const fileNameInput = document.getElementById('fileNameInput');
deleteButton.addEventListener('click', async () => {
const folderName = folderNameInput.value;
const fileName = fileNameInput.value;
if (!folderName || !fileName) {
console.error('Please provide both folderName and fileName');
return;
}
try {
const response = await fetch(`/delete-file?folderName=${folderName}&fileName=${fileName}`, {
method: 'DELETE',
});
// Handling the response
const result = await response.json();
console.log(result);
} catch (error) {
console.error('Error making the request:', error);
}
});
Delete Multiple Files
Overview
The Delete Multiple Files API allows clients to delete multiple files from a specified folder. Clients can use the /delete-files
route with the HTTP DELETE
method to remove multiple files. This document provides detailed information on making successful requests for deleting multiple files.
Request Endpoint
Route: /delete-files
Method: DELETE
Request Parameters
To delete multiple files, clients must include the following query parameter in the request:
queries
(array): An array of objects containing the details of files to be deleted.fileName
(string): The name of the file to be deleted.folderName
(string): The name of the folder where the file is located.
Sample Request
DELETE /delete-files?queries[0][fileName]=file1.txt&queries[0][folderName]=documents&queries[1][fileName]=file2.png&queries[1][folderName]=images
Example Client-Side Request
// Making a client-side request to delete multiple files
try {
const response = await fetch('/delete-files', {
method: 'DELETE',
});
// Handling the response
const result = await response.json();
console.log(result);
} catch (error) {
console.error('Error making the request:', error);
}
Example Server-Side Request
// Making a server-side request to delete multiple files
// Dependencies
import { controllers } from 'bapig';
try {
// Invoking the fileBulkDelete function from bapig controllers
const result = await controllers.fileBulkDelete([
{ fileName: 'file1.txt', folderName: 'documents' },
{ fileName: 'file2.png', folderName: 'images' },
]);
// Logging the result
console.log(result);
} catch (error) {
console.error('Error handling the request:', error);
}
Send SMS
Overview
The Send SMS API allows clients to send SMS messages to multiple recipients. Clients can use the /send-sms
route with the HTTP POST
method to send SMS messages. This document provides detailed information on making successful requests for sending SMS messages.
Request Endpoint
Route: /send-sms
Method: POST
Request Body
To send an SMS, clients must include the following parameters in the request body:
message
(string): The content of the SMS message.receivers
(array of strings): An array of phone numbers to which the SMS will be sent.
Sample Request
POST /send-sms
Content-Type: application/json
{
"message": "Hello, this is a test SMS message.",
"receivers": ["+1234567890", "+9876543210"]
}
Example Client-Side Request
// Making a client-side request to send an SMS
try {
const response = await fetch('/send-sms', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
message: 'Hello, this is a test SMS message.',
receivers: ['+1234567890', '+9876543210'],
}),
});
// Handling the response
const result = await response.json();
console.log(result);
} catch (error) {
console.error('Error making the request:', error);
}
Example Server-Side Request
// Making a server-side request to send an SMS
// Dependencies
import { controllers } from 'bapig';
try {
// Invoking the sendMessage function from bapig controllers
const result = await controllers.sendMessage({
message: 'Hello, this is a test SMS message.',
receivers: ['+1234567890', '+9876543210'],
});
// Logging the result
console.log(result);
} catch (error) {
console.error('Error handling the request:', error);
}
Send Email
Overview
The Send Email API allows clients to send email messages. Clients can use the /send-email
route with the HTTP POST
method to send emails. This document provides detailed information on making successful requests for sending email messages.
Request Endpoint
Route: /send-email
Method: POST
Request Body
To send an email, clients must include the following parameters in the request body:
to
(string): The email address of the recipient.text
(string): The plain text content of the email.from
(string): The sender's email address.html
(string): The HTML content of the email (optional).subject
(string): The subject of the email.
Sample Request
POST /send-email
Content-Type: application/json
{
"to": "recipient@example.com",
"text": "Hello, this is a test email message.",
"from": "sender@example.com",
"html": "<p>Hello, this is a test email message.</p>",
"subject": "Test Email"
}
Example Client-Side Request
// Making a client-side request to send an email
try {
const response = await fetch('/send-email', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
to: 'recipient@example.com',
text: 'Hello, this is a test email message.',
from: 'sender@example.com',
html: '<p>Hello, this is a test email message.</p>',
subject: 'Test Email',
}),
});
// Handling the response
const result = await response.json();
console.log(result);
} catch (error) {
console.error('Error making the request:', error);
}
Example Server-Side Request
// Making a server-side request to send an email
// Dependencies
import { controllers } from 'bapig';
try {
// Invoking the sendEmail function from bapig controllers
const result = await controllers.sendEmail({
to: 'recipient@example.com',
text: 'Hello, this is a test email message.',
from: 'sender@example.com',
html: '<p>Hello, this is a test email message.</p>',
subject: 'Test Email',
});
// Logging the result
console.log(result);
} catch (error) {
console.error('Error handling the request:', error);
}
Generate OTP
Overview
The Generate OTP (One-Time Password) API allows clients to generate a six-digit random one-time password. Clients can use the /generate-otp
route with the HTTP GET
method to obtain a new OTP. This document provides detailed information on making successful requests for generating OTP.
Request Endpoint
Route: /generate-otp
Method: GET
Request
To generate a one-time password, clients can make a simple GET
request to the /generate-otp
e
1 month ago
1 month ago
1 month ago
1 month ago
2 months ago
2 months ago
2 months ago
4 months ago
5 months ago
5 months ago
5 months ago
5 months ago
5 months ago
5 months ago
5 months ago
5 months ago
5 months ago
10 months ago
12 months ago
12 months ago
11 months ago
12 months ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago