@inweb/client
Version:
JavaScript REST API client for the Open Cloud Server
544 lines (483 loc) • 17.8 kB
text/typescript
///////////////////////////////////////////////////////////////////////////////
// Copyright (C) 2002-2025, Open Design Alliance (the "Alliance").
// All rights reserved.
//
// This software and its documentation and related materials are owned by
// the Alliance. The software may only be incorporated into application
// programs owned by members of the Alliance, subject to a signed
// Membership Agreement and Supplemental Software License Agreement with the
// Alliance. The structure and organization of this software are the valuable
// trade secrets of the Alliance and its suppliers. The software is also
// protected by copyright law and international treaty provisions. Application
// programs incorporating this software must include the following statement
// with their copyright notices:
//
// This application incorporates Open Design Alliance software pursuant to a
// license agreement with Open Design Alliance.
// Open Design Alliance Copyright (C) 2002-2025 by Open Design Alliance.
// All rights reserved.
//
// By use of this software, its documentation or related materials, you
// acknowledge and accept the above terms.
///////////////////////////////////////////////////////////////////////////////
import { IHttpClient } from "./IHttpClient";
import { Endpoint } from "./Endpoint";
import { IShortUserDesc } from "./IUser";
import { Role } from "./Role";
import { IRoleActions } from "./IRole";
import { Member } from "./Member";
import { File } from "./File";
import { userFullName, userInitials } from "./Utils";
/**
* Provides properties and methods for obtaining information about a project on the Open Cloud Server and
* managing its {@link Role | roles}, {@link Member | members} and models.
*/
export class Project extends Endpoint {
private _data: any;
/**
* @param data - Raw project data received from the server. For more information, see
* {@link https://cloud.opendesign.com/docs//pages/server/api.html#Project | Open Cloud Projects API}.
* @param httpClient - HTTP client instance used to send requests to the REST API server.
*/
constructor(data: any, httpClient: IHttpClient) {
super(`/projects/${data.id}`, httpClient);
this.data = data;
}
/**
* Project features the user has access to.
*
* @readonly
*/
get authorization(): {
/**
* Actions are allowed to be performed:
*
* - `update` - The ability to update the project details.
* - `createTopic` - The ability to create a new topic.
* - `createDocument` - The ability to create a new document.
*/
project_actions: string[];
} {
return this.data.authorization;
}
/**
* Project creation time (UTC) in the format specified in
* {@link https://www.wikipedia.org/wiki/ISO_8601 | ISO 8601}.
*
* @readonly
*/
get createdAt(): string {
return this.data.createdAt;
}
/**
* Project custom fields object, to store custom data.
*/
get customFields(): any {
return this.data.customFields;
}
set customFields(value: any) {
this.data.customFields = value;
}
/**
* Raw project data received from the server. For more information, see
* {@link https://cloud.opendesign.com/docs//pages/server/api.html#Project | Open Cloud Projects API}.
*
* @readonly
*/
get data(): any {
return this._data;
}
private set data(value: any) {
this._data = value;
this._data.previewUrl = value.avatarUrl
? `${this.httpClient.serverUrl}/projects/${this._data.id}/preview?updated=${value.updatedAt}`
: "";
this._data.owner.avatarUrl = `${this.httpClient.serverUrl}/users/${this._data.owner.userId}/avatar`;
this._data.owner.fullName = userFullName(this._data.owner);
this._data.owner.initials = userInitials(this._data.owner.fullName);
}
/**
* Project description.
*/
get description(): string {
return this.data.description;
}
set description(value: string) {
this.data.description = value;
}
/**
* Project end date in the format specified in
* {@link https://www.wikipedia.org/wiki/ISO_8601 | ISO 8601}.
*/
get endDate(): string {
return this.data.endDate;
}
set endDate(value: string | Date) {
this.data.endDate = value instanceof Date ? value.toISOString() : value;
}
/**
* Unique project ID.
*
* @readonly
*/
get id(): string {
return this.data.id;
}
/**
* The number of members in the project.
*
* @readonly
*/
get memberCount(): number {
return this.data.memberCount;
}
/**
* The number of models in the project.
*
* @readonly
*/
get modelCount(): number {
return this.data.modelCount;
}
/**
* Project name.
*/
get name(): string {
return this.data.name;
}
set name(value: string) {
this.data.name = value;
}
/**
* Project owner information.
*
* @readonly
*/
get owner(): IShortUserDesc {
return this.data.owner;
}
/**
* Project preview image URL or empty string if the project does not have a preview. Use
* {@link Project.setPreview | setPreview()} to change preview image.
*
* @readonly
*/
get previewUrl(): string {
return this._data.previewUrl;
}
/**
* `true` if project is shared project.
*/
get public(): boolean {
return this.data.public;
}
set public(value: boolean) {
this.data.public = value;
}
/**
* Project start date in the format specified in
* {@link https://www.wikipedia.org/wiki/ISO_8601 | ISO 8601}.
*/
get startDate(): string {
return this.data.startDate;
}
set startDate(value: string | Date) {
this.data.startDate = value instanceof Date ? value.toISOString() : value;
}
/**
* The number of topics in the project.
*
* @readonly
*/
get topicCount(): number {
return this.data.topicCount;
}
/**
* Project last update time (UTC) in the format specified in
* {@link https://www.wikipedia.org/wiki/ISO_8601 | ISO 8601}.
*
* @readonly
*/
get updatedAt(): string {
return this.data.updatedAt;
}
/**
* Reloads project data from the server.
*/
async checkout(): Promise<this> {
const response = await this.get("");
this.data = await response.json();
return this;
}
/**
* Updates project data on the server.
*
* @param data - Raw project data. For more information, see
* {@link https://cloud.opendesign.com/docs//pages/server/api.html#Project | Open Cloud Projects API}.
*/
async update(data: any): Promise<this> {
const response = await this.put("", data);
this.data = await response.json();
return this;
}
/**
* Deletes a project from the server.
*
* @returns Returns the raw data of a deleted project. For more information, see
* {@link https://cloud.opendesign.com/docs//pages/server/api.html#Project | Open Cloud Projects API}.
*/
override delete(): Promise<any> {
return super
.delete("")
.then((response) => response.text())
.then((text) => {
// TODO fix for server 23.5 and below
try {
return JSON.parse(text);
} catch {
return { id: this.id };
}
});
}
/**
* Saves project properties changes to the server. Call this method to update project data on the
* server after any property changes.
*/
save(): Promise<this> {
return this.update(this.data);
}
/**
* Sets or removes the project preview.
*
* @param image - Preview image. Can be a
* {@link https://developer.mozilla.org/docs/Web/HTTP/Basics_of_HTTP/Data_URIs | Data URL} string,
* {@link https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer | ArrayBuffer},
* {@link https://developer.mozilla.org/docs/Web/API/Blob/Blob | Blob} or
* {@link https://developer.mozilla.org/docs/Web/API/File | Web API File} object. Setting the `image`
* to `null` will remove the preview.
*/
async setPreview(image?: BodyInit | null): Promise<this> {
if (!image) {
await this.deletePreview();
} else {
const response = await this.post("/preview", image);
this.data = await response.json();
}
return this;
}
/**
* Removes the project preview.
*/
async deletePreview(): Promise<this> {
const response = await super.delete("/preview");
this.data = await response.json();
return this;
}
/**
* Returns a list of project roles. Project members have different abilities depending on the role they
* have in a project.
*/
getRoles(): Promise<Role[]> {
return this.get("/roles")
.then((response) => response.json())
.then((array) => array.map((data) => new Role(data, this.id, this.httpClient)));
}
/**
* Returns information about the specified project role.
*
* @param name - Role name.
*/
getRole(name: string): Promise<Role> {
return this.get(`/roles/${name}`)
.then((response) => response.json())
.then((data) => new Role(data, this.id, this.httpClient));
}
/**
* Creates a new project role.
*
* @param name - Role name.
* @param description - Role description.
* @param permissions - Actions are allowed to be performed for the role.
*/
createRole(name: string, description: string, permissions: IRoleActions): Promise<Role> {
return this.post("/roles", {
name,
description,
permissions: permissions || {},
})
.then((response) => response.json())
.then((data) => new Role(data, this.id, this.httpClient));
}
/**
* Deletes the specified project role.
*
* @param name - Role name.
* @returns Returns the raw data of a deleted role. For more information, see
* {@link https://cloud.opendesign.com/docs//pages/server/api.html#Project | Open Cloud Projects API}.
*/
deleteRole(name: string): Promise<any> {
return super.delete(`/roles/${name}`).then((response) => response.json());
}
/**
* Returns a list of project members.
*/
getMembers(): Promise<Member[]> {
return this.get("/members")
.then((response) => response.json())
.then((array) => array.map((data) => new Member(data, this.id, this.httpClient)));
}
/**
* Returns information about the specified project member.
*
* @param memberId - Member ID.
*/
getMember(memberId: string): Promise<Member> {
return this.get(`/members/${memberId}`)
.then((response) => response.json())
.then((data) => new Member(data, this.id, this.httpClient));
}
/**
* Adds a user to the project to become a member and have permission to perform actions.
*
* @param userId - User ID.
* @param role - Role name from the list of project {@link getRoles | roles}.
*/
addMember(userId: string, role: string): Promise<Member> {
return this.post("/members", { userId, role })
.then((response) => response.json())
.then((data) => new Member(data, this.id, this.httpClient));
}
/**
* Removes the specified member from a project.
*
* @param memberId - Member ID.
* @returns Returns the raw data of a deleted member. For more information, see
* {@link https://cloud.opendesign.com/docs//pages/server/api.html#Project | Open Cloud Projects API}.
*/
removeMember(memberId: string): Promise<any> {
return super.delete(`/members/${memberId}`).then((response) => response.json());
}
/**
* Information about the file (model) that can be reference in the project topics.
*
* @typedef {any} FileInformation
* @property {any[]} display_information - The list of fields to allow users to associate the file with
* a server model.
* @property {string} display_information.field_display_name - Field display name.
* @property {string} display_information.field_value - Field value.
* @property {any} file - The file reference object.
* @property {string} file.file_name - File name.
* @property {string} file.reference - File ID.
*/
/**
* Returns a list of project files. For more information, see
* {@link https://cloud.opendesign.com/docs//pages/server/bcf3.html#ProjectFilesInformation | Open Cloud BCF3 API}.
*
* This list contains all files that the project has access to. To add a file to this list, create a
* {@link IGrantedTo.project | project} permission on the file using
* {@link File.createPermission | File.createPermission()}.
*/
getFilesInformation(): Promise<any[]> {
const bcfProjects = new Endpoint("/bcf/3.0/projects", this.httpClient, this.headers);
return bcfProjects
.get(`/${this.id}/files_information`)
.then((response) => response.json())
.then((items) => {
items.forEach((item) => {
const getFieldValue = (displayName: string) => {
return (item.display_information.find((x) => x.field_display_name === displayName) || {}).field_value;
};
const previewUrl = `${this.httpClient.serverUrl}/files/${item.file.reference}/preview`;
const ownerAvatarUrl = `${this.httpClient.serverUrl}/users/${getFieldValue("Owner")}/avatar`;
const ownerFirstName = getFieldValue("Owner First Name");
const ownerLastName = getFieldValue("Owner Last Name");
const ownerUserName = getFieldValue("Owner User Name");
const ownerFullName = userFullName(ownerFirstName, ownerLastName, ownerUserName);
const ownerInitials = userInitials(ownerFullName);
item.display_information.push({ field_display_name: "Preview URL", field_value: previewUrl });
item.display_information.push({ field_display_name: "Owner Avatar URL", field_value: ownerAvatarUrl });
item.display_information.push({ field_display_name: "Owner Full Name", field_value: ownerFullName });
item.display_information.push({ field_display_name: "Owner Initials", field_value: ownerInitials });
// updatedBy since 24.10
const updatedByAvatarUrl = `${this.httpClient.serverUrl}/users/${getFieldValue("Updated By")}/avatar`;
const updatedByFirstName = getFieldValue("Updated By First Name");
const updatedByLastName = getFieldValue("Updated By Last Name");
const updatedByUserName = getFieldValue("Updated By User Name");
const updatedByFullName = userFullName(updatedByFirstName, updatedByLastName, updatedByUserName);
const updatedByInitials = userInitials(updatedByFullName);
item.display_information.push({
field_display_name: "Updated By Avatar URL",
field_value: updatedByAvatarUrl,
});
item.display_information.push({ field_display_name: "Updated By Full Name", field_value: updatedByFullName });
item.display_information.push({ field_display_name: "Updated By Initials", field_value: updatedByInitials });
// geometryType since 24.12
const geometry = getFieldValue("Geometry Status");
const geometryGltf = getFieldValue("GeometryGltf Status");
const geometryType = geometry === "done" ? "vsfx" : geometryGltf === "done" ? "gltf" : "";
item.display_information.push({ field_display_name: "Geometry Type", field_value: geometryType });
});
return items;
});
}
/**
* Returns a list of project files.
*/
getModels(): Promise<File[]> {
return this.getFilesInformation()
.then((filesInformation) => filesInformation.map((item) => item.file.reference))
.then((ids) => {
const files = new Endpoint("/files", this.httpClient, this.headers);
return files.get(`?id=${ids.join("|")}`);
})
.then((response) => response.json())
.then((files) => files.result.map((data) => new File(data, this.httpClient)));
}
/**
* Adds a file to the project with specified permissions.
*
* To change file permissions for the project use {@link Permission.actions}.
*
* @param fileId - File ID.
* @param actions - Actions are allowed to be performed on a file:
*
* - `read` - The ability to read file description, geometry data and properties.
* - `readSourceFile` - The ability to download source file.
* - `write` - The ability to modify file name, description and references.
* - `readViewpoint` - The ability to read file viewpoints.
* - `createViewpoint` - The ability to create file viewpoints.
*
* @param _public - Specifies whether all users have access to the file or not.
* @returns Returns a file instance added to the project.
*/
async addModel(fileId: string, actions: string | string[], _public: boolean): Promise<File> {
const files = new Endpoint("/files", this.httpClient, this.headers);
const file = await files
.get(`/${fileId}`)
.then((response) => response.json())
.then((data) => new File(data, this.httpClient));
const grantedTo = [{ project: { id: this.id, name: this.name } }];
await file.createPermission(actions, grantedTo, _public);
return file;
}
/**
* Removes the specified file from a project.
*
* @param fileId - File ID.
* @returns Returns a file instance removed from the project.
*/
async removeModel(fileId: string): Promise<File> {
const files = new Endpoint("/files", this.httpClient, this.headers);
const file = await files
.get(`/${fileId}`)
.then((response) => response.json())
.then((data) => new File(data, this.httpClient));
const permissions = await file.getPermissions();
await Promise.allSettled(
permissions
.filter((permission) => permission.grantedTo.some((x) => x.project?.id === this.id))
.map((permission) => permission.delete())
);
return file;
}
}