diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f382792 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/cjs +/esm \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..f02be76 --- /dev/null +++ b/README.md @@ -0,0 +1,5 @@ +# T4.ts +A TypeScript wrapper for TerminalFour's REST API + + + diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..75943d4 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,99 @@ +{ + "name": "t4api", + "version": "0.0.1", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "t4api", + "version": "0.0.1", + "dependencies": { + "node-fetch": "^3.3.1" + } + }, + "node_modules/data-uri-to-buffer": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", + "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", + "engines": { + "node": ">= 12" + } + }, + "node_modules/fetch-blob": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", + "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "paypal", + "url": "https://paypal.me/jimmywarting" + } + ], + "dependencies": { + "node-domexception": "^1.0.0", + "web-streams-polyfill": "^3.0.3" + }, + "engines": { + "node": "^12.20 || >= 14.13" + } + }, + "node_modules/formdata-polyfill": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", + "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", + "dependencies": { + "fetch-blob": "^3.1.2" + }, + "engines": { + "node": ">=12.20.0" + } + }, + "node_modules/node-domexception": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", + "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "github", + "url": "https://paypal.me/jimmywarting" + } + ], + "engines": { + "node": ">=10.5.0" + } + }, + "node_modules/node-fetch": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.1.tgz", + "integrity": "sha512-cRVc/kyto/7E5shrWca1Wsea4y6tL9iYJE5FBCius3JQfb/4P4I295PfhgbJQBLTx6lATE4z+wK0rPM4VS2uow==", + "dependencies": { + "data-uri-to-buffer": "^4.0.0", + "fetch-blob": "^3.1.4", + "formdata-polyfill": "^4.0.10" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/node-fetch" + } + }, + "node_modules/web-streams-polyfill": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.2.1.tgz", + "integrity": "sha512-e0MO3wdXWKrLbL0DgGnUV7WHVuw9OUvL4hjgnPkIeEvESk74gAITi5G606JtZPp39cd8HA9VQzCIvA49LpPN5Q==", + "engines": { + "node": ">= 8" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..b7f685d --- /dev/null +++ b/package.json @@ -0,0 +1,23 @@ +{ + "name": "t4ts", + "version": "0.0.1", + "description": "A simple terminalfour web api wrapper.", + "type": "module", + "exports": { + "require": "./cjs/index.js", + "import": "./esm/index.js", + "default": "./esm/index.js" + }, + "typesVersions": { + "*": { + "index.d.ts": ["./esm/index.d.ts"] + } + }, + "main": "./cjs/index.js", + "dependencies": { + "node-fetch": "^3.3.1" + }, + "scripts": { + "build": "npx tsc --module es2022 --outDir esm/ && npx tsc --module commonjs --outDir cjs/" + } +} diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..ca04018 --- /dev/null +++ b/src/index.ts @@ -0,0 +1 @@ +export { Client } from './lib/Client.js' \ No newline at end of file diff --git a/src/lib/Client.ts b/src/lib/Client.ts new file mode 100644 index 0000000..656c125 --- /dev/null +++ b/src/lib/Client.ts @@ -0,0 +1,52 @@ +import { Elements } from "./utility/Global.js" +import { Hierarchy } from "./Hierarchy.js" +import { Profile } from "./Profile.js" + +export class Client { + url: String + private token: String + hierarchy: Hierarchy + profile: Profile + constructor(url: String, token: String) { + this.url = url + this.token = token + + this.hierarchy = new Hierarchy(this) + this.profile = new Profile(this) + } + + async call(method: string, endpoint: string, options: any) { + if (!this.token) throw Error('Token not specified') + try { + let headers: Elements = { + 'authorization': `Bearer ${this.token}`, + 'accept': 'application/json, text/javascript, */*; q=0.01', + 'accept-language': 'en-US,en;q=0.9' + } + if (options?.body && typeof options.body == 'object') { + options.body = JSON.stringify(options.body) + headers['content-type'] = 'application/json' + } + const request = await fetch(`${this.url}/${endpoint}`, { + ...options, + headers, + method, + }) + return request + } catch (error) { + throw Error(`Request failed due to:\n${error}`) + } + } + + setToken(token: string) { + this.token = token + } + + setURL(url: string) { + this.url = url + } + + verify() { + // Check if client can make requests + } +} \ No newline at end of file diff --git a/src/lib/Hierarchy.ts b/src/lib/Hierarchy.ts new file mode 100644 index 0000000..9c858db --- /dev/null +++ b/src/lib/Hierarchy.ts @@ -0,0 +1,78 @@ +import { Client } from './Client.js'; +import { Channels, ContentTypeScopes, MetaDatas, MetaData, + LinkInfo, AccessControl, sortLock, InheritedPageLayouts } from './utility/Global.js'; + +export const HierarchyEndpoint = 'hierarchy' +export class Hierarchy { + private client: Client; + constructor(client: Client) { + this.client = client + } + + async get(id: number, language: string) { + const response = await this.client.call('GET', `${HierarchyEndpoint}/${id}/${language}`, null) + return response?.ok ? await response.json() as SectionDTO : null + } + + async delete(section:number | SectionDTO) { + const id = typeof section == 'number' ? section : section.id + + } + +} + +export interface SectionDTO { + id: string; + parent: string; + name: string; + description: string; + outputUrl: string; + outputFilename: string; + accessKey: string; + keyPhrase: string; + status: string; + workflow: string; + parentWorkflowName: string; + show: string; + iseForm: string; + archive: string; + lastModified: Date; + printSequence: string; + contentSortMethod: string; + sectionSortMethod: string; + path: string; + mirrorOf: string; + sourceOfMirror: string; + link: string; + channels: Channels; + userIDs: string; + inheritedUserIDs: string; + groupIDs: string; + inheritedGroupIDs: string; + viewUserIDs: string; + viewGroupIDs: string; + contentTypeScopes: ContentTypeScopes; + metaDatas: MetaDatas; + linkInfo: LinkInfo; + excludedMirrorSections: string; + workflowName: string; + parentWorkflowID: string; + accessControl: AccessControl; + metaData: MetaData; + pathMembers: string; + sortLock: sortLock; + editable: string; + inheritedLinkSection: string; + accessControlEnabled: string; + accessControlType: string; + metaDataType: string; + accessControlInherited: string; + allowedGroups: string; + mirrorOfPath: string; + inheritedPageLayouts: InheritedPageLayouts; + outputUriEnabled: string; + publishEnabled: string; + outputFilenameEnabled: string; + spellCheckEnabled: string; + pathAsOutputUriEnabled: string; +} diff --git a/src/lib/Profile.ts b/src/lib/Profile.ts new file mode 100644 index 0000000..971e9d7 --- /dev/null +++ b/src/lib/Profile.ts @@ -0,0 +1,38 @@ +import { Client } from "./Client.js" + +export const ProfileEndpoint = 'profile' +export class Profile { + private client: Client + constructor(client: Client) { + this.client = client + } + + async get() { + const response = await this.client.call('GET', ProfileEndpoint, null) + return response?.ok ? await response.json() : null + } + + async update(body: Partial) { + const currentProfile = await this.get() + if (!currentProfile) throw Error('Failed to get client profile') + const response = await this.client.call('POST', ProfileEndpoint, { + body: Object.assign(currentProfile, body), + }) + return response?.ok ? await response.json() : false + } +} + +export interface UserProfileView { + firstName: string; + lastName: string; + username: string; + emailAddress: string; + defaultLanguage: string; + oldPassword?: string; + newPassword?: string; + newPasswordConfirm?: string; + uiLocale: string; + htmlEditorId: string; + defaultPreviewChannelId: string; + userLevel: string; +} \ No newline at end of file diff --git a/src/lib/utility/Global.ts b/src/lib/utility/Global.ts new file mode 100644 index 0000000..e63f17d --- /dev/null +++ b/src/lib/utility/Global.ts @@ -0,0 +1,124 @@ +export interface ContentDto { + id: string; + contentTypeID: string; + archiveSection: string; + publishDate: Date; + reviewDate: Date; + expiryDate: Date; + language: string; + name: string; + alternativeLanguages: string; + status: string; + lastModified: Date; + lastModifiedBy: string; + lastModifierName: string; + contentTypeName: string; + contentTypeAlias: string; + owner: Owner; + version: string; + previousVersion: string; + channels: string; + mirroredSectionPaths: Elements; + editable: string; + expired: string; + lock: Lock; + canPublishNow: string; + canSaveAndApprove: string; + contentTypeAccess: string; + contentTypeLock: Lock; + elements: Elements; + contentType: ContentType; + types: string; + insertAtIndex: string; + sortLock: sortLock; + excludedMirrorSectionIds: string; + locked: string; + sectionIDs: string; +} + +export interface ContentType { + id: string; + name: string; + description: string; + type: string; + editable: string; + primaryGroup: string; + sharedGroups: string; + alias: string; + workflow: string; + systemTemplate: string; + sharedGroupCount: string; + contentTypeElements: string; + metaMapped: string; + lock: string; + minAuthLevel: string; + enableDirectEdit: string; + elementIdforFilename: string; + warningMessage: string; + sectionId: string; + duplicate: string; + duplicatedFromId: string; + fullyAccessible: string; +} + +export type sortLock = 'TOP' | 'BOTTOM' | 'UNLOCKED' +export interface Lock { + assetID: string; + assetType: string; + lockType: string; + expiry: string; + owner: string; + ownerName: string; + currentUser: string; + language: string; +} + +export interface Elements { + [Key: string]: string; +} + +export interface Owner { + id: string; + type: string; +} + +export interface AccessControl { + id: string; + type: string; + enabled: string; + active: string; +} +export interface MetaData extends AccessControl {} + +export interface Channels { + id: string; + pageLayout: string; + inheritedPageLayout: string; + valid: string; +} +export interface AppliedPageLayoutsDTO extends Channels {} + +export interface ContentTypeScopes { + id: string; + scope: string; + inherited: string; +} + +export interface InheritedPageLayouts extends Elements {} + +export interface LinkInfo { + type: string; + section: string; + url: string; + language: string; + target: string; + accessControl: string; + override: string; + overridden: string; +} + +export interface MetaDatas { + id: string; + value: string; + lang: string; +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..67b93b7 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,12 @@ +{ + "compilerOptions": { + "moduleResolution": "node", + "module": "NodeNext", + "lib": ["es2020", "dom"], + "target": "es2020", + "declaration": true, + "strict": true, + "sourceMap": true, + }, + "exclude": ["cjs", "esm"] +} \ No newline at end of file