mirror of
https://github.com/usatiuk/writer.git
synced 2025-10-29 00:17:48 +01:00
simple docs ui
This commit is contained in:
115
frontend/package-lock.json
generated
115
frontend/package-lock.json
generated
@@ -2132,6 +2132,17 @@
|
||||
"proto-list": "~1.2.1"
|
||||
}
|
||||
},
|
||||
"connect": {
|
||||
"version": "3.6.6",
|
||||
"resolved": "https://registry.npmjs.org/connect/-/connect-3.6.6.tgz",
|
||||
"integrity": "sha1-Ce/2xVr3I24TcTWnJXSFi2eG9SQ=",
|
||||
"requires": {
|
||||
"debug": "2.6.9",
|
||||
"finalhandler": "1.1.0",
|
||||
"parseurl": "~1.3.2",
|
||||
"utils-merge": "1.0.1"
|
||||
}
|
||||
},
|
||||
"console-browserify": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.1.0.tgz",
|
||||
@@ -2478,6 +2489,14 @@
|
||||
"node-addon-api": "^1.6.0"
|
||||
}
|
||||
},
|
||||
"debug": {
|
||||
"version": "2.6.9",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
|
||||
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
|
||||
"requires": {
|
||||
"ms": "2.0.0"
|
||||
}
|
||||
},
|
||||
"decamelize": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz",
|
||||
@@ -2732,8 +2751,7 @@
|
||||
"ee-first": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
|
||||
"integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=",
|
||||
"dev": true
|
||||
"integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0="
|
||||
},
|
||||
"electron-to-chromium": {
|
||||
"version": "1.3.96",
|
||||
@@ -2759,8 +2777,7 @@
|
||||
"encodeurl": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
|
||||
"integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=",
|
||||
"dev": true
|
||||
"integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k="
|
||||
},
|
||||
"encoding": {
|
||||
"version": "0.1.12",
|
||||
@@ -2812,8 +2829,7 @@
|
||||
"escape-html": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
|
||||
"integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=",
|
||||
"dev": true
|
||||
"integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg="
|
||||
},
|
||||
"escape-string-regexp": {
|
||||
"version": "1.0.5",
|
||||
@@ -3124,6 +3140,27 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"finalhandler": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.0.tgz",
|
||||
"integrity": "sha1-zgtoVbRYU+eRsvzGgARtiCU91/U=",
|
||||
"requires": {
|
||||
"debug": "2.6.9",
|
||||
"encodeurl": "~1.0.1",
|
||||
"escape-html": "~1.0.3",
|
||||
"on-finished": "~2.3.0",
|
||||
"parseurl": "~1.3.2",
|
||||
"statuses": "~1.3.1",
|
||||
"unpipe": "~1.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"statuses": {
|
||||
"version": "1.3.1",
|
||||
"resolved": "https://registry.npmjs.org/statuses/-/statuses-1.3.1.tgz",
|
||||
"integrity": "sha1-+vUbnrdKrvOzrPStX2Gr8ky3uT4="
|
||||
}
|
||||
}
|
||||
},
|
||||
"find-up": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz",
|
||||
@@ -3210,8 +3247,7 @@
|
||||
"ansi-regex": {
|
||||
"version": "2.1.1",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true
|
||||
"dev": true
|
||||
},
|
||||
"aproba": {
|
||||
"version": "1.2.0",
|
||||
@@ -3232,14 +3268,12 @@
|
||||
"balanced-match": {
|
||||
"version": "1.0.0",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true
|
||||
"dev": true
|
||||
},
|
||||
"brace-expansion": {
|
||||
"version": "1.1.11",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"balanced-match": "^1.0.0",
|
||||
"concat-map": "0.0.1"
|
||||
@@ -3254,20 +3288,17 @@
|
||||
"code-point-at": {
|
||||
"version": "1.1.0",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true
|
||||
"dev": true
|
||||
},
|
||||
"concat-map": {
|
||||
"version": "0.0.1",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true
|
||||
"dev": true
|
||||
},
|
||||
"console-control-strings": {
|
||||
"version": "1.1.0",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true
|
||||
"dev": true
|
||||
},
|
||||
"core-util-is": {
|
||||
"version": "1.0.2",
|
||||
@@ -3384,8 +3415,7 @@
|
||||
"inherits": {
|
||||
"version": "2.0.3",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true
|
||||
"dev": true
|
||||
},
|
||||
"ini": {
|
||||
"version": "1.3.5",
|
||||
@@ -3397,7 +3427,6 @@
|
||||
"version": "1.0.0",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"number-is-nan": "^1.0.0"
|
||||
}
|
||||
@@ -3412,7 +3441,6 @@
|
||||
"version": "3.0.4",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"brace-expansion": "^1.1.7"
|
||||
}
|
||||
@@ -3420,14 +3448,12 @@
|
||||
"minimist": {
|
||||
"version": "0.0.8",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true
|
||||
"dev": true
|
||||
},
|
||||
"minipass": {
|
||||
"version": "2.2.4",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"safe-buffer": "^5.1.1",
|
||||
"yallist": "^3.0.0"
|
||||
@@ -3446,7 +3472,6 @@
|
||||
"version": "0.5.1",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"minimist": "0.0.8"
|
||||
}
|
||||
@@ -3527,8 +3552,7 @@
|
||||
"number-is-nan": {
|
||||
"version": "1.0.1",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true
|
||||
"dev": true
|
||||
},
|
||||
"object-assign": {
|
||||
"version": "4.1.1",
|
||||
@@ -3540,7 +3564,6 @@
|
||||
"version": "1.4.0",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"wrappy": "1"
|
||||
}
|
||||
@@ -3626,8 +3649,7 @@
|
||||
"safe-buffer": {
|
||||
"version": "5.1.1",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true
|
||||
"dev": true
|
||||
},
|
||||
"safer-buffer": {
|
||||
"version": "2.1.2",
|
||||
@@ -3663,7 +3685,6 @@
|
||||
"version": "1.0.2",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"code-point-at": "^1.0.0",
|
||||
"is-fullwidth-code-point": "^1.0.0",
|
||||
@@ -3683,7 +3704,6 @@
|
||||
"version": "3.0.1",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"ansi-regex": "^2.0.0"
|
||||
}
|
||||
@@ -3727,14 +3747,12 @@
|
||||
"wrappy": {
|
||||
"version": "1.0.2",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true
|
||||
"dev": true
|
||||
},
|
||||
"yallist": {
|
||||
"version": "3.0.2",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -5419,8 +5437,7 @@
|
||||
"ms": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
|
||||
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=",
|
||||
"dev": true
|
||||
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
|
||||
},
|
||||
"nan": {
|
||||
"version": "2.12.1",
|
||||
@@ -5809,7 +5826,6 @@
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz",
|
||||
"integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"ee-first": "1.1.1"
|
||||
}
|
||||
@@ -6002,8 +6018,7 @@
|
||||
"parseurl": {
|
||||
"version": "1.3.2",
|
||||
"resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.2.tgz",
|
||||
"integrity": "sha1-/CidTtiZMRlGDBViUyYs3I3mW/M=",
|
||||
"dev": true
|
||||
"integrity": "sha1-/CidTtiZMRlGDBViUyYs3I3mW/M="
|
||||
},
|
||||
"pascalcase": {
|
||||
"version": "0.1.1",
|
||||
@@ -7190,6 +7205,14 @@
|
||||
"integrity": "sha1-9JvmtIeJTdxA3MlKMi9hEJLgDV4=",
|
||||
"dev": true
|
||||
},
|
||||
"rea": {
|
||||
"version": "0.0.1",
|
||||
"resolved": "https://registry.npmjs.org/rea/-/rea-0.0.1.tgz",
|
||||
"integrity": "sha1-EO+mFMyQc1Ib2mdLb8lQsDFb16Q=",
|
||||
"requires": {
|
||||
"connect": ">=1.4.1"
|
||||
}
|
||||
},
|
||||
"react": {
|
||||
"version": "16.7.0",
|
||||
"resolved": "https://registry.npmjs.org/react/-/react-16.7.0.tgz",
|
||||
@@ -8657,6 +8680,11 @@
|
||||
"integrity": "sha1-/+3ks2slKQaW5uFl1KWe25mOawI=",
|
||||
"dev": true
|
||||
},
|
||||
"unpipe": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
|
||||
"integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw="
|
||||
},
|
||||
"unquote": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/unquote/-/unquote-1.1.1.tgz",
|
||||
@@ -8781,6 +8809,11 @@
|
||||
"object.getownpropertydescriptors": "^2.0.3"
|
||||
}
|
||||
},
|
||||
"utils-merge": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
|
||||
"integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM="
|
||||
},
|
||||
"uuid": {
|
||||
"version": "3.3.2",
|
||||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz",
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
"dependencies": {
|
||||
"@blueprintjs/core": "^3.10.0",
|
||||
"@blueprintjs/icons": "^3.4.0",
|
||||
"rea": "0.0.1",
|
||||
"react": "^16.7.0",
|
||||
"react-dom": "^16.7.0",
|
||||
"react-redux": "^6.0.0",
|
||||
|
||||
5
frontend/src/Documents/List.tsx
Normal file
5
frontend/src/Documents/List.tsx
Normal file
@@ -0,0 +1,5 @@
|
||||
import * as React from "react";
|
||||
|
||||
export function List() {
|
||||
return <div />;
|
||||
}
|
||||
62
frontend/src/Documents/Overview.tsx
Normal file
62
frontend/src/Documents/Overview.tsx
Normal file
@@ -0,0 +1,62 @@
|
||||
import { H1 } from "@blueprintjs/core";
|
||||
import * as React from "react";
|
||||
import { connect } from "react-redux";
|
||||
import { Dispatch } from "redux";
|
||||
import { IDocumentJSON } from "~../../src/entity/Document";
|
||||
import { fetchDocsStart } from "~redux/docs/actions";
|
||||
import { IAppState } from "~redux/reducers";
|
||||
|
||||
export interface IOverviewComponentProps {
|
||||
recent: IDocumentJSON[] | null;
|
||||
all: IDocumentJSON[] | null;
|
||||
fetching: boolean;
|
||||
|
||||
fetchDocs: () => void;
|
||||
}
|
||||
|
||||
export class OverviewComponent extends React.PureComponent<
|
||||
IOverviewComponentProps,
|
||||
null
|
||||
> {
|
||||
constructor(props: IOverviewComponentProps) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
public componentDidMount() {
|
||||
if (!this.props.all) {
|
||||
this.props.fetchDocs();
|
||||
}
|
||||
}
|
||||
|
||||
public render() {
|
||||
let docsList;
|
||||
if (this.props.all) {
|
||||
docsList = this.props.all.map(doc => (
|
||||
<div key={doc.id}>
|
||||
<H1>{doc.name}</H1>
|
||||
<p>{doc.content}</p>
|
||||
</div>
|
||||
));
|
||||
}
|
||||
return docsList || <div>Loading</div>;
|
||||
}
|
||||
}
|
||||
|
||||
function mapStateToProps(state: IAppState) {
|
||||
return {
|
||||
recent: state.docs.recent,
|
||||
all: state.docs.all,
|
||||
fetching: state.docs.fetching,
|
||||
};
|
||||
}
|
||||
|
||||
function mapDispatchToProps(dispatch: Dispatch) {
|
||||
return {
|
||||
fetchDocs: () => dispatch(fetchDocsStart()),
|
||||
};
|
||||
}
|
||||
|
||||
export const Overview = connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps,
|
||||
)(OverviewComponent);
|
||||
@@ -8,9 +8,10 @@ import {
|
||||
} from "@blueprintjs/core";
|
||||
import * as React from "react";
|
||||
import { connect } from "react-redux";
|
||||
import { RouteComponentProps, withRouter } from "react-router";
|
||||
import { Route, RouteComponentProps, Switch, withRouter } from "react-router";
|
||||
import { Dispatch } from "redux";
|
||||
import { IUserJSON } from "~../../src/entity/User";
|
||||
import { Overview } from "~Documents/Overview";
|
||||
import { IAppState } from "~redux/reducers";
|
||||
import { logoutUser } from "~redux/user/actions";
|
||||
|
||||
@@ -70,7 +71,11 @@ export class HomeComponent extends React.PureComponent<IHomeProps> {
|
||||
/>
|
||||
</Navbar.Group>
|
||||
</Navbar>
|
||||
<div id="MainScreen" />
|
||||
<div id="MainScreen">
|
||||
<Switch>
|
||||
<Route exact={true} path="/" component={Overview} />
|
||||
</Switch>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
);
|
||||
|
||||
14
frontend/src/redux/api/docs/index.ts
Normal file
14
frontend/src/redux/api/docs/index.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import { IDocumentJSON } from "~../../src/entity/Document";
|
||||
import { IAPIResponse } from "~../../src/types";
|
||||
|
||||
import { fetchJSONAuth } from "../utils";
|
||||
|
||||
export async function fetchRecentDocs(): Promise<
|
||||
IAPIResponse<IDocumentJSON[]>
|
||||
> {
|
||||
return fetchJSONAuth("/docs/list/recent", "GET");
|
||||
}
|
||||
|
||||
export async function fetchAllDocs(): Promise<IAPIResponse<IDocumentJSON[]>> {
|
||||
return fetchJSONAuth("/docs/list", "GET");
|
||||
}
|
||||
47
frontend/src/redux/docs/actions.ts
Normal file
47
frontend/src/redux/docs/actions.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
import { Action } from "redux";
|
||||
import { IDocumentJSON } from "~../../src/entity/Document";
|
||||
|
||||
export enum DocsTypes {
|
||||
DOCS_FETCH_START = "DOCS_FETCH_START",
|
||||
DOCS_FETCH_FAIL = "DOCS_FETCH_FAIL",
|
||||
DOCS_FETCH_SUCCESS = "DOCS_FETCH_SUCCESS",
|
||||
}
|
||||
|
||||
export interface IDocsFetchStartAction extends Action {
|
||||
type: DocsTypes.DOCS_FETCH_START;
|
||||
}
|
||||
|
||||
export interface IDocsFetchFailAction extends Action {
|
||||
type: DocsTypes.DOCS_FETCH_FAIL;
|
||||
payload: {
|
||||
error: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface IDocsFetchSuccessAction extends Action {
|
||||
type: DocsTypes.DOCS_FETCH_SUCCESS;
|
||||
payload: {
|
||||
recent: IDocumentJSON[];
|
||||
all: IDocumentJSON[];
|
||||
};
|
||||
}
|
||||
|
||||
export function fetchDocsStart(): IDocsFetchStartAction {
|
||||
return { type: DocsTypes.DOCS_FETCH_START };
|
||||
}
|
||||
|
||||
export function fetchDocsFail(error: string): IDocsFetchFailAction {
|
||||
return { type: DocsTypes.DOCS_FETCH_FAIL, payload: { error } };
|
||||
}
|
||||
|
||||
export function fetchDocsSuccess(
|
||||
recent: IDocumentJSON[],
|
||||
all: IDocumentJSON[],
|
||||
): IDocsFetchSuccessAction {
|
||||
return { type: DocsTypes.DOCS_FETCH_SUCCESS, payload: { recent, all } };
|
||||
}
|
||||
|
||||
export type DocsAction =
|
||||
| IDocsFetchStartAction
|
||||
| IDocsFetchFailAction
|
||||
| IDocsFetchSuccessAction;
|
||||
35
frontend/src/redux/docs/reducer.ts
Normal file
35
frontend/src/redux/docs/reducer.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
import { Reducer } from "react";
|
||||
import { IDocumentJSON } from "~../../src/entity/Document";
|
||||
|
||||
import { DocsAction, DocsTypes } from "./actions";
|
||||
|
||||
export interface IDocsState {
|
||||
recent: IDocumentJSON[] | null;
|
||||
all: IDocumentJSON[] | null;
|
||||
fetching: boolean;
|
||||
error: string | null;
|
||||
}
|
||||
|
||||
const defaultDocsState: IDocsState = {
|
||||
recent: null,
|
||||
all: null,
|
||||
fetching: false,
|
||||
error: null,
|
||||
};
|
||||
|
||||
export const docsReducer: Reducer<IDocsState, DocsAction> = (
|
||||
state: IDocsState = defaultDocsState,
|
||||
action: DocsAction,
|
||||
) => {
|
||||
switch (action.type) {
|
||||
case DocsTypes.DOCS_FETCH_START:
|
||||
return { ...defaultDocsState, fetching: true };
|
||||
case DocsTypes.DOCS_FETCH_SUCCESS:
|
||||
return { ...defaultDocsState, ...action.payload };
|
||||
case DocsTypes.DOCS_FETCH_FAIL:
|
||||
return { ...defaultDocsState, ...action.payload };
|
||||
default:
|
||||
return state;
|
||||
break;
|
||||
}
|
||||
};
|
||||
56
frontend/src/redux/docs/sagas.ts
Normal file
56
frontend/src/redux/docs/sagas.ts
Normal file
@@ -0,0 +1,56 @@
|
||||
import { delay } from "redux-saga";
|
||||
import {
|
||||
all,
|
||||
call,
|
||||
cancel,
|
||||
fork,
|
||||
put,
|
||||
race,
|
||||
takeLatest,
|
||||
} from "redux-saga/effects";
|
||||
import { fetchAllDocs, fetchRecentDocs } from "~redux/api/docs";
|
||||
|
||||
import {
|
||||
DocsTypes,
|
||||
fetchDocsFail,
|
||||
fetchDocsSuccess,
|
||||
IDocsFetchStartAction,
|
||||
} from "./actions";
|
||||
|
||||
function* startSpinner() {
|
||||
yield delay(300);
|
||||
}
|
||||
|
||||
function* docsFetchStart(action: IDocsFetchStartAction) {
|
||||
try {
|
||||
const spinner = yield fork(startSpinner);
|
||||
|
||||
const { response, timeout } = yield race({
|
||||
response: all([call(fetchRecentDocs), call(fetchAllDocs)]),
|
||||
timeout: call(delay, 10000),
|
||||
});
|
||||
|
||||
yield cancel(spinner);
|
||||
|
||||
if (timeout) {
|
||||
yield put(fetchDocsFail("Timeout"));
|
||||
return;
|
||||
}
|
||||
|
||||
if (response) {
|
||||
if (response[0].data == null || response[1].data == null) {
|
||||
yield put(fetchDocsFail(response[0].error));
|
||||
} else {
|
||||
const recentDocs = response[0].data;
|
||||
const allDocs = response[1].data;
|
||||
yield put(fetchDocsSuccess(recentDocs, allDocs));
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
yield put(fetchDocsFail("Internal error"));
|
||||
}
|
||||
}
|
||||
|
||||
export function* docsSaga() {
|
||||
yield all([takeLatest(DocsTypes.DOCS_FETCH_START, docsFetchStart)]);
|
||||
}
|
||||
@@ -3,11 +3,13 @@ import { persistReducer } from "redux-persist";
|
||||
import storage from "redux-persist/lib/storage";
|
||||
import { authReducer, IAuthState } from "~redux/auth/reducer";
|
||||
|
||||
import { docsReducer, IDocsState } from "./docs/reducer";
|
||||
import { IUserState, userReducer } from "./user/reducer";
|
||||
|
||||
export interface IAppState {
|
||||
auth: IAuthState;
|
||||
user: IUserState;
|
||||
docs: IDocsState;
|
||||
}
|
||||
|
||||
const authPersistConfig = {
|
||||
@@ -19,4 +21,5 @@ const authPersistConfig = {
|
||||
export const rootReducer = combineReducers({
|
||||
auth: persistReducer(authPersistConfig, authReducer),
|
||||
user: userReducer,
|
||||
docs: docsReducer,
|
||||
});
|
||||
|
||||
@@ -5,6 +5,7 @@ import { rootReducer } from "~redux/reducers";
|
||||
|
||||
import { setToken } from "./api/utils";
|
||||
import { authSaga } from "./auth/sagas";
|
||||
import { docsSaga } from "./docs/sagas";
|
||||
import { getUser } from "./user/actions";
|
||||
import { userSaga } from "./user/sagas";
|
||||
|
||||
@@ -22,3 +23,4 @@ export const persistor = persistStore(store, null, () => {
|
||||
|
||||
sagaMiddleware.run(authSaga);
|
||||
sagaMiddleware.run(userSaga);
|
||||
sagaMiddleware.run(docsSaga);
|
||||
|
||||
@@ -8,10 +8,14 @@ import {
|
||||
|
||||
import { User } from "./User";
|
||||
|
||||
export type IDocumentJSON = Pick<
|
||||
Document,
|
||||
"id" | "user" | "name" | "content" | "createdAt"
|
||||
>;
|
||||
export interface IDocumentJSON {
|
||||
id: number;
|
||||
user: number;
|
||||
name: string;
|
||||
content: string;
|
||||
createdAt: number;
|
||||
editedAt: number;
|
||||
}
|
||||
|
||||
@Entity()
|
||||
export class Document extends BaseEntity {
|
||||
@@ -41,4 +45,15 @@ export class Document extends BaseEntity {
|
||||
this.name = name;
|
||||
this.content = content;
|
||||
}
|
||||
|
||||
public toJSON(user: number): IDocumentJSON {
|
||||
return {
|
||||
id: this.id,
|
||||
user: user as any,
|
||||
name: this.name,
|
||||
content: this.content,
|
||||
createdAt: this.createdAt.getTime(),
|
||||
editedAt: this.editedAt.getTime(),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,8 +3,6 @@ import { Document } from "~entity/Document";
|
||||
|
||||
export const docsRouter = new Router();
|
||||
|
||||
export type IDocumentJSON = Pick<Document, "id" | "name" | "content">;
|
||||
|
||||
docsRouter.post("/docs/new", async ctx => {
|
||||
if (!ctx.state.user) {
|
||||
ctx.throw(401);
|
||||
@@ -31,11 +29,7 @@ docsRouter.post("/docs/new", async ctx => {
|
||||
|
||||
ctx.body = {
|
||||
error: false,
|
||||
data: {
|
||||
id: document.id,
|
||||
name: document.name,
|
||||
content: document.content,
|
||||
},
|
||||
data: document.toJSON(user.id),
|
||||
};
|
||||
});
|
||||
|
||||
@@ -81,11 +75,7 @@ docsRouter.patch("/docs/byID/:id", async ctx => {
|
||||
|
||||
ctx.body = {
|
||||
error: false,
|
||||
data: {
|
||||
id: document.id,
|
||||
name: document.name,
|
||||
content: document.content,
|
||||
},
|
||||
data: document.toJSON(user.id),
|
||||
};
|
||||
});
|
||||
|
||||
@@ -100,11 +90,25 @@ docsRouter.get("/docs/list", async ctx => {
|
||||
|
||||
ctx.body = {
|
||||
error: false,
|
||||
data: documents.map(document => ({
|
||||
id: document.id,
|
||||
name: document.name,
|
||||
content: document.content,
|
||||
})),
|
||||
data: documents.map(document => document.toJSON(user.id)),
|
||||
};
|
||||
});
|
||||
|
||||
docsRouter.get("/docs/list/recent", async ctx => {
|
||||
if (!ctx.state.user) {
|
||||
ctx.throw(401);
|
||||
}
|
||||
|
||||
const { user } = ctx.state;
|
||||
|
||||
const documents = await Document.find({
|
||||
where: { user: user.id },
|
||||
order: { editedAt: "DESC" },
|
||||
});
|
||||
|
||||
ctx.body = {
|
||||
error: false,
|
||||
data: documents.map(document => document.toJSON(user.id)),
|
||||
};
|
||||
});
|
||||
|
||||
@@ -131,11 +135,7 @@ docsRouter.get("/docs/byID/:id", async ctx => {
|
||||
|
||||
ctx.body = {
|
||||
error: false,
|
||||
data: {
|
||||
id: document.id,
|
||||
name: document.name,
|
||||
content: document.content,
|
||||
},
|
||||
data: document.toJSON(user.id),
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
4
src/types.ts
Normal file
4
src/types.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export interface IAPIResponse<T> {
|
||||
data: T | null;
|
||||
error: string | null;
|
||||
}
|
||||
@@ -3,8 +3,7 @@ import { connect } from "config/database";
|
||||
import * as request from "supertest";
|
||||
import { getConnection } from "typeorm";
|
||||
import { app } from "~app";
|
||||
import { Document } from "~entity/Document";
|
||||
import { IDocumentJSON } from "~routes/docs";
|
||||
import { Document, IDocumentJSON } from "~entity/Document";
|
||||
|
||||
import { ISeed, seedDB } from "./util";
|
||||
|
||||
@@ -89,12 +88,38 @@ describe("docs", () => {
|
||||
|
||||
const documents = response.body.data as IDocumentJSON[];
|
||||
|
||||
const userDocs = [seed.doc1.toJSON(seed.user1.id)];
|
||||
|
||||
expect(documents).to.deep.equal(userDocs);
|
||||
});
|
||||
|
||||
it("should list recent docs", async () => {
|
||||
const doc1 = new Document(seed.user1, "doc1", "");
|
||||
doc1.editedAt = new Date(doc1.editedAt.getTime() + 10000);
|
||||
await doc1.save();
|
||||
const doc2 = new Document(seed.user1, "doc2", "");
|
||||
doc2.editedAt = new Date(doc2.editedAt.getTime() + 20000);
|
||||
await doc2.save();
|
||||
const doc3 = new Document(seed.user1, "doc3", "");
|
||||
doc3.editedAt = new Date(doc3.editedAt.getTime() + 30000);
|
||||
await doc3.save();
|
||||
|
||||
const response = await request(callback)
|
||||
.get("/docs/list/recent")
|
||||
.set({
|
||||
Authorization: `Bearer ${seed.user1.toJWT()}`,
|
||||
})
|
||||
.expect(200);
|
||||
|
||||
expect(response.body.error).to.be.false;
|
||||
|
||||
const documents = response.body.data as IDocumentJSON[];
|
||||
|
||||
const userDocs = [
|
||||
{
|
||||
id: seed.doc1.id,
|
||||
name: seed.doc1.name,
|
||||
content: seed.doc1.content,
|
||||
},
|
||||
doc3.toJSON(seed.user1.id),
|
||||
doc2.toJSON(seed.user1.id),
|
||||
doc1.toJSON(seed.user1.id),
|
||||
seed.doc1.toJSON(seed.user1.id),
|
||||
];
|
||||
|
||||
expect(documents).to.deep.equal(userDocs);
|
||||
@@ -112,11 +137,7 @@ describe("docs", () => {
|
||||
|
||||
const document = response.body.data as IDocumentJSON;
|
||||
|
||||
const usedDoc = {
|
||||
id: seed.doc1.id,
|
||||
name: seed.doc1.name,
|
||||
content: seed.doc1.content,
|
||||
};
|
||||
const usedDoc = seed.doc1.toJSON(seed.user1.id);
|
||||
|
||||
expect(document).to.deep.equal(usedDoc);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user