Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .adonisjs/client/manifest.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@

/// <reference path="../../adonisrc.ts" />
/// <reference path="../../config/auth.ts" />
/// <reference path="../../config/cache.ts" />
/// <reference path="../../config/hash.ts" />
/// <reference path="../../config/limiter.ts" />
/// <reference path="../../config/logger.ts" />
/// <reference path="../../config/mail.ts" />
/// <reference path="../../config/swagger.ts" />
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -665,7 +665,11 @@ export default await Env.create(new URL('../', import.meta.url), {
- [ ] Implement global exception handler in `app/exceptions/handler.ts` (wraps all errors in `ApiResponse` shape with `success: false`)
- [ ] Implement `ResponseTimeMiddleware` and register it globally in `start/kernel.ts`
- [ ] Register `auth` and `role` as named middleware in `start/kernel.ts` (stubs - implementation comes in `feature/auth`)
- [ ] Configure `adonis-autoswagger` for API documentation
- [ ] Configure `adonis-autoswagger` for API documentation, expose `/swagger` and `/docs`
- [ ] Install every dependency the whole project's Tech Stack needs, not just this branch's own: `pg`, `@adonisjs/bouncer`, `@adonisjs/limiter`, `@adonisjs/mail`, `@adonisjs/cache`, `argon2`, `jose`, `adonis-autoswagger`, so no later branch needs a mid-branch install
- [ ] Configure `@adonisjs/bouncer`, `@adonisjs/limiter` (database store) and `@adonisjs/mail` (SMTP against Mailhog) with their base scaffolding; the app-specific wiring (the JWT guard, Argon2 as the active hasher, real policies) still belongs to the branch that owns that behavior
- [ ] Configure `@adonisjs/cache` with an in-memory L1 layer and a database L2 layer, for `feature/categories` to use on its list endpoint
- [ ] Add Mailhog to both `docker-compose.yml` and `docker-compose.test.yml`, and to both CI workflows
- [ ] Add `GET /api/v1/health` health-check route
- [ ] Set up GitHub Actions `ci.yml` and `pr-checks.yml`
- [ ] Functional test: `GET /api/v1/health` returns 200 with `ApiResponse` shape
Expand Down
2 changes: 2 additions & 0 deletions adonisrc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ export default defineConfig({
() => import('@adonisjs/session/commands'),
() => import('@adonisjs/bouncer/commands'),
() => import('@adonisjs/mail/commands'),
() => import('@adonisjs/cache/commands'),
],

/*
Expand Down Expand Up @@ -59,6 +60,7 @@ export default defineConfig({
() => import('@adonisjs/bouncer/bouncer_provider'),
() => import('@adonisjs/limiter/limiter_provider'),
() => import('@adonisjs/mail/mail_provider'),
() => import('@adonisjs/cache/cache_provider'),
],

/*
Expand Down
30 changes: 30 additions & 0 deletions config/cache.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { defineConfig, store, drivers } from '@adonisjs/cache'

/**
* The database driver reuses the existing pg connection from
* config/database.ts, the same reasoning as config/limiter.ts's own choice
* of a database store over Redis: this project already runs one shared
* PostgreSQL instance in every environment via docker-compose, and adding
* Redis as a second piece of infrastructure just for caching a handful of
* frequently read list endpoints (the README names the categories list
* specifically) is not worth the extra moving part.
*
* The in-memory L1 layer sits in front of it regardless, so most reads
* never touch the database at all; L2 only matters for cache entries that
* need to survive a process restart or be shared across instances.
*/
const cacheConfig = defineConfig({
default: 'default',

stores: {
memoryOnly: store().useL1Layer(drivers.memory()),

default: store().useL1Layer(drivers.memory()).useL2Layer(drivers.database({})),
},
})

export default cacheConfig

declare module '@adonisjs/cache/types' {
interface CacheStores extends InferStores<typeof cacheConfig> {}
}
36 changes: 36 additions & 0 deletions config/swagger.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import path from 'node:path'
import url from 'node:url'

/**
* adonis-autoswagger generates the OpenAPI spec from start/routes.ts and
* app/models/* directly, so this file only carries presentation and a few
* behavioral settings, not the API surface itself. Controllers must be
* imported lazily in routes.ts for their JSDoc annotations to be picked up,
* which every controller in this project already does.
*/
export default {
path: path.dirname(url.fileURLToPath(import.meta.url)) + '/../',
title: 'AdonisJS Tutorial API',
version: '1.0.0',
description:
'REST API for the AdonisJS Tutorial project: categories, products, customers and orders, secured by JWT authentication and role based access control.',

// Routes are declared as /api/v1/<resource>, so the third path segment
// (index 3) is the resource name autoswagger groups endpoints under.
tagIndex: 3,

ignore: ['/swagger', '/docs'],
snakeCase: true,
preferredPutPatch: 'PUT',
common: {
parameters: {},
headers: {},
},

// AuthMiddleware is registered under the name "auth" in start/kernel.ts;
// any route using middleware.auth() is detected as requiring the bearer
// token security scheme below.
authMiddlewares: ['auth'],
defaultSecurityScheme: 'BearerAuth',
persistAuthorization: true,
}
17 changes: 17 additions & 0 deletions database/migrations/1787722100000_create_cache_table.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { BaseSchema } from '@adonisjs/lucid/schema'

export default class extends BaseSchema {
protected tableName = 'cache'

async up() {
this.schema.createTable(this.tableName, (table) => {
table.string('key', 255).notNullable().primary()
table.text('value', 'longtext')
table.timestamp('expires_at').nullable()
})
}

async down() {
this.schema.dropTable(this.tableName)
}
}
Loading
Loading