fix: Comprehensive validation and bug fixes

Infrastructure:
- Add Redis and Celery workers to all docker-compose files
- Fix celery migration race condition in entrypoint.sh
- Add healthchecks and resource limits to dev compose
- Update .env.template with Redis/Celery variables

Backend Models & Schemas:
- Rename Sprint.completed_points to velocity (per requirements)
- Add AgentInstance.name as required field
- Rename Issue external tracker fields for consistency
- Add IssueSource and TrackerType enums
- Add Project.default_tracker_type field

Backend Fixes:
- Add Celery retry configuration with exponential backoff
- Remove unused sequence counter from EventBus
- Add mypy overrides for test dependencies
- Fix test file using wrong schema (UserUpdate -> dict)

Frontend Fixes:
- Fix memory leak in useProjectEvents (proper cleanup)
- Fix race condition with stale closure in reconnection
- Sync TokenWithUser type with regenerated API client
- Fix expires_in null handling in useAuth
- Clean up unused imports in prototype pages
- Add ESLint relaxed rules for prototype files

CI/CD:
- Add E2E testing stage with Testcontainers
- Add security scanning with Trivy and pip-audit
- Add dependency caching for faster builds

Tests:
- Update all tests to use renamed fields (velocity, name, etc.)
- Fix 14 schema test failures
- All 1500 tests pass with 91% coverage

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2025-12-30 10:35:30 +01:00
parent 6ea9edf3d1
commit 742ce4c9c8
57 changed files with 1062 additions and 332 deletions

View File

@@ -3,7 +3,7 @@
import { type Client, type Options as Options2, type TDataShape, urlSearchParamsBodySerializer } from './client';
import { client } from './client.gen';
import type { AdminActivateUserData, AdminActivateUserErrors, AdminActivateUserResponses, AdminAddOrganizationMemberData, AdminAddOrganizationMemberErrors, AdminAddOrganizationMemberResponses, AdminBulkUserActionData, AdminBulkUserActionErrors, AdminBulkUserActionResponses, AdminCreateOrganizationData, AdminCreateOrganizationErrors, AdminCreateOrganizationResponses, AdminCreateUserData, AdminCreateUserErrors, AdminCreateUserResponses, AdminDeactivateUserData, AdminDeactivateUserErrors, AdminDeactivateUserResponses, AdminDeleteOrganizationData, AdminDeleteOrganizationErrors, AdminDeleteOrganizationResponses, AdminDeleteUserData, AdminDeleteUserErrors, AdminDeleteUserResponses, AdminGetOrganizationData, AdminGetOrganizationErrors, AdminGetOrganizationResponses, AdminGetStatsData, AdminGetStatsResponses, AdminGetUserData, AdminGetUserErrors, AdminGetUserResponses, AdminListOrganizationMembersData, AdminListOrganizationMembersErrors, AdminListOrganizationMembersResponses, AdminListOrganizationsData, AdminListOrganizationsErrors, AdminListOrganizationsResponses, AdminListSessionsData, AdminListSessionsErrors, AdminListSessionsResponses, AdminListUsersData, AdminListUsersErrors, AdminListUsersResponses, AdminRemoveOrganizationMemberData, AdminRemoveOrganizationMemberErrors, AdminRemoveOrganizationMemberResponses, AdminUpdateOrganizationData, AdminUpdateOrganizationErrors, AdminUpdateOrganizationResponses, AdminUpdateUserData, AdminUpdateUserErrors, AdminUpdateUserResponses, ChangeCurrentUserPasswordData, ChangeCurrentUserPasswordErrors, ChangeCurrentUserPasswordResponses, CleanupExpiredSessionsData, CleanupExpiredSessionsResponses, ConfirmPasswordResetData, ConfirmPasswordResetErrors, ConfirmPasswordResetResponses, DeleteOauthClientData, DeleteOauthClientErrors, DeleteOauthClientResponses, DeleteUserData, DeleteUserErrors, DeleteUserResponses, GetCurrentUserProfileData, GetCurrentUserProfileResponses, GetMyOrganizationsData, GetMyOrganizationsErrors, GetMyOrganizationsResponses, GetOauthAuthorizationUrlData, GetOauthAuthorizationUrlErrors, GetOauthAuthorizationUrlResponses, GetOauthServerMetadataData, GetOauthServerMetadataResponses, GetOrganizationData, GetOrganizationErrors, GetOrganizationMembersData, GetOrganizationMembersErrors, GetOrganizationMembersResponses, GetOrganizationResponses, GetUserByIdData, GetUserByIdErrors, GetUserByIdResponses, HandleOauthCallbackData, HandleOauthCallbackErrors, HandleOauthCallbackResponses, HealthCheckData, HealthCheckResponses, ListMyOauthConsentsData, ListMyOauthConsentsResponses, ListMySessionsData, ListMySessionsResponses, ListOauthAccountsData, ListOauthAccountsResponses, ListOauthClientsData, ListOauthClientsResponses, ListOauthProvidersData, ListOauthProvidersResponses, ListUsersData, ListUsersErrors, ListUsersResponses, LoginData, LoginErrors, LoginOauthData, LoginOauthErrors, LoginOauthResponses, LoginResponses, LogoutAllData, LogoutAllResponses, LogoutData, LogoutErrors, LogoutResponses, OauthProviderAuthorizeData, OauthProviderAuthorizeErrors, OauthProviderAuthorizeResponses, OauthProviderConsentData, OauthProviderConsentErrors, OauthProviderConsentResponses, OauthProviderIntrospectData, OauthProviderIntrospectErrors, OauthProviderIntrospectResponses, OauthProviderRevokeData, OauthProviderRevokeErrors, OauthProviderRevokeResponses, OauthProviderTokenData, OauthProviderTokenErrors, OauthProviderTokenResponses, RefreshTokenData, RefreshTokenErrors, RefreshTokenResponses, RegisterData, RegisterErrors, RegisterOauthClientData, RegisterOauthClientErrors, RegisterOauthClientResponses, RegisterResponses, RequestPasswordResetData, RequestPasswordResetErrors, RequestPasswordResetResponses, RevokeMyOauthConsentData, RevokeMyOauthConsentErrors, RevokeMyOauthConsentResponses, RevokeSessionData, RevokeSessionErrors, RevokeSessionResponses, RootGetData, RootGetResponses, StartOauthLinkData, StartOauthLinkErrors, StartOauthLinkResponses, UnlinkOauthAccountData, UnlinkOauthAccountErrors, UnlinkOauthAccountResponses, UpdateCurrentUserData, UpdateCurrentUserErrors, UpdateCurrentUserResponses, UpdateOrganizationData, UpdateOrganizationErrors, UpdateOrganizationResponses, UpdateUserData, UpdateUserErrors, UpdateUserResponses } from './types.gen';
import type { AdminActivateUserData, AdminActivateUserErrors, AdminActivateUserResponses, AdminAddOrganizationMemberData, AdminAddOrganizationMemberErrors, AdminAddOrganizationMemberResponses, AdminBulkUserActionData, AdminBulkUserActionErrors, AdminBulkUserActionResponses, AdminCreateOrganizationData, AdminCreateOrganizationErrors, AdminCreateOrganizationResponses, AdminCreateUserData, AdminCreateUserErrors, AdminCreateUserResponses, AdminDeactivateUserData, AdminDeactivateUserErrors, AdminDeactivateUserResponses, AdminDeleteOrganizationData, AdminDeleteOrganizationErrors, AdminDeleteOrganizationResponses, AdminDeleteUserData, AdminDeleteUserErrors, AdminDeleteUserResponses, AdminGetOrganizationData, AdminGetOrganizationErrors, AdminGetOrganizationResponses, AdminGetStatsData, AdminGetStatsResponses, AdminGetUserData, AdminGetUserErrors, AdminGetUserResponses, AdminListOrganizationMembersData, AdminListOrganizationMembersErrors, AdminListOrganizationMembersResponses, AdminListOrganizationsData, AdminListOrganizationsErrors, AdminListOrganizationsResponses, AdminListSessionsData, AdminListSessionsErrors, AdminListSessionsResponses, AdminListUsersData, AdminListUsersErrors, AdminListUsersResponses, AdminRemoveOrganizationMemberData, AdminRemoveOrganizationMemberErrors, AdminRemoveOrganizationMemberResponses, AdminUpdateOrganizationData, AdminUpdateOrganizationErrors, AdminUpdateOrganizationResponses, AdminUpdateUserData, AdminUpdateUserErrors, AdminUpdateUserResponses, ChangeCurrentUserPasswordData, ChangeCurrentUserPasswordErrors, ChangeCurrentUserPasswordResponses, CleanupExpiredSessionsData, CleanupExpiredSessionsResponses, ConfirmPasswordResetData, ConfirmPasswordResetErrors, ConfirmPasswordResetResponses, DeleteOauthClientData, DeleteOauthClientErrors, DeleteOauthClientResponses, DeleteUserData, DeleteUserErrors, DeleteUserResponses, GetCurrentUserProfileData, GetCurrentUserProfileResponses, GetMyOrganizationsData, GetMyOrganizationsErrors, GetMyOrganizationsResponses, GetOauthAuthorizationUrlData, GetOauthAuthorizationUrlErrors, GetOauthAuthorizationUrlResponses, GetOauthServerMetadataData, GetOauthServerMetadataResponses, GetOrganizationData, GetOrganizationErrors, GetOrganizationMembersData, GetOrganizationMembersErrors, GetOrganizationMembersResponses, GetOrganizationResponses, GetUserByIdData, GetUserByIdErrors, GetUserByIdResponses, HandleOauthCallbackData, HandleOauthCallbackErrors, HandleOauthCallbackResponses, HealthCheckData, HealthCheckResponses, ListMyOauthConsentsData, ListMyOauthConsentsResponses, ListMySessionsData, ListMySessionsResponses, ListOauthAccountsData, ListOauthAccountsResponses, ListOauthClientsData, ListOauthClientsResponses, ListOauthProvidersData, ListOauthProvidersResponses, ListUsersData, ListUsersErrors, ListUsersResponses, LoginData, LoginErrors, LoginOauthData, LoginOauthErrors, LoginOauthResponses, LoginResponses, LogoutAllData, LogoutAllResponses, LogoutData, LogoutErrors, LogoutResponses, OauthProviderAuthorizeData, OauthProviderAuthorizeErrors, OauthProviderAuthorizeResponses, OauthProviderConsentData, OauthProviderConsentErrors, OauthProviderConsentResponses, OauthProviderIntrospectData, OauthProviderIntrospectErrors, OauthProviderIntrospectResponses, OauthProviderRevokeData, OauthProviderRevokeErrors, OauthProviderRevokeResponses, OauthProviderTokenData, OauthProviderTokenErrors, OauthProviderTokenResponses, RefreshTokenData, RefreshTokenErrors, RefreshTokenResponses, RegisterData, RegisterErrors, RegisterOauthClientData, RegisterOauthClientErrors, RegisterOauthClientResponses, RegisterResponses, RequestPasswordResetData, RequestPasswordResetErrors, RequestPasswordResetResponses, RevokeMyOauthConsentData, RevokeMyOauthConsentErrors, RevokeMyOauthConsentResponses, RevokeSessionData, RevokeSessionErrors, RevokeSessionResponses, RootGetData, RootGetResponses, SendTestEventData, SendTestEventErrors, SendTestEventResponses, StartOauthLinkData, StartOauthLinkErrors, StartOauthLinkResponses, StreamProjectEventsData, StreamProjectEventsErrors, StreamProjectEventsResponses, UnlinkOauthAccountData, UnlinkOauthAccountErrors, UnlinkOauthAccountResponses, UpdateCurrentUserData, UpdateCurrentUserErrors, UpdateCurrentUserResponses, UpdateOrganizationData, UpdateOrganizationErrors, UpdateOrganizationResponses, UpdateUserData, UpdateUserErrors, UpdateUserResponses } from './types.gen';
export type Options<TData extends TDataShape = TDataShape, ThrowOnError extends boolean = boolean> = Options2<TData, ThrowOnError> & {
/**
@@ -1288,6 +1288,74 @@ export const getOrganizationMembers = <ThrowOnError extends boolean = false>(opt
});
};
/**
* Stream Project Events
*
* Stream real-time events for a project via Server-Sent Events (SSE).
*
* **Authentication**: Required (Bearer token)
* **Authorization**: Must have access to the project
*
* **SSE Event Format**:
* ```
* event: agent.status_changed
* id: 550e8400-e29b-41d4-a716-446655440000
* data: {"id": "...", "type": "agent.status_changed", "project_id": "...", ...}
*
* : keepalive
*
* event: issue.created
* id: 550e8400-e29b-41d4-a716-446655440001
* data: {...}
* ```
*
* **Reconnection**: Include the `Last-Event-ID` header with the last received
* event ID to resume from where you left off.
*
* **Keepalive**: The server sends a comment (`: keepalive`) every 30 seconds
* to keep the connection alive.
*
* **Rate Limit**: 10 connections/minute per IP
*/
export const streamProjectEvents = <ThrowOnError extends boolean = false>(options: Options<StreamProjectEventsData, ThrowOnError>) => {
return (options.client ?? client).sse.get<StreamProjectEventsResponses, StreamProjectEventsErrors, ThrowOnError>({
responseType: 'text',
security: [
{
scheme: 'bearer',
type: 'http'
}
],
url: '/api/v1/projects/{project_id}/events/stream',
...options
});
};
/**
* Send Test Event (Development Only)
*
* Send a test event to a project's event stream. This endpoint is
* intended for development and testing purposes.
*
* **Authentication**: Required (Bearer token)
* **Authorization**: Must have access to the project
*
* **Note**: This endpoint should be disabled or restricted in production.
*/
export const sendTestEvent = <ThrowOnError extends boolean = false>(options: Options<SendTestEventData, ThrowOnError>) => {
return (options.client ?? client).post<SendTestEventResponses, SendTestEventErrors, ThrowOnError>({
responseType: 'json',
security: [
{
scheme: 'bearer',
type: 'http'
}
],
url: '/api/v1/projects/{project_id}/events/test',
...options
});
};
/**
* OAuth Server Metadata
*

View File

@@ -3186,6 +3186,94 @@ export type GetOrganizationMembersResponses = {
export type GetOrganizationMembersResponse = GetOrganizationMembersResponses[keyof GetOrganizationMembersResponses];
export type StreamProjectEventsData = {
body?: never;
headers?: {
/**
* Last-Event-Id
*/
'Last-Event-ID'?: string | null;
};
path: {
/**
* Project Id
*/
project_id: string;
};
query?: never;
url: '/api/v1/projects/{project_id}/events/stream';
};
export type StreamProjectEventsErrors = {
/**
* Not authenticated
*/
401: unknown;
/**
* Not authorized to access this project
*/
403: unknown;
/**
* Project not found
*/
404: unknown;
/**
* Validation Error
*/
422: HttpValidationError;
};
export type StreamProjectEventsError = StreamProjectEventsErrors[keyof StreamProjectEventsErrors];
export type StreamProjectEventsResponses = {
/**
* SSE stream established
*/
200: unknown;
};
export type SendTestEventData = {
body?: never;
path: {
/**
* Project Id
*/
project_id: string;
};
query?: never;
url: '/api/v1/projects/{project_id}/events/test';
};
export type SendTestEventErrors = {
/**
* Not authenticated
*/
401: unknown;
/**
* Not authorized to access this project
*/
403: unknown;
/**
* Validation Error
*/
422: HttpValidationError;
};
export type SendTestEventError = SendTestEventErrors[keyof SendTestEventErrors];
export type SendTestEventResponses = {
/**
* Response Send Test Event
*
* Test event sent
*/
200: {
[key: string]: unknown;
};
};
export type SendTestEventResponse = SendTestEventResponses[keyof SendTestEventResponses];
export type GetOauthServerMetadataData = {
body?: never;
path?: never;

View File

@@ -121,7 +121,7 @@ export function useLogin(onSuccess?: () => void) {
const { access_token, refresh_token, user, expires_in } = data;
// Update auth store with user and tokens
await setAuth(user as User, access_token, refresh_token || '', expires_in);
await setAuth(user as User, access_token, refresh_token || '', expires_in ?? undefined);
// Invalidate and refetch user data
queryClient.invalidateQueries({ queryKey: authKeys.all });
@@ -194,7 +194,7 @@ export function useRegister(onSuccess?: () => void) {
const { access_token, refresh_token, user, expires_in } = data;
// Update auth store with user and tokens (auto-login)
await setAuth(user as User, access_token, refresh_token || '', expires_in);
await setAuth(user as User, access_token, refresh_token || '', expires_in ?? undefined);
// Invalidate and refetch user data
queryClient.invalidateQueries({ queryKey: authKeys.all });

View File

@@ -7,21 +7,15 @@
* @module lib/api/types
*/
import type { Token, UserResponse } from './generated/types.gen';
import type { Token } from './generated/types.gen';
/**
* Extended Token Response
* Token with User Response
*
* The actual backend response includes additional fields not captured in OpenAPI spec:
* - user: UserResponse object
* - expires_in: Token expiration in seconds
*
* TODO: Update backend OpenAPI spec to include these fields
* Alias for Token type which now includes user and expires_in fields.
* Kept for backwards compatibility with existing type guards.
*/
export interface TokenWithUser extends Token {
user: UserResponse;
expires_in?: number;
}
export type TokenWithUser = Token;
/**
* Success Response (for operations that return success messages)