feat: add activity view

This commit is contained in:
Naomi Carrigan 2024-08-24 22:12:16 -07:00
parent a137511c99
commit 4cba941389
Signed by: naomi
SSH Key Fingerprint: SHA256:rca1iUI2OhAM6n4FIUaFcZcicmri0jgocqKiTTAfrt8
6 changed files with 1041 additions and 1 deletions

49
src/app/activity/page.tsx Normal file
View File

@ -0,0 +1,49 @@
"use client";
import { Activity } from "@/components/activity";
import { Review } from "@/components/review";
import { Rule } from "@/components/rule";
import { Testimonials } from "@/config/Testimonials";
import { useEffect, useState } from "react";
const Reviews = (): JSX.Element => {
const [activity, setActivity] = useState<
{
type: string;
date: Date;
repo: string;
repoName: string;
}[]
>([]);
useEffect(() => {
fetch("/api/activity")
.then((data) => data.json())
.then((data) => setActivity(data));
}, []);
return (
<>
<main className="w-[95%] text-center max-w-4xl m-auto mt-16 mb-16 rounded-lg">
<h1 className="text-5xl">Recent Activity</h1>
<section>
<p className="mb-2">See what Naomi has been up to lately.</p>
<Rule />
<ol className="relative border-s border-[--primary] w-4/5 m-auto">
{activity.map((act, i) => (
<Activity
key={act.date.toString()}
type={act.type}
date={act.date}
repo={act.repo}
repoName={act.repoName}
heart={i % 2 ? "🩷" : "🩵"}
/>
))}
</ol>
</section>
</main>
</>
);
};
export default Reviews;

View File

@ -0,0 +1,17 @@
import { getCodebergData } from "@/lib/codeberg";
import { getGithubData } from "@/lib/github";
import { NextResponse } from "next/server";
export async function GET() {
const codeberg = await getCodebergData();
const github = await getGithubData();
const normalised: {
type: string;
date: Date;
repo: string;
repoName: string;
}[] = [...codeberg.map(i => ({ type: i.op_type, date: new Date(i.created), repo: i.repo.html_url, repoName: i.repo.full_name })), ...github.map(i => ({ type: i.type, date: new Date(i.created_at), repo: i.repo.url.replace("api.github.com/repos", "github.com"), repoName: i.repo.name }))]
return NextResponse.json(normalised.sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime()).slice(0, 100))
}

View File

@ -0,0 +1,55 @@
import { IconDefinition } from "@fortawesome/fontawesome-svg-core";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { Rule } from "./rule";
interface ActivityProps {
type: string;
date: Date;
repo: string;
repoName: string;
heart: string;
}
const TypeToString: Record<string, string> = {
commit_repo: "committed to",
delete_branch: "deleted a branch on",
merge_pull_request: "merged a PR in",
create_pull_request: "created a PR in",
create_branch: "created a branch in",
PushEvent: "committed to",
DeleteEvent: "deleted a branch on",
PullRequestEvent: "created or merged a PR in",
PullRequestReviewEvent: "reviewed a PR in",
PullRequestReviewCommentEvent: "commented on a PR in",
IssueCommentEvent: "commented on",
IssuesEvent: "created or updated an issue in",
close_issue: "closed an issue in",
create_issue: "created an issue in",
};
export const Activity = (props: ActivityProps): JSX.Element => {
const { type, date, repo, repoName, heart } = props;
return (
<li className="ms-6">
<span className="absolute flex items-center justify-center w-6 h-6 rounded-full -start-3">
{heart}
</span>
<div className="items-center justify-between p-4 bg-white border border-gray-200 rounded-lg shadow-sm sm:flex dark:bg-gray-700 dark:border-gray-600">
<time className="mb-1 text-xs font-normal text-gray-400 sm:order-last sm:mb-0">
{date.toLocaleString("en-GB")}
</time>
<div className="text-sm font-normal text-gray-500 lex dark:text-gray-300">
Naomi has {TypeToString[type] ?? "performed a " + type}{" "}
<a
href={repo}
target="_blank"
rel="noopener noreferrer"
className="font-semibold text-[#abfcec] hover:underline"
>
{repoName}
</a>{" "}
</div>
</div>
</li>
);
};

View File

@ -7,5 +7,6 @@ export const NavItems = [
{ href: "/reviews", text: "Reviews" },
{ href: "/games", text: "Games"},
{ href: "/team", text: "Our Team" },
{ href: "/polycule", text: "Polycule"}
{ href: "/polycule", text: "Polycule"},
{ href: "/activity", text: "Activity"}
].sort((a, b) => a.text.localeCompare(b.text));

190
src/lib/codeberg.ts Normal file
View File

@ -0,0 +1,190 @@
interface ActivityData {
id: number
user_id: number
op_type: string
act_user_id: number
act_user: ActUser
repo_id: number
repo: Repo
comment_id: number
comment?: Comment
ref_name: string
is_private: boolean
content: string
created: string
}
interface ActUser {
id: number
login: string
login_name: string
source_id: number
full_name: string
email: string
avatar_url: string
html_url: string
language: string
is_admin: boolean
last_login: string
created: string
restricted: boolean
active: boolean
prohibit_login: boolean
location: string
pronouns: string
website: string
description: string
visibility: string
followers_count: number
following_count: number
starred_repos_count: number
username: string
}
interface Repo {
id: number
owner: Owner
name: string
full_name: string
description: string
empty: boolean
private: boolean
fork: boolean
template: boolean
parent: any
mirror: boolean
size: number
language: string
languages_url: string
html_url: string
url: string
link: string
ssh_url: string
clone_url: string
original_url: string
website: string
stars_count: number
forks_count: number
watchers_count: number
open_issues_count: number
open_pr_counter: number
release_counter: number
default_branch: string
archived: boolean
created_at: string
updated_at: string
archived_at: string
permissions: Permissions
has_issues: boolean
external_tracker: ExternalTracker
has_wiki: boolean
wiki_branch: string
globally_editable_wiki: boolean
has_pull_requests: boolean
has_projects: boolean
has_releases: boolean
has_packages: boolean
has_actions: boolean
ignore_whitespace_conflicts: boolean
allow_merge_commits: boolean
allow_rebase: boolean
allow_rebase_explicit: boolean
allow_squash_merge: boolean
allow_fast_forward_only_merge: boolean
allow_rebase_update: boolean
default_delete_branch_after_merge: boolean
default_merge_style: string
default_allow_maintainer_edit: boolean
avatar_url: string
internal: boolean
mirror_interval: string
object_format_name: string
mirror_updated: string
repo_transfer: any
topics: any
}
interface Owner {
id: number
login: string
login_name: string
source_id: number
full_name: string
email: string
avatar_url: string
html_url: string
language: string
is_admin: boolean
last_login: string
created: string
restricted: boolean
active: boolean
prohibit_login: boolean
location: string
pronouns: string
website: string
description: string
visibility: string
followers_count: number
following_count: number
starred_repos_count: number
username: string
}
interface Permissions {
admin: boolean
push: boolean
pull: boolean
}
interface ExternalTracker {
external_tracker_url: string
external_tracker_format: string
external_tracker_style: string
external_tracker_regexp_pattern: string
}
interface Comment {
id: number
html_url: string
pull_request_url: string
issue_url: string
user: any
original_author: string
original_author_id: number
body: string
assets: any[]
created_at: string
updated_at: string
}
class Codeberg {
private cache: ActivityData[];
private lastUpdate: Date | null = null;
constructor() {
this.cache = [];
}
private async refreshData(): Promise<Codeberg> {
const response = await fetch("https://codeberg.org/api/v1/users/naomi-lgbt/activities/feeds?only-performed-by=true");
const data = await response.json() as ActivityData[];
this.cache = data;
this.lastUpdate = new Date();
return this;
}
public async getActivities(): Promise<ActivityData[]> {
// Stale after 6 hours
if (this.lastUpdate && (new Date().getTime() - this.lastUpdate.getTime()) < 6 * 60 * 60 * 1000) {
return this.cache;
}
return this.refreshData().then(() => this.cache);
}
}
const instantiated = new Codeberg();
export const getCodebergData = async (): Promise<ActivityData[]> => {
return await instantiated.getActivities();
}

728
src/lib/github.ts Normal file
View File

@ -0,0 +1,728 @@
interface ActivityData {
id: string
type: string
actor: Actor
repo: Repo
payload: Payload
public: boolean
created_at: string
}
interface Actor {
id: number
login: string
display_login: string
gravatar_id: string
url: string
avatar_url: string
}
interface Repo {
id: number
name: string
url: string
}
interface Payload {
repository_id?: number
push_id?: number
size?: number
distinct_size?: number
ref?: string
head?: string
before?: string
commits?: Commit[]
ref_type?: string
pusher_type?: string
action?: string
number?: number
pull_request?: PullRequest
review?: Review
issue?: Issue2
comment?: Comment
}
interface Commit {
sha: string
author: Author
message: string
distinct: boolean
url: string
}
interface Author {
email: string
name: string
}
interface PullRequest {
url: string
id: number
node_id: string
html_url: string
diff_url: string
patch_url: string
issue_url: string
number: number
state: string
locked: boolean
title: string
user: User
body?: string
created_at: string
updated_at: string
closed_at?: string
merged_at?: string
merge_commit_sha: string
assignee: any
assignees: any[]
requested_reviewers: any[]
requested_teams: any[]
labels: Label[]
milestone: any
draft: boolean
commits_url: string
review_comments_url: string
review_comment_url: string
comments_url: string
statuses_url: string
head: Head
base: Base
_links: Links
author_association: string
auto_merge: any
active_lock_reason: any
merged?: boolean
mergeable: any
rebaseable: any
mergeable_state?: string
merged_by?: MergedBy
comments?: number
review_comments?: number
maintainer_can_modify?: boolean
commits?: number
additions?: number
deletions?: number
changed_files?: number
}
interface User {
login: string
id: number
node_id: string
avatar_url: string
gravatar_id: string
url: string
html_url: string
followers_url: string
following_url: string
gists_url: string
starred_url: string
subscriptions_url: string
organizations_url: string
repos_url: string
events_url: string
received_events_url: string
type: string
site_admin: boolean
}
interface Label {
id: number
node_id: string
url: string
name: string
color: string
default: boolean
description: string
}
interface Head {
label: string
ref: string
sha: string
user: User2
repo: Repo2
}
interface User2 {
login: string
id: number
node_id: string
avatar_url: string
gravatar_id: string
url: string
html_url: string
followers_url: string
following_url: string
gists_url: string
starred_url: string
subscriptions_url: string
organizations_url: string
repos_url: string
events_url: string
received_events_url: string
type: string
site_admin: boolean
}
interface Repo2 {
id: number
node_id: string
name: string
full_name: string
private: boolean
owner: Owner
html_url: string
description: string
fork: boolean
url: string
forks_url: string
keys_url: string
collaborators_url: string
teams_url: string
hooks_url: string
issue_events_url: string
events_url: string
assignees_url: string
branches_url: string
tags_url: string
blobs_url: string
git_tags_url: string
git_refs_url: string
trees_url: string
statuses_url: string
languages_url: string
stargazers_url: string
contributors_url: string
subscribers_url: string
subscription_url: string
commits_url: string
git_commits_url: string
comments_url: string
issue_comment_url: string
contents_url: string
compare_url: string
merges_url: string
archive_url: string
downloads_url: string
issues_url: string
pulls_url: string
milestones_url: string
notifications_url: string
labels_url: string
releases_url: string
deployments_url: string
created_at: string
updated_at: string
pushed_at: string
git_url: string
ssh_url: string
clone_url: string
svn_url: string
homepage: string
size: number
stargazers_count: number
watchers_count: number
language: string
has_issues: boolean
has_projects: boolean
has_downloads: boolean
has_wiki: boolean
has_pages: boolean
has_discussions: boolean
forks_count: number
mirror_url: any
archived: boolean
disabled: boolean
open_issues_count: number
license: License
allow_forking: boolean
is_template: boolean
web_commit_signoff_required: boolean
topics: string[]
visibility: string
forks: number
open_issues: number
watchers: number
default_branch: string
}
interface Owner {
login: string
id: number
node_id: string
avatar_url: string
gravatar_id: string
url: string
html_url: string
followers_url: string
following_url: string
gists_url: string
starred_url: string
subscriptions_url: string
organizations_url: string
repos_url: string
events_url: string
received_events_url: string
type: string
site_admin: boolean
}
interface License {
key: string
name: string
spdx_id: string
url: string
node_id: string
}
interface Base {
label: string
ref: string
sha: string
user: User3
repo: Repo3
}
interface User3 {
login: string
id: number
node_id: string
avatar_url: string
gravatar_id: string
url: string
html_url: string
followers_url: string
following_url: string
gists_url: string
starred_url: string
subscriptions_url: string
organizations_url: string
repos_url: string
events_url: string
received_events_url: string
type: string
site_admin: boolean
}
interface Repo3 {
id: number
node_id: string
name: string
full_name: string
private: boolean
owner: Owner2
html_url: string
description: string
fork: boolean
url: string
forks_url: string
keys_url: string
collaborators_url: string
teams_url: string
hooks_url: string
issue_events_url: string
events_url: string
assignees_url: string
branches_url: string
tags_url: string
blobs_url: string
git_tags_url: string
git_refs_url: string
trees_url: string
statuses_url: string
languages_url: string
stargazers_url: string
contributors_url: string
subscribers_url: string
subscription_url: string
commits_url: string
git_commits_url: string
comments_url: string
issue_comment_url: string
contents_url: string
compare_url: string
merges_url: string
archive_url: string
downloads_url: string
issues_url: string
pulls_url: string
milestones_url: string
notifications_url: string
labels_url: string
releases_url: string
deployments_url: string
created_at: string
updated_at: string
pushed_at: string
git_url: string
ssh_url: string
clone_url: string
svn_url: string
homepage: string
size: number
stargazers_count: number
watchers_count: number
language: string
has_issues: boolean
has_projects: boolean
has_downloads: boolean
has_wiki: boolean
has_pages: boolean
has_discussions: boolean
forks_count: number
mirror_url: any
archived: boolean
disabled: boolean
open_issues_count: number
license: License2
allow_forking: boolean
is_template: boolean
web_commit_signoff_required: boolean
topics: string[]
visibility: string
forks: number
open_issues: number
watchers: number
default_branch: string
}
interface Owner2 {
login: string
id: number
node_id: string
avatar_url: string
gravatar_id: string
url: string
html_url: string
followers_url: string
following_url: string
gists_url: string
starred_url: string
subscriptions_url: string
organizations_url: string
repos_url: string
events_url: string
received_events_url: string
type: string
site_admin: boolean
}
interface License2 {
key: string
name: string
spdx_id: string
url: string
node_id: string
}
interface Links {
self: Self
html: Html
issue: Issue
comments: Comments
review_comments: ReviewComments
review_comment: ReviewComment
commits: Commits
statuses: Statuses
}
interface Self {
href: string
}
interface Html {
href: string
}
interface Issue {
href: string
}
interface Comments {
href: string
}
interface ReviewComments {
href: string
}
interface ReviewComment {
href: string
}
interface Commits {
href: string
}
interface Statuses {
href: string
}
interface MergedBy {
login: string
id: number
node_id: string
avatar_url: string
gravatar_id: string
url: string
html_url: string
followers_url: string
following_url: string
gists_url: string
starred_url: string
subscriptions_url: string
organizations_url: string
repos_url: string
events_url: string
received_events_url: string
type: string
site_admin: boolean
}
interface Review {
id: number
node_id: string
user: User4
body: any
commit_id: string
submitted_at: string
state: string
html_url: string
pull_request_url: string
author_association: string
_links: Links2
}
interface User4 {
login: string
id: number
node_id: string
avatar_url: string
gravatar_id: string
url: string
html_url: string
followers_url: string
following_url: string
gists_url: string
starred_url: string
subscriptions_url: string
organizations_url: string
repos_url: string
events_url: string
received_events_url: string
type: string
site_admin: boolean
}
interface Links2 {
html: Html2
pull_request: PullRequest2
}
interface Html2 {
href: string
}
interface PullRequest2 {
href: string
}
interface Issue2 {
url: string
repository_url: string
labels_url: string
comments_url: string
events_url: string
html_url: string
id: number
node_id: string
number: number
title: string
user: User5
labels: Label2[]
state: string
locked: boolean
assignee: any
assignees: any[]
milestone: any
comments: number
created_at: string
updated_at: string
closed_at?: string
author_association: string
active_lock_reason: any
body: string
reactions: Reactions
timeline_url: string
performed_via_github_app: any
state_reason?: string
}
interface User5 {
login: string
id: number
node_id: string
avatar_url: string
gravatar_id: string
url: string
html_url: string
followers_url: string
following_url: string
gists_url: string
starred_url: string
subscriptions_url: string
organizations_url: string
repos_url: string
events_url: string
received_events_url: string
type: string
site_admin: boolean
}
interface Label2 {
id: number
node_id: string
url: string
name: string
color: string
default: boolean
description: string
}
interface Reactions {
url: string
total_count: number
"+1": number
"-1": number
laugh: number
hooray: number
confused: number
heart: number
rocket: number
eyes: number
}
interface Comment {
url: string
html_url: string
issue_url?: string
id: number
node_id: string
user: User6
created_at: string
updated_at: string
author_association: string
body: string
reactions: Reactions2
performed_via_github_app: any
pull_request_review_id?: number
diff_hunk?: string
path?: string
commit_id?: string
original_commit_id?: string
pull_request_url?: string
_links?: Links3
start_line?: number
original_start_line?: number
start_side?: string
line?: number
original_line?: number
side?: string
in_reply_to_id?: number
original_position?: number
position?: number
subject_type?: string
}
interface User6 {
login: string
id: number
node_id: string
avatar_url: string
gravatar_id: string
url: string
html_url: string
followers_url: string
following_url: string
gists_url: string
starred_url: string
subscriptions_url: string
organizations_url: string
repos_url: string
events_url: string
received_events_url: string
type: string
site_admin: boolean
}
interface Reactions2 {
url: string
total_count: number
"+1": number
"-1": number
laugh: number
hooray: number
confused: number
heart: number
rocket: number
eyes: number
}
interface Links3 {
self: Self2
html: Html3
pull_request: PullRequest3
}
interface Self2 {
href: string
}
interface Html3 {
href: string
}
interface PullRequest3 {
href: string
}
interface Org {
id: number
login: string
gravatar_id: string
url: string
avatar_url: string
}
class Github {
private cache: ActivityData[];
private lastUpdate: Date | null = null;
constructor() {
this.cache = [];
}
private async refreshData(): Promise<Github> {
const response = await fetch("https://api.github.com/users/naomi-lgbt/events");
const data = await response.json() as ActivityData[];
this.cache = data;
this.lastUpdate = new Date();
return this;
}
public async getActivities(): Promise<ActivityData[]> {
// Stale after 1 hour
if (this.lastUpdate && (new Date().getTime() - this.lastUpdate.getTime()) < 60 * 60 * 1000) {
return this.cache;
}
return this.refreshData().then(() => this.cache);
}
}
const instantiated = new Github();
export const getGithubData = async (): Promise<ActivityData[]> => {
return await instantiated.getActivities();
}