// SPDX-FileCopyrightText: 2023 Aravinth Manivannan // // SPDX-License-Identifier: AGPL-3.0-or-later import Auth from "./auth"; import User from "./spec/user"; import Notification from "./spec/notification"; import Issue from "./spec/issue"; import Repository from "./spec/repository"; import Comment from "./spec/comments"; class Forgejo { url: URL; username: string; token?: Auth; /** * Represents a Forgejo instance. * @constructor * @param {string} url - The URL of the Forgejo instance */ constructor(url: string) { this.url = new URL(url); } /** * Authenticate with token * @param {Auth} token - access token */ async setTokenAuth(token: string) { this.token = new Auth(token); let res = await this.getUser(); this.username = res.username; } /** * Get access token */ getTokenAuth(): Auth { if (this.token) { return this.token; } else { throw new Error("set authentication token before use"); } } /** * Get access token in header format for the fetch API */ getTokenAuthHeader() { return { Authorization: `token ${this.getTokenAuth().getToken()}` }; } /** * Get logged in user */ async getUser(): Promise { this.url.pathname = "/api/v1/user"; let res = await fetch(this.url, { method: "GET", credentials: "omit", headers: this.getTokenAuthHeader(), }); return await res.json(); } /** * Get all notifications */ async getNotifications(): Promise> { this.url.pathname = "/api/v1/notifications"; this.url.pathname = "/api/v1/notifications"; console.log(this.url); let res = await fetch(this.url, { method: "GET", credentials: "omit", headers: this.getTokenAuthHeader(), }); return await res.json(); } /** * Get number of unread notifications */ async getNumUnreadNotifications(): Promise { this.url.pathname = "/api/v1/notifications/new"; let res = await fetch(this.url, { method: "GET", credentials: "omit", headers: this.getTokenAuthHeader(), }); let data = await res.json(); return data["new"]; } /** * Get Notification Thread * @param {number} id - The ID of a notification thread */ async getNotificationThread(id: number): Promise { this.url.pathname = `/api/v1/notifications/threads/${id}`; let res = await fetch(this.url, { method: "GET", credentials: "omit", headers: this.getTokenAuthHeader(), }); return await res.json(); } /** * Get the time at which the issue was last read */ lastReadTime(issue: Issue): Date { return new Date("01/01/01"); // TODO: return last read time from storage. If unread, return 0 } /** * Check if logged in user is mentioned in the issue */ async findMentionsInIssue(owner: string, repo: string, id: number) { let issue = await this.getIssue(owner, repo, id); issue = await this.getCommentsForIssue(issue); let unreadTime = this.lastReadTime(issue); const mentionUtil = (str: string): boolean => { return str.includes(this.username); }; const items = []; if (new Date(issue.created_at) > unreadTime) { items.push(issue.title); items.push(issue.body); } issue.lazy_comments_data.forEach((comment) => { if (comment.created_at > unreadTime) { items.push(comment.body); return; } if (comment.updated_at) { if (comment.updated_at > unreadTime) { items.push(comment.body); return; } } }); let mention = false; items.forEach((item) => { if (mentionUtil(item)) { mention = true; return; } }); return mention; } /** * Mark Notification Read * @param {number} id - The ID of a notification thread */ async markNotificationRead(id: number) { this.url.pathname = `/api/v1/notifications/threads/${id}`; await fetch(this.url, { method: "PATCH" }); } /** * Mark Notification Read for a specific repository * @param {string} owner - Name of the owner of the repository * @param {string} repo - Name of the repository */ async markNotificationReadForRepo(owner: string, repo: string) { this.url.pathname = `/api/v1/repos/${owner}/${repo}/notifications`; await fetch(this.url, { method: "PUT" }); } /** * Get Issue and its comments * @param {str} owner - The owner of the repository * @param {str} repo - The name of the repository * @param {number} id - The ID of an issue */ async getIssue(owner: string, repo: string, id: number): Promise { this.url.pathname = `/api/v1/repos/${owner}/${repo}/issues/${id}`; let res = await fetch(this.url, { method: "GET", credentials: "omit", headers: this.getTokenAuthHeader(), }); let issue = await res.json(); if (typeof issue.created_at === "string") { issue.created_at = new Date(issue.created_at); } if (issue.updated_at) { if (typeof issue.updated_at === "string") { issue.updated_at = new Date(issue.updated_at); } } if (issue.closed_at) { if (typeof issue.closed_at === "string") { issue.closed_at = new Date(issue.closed_at); } } return issue; } /** * Fetch and save comments for the issue objects */ async getCommentsForIssue(issue: Issue): Promise { // TODO: check if issue.number != issue.id causes problems. I'm assuming // Issue.number is the local repository issue ID and issue.id is DB ID this.url.pathname = `/api/v1/repos/${issue.repository.owner}/${issue.repository.name}/issues/${issue.number}/comments`; let res = await fetch(this.url, { method: "GET", credentials: "omit", headers: this.getTokenAuthHeader(), }); let c: Array = await res.json(); c = c.map((comment) => { if (typeof comment.created_at === "string") { comment.created_at = new Date(comment.created_at); } if (comment.updated_at) { if (typeof comment.updated_at === "string") { comment.updated_at = new Date(comment.updated_at); } } return comment; }); issue.lazy_comments_data = c; return issue; } } export default Forgejo; //export { Notification, Issue, Repository, Comment, User };