import type { TimeInterval } from "$lib/models/common"; import type Item from "$lib/models/item"; import type { GroupItemsOptions, ItemFilter, ItemGroup, ItemInput } from "$lib/models/item"; import type Project from "$lib/models/project"; import type { ProjectEntry, ProjectInput, Requirement, RequirementInput } from "$lib/models/project"; import type { ScopeInput } from "$lib/models/scope"; import type Scope from "$lib/models/scope"; import type { SprintInput, SprintInputPart } from "$lib/models/sprint"; import type Sprint from "$lib/models/sprint"; import type Stat from "$lib/models/stat"; import type { StatInput } from "$lib/models/stat"; import type User from "$lib/models/user"; import type { AuthResult } from "$lib/models/user"; type FetchImpl = (info: RequestInfo, init?: RequestInit) => Promise; export default class SL3APIClient { private fetcher: FetchImpl private root: string private tokenPromise: Promise constructor(fetcher: FetchImpl, tokenPromise?: Promise, root?: string | boolean) { this.fetcher = fetcher; if (typeof(root) === "string") { this.root = root || ""; } else if (root === true) { this.root = import.meta.env.VITE_STUFFLOG3_API; } else { this.root = "" } this.tokenPromise = tokenPromise || null; } async findScope(id: number): Promise { return this.fetch<{scope: Scope}>("GET", `scopes/${id}`).then(r => r.scope); } async listScopes(): Promise { return this.fetch<{scopes: Scope[]}>("GET", "scopes").then(r => r.scopes); } async createScope(scope: ScopeInput): Promise { return this.fetch<{scope: Scope}>("POST", "scopes", scope).then(r => r.scope); } async updateScope(id: number, scope: Partial): Promise { return this.fetch<{scope: Scope}>("PUT", `scopes/${id}`, scope).then(r => r.scope); } async listProjects(scopeId: number): Promise { return this.fetch<{projects: ProjectEntry[]}>("GET", `scopes/${scopeId}/projects`).then(r => r.projects); } async createRequirement(scopeId: number, projectId: number, input: RequirementInput): Promise { return this.fetch<{requirement: Requirement}>("POST", `scopes/${scopeId}/projects/${projectId}/requirements`, input).then(r => r.requirement); } async updateRequirement(scopeId: number, projectId: number, requriementId: number, input: Partial): Promise { return this.fetch<{requirement: Requirement}>("PUT", `scopes/${scopeId}/projects/${projectId}/requirements/${requriementId}`, input).then(r => r.requirement); } async findProject(scopeId: number, projectId: number): Promise { return this.fetch<{project: Project}>("GET", `scopes/${scopeId}/projects/${projectId}`).then(r => r.project); } async createProject(scopeId: number, input: ProjectInput): Promise { return this.fetch<{project: Project}>("POST", `scopes/${scopeId}/projects`, input).then(r => r.project); } async updateProject(scopeId: number, projectId: number, input: Partial): Promise { return this.fetch<{project: Project}>("PUT", `scopes/${scopeId}/projects/${projectId}`, input).then(r => r.project); } async listItems(scopeId: number | "ALL", filter: ItemFilter, grouped: true, groupOptions: GroupItemsOptions): Promise<{items: Item[], groups: ItemGroup[]}> async listItems(scopeId: number | "ALL", filter: ItemFilter, grouped?: false): Promise async listItems(scopeId: number | "ALL", filter: ItemFilter, grouped?: boolean, groupOptions?: GroupItemsOptions): Promise { const scopePrefix = (scopeId !== "ALL") ? `scopes/${scopeId}` : "all-scopes"; const groupedSuffix = !!grouped ? `&grouped=true&hideCreated=${groupOptions?.hideCreated}&hideAcquired=${groupOptions?.hideAcquired}&hideScheduled=${groupOptions?.hideScheduled}` : ""; return this.fetch<{items: Item[], groups: ItemGroup[]}>("GET", `${scopePrefix}/items?filter=${encodeURIComponent(JSON.stringify(filter))}${groupedSuffix}`).then(r => { if (!grouped) { return r.items } return {groups: r.groups, items: r.items} }); } async createItem(scopeId: number, input: ItemInput): Promise { return this.fetch<{item: Item}>("POST", `scopes/${scopeId}/items`, input).then(r => r.item); } async updateItem(scopeId: number, itemId: number, input: Partial): Promise { return this.fetch<{item: Item}>("PUT", `scopes/${scopeId}/items/${itemId}`, input).then(r => r.item); } async createStat(scopeId: number, input: StatInput): Promise { return this.fetch<{stat: Stat}>("POST", `scopes/${scopeId}/stats`, input).then(r => r.stat); } async updateStat(scopeId: number, statId: number, input: Partial): Promise { return this.fetch<{stat: Stat}>("PUT", `scopes/${scopeId}/stats/${statId}`, input).then(r => r.stat); } async findSprint(scopeId: number, sprintId: number): Promise { return this.fetch<{sprint: Sprint}>("GET", `scopes/${scopeId}/sprints/${sprintId}`).then(r => r.sprint); } async listSprints(scopeId: number | "ALL", interval?: TimeInterval): Promise { let qs = ""; if (interval != null) { const minStr = (interval.min instanceof Date) ? interval.min.toISOString() : interval.min; const maxStr = (interval.max instanceof Date) ? interval.max.toISOString() : interval.max; qs = `?from=${encodeURIComponent(minStr)}&to=${encodeURIComponent(maxStr)}` } let prefix = (scopeId === "ALL") ? `all-scopes` : `scopes/${scopeId}` return this.fetch<{sprints: Sprint[]}>("GET", `${prefix}/sprints${qs}`).then(r => r.sprints); } async createSprint(scopeId: number, input: SprintInput): Promise { return this.fetch<{sprint: Sprint}>("POST", `scopes/${scopeId}/sprints`, input).then(r => r.sprint); } async updateSprint(scopeId: number, sprintId: number, input: Partial): Promise { input = {...input, parts: void(0)}; return this.fetch<{sprint: Sprint}>("PUT", `scopes/${scopeId}/sprints/${sprintId}`, input).then(r => r.sprint); } async upsertSprintPart(scopeId: number, sprintId: number, part: SprintInputPart): Promise { return this.fetch<{sprint: Sprint}>("PUT", `scopes/${scopeId}/sprints/${sprintId}/parts/${part.partId}`, part).then(r => r.sprint); } async deleteSprintPart(scopeId: number, sprintId: number, part: SprintInputPart): Promise { return this.fetch<{sprint: Sprint}>("DELETE", `scopes/${scopeId}/sprints/${sprintId}/parts/${part.partId}`).then(r => r.sprint); } async autchCheck(): Promise { return this.fetch<{user: User}>("GET", "auth").then(r => r.user); } async authLogin(username: string, password: string): Promise { return this.fetch<{auth: AuthResult}>("POST", "auth/login", {username, password}).then(r => r.auth); } async authRefresh(refreshToken: string): Promise { return this.fetch<{auth: AuthResult}>("POST", "auth/refresh", {refreshToken}).then(r => r.auth); } async fetch(method: string, path: string, body: any = null): Promise { const headers: Record = {}; let bodyData: string | undefined = void(0); if (body != null) { headers["Content-Type"] = "application/json"; bodyData = JSON.stringify(body); } if (this.tokenPromise != null) { headers["Authorization"] = `Bearer ${await this.tokenPromise}`; } const res = await this.fetcher(`${this.root}/api/v1/${path}`, { method, headers, body: bodyData, credentials: "same-origin", }); if (!res.ok) { if (!(res.headers.get("content-type")||"").includes("application/json")) { throw await res.text(); } throw await res.json(); } return res.json(); } } export function sl3(fetcher: FetchImpl, idToken?: string) { return new SL3APIClient(fetcher, idToken ? Promise.resolve(idToken) : null); }