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 .actrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
--container-architecture=linux/amd64
--action-offline-mode
9 changes: 9 additions & 0 deletions .changeset/nasty-worlds-wait.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
"@fedimod/fires-server": minor
---

Bootstrap fires-server component

- Sets up an adonis.js application with postgresql, lucid, vite, edge.js, and pico.css
- Adds database configuration for using SSL CA Certificates (needed for people to deploy with providers like DigitalOcean's Managed Databases)
- Disables multipart/form-data requests, as the FIRES server doesn't need to handle these, but there's no way to disable them in Adonis.js yet. See: https://github.com/adonisjs/bodyparser/pull/66
2 changes: 1 addition & 1 deletion .env.example
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
# Used for changesets during the release process:
GITHUB_TOKEN="op://..."

84 changes: 84 additions & 0 deletions .github/workflows/test-server.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
name: Test Reference Server

on:
pull_request:
push:
paths:
- "components/fires-server/**"
- "package-lock.json"

# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued.
# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete.
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: false

permissions:
contents: read

jobs:
check:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: 22
cache: npm
- name: Install dependencies
run: npm ci
- name: Check formatting
working-directory: "components/fires-server/"
run: npm run format:check
- name: Lint
working-directory: "components/fires-server/"
run: npm run lint
- name: Typecheck
working-directory: "components/fires-server/"
run: npm run typecheck

test:
Comment thread Fixed
runs-on: ubuntu-latest
services:
postgres:
image: postgres:16-alpine
env:
POSTGRES_PASSWORD: postgres
POSTGRES_USER: postgres
POSTGRES_DB: fires_test
options: >-
--health-cmd pg_isready
--health-interval 10ms
--health-timeout 3s
--health-retries 50
ports:
# using a non-standard port to support running workflow with act:
# https://nektosact.com/
- 54321:5432
env:
TZ: UTC
NODE_ENV: development
PORT: 4444
HOST: localhost
LOG_LEVEL: info
APP_KEY: "test_determinist_key_DO_NOT_USE_IN_PRODUCTION"
DATABASE_HOST: 127.0.0.1
DATABASE_PORT: 54321
DATABASE_USER: postgres
DATABASE_PASSWORD: postgres
DATABASE_DATABASE: fires_test
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: 22
cache: npm
- name: Install dependencies
run: npm ci
- name: Run tests
working-directory: "components/fires-server/"
run: npm test -- --no-clear
Comment thread Fixed
22 changes: 22 additions & 0 deletions components/fires-server/.editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# http://editorconfig.org

[*]
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 = unset

[**.min.js]
indent_style = unset
insert_final_newline = unset

[MakeFile]
indent_style = space

[*.md]
trim_trailing_whitespace = false
17 changes: 17 additions & 0 deletions components/fires-server/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
TZ=UTC
NODE_ENV=development

PORT=4444
HOST=localhost

LOG_LEVEL=info

# You can generate an APP_KEY with: node ace generate:key
APP_KEY=

DATABASE_HOST=127.0.0.1
DATABASE_PORT=5432
DATABASE_USER=fires
DATABASE_PASSWORD=some-long-password
# Optional: defaults to fires_<NODE_ENV>
# DATABASE_DATABASE=fires_production
25 changes: 25 additions & 0 deletions components/fires-server/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Dependencies and AdonisJS build
node_modules
build
tmp

# Secrets
.env
.env.local
.env.production.local
.env.development.local

# Frontend assets compiled code
public/assets

# Build tools specific
npm-debug.log
yarn-error.log

# Editors specific
.fleet
.idea
.vscode

# Platform specific
.DS_Store
27 changes: 27 additions & 0 deletions components/fires-server/ace.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/*
|--------------------------------------------------------------------------
| JavaScript entrypoint for running ace commands
|--------------------------------------------------------------------------
|
| DO NOT MODIFY THIS FILE AS IT WILL BE OVERRIDDEN DURING THE BUILD
| PROCESS.
|
| See docs.adonisjs.com/guides/typescript-build-process#creating-production-build
|
| Since, we cannot run TypeScript source code using "node" binary, we need
| a JavaScript entrypoint to run ace commands.
|
| This file registers the "ts-node/esm" hook with the Node.js module system
| and then imports the "bin/console.ts" file.
|
*/

/**
* Register hook to process TypeScript files using ts-node
*/
import 'ts-node-maintained/register/esm'

/**
* Import ace console entrypoint
*/
await import('./bin/console.js')
95 changes: 95 additions & 0 deletions components/fires-server/adonisrc.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import { defineConfig } from '@adonisjs/core/app'

export default defineConfig({
/*
|--------------------------------------------------------------------------
| Commands
|--------------------------------------------------------------------------
|
| List of ace commands to register from packages. The application commands
| will be scanned automatically from the "./commands" directory.
|
*/
commands: [() => import('@adonisjs/core/commands'), () => import('@adonisjs/lucid/commands')],

/*
|--------------------------------------------------------------------------
| Service providers
|--------------------------------------------------------------------------
|
| List of service providers to import and register when booting the
| application
|
*/
providers: [
() => import('@adonisjs/core/providers/app_provider'),
() => import('@adonisjs/core/providers/hash_provider'),
() => import('@adonisjs/static/static_provider'),
{
file: () => import('@adonisjs/core/providers/repl_provider'),
environment: ['repl', 'test'],
},
() => import('@adonisjs/cors/cors_provider'),
() => import('@adonisjs/core/providers/vinejs_provider'),
() => import('@adonisjs/lucid/database_provider'),
() => import('@adonisjs/vite/vite_provider'),
() => import('@adonisjs/core/providers/edge_provider'),
() => import('@adonisjs/shield/shield_provider'),
],

/*
|--------------------------------------------------------------------------
| Preloads
|--------------------------------------------------------------------------
|
| List of modules to import before starting the application.
|
*/
preloads: [
() => import('#start/logging'),
() => import('#start/routes'),
() => import('#start/kernel'),
],

/*
|--------------------------------------------------------------------------
| Tests
|--------------------------------------------------------------------------
|
| List of test suites to organize tests by their type. Feel free to remove
| and add additional suites.
|
*/
tests: {
suites: [
{
files: ['tests/unit/**/*.spec(.ts|.js)'],
name: 'unit',
timeout: 2000,
},
{
files: ['tests/functional/**/*.spec(.ts|.js)'],
name: 'functional',
timeout: 30000,
},
],
forceExit: false,
},

metaFiles: [
{
pattern: 'public/**',
reloadServer: false,
},
{
pattern: 'resources/views/**/*.edge',
reloadServer: false,
},
],

hooks: {
onBuildStarting: [() => import('@adonisjs/vite/build_hook')],
},

assetsBundler: false,
})
7 changes: 7 additions & 0 deletions components/fires-server/app/controllers/about_controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import type { HttpContext } from '@adonisjs/core/http'

export default class AboutController {
async index({ view }: HttpContext) {
return view.render('about/index')
}
}
28 changes: 28 additions & 0 deletions components/fires-server/app/exceptions/handler.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import app from '@adonisjs/core/services/app'
import { HttpContext, ExceptionHandler } from '@adonisjs/core/http'

export default class HttpExceptionHandler extends ExceptionHandler {
/**
* In debug mode, the exception handler will display verbose errors
* with pretty printed stack traces.
*/
protected debug = !app.inProduction

/**
* The method is used for handling errors and returning
* response to the client
*/
async handle(error: unknown, ctx: HttpContext) {
return super.handle(error, ctx)
}

/**
* The method is used to report error to the logging service or
* the third party error monitoring service.
*
* @note You should not attempt to send a response from this method.
*/
async report(error: unknown, ctx: HttpContext) {
return super.report(error, ctx)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { Logger } from '@adonisjs/core/logger'
import { HttpContext } from '@adonisjs/core/http'
import type { NextFn } from '@adonisjs/core/types/http'

/**
* The container bindings middleware binds classes to their request
* specific value using the container resolver.
*
* - We bind "HttpContext" class to the "ctx" object
* - And bind "Logger" class to the "ctx.logger" object
*/
export default class ContainerBindingsMiddleware {
handle(ctx: HttpContext, next: NextFn) {
ctx.containerResolver.bindValue(HttpContext, ctx)
ctx.containerResolver.bindValue(Logger, ctx.logger)

return next()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import type { HttpContext } from '@adonisjs/core/http'
import type { NextFn } from '@adonisjs/core/types/http'

/**
* This middleware ensures that any multipart requests do not make it through
* and are immediately terminated before being parsed or handled in anyway
*/
export default class DisableMultipartRequestsMiddleware {
async handle({ request, response }: HttpContext, next: NextFn) {
const type = request.is(['multipart/form-data'])

if (type === 'multipart/form-data') {
return response.status(400).json({ error: 'Bad request' })
}

return next()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import type { HttpContext } from '@adonisjs/core/http'
import type { NextFn } from '@adonisjs/core/types/http'

/**
* Updating the "Accept" header to always accept "application/json" response
* from the server. This will force the internals of the framework like
* validator errors or auth errors to return a JSON response.
*/
export default class ForceJsonResponseMiddleware {
async handle({ request }: HttpContext, next: NextFn) {
const headers = request.headers()
headers.accept = 'application/json'

return next()
}
}
Loading