rename content to document

This commit is contained in:
monoid 2021-01-11 23:37:29 +09:00
parent ead51b5dfe
commit 61a7a4b651
30 changed files with 212 additions and 191 deletions

6
app.ts
View File

@ -3,7 +3,7 @@ import { get_setting } from "./src/setting";
import { create_server, start_server } from "./src/server"; import { create_server, start_server } from "./src/server";
import { getAdminAccessTokenValue,getAdminRefreshTokenValue, accessTokenName, refreshTokenName } from "./src/login"; import { getAdminAccessTokenValue,getAdminRefreshTokenValue, accessTokenName, refreshTokenName } from "./src/login";
const get_loading_html = (content?:string)=> `<!DOCTYPE html> const get_loading_html = (message?:string)=> `<!DOCTYPE html>
<html lang="ko"><head> <html lang="ko"><head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<title>react-sample</title> <title>react-sample</title>
@ -31,8 +31,8 @@ h1 {
} }
</style> </style>
<body> <body>
<h1>${content || "Loading..."}</h1> <h1>${message || "Loading..."}</h1>
${content === undefined ? '<div id="loading"></div>' : ""} ${message === undefined ? '<div id="loading"></div>' : ""}
</body> </body>
</html> </html>
`; `;

View File

@ -6,7 +6,7 @@ export async function up(knex:Knex) {
b.string("password_hash",64).notNullable(); b.string("password_hash",64).notNullable();
b.string("password_salt",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.increments("id").primary();
b.string("title").notNullable(); b.string("title").notNullable();
b.string("content_type",16).notNullable(); b.string("content_type",16).notNullable();
@ -21,12 +21,12 @@ export async function up(knex:Knex) {
b.string("name").primary(); b.string("name").primary();
b.text("description"); b.text("description");
}); });
await knex.schema.createTable("content_tag_relation",(b)=>{ await knex.schema.createTable("doc_tag_relation",(b)=>{
b.integer("content_id").unsigned().notNullable(); b.integer("doc_id").unsigned().notNullable();
b.string("tag_name").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.foreign("tag_name").references("tags.name");
b.primary(["content_id","tag_name"]); b.primary(["doc_id","tag_name"]);
}); });
await knex.schema.createTable("permissions",b=>{ await knex.schema.createTable("permissions",b=>{
b.string('username').notNullable(); b.string('username').notNullable();
@ -45,8 +45,8 @@ export async function up(knex:Knex) {
export async function down(knex:Knex) { export async function down(knex:Knex) {
//throw new Error('Downward migrations are not supported. Restore from backup.'); //throw new Error('Downward migrations are not supported. Restore from backup.');
await knex.schema.dropTable("users"); await knex.schema.dropTable("users");
await knex.schema.dropTable("contents"); await knex.schema.dropTable("document");
await knex.schema.dropTable("tags"); await knex.schema.dropTable("tags");
await knex.schema.dropTable("content_tag_relation"); await knex.schema.dropTable("document_tag_relation");
await knex.schema.dropTable("permissions"); await knex.schema.dropTable("permissions");
}; };

View File

@ -15,7 +15,7 @@
}, },
"build": { "build": {
"asar": false, "asar": false,
"files":[ "files": [
"build/**/*", "build/**/*",
"node_modules/**/*", "node_modules/**/*",
"package.json" "package.json"
@ -79,6 +79,7 @@
"css-loader": "^5.0.1", "css-loader": "^5.0.1",
"electron": "^11.1.1", "electron": "^11.1.1",
"electron-builder": "^22.9.1", "electron-builder": "^22.9.1",
"eslint-plugin-node": "^11.1.0",
"mini-css-extract-plugin": "^1.3.3", "mini-css-extract-plugin": "^1.3.3",
"style-loader": "^2.0.0", "style-loader": "^2.0.0",
"ts-node": "^9.1.1", "ts-node": "^9.1.1",

10
plan.md
View File

@ -5,8 +5,9 @@
### server routing ### server routing
- content - content
- \d+ - \d+
- image - manga
- (?P<page>\d+) - (?P&lt;page&gt;\d+)
- video
- diff - diff
- itemNum - itemNum
- diff - diff
@ -14,6 +15,8 @@
- commit - commit
- user - user
- login - login
- logout
- refresh
### client routing ### client routing
@ -23,6 +26,9 @@
- login - login
- profile - profile
- search ? querystring...; - search ? querystring...;
- setting
- difference
- profile
## TODO ## TODO
- server push - server push

View File

@ -1,8 +1,8 @@
import {Content, ContentAccessor, ContentContent, QueryListOption} from "../../model/contents"; import {Document, DocumentAccessor, DocumentBody, QueryListOption} from "../../model/doc";
import {toQueryString} from './util'; import {toQueryString} from './util';
const baseurl = "/content"; const baseurl = "/api/doc";
export * from "../../model/contents"; export * from "../../model/doc";
export class FetchFailError implements Error{ export class FetchFailError implements Error{
name: string; name: string;
@ -14,14 +14,14 @@ export class FetchFailError implements Error{
this.stack = stack; this.stack = stack;
} }
} }
export class ClientContentAccessor implements ContentAccessor{ export class ClientDocumentAccessor implements DocumentAccessor{
async findList(option?: QueryListOption | undefined): Promise<Content[]>{ async findList(option?: QueryListOption | undefined): Promise<Document[]>{
let res = await fetch(`${baseurl}/search?${option !== undefined ? toQueryString(option) : ""}`); let res = await fetch(`${baseurl}/search?${option !== undefined ? toQueryString(option) : ""}`);
if(res.status !== 200) throw new FetchFailError("findList Failed"); if(res.status !== 200) throw new FetchFailError("findList Failed");
let ret = await res.json(); let ret = await res.json();
return ret; return ret;
} }
async findById(id: number, tagload?: boolean | undefined): Promise<Content | undefined>{ async findById(id: number, tagload?: boolean | undefined): Promise<Document | undefined>{
let res = await fetch(`${baseurl}/${id}`); let res = await fetch(`${baseurl}/${id}`);
if(res.status !== 200) throw new FetchFailError("findById Failed");; if(res.status !== 200) throw new FetchFailError("findById Failed");;
let ret = await res.json(); let ret = await res.json();
@ -30,11 +30,11 @@ export class ClientContentAccessor implements ContentAccessor{
/** /**
* not implement * not implement
*/ */
async findListByBasePath(basepath: string): Promise<Content[]>{ async findListByBasePath(basepath: string): Promise<Document[]>{
throw new Error("not implement"); throw new Error("not implement");
return []; return [];
} }
async update(c: Partial<Content> & { id: number; }): Promise<boolean>{ async update(c: Partial<Document> & { id: number; }): Promise<boolean>{
const {id,...rest} = c; const {id,...rest} = c;
const res = await fetch(`${baseurl}/${id}`,{ const res = await fetch(`${baseurl}/${id}`,{
method: "POST", method: "POST",
@ -43,7 +43,7 @@ export class ClientContentAccessor implements ContentAccessor{
const ret = await res.json(); const ret = await res.json();
return ret; return ret;
} }
async add(c: ContentContent): Promise<number>{ async add(c: DocumentBody): Promise<number>{
const res = await fetch(`${baseurl}`,{ const res = await fetch(`${baseurl}`,{
method: "POST", method: "POST",
body: JSON.stringify(c) body: JSON.stringify(c)
@ -58,7 +58,7 @@ export class ClientContentAccessor implements ContentAccessor{
const ret = await res.json(); const ret = await res.json();
return ret; return ret;
} }
async addTag(c: Content, tag_name: string): Promise<boolean>{ async addTag(c: Document, tag_name: string): Promise<boolean>{
const {id,...rest} = c; const {id,...rest} = c;
const res = await fetch(`${baseurl}/${id}/tags/${tag_name}`,{ const res = await fetch(`${baseurl}/${id}/tags/${tag_name}`,{
method: "POST", method: "POST",
@ -67,7 +67,7 @@ export class ClientContentAccessor implements ContentAccessor{
const ret = await res.json(); const ret = await res.json();
return ret; return ret;
} }
async delTag(c: Content, tag_name: string): Promise<boolean>{ async delTag(c: Document, tag_name: string): Promise<boolean>{
const {id,...rest} = c; const {id,...rest} = c;
const res = await fetch(`${baseurl}/${id}/tags/${tag_name}`,{ const res = await fetch(`${baseurl}/${id}/tags/${tag_name}`,{
method: "DELETE", method: "DELETE",
@ -77,9 +77,9 @@ export class ClientContentAccessor implements ContentAccessor{
return ret; return ret;
} }
} }
export const CContentAccessor = new ClientContentAccessor; export const CDocumentAccessor = new ClientDocumentAccessor;
export const makeThumbnailUrl = (x: Content)=>{ export const makeThumbnailUrl = (x: Document)=>{
return `/content/${x.id}/${x.content_type}/thumbnail`; return `${baseurl}/${x.id}/${x.content_type}/thumbnail`;
} }
export default CContentAccessor; export default CDocumentAccessor;

View File

@ -1,7 +1,7 @@
import React, { createContext, useEffect, useRef, useState } from 'react'; import React, { createContext, useEffect, useRef, useState } from 'react';
import ReactDom from 'react-dom'; import ReactDom from 'react-dom';
import {BrowserRouter, Redirect, Route, Switch as RouterSwitch} from 'react-router-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 {getInitialValue, UserContext} from './state';
import './css/style.css'; import './css/style.css';
@ -27,7 +27,7 @@ const App = () => {
<RouterSwitch> <RouterSwitch>
<Route path="/" exact render={()=><Redirect to='/search?'/>}></Route> <Route path="/" exact render={()=><Redirect to='/search?'/>}></Route>
<Route path="/search" render={()=><Gallery />}></Route> <Route path="/search" render={()=><Gallery />}></Route>
<Route path="/doc" render={(prop)=><ContentAbout {...prop}/>}></Route> <Route path="/doc" render={(prop)=><DocumentAbout {...prop}/>}></Route>
<Route path="/login" render={()=><LoginPage></LoginPage>}/> <Route path="/login" render={()=><LoginPage></LoginPage>}/>
<Route path="/profile" component={ProfilePage}></Route> <Route path="/profile" component={ProfilePage}></Route>
<Route path="/difference" component={DifferencePage}></Route> <Route path="/difference" component={DifferencePage}></Route>

View File

@ -1,6 +1,6 @@
import React, { useState, useEffect } from 'react'; import React, { useState, useEffect } from 'react';
import { Redirect, Route, Switch, useHistory, useRouteMatch, match as MatchType, Link as RouterLink } from 'react-router-dom'; 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 { LoadingCircle } from '../component/loading';
import { Link, Paper, makeStyles, Theme, Box, useTheme, Typography } from '@material-ui/core'; import { Link, Paper, makeStyles, Theme, Box, useTheme, Typography } from '@material-ui/core';
import { ThumbnailContainer } from '../page/reader/reader'; import { ThumbnailContainer } from '../page/reader/reader';
@ -74,7 +74,7 @@ const useStyles = makeStyles((theme: Theme) => ({
})) }))
export const ContentInfo = (props: { export const ContentInfo = (props: {
content: Content, children?: React.ReactNode, classes?: { document: Document, children?: React.ReactNode, classes?: {
root?: string, root?: string,
thumbnail_anchor?: string, thumbnail_anchor?: string,
thumbnail_content?: string, thumbnail_content?: string,
@ -88,31 +88,30 @@ export const ContentInfo = (props: {
}) => { }) => {
const classes = useStyles(); const classes = useStyles();
const theme = useTheme(); const theme = useTheme();
const content = props.content; const document = props.document;
const propclasses = props.classes || {}; const propclasses = props.classes || {};
const rootName = props.short ? classes.short_root : classes.root; const rootName = props.short ? classes.short_root : classes.root;
const thumbnail_anchor = props.short ? classes.short_thumbnail_anchor : ""; const thumbnail_anchor = props.short ? classes.short_thumbnail_anchor : "";
const thumbnail_content = props.short ? classes.short_thumbnail_content : const thumbnail_content = props.short ? classes.short_thumbnail_content :
classes.thumbnail_content; classes.thumbnail_content;
const subinfoContainer = props.short ? classes.short_subinfoContainer : const subinfoContainer = props.short ? classes.short_subinfoContainer :
classes.subinfoContainer; classes.subinfoContainer;
let allTag = content.tags; let allTag = document.tags;
const artists = allTag.filter(x => x.startsWith("artist:")).map(x => x.substr(7)); const artists = allTag.filter(x => x.startsWith("artist:")).map(x => x.substr(7));
allTag = allTag.filter(x => !x.startsWith("artist:")); allTag = allTag.filter(x => !x.startsWith("artist:"));
return (<Paper className={propclasses.root || rootName} elevation={4}> return (<Paper className={propclasses.root || rootName} elevation={4}>
<Link className={propclasses.thumbnail_anchor || thumbnail_anchor} component={RouterLink} to={{ <Link className={propclasses.thumbnail_anchor || thumbnail_anchor} component={RouterLink} to={{
pathname:makeContentReaderUrl(content.id) pathname:makeContentReaderUrl(document.id)
}}> }}>
<ThumbnailContainer content={content} <ThumbnailContainer content={document}
className={propclasses.thumbnail_content || thumbnail_content}/> className={propclasses.thumbnail_content || thumbnail_content}/>
</Link> </Link>
<Box className={propclasses.infoContainer || classes.infoContainer}> <Box className={propclasses.infoContainer || classes.infoContainer}>
<Link variant='h5' color='inherit' component={RouterLink} to={{ <Link variant='h5' color='inherit' component={RouterLink} to={{
pathname: props.gallery === undefined ? makeContentReaderUrl(content.id) : makeContentInfoUrl(content.id), pathname: props.gallery === undefined ? makeContentReaderUrl(document.id) : makeContentInfoUrl(document.id),
}} }}
className={propclasses.title || classes.title}> className={propclasses.title || classes.title}>
{content.title} {document.title}
</Link> </Link>
<Box className={propclasses.subinfoContainer || subinfoContainer}> <Box className={propclasses.subinfoContainer || subinfoContainer}>
<Typography variant='subtitle1'>Artist</Typography> <Typography variant='subtitle1'>Artist</Typography>

View File

@ -1,7 +1,7 @@
import { Box, Paper, Link, useMediaQuery, Portal, List, ListItem, ListItemIcon, Tooltip, ListItemText } from '@material-ui/core'; 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 {useTheme, makeStyles, Theme} from '@material-ui/core/styles';
import React, { useEffect, useState } from 'react'; 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 {Link as RouterLink} from 'react-router-dom';
import { LoadingCircle, ContentInfo, NavList, NavItem } from './mod'; import { LoadingCircle, ContentInfo, NavList, NavItem } from './mod';
import {toQueryString} from '../accessor/util'; import {toQueryString} from '../accessor/util';
@ -42,29 +42,29 @@ export type GalleryProp = {
option?:QueryListOption; option?:QueryListOption;
}; };
type GalleryState = { type GalleryState = {
content:Content[]|undefined; documents:Document[]|undefined;
} }
export const GalleryInfo = (props: GalleryProp)=>{ export const GalleryInfo = (props: GalleryProp)=>{
const [state,setState]= useState<GalleryState>({content:undefined}); const [state,setState]= useState<GalleryState>({documents:undefined});
useEffect(()=>{ useEffect(()=>{
const load = (async ()=>{ const load = (async ()=>{
const c = await ContentAccessor.findList(props.option); const c = await ContentAccessor.findList(props.option);
//todo : if c is undefined, retry to fetch 3 times. and show error message. //todo : if c is undefined, retry to fetch 3 times. and show error message.
setState({content:c}); setState({documents:c});
}) })
load(); load();
},[props.option]); },[props.option]);
const classes = useStyles(); const classes = useStyles();
const queryString = toQueryString(props.option||{}); const queryString = toQueryString(props.option||{});
if(state.content === undefined){ if(state.documents === undefined){
return (<LoadingCircle/>); return (<LoadingCircle/>);
} }
else{ else{
return (<Box className={classes.root}>{ return (<Box className={classes.root}>{
state.content.map(x=>{ state.documents.map(x=>{
return (<ContentInfo content={x} key={x.id} return (<ContentInfo document={x} key={x.id}
gallery={`/search?${queryString}`} short/>); gallery={`/search?${queryString}`} short/>);
}) })
} }

View File

@ -1,6 +1,6 @@
import React, { useState, useEffect, useContext } from 'react'; 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 { 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 { LoadingCircle } from '../component/loading';
import { Link, Paper, makeStyles, Theme, Box, useTheme, Typography } from '@material-ui/core'; import { Link, Paper, makeStyles, Theme, Box, useTheme, Typography } from '@material-ui/core';
import {ArrowBack as ArrowBackIcon } from '@material-ui/icons'; 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 makeContentInfoUrl = (id: number) => `/doc/${id}`;
export const makeMangaReaderUrl = (id: number) => `/doc/${id}/reader`; export const makeMangaReaderUrl = (id: number) => `/doc/${id}/reader`;
type ContentState = { type DocumentState = {
content: Content | undefined, doc: Document | undefined,
notfound: boolean, 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"); const match = useRouteMatch<{id:string}>("/doc/:id");
if (match == null) { if (match == null) {
throw new Error("unreachable"); throw new Error("unreachable");
} }
const id = Number.parseInt(match.params['id']); const id = Number.parseInt(match.params['id']);
const [info, setInfo] = useState<ContentState>({ content: undefined, notfound:false }); const [info, setInfo] = useState<DocumentState>({ doc: undefined, notfound:false });
const menu_list = () => <CommonMenuList></CommonMenuList>; const menu_list = (link?:string) => <CommonMenuList url={link}></CommonMenuList>;
useEffect(() => { useEffect(() => {
(async () => { (async () => {
if (!isNaN(id)) { if (!isNaN(id)) {
const c = await ContentAccessor.findById(id); const c = await DocumentAccessor.findById(id);
setInfo({ content: c, notfound: c === undefined }); setInfo({ doc: c, notfound: c === undefined });
} }
})() })();
}, []); }, []);
const classes = useStyles(); const classes = useStyles();
console.log(info.doc);
if (isNaN(id)) { if (isNaN(id)) {
return ( return (
<Headline menu={menu_list()}> <Headline menu={menu_list()}>
@ -60,18 +60,18 @@ export const ContentAbout = (prop: { match: MatchType }) => {
</Headline> </Headline>
) )
} }
else if (info.content === undefined) { else if (info.doc === undefined) {
return (<Headline menu={menu_list()}> return (<Headline menu={menu_list()}>
<LoadingCircle /> <LoadingCircle />
</Headline> </Headline>
); );
} }
else{ else{
const ReaderPage = getPresenter(info.content); const ReaderPage = getPresenter(info.doc);
return (<Switch> return (<Switch>
<Route exact path={`${prop.match.path}/:id`}> <Route exact path={`${prop.match.path}/:id`}>
<Headline menu={menu_list()}> <Headline menu={menu_list()}>
<ContentInfo content={info.content}></ContentInfo> <ContentInfo document={info.doc}></ContentInfo>
</Headline> </Headline>
</Route> </Route>
<Route exact path={`${prop.match.path}/:id/reader`}> <Route exact path={`${prop.match.path}/:id/reader`}>
@ -79,7 +79,7 @@ export const ContentAbout = (prop: { match: MatchType }) => {
content:classes.noPaddingContent, content:classes.noPaddingContent,
toolbar:classes.noPaddingToolbar toolbar:classes.noPaddingToolbar
}}> }}>
<ReaderPage content={info.content}></ReaderPage> <ReaderPage doc={info.doc}></ReaderPage>
</Headline> </Headline>
</Route> </Route>
<Route> <Route>

View File

@ -8,9 +8,8 @@ import { QueryStringToMap } from '../accessor/util';
export const Gallery = ()=>{ export const Gallery = ()=>{
const location = useLocation(); const location = useLocation();
const query = QueryStringToMap(location.search); const query = QueryStringToMap(location.search);
location;
const menu_list = CommonMenuList({url:location.search}); const menu_list = CommonMenuList({url:location.search});
return (<Headline menu={menu_list}> return (<Headline menu={menu_list}>
<GalleryInfo option={query}></GalleryInfo> <GalleryInfo option={query}></GalleryInfo>
</Headline>) </Headline>)

View File

@ -1,6 +1,6 @@
import React, {useState, useEffect} from 'react'; import React, {useState, useEffect} from 'react';
import { Typography, useTheme } from '@material-ui/core'; import { Typography, useTheme } from '@material-ui/core';
import { Content } from '../../accessor/contents'; import { Document } from '../../accessor/document';
type MangaType = "manga"|"artist cg"|"donjinshi"|"western" type MangaType = "manga"|"artist cg"|"donjinshi"|"western"
@ -13,9 +13,9 @@ export type PresentableTag = {
tags: string[], tags: string[],
} }
export const MangaReader = (props:{content:Content})=>{ export const MangaReader = (props:{doc:Document})=>{
const theme = useTheme(); const theme = useTheme();
const additional = props.content.additional; const additional = props.doc.additional;
const [curPage,setCurPage] = useState(0); const [curPage,setCurPage] = useState(0);
if(!('page' in additional)){ if(!('page' in additional)){
console.error("invalid content : page read fail : "+ JSON.stringify(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; //theme.mixins.toolbar.minHeight;
return (<div style={{overflow: 'hidden', alignSelf:'center'}}> return (<div style={{overflow: 'hidden', alignSelf:'center'}}>
<img onClick={PageUP} src={`/content/${props.content.id}/manga/${curPage}`} <img onClick={PageUP} src={`/api/doc/${props.doc.id}/manga/${curPage}`}
style={{maxWidth:'100%', maxHeight:'calc(100vh - 64px)'}}></img> style={{maxWidth:'100%', maxHeight:'calc(100vh - 64px)'}}></img>
</div>); </div>);
} }

View File

@ -1,18 +1,18 @@
import { Typography } from '@material-ui/core'; import { Typography } from '@material-ui/core';
import React from 'react'; import React from 'react';
import { Content, makeThumbnailUrl } from '../../accessor/contents'; import { Document, makeThumbnailUrl } from '../../accessor/document';
import {MangaReader} from './manga'; import {MangaReader} from './manga';
import {VideoReader} from './video' import {VideoReader} from './video'
export interface PagePresenterProp{ export interface PagePresenterProp{
content:Content, doc:Document,
className?:string className?:string
} }
interface PagePresenter{ interface PagePresenter{
(prop:PagePresenterProp):JSX.Element (prop:PagePresenterProp):JSX.Element
} }
export const getPresenter = (content:Content):PagePresenter => { export const getPresenter = (content:Document):PagePresenter => {
switch (content.content_type) { switch (content.content_type) {
case "manga": case "manga":
return MangaReader; return MangaReader;
@ -22,7 +22,7 @@ export const getPresenter = (content:Content):PagePresenter => {
return ()=><Typography variant='h2'>Not implemented reader</Typography>; return ()=><Typography variant='h2'>Not implemented reader</Typography>;
} }
export const ThumbnailContainer = (props:{content:Content, className?:string})=>{ export const ThumbnailContainer = (props:{content:Document, className?:string})=>{
const thumbnailurl = makeThumbnailUrl(props.content); const thumbnailurl = makeThumbnailUrl(props.content);
if(props.content.content_type === "video"){ if(props.content.content_type === "video"){
return (<video src={thumbnailurl} muted autoPlay loop className={props.className}></video>) return (<video src={thumbnailurl} muted autoPlay loop className={props.className}></video>)

View File

@ -1,7 +1,7 @@
import React from 'react'; import React from 'react';
import { Content } from '../../accessor/contents'; import { Document } from '../../accessor/document';
export const VideoReader = (props:{content:Content})=>{ export const VideoReader = (props:{doc:Document})=>{
const id = props.content.id; const id = props.doc.id;
return <video controls autoPlay src={`/content/${props.content.id}/video`} style={{maxHeight:'100%',maxWidth:'100%'}}></video>; return <video controls autoPlay src={`/api/doc/${props.doc.id}/video`} style={{maxHeight:'100%',maxWidth:'100%'}}></video>;
} }

View File

@ -1,8 +1,14 @@
import {ContentReferrer} from './referrer'; import {ContentFile} from './referrer';
import {createDefaultClass,registerContentReferrer} from './referrer'; import {createDefaultClass,registerContentReferrer} from './referrer';
import {readZip} from '../util/zipwrap';
export class MangaReferrer extends createDefaultClass("manga"){ export class MangaReferrer extends createDefaultClass("manga"){
constructor(path:string){ constructor(path:string){
super(path); super(path);
/*(async ()=>{
const zip = await readZip(path);
const entry = zip.entries();
})*/
} }
}; };
registerContentReferrer(MangaReferrer); registerContentReferrer(MangaReferrer);

View File

@ -1,3 +1,3 @@
import './manga'; import './manga';
import './video'; import './video';
export {ContentReferrer,createContentReferrer} from './referrer'; export {ContentFile as ContentReferrer,createContentReferrer} from './referrer';

View File

@ -6,15 +6,15 @@ import {extname} from 'path';
/** /**
* content file or directory referrer * content file or directory referrer
*/ */
export interface ContentReferrer{ export interface ContentFile{
getHash():Promise<string>; getHash():Promise<string>;
readonly path: string; readonly path: string;
readonly type: string; readonly type: string;
desc: object|undefined; desc: object|undefined;
} }
type ContentReferrerConstructor = (new (path:string,desc?:object) => ContentReferrer)&{content_type:string}; type ContentFileConstructor = (new (path:string,desc?:object) => ContentFile)&{content_type:string};
export const createDefaultClass = (type:string):ContentReferrerConstructor=>{ export const createDefaultClass = (type:string):ContentFileConstructor=>{
let cons = class implements ContentReferrer{ let cons = class implements ContentFile{
readonly path: string; readonly path: string;
type = type; type = type;
static content_type = type; static content_type = type;
@ -37,8 +37,8 @@ export const createDefaultClass = (type:string):ContentReferrerConstructor=>{
}; };
return cons; return cons;
} }
let ContstructorTable:{[k:string]:ContentReferrerConstructor} = {}; let ContstructorTable:{[k:string]:ContentFileConstructor} = {};
export function registerContentReferrer(s: ContentReferrerConstructor){ export function registerContentReferrer(s: ContentFileConstructor){
console.log(`registered content type: ${s.content_type}`) console.log(`registered content type: ${s.content_type}`)
ContstructorTable[s.content_type] = s; ContstructorTable[s.content_type] = s;
} }
@ -50,7 +50,7 @@ export function createContentReferrer(type:string,path:string,desc?:object){
} }
return new constructorMethod(path,desc); return new constructorMethod(path,desc);
} }
export function getContentRefererConstructor(type:string): ContentReferrerConstructor|undefined{ export function getContentRefererConstructor(type:string): ContentFileConstructor|undefined{
const ret = ContstructorTable[type]; const ret = ContstructorTable[type];
return ret; return ret;
} }

View File

@ -1,4 +1,4 @@
import {ContentReferrer, registerContentReferrer} from './referrer'; import {ContentFile, registerContentReferrer} from './referrer';
import {createDefaultClass} from './referrer'; import {createDefaultClass} from './referrer';
export class VideoReferrer extends createDefaultClass("video"){ export class VideoReferrer extends createDefaultClass("video"){

View File

@ -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 Knex from 'knex';
import {createKnexTagController} from './tag'; import {createKnexTagController} from './tag';
import { TagAccessor } from '../model/tag'; import { TagAccessor } from '../model/tag';
type DBTagContentRelation = { type DBTagContentRelation = {
content_id:number, doc_id:number,
tag_name:string tag_name:string
} }
class KnexContentsAccessor implements ContentAccessor{ class KnexContentsAccessor implements DocumentAccessor{
knex : Knex; knex : Knex;
tagController: TagAccessor; tagController: TagAccessor;
constructor(knex : Knex){ constructor(knex : Knex){
this.knex = knex; this.knex = knex;
this.tagController = createKnexTagController(knex); this.tagController = createKnexTagController(knex);
} }
async add(c: ContentContent){ async add(c: DocumentBody){
const {tags,additional, ...rest} = c; const {tags,additional, ...rest} = c;
const id_lst = await this.knex.insert({ const id_lst = await this.knex.insert({
additional:JSON.stringify(additional), additional:JSON.stringify(additional),
...rest ...rest
}).into('contents'); }).into('document');
const id = id_lst[0]; const id = id_lst[0];
for (const it of tags) { for (const it of tags) {
this.tagController.addTag({name:it}); this.tagController.addTag({name:it});
} }
if(tags.length > 0){ if(tags.length > 0){
await this.knex.insert<DBTagContentRelation>( await this.knex.insert<DBTagContentRelation>(
tags.map(x=>({content_id:id,tag_name:x})) tags.map(x=>({doc_id:id,tag_name:x}))
).into("content_tag_relation"); ).into("doc_tag_relation");
} }
return id; return id;
}; };
async del(id:number) { async del(id:number) {
if (await this.findById(id) !== undefined){ if (await this.findById(id) !== undefined){
await this.knex.delete().from("content_tag_relation").where({content_id:id}); await this.knex.delete().from("doc_tag_relation").where({doc_id:id});
await this.knex.delete().from("contents").where({id:id}); await this.knex.delete().from("document").where({id:id});
return true; return true;
} }
return false; return false;
}; };
async findById(id:number,tagload?:boolean): Promise<Content|undefined>{ async findById(id:number,tagload?:boolean): Promise<Document|undefined>{
const s = await this.knex.select("*").from("contents").where({id:id}); const s = await this.knex.select("*").from("document").where({id:id});
if(s.length === 0) return undefined; if(s.length === 0) return undefined;
const first = s[0]; const first = s[0];
let ret_tags:string[] = [] let ret_tags:string[] = []
if(tagload === true){ if(tagload === true){
const tags : DBTagContentRelation[] = await this.knex.select("*") 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); ret_tags = tags.map(x=>x.tag_name);
} }
return { return {
@ -70,13 +70,13 @@ class KnexContentsAccessor implements ContentAccessor{
const buildquery = ()=>{ const buildquery = ()=>{
let query = this.knex.select("*"); let query = this.knex.select("*");
if(allow_tag.length > 0){ 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){ for(const tag of allow_tag){
query = query.where({tag_name:tag}); query = query.where({tag_name:tag});
} }
} }
else{ else{
query = query.from("contents"); query = query.from("document");
} }
if(word !== undefined){ if(word !== undefined){
//don't worry about sql injection. //don't worry about sql injection.
@ -98,56 +98,58 @@ class KnexContentsAccessor implements ContentAccessor{
} }
let query = buildquery(); let query = buildquery();
//console.log(query.toSQL()); //console.log(query.toSQL());
let result:Content[] = await query; let result:Document[] = await query;
for(let i of result){ for(let i of result){
i.additional = JSON.parse((i.additional as unknown) as string); i.additional = JSON.parse((i.additional as unknown) as string);
} }
if(eager_loading){ if(eager_loading){
let idmap: {[index:number]:Content} = {}; let idmap: {[index:number]:Document} = {};
for(const r of result){ for(const r of result){
idmap[r.id] = r; idmap[r.id] = r;
r.tags = []; r.tags = [];
} }
let subquery = buildquery(); let subquery = buildquery();
let tagresult:{id:number,tag_name:string}[] = await this.knex.select("id","content_tag_relation.tag_name").from(subquery) let tagquery= this.knex.select("id","doc_tag_relation.tag_name").from(subquery)
.innerJoin("content_tag_relation","content_tag_relation.content_id","id"); .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){ for(const {id,tag_name} of tagresult){
idmap[id].tags.push(tag_name); idmap[id].tags.push(tag_name);
} }
} }
return result; return result;
}; };
async findListByBasePath(path:string):Promise<Content[]>{ async findListByBasePath(path:string):Promise<Document[]>{
let results = await this.knex.select("*").from("contents").where({basepath:path}); let results = await this.knex.select("*").from("document").where({basepath:path});
return results.map(x=>({ return results.map(x=>({
...x, ...x,
tags:[], tags:[],
additional:JSON.parse(x.additional || "{}"), additional:JSON.parse(x.additional || "{}"),
})); }));
} }
async update(c:Partial<Content> & { id:number }){ async update(c:Partial<Document> & { id:number }){
const {id,tags,...rest} = c; const {id,tags,...rest} = c;
if (await this.findById(id) !== undefined){ 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 true;
} }
return false; return false;
} }
async addTag(c: Content,tag_name:string){ async addTag(c: Document,tag_name:string){
if (c.tags.includes(tag_name)) return false; if (c.tags.includes(tag_name)) return false;
this.tagController.addTag({name:tag_name}); this.tagController.addTag({name:tag_name});
await this.knex.insert<DBTagContentRelation>({tag_name: tag_name, content_id: c.id}) await this.knex.insert<DBTagContentRelation>({tag_name: tag_name, doc_id: c.id})
.into("content_tag_relation"); .into("doc_tag_relation");
c.tags.push(tag_name); c.tags.push(tag_name);
return true; return true;
} }
async delTag(c: Content,tag_name:string){ async delTag(c: Document,tag_name:string){
if (c.tags.includes(tag_name)) return false; 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); c.tags.push(tag_name);
return true; return true;
} }
} }
export const createKnexContentsAccessor = (knex:Knex): ContentAccessor=>{ export const createKnexContentsAccessor = (knex:Knex): DocumentAccessor=>{
return new KnexContentsAccessor(knex); return new KnexContentsAccessor(knex);
} }

View File

@ -1,3 +1,3 @@
export * from './contents'; export * from './doc';
export * from './tag'; export * from './tag';
export * from './user'; export * from './user';

View File

@ -1,6 +1,6 @@
import { watch } from 'fs'; import { watch } from 'fs';
import { promises } from 'fs'; import { promises } from 'fs';
import { ContentReferrer, createContentReferrer, getContentRefererConstructor } from '../content/referrer' import { ContentFile, createContentReferrer, getContentRefererConstructor } from '../content/referrer'
import path from 'path'; import path from 'path';
const readdir = promises.readdir; const readdir = promises.readdir;
@ -12,21 +12,21 @@ export class Watcher{
/** /**
* @todo : alter type Map<string,ContentReferrer> * @todo : alter type Map<string,ContentReferrer>
*/ */
private _added: ContentReferrer[]; private _added: ContentFile[];
private _deleted: ContentReferrer[]; private _deleted: ContentFile[];
constructor(path:string,type:string){ constructor(path:string,type:string){
this._path = path; this._path = path;
this._added =[]; this._added =[];
this._deleted =[]; this._deleted =[];
this._type = type; this._type = type;
} }
public get added() : ContentReferrer[] { public get added() : ContentFile[] {
return this._added; return this._added;
} }
/*public set added(diff : FileDiff[]) { /*public set added(diff : FileDiff[]) {
this._added = diff; this._added = diff;
}*/ }*/
public get deleted(): ContentReferrer[]{ public get deleted(): ContentFile[]{
return this._deleted; return this._deleted;
} }
/*public set deleted(diff : FileDiff[]){ /*public set deleted(diff : FileDiff[]){

View File

@ -1,12 +1,12 @@
import {TagAccessor} from './tag'; import {TagAccessor} from './tag';
import {check_type} from './../util/type_check' import {check_type} from '../util/type_check'
type JSONPrimitive = null|boolean|number|string; type JSONPrimitive = null|boolean|number|string;
interface JSONMap extends Record<string, JSONType>{} interface JSONMap extends Record<string, JSONType>{}
interface JSONArray extends Array<JSONType>{}; interface JSONArray extends Array<JSONType>{};
type JSONType = JSONMap|JSONPrimitive|JSONArray; type JSONType = JSONMap|JSONPrimitive|JSONArray;
export interface ContentContent{ export interface DocumentBody{
title : string, title : string,
content_type : string, content_type : string,
basepath : string, basepath : string,
@ -16,7 +16,7 @@ export interface ContentContent{
tags : string[],//eager loading tags : string[],//eager loading
} }
export const MetaContentContent = { export const MetaContentBody = {
title : "string", title : "string",
content_type : "string", content_type : "string",
basepath : "string", basepath : "string",
@ -26,18 +26,18 @@ export const MetaContentContent = {
tags : "string[]", tags : "string[]",
} }
export const isContentContent = (c : any):c is ContentContent =>{ export const isDocBody = (c : any):c is DocumentBody =>{
return check_type<ContentContent>(c,MetaContentContent); return check_type<DocumentBody>(c,MetaContentBody);
} }
export interface Content extends ContentContent{ export interface Document extends DocumentBody{
readonly id: number; 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"){ if('id' in c && typeof c['id'] === "number"){
const {id, ...rest} = c; const {id, ...rest} = c;
return isContentContent(rest); return isDocBody(rest);
} }
return false; return false;
} }
@ -59,11 +59,11 @@ export type QueryListOption = {
*/ */
use_offset?:boolean, use_offset?:boolean,
/** /**
* cursor of contents * cursor of documents
*/ */
cursor?:number, cursor?:number,
/** /**
* offset of contents * offset of documents
*/ */
offset?:number, offset?:number,
/** /**
@ -77,41 +77,41 @@ export type QueryListOption = {
content_type?:string content_type?:string
} }
export interface ContentAccessor{ export interface DocumentAccessor{
/** /**
* find list by option * find list by option
* @returns content list * @returns documents list
*/ */
findList: (option?:QueryListOption)=>Promise<Content[]>, findList: (option?:QueryListOption)=>Promise<Document[]>,
/** /**
* @returns content if exist, otherwise undefined * @returns document if exist, otherwise undefined
*/ */
findById: (id:number,tagload?:boolean)=> Promise<Content| undefined>, findById: (id:number,tagload?:boolean)=> Promise<Document| undefined>,
/** /**
* *
*/ */
findListByBasePath:(basepath: string)=>Promise<Content[]>; findListByBasePath:(basepath: string)=>Promise<Document[]>;
/** /**
* update content except tag. * update document except tag.
*/ */
update:(c:Partial<Content> & { id:number })=>Promise<boolean>; update:(c:Partial<Document> & { id:number })=>Promise<boolean>;
/** /**
* add content * add document
*/ */
add:(c:ContentContent)=>Promise<number>; add:(c:DocumentBody)=>Promise<number>;
/** /**
* delete content * delete document
* @returns if it exists, return true. * @returns if it exists, return true.
*/ */
del:(id:number)=>Promise<boolean>; del:(id:number)=>Promise<boolean>;
/** /**
* @param c Valid Content * @param c Valid Document
* @param tagname tag name to add * @param tagname tag name to add
* @returns if success, return true * @returns if success, return true
*/ */
addTag:(c:Content,tag_name:string)=>Promise<boolean>; addTag:(c:Document,tag_name:string)=>Promise<boolean>;
/** /**
* @returns if success, return true * @returns if success, return true
*/ */
delTag:(c:Content,tag_name:string)=>Promise<boolean>; delTag:(c:Document,tag_name:string)=>Promise<boolean>;
}; };

View File

@ -1,3 +1,3 @@
export * from './contents'; export * from './doc';
export * from './tag'; export * from './tag';
export * from './user'; export * from './user';

View File

@ -14,7 +14,7 @@ const all_middleware = (cont: string|undefined, restarg: string|undefined)=>asyn
ctx.status = 404; ctx.status = 404;
return; return;
} }
if(ctx.state.content.type != cont){ if(ctx.state.location.type != cont){
console.error("not matched") console.error("not matched")
ctx.status = 404; ctx.status = 404;
return; return;
@ -24,8 +24,7 @@ const all_middleware = (cont: string|undefined, restarg: string|undefined)=>asyn
ctx.status = 404; ctx.status = 404;
return; return;
} }
const rest = "/"+(restarg as string|undefined || ""); const rest = "/"+(restarg || "");
const result = router.match(rest,"GET"); const result = router.match(rest,"GET");
if(!result.route){ if(!result.route){
return await next(); return await next();

View File

@ -1,34 +1,34 @@
import { Context, Next } from 'koa'; import { Context, Next } from 'koa';
import Router from 'koa-router'; import Router from 'koa-router';
import {Content, ContentAccessor, isContentContent} from './../model/contents'; import {Document, DocumentAccessor, isDocBody} from '../model/doc';
import {QueryListOption} from './../model/contents'; import {QueryListOption} from '../model/doc';
import {ParseQueryNumber, ParseQueryArray, ParseQueryBoolean} from './util' import {ParseQueryNumber, ParseQueryArray, ParseQueryBoolean} from './util'
import {sendError} from './error_handler'; import {sendError} from './error_handler';
import { createContentReferrer } from '../content/mod';
import { join } from 'path'; import { join } from 'path';
import {AllContentRouter} from './all'; import {AllContentRouter} from './all';
import {createPermissionCheckMiddleware as PerCheck, Permission as Per, AdminOnlyMiddleware as AdminOnly} from '../permission/permission'; 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']); const num = Number.parseInt(ctx.params['num']);
let content = await controller.findById(num,true); let document = await controller.findById(num,true);
if (content == undefined){ if (document == undefined){
return sendError(404,"content does not exist."); return sendError(404,"document does not exist.");
} }
ctx.body = content; ctx.body = document;
ctx.type = 'json'; 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']); const num = Number.parseInt(ctx.params['num']);
let content = await controller.findById(num,true); let document = await controller.findById(num,true);
if (content == undefined){ if (document == undefined){
return sendError(404,"content does not exist."); return sendError(404,"document does not exist.");
} }
ctx.body = content.tags || []; ctx.body = document.tags || [];
ctx.type = 'json'; 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 limit = ParseQueryNumber(ctx.query['limit']);
const cursor = ParseQueryNumber(ctx.query['cursor']); const cursor = ParseQueryNumber(ctx.query['cursor']);
const word: string|undefined = ctx.query['word']; const word: string|undefined = ctx.query['word'];
@ -52,35 +52,35 @@ const ContentQueryHandler = (controller : ContentAccessor) => async (ctx: Contex
use_offset: use_offset, use_offset: use_offset,
content_type:content_type, content_type:content_type,
}; };
let content = await controller.findList(option); let document = await controller.findList(option);
ctx.body = content; ctx.body = document;
ctx.type = 'json'; 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']); const num = Number.parseInt(ctx.params['num']);
if(ctx.request.type !== 'json'){ 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"){ if(typeof ctx.request.body !== "object"){
return sendError(400,"update fail. invalid argument: not"); return sendError(400,"update fail. invalid argument: not");
} }
const content_desc: Partial<Content> & {id: number} = { const content_desc: Partial<Document> & {id: number} = {
id:num,...ctx.request.body id:num,...ctx.request.body
}; };
const success = await controller.update(content_desc); const success = await controller.update(content_desc);
ctx.body = JSON.stringify(success); ctx.body = JSON.stringify(success);
ctx.type = 'json'; 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; const content_desc = ctx.request.body;
if(!isContentContent(content_desc)){ if(!isDocBody(content_desc)){
return sendError(400,"it is not a valid format"); return sendError(400,"it is not a valid format");
} }
const id = await controller.add(content_desc); const id = await controller.add(content_desc);
ctx.body = JSON.stringify(id); ctx.body = JSON.stringify(id);
ctx.type = 'json'; 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']; let tag_name = ctx.params['tag'];
const num = Number.parseInt(ctx.params['num']); const num = Number.parseInt(ctx.params['num']);
if(typeof tag_name === undefined){ if(typeof tag_name === undefined){
@ -95,7 +95,7 @@ const AddTagHandler = (controller: ContentAccessor)=>async (ctx: Context, next:
ctx.body = JSON.stringify(r); ctx.body = JSON.stringify(r);
ctx.type = 'json'; 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']; let tag_name = ctx.params['tag'];
const num = Number.parseInt(ctx.params['num']); const num = Number.parseInt(ctx.params['num']);
if(typeof tag_name === undefined){ if(typeof tag_name === undefined){
@ -110,24 +110,28 @@ const DelTagHandler = (controller: ContentAccessor)=>async (ctx: Context, next:
ctx.body = JSON.stringify(r); ctx.body = JSON.stringify(r);
ctx.type = 'json'; 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 num = Number.parseInt(ctx.params['num']);
const r = await controller.del(num); const r = await controller.del(num);
ctx.body = JSON.stringify(r); ctx.body = JSON.stringify(r);
ctx.type = 'json'; 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']); const num = Number.parseInt(ctx.params['num']);
let content = await controller.findById(num,true); let document = await controller.findById(num,true);
if (content == undefined){ if (document == undefined){
return sendError(404,"content does not exist."); return sendError(404,"document does not exist.");
} }
const path = join(content.basepath,content.filename); const path = join(document.basepath,document.filename);
ctx.state['content'] = createContentReferrer(content.content_type,path,content.additional); ctx.state['location'] = {
path:path,
type:document.content_type,
additional:document.additional,
} as ContentLocation;
await next(); await next();
}; };
export const getContentRouter = (controller: ContentAccessor)=>{ export const getContentRouter = (controller: DocumentAccessor)=>{
const ret = new Router(); const ret = new Router();
ret.get("/search",PerCheck(Per.QueryContent),ContentQueryHandler(controller)); ret.get("/search",PerCheck(Per.QueryContent),ContentQueryHandler(controller));
ret.get("/:num(\\d+)",PerCheck(Per.QueryContent),ContentIDHandler(controller)); ret.get("/:num(\\d+)",PerCheck(Per.QueryContent),ContentIDHandler(controller));

View File

@ -1,5 +1,10 @@
import {ContentReferrer} from '../content/mod'; import {ContentReferrer} from '../content/mod';
export type ContentLocation = {
path:string,
type:string,
additional:object|undefined,
}
export interface ContentContext{ export interface ContentContext{
content:ContentReferrer location:ContentLocation
} }

View File

@ -37,14 +37,14 @@ export class MangaRouter extends Router<ContentContext>{
constructor(){ constructor(){
super(); super();
this.get("/",async (ctx,next)=>{ 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)=>{ this.get("/:page(\\d+)",async (ctx,next)=>{
const page = Number.parseInt(ctx.params['page']); 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)=>{ this.get("/thumbnail", async (ctx,next)=>{
await renderZipImage(ctx,ctx.state.content.path,0); await renderZipImage(ctx,ctx.state.location.path,0);
}); });
} }
} }

View File

@ -57,10 +57,10 @@ export class VideoRouter extends Router<ContentContext>{
constructor(){ constructor(){
super(); super();
this.get("/", async (ctx,next)=>{ 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)=>{ this.get("/thumbnail", async (ctx,next)=>{
await renderVideo(ctx,ctx.state.content.path); await renderVideo(ctx,ctx.state.location.path);
}) })
} }
} }

View File

@ -7,7 +7,7 @@ import {Watcher} from './diff/diff'
import { createReadStream, readFileSync } from 'fs'; import { createReadStream, readFileSync } from 'fs';
import getContentRouter from './route/contents'; import getContentRouter from './route/contents';
import { createKnexContentsAccessor } from './db/contents'; import { createKnexContentsAccessor } from './db/doc';
import bodyparser from 'koa-bodyparser'; import bodyparser from 'koa-bodyparser';
import {error_handler} from './route/error_handler'; 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'); static_file_server('dist/js/bundle.js.map','text');
const content_router = getContentRouter(createKnexContentsAccessor(db)); const content_router = getContentRouter(createKnexContentsAccessor(db));
router.use('/content',content_router.routes()); router.use('/api/doc',content_router.routes());
router.use('/content',content_router.allowedMethods()); router.use('/api/doc',content_router.allowedMethods());
router.post('/user/login',createLoginMiddleware(db)); router.post('/user/login',createLoginMiddleware(db));
router.post('/user/logout',LogoutMiddleware); router.post('/user/logout',LogoutMiddleware);

6
src/types/db.d.ts vendored
View File

@ -11,7 +11,7 @@ declare module "knex" {
password_hash: string; password_hash: string;
password_salt: string; password_salt: string;
}; };
contents: { document: {
id: number; id: number;
title: string; title: string;
content_type: string; content_type: string;
@ -20,8 +20,8 @@ declare module "knex" {
content_hash?: string; content_hash?: string;
additional?: string; additional?: string;
}; };
content_tag_relation: { doc_tag_relation: {
content_id: number; doc_id: number;
tag_name: string; tag_name: string;
}; };
permissions: { permissions: {

View File

@ -24,7 +24,7 @@ module.exports = ()=>{return {
}, },
plugins : [ plugins : [
new MiniCssExtractPlugin({ new MiniCssExtractPlugin({
"filename":'../css/style.css'}) "filename":'../css/style.css'}),
], ],
devtool: 'source-map', devtool: 'source-map',
resolve: { resolve: {