diff --git a/src/lib/components/footer/index.svelte b/src/lib/components/footer/index.svelte index ee9b420..73f0bb3 100644 --- a/src/lib/components/footer/index.svelte +++ b/src/lib/components/footer/index.svelte @@ -31,6 +31,7 @@ background: var(--footerBackground); place-content: center; place-items: center; + margin-top: 5rem; padding: 2rem; @media (max-width: 800px) { diff --git a/src/lib/types/article.ts b/src/lib/types/article.ts new file mode 100644 index 0000000..3ef8156 --- /dev/null +++ b/src/lib/types/article.ts @@ -0,0 +1,11 @@ +export type Article { + tags: string[]; + title: string; + url: URL; + hashed_url: string; + reading_time: number; + preview_picture: string; + created_at: Date; + updated_at: Date; + archived_at: Date | null; +} \ No newline at end of file diff --git a/src/lib/types/pageQuery.ts b/src/lib/types/pageQuery.ts new file mode 100644 index 0000000..f03201a --- /dev/null +++ b/src/lib/types/pageQuery.ts @@ -0,0 +1,5 @@ +export type PageQuery { + sort: string; + perPage: number; + since: number; +} \ No newline at end of file diff --git a/src/lib/util/cookieUtils.ts b/src/lib/util/cookieUtils.ts new file mode 100644 index 0000000..261342a --- /dev/null +++ b/src/lib/util/cookieUtils.ts @@ -0,0 +1,17 @@ +export function getCookieLookup() { + if (typeof document !== 'object') { + return {}; + } + + return document.cookie.split('; ').reduce((lookup, v) => { + const parts = v.split('='); + lookup[parts[0]] = parts[1]; + + return lookup; + }, {}); +} + +export const getCurrentCookieValue = (name) => { + const cookies = getCookieLookup(); + return cookies[name] ?? ''; +}; diff --git a/src/routes/+layout.server.ts b/src/routes/+layout.server.ts new file mode 100644 index 0000000..2ceb7d5 --- /dev/null +++ b/src/routes/+layout.server.ts @@ -0,0 +1,25 @@ +import { error, type ServerLoad } from '@sveltejs/kit'; +import { fetchArticlesApi } from './api'; +// import type { PageServerLoad } from './$types'; + +export const load: ServerLoad = async ({ isDataRequest, cookies, params, setHeaders }) => { + const queryParams = { + // ids: `${params?.id}`, + // fields: + // 'id,name,price,min_age,min_players,max_players,thumb_url,playtime,min_playtime,max_playtime,min_age,description,year_published,url,image_url' + }; + + const initialRequest = !isDataRequest; + console.log(`Is initialRequest: ${initialRequest}`); + + const cacheValue = `${initialRequest ? +new Date() : cookies.get('articles-cache')}`; + console.log(`Cache Value: ${cacheValue}`); + + if (initialRequest) { + cookies.set('articles-cache', cacheValue, { path: '/', httpOnly: false }); + } + + return { + articlesCacheBust: cacheValue + }; +}; diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte index 495604d..1ee18b9 100644 --- a/src/routes/+layout.svelte +++ b/src/routes/+layout.svelte @@ -61,7 +61,7 @@ max-width: 850px; margin: 0 auto; padding: 2rem 0rem; - max-width: 80vw; + /* max-width: 80vw; */ @media (min-width: 1600px) { max-width: 70vw; diff --git a/src/routes/api.ts b/src/routes/api.ts index 5022182..1c608f1 100644 --- a/src/routes/api.ts +++ b/src/routes/api.ts @@ -5,6 +5,8 @@ import { WALLABAG_PASSWORD, WALLABAG_URL } from '$env/static/private'; +import type { Article } from '$root/lib/types/article'; +import type { PageQuery } from '$root/lib/types/pageQuery'; import { URLSearchParams } from 'url'; const base: string = WALLABAG_URL; @@ -15,7 +17,7 @@ export async function fetchArticlesApi( queryParams: Record, data?: Record ) { - let lastFetched = null; + let lastFetched: Date | null = null; const authBody = { grant_type: 'password', @@ -33,10 +35,13 @@ export async function fetchArticlesApi( const auth = await authResponse.json(); - const pageQuery = { + const pageQuery: PageQuery = { sort: 'updated', - perPage: 500 + perPage: 6, + since: 0 }; + const entriesQueryParams = new URLSearchParams(pageQuery); + console.log(`Entries params: ${entriesQueryParams}`); if (lastFetched) { pageQuery.since = Math.round(lastFetched / 1000); @@ -44,45 +49,43 @@ export async function fetchArticlesApi( lastFetched = new Date(); - let nbEntries = 0; - const pageResponse = await fetch( - `${WALLABAG_URL}/api/entries.json?${new URLSearchParams(pageQuery)}`, - { - method: 'GET', - headers: { - Authorization: `Bearer ${auth.access_token}` - } + const nbEntries = 0; + const pageResponse = await fetch(`${WALLABAG_URL}/api/entries.json?${entriesQueryParams}`, { + method: 'GET', + headers: { + Authorization: `Bearer ${auth.access_token}` } - ); + }); if (!pageResponse.ok) { throw new Error(pageResponse.statusText); } - let entries = await pageResponse.json(); - const articles = []; + const entries = await pageResponse.json(); + const articles: Article[] = []; - do { - nbEntries += entries._embedded.items.length; - console.log(`number of articles fetched: ${nbEntries}`); - entries._embedded.items.forEach((article) => { - article.created_at = new Date(article.created_at); - article.updated_at = new Date(article.updated_at); - article.archived_at = article.archived_at ? new Date(article.archived_at) : null; - articles.push(article); - }); + // do { + // nbEntries += entries._embedded.items.length; + console.log(`number of articles fetched: ${entries._embedded.items.length}`); + entries._embedded.items.forEach((article: Article) => { + article.created_at = new Date(article.created_at); + article.updated_at = new Date(article.updated_at); + article.archived_at = article.archived_at ? new Date(article.archived_at) : null; + articles.push(article); + }); - if (!entries._links.next) { - return; - } - const response = await fetch(entries._links.next.href, { - method: 'GET', - headers: { - Authorization: `Bearer ${auth.access_token}` - } - }); - entries = await response.json(); - } while (entries._links.next); + // if (!entries._links.next) { + // return; + // } + // console.log(`Links next ${JSON.stringify(entries._links.next)}`); + // const response = await fetch(entries._links.next.href, { + // method: 'GET', + // headers: { + // Authorization: `Bearer ${auth.access_token}` + // } + // }); + // entries = await response.json(); + // } while (entries._links.next); return { articles }; } diff --git a/src/routes/api/articles/+server.ts b/src/routes/api/articles/+server.ts new file mode 100644 index 0000000..5c8c89a --- /dev/null +++ b/src/routes/api/articles/+server.ts @@ -0,0 +1,28 @@ +import { json } from '@sveltejs/kit'; +import type { RequestHandler, RequestEvent } from './$types'; +import { fetchArticlesApi } from '$root/routes/api'; + +export const GET: RequestHandler = async ({ url, setHeaders, request, params }: RequestEvent) => { + try { + const response = await fetchArticlesApi('get', `search`, {}); + + if (response?.articles) { + setHeaders({ + 'cache-control': 'max-age=604800' + }); + + const articlesResponse = response.articles; + console.log(`Found articles ${articlesResponse.length}`); + const articles = []; + + for (const article of articlesResponse) { + const { tags, title, url, hashed_url, reading_time, preview_picture } = article; + articles.push({ tags, title, url, hashed_url, reading_time, preview_picture }); + } + + return json(articles); + } + } catch (error) { + console.error(error); + } +}; diff --git a/src/routes/articles/+page.server.ts b/src/routes/articles/+page.server.ts deleted file mode 100644 index 1e928ad..0000000 --- a/src/routes/articles/+page.server.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { error } from '@sveltejs/kit'; -import { fetchArticlesApi } from '../api'; -import type { PageServerLoad } from './$types'; - -export const load: PageServerLoad = async ({ params, setHeaders }) => { - const queryParams = { - // ids: `${params?.id}`, - // fields: - // 'id,name,price,min_age,min_players,max_players,thumb_url,playtime,min_playtime,max_playtime,min_age,description,year_published,url,image_url' - }; - - try { - const response = await fetchArticlesApi('get', `search`, queryParams); - - if (response?.articles) { - // const gameResponse = await response.json(); - - setHeaders({ - 'Cache-Control': 'max-age=3600' - }); - - const articles = response.articles; - console.log(`Found articles ${articles.length}`); - - return {}; - } - } catch (error) { - console.error(error); - } - - throw error(500, 'error'); -}; diff --git a/src/routes/articles/+page.svelte b/src/routes/articles/+page.svelte index 1e36813..65944b1 100644 --- a/src/routes/articles/+page.svelte +++ b/src/routes/articles/+page.svelte @@ -1,10 +1,118 @@ + +
+

Favorite Tech Articles

+ +
+ {#each articles as article} +
+
+

+ + {article.title} + +

+
+
+

Reading time: {article.reading_time} minutes

+
+

Tags:

+ {#each article.tags as tag} +

{tag.label}

+ {/each} +
+
+
+ {/each} +
+ +
+ + \ No newline at end of file diff --git a/src/routes/articles/+page.ts b/src/routes/articles/+page.ts new file mode 100644 index 0000000..28d102e --- /dev/null +++ b/src/routes/articles/+page.ts @@ -0,0 +1,17 @@ +import { getCurrentCookieValue } from '$lib/util/cookieUtils'; +import type { Article } from '$root/lib/types/article'; +import type { PageLoad } from './$types'; + +export const load: PageLoad = async ({ fetch, parent, url, setHeaders }) => { + const parentData = await parent(); + + const cacheBust = getCurrentCookieValue('articles-cache') || parentData.articlesCacheBust; + const search = url.searchParams.get('search') || ''; + + const resp = await fetch(`/api/articles?cache=${cacheBust}`); + const articles: Article[] = await resp.json(); + + return { + articles + }; +};