Merge pull request #2 from BradNut/redis

Redis
This commit is contained in:
Bradley Shellnut 2023-02-15 21:56:25 -08:00 committed by GitHub
commit 7a0ff588a8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 122 additions and 33 deletions

View file

@ -54,5 +54,8 @@
"vite-imagetools": "^4.0.18",
"vitest": "^0.25.3"
},
"type": "module"
"type": "module",
"dependencies": {
"ioredis": "^5.3.1"
}
}

View file

@ -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
View file

@ -0,0 +1,4 @@
import { Redis } from 'ioredis';
import { REDIS_URI } from '$env/static/private';
export const redis = new Redis(REDIS_URI);

View file

@ -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;
}

View file

@ -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) {

View file

@ -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,

View file

@ -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>