create: new folder

This commit is contained in:
abiyasa05 2024-12-31 10:16:45 +07:00
parent ffa930b201
commit 4edeb880a6
72 changed files with 4593 additions and 1 deletions

45
.adonisrc.json Normal file
View File

@ -0,0 +1,45 @@
{
"typescript": true,
"commands": [
"./commands",
"@adonisjs/core/commands",
"@adonisjs/repl/build/commands",
"@adonisjs/lucid/build/commands",
"@adonisjs/mail/build/commands"
],
"exceptionHandlerNamespace": "App/Exceptions/Handler",
"aliases": {
"App": "app",
"Config": "config",
"Database": "database",
"Contracts": "contracts"
},
"preloads": [
"./start/routes",
"./start/kernel"
],
"providers": [
"./providers/AppProvider",
"@adonisjs/core",
"@adonisjs/session",
"@adonisjs/view",
"@adonisjs/shield",
"@adonisjs/lucid",
"@adonisjs/auth",
"@adonisjs/mail",
"@adonisjs/ally"
],
"metaFiles": [
{
"pattern": "public/**",
"reloadServer": false
},
{
"pattern": "resources/views/**/*.edge",
"reloadServer": false
}
],
"aceProviders": [
"@adonisjs/repl"
]
}

14
.editorconfig Normal file
View File

@ -0,0 +1,14 @@
[*]
indent_style = space
indent_size = 2
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
[*.json]
insert_final_newline = ignore
[*.md]
trim_trailing_whitespace = false

21
.env.example Normal file
View File

@ -0,0 +1,21 @@
PORT=3333
HOST=0.0.0.0
NODE_ENV=development
APP_KEY=ltoWFqOgfC6B8eVs6Kj2YLxyyXCvwgcu
SESSION_DRIVER=cookie
CACHE_VIEWS=false
DB_CONNECTION=pg
PG_HOST=ec2-54-179-80-119.ap-southeast-1.compute.amazonaws.com
PG_PORT=5432
PG_USER=roadreportpgdb
PG_PASSWORD=roadreportpgdb1qaz
PG_DB_NAME=pg_roadreport_dev
SMTP_HOST=localhost
SMTP_PORT=587
SMTP_USERNAME=<username>
SMTP_PASSWORD=<password>
GOOGLE_CLIENT_ID=clientId
GOOGLE_CLIENT_SECRET=clientSecret

8
.gitignore vendored Normal file
View File

@ -0,0 +1,8 @@
node_modules
build
coverage
.vscode
.DS_STORE
.env
tmp
package-lock.json

35
README.md Normal file
View File

@ -0,0 +1,35 @@
# AdonisJS v5 Boilerplate
Using repository and service pattern
## Setup
1. Clone this repository `git clone git@gitlab.com:profile-image/boilerplate/adonisjs-v5-boilerplate.git`
2. Copy file `.env.example` to `.env`
3. Create database
4. Run `boilerplate.sql` in folder `database/sql`
5. Change database name and connection in `.env`
6. Run command `npm install` to install dependencies
7. Run server with `npm run dev`
## Creating Module
Create model, repository, service, controller, validators and route using below command:
```bash
node ace make:module <Namespace> <ModelName> --endpoint <EndpointName> --soft-delete --uuid
```
Example: I will make module for user table with soft delete
```bash
node ace make:module User User --endpoint users --soft-delete
```
Notes:
1. Namespace is required and using CamelCase.
2. ModelName is required and using CamelCase.
3. EndpointName is required and using lowercase. If endpoint have more than one word, separate them with `-`.
4. --soft-delete is optional. Use only when your table has `deleted_at` column.
5. --uuid is optional. Use only when your primaryKey using uuid.

16
ace Normal file
View File

@ -0,0 +1,16 @@
/*
|--------------------------------------------------------------------------
| Ace Commands
|--------------------------------------------------------------------------
|
| This file is the entry point for running ace commands.
|
*/
require('reflect-metadata')
require('source-map-support').install({ handleUncaughtExceptions: false })
const { Ignitor } = require('@adonisjs/core/build/standalone')
new Ignitor(__dirname)
.ace()
.handle(process.argv.slice(2))

332
ace-manifest.json Normal file
View File

@ -0,0 +1,332 @@
{
"commands": {
"make:module": {
"settings": {
"loadApp": false,
"stayAlive": false
},
"commandPath": "./commands/MakeModule",
"commandName": "make:module",
"description": "Make a new module",
"args": [
{
"type": "string",
"propertyName": "domain",
"name": "domain",
"required": true,
"description": "Domain name"
},
{
"type": "string",
"propertyName": "model",
"name": "model",
"required": true,
"description": "Model name"
}
],
"aliases": [],
"flags": [
{
"name": "soft-delete",
"propertyName": "softDelete",
"type": "boolean",
"description": "Enable soft delete"
},
{
"name": "enable-uuid",
"propertyName": "enableUUID",
"type": "boolean",
"alias": "uuid",
"description": "Enable soft delete"
},
{
"name": "endpoint",
"propertyName": "endpoint",
"type": "string",
"alias": "e",
"description": "Set endpoint name"
}
]
},
"dump:rcfile": {
"settings": {},
"commandPath": "@adonisjs/core/commands/DumpRc",
"commandName": "dump:rcfile",
"description": "Dump contents of .adonisrc.json file along with defaults",
"args": [],
"aliases": [],
"flags": []
},
"list:routes": {
"settings": {
"loadApp": true
},
"commandPath": "@adonisjs/core/commands/ListRoutes",
"commandName": "list:routes",
"description": "List application routes",
"args": [],
"aliases": [],
"flags": [
{
"name": "json",
"propertyName": "json",
"type": "boolean",
"description": "Output as JSON"
}
]
},
"generate:key": {
"settings": {},
"commandPath": "@adonisjs/core/commands/GenerateKey",
"commandName": "generate:key",
"description": "Generate a new APP_KEY secret",
"args": [],
"aliases": [],
"flags": []
},
"repl": {
"settings": {
"loadApp": true,
"environment": "repl",
"stayAlive": true
},
"commandPath": "@adonisjs/repl/build/commands/AdonisRepl",
"commandName": "repl",
"description": "Start a new REPL session",
"args": [],
"aliases": [],
"flags": []
},
"db:seed": {
"settings": {
"loadApp": true
},
"commandPath": "@adonisjs/lucid/build/commands/DbSeed",
"commandName": "db:seed",
"description": "Execute database seeder files",
"args": [],
"aliases": [],
"flags": [
{
"name": "connection",
"propertyName": "connection",
"type": "string",
"description": "Define a custom database connection for the seeders",
"alias": "c"
},
{
"name": "interactive",
"propertyName": "interactive",
"type": "boolean",
"description": "Run seeders in interactive mode",
"alias": "i"
},
{
"name": "files",
"propertyName": "files",
"type": "array",
"description": "Define a custom set of seeders files names to run",
"alias": "f"
}
]
},
"make:model": {
"settings": {},
"commandPath": "@adonisjs/lucid/build/commands/MakeModel",
"commandName": "make:model",
"description": "Make a new Lucid model",
"args": [
{
"type": "string",
"propertyName": "name",
"name": "name",
"required": true,
"description": "Name of the model class"
}
],
"aliases": [],
"flags": [
{
"name": "migration",
"propertyName": "migration",
"type": "boolean",
"alias": "m",
"description": "Generate the migration for the model"
},
{
"name": "controller",
"propertyName": "controller",
"type": "boolean",
"alias": "c",
"description": "Generate the controller for the model"
}
]
},
"make:migration": {
"settings": {
"loadApp": true
},
"commandPath": "@adonisjs/lucid/build/commands/MakeMigration",
"commandName": "make:migration",
"description": "Make a new migration file",
"args": [
{
"type": "string",
"propertyName": "name",
"name": "name",
"required": true,
"description": "Name of the migration file"
}
],
"aliases": [],
"flags": [
{
"name": "connection",
"propertyName": "connection",
"type": "string",
"description": "The connection flag is used to lookup the directory for the migration file"
},
{
"name": "folder",
"propertyName": "folder",
"type": "string",
"description": "Pre-select a migration directory"
},
{
"name": "create",
"propertyName": "create",
"type": "string",
"description": "Define the table name for creating a new table"
},
{
"name": "table",
"propertyName": "table",
"type": "string",
"description": "Define the table name for altering an existing table"
}
]
},
"make:seeder": {
"settings": {},
"commandPath": "@adonisjs/lucid/build/commands/MakeSeeder",
"commandName": "make:seeder",
"description": "Make a new Seeder file",
"args": [
{
"type": "string",
"propertyName": "name",
"name": "name",
"required": true,
"description": "Name of the seeder class"
}
],
"aliases": [],
"flags": []
},
"migration:run": {
"settings": {
"loadApp": true
},
"commandPath": "@adonisjs/lucid/build/commands/Migration/Run",
"commandName": "migration:run",
"description": "Run pending migrations",
"args": [],
"aliases": [],
"flags": [
{
"name": "connection",
"propertyName": "connection",
"type": "string",
"description": "Define a custom database connection",
"alias": "c"
},
{
"name": "force",
"propertyName": "force",
"type": "boolean",
"description": "Explicitly force to run migrations in production"
},
{
"name": "dry-run",
"propertyName": "dryRun",
"type": "boolean",
"description": "Print SQL queries, instead of running the migrations"
}
]
},
"migration:rollback": {
"settings": {
"loadApp": true
},
"commandPath": "@adonisjs/lucid/build/commands/Migration/Rollback",
"commandName": "migration:rollback",
"description": "Rollback migrations to a given batch number",
"args": [],
"aliases": [],
"flags": [
{
"name": "connection",
"propertyName": "connection",
"type": "string",
"description": "Define a custom database connection",
"alias": "c"
},
{
"name": "force",
"propertyName": "force",
"type": "boolean",
"description": "Explictly force to run migrations in production"
},
{
"name": "dry-run",
"propertyName": "dryRun",
"type": "boolean",
"description": "Print SQL queries, instead of running the migrations"
},
{
"name": "batch",
"propertyName": "batch",
"type": "number",
"description": "Define custom batch number for rollback. Use 0 to rollback to initial state"
}
]
},
"migration:status": {
"settings": {
"loadApp": true
},
"commandPath": "@adonisjs/lucid/build/commands/Migration/Status",
"commandName": "migration:status",
"description": "Check migrations current status.",
"args": [],
"aliases": [],
"flags": [
{
"name": "connection",
"propertyName": "connection",
"type": "string",
"description": "Define a custom database connection",
"alias": "c"
}
]
},
"make:mailer": {
"settings": {},
"commandPath": "@adonisjs/mail/build/commands/MakeMailer",
"commandName": "make:mailer",
"description": "Make a new mailer class",
"args": [
{
"type": "string",
"propertyName": "name",
"name": "name",
"required": true,
"description": "Name of the mailer class"
}
],
"aliases": [],
"flags": []
}
},
"aliases": {}
}

View File

@ -0,0 +1,381 @@
import { DateTime } from "luxon"
export default class BaseRepository {
protected model: any
protected mainModel: any
protected isSoftDelete: boolean
protected RELATIONS: string[]
protected RELATION_OPTIONS: any
constructor(model: any) {
this.model = model
this.mainModel = model
this.isSoftDelete = model.softDelete
}
async getAll(pagination: any, sort: any, whereClauses: any, fields: any, search: any) {
try {
this.model = this.mainModel
this.model = this.model.query()
this.model = this.parseSelectedFields(this.model, fields)
this.model = this.parseWhere(this.model, whereClauses)
this.model = this.parseSearch(this.model, whereClauses, search)
this.model = this.parseRelation(this.model)
this.model = this.parseSort(this.model, sort)
if (pagination.page && pagination.limit) {
if (this.isSoftDelete) {
return await this.model.whereNull('deleted_at').paginate(pagination.page, pagination.limit)
}
return await this.model.paginate(pagination.page, pagination.limit)
} else {
if (this.isSoftDelete) {
return await this.model.whereNull('deleted_at')
}
return await this.model
}
} catch (error) {
throw error
}
}
async get(data: any = {}) {
try {
this.model = this.mainModel
this.model = this.model.query()
if (this.isSoftDelete) {
this.model = this.model.whereNull('deleted_at')
}
if (data.sort) {
this.model = this.parseSort(this.model, data.sort)
}
return await this.model
} catch (error) {
throw error
}
}
async store(data: any) {
try {
this.model = this.mainModel
return await this.model.create(data)
} catch (error) {
throw error
}
}
async multiInsert(data: any[]) {
try {
this.model = this.mainModel
return await this.model.createMany(data)
} catch (error) {
throw error
}
}
async show(id: any, fields: any) {
try {
this.model = this.mainModel
this.model = this.model.query().where(this.model.primaryKey, id)
this.model = this.parseSelectedFields(this.model, fields)
this.model = this.parseRelation(this.model)
if (this.isSoftDelete) {
this.model = this.model.whereNull('deleted_at')
}
return await this.model.first()
} catch (error) {
throw error
}
}
async update(id: any, data: any) {
try {
this.model = this.mainModel
if (! await this.model.find(id)) {
return null
}
data.updated_at = DateTime.now()
if (Object.keys(data).length) {
await this.model.query().where(this.model.primaryKey, id).update(data)
}
return await this.model.find(id)
} catch (error) {
throw error
}
}
async delete(id: any) {
try {
this.model = this.mainModel
const result = await this.model.find(id)
if (this.isSoftDelete) {
await this.model.query().where(this.model.primaryKey, id).update({ deleted_at: DateTime.local() })
} else {
await this.model.query().where(this.model.primaryKey, id).delete()
}
return result
} catch (error) {
throw error
}
}
async deleteAll() {
try {
this.model = this.mainModel
if (this.isSoftDelete) {
return await this.model.query().whereNull('deleted_at').update({ deleted_at: DateTime.local() })
} else {
return await this.model.query().delete()
}
} catch (error) {
throw error
}
}
async first() {
try {
this.model = this.mainModel
this.model = this.model.query()
if (this.isSoftDelete) {
this.model = this.model.whereNull('deleted_at')
}
return await this.model.first()
} catch (error) {
throw error
}
}
async find(id: any) {
try {
this.model = this.mainModel
this.model = this.model.query().where(this.model.primaryKey, id)
if (this.isSoftDelete) {
this.model = this.model.whereNull('deleted_at')
}
return await this.model.first()
} catch (error) {
throw error
}
}
setRelation(relation: string[]) {
this.RELATIONS = relation
}
setRelationOptions(relationOptions: any) {
this.RELATION_OPTIONS = relationOptions
}
parseSelectedFields(model: any, fields: any) {
if (fields) {
model = model.select(fields)
}
return model
}
parseWhere(model: any, whereClauses: any) {
if (whereClauses.data) {
if (whereClauses.operation == 'and') {
whereClauses.data.forEach((whereClause: any) => {
if (whereClause.operator == 'between') {
model = model.whereBetween(whereClause.attribute, whereClause.value)
} else {
if (whereClause.value == 'null') {
model = model.whereNull(whereClause.attribute)
} else {
if (whereClause.attribute.includes('.')) {
const attr = whereClause.attribute.split('.')
model = model.whereHas(attr[0], (builder: any) => {
builder.where(attr[1], whereClause.operator, whereClause.value)
})
} else {
model = model.where(whereClause.attribute, whereClause.operator, whereClause.value)
}
}
}
});
} else {
whereClauses.data.forEach((whereClause: any, index: number) => {
if (whereClause.operator == 'between') {
model = model.whereBetween(whereClause.attribute, whereClause.value)
} else {
if (index == 0) {
if (whereClause.value == 'null') {
model = model.whereNull(whereClause.attribute)
} else {
if (whereClause.attribute.includes('.')) {
const attr = whereClause.attribute.split('.')
model = model.whereHas(attr[0], (builder: any) => {
builder.where(attr[1], whereClause.operator, whereClause.value)
})
} else {
model = model.where(whereClause.attribute, whereClause.operator, whereClause.value)
}
}
} else {
if (whereClause.value == 'null') {
model = model.orWhereNull(whereClause.attribute)
} else {
if (whereClause.attribute.includes('.')) {
const attr = whereClause.attribute.split('.')
model = model.orWhereHas(attr[0], (builder: any) => {
builder.where(attr[1], whereClause.operator, whereClause.value)
})
} else {
model = model.orWhere(whereClause.attribute, whereClause.operator, whereClause.value)
}
}
}
}
});
}
}
return model
}
parseRelation(model: any) {
if (this.RELATIONS) {
this.RELATIONS.forEach((relation) => {
if (relation.split('.').length > 1) {
const firstRelation = relation.substr(0, relation.indexOf('.'))
model = model.preload(firstRelation, (query) => {
if (this.RELATION_OPTIONS) {
let relationOption = this.RELATION_OPTIONS.find((item: any) => { return item.relation == firstRelation })
this.parseRelationOption(query, relationOption)
}
this.parseNestedRelation(query, relation.substr(relation.indexOf('.') + 1), firstRelation)
})
} else {
model = model.preload(relation, (query) => {
if (this.RELATION_OPTIONS) {
let relationOption = this.RELATION_OPTIONS.find((item: any) => { return item.relation == relation })
this.parseRelationOption(query, relationOption)
}
})
}
})
}
return model
}
parseNestedRelation(query: any, relation: string, firstRelation: string) {
let relations = this.RELATIONS.filter(d => { return typeof d == 'string' })
relations = relations.filter(d => { return d.includes(firstRelation + '.') })
if (relations.length > 1) {
relations.map(data => {
this.parseNestedRelation(query, data.substr(data.indexOf('.') + 1), relation.substr(0, data.indexOf('.')))
})
} else {
if (relation.indexOf('.') > 0) {
let subRelation = relation.substr(0, relation.indexOf('.'))
query.preload(subRelation, (subQuery) => {
if (this.RELATION_OPTIONS) {
let relationOption = this.RELATION_OPTIONS.find((item: any) => { return item.relation == subRelation })
this.parseRelationOption(subQuery, relationOption)
}
this.parseNestedRelation(subQuery, relation.substr(relation.indexOf('.') + 1), subRelation)
})
} else {
query.preload(relation, (subQuery) => {
if (this.RELATION_OPTIONS) {
let relationOption = this.RELATION_OPTIONS.find((item: any) => { return item.relation == relation })
this.parseRelationOption(subQuery, relationOption)
}
})
}
}
}
parseRelationOption(query: any, relationOption: any) {
if (relationOption) {
if (relationOption.fields) {
query = query.select(relationOption.fields)
}
if (relationOption.sort) {
query = query.orderBy(relationOption.sort, relationOption.order)
}
if (relationOption.filter) {
query = this.parseWhere(query, relationOption.filter)
}
if (relationOption.limit) {
query = query.limit(relationOption.limit)
}
if (relationOption.search) {
query = this.parseSearch(query, relationOption.filter, relationOption.search)
}
}
return query
}
parseSort(model: any, sort: any[]) {
if (sort) {
sort.forEach((sort: any) => {
model = model.orderBy(sort.attribute, sort.order)
});
}
return model
}
parseSearch(model: any, whereClauses: any, search: any) {
if (search) {
const data = search.data
const attributes = search.attributes
const operator = search.operator
if (attributes) {
model = model.where((query) => {
if (whereClauses.data.length > 0) {
attributes.forEach((attribute: string) => {
if (attribute.includes('.')) {
const attr = attribute.split('.')
const field = attr[attr.length - 1]
const relations = attr.slice(0, attr.length - 1)
query.whereHas(relations[0], (query: any) => {
this.parseNestedSearch(query, relations.slice(1), field, data, operator)
})
} else {
query.orWhere(attribute, operator, data)
}
});
} else {
attributes.forEach((attribute: any, index: number) => {
if (index == 0) {
if (attribute.includes('.')) {
const attr = attribute.split('.')
const field = attr[attr.length - 1]
const relations = attr.slice(0, attr.length - 1)
query.whereHas(relations[0], (query: any) => {
this.parseNestedSearch(query, relations.slice(1), field, data, operator)
})
} else {
query.where(attribute, operator, data)
}
} else {
if (attribute.includes('.')) {
const attr = attribute.split('.')
const field = attr[attr.length - 1]
const relations = attr.slice(0, attr.length - 1)
query.orWhereHas(relations[0], (query: any) => {
this.parseNestedSearch(query, relations.slice(1), field, data, operator)
})
} else {
query.orWhere(attribute, operator, data)
}
}
});
}
})
}
}
return model
}
parseNestedSearch(model: any, relations: any, field: any, value: any, operator: any = 'ilike') {
if (relations.length > 0) {
model = model.whereHas(relations[0], (query: any) => {
this.parseNestedSearch(query, relations.slice(1), field, value)
})
} else {
model = model.where(field, operator, value)
}
return model
}
}

View File

@ -0,0 +1,127 @@
export default class BaseService {
repository: any
constructor(repository: any) {
this.repository = repository
}
async getAll(options: any) {
try {
this.repository.setRelation(options.relation)
this.repository.setRelationOptions(options.relationOptions)
const results = await this.repository.getAll(options.pagination, options.sort, options.filter, options.fields, options.search)
return results
} catch (error) {
throw error
}
}
async store(data: any) {
try {
return await this.repository.store(data)
} catch (error) {
throw error
}
}
async show(id: any, options: any = {}) {
try {
this.repository.setRelation(options.relation)
this.repository.setRelationOptions(options.relationOptions)
return await this.repository.show(id, options.fields)
} catch (error) {
throw error
}
}
async find(id: any) {
try {
return await this.repository.find(id)
} catch (error) {
throw error
}
}
async first() {
try {
return await this.repository.first()
} catch (error) {
throw error
}
}
async update(id: any, data: any) {
try {
return await this.repository.update(id, data)
} catch (error) {
throw error
}
}
async delete(id: any) {
try {
return await this.repository.delete(id)
} catch (error) {
throw error
}
}
async deleteAll() {
try {
return await this.repository.deleteAll()
} catch (error) {
throw error
}
}
async getDeletedAll(options: any) {
try {
this.repository.setRelation(options.relation)
const results = await this.repository.getDeletedAll(options.pagination, options.sort, options.filter, options.fields, options.search)
return results
} catch (error) {
throw error
}
}
async showDeleted(id: any, options: any = {}) {
try {
this.repository.setRelation(options.relation)
return await this.repository.showDeleted(id, options.fields)
} catch (error) {
throw error
}
}
async restore(id: any) {
try {
return await this.repository.restore(id)
} catch (error) {
throw error
}
}
async restoreAll() {
try {
return await this.repository.restoreAll()
} catch (error) {
throw error
}
}
async permanentDelete(id: any) {
try {
return await this.repository.permanentDelete(id)
} catch (error) {
throw error
}
}
async permanentDeleteAll() {
try {
return await this.repository.permanentDeleteAll()
} catch (error) {
throw error
}
}
}

View File

@ -0,0 +1,215 @@
export default class ParseParamService {
LIKE = 'like';
EQUAL = 'eq';
NOT_EQUAL = 'ne';
GREATER_THAN = 'gt';
GREATER_THAN_EQUAL = 'gte';
LESS_THAN = 'lt';
LESS_THAN_EQUAL = 'lte';
BETWEEN = 'between';
OPERATOR_LIKE = 'ILIKE';
OPERATOR_EQUAL = '=';
OPERATOR_NOT_EQUAL = '!=';
OPERATOR_GREATER_THAN = '>';
OPERATOR_GREATER_THAN_EQUAL = '>=';
OPERATOR_LESS_THAN = '<';
OPERATOR_LESS_THAN_EQUAL = '<=';
OPERATION_DEFAULT = "and";
parse(data: { sort: any; fields: any; embed: any; search: any; page: any; limit: any; }) {
const paginateParams = this.parsePaginateParams(data)
const sortParams = this.parseSortParams(data.sort)
const filterParams = this.parseFilterParams(data)
const projectionParams = this.parseProjectionParams(data.fields)
const relationParams = this.parseRelationParams(data.embed)
const relationOptionParams = this.parseRelationOptionParams(data)
const searchParams = this.parseSearch(data.search)
const results = {
pagination: paginateParams,
sort: sortParams,
filter: filterParams,
fields: projectionParams,
relation: relationParams,
search: searchParams,
relationOptions: relationOptionParams
}
return results
}
parsePaginateParams(data: { page: any; limit: any; }) {
return {
page: data.page ?? null,
limit: data.limit ?? null
}
}
parseSortParams(data: string) {
if (data) {
const sorts = data.split(',')
const parsedSort: any[] = []
sorts.forEach((sort: string) => {
if (sort.includes('-')) {
parsedSort.push({
order: 'desc',
attribute: sort.split('-')[1]
})
} else {
parsedSort.push({
order: 'asc',
attribute: sort
})
}
});
return parsedSort
}
}
parseFilterParams(data: { [x: string]: { [x: string]: any; }; operation?: any; }) {
const filters: any[] = []
Object.keys(data).forEach(key => {
if (key != 'page' && key != 'limit' && key != 'sort' && key != 'fields' && key != 'embed' && key != 'operation' && key != 'search' && !key.includes('.')) {
Object.keys(data[key]).forEach(k => {
if (k == 'between') {
const values = data[key][k].split(',')
if (values.length != 2) {
return
} else {
filters.push({
attribute: key,
operator: this.BETWEEN,
value: values
})
}
} else {
if (data[key][k].includes(',')) {
const values = data[key][k].split(',')
values.forEach((val: any) => {
filters.push(this.parseFilter(key, k, val))
});
} else {
filters.push(this.parseFilter(key, k, data[key][k]))
}
}
})
}
})
return {
operation: data.operation || 'and',
data: filters
}
}
parseProjectionParams(data: string) {
if (data) {
return data.split(',')
}
}
parseRelationParams(data: string) {
if (data) {
return data.split(',')
}
}
parseRelationOptionParams(data: { [x: string]: any; embed: any; }) {
const relationOptions: any[] = []
if (data.embed) {
data.embed.replace(/\./g, ',').split(',').forEach((relation: string) => {
const option: any = {}
option.relation = relation
if (data[`${relation}.fields`]) {
option.fields = data[`${relation}.fields`].split(',')
}
if (data[`${relation}.sort`]) {
option.sort = data[`${relation}.sort`].replace('-', '')
option.order = data[`${relation}.sort`].includes('-') ? 'desc' : 'asc'
}
const filterParams = Object.keys(data).filter((key: string) => key.split('.')[0] == relation && key != `${relation}.operation` && key != `${relation}.limit` && key != `${relation}.search` && key != `${relation}.fields` && key != `${relation}.sort`).map(key => {
const filter: any = {}
const operator: any = Object.keys(data[key])[0]
filter.attribute = key.split('.')[1]
filter.operator = this.parseOperator(operator)
filter.value = data[key][operator]
return filter
})
if (filterParams.length > 0) {
option.filter = {
operation: data[`${relation}.operation`] || 'and',
data: filterParams
}
}
if (data[`${relation}.limit`]) {
option.limit = data[`${relation}.limit`]
}
if (data[`${relation}.search`]) {
option.search = this.parseSearch(data[`${relation}.search`])
}
relationOptions.push(option)
})
}
return relationOptions
}
parseSearch(data: { [x: string]: any; }) {
if (data) {
const search: any = {
data: null,
attributes: [],
operator: this.OPERATOR_LIKE
}
Object.keys(data).forEach(key => {
search.data = `%${data[key]}%`
search.attributes = key.split(',')
})
return search
}
}
parseFilter(attribute: string, operator: string, value: string) {
if (operator == this.LIKE) {
value = `%${value}%`
}
return {
attribute: attribute,
operator: this.parseOperator(operator),
value: value
}
}
parseOperator(operator: any) {
switch (operator) {
case this.LIKE:
return this.OPERATOR_LIKE
case this.EQUAL:
return this.OPERATOR_EQUAL
case this.NOT_EQUAL:
return this.OPERATOR_NOT_EQUAL
case this.GREATER_THAN:
return this.OPERATOR_GREATER_THAN
case this.GREATER_THAN_EQUAL:
return this.OPERATOR_GREATER_THAN_EQUAL
case this.LESS_THAN:
return this.OPERATOR_LESS_THAN
case this.LESS_THAN_EQUAL:
return this.OPERATOR_LESS_THAN_EQUAL
}
}
}

View File

@ -0,0 +1,72 @@
export default class ParseUrlService {
parseUrl(request: any, currentPage: any, lastPage: any) {
const url: any = {
nextUrl: null,
prevUrl: null
}
if (request) {
let urlToParsed: any = request.completeUrl(true)
url.nextUrl = this.parseNextUrl(urlToParsed, currentPage, lastPage)
url.prevUrl = this.parsePreviousUrl(urlToParsed, currentPage, lastPage)
}
return url
}
parseNextUrl(url: string, currentPage: number, lastPage: number) {
let nextUrl: any = null
if (currentPage < lastPage && currentPage >= 1) {
if (url.includes('page=')) {
const splitedUrl = url.split('page=')
if (splitedUrl[1].includes('&')) {
const lengthToRemove = splitedUrl[1].split('&')[0].length
nextUrl = splitedUrl[0] + 'page=' + (currentPage + 1) + splitedUrl[1].substring(lengthToRemove)
} else {
nextUrl = splitedUrl[0] + 'page=' + (currentPage + 1)
}
}
} else if (currentPage < 1) {
if (url.includes('page=')) {
const splitedUrl = url.split('page=')
if (splitedUrl[1].includes('&')) {
const lengthToRemove = splitedUrl[1].split('&')[0].length
nextUrl = splitedUrl[0] + 'page=' + 1 + splitedUrl[1].substring(lengthToRemove)
} else {
nextUrl = splitedUrl[0] + 'page=' + 1
}
}
}
return nextUrl
}
parsePreviousUrl(url: string, currentPage: number, lastPage: number) {
let previousUrl: any = null
if (currentPage <= lastPage && currentPage > 1) {
if (url.includes('page=')) {
const splitedUrl = url.split('page=')
if (splitedUrl[1].includes('&')) {
const lengthToRemove = splitedUrl[1].split('&')[0].length
previousUrl = splitedUrl[0] + 'page=' + (currentPage - 1) + splitedUrl[1].substring(lengthToRemove)
} else {
previousUrl = splitedUrl[0] + 'page=' + (currentPage - 1)
}
}
} else if (currentPage > lastPage && lastPage != 0) {
if (url.includes('page=')) {
const splitedUrl = url.split('page=')
if (splitedUrl[1].includes('&')) {
const lengthToRemove = splitedUrl[1].split('&')[0].length
previousUrl = splitedUrl[0] + 'page=' + lastPage + splitedUrl[1].substring(lengthToRemove)
} else {
previousUrl = splitedUrl[0] + 'page=' + lastPage
}
}
}
return previousUrl
}
}

View File

@ -0,0 +1,83 @@
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'
import AuthService from 'App/Services/Auth/AuthService'
import AccountService from 'App/Services/User/AccountService'
import Base64 from 'base-64'
export default class AuthController {
service = new AuthService()
accountService = new AccountService()
public async login ({ auth, request, response }: HttpContextContract) {
try {
const credentials = this.getBasicAuth(request.header('authorization'))
const rememberMe = request.body().remember_me ?? false
const token = await this.service.login(credentials, auth, rememberMe)
return response.api(token, 'OK', 200, request)
} catch (error) {
return response.error(error.message)
}
}
public async logout ({ auth, response }: HttpContextContract) {
try {
await auth.use('api').revoke()
return response.api(null, 'Logout successful!')
} catch (error) {
return response.error(error.message)
}
}
public async oauthRedirect ({ ally, response }) {
try {
return ally.use('google').redirect()
} catch (error) {
return response.error(error.message)
}
}
public async oauthCallback ({ ally, auth, response }) {
try {
const google = await ally.use('google').user()
let user = await this.accountService.findByEmail(google.email)
if (!user) {
return response.error('akun tidak terdaftar')
}else{
if (!user.google_id) {
await this.accountService.update(user.id, {
google_id: google.id,
status:true
})
}
await user.load('role')
const authResult = await this.service.generateToken(auth, user, true)
const encodeToken = Base64.encode(JSON.stringify(authResult))
return response.redirect().toPath(`http://localhost:4200/google-redirect?token=${encodeToken}`)
}
} catch (error) {
return response.error(error.message)
}
}
public async forgotPassword ({ request, response }) {
try {
const data = await request.all()
await this.service.forgotPassword(data.credential, request)
return response.api(null, 'Forgot password link has been sent to your email')
} catch (error) {
return response.error(error.message)
}
}
public async restorePassword ({ request, response }) {
try {
const data = await request.all()
await this.service.restorePassword(data)
return response.api(null, 'Password restored!')
} catch (error) {
return response.error(error.message)
}
}
getBasicAuth(authHeader: any) {
const data = Base64.decode(authHeader.split(' ')[1]).split(':')
return { email: data[0], password: data[1] }
}
}

View File

@ -0,0 +1,96 @@
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'
import AccountService from 'App/Services/User/AccountService'
import CreateAccountValidator from 'App/Validators/User/CreateAccountValidator'
import UpdateAccountValidator from 'App/Validators/User/UpdateAccountValidator'
import { ValidationException } from '@ioc:Adonis/Core/Validator'
export default class AccountController {
service = new AccountService()
FETCHED_ATTRIBUTE = [
'urole_id',
'username',
'pwd',
'email',
'google_id',
'fullname',
'avatar',
'is_ban',
]
public async index ({ request, response }: HttpContextContract) {
try {
const options = request.parseParams(request.all())
const result = await this.service.getAll(options)
return response.api(result, 'OK', 200, request)
} catch (error) {
return response.error(error.message)
}
}
public async store ({ request, response }: HttpContextContract) {
try {
await request.validate(CreateAccountValidator)
const data = request.only(this.FETCHED_ATTRIBUTE)
const result = await this.service.store(data)
return response.api(result, 'Account created!', 201)
} catch (error) {
if (error instanceof ValidationException) {
const errorValidation: any = error
return response.error(errorValidation.message, errorValidation.messages.errors, 422)
}
return response.error(error.message)
}
}
public async show ({ params, request, response }: HttpContextContract) {
try {
const options = request.parseParams(request.all())
const result = await this.service.show(params.id, options)
if (!result) {
return response.api(null, `Account with id: ${params.id} not found`)
}
return response.api(result)
} catch (error) {
return response.error(error.message)
}
}
public async update ({ params, request, response }: HttpContextContract) {
try {
await request.validate(UpdateAccountValidator)
const data = request.only(this.FETCHED_ATTRIBUTE)
const result = await this.service.update(params.id, data)
if (!result) {
return response.api(null, `Account with id: ${params.id} not found`)
}
return response.api(result, 'Account updated!')
} catch (error) {
if (error instanceof ValidationException) {
const errorValidation: any = error
return response.error(errorValidation.message, errorValidation.messages.errors, 422)
}
return response.error(error.message)
}
}
public async destroy ({ params, response }: HttpContextContract) {
try {
const result = await this.service.delete(params.id)
if (!result) {
return response.api(null, `Account with id: ${params.id} not found`)
}
return response.api(null, 'Account deleted!')
} catch (error) {
return response.error(error.message)
}
}
public async destroyAll ({ response }: HttpContextContract) {
try {
await this.service.deleteAll()
return response.api(null, 'All Account deleted!')
} catch (error) {
return response.error(error.message)
}
}
}

View File

@ -0,0 +1,90 @@
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'
import RoleService from 'App/Services/User/RoleService'
import CreateRoleValidator from 'App/Validators/User/CreateRoleValidator'
import UpdateRoleValidator from 'App/Validators/User/UpdateRoleValidator'
import { ValidationException } from '@ioc:Adonis/Core/Validator'
export default class RoleController {
service = new RoleService()
FETCHED_ATTRIBUTE = [
'code',
'name',
]
public async index ({ request, response }: HttpContextContract) {
try {
const options = request.parseParams(request.all())
const result = await this.service.getAll(options)
return response.api(result, 'OK', 200, request)
} catch (error) {
return response.error(error.message)
}
}
public async store ({ request, response }: HttpContextContract) {
try {
await request.validate(CreateRoleValidator)
const data = request.only(this.FETCHED_ATTRIBUTE)
const result = await this.service.store(data)
return response.api(result, 'Role created!', 201)
} catch (error) {
if (error instanceof ValidationException) {
const errorValidation: any = error
return response.error(errorValidation.message, errorValidation.messages.errors, 422)
}
return response.error(error.message)
}
}
public async show ({ params, request, response }: HttpContextContract) {
try {
const options = request.parseParams(request.all())
const result = await this.service.show(params.id, options)
if (!result) {
return response.api(null, `Role with id: ${params.id} not found`)
}
return response.api(result)
} catch (error) {
return response.error(error.message)
}
}
public async update ({ params, request, response }: HttpContextContract) {
try {
await request.validate(UpdateRoleValidator)
const data = request.only(this.FETCHED_ATTRIBUTE)
const result = await this.service.update(params.id, data)
if (!result) {
return response.api(null, `Role with id: ${params.id} not found`)
}
return response.api(result, 'Role updated!')
} catch (error) {
if (error instanceof ValidationException) {
const errorValidation: any = error
return response.error(errorValidation.message, errorValidation.messages.errors, 422)
}
return response.error(error.message)
}
}
public async destroy ({ params, response }: HttpContextContract) {
try {
const result = await this.service.delete(params.id)
if (!result) {
return response.api(null, `Role with id: ${params.id} not found`)
}
return response.api(null, 'Role deleted!')
} catch (error) {
return response.error(error.message)
}
}
public async destroyAll ({ response }: HttpContextContract) {
try {
await this.service.deleteAll()
return response.api(null, 'All Role deleted!')
} catch (error) {
return response.error(error.message)
}
}
}

View File

@ -0,0 +1,16 @@
import { Exception } from '@adonisjs/core/build/standalone'
/*
|--------------------------------------------------------------------------
| Exception
|--------------------------------------------------------------------------
|
| The Exception class imported from `@adonisjs/core` allows defining
| a status code and error code for every exception.
|
| @example
| new DefaultException('message', 500, 'E_RUNTIME_EXCEPTION')
|
*/
export default class DefaultException extends Exception {
}

45
app/Exceptions/Handler.ts Normal file
View File

@ -0,0 +1,45 @@
/*
|--------------------------------------------------------------------------
| Http Exception Handler
|--------------------------------------------------------------------------
|
| AdonisJs will forward all exceptions occurred during an HTTP request to
| the following class. You can learn more about exception handling by
| reading docs.
|
| The exception handler extends a base `HttpExceptionHandler` which is not
| mandatory, however it can do lot of heavy lifting to handle the errors
| properly.
|
*/
import Logger from '@ioc:Adonis/Core/Logger'
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'
import HttpExceptionHandler from '@ioc:Adonis/Core/HttpExceptionHandler'
export default class ExceptionHandler extends HttpExceptionHandler {
protected statusPages = {
'403': 'errors/unauthorized',
'404': 'errors/not-found',
'500..599': 'errors/server-error',
}
constructor () {
super(Logger)
}
public async handle(error: any, ctx: HttpContextContract) {
/**
* Self handle the validation exception
*/
if (error.code === 'E_ROUTE_NOT_FOUND') {
return ctx.response.error(`Route [${ctx.request.method()}] ${ctx.request.completeUrl()} not found!`, null, 404)
}
/**
* Forward rest of the exceptions to the parent class
*/
return super.handle(error, ctx)
}
}

76
app/Middleware/Auth.ts Normal file
View File

@ -0,0 +1,76 @@
import { GuardsList } from '@ioc:Adonis/Addons/Auth'
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'
import { AuthenticationException } from '@adonisjs/auth/build/standalone'
/**
* Auth middleware is meant to restrict un-authenticated access to a given route
* or a group of routes.
*
* You must register this middleware inside `start/kernel.ts` file under the list
* of named middleware.
*/
export default class AuthMiddleware {
/**
* The URL to redirect to when request is Unauthorized
*/
protected redirectTo = '/login'
/**
* Authenticates the current HTTP request against a custom set of defined
* guards.
*
* The authentication loop stops as soon as the user is authenticated using any
* of the mentioned guards and that guard will be used by the rest of the code
* during the current request.
*/
protected async authenticate(auth: HttpContextContract['auth'], guards: (keyof GuardsList)[]) {
/**
* Hold reference to the guard last attempted within the for loop. We pass
* the reference of the guard to the "AuthenticationException", so that
* it can decide the correct response behavior based upon the guard
* driver
*/
let guardLastAttempted: string | undefined
for (let guard of guards) {
guardLastAttempted = guard
if (await auth.use(guard).check()) {
/**
* Instruct auth to use the given guard as the default guard for
* the rest of the request, since the user authenticated
* succeeded here
*/
auth.defaultGuard = guard
return true
}
}
/**
* Unable to authenticate using any guard
*/
throw new AuthenticationException(
'Unauthorized access',
'E_UNAUTHORIZED_ACCESS',
guardLastAttempted,
this.redirectTo,
)
}
/**
* Handle request
*/
public async handle (
{ auth }: HttpContextContract,
next: () => Promise<void>,
customGuards: (keyof GuardsList)[]
) {
/**
* Uses the user defined guards or the default guard mentioned in
* the config file
*/
const guards = customGuards.length ? customGuards : [auth.name]
await this.authenticate(auth, guards)
await next()
}
}

View File

@ -0,0 +1,21 @@
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'
/**
* Silent auth middleware can be used as a global middleware to silent check
* if the user is logged-in or not.
*
* The request continues as usual, even when the user is not logged-in.
*/
export default class SilentAuthMiddleware {
/**
* Handle request
*/
public async handle({ auth }: HttpContextContract, next: () => Promise<void>) {
/**
* Check if user is logged-in or not. If yes, then `ctx.auth.user` will be
* set to the instance of the currently logged in user.
*/
await auth.check()
await next()
}
}

View File

@ -0,0 +1,62 @@
import { DateTime } from 'luxon'
import { BaseModel, beforeFetch, beforeFind, BelongsTo, belongsTo, column } from '@ioc:Adonis/Lucid/Orm'
import Role from './Role'
export default class Account extends BaseModel {
public static softDelete = true
@column({ isPrimary: true })
public id: string
@column()
public urole_id: string
@column()
public username: string
@column({ serializeAs: null })
public pwd: string
@column()
public email: string
@column()
public google_id: string
@column()
public fullname: string
@column()
public avatar: string
@column()
public is_ban: boolean
@column.dateTime({ autoCreate: true })
public created_at: DateTime
@column.dateTime({ autoCreate: true, autoUpdate: true })
public updated_at: DateTime
@column.dateTime()
public deleted_at: DateTime
static get table() {
return "user.account"
}
@beforeFind()
public static findWithoutSoftDeletes(query) {
query.whereNull("deleted_at")
}
@beforeFetch()
public static fetchWithoutSoftDeletes(query) {
query.whereNull("deleted_at")
}
@belongsTo(() => Role, {
foreignKey: 'urole_id'
})
public role: BelongsTo<typeof Role>
}

23
app/Models/User/Role.ts Normal file
View File

@ -0,0 +1,23 @@
import { DateTime } from 'luxon'
import { BaseModel, column } from '@ioc:Adonis/Lucid/Orm'
export default class Role extends BaseModel {
@column({ isPrimary: true })
public id: string
@column()
public code: string
@column()
public name: string
@column.dateTime({ autoCreate: true })
public created_at: DateTime
@column.dateTime({ autoCreate: true, autoUpdate: true })
public updated_at: DateTime
static get table() {
return "user.role"
}
}

View File

@ -0,0 +1,17 @@
import BaseRepository from "App/Base/Repositories/BaseRepository";
import Account from "App/Models/User/Account";
export default class AccountRepository extends BaseRepository {
constructor() {
super(Account)
}
async findByEmail(email: string) {
try {
return await this.model.query().where('email', email).first()
} catch (error) {
throw error
}
}
}

View File

@ -0,0 +1,9 @@
import BaseRepository from "App/Base/Repositories/BaseRepository";
import Role from "App/Models/User/Role";
export default class RoleRepository extends BaseRepository {
constructor() {
super(Role)
}
}

View File

@ -0,0 +1,113 @@
import AccountRepository from "App/Repositories/User/AccountRepository"
import Hash from "@ioc:Adonis/Core/Hash"
import * as jwt from 'jsonwebtoken'
import DefaultException from "App/Exceptions/DefaultException"
import moment from 'moment'
import Base64 from 'base-64'
import Env from '@ioc:Adonis/Core/Env'
import Mail from "@ioc:Adonis/Addons/Mail"
export default class AuthService {
accountRepository = new AccountRepository()
async login (credentials: any, auth: any, rememberMe: boolean) {
try {
const user = await this.accountRepository.findByEmail(credentials.email)
if (!user || !(await Hash.verify(user.pwd, credentials.password))) {
throw new DefaultException('Invalid email or password!')
}
if (user.is_ban) {
throw new DefaultException('User banned!')
}
await user.load("role");
return await this.generateToken(auth, user, rememberMe)
} catch (error) {
throw error
}
}
async forgotPassword (credential, request) {
try {
const user = await this.accountRepository.findByEmail(credential)
if (!user) {
throw new DefaultException('User not found')
}
const token = await this.generateRestorePasswordToken(user)
const restoreUrlWithToken = request.header('origin') + 'reset-password?token=' + token
await this.sendEmailRestorePassword(user.email, restoreUrlWithToken)
} catch (error) {
throw error
}
}
async restorePassword (request) {
try {
const { expiresIn, credential } = await this.decryptToken(request.token)
if (!this.checkExpirationToken(expiresIn)) {
throw new DefaultException('Token expired')
}
const user = await this.accountRepository.findByEmail(credential)
await this.accountRepository.update(user.id, {
pwd: await Hash.make(request.pwd)
})
} catch (error) {
throw error
}
}
async generateToken(auth: any, user: any, rememberMe: boolean) {
try {
const expiresIn = rememberMe ? '7days' : '4hours'
const token = await auth.use('api').generate(user, {expiresIn})
const jwtToken = jwt.sign({user}, 'PeSgVkYp3s6v9y$B&E)H@McQfTjWnZq4', {expiresIn: rememberMe ? '7d' : '4h'})
return { token: token.token, jwtToken }
} catch (error) {
throw error
}
}
async generateRestorePasswordToken (user) {
try {
const expiresIn = moment().add(30, 'm')
const token = Base64.encode(expiresIn + ';' + user.email)
return token
} catch (error) {
throw error
}
}
async decryptToken (token) {
try {
const encrypt = Base64.decode(token).split(';')
const result = {
expiresIn: encrypt[0],
credential: encrypt[1]
}
return result
} catch (error) {
throw error
}
}
async sendEmailRestorePassword (email, token) {
try {
await Mail.send((message) => {
message
.from(Env.get('SMTP_USERNAME'), 'Me')
.to(email)
.subject('Restore Password!')
.htmlView('emails/restore_password', { token })
})
} catch (error) {
throw error
}
}
checkExpirationToken (expiresIn) {
return expiresIn > moment()
}
}

View File

@ -0,0 +1,41 @@
import BaseService from "App/Base/Services/BaseService"
import AccountRepository from "App/Repositories/User/AccountRepository"
import Hash from '@ioc:Adonis/Core/Hash'
export default class AccountService extends BaseService {
constructor() {
super(new AccountRepository())
}
async store(data: any) {
try {
if (data.pwd) {
data.pwd = await Hash.make(data.pwd)
}
return await this.repository.store(data)
} catch (error) {
throw error
}
}
async update(id: any, data: any) {
try {
if (data.pwd) {
data.pwd = await Hash.make(data.pwd)
}
return await this.repository.update(id, data)
} catch (error) {
throw error
}
}
async findByEmail (email: string) {
try {
const akun = await this.repository.findByEmail(email)
return akun
} catch (error) {
throw error
}
}
}

View File

@ -0,0 +1,9 @@
import BaseService from "App/Base/Services/BaseService"
import RoleRepository from "App/Repositories/User/RoleRepository"
export default class RoleService extends BaseService {
constructor() {
super(new RoleRepository())
}
}

View File

@ -0,0 +1,37 @@
import { schema, validator, rules } from '@ioc:Adonis/Core/Validator'
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'
import Account from 'App/Models/User/Account'
import Role from 'App/Models/User/Role'
export default class CreateAccountValidator {
constructor (protected ctx: HttpContextContract) {
}
public reporter = validator.reporters.api
public schema = schema.create({
urole_id: schema.string({}, [
rules.exists({table: Role.table, column: Role.primaryKey})
]),
username: schema.string({}, [
rules.maxLength(100),
rules.unique({table: Account.table, column: 'username', where: {deleted_at: null}})
]),
pwd: schema.string({}, [
rules.minLength(6)
]),
email: schema.string({}, [
rules.maxLength(255),
rules.email(),
rules.unique({column: 'email', table: Account.table, where: {deleted_at: null}})
]),
google_id: schema.string.optional({}, [
rules.maxLength(255)
]),
fullname: schema.string({}, [
rules.maxLength(100)
]),
avatar: schema.string.optional(),
is_ban: schema.boolean.optional(),
})
}

View File

@ -0,0 +1,18 @@
import { schema, validator, rules } from '@ioc:Adonis/Core/Validator'
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'
export default class CreateRoleValidator {
constructor (protected ctx: HttpContextContract) {
}
public reporter = validator.reporters.api
public schema = schema.create({
code: schema.string({}, [
rules.maxLength(4)
]),
name: schema.string({}, [
rules.maxLength(50)
]),
})
}

View File

@ -0,0 +1,37 @@
import { schema, validator, rules } from '@ioc:Adonis/Core/Validator'
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'
import Role from 'App/Models/User/Role'
import Account from 'App/Models/User/Account'
export default class UpdateAccountValidator {
constructor (protected ctx: HttpContextContract) {
}
public reporter = validator.reporters.api
public schema = schema.create({
urole_id: schema.string.optional({}, [
rules.exists({table: Role.table, column: Role.primaryKey})
]),
username: schema.string.optional({}, [
rules.maxLength(100),
rules.unique({table: Account.table, column: 'username', where: {deleted_at: null}, whereNot: {id: this.ctx.params.id}})
]),
pwd: schema.string.optional({}, [
rules.minLength(6)
]),
email: schema.string.optional({}, [
rules.maxLength(255),
rules.email(),
rules.unique({column: 'email', table: Account.table, where: {deleted_at: null}, whereNot: {id: this.ctx.params.id}})
]),
google_id: schema.string.optional({}, [
rules.maxLength(255)
]),
fullname: schema.string.optional({}, [
rules.maxLength(100)
]),
avatar: schema.string.optional(),
is_ban: schema.boolean.optional(),
})
}

View File

@ -0,0 +1,18 @@
import { schema, validator, rules } from '@ioc:Adonis/Core/Validator'
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'
export default class UpdateRoleValidator {
constructor (protected ctx: HttpContextContract) {
}
public reporter = validator.reporters.api
public schema = schema.create({
code: schema.string.optional({}, [
rules.maxLength(4)
]),
name: schema.string.optional({}, [
rules.maxLength(50)
]),
})
}

270
commands/MakeModule.ts Normal file
View File

@ -0,0 +1,270 @@
import { BaseCommand, args, flags } from '@adonisjs/core/build/standalone'
import fs from 'fs'
export default class MakeModule extends BaseCommand {
public static commandName = 'make:module'
public static description = 'Make a new module'
@args.string({ description: "Domain name" })
public domain: string
@args.string({ description: "Model name" })
public model: string
@flags.boolean({ description: "Enable soft delete" })
public softDelete: boolean
@flags.boolean({ alias: 'uuid', description: "Enable soft delete" })
public enableUUID: boolean
@flags.string({ alias: 'e', description: "Set endpoint name"})
public endpoint: string
public static settings = {
loadApp: false,
stayAlive: false,
}
public async run() {
if (!fs.existsSync(`app/Models/${this.domain}`)) {
if (!fs.existsSync('app/Models')) {
fs.mkdirSync(`app/Models`)
}
fs.mkdirSync(`app/Models/${this.domain}`)
}
if (!fs.existsSync(`app/Repositories/${this.domain}`)) {
if (!fs.existsSync('app/Repositories')) {
fs.mkdirSync(`app/Repositories`)
}
fs.mkdirSync(`app/Repositories/${this.domain}`)
}
if (!fs.existsSync(`app/Services/${this.domain}`)) {
if (!fs.existsSync('app/Services')) {
fs.mkdirSync(`app/Services`)
}
fs.mkdirSync(`app/Services/${this.domain}`)
}
if (!fs.existsSync(`app/Controllers/Http/${this.domain}`)) {
if (!fs.existsSync('app/Controllers')) {
fs.mkdirSync(`app/Controllers`)
fs.mkdirSync(`app/Controllers/Http`)
}
fs.mkdirSync(`app/Controllers/Http/${this.domain}`)
}
if (!fs.existsSync(`app/Validators/${this.domain}`)) {
if (!fs.existsSync('app/Validators')) {
fs.mkdirSync(`app/Validators`)
}
fs.mkdirSync(`app/Validators/${this.domain}`)
}
if (!fs.existsSync(`start/routes/${this.domain.toLowerCase()}`)) {
if (!fs.existsSync('start/routes')) {
fs.mkdirSync(`start/routes`)
}
fs.mkdirSync(`start/routes/${this.domain.toLowerCase()}`)
}
fs.writeFileSync(`app/Models/${this.domain}/${this.model}.ts`, this.generateModel())
this.logger.success(`${this.model} Model Created!`)
fs.writeFileSync(`app/Repositories/${this.domain}/${this.model}Repository.ts`, this.generateRepository())
this.logger.success(`${this.model} Repository Created!`)
fs.writeFileSync(`app/Services/${this.domain}/${this.model}Service.ts`, this.generateService())
this.logger.success(`${this.model} Service Created!`)
fs.writeFileSync(`app/Controllers/Http/${this.domain}/${this.model}Controller.ts`, this.generateController())
this.logger.success(`${this.model} Controller Created!`)
fs.writeFileSync(`app/Validators/${this.domain}/Create${this.model}Validator.ts`, this.generateCreateValidator())
this.logger.success(`${this.model} Create Validator Created!`)
fs.writeFileSync(`app/Validators/${this.domain}/Update${this.model}Validator.ts`, this.generateUpdateValidator())
this.logger.success(`${this.model} Update Validator Created!`)
fs.writeFileSync(`start/routes/${this.domain.toLowerCase()}/${this.endpoint}.ts`, this.generateRoute())
this.logger.success(`${this.model} Route Created!`)
}
generateModel() {
return `import { DateTime } from 'luxon'
import { BaseModel${this.enableUUID ? ', beforeCreate' : ''}${this.softDelete ? ', beforeFetch, beforeFind' : ''}, column } from '@ioc:Adonis/Lucid/Orm'${this.enableUUID ? "\nimport { v4 as uuidv4, v5 as uuidv5 } from 'uuid'" : ''}
export default class ${this.model} extends BaseModel {
${this.softDelete ? 'public static softDelete = true\n\n ' : ''}@column({ isPrimary: true })
public id: string
@column.dateTime({ autoCreate: true })
public created_at: DateTime
@column.dateTime({ autoCreate: true, autoUpdate: true })
public updated_at: DateTime
${this.softDelete ? '@column.dateTime()\n public deleted_at: DateTime\n\n ' : ''}static get table() {
return "" // table name
}${this.softDelete ? '\n\n @beforeFind()\n public static findWithoutSoftDeletes(query) {\n query.whereNull("deleted_at")\n }\n\n @beforeFetch()\n public static fetchWithoutSoftDeletes(query) {\n query.whereNull("deleted_at")\n }' : ''}${this.enableUUID ? "\n\n @beforeCreate()\n public static setUUID(data: " + this.model + ") {\n const namespace = uuidv4()\n data.id = uuidv5('" + this.model + "', namespace)\n }" : ''}
}
`
}
generateRepository() {
return `import BaseRepository from "App/Base/Repositories/BaseRepository";
import ${this.model} from "App/Models/${this.domain}/${this.model}";
export default class ${this.model}Repository extends BaseRepository {
constructor() {
super(${this.model})
}
}
`
}
generateService() {
return `import BaseService from "App/Base/Services/BaseService"
import ${this.model}Repository from "App/Repositories/${this.domain}/${this.model}Repository"
export default class ${this.model}Service extends BaseService {
constructor() {
super(new ${this.model}Repository())
}
}
`
}
generateController() {
return `import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'
import ${this.model}Service from 'App/Services/${this.domain}/${this.model}Service'
import Create${this.model}Validator from 'App/Validators/${this.domain}/Create${this.model}Validator'
import Update${this.model}Validator from 'App/Validators/${this.domain}/Update${this.model}Validator'
import { ValidationException } from '@ioc:Adonis/Core/Validator'
export default class ${this.model}Controller {
service = new ${this.model}Service()
FETCHED_ATTRIBUTE = [
// attribute
]
public async index ({ request, response }: HttpContextContract) {
try {
const options = request.parseParams(request.all())
const result = await this.service.getAll(options)
return response.api(result, 'OK', 200, request)
} catch (error) {
return response.error(error.message)
}
}
public async store ({ request, response }: HttpContextContract) {
try {
await request.validate(Create${this.model}Validator)
const data = request.only(this.FETCHED_ATTRIBUTE)
const result = await this.service.store(data)
return response.api(result, '${this.model} created!', 201)
} catch (error) {
if (error instanceof ValidationException) {
const errorValidation: any = error
return response.error(errorValidation.message, errorValidation.messages.errors, 422)
}
return response.error(error.message)
}
}
public async show ({ params, request, response }: HttpContextContract) {
try {
const options = request.parseParams(request.all())
const result = await this.service.show(params.id, options)
if (!result) {
return response.api(null, ${'`'+ this.model + ' with id: ${params.id} not found`'})
}
return response.api(result)
} catch (error) {
return response.error(error.message)
}
}
public async update ({ params, request, response }: HttpContextContract) {
try {
await request.validate(Update${this.model}Validator)
const data = request.only(this.FETCHED_ATTRIBUTE)
const result = await this.service.update(params.id, data)
if (!result) {
return response.api(null, ${'`'+ this.model + ' with id: ${params.id} not found`'})
}
return response.api(result, '${this.model} updated!')
} catch (error) {
if (error instanceof ValidationException) {
const errorValidation: any = error
return response.error(errorValidation.message, errorValidation.messages.errors, 422)
}
return response.error(error.message)
}
}
public async destroy ({ params, response }: HttpContextContract) {
try {
const result = await this.service.delete(params.id)
if (!result) {
return response.api(null, ${'`'+ this.model + ' with id: ${params.id} not found`'})
}
return response.api(null, '${this.model} deleted!')
} catch (error) {
return response.error(error.message)
}
}
public async destroyAll ({ response }: HttpContextContract) {
try {
await this.service.deleteAll()
return response.api(null, 'All ${this.model} deleted!')
} catch (error) {
return response.error(error.message)
}
}
}
`
}
generateCreateValidator() {
return `import { schema, validator, rules } from '@ioc:Adonis/Core/Validator'
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'
export default class Create${this.model}Validator {
constructor (protected ctx: HttpContextContract) {
}
public reporter = validator.reporters.api
public schema = schema.create({
// your validation rules
})
}
`
}
generateUpdateValidator() {
return `import { schema, validator, rules } from '@ioc:Adonis/Core/Validator'
import { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'
export default class Update${this.model}Validator {
constructor (protected ctx: HttpContextContract) {
}
public reporter = validator.reporters.api
public schema = schema.create({
// your validation rules
})
}
`
}
generateRoute() {
return `import Route from '@ioc:Adonis/Core/Route'
Route.group(function () {
Route.delete('/', '${this.domain}/${this.model}Controller.destroyAll').as('${this.endpoint}.destroyAll')
}).prefix('${this.endpoint}')
Route.resource('${this.endpoint}', '${this.domain}/${this.model}Controller').apiOnly()
`
}
}

19
commands/index.ts Normal file
View File

@ -0,0 +1,19 @@
import { listDirectoryFiles } from '@adonisjs/core/build/standalone'
import Application from '@ioc:Adonis/Core/Application'
/*
|--------------------------------------------------------------------------
| Exporting an array of commands
|--------------------------------------------------------------------------
|
| Instead of manually exporting each file from this directory, we use the
| helper `listDirectoryFiles` to recursively collect and export an array
| of filenames.
|
| Couple of things to note:
|
| 1. The file path must be relative from the project root and not this directory.
| 2. We must ignore this file to avoid getting into an infinite loop
|
*/
export default listDirectoryFiles(__dirname, Application.appRoot, ['./commands/index'])

34
config/ally.ts Normal file
View File

@ -0,0 +1,34 @@
/**
* Config source: https://git.io/JOdi5
*
* Feel free to let us know via PR, if you find something broken in this config
* file.
*/
import Env from '@ioc:Adonis/Core/Env'
import { AllyConfig } from '@ioc:Adonis/Addons/Ally'
/*
|--------------------------------------------------------------------------
| Ally Config
|--------------------------------------------------------------------------
|
| The `AllyConfig` relies on the `SocialProviders` interface which is
| defined inside `contracts/ally.ts` file.
|
*/
const allyConfig: AllyConfig = {
/*
|--------------------------------------------------------------------------
| Google driver
|--------------------------------------------------------------------------
*/
google: {
driver: 'google',
clientId: Env.get('GOOGLE_CLIENT_ID'),
clientSecret: Env.get('GOOGLE_CLIENT_SECRET'),
callbackUrl: 'http://localhost:3333/auth/google/callback',
},
}
export default allyConfig

217
config/app.ts Normal file
View File

@ -0,0 +1,217 @@
/**
* Config source: https://git.io/JfefZ
*
* Feel free to let us know via PR, if you find something broken in this config
* file.
*/
import proxyAddr from 'proxy-addr'
import Env from '@ioc:Adonis/Core/Env'
import { ServerConfig } from '@ioc:Adonis/Core/Server'
import { LoggerConfig } from '@ioc:Adonis/Core/Logger'
import { ProfilerConfig } from '@ioc:Adonis/Core/Profiler'
import { ValidatorConfig } from '@ioc:Adonis/Core/Validator'
/*
|--------------------------------------------------------------------------
| Application secret key
|--------------------------------------------------------------------------
|
| The secret to encrypt and sign different values in your application.
| Make sure to keep the `APP_KEY` as an environment variable and secure.
|
| Note: Changing the application key for an existing app will make all
| the cookies invalid and also the existing encrypted data will not
| be decrypted.
|
*/
export const appKey: string = Env.get('APP_KEY')
/*
|--------------------------------------------------------------------------
| Http server configuration
|--------------------------------------------------------------------------
|
| The configuration for the HTTP(s) server. Make sure to go through all
| the config properties to make keep server secure.
|
*/
export const http: ServerConfig = {
/*
|--------------------------------------------------------------------------
| Allow method spoofing
|--------------------------------------------------------------------------
|
| Method spoofing enables defining custom HTTP methods using a query string
| `_method`. This is usually required when you are making traditional
| form requests and wants to use HTTP verbs like `PUT`, `DELETE` and
| so on.
|
*/
allowMethodSpoofing: false,
/*
|--------------------------------------------------------------------------
| Subdomain offset
|--------------------------------------------------------------------------
*/
subdomainOffset: 2,
/*
|--------------------------------------------------------------------------
| Request Ids
|--------------------------------------------------------------------------
|
| Setting this value to `true` will generate a unique request id for each
| HTTP request and set it as `x-request-id` header.
|
*/
generateRequestId: false,
/*
|--------------------------------------------------------------------------
| Trusting proxy servers
|--------------------------------------------------------------------------
|
| Define the proxy servers that AdonisJs must trust for reading `X-Forwarded`
| headers.
|
*/
trustProxy: proxyAddr.compile('loopback'),
/*
|--------------------------------------------------------------------------
| Generating Etag
|--------------------------------------------------------------------------
|
| Whether or not to generate an etag for every response.
|
*/
etag: false,
/*
|--------------------------------------------------------------------------
| JSONP Callback
|--------------------------------------------------------------------------
*/
jsonpCallbackName: 'callback',
/*
|--------------------------------------------------------------------------
| Cookie settings
|--------------------------------------------------------------------------
*/
cookie: {
domain: '',
path: '/',
maxAge: '2h',
httpOnly: true,
secure: false,
sameSite: false,
},
}
/*
|--------------------------------------------------------------------------
| Logger
|--------------------------------------------------------------------------
*/
export const logger: LoggerConfig = {
/*
|--------------------------------------------------------------------------
| Application name
|--------------------------------------------------------------------------
|
| The name of the application you want to add to the log. It is recommended
| to always have app name in every log line.
|
| The `APP_NAME` environment variable is automatically set by AdonisJS by
| reading the `name` property from the `package.json` file.
|
*/
name: Env.get('APP_NAME'),
/*
|--------------------------------------------------------------------------
| Toggle logger
|--------------------------------------------------------------------------
|
| Enable or disable logger application wide
|
*/
enabled: true,
/*
|--------------------------------------------------------------------------
| Logging level
|--------------------------------------------------------------------------
|
| The level from which you want the logger to flush logs. It is recommended
| to make use of the environment variable, so that you can define log levels
| at deployment level and not code level.
|
*/
level: Env.get('LOG_LEVEL', 'info'),
/*
|--------------------------------------------------------------------------
| Pretty print
|--------------------------------------------------------------------------
|
| It is highly advised NOT to use `prettyPrint` in production, since it
| can have huge impact on performance.
|
*/
prettyPrint: Env.get('NODE_ENV') === 'development',
}
/*
|--------------------------------------------------------------------------
| Profiler
|--------------------------------------------------------------------------
*/
export const profiler: ProfilerConfig = {
/*
|--------------------------------------------------------------------------
| Toggle profiler
|--------------------------------------------------------------------------
|
| Enable or disable profiler
|
*/
enabled: true,
/*
|--------------------------------------------------------------------------
| Blacklist actions/row labels
|--------------------------------------------------------------------------
|
| Define an array of actions or row labels that you want to disable from
| getting profiled.
|
*/
blacklist: [],
/*
|--------------------------------------------------------------------------
| Whitelist actions/row labels
|--------------------------------------------------------------------------
|
| Define an array of actions or row labels that you want to whitelist for
| the profiler. When whitelist is defined, then `blacklist` is ignored.
|
*/
whitelist: [],
}
/*
|--------------------------------------------------------------------------
| Validator
|--------------------------------------------------------------------------
|
| Configure the global configuration for the validator. Here's the reference
| to the default config https://git.io/JT0WE
|
*/
export const validator: ValidatorConfig = {
}

109
config/auth.ts Normal file
View File

@ -0,0 +1,109 @@
/**
* Config source: https://git.io/JY0mp
*
* Feel free to let us know via PR, if you find something broken in this config
* file.
*/
import { AuthConfig } from '@ioc:Adonis/Addons/Auth'
/*
|--------------------------------------------------------------------------
| Authentication Mapping
|--------------------------------------------------------------------------
|
| List of available authentication mapping. You must first define them
| inside the `contracts/auth.ts` file before mentioning them here.
|
*/
const authConfig: AuthConfig = {
guard: 'api',
guards: {
/*
|--------------------------------------------------------------------------
| OAT Guard
|--------------------------------------------------------------------------
|
| OAT (Opaque access tokens) guard uses database backed tokens to authenticate
| HTTP request. This guard DOES NOT rely on sessions or cookies and uses
| Authorization header value for authentication.
|
| Use this guard to authenticate mobile apps or web clients that cannot rely
| on cookies/sessions.
|
*/
api: {
driver: 'oat',
/*
|--------------------------------------------------------------------------
| Tokens provider
|--------------------------------------------------------------------------
|
| Uses SQL database for managing tokens. Use the "database" driver, when
| tokens are the secondary mode of authentication.
| For example: The Github personal tokens
|
| The foreignKey column is used to make the relationship between the user
| and the token. You are free to use any column name here.
|
*/
tokenProvider: {
type: 'api',
driver: 'database',
table: 'user.api_tokens',
foreignKey: 'user_id',
},
provider: {
/*
|--------------------------------------------------------------------------
| Driver
|--------------------------------------------------------------------------
|
| Name of the driver
|
*/
driver: 'lucid',
/*
|--------------------------------------------------------------------------
| Identifier key
|--------------------------------------------------------------------------
|
| The identifier key is the unique key on the model. In most cases specifying
| the primary key is the right choice.
|
*/
identifierKey: 'id',
/*
|--------------------------------------------------------------------------
| Uids
|--------------------------------------------------------------------------
|
| Uids are used to search a user against one of the mentioned columns. During
| login, the auth module will search the user mentioned value against one
| of the mentioned columns to find their user record.
|
*/
uids: ['email'],
/*
|--------------------------------------------------------------------------
| Model
|--------------------------------------------------------------------------
|
| The model to use for fetching or finding users. The model is imported
| lazily since the config files are read way earlier in the lifecycle
| of booting the app and the models may not be in a usable state at
| that time.
|
*/
model: () => import('App/Models/User/Account'),
},
},
},
}
export default authConfig

211
config/bodyparser.ts Normal file
View File

@ -0,0 +1,211 @@
/**
* Config source: https://git.io/Jfefn
*
* Feel free to let us know via PR, if you find something broken in this config
* file.
*/
import { BodyParserConfig } from '@ioc:Adonis/Core/BodyParser'
const bodyParserConfig: BodyParserConfig = {
/*
|--------------------------------------------------------------------------
| White listed methods
|--------------------------------------------------------------------------
|
| HTTP methods for which body parsing must be performed. It is a good practice
| to avoid body parsing for `GET` requests.
|
*/
whitelistedMethods: ['POST', 'PUT', 'PATCH', 'DELETE'],
/*
|--------------------------------------------------------------------------
| JSON parser settings
|--------------------------------------------------------------------------
|
| The settings for the JSON parser. The types defines the request content
| types which gets processed by the JSON parser.
|
*/
json: {
encoding: 'utf-8',
limit: '1mb',
strict: true,
types: [
'application/json',
'application/json-patch+json',
'application/vnd.api+json',
'application/csp-report',
],
},
/*
|--------------------------------------------------------------------------
| Form parser settings
|--------------------------------------------------------------------------
|
| The settings for the `application/x-www-form-urlencoded` parser. The types
| defines the request content types which gets processed by the form parser.
|
*/
form: {
encoding: 'utf-8',
limit: '1mb',
queryString: {},
/*
|--------------------------------------------------------------------------
| Convert empty strings to null
|--------------------------------------------------------------------------
|
| Convert empty form fields to null. HTML forms results in field string
| value when the field is left blank. This option normalizes all the blank
| field values to "null"
|
*/
convertEmptyStringsToNull: true,
types: [
'application/x-www-form-urlencoded',
],
},
/*
|--------------------------------------------------------------------------
| Raw body parser settings
|--------------------------------------------------------------------------
|
| Raw body just reads the request body stream as a plain text, which you
| can process by hand. This must be used when request body type is not
| supported by the body parser.
|
*/
raw: {
encoding: 'utf-8',
limit: '1mb',
queryString: {},
types: [
'text/*',
],
},
/*
|--------------------------------------------------------------------------
| Multipart parser settings
|--------------------------------------------------------------------------
|
| The settings for the `multipart/form-data` parser. The types defines the
| request content types which gets processed by the form parser.
|
*/
multipart: {
/*
|--------------------------------------------------------------------------
| Auto process
|--------------------------------------------------------------------------
|
| The auto process option will process uploaded files and writes them to
| the `tmp` folder. You can turn it off and then manually use the stream
| to pipe stream to a different destination.
|
| It is recommended to keep `autoProcess=true`. Unless you are processing bigger
| file sizes.
|
*/
autoProcess: true,
/*
|--------------------------------------------------------------------------
| Files to be processed manually
|--------------------------------------------------------------------------
|
| You can turn off `autoProcess` for certain routes by defining
| routes inside the following array.
|
| NOTE: Make sure the route pattern starts with a leading slash.
|
| Correct
| ```js
| /projects/:id/file
| ```
|
| Incorrect
| ```js
| projects/:id/file
| ```
*/
processManually: [],
/*
|--------------------------------------------------------------------------
| Temporary file name
|--------------------------------------------------------------------------
|
| When auto processing is on. We will use this method to compute the temporary
| file name. AdonisJs will compute a unique `tmpPath` for you automatically,
| However, you can also define your own custom method.
|
*/
// tmpFileName () {
// },
/*
|--------------------------------------------------------------------------
| Encoding
|--------------------------------------------------------------------------
|
| Request body encoding
|
*/
encoding: 'utf-8',
/*
|--------------------------------------------------------------------------
| Convert empty strings to null
|--------------------------------------------------------------------------
|
| Convert empty form fields to null. HTML forms results in field string
| value when the field is left blank. This option normalizes all the blank
| field values to "null"
|
*/
convertEmptyStringsToNull: true,
/*
|--------------------------------------------------------------------------
| Max Fields
|--------------------------------------------------------------------------
|
| The maximum number of fields allowed in the request body. The field includes
| text inputs and files both.
|
*/
maxFields: 1000,
/*
|--------------------------------------------------------------------------
| Request body limit
|--------------------------------------------------------------------------
|
| The total limit to the multipart body. This includes all request files
| and fields data.
|
*/
limit: '20mb',
/*
|--------------------------------------------------------------------------
| Types
|--------------------------------------------------------------------------
|
| The types that will be considered and parsed as multipart body.
|
*/
types: [
'multipart/form-data',
],
},
}
export default bodyParserConfig

134
config/cors.ts Normal file
View File

@ -0,0 +1,134 @@
/**
* Config source: https://git.io/JfefC
*
* Feel free to let us know via PR, if you find something broken in this config
* file.
*/
import { CorsConfig } from '@ioc:Adonis/Core/Cors'
const corsConfig: CorsConfig = {
/*
|--------------------------------------------------------------------------
| Enabled
|--------------------------------------------------------------------------
|
| A boolean to enable or disable CORS integration from your AdonisJs
| application.
|
| Setting the value to `true` will enable the CORS for all HTTP request. However,
| you can define a function to enable/disable it on per request basis as well.
|
*/
enabled: true,
// You can also use a function that return true or false.
// enabled: (request) => request.url().startsWith('/api')
/*
|--------------------------------------------------------------------------
| Origin
|--------------------------------------------------------------------------
|
| Set a list of origins to be allowed for `Access-Control-Allow-Origin`.
| The value can be one of the following:
|
| https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Origin
|
| Boolean (true) - Allow current request origin.
| Boolean (false) - Disallow all.
| String - Comma separated list of allowed origins.
| Array - An array of allowed origins.
| String (*) - A wildcard (*) to allow all request origins.
| Function - Receives the current origin string and should return
| one of the above values.
|
*/
origin: '*',
/*
|--------------------------------------------------------------------------
| Methods
|--------------------------------------------------------------------------
|
| An array of allowed HTTP methods for CORS. The `Access-Control-Request-Method`
| is checked against the following list.
|
| Following is the list of default methods. Feel free to add more.
*/
methods: ['GET', 'HEAD', 'POST', 'PUT', 'DELETE'],
/*
|--------------------------------------------------------------------------
| Headers
|--------------------------------------------------------------------------
|
| List of headers to be allowed for `Access-Control-Allow-Headers` header.
| The value can be one of the following:
|
| https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Request-Headers
|
| Boolean(true) - Allow all headers mentioned in `Access-Control-Request-Headers`.
| Boolean(false) - Disallow all headers.
| String - Comma separated list of allowed headers.
| Array - An array of allowed headers.
| Function - Receives the current header and should return one of the above values.
|
*/
headers: true,
/*
|--------------------------------------------------------------------------
| Expose Headers
|--------------------------------------------------------------------------
|
| A list of headers to be exposed by setting `Access-Control-Expose-Headers`.
| header. By default following 6 simple response headers are exposed.
|
| Cache-Control
| Content-Language
| Content-Type
| Expires
| Last-Modified
| Pragma
|
| In order to add more headers, simply define them inside the following array.
|
| https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Expose-Headers
|
*/
exposeHeaders: [
'cache-control',
'content-language',
'content-type',
'expires',
'last-modified',
'pragma',
],
/*
|--------------------------------------------------------------------------
| Credentials
|--------------------------------------------------------------------------
|
| Toggle `Access-Control-Allow-Credentials` header. If value is set to `true`,
| then header will be set, otherwise not.
|
| https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Credentials
|
*/
credentials: true,
/*
|--------------------------------------------------------------------------
| MaxAge
|--------------------------------------------------------------------------
|
| Define `Access-Control-Max-Age` header in seconds.
| https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Max-Age
|
*/
maxAge: 90,
}
export default corsConfig

55
config/database.ts Normal file
View File

@ -0,0 +1,55 @@
/**
* Config source: https://git.io/JesV9
*
* Feel free to let us know via PR, if you find something broken in this config
* file.
*/
import Env from '@ioc:Adonis/Core/Env'
import { DatabaseConfig } from '@ioc:Adonis/Lucid/Database'
const databaseConfig: DatabaseConfig = {
/*
|--------------------------------------------------------------------------
| Connection
|--------------------------------------------------------------------------
|
| The primary connection for making database queries across the application
| You can use any key from the `connections` object defined in this same
| file.
|
*/
connection: Env.get('DB_CONNECTION'),
connections: {
/*
|--------------------------------------------------------------------------
| PostgreSQL config
|--------------------------------------------------------------------------
|
| Configuration for PostgreSQL database. Make sure to install the driver
| from npm when using this connection
|
| npm i pg
|
*/
pg: {
client: 'pg',
connection: {
host: Env.get('PG_HOST'),
port: Env.get('PG_PORT'),
user: Env.get('PG_USER'),
password: Env.get('PG_PASSWORD', ''),
database: Env.get('PG_DB_NAME'),
},
migrations: {
naturalSort: true,
},
healthCheck: false,
debug: false,
},
}
}
export default databaseConfig

75
config/hash.ts Normal file
View File

@ -0,0 +1,75 @@
/**
* Config source: https://git.io/JfefW
*
* Feel free to let us know via PR, if you find something broken in this config
* file.
*/
import Env from '@ioc:Adonis/Core/Env'
import { HashConfig } from '@ioc:Adonis/Core/Hash'
/*
|--------------------------------------------------------------------------
| Hash Config
|--------------------------------------------------------------------------
|
| The `HashConfig` relies on the `HashList` interface which is
| defined inside `contracts` directory.
|
*/
const hashConfig: HashConfig = {
/*
|--------------------------------------------------------------------------
| Default hasher
|--------------------------------------------------------------------------
|
| By default we make use of the bcrypt hasher to hash values. However, feel
| free to change the default value
|
*/
default: Env.get('HASH_DRIVER', 'argon'),
list: {
/*
|--------------------------------------------------------------------------
| Argon
|--------------------------------------------------------------------------
|
| Argon mapping uses the `argon2` driver to hash values.
|
| Make sure you install the underlying dependency for this driver to work.
| https://www.npmjs.com/package/phc-argon2.
|
| npm install phc-argon2
|
*/
argon: {
driver: 'argon2',
variant: 'id',
iterations: 3,
memory: 4096,
parallelism: 1,
saltSize: 16,
},
/*
|--------------------------------------------------------------------------
| Bcrypt
|--------------------------------------------------------------------------
|
| Bcrypt mapping uses the `bcrypt` driver to hash values.
|
| Make sure you install the underlying dependency for this driver to work.
| https://www.npmjs.com/package/phc-bcrypt.
|
| npm install phc-bcrypt
|
*/
bcrypt: {
driver: 'bcrypt',
rounds: 10,
},
},
}
export default hashConfig

59
config/mail.ts Normal file
View File

@ -0,0 +1,59 @@
/**
* Config source: https://git.io/JvgAf
*
* Feel free to let us know via PR, if you find something broken in this contract
* file.
*/
import Env from '@ioc:Adonis/Core/Env'
import { MailConfig } from '@ioc:Adonis/Addons/Mail'
const mailConfig: MailConfig = {
/*
|--------------------------------------------------------------------------
| Default mailer
|--------------------------------------------------------------------------
|
| The following mailer will be used to send emails, when you don't specify
| a mailer
|
*/
mailer: 'smtp',
/*
|--------------------------------------------------------------------------
| Mailers
|--------------------------------------------------------------------------
|
| You can define or more mailers to send emails from your application. A
| single `driver` can be used to define multiple mailers with different
| config.
|
| For example: Postmark driver can be used to have different mailers for
| sending transactional and promotional emails
|
*/
mailers: {
/*
|--------------------------------------------------------------------------
| Smtp
|--------------------------------------------------------------------------
|
| Uses SMTP protocol for sending email
|
*/
smtp: {
driver: 'smtp',
host: Env.get('SMTP_HOST'),
port: Env.get('SMTP_PORT'),
auth: {
user: Env.get('SMTP_USERNAME'),
pass: Env.get('SMTP_PASSWORD'),
type: 'login',
}
},
},
}
export default mailConfig

118
config/session.ts Normal file
View File

@ -0,0 +1,118 @@
/**
* Config source: https://git.io/JeYHp
*
* Feel free to let us know via PR, if you find something broken in this config
* file.
*/
import Env from '@ioc:Adonis/Core/Env'
import Application from '@ioc:Adonis/Core/Application'
import { SessionConfig } from '@ioc:Adonis/Addons/Session'
const sessionConfig: SessionConfig = {
/*
|--------------------------------------------------------------------------
| Enable/Disable sessions
|--------------------------------------------------------------------------
|
| Setting the following property to "false" will disable the session for the
| entire application
|
*/
enabled: true,
/*
|--------------------------------------------------------------------------
| Driver
|--------------------------------------------------------------------------
|
| The session driver to use. You can choose between one of the following
| drivers.
|
| - cookie (Uses signed cookies to store session values)
| - file (Uses filesystem to store session values)
| - redis (Uses redis. Make sure to install "@adonisjs/redis" as well)
|
| Note: Switching drivers will make existing sessions invalid.
|
*/
driver: Env.get('SESSION_DRIVER'),
/*
|--------------------------------------------------------------------------
| Cookie name
|--------------------------------------------------------------------------
|
| The name of the cookie that will hold the session id.
|
*/
cookieName: 'adonis-session',
/*
|--------------------------------------------------------------------------
| Clear session when browser closes
|--------------------------------------------------------------------------
|
| Whether or not you want to destroy the session when browser closes. Setting
| this value to `true` will ignore the `age`.
|
*/
clearWithBrowser: false,
/*
|--------------------------------------------------------------------------
| Session age
|--------------------------------------------------------------------------
|
| The duration for which session stays active after no activity. A new HTTP
| request to the server is considered as activity.
|
| The value can be a number in milliseconds or a string that must be valid
| as per https://npmjs.org/package/ms package.
|
| Example: `2 days`, `2.5 hrs`, `1y`, `5s` and so on.
|
*/
age: '2h',
/*
|--------------------------------------------------------------------------
| Cookie values
|--------------------------------------------------------------------------
|
| The cookie settings are used to setup the session id cookie and also the
| driver will use the same values.
|
*/
cookie: {
path: '/',
httpOnly: true,
sameSite: false,
},
/*
|--------------------------------------------------------------------------
| Configuration for the file driver
|--------------------------------------------------------------------------
|
| The file driver needs absolute path to the directory in which sessions
| must be stored.
|
*/
file: {
location: Application.tmpPath('sessions'),
},
/*
|--------------------------------------------------------------------------
| Redis driver
|--------------------------------------------------------------------------
|
| The redis connection you want session driver to use. The same connection
| must be defined inside `config/redis.ts` file as well.
|
*/
redisConnection: 'local',
}
export default sessionConfig

238
config/shield.ts Normal file
View File

@ -0,0 +1,238 @@
/**
* Config source: https://git.io/Jvwvt
*
* Feel free to let us know via PR, if you find something broken in this config
* file.
*/
import Env from '@ioc:Adonis/Core/Env'
import { ShieldConfig } from '@ioc:Adonis/Addons/Shield'
/*
|--------------------------------------------------------------------------
| Content Security Policy
|--------------------------------------------------------------------------
|
| Content security policy filters out the origins not allowed to execute
| and load resources like scripts, styles and fonts. There are wide
| variety of options to choose from.
*/
export const csp: ShieldConfig['csp'] = {
/*
|--------------------------------------------------------------------------
| Enable/disable CSP
|--------------------------------------------------------------------------
|
| The CSP rules are disabled by default for seamless onboarding.
|
*/
enabled: false,
/*
|--------------------------------------------------------------------------
| Directives
|--------------------------------------------------------------------------
|
| All directives are defined in camelCase and here is the list of
| available directives and their possible values.
|
| https://content-security-policy.com
|
| @example
| directives: {
| defaultSrc: ['self', '@nonce', 'cdnjs.cloudflare.com']
| }
|
*/
directives: {
},
/*
|--------------------------------------------------------------------------
| Report only
|--------------------------------------------------------------------------
|
| Setting `reportOnly=true` will not block the scripts from running and
| instead report them to a URL.
|
*/
reportOnly: false,
}
/*
|--------------------------------------------------------------------------
| CSRF Protection
|--------------------------------------------------------------------------
|
| CSRF Protection adds another layer of security by making sure, actionable
| routes does have a valid token to execute an action.
|
*/
export const csrf: ShieldConfig['csrf'] = {
/*
|--------------------------------------------------------------------------
| Enable/Disable CSRF
|--------------------------------------------------------------------------
*/
enabled: Env.get('NODE_ENV') !== 'testing',
/*
|--------------------------------------------------------------------------
| Routes to Ignore
|--------------------------------------------------------------------------
|
| Define an array of route patterns that you want to ignore from CSRF
| validation. Make sure the route patterns are started with a leading
| slash. Example:
|
| `/foo/bar`
|
| Also you can define a function that is evaluated on every HTTP Request.
| ```
| exceptRoutes: ({ request }) => request.url().includes('/api')
| ```
|
*/
exceptRoutes: [],
/*
|--------------------------------------------------------------------------
| Enable Sharing Token Via Cookie
|--------------------------------------------------------------------------
|
| When the following flag is enabled, AdonisJS will drop `XSRF-TOKEN`
| cookie that frontend frameworks can read and return back as a
| `X-XSRF-TOKEN` header.
|
| The cookie has `httpOnly` flag set to false, so it is little insecure and
| can be turned off when you are not using a frontend framework making
| AJAX requests.
|
*/
enableXsrfCookie: true,
/*
|--------------------------------------------------------------------------
| Methods to Validate
|--------------------------------------------------------------------------
|
| Define an array of HTTP methods to be validated for a valid CSRF token.
|
*/
methods: ['POST', 'PUT', 'PATCH', 'DELETE'],
}
/*
|--------------------------------------------------------------------------
| DNS Prefetching
|--------------------------------------------------------------------------
|
| DNS prefetching allows browsers to proactively perform domain name
| resolution in background.
|
| Learn more at https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-DNS-Prefetch-Control
|
*/
export const dnsPrefetch: ShieldConfig['dnsPrefetch'] = {
/*
|--------------------------------------------------------------------------
| Enable/disable this feature
|--------------------------------------------------------------------------
*/
enabled: true,
/*
|--------------------------------------------------------------------------
| Allow or Dis-Allow Explicitly
|--------------------------------------------------------------------------
|
| The `enabled` boolean does not set `X-DNS-Prefetch-Control` header. However
| the `allow` boolean controls the value of `X-DNS-Prefetch-Control` header.
|
| - When `allow = true`, then `X-DNS-Prefetch-Control = 'on'`
| - When `allow = false`, then `X-DNS-Prefetch-Control = 'off'`
|
*/
allow: true,
}
/*
|--------------------------------------------------------------------------
| Iframe Options
|--------------------------------------------------------------------------
|
| xFrame defines whether or not your website can be embedded inside an
| iframe. Choose from one of the following options.
|
| - DENY
| - SAMEORIGIN
| - ALLOW-FROM http://example.com
|
| Learn more at https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options
*/
export const xFrame: ShieldConfig['xFrame'] = {
enabled: true,
action: 'DENY',
}
/*
|--------------------------------------------------------------------------
| Http Strict Transport Security
|--------------------------------------------------------------------------
|
| A security to ensure that a browser always makes a connection over
| HTTPS.
|
| Learn more at https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Strict-Transport-Security
|
*/
export const hsts: ShieldConfig['hsts'] = {
enabled: true,
/*
|--------------------------------------------------------------------------
| Max Age
|--------------------------------------------------------------------------
|
| Control, how long the browser should remember that a site is only to be
| accessed using HTTPS.
|
*/
maxAge: '180 days',
/*
|--------------------------------------------------------------------------
| Include Subdomains
|--------------------------------------------------------------------------
|
| Apply rules on the subdomains as well.
|
*/
includeSubDomains: true,
/*
|--------------------------------------------------------------------------
| Preloading
|--------------------------------------------------------------------------
|
| Google maintains a service to register your domain and it will preload
| the HSTS policy. Learn more https://hstspreload.org/
|
*/
preload: false,
}
/*
|--------------------------------------------------------------------------
| No Sniff
|--------------------------------------------------------------------------
|
| Browsers have a habit of sniffing content-type of a response. Which means
| files with .txt extension containing Javascript code will be executed as
| Javascript. You can disable this behavior by setting nosniff to false.
|
| Learn more at https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options
|
*/
export const contentTypeSniffing: ShieldConfig['contentTypeSniffing'] = {
enabled: true,
}

64
config/static.ts Normal file
View File

@ -0,0 +1,64 @@
/**
* Config source: https://git.io/Jfefl
*
* Feel free to let us know via PR, if you find something broken in this config
* file.
*/
import { AssetsConfig } from '@ioc:Adonis/Core/Static'
const staticConfig: AssetsConfig = {
/*
|--------------------------------------------------------------------------
| Enabled
|--------------------------------------------------------------------------
|
| A boolean to enable or disable serving static files. The static files
| are served from the `public` directory inside the application root.
| However, you can override the default path inside `.adonisrc.json`
| file.
|
|
*/
enabled: true,
/*
|--------------------------------------------------------------------------
| Handling Dot Files
|--------------------------------------------------------------------------
|
| Decide how you want the static assets server to handle the `dotfiles`.
| By default, we ignore them as if they don't exists. However, you
| can choose between one of the following options.
|
| - ignore: Behave as if the file doesn't exists. Results in 404.
| - deny: Deny access to the file. Results in 403.
| - allow: Serve the file contents
|
*/
dotFiles: 'ignore',
/*
|--------------------------------------------------------------------------
| Generating Etag
|--------------------------------------------------------------------------
|
| Handle whether or not to generate etags for the files. Etag allows browser
| to utilize the cache when file hasn't been changed.
|
*/
etag: true,
/*
|--------------------------------------------------------------------------
| Set Last Modified
|--------------------------------------------------------------------------
|
| Whether or not to set the `Last-Modified` header in the response. Uses
| the file system's last modified value.
|
*/
lastModified: true,
}
export default staticConfig

15
contracts/ally.ts Normal file
View File

@ -0,0 +1,15 @@
/**
* Contract source: https://git.io/JOdiQ
*
* Feel free to let us know via PR, if you find something broken in this contract
* file.
*/
declare module '@ioc:Adonis/Addons/Ally' {
interface SocialProviders {
google: {
config: GoogleDriverConfig
implementation: GoogleDriverContract
}
}
}

72
contracts/auth.ts Normal file
View File

@ -0,0 +1,72 @@
/**
* Contract source: https://git.io/JOdz5
*
* Feel free to let us know via PR, if you find something broken in this
* file.
*/
import Account from 'App/Models/Account'
declare module '@ioc:Adonis/Addons/Auth' {
/*
|--------------------------------------------------------------------------
| Providers
|--------------------------------------------------------------------------
|
| The providers are used to fetch users. The Auth module comes pre-bundled
| with two providers that are `Lucid` and `Database`. Both uses database
| to fetch user details.
|
| You can also create and register your own custom providers.
|
*/
interface ProvidersList {
/*
|--------------------------------------------------------------------------
| User Provider
|--------------------------------------------------------------------------
|
| The following provider uses Lucid models as a driver for fetching user
| details from the database for authentication.
|
| You can create multiple providers using the same underlying driver with
| different Lucid models.
|
*/
user: {
implementation: LucidProviderContract<typeof Account>
config: LucidProviderConfig<typeof Account>
}
}
/*
|--------------------------------------------------------------------------
| Guards
|--------------------------------------------------------------------------
|
| The guards are used for authenticating users using different drivers.
| The auth module comes with 3 different guards.
|
| - SessionGuardContract
| - BasicAuthGuardContract
| - OATGuardContract ( Opaque access token )
|
| Every guard needs a provider for looking up users from the database.
|
*/
interface GuardsList {
/*
|--------------------------------------------------------------------------
| OAT Guard
|--------------------------------------------------------------------------
|
| OAT, stands for (Opaque access tokens) guard uses database backed tokens
| to authenticate requests.
|
*/
api: {
implementation: OATGuardContract<'user', 'api'>
config: OATGuardConfig<'user'>
}
}
}

24
contracts/env.ts Normal file
View File

@ -0,0 +1,24 @@
/**
* Contract source: https://git.io/JTm6U
*
* Feel free to let us know via PR, if you find something broken in this contract
* file.
*/
declare module '@ioc:Adonis/Core/Env' {
/*
|--------------------------------------------------------------------------
| Getting types for validated environment variables
|--------------------------------------------------------------------------
|
| The `default` export from the "../env.ts" file exports types for the
| validated environment variables. Here we merge them with the `EnvTypes`
| interface so that you can enjoy intellisense when using the "Env"
| module.
|
*/
type CustomTypes = typeof import("../env").default;
interface EnvTypes extends CustomTypes {
}
}

30
contracts/events.ts Normal file
View File

@ -0,0 +1,30 @@
/**
* Contract source: https://git.io/JfefG
*
* Feel free to let us know via PR, if you find something broken in this contract
* file.
*/
declare module '@ioc:Adonis/Core/Event' {
/*
|--------------------------------------------------------------------------
| Define typed events
|--------------------------------------------------------------------------
|
| You can define types for events inside the following interface and
| AdonisJS will make sure that all listeners and emit calls adheres
| to the defined types.
|
| For example:
|
| interface EventsList {
| 'new:user': UserModel
| }
|
| Now calling `Event.emit('new:user')` will statically ensure that passed value is
| an instance of the the UserModel only.
|
*/
interface EventsList {
}
}

19
contracts/hash.ts Normal file
View File

@ -0,0 +1,19 @@
/**
* Contract source: https://git.io/Jfefs
*
* Feel free to let us know via PR, if you find something broken in this contract
* file.
*/
declare module '@ioc:Adonis/Core/Hash' {
interface HashersList {
bcrypt: {
config: BcryptConfig,
implementation: BcryptContract,
},
argon: {
config: ArgonConfig,
implementation: ArgonContract,
},
}
}

14
contracts/mail.ts Normal file
View File

@ -0,0 +1,14 @@
/**
* Contract source: https://git.io/JvgAT
*
* Feel free to let us know via PR, if you find something broken in this contract
* file.
*/
declare module '@ioc:Adonis/Addons/Mail' {
import { MailDrivers } from '@ioc:Adonis/Addons/Mail'
interface MailersList {
smtp: MailDrivers['smtp'],
}
}

5
contracts/request.ts Normal file
View File

@ -0,0 +1,5 @@
declare module '@ioc:Adonis/Core/Request' {
interface RequestContract {
parseParams(request: any): any
}
}

6
contracts/response.ts Normal file
View File

@ -0,0 +1,6 @@
declare module '@ioc:Adonis/Core/Response' {
interface ResponseContract {
api(data?: any | null, message?: string | null, code?: number | null, request?: any | null): any,
error(message?: string | null, errors?: any | null, code?: number | null): any
}
}

View File

@ -0,0 +1 @@
// import Factory from '@ioc:Adonis/Lucid/Factory'

View File

@ -0,0 +1,31 @@
-- CREATE DIRECTORY FOR TABLESPACE
# sudo mkdir /data/postgres/tablespaces/ts_pg_roadreport_dev
-- GRANT PERMISSION ON TS FOR postgres
# sudo chown postgres:postgres /data/postgres/tablespaces/ts_pg_roadreport_dev
-- CREATE TABLESPACE FOR DATA
CREATE TABLESPACE "ts_pg_roadreport_dev"
OWNER postgres
LOCATION '/data/postgres/tablespaces/ts_pg_roadreport_dev';
-- CREATE DATABASE
CREATE DATABASE "pg_roadreport_dev"
WITH
OWNER = postgres
ENCODING = 'UTF8'
TABLESPACE = "ts_pg_roadreport_dev"
CONNECTION LIMIT = -1;
-- ENABLE UUID SUPPORT
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
-- CREATE USER
CREATE ROLE "roadreportpgdb" LOGIN PASSWORD 'roadreportpgdb1qaz';
-- ADD USER ACCESS IN PG HBA_CONF
-- Refresh PG_CONF
SELECT pg_reload_conf();

View File

@ -0,0 +1,7 @@
-- === Grant Script on database---
-- === GRANT PUBLIC SCHEMA ==== --
GRANT USAGE ON SCHEMA "public" TO roadreportpgdb;
GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO roadreportpgdb;
GRANT EXECUTE ON ALL FUNCTIONS IN SCHEMA public TO roadreportpgdb;
GRANT USAGE ON ALL SEQUENCES IN SCHEMA public TO roadreportpgdb;

View File

@ -0,0 +1,114 @@
-- =============START USER SCHEMA==================
DROP SCHEMA IF EXISTS "user";
CREATE SCHEMA "user"
-- ----------------------------
-- Table structure for role
-- ----------------------------
DROP TABLE IF EXISTS "user"."role" CASCADE;
-- Ver 0.1.0 ( Design )
-- Ver 0.1.0 ( Current & Implemented )
CREATE TABLE "user"."role"(
"id" UUID NOT NULL DEFAULT uuid_generate_v4(),
"code" VARCHAR(5) NOT NULL ,
"name" VARCHAR(100) NOT NULL ,
"desc" TEXT NULL ,
"created_at" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ,
"updated_at" TIMESTAMP NULL ,
PRIMARY KEY ("id")
);
-- INDEX TABLE role
CREATE INDEX "pkey_urole" ON "user"."role" ("id");
-- ----------------------------
-- Table structure for account
-- ----------------------------
DROP TABLE IF EXISTS "user"."account" CASCADE;
-- Ver 0.1.0 ( Design )
-- Ver 0.1.0 ( Current & Implemented )
CREATE TABLE "user"."account"(
"id" UUID NOT NULL DEFAULT uuid_generate_v4(),
"urole_id" UUID NOT NULL ,
"username" varchar(255) NOT NULL UNIQUE,
"pwd" TEXT NOT NULL ,
"fullname" varchar(255) NOT NULL ,
"shortname" varchar(255) NOT NULL ,
"email" varchar(255) NOT NULL UNIQUE ,
"avatar" varchar(255) NOT NULL ,
"note" varchar(255) NULL ,
"status" BOOLEAN NOT NULL DEFAULT FALSE ,
"is_ban" BOOLEAN NOT NULL DEFAULT FALSE ,
"last_active" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ,
"created_at" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ,
"updated_at" TIMESTAMP NULL ,
"deleted_at" TIMESTAMP NULL ,
PRIMARY KEY ("id"),
FOREIGN KEY ("urole_id") REFERENCES "user"."role"("id") ON UPDATE CASCADE ON DELETE CASCADE
);
-- INDEX TABLE account
CREATE INDEX "pkey_uaccount" ON "user"."account" ("id");
CREATE INDEX "fkey_uaccount_urole" ON "user"."account" ("urole_id");
-- ----------------------------
-- Table structure for api_tokens
-- ----------------------------
DROP TABLE IF EXISTS "user"."api_tokens" CASCADE;
-- Ver 0.1.0 ( Design )
-- Ver 0.1.0 ( Current & Implemented )
CREATE TABLE "user"."api_tokens"(
"id" UUID NOT NULL DEFAULT uuid_generate_v4(),
"user_id" UUID NOT NULL ,
"name" VARCHAR(255) NOT NULL ,
"type" VARCHAR(255) NOT NULL ,
"token" VARCHAR(255) NOT NULL ,
"created_at" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
"expires_at" TIMESTAMP ,
PRIMARY KEY ("id"),
FOREIGN KEY ("user_id") REFERENCES "user"."account"("id")
);
-- INDEX TABLE account
CREATE INDEX "pkey_uapi_tokens" ON "user"."api_tokens" ("id");
CREATE INDEX "fkey_uapi_tokens_uaccount" ON "user"."api_tokens" ("user_id");
-- ----------------------------
-- Table structure for event
-- ----------------------------
DROP TABLE IF EXISTS "user"."event" CASCADE;
DROP SEQUENCE IF EXISTS event_id_seq;
-- Ver 1
-- ( Current & Implemented )
CREATE TABLE "user"."event"(
"id" UUID NOT NULL DEFAULT uuid_generate_v4(),
"uaccount_id" UUID NOT NULL ,
"project_id" UUID NULL ,
"fullname" VARCHAR NOT NULL ,
"title" VARCHAR NOT NULL ,
"body" TEXT NULL ,
"start" DATE NOT NULL ,
"end" DATE NOT NULL ,
"created_at" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMP NULL,
"deleted_at" TIMESTAMP NULL,
PRIMARY KEY ("id"),
FOREIGN KEY ("uaccount_id") REFERENCES "user"."account"("id")
);
-- =============END USER SCHEMA==================

31
env.ts Normal file
View File

@ -0,0 +1,31 @@
/*
|--------------------------------------------------------------------------
| Validating Environment Variables
|--------------------------------------------------------------------------
|
| In this file we define the rules for validating environment variables.
| By performing validation we ensure that your application is running in
| a stable environment with correct configuration values.
|
| This file is read automatically by the framework during the boot lifecycle
| and hence do not rename or move this file to a different location.
|
*/
import Env from '@ioc:Adonis/Core/Env'
export default Env.rules({
HOST: Env.schema.string({ format: 'host' }),
PORT: Env.schema.number(),
APP_KEY: Env.schema.string(),
APP_NAME: Env.schema.string(),
CACHE_VIEWS: Env.schema.boolean(),
SESSION_DRIVER: Env.schema.string(),
NODE_ENV: Env.schema.enum(['development', 'production', 'testing'] as const),
SMTP_HOST: Env.schema.string({ format: 'host' }),
SMTP_PORT: Env.schema.number(),
SMTP_USERNAME: Env.schema.string(),
SMTP_PASSWORD: Env.schema.string(),
GOOGLE_CLIENT_ID: Env.schema.string(),
GOOGLE_CLIENT_SECRET: Env.schema.string(),
})

40
package.json Normal file
View File

@ -0,0 +1,40 @@
{
"name": "roadreport-backend",
"version": "1.0.0",
"private": true,
"scripts": {
"build": "node ace build --production",
"start": "node server.js",
"dev": "node ace serve --watch"
},
"devDependencies": {
"@adonisjs/assembler": "^5.3.5",
"@types/uuid": "^8.3.1",
"adonis-preset-ts": "^2.1.0",
"pino-pretty": "^5.1.2",
"typescript": "^4.2.4",
"youch": "^2.2.2",
"youch-terminal": "^1.1.1"
},
"dependencies": {
"@adonisjs/ally": "^4.1.1",
"@adonisjs/auth": "^8.2.1",
"@adonisjs/core": "^5.1.11",
"@adonisjs/lucid": "^18.0.0",
"@adonisjs/mail": "^7.2.4",
"@adonisjs/repl": "^3.1.5",
"@adonisjs/session": "^6.1.1",
"@adonisjs/shield": "^7.0.5",
"@adonisjs/view": "^6.0.8",
"base-64": "^1.0.0",
"jsonwebtoken": "^8.5.1",
"luxon": "^2.0.2",
"moment": "^2.29.1",
"pg": "^8.7.1",
"phc-argon2": "^1.1.2",
"proxy-addr": "^2.0.7",
"reflect-metadata": "^0.1.13",
"source-map-support": "^0.5.19",
"uuid": "^8.3.2"
}
}

94
providers/AppProvider.ts Normal file
View File

@ -0,0 +1,94 @@
import { ApplicationContract } from '@ioc:Adonis/Core/Application'
import ParseParamService from 'App/Base/Services/ParseParamService'
import ParseUrlService from 'App/Base/Services/ParseUrlService'
export default class AppProvider {
public static needsApplication = true
constructor (protected app: ApplicationContract) {
}
public register () {
// Register your own bindings
}
public async boot () {
// IoC container is ready
this.extendRequest()
this.extendResponse()
}
public async ready () {
// App is ready
}
public async shutdown () {
// Cleanup, since app is going down
}
extendResponse() {
const Response = this.app.container.use('Adonis/Core/Response')
Response.macro('api', function (data, message = 'OK', code: any = 200, request = null) {
const parseUrlService = new ParseUrlService()
if (data) {
if (data.rows) {
const url = parseUrlService.parseUrl(request, data.currentPage ?? 1, data.lastPage ?? 1)
this.status(code).json({
data: data.rows,
page: data.currentPage ?? 1,
total: data.total ? parseInt(data.total) : data.rows.length,
perPage: data.perPage ?? data.rows.length,
lastPage: data.lastPage ?? 1,
nextPage: url.nextUrl,
previousPage: url.prevUrl,
statusCode: code,
message: message
})
} else if (data.length || Array.isArray(data)) {
this.status(code).json({
data: data,
page: 1,
total: data.length,
perPage: data.length,
lastPage: 1,
nextPage: null,
previousPage: null,
statusCode: code,
message: message
})
} else {
this.status(code).json({
data: data,
statusCode: code,
message: message
})
}
} else {
this.status(code).json({
data: null,
statusCode: code,
message: message
})
}
})
Response.macro('error', function (message, errors = null, code: any = 400) {
this.status(code).json({
statusCode: code,
message: message,
errors: errors
})
})
}
extendRequest() {
const Request = this.app.container.use('Adonis/Core/Request')
Request.macro('parseParams', function (request) {
const parseParamService = new ParseParamService()
return parseParamService.parse(request)
})
}
}

BIN
public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View File

@ -0,0 +1,3 @@
<p>
token: {{token}}
</p>

View File

@ -0,0 +1 @@
<p> It's a 404 </p>

View File

@ -0,0 +1 @@
<p> It's a 500 </p>

View File

@ -0,0 +1 @@
<p> It's a 403 </p>

110
resources/views/index.edge Normal file
View File

@ -0,0 +1,110 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>AdonisJS - A fully featured web framework for Node.js</title>
<link href="https://fonts.googleapis.com/css?family=Poppins:400,500&display=swap" rel="stylesheet">
<style>
html, body {
background-color: #F7F8FA;
font-family: 'Poppins', sans-serif;
height: 100vh;
color: #46444c;
position: relative;
}
body:before {
content: '';
background: #5A45FF;
top: 0;
left: 0;
right: 0;
height: 6px;
position: absolute;
}
* {
margin: 0;
padding: 0;
}
a {
color: #5A45FF;
text-decoration: none;
}
main {
max-width: 620px;
margin: auto;
height: 100vh;
padding: 0 30px;
align-items: center;
display: flex;
justify-content: center;
}
.title {
font-size: 50px;
line-height: 50px;
margin-bottom: 10px;
color: #17161A;
}
.subtitle {
font-size: 26px;
margin-bottom: 40px;
}
p {
margin-bottom: 20px;
}
main ul {
list-style: none;
}
main li {
margin-bottom: 5px;
position: relative;
padding-left: 25px;
}
main li:before {
content: '—';
position: absolute;
left: 0;
}
main code {
font-size: 16px;
background: #e6e2ff;
}
</style>
</head>
<body>
<main>
<div>
<h1 class="title"> It Works! </h1>
<p class="subtitle">
Congratulations, you have just created your first AdonisJS app.
</p>
<ul>
<li>
The route for this page is defined inside <code>start/routes.ts</code> file
</li>
<li>
You can update this page by editing <code>resources/views/welcome.edge</code> file
</li>
<li>
If you run into problems, you can reach us on <a href="https://discord.gg/vDcEjq6?">Discord</a> or the <a href="https://forum.adonisjs.com/">Forum</a>.
</li>
</ul>
</div>
</main>
</body>
</html>

View File

@ -0,0 +1,110 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>AdonisJS - A fully featured web framework for Node.js</title>
<link href="https://fonts.googleapis.com/css?family=Poppins:400,500&display=swap" rel="stylesheet">
<style>
html, body {
background-color: #F7F8FA;
font-family: 'Poppins', sans-serif;
height: 100vh;
color: #46444c;
position: relative;
}
body:before {
content: '';
background: #5A45FF;
top: 0;
left: 0;
right: 0;
height: 6px;
position: absolute;
}
* {
margin: 0;
padding: 0;
}
a {
color: #5A45FF;
text-decoration: none;
}
main {
max-width: 620px;
margin: auto;
height: 100vh;
padding: 0 30px;
align-items: center;
display: flex;
justify-content: center;
}
.title {
font-size: 50px;
line-height: 50px;
margin-bottom: 10px;
color: #17161A;
}
.subtitle {
font-size: 26px;
margin-bottom: 40px;
}
p {
margin-bottom: 20px;
}
main ul {
list-style: none;
}
main li {
margin-bottom: 5px;
position: relative;
padding-left: 25px;
}
main li:before {
content: '—';
position: absolute;
left: 0;
}
main code {
font-size: 16px;
background: #e6e2ff;
}
</style>
</head>
<body>
<main>
<div>
<h1 class="title"> It Works! </h1>
<p class="subtitle">
Congratulations, you have just created your first AdonisJS app.
</p>
<ul>
<li>
The route for this page is defined inside <code>start/routes.ts</code> file
</li>
<li>
You can update this page by editing <code>resources/views/welcome.edge</code> file
</li>
<li>
If you run into problems, you can reach us on <a href="https://discord.gg/vDcEjq6?">Discord</a> or the <a href="https://forum.adonisjs.com/">Forum</a>.
</li>
</ul>
</div>
</main>
</body>
</html>

21
server.ts Normal file
View File

@ -0,0 +1,21 @@
/*
|--------------------------------------------------------------------------
| AdonisJs Server
|--------------------------------------------------------------------------
|
| The contents in this file is meant to bootstrap the AdonisJs application
| and start the HTTP server to accept incoming connections. You must avoid
| making this file dirty and instead make use of `lifecycle hooks` provided
| by AdonisJs service providers for custom code.
|
*/
import 'reflect-metadata'
import sourceMapSupport from 'source-map-support'
import { Ignitor } from '@adonisjs/core/build/standalone'
sourceMapSupport.install({ handleUncaughtExceptions: false })
new Ignitor(__dirname)
.httpServer()
.start()

44
start/kernel.ts Normal file
View File

@ -0,0 +1,44 @@
/*
|--------------------------------------------------------------------------
| Application middleware
|--------------------------------------------------------------------------
|
| This file is used to define middleware for HTTP requests. You can register
| middleware as a `closure` or an IoC container binding. The bindings are
| preferred, since they keep this file clean.
|
*/
import Server from '@ioc:Adonis/Core/Server'
/*
|--------------------------------------------------------------------------
| Global middleware
|--------------------------------------------------------------------------
|
| An array of global middleware, that will be executed in the order they
| are defined for every HTTP requests.
|
*/
Server.middleware.register([
() => import('@ioc:Adonis/Core/BodyParser'),
])
/*
|--------------------------------------------------------------------------
| Named middleware
|--------------------------------------------------------------------------
|
| Named middleware are defined as key-value pair. The value is the namespace
| or middleware function and key is the alias. Later you can use these
| alias on individual routes. For example:
|
| { auth: () => import('App/Middleware/Auth') }
|
| and then use it as follows
|
| Route.get('dashboard', 'UserController.dashboard').middleware('auth')
|
*/
Server.middleware.registerNamed({
})

59
start/routes.ts Normal file
View File

@ -0,0 +1,59 @@
/*
|--------------------------------------------------------------------------
| Routes
|--------------------------------------------------------------------------
|
| This file is dedicated for defining HTTP routes. A single file is enough
| for majority of projects, however you can define routes in different
| files and just make sure to import them inside this file. For example
|
| Define routes in following two files
| start/routes/cart.ts
| start/routes/customer.ts
|
| and then import them inside `start/routes.ts` as follows
|
| import './routes/cart'
| import './routes/customer''
|
*/
import Route from '@ioc:Adonis/Core/Route'
import fs from 'fs';
Route.group(function () {
if (fs.existsSync(`${__dirname}/routes`)) {
const folders = fs.readdirSync(`${__dirname}/routes`)
folders.map((folder) => {
if (folder != 'auth') {
const files = fs.readdirSync(`${__dirname}/routes/${folder}`)
files.map((file) => {
if (!file.includes('.map')) {
require(`${__dirname}/routes/${folder}/${file}`)
}
})
}
})
}
}).prefix('api')
Route.group(function () {
if (fs.existsSync(`${__dirname}/routes/auth`)) {
const files = fs.readdirSync(`${__dirname}/routes/auth`)
files.map((file) => {
if (!file.includes('.map')) {
require(`${__dirname}/routes/auth/${file}`)
}
})
}
}).prefix('auth')
Route.get('/', async ({ view }) => {
return view.render('welcome')
})
Route.get('/api', async ({ response }) => {
return response.api(null, 'It works!')
})
Route.on('*').render('index')

View File

@ -0,0 +1,8 @@
import Route from '@ioc:Adonis/Core/Route'
Route.post('/login', 'Auth/AuthController.login').as('auth.login')
Route.post('/logout', 'Auth/AuthController.logout').as('auth.logout')
Route.get('/google/redirect', 'Auth/AuthController.oauthRedirect').as('auth.redirect')
Route.get('/google/callback', 'Auth/AuthController.oauthCallback').as('auth.callback')
Route.post('/forgot-password', 'Auth/AuthController.forgotPassword').as('auth.forgot-password')
Route.post('/reset-password', 'Auth/AuthController.restorePassword').as('auth.reset-password')

View File

@ -0,0 +1,6 @@
import Route from '@ioc:Adonis/Core/Route'
Route.group(function () {
Route.delete('/', 'User/RoleController.destroyAll').as('roles.destroyAll')
}).prefix('roles')
Route.resource('roles', 'User/RoleController').apiOnly()

View File

@ -0,0 +1,6 @@
import Route from '@ioc:Adonis/Core/Route'
Route.group(function () {
Route.delete('/', 'User/AccountController.destroyAll').as('users.destroyAll')
}).prefix('users')
Route.resource('users', 'User/AccountController').apiOnly()

View File

@ -1 +0,0 @@
tes

40
tsconfig.json Normal file
View File

@ -0,0 +1,40 @@
{
"extends": "./node_modules/adonis-preset-ts/tsconfig",
"include": [
"**/*"
],
"exclude": [
"node_modules",
"build"
],
"compilerOptions": {
"outDir": "build",
"rootDir": "./",
"sourceMap": true,
"paths": {
"App/*": [
"./app/*"
],
"Config/*": [
"./config/*"
],
"Contracts/*": [
"./contracts/*"
],
"Database/*": [
"./database/*"
]
},
"types": [
"@adonisjs/core",
"@adonisjs/repl",
"@adonisjs/session",
"@adonisjs/view",
"@adonisjs/shield",
"@adonisjs/lucid",
"@adonisjs/auth",
"@adonisjs/mail",
"@adonisjs/ally"
]
}
}