import * as plugins from './plugins.js'; import * as interfaces from '../ts_interfaces/index.js'; // ============================================================================ // Smartstate instance // ============================================================================ export const appState = new plugins.domtools.plugins.smartstate.Smartstate(); // ============================================================================ // State Part Interfaces // ============================================================================ export interface ILoginState { identity: interfaces.data.IIdentity | null; isLoggedIn: boolean; } export interface IOrganizationsState { organizations: interfaces.data.IOrganization[]; currentOrg: interfaces.data.IOrganizationDetail | null; repositories: interfaces.data.IRepository[]; members: interfaces.data.IOrganizationMember[]; } export interface IPackagesState { packages: interfaces.data.IPackage[]; currentPackage: interfaces.data.IPackageDetail | null; versions: interfaces.data.IPackageVersion[]; total: number; query: string; protocolFilter: string; } export interface ITokensState { tokens: interfaces.data.IToken[]; } export interface ISettingsState { user: interfaces.data.IUser | null; sessions: interfaces.data.ISession[]; } export interface IAdminState { providers: interfaces.data.IAuthProvider[]; platformSettings: interfaces.data.IPlatformSettings | null; } export interface IUiState { activeView: string; activeEntityId?: string; } // ============================================================================ // State Parts // ============================================================================ export const loginStatePart = await appState.getStatePart( 'login', { identity: null, isLoggedIn: false, }, 'persistent', ); export const organizationsStatePart = await appState.getStatePart( 'organizations', { organizations: [], currentOrg: null, repositories: [], members: [], }, 'soft', ); export const packagesStatePart = await appState.getStatePart( 'packages', { packages: [], currentPackage: null, versions: [], total: 0, query: '', protocolFilter: '', }, 'soft', ); export const tokensStatePart = await appState.getStatePart( 'tokens', { tokens: [] }, 'soft', ); export const settingsStatePart = await appState.getStatePart( 'settings', { user: null, sessions: [] }, 'soft', ); export const adminStatePart = await appState.getStatePart( 'admin', { providers: [], platformSettings: null }, 'soft', ); export const uiStatePart = await appState.getStatePart( 'ui', { activeView: 'dashboard' }, ); // ============================================================================ // Helpers // ============================================================================ const getActionContext = () => ({ identity: loginStatePart.getState().identity, }); function createTypedRequest( method: string, ) { return new plugins.domtools.plugins.typedrequest.TypedRequest('/typedrequest', method); } // ============================================================================ // Auth Actions // ============================================================================ export const loginAction = loginStatePart.createAction<{ email: string; password: string; }>(async (statePartArg, dataArg) => { try { const typedRequest = createTypedRequest('login'); const response = await typedRequest.fire({ email: dataArg.email, password: dataArg.password, }); if (response.identity) { return { identity: response.identity, isLoggedIn: true }; } return { identity: null, isLoggedIn: false }; } catch (err) { console.error('Login failed:', err); return { identity: null, isLoggedIn: false }; } }); export const logoutAction = loginStatePart.createAction(async () => { const context = getActionContext(); if (context.identity) { try { const typedRequest = createTypedRequest('logout'); await typedRequest.fire({ identity: context.identity }); } catch { // Ignore logout errors } } return { identity: null, isLoggedIn: false }; }); export const refreshTokenAction = loginStatePart.createAction(async (statePartArg) => { const context = getActionContext(); if (!context.identity) return statePartArg.getState(); try { const typedRequest = createTypedRequest('refreshToken'); const response = await typedRequest.fire({ identity: context.identity }); return { identity: response.identity, isLoggedIn: true }; } catch { return { identity: null, isLoggedIn: false }; } }); export const fetchMeAction = settingsStatePart.createAction(async (statePartArg) => { const context = getActionContext(); if (!context.identity) return statePartArg.getState(); try { const typedRequest = createTypedRequest('getMe'); const response = await typedRequest.fire({ identity: context.identity }); return { ...statePartArg.getState(), user: response.user }; } catch { return statePartArg.getState(); } }); // Handle OAuth callback tokens export function handleOAuthCallback( accessToken: string, refreshToken: string, sessionId: string, ) { // Build a minimal identity from the callback tokens // The full identity will be populated when getMe is called loginStatePart.setState({ identity: { jwt: accessToken, refreshJwt: refreshToken, userId: '', email: '', username: '', displayName: '', isSystemAdmin: false, expiresAt: Date.now() + 900000, sessionId, }, isLoggedIn: true, }); } // ============================================================================ // Organization Actions // ============================================================================ export const fetchOrganizationsAction = organizationsStatePart.createAction( async (statePartArg) => { const context = getActionContext(); if (!context.identity) return statePartArg.getState(); try { const typedRequest = createTypedRequest( 'getOrganizations', ); const response = await typedRequest.fire({ identity: context.identity }); return { ...statePartArg.getState(), organizations: response.organizations }; } catch { return statePartArg.getState(); } }, ); export const fetchOrganizationAction = organizationsStatePart.createAction<{ organizationId: string; }>(async (statePartArg, dataArg) => { const context = getActionContext(); if (!context.identity) return statePartArg.getState(); try { const typedRequest = createTypedRequest( 'getOrganization', ); const response = await typedRequest.fire({ identity: context.identity, organizationId: dataArg.organizationId, }); return { ...statePartArg.getState(), currentOrg: response.organization }; } catch { return statePartArg.getState(); } }); export const createOrganizationAction = organizationsStatePart.createAction<{ name: string; displayName?: string; description?: string; }>(async (statePartArg, dataArg) => { const context = getActionContext(); if (!context.identity) return statePartArg.getState(); try { const typedRequest = createTypedRequest( 'createOrganization', ); await typedRequest.fire({ identity: context.identity, name: dataArg.name, displayName: dataArg.displayName, description: dataArg.description, }); // Re-fetch list const listReq = createTypedRequest( 'getOrganizations', ); const listResp = await listReq.fire({ identity: context.identity }); return { ...statePartArg.getState(), organizations: listResp.organizations }; } catch { return statePartArg.getState(); } }); export const updateOrganizationAction = organizationsStatePart.createAction<{ organizationId: string; displayName?: string; description?: string; website?: string; isPublic?: boolean; }>(async (statePartArg, dataArg) => { const context = getActionContext(); if (!context.identity) return statePartArg.getState(); try { const typedRequest = createTypedRequest( 'updateOrganization', ); const response = await typedRequest.fire({ identity: context.identity, ...dataArg, }); // Update the current org in state return { ...statePartArg.getState(), currentOrg: response.organization }; } catch { return statePartArg.getState(); } }); export const deleteOrganizationAction = organizationsStatePart.createAction<{ organizationId: string; }>(async (statePartArg, dataArg) => { const context = getActionContext(); if (!context.identity) return statePartArg.getState(); try { const typedRequest = createTypedRequest( 'deleteOrganization', ); await typedRequest.fire({ identity: context.identity, organizationId: dataArg.organizationId, }); const listReq = createTypedRequest( 'getOrganizations', ); const listResp = await listReq.fire({ identity: context.identity }); return { ...statePartArg.getState(), organizations: listResp.organizations, currentOrg: null, }; } catch { return statePartArg.getState(); } }); export const fetchRepositoriesAction = organizationsStatePart.createAction<{ organizationId: string; }>(async (statePartArg, dataArg) => { const context = getActionContext(); if (!context.identity) return statePartArg.getState(); try { const typedRequest = createTypedRequest( 'getRepositories', ); const response = await typedRequest.fire({ identity: context.identity, organizationId: dataArg.organizationId, }); return { ...statePartArg.getState(), repositories: response.repositories }; } catch { return statePartArg.getState(); } }); export const fetchMembersAction = organizationsStatePart.createAction<{ organizationId: string; }>(async (statePartArg, dataArg) => { const context = getActionContext(); if (!context.identity) return statePartArg.getState(); try { const typedRequest = createTypedRequest( 'getOrganizationMembers', ); const response = await typedRequest.fire({ identity: context.identity, organizationId: dataArg.organizationId, }); return { ...statePartArg.getState(), members: response.members }; } catch { return statePartArg.getState(); } }); // ============================================================================ // Package Actions // ============================================================================ export const searchPackagesAction = packagesStatePart.createAction<{ query?: string; protocol?: string; offset?: number; }>(async (statePartArg, dataArg) => { const context = getActionContext(); try { const typedRequest = createTypedRequest( 'searchPackages', ); const response = await typedRequest.fire({ identity: context.identity || undefined, query: dataArg.query, protocol: dataArg.protocol as interfaces.data.TRegistryProtocol | undefined, offset: dataArg.offset, limit: 50, }); return { ...statePartArg.getState(), packages: response.packages, total: response.total, query: dataArg.query || '', protocolFilter: dataArg.protocol || '', }; } catch { return statePartArg.getState(); } }); export const fetchPackageAction = packagesStatePart.createAction<{ packageId: string; }>(async (statePartArg, dataArg) => { const context = getActionContext(); try { const typedRequest = createTypedRequest('getPackage'); const response = await typedRequest.fire({ identity: context.identity || undefined, packageId: dataArg.packageId, }); return { ...statePartArg.getState(), currentPackage: response.package }; } catch { return statePartArg.getState(); } }); export const fetchPackageVersionsAction = packagesStatePart.createAction<{ packageId: string; }>(async (statePartArg, dataArg) => { const context = getActionContext(); try { const typedRequest = createTypedRequest( 'getPackageVersions', ); const response = await typedRequest.fire({ identity: context.identity || undefined, packageId: dataArg.packageId, }); return { ...statePartArg.getState(), versions: response.versions }; } catch { return statePartArg.getState(); } }); export const deletePackageAction = packagesStatePart.createAction<{ packageId: string; }>(async (statePartArg, dataArg) => { const context = getActionContext(); if (!context.identity) return statePartArg.getState(); try { const typedRequest = createTypedRequest( 'deletePackage', ); await typedRequest.fire({ identity: context.identity, packageId: dataArg.packageId }); return { ...statePartArg.getState(), currentPackage: null }; } catch { return statePartArg.getState(); } }); // ============================================================================ // Token Actions // ============================================================================ export const fetchTokensAction = tokensStatePart.createAction<{ organizationId?: string; }>(async (statePartArg, dataArg) => { const context = getActionContext(); if (!context.identity) return statePartArg.getState(); try { const typedRequest = createTypedRequest('getTokens'); const response = await typedRequest.fire({ identity: context.identity, organizationId: dataArg?.organizationId, }); return { tokens: response.tokens }; } catch { return statePartArg.getState(); } }); export const createTokenAction = tokensStatePart.createAction<{ name: string; protocols: interfaces.data.TRegistryProtocol[]; scopes: interfaces.data.ITokenScope[]; organizationId?: string; expiresInDays?: number; }>(async (statePartArg, dataArg) => { const context = getActionContext(); if (!context.identity) return statePartArg.getState(); try { const typedRequest = createTypedRequest('createToken'); await typedRequest.fire({ identity: context.identity, ...dataArg, }); // Re-fetch const listReq = createTypedRequest('getTokens'); const listResp = await listReq.fire({ identity: context.identity }); return { tokens: listResp.tokens }; } catch { return statePartArg.getState(); } }); export const revokeTokenAction = tokensStatePart.createAction<{ tokenId: string; }>(async (statePartArg, dataArg) => { const context = getActionContext(); if (!context.identity) return statePartArg.getState(); try { const typedRequest = createTypedRequest('revokeToken'); await typedRequest.fire({ identity: context.identity, tokenId: dataArg.tokenId, }); const listReq = createTypedRequest('getTokens'); const listResp = await listReq.fire({ identity: context.identity }); return { tokens: listResp.tokens }; } catch { return statePartArg.getState(); } }); // ============================================================================ // Settings Actions // ============================================================================ export const fetchUserSessionsAction = settingsStatePart.createAction(async (statePartArg) => { const context = getActionContext(); if (!context.identity) return statePartArg.getState(); try { const typedRequest = createTypedRequest( 'getUserSessions', ); const response = await typedRequest.fire({ identity: context.identity }); return { ...statePartArg.getState(), sessions: response.sessions }; } catch { return statePartArg.getState(); } }); export const changePasswordAction = settingsStatePart.createAction<{ currentPassword: string; newPassword: string; }>(async (statePartArg, dataArg) => { const context = getActionContext(); if (!context.identity) return statePartArg.getState(); const typedRequest = createTypedRequest( 'changePassword', ); await typedRequest.fire({ identity: context.identity, currentPassword: dataArg.currentPassword, newPassword: dataArg.newPassword, }); return statePartArg.getState(); }); export const updateProfileAction = settingsStatePart.createAction<{ displayName?: string; avatarUrl?: string; }>(async (statePartArg, dataArg) => { const context = getActionContext(); if (!context.identity) return statePartArg.getState(); try { const typedRequest = createTypedRequest('updateUser'); const response = await typedRequest.fire({ identity: context.identity, userId: context.identity.userId, ...dataArg, }); return { ...statePartArg.getState(), user: response.user }; } catch { return statePartArg.getState(); } }); export const revokeSessionAction = settingsStatePart.createAction<{ sessionId: string; }>(async (statePartArg, dataArg) => { const context = getActionContext(); if (!context.identity) return statePartArg.getState(); try { const typedRequest = createTypedRequest( 'revokeSession', ); await typedRequest.fire({ identity: context.identity, sessionId: dataArg.sessionId, }); // Re-fetch sessions const listReq = createTypedRequest('getUserSessions'); const listResp = await listReq.fire({ identity: context.identity }); return { ...statePartArg.getState(), sessions: listResp.sessions }; } catch { return statePartArg.getState(); } }); // ============================================================================ // Admin Actions // ============================================================================ export const fetchAdminProvidersAction = adminStatePart.createAction(async (statePartArg) => { const context = getActionContext(); if (!context.identity) return statePartArg.getState(); try { const typedRequest = createTypedRequest( 'getAdminProviders', ); const response = await typedRequest.fire({ identity: context.identity }); return { ...statePartArg.getState(), providers: response.providers }; } catch { return statePartArg.getState(); } }); export const fetchPlatformSettingsAction = adminStatePart.createAction(async (statePartArg) => { const context = getActionContext(); if (!context.identity) return statePartArg.getState(); try { const typedRequest = createTypedRequest( 'getPlatformSettings', ); const response = await typedRequest.fire({ identity: context.identity }); return { ...statePartArg.getState(), platformSettings: response.settings }; } catch { return statePartArg.getState(); } }); export const updatePlatformSettingsAction = adminStatePart.createAction<{ auth?: Partial; }>(async (statePartArg, dataArg) => { const context = getActionContext(); if (!context.identity) return statePartArg.getState(); try { const typedRequest = createTypedRequest( 'updatePlatformSettings', ); const response = await typedRequest.fire({ identity: context.identity, auth: dataArg.auth, }); return { ...statePartArg.getState(), platformSettings: response.settings }; } catch { return statePartArg.getState(); } }); export const deleteAdminProviderAction = adminStatePart.createAction<{ providerId: string; }>(async (statePartArg, dataArg) => { const context = getActionContext(); if (!context.identity) return statePartArg.getState(); try { const typedRequest = createTypedRequest( 'deleteAdminProvider', ); await typedRequest.fire({ identity: context.identity, providerId: dataArg.providerId, }); const listReq = createTypedRequest( 'getAdminProviders', ); const listResp = await listReq.fire({ identity: context.identity }); return { ...statePartArg.getState(), providers: listResp.providers }; } catch { return statePartArg.getState(); } }); export const testAdminProviderAction = adminStatePart.createAction<{ providerId: string; }>(async (statePartArg, dataArg) => { const context = getActionContext(); if (!context.identity) return statePartArg.getState(); try { const typedRequest = createTypedRequest( 'testAdminProvider', ); await typedRequest.fire({ identity: context.identity, providerId: dataArg.providerId, }); // Re-fetch to get updated test results const listReq = createTypedRequest( 'getAdminProviders', ); const listResp = await listReq.fire({ identity: context.identity }); return { ...statePartArg.getState(), providers: listResp.providers }; } catch { return statePartArg.getState(); } });