Plan — BJ-5: atlassian_oauth_state persistence#
Summary#
Add a new atlassian_oauth_state table (via TypeORM entity, not a raw migration — see Risks) plus a AtlassianOauthStateRepo with get/upsert, so Atlassian OAuth tokens (which rotate on every refresh) can be persisted durably per (platform, tenantId).
Files#
| File | Action | Purpose |
|---|---|---|
src/database/entities/atlassian-oauth-state.entity.ts | create | TypeORM entity atlassian_oauth_state with unique (platform, tenantId) |
src/database/entities/index.ts | modify | Barrel export the new entity |
src/database/database.module.ts | modify | Register entity in forRoot + forFeature arrays |
src/atlassian/atlassian-oauth-state.repo.ts | create | @Injectable() service wrapping Repository<Entity>; get + upsert |
src/atlassian/atlassian-oauth-state.repo.spec.ts | create | Unit tests against in-memory SQLite |
src/atlassian/atlassian.module.ts | create | NestJS module: TypeOrmModule.forFeature([Entity]) + exports repo |
src/atlassian/index.ts | create | Barrel: module, repo, entity |
Steps#
- Create
AtlassianOauthStateEntitywith columns:id(pk generated uuid),platform(varchar),tenantId(varchar, column nametenant_id),accessToken(text, nullable),refreshToken(text, not null),expiresAt(datetime, nullable, column nameexpires_at),updatedAt(@UpdateDateColumn);@Unique(['platform', 'tenantId']). - Export the entity from
src/database/entities/index.tsand add it to both entity arrays indatabase.module.ts. - Create
AtlassianOauthStateRepowithget(platform, tenantId)→ entity ornull, andupsert(platform, tenantId, { accessToken, refreshToken, expiresAt })usingRepository.upsert(..., ['platform','tenantId'])fallback to find+save if driver lacks support. - Create
AtlassianModuleregisteringTypeOrmModule.forFeature([AtlassianOauthStateEntity]), providing and exportingAtlassianOauthStateRepo. - Add barrel
src/atlassian/index.tsexporting module, repo, entity. - Write
atlassian-oauth-state.repo.spec.tsusing NestJSTest.createTestingModulewithDatabaseModule(NODE_ENV=test →:memory:) covering: insert via upsert, read via get, idempotent upsert on same key (no duplicate), nullableaccessTokenandexpiresAtround-trip.
Verification#
npm run testpasses; new spec green.npm run lintzero warnings;npm run buildclean (strict TS, noany).- Boot-time
synchronize:truecreates theatlassian_oauth_statetable (confirmed by spec running against in-memory sqlite).
Risks#
- Issue wording vs. repo reality: issue asks for a migration file + PG types (
timestamptz,uuid).agent-coreusesbetter-sqlite3withsynchronize:trueand has nosrc/migrations/directory. Plan delivers an entity (the de-facto "migration" here); types are mapped to SQLite equivalents (varchar/datetime). Behaviour and acceptance criteria are preserved. - Upsert portability:
Repository.upsertworks withbetter-sqlite3≥ 0.3 viaON CONFLICT DO UPDATE; spec covers the concurrent-key case explicitly.
Open Questions#
- None blocking. PR targets
rc/atlassian-integrationper EPIC #539.