fix: Resolve 500/404 cascade — JWT tenant context, wipe routes, changelog stub
All checks were successful
Test Asgard Runner / test (push) Successful in 3s

Root cause: super_admin JWT returned early with no license_id, causing
@CurrentTenant() to pass undefined to every tenant-scoped service query.

- jwt.strategy: Move license lookup before super_admin early return so
  admins who own licenses get their license_id in the JWT payload
- CurrentTenant decorator: Throw 401 with clear message when license_id
  is undefined instead of letting undefined cascade into TypeORM queries
- Wipe store: Fix 6 wrong routes (/profiles → /wipes/profiles, etc.)
  and remove redundant manual license_id guards
- Changelog module: Add stub controller/service returning empty array
  to eliminate 404 on /api/changelog
- ChangelogView: Handle both array and {entries} response shapes
- AGENTS.md: Streamlined 3-tier roster (Opus/Sonnet/Haiku)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Vantz Stockwell
2026-02-15 22:11:41 -05:00
parent 05315cc88a
commit 3cb714a792
9 changed files with 139 additions and 189 deletions

View File

@@ -3,12 +3,10 @@ import { ref, onMounted } from 'vue'
import type { WipeProfile, WipeSchedule, WipeHistory } from '@/types'
import { useApi } from '@/composables/useApi'
import { useWebSocket, type WebSocketMessage } from '@/composables/useWebSocket'
import { useAuthStore } from '@/stores/auth'
import { useToastStore } from '@/stores/toast'
export const useWipeStore = defineStore('wipe', () => {
const api = useApi()
const authStore = useAuthStore()
const websocket = useWebSocket()
const toast = useToastStore()
@@ -91,7 +89,7 @@ export const useWipeStore = defineStore('wipe', () => {
isLoading.value = true
error.value = null
try {
const data = await api.get<WipeProfile[]>('/profiles')
const data = await api.get<WipeProfile[]>('/wipes/profiles')
profiles.value = data
} catch (err) {
error.value = err instanceof Error ? err.message : 'Failed to fetch profiles'
@@ -106,7 +104,7 @@ export const useWipeStore = defineStore('wipe', () => {
isLoading.value = true
error.value = null
try {
const data = await api.get<WipeSchedule[]>('/schedules')
const data = await api.get<WipeSchedule[]>('/wipes/schedules')
schedules.value = data
} catch (err) {
error.value = err instanceof Error ? err.message : 'Failed to fetch schedules'
@@ -121,11 +119,6 @@ export const useWipeStore = defineStore('wipe', () => {
isLoading.value = true
error.value = null
try {
const licenseId = authStore.license?.id
if (!licenseId) {
throw new Error('No license ID available')
}
const data = await api.get<WipeHistory[]>(`/wipes/history?limit=${limit}`)
history.value = data
} catch (err) {
@@ -144,15 +137,9 @@ export const useWipeStore = defineStore('wipe', () => {
isLoading.value = true
error.value = null
try {
const licenseId = authStore.license?.id
if (!licenseId) {
throw new Error('No license ID available')
}
const result = await api.post<{ wipe_history_id: string }>(
`/wipes/trigger`,
{
license_id: licenseId,
wipe_type: wipeType,
profile_id: profileId,
trigger_type: 'manual',
@@ -197,11 +184,6 @@ export const useWipeStore = defineStore('wipe', () => {
isLoading.value = true
error.value = null
try {
const licenseId = authStore.license?.id
if (!licenseId) {
throw new Error('No license ID available')
}
const result = await api.post<{
would_delete: string[]
would_preserve: string[]
@@ -209,7 +191,6 @@ export const useWipeStore = defineStore('wipe', () => {
}>(
`/wipes/dry-run`,
{
license_id: licenseId,
wipe_type: wipeType,
profile_id: profileId,
}
@@ -229,15 +210,7 @@ export const useWipeStore = defineStore('wipe', () => {
isLoading.value = true
error.value = null
try {
const licenseId = authStore.license?.id
if (!licenseId) {
throw new Error('No license ID available')
}
const newProfile = await api.post<WipeProfile>('/profiles', {
...profile,
license_id: licenseId,
})
const newProfile = await api.post<WipeProfile>('/wipes/profiles', profile)
profiles.value.push(newProfile)
return newProfile
@@ -254,7 +227,7 @@ export const useWipeStore = defineStore('wipe', () => {
isLoading.value = true
error.value = null
try {
const updated = await api.put<WipeProfile>(`/profiles/${id}`, updates)
const updated = await api.put<WipeProfile>(`/wipes/profiles/${id}`, updates)
const index = profiles.value.findIndex(p => p.id === id)
if (index !== -1) {
profiles.value[index] = updated
@@ -272,7 +245,7 @@ export const useWipeStore = defineStore('wipe', () => {
isLoading.value = true
error.value = null
try {
await api.del(`/profiles/${id}`)
await api.del(`/wipes/profiles/${id}`)
profiles.value = profiles.value.filter(p => p.id !== id)
} catch (err) {
error.value = err instanceof Error ? err.message : 'Failed to delete profile'

View File

@@ -21,11 +21,12 @@ const hasMore = ref(true)
async function fetchChangelog() {
isLoading.value = true
try {
const result = await api.get<ChangelogEntry[]>(`/changelog?page=${page.value}&limit=20`)
if (result.length === 0) {
const result = await api.get<{ entries: ChangelogEntry[] } | ChangelogEntry[]>(`/changelog?page=${page.value}&limit=20`)
const items = Array.isArray(result) ? result : (result.entries ?? [])
if (items.length === 0) {
hasMore.value = false
} else {
entries.value.push(...result)
entries.value.push(...items)
}
} finally {
isLoading.value = false