Skip to content

Commit

Permalink
Base authorization request evaluation flow
Browse files Browse the repository at this point in the history
This commit encompasses significant groundwork to ensure everything functions
correctly. Key highlights include:

- Integration of BullMQ for managing queues using Redis.
- Implementation of the initial database schema. I've opted to use JSON for the
  request, as it simplifies extending from the AuthZ node without modifying the
  Orchestration. This choice may affect searchability and is not final.
- Inclusion of an example of an end-to-end test that involves the database and
  queue, complete with state cleaning during teardown.
- Development of DTOs for the public interface, integrated with Swagger for
  documentation.
- Refactor organization to reflect the modular monolith agreed on RFC 006 [1].
- Type gymnastics from domain to DTO is not great. The discrimination property
  for the request outside it is making things painful.

[1] https://www.notion.so/narvalxyz/RFC-006-Moving-to-a-modular-monolith-f1ba845081e04b1790065cecc47dc5ba
  • Loading branch information
wcalderipe committed Jan 11, 2024
1 parent 8477ae2 commit ffbf03a
Show file tree
Hide file tree
Showing 40 changed files with 1,604 additions and 308 deletions.
13 changes: 11 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,10 +53,19 @@ make format/check
make lint/check
```

## Creating a NestJS library
## Generating a new project

Run the command below and follow the instructions to create a new NestJS library.
NX provides two types of projects: applications and libraries. Run the commands
below to generate a project of your choice.

```bash
# Generate an standard JavaScript library.
npx nx g @nrwl/workspace:lib
# Generate an NestJS library.
npx nx g @nx/nest:library
# Generate an NestJS application.
npx nx g @nx/nest:application
```

For more information about code generation, please refer to the [NX
documentation](https://nx.dev/nx-api/nx).
10 changes: 8 additions & 2 deletions apps/authz/Makefile
Original file line number Diff line number Diff line change
@@ -1,16 +1,22 @@
AUTHZ_PROJECT_NAME := authz
AUTHZ_PROJECT_DIR := ./apps/authz

authz/setup:
make authz/copy-default-env
# === Start ===

authz/start/dev:
npx nx serve ${AUTHZ_PROJECT_NAME}

# === Setup ===

authz/setup:
make authz/copy-default-env

authz/copy-default-env:
cp ${AUTHZ_PROJECT_DIR}/.env.default ${AUTHZ_PROJECT_DIR}/.env
cp ${AUTHZ_PROJECT_DIR}/.env.test.default ${AUTHZ_PROJECT_DIR}/.env.test

# === Testing ===

authz/test/type:
npx nx test:type ${AUTHZ_PROJECT_NAME}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Debugging Redis with Redis Insight

Redis Insight is a graphical user interface for Redis. It is available as a
Docker image, and can be used to inspect the contents of your Redis database.

```bash
docker run -v redisinsight:/db \
--publish 8001:8001 \
redislabs/redisinsight:latest
```

You can then access Redis Insight at http://localhost:8001.

> [!IMPORTANT]
> When adding a new connection, use the hostname of the host machine, not
> `localhost`.
> If you're on macOS, use `host.docker.internal`.
5 changes: 5 additions & 0 deletions apps/orchestration/.env.default
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
NODE_ENV=development

PORT=3005

ORCHESTRATION_DATABASE_URL="postgresql://postgres:postgres@localhost:5432/orchestration?schema=public"

REDIS_HOST=localhost
REDIS_PORT=6379
27 changes: 24 additions & 3 deletions apps/orchestration/Makefile
Original file line number Diff line number Diff line change
@@ -1,19 +1,25 @@
ORCHESTRATION_PROJECT_NAME := orchestration
ORCHESTRATION_PROJECT_DIR := ./apps/orchestration
ORCHESTRATION_DATABASE_SCHEMA := ${ORCHESTRATION_PROJECT_DIR}/src/persistence/schema/schema.prisma
ORCHESTRATION_DATABASE_SCHEMA := ${ORCHESTRATION_PROJECT_DIR}/src/shared/module/persistence/schema/schema.prisma

# === Start ===

orchestration/start/dev:
npx nx serve ${ORCHESTRATION_PROJECT_NAME}

# === Setup ===

orchestration/setup:
make orchestration/copy-default-env
make orchestration/db/migrate
make orchestration/db/setup
make orchestration/test/db/setup

orchestration/copy-default-env:
cp ${ORCHESTRATION_PROJECT_DIR}/.env.default ${ORCHESTRATION_PROJECT_DIR}/.env
cp ${ORCHESTRATION_PROJECT_DIR}/.env.test.default ${ORCHESTRATION_PROJECT_DIR}/.env.test

# === Database ===

orchestration/db/generate-types:
npx prisma generate \
--schema ${ORCHESTRATION_DATABASE_SCHEMA}
Expand All @@ -23,12 +29,27 @@ orchestration/db/migrate:
prisma migrate dev \
--schema ${ORCHESTRATION_DATABASE_SCHEMA}

orchestration/db/setup:
npx dotenv -e ${ORCHESTRATION_PROJECT_DIR}/.env -- \
prisma migrate reset \
--schema ${ORCHESTRATION_DATABASE_SCHEMA} \
--force
make orchestration/db/seed

orchestration/db/create-migration:
npx dotenv -e ${ORCHESTRATION_PROJECT_DIR}/.env -- \
prisma migrate dev \
--schema ${ORCHESTRATION_DATABASE_SCHEMA} \
--name ${NAME}

# Reference: https://www.prisma.io/docs/orm/prisma-migrate/workflows/seeding#seeding-your-database-with-typescript-or-javascript
orchestration/db/seed:
npx dotenv -e ${ORCHESTRATION_PROJECT_DIR}/.env -- \
ts-node \
--compiler-options "{\"module\":\"CommonJS\"}" \
${ORCHESTRATION_PROJECT_DIR}/src/persistence/seed.ts
${ORCHESTRATION_PROJECT_DIR}/src/shared/module/persistence/seed.ts

# === Testing ===

orchestration/test/db/setup:
npx dotenv -e ${ORCHESTRATION_PROJECT_DIR}/.env.test --override -- \
Expand Down
7 changes: 7 additions & 0 deletions apps/orchestration/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,10 @@ make orchestration/test/unit
make orchestration/test/integration
make orchestration/test/e2e
```

## Database

```bash
make orchestration/db/migrate
make orchestration/db/create-migration NAME=your-migration-name
```
26 changes: 13 additions & 13 deletions apps/orchestration/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,23 @@ import { INestApplication, Logger, ValidationPipe } from '@nestjs/common'
import { ConfigService } from '@nestjs/config'
import { NestFactory } from '@nestjs/core'
import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger'
import { lastValueFrom, map, of, switchMap } from 'rxjs'
import { lastValueFrom, map, of, switchMap, tap } from 'rxjs'
import { Config } from './orchestration.config'

/**
* Sets up Swagger documentation for the application.
*
* @param app - The INestApplication instance.
* @param logger - The logger instance.
* @returns The modified INestApplication instance.
*/
const setupSwagger = (app: INestApplication, logger: Logger): INestApplication => {
logger.log('Setting up Swagger')

const withSwagger = (app: INestApplication): INestApplication => {
const document = SwaggerModule.createDocument(
app,
new DocumentBuilder().setTitle('Orchestration').setVersion('1.0').addTag('Orchestration').build()
new DocumentBuilder()
.setTitle('Orchestration')
.setDescription('Orchestration is the most secure infrastructure to run authorization for web3.')
.setVersion('1.0')
.build()
)
SwaggerModule.setup('docs', app, document, {
swaggerOptions: {
Expand All @@ -35,12 +36,9 @@ const setupSwagger = (app: INestApplication, logger: Logger): INestApplication =
* Sets up REST global validation for the application.
*
* @param app - The INestApplication instance.
* @param logger - The logger instance.
* @returns The modified INestApplication instance.
*/
const setupRestValidation = (app: INestApplication, logger: Logger): INestApplication => {
logger.log('Setting up REST global validation')

const withRestValidation = (app: INestApplication): INestApplication => {
app.useGlobalPipes(new ValidationPipe())

return app
Expand All @@ -52,16 +50,18 @@ const setupRestValidation = (app: INestApplication, logger: Logger): INestApplic
* @returns {Promise<void>} A promise that resolves when the application is
* successfully bootstrapped.
*/
async function bootstrap() {
async function bootstrap(): Promise<void> {
const logger = new Logger('OrchestrationBootstrap')
const application = await NestFactory.create(OrchestrationModule)
const configService = application.get<ConfigService<Config, true>>(ConfigService)
const port = configService.get('port', { infer: true })

await lastValueFrom(
of(application).pipe(
map((app) => setupSwagger(app, logger)),
map((app) => setupRestValidation(app, logger)),
map((app) => withSwagger(app)),
tap(() => logger.log('Added Swagger')),
map((app) => withRestValidation(app)),
tap(() => logger.log('Added REST global validation')),
switchMap((app) => app.listen(port))
)
)
Expand Down
23 changes: 19 additions & 4 deletions apps/orchestration/src/orchestration.config.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,40 @@
import { z } from 'zod'

const ConfigSchema = z.object({
export enum Env {
DEVELOPMENT = 'development',
TEST = 'test'
}

const configSchema = z.object({
env: z.nativeEnum(Env),
port: z.coerce.number(),
database: z.object({
url: z.string().startsWith('postgresql://')
}),
redis: z.object({
host: z.string().min(0),
port: z.coerce.number()
})
})

export type Config = z.infer<typeof ConfigSchema>
export type Config = z.infer<typeof configSchema>

export const load = (): Config => {
const result = ConfigSchema.safeParse({
const result = configSchema.safeParse({
env: process.env.NODE_ENV,
port: process.env.PORT,
database: {
url: process.env.ORCHESTRATION_DATABASE_URL
},
redis: {
host: process.env.REDIS_HOST,
port: process.env.REDIS_PORT
}
})

if (result.success) {
return result.data
}

throw new Error(`Invalid application configuration: ${result.error.message}`)
throw new Error(`Invalid Orchestration configuration: ${result.error.message}`)
}
4 changes: 4 additions & 0 deletions apps/orchestration/src/orchestration.constant.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export const QUEUE_PREFIX = 'orchestration'
export const AUTHORIZATION_REQUEST_PROCESSING_QUEUE = 'authorization-request:processing'

export const REQUEST_HEADER_ORG_ID = 'x-org-id'
6 changes: 3 additions & 3 deletions apps/orchestration/src/orchestration.module.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
import { PolicyEngineModule } from '@app/orchestration/policy-engine/policy-engine.module'
import { TransactionEngineModule } from '@narval/transaction-engine-module'
import { Module } from '@nestjs/common'
import { ConfigModule } from '@nestjs/config'
import { load } from './orchestration.config'
import { QueueModule } from './shared/module/queue/queue.module'

@Module({
imports: [
ConfigModule.forRoot({
load: [load],
isGlobal: true
}),
PolicyEngineModule,
TransactionEngineModule
QueueModule.forRoot(),
PolicyEngineModule
]
})
export class OrchestrationModule {}
9 changes: 0 additions & 9 deletions apps/orchestration/src/persistence/persistence.module.ts

This file was deleted.

This file was deleted.

19 changes: 0 additions & 19 deletions apps/orchestration/src/persistence/schema/schema.prisma

This file was deleted.

35 changes: 0 additions & 35 deletions apps/orchestration/src/persistence/seed.ts

This file was deleted.

Loading

0 comments on commit ffbf03a

Please sign in to comment.