Start working towards a consistent model/transformer layout

This commit is contained in:
Dane Everitt 2022-02-27 11:26:13 -05:00
parent 9f934b5ab8
commit a00fee5516
No known key found for this signature in database
GPG key ID: EEA66103B3D71F53
17 changed files with 303 additions and 267 deletions

View file

@ -150,6 +150,7 @@
"svg-url-loader": "^7.1.1", "svg-url-loader": "^7.1.1",
"tailwindcss": "^3.0.23", "tailwindcss": "^3.0.23",
"terser-webpack-plugin": "^4.2.3", "terser-webpack-plugin": "^4.2.3",
"ts-essentials": "^9.1.2",
"twin.macro": "^2.8.2", "twin.macro": "^2.8.2",
"typescript": "^4.4.4", "typescript": "^4.4.4",
"webpack": "^4.46.0", "webpack": "^4.46.0",

View file

@ -1,7 +1,7 @@
import { Model, UUID, WithRelationships, withRelationships } from '@/api/admin/index'; import { Model, UUID, WithRelationships, withRelationships } from '@/api/admin/index';
import { Nest } from '@/api/admin/nest'; import { Nest } from '@/api/admin/nest';
import http, { QueryBuilderParams, withQueryBuilderParams } from '@/api/http'; import http, { QueryBuilderParams, withQueryBuilderParams } from '@/api/http';
import { AdminTransformers } from '@/api/admin/transformers'; import Transformers from '@definitions/admin/transformers';
import { AxiosError } from 'axios'; import { AxiosError } from 'axios';
import { useRouteMatch } from 'react-router-dom'; import { useRouteMatch } from 'react-router-dom';
import useSWR, { SWRResponse } from 'swr'; import useSWR, { SWRResponse } from 'swr';
@ -64,7 +64,7 @@ export const getEgg = async (id: number | string): Promise<LoadedEgg> => {
}, },
}); });
return withRelationships(AdminTransformers.toEgg(data), 'nest', 'variables'); return withRelationships(Transformers.toEgg(data), 'nest', 'variables');
}; };
export const searchEggs = async (nestId: number, params: QueryBuilderParams<'name'>): Promise<WithRelationships<Egg, 'variables'>[]> => { export const searchEggs = async (nestId: number, params: QueryBuilderParams<'name'>): Promise<WithRelationships<Egg, 'variables'>[]> => {
@ -75,7 +75,7 @@ export const searchEggs = async (nestId: number, params: QueryBuilderParams<'nam
}, },
}); });
return data.data.map(AdminTransformers.toEgg); return data.data.map(Transformers.toEgg);
}; };
export const exportEgg = async (eggId: number): Promise<Record<string, any>> => { export const exportEgg = async (eggId: number): Promise<Record<string, any>> => {

View file

@ -1,6 +1,6 @@
import http from '@/api/http'; import http from '@/api/http';
import { EggVariable } from '@/api/admin/egg'; import { EggVariable } from '@/api/admin/egg';
import { AdminTransformers } from '@/api/admin/transformers'; import Transformers from '@definitions/admin/transformers';
export type CreateEggVariable = Omit<EggVariable, 'id' | 'eggId' | 'createdAt' | 'updatedAt' | 'relationships'>; export type CreateEggVariable = Omit<EggVariable, 'id' | 'eggId' | 'createdAt' | 'updatedAt' | 'relationships'>;
@ -18,5 +18,5 @@ export default async (eggId: number, variable: CreateEggVariable): Promise<EggVa
}, },
); );
return AdminTransformers.toEggVariable(data); return Transformers.toEggVariable(data);
}; };

View file

@ -1,6 +1,6 @@
import http from '@/api/http'; import http from '@/api/http';
import { EggVariable } from '@/api/admin/egg'; import { EggVariable } from '@/api/admin/egg';
import { AdminTransformers } from '@/api/admin/transformers'; import Transformers from '@definitions/admin/transformers';
export default async (eggId: number, variables: Omit<EggVariable, 'eggId' | 'createdAt' | 'updatedAt'>[]): Promise<EggVariable[]> => { export default async (eggId: number, variables: Omit<EggVariable, 'eggId' | 'createdAt' | 'updatedAt'>[]): Promise<EggVariable[]> => {
const { data } = await http.patch( const { data } = await http.patch(
@ -17,5 +17,5 @@ export default async (eggId: number, variables: Omit<EggVariable, 'eggId' | 'cre
})), })),
); );
return data.data.map(AdminTransformers.toEggVariable); return data.data.map(Transformers.toEggVariable);
}; };

View file

@ -1,7 +1,7 @@
import { Model, UUID } from '@/api/admin/index'; import { Model, UUID } from '@/api/admin/index';
import { Egg } from '@/api/admin/egg'; import { Egg } from '@/api/admin/egg';
import http, { QueryBuilderParams, withQueryBuilderParams } from '@/api/http'; import http, { QueryBuilderParams, withQueryBuilderParams } from '@/api/http';
import { AdminTransformers } from '@/api/admin/transformers'; import Transformers from '@definitions/admin/transformers';
export interface Nest extends Model { export interface Nest extends Model {
id: number; id: number;
@ -21,5 +21,5 @@ export const searchNests = async (params: QueryBuilderParams<'name'>): Promise<N
params: withQueryBuilderParams(params), params: withQueryBuilderParams(params),
}); });
return data.data.map(AdminTransformers.toNest); return data.data.map(Transformers.toNest);
}; };

View file

@ -1,7 +1,7 @@
import { Model, UUID, WithRelationships, withRelationships } from '@/api/admin/index'; import { Model, UUID, WithRelationships, withRelationships } from '@/api/admin/index';
import { Location } from '@/api/admin/location'; import { Location } from '@/api/admin/location';
import http, { QueryBuilderParams, withQueryBuilderParams } from '@/api/http'; import http, { QueryBuilderParams, withQueryBuilderParams } from '@/api/http';
import { AdminTransformers } from '@/api/admin/transformers'; import Transformers from '@definitions/admin/transformers';
import { Server } from '@/api/admin/server'; import { Server } from '@/api/admin/server';
interface NodePorts { interface NodePorts {
@ -64,7 +64,7 @@ export const getNode = async (id: string | number): Promise<WithRelationships<No
}, },
}); });
return withRelationships(AdminTransformers.toNode(data.data), 'location'); return withRelationships(Transformers.toNode(data.data), 'location');
}; };
export const searchNodes = async (params: QueryBuilderParams<'name'>): Promise<Node[]> => { export const searchNodes = async (params: QueryBuilderParams<'name'>): Promise<Node[]> => {
@ -72,7 +72,7 @@ export const searchNodes = async (params: QueryBuilderParams<'name'>): Promise<N
params: withQueryBuilderParams(params), params: withQueryBuilderParams(params),
}); });
return data.data.map(AdminTransformers.toNode); return data.data.map(Transformers.toNode);
}; };
export const getAllocations = async (id: string | number, params?: QueryBuilderParams<'ip' | 'server_id'>): Promise<Allocation[]> => { export const getAllocations = async (id: string | number, params?: QueryBuilderParams<'ip' | 'server_id'>): Promise<Allocation[]> => {
@ -80,5 +80,5 @@ export const getAllocations = async (id: string | number, params?: QueryBuilderP
params: withQueryBuilderParams(params), params: withQueryBuilderParams(params),
}); });
return data.data.map(AdminTransformers.toAllocation); return data.data.map(Transformers.toAllocation);
}; };

View file

@ -3,9 +3,9 @@ import { AxiosError } from 'axios';
import { useRouteMatch } from 'react-router-dom'; import { useRouteMatch } from 'react-router-dom';
import http from '@/api/http'; import http from '@/api/http';
import { Model, UUID, withRelationships, WithRelationships } from '@/api/admin/index'; import { Model, UUID, withRelationships, WithRelationships } from '@/api/admin/index';
import { AdminTransformers } from '@/api/admin/transformers'; import Transformers from '@definitions/admin/transformers';
import { Allocation, Node } from '@/api/admin/node'; import { Allocation, Node } from '@/api/admin/node';
import { User } from '@/api/admin/user'; import { User } from '@definitions/admin/models';
import { Egg, EggVariable } from '@/api/admin/egg'; import { Egg, EggVariable } from '@/api/admin/egg';
import { Nest } from '@/api/admin/nest'; import { Nest } from '@/api/admin/nest';
@ -83,7 +83,7 @@ export const getServer = async (id: number | string): Promise<LoadedServer> => {
}, },
}); });
return withRelationships(AdminTransformers.toServer(data), 'allocations', 'user', 'node', 'variables'); return withRelationships(Transformers.toServer(data), 'allocations', 'user', 'node', 'variables');
}; };
/** /**

View file

@ -1,212 +0,0 @@
/* eslint-disable camelcase */
import { Allocation, Node } from '@/api/admin/node';
import { Server, ServerVariable } from '@/api/admin/server';
import { FractalResponseData, FractalResponseList } from '@/api/http';
import { User, UserRole } from '@/api/admin/user';
import { Location } from '@/api/admin/location';
import { Egg, EggVariable } from '@/api/admin/egg';
import { Nest } from '@/api/admin/nest';
const isList = (data: FractalResponseList | FractalResponseData): data is FractalResponseList => data.object === 'list';
function transform<T, M = undefined> (data: undefined, transformer: (callback: FractalResponseData) => T, missing?: M): undefined;
function transform<T, M> (data: FractalResponseData | undefined, transformer: (callback: FractalResponseData) => T, missing?: M): T | M | undefined;
function transform<T, M> (data: FractalResponseList | undefined, transformer: (callback: FractalResponseData) => T, missing?: M): T[] | undefined;
function transform<T> (data: FractalResponseData | FractalResponseList | undefined, transformer: (callback: FractalResponseData) => T, missing = undefined) {
if (data === undefined) return undefined;
if (isList(data)) {
return data.data.map(transformer);
}
return !data ? missing : transformer(data);
}
export class AdminTransformers {
static toServer = ({ attributes }: FractalResponseData): Server => {
const { oom_disabled, ...limits } = attributes.limits;
const { allocations, egg, nest, node, user, variables } = attributes.relationships || {};
return {
id: attributes.id,
uuid: attributes.uuid,
externalId: attributes.external_id,
identifier: attributes.identifier,
name: attributes.name,
description: attributes.description,
status: attributes.status,
userId: attributes.owner_id,
nodeId: attributes.node_id,
allocationId: attributes.allocation_id,
eggId: attributes.egg_id,
nestId: attributes.nest_id,
limits: { ...limits, oomDisabled: oom_disabled },
featureLimits: attributes.feature_limits,
container: attributes.container,
createdAt: new Date(attributes.created_at),
updatedAt: new Date(attributes.updated_at),
relationships: {
allocations: transform(allocations as FractalResponseList | undefined, this.toAllocation),
nest: transform(nest as FractalResponseData | undefined, this.toNest),
egg: transform(egg as FractalResponseData | undefined, this.toEgg),
node: transform(node as FractalResponseData | undefined, this.toNode),
user: transform(user as FractalResponseData | undefined, this.toUser),
variables: transform(variables as FractalResponseList | undefined, this.toServerEggVariable),
},
};
};
static toNode = ({ attributes }: FractalResponseData): Node => {
return {
id: attributes.id,
uuid: attributes.uuid,
isPublic: attributes.public,
locationId: attributes.location_id,
databaseHostId: attributes.database_host_id,
name: attributes.name,
description: attributes.description,
fqdn: attributes.fqdn,
ports: {
http: {
public: attributes.publicPortHttp,
listen: attributes.listenPortHttp,
},
sftp: {
public: attributes.publicPortSftp,
listen: attributes.listenPortSftp,
},
},
scheme: attributes.scheme,
isBehindProxy: attributes.behindProxy,
isMaintenanceMode: attributes.maintenance_mode,
memory: attributes.memory,
memoryOverallocate: attributes.memory_overallocate,
disk: attributes.disk,
diskOverallocate: attributes.disk_overallocate,
uploadSize: attributes.upload_size,
daemonBase: attributes.daemonBase,
createdAt: new Date(attributes.created_at),
updatedAt: new Date(attributes.updated_at),
relationships: {
location: transform(attributes.relationships?.location as FractalResponseData, this.toLocation),
},
};
};
static toUserRole = ({ attributes }: FractalResponseData): UserRole => ({
id: attributes.id,
name: attributes.name,
description: attributes.description,
relationships: {},
});
static toUser = ({ attributes }: FractalResponseData): User => {
return {
id: attributes.id,
uuid: attributes.uuid,
externalId: attributes.external_id,
username: attributes.username,
email: attributes.email,
language: attributes.language,
adminRoleId: attributes.adminRoleId || null,
roleName: attributes.role_name,
isRootAdmin: attributes.root_admin,
isUsingTwoFactor: attributes['2fa'] || false,
avatarUrl: attributes.avatar_url,
createdAt: new Date(attributes.created_at),
updatedAt: new Date(attributes.updated_at),
relationships: {
role: transform(attributes.relationships?.role as FractalResponseData, this.toUserRole) || null,
},
};
};
static toLocation = ({ attributes }: FractalResponseData): Location => ({
id: attributes.id,
short: attributes.short,
long: attributes.long,
createdAt: new Date(attributes.created_at),
updatedAt: new Date(attributes.updated_at),
relationships: {
nodes: transform(attributes.relationships?.node as FractalResponseList, this.toNode),
},
});
static toEgg = ({ attributes }: FractalResponseData): Egg => ({
id: attributes.id,
uuid: attributes.uuid,
nestId: attributes.nest_id,
author: attributes.author,
name: attributes.name,
description: attributes.description,
features: attributes.features,
dockerImages: attributes.docker_images,
configFiles: attributes.config?.files,
configStartup: attributes.config?.startup,
configStop: attributes.config?.stop,
configFrom: attributes.config?.extends,
startup: attributes.startup,
copyScriptFrom: attributes.copy_script_from,
scriptContainer: attributes.script?.container,
scriptEntry: attributes.script?.entry,
scriptIsPrivileged: attributes.script?.privileged,
scriptInstall: attributes.script?.install,
createdAt: new Date(attributes.created_at),
updatedAt: new Date(attributes.updated_at),
relationships: {
nest: transform(attributes.relationships?.nest as FractalResponseData, this.toNest),
variables: transform(attributes.relationships?.variables as FractalResponseList, this.toEggVariable),
},
});
static toEggVariable = ({ attributes }: FractalResponseData): EggVariable => ({
id: attributes.id,
eggId: attributes.egg_id,
name: attributes.name,
description: attributes.description,
environmentVariable: attributes.env_variable,
defaultValue: attributes.default_value,
isUserViewable: attributes.user_viewable,
isUserEditable: attributes.user_editable,
// isRequired: attributes.required,
rules: attributes.rules,
createdAt: new Date(attributes.created_at),
updatedAt: new Date(attributes.updated_at),
relationships: {},
});
static toServerEggVariable = (data: FractalResponseData): ServerVariable => ({
...this.toEggVariable(data),
serverValue: data.attributes.server_value,
});
static toAllocation = ({ attributes }: FractalResponseData): Allocation => ({
id: attributes.id,
ip: attributes.ip,
port: attributes.port,
alias: attributes.alias || null,
isAssigned: attributes.assigned,
relationships: {
node: transform(attributes.relationships?.node as FractalResponseData, this.toNode),
server: transform(attributes.relationships?.server as FractalResponseData, this.toServer),
},
getDisplayText (): string {
const raw = `${this.ip}:${this.port}`;
return !this.alias ? raw : `${this.alias} (${raw})`;
},
});
static toNest = ({ attributes }: FractalResponseData): Nest => ({
id: attributes.id,
uuid: attributes.uuid,
author: attributes.author,
name: attributes.name,
description: attributes.description,
createdAt: new Date(attributes.created_at),
updatedAt: new Date(attributes.updated_at),
relationships: {
eggs: transform(attributes.relationships?.eggs as FractalResponseList, this.toEgg),
},
});
}

View file

@ -1,38 +1,11 @@
import { Model, UUID } from '@/api/admin/index';
import { Server } from '@/api/admin/server';
import http, { QueryBuilderParams, withQueryBuilderParams } from '@/api/http'; import http, { QueryBuilderParams, withQueryBuilderParams } from '@/api/http';
import { AdminTransformers } from '@/api/admin/transformers'; import Transformers from '@definitions/admin/transformers';
import { User } from '@definitions/admin/models';
export interface User extends Model {
id: number;
uuid: UUID;
externalId: string;
username: string;
email: string;
language: string;
adminRoleId: number | null;
roleName: string;
isRootAdmin: boolean;
isUsingTwoFactor: boolean;
avatarUrl: string;
createdAt: Date;
updatedAt: Date;
relationships: {
role: UserRole | null;
servers?: Server[];
};
}
export interface UserRole extends Model {
id: string;
name: string;
description: string;
}
export const getUser = async (id: string | number): Promise<User> => { export const getUser = async (id: string | number): Promise<User> => {
const { data } = await http.get(`/api/application/users/${id}`); const { data } = await http.get(`/api/application/users/${id}`);
return AdminTransformers.toUser(data.data); return Transformers.toUser(data.data);
}; };
export const searchUserAccounts = async (params: QueryBuilderParams<'username' | 'email'>): Promise<User[]> => { export const searchUserAccounts = async (params: QueryBuilderParams<'username' | 'email'>): Promise<User[]> => {
@ -40,5 +13,5 @@ export const searchUserAccounts = async (params: QueryBuilderParams<'username' |
params: withQueryBuilderParams(params), params: withQueryBuilderParams(params),
}); });
return data.data.map(AdminTransformers.toUser); return data.data.map(Transformers.toUser);
}; };

View file

@ -0,0 +1,48 @@
import { Model as BaseModel, UUID } from '@/api/definitions';
import { Server } from '@/api/admin/server';
import { MarkRequired } from 'ts-essentials';
interface Model extends BaseModel {
relationships: Record<string, unknown>;
}
/**
* Allows a model to have optional relationships that are marked as being
* present in a given pathway. This allows different API calls to specify the
* "completeness" of a response object without having to make every API return
* the same information, or every piece of logic do explicit null checking.
*
* Example:
* >> const user: WithLoadedRelations<User, 'servers'> = {};
* >> // "user.servers" is no longer potentially undefined.
*/
type WithLoadedRelations<M extends Model, R extends keyof M['relationships']> = M & {
relationships: MarkRequired<M['relationships'], R>;
};
interface User extends Model {
id: number;
uuid: UUID;
externalId: string;
username: string;
email: string;
language: string;
adminRoleId: number | null;
roleName: string;
isRootAdmin: boolean;
isUsingTwoFactor: boolean;
avatarUrl: string;
createdAt: Date;
updatedAt: Date;
relationships: {
role: UserRole | null;
// TODO: just use an API call, this is probably a bad idea for performance.
servers?: Server[];
};
}
interface UserRole extends Model {
id: string;
name: string;
description: string;
}

View file

@ -0,0 +1,212 @@
/* eslint-disable camelcase */
import { Allocation, Node } from '@/api/admin/node';
import { Server, ServerVariable } from '@/api/admin/server';
import { FractalResponseData, FractalResponseList } from '@/api/http';
import * as Models from '@definitions/admin/models';
import { Location } from '@/api/admin/location';
import { Egg, EggVariable } from '@/api/admin/egg';
import { Nest } from '@/api/admin/nest';
const isList = (data: FractalResponseList | FractalResponseData): data is FractalResponseList => data.object === 'list';
function transform<T, M = undefined> (data: undefined, transformer: (callback: FractalResponseData) => T, missing?: M): undefined;
function transform<T, M> (data: FractalResponseData | undefined, transformer: (callback: FractalResponseData) => T, missing?: M): T | M | undefined;
function transform<T, M> (data: FractalResponseList | undefined, transformer: (callback: FractalResponseData) => T, missing?: M): T[] | undefined;
function transform<T> (data: FractalResponseData | FractalResponseList | undefined, transformer: (callback: FractalResponseData) => T, missing = undefined) {
if (data === undefined) return undefined;
if (isList(data)) {
return data.data.map(transformer);
}
return !data ? missing : transformer(data);
}
export default class Transformers {
static toServer = ({ attributes }: FractalResponseData): Server => {
const { oom_disabled, ...limits } = attributes.limits;
const { allocations, egg, nest, node, user, variables } = attributes.relationships || {};
return {
id: attributes.id,
uuid: attributes.uuid,
externalId: attributes.external_id,
identifier: attributes.identifier,
name: attributes.name,
description: attributes.description,
status: attributes.status,
userId: attributes.owner_id,
nodeId: attributes.node_id,
allocationId: attributes.allocation_id,
eggId: attributes.egg_id,
nestId: attributes.nest_id,
limits: { ...limits, oomDisabled: oom_disabled },
featureLimits: attributes.feature_limits,
container: attributes.container,
createdAt: new Date(attributes.created_at),
updatedAt: new Date(attributes.updated_at),
relationships: {
allocations: transform(allocations as FractalResponseList | undefined, this.toAllocation),
nest: transform(nest as FractalResponseData | undefined, this.toNest),
egg: transform(egg as FractalResponseData | undefined, this.toEgg),
node: transform(node as FractalResponseData | undefined, this.toNode),
user: transform(user as FractalResponseData | undefined, this.toUser),
variables: transform(variables as FractalResponseList | undefined, this.toServerEggVariable),
},
};
};
static toNode = ({ attributes }: FractalResponseData): Node => {
return {
id: attributes.id,
uuid: attributes.uuid,
isPublic: attributes.public,
locationId: attributes.location_id,
databaseHostId: attributes.database_host_id,
name: attributes.name,
description: attributes.description,
fqdn: attributes.fqdn,
ports: {
http: {
public: attributes.publicPortHttp,
listen: attributes.listenPortHttp,
},
sftp: {
public: attributes.publicPortSftp,
listen: attributes.listenPortSftp,
},
},
scheme: attributes.scheme,
isBehindProxy: attributes.behindProxy,
isMaintenanceMode: attributes.maintenance_mode,
memory: attributes.memory,
memoryOverallocate: attributes.memory_overallocate,
disk: attributes.disk,
diskOverallocate: attributes.disk_overallocate,
uploadSize: attributes.upload_size,
daemonBase: attributes.daemonBase,
createdAt: new Date(attributes.created_at),
updatedAt: new Date(attributes.updated_at),
relationships: {
location: transform(attributes.relationships?.location as FractalResponseData, this.toLocation),
},
};
};
static toUserRole = ({ attributes }: FractalResponseData): Models.UserRole => ({
id: attributes.id,
name: attributes.name,
description: attributes.description,
relationships: {},
});
static toUser = ({ attributes }: FractalResponseData): Models.User => {
return {
id: attributes.id,
uuid: attributes.uuid,
externalId: attributes.external_id,
username: attributes.username,
email: attributes.email,
language: attributes.language,
adminRoleId: attributes.adminRoleId || null,
roleName: attributes.role_name,
isRootAdmin: attributes.root_admin,
isUsingTwoFactor: attributes['2fa'] || false,
avatarUrl: attributes.avatar_url,
createdAt: new Date(attributes.created_at),
updatedAt: new Date(attributes.updated_at),
relationships: {
role: transform(attributes.relationships?.role as FractalResponseData, this.toUserRole) || null,
},
};
};
static toLocation = ({ attributes }: FractalResponseData): Location => ({
id: attributes.id,
short: attributes.short,
long: attributes.long,
createdAt: new Date(attributes.created_at),
updatedAt: new Date(attributes.updated_at),
relationships: {
nodes: transform(attributes.relationships?.node as FractalResponseList, this.toNode),
},
});
static toEgg = ({ attributes }: FractalResponseData): Egg => ({
id: attributes.id,
uuid: attributes.uuid,
nestId: attributes.nest_id,
author: attributes.author,
name: attributes.name,
description: attributes.description,
features: attributes.features,
dockerImages: attributes.docker_images,
configFiles: attributes.config?.files,
configStartup: attributes.config?.startup,
configStop: attributes.config?.stop,
configFrom: attributes.config?.extends,
startup: attributes.startup,
copyScriptFrom: attributes.copy_script_from,
scriptContainer: attributes.script?.container,
scriptEntry: attributes.script?.entry,
scriptIsPrivileged: attributes.script?.privileged,
scriptInstall: attributes.script?.install,
createdAt: new Date(attributes.created_at),
updatedAt: new Date(attributes.updated_at),
relationships: {
nest: transform(attributes.relationships?.nest as FractalResponseData, this.toNest),
variables: transform(attributes.relationships?.variables as FractalResponseList, this.toEggVariable),
},
});
static toEggVariable = ({ attributes }: FractalResponseData): EggVariable => ({
id: attributes.id,
eggId: attributes.egg_id,
name: attributes.name,
description: attributes.description,
environmentVariable: attributes.env_variable,
defaultValue: attributes.default_value,
isUserViewable: attributes.user_viewable,
isUserEditable: attributes.user_editable,
// isRequired: attributes.required,
rules: attributes.rules,
createdAt: new Date(attributes.created_at),
updatedAt: new Date(attributes.updated_at),
relationships: {},
});
static toServerEggVariable = (data: FractalResponseData): ServerVariable => ({
...this.toEggVariable(data),
serverValue: data.attributes.server_value,
});
static toAllocation = ({ attributes }: FractalResponseData): Allocation => ({
id: attributes.id,
ip: attributes.ip,
port: attributes.port,
alias: attributes.alias || null,
isAssigned: attributes.assigned,
relationships: {
node: transform(attributes.relationships?.node as FractalResponseData, this.toNode),
server: transform(attributes.relationships?.server as FractalResponseData, this.toServer),
},
getDisplayText (): string {
const raw = `${this.ip}:${this.port}`;
return !this.alias ? raw : `${this.alias} (${raw})`;
},
});
static toNest = ({ attributes }: FractalResponseData): Nest => ({
id: attributes.id,
uuid: attributes.uuid,
author: attributes.author,
name: attributes.name,
description: attributes.description,
createdAt: new Date(attributes.created_at),
updatedAt: new Date(attributes.updated_at),
relationships: {
eggs: transform(attributes.relationships?.eggs as FractalResponseList, this.toEgg),
},
});
}

View file

@ -0,0 +1,4 @@
// eslint-disable-next-line @typescript-eslint/no-empty-interface
export interface Model {}
export type UUID = string;

View file

@ -1,8 +1,7 @@
// eslint-disable-next-line @typescript-eslint/no-empty-interface import { Model, UUID } from '@/api/definitions';
export interface Model {}
interface SecurityKey extends Model { interface SecurityKey extends Model {
uuid: string; uuid: UUID;
name: string; name: string;
type: 'public-key'; type: 'public-key';
publicKeyId: string; publicKeyId: string;

View file

@ -1,7 +1,8 @@
import React, { useState } from 'react'; import React, { useState } from 'react';
import { useFormikContext } from 'formik'; import { useFormikContext } from 'formik';
import SearchableSelect, { Option } from '@/components/elements/SearchableSelect'; import SearchableSelect, { Option } from '@/components/elements/SearchableSelect';
import { User, searchUserAccounts } from '@/api/admin/user'; import { User } from '@definitions/admin/models';
import { searchUserAccounts } from '@/api/admin/user';
export default ({ selected }: { selected?: User }) => { export default ({ selected }: { selected?: User }) => {
const { setFieldValue } = useFormikContext(); const { setFieldValue } = useFormikContext();

View file

@ -2,7 +2,7 @@ import { Checkbox } from '@/components/elements/inputs';
import { Dropdown } from '@/components/elements/dropdown'; import { Dropdown } from '@/components/elements/dropdown';
import { BanIcon, DotsVerticalIcon, LockOpenIcon, PencilIcon, SupportIcon, TrashIcon } from '@heroicons/react/solid'; import { BanIcon, DotsVerticalIcon, LockOpenIcon, PencilIcon, SupportIcon, TrashIcon } from '@heroicons/react/solid';
import React, { useState } from 'react'; import React, { useState } from 'react';
import { User } from '@/api/admin/user'; import { User } from '@definitions/admin/models';
import { Dialog } from '@/components/elements/dialog'; import { Dialog } from '@/components/elements/dialog';
import { Button } from '@/components/elements/button/index'; import { Button } from '@/components/elements/button/index';

View file

@ -1,7 +1,7 @@
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import http from '@/api/http'; import http from '@/api/http';
import { User } from '@/api/admin/user'; import { User } from '@definitions/admin/models';
import { AdminTransformers } from '@/api/admin/transformers'; import Transformers from '@definitions/admin/transformers';
import { LockOpenIcon, PlusIcon, SupportIcon, TrashIcon } from '@heroicons/react/solid'; import { LockOpenIcon, PlusIcon, SupportIcon, TrashIcon } from '@heroicons/react/solid';
import { Button } from '@/components/elements/button/index'; import { Button } from '@/components/elements/button/index';
import { Checkbox, InputField } from '@/components/elements/inputs'; import { Checkbox, InputField } from '@/components/elements/inputs';
@ -16,7 +16,7 @@ const UsersContainerV2 = () => {
useEffect(() => { useEffect(() => {
http.get('/api/application/users') http.get('/api/application/users')
.then(({ data }) => { .then(({ data }) => {
setUsers(data.data.map(AdminTransformers.toUser)); setUsers(data.data.map(Transformers.toUser));
}) })
.catch(console.error); .catch(console.error);
}, []); }, []);

View file

@ -11036,6 +11036,7 @@ fsevents@^1.2.7:
swr: ^1.0.1 swr: ^1.0.1
tailwindcss: ^3.0.23 tailwindcss: ^3.0.23
terser-webpack-plugin: ^4.2.3 terser-webpack-plugin: ^4.2.3
ts-essentials: ^9.1.2
twin.macro: ^2.8.2 twin.macro: ^2.8.2
typescript: ^4.4.4 typescript: ^4.4.4
uuid: ^3.4.0 uuid: ^3.4.0
@ -13389,6 +13390,15 @@ resolve@^2.0.0-next.3:
languageName: node languageName: node
linkType: hard linkType: hard
"ts-essentials@npm:^9.1.2":
version: 9.1.2
resolution: "ts-essentials@npm:9.1.2"
peerDependencies:
typescript: ">=4.1.0"
checksum: 3b14d8511557bd1bdec068505074ee41d3b1cdb052d3c66a5046f00b483bd4722d529b32a99cca424a9d7db9caff7ba108acbf344a5477a741e0616af5649b47
languageName: node
linkType: hard
"ts-toolbelt@npm:^9.6.0": "ts-toolbelt@npm:^9.6.0":
version: 9.6.0 version: 9.6.0
resolution: "ts-toolbelt@npm:9.6.0" resolution: "ts-toolbelt@npm:9.6.0"