Compare commits

...

6 Commits

15 changed files with 459 additions and 321 deletions

@ -1 +1 @@
Subproject commit 3744bd78995594e5fdfcaeff32f426f1d41daffa Subproject commit d1d724d1170e8cd2460cf1beb5098161ac33ef9a

35
package-lock.json generated
View File

@ -11,7 +11,8 @@
"dotenv": "^16.4.5", "dotenv": "^16.4.5",
"mdsvex": "^0.12.3", "mdsvex": "^0.12.3",
"pg": "^8.12.0", "pg": "^8.12.0",
"sass": "^1.77.8" "sass": "^1.77.8",
"simple-git": "^3.26.0"
}, },
"devDependencies": { "devDependencies": {
"@playwright/test": "^1.28.1", "@playwright/test": "^1.28.1",
@ -299,6 +300,21 @@
"@jridgewell/sourcemap-codec": "^1.4.14" "@jridgewell/sourcemap-codec": "^1.4.14"
} }
}, },
"node_modules/@kwsites/file-exists": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/@kwsites/file-exists/-/file-exists-1.1.1.tgz",
"integrity": "sha512-m9/5YGR18lIwxSFDwfE3oA7bWuq9kdau6ugN4H2rJeyhFQZcG9AgSHkQtSD15a8WvTgfz9aikZMrKPHvbpqFiw==",
"license": "MIT",
"dependencies": {
"debug": "^4.1.1"
}
},
"node_modules/@kwsites/promise-deferred": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/@kwsites/promise-deferred/-/promise-deferred-1.1.1.tgz",
"integrity": "sha512-GaHYm+c0O9MjZRu0ongGBRbinu8gVAMd2UZjji6jVmqKtZluZnptXGWhz1E8j8D2HJ3f/yMxKAUC0b+57wncIw==",
"license": "MIT"
},
"node_modules/@nodelib/fs.scandir": { "node_modules/@nodelib/fs.scandir": {
"version": "2.1.5", "version": "2.1.5",
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
@ -1253,7 +1269,6 @@
"version": "4.3.6", "version": "4.3.6",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.6.tgz", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.6.tgz",
"integrity": "sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==", "integrity": "sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"ms": "2.1.2" "ms": "2.1.2"
@ -2408,7 +2423,6 @@
"version": "2.1.2", "version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
"dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/nanoid": { "node_modules/nanoid": {
@ -3313,6 +3327,21 @@
"url": "https://github.com/sponsors/isaacs" "url": "https://github.com/sponsors/isaacs"
} }
}, },
"node_modules/simple-git": {
"version": "3.26.0",
"resolved": "https://registry.npmjs.org/simple-git/-/simple-git-3.26.0.tgz",
"integrity": "sha512-5tbkCSzuskR6uA7uA23yjasmA0RzugVo8QM2bpsnxkrgP13eisFT7TMS4a+xKEJvbmr4qf+l0WT3eKa9IxxUyw==",
"license": "MIT",
"dependencies": {
"@kwsites/file-exists": "^1.1.1",
"@kwsites/promise-deferred": "^1.1.1",
"debug": "^4.3.5"
},
"funding": {
"type": "github",
"url": "https://github.com/steveukx/git-js?sponsor=1"
}
},
"node_modules/sirv": { "node_modules/sirv": {
"version": "2.0.4", "version": "2.0.4",
"resolved": "https://registry.npmjs.org/sirv/-/sirv-2.0.4.tgz", "resolved": "https://registry.npmjs.org/sirv/-/sirv-2.0.4.tgz",

View File

@ -38,6 +38,7 @@
"dotenv": "^16.4.5", "dotenv": "^16.4.5",
"mdsvex": "^0.12.3", "mdsvex": "^0.12.3",
"pg": "^8.12.0", "pg": "^8.12.0",
"sass": "^1.77.8" "sass": "^1.77.8",
"simple-git": "^3.26.0"
} }
} }

View File

@ -6,7 +6,7 @@
} }
:root { :root {
font-size: 16px; font-size: 18px;
} }
body { body {
@ -15,5 +15,5 @@ body {
color: #ffffff; color: #ffffff;
font-family: 'ZenKakuGothicNew-Regular', '游ゴシック体', 'Yu Gothic', YuGothic, 'ヒラギノ角ゴシック Pro', font-family: 'ZenKakuGothicNew-Regular', '游ゴシック体', 'Yu Gothic', YuGothic, 'ヒラギノ角ゴシック Pro',
'Hiragino Kaku Gothic Pro', 'メイリオ', Meiryo, Osaka, 'MS PGothic', sans-serif; 'Hiragino Kaku Gothic Pro', 'メイリオ', Meiryo, Osaka, 'MS PGothic', sans-serif;
font-size: 16px; font-size: 1rem;
} }

View File

@ -7,6 +7,8 @@ export type Article = {
category: string; category: string;
released_at: Date; released_at: Date;
updated_at: Date; updated_at: Date;
author: string;
email: string;
tags: string[]; tags: string[];
image: string; image: string;
publish: Publish; publish: Publish;

View File

@ -7,11 +7,15 @@
color: var(--color-text); color: var(--color-text);
padding-left: 2rem;
box-sizing: border-box;
h1 { h1 {
position: relative; position: relative;
width: auto; width: auto;
font-size: 2rem; font-size: 1.8rem;
margin-bottom: 0.5rem; margin-bottom: 0.5rem;
margin-left: -1rem;
} }
hr { hr {
@ -19,24 +23,21 @@
margin: 0; margin: 0;
border: none; border: none;
border-top: 1px solid var(--color-outline); border-top: 1px solid var(--color-outline);
} margin-left: -1rem;
>*:not(h1, hr) {
margin-left: 2rem;
} }
h2 { h2 {
font-size: 1.5rem; font-size: 1.5rem;
position: relative; position: relative;
&::before { // &::before {
content: '#'; // content: '#';
position: absolute; // position: absolute;
top: 0; // top: 0;
left: -1.5rem; // left: -1.5rem;
font-size: 1.75rem; // font-size: 1.75rem;
color: rgb(131, 131, 131); // color: rgb(131, 131, 131);
} // }
} }
h3 { h3 {
@ -111,24 +112,35 @@
} }
} }
code:not(pre>code) {
display: inline-block;
font-family: monospace;
font-size: 0.9rem;
background: #66666666;
padding: 0 0.3rem;
border-radius: 0.5rem;
}
pre { pre {
font-family: monospace; font-family: monospace;
position: relative; position: relative;
display: block; display: block;
margin-top: 0.75rem; margin-top: 0.75rem;
margin-bottom: 0.75rem; margin-bottom: 0.75rem;
border: 2px solid #ffffff40; // border: 2px solid #ffffff40;
border-radius: 0.5rem; border-radius: 0.5rem;
overflow-x: scroll; overflow-x: auto;
font-size: 0.9rem;
&::-webkit-scrollbar { &::-webkit-scrollbar {
border-radius: 10px; border-radius: 10px;
height: 10px; height: 10px;
width: 8px; width: 8px;
background: #3d3d3d;
} }
&::-webkit-scrollbar-thumb { &::-webkit-scrollbar-thumb {
background: #999; background: #6f6f6f;
border-radius: 10px; border-radius: 10px;
} }
@ -136,7 +148,7 @@
border-radius: 10px; border-radius: 10px;
} }
>code { > code {
display: inline-block; display: inline-block;
padding: 0.5rem; padding: 0.5rem;
border-radius: 0.6rem; border-radius: 0.6rem;
@ -154,7 +166,7 @@
transition-property: height; transition-property: height;
padding-inline-start: 0.1rem; padding-inline-start: 0.1rem;
>summary { > summary {
position: relative; position: relative;
border-radius: 0.5rem; border-radius: 0.5rem;
list-style: ' ' outside; list-style: ' ' outside;
@ -168,7 +180,7 @@
} }
} }
>p { > p {
margin-block-start: 0.5rem; margin-block-start: 0.5rem;
margin-block-end: 0.5rem; margin-block-end: 0.5rem;
margin-left: 1rem; margin-left: 1rem;
@ -176,11 +188,11 @@
} }
&[open] { &[open] {
>summary { > summary {
list-style: ' ' outside; list-style: ' ' outside;
} }
>p { > p {
animation: detailsIn 0.5s ease; animation: detailsIn 0.5s ease;
display: block; display: block;
} }

View File

@ -3,22 +3,9 @@
if (typeof date === 'string') date = new Date(date); if (typeof date === 'string') date = new Date(date);
const pad = function (str: string): string { import { format } from "$lib/scripts/formatted_date";
return ('0' + str).slice(-2);
};
const year = date.getFullYear().toString(); const formattedDate = format(date);
const month = pad((date.getMonth() + 1).toString());
const day = pad(date.getDate().toString());
const hour = pad(date.getHours().toString());
const min = pad(date.getMinutes().toString());
const sec = pad(date.getSeconds().toString());
const tz = -date.getTimezoneOffset();
const sign = tz >= 0 ? '+' : '-';
const tzHour = pad((tz / 60).toString());
const tzMin = pad((tz % 60).toString());
let formattedDate = `${year}-${month}-${day}T${hour}:${min}:${sec}${sign}${tzHour}:${tzMin}`;
</script> </script>
{formattedDate} {formattedDate}

View File

@ -21,7 +21,7 @@
{#if isUpdated} {#if isUpdated}
<meta property="article:modified_time" content={data.updated_at.toISOString()} /> <meta property="article:modified_time" content={data.updated_at.toISOString()} />
{/if} {/if}
<meta property="article:author" content="HareWorks" /> <meta property="article:author" content={data.author} />
<meta property="article:section" content="Blog" /> <meta property="article:section" content="Blog" />
<meta property="article:tag" content={data.tags.join(',')} /> <meta property="article:tag" content={data.tags.join(',')} />
<meta name="robots" content={data.publish as publish.Publish} /> <meta name="robots" content={data.publish as publish.Publish} />
@ -35,6 +35,7 @@
<div class="title"> <div class="title">
<h1>{data.title}</h1> <h1>{data.title}</h1>
<div class="meta"> <div class="meta">
<div class="left">
<span> <span>
released <FormattedDate date={data.released_at} /> released <FormattedDate date={data.released_at} />
{#if isUpdated}<br /> {#if isUpdated}<br />
@ -50,6 +51,14 @@
{/each} {/each}
</span> </span>
</div> </div>
<div class="right">
<img
src="https://gitea.hareworks.net/avatars/e595ec10f8052671deab92caca78220fc838f63259bcd542a0d93f53e2371d52?size=512"
alt="Author"
/>
<div>@{data.author}</div>
</div>
</div>
</div> </div>
<div class="panel"> <div class="panel">
<div class="document"> <div class="document">
@ -91,6 +100,11 @@
height: 100%; height: 100%;
object-fit: cover; object-fit: cover;
} }
.card {
position: fixed;
top: 0;
right: 0;
}
main { main {
border: 1px solid var(--line-primary); border: 1px solid var(--line-primary);
border-top: none; border-top: none;
@ -100,7 +114,7 @@
min-height: 100%; min-height: 100%;
padding: 20px; padding: 20px;
padding-top: 100px; padding-top: 100px;
max-width: 1000px; max-width: 800px;
margin: 0 auto; margin: 0 auto;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
@ -110,7 +124,7 @@
font-size: 2rem; font-size: 2rem;
text-align: center; text-align: center;
margin: 0; margin: 0;
padding: 12px 100px 12px 0; padding: 12px;
border-bottom: 1px solid rgba(255, 255, 255, 0.3); border-bottom: 1px solid rgba(255, 255, 255, 0.3);
box-sizing: border-box; box-sizing: border-box;
display: flex; display: flex;
@ -120,8 +134,14 @@
.meta { .meta {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
flex-direction: row;
padding: 0 20px; padding: 0 20px;
font-size: 0.8rem; font-size: 1rem;
> .left {
display: flex;
flex-direction: column;
justify-content: center;
align-items: left;
span { span {
opacity: 0.6; opacity: 0.6;
} }
@ -134,6 +154,8 @@
text-decoration: underline; text-decoration: underline;
} }
&:first-child { &:first-child {
margin-left: 0;
padding-left: 0;
&::before { &::before {
content: '🗀 '; content: '🗀 ';
} }
@ -144,6 +166,25 @@
} }
} }
} }
> .right {
display: flex;
flex-direction: row;
justify-content: right;
align-items: center;
gap: 10px;
height: 50px;
margin: 10px;
img {
width: 50px;
height: 50px;
border-radius: 15%;
}
div {
font-size: 1rem;
}
}
}
} }
.panel { .panel {
flex: 1; flex: 1;

View File

@ -0,0 +1,20 @@
export const format = (
date: Date
) => {
const pad = function (str: string): string {
return ('0' + str).slice(-2);
};
const year = date.getFullYear().toString();
const month = pad((date.getMonth() + 1).toString());
const day = pad(date.getDate().toString());
const hour = pad(date.getHours().toString());
const min = pad(date.getMinutes().toString());
const sec = pad(date.getSeconds().toString());
const tz = -date.getTimezoneOffset();
const sign = tz >= 0 ? '+' : '-';
const tzHour = pad((tz / 60).toString());
const tzMin = pad((tz % 60).toString());
return `${year}-${month}-${day}T${hour}:${min}:${sec}${sign}${tzHour}:${tzMin}`;
};

View File

@ -0,0 +1,15 @@
import { simpleGit, } from 'simple-git';
import type { SimpleGit, SimpleGitOptions } from 'simple-git';
console.log(process.cwd()+'/articles');
export const gitOptions: Partial<SimpleGitOptions> = {
baseDir: process.cwd()+'/articles',
binary: 'git',
maxConcurrentProcesses: 6,
config: [
'core.sshCommand=ssh -i ../key -F /dev/null'
],
};
const git: SimpleGit = simpleGit(gitOptions);
export default git;

View File

@ -1,3 +1,4 @@
//@ts-ignore
import { compile, escapeSvelte } from "mdsvex"; import { compile, escapeSvelte } from "mdsvex";
import { createHighlighter } from "shiki"; import { createHighlighter } from "shiki";
import { kanagawa_dark as theme } from "$lib/kanagawa" import { kanagawa_dark as theme } from "$lib/kanagawa"

View File

@ -10,10 +10,14 @@ import {
} from '$env/static/private' } from '$env/static/private'
const connectionString = `postgres://${PG_USER}:${PG_PASS}@${PG_HOST}:${PG_PORT}/${PG_DB}`; const connectionString = `postgres://${PG_USER}:${PG_PASS}@${PG_HOST}:${PG_PORT}/${PG_DB}`;
const pool = new Pool({ connectionString }); let pool = new Pool({ connectionString });
export const getConnection = () => pool; export const getConnection = () => pool;
export const reConnect = async () => {
pool = new Pool({ connectionString });
}
import init_db from '$lib/server/database/init'; import init_db from '$lib/server/database/init';
import PG from '$lib/server/database'; import PG from '$lib/server/database';
await init_db(await PG(pool)); await init_db(await PG(pool));

View File

@ -1,30 +1,25 @@
// initialize // initialize
import type { Postgres } from '$lib/server/database'; import type { Postgres } from '$lib/server/database';
import fs from 'fs'; import fs from 'fs';
import { execSync } from 'child_process';
import compile from '$lib/server/article'; import compile from '$lib/server/article';
import article_git from '$lib/server/aritcle-git';
import { format } from '$lib/scripts/formatted_date';
export default async function init(db: Postgres) { export default async function init(db: Postgres) {
if (fs.existsSync('./articles/')) { await cloneRepo();
console.log('Pulling articles from git..'); await createTable(db, [
const stdout = execSync('git -c core.sshCommand="ssh -i ../key -F /dev/null" pull', { cwd: './articles/' });
console.log(stdout.toString());
} else {
console.log('Cloning articles from git..');
const stdout = execSync('git -c core.sshCommand="ssh -i ./key -F /dev/null" clone git@gitea.hareworks.net:Hare/blog-articles.git articles', { cwd: './' });
console.log(stdout.toString());
}
const schemas = [
{ {
name: 'article', name: 'article',
columns: [ columns: [
{ name: 'seq', type: 'serial', constraint: 'primary key' }, { name: 'seq', type: 'serial', constraint: 'primary key' },
{ name: 'id', type: 'text', constraint: 'not null' }, { name: 'id', type: 'text', constraint: 'not null' },
{ name: 'title', type: 'text', constraint: 'not null' },
{ name: 'category', type: 'text', constraint: 'not null' },
{ name: 'released_at', type: 'timestamp', constraint: 'not null' }, { name: 'released_at', type: 'timestamp', constraint: 'not null' },
{ name: 'updated_at', type: 'timestamp', constraint: 'not null' }, { name: 'updated_at', type: 'timestamp', constraint: 'not null' },
{ name: 'author', type: 'text', constraint: 'not null' },
{ name: 'email', type: 'text', constraint: 'not null' },
{ name: 'title', type: 'text', constraint: 'not null' },
{ name: 'category', type: 'text', constraint: 'not null' },
{ name: 'tags', type: 'text[]', constraint: 'not null' }, { name: 'tags', type: 'text[]', constraint: 'not null' },
{ name: 'image', type: 'text', constraint: '' }, { name: 'image', type: 'text', constraint: '' },
{ name: 'publish', type: 'text', constraint: 'not null' }, { name: 'publish', type: 'text', constraint: 'not null' },
@ -80,73 +75,53 @@ export default async function init(db: Postgres) {
{ name: 'ref_count', type: 'integer', constraint: 'not null' }, { name: 'ref_count', type: 'integer', constraint: 'not null' },
], ],
} }
]; ]);
await db.begin();
try {
// Create tables
for (const schema of schemas) {
const res = await db.query(`select * from information_schema.tables where table_name = '${schema.name}'`)
if (res.rowCount == 0) {
console.log(`Creating table ${schema.name}`);
const columnStr = schema.columns.map(c => `${c.name} ${c.type} ${c.constraint}`).join(', ');
await db.query(`create table ${schema.name} (${columnStr})`);
} else {
console.log(`Table ${schema.name} already exists`);
}
}
} catch (err) {
console.error(err);
await db.rollback();
} finally {
await db.commit();
}
const articleFiles: ArticleFileItem[] = [];
function scanDir(path: string) {
const files = fs.readdirSync(path);
for (const file of files) {
const dir = `${path}/${file}`;
const stat = fs.statSync(dir);
if (stat.isDirectory()) {
scanDir(dir);
} else {
articleFiles.push({ path: `${path}/${file}`, id: file.replace('.md', '') });
}
}
}
scanDir('./articles/article');
const articleFiles = await crawlArticles(db);
await db.query('update tag set ref_count = 0'); await db.query('update tag set ref_count = 0');
for (const { path, id } of articleFiles) { for (const { path, id } of articleFiles) {
console.log(`Processing ${id}...`);
await db.begin(); await db.begin();
try { try {
const gitlog = await article_git.log({
file: path.slice(11),
strictDate: true,
});
const res = await db.query('select * from article where id = $1', [id]); const res = await db.query('select * from article where id = $1', [id]);
const compiled = await compile(fs.readFileSync(path, 'utf-8'));
const title = compiled.data.fm.title; // console.log(gitlog);
const latest = gitlog.latest!;
const oldest = gitlog.all[gitlog.all.length - 1];
const author = oldest.author_name;
const email = oldest.author_email;
const released_at = new Date(oldest.date);
const updated_at = new Date(latest.date);
console.log(`Author: ${author} <${email}>\nReleased at: ${format(released_at)}\nUpdated at: ${format(updated_at)}`);
const category = path.split('/')[3]; const category = path.split('/')[3];
const compiled = await compile(fs.readFileSync(path, 'utf-8'));
const title = compiled.data.fm.title;
const tags: string[] = compiled.data.fm.tags; const tags: string[] = compiled.data.fm.tags;
const released_at = new Date(compiled.data.fm.released_at);
const updated_at = (compiled.data.fm.updated_at !== null) ? new Date(compiled.data.fm.updated_at) : released_at;
const image = compiled.data.fm.image; const image = compiled.data.fm.image;
const publish = compiled.data.fm.publish; const publish = compiled.data.fm.publish;
const content = compiled.code const content = compiled.code
.replace(/>{@html `<code class="language-/g, '><code class="language-') .replace(/>{@html `<code class="language-/g, '><code class="language-')
.replace(/<\/code>`}<\/pre>/g, '</code></pre>') .replace(/<\/code>`}<\/pre>/g, '</code></pre>')
if (res.rowCount == 0) { if (res.rowCount == 0) {
console.log(`New article: ${id}`); console.log(`New article: ${id}`);
await db.query( await db.query(
'insert into article (id, title, category, released_at, updated_at, tags, image, publish, content) values ($1, $2, $3, $4, $5, $6, $7, $8, $9)', 'insert into article (id, released_at, updated_at, author, email, title, category, tags, image, publish, content) values ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)',
[id, title, category, released_at, updated_at, tags, image, publish, content] [id, released_at, updated_at, author, email, title, category, tags, image, publish, content]
); );
} else if (res.rows[0].updated_at < updated_at) { // } else if (res.rows[0].updated_at < updated_at) {
// } else if (true) { } else if (true) {
console.log(`Update article: ${id}`); console.log(`Update article: ${id}`);
await db.query( await db.query(
'update article set title = $2, category = $3, released_at = $4, updated_at = $5, tags = $6, image = $7, publish = $8, content = $9 where id = $1', 'update article set released_at = $2, updated_at = $3, author = $4, email = $5, title = $6, category = $7, tags = $8, image = $9, publish = $10, content = $11 where id = $1',
[id, title, category, released_at, updated_at, tags, image, publish, content] [id, released_at, updated_at, author, email, title, category, tags, image, publish, content]
); );
} else { } else {
console.log(`Article ${id} is already up-to-date`); console.log(`Article ${id} is already up-to-date`);
@ -162,6 +137,7 @@ export default async function init(db: Postgres) {
console.log(err); console.log(err);
await db.rollback(); await db.rollback();
} finally { } finally {
console.log('');
await db.commit(); await db.commit();
} }
} }
@ -181,3 +157,52 @@ export type TableSchema = {
constraint: string, constraint: string,
}[], }[],
} }
const cloneRepo = async () => {
if (fs.existsSync('./articles/')) {
console.log('Pulling articles from git..');
await article_git.pull();
} else {
console.log('Cloning articles from git..');
await article_git.clone('git@gitea.hareworks.net:Hare/blog-articles.git', 'articles');
}
}
const createTable = async (db: Postgres, schemas: TableSchema[]) => {
await db.begin();
try {
for (const schema of schemas) {
const res = await db.query(`select * from information_schema.tables where table_name = '${schema.name}'`)
if (res.rowCount == 0) {
console.log(`Creating table ${schema.name}`);
const columnStr = schema.columns.map(c => `${c.name} ${c.type} ${c.constraint}`).join(', ');
await db.query(`create table ${schema.name} (${columnStr})`);
} else {
console.log(`Table ${schema.name} already exists`);
}
}
} catch (err) {
console.error(err);
await db.rollback();
} finally {
await db.commit();
}
}
const crawlArticles = async (db: Postgres): Promise<ArticleFileItem[]> => {
const articleFiles: ArticleFileItem[] = [];
function scanDir(path: string) {
const files = fs.readdirSync(path);
for (const file of files) {
const dir = `${path}/${file}`;
const stat = fs.statSync(dir);
if (stat.isDirectory()) {
scanDir(dir);
} else {
articleFiles.push({ path: `${path}/${file}`, id: file.replace('.md', '') });
}
}
}
scanDir('./articles/article');
return articleFiles;
}

View File

@ -1,6 +1,7 @@
import { error } from '@sveltejs/kit'; import { error } from '@sveltejs/kit';
import type { PageServerLoad } from './$types'; import type { PageServerLoad } from './$types';
import PG from '$lib/server/database'; import PG from '$lib/server/database';
import { format } from '$lib/scripts/formatted_date';
let data: { let data: {
recent: { recent: {
@ -28,7 +29,7 @@ export const load: PageServerLoad = async ({ params, locals }) => {
data.recent = recent_articles.rows.map((row) => ({ data.recent = recent_articles.rows.map((row) => ({
title: row.title, title: row.title,
image: row.image, image: row.image,
date: row.updated_at.toISOString().slice(0, 10), date: format(row.updated_at),
link: `/article/${row.category}/${row.id}`, link: `/article/${row.category}/${row.id}`,
tags: row.tags, tags: row.tags,
})); }));