diff --git a/.gitignore b/.gitignore index f721e1a..ae79ead 100644 --- a/.gitignore +++ b/.gitignore @@ -5,7 +5,7 @@ .nitro .cache dist -server/mocks +server/cache # Node dependencies node_modules diff --git a/bun.lockb b/bun.lockb index 4c05bea..10c2b4b 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/package.json b/package.json index 2ec5d19..22c8b84 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,7 @@ "generate": "nuxt generate", "preview": "nuxt preview", "postinstall": "nuxt prepare", - "mock": "node ./server/mocker.js" + "cache": "bun ./server/utils/cache-builder" }, "author": "Evren Ceyhan", "license": "MIT", @@ -26,6 +26,7 @@ "@pinia/nuxt": "^0.5.1", "@types/node": "^20.9.0", "bootstrap-icons": "^1.11.1", + "bun-types": "^1.0.15", "daisyui": "^4.0.3", "nuxt": "^3.8.2", "pinia": "^2.1.7", diff --git a/server/api/languages.ts b/server/api/languages.ts index 3e1c9a0..c9637c1 100644 --- a/server/api/languages.ts +++ b/server/api/languages.ts @@ -1,4 +1,4 @@ -import { Language, Repo } from '../types/repo'; +import type { Language, Repo } from '../types/repo'; export default defineEventHandler(async (event) => { // try to get cached data @@ -8,7 +8,7 @@ export default defineEventHandler(async (event) => { if (cache.value) return cache.value; // fetch fresh data - const repos = await $fetch('/api/repos'); + const repos = await $fetch('/api/repos'); const languages = collectLanguages(repos); // save fresh data to cache diff --git a/server/api/profile.ts b/server/api/profile.ts index aa0dc65..02597f0 100644 --- a/server/api/profile.ts +++ b/server/api/profile.ts @@ -1,5 +1,5 @@ import { API_USERNAME } from '../constants/github'; -import { Profile } from '../types/profile'; +import type { Profile } from '../types/profile'; export default defineEventHandler(async (event) => { // try to get cached data diff --git a/server/api/repos.ts b/server/api/repos.ts index 6dbfdc0..c4bb5b8 100644 --- a/server/api/repos.ts +++ b/server/api/repos.ts @@ -1,6 +1,6 @@ import { API_USERNAME } from '../constants/github'; import { COLORS } from '../constants/language'; -import { Language, Repo } from '../types/repo'; +import type { Language, Repo } from '../types/repo'; export default defineEventHandler(async (event) => { // try to get cached data diff --git a/server/api/social-links.ts b/server/api/social-links.ts index 39931f7..7ae6089 100644 --- a/server/api/social-links.ts +++ b/server/api/social-links.ts @@ -1,5 +1,5 @@ import { LINKS } from '../constants/social'; -import { SocialLink } from '../types/social'; +import type { SocialLink } from '../types/social'; export default defineEventHandler((event) => { return LINKS.filter(havingId).map(normalizeUrl); diff --git a/server/api/topics.ts b/server/api/topics.ts index 419088c..388e888 100644 --- a/server/api/topics.ts +++ b/server/api/topics.ts @@ -1,5 +1,5 @@ import { CATEGORIES } from '../constants/topic'; -import { Repo, Topic } from '../types/repo'; +import type { Repo, Topic } from '../types/repo'; export default defineEventHandler(async (event) => { // try to get cached data @@ -9,7 +9,7 @@ export default defineEventHandler(async (event) => { if (cache.value) return cache.value; // fetch fresh data - const repos = await $fetch('/api/repos'); + const repos = await $fetch('/api/repos'); const topics = collectTopics(repos); // save fresh data to cache diff --git a/server/mocker.js b/server/mocker.js deleted file mode 100644 index 0217bd6..0000000 --- a/server/mocker.js +++ /dev/null @@ -1,111 +0,0 @@ -/** - * This is a server side script to generate mock data, it's not part of the app, - * It's only used to generate mock data for the DEV environment! - * - * Github has a rate limit of 60 requests per hour for unauthenticated requests, - * so we need to use a personal access token to increase the limit to 5000 requests per hour. - */ - -import { - readFileSync, - writeFileSync, - existsSync, - mkdirSync, - rmSync, -} from 'fs'; -import { dirname } from 'path'; -import { fileURLToPath } from 'url'; -import dotenv from 'dotenv'; - -// load env vars -dotenv.config(); - -const API_URL = 'https://api.github.com'; -const API_TOKEN = process.env.GITHUB_API_TOKEN; -const API_USERNAME = process.env.GITHUB_API_USERNAME; - -// __dirname is not available in ES6 modules -// https://stackoverflow.com/questions/46745014/using-dirname-in-es6-modules -const __filename = fileURLToPath(import.meta.url); -const __dirname = dirname(__filename); -const MOCKS_DIR = __dirname + '/mocks'; - -// GENERATORS ////////////////////////////////////////////////////////////////////////////////////// - -const generateProfile = async () => { - console.log('generating profile'); - - const data = await fetchApi(`/users/${API_USERNAME}`); - writeMockFile('profile', data); -}; - -const generateRepos = async () => { - console.log('generating repos'); - - const data = await fetchApi(`/users/${API_USERNAME}/repos`, { - per_page: 100, - sort: 'updated', - }); - - writeMockFile('repos', data); -}; - -const generateRepoLanguages = async () => { - console.log('generating repo languages'); - - const repos = readMockFile('repos'); - - repos.forEach(async ({ name }) => { - const data = await fetchApi(`/repos/${API_USERNAME}/${name}/languages`); - writeMockFile(`${name}-languages`, data); - }); -}; - -// HELPERS ///////////////////////////////////////////////////////////////////////////////////////// - -const fetchApi = async (path, query = {}) => { - // create url object - const url = new URL(API_URL + path); - - // add query params - Object.entries(query).forEach(([key, value]) => { - url.searchParams.append(key, value); - }); - - const response = await fetch(url, { - headers: { Authorization: `Bearer ${API_TOKEN}` }, - }); - - return await response.json(); -}; - -const readMockFile = (name) => { - const filename = `${MOCKS_DIR}/${name}.json`; - const jsonData = readFileSync(filename); - - return JSON.parse(jsonData); -}; - -const writeMockFile = (name, data) => { - const filename = `${MOCKS_DIR}/${name}.json`; - const jsonData = JSON.stringify(data, null, 2); - - writeFileSync(filename, jsonData); -}; - -const ensureMocksDir = () => { - existsSync(MOCKS_DIR) || mkdirSync(MOCKS_DIR); -}; - -const purgeMocksDir = () => { - existsSync(MOCKS_DIR) && rmSync(MOCKS_DIR, { recursive: true }); -}; - -// RUN ///////////////////////////////////////////////////////////////////////////////////////////// - -purgeMocksDir(); -ensureMocksDir(); - -await generateProfile(); -await generateRepos(); -await generateRepoLanguages(); diff --git a/server/tsconfig.json b/server/tsconfig.json index b9ed69c..2f54888 100644 --- a/server/tsconfig.json +++ b/server/tsconfig.json @@ -1,3 +1,6 @@ { - "extends": "../.nuxt/tsconfig.server.json" + "extends": "../.nuxt/tsconfig.server.json", + "compilerOptions": { + "types": ["bun-types"] + } } diff --git a/server/types/social.ts b/server/types/social.ts index bd0a0a1..7157766 100644 --- a/server/types/social.ts +++ b/server/types/social.ts @@ -1,5 +1,5 @@ -export type SocialLink = { +export interface SocialLink { id?: string; url: string; icon: string; -}; \ No newline at end of file +} diff --git a/server/utils/cache-builder.ts b/server/utils/cache-builder.ts new file mode 100644 index 0000000..75673b0 --- /dev/null +++ b/server/utils/cache-builder.ts @@ -0,0 +1,56 @@ +import { existsSync, mkdirSync, rmSync } from 'fs'; +import { API_HEADERS, API_URL, API_USERNAME } from '../constants/github'; + +/** + * This is a server side script to cache data. + * It's only used to generate mock data for the DEV environment! + * + * Github has a rate limit of 60 requests per hour for unauthenticated requests, + * so we need to use a personal access token to increase the limit to 5000 requests per hour. + */ + +const DIR = import.meta.dir; +const CACHE_DIR = `${DIR}/../cache`; + +async function fetchApi(path: string, query: any = {}): Promise { + // create url object + const url = new URL(API_URL + path); + + // add query params + Object.entries(query).forEach(([key, value]) => { + url.searchParams.append(key, value as any); + }); + + return (await fetch(url, { headers: API_HEADERS })).json() as any; +} + +function writeToCacheFile(path: string, data: any): void { + const filename = `${CACHE_DIR}/${path}.json`; + const jsonData = JSON.stringify(data, null, 4); + + console.log(`Writing to cache file: ${path}.json`); + Bun.write(filename, jsonData); +} + +// Create cache directory if it doesn't exist +existsSync(CACHE_DIR) || mkdirSync(CACHE_DIR); + +// Purge existing cache directory +rmSync(`${CACHE_DIR}/*`, { force: true }); + +// Fetch and cache user profile +const profile = await fetchApi(`/users/${API_USERNAME}`); +writeToCacheFile('profile', profile); + +// Fetch and cache repositories +const repos = await fetchApi(`/users/${API_USERNAME}/repos`, { + per_page: 100, + sort: 'updated', +}); +writeToCacheFile('repos', repos); + +// Fetch and cache languages for each repository +repos.forEach(({ name }) => { + const data = fetchApi(`/repos/${API_USERNAME}/${name}/languages`); + writeToCacheFile(`${name}-languages`, data); +}); diff --git a/tailwind.config.js b/tailwind.config.js deleted file mode 100644 index d0d1f4e..0000000 --- a/tailwind.config.js +++ /dev/null @@ -1,7 +0,0 @@ -export default { - plugins: [require('daisyui')], - daisyui: { - themes: ['night'], - darkTheme: 'night', - }, -}; diff --git a/tailwind.config.ts b/tailwind.config.ts new file mode 100644 index 0000000..4b1aa6b --- /dev/null +++ b/tailwind.config.ts @@ -0,0 +1,14 @@ +import type { Config } from 'tailwindcss'; +import daisyui from 'daisyui'; + +export default { + content: [], + theme: { + extend: {}, + }, + plugins: [daisyui], + daisyui: { + themes: ['night'], + darkTheme: 'night', + }, +} satisfies Config;