Merge remote-tracking branch 'gitea/main' into feature/robots-sitemap-rss
# Conflicts: # web/src/components/Icon.astro # web/src/layouts/Base.astro
This commit is contained in:
commit
0aa3d5979e
3
.gitignore
vendored
3
.gitignore
vendored
@ -1,4 +1,5 @@
|
||||
node_modules/
|
||||
# node_modules as a folder or a link (links were once committed by mistake)
|
||||
node_modules
|
||||
dist/
|
||||
.astro/
|
||||
.env
|
||||
|
||||
@ -1 +0,0 @@
|
||||
/Users/anasrashid/divan/api/node_modules
|
||||
@ -25,6 +25,25 @@ import { ebookRoutes } from './ebooks.ts';
|
||||
import { feedRoutes } from './feeds.ts';
|
||||
|
||||
const app = Fastify({ logger: { level: process.env.LOG_LEVEL ?? 'info' } });
|
||||
|
||||
// Errors: readers get a short message, never internal details; the log gets the whole error (one JSON line each,
|
||||
// with the method and address), which is what the server's error report reads (divan-deploy scripts/errors.sh).
|
||||
app.setErrorHandler((err: any, req, reply) => {
|
||||
const status = err.statusCode >= 400 && err.statusCode < 500 ? err.statusCode : 500;
|
||||
if (status === 500) req.log.error({ err, method: req.method, url: req.url }, 'request failed');
|
||||
reply.code(status).send({ error: status === 500 ? 'سرور میں خرابی۔ تھوڑی دیر بعد کوشش کریں۔' : err.message });
|
||||
});
|
||||
|
||||
// for the site's /health (uptime monitoring): the API answers and the database too
|
||||
app.get('/api/health', async (req, reply) => {
|
||||
try {
|
||||
await pool.query('SELECT 1');
|
||||
return { ok: true, db: 'ok' };
|
||||
} catch (err) {
|
||||
req.log.error({ err }, 'health: database unreachable');
|
||||
return reply.code(503).send({ ok: false, db: 'down' });
|
||||
}
|
||||
});
|
||||
const PAGE_SIZE = 20;
|
||||
|
||||
app.get('/health', async () => {
|
||||
|
||||
@ -1 +0,0 @@
|
||||
/Users/anasrashid/divan/web/node_modules
|
||||
@ -56,6 +56,9 @@ const PATHS = {
|
||||
chevron: '<path d="m15 18-6-6 6-6"/>',
|
||||
// footer
|
||||
rss: '<path d="M4 11a9 9 0 0 1 9 9"/><path d="M4 4a16 16 0 0 1 16 16"/><circle cx="5" cy="19" r="1"/>',
|
||||
home: '<path d="M15 21v-8a1 1 0 0 0-1-1h-4a1 1 0 0 0-1 1v8"/><path d="M3 10a2 2 0 0 1 .709-1.528l7-5.999a2 2 0 0 1 2.582 0l7 5.999A2 2 0 0 1 21 10v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/>',
|
||||
lock: '<rect width="18" height="11" x="3" y="11" rx="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4"/>',
|
||||
scale: '<path d="m16 16 3-8 3 8c-.87.65-1.92 1-3 1s-2.13-.35-3-1Z"/><path d="m2 16 3-8 3 8c-.87.65-1.92 1-3 1s-2.13-.35-3-1Z"/><path d="M7 21h10"/><path d="M12 3v18"/><path d="M3 7h2c2 0 5-1 7-2 2 1 5 2 7 2h2"/>',
|
||||
info: '<circle cx="12" cy="12" r="10"/><path d="M12 16v-4"/><path d="M12 8h.01"/>',
|
||||
globe: '<circle cx="12" cy="12" r="10"/><path d="M12 2a14.5 14.5 0 0 0 0 20 14.5 14.5 0 0 0 0-20"/><path d="M2 12h20"/>',
|
||||
database: '<ellipse cx="12" cy="5" rx="9" ry="3"/><path d="M3 5v14a9 3 0 0 0 18 0V5"/><path d="M3 12a9 3 0 0 0 18 0"/>',
|
||||
|
||||
@ -136,6 +136,8 @@ import { brand } from '../lib/brand';
|
||||
<li><a href="/about"><Icon name="info" size={18} /><span>{brand.name} کے بارے میں<small>مقصد اور طریقۂ کار</small></span></a></li>
|
||||
<li><a href="/tags"><Icon name="tag" size={18} /><span>ٹیگ<small>موضوعات سے کلام</small></span></a></li>
|
||||
<li><a href="/rss.xml"><Icon name="rss" size={18} /><span>نئی تبدیلیاں<small>RSS: نئے ورژن اور ای بکس</small></span></a></li>
|
||||
<li><a href="/privacy"><Icon name="lock" size={18} /><span>رازداری<small>ہم کیا محفوظ رکھتے ہیں</small></span></a></li>
|
||||
<li><a href="/terms"><Icon name="scale" size={18} /><span>استعمال کی شرائط<small>لائسنس اور ذمہ داریاں</small></span></a></li>
|
||||
</ul></div>
|
||||
<div><h2>ماخذ</h2><ul>
|
||||
<li><a href="https://ur.wikisource.org" rel="noopener"><Icon name="dict" size={18} /><span>ویکی ماخذ<small>متن · عوامی ملکیت</small></span><Icon name="external" size={13} class="ext" /></a></li>
|
||||
|
||||
@ -4,6 +4,8 @@ export const brand = {
|
||||
name: 'دیوان',
|
||||
tagline: 'اردو کا کلاسیکی ادب',
|
||||
madeBy: 'ایک پاکستانی کی طرف سے، پاکستان کے لیے', // footer, beside Pakistan's flag
|
||||
// where readers write: questions, corrections, copyright claims (privacy and terms pages)
|
||||
contact: { text: 'ہم سے رابطہ کریں', url: 'https://git.anasrashid.net/anas/divan/issues' },
|
||||
// the attribution forks keep (NOTICE, GPL v3 section 7(b))
|
||||
basedOn: { text: 'Divan by Anas Rashid', url: 'https://git.anasrashid.net/anas/divan' },
|
||||
};
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
// The signed-in reader (or null) for every page, from the session cookie; and the reader's numerals: pages are
|
||||
// written with Eastern Arabic (Urdu) digits, and a reader who chose Western digits (cookie divan-digits=latn, set in
|
||||
// the display settings) gets them in the page as sent, so nothing flashes
|
||||
// the display settings) gets them in the page as sent, so nothing flashes. Errors are logged as one JSON line each.
|
||||
import { defineMiddleware } from 'astro:middleware';
|
||||
import { auth, COOKIE } from './lib/auth';
|
||||
|
||||
@ -12,7 +12,15 @@ export const onRequest = defineMiddleware(async (ctx, next) => {
|
||||
if (r.ok) ctx.locals.user = r.data.user;
|
||||
else if (r.status === 401) ctx.cookies.delete(COOKIE, { path: '/' }); // expired or signed out elsewhere
|
||||
}
|
||||
const res = await next();
|
||||
let res: Response;
|
||||
try {
|
||||
res = await next();
|
||||
} catch (err: any) {
|
||||
// one JSON line per error (the server's error report reads these); the reader sees the Urdu error page (500.astro)
|
||||
console.error(JSON.stringify({ level: 'error', time: new Date().toISOString(), method: ctx.request.method, path: ctx.url.pathname,
|
||||
msg: String(err?.message ?? err), stack: String(err?.stack ?? '').split('\n').slice(0, 8).join('\n') }));
|
||||
throw err;
|
||||
}
|
||||
if (ctx.cookies.get('divan-digits')?.value !== 'latn' || !res.headers.get('content-type')?.includes('text/html')) return res;
|
||||
// text only: scripts and styles are left alone (the page's own scripts look for Eastern digits)
|
||||
const html = (await res.text()).split(/(<script[\s\S]*?<\/script>|<style[\s\S]*?<\/style>)/)
|
||||
|
||||
18
web/src/pages/404.astro
Normal file
18
web/src/pages/404.astro
Normal file
@ -0,0 +1,18 @@
|
||||
---
|
||||
// صفحہ نہیں ملا: any address that is not a page, and pages that answer "not found" (a missing poet, work or book)
|
||||
import Base from '../layouts/Base.astro';
|
||||
import Icon from '../components/Icon.astro';
|
||||
Astro.response.status = 404;
|
||||
---
|
||||
<Base title="صفحہ نہیں ملا">
|
||||
<section class="error-page">
|
||||
<p class="error-code" aria-hidden="true">۴۰۴</p>
|
||||
<h1>یہ صفحہ نہیں ملا</h1>
|
||||
<p class="muted">ہو سکتا ہے پتہ بدل گیا ہو یا اس میں کوئی غلطی ہو۔ اوپر کے خانے میں تلاش کیجیے، یا یہاں سے شروع کیجیے:</p>
|
||||
<p class="error-links">
|
||||
<a class="btn primary" href="/"><Icon name="home" size={18} /> سرورق</a>
|
||||
<a class="btn" href="/search"><Icon name="search" size={18} /> تلاش</a>
|
||||
<a class="btn" href="/tags"><Icon name="tag" size={18} /> ٹیگ</a>
|
||||
</p>
|
||||
</section>
|
||||
</Base>
|
||||
16
web/src/pages/500.astro
Normal file
16
web/src/pages/500.astro
Normal file
@ -0,0 +1,16 @@
|
||||
---
|
||||
// کچھ غلط ہو گیا: an error in the site; nothing internal is shown (the error is in the server's log, see /health)
|
||||
import Base from '../layouts/Base.astro';
|
||||
import Icon from '../components/Icon.astro';
|
||||
---
|
||||
<Base title="کچھ غلط ہو گیا">
|
||||
<section class="error-page">
|
||||
<p class="error-code" aria-hidden="true">!</p>
|
||||
<h1>معذرت، کچھ غلط ہو گیا</h1>
|
||||
<p class="muted">یہ ہماری طرف کی خرابی ہے اور درج ہو گئی ہے۔ تھوڑی دیر بعد صفحہ دوبارہ کھولیے۔</p>
|
||||
<p class="error-links">
|
||||
<a class="btn primary" href="javascript:location.reload()"><Icon name="undo" size={18} /> دوبارہ کوشش کریں</a>
|
||||
<a class="btn" href="/"><Icon name="home" size={18} /> سرورق</a>
|
||||
</p>
|
||||
</section>
|
||||
</Base>
|
||||
20
web/src/pages/health.ts
Normal file
20
web/src/pages/health.ts
Normal file
@ -0,0 +1,20 @@
|
||||
// /health for an uptime monitor (divan-deploy README): 200 when the site, the API and the database all answer,
|
||||
// 503 otherwise, with which part is down. Never cached.
|
||||
import type { APIRoute } from 'astro';
|
||||
|
||||
const API = process.env.API_URL ?? 'http://127.0.0.1:4100';
|
||||
|
||||
export const GET: APIRoute = async () => {
|
||||
const started = Date.now();
|
||||
let api = 'down', db = 'down';
|
||||
try {
|
||||
const r = await fetch(`${API}/api/health`, { signal: AbortSignal.timeout(3000) });
|
||||
api = 'ok';
|
||||
db = (await r.json().catch(() => ({}))).db ?? 'down';
|
||||
} catch {}
|
||||
const ok = api === 'ok' && db === 'ok';
|
||||
if (!ok) console.error(JSON.stringify({ level: 'error', time: new Date().toISOString(), msg: 'health check failed', api, db }));
|
||||
return new Response(JSON.stringify({ ok, site: 'ok', api, db, ms: Date.now() - started }), {
|
||||
status: ok ? 200 : 503, headers: { 'content-type': 'application/json', 'cache-control': 'no-store' },
|
||||
});
|
||||
};
|
||||
51
web/src/pages/privacy.astro
Normal file
51
web/src/pages/privacy.astro
Normal file
@ -0,0 +1,51 @@
|
||||
---
|
||||
// رازداری (privacy): what Divan keeps about readers and moderators, why, what it does not do, and how to remove it
|
||||
import Base from '../layouts/Base.astro';
|
||||
import { brand } from '../lib/brand';
|
||||
---
|
||||
<Base title="رازداری" description={`${brand.name} آپ کے بارے میں کیا محفوظ رکھتا ہے، کیوں، اور اسے کیسے مٹایا جائے`}>
|
||||
<article class="guide about">
|
||||
<h1>رازداری</h1>
|
||||
<p class="muted">آخری تبدیلی: ۹ اکتوبر ۲۰۲۶ء</p>
|
||||
<p>
|
||||
{brand.name} پڑھنے کے لیے کسی اکاؤنٹ کی ضرورت نہیں۔ ہم صرف وہی محفوظ رکھتے ہیں جو سائٹ کے چلنے کے لیے ضروری ہے، اور اسے
|
||||
کسی کو بیچتے یا دیتے نہیں۔ نہ اشتہار ہیں، نہ اشتہاری یا جاسوس کوکیز، نہ کسی اور کمپنی کے ٹریکر۔
|
||||
</p>
|
||||
|
||||
<h2>بغیر اکاؤنٹ کے پڑھتے ہوئے</h2>
|
||||
<ul>
|
||||
<li>آپ کی پسند (خط، رنگ، روشنی، پڑھنے کا انداز) آپ کے اپنے براؤزر میں رہتی ہے، ہمارے سرور پر نہیں۔ ہندسوں کی پسند ایک چھوٹی کوکی میں رہتی ہے تاکہ صفحہ انھی ہندسوں کے ساتھ آئے۔</li>
|
||||
<li>لاگ ان اور اکاؤنٹ بنانے کی کوششوں کی حد کے لیے سرور آپ کا انٹرنیٹ پتہ (IP) تھوڑی دیر کے لیے یاد رکھتا ہے، اور غلطیوں کے ریکارڈ میں صفحے کا پتہ اور وقت درج ہوتا ہے۔ یہ صرف سائٹ کی حفاظت اور خرابیاں ٹھیک کرنے کے لیے ہیں اور کچھ دن بعد مٹ جاتے ہیں۔</li>
|
||||
</ul>
|
||||
|
||||
<h2>اکاؤنٹ کے ساتھ</h2>
|
||||
<ul>
|
||||
<li><strong>اکاؤنٹ:</strong> ای میل پتہ، اور پاس ورڈ کی ایک نہ پلٹنے والی شکل (اصل پاس ورڈ کہیں محفوظ نہیں ہوتا)۔ اگر آپ لکھیں تو پورا نام اور تعارف۔</li>
|
||||
<li><strong>آپ کا ذخیرہ:</strong> محفوظ شدہ شعرا، کتابیں، کلام، اشعار، عبارتیں اور کتابوں کے صفحات؛ نوٹ؛ ذخیرۂ الفاظ۔ یہ صرف آپ کو نظر آتے ہیں۔</li>
|
||||
<li><strong>لاگ ان کی کوکی:</strong> ایک کوکی جو ۳۰ دن آپ کو لاگ ان رکھتی ہے۔ سرور پر اس کی صرف ایک نہ پلٹنے والی شکل رہتی ہے۔</li>
|
||||
</ul>
|
||||
|
||||
<h2>موڈریٹروں کا کام عوامی ہے</h2>
|
||||
<p>
|
||||
جو تبدیلی شائع ہوتی ہے وہ {brand.name} کے کھلے ڈیٹا کے ذخیرے میں درج ہوتی ہے: کس نے لکھی، کس نے منظور کی، کب۔ وہاں آپ کا
|
||||
<strong>عوامی نام</strong> لکھا جاتا ہے (پورا نام، ورنہ «موڈریٹر» اور ایک نمبر)، ای میل کبھی نہیں۔ یہ تاریخ کھلی اور مستقل ہے،
|
||||
اکاؤنٹ ختم کرنے کے بعد بھی شائع شدہ کام کے ساتھ رہتی ہے۔ مسودے اور جائزے کے تبصرے صرف موڈریٹر دیکھتے ہیں۔
|
||||
</p>
|
||||
|
||||
<h2>دوسری ویب سائٹیں</h2>
|
||||
<p>
|
||||
خط اور تمام فائلیں ہمارے اپنے سرور سے آتی ہیں۔ دوسری سائٹ سے صرف تب رابطہ ہوتا ہے جب آپ خود چنیں: لغت میں تلفظ کی آواز
|
||||
ویکی میڈیا سے چلتی ہے، archive.org کی کتاب archive.org کا ریڈر کھولتی ہے، اور «گوگل» نئی ٹیب میں گوگل کھولتا ہے۔ ان سائٹوں
|
||||
کی اپنی رازداری کی پالیسیاں ہیں۔
|
||||
</p>
|
||||
|
||||
<h2>آپ کا اختیار</h2>
|
||||
<ul>
|
||||
<li><a href="/account">اکاؤنٹ</a> میں نام اور تعارف بدل سکتے ہیں اور اکاؤنٹ اپنے تمام ذخیرے سمیت مستقل طور پر ختم کر سکتے ہیں۔</li>
|
||||
<li>محفوظ کی ہوئی ہر چیز اور ہر نوٹ اپنی جگہ سے ہٹایا جا سکتا ہے۔</li>
|
||||
<li>اپنے ڈیٹا کی نقل یا کسی سوال کے لیے <a href={brand.contact.url}>{brand.contact.text}</a>۔</li>
|
||||
</ul>
|
||||
|
||||
<p class="muted">یہ پالیسی بدلے تو یہی صفحہ بدلے گا، تاریخ کے ساتھ۔ دیکھیے: <a href="/terms">استعمال کی شرائط</a>۔</p>
|
||||
</article>
|
||||
</Base>
|
||||
@ -28,6 +28,7 @@ if (Astro.request.method === 'POST') {
|
||||
<label>پاس ورڈ (کم از کم ۸ حروف)<input type="password" name="password" required minlength="8" autocomplete="new-password" dir="ltr" /></label>
|
||||
<label>پاس ورڈ دوبارہ<input type="password" name="password2" required minlength="8" autocomplete="new-password" dir="ltr" /></label>
|
||||
<button type="submit">اکاؤنٹ بنائیں</button>
|
||||
<p class="muted small-line">اکاؤنٹ بنا کر آپ <a href="/terms">استعمال کی شرائط</a> مانتے ہیں؛ ہم کیا محفوظ رکھتے ہیں، یہ <a href="/privacy">رازداری</a> میں ہے۔</p>
|
||||
<p class="muted">پہلے سے اکاؤنٹ ہے؟ <a href="/signin">لاگ ان کریں</a></p>
|
||||
</form>
|
||||
</Base>
|
||||
|
||||
45
web/src/pages/terms.astro
Normal file
45
web/src/pages/terms.astro
Normal file
@ -0,0 +1,45 @@
|
||||
---
|
||||
// استعمال کی شرائط (terms): licences of the content and the code, accounts, moderators' and uploaders' responsibilities,
|
||||
// reporting problems and copyright claims
|
||||
import Base from '../layouts/Base.astro';
|
||||
import { brand } from '../lib/brand';
|
||||
---
|
||||
<Base title="استعمال کی شرائط" description={`${brand.name} کے استعمال کی شرائط: مواد کے لائسنس، اکاؤنٹ، اپ لوڈ اور شکایت`}>
|
||||
<article class="guide about">
|
||||
<h1>استعمال کی شرائط</h1>
|
||||
<p class="muted">آخری تبدیلی: ۹ اکتوبر ۲۰۲۶ء</p>
|
||||
<p>{brand.name} مفت ہے اور سب کے لیے کھلا ہے۔ سائٹ استعمال کر کے آپ ان شرائط کو مانتے ہیں۔ ساتھ <a href="/privacy">رازداری</a> کا صفحہ بھی دیکھیے۔</p>
|
||||
|
||||
<h2>مواد</h2>
|
||||
<ul>
|
||||
<li>کلاسیکی متن <strong>عوامی ملکیت</strong> میں ہیں: انھیں پڑھنا، نقل کرنا اور آگے بانٹنا سب کے لیے آزاد ہے۔</li>
|
||||
<li>{brand.name} کا ڈیٹا (متن کی ترتیب، تعارف، تبدیلیاں، ٹیگ) <a href="https://creativecommons.org/licenses/by-sa/4.0/deed.ur" lang="en" dir="ltr">CC BY-SA 4.0</a> کے تحت ہے: استعمال کریں تو ویکی ماخذ، ویکیپیڈیا اور {brand.name} کا حوالہ دیں اور اسی لائسنس کے ساتھ بانٹیں۔</li>
|
||||
<li>سائٹ کا کوڈ <span lang="en" dir="ltr">GNU GPL v3</span> کے تحت کھلا ہے۔ نام «{brand.name}»، اس کا لوگو اور شناخت اس لائسنس میں شامل نہیں: اپنی سائٹ بنائیں تو اپنا نام اور لوگو رکھیں (کوڈ کے ساتھ <span lang="en" dir="ltr">NOTICE</span> اور <span lang="en" dir="ltr">TRADEMARKS.md</span>)۔</li>
|
||||
<li>ای بکس اپنے ناشر یا مصنف کے حقوق کے ساتھ ہیں جیسا کہ ہر کتاب پر لکھا ہے۔ archive.org کی کتابیں وہیں سے دکھائی جاتی ہیں۔</li>
|
||||
</ul>
|
||||
|
||||
<h2>اکاؤنٹ</h2>
|
||||
<ul>
|
||||
<li>اپنا پاس ورڈ محفوظ رکھیے؛ اکاؤنٹ سے ہونے والا کام آپ کی ذمہ داری ہے۔</li>
|
||||
<li>سائٹ کو نقصان پہنچانا، بے جا بوجھ ڈالنا، دوسروں کے اکاؤنٹ میں دخل دینا یا خودکار طریقے سے اکاؤنٹ بنانا منع ہے۔ ایسی صورت میں اکاؤنٹ بند کیا جا سکتا ہے۔</li>
|
||||
</ul>
|
||||
|
||||
<h2>موڈریٹر اور اپ لوڈ</h2>
|
||||
<ul>
|
||||
<li>موڈریٹر کی ہر تبدیلی جائزے کے مراحل سے گزرتی ہے اور شائع ہونے پر کھلے ڈیٹا میں ان کے عوامی نام کے ساتھ درج ہوتی ہے۔</li>
|
||||
<li>ای بک اپ لوڈ کرنے والا تصدیق کرتا ہے کہ اسے یہ کتاب بانٹنے کا حق ہے (عوامی ملکیت یا اجازت) اور ماخذ و حقوق درست لکھتا ہے۔ جائزہ لینے والے اس کی تصدیق کرتے ہیں؛ شک ہو تو کتاب شائع نہیں ہوتی۔</li>
|
||||
</ul>
|
||||
|
||||
<h2>شکایت اور حقوق کا دعویٰ</h2>
|
||||
<p>
|
||||
کسی متن میں غلطی، کسی کتاب کے حقوق پر اعتراض، یا کسی اور مسئلے کے لیے <a href={brand.contact.url}>{brand.contact.text}</a>۔ حقوق کے
|
||||
دعوے میں کتاب یا صفحے کا پتہ اور اپنی ملکیت کی تفصیل لکھیے؛ دعویٰ درست ہو تو مواد جلد ہٹا دیا جاتا ہے۔
|
||||
</p>
|
||||
|
||||
<h2>ذمہ داری کی حد</h2>
|
||||
<p>
|
||||
{brand.name} جیسا ہے ویسا مہیا کیا جاتا ہے۔ ہم متن کو درست رکھنے کی پوری کوشش کرتے ہیں، مگر کسی غلطی یا سائٹ کے بند رہنے سے
|
||||
ہونے والے نقصان کی ذمہ داری نہیں لیتے۔ یہ شرائط بدلیں تو یہی صفحہ تاریخ کے ساتھ بدلے گا۔
|
||||
</p>
|
||||
</article>
|
||||
</Base>
|
||||
@ -833,3 +833,7 @@ p, li { text-wrap: pretty; }
|
||||
.byline { display: block; text-align: center; }
|
||||
.poem { background: none; border: 0; }
|
||||
}
|
||||
/* 404 and error pages */
|
||||
.error-page { text-align: center; padding: 32px 0 48px; }
|
||||
.error-code { font: 700 4.5rem/1 'Noto Naskh Arabic', serif; color: var(--accent, var(--brand)); opacity: .35; margin: 0 0 8px; }
|
||||
.error-links { display: flex; flex-wrap: wrap; justify-content: center; gap: 10px; margin-top: 18px; }
|
||||
|
||||
Loading…
Reference in New Issue
Block a user