Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/mamena2020/fireme
Fastest way to Build REST APIs with no Budget. Boilerplate for Nodejs x Firebase.
https://github.com/mamena2020/fireme
backend backend-template boilderplate boilerplate-nodejs expressjs firebase-api firebase-backend firebase-boilerplate firebase-database firebase-firestore firebase-storage firebase-template nodejs rest-api role-and-permission
Last synced: 2 months ago
JSON representation
Fastest way to Build REST APIs with no Budget. Boilerplate for Nodejs x Firebase.
- Host: GitHub
- URL: https://github.com/mamena2020/fireme
- Owner: Mamena2020
- License: mit
- Created: 2023-07-05T22:22:17.000Z (over 1 year ago)
- Default Branch: master
- Last Pushed: 2023-10-16T10:53:59.000Z (about 1 year ago)
- Last Synced: 2024-10-04T16:11:23.365Z (3 months ago)
- Topics: backend, backend-template, boilderplate, boilerplate-nodejs, expressjs, firebase-api, firebase-backend, firebase-boilerplate, firebase-database, firebase-firestore, firebase-storage, firebase-template, nodejs, rest-api, role-and-permission
- Language: JavaScript
- Homepage: https://fireme.vercel.app
- Size: 505 KB
- Stars: 20
- Watchers: 1
- Forks: 7
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE.md
Awesome Lists containing this project
README
# Fireme
Boilerplate for nodejs. base on express js with Firebase.
- ### Features
- Model
Create model via cli.
Connect to firestore collections.- Media library
Any model can own the media, and will able to save media, get media, and destroy media.
Media stored to `Firebase Storage`.- File request handling
Not worry about handling uploaded file, just upload from client side, and file will accessible at req, `req.body.avatar`.
- Request validation
Determine if request passes the rule.
You can create `custom rule` via cli.- Role and Permissions
Any model can have a role and permissions, set role, checking access.
- Resources
Create custom resource from resources.
- Auth - JWT/Basic
Create token, re generate token, and set middleware authorization for certain routes.
- Locale
Enabled or disabled locale or just enabled on certain routes.
create mail via cli, and send mail with html, file, or just text.
- Firebase Cloud Messaging
Sending push notification from server to client device.
- Seeder
Running seeder via cli.
- ### Live demo
| Action | Method | Auth | Body | EndPoint |
| ------------- | ------ | ------ | ---------------- | ----------------------------------------- |
| Login | POST | | email | https://fireme.vercel.app/api/login |
| | | | password | |
| | | | | |
| Register | POST | | email | https://fireme.vercel.app/api/register |
| | | | name | |
| | | | password | |
| | | | confirm_password | |
| | | | | |
| Token | GET | | | https://fireme.vercel.app/api/token |
| | | | | |
| | | | | |
| Logout | DELETE | | | https://fireme.vercel.app/api/logout |
| | | | | |
| Get User | GET | Bearer | | https://fireme.vercel.app/api/user |
| | | token | | |
| | | | | |
| Upload avatar | POST | Bearer | avatar (file) | https://fireme.vercel.app/api/user/avatar |
| | | token | | |
| | | | | |
| Remove avatar | DELETE | Bearer | | https://fireme.vercel.app/api/user/avatar |
| | | token | | |
| | | | | |
| Get Users | GET | | | https://fireme.vercel.app/api/users |
| | | | | |# Getting Started
- ### Clone
Clone this repo `https` or `SSH` and move to directory project and run `npm install`
```
git clone [email protected]:Mamena2020/fireme.git```
- ### Setup .env
After clone, you can create `.env` file from `.env.example`.
```
cp .env.example .env```
- ### Create firebase project
Create new Firebase project on Firebase Console.
After create firebase project, go to `project settings -> service accounts`, then generate new private key,
after download Service Account .json, convert to `base64 string`, then set to
`FIREBASE_SERVICE_ACCOUNT_BASE64` in the `.env` file, and then go to firebase `storage` and copy firebase bucket name and
set to `FIREBASE_STORAGE_BUCKET` in the `.env` file, and also create firestore database.```
FIREBASE_STORAGE_BUCKET=gs://your-project.appspot.com
FIREBASE_SERVICE_ACCOUNT_BASE64= # base64 of firebaseServiceAccount.json```
After setup firebase config then you ready to write the code.
# Model
- Create new model via cli.
```
npx fireme make:model Product
```The model will be created in the `models/Product.js` directory.
```
import Model, { DataTypes } from '../core/model/Model.js';
class Product extends Model {}
Product.init({
fields: {
name: {
type: DataTypes.string,
},
price: {
type: DataTypes.number,
},
description: {
type: DataTypes.string,
nullable: true,
},
},
collection: 'products',
hasRole: false,
});export default Product
```
- ### Store data
Stored data using `stored` static method. This method will return object that you save or null if failed.
```
const data = {
name: 'Macbook pro M1',
price: 20000,
description: 'Macbook pro chipset M1 16inch screen...',
};const product = await Product.stored(data);
```
Bulk stored, stored many data at once. This `bulkStored` static method will return boolean true if success false if failed.
```
const data = [
{
name: 'Macbook pro M1',
price: 20000,
description: 'Macbook pro chipset M1 16inch screen...',
},
{
name: 'Macbook pro M2',
price: 40000,
description: 'Macbook pro chipset M2 14inch screen...',
}
];const products = await Product.bulkStored(data);
```
- ### Update data
Update data using `update` method. This method will return true if success or false if failed.
```
const product = await Product.findOne({
where: [{
field: 'id',
operator: Operator.equal,
value: id,
}],
});const newData = {
price: 22000,
};const updated = await product.update(newData);
```
Update many, using `update` static method. This method will return true if success or false if failed.
```
const oldPrice = 20000;
const newPrice = 22000;const updated = await Product.update({
data: {
price: newPrice,
},
where: [
{ field: 'price', operator: Operator.lte, value: oldPrice },
],
});```
- ### Delete data
Delete data using `destroy` method. This method will return true if success or false if failed.
```
const product = await Product.findOne({
where: [{
field: 'id',
operator: Operator.equal,
value: id,
}],
});const deleted = await product.destroy();
```
Delete many data using `destroy` static method. This method required `where` condition, and will return true if success or false if failed.
```
const deleted = await Product.destroy({
where: [
{ field: 'price', operator: Operator.lt, value: 20000 },
],
});```
- ### Get data
Get single data using `findOne` static method. This method required `where` condition will return object if success of null if failed.
```
const product = await Product.findOne({
where: [{
field: 'id',
operator: Operator.equal,
value: id,
}],
});```
Get many data using `findAll` static method. This method required `where` condition, and have optional parameter `limit`, and `orderBy`.
`orderBy` required 2 property `field` and `sort`, `sort` can be `asc` or `desc`.```
const products = await Product.findAll({
where: [{
field: 'price',
operator: Operator.gte,
value: 20000,
}],
limit: 10,
orderBy: {
field: "updated_at",
sort: "desc"
}
});```
- ### Count
Get count of collection from the database by calling `count()`. This method has `where` condition, and will return number if success of null if failed
```
const totalProduct = await Product.count(
where: [{
field: 'price',
operator: Operator.gte,
value: 20000,
}],
);
```- ### Refresh
Reload an instance from the database by calling `refresh()`.
```
await product.refresh();
```- ### Firestore
Using firestore instance directly.
```
import FirebaseCore from '../core/firebase/FirebaseCore.js';const snapshot = await FirebaseCore.admin.firestore().collection('myCollection').get();
snapshot.docs.forEach((doc)=>{
.... document
});```
- ### Instance info
Any instance of model has 'info' method with information.
```
const info = product.info();// collection -> collection name
// fields -> fields of model
// hasRole -> has role status
// doc -> raw data of document
// id -> document id
// ref -> reference document
// data -> data
// role -> role data
// medias -> array of medias```
- ### Field Data Types
```
string: 'string',
number: 'number',
boolean: 'boolean',
map: 'map',
array: 'array',
null: 'null',
timestamp: 'timestamp',
geopoint: 'geopoint',
reference: 'reference',
```- ### Operators
```
equal: '==',
notEqual: '!=',
lt: '<',
lte: '<=',
gt: '>',
gte: '>=',
arrayContains: 'array-contains',
in: 'in',
like: 'like',
arrayContainsAny: 'array-contains-any',
startsWith: 'startsWith',
endsWith: 'endsWith',
contains: 'contains',```
- ### Noted
All model by default has `created_at` and `updated_at` property as timestamp.
to convert to datetime using `toDate()`.```
const product = await Product.findOne({
where: [{
field: 'id',
operator: Operator.equal,
value: id,
}],
});const productData = {
id: product.id,
name: product.name,
price: product.price,
created_at: product.created_at.toDate(),
updated_at: product.updated_at.toDate(),
}```
# Media
Any model already has ability to save and get media.
- ### Save a file
Save file using `saveMedia` method. The method required file object and media name, and will return media object if success or null if failed.
If the instance already has a file with the same name, then the file will be replaced with a new file.```
const id = req.params.id;
const { image } = req.body;const product = await Product.findOne({
where: [{
field: 'id',
operator: Operator.equal,
value: id,
}],
});const media = await product.saveMedia(image, 'thumbnail');
```
- ### Get media
Get all media by calling `instance.getMedia()`.
```
const product = await Product.findOne({
where: [{
field: 'id',
operator: Operator.equal,
value: id,
}],
});product.getMedia() // return list of object
```
Get media by name, params is media name, will return media object if success and null if failed.
```
product.getMedia('thumbnail') // return single object
product.getMedia('thumbnail').url // return single object url```
- ### Destroy media
Destroy media by calling `instance.destroyMedia(mediaName)`. This method will return true if success and false if failed.
```
const product = await Product.findOne({
where: [{
field: 'id',
operator: Operator.equal,
value: id,
}],
});const deleted = await product.destroyMedia('thumbnail')
```
- ### Storage
Using `saveMedia` directly and return url and path.
```
const { avatar } = req.body;const myAvatar = await FirebaseCore.saveMedia(avatar);
const { url, path } = myAvatar;
```Using `deleteMedia` or `deleteMedias` to delete media.
```
await FirebaseCore.deleteMedia(mediaPath); // delete single media.await FirebaseCore.deleteMedias(mediaPaths); // delete many media at once.
```- ### Noted
All media files will be automatically deleted whenever `instance` of model is deleted.
# Request & Upload Files
Handling Content-Type header for
- application/json
- application/form-data
- application/x-www-form-urlencodedHandling all upload files on `POST` and `PUT` method, and nested fields.
- ### File properties
Uploaded file will have this properties.
```
name -> file name,
encoding -> file encoding,
type -> file mimeType,
size -> file size,
sizeUnit -> file size in bytes
extension -> file extension
tempDir -> file temporary directory```
# Rule Validation
Create Request validation via cli.
```
npx fireme make:request ProductRequest
```The Request will be created in the `requests` directory.
```
import RequestValidation from "../core/validation/RequestValidation.js"
class ProductRequest extends RequestValidation {
constructor(req) {
super(req).load(this)
}/**
* Get the validation rules that apply to the request.
*
* @return object
*/
rules() {
return {}
}
}export default ProductRequest
```
- ### Basic usage.
```
const valid = new ProductRequest(req)
await valid.check()
if (valid.isError)
return valid.responseError(res) // or return res.status(422).json(valid.errors)```
- ### Example html form.
```
Item
Item 1
Item 2
Submit
```
- ### Example rules.
```
rules() {
return {
"name": {
"rules": ["required"]
},
"discount": {
"rules": ["required", "float", "min:3", "max:4"]
},
"expired_date": {
"rules": ["required", "date", "date_after:now"]
},
"product_image": {
"rules": ["required", "image", "max_file:1,MB"]
},
"item.*.name": {
"rules": ["required"]
},
"item.*.description": {
"rules": ["required"]
},
"price.*": {
"rules": ["required", "float"]
},
"comments.*": {
"rules": ["required"]
},
"seo.title": {
"rules": ["required"]
},
"seo.description.long": {
"rules": ["required"]
},
"seo.description.short": {
"rules": ["required"]
}
}
}```
- ### Example error messages
```
{
"errors": {
"name": [
"The Name is required"
],
"discount": [
"The Discount is required",
"The Discount must be valid format of float",
"The Discount should be more or equal than 3",
"The Discount should be less or equal than 4"
],
"expired_date": [
"The Expired date is required",
"The Expired date must be valid format of date",
"The Expired date date must be after the now's date"
],
"product_image": [
"The Product image is required"
],
"item.0.name": [
"The Item.0.name is required"
],
"item.1.name": [
"The Item.1.name is required"
],
"item.0.description": [
"The Item.0.description is required"
],
"item.1.description": [
"The Item.1.description is required"
],
"price.0": [
"The Price.0 is required",
"The Price.0 must be valid format of float"
],
"price.1": [
"The Price.1 is required",
"The Price.1 must be valid format of float"
],
"comments.0": [
"The Comments.0 is required"
],
"comments.1": [
"The Comments.1 is required"
],
"comments.2": [
"The Comments.2 is required"
]
"seo.title": [
"The Seo.title is required"
],
"seo.description.long": [
"The Seo.description.long is required"
],
"seo.description.short": [
"The Seo.description.short is required"
]
}
}```
- ### Basic rules
| Rule | Description | Example |
| -------------------- | ------------------------------------------- | ---------------------------------------------------------- |
| required | check empty value | "required" |
| email | check email formats | "email" |
| match | check match value with other value | "match:password" |
| exists | check value exists in the database | "exists:users,email" or "exists:users,email,"+this.body.id |
| unique | check value is unique in database | "unique:users,email" or "unique:users,email,"+this.body.id |
| string | check value is an string | "string" |
| float | check value is an float | "float" |
| integer | check value is an ineteger | "integer" |
| max | count maximum value of numeric, | "max:12" |
| | if string/array its count the length | |
| min | count minimum value of numeric, | "min:5" |
| | if string/array its count the length | |
| date | check value is date format | "date" |
| array | check value is an array | "array" |
| mimetypes | check file mimetypes | "mimetypes:image/webp,image/x-icon,video/mp4" |
| mimes | check file extension | "mimes:jpg,png,jpeg" |
| max_file | check maximum file size, | "max_file:1,GB" or "max_file:1,MB" or "max_file:1,Byte" |
| | param can be `GB`, `MB`, `KB` or `Byte` | |
| image | check file is an image format | "image" |
| date_after | check value after particular date | "date_after:now" or "date_after:birthdate" |
| | param can be `now`, or other field name | |
| date_after_or_equal | check value after or equal particular date | "date_after_or_equal:now" |
| | param can be `now`, or other field name | |
| date_before | check value before particular date | "date_before:now" or "date_before:birthdate" |
| | param can be `now`, or other field name | |
| date_before_or_equal | check value before or equal particular date | "date_before_or_equal:now" |
| | param can be `now`, or other field name | |
| boolean | check value is an boolean | "boolean" |
| in_array | check value exist in array | "in_array:1,3,4,1,4,5" |
| not_in_array | check value is not include in array | "not_in_array:1,3,4,1,4,5" |
| ip | check value is as ip address | "ip" |
| url | check value is as url | "url" |
| json | check value is as json format | "json" |
| digits | check value digits, | "digits:4" |
| max_digits | check maximum digits of value | "max_digits:20" |
| min_digits | check minumum digits of value | "min_digits:20" |
| digits_between | check digits bewteen of value | "digits_between:5,10" |- ### Custom
Custom validation `messages` and `attribute`
```
rules() {
return {
"name": {
"rules": ["required"],
"attribute": "Product name"
},
"discount": {
"rules": ["required", "float", "min:3", "max:4"],
"messages": {
"required": "The _attribute_ need discount",
"float": "The data must be numeric"
},
"attribute": "DISCOUNT"
}
}
}```
- ### Direct add error messages
```
const valid = new ProductRequest(req)
await valid.check()if (valid.isError)
{
valid.addError("name","Name have to .....")
valid.addError("name","Name must be .....")```
# Custom Rule
Create Custom Rule via cli.
```
npx fireme make:rule GmailRule
```The Rule will be created in the `rules` directory.
```
class GmailRule {constructor() {
}/**
* Determine if the validation rule passes.
* @param {*} attribute
* @param {*} value
* @returns bolean
*/
passes(attribute, value) {return value.includes("@gmail.com")
}/**
* Get the validation error message.
*
* @return string
*/
message() {
return 'The _attribute_ must be using @gmail.com'
}
}export default GmailRule
```
- ### Custom rule usage
```
rules() {
return {
"email": {
"rules": [ new GmailRule, "required","email" ]
}
}
}
```- ### Noted
Default error messages outputs are dependent on the locale. If you haven't set up the locale as a middleware, it will be set to English `en` by default.
# Role and Permissions
A user model can have a role by set `hasRole` to true.
```
User.init({
fields: {
name: {
type: DataTypes.string,
},
email: {
type: DataTypes.string,
},
password: {
type: DataTypes.string,
},
},
collection: 'users',
hasRole: true, // set true
});```
- ### Set role
If the user instance already has a role, then the user role will be replaced with a new role. `instance.setRole(params)` params is role `name`. `setRole` will return status in boolean.
```
const user = await User.stored({
name: 'andre',
emmail: '[email protected]',
password: 'mypassword',
});const status = await user.setRole('admin');
```
- ### Get role
Get role object by calling `instance.getRole()`, or direcly access role name `instance.getRole().name`.
```
const role = user.getRole() // role object
const roleName = user.getRole().name // role name```
- ### Get permissions
Get permission by calling `instance.getRole().permissions` will get array of permissions name.
```
const permissions = user.getRole().permissions; // array of permissions name [ "user-create","user-stored"]```
- ### Remove role
Remove role from an instance.```
const removed = await user.removeRole();```
- ### Check user access
Limitation user access using `gateAccess(userInstance,permissionNames)`, `permissionNames` must be an array of permission names.
```
import gateAccess from '../core/service/RolePermission/RolePermissionService.js';if (!gateAccess(user, ["user-create","user-stored","user-access"]))
return res.sendStatus(403); // return forbidden status code```
- ### Add permissions
```
import Permission from '../core/service/RolePermission/Permission.js';const permissions = [
"user-create",
"user-stored",
"user-edit",
"user-update",
"user-delete",
"user-search"
]for (let permission of permissions) {
await Permission.stored({ name: permission })
}```
- ### Add Role
```
import Role from '../core/service/RolePermission/Role.js';
const roles = [ "admin","customer" ]
for (let role of roles) {
await Role.stored({ name: role })
}```
- ### Add Permissions to Role
Assign permissions to a role by update. Permissions must be an array of permission names.
```
const permissions = [
"user-create",
"user-stored"
]const roleAdmin = await Role.findOne({
where: [{
field: 'name',
operator: Operator.equal,
value: 'admin',
}],
});if (roleAdmin) {
await roleAdmin.update({permissions: permissions})
}```
- ### Noted
There is no ref between permission and role, permission only stored all permission. The role has own field type an array to stored permission.
# Resource
Create new resource via cli.
```
npx fireme make:resource UserResource
```The Resource will be created in `resources/UserResource.js` directory.
```
import Resource from "../core/resource/Resource.js"
class UserResource extends Resource {
constructor() {
super().load(this)
}/**
* Transform the resource into custom object.
*
* @return
*/
toArray(data) {
return {}
}
}export default UserResource
```
- ### Basic usage
To create resources from a single object use `make` or `collection` for an array of objects.
```
let userResource = new UserResource().make(user); // for single object
let userResources = new UserResource().collection(users); // for array of object
```
- ### Example user resource
```
class UserResource extends Resource {
constructor() {
super().load(this)
}toArray(data) {
return {
"id": data.id,
"name": data.name,
"email": data.email,
"image": data.getMedia("avatar")?.url || '',
"role": data.getRole()?.name || '',
}
}
}```
- ### Example usage
```
const id = req.params.id;const user = await User.findOne({
where: [{
field: 'id',
operator: Operator.equal,
value: id,
}],
});const userResource = new UserResource().make(user);
return res.json(userResource);
```
- ### Example result
```
{
"id": 'b96jRXfy8nSfZqk545SN',
"name": 'Andre',
"email": '[email protected]',
"image": 'https://storage.googleapis.com/projectx.appspot.com/91ecb634-46dd-4fce-995c-b38d4eaa2bd1.jpeg',
"role": 'admin',
}```
# Auth
- ### Create token
Create token by calling `JwtAuth.createToken()`, that will return `refreshToken` and `accessToken`.
```
const payload = {
id: user.id,
name: user.name,
email: user.email
}const token = JwtAuth.createToken(payload)
console.log(token.refreshToken)
console.log(token.accessToken)```
- ### Regenerate access token
Regenerate access token by calling `JwtAuth.regenerateAccessToken(refreshToken)`, that will return new access token.
```
const accessToken = JwtAuth.regenerateAccessToken(refreshToken)
```
- ### Get Auth user
Get authenticated user by `calling JwtAuth.getUser(email)`, that will get user by email.
```
const user = await JwtAuth.getUser(email)
```
Or you just setup the .env `AUTH_GET_CURRENT_USER_ON_REQUEST=true` and you can access current authenticated user by access
`req.user`. You can found the code where current user instance set to req.user in `core/middleware/JwtAuthPass.js` file.```
req.user = await JwtAuth.getUser(email);
```Before using `JwtAuth.GetUser()`, ensure that you have set up your `User` model inside the `AuthConfig` in the `core/config/Auth.js` file.
- Default user model for auth in `core/config/Auth.js`.
```
class AuthConfig {/**
* Default user model for auth
* @returns
*/
static user = User
```It is crucial that your User model has a `email` field, as `JwtAuth.GetUser()` will retrieve the user instance based on the `email` by default. However, if you prefer to retrieve the current authenticated user in a different manner, you can modify the `JwtAuth.GetUser()` function to suit your needs in `core/auth/JwtAuth.js` file.
- ### Use Middleware - Auth jwt
For secure access to controller by adding `JwtAuthPass` to your router.
```
import JwtAuthPass from '../core/middleware/JwtAuthPass.js';routerAuth.use(JwtAuthPass)
routerAuth.get("/upload", UserController.upload)app.use("/api",routerAuth)
```
- ### Use Middleware - Basic auth
For secure access to controller by adding `BasicAuthPass` to your router.
```
import BasicAuthPass from '../core/middleware/BasicAuthPass.js';routerAuth.use(BasicAuthPass)
routerAuth.get("/upload", UserController.upload)app.use("/api",routerAuth)
```
Before using this, make sure already set username and password for basic auth in `.env` file.
```
AUTH_BASIC_AUTH_USERNAME=myBasicUsername
AUTH_BASIC_AUTH_PASSWORD=myBasicPassword
```# Locale
- ### Config
Setup locale in `core/config/Locale.js`. by default locale setup to english `en`
```
defaultLocale: "en",
useLocale: useLocale,
locales: ["en", "id"]```
You can add more locale Code to `locales`. By default `locales` are only available for English `en`, and for Indonesia `id`.
- ### Default validation error Messages
After adding additional `locales`, it is important to update the validation error messages in the `core/locale/LangValidation.js` file, as the messages generated will depend on the selected locale.
```
const langValidation = Object.freeze({
required: {
en: "The _attribute_ is required",
id: "_attribute_ wajib di isi",
//ja: "_attribute_ ........." -> adding new validation messages for code ja
},
email: {
en: "The _attribute_ must in E-mail format",
id: "_attribute_ harus dalam format E-mail",
},
match: {
en: "The _attribute_ must be match with _param1_",
id: "_attribute_ harus sama dengan _param1_"
},
......```
- ### Use Locale
Its easy to use locale, just setup `.env` `LOCALE_USE=true`, then this will effect to `all` routes, so that have to has a params for locale, for the API router it should be `/api/:locale` and for the web router it should be `/:locale`.
```
// example for api route
const routerAuth = express.Router()routerAuth.use(JwtAuthPass)
routerAuth.get("/user", UserController.getUser)
routerAuth.post("/upload", UserController.upload)sapp.use("/api/:locale", routerAuth)
// http://localhost:5000/api/en/endpoint | http://localhost:5000/api/id/endpoint
```
If you don't want to set the locale for all routes, only for a particular route, then simply set up the `.env` as `LOCALE_USE=false`. Then you can use the `LocalePass` middleware directly to your route.
```
import LocalePass from '../core/middleware/LocalePass.js';// example for web route
app.get("/:locale",LocalePass, (req, res) => {// http://localhost:5000/en | http://localhost:5000/id
// example for api route
app.get("/api/:locale/login",LocalePass, (req, res) => {// http://localhost:5000/api/en/login | http://localhost:5000/api/id/login
```
- ### Noted
All routers that using `LocalePass` will have the locale Code on `req`, accessible via `req.locale`.
Create mail via cli.
```
npx fireme make:mail AccountVerify
```The mail will be created in the `mails` directory, with `examplefile.txt` and `template.ejs`
```
import Mail from "../../core/mail/Mail.js"
class AccountVerify extends Mail {
constructor(from = String, to = [], subject = String) {
super().load({
from: from,
to: to,
subject: subject,
text: "Just need to verify that this is your email address.",
attachments: [
{
filename: "theFile.txt",
path: "mails/AccountVerify/examplefile.txt"
},
],
html: {
path: "mails/AccountVerify/template.ejs",
data: {
title: "Welcome to the party!",
message: "Just need to verify that this is your email address."
}
},
})
}
}export default AccountVerify
```
The `template.ejs` using express view engine `ejs` to render html into mail content.
```
<%= title %>
Hello!
<%= message %>
Regards.
fireme
```
To use this `template.ejs`, you need to add an `html` object with a `path` and `data` (if needed) into `super().load()` method.
```
html: {
path: "mails/AccountVerify/template.ejs", // path is required
data: // data is optional base on your template.ejs
{
title: "Welcome to the party!",
message: "Just need to verify that this is your email address."
}
}
```- ### Basic usage
To send email by calling `instance.send()`
```
const sendMail = new AccountVerify("[email protected]",["[email protected]"],"Verify Account")await sendMail.send()
```
- ### Send file
To send files, you need to add an `attachments` to `super().load()`. See full doc.
```
attachments: [
{
filename: "theFile.txt",
path: "mails/AccountVerify/examplefile.txt"
}
]```
- ### Mail message options
Message options that you can add into `super().load()`.
| Name | Description | Type | Required |
| ------------ | ------------------------------------------------------------------------------------------------------------------------------------ | ------ | -------- |
| from | The e-mail address of the sender. All e-mail addresses can be plain '[email protected]' | string | `Yes` |
| to | Recipients e-mail addresses that will appear on the To | array | `Yes` |
| subject | Subject of the e-mail | string | No |
| text | If you are using HTML for the body of the email, then this text will not be used again | string | No |
| html | The HTML version of the message | object | No |
| attachments | An array of objects is used for the purpose of sending files. See full doc | array | No |
| cc | Recipients e-mail addresses that will appear on the Cc | array | No |
| bcc | Recipients e-mail addresses that will appear on the Bcc | array | No |
| sender | E-mail address that will appear on the Sender | string | No |
| replyTo | An array of e-mail addresses that will appear on the Reply-To | array | No |
| alternatives | An array of alternative text contents. See full doc | array | No |
| encoding | optional transfer encoding for the textual parts. | string | No |- ### Noted
Before using mail, make sure you already setup `.env` file.
```
MAIL_HOST= #example: smtp.gmail.com | smtp-relay.sendinblue.com
MAIL_PORT=587
MAIL_USERNAME=
MAIL_PASSWORD=```
# Firebase Cloud Messaging
- ### Send message
```
import FirebaseCore from '../core/firebase/FirebaseCore.js';const message = {
title: "Notification", // notification title
body: "Hello there", // notification body
data: {
// payload
},
registrationTokens: ["token1","token2"] // target token
}await FirebaseCore.sendMessage(message)
```
- ### Noted
Before using FCM, make sure you already `enable` Firebase Cloud Messaging API on Google Cloud Console, by selecting your project and navigating to `APIs & Services`. Once you have enabled the API, you can set up your .env file.
```
FIREBASE_CLOUD_MESSAGING_SERVER_KEY= #fcm server key```
# Seeder
- ### Running seeder
Running seeder via cli
```
npx fireme seed:run
```You can put your seeder code inside `seeder` function in the `core/seeder/Seeder.js` file
```
const seeder = async () => {
// put seeder code here..
}
```
# Cors
The configuration for Cross-Origin Resource Sharing (CORS) can be found in the `core/config/Cors.js` file.