├── preview.png ├── tsconfig.json ├── tests ├── functional │ └── hello_world.spec.ts └── bootstrap.ts ├── .env.example ├── seed.sh ├── Makefile ├── .editorconfig ├── database └── migrations │ ├── 1713033061142_create_departments_table.ts │ ├── 1713033569875_create_districts_table.ts │ ├── 1713033174356_create_towns_table.ts │ ├── 1713033631981_create_neighborhoods_table.ts │ ├── 1713031088037_create_users_table.ts │ └── 1713031088042_create_access_tokens_table.ts ├── .gitignore ├── config ├── static.ts ├── cors.ts ├── database.ts ├── hash.ts ├── auth.ts ├── logger.ts ├── app.ts └── bodyparser.ts ├── app ├── models │ ├── neighborhood.ts │ ├── district.ts │ ├── department.ts │ ├── town.ts │ └── user.ts ├── controllers │ ├── neighborhoods_controller.ts │ ├── districts_controller.ts │ ├── towns_controller.ts │ └── departments_controller.ts ├── middleware │ ├── force_json_response_middleware.ts │ ├── container_bindings_middleware.ts │ └── auth_middleware.ts └── exceptions │ └── handler.ts ├── resources └── views │ └── docs.edge ├── ace.js ├── start ├── env.ts ├── kernel.ts └── routes.ts ├── LICENSE ├── bin ├── server.ts ├── console.ts └── test.ts ├── .github └── workflows │ └── main.yaml ├── README.md ├── package.json ├── adonisrc.ts └── public └── openapi.yaml /preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jsbenin/bj-decoupage-territorial/HEAD/preview.png -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@adonisjs/tsconfig/tsconfig.app.json", 3 | "compilerOptions": { 4 | "rootDir": "./", 5 | "outDir": "./build", 6 | }, 7 | } 8 | -------------------------------------------------------------------------------- /tests/functional/hello_world.spec.ts: -------------------------------------------------------------------------------- 1 | import { test } from '@japa/runner' 2 | 3 | test.group('Hello world', () => { 4 | test('example test', async ({ assert }) => { 5 | assert.isTrue(true) 6 | }) 7 | }) 8 | -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | TZ=UTC 2 | PORT=3333 3 | HOST=localhost 4 | LOG_LEVEL=info 5 | APP_KEY= 6 | NODE_ENV=development 7 | DB_HOST=127.0.0.1 8 | DB_PORT=5432 9 | DB_USER=postgres 10 | DB_PASSWORD= 11 | DB_DATABASE= 12 | -------------------------------------------------------------------------------- /seed.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Replace with your database file path 4 | DB_FILE="tmp/db.sqlite3" 5 | 6 | # Replace with your SQL file path 7 | SQL_FILE="data.sql" 8 | 9 | # Check if the SQL file exists 10 | if [ ! -f "$SQL_FILE" ]; then 11 | echo "Error: SQL file '$SQL_FILE' does not exist!" 12 | exit 1 13 | fi 14 | 15 | # Open the SQLite command line tool 16 | sqlite3 "$DB_FILE" <"$SQL_FILE" 17 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: reset, seed 2 | 3 | setup: 4 | cp env.exemple 5 | pnpm install 6 | node ace generate:key 7 | mkdir tmp && touch tmp/db.sqlite3 8 | node ace migration:run 9 | sudo chmod +x ./seed.sh 10 | ./seed.sh 11 | pnpm run dev 12 | 13 | 14 | seed: 15 | chmod +x ./seed.sh 16 | sh ./seed.sh 17 | 18 | reset: 19 | node ace db:wipe 20 | node ace migration:run 21 | node ace db:seed 22 | 23 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 2 6 | end_of_line = lf 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | 11 | [*.json] 12 | insert_final_newline = unset 13 | 14 | [**.min.js] 15 | indent_style = unset 16 | insert_final_newline = unset 17 | 18 | [MakeFile] 19 | indent_style = space 20 | 21 | [*.md] 22 | trim_trailing_whitespace = false 23 | -------------------------------------------------------------------------------- /database/migrations/1713033061142_create_departments_table.ts: -------------------------------------------------------------------------------- 1 | import { BaseSchema } from '@adonisjs/lucid/schema' 2 | 3 | export default class extends BaseSchema { 4 | protected tableName = 'departments' 5 | 6 | async up() { 7 | this.schema.createTable(this.tableName, (table) => { 8 | table.increments('id') 9 | table.string('name') 10 | }) 11 | } 12 | 13 | async down() { 14 | this.schema.dropTable(this.tableName) 15 | } 16 | } 17 | 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Dependencies and AdonisJS build 2 | node_modules 3 | build 4 | tmp 5 | 6 | # Secrets 7 | .env 8 | .env.local 9 | .env.production.local 10 | .env.development.local 11 | 12 | # Frontend assets compiled code 13 | public/assets 14 | 15 | # Build tools specific 16 | npm-debug.log 17 | yarn-error.log 18 | 19 | # Editors specific 20 | .fleet 21 | .idea 22 | .vscode 23 | 24 | # Platform specific 25 | .DS_Store 26 | 27 | 28 | database/*.sqlite 29 | database/*.db 30 | -------------------------------------------------------------------------------- /config/static.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from '@adonisjs/static' 2 | 3 | /** 4 | * Configuration options to tweak the static files middleware. 5 | * The complete set of options are documented on the 6 | * official documentation website. 7 | * 8 | * https://docs.adonisjs.com/guides/static-assets 9 | */ 10 | const staticServerConfig = defineConfig({ 11 | enabled: true, 12 | etag: true, 13 | lastModified: true, 14 | dotFiles: 'ignore', 15 | }) 16 | 17 | export default staticServerConfig 18 | -------------------------------------------------------------------------------- /app/models/neighborhood.ts: -------------------------------------------------------------------------------- 1 | import { BaseModel, belongsTo, column } from '@adonisjs/lucid/orm' 2 | import type { BelongsTo } from '@adonisjs/lucid/types/relations' 3 | import District from './district.js' 4 | 5 | export default class Neighborhood extends BaseModel { 6 | @column({ isPrimary: true }) 7 | declare id: number 8 | 9 | @column() 10 | declare name: string 11 | 12 | @column() 13 | declare districtId: number 14 | 15 | @belongsTo(() => District) 16 | declare district: BelongsTo 17 | } 18 | -------------------------------------------------------------------------------- /app/controllers/neighborhoods_controller.ts: -------------------------------------------------------------------------------- 1 | import Neighborhood from '#models/neighborhood' 2 | import type { HttpContext } from '@adonisjs/core/http' 3 | 4 | export default class NeighborhoodsController { 5 | async index({ request, response }: HttpContext) { 6 | const page = request.input('page', 1) 7 | const limit = request.input('limit', 20) 8 | 9 | let neighborhoods = await Neighborhood.query().paginate(page, limit) 10 | return response.status(200).json({ 11 | neighborhoods, 12 | }) 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /config/cors.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from '@adonisjs/cors' 2 | 3 | /** 4 | * Configuration options to tweak the CORS policy. The following 5 | * options are documented on the official documentation website. 6 | * 7 | * https://docs.adonisjs.com/guides/security/cors 8 | */ 9 | const corsConfig = defineConfig({ 10 | enabled: true, 11 | origin: true, 12 | methods: ['GET', 'HEAD', 'POST', 'PUT', 'DELETE'], 13 | headers: true, 14 | exposeHeaders: [], 15 | credentials: true, 16 | maxAge: 90, 17 | }) 18 | 19 | export default corsConfig 20 | -------------------------------------------------------------------------------- /config/database.ts: -------------------------------------------------------------------------------- 1 | import app from '@adonisjs/core/services/app' 2 | import { defineConfig } from '@adonisjs/lucid' 3 | 4 | const dbConfig = defineConfig({ 5 | connection: 'sqlite', 6 | connections: { 7 | sqlite: { 8 | client: 'better-sqlite3', 9 | connection: { 10 | filename: app.tmpPath('db.sqlite3') 11 | }, 12 | useNullAsDefault: true, 13 | migrations: { 14 | naturalSort: true, 15 | paths: ['database/migrations'], 16 | }, 17 | }, 18 | }, 19 | }) 20 | 21 | export default dbConfig -------------------------------------------------------------------------------- /database/migrations/1713033569875_create_districts_table.ts: -------------------------------------------------------------------------------- 1 | import { BaseSchema } from '@adonisjs/lucid/schema' 2 | 3 | export default class extends BaseSchema { 4 | protected tableName = 'districts' 5 | 6 | async up() { 7 | this.schema.createTable(this.tableName, (table) => { 8 | table.increments('id') 9 | table.string('name').notNullable() 10 | table.integer('town_id') 11 | table.foreign('town_id').references('id').inTable('towns') 12 | }) 13 | } 14 | 15 | async down() { 16 | this.schema.dropTable(this.tableName) 17 | } 18 | } 19 | 20 | -------------------------------------------------------------------------------- /database/migrations/1713033174356_create_towns_table.ts: -------------------------------------------------------------------------------- 1 | import { BaseSchema } from '@adonisjs/lucid/schema' 2 | 3 | export default class extends BaseSchema { 4 | protected tableName = 'towns' 5 | 6 | async up() { 7 | this.schema.createTable(this.tableName, (table) => { 8 | table.increments('id') 9 | table.string('name').notNullable().unique() 10 | table.integer('department_id') 11 | table.foreign('department_id').references('id').inTable('departments') 12 | }) 13 | } 14 | 15 | async down() { 16 | this.schema.dropTable(this.tableName) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /database/migrations/1713033631981_create_neighborhoods_table.ts: -------------------------------------------------------------------------------- 1 | import { BaseSchema } from '@adonisjs/lucid/schema' 2 | 3 | export default class extends BaseSchema { 4 | protected tableName = 'neighborhoods' 5 | 6 | async up() { 7 | this.schema.createTable(this.tableName, (table) => { 8 | table.increments('id') 9 | table.string('name').notNullable() 10 | table.integer('district_id') 11 | table.foreign('district_id').references('id').inTable('districts') 12 | }) 13 | } 14 | 15 | async down() { 16 | this.schema.dropTable(this.tableName) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /config/hash.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig, drivers } from '@adonisjs/core/hash' 2 | 3 | const hashConfig = defineConfig({ 4 | default: 'scrypt', 5 | 6 | list: { 7 | scrypt: drivers.scrypt({ 8 | cost: 16384, 9 | blockSize: 8, 10 | parallelization: 1, 11 | maxMemory: 33554432, 12 | }), 13 | }, 14 | }) 15 | 16 | export default hashConfig 17 | 18 | /** 19 | * Inferring types for the list of hashers you have configured 20 | * in your application. 21 | */ 22 | declare module '@adonisjs/core/types' { 23 | export interface HashersList extends InferHashers {} 24 | } 25 | -------------------------------------------------------------------------------- /app/middleware/force_json_response_middleware.ts: -------------------------------------------------------------------------------- 1 | import type { HttpContext } from '@adonisjs/core/http' 2 | import type { NextFn } from '@adonisjs/core/types/http' 3 | 4 | /** 5 | * Updating the "Accept" header to always accept "application/json" response 6 | * from the server. This will force the internals of the framework like 7 | * validator errors or auth errors to return a JSON response. 8 | */ 9 | export default class ForceJsonResponseMiddleware { 10 | async handle({ request }: HttpContext, next: NextFn) { 11 | const headers = request.headers() 12 | headers.accept = 'application/json' 13 | 14 | return next() 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /app/models/district.ts: -------------------------------------------------------------------------------- 1 | import { BaseModel, belongsTo, column, hasMany } from '@adonisjs/lucid/orm' 2 | import type { BelongsTo, HasMany } from '@adonisjs/lucid/types/relations' 3 | import Town from './town.js' 4 | import Neighborhood from './neighborhood.js' 5 | 6 | export default class District extends BaseModel { 7 | @column({ isPrimary: true }) 8 | declare id: number 9 | 10 | @column() 11 | declare name: string 12 | 13 | @column() 14 | declare townId: number 15 | 16 | @belongsTo(() => Town) 17 | declare town: BelongsTo 18 | 19 | @hasMany(() => Neighborhood) 20 | declare neighborhoods: HasMany 21 | } 22 | -------------------------------------------------------------------------------- /database/migrations/1713031088037_create_users_table.ts: -------------------------------------------------------------------------------- 1 | import { BaseSchema } from '@adonisjs/lucid/schema' 2 | 3 | export default class extends BaseSchema { 4 | protected tableName = 'users' 5 | 6 | async up() { 7 | this.schema.createTable(this.tableName, (table) => { 8 | table.increments('id').notNullable() 9 | table.string('full_name').nullable() 10 | table.string('email', 254).notNullable().unique() 11 | table.string('password').notNullable() 12 | 13 | table.timestamp('created_at').notNullable() 14 | table.timestamp('updated_at').nullable() 15 | }) 16 | } 17 | 18 | async down() { 19 | this.schema.dropTable(this.tableName) 20 | } 21 | } -------------------------------------------------------------------------------- /app/controllers/districts_controller.ts: -------------------------------------------------------------------------------- 1 | import District from '#models/district' 2 | import type { HttpContext } from '@adonisjs/core/http' 3 | 4 | export default class DistrictsController { 5 | async index({ response }: HttpContext) { 6 | const districts = await District.all() 7 | return response.status(200).json({ 8 | districts, 9 | }) 10 | } 11 | 12 | async findNeighborhoods({ response, params }: HttpContext) { 13 | const district = await District.findByOrFail('name', params.name.toUpperCase()) 14 | await district.load('neighborhoods') 15 | return response.status(200).json({ 16 | district: district?.name, 17 | neighborhoods: district.neighborhoods, 18 | }) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /app/middleware/container_bindings_middleware.ts: -------------------------------------------------------------------------------- 1 | import { Logger } from '@adonisjs/core/logger' 2 | import { HttpContext } from '@adonisjs/core/http' 3 | import type { NextFn } from '@adonisjs/core/types/http' 4 | 5 | /** 6 | * The container bindings middleware binds classes to their request 7 | * specific value using the container resolver. 8 | * 9 | * - We bind "HttpContext" class to the "ctx" object 10 | * - And bind "Logger" class to the "ctx.logger" object 11 | */ 12 | export default class ContainerBindingsMiddleware { 13 | handle(ctx: HttpContext, next: NextFn) { 14 | ctx.containerResolver.bindValue(HttpContext, ctx) 15 | ctx.containerResolver.bindValue(Logger, ctx.logger) 16 | 17 | return next() 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /resources/views/docs.edge: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Documentation - Decoupage territorial 5 | 6 | 7 | 8 | 9 | 10 | 13 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /app/middleware/auth_middleware.ts: -------------------------------------------------------------------------------- 1 | import type { HttpContext } from '@adonisjs/core/http' 2 | import type { NextFn } from '@adonisjs/core/types/http' 3 | import type { Authenticators } from '@adonisjs/auth/types' 4 | 5 | /** 6 | * Auth middleware is used authenticate HTTP requests and deny 7 | * access to unauthenticated users. 8 | */ 9 | export default class AuthMiddleware { 10 | /** 11 | * The URL to redirect to, when authentication fails 12 | */ 13 | redirectTo = '/login' 14 | 15 | async handle( 16 | ctx: HttpContext, 17 | next: NextFn, 18 | options: { 19 | guards?: (keyof Authenticators)[] 20 | } = {} 21 | ) { 22 | await ctx.auth.authenticateUsing(options.guards, { loginRoute: this.redirectTo }) 23 | return next() 24 | } 25 | } -------------------------------------------------------------------------------- /app/models/department.ts: -------------------------------------------------------------------------------- 1 | import { BaseModel, column, hasMany, hasManyThrough } from '@adonisjs/lucid/orm' 2 | import District from './district.js' 3 | import type { HasMany, HasManyThrough } from '@adonisjs/lucid/types/relations' 4 | import Town from './town.js' 5 | import Neighborhood from './neighborhood.js' 6 | 7 | export default class Department extends BaseModel { 8 | @column({ isPrimary: true }) 9 | declare id: number 10 | 11 | @column() 12 | declare name: string 13 | 14 | @hasMany(() => Town) 15 | declare towns: HasMany 16 | 17 | @hasManyThrough([() => District, () => Town]) 18 | declare districts: HasManyThrough 19 | 20 | @hasManyThrough([() => Neighborhood, () => District]) 21 | declare neighborhoods: HasManyThrough 22 | } 23 | -------------------------------------------------------------------------------- /config/auth.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from '@adonisjs/auth' 2 | import { InferAuthEvents, Authenticators } from '@adonisjs/auth/types' 3 | import { tokensGuard, tokensUserProvider } from '@adonisjs/auth/access_tokens' 4 | 5 | const authConfig = defineConfig({ 6 | default: 'api', 7 | guards: { 8 | api: tokensGuard({ 9 | provider: tokensUserProvider({ 10 | tokens: 'accessTokens', 11 | model: () => import('#models/user') 12 | }), 13 | }), 14 | }, 15 | }) 16 | 17 | export default authConfig 18 | 19 | /** 20 | * Inferring types from the configured auth 21 | * guards. 22 | */ 23 | declare module '@adonisjs/auth/types' { 24 | interface Authenticators extends InferAuthenticators {} 25 | } 26 | declare module '@adonisjs/core/types' { 27 | interface EventsList extends InferAuthEvents {} 28 | } -------------------------------------------------------------------------------- /app/models/town.ts: -------------------------------------------------------------------------------- 1 | import { BaseModel, belongsTo, column, hasMany, hasManyThrough } from '@adonisjs/lucid/orm' 2 | import type { BelongsTo, HasMany, HasManyThrough } from '@adonisjs/lucid/types/relations' 3 | import District from './district.js' 4 | import Neighborhood from './neighborhood.js' 5 | import Department from './department.js' 6 | 7 | export default class Town extends BaseModel { 8 | @column({ isPrimary: true }) 9 | declare id: number 10 | 11 | @column() 12 | declare name: string 13 | 14 | @column() 15 | declare departmentId: number 16 | 17 | @belongsTo(() => Department) 18 | declare department: BelongsTo 19 | 20 | @hasMany(() => District) 21 | declare districts: HasMany 22 | 23 | @hasManyThrough([() => Neighborhood, () => District]) 24 | declare neighborhoods: HasManyThrough 25 | } 26 | -------------------------------------------------------------------------------- /ace.js: -------------------------------------------------------------------------------- 1 | /* 2 | |-------------------------------------------------------------------------- 3 | | JavaScript entrypoint for running ace commands 4 | |-------------------------------------------------------------------------- 5 | | 6 | | DO NOT MODIFY THIS FILE AS IT WILL BE OVERRIDDEN DURING THE BUILD 7 | | PROCESS. 8 | | 9 | | See docs.adonisjs.com/guides/typescript-build-process#creating-production-build 10 | | 11 | | Since, we cannot run TypeScript source code using "node" binary, we need 12 | | a JavaScript entrypoint to run ace commands. 13 | | 14 | | This file registers the "ts-node/esm" hook with the Node.js module system 15 | | and then imports the "bin/console.ts" file. 16 | | 17 | */ 18 | 19 | /** 20 | * Register hook to process TypeScript files using ts-node 21 | */ 22 | import { register } from 'node:module' 23 | register('ts-node/esm', import.meta.url) 24 | 25 | /** 26 | * Import ace console entrypoint 27 | */ 28 | await import('./bin/console.js') 29 | -------------------------------------------------------------------------------- /app/exceptions/handler.ts: -------------------------------------------------------------------------------- 1 | import app from '@adonisjs/core/services/app' 2 | import { HttpContext, ExceptionHandler } from '@adonisjs/core/http' 3 | 4 | export default class HttpExceptionHandler extends ExceptionHandler { 5 | /** 6 | * In debug mode, the exception handler will display verbose errors 7 | * with pretty printed stack traces. 8 | */ 9 | protected debug = !app.inProduction 10 | 11 | /** 12 | * The method is used for handling errors and returning 13 | * response to the client 14 | */ 15 | async handle(error: unknown, ctx: HttpContext) { 16 | return super.handle(error, ctx) 17 | } 18 | 19 | /** 20 | * The method is used to report error to the logging service or 21 | * the third party error monitoring service. 22 | * 23 | * @note You should not attempt to send a response from this method. 24 | */ 25 | async report(error: unknown, ctx: HttpContext) { 26 | return super.report(error, ctx) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /app/controllers/towns_controller.ts: -------------------------------------------------------------------------------- 1 | import Town from '#models/town' 2 | import type { HttpContext } from '@adonisjs/core/http' 3 | 4 | export default class TownsController { 5 | async index({ response }: HttpContext) { 6 | const towns = await Town.all() 7 | return response.status(200).json({ 8 | towns, 9 | }) 10 | } 11 | 12 | async findDistricts({ response, params }: HttpContext) { 13 | const town = await Town.findByOrFail('name', params.name.toUpperCase()) 14 | await town.load('districts') 15 | return response.status(200).json({ 16 | town: town?.name, 17 | districts: town.districts, 18 | }) 19 | } 20 | 21 | async findNeighborhoods({ response, params }: HttpContext) { 22 | const town = await Town.findByOrFail('name', params.name.toUpperCase()) 23 | await town.load('neighborhoods') 24 | return response.status(200).json({ 25 | town: town?.name, 26 | neighborhoods: town.neighborhoods, 27 | }) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /start/env.ts: -------------------------------------------------------------------------------- 1 | /* 2 | |-------------------------------------------------------------------------- 3 | | Environment variables service 4 | |-------------------------------------------------------------------------- 5 | | 6 | | The `Env.create` method creates an instance of the Env service. The 7 | | service validates the environment variables and also cast values 8 | | to JavaScript data types. 9 | | 10 | */ 11 | 12 | import { Env } from '@adonisjs/core/env' 13 | 14 | export default await Env.create(new URL('../', import.meta.url), { 15 | NODE_ENV: Env.schema.enum(['development', 'production', 'test'] as const), 16 | PORT: Env.schema.number(), 17 | APP_KEY: Env.schema.string(), 18 | HOST: Env.schema.string({ format: 'host' }), 19 | LOG_LEVEL: Env.schema.string(), 20 | 21 | /* 22 | |---------------------------------------------------------- 23 | | Variables for configuring database connection 24 | |---------------------------------------------------------- 25 | */ 26 | }) 27 | -------------------------------------------------------------------------------- /database/migrations/1713031088042_create_access_tokens_table.ts: -------------------------------------------------------------------------------- 1 | import { BaseSchema } from '@adonisjs/lucid/schema' 2 | 3 | export default class extends BaseSchema { 4 | protected tableName = 'auth_access_tokens' 5 | 6 | async up() { 7 | this.schema.createTable(this.tableName, (table) => { 8 | table.increments('id') 9 | table 10 | .integer('tokenable_id') 11 | .notNullable() 12 | .unsigned() 13 | .references('id') 14 | .inTable('users') 15 | .onDelete('CASCADE') 16 | 17 | table.string('type').notNullable() 18 | table.string('name').nullable() 19 | table.string('hash').notNullable() 20 | table.text('abilities').notNullable() 21 | table.timestamp('created_at') 22 | table.timestamp('updated_at') 23 | table.timestamp('last_used_at').nullable() 24 | table.timestamp('expires_at').nullable() 25 | }) 26 | } 27 | 28 | async down() { 29 | this.schema.dropTable(this.tableName) 30 | } 31 | } -------------------------------------------------------------------------------- /app/models/user.ts: -------------------------------------------------------------------------------- 1 | import { DateTime } from 'luxon' 2 | import hash from '@adonisjs/core/services/hash' 3 | import { compose } from '@adonisjs/core/helpers' 4 | import { BaseModel, column } from '@adonisjs/lucid/orm' 5 | import { withAuthFinder } from '@adonisjs/auth/mixins/lucid' 6 | import { DbAccessTokensProvider } from '@adonisjs/auth/access_tokens' 7 | 8 | const AuthFinder = withAuthFinder(() => hash.use('scrypt'), { 9 | uids: ['email'], 10 | passwordColumnName: 'password', 11 | }) 12 | 13 | export default class User extends compose(BaseModel, AuthFinder) { 14 | @column({ isPrimary: true }) 15 | declare id: number 16 | 17 | @column() 18 | declare fullName: string | null 19 | 20 | @column() 21 | declare email: string 22 | 23 | @column() 24 | declare password: string 25 | 26 | @column.dateTime({ autoCreate: true }) 27 | declare createdAt: DateTime 28 | 29 | @column.dateTime({ autoCreate: true, autoUpdate: true }) 30 | declare updatedAt: DateTime | null 31 | 32 | static accessTokens = DbAccessTokensProvider.forModel(User) 33 | } -------------------------------------------------------------------------------- /config/logger.ts: -------------------------------------------------------------------------------- 1 | import env from '#start/env' 2 | import app from '@adonisjs/core/services/app' 3 | import { defineConfig, targets } from '@adonisjs/core/logger' 4 | 5 | const loggerConfig = defineConfig({ 6 | default: 'app', 7 | 8 | /** 9 | * The loggers object can be used to define multiple loggers. 10 | * By default, we configure only one logger (named "app"). 11 | */ 12 | loggers: { 13 | app: { 14 | enabled: true, 15 | name: env.get('APP_NAME'), 16 | level: env.get('LOG_LEVEL'), 17 | transport: { 18 | targets: targets() 19 | .pushIf(!app.inProduction, targets.pretty()) 20 | .pushIf(app.inProduction, targets.file({ destination: 1 })) 21 | .toArray(), 22 | }, 23 | }, 24 | }, 25 | }) 26 | 27 | export default loggerConfig 28 | 29 | /** 30 | * Inferring types for the list of loggers you have configured 31 | * in your application. 32 | */ 33 | declare module '@adonisjs/core/types' { 34 | export interface LoggersList extends InferLoggers {} 35 | } 36 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 JSBenin 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /config/app.ts: -------------------------------------------------------------------------------- 1 | import env from '#start/env' 2 | import app from '@adonisjs/core/services/app' 3 | import { Secret } from '@adonisjs/core/helpers' 4 | import { defineConfig } from '@adonisjs/core/http' 5 | 6 | /** 7 | * The app key is used for encrypting cookies, generating signed URLs, 8 | * and by the "encryption" module. 9 | * 10 | * The encryption module will fail to decrypt data if the key is lost or 11 | * changed. Therefore it is recommended to keep the app key secure. 12 | */ 13 | export const appKey = new Secret(env.get('APP_KEY')) 14 | 15 | /** 16 | * The configuration settings used by the HTTP server 17 | */ 18 | export const http = defineConfig({ 19 | generateRequestId: true, 20 | allowMethodSpoofing: false, 21 | 22 | /** 23 | * Enabling async local storage will let you access HTTP context 24 | * from anywhere inside your application. 25 | */ 26 | useAsyncLocalStorage: false, 27 | 28 | /** 29 | * Manage cookies configuration. The settings for the session id cookie are 30 | * defined inside the "config/session.ts" file. 31 | */ 32 | cookie: { 33 | domain: '', 34 | path: '/', 35 | maxAge: '2h', 36 | httpOnly: true, 37 | secure: app.inProduction, 38 | sameSite: 'lax', 39 | }, 40 | }) 41 | -------------------------------------------------------------------------------- /tests/bootstrap.ts: -------------------------------------------------------------------------------- 1 | import { assert } from '@japa/assert' 2 | import { apiClient } from '@japa/api-client' 3 | import app from '@adonisjs/core/services/app' 4 | import type { Config } from '@japa/runner/types' 5 | import { pluginAdonisJS } from '@japa/plugin-adonisjs' 6 | import testUtils from '@adonisjs/core/services/test_utils' 7 | 8 | /** 9 | * This file is imported by the "bin/test.ts" entrypoint file 10 | */ 11 | 12 | /** 13 | * Configure Japa plugins in the plugins array. 14 | * Learn more - https://japa.dev/docs/runner-config#plugins-optional 15 | */ 16 | export const plugins: Config['plugins'] = [assert(), apiClient(), pluginAdonisJS(app)] 17 | 18 | /** 19 | * Configure lifecycle function to run before and after all the 20 | * tests. 21 | * 22 | * The setup functions are executed before all the tests 23 | * The teardown functions are executer after all the tests 24 | */ 25 | export const runnerHooks: Required> = { 26 | setup: [], 27 | teardown: [], 28 | } 29 | 30 | /** 31 | * Configure suites by tapping into the test suite instance. 32 | * Learn more - https://japa.dev/docs/test-suites#lifecycle-hooks 33 | */ 34 | export const configureSuite: Config['configureSuite'] = (suite) => { 35 | if (['browser', 'functional', 'e2e'].includes(suite.name)) { 36 | return suite.setup(() => testUtils.httpServer().start()) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /bin/server.ts: -------------------------------------------------------------------------------- 1 | /* 2 | |-------------------------------------------------------------------------- 3 | | HTTP server entrypoint 4 | |-------------------------------------------------------------------------- 5 | | 6 | | The "server.ts" file is the entrypoint for starting the AdonisJS HTTP 7 | | server. Either you can run this file directly or use the "serve" 8 | | command to run this file and monitor file changes 9 | | 10 | */ 11 | 12 | import 'reflect-metadata' 13 | import { Ignitor, prettyPrintError } from '@adonisjs/core' 14 | 15 | /** 16 | * URL to the application root. AdonisJS need it to resolve 17 | * paths to file and directories for scaffolding commands 18 | */ 19 | const APP_ROOT = new URL('../', import.meta.url) 20 | 21 | /** 22 | * The importer is used to import files in context of the 23 | * application. 24 | */ 25 | const IMPORTER = (filePath: string) => { 26 | if (filePath.startsWith('./') || filePath.startsWith('../')) { 27 | return import(new URL(filePath, APP_ROOT).href) 28 | } 29 | return import(filePath) 30 | } 31 | 32 | new Ignitor(APP_ROOT, { importer: IMPORTER }) 33 | .tap((app) => { 34 | app.booting(async () => { 35 | await import('#start/env') 36 | }) 37 | app.listen('SIGTERM', () => app.terminate()) 38 | app.listenIf(app.managedByPm2, 'SIGINT', () => app.terminate()) 39 | }) 40 | .httpServer() 41 | .start() 42 | .catch((error) => { 43 | process.exitCode = 1 44 | prettyPrintError(error) 45 | }) 46 | -------------------------------------------------------------------------------- /bin/console.ts: -------------------------------------------------------------------------------- 1 | /* 2 | |-------------------------------------------------------------------------- 3 | | Ace entry point 4 | |-------------------------------------------------------------------------- 5 | | 6 | | The "console.ts" file is the entrypoint for booting the AdonisJS 7 | | command-line framework and executing commands. 8 | | 9 | | Commands do not boot the application, unless the currently running command 10 | | has "options.startApp" flag set to true. 11 | | 12 | */ 13 | 14 | import 'reflect-metadata' 15 | import { Ignitor, prettyPrintError } from '@adonisjs/core' 16 | 17 | /** 18 | * URL to the application root. AdonisJS need it to resolve 19 | * paths to file and directories for scaffolding commands 20 | */ 21 | const APP_ROOT = new URL('../', import.meta.url) 22 | 23 | /** 24 | * The importer is used to import files in context of the 25 | * application. 26 | */ 27 | const IMPORTER = (filePath: string) => { 28 | if (filePath.startsWith('./') || filePath.startsWith('../')) { 29 | return import(new URL(filePath, APP_ROOT).href) 30 | } 31 | return import(filePath) 32 | } 33 | 34 | new Ignitor(APP_ROOT, { importer: IMPORTER }) 35 | .tap((app) => { 36 | app.booting(async () => { 37 | await import('#start/env') 38 | }) 39 | app.listen('SIGTERM', () => app.terminate()) 40 | app.listenIf(app.managedByPm2, 'SIGINT', () => app.terminate()) 41 | }) 42 | .ace() 43 | .handle(process.argv.splice(2)) 44 | .catch((error) => { 45 | process.exitCode = 1 46 | prettyPrintError(error) 47 | }) 48 | -------------------------------------------------------------------------------- /config/bodyparser.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from '@adonisjs/core/bodyparser' 2 | 3 | const bodyParserConfig = defineConfig({ 4 | /** 5 | * The bodyparser middleware will parse the request body 6 | * for the following HTTP methods. 7 | */ 8 | allowedMethods: ['POST', 'PUT', 'PATCH', 'DELETE'], 9 | 10 | /** 11 | * Config for the "application/x-www-form-urlencoded" 12 | * content-type parser 13 | */ 14 | form: { 15 | convertEmptyStringsToNull: true, 16 | types: ['application/x-www-form-urlencoded'], 17 | }, 18 | 19 | /** 20 | * Config for the JSON parser 21 | */ 22 | json: { 23 | convertEmptyStringsToNull: true, 24 | types: [ 25 | 'application/json', 26 | 'application/json-patch+json', 27 | 'application/vnd.api+json', 28 | 'application/csp-report', 29 | ], 30 | }, 31 | 32 | /** 33 | * Config for the "multipart/form-data" content-type parser. 34 | * File uploads are handled by the multipart parser. 35 | */ 36 | multipart: { 37 | /** 38 | * Enabling auto process allows bodyparser middleware to 39 | * move all uploaded files inside the tmp folder of your 40 | * operating system 41 | */ 42 | autoProcess: true, 43 | convertEmptyStringsToNull: true, 44 | processManually: [], 45 | 46 | /** 47 | * Maximum limit of data to parse including all files 48 | * and fields 49 | */ 50 | limit: '20mb', 51 | types: ['multipart/form-data'], 52 | }, 53 | }) 54 | 55 | export default bodyParserConfig 56 | -------------------------------------------------------------------------------- /start/kernel.ts: -------------------------------------------------------------------------------- 1 | /* 2 | |-------------------------------------------------------------------------- 3 | | HTTP kernel file 4 | |-------------------------------------------------------------------------- 5 | | 6 | | The HTTP kernel file is used to register the middleware with the server 7 | | or the router. 8 | | 9 | */ 10 | 11 | import router from '@adonisjs/core/services/router' 12 | import server from '@adonisjs/core/services/server' 13 | 14 | /** 15 | * The error handler is used to convert an exception 16 | * to a HTTP response. 17 | */ 18 | server.errorHandler(() => import('#exceptions/handler')) 19 | 20 | /** 21 | * The server middleware stack runs middleware on all the HTTP 22 | * requests, even if there is no route registered for 23 | * the request URL. 24 | */ 25 | server.use([ 26 | () => import('#middleware/container_bindings_middleware'), 27 | () => import('#middleware/force_json_response_middleware'), 28 | () => import('@adonisjs/cors/cors_middleware'), 29 | () => import('@adonisjs/static/static_middleware') 30 | ]) 31 | 32 | /** 33 | * The router middleware stack runs middleware on all the HTTP 34 | * requests with a registered route. 35 | */ 36 | router.use([() => import('@adonisjs/core/bodyparser_middleware'), () => import('@adonisjs/auth/initialize_auth_middleware')]) 37 | 38 | /** 39 | * Named middleware collection must be explicitly assigned to 40 | * the routes or the routes group. 41 | */ 42 | export const middleware = router.named({ 43 | auth: () => import('#middleware/auth_middleware') 44 | }) 45 | -------------------------------------------------------------------------------- /app/controllers/departments_controller.ts: -------------------------------------------------------------------------------- 1 | import Department from '#models/department' 2 | import Neighborhood from '#models/neighborhood' 3 | import type { HttpContext } from '@adonisjs/core/http' 4 | 5 | export default class DepartmentsController { 6 | async index({ response }: HttpContext) { 7 | const departments = await Department.all() 8 | return response.status(200).json({ 9 | departments, 10 | }) 11 | } 12 | 13 | async findTowns({ response, params }: HttpContext) { 14 | const department = await Department.findByOrFail('name', params.name.toUpperCase()) 15 | await department.load('towns') 16 | return response.status(200).json({ 17 | department: department?.name, 18 | towns: department.towns, 19 | }) 20 | } 21 | 22 | async findDistricts({ response, params }: HttpContext) { 23 | const department = await Department.findByOrFail('name', params.name.toUpperCase()) 24 | await department.load('districts') 25 | return response.status(200).json({ 26 | department: department?.name, 27 | districts: department?.districts, 28 | }) 29 | } 30 | 31 | async findNeighborhoods({ response, params }: HttpContext) { 32 | const department = await Department.findByOrFail('name', params.name.toUpperCase()) 33 | const neighborhoods = await Neighborhood.query().whereHas('district', (query) => { 34 | query.whereHas('town', (q) => { 35 | q.where('department_id', department.id) 36 | }) 37 | }) 38 | return response.status(200).json({ 39 | department: department?.name, 40 | neighborhoods: neighborhoods, 41 | }) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /start/routes.ts: -------------------------------------------------------------------------------- 1 | /* 2 | |-------------------------------------------------------------------------- 3 | | Routes file 4 | |-------------------------------------------------------------------------- 5 | | 6 | | The routes file is used for defining the HTTP routes. 7 | | 8 | */ 9 | 10 | import { HttpContext } from '@adonisjs/core/http' 11 | 12 | const DepartmentsController = () => import('#controllers/departments_controller') 13 | const TownsController = () => import('#controllers/towns_controller') 14 | const DistrictsController = () => import('#controllers/districts_controller') 15 | const NeighborhoodsController = () => import('#controllers/neighborhoods_controller') 16 | import router from '@adonisjs/core/services/router' 17 | 18 | router.get('docs', ({ view }: HttpContext) => view.render('docs')) 19 | router.on('/').render('docs') 20 | 21 | router 22 | .group(() => { 23 | router.get('departments', [DepartmentsController, 'index']) 24 | router.get('departments/:name/towns', [DepartmentsController, 'findTowns']) 25 | router.get('departments/:name/districts', [DepartmentsController, 'findDistricts']) 26 | router.get('departments/:name/neighborhoods', [DepartmentsController, 'findNeighborhoods']) 27 | 28 | router.get('towns', [TownsController, 'index']) 29 | router.get('towns/:name/districts', [TownsController, 'findDistricts']) 30 | router.get('towns/:name/neighborhoods', [TownsController, 'findNeighborhoods']) 31 | 32 | router.get('districts', [DistrictsController, 'index']) 33 | router.get('districts/:name/neighborhoods', [DistrictsController, 'findNeighborhoods']) 34 | 35 | router.get('neighborhoods', [NeighborhoodsController, 'index']) 36 | }) 37 | .prefix('api') 38 | -------------------------------------------------------------------------------- /.github/workflows/main.yaml: -------------------------------------------------------------------------------- 1 | name: Deployment 2 | 3 | concurrency: production 4 | 5 | on: 6 | pull_request: 7 | branches: ["main"] 8 | push: 9 | branches: ["main"] 10 | 11 | permissions: 12 | contents: read 13 | 14 | env: 15 | TZ: UTC 16 | PORT: 3333 17 | HOST: localhost 18 | LOG_LEVEL: info 19 | APP_KEY: IcoF3UXzHDXjAyACjdl8TqAJRBFgB6K_ 20 | NODE_ENV: test 21 | 22 | jobs: 23 | build-and-deploy: 24 | runs-on: ubuntu-latest 25 | 26 | steps: 27 | - name: Checkout repository 28 | uses: actions/checkout@v4 29 | 30 | - name: Set up pnpm 31 | uses: pnpm/action-setup@0609f0983b7a228f052f81ef4c3d6510cae254ad 32 | with: 33 | version: 8.15.5 34 | 35 | - name: Set up Node.js 36 | uses: actions/setup-node@v4 37 | with: 38 | node-version: '21.6.1' 39 | cache: 'pnpm' 40 | 41 | - name: Install dependencies 42 | run: pnpm install 43 | 44 | - name: Build 45 | run: pnpm build 46 | 47 | - name: Test 48 | run: pnpm test 49 | 50 | - name: Deploy 51 | uses: appleboy/ssh-action@master 52 | with: 53 | host: ${{ secrets.HOST }} 54 | port: ${{ secrets.PORT }} 55 | username: ${{ secrets.USERNAME }} 56 | key: ${{ secrets.KEY }} 57 | script: | 58 | cd ~/apps/bj-decoupage-territorial 59 | git pull 60 | pnpm install 61 | node ace build 62 | cp .env build/ 63 | cp -r tmp build/ 64 | cd build 65 | pnpm i --prod 66 | pm2 restart bj-decoupage-territorial 67 | 68 | 69 | 70 | 71 | -------------------------------------------------------------------------------- /bin/test.ts: -------------------------------------------------------------------------------- 1 | /* 2 | |-------------------------------------------------------------------------- 3 | | Test runner entrypoint 4 | |-------------------------------------------------------------------------- 5 | | 6 | | The "test.ts" file is the entrypoint for running tests using Japa. 7 | | 8 | | Either you can run this file directly or use the "test" 9 | | command to run this file and monitor file changes. 10 | | 11 | */ 12 | 13 | process.env.NODE_ENV = 'test' 14 | 15 | import 'reflect-metadata' 16 | import { Ignitor, prettyPrintError } from '@adonisjs/core' 17 | import { configure, processCLIArgs, run } from '@japa/runner' 18 | 19 | /** 20 | * URL to the application root. AdonisJS need it to resolve 21 | * paths to file and directories for scaffolding commands 22 | */ 23 | const APP_ROOT = new URL('../', import.meta.url) 24 | 25 | /** 26 | * The importer is used to import files in context of the 27 | * application. 28 | */ 29 | const IMPORTER = (filePath: string) => { 30 | if (filePath.startsWith('./') || filePath.startsWith('../')) { 31 | return import(new URL(filePath, APP_ROOT).href) 32 | } 33 | return import(filePath) 34 | } 35 | 36 | new Ignitor(APP_ROOT, { importer: IMPORTER }) 37 | .tap((app) => { 38 | app.booting(async () => { 39 | await import('#start/env') 40 | }) 41 | app.listen('SIGTERM', () => app.terminate()) 42 | app.listenIf(app.managedByPm2, 'SIGINT', () => app.terminate()) 43 | }) 44 | .testRunner() 45 | .configure(async (app) => { 46 | const { runnerHooks, ...config } = await import('../tests/bootstrap.js') 47 | 48 | processCLIArgs(process.argv.splice(2)) 49 | configure({ 50 | ...app.rcFile.tests, 51 | ...config, 52 | ...{ 53 | setup: runnerHooks.setup, 54 | teardown: runnerHooks.teardown.concat([() => app.terminate()]), 55 | }, 56 | }) 57 | }) 58 | .run(() => run()) 59 | .catch((error) => { 60 | process.exitCode = 1 61 | prettyPrintError(error) 62 | }) 63 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # bj-decoupage-territorial 2 | 3 | [API](https://bj-decoupage-territorial.herokuapp.com/) (non officielle) pour obtenir des informations de base sur les départements, communes, arrondissements et les quartiers du Bénin. Elle s'inspire du fichier du découpage territorial du Bénin proposé par [leplutonien](https://github.com/leplutonien/decoupage_territorial_benin). 4 | 5 | Ce projet est une migration de l'ancienne [version](https://github.com/nioperas06/bj-decoupage-territorial) qui utilisait une version obsolète d'AdonisJS 5 vers une version plus récente (^6.2.2). 6 | 7 | [![bj-decoupage-territorial](preview.png)](https://github.com/jsbenin/bj-decoupage-territorial) 8 | 9 | 10 | # Stack Technique 11 | * [Node.js](https://nodejs.org/en/) ( [AdonisJS](https://adonisjs.com/) 😏 ) pour l'API 12 | * [SQLite](https://www.mysql.com/) pour stocker les données 13 | 14 | # Tu as envie de donner un coup de pouce? 15 | > Bah il y a beaucoup à faire 😁 16 | * [ ] Ecrire les tests ( C'est con, mais c'est utile 😛 ) 17 | * [ ] Améliorer la doc 📚 18 | * [ ] Rajouter des nouvelles infos, donc écrire du code 🏄 19 | * [ ] Mettre un petit star ⭐️ à ce dépôt. 20 | * N'oublie pas de nous suivre sur [Twitter](https://twitter.com/jsbenincommunity) 👊! 21 | 22 | ### Installation 23 | ```bash 24 | git clone https://github.com/jsbenin/bj-decoupage-territorial 25 | ``` 26 | ```bash 27 | cd bj-decoupage-territorial && pnpm install 28 | ``` 29 | 30 | ```bash 31 | cp .env.example .env 32 | node ace generate:key 33 | ``` 34 | 35 | ```bash 36 | # Create sqlite db 37 | mkdir tmp && touch tmp/db.sqlite3 38 | ``` 39 | 40 | ```bash 41 | # start the project 42 | pnpm run dev 43 | ``` 44 | 45 | ```bash 46 | # charger les données dans votre base de donnée sqlite 47 | sudo chmod +x seed.sh 48 | ./seed.sh 49 | ``` 50 | 51 | ```bash 52 | # start the project 53 | pnpm run dev 54 | ``` 55 | 56 | *Utiliser cette commande si vous êtes paresseux* 57 | ```bash 58 | make setup 59 | ``` 60 | 61 | Vous pouvez accéder à la documentation en passant par ici: `http://localhost:3333` 62 | 63 | 64 | 65 | # Utilisé par : 66 | * [Portail National des services publics du Bénin](https://service-public.bj) 67 | 68 | # License 69 | [MIT License](LICENSE.md) 70 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bj-decoupage-territorial", 3 | "version": "0.0.0", 4 | "private": true, 5 | "type": "module", 6 | "license": "UNLICENSED", 7 | "scripts": { 8 | "start": "node bin/server.js", 9 | "build": "node ace build", 10 | "dev": "node ace serve --watch", 11 | "test": "node ace test", 12 | "lint": "eslint .", 13 | "format": "prettier --write .", 14 | "typecheck": "tsc --noEmit" 15 | }, 16 | "imports": { 17 | "#controllers/*": "./app/controllers/*.js", 18 | "#exceptions/*": "./app/exceptions/*.js", 19 | "#models/*": "./app/models/*.js", 20 | "#mails/*": "./app/mails/*.js", 21 | "#services/*": "./app/services/*.js", 22 | "#listeners/*": "./app/listeners/*.js", 23 | "#events/*": "./app/events/*.js", 24 | "#middleware/*": "./app/middleware/*.js", 25 | "#validators/*": "./app/validators/*.js", 26 | "#providers/*": "./providers/*.js", 27 | "#policies/*": "./app/policies/*.js", 28 | "#abilities/*": "./app/abilities/*.js", 29 | "#database/*": "./database/*.js", 30 | "#start/*": "./start/*.js", 31 | "#tests/*": "./tests/*.js", 32 | "#config/*": "./config/*.js" 33 | }, 34 | "devDependencies": { 35 | "@adonisjs/assembler": "^7.1.1", 36 | "@adonisjs/eslint-config": "^1.2.1", 37 | "@adonisjs/prettier-config": "^1.2.1", 38 | "@adonisjs/tsconfig": "^1.2.1", 39 | "@japa/api-client": "^2.0.2", 40 | "@japa/assert": "^2.1.0", 41 | "@japa/plugin-adonisjs": "^3.0.0", 42 | "@japa/runner": "^3.1.1", 43 | "@swc/core": "^1.3.107", 44 | "@types/luxon": "^3.4.2", 45 | "@types/node": "^20.11.10", 46 | "eslint": "^8.56.0", 47 | "pino-pretty": "^10.3.1", 48 | "prettier": "^3.2.4", 49 | "ts-node": "^10.9.2", 50 | "typescript": "^5.4.3" 51 | }, 52 | "dependencies": { 53 | "@adonisjs/auth": "^9.1.1", 54 | "@adonisjs/core": "^6.2.2", 55 | "@adonisjs/cors": "^2.2.1", 56 | "@adonisjs/lucid": "^20.1.0", 57 | "@adonisjs/static": "^1.1.1", 58 | "@vinejs/vine": "^1.7.1", 59 | "better-sqlite3": "^9.6.0", 60 | "edge.js": "^6.0.2", 61 | "luxon": "^3.4.4", 62 | "pg": "^8.11.5", 63 | "reflect-metadata": "^0.2.1" 64 | }, 65 | "eslintConfig": { 66 | "extends": "@adonisjs/eslint-config/app" 67 | }, 68 | "prettier": "@adonisjs/prettier-config", 69 | "volta": { 70 | "node": "21.6.1" 71 | } 72 | } -------------------------------------------------------------------------------- /adonisrc.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from '@adonisjs/core/app' 2 | 3 | export default defineConfig({ 4 | /* 5 | |-------------------------------------------------------------------------- 6 | | Commands 7 | |-------------------------------------------------------------------------- 8 | | 9 | | List of ace commands to register from packages. The application commands 10 | | will be scanned automatically from the "./commands" directory. 11 | | 12 | */ 13 | commands: [() => import('@adonisjs/core/commands'), () => import('@adonisjs/lucid/commands')], 14 | 15 | /* 16 | |-------------------------------------------------------------------------- 17 | | Service providers 18 | |-------------------------------------------------------------------------- 19 | | 20 | | List of service providers to import and register when booting the 21 | | application 22 | | 23 | */ 24 | providers: [ 25 | () => import('@adonisjs/core/providers/app_provider'), 26 | () => import('@adonisjs/core/providers/hash_provider'), 27 | { 28 | file: () => import('@adonisjs/core/providers/repl_provider'), 29 | environment: ['repl', 'test'], 30 | }, 31 | () => import('@adonisjs/core/providers/vinejs_provider'), 32 | () => import('@adonisjs/cors/cors_provider'), 33 | () => import('@adonisjs/lucid/database_provider'), 34 | () => import('@adonisjs/auth/auth_provider'), 35 | () => import('@adonisjs/core/providers/edge_provider'), 36 | () => import('@adonisjs/static/static_provider') 37 | ], 38 | 39 | /* 40 | |-------------------------------------------------------------------------- 41 | | Preloads 42 | |-------------------------------------------------------------------------- 43 | | 44 | | List of modules to import before starting the application. 45 | | 46 | */ 47 | preloads: [() => import('#start/routes'), () => import('#start/kernel')], 48 | 49 | /* 50 | |-------------------------------------------------------------------------- 51 | | Tests 52 | |-------------------------------------------------------------------------- 53 | | 54 | | List of test suites to organize tests by their type. Feel free to remove 55 | | and add additional suites. 56 | | 57 | */ 58 | tests: { 59 | suites: [ 60 | { 61 | files: ['tests/unit/**/*.spec(.ts|.js)'], 62 | name: 'unit', 63 | timeout: 2000, 64 | }, 65 | { 66 | files: ['tests/functional/**/*.spec(.ts|.js)'], 67 | name: 'functional', 68 | timeout: 30000, 69 | }, 70 | ], 71 | forceExit: false, 72 | }, 73 | metaFiles: [{ 74 | pattern: 'resources/views/**/*.edge', 75 | reloadServer: false, 76 | }, 77 | { 78 | pattern: 'public/**', 79 | reloadServer: false, 80 | } 81 | ] 82 | }) 83 | -------------------------------------------------------------------------------- /public/openapi.yaml: -------------------------------------------------------------------------------- 1 | openapi: 3.1.0 2 | info: 3 | title: BJ Découpage territorial 4 | description: API (non officielle) pour obtenir des informations de base sur les départements, communes, arrondissements et les quartiers du Bénin. 5 | version: 0.0.1 6 | 7 | paths: 8 | /api/departments: 9 | get: 10 | tags: 11 | - departments 12 | summary: Informations liées aux départements 13 | description: Récupérer tous les départements 14 | responses: 15 | "200": 16 | $ref: '#/components/responses/Department' 17 | /api/departments/{name}/towns: 18 | get: 19 | tags: 20 | - departments 21 | summary: Récupérer toutes les communes d'un département 22 | description: Récupérer toutes les communes d'un département 23 | responses: 24 | "200": 25 | $ref: '#/components/responses/DepartmentTowns' 26 | 27 | /api/departments/{name}/districts: 28 | get: 29 | tags: 30 | - departments 31 | summary: Récupérer tous les arrondissements d'un département 32 | description: Récupérer tous les arrondissements d'un département 33 | responses: 34 | "200": 35 | $ref: '#/components/responses/DepartmentDistricts' 36 | /api/departments/{name}/neighborhoods: 37 | get: 38 | tags: 39 | - departments 40 | summary: Récupérer tous les quartiers d'un département 41 | description: Récupérer tous les quartiers d'un département 42 | responses: 43 | "200": 44 | $ref: '#/components/responses/DepartmentNeighborHoods' 45 | 46 | # Towns 47 | /api/towns: 48 | get: 49 | tags: 50 | - towns 51 | summary: Informations liées aux communes 52 | description: Récupérer toutes les commuunes 53 | responses: 54 | "200": 55 | $ref: '#/components/responses/Town' 56 | /api/towns/{name}/districts: 57 | get: 58 | tags: 59 | - towns 60 | summary: Récupérer tous les arrondissements d'une commmune 61 | description: Récupérer tous les arrondissements d'une commune 62 | responses: 63 | "200": 64 | $ref: '#/components/responses/TownDisticts' 65 | /api/towns/{name}/neighborhoods: 66 | get: 67 | tags: 68 | - towns 69 | summary: Récupérer tous les quartiers d'une commmune 70 | description: Récupérer tous les quartiers d'une commune 71 | responses: 72 | "200": 73 | $ref: '#/components/responses/TownNeighborhoods' 74 | /districts: 75 | get: 76 | tags: 77 | - districts 78 | summary: Informations liées aux communes 79 | description: Récupérer toutes les commuunes 80 | responses: 81 | "200": 82 | $ref: '#/components/responses/District' 83 | /api/districts/{name}/neighborhoods: 84 | get: 85 | tags: 86 | - districts 87 | summary: Récupérer tous les quartiers d'une commmune 88 | description: Récupérer tous les quartiers d'une commune 89 | responses: 90 | "200": 91 | $ref: '#/components/responses/DistrictNeighborhoods' 92 | 93 | # Neighborhoods 94 | /api/neighborhoods: 95 | get: 96 | tags: 97 | - neighborhoods 98 | summary: Informations liées aux quartiers 99 | description: Récupérer tous les quartiers 100 | responses: 101 | "200": 102 | $ref: '#/components/responses/Neighborhoods' 103 | 104 | 105 | 106 | components: 107 | schemas: 108 | Department: 109 | type: object 110 | properties: 111 | id: 112 | type: number 113 | name: 114 | type: string 115 | Town: 116 | type: object 117 | properties: 118 | id: 119 | type: number 120 | name: 121 | type: string 122 | Districts: 123 | type: object 124 | properties: 125 | id: 126 | type: number 127 | name: 128 | type: string 129 | Neighborhoods: 130 | type: object 131 | properties: 132 | id: 133 | type: number 134 | name: 135 | type: string 136 | 137 | 138 | 139 | responses: 140 | Department: 141 | description: Ok 142 | content: 143 | application/json: 144 | schema: 145 | type: object 146 | properties: 147 | departments: 148 | $ref: '#/components/schemas/Department' 149 | DepartmentTowns: 150 | description: Ok 151 | content: 152 | application/json: 153 | schema: 154 | type: object 155 | properties: 156 | department: 157 | type: string 158 | towns: 159 | type: array 160 | items: 161 | $ref: '#/components/schemas/Town' 162 | DepartmentDistricts: 163 | description: Ok 164 | content: 165 | application/json: 166 | schema: 167 | type: object 168 | properties: 169 | department: 170 | type: string 171 | districts: 172 | type: array 173 | items: 174 | $ref: '#/components/schemas/Districts' 175 | DepartmentNeighborHoods: 176 | description: Ok 177 | content: 178 | application/json: 179 | schema: 180 | type: object 181 | properties: 182 | department: 183 | type: string 184 | neighborhoods: 185 | type: array 186 | items: 187 | $ref: '#/components/schemas/Neighborhoods' 188 | Town: 189 | description: Ok 190 | content: 191 | application/json: 192 | schema: 193 | type: object 194 | properties: 195 | towns: 196 | $ref: '#/components/schemas/Town' 197 | TownDisticts: 198 | description: Ok 199 | content: 200 | application/json: 201 | schema: 202 | type: object 203 | properties: 204 | town: 205 | type: string 206 | districts: 207 | type: array 208 | items: 209 | $ref: '#/components/schemas/Districts' 210 | 211 | TownNeighborhoods: 212 | description: Ok 213 | content: 214 | application/json: 215 | schema: 216 | type: object 217 | properties: 218 | town: 219 | type: string 220 | neighborhoods: 221 | type: array 222 | items: 223 | $ref: '#/components/schemas/Neighborhoods' 224 | # District 225 | 226 | District: 227 | description: Ok 228 | content: 229 | application/json: 230 | schema: 231 | type: object 232 | properties: 233 | district: 234 | $ref: '#/components/schemas/Districts' 235 | DistrictNeighborhoods: 236 | description: Ok 237 | content: 238 | application/json: 239 | schema: 240 | type: object 241 | properties: 242 | district: 243 | type: string 244 | neighborhoods: 245 | type: array 246 | items: 247 | $ref: '#/components/schemas/Neighborhoods' 248 | 249 | # Neighborhoods 250 | Neighborhoods: 251 | description: Ok 252 | content: 253 | application/json: 254 | schema: 255 | properties: 256 | neighborhoods: 257 | $ref: '#/components/schemas/Neighborhoods' 258 | 259 | 260 | --------------------------------------------------------------------------------