// initialize
import type { Postgres } from '$lib/server/database';
import fs from 'fs';
import compile from '$lib/server/article';
import article_git from '$lib/server/article-git';
import { format } from '$lib/scripts/formatted_date';
export default async function init(db: Postgres) {
await cloneRepo();
await createTable(db, [
{
name: 'article',
columns: [
{ name: 'seq', type: 'serial', constraint: 'primary key' },
{ name: 'id', type: 'text', constraint: 'not null' },
{ name: 'released_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: 'image', type: 'text', constraint: '' },
{ name: 'publish', type: 'text', constraint: 'not null' },
{ name: 'content', type: 'text', constraint: 'not null' },
],
},
{
name: 'article_comment',
columns: [
{ name: 'id', type: 'serial', constraint: 'primary key' },
{ name: 'article', type: 'integer', constraint: 'not null' },
{ name: 'posted_at', type: 'timestamp', constraint: 'not null' },
{ name: 'content', type: 'text', constraint: 'not null' },
],
},
{
name: 'thread',
columns: [
{ name: 'seq', type: 'serial', constraint: 'primary key' },
{ name: 'id', type: 'text', constraint: 'not null' },
{ name: 'title', type: 'text', constraint: 'not null' },
{ name: 'category', type: 'text', constraint: 'not null' },
{ name: 'created_at', type: 'timestamp', constraint: 'not null' },
{ name: 'updated_at', type: 'timestamp', constraint: 'not null' },
{ name: 'tags', type: 'text[]', constraint: 'not null' },
{ name: 'content', type: 'text', constraint: 'not null' },
],
},
{
name: 'thread_post',
columns: [
{ name: 'seq', type: 'serial', constraint: 'primary key' },
{ name: 'thread_id', type: 'integer', constraint: 'not null' },
{ name: 'title', type: 'text', constraint: 'not null' },
{ name: 'posted_at', type: 'timestamp', constraint: 'not null' },
{ name: 'content', type: 'text', constraint: 'not null' },
],
},
{
name: 'thread_comment',
columns: [
{ name: 'id', type: 'serial', constraint: 'primary key' },
{ name: 'thread', type: 'integer', constraint: 'not null' },
{ name: 'posted_at', type: 'timestamp', constraint: 'not null' },
{ name: 'content', type: 'text', constraint: 'not null' },
],
},
{
name: 'tag',
columns: [
{ name: 'seq', type: 'serial', constraint: 'primary key' },
{ name: 'name', type: 'text', constraint: 'not null' },
{ name: 'ref_count', type: 'integer', constraint: 'not null' },
],
}
]);
const articleFiles = await crawlArticles(db);
await db.query('update tag set ref_count = 0');
for (const { path, id } of articleFiles) {
console.log(`Processing ${id}...`);
await db.begin();
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]);
// 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 compiled = await compile(fs.readFileSync(path, 'utf-8'));
const title = compiled.data.fm.title;
const tags: string[] = compiled.data.fm.tags;
const image = compiled.data.fm.image;
const publish = compiled.data.fm.publish;
const content = compiled.code
.replace(/>{@html ``}<\/pre>/g, '
')
if (res.rowCount == 0) {
console.log(`New article: ${id}`);
await db.query(
'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, released_at, updated_at, author, email, title, category, tags, image, publish, content]
);
// } else if (res.rows[0].updated_at < updated_at) {
} else if (true) {
console.log(`Update article: ${id}`);
await db.query(
'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, released_at, updated_at, author, email, title, category, tags, image, publish, content]
);
} else {
console.log(`Article ${id} is already up-to-date`);
}
for (const tag of tags) {
if ((await db.query('select * from tag where name = $1', [tag])).rowCount == 0) {
db.query('insert into tag (name, ref_count) values ($1, 1)', [tag]);
} else {
db.query('update tag set ref_count = ref_count + 1 where name = $1', [tag]);
}
}
} catch (err) {
console.log(err);
await db.rollback();
} finally {
console.log('');
await db.commit();
}
}
await db.release();
}
type ArticleFileItem = {
path: string,
id: string,
}
export type TableSchema = {
name: string,
columns: {
name: string,
type: 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 => {
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;
}