Skip to content

Commit

Permalink
Manage database dependencies so the integrating form service apps may…
Browse files Browse the repository at this point in the history
… import and run the server's express handler. This includes:

- Create helper in @atj/common to create service objects from a collection of service functions
- Remove module-global imports of @atj/database from the @atj/server, replacing with async imports where appropriate
TODO: The app integration tests for Kansas and DOJ are now passing, but the build bundle fails.
  • Loading branch information
danielnaab committed Aug 6, 2024
1 parent 3f6ee77 commit 947b2db
Show file tree
Hide file tree
Showing 34 changed files with 263 additions and 148 deletions.
25 changes: 23 additions & 2 deletions apps/server-doj/src/server.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,28 @@
import { createServer } from '@atj/server/dist/handler.js';
import path, { dirname } from 'path';
import { fileURLToPath } from 'url';

const getDirname = () => dirname(fileURLToPath(import.meta.url));

export const createCustomServer = async (): Promise<any> => {
const { createDevDatabaseContext, createDatabaseService } = await import(
'@atj/database'
);
const { createServer } = await import('@atj/server');

const dbCtx = await createDevDatabaseContext(
path.join(getDirname(), '../doj.db')
);
const database = createDatabaseService(dbCtx);

export const createCustomServer = () => {
return createServer({
title: 'DOJ Form Service',
database,
loginGovOptions: {
loginGovUrl: 'https://idp.int.identitysandbox.gov',
clientId:
'urn:gov:gsa:openidconnect.profiles:sp:sso:gsa:tts-10x-atj-dev-server-doj',
clientSecret: '',
redirectURI: 'http://localhost:4322/signin/callback',
},
});
};
1 change: 1 addition & 0 deletions apps/server-kansas/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
"test": "vitest run --coverage"
},
"dependencies": {
"@atj/database": "workspace:*",
"@atj/server": "workspace:*"
},
"devDependencies": {
Expand Down
25 changes: 23 additions & 2 deletions apps/server-kansas/src/server.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,28 @@
import { createServer } from '@atj/server/dist/handler.js';
import path, { dirname } from 'path';
import { fileURLToPath } from 'url';

const getDirname = () => dirname(fileURLToPath(import.meta.url));

export const createCustomServer = async (): Promise<any> => {
const { createDevDatabaseContext, createDatabaseService } = await import(
'@atj/database'
);
const { createServer } = await import('@atj/server');

const dbCtx = await createDevDatabaseContext(
path.join(getDirname(), '../doj.db')
);
const database = createDatabaseService(dbCtx);

export const createCustomServer = () => {
return createServer({
title: 'KS Courts Form Service',
database,
loginGovOptions: {
loginGovUrl: 'https://idp.int.identitysandbox.gov',
clientId:
'urn:gov:gsa:openidconnect.profiles:sp:sso:gsa:tts-10x-atj-dev-server-doj',
clientSecret: '',
redirectURI: 'http://localhost:4322/signin/callback',
},
});
};
7 changes: 4 additions & 3 deletions packages/auth/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@
"description": "10x ATJ auth module",
"type": "module",
"license": "CC0",
"main": "src/index.ts",
"main": "dist/index.js",
"types": "dist/index.d.js",
"scripts": {
"build": "tsup src/* --env.NODE_ENV production",
"dev": "tsup src/* --watch",
"build": "tsc",
"dev": "tsc --watch",
"test": "vitest run --coverage"
},
"dependencies": {
Expand Down
6 changes: 3 additions & 3 deletions packages/auth/src/context/dev.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Cookie, Lucia } from 'lucia';

import { type DevDatabaseContext } from '@atj/database';
import { type DatabaseService } from '@atj/database';

import { type AuthContext, type UserSession } from '..';
import { createTestLuciaAdapter } from '../lucia';
Expand All @@ -10,7 +10,7 @@ export class DevAuthContext implements AuthContext {
private lucia?: Lucia;

constructor(
public database: DevDatabaseContext,
public database: DatabaseService,
public provider: LoginGov,
public getCookie: (name: string) => string | undefined,
public setCookie: (cookie: Cookie) => void,
Expand All @@ -19,7 +19,7 @@ export class DevAuthContext implements AuthContext {

async getLucia() {
const sqlite3Adapter = createTestLuciaAdapter(
await this.database.getSqlite3()
await (this.database.getContext() as any).getSqlite3()
);
if (!this.lucia) {
this.lucia = new Lucia(sqlite3Adapter, {
Expand Down
10 changes: 6 additions & 4 deletions packages/auth/src/context/test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@ import { Cookie, Lucia } from 'lucia';
import { vi } from 'vitest';

import {
type TestDatabaseContext,
type DatabaseService,
createTestDatabaseContext,
createDatabaseService,
} from '@atj/database';

import { type AuthContext, type UserSession } from '..';
Expand All @@ -22,7 +23,8 @@ export const createTestAuthContext = async (opts?: Partial<Options>) => {
setCookie: opts?.setCookie || vi.fn(),
setUserSession: opts?.setUserSession || vi.fn(),
};
const database = await createTestDatabaseContext();
const dbContext = await createTestDatabaseContext();
const database = createDatabaseService(dbContext);
return new TestAuthContext(
database,
new LoginGov({
Expand All @@ -42,15 +44,15 @@ export class TestAuthContext implements AuthContext {
private lucia?: Lucia;

constructor(
public database: TestDatabaseContext,
public database: DatabaseService,
public provider: LoginGov,
public getCookie: (name: string) => string | undefined,
public setCookie: (cookie: Cookie) => void,
public setUserSession: (userSession: UserSession) => void
) {}

async getLucia() {
const sqlite3 = await this.database.getSqlite3();
const sqlite3 = await (this.database.getContext() as any).getSqlite3();
const sqlite3Adapter = createTestLuciaAdapter(sqlite3);
if (!this.lucia) {
this.lucia = new Lucia(sqlite3Adapter, {
Expand Down
8 changes: 4 additions & 4 deletions packages/auth/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { type Cookie, type User, type Session, type Lucia } from 'lucia';

import { type DatabaseContext } from '@atj/database';
import { type DatabaseService } from '@atj/database';

export { DevAuthContext } from './context/dev';
import { LoginGov } from './provider';
export { LoginGov };
import { type LoginGovOptions, LoginGov } from './provider';
export { type LoginGovOptions, LoginGov };
export { getProviderRedirect } from './services/get-provider-redirect';
export { logOut } from './services/logout';
export { processProviderCallback } from './services/process-provider-callback';
Expand All @@ -17,7 +17,7 @@ export type UserSession = {
};

export type AuthContext = {
database: DatabaseContext;
database: DatabaseService;
provider: LoginGov;
getCookie: (name: string) => string | undefined;
setCookie: (cookie: Cookie) => void;
Expand Down
31 changes: 30 additions & 1 deletion packages/auth/src/lucia.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { BetterSqlite3Adapter } from '@lucia-auth/adapter-sqlite';
import { Database as Sqlite3Database } from 'better-sqlite3';
import { type Database as Sqlite3Database } from 'better-sqlite3';
import { Lucia } from 'lucia';

import { type Database } from '@atj/database';
Expand All @@ -9,6 +9,7 @@ export const createTestLuciaAdapter = (db: Sqlite3Database) => {
user: 'users',
session: 'sessions',
});
//const adapter = new KyselyAdapter();
return adapter;
};

Expand All @@ -18,3 +19,31 @@ declare module 'lucia' {
DatabaseUserAttributes: Omit<Database['users'], 'id'>;
}
}

/*
export class KyselyAdapter implements Adapter {
getSessionAndUser(
sessionId: string
): Promise<[session: DatabaseSession | null, user: DatabaseUser | null]> {
throw new Error('Method not implemented.');
}
getUserSessions(userId: string): Promise<DatabaseSession[]> {
throw new Error('Method not implemented.');
}
setSession(session: DatabaseSession): Promise<void> {
throw new Error('Method not implemented.');
}
updateSessionExpiration(sessionId: string, expiresAt: Date): Promise<void> {
throw new Error('Method not implemented.');
}
deleteSession(sessionId: string): Promise<void> {
throw new Error('Method not implemented.');
}
deleteUserSessions(userId: string): Promise<void> {
throw new Error('Method not implemented.');
}
deleteExpiredSessions(): Promise<void> {
throw new Error('Method not implemented.');
}
}
*/
3 changes: 1 addition & 2 deletions packages/auth/src/services/process-provider-callback.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import { beforeEach, describe, expect, it, vi } from 'vitest';

import { processProviderCallback } from './process-provider-callback';
import { createTestAuthContext } from '../context/test';
import { createUser } from '@atj/database';
import { AuthContext } from '..';

describe('processProviderCallback', () => {
Expand All @@ -14,7 +13,7 @@ describe('processProviderCallback', () => {

// Create test auth context with a test user in the db
ctx = await createTestAuthContext();
const user = await createUser(ctx.database, '[email protected]');
const user = await ctx.database.createUser('[email protected]');
if (!user) {
expect.fail('error creating test user');
}
Expand Down
5 changes: 2 additions & 3 deletions packages/auth/src/services/process-provider-callback.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { OAuth2RequestError } from 'arctic';

import * as r from '@atj/common';
import { getUserId, createUser } from '@atj/database';
import { type AuthContext } from '..';
import { randomUUID } from 'crypto';

Expand Down Expand Up @@ -86,9 +85,9 @@ export const processProviderCallback = async (
if (!userDataResult.success) {
return userDataResult;
}
let userId = await getUserId(ctx.database, userDataResult.data.email);
let userId = await ctx.database.getUserId(userDataResult.data.email);
if (!userId) {
const newUser = await createUser(ctx.database, userDataResult.data.email);
const newUser = await ctx.database.createUser(userDataResult.data.email);
if (!newUser) {
return r.failure({ status: 500, message: 'error creating new user' });
}
Expand Down
6 changes: 2 additions & 4 deletions packages/auth/src/services/process-session-cookie.test.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import { randomUUID } from 'crypto';
import { beforeEach, describe, expect, it, vi } from 'vitest';

import { createSession, createUser } from '@atj/database';

import { createTestAuthContext } from '../context/test';
import { processSessionCookie } from './process-session-cookie';

Expand Down Expand Up @@ -121,11 +119,11 @@ const setUpTest = async (sessionExpirationDate: Date) => {
setUserSession: vi.fn(),
};
const ctx = await createTestAuthContext(mocks);
const user = await createUser(ctx.database, '[email protected]');
const user = await ctx.database.createUser('[email protected]');
if (!user) {
expect.fail('error creating test user');
}
const sessionId = await createSession(ctx.database, {
const sessionId = await ctx.database.createSession({
id: randomUUID(),
expiresAt: addOneDay(sessionExpirationDate),
sessionToken: 'my-token',
Expand Down
6 changes: 4 additions & 2 deletions packages/auth/tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"emitDeclarationOnly": false,
"outDir": "./dist",
"emitDeclarationOnly": true
"rootDir": "./src"
},
"include": ["./src", "global.d.ts"],
"include": ["src", "global.d.ts"],
"exclude": ["./dist"],
"references": []
}
2 changes: 2 additions & 0 deletions packages/common/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
export { createService } from './service';

export type Success<T> = { success: true; data: T };
export type VoidSuccess = { success: true };
export type Failure<E> = { success: false; error: E };
Expand Down
44 changes: 44 additions & 0 deletions packages/common/src/service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
type ServiceFunction<Args extends any[], Return, Context> = (
context: Context,
...args: Args
) => Return;

type ServiceFunctions<Context> = {
[key: string]: ServiceFunction<any[], any, Context>;
};

type WithoutFirstArg<F> = F extends (context: any, ...args: infer P) => infer R
? (...args: P) => R
: never;

type Service<
Context extends any,
Functions extends ServiceFunctions<Context>,
> = {
[K in keyof Functions]: WithoutFirstArg<Functions[K]>;
} & { getContext: () => Context };

export const createService = <
Context extends any,
Functions extends ServiceFunctions<Context>,
>(
ctx: Context,
serviceFunctions: Functions
): Service<Context, Functions> => {
const handler: ProxyHandler<Functions> = {
get(target: Functions, prop: string | symbol) {
if (prop === 'getContext') {
return () => ctx;
}
const propKey = prop as keyof Functions;
const originalFn = target[propKey];
return (...args: any[]) =>
(originalFn as Function).call(null, ctx, ...args);
},
};

return new Proxy(serviceFunctions, handler) as unknown as Service<
Context,
Functions
>;
};
1 change: 1 addition & 0 deletions packages/database/build.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ esbuild
bundle: false,
entryPoints: ['./src/index.ts'],
//external: ['sqlite3', 'knex'],
packages: 'external',
format: 'esm',
minify: true,
outdir: './dist',
Expand Down
7 changes: 4 additions & 3 deletions packages/database/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@
"description": "10x ATJ database",
"type": "module",
"license": "CC0",
"main": "src/index.ts",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"scripts": {
"build": "tsup src/* --env.NODE_ENV production",
"dev": "tsup src/* --watch",
"build": "tsc",
"dev": "tsc --watch",
"test": "vitest run --coverage"
},
"dependencies": {
Expand Down
2 changes: 1 addition & 1 deletion packages/database/src/clients/kysely/sqlite3.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Kysely, SqliteDialect } from 'kysely';
import BetterSqliteDatabase, {
Database as SqliteDatabase,
type Database as SqliteDatabase,
} from 'better-sqlite3';

import { type Database } from './types';
Expand Down
4 changes: 2 additions & 2 deletions packages/database/src/context/dev.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import path, { dirname } from 'path';
import { fileURLToPath } from 'url';

import { Database as SqliteDatabase } from 'better-sqlite3';
import { type Database as SqliteDatabase } from 'better-sqlite3';
import knex, { type Knex } from 'knex';
import { type Kysely } from 'kysely';

import { type Database, createSqliteDatabase } from '../clients/kysely';
import { migrateDatabase } from '../management';
import { migrateDatabase } from '../management/migrate-database';

import { type DatabaseContext } from './types';

Expand Down
Loading

0 comments on commit 947b2db

Please sign in to comment.