mirror of
https://github.com/BradNut/personal-website-sveltekit
synced 2025-09-08 23:20:18 +00:00
commit
7a0ff588a8
7 changed files with 122 additions and 33 deletions
|
|
@ -54,5 +54,8 @@
|
|||
"vite-imagetools": "^4.0.18",
|
||||
"vitest": "^0.25.3"
|
||||
},
|
||||
"type": "module"
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"ioredis": "^5.3.1"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ specifiers:
|
|||
eslint-config-prettier: ^8.5.0
|
||||
eslint-plugin-svelte3: ^4.0.0
|
||||
iconify-icon: ^1.0.5
|
||||
ioredis: ^5.3.1
|
||||
just-intersect: ^4.3.0
|
||||
postcss: ^8.4.21
|
||||
postcss-import: ^15.1.0
|
||||
|
|
@ -41,6 +42,9 @@ specifiers:
|
|||
vite-imagetools: ^4.0.18
|
||||
vitest: ^0.25.3
|
||||
|
||||
dependencies:
|
||||
ioredis: 5.3.1
|
||||
|
||||
devDependencies:
|
||||
'@iconify-icons/material-symbols': 1.2.27
|
||||
'@iconify-icons/mdi': 1.2.41
|
||||
|
|
@ -589,6 +593,10 @@ packages:
|
|||
resolution: {integrity: sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==}
|
||||
dev: true
|
||||
|
||||
/@ioredis/commands/1.2.0:
|
||||
resolution: {integrity: sha512-Sx1pU8EM64o2BrqNpEO1CNLtKQwyhuXuqyfH7oGKCk+1a33d2r5saW8zNwm3j6BTExtjrv2BxTgzzkMwts6vGg==}
|
||||
dev: false
|
||||
|
||||
/@jridgewell/resolve-uri/3.1.0:
|
||||
resolution: {integrity: sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==}
|
||||
engines: {node: '>=6.0.0'}
|
||||
|
|
@ -1338,6 +1346,11 @@ packages:
|
|||
engines: {node: '>=10'}
|
||||
dev: true
|
||||
|
||||
/cluster-key-slot/1.1.2:
|
||||
resolution: {integrity: sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
dev: false
|
||||
|
||||
/color-convert/2.0.1:
|
||||
resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==}
|
||||
engines: {node: '>=7.0.0'}
|
||||
|
|
@ -1507,7 +1520,6 @@ packages:
|
|||
optional: true
|
||||
dependencies:
|
||||
ms: 2.1.2
|
||||
dev: true
|
||||
|
||||
/decompress-response/6.0.0:
|
||||
resolution: {integrity: sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==}
|
||||
|
|
@ -1553,6 +1565,11 @@ packages:
|
|||
resolution: {integrity: sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==}
|
||||
dev: true
|
||||
|
||||
/denque/2.1.0:
|
||||
resolution: {integrity: sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==}
|
||||
engines: {node: '>=0.10'}
|
||||
dev: false
|
||||
|
||||
/detect-indent/6.1.0:
|
||||
resolution: {integrity: sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA==}
|
||||
engines: {node: '>=8'}
|
||||
|
|
@ -2253,6 +2270,23 @@ packages:
|
|||
resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==}
|
||||
dev: true
|
||||
|
||||
/ioredis/5.3.1:
|
||||
resolution: {integrity: sha512-C+IBcMysM6v52pTLItYMeV4Hz7uriGtoJdz7SSBDX6u+zwSYGirLdQh3L7t/OItWITcw3gTFMjJReYUwS4zihg==}
|
||||
engines: {node: '>=12.22.0'}
|
||||
dependencies:
|
||||
'@ioredis/commands': 1.2.0
|
||||
cluster-key-slot: 1.1.2
|
||||
debug: 4.3.4
|
||||
denque: 2.1.0
|
||||
lodash.defaults: 4.2.0
|
||||
lodash.isarguments: 3.1.0
|
||||
redis-errors: 1.2.0
|
||||
redis-parser: 3.0.0
|
||||
standard-as-callback: 2.1.0
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
dev: false
|
||||
|
||||
/is-arrayish/0.3.2:
|
||||
resolution: {integrity: sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==}
|
||||
dev: true
|
||||
|
|
@ -2433,6 +2467,14 @@ packages:
|
|||
p-locate: 5.0.0
|
||||
dev: true
|
||||
|
||||
/lodash.defaults/4.2.0:
|
||||
resolution: {integrity: sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==}
|
||||
dev: false
|
||||
|
||||
/lodash.isarguments/3.1.0:
|
||||
resolution: {integrity: sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg==}
|
||||
dev: false
|
||||
|
||||
/lodash.merge/4.6.2:
|
||||
resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==}
|
||||
dev: true
|
||||
|
|
@ -2582,7 +2624,6 @@ packages:
|
|||
|
||||
/ms/2.1.2:
|
||||
resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==}
|
||||
dev: true
|
||||
|
||||
/nanoid/3.3.4:
|
||||
resolution: {integrity: sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==}
|
||||
|
|
@ -3328,6 +3369,18 @@ packages:
|
|||
picomatch: 2.3.1
|
||||
dev: true
|
||||
|
||||
/redis-errors/1.2.0:
|
||||
resolution: {integrity: sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w==}
|
||||
engines: {node: '>=4'}
|
||||
dev: false
|
||||
|
||||
/redis-parser/3.0.0:
|
||||
resolution: {integrity: sha512-DJnGAeenTdpMEH6uAJRK/uiyEIH9WVsUmoLwzudwGJUwZPp80PDBWPHXSAGNPwNvIXAbe7MSUB1zQFugFml66A==}
|
||||
engines: {node: '>=4'}
|
||||
dependencies:
|
||||
redis-errors: 1.2.0
|
||||
dev: false
|
||||
|
||||
/regex-escape/3.4.10:
|
||||
resolution: {integrity: sha512-qEqf7uzW+iYcKNLMDFnMkghhQBnGdivT6KqVQyKsyjSWnoFyooXVnxrw9dtv3AFLnD6VBGXxtZGAQNFGFTnCqA==}
|
||||
dev: true
|
||||
|
|
@ -3620,6 +3673,10 @@ packages:
|
|||
dev: true
|
||||
optional: true
|
||||
|
||||
/standard-as-callback/2.1.0:
|
||||
resolution: {integrity: sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A==}
|
||||
dev: false
|
||||
|
||||
/streamsearch/1.1.0:
|
||||
resolution: {integrity: sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==}
|
||||
engines: {node: '>=10.0.0'}
|
||||
|
|
|
|||
4
src/lib/server/redis.ts
Normal file
4
src/lib/server/redis.ts
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
import { Redis } from 'ioredis';
|
||||
import { REDIS_URI } from '$env/static/private';
|
||||
|
||||
export const redis = new Redis(REDIS_URI);
|
||||
|
|
@ -6,13 +6,15 @@ import {
|
|||
WALLABAG_URL,
|
||||
WALLABAG_MAX_PAGES,
|
||||
PAGE_SIZE,
|
||||
WALLABAG_MAX_ARTICLES
|
||||
WALLABAG_MAX_ARTICLES,
|
||||
USE_REDIS_CACHE
|
||||
} from '$env/static/private';
|
||||
import intersect from 'just-intersect';
|
||||
import type { Article, WallabagArticle } from '$root/lib/types/article';
|
||||
import { ArticleTag } from '$root/lib/types/articleTag';
|
||||
import type { PageQuery } from '$root/lib/types/pageQuery';
|
||||
import { URLSearchParams } from 'url';
|
||||
import { redis } from '$root/lib/server/redis';
|
||||
|
||||
const base: string = WALLABAG_URL;
|
||||
|
||||
|
|
@ -22,7 +24,33 @@ export async function fetchArticlesApi(
|
|||
queryParams: Record<string, string>,
|
||||
data?: Record<string, unknown>
|
||||
) {
|
||||
let lastFetched: Date | null = null;
|
||||
const pageQuery: PageQuery = {
|
||||
sort: 'updated',
|
||||
perPage: +queryParams?.limit || +PAGE_SIZE,
|
||||
since: 0,
|
||||
page: +queryParams?.page || 1,
|
||||
tags: 'programming',
|
||||
content: 'metadata'
|
||||
};
|
||||
const entriesQueryParams = new URLSearchParams({
|
||||
...pageQuery,
|
||||
perPage: `${pageQuery.perPage}`,
|
||||
since: `${pageQuery.since}`,
|
||||
page: `${pageQuery.page}`
|
||||
});
|
||||
console.log(`Entries params: ${entriesQueryParams}`);
|
||||
|
||||
if (USE_REDIS_CACHE) {
|
||||
const cached = await redis.get(entriesQueryParams.toString());
|
||||
|
||||
if (cached) {
|
||||
const response = JSON.parse(cached);
|
||||
console.log('Cache hit!');
|
||||
const ttl = await redis.ttl(entriesQueryParams.toString());
|
||||
|
||||
return { ...response, cacheControl: `max-age=${ttl}` };
|
||||
}
|
||||
}
|
||||
|
||||
const authBody = {
|
||||
grant_type: 'password',
|
||||
|
|
@ -40,29 +68,6 @@ export async function fetchArticlesApi(
|
|||
|
||||
const auth = await authResponse.json();
|
||||
|
||||
const pageQuery: PageQuery = {
|
||||
sort: 'updated',
|
||||
perPage: +queryParams?.limit || +PAGE_SIZE,
|
||||
since: 0,
|
||||
page: +queryParams?.page || 1,
|
||||
tags: 'programming',
|
||||
content: 'metadata'
|
||||
};
|
||||
const entriesQueryParams = new URLSearchParams({
|
||||
...pageQuery,
|
||||
perPage: `${pageQuery.perPage}`,
|
||||
since: `${pageQuery.since}`,
|
||||
page: `${pageQuery.page}`
|
||||
});
|
||||
console.log(`Entries params: ${entriesQueryParams}`);
|
||||
|
||||
if (lastFetched) {
|
||||
pageQuery.since = Math.round(lastFetched / 1000);
|
||||
}
|
||||
|
||||
lastFetched = new Date();
|
||||
|
||||
const nbEntries = 0;
|
||||
const pageResponse = await fetch(`${WALLABAG_URL}/api/entries.json?${entriesQueryParams}`, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
|
|
@ -105,7 +110,7 @@ export async function fetchArticlesApi(
|
|||
}
|
||||
});
|
||||
|
||||
return {
|
||||
const responseData = {
|
||||
articles,
|
||||
currentPage: page,
|
||||
totalPages: pages > +WALLABAG_MAX_PAGES ? +WALLABAG_MAX_PAGES : pages,
|
||||
|
|
@ -113,4 +118,10 @@ export async function fetchArticlesApi(
|
|||
totalArticles: total > +WALLABAG_MAX_ARTICLES ? +WALLABAG_MAX_ARTICLES : total,
|
||||
cacheControl
|
||||
};
|
||||
|
||||
if (USE_REDIS_CACHE) {
|
||||
redis.set(entriesQueryParams.toString(), JSON.stringify(responseData), 'EX', 43200);
|
||||
}
|
||||
|
||||
return responseData;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import { WALLABAG_MAX_PAGES } from '$env/static/private';
|
|||
import type { RequestHandler, RequestEvent } from './$types';
|
||||
import { fetchArticlesApi } from '$root/routes/api';
|
||||
|
||||
export const GET: RequestHandler = async ({ url, setHeaders }: RequestEvent) => {
|
||||
export const GET: RequestHandler = async ({ setHeaders, url }: RequestEvent) => {
|
||||
try {
|
||||
const page = url?.searchParams?.get('page') || '1';
|
||||
if (+page > +WALLABAG_MAX_PAGES) {
|
||||
|
|
|
|||
|
|
@ -9,9 +9,10 @@ export type ArticlePageLoad = {
|
|||
totalPages: number;
|
||||
limit: number;
|
||||
totalArticles: number;
|
||||
cacheControl: string;
|
||||
};
|
||||
|
||||
export const load: PageServerLoad = async ({ fetch, params }) => {
|
||||
export const load: PageServerLoad = async ({ fetch, params, setHeaders }) => {
|
||||
const { page } = params;
|
||||
if (+page > +WALLABAG_MAX_PAGES) {
|
||||
throw error(404, {
|
||||
|
|
@ -19,9 +20,18 @@ export const load: PageServerLoad = async ({ fetch, params }) => {
|
|||
});
|
||||
}
|
||||
const resp = await fetch(`/api/articles?page=${page}`);
|
||||
const { articles, currentPage, totalPages, limit, totalArticles }: ArticlePageLoad =
|
||||
const { articles, currentPage, totalPages, limit, totalArticles, cacheControl }: ArticlePageLoad =
|
||||
await resp.json();
|
||||
|
||||
if (cacheControl?.includes('no-cache')) {
|
||||
setHeaders({
|
||||
'cache-control': cacheControl
|
||||
});
|
||||
} else {
|
||||
setHeaders({
|
||||
'cache-control': 'max-age=43200' // 12 hours
|
||||
});
|
||||
}
|
||||
return {
|
||||
articles,
|
||||
currentPage,
|
||||
|
|
|
|||
|
|
@ -11,9 +11,13 @@
|
|||
let totalArticles: number;
|
||||
let limit: number;
|
||||
$: ({ articles, currentPage, totalPages, totalArticles, limit } = data);
|
||||
$: seoTitle = `Tech Articles - Page ${currentPage} | Bradley Shellnut`;
|
||||
</script>
|
||||
|
||||
<SEO title={`Tech Articles - Page ${currentPage}`} />
|
||||
<svelte:head>
|
||||
<title>{seoTitle}</title>
|
||||
<meta name="og:site_name" content={seoTitle} />
|
||||
</svelte:head>
|
||||
|
||||
<div class="pageStyles">
|
||||
<h1 style="margin-bottom: 2rem">Favorite Tech Articles</h1>
|
||||
|
|
|
|||
Loading…
Reference in a new issue