Compare commits

..

5 commits

5 changed files with 129 additions and 85 deletions

View file

@ -9,11 +9,11 @@ import { handler as ssrHandler } from './dist/server/entry.mjs'
const app = Fastify({ logger: true }) const app = Fastify({ logger: true })
await app await app
.register(import('@fastify/rate-limit'), { // .register(import('@fastify/rate-limit'), {
global: true, // global: true,
max: 25, // max: 25,
timeWindow: 1000 * 60 * 5, // timeWindow: 1000 * 60 * 5,
}) // })
.register(fastifyStatic, { .register(fastifyStatic, {
root: fileURLToPath(new URL('./dist/client', import.meta.url)), root: fileURLToPath(new URL('./dist/client', import.meta.url)),
}) })
@ -21,21 +21,21 @@ await app
app.use(ssrHandler) app.use(ssrHandler)
await app.setNotFoundHandler( // await app.setNotFoundHandler(
{ // {
preHandler: app.rateLimit(), // preHandler: app.rateLimit(),
}, // },
function (request, reply) { // function (request, reply) {
reply.code(404).send({ nothing: 'to see here' }) // reply.code(404).send({ nothing: 'to see here' })
} // }
) // )
await app.setErrorHandler(function (error, request, reply) { // await app.setErrorHandler(function (error, request, reply) {
if (error.statusCode === 429) { // if (error.statusCode === 429) {
reply.code(429) // reply.code(429)
error.message = 'You hit the rate limit! Slow down please!' // error.message = 'You hit the rate limit! Slow down please!'
} // }
reply.send(error) // reply.send(error)
}) // })
app.listen({ port: 4321 }) app.listen({ port: 4321 })

View file

@ -6,4 +6,4 @@ export const SITE_AUTHOR_MASTODON = 'https://social.ayco.io/@ayo'
export const SITE_PROJECT_REPO = 'https://github.com/ayoayco/Cozy' export const SITE_PROJECT_REPO = 'https://github.com/ayoayco/Cozy'
export const SITE_DESCRIPTION = 'The Web is Yours.' export const SITE_DESCRIPTION = 'The Web is Yours.'
export const VERSION = 'Drooling-Dogs' export const VERSION = 'Maintenance'

View file

@ -120,11 +120,13 @@ const siteName = 'cozy.pub'
} }
} }
/*
&:has(#router-outlet #post) { &:has(#router-outlet #post) {
#jumbotron { #jumbotron {
display: none; display: none;
} }
} }
*/
} }
</style> </style>
<style is:global> <style is:global>

View file

@ -1,74 +1,42 @@
--- ---
import { createClient, type RedisJSON } from 'redis'
import { type ArticleData, extract } from '@extractus/article-extractor'
import AddressBar from '../components/AddressBar.astro'
import Post from '../components/Post.astro'
import App from '../layouts/App.astro' import App from '../layouts/App.astro'
import Library from '../components/Library.astro' import Library from '../components/Library.astro'
import Footer from '../components/Footer.astro' import Footer from '../components/Footer.astro'
// Initialize Redis client
const client = createClient()
client.on('error', (err) => console.error('Redis Client Error', err))
await client.connect()
// Disable prerendering for dynamic content
export const prerender = false
// Get URL parameter from query string
let url = Astro.url.searchParams.get('url')
let article: ArticleData | null = { url: '/' }
// Handle redirect loops by extracting URL from nested parameters
while (url?.startsWith(Astro.url.origin)) {
try {
// Parse the URL to extract search parameters
const parsedUrl = new URL(url)
url = parsedUrl.searchParams.get('url')
} catch {
// If URL parsing fails, break the loop
console.error('Failed to parse URL:', url)
break
}
}
// Process article extraction only if a valid URL is provided
if (url && url !== '/' && url !== '') {
const cacheKey = 'cozy:url:' + url
try {
// Check if article exists in Redis cache
const exists = await client.exists(cacheKey)
if (exists) {
// Retrieve cached article data
article = (await client.json.get(cacheKey)) as ArticleData
console.log('>>> Using cached content', article.url)
} else {
// Fetch article from the web
article = await extract(url)
console.log('>>> Using fetched content', article?.url)
if (article !== null && article.url) {
// Cache the fetched article in Redis
await client.json.set(cacheKey, '$', article as RedisJSON)
console.log('>>> Added to cache', article.url)
}
}
} catch (error) {
// Log error and continue with null article
console.error('Error processing article:', error)
article = null
}
}
--- ---
<App article={article}> <App article={null}>
<AddressBar url={url} /> <button
id="home-btn"
onclick="document.getElementById('router-outlet').innerHTML = `
<h1>Under Maintenance</h1>
<p>
Currently performing some maintenance, working to improve your
experience. Please check back soon. We apologize for any inconvenience
this may cause.
</p>
`;"
>Home</button
>
<div slot="post" id="router-outlet"> <div slot="post" id="router-outlet">
<Post article={article} /> <h1>Under Maintenance</h1>
<p>
Currently performing some maintenance, working to improve your experience.
Please check back soon. We apologize for any inconvenience this may cause.
</p>
</div> </div>
<Library slot="library" skipSave={article === null} /> <Library slot="library" skipSave={true} />
<Footer slot="footer" /> <Footer slot="footer" />
</App> </App>
<style>
#home-btn {
width: 100%;
padding: 0.5rem 1rem;
text-align: center;
border-radius: 30px;
border: 2px solid rgb(var(--gray));
background-color: white;
box-shadow: 0 1px 3px 1px rgb(var(--gray-light));
cursor: pointer;
}
</style>

View file

@ -0,0 +1,74 @@
---
import { createClient, type RedisJSON } from 'redis'
import { type ArticleData, extract } from '@extractus/article-extractor'
import AddressBar from '../components/AddressBar.astro'
import Post from '../components/Post.astro'
import App from '../layouts/App.astro'
import Library from '../components/Library.astro'
import Footer from '../components/Footer.astro'
// Initialize Redis client
const client = createClient()
client.on('error', (err) => console.error('Redis Client Error', err))
await client.connect()
// Disable prerendering for dynamic content
export const prerender = false
// Get URL parameter from query string
let url = Astro.url.searchParams.get('url')
let article: ArticleData | null = { url: '/' }
// Handle redirect loops by extracting URL from nested parameters
while (url?.startsWith(Astro.url.origin)) {
try {
// Parse the URL to extract search parameters
const parsedUrl = new URL(url)
url = parsedUrl.searchParams.get('url')
} catch {
// If URL parsing fails, break the loop
console.error('Failed to parse URL:', url)
break
}
}
// Process article extraction only if a valid URL is provided
if (url && url !== '/' && url !== '') {
const cacheKey = 'cozy:url:' + url
try {
// Check if article exists in Redis cache
const exists = await client.exists(cacheKey)
if (exists) {
// Retrieve cached article data
article = (await client.json.get(cacheKey)) as ArticleData
console.log('>>> Using cached content', article.url)
} else {
// Fetch article from the web
article = await extract(url)
console.log('>>> Using fetched content', article?.url)
if (article !== null && article.url) {
// Cache the fetched article in Redis
await client.json.set(cacheKey, '$', article as RedisJSON)
console.log('>>> Added to cache', article.url)
}
}
} catch (error) {
// Log error and continue with null article
console.error('Error processing article:', error)
article = null
}
}
---
<App article={article}>
<AddressBar url={url} />
<div slot="post" id="router-outlet">
<Post article={article} />
</div>
<Library slot="library" skipSave={article === null} />
<Footer slot="footer" />
</App>