Files
registry/ts_web/appstate.ts

688 lines
22 KiB
TypeScript
Raw Normal View History

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<ILoginState>(
'login',
{
identity: null,
isLoggedIn: false,
},
'persistent',
);
export const organizationsStatePart = await appState.getStatePart<IOrganizationsState>(
'organizations',
{
organizations: [],
currentOrg: null,
repositories: [],
members: [],
},
'soft',
);
export const packagesStatePart = await appState.getStatePart<IPackagesState>(
'packages',
{
packages: [],
currentPackage: null,
versions: [],
total: 0,
query: '',
protocolFilter: '',
},
'soft',
);
export const tokensStatePart = await appState.getStatePart<ITokensState>(
'tokens',
{ tokens: [] },
'soft',
);
export const settingsStatePart = await appState.getStatePart<ISettingsState>(
'settings',
{ user: null, sessions: [] },
'soft',
);
export const adminStatePart = await appState.getStatePart<IAdminState>(
'admin',
{ providers: [], platformSettings: null },
'soft',
);
export const uiStatePart = await appState.getStatePart<IUiState>(
'ui',
{ activeView: 'dashboard' },
);
// ============================================================================
// Helpers
// ============================================================================
const getActionContext = () => ({
identity: loginStatePart.getState().identity,
});
function createTypedRequest<T extends plugins.domtools.plugins.typedrequest.ITypedRequest>(
method: string,
) {
return new plugins.domtools.plugins.typedrequest.TypedRequest<T>('/typedrequest', method);
}
// ============================================================================
// Auth Actions
// ============================================================================
export const loginAction = loginStatePart.createAction<{
email: string;
password: string;
}>(async (statePartArg, dataArg) => {
try {
const typedRequest = createTypedRequest<interfaces.requests.IReq_Login>('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<interfaces.requests.IReq_Logout>('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<interfaces.requests.IReq_RefreshToken>('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<interfaces.requests.IReq_GetMe>('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<interfaces.requests.IReq_GetOrganizations>(
'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<interfaces.requests.IReq_GetOrganization>(
'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<interfaces.requests.IReq_CreateOrganization>(
'createOrganization',
);
await typedRequest.fire({
identity: context.identity,
name: dataArg.name,
displayName: dataArg.displayName,
description: dataArg.description,
});
// Re-fetch list
const listReq = createTypedRequest<interfaces.requests.IReq_GetOrganizations>(
'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<interfaces.requests.IReq_UpdateOrganization>(
'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<interfaces.requests.IReq_DeleteOrganization>(
'deleteOrganization',
);
await typedRequest.fire({
identity: context.identity,
organizationId: dataArg.organizationId,
});
const listReq = createTypedRequest<interfaces.requests.IReq_GetOrganizations>(
'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<interfaces.requests.IReq_GetRepositories>(
'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<interfaces.requests.IReq_GetOrganizationMembers>(
'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<interfaces.requests.IReq_SearchPackages>(
'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<interfaces.requests.IReq_GetPackage>('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<interfaces.requests.IReq_GetPackageVersions>(
'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<interfaces.requests.IReq_DeletePackage>(
'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<interfaces.requests.IReq_GetTokens>('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<interfaces.requests.IReq_CreateToken>('createToken');
await typedRequest.fire({
identity: context.identity,
...dataArg,
});
// Re-fetch
const listReq = createTypedRequest<interfaces.requests.IReq_GetTokens>('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<interfaces.requests.IReq_RevokeToken>('revokeToken');
await typedRequest.fire({
identity: context.identity,
tokenId: dataArg.tokenId,
});
const listReq = createTypedRequest<interfaces.requests.IReq_GetTokens>('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<interfaces.requests.IReq_GetUserSessions>(
'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<interfaces.requests.IReq_ChangePassword>(
'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<interfaces.requests.IReq_UpdateUser>('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<interfaces.requests.IReq_RevokeSession>(
'revokeSession',
);
await typedRequest.fire({
identity: context.identity,
sessionId: dataArg.sessionId,
});
// Re-fetch sessions
const listReq = createTypedRequest<interfaces.requests.IReq_GetUserSessions>('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<interfaces.requests.IReq_GetAdminProviders>(
'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<interfaces.requests.IReq_GetPlatformSettings>(
'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<interfaces.data.IPlatformAuthSettings>;
}>(async (statePartArg, dataArg) => {
const context = getActionContext();
if (!context.identity) return statePartArg.getState();
try {
const typedRequest = createTypedRequest<interfaces.requests.IReq_UpdatePlatformSettings>(
'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<interfaces.requests.IReq_DeleteAdminProvider>(
'deleteAdminProvider',
);
await typedRequest.fire({
identity: context.identity,
providerId: dataArg.providerId,
});
const listReq = createTypedRequest<interfaces.requests.IReq_GetAdminProviders>(
'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<interfaces.requests.IReq_TestAdminProvider>(
'testAdminProvider',
);
await typedRequest.fire({
identity: context.identity,
providerId: dataArg.providerId,
});
// Re-fetch to get updated test results
const listReq = createTypedRequest<interfaces.requests.IReq_GetAdminProviders>(
'getAdminProviders',
);
const listResp = await listReq.fire({ identity: context.identity });
return { ...statePartArg.getState(), providers: listResp.providers };
} catch {
return statePartArg.getState();
}
});