diff --git a/src/api.test.ts b/src/api.test.ts index 0e718ef..e9abbae 100644 --- a/src/api.test.ts +++ b/src/api.test.ts @@ -1,4 +1,8 @@ -import Forgejo from "./api"; +// SPDX-FileCopyrightText: 2023 Aravinth Manivannan +// +// SPDX-License-Identifier: AGPL-3.0-or-later + +import Forgejo from "./index"; import authtoken from "../secrets/user1-accesstoken.json"; const token = authtoken["sha1"]; diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..4bdbcac --- /dev/null +++ b/src/index.ts @@ -0,0 +1,235 @@ +// 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 + * @param {string} username - username of the logged in user + */ + constructor(url: string, username: string) { + this.url = new URL(url); + this.username = username; + } + + /** + * Authenticate with token + * @param {Auth} token - access token + */ + setTokenAuth(token: string) { + this.token = new Auth(token); + } + + getTokenAuth(): Auth { + if (this.token) { + return this.token; + } else { + throw new Error("set authentication token before use"); + } + } + + 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: "include", + headers: this.getTokenAuthHeader(), + }); + return await res.json(); + } + + /** + * Get all notifications + */ + async getNotifications(): Promise> { + this.url.pathname = "/api/v1/notifications"; + let res = await fetch(this.url, { + method: "GET", + credentials: "include", + 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: "include", + 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: "include", + 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: "include", + 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; + } + + 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: "include", + 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 { Repository, Notification, User, Comment, Issue, Auth };