ionian/src/db/doc.ts
2021-02-22 23:10:00 +09:00

173 lines
6.4 KiB
TypeScript

import { Document, DocumentBody, DocumentAccessor, QueryListOption } from '../model/doc';
import Knex from 'knex';
import {createKnexTagController} from './tag';
import { TagAccessor } from '../model/tag';
type DBTagContentRelation = {
doc_id:number,
tag_name:string
}
class KnexDocumentAccessor implements DocumentAccessor{
knex : Knex;
tagController: TagAccessor;
constructor(knex : Knex){
this.knex = knex;
this.tagController = createKnexTagController(knex);
}
async add(c: DocumentBody){
const {tags,additional, ...rest} = c;
const id_lst = await this.knex.insert({
additional:JSON.stringify(additional),
created_at:Date.now(),
...rest
}).into('document');
const id = id_lst[0];
for (const it of tags) {
this.tagController.addTag({name:it});
}
if(tags.length > 0){
await this.knex.insert<DBTagContentRelation>(
tags.map(x=>({doc_id:id,tag_name:x}))
).into("doc_tag_relation");
}
return id;
};
async del(id:number) {
if (await this.findById(id) !== undefined){
await this.knex.delete().from("doc_tag_relation").where({doc_id:id});
await this.knex.delete().from("document").where({id:id});
return true;
}
return false;
};
async findById(id:number,tagload?:boolean): Promise<Document|undefined>{
const s = await this.knex.select("*").from("document").where({id:id});
if(s.length === 0) return undefined;
const first = s[0];
let ret_tags:string[] = []
if(tagload === true){
const tags : DBTagContentRelation[] = await this.knex.select("*")
.from("doc_tag_relation").where({doc_id:first.id});
ret_tags = tags.map(x=>x.tag_name);
}
return {
...first,
tags:ret_tags,
additional: first.additional !== null ? JSON.parse(first.additional) : {},
};
};
async findDeleted(content_type:string){
const s = await this.knex.select("*")
.where({content_type:content_type})
.whereNotNull("update_at")
.from("document");
return s.map(x=>({
...x,
tags:[],
additional:{}
}));
}
async findList(option?:QueryListOption){
option = option || {};
const allow_tag = option.allow_tag || [];
const eager_loading = typeof option.eager_loading === "undefined" || option.eager_loading;
const limit = option.limit || 20;
const use_offset = option.use_offset || false;
const offset = option.offset || 0;
const word = option.word;
const content_type = option.content_type;
const cursor = option.cursor;
const buildquery = ()=>{
let query = this.knex.select("document.*");
if(allow_tag.length > 0){
query = query.from("doc_tag_relation as tags_0");
query = query.where("tags_0.tag_name","=",allow_tag[0]);
for (let index = 1; index < allow_tag.length; index++) {
const element = allow_tag[index];
query = query.innerJoin(`doc_tag_relation as tags_${index}`,`tags_${index}.doc_id`,"tags_0.doc_id");
query = query.where(`tags_${index}.tag_name`,'=',element);
}
query = query.innerJoin("document","tags_0.doc_id","document.id");
}
else{
query = query.from("document");
}
if(word !== undefined){
//don't worry about sql injection.
query = query.where('title','like',`%${word}%`);
}
if(content_type !== undefined){
query = query.where('content_type','=',content_type);
}
if(use_offset){
query = query.offset(offset);
}
else{
if(cursor !== undefined){
query = query.where('id','<',cursor);
}
}
query = query.limit(limit);
query = query.orderBy('id',"desc");
return query;
}
let query = buildquery();
//console.log(query.toSQL());
let result:Document[] = await query;
for(let i of result){
i.additional = JSON.parse((i.additional as unknown) as string);
}
if(eager_loading){
let idmap: {[index:number]:Document} = {};
for(const r of result){
idmap[r.id] = r;
r.tags = [];
}
let subquery = buildquery();
let tagquery= this.knex.select("id","doc_tag_relation.tag_name").from(subquery)
.innerJoin("doc_tag_relation","doc_tag_relation.doc_id","id");
//console.log(tagquery.toSQL());
let tagresult:{id:number,tag_name:string}[] = await tagquery;
for(const {id,tag_name} of tagresult){
idmap[id].tags.push(tag_name);
}
}
return result;
};
async findByPath(path:string,filename?:string):Promise<Document[]>{
const e = filename == undefined ? {} : {filename:filename}
const results = await this.knex.select("*").from("document").where({basepath:path,...e});
return results.map(x=>({
...x,
tags:[],
additional:{}
}))
}
async update(c:Partial<Document> & { id:number }){
const {id,tags,...rest} = c;
if (await this.findById(id) !== undefined){
await this.knex.update(rest).where({id: id}).from("document");
return true;
}
return false;
}
async addTag(c: Document,tag_name:string){
if (c.tags.includes(tag_name)) return false;
this.tagController.addTag({name:tag_name});
await this.knex.insert<DBTagContentRelation>({tag_name: tag_name, doc_id: c.id})
.into("doc_tag_relation");
c.tags.push(tag_name);
return true;
}
async delTag(c: Document,tag_name:string){
if (c.tags.includes(tag_name)) return false;
await this.knex.delete().where({tag_name: tag_name,doc_id: c.id}).from("doc_tag_relation");
c.tags.push(tag_name);
return true;
}
}
export const createKnexDocumentAccessor = (knex:Knex): DocumentAccessor=>{
return new KnexDocumentAccessor(knex);
}