From 7c185dd45376d75a09ef02f7662eced2cfd04120 Mon Sep 17 00:00:00 2001 From: Lucien Cartier-Tilet Date: Thu, 4 May 2023 23:21:43 +0200 Subject: [PATCH] feat: add bases for GitHub API interaction and cache --- .editorconfig | 2 +- content/.vuepress/client.ts | 2 + .../components/LatestRepositories.vue | 36 +++++ content/.vuepress/composables/cache.ts | 46 ++++++ content/.vuepress/composables/github.ts | 139 ++++++++++++++++++ content/projects.md | 2 + package.json | 1 + yarn.lock | 7 + 8 files changed, 234 insertions(+), 1 deletion(-) create mode 100644 content/.vuepress/components/LatestRepositories.vue create mode 100644 content/.vuepress/composables/cache.ts create mode 100644 content/.vuepress/composables/github.ts diff --git a/.editorconfig b/.editorconfig index bd45cca..d664d14 100644 --- a/.editorconfig +++ b/.editorconfig @@ -11,5 +11,5 @@ trim_trailing_whitespace = true indent_style = space indent_size = 2 -[*.ts] +[*.{ts,vue}] quote_type = single diff --git a/content/.vuepress/client.ts b/content/.vuepress/client.ts index abf3254..8de67b6 100644 --- a/content/.vuepress/client.ts +++ b/content/.vuepress/client.ts @@ -1,11 +1,13 @@ import { defineClientConfig } from '@vuepress/client'; import PreviewImage from './components/PreviewImage.vue'; import ResponsiveImage from './components/ResponsiveImage.vue'; +import LatestRepositories from './components/LatestRepositories.vue'; export default defineClientConfig({ enhance({ app, router, siteData }) { app.component('PreviewImage', PreviewImage); app.component('ResponsiveImage', ResponsiveImage); + app.component('LatestRepositories', LatestRepositories); }, setup() {}, layouts: {}, diff --git a/content/.vuepress/components/LatestRepositories.vue b/content/.vuepress/components/LatestRepositories.vue new file mode 100644 index 0000000..04eeca8 --- /dev/null +++ b/content/.vuepress/components/LatestRepositories.vue @@ -0,0 +1,36 @@ + + + + + diff --git a/content/.vuepress/composables/cache.ts b/content/.vuepress/composables/cache.ts new file mode 100644 index 0000000..2304b6c --- /dev/null +++ b/content/.vuepress/composables/cache.ts @@ -0,0 +1,46 @@ +import { Observable, of } from 'rxjs'; + +const cacheAgeLimitInMilliseconds = 1000 * 60 * 60; + +export function isDataOutdated(name: string): boolean { + const lastUpdated: number = +localStorage.getItem(name + '-timestamp'); + const now: number = Date.now(); + const elapsedTime: number = now - lastUpdated; + return elapsedTime > cacheAgeLimitInMilliseconds; +} + +export function storeInCache( + data: Observable, + name: string +): Observable { + data.subscribe({ + next: (response: T) => { + localStorage.setItem(name, JSON.stringify(response)); + localStorage.setItem(name + '-timestamp', `${Date.now()}`); + }, + }); + return data; +} + +export function readFromCache( + name: string, + callback: () => Observable +): Observable { + let data: Observable; + if (isDataOutdated(name)) { + data = storeInCache(callback(), name); + } else { + let dataFromCache = localStorage.getItem(name); + try { + data = of(JSON.parse(dataFromCache)); + } catch (err) { + console.error( + `Could not parse ${JSON.stringify( + dataFromCache + )}: ${err}. Fetching again data from callback function.` + ); + data = storeInCache(callback(), name); + } + } + return data; +} diff --git a/content/.vuepress/composables/github.ts b/content/.vuepress/composables/github.ts new file mode 100644 index 0000000..d531fb9 --- /dev/null +++ b/content/.vuepress/composables/github.ts @@ -0,0 +1,139 @@ +import { Observable, switchMap, map } from 'rxjs'; +import { fromFetch } from 'rxjs/fetch'; + +export interface GithubRepo { + 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: Date; + updated_at: Date; + pushed_at: Date; + 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; + forks_count: number; + mirror_url: null; + archived: boolean; + disabled: boolean; + open_issues_count: number; + license: null; + allow_forking: boolean; + is_template: boolean; + web_commit_signoff_required: boolean; + topics: any[]; + visibility: string; + forks: number; + open_issues: number; + watchers: number; + default_branch: string; +} + +export 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; +} + +export interface GithubError { + message: string; + documentation_url: string; +} + +export function getLatestRepositories( + user: string, + amount: number +): Observable { + return getRepositoriesOfUser(user).pipe( + map((repositories: GithubRepo[]) => { + return repositories + .sort( + (a: GithubRepo, b: GithubRepo) => + +b.updated_at - +a.updated_at + ) + .slice(0, amount); + }) + ); +} + +export function getRepositoriesOfUser(user: string): Observable { + const fetchUrl = `https://api.github.com/users/${user}/repos`; + return fromFetch(fetchUrl).pipe( + switchMap((response: Response) => { + if (response.ok) { + return response.json(); + } else { + console.error(`Error ${response.status}: ${JSON.stringify(response)}`); + return []; + } + }), + ); +} diff --git a/content/projects.md b/content/projects.md index 2cb84de..b6ea487 100644 --- a/content/projects.md +++ b/content/projects.md @@ -3,5 +3,7 @@ title: Projets --- # Programmation ## Projets GitHub les plus étoilés + + ## Derniers dépôts de code actifs sur GitHub # Linguistique diff --git a/package.json b/package.json index 369c59f..45ad962 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,7 @@ "build": "vuepress build content" }, "dependencies": { + "rxjs": "^7.8.1", "vuepress-plugin-remove-html-extension": "^0.1.0" }, "config": { diff --git a/yarn.lock b/yarn.lock index e320c9a..e0db420 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2323,6 +2323,13 @@ rxjs@^7.5.5: dependencies: tslib "^2.1.0" +rxjs@^7.8.1: + version "7.8.1" + resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-7.8.1.tgz#6f6f3d99ea8044291efd92e7c7fcf562c4057543" + integrity sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg== + dependencies: + tslib "^2.1.0" + safe-buffer@~5.2.0: version "5.2.1" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6"