You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
185 lines
8.0 KiB
185 lines
8.0 KiB
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<Response>;
|
|
|
|
export default class SL3APIClient {
|
|
private fetcher: FetchImpl
|
|
private root: string
|
|
private tokenPromise: Promise<string>
|
|
|
|
constructor(fetcher: FetchImpl, tokenPromise?: Promise<string>, 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<Scope> {
|
|
return this.fetch<{scope: Scope}>("GET", `scopes/${id}`).then(r => r.scope);
|
|
}
|
|
|
|
async listScopes(): Promise<Scope[]> {
|
|
return this.fetch<{scopes: Scope[]}>("GET", "scopes").then(r => r.scopes);
|
|
}
|
|
|
|
async createScope(scope: ScopeInput): Promise<Scope> {
|
|
return this.fetch<{scope: Scope}>("POST", "scopes", scope).then(r => r.scope);
|
|
}
|
|
|
|
async updateScope(id: number, scope: Partial<ScopeInput>): Promise<Scope> {
|
|
return this.fetch<{scope: Scope}>("PUT", `scopes/${id}`, scope).then(r => r.scope);
|
|
}
|
|
|
|
async listProjects(scopeId: number): Promise<ProjectEntry[]> {
|
|
return this.fetch<{projects: ProjectEntry[]}>("GET", `scopes/${scopeId}/projects`).then(r => r.projects);
|
|
}
|
|
|
|
async createRequirement(scopeId: number, projectId: number, input: RequirementInput): Promise<Requirement> {
|
|
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<RequirementInput>): Promise<Requirement> {
|
|
return this.fetch<{requirement: Requirement}>("PUT", `scopes/${scopeId}/projects/${projectId}/requirements/${requriementId}`, input).then(r => r.requirement);
|
|
}
|
|
|
|
async findProject(scopeId: number, projectId: number): Promise<Project> {
|
|
return this.fetch<{project: Project}>("GET", `scopes/${scopeId}/projects/${projectId}`).then(r => r.project);
|
|
}
|
|
|
|
async createProject(scopeId: number, input: ProjectInput): Promise<Project> {
|
|
return this.fetch<{project: Project}>("POST", `scopes/${scopeId}/projects`, input).then(r => r.project);
|
|
}
|
|
|
|
async updateProject(scopeId: number, projectId: number, input: Partial<ProjectInput>): Promise<Project> {
|
|
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<Item[]>
|
|
async listItems(scopeId: number | "ALL", filter: ItemFilter, grouped?: boolean, groupOptions?: GroupItemsOptions): Promise<Item[] | {items: Item[], groups: ItemGroup[]}> {
|
|
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<Item> {
|
|
return this.fetch<{item: Item}>("POST", `scopes/${scopeId}/items`, input).then(r => r.item);
|
|
}
|
|
|
|
async updateItem(scopeId: number, itemId: number, input: Partial<ItemInput>): Promise<Item> {
|
|
return this.fetch<{item: Item}>("PUT", `scopes/${scopeId}/items/${itemId}`, input).then(r => r.item);
|
|
}
|
|
|
|
async createStat(scopeId: number, input: StatInput): Promise<Stat> {
|
|
return this.fetch<{stat: Stat}>("POST", `scopes/${scopeId}/stats`, input).then(r => r.stat);
|
|
}
|
|
|
|
async updateStat(scopeId: number, statId: number, input: Partial<StatInput>): Promise<Stat> {
|
|
return this.fetch<{stat: Stat}>("PUT", `scopes/${scopeId}/stats/${statId}`, input).then(r => r.stat);
|
|
}
|
|
|
|
async findSprint(scopeId: number, sprintId: number): Promise<Sprint> {
|
|
return this.fetch<{sprint: Sprint}>("GET", `scopes/${scopeId}/sprints/${sprintId}`).then(r => r.sprint);
|
|
}
|
|
|
|
async listSprints(scopeId: number | "ALL", interval?: TimeInterval<Date | string>): Promise<Sprint[]> {
|
|
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<Sprint> {
|
|
return this.fetch<{sprint: Sprint}>("POST", `scopes/${scopeId}/sprints`, input).then(r => r.sprint);
|
|
}
|
|
|
|
async updateSprint(scopeId: number, sprintId: number, input: Partial<SprintInput>): Promise<Sprint> {
|
|
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<Sprint> {
|
|
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<Sprint> {
|
|
return this.fetch<{sprint: Sprint}>("DELETE", `scopes/${scopeId}/sprints/${sprintId}/parts/${part.partId}`).then(r => r.sprint);
|
|
}
|
|
|
|
async autchCheck(): Promise<User> {
|
|
return this.fetch<{user: User}>("GET", "auth").then(r => r.user);
|
|
}
|
|
|
|
async authLogin(username: string, password: string): Promise<AuthResult> {
|
|
return this.fetch<{auth: AuthResult}>("POST", "auth/login", {username, password}).then(r => r.auth);
|
|
}
|
|
|
|
async authRefresh(refreshToken: string): Promise<AuthResult> {
|
|
return this.fetch<{auth: AuthResult}>("POST", "auth/refresh", {refreshToken}).then(r => r.auth);
|
|
}
|
|
|
|
async fetch<T>(method: string, path: string, body: any = null): Promise<T> {
|
|
const headers: Record<string, string> = {};
|
|
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);
|
|
}
|