diff --git a/app.ts b/app.ts index c769f3f..c1701d7 100644 --- a/app.ts +++ b/app.ts @@ -3,7 +3,7 @@ import { get_setting } from "./src/setting"; import { create_server, start_server } from "./src/server"; import { getAdminAccessTokenValue,getAdminRefreshTokenValue, accessTokenName, refreshTokenName } from "./src/login"; -const get_loading_html = (content?:string)=> ` +const get_loading_html = (message?:string)=> ` react-sample @@ -31,8 +31,8 @@ h1 { } -

${content || "Loading..."}

- ${content === undefined ? '
' : ""} +

${message || "Loading..."}

+ ${message === undefined ? '
' : ""} `; diff --git a/migrations/initial.ts b/migrations/initial.ts index 6a8b075..86a16d3 100644 --- a/migrations/initial.ts +++ b/migrations/initial.ts @@ -6,7 +6,7 @@ export async function up(knex:Knex) { b.string("password_hash",64).notNullable(); b.string("password_salt",64).notNullable(); }); - await knex.schema.createTable("contents",(b)=>{ + await knex.schema.createTable("document",(b)=>{ b.increments("id").primary(); b.string("title").notNullable(); b.string("content_type",16).notNullable(); @@ -21,12 +21,12 @@ export async function up(knex:Knex) { b.string("name").primary(); b.text("description"); }); - await knex.schema.createTable("content_tag_relation",(b)=>{ - b.integer("content_id").unsigned().notNullable(); + await knex.schema.createTable("doc_tag_relation",(b)=>{ + b.integer("doc_id").unsigned().notNullable(); b.string("tag_name").notNullable(); - b.foreign("content_id").references("contents.id"); + b.foreign("doc_id").references("document.id"); b.foreign("tag_name").references("tags.name"); - b.primary(["content_id","tag_name"]); + b.primary(["doc_id","tag_name"]); }); await knex.schema.createTable("permissions",b=>{ b.string('username').notNullable(); @@ -45,8 +45,8 @@ export async function up(knex:Knex) { export async function down(knex:Knex) { //throw new Error('Downward migrations are not supported. Restore from backup.'); await knex.schema.dropTable("users"); - await knex.schema.dropTable("contents"); + await knex.schema.dropTable("document"); await knex.schema.dropTable("tags"); - await knex.schema.dropTable("content_tag_relation"); + await knex.schema.dropTable("document_tag_relation"); await knex.schema.dropTable("permissions"); }; diff --git a/package.json b/package.json index 9ca8a54..52e83ab 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,7 @@ }, "build": { "asar": false, - "files":[ + "files": [ "build/**/*", "node_modules/**/*", "package.json" @@ -79,6 +79,7 @@ "css-loader": "^5.0.1", "electron": "^11.1.1", "electron-builder": "^22.9.1", + "eslint-plugin-node": "^11.1.0", "mini-css-extract-plugin": "^1.3.3", "style-loader": "^2.0.0", "ts-node": "^9.1.1", diff --git a/plan.md b/plan.md index 6c91847..8427437 100644 --- a/plan.md +++ b/plan.md @@ -5,8 +5,9 @@ ### server routing - content - \d+ - - image - - (?P\d+) + - manga + - (?P<page>\d+) + - video - diff - itemNum - diff @@ -14,6 +15,8 @@ - commit - user - login + - logout + - refresh ### client routing @@ -23,6 +26,9 @@ - login - profile - search ? querystring...; +- setting +- difference +- profile ## TODO - server push diff --git a/src/client/accessor/contents.ts b/src/client/accessor/document.ts similarity index 72% rename from src/client/accessor/contents.ts rename to src/client/accessor/document.ts index d35353c..98d442f 100644 --- a/src/client/accessor/contents.ts +++ b/src/client/accessor/document.ts @@ -1,8 +1,8 @@ -import {Content, ContentAccessor, ContentContent, QueryListOption} from "../../model/contents"; +import {Document, DocumentAccessor, DocumentBody, QueryListOption} from "../../model/doc"; import {toQueryString} from './util'; -const baseurl = "/content"; +const baseurl = "/api/doc"; -export * from "../../model/contents"; +export * from "../../model/doc"; export class FetchFailError implements Error{ name: string; @@ -14,14 +14,14 @@ export class FetchFailError implements Error{ this.stack = stack; } } -export class ClientContentAccessor implements ContentAccessor{ - async findList(option?: QueryListOption | undefined): Promise{ +export class ClientDocumentAccessor implements DocumentAccessor{ + async findList(option?: QueryListOption | undefined): Promise{ let res = await fetch(`${baseurl}/search?${option !== undefined ? toQueryString(option) : ""}`); if(res.status !== 200) throw new FetchFailError("findList Failed"); let ret = await res.json(); return ret; } - async findById(id: number, tagload?: boolean | undefined): Promise{ + async findById(id: number, tagload?: boolean | undefined): Promise{ let res = await fetch(`${baseurl}/${id}`); if(res.status !== 200) throw new FetchFailError("findById Failed");; let ret = await res.json(); @@ -30,11 +30,11 @@ export class ClientContentAccessor implements ContentAccessor{ /** * not implement */ - async findListByBasePath(basepath: string): Promise{ + async findListByBasePath(basepath: string): Promise{ throw new Error("not implement"); return []; } - async update(c: Partial & { id: number; }): Promise{ + async update(c: Partial & { id: number; }): Promise{ const {id,...rest} = c; const res = await fetch(`${baseurl}/${id}`,{ method: "POST", @@ -43,7 +43,7 @@ export class ClientContentAccessor implements ContentAccessor{ const ret = await res.json(); return ret; } - async add(c: ContentContent): Promise{ + async add(c: DocumentBody): Promise{ const res = await fetch(`${baseurl}`,{ method: "POST", body: JSON.stringify(c) @@ -58,7 +58,7 @@ export class ClientContentAccessor implements ContentAccessor{ const ret = await res.json(); return ret; } - async addTag(c: Content, tag_name: string): Promise{ + async addTag(c: Document, tag_name: string): Promise{ const {id,...rest} = c; const res = await fetch(`${baseurl}/${id}/tags/${tag_name}`,{ method: "POST", @@ -67,7 +67,7 @@ export class ClientContentAccessor implements ContentAccessor{ const ret = await res.json(); return ret; } - async delTag(c: Content, tag_name: string): Promise{ + async delTag(c: Document, tag_name: string): Promise{ const {id,...rest} = c; const res = await fetch(`${baseurl}/${id}/tags/${tag_name}`,{ method: "DELETE", @@ -77,9 +77,9 @@ export class ClientContentAccessor implements ContentAccessor{ return ret; } } -export const CContentAccessor = new ClientContentAccessor; -export const makeThumbnailUrl = (x: Content)=>{ - return `/content/${x.id}/${x.content_type}/thumbnail`; +export const CDocumentAccessor = new ClientDocumentAccessor; +export const makeThumbnailUrl = (x: Document)=>{ + return `${baseurl}/${x.id}/${x.content_type}/thumbnail`; } -export default CContentAccessor; \ No newline at end of file +export default CDocumentAccessor; \ No newline at end of file diff --git a/src/client/app.tsx b/src/client/app.tsx index a469b01..baaf2f4 100644 --- a/src/client/app.tsx +++ b/src/client/app.tsx @@ -1,7 +1,7 @@ import React, { createContext, useEffect, useRef, useState } from 'react'; import ReactDom from 'react-dom'; import {BrowserRouter, Redirect, Route, Switch as RouterSwitch} from 'react-router-dom'; -import { Gallery, ContentAbout, LoginPage, NotFoundPage, ProfilePage,DifferencePage} from './page/mod'; +import { Gallery, DocumentAbout, LoginPage, NotFoundPage, ProfilePage,DifferencePage} from './page/mod'; import {getInitialValue, UserContext} from './state'; import './css/style.css'; @@ -27,7 +27,7 @@ const App = () => { }> }> - }> + }> }/> diff --git a/src/client/component/contentinfo.tsx b/src/client/component/contentinfo.tsx index 6e58843..5d2d484 100644 --- a/src/client/component/contentinfo.tsx +++ b/src/client/component/contentinfo.tsx @@ -1,6 +1,6 @@ import React, { useState, useEffect } from 'react'; import { Redirect, Route, Switch, useHistory, useRouteMatch, match as MatchType, Link as RouterLink } from 'react-router-dom'; -import ContentAccessor, { Content } from '../accessor/contents'; +import { Document } from '../accessor/document'; import { LoadingCircle } from '../component/loading'; import { Link, Paper, makeStyles, Theme, Box, useTheme, Typography } from '@material-ui/core'; import { ThumbnailContainer } from '../page/reader/reader'; @@ -74,7 +74,7 @@ const useStyles = makeStyles((theme: Theme) => ({ })) export const ContentInfo = (props: { - content: Content, children?: React.ReactNode, classes?: { + document: Document, children?: React.ReactNode, classes?: { root?: string, thumbnail_anchor?: string, thumbnail_content?: string, @@ -88,31 +88,30 @@ export const ContentInfo = (props: { }) => { const classes = useStyles(); const theme = useTheme(); - const content = props.content; + const document = props.document; const propclasses = props.classes || {}; - const rootName = props.short ? classes.short_root : classes.root; const thumbnail_anchor = props.short ? classes.short_thumbnail_anchor : ""; const thumbnail_content = props.short ? classes.short_thumbnail_content : classes.thumbnail_content; const subinfoContainer = props.short ? classes.short_subinfoContainer : classes.subinfoContainer; - let allTag = content.tags; + let allTag = document.tags; const artists = allTag.filter(x => x.startsWith("artist:")).map(x => x.substr(7)); allTag = allTag.filter(x => !x.startsWith("artist:")); return ( - - {content.title} + {document.title} Artist diff --git a/src/client/component/galleryinfo.tsx b/src/client/component/galleryinfo.tsx index e9f3fe4..886e2e4 100644 --- a/src/client/component/galleryinfo.tsx +++ b/src/client/component/galleryinfo.tsx @@ -1,7 +1,7 @@ import { Box, Paper, Link, useMediaQuery, Portal, List, ListItem, ListItemIcon, Tooltip, ListItemText } from '@material-ui/core'; import {useTheme, makeStyles, Theme} from '@material-ui/core/styles'; import React, { useEffect, useState } from 'react'; -import ContentAccessor,{QueryListOption, Content} from '../accessor/contents'; +import ContentAccessor,{QueryListOption, Document} from '../accessor/document'; import {Link as RouterLink} from 'react-router-dom'; import { LoadingCircle, ContentInfo, NavList, NavItem } from './mod'; import {toQueryString} from '../accessor/util'; @@ -42,29 +42,29 @@ export type GalleryProp = { option?:QueryListOption; }; type GalleryState = { - content:Content[]|undefined; + documents:Document[]|undefined; } export const GalleryInfo = (props: GalleryProp)=>{ - const [state,setState]= useState({content:undefined}); + const [state,setState]= useState({documents:undefined}); useEffect(()=>{ const load = (async ()=>{ const c = await ContentAccessor.findList(props.option); //todo : if c is undefined, retry to fetch 3 times. and show error message. - setState({content:c}); + setState({documents:c}); }) load(); },[props.option]); const classes = useStyles(); const queryString = toQueryString(props.option||{}); - if(state.content === undefined){ + if(state.documents === undefined){ return (); } else{ return ({ - state.content.map(x=>{ - return ({ + return (); }) } diff --git a/src/client/page/contentinfo.tsx b/src/client/page/contentinfo.tsx index 7e38221..755a85f 100644 --- a/src/client/page/contentinfo.tsx +++ b/src/client/page/contentinfo.tsx @@ -1,6 +1,6 @@ import React, { useState, useEffect, useContext } from 'react'; import { Redirect, Route, Switch, useHistory, useRouteMatch, match as MatchType, Link as RouterLink, useParams, useLocation } from 'react-router-dom'; -import ContentAccessor, { Content } from '../accessor/contents'; +import DocumentAccessor, { Document } from '../accessor/document'; import { LoadingCircle } from '../component/loading'; import { Link, Paper, makeStyles, Theme, Box, useTheme, Typography } from '@material-ui/core'; import {ArrowBack as ArrowBackIcon } from '@material-ui/icons'; @@ -10,8 +10,8 @@ import { BackItem, CommonMenuList, ContentInfo, Headline, NavItem, NavList } fro export const makeContentInfoUrl = (id: number) => `/doc/${id}`; export const makeMangaReaderUrl = (id: number) => `/doc/${id}/reader`; -type ContentState = { - content: Content | undefined, +type DocumentState = { + doc: Document | undefined, notfound: boolean, } @@ -27,25 +27,25 @@ const useStyles = makeStyles((theme:Theme)=>({ } })); -export const ContentAbout = (prop: { match: MatchType }) => { +export const DocumentAbout = (prop: { match: MatchType }) => { const match = useRouteMatch<{id:string}>("/doc/:id"); if (match == null) { throw new Error("unreachable"); } const id = Number.parseInt(match.params['id']); - const [info, setInfo] = useState({ content: undefined, notfound:false }); - const menu_list = () => ; + const [info, setInfo] = useState({ doc: undefined, notfound:false }); + const menu_list = (link?:string) => ; useEffect(() => { (async () => { if (!isNaN(id)) { - const c = await ContentAccessor.findById(id); - setInfo({ content: c, notfound: c === undefined }); + const c = await DocumentAccessor.findById(id); + setInfo({ doc: c, notfound: c === undefined }); } - })() + })(); }, []); const classes = useStyles(); - + console.log(info.doc); if (isNaN(id)) { return ( @@ -60,18 +60,18 @@ export const ContentAbout = (prop: { match: MatchType }) => { ) } - else if (info.content === undefined) { + else if (info.doc === undefined) { return ( ); } else{ - const ReaderPage = getPresenter(info.content); + const ReaderPage = getPresenter(info.doc); return ( - + @@ -79,7 +79,7 @@ export const ContentAbout = (prop: { match: MatchType }) => { content:classes.noPaddingContent, toolbar:classes.noPaddingToolbar }}> - + diff --git a/src/client/page/gallery.tsx b/src/client/page/gallery.tsx index 33fd96d..74d710f 100644 --- a/src/client/page/gallery.tsx +++ b/src/client/page/gallery.tsx @@ -8,9 +8,8 @@ import { QueryStringToMap } from '../accessor/util'; export const Gallery = ()=>{ const location = useLocation(); const query = QueryStringToMap(location.search); - location; const menu_list = CommonMenuList({url:location.search}); - + return ( ) diff --git a/src/client/page/reader/manga.tsx b/src/client/page/reader/manga.tsx index c1c857d..631fd83 100644 --- a/src/client/page/reader/manga.tsx +++ b/src/client/page/reader/manga.tsx @@ -1,6 +1,6 @@ import React, {useState, useEffect} from 'react'; import { Typography, useTheme } from '@material-ui/core'; -import { Content } from '../../accessor/contents'; +import { Document } from '../../accessor/document'; type MangaType = "manga"|"artist cg"|"donjinshi"|"western" @@ -13,9 +13,9 @@ export type PresentableTag = { tags: string[], } -export const MangaReader = (props:{content:Content})=>{ +export const MangaReader = (props:{doc:Document})=>{ const theme = useTheme(); - const additional = props.content.additional; + const additional = props.doc.additional; const [curPage,setCurPage] = useState(0); if(!('page' in additional)){ console.error("invalid content : page read fail : "+ JSON.stringify(additional)); @@ -40,7 +40,7 @@ export const MangaReader = (props:{content:Content})=>{ }); //theme.mixins.toolbar.minHeight; return (
-
); } diff --git a/src/client/page/reader/reader.tsx b/src/client/page/reader/reader.tsx index 8ec4e41..71601b1 100644 --- a/src/client/page/reader/reader.tsx +++ b/src/client/page/reader/reader.tsx @@ -1,18 +1,18 @@ import { Typography } from '@material-ui/core'; import React from 'react'; -import { Content, makeThumbnailUrl } from '../../accessor/contents'; +import { Document, makeThumbnailUrl } from '../../accessor/document'; import {MangaReader} from './manga'; import {VideoReader} from './video' export interface PagePresenterProp{ - content:Content, + doc:Document, className?:string } interface PagePresenter{ (prop:PagePresenterProp):JSX.Element } -export const getPresenter = (content:Content):PagePresenter => { +export const getPresenter = (content:Document):PagePresenter => { switch (content.content_type) { case "manga": return MangaReader; @@ -22,7 +22,7 @@ export const getPresenter = (content:Content):PagePresenter => { return ()=>Not implemented reader; } -export const ThumbnailContainer = (props:{content:Content, className?:string})=>{ +export const ThumbnailContainer = (props:{content:Document, className?:string})=>{ const thumbnailurl = makeThumbnailUrl(props.content); if(props.content.content_type === "video"){ return () diff --git a/src/client/page/reader/video.tsx b/src/client/page/reader/video.tsx index caa546e..fccd2d6 100644 --- a/src/client/page/reader/video.tsx +++ b/src/client/page/reader/video.tsx @@ -1,7 +1,7 @@ import React from 'react'; -import { Content } from '../../accessor/contents'; +import { Document } from '../../accessor/document'; -export const VideoReader = (props:{content:Content})=>{ - const id = props.content.id; - return ; +export const VideoReader = (props:{doc:Document})=>{ + const id = props.doc.id; + return ; } \ No newline at end of file diff --git a/src/content/manga.ts b/src/content/manga.ts index 68535ab..66f54f3 100644 --- a/src/content/manga.ts +++ b/src/content/manga.ts @@ -1,8 +1,14 @@ -import {ContentReferrer} from './referrer'; +import {ContentFile} from './referrer'; import {createDefaultClass,registerContentReferrer} from './referrer'; +import {readZip} from '../util/zipwrap'; export class MangaReferrer extends createDefaultClass("manga"){ constructor(path:string){ super(path); + /*(async ()=>{ + const zip = await readZip(path); + const entry = zip.entries(); + + })*/ } }; registerContentReferrer(MangaReferrer); \ No newline at end of file diff --git a/src/content/mod.ts b/src/content/mod.ts index 6bebf16..9bef609 100644 --- a/src/content/mod.ts +++ b/src/content/mod.ts @@ -1,3 +1,3 @@ import './manga'; import './video'; -export {ContentReferrer,createContentReferrer} from './referrer'; \ No newline at end of file +export {ContentFile as ContentReferrer,createContentReferrer} from './referrer'; \ No newline at end of file diff --git a/src/content/referrer.ts b/src/content/referrer.ts index f4b0395..4284a0d 100644 --- a/src/content/referrer.ts +++ b/src/content/referrer.ts @@ -6,15 +6,15 @@ import {extname} from 'path'; /** * content file or directory referrer */ -export interface ContentReferrer{ +export interface ContentFile{ getHash():Promise; readonly path: string; readonly type: string; desc: object|undefined; } -type ContentReferrerConstructor = (new (path:string,desc?:object) => ContentReferrer)&{content_type:string}; -export const createDefaultClass = (type:string):ContentReferrerConstructor=>{ - let cons = class implements ContentReferrer{ +type ContentFileConstructor = (new (path:string,desc?:object) => ContentFile)&{content_type:string}; +export const createDefaultClass = (type:string):ContentFileConstructor=>{ + let cons = class implements ContentFile{ readonly path: string; type = type; static content_type = type; @@ -37,8 +37,8 @@ export const createDefaultClass = (type:string):ContentReferrerConstructor=>{ }; return cons; } -let ContstructorTable:{[k:string]:ContentReferrerConstructor} = {}; -export function registerContentReferrer(s: ContentReferrerConstructor){ +let ContstructorTable:{[k:string]:ContentFileConstructor} = {}; +export function registerContentReferrer(s: ContentFileConstructor){ console.log(`registered content type: ${s.content_type}`) ContstructorTable[s.content_type] = s; } @@ -50,7 +50,7 @@ export function createContentReferrer(type:string,path:string,desc?:object){ } return new constructorMethod(path,desc); } -export function getContentRefererConstructor(type:string): ContentReferrerConstructor|undefined{ +export function getContentRefererConstructor(type:string): ContentFileConstructor|undefined{ const ret = ContstructorTable[type]; return ret; } \ No newline at end of file diff --git a/src/content/video.ts b/src/content/video.ts index 8a16429..06a206c 100644 --- a/src/content/video.ts +++ b/src/content/video.ts @@ -1,4 +1,4 @@ -import {ContentReferrer, registerContentReferrer} from './referrer'; +import {ContentFile, registerContentReferrer} from './referrer'; import {createDefaultClass} from './referrer'; export class VideoReferrer extends createDefaultClass("video"){ diff --git a/src/db/contents.ts b/src/db/doc.ts similarity index 69% rename from src/db/contents.ts rename to src/db/doc.ts index bd310d7..8230072 100644 --- a/src/db/contents.ts +++ b/src/db/doc.ts @@ -1,53 +1,53 @@ -import { Content, ContentContent, ContentAccessor, QueryListOption } from '../model/contents'; +import { Document, DocumentBody, DocumentAccessor, QueryListOption } from '../model/doc'; import Knex from 'knex'; import {createKnexTagController} from './tag'; import { TagAccessor } from '../model/tag'; type DBTagContentRelation = { - content_id:number, + doc_id:number, tag_name:string } -class KnexContentsAccessor implements ContentAccessor{ +class KnexContentsAccessor implements DocumentAccessor{ knex : Knex; tagController: TagAccessor; constructor(knex : Knex){ this.knex = knex; this.tagController = createKnexTagController(knex); } - async add(c: ContentContent){ + async add(c: DocumentBody){ const {tags,additional, ...rest} = c; const id_lst = await this.knex.insert({ additional:JSON.stringify(additional), ...rest - }).into('contents'); + }).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( - tags.map(x=>({content_id:id,tag_name:x})) - ).into("content_tag_relation"); + 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("content_tag_relation").where({content_id:id}); - await this.knex.delete().from("contents").where({id:id}); + 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{ - const s = await this.knex.select("*").from("contents").where({id:id}); + async findById(id:number,tagload?:boolean): Promise{ + 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("content_tag_relation").where({content_id:first.id}); + .from("doc_tag_relation").where({doc_id:first.id}); ret_tags = tags.map(x=>x.tag_name); } return { @@ -70,13 +70,13 @@ class KnexContentsAccessor implements ContentAccessor{ const buildquery = ()=>{ let query = this.knex.select("*"); if(allow_tag.length > 0){ - query = query.from("content_tag_relation").innerJoin("contents","content_tag_relation.content_id","contents.id"); + query = query.from("doc_tag_relation").innerJoin("document","doc_tag_relation.doc_id","document.id"); for(const tag of allow_tag){ query = query.where({tag_name:tag}); } } else{ - query = query.from("contents"); + query = query.from("document"); } if(word !== undefined){ //don't worry about sql injection. @@ -98,56 +98,58 @@ class KnexContentsAccessor implements ContentAccessor{ } let query = buildquery(); //console.log(query.toSQL()); - let result:Content[] = await query; + 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]:Content} = {}; + let idmap: {[index:number]:Document} = {}; for(const r of result){ idmap[r.id] = r; r.tags = []; } let subquery = buildquery(); - let tagresult:{id:number,tag_name:string}[] = await this.knex.select("id","content_tag_relation.tag_name").from(subquery) - .innerJoin("content_tag_relation","content_tag_relation.content_id","id"); + 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 findListByBasePath(path:string):Promise{ - let results = await this.knex.select("*").from("contents").where({basepath:path}); + async findListByBasePath(path:string):Promise{ + let results = await this.knex.select("*").from("document").where({basepath:path}); return results.map(x=>({ ...x, tags:[], additional:JSON.parse(x.additional || "{}"), })); } - async update(c:Partial & { id:number }){ + async update(c:Partial & { id:number }){ const {id,tags,...rest} = c; if (await this.findById(id) !== undefined){ - await this.knex.update(rest).where({id: id}).from("contents"); + await this.knex.update(rest).where({id: id}).from("document"); return true; } return false; } - async addTag(c: Content,tag_name:string){ + 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({tag_name: tag_name, content_id: c.id}) - .into("content_tag_relation"); + await this.knex.insert({tag_name: tag_name, doc_id: c.id}) + .into("doc_tag_relation"); c.tags.push(tag_name); return true; } - async delTag(c: Content,tag_name:string){ + async delTag(c: Document,tag_name:string){ if (c.tags.includes(tag_name)) return false; - await this.knex.delete().where({tag_name: tag_name,content_id: c.id}).from("content_tag_relation"); + 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 createKnexContentsAccessor = (knex:Knex): ContentAccessor=>{ +export const createKnexContentsAccessor = (knex:Knex): DocumentAccessor=>{ return new KnexContentsAccessor(knex); } \ No newline at end of file diff --git a/src/db/mod.ts b/src/db/mod.ts index c36784f..747d980 100644 --- a/src/db/mod.ts +++ b/src/db/mod.ts @@ -1,3 +1,3 @@ -export * from './contents'; +export * from './doc'; export * from './tag'; export * from './user'; \ No newline at end of file diff --git a/src/diff/diff.ts b/src/diff/diff.ts index 96b3d66..d9578ea 100644 --- a/src/diff/diff.ts +++ b/src/diff/diff.ts @@ -1,6 +1,6 @@ import { watch } from 'fs'; import { promises } from 'fs'; -import { ContentReferrer, createContentReferrer, getContentRefererConstructor } from '../content/referrer' +import { ContentFile, createContentReferrer, getContentRefererConstructor } from '../content/referrer' import path from 'path'; const readdir = promises.readdir; @@ -12,21 +12,21 @@ export class Watcher{ /** * @todo : alter type Map */ - private _added: ContentReferrer[]; - private _deleted: ContentReferrer[]; + private _added: ContentFile[]; + private _deleted: ContentFile[]; constructor(path:string,type:string){ this._path = path; this._added =[]; this._deleted =[]; this._type = type; } - public get added() : ContentReferrer[] { + public get added() : ContentFile[] { return this._added; } /*public set added(diff : FileDiff[]) { this._added = diff; }*/ - public get deleted(): ContentReferrer[]{ + public get deleted(): ContentFile[]{ return this._deleted; } /*public set deleted(diff : FileDiff[]){ diff --git a/src/model/contents.ts b/src/model/doc.ts similarity index 61% rename from src/model/contents.ts rename to src/model/doc.ts index dd380ed..3d2b7c5 100644 --- a/src/model/contents.ts +++ b/src/model/doc.ts @@ -1,12 +1,12 @@ import {TagAccessor} from './tag'; -import {check_type} from './../util/type_check' +import {check_type} from '../util/type_check' type JSONPrimitive = null|boolean|number|string; interface JSONMap extends Record{} interface JSONArray extends Array{}; type JSONType = JSONMap|JSONPrimitive|JSONArray; -export interface ContentContent{ +export interface DocumentBody{ title : string, content_type : string, basepath : string, @@ -16,7 +16,7 @@ export interface ContentContent{ tags : string[],//eager loading } -export const MetaContentContent = { +export const MetaContentBody = { title : "string", content_type : "string", basepath : "string", @@ -26,18 +26,18 @@ export const MetaContentContent = { tags : "string[]", } -export const isContentContent = (c : any):c is ContentContent =>{ - return check_type(c,MetaContentContent); +export const isDocBody = (c : any):c is DocumentBody =>{ + return check_type(c,MetaContentBody); } -export interface Content extends ContentContent{ +export interface Document extends DocumentBody{ readonly id: number; }; -export const isContent = (c: any):c is Content =>{ +export const isDoc = (c: any):c is Document =>{ if('id' in c && typeof c['id'] === "number"){ const {id, ...rest} = c; - return isContentContent(rest); + return isDocBody(rest); } return false; } @@ -59,11 +59,11 @@ export type QueryListOption = { */ use_offset?:boolean, /** - * cursor of contents + * cursor of documents */ cursor?:number, /** - * offset of contents + * offset of documents */ offset?:number, /** @@ -77,41 +77,41 @@ export type QueryListOption = { content_type?:string } -export interface ContentAccessor{ +export interface DocumentAccessor{ /** * find list by option - * @returns content list + * @returns documents list */ - findList: (option?:QueryListOption)=>Promise, + findList: (option?:QueryListOption)=>Promise, /** - * @returns content if exist, otherwise undefined + * @returns document if exist, otherwise undefined */ - findById: (id:number,tagload?:boolean)=> Promise, + findById: (id:number,tagload?:boolean)=> Promise, /** * */ - findListByBasePath:(basepath: string)=>Promise; + findListByBasePath:(basepath: string)=>Promise; /** - * update content except tag. + * update document except tag. */ - update:(c:Partial & { id:number })=>Promise; + update:(c:Partial & { id:number })=>Promise; /** - * add content + * add document */ - add:(c:ContentContent)=>Promise; + add:(c:DocumentBody)=>Promise; /** - * delete content + * delete document * @returns if it exists, return true. */ del:(id:number)=>Promise; /** - * @param c Valid Content + * @param c Valid Document * @param tagname tag name to add * @returns if success, return true */ - addTag:(c:Content,tag_name:string)=>Promise; + addTag:(c:Document,tag_name:string)=>Promise; /** * @returns if success, return true */ - delTag:(c:Content,tag_name:string)=>Promise; + delTag:(c:Document,tag_name:string)=>Promise; }; \ No newline at end of file diff --git a/src/model/mod.ts b/src/model/mod.ts index c36784f..747d980 100644 --- a/src/model/mod.ts +++ b/src/model/mod.ts @@ -1,3 +1,3 @@ -export * from './contents'; +export * from './doc'; export * from './tag'; export * from './user'; \ No newline at end of file diff --git a/src/route/all.ts b/src/route/all.ts index 1237c03..9591cec 100644 --- a/src/route/all.ts +++ b/src/route/all.ts @@ -14,7 +14,7 @@ const all_middleware = (cont: string|undefined, restarg: string|undefined)=>asyn ctx.status = 404; return; } - if(ctx.state.content.type != cont){ + if(ctx.state.location.type != cont){ console.error("not matched") ctx.status = 404; return; @@ -24,8 +24,7 @@ const all_middleware = (cont: string|undefined, restarg: string|undefined)=>asyn ctx.status = 404; return; } - const rest = "/"+(restarg as string|undefined || ""); - + const rest = "/"+(restarg || ""); const result = router.match(rest,"GET"); if(!result.route){ return await next(); diff --git a/src/route/contents.ts b/src/route/contents.ts index e1f5459..ee370f7 100644 --- a/src/route/contents.ts +++ b/src/route/contents.ts @@ -1,34 +1,34 @@ import { Context, Next } from 'koa'; import Router from 'koa-router'; -import {Content, ContentAccessor, isContentContent} from './../model/contents'; -import {QueryListOption} from './../model/contents'; +import {Document, DocumentAccessor, isDocBody} from '../model/doc'; +import {QueryListOption} from '../model/doc'; import {ParseQueryNumber, ParseQueryArray, ParseQueryBoolean} from './util' import {sendError} from './error_handler'; -import { createContentReferrer } from '../content/mod'; import { join } from 'path'; import {AllContentRouter} from './all'; import {createPermissionCheckMiddleware as PerCheck, Permission as Per, AdminOnlyMiddleware as AdminOnly} from '../permission/permission'; +import {ContentLocation} from './context' -const ContentIDHandler = (controller: ContentAccessor) => async (ctx: Context,next: Next)=>{ +const ContentIDHandler = (controller: DocumentAccessor) => async (ctx: Context,next: Next)=>{ const num = Number.parseInt(ctx.params['num']); - let content = await controller.findById(num,true); - if (content == undefined){ - return sendError(404,"content does not exist."); + let document = await controller.findById(num,true); + if (document == undefined){ + return sendError(404,"document does not exist."); } - ctx.body = content; + ctx.body = document; ctx.type = 'json'; - console.log(content.additional); + console.log(document.additional); }; -const ContentTagIDHandler = (controller: ContentAccessor) => async (ctx: Context,next: Next)=>{ +const ContentTagIDHandler = (controller: DocumentAccessor) => async (ctx: Context,next: Next)=>{ const num = Number.parseInt(ctx.params['num']); - let content = await controller.findById(num,true); - if (content == undefined){ - return sendError(404,"content does not exist."); + let document = await controller.findById(num,true); + if (document == undefined){ + return sendError(404,"document does not exist."); } - ctx.body = content.tags || []; + ctx.body = document.tags || []; ctx.type = 'json'; }; -const ContentQueryHandler = (controller : ContentAccessor) => async (ctx: Context,next: Next)=>{ +const ContentQueryHandler = (controller : DocumentAccessor) => async (ctx: Context,next: Next)=>{ const limit = ParseQueryNumber(ctx.query['limit']); const cursor = ParseQueryNumber(ctx.query['cursor']); const word: string|undefined = ctx.query['word']; @@ -52,35 +52,35 @@ const ContentQueryHandler = (controller : ContentAccessor) => async (ctx: Contex use_offset: use_offset, content_type:content_type, }; - let content = await controller.findList(option); - ctx.body = content; + let document = await controller.findList(option); + ctx.body = document; ctx.type = 'json'; } -const UpdateContentHandler = (controller : ContentAccessor) => async (ctx: Context, next: Next) => { +const UpdateContentHandler = (controller : DocumentAccessor) => async (ctx: Context, next: Next) => { const num = Number.parseInt(ctx.params['num']); if(ctx.request.type !== 'json'){ - return sendError(400,"update fail. invalid content type: it is not json."); + return sendError(400,"update fail. invalid document type: it is not json."); } if(typeof ctx.request.body !== "object"){ return sendError(400,"update fail. invalid argument: not"); } - const content_desc: Partial & {id: number} = { + const content_desc: Partial & {id: number} = { id:num,...ctx.request.body }; const success = await controller.update(content_desc); ctx.body = JSON.stringify(success); ctx.type = 'json'; } -const CreateContentHandler = (controller : ContentAccessor) => async (ctx: Context, next: Next) => { +const CreateContentHandler = (controller : DocumentAccessor) => async (ctx: Context, next: Next) => { const content_desc = ctx.request.body; - if(!isContentContent(content_desc)){ + if(!isDocBody(content_desc)){ return sendError(400,"it is not a valid format"); } const id = await controller.add(content_desc); ctx.body = JSON.stringify(id); ctx.type = 'json'; }; -const AddTagHandler = (controller: ContentAccessor)=>async (ctx: Context, next: Next)=>{ +const AddTagHandler = (controller: DocumentAccessor)=>async (ctx: Context, next: Next)=>{ let tag_name = ctx.params['tag']; const num = Number.parseInt(ctx.params['num']); if(typeof tag_name === undefined){ @@ -95,7 +95,7 @@ const AddTagHandler = (controller: ContentAccessor)=>async (ctx: Context, next: ctx.body = JSON.stringify(r); ctx.type = 'json'; }; -const DelTagHandler = (controller: ContentAccessor)=>async (ctx: Context, next: Next)=>{ +const DelTagHandler = (controller: DocumentAccessor)=>async (ctx: Context, next: Next)=>{ let tag_name = ctx.params['tag']; const num = Number.parseInt(ctx.params['num']); if(typeof tag_name === undefined){ @@ -110,24 +110,28 @@ const DelTagHandler = (controller: ContentAccessor)=>async (ctx: Context, next: ctx.body = JSON.stringify(r); ctx.type = 'json'; } -const DeleteContentHandler = (controller : ContentAccessor) => async (ctx: Context, next: Next) => { +const DeleteContentHandler = (controller : DocumentAccessor) => async (ctx: Context, next: Next) => { const num = Number.parseInt(ctx.params['num']); const r = await controller.del(num); ctx.body = JSON.stringify(r); ctx.type = 'json'; }; -const ContentHandler = (controller : ContentAccessor) => async (ctx:Context, next:Next) => { +const ContentHandler = (controller : DocumentAccessor) => async (ctx:Context, next:Next) => { const num = Number.parseInt(ctx.params['num']); - let content = await controller.findById(num,true); - if (content == undefined){ - return sendError(404,"content does not exist."); + let document = await controller.findById(num,true); + if (document == undefined){ + return sendError(404,"document does not exist."); } - const path = join(content.basepath,content.filename); - ctx.state['content'] = createContentReferrer(content.content_type,path,content.additional); + const path = join(document.basepath,document.filename); + ctx.state['location'] = { + path:path, + type:document.content_type, + additional:document.additional, + } as ContentLocation; await next(); }; -export const getContentRouter = (controller: ContentAccessor)=>{ +export const getContentRouter = (controller: DocumentAccessor)=>{ const ret = new Router(); ret.get("/search",PerCheck(Per.QueryContent),ContentQueryHandler(controller)); ret.get("/:num(\\d+)",PerCheck(Per.QueryContent),ContentIDHandler(controller)); diff --git a/src/route/context.ts b/src/route/context.ts index 9ca6268..2e08aba 100644 --- a/src/route/context.ts +++ b/src/route/context.ts @@ -1,5 +1,10 @@ import {ContentReferrer} from '../content/mod'; +export type ContentLocation = { + path:string, + type:string, + additional:object|undefined, +} export interface ContentContext{ - content:ContentReferrer + location:ContentLocation } \ No newline at end of file diff --git a/src/route/manga.ts b/src/route/manga.ts index a109c2a..a7972e8 100644 --- a/src/route/manga.ts +++ b/src/route/manga.ts @@ -37,14 +37,14 @@ export class MangaRouter extends Router{ constructor(){ super(); this.get("/",async (ctx,next)=>{ - await renderZipImage(ctx,ctx.state.content.path,0); + await renderZipImage(ctx,ctx.state.location.path,0); }); this.get("/:page(\\d+)",async (ctx,next)=>{ const page = Number.parseInt(ctx.params['page']); - await renderZipImage(ctx,ctx.state.content.path,page); + await renderZipImage(ctx,ctx.state.location.path,page); }); this.get("/thumbnail", async (ctx,next)=>{ - await renderZipImage(ctx,ctx.state.content.path,0); + await renderZipImage(ctx,ctx.state.location.path,0); }); } } diff --git a/src/route/video.ts b/src/route/video.ts index c859384..2ab431e 100644 --- a/src/route/video.ts +++ b/src/route/video.ts @@ -57,10 +57,10 @@ export class VideoRouter extends Router{ constructor(){ super(); this.get("/", async (ctx,next)=>{ - await renderVideo(ctx,ctx.state.content.path); + await renderVideo(ctx,ctx.state.location.path); }); this.get("/thumbnail", async (ctx,next)=>{ - await renderVideo(ctx,ctx.state.content.path); + await renderVideo(ctx,ctx.state.location.path); }) } } diff --git a/src/server.ts b/src/server.ts index ea8da2e..fd96956 100644 --- a/src/server.ts +++ b/src/server.ts @@ -7,7 +7,7 @@ import {Watcher} from './diff/diff' import { createReadStream, readFileSync } from 'fs'; import getContentRouter from './route/contents'; -import { createKnexContentsAccessor } from './db/contents'; +import { createKnexContentsAccessor } from './db/doc'; import bodyparser from 'koa-bodyparser'; import {error_handler} from './route/error_handler'; @@ -64,8 +64,8 @@ export async function create_server(){ static_file_server('dist/js/bundle.js.map','text'); const content_router = getContentRouter(createKnexContentsAccessor(db)); - router.use('/content',content_router.routes()); - router.use('/content',content_router.allowedMethods()); + router.use('/api/doc',content_router.routes()); + router.use('/api/doc',content_router.allowedMethods()); router.post('/user/login',createLoginMiddleware(db)); router.post('/user/logout',LogoutMiddleware); diff --git a/src/types/db.d.ts b/src/types/db.d.ts index 696ec55..5545f6a 100644 --- a/src/types/db.d.ts +++ b/src/types/db.d.ts @@ -11,7 +11,7 @@ declare module "knex" { password_hash: string; password_salt: string; }; - contents: { + document: { id: number; title: string; content_type: string; @@ -20,8 +20,8 @@ declare module "knex" { content_hash?: string; additional?: string; }; - content_tag_relation: { - content_id: number; + doc_tag_relation: { + doc_id: number; tag_name: string; }; permissions: { diff --git a/webpack.config.js b/webpack.config.js index 655cb81..32cba80 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -24,7 +24,7 @@ module.exports = ()=>{return { }, plugins : [ new MiniCssExtractPlugin({ - "filename":'../css/style.css'}) + "filename":'../css/style.css'}), ], devtool: 'source-map', resolve: {