mirror of
https://github.com/QingWei-Li/notea.git
synced 2024-12-04 20:32:36 +03:00
refactor: middlewares (#40)
This commit is contained in:
parent
399b6a8ac4
commit
c4ff336c6a
36
README.md
36
README.md
@ -135,9 +135,6 @@ PASSWORD=
|
||||
# bucketname,namespace and region “ap-chuncheon-1” need check your profile and https://docs.oracle.com/en-us/iaas/api/#/en/s3objectstorage/20160918/
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
### Exoscale
|
||||
|
||||
`.env`
|
||||
@ -152,24 +149,23 @@ STORE_FORCE_PATH_STYLE=true
|
||||
PASSWORD=
|
||||
```
|
||||
|
||||
|
||||
Other services that support the s3 protocol can also be used.
|
||||
Contribution examples are welcome.
|
||||
|
||||
## Environment variables
|
||||
|
||||
| Name | Description | Default | Optional | Required |
|
||||
| ---------------------- | -------------------------------------------------------------------------------------------------------------------------- | --------- | -------- | -------- |
|
||||
| PASSWORD | Password to login to the app | | | true |
|
||||
| STORE_ACCESS_KEY | AccessKey | | | true |
|
||||
| STORE_SECRET_KEY | SecretKey | | | true |
|
||||
| STORE_BUCKET | Bucket | | | true |
|
||||
| STORE_END_POINT | Host name or an IP address. | | | |
|
||||
| STORE_REGION | region | us-east-1 | | |
|
||||
| STORE_FORCE_PATH_STYLE | Whether to force path style URLs for S3 objects | false | | |
|
||||
| COOKIE_SECURE | Only works under https: scheme **If the website is not https, you may not be able to log in, you need to set it to false** | true | | |
|
||||
| BASE_URL | The domain of the website, used for SEO | | | |
|
||||
| DISABLE_PASSWORD | Disable password protection | false | | |
|
||||
| Name | Description | Default | Optional | Required |
|
||||
| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | --------- | -------- | -------- |
|
||||
| PASSWORD | Password to login to the app | | | true |
|
||||
| STORE_ACCESS_KEY | AccessKey | | | true |
|
||||
| STORE_SECRET_KEY | SecretKey | | | true |
|
||||
| STORE_BUCKET | Bucket | | | true |
|
||||
| STORE_END_POINT | Host name or an IP address. | | | |
|
||||
| STORE_REGION | region | us-east-1 | | |
|
||||
| STORE_FORCE_PATH_STYLE | Whether to force path style URLs for S3 objects | false | | |
|
||||
| COOKIE_SECURE | Only works under https: scheme **If the website is not https, you may not be able to log in, and you need to set it to false** | true | | |
|
||||
| BASE_URL | The domain of the website, used for SEO | | | |
|
||||
| DISABLE_PASSWORD | Disable password protection. This means that you need to implement authentication on the server yourself, but the route `/share/:id` needs to be accessible anonymously, if you need share page. | false | | |
|
||||
|
||||
## Development
|
||||
|
||||
@ -182,16 +178,16 @@ yarn dev
|
||||
|
||||
### What is S3? And what is MinIO?
|
||||
|
||||
- Amazon Simple Storage Service (AKA Amazon S3) . TLDR: Read and write stored files or pictures through RESTful API.
|
||||
- MinIO: a self-hosted S3. Install by docker: docker run -p 9000:9000 minio/minio server /data
|
||||
- Amazon Simple Storage Service (AKA Amazon S3). TLDR: Read and write stored files or pictures through RESTful API.
|
||||
- MinIO: a self-hosted S3. Install by docker: `docker run -p 9000:9000 minio/minio server /data`
|
||||
|
||||
### Why not use Database?
|
||||
|
||||
My understanding: The data stored in Notea is mainly files (such as text or pictures), which is not what the database is good at; Database is more expensive than S3 to read and write small data; Use S3 to generate a signed URL to access files, but the database cannot do it.
|
||||
Personally speaking, the data stored in Notea is mainly files (such as text or pictures) but the database is not good at reading and writing these type of files; S3 can generate a signed URL to access the remote files, but the database cannot do it.
|
||||
|
||||
### Why not use filesystem storage?
|
||||
|
||||
Too many excellent offline note-taking apps on the web. Because I couldn't find a product that supports both self-hosted and data synchronization, I create it.
|
||||
There are many excellent offline note-taking apps supporting filesystem storage available. However, I couldn't find a APP that supports both self-hosted and easy to manage the synchronized data. The purpose of this project is to mitigate the above pain-point.
|
||||
|
||||
## LICENSE
|
||||
|
||||
|
@ -6,16 +6,26 @@ import { NextSeo } from 'next-seo'
|
||||
import { renderMarkdown } from 'libs/web/render-markdown'
|
||||
// TODO: Maybe can custom
|
||||
import 'highlight.js/styles/zenburn.css'
|
||||
import { PageMode } from 'libs/shared/page'
|
||||
import Error from 'next/error'
|
||||
import useI18n from 'libs/web/hooks/use-i18n'
|
||||
|
||||
export const PostContainer: FC<{ baseURL: string }> = ({ baseURL }) => {
|
||||
export const PostContainer: FC<{ baseURL: string; pageMode: PageMode }> = ({
|
||||
baseURL,
|
||||
pageMode,
|
||||
}) => {
|
||||
const { t } = useI18n()
|
||||
const { note } = NoteState.useContainer()
|
||||
|
||||
const content = useMemo(() => renderMarkdown(note?.content ?? ''), [note])
|
||||
const description = useMemo(
|
||||
() => removeMarkdown(note?.content).slice(0, 100),
|
||||
[note]
|
||||
)
|
||||
|
||||
if (pageMode !== PageMode.PUBLIC) {
|
||||
return <Error statusCode={404} title={t('Not a public page')}></Error>
|
||||
}
|
||||
|
||||
return (
|
||||
<AriticleStyle className="prose mx-auto prose-sm lg:prose-xl px-4 md:px-0">
|
||||
<NextSeo
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { TextareaAutosize } from '@material-ui/core'
|
||||
import useI18n from 'libs/web/hooks/use-i18n'
|
||||
import { NoteModel } from 'libs/web/state/note'
|
||||
import { NoteModel } from 'libs/shared/note'
|
||||
import { has } from 'lodash'
|
||||
import { useRouter } from 'next/router'
|
||||
import {
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { FC, RefObject, useCallback } from 'react'
|
||||
import { use100vh } from 'react-div-100vh'
|
||||
import useEditState from './edit-state'
|
||||
import { NoteModel } from 'libs/web/state/note'
|
||||
import { NoteModel } from 'libs/shared/note'
|
||||
import MarkdownEditor from 'rich-markdown-editor'
|
||||
import { DebouncedState } from 'use-debounce/lib/useDebouncedCallback'
|
||||
import { useTheme } from 'next-themes'
|
||||
|
@ -1,5 +1,5 @@
|
||||
import MarkdownEditor from 'rich-markdown-editor'
|
||||
import NoteState, { NoteModel } from 'libs/web/state/note'
|
||||
import NoteState from 'libs/web/state/note'
|
||||
import { useRef } from 'react'
|
||||
import router from 'next/router'
|
||||
import { has } from 'lodash'
|
||||
@ -7,6 +7,7 @@ import { useDebouncedCallback } from 'use-debounce'
|
||||
import EditTitle from './edit-title'
|
||||
import Editor from './editor'
|
||||
import styled from 'styled-components'
|
||||
import { NoteModel } from 'libs/shared/note'
|
||||
|
||||
const Article = styled.article`
|
||||
ul {
|
||||
|
@ -1,6 +1,6 @@
|
||||
import NoteTreeState from 'libs/web/state/tree'
|
||||
import { FC, useEffect } from 'react'
|
||||
import NoteState, { NoteModel } from 'libs/web/state/note'
|
||||
import NoteState from 'libs/web/state/note'
|
||||
import { useResizeDetector } from 'react-resize-detector'
|
||||
import Sidebar from 'components/sidebar/sidebar'
|
||||
import UIState from 'libs/web/state/ui'
|
||||
@ -14,6 +14,7 @@ import SearchModal from 'components/portal/search-modal/search-modal'
|
||||
import ShareModal from 'components/portal/share-modal'
|
||||
import { SwipeableDrawer } from '@material-ui/core'
|
||||
import SidebarMenu from 'components/portal/sidebar-menu'
|
||||
import { NoteModel } from 'libs/shared/note'
|
||||
|
||||
const StyledWrapper = styled.div`
|
||||
.gutter {
|
||||
@ -67,7 +68,7 @@ const MobileMainWrapper: FC = ({ children }) => {
|
||||
}
|
||||
|
||||
const LayoutMain: FC<{
|
||||
tree: TreeModel
|
||||
tree?: TreeModel
|
||||
note?: NoteModel
|
||||
}> = ({ children, tree, note }) => {
|
||||
const { ua } = UIState.useContainer()
|
||||
|
@ -1,10 +1,11 @@
|
||||
import { NoteModel } from 'libs/shared/note'
|
||||
import { TreeModel } from 'libs/shared/tree'
|
||||
import NoteState, { NoteModel } from 'libs/web/state/note'
|
||||
import NoteState from 'libs/web/state/note'
|
||||
import NoteTreeState from 'libs/web/state/tree'
|
||||
import { FC } from 'react'
|
||||
|
||||
const LayoutPublic: FC<{
|
||||
tree: TreeModel
|
||||
tree?: TreeModel
|
||||
note?: NoteModel
|
||||
}> = ({ children, note, tree }) => {
|
||||
return (
|
||||
|
@ -4,7 +4,7 @@ import FilterModal from 'components/portal/filter-modal/filter-modal'
|
||||
import FilterModalInput from 'components/portal/filter-modal/filter-modal-input'
|
||||
import FilterModalList from 'components/portal/filter-modal/filter-modal-list'
|
||||
import SearchItem from './search-item'
|
||||
import { NoteModel } from 'libs/web/state/note'
|
||||
import { NoteModel } from 'libs/shared/note'
|
||||
import PortalState from 'libs/web/state/portal'
|
||||
import useI18n from 'libs/web/hooks/use-i18n'
|
||||
|
||||
|
@ -7,6 +7,7 @@ import NoteState from 'libs/web/state/note'
|
||||
import { NOTE_SHARED } from 'libs/shared/meta'
|
||||
import { useRouter } from 'next/router'
|
||||
import useI18n from 'libs/web/hooks/use-i18n'
|
||||
import UIState from 'libs/web/state/ui'
|
||||
|
||||
const ShareModal: FC = () => {
|
||||
const { t } = useI18n()
|
||||
@ -15,6 +16,7 @@ const ShareModal: FC = () => {
|
||||
const [copied, setCopied] = useState(false)
|
||||
const { note, updateNote } = NoteState.useContainer()
|
||||
const router = useRouter()
|
||||
const { disablePassword } = UIState.useContainer()
|
||||
|
||||
const handleCopy = useCallback(() => {
|
||||
url && navigator.clipboard.writeText(url)
|
||||
@ -31,8 +33,12 @@ const ShareModal: FC = () => {
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
setUrl(location.href)
|
||||
}, [router.query])
|
||||
if (disablePassword) {
|
||||
setUrl(`${location.origin}/share/${router.query.id}`)
|
||||
} else {
|
||||
setUrl(location.href)
|
||||
}
|
||||
}, [disablePassword, router.query])
|
||||
|
||||
return (
|
||||
<Popover
|
||||
|
@ -3,7 +3,7 @@ import FilterModal from 'components/portal/filter-modal/filter-modal'
|
||||
import FilterModalInput from 'components/portal/filter-modal/filter-modal-input'
|
||||
import FilterModalList from 'components/portal/filter-modal/filter-modal-list'
|
||||
import TrashItem from './trash-item'
|
||||
import { NoteModel } from 'libs/web/state/note'
|
||||
import { NoteModel } from 'libs/shared/note'
|
||||
import TrashState from 'libs/web/state/trash'
|
||||
import PortalState from 'libs/web/state/portal'
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { NoteModel } from 'libs/web/state/note'
|
||||
import { NoteModel } from 'libs/shared/note'
|
||||
import Link from 'next/link'
|
||||
import { FC, ReactText, MouseEvent, useCallback } from 'react'
|
||||
import classNames from 'classnames'
|
||||
|
@ -1,29 +0,0 @@
|
||||
import nc from 'next-connect'
|
||||
import { onError, useError } from './middlewares/error'
|
||||
import { NextApiRequest, NextApiResponse } from 'next'
|
||||
import { API } from './middlewares/error'
|
||||
import { StoreProvider } from 'libs/server/store'
|
||||
import { useSession } from './middlewares/session'
|
||||
import { Session } from 'next-iron-session'
|
||||
import TreeStore from './tree'
|
||||
import { useCsrf } from 'libs/server/middlewares/csrf'
|
||||
|
||||
export type ApiRequest = NextApiRequest & {
|
||||
store: StoreProvider
|
||||
treeStore: TreeStore
|
||||
session: Session
|
||||
}
|
||||
|
||||
export type ApiResponse = NextApiResponse & {
|
||||
APIError: typeof API
|
||||
}
|
||||
|
||||
export type ApiNext = () => void
|
||||
|
||||
export const api = () =>
|
||||
nc<ApiRequest, ApiResponse>({
|
||||
onError,
|
||||
})
|
||||
.use(useError)
|
||||
.use(useSession)
|
||||
.use(useCsrf)
|
80
libs/server/connect.ts
Normal file
80
libs/server/connect.ts
Normal file
@ -0,0 +1,80 @@
|
||||
import nc, { Middleware } from 'next-connect'
|
||||
import { onError, useError } from './middlewares/error'
|
||||
import {
|
||||
GetServerSidePropsContext,
|
||||
NextApiRequest,
|
||||
NextApiResponse,
|
||||
Redirect,
|
||||
} from 'next'
|
||||
import { API } from './middlewares/error'
|
||||
import { StoreProvider } from 'libs/server/store'
|
||||
import { useSession } from './middlewares/session'
|
||||
import { Session } from 'next-iron-session'
|
||||
import TreeStore from './tree'
|
||||
import { useCsrf } from 'libs/server/middlewares/csrf'
|
||||
import { PageMode } from 'libs/shared/page'
|
||||
import { NoteModel } from 'libs/shared/note'
|
||||
import { Settings } from 'libs/shared/settings'
|
||||
import { TreeModel } from 'libs/shared/tree'
|
||||
import { UserAgentType } from 'libs/shared/ua'
|
||||
import { useStore } from './middlewares/store'
|
||||
|
||||
export interface ServerState {
|
||||
store: StoreProvider
|
||||
treeStore: TreeStore
|
||||
}
|
||||
|
||||
export interface ServerProps {
|
||||
isLoggedIn: boolean
|
||||
csrfToken?: string
|
||||
pageMode: PageMode
|
||||
note?: NoteModel
|
||||
baseURL: string
|
||||
settings?: Settings
|
||||
lngDict: JSON
|
||||
tree?: TreeModel
|
||||
ua?: UserAgentType
|
||||
disablePassword: boolean
|
||||
}
|
||||
|
||||
export type ApiRequest = NextApiRequest & {
|
||||
session: Session
|
||||
state: ServerState
|
||||
props: ServerProps
|
||||
redirect: Redirect
|
||||
}
|
||||
|
||||
export type ApiResponse = NextApiResponse & {
|
||||
APIError: typeof API
|
||||
}
|
||||
|
||||
export type ApiNext = () => void
|
||||
|
||||
export const api = () =>
|
||||
nc<ApiRequest, ApiResponse>({
|
||||
onError,
|
||||
})
|
||||
.use(useError)
|
||||
.use(useSession)
|
||||
.use(useCsrf)
|
||||
|
||||
// used by getServerSideProps
|
||||
export const ssr = () =>
|
||||
nc<ApiRequest, ApiResponse>({
|
||||
onError,
|
||||
})
|
||||
// init props
|
||||
.use((req, _res, next) => {
|
||||
req.props = {} as ServerProps
|
||||
req.state = {} as ServerState
|
||||
next()
|
||||
})
|
||||
.use(useError)
|
||||
.use(useStore)
|
||||
|
||||
export type SSRContext = GetServerSidePropsContext & {
|
||||
req: ApiRequest
|
||||
res: ApiResponse
|
||||
}
|
||||
|
||||
export type SSRMiddeware = Middleware<ApiRequest, ApiResponse>
|
@ -1,7 +1,6 @@
|
||||
import { getEnv } from 'libs/shared/env'
|
||||
import { PageMode } from 'libs/shared/page'
|
||||
import { GetServerSidePropsContext } from 'next'
|
||||
import { ApiRequest, ApiResponse, ApiNext } from '../api'
|
||||
import { ApiRequest, ApiResponse, ApiNext, SSRMiddeware } from '../connect'
|
||||
|
||||
export async function useAuth(
|
||||
req: ApiRequest,
|
||||
@ -15,37 +14,41 @@ export async function useAuth(
|
||||
return next()
|
||||
}
|
||||
|
||||
export function withAuth(wrapperHandler: any) {
|
||||
return async function handler(
|
||||
ctx: GetServerSidePropsContext & {
|
||||
req: ApiRequest
|
||||
}
|
||||
) {
|
||||
const redirectLogin = {
|
||||
redirect: {
|
||||
destination: `/login?redirect=${ctx.resolvedUrl}`,
|
||||
permanent: false,
|
||||
},
|
||||
}
|
||||
|
||||
const res = await wrapperHandler(ctx)
|
||||
|
||||
if (res.props?.pageMode !== PageMode.PUBLIC && !isLoggedIn(ctx.req)) {
|
||||
return redirectLogin
|
||||
}
|
||||
|
||||
res.props = {
|
||||
...res.props,
|
||||
}
|
||||
|
||||
return res
|
||||
}
|
||||
}
|
||||
|
||||
export function isLoggedIn(req: ApiRequest) {
|
||||
if (getEnv('IS_DEMO') || getEnv('DISABLE_PASSWORD', false)) {
|
||||
return true
|
||||
}
|
||||
|
||||
return req.session.get('user')?.isLoggedIn
|
||||
return !!req.session.get('user')?.isLoggedIn
|
||||
}
|
||||
|
||||
export const applyAuth: SSRMiddeware = async (req, _res, next) => {
|
||||
req.props = {
|
||||
...req.props,
|
||||
isLoggedIn: isLoggedIn(req),
|
||||
disablePassword: getEnv('IS_DEMO') || getEnv('DISABLE_PASSWORD', false),
|
||||
}
|
||||
|
||||
next()
|
||||
}
|
||||
|
||||
export const applyRedirectLogin: (resolvedUrl: string) => SSRMiddeware = (
|
||||
resolvedUrl: string
|
||||
) => async (req, _res, next) => {
|
||||
const redirect = {
|
||||
destination: `/login?redirect=${resolvedUrl}`,
|
||||
permanent: false,
|
||||
}
|
||||
|
||||
// note 存在的情况
|
||||
if (req.props.pageMode) {
|
||||
if (req.props.pageMode !== PageMode.PUBLIC && !req.props.isLoggedIn) {
|
||||
req.redirect = redirect
|
||||
}
|
||||
// 访问首页没有 note,则判断是否登录
|
||||
} else if (!req.props.isLoggedIn) {
|
||||
req.redirect = redirect
|
||||
}
|
||||
|
||||
next()
|
||||
}
|
||||
|
@ -1,10 +1,8 @@
|
||||
import Tokens from 'csrf'
|
||||
import { CRSF_HEADER_KEY } from 'libs/shared/crsf'
|
||||
import { CRSF_HEADER_KEY } from 'libs/shared/const'
|
||||
import { getEnv } from 'libs/shared/env'
|
||||
import { PageMode } from 'libs/shared/page'
|
||||
import md5 from 'md5'
|
||||
import { GetServerSidePropsContext } from 'next'
|
||||
import { ApiNext, ApiRequest, ApiResponse } from '../api'
|
||||
import { ApiNext, ApiRequest, ApiResponse, SSRMiddeware } from '../connect'
|
||||
|
||||
const tokens = new Tokens()
|
||||
|
||||
@ -16,42 +14,32 @@ export const getCsrfToken = () => tokens.create(csrfSecret)
|
||||
export const verifyCsrfToken = (token: string) =>
|
||||
tokens.verify(csrfSecret, token)
|
||||
|
||||
export function withCsrf(wrapperHandler: any) {
|
||||
return async function handler(
|
||||
ctx: GetServerSidePropsContext & {
|
||||
req: ApiRequest
|
||||
}
|
||||
) {
|
||||
const res = await wrapperHandler(ctx)
|
||||
let csrfToken
|
||||
|
||||
if (res.redirect) {
|
||||
return res
|
||||
}
|
||||
|
||||
if (res.pageMode !== PageMode.PUBLIC) {
|
||||
csrfToken = getCsrfToken()
|
||||
}
|
||||
|
||||
res.props = {
|
||||
...res.props,
|
||||
csrfToken,
|
||||
}
|
||||
|
||||
return res
|
||||
export const applyCsrf: SSRMiddeware = async (req, _res, next) => {
|
||||
req.props = {
|
||||
...req.props,
|
||||
csrfToken: getCsrfToken(),
|
||||
}
|
||||
req.session.set(CRSF_HEADER_KEY, req.props.csrfToken)
|
||||
await req.session.save()
|
||||
next()
|
||||
}
|
||||
|
||||
const ignoredMethods = ['GET', 'HEAD', 'OPTIONS']
|
||||
|
||||
export function useCsrf(req: ApiRequest, res: ApiResponse, next: ApiNext) {
|
||||
const token = req.headers[CRSF_HEADER_KEY] as string
|
||||
const sessionToken = req.session.get(CRSF_HEADER_KEY)
|
||||
|
||||
if (ignoredMethods.includes(req.method?.toLocaleUpperCase() as string)) {
|
||||
return next()
|
||||
}
|
||||
|
||||
if (token && verifyCsrfToken(token)) {
|
||||
if (
|
||||
token &&
|
||||
sessionToken &&
|
||||
token === sessionToken &&
|
||||
verifyCsrfToken(token)
|
||||
) {
|
||||
next()
|
||||
} else {
|
||||
return res.APIError.INVALID_CSRF_TOKEN.throw()
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { mapValues } from 'lodash'
|
||||
import { ApiRequest, ApiResponse, ApiNext } from '../api'
|
||||
import { ApiRequest, ApiResponse, ApiNext } from '../connect'
|
||||
|
||||
export const API_ERROR: {
|
||||
[key: string]: {
|
||||
@ -74,8 +74,7 @@ export const API = mapValues(
|
||||
export async function onError(
|
||||
err: Error & APIError,
|
||||
_req: ApiRequest,
|
||||
res: ApiResponse,
|
||||
_next: ApiNext
|
||||
res: ApiResponse
|
||||
) {
|
||||
const e = {
|
||||
name: err.name || 'UNKNOWN_ERR',
|
||||
@ -88,7 +87,7 @@ export async function onError(
|
||||
stack: err.stack,
|
||||
})
|
||||
|
||||
res.status(e.status).json(e)
|
||||
res.status?.(e.status).json?.(e)
|
||||
}
|
||||
|
||||
export async function useError(
|
||||
|
@ -1,45 +1,42 @@
|
||||
import { PageMode } from 'libs/shared/page'
|
||||
import { NOTE_SHARED } from 'libs/shared/meta'
|
||||
import { GetServerSidePropsContext } from 'next'
|
||||
import { getNote } from 'pages/api/notes/[id]'
|
||||
import { ApiRequest } from '../api'
|
||||
import { NoteModel } from 'libs/web/state/note'
|
||||
import { SSRMiddeware } from '../connect'
|
||||
import { NoteModel } from 'libs/shared/note'
|
||||
import { getEnv } from 'libs/shared/env'
|
||||
import { isLoggedIn } from './auth'
|
||||
|
||||
const RESERVED_ROUTES = ['new', 'settings', 'login']
|
||||
|
||||
export function withNote(wrapperHandler: any) {
|
||||
return async function handler(
|
||||
ctx: GetServerSidePropsContext & {
|
||||
req: ApiRequest
|
||||
}
|
||||
) {
|
||||
const res = await wrapperHandler(ctx)
|
||||
const id = ctx.query.id as string
|
||||
const props: { note?: NoteModel; pageMode: PageMode } = {
|
||||
pageMode: PageMode.NOTE,
|
||||
}
|
||||
|
||||
// todo 页面不存在时应该跳转到新建页
|
||||
if (!RESERVED_ROUTES.includes(id)) {
|
||||
try {
|
||||
props.note = await getNote(ctx.req.store, id)
|
||||
} catch (e) {
|
||||
// do nothing
|
||||
}
|
||||
}
|
||||
|
||||
if (props.note?.shared === NOTE_SHARED.PUBLIC && !isLoggedIn(ctx.req)) {
|
||||
props.pageMode = PageMode.PUBLIC
|
||||
}
|
||||
|
||||
res.props = {
|
||||
...res.props,
|
||||
...props,
|
||||
baseURL: getEnv('BASE_URL', '//' + ctx.req.headers.host),
|
||||
}
|
||||
|
||||
return res
|
||||
export const applyNote: (id: string) => SSRMiddeware = (id: string) => async (
|
||||
req,
|
||||
_res,
|
||||
next
|
||||
) => {
|
||||
const props: {
|
||||
note?: NoteModel
|
||||
pageMode: PageMode
|
||||
} = {
|
||||
pageMode: PageMode.NOTE,
|
||||
}
|
||||
|
||||
// todo 页面不存在时应该跳转到新建页
|
||||
if (!RESERVED_ROUTES.includes(id)) {
|
||||
try {
|
||||
props.note = await getNote(req.state.store, id)
|
||||
} catch (e) {
|
||||
// do nothing
|
||||
}
|
||||
}
|
||||
|
||||
if (props.note?.shared === NOTE_SHARED.PUBLIC) {
|
||||
props.pageMode = PageMode.PUBLIC
|
||||
}
|
||||
|
||||
req.props = {
|
||||
...req.props,
|
||||
...props,
|
||||
baseURL: getEnv('BASE_URL', '//' + req.headers.host),
|
||||
}
|
||||
|
||||
next()
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { ironSession, withIronSession, Handler } from 'next-iron-session'
|
||||
import { ironSession} from 'next-iron-session'
|
||||
import md5 from 'md5'
|
||||
import { getEnv } from 'libs/shared/env'
|
||||
|
||||
@ -15,5 +15,3 @@ const sessionOptions = {
|
||||
}
|
||||
|
||||
export const useSession = ironSession(sessionOptions)
|
||||
export const withSession = (handler: Handler) =>
|
||||
withIronSession(handler, sessionOptions)
|
||||
|
@ -1,36 +1,18 @@
|
||||
import { PageMode } from 'libs/shared/page'
|
||||
import { GetServerSidePropsContext } from 'next'
|
||||
import { DEFAULT_SETTINGS } from 'libs/shared/settings'
|
||||
import { getSettings } from 'pages/api/settings'
|
||||
import { ApiRequest } from '../api'
|
||||
import { SSRMiddeware } from '../connect'
|
||||
|
||||
export function withSettings(wrapperHandler: any) {
|
||||
return async function handler(
|
||||
ctx: GetServerSidePropsContext & {
|
||||
req: ApiRequest
|
||||
}
|
||||
) {
|
||||
const res = await wrapperHandler(ctx)
|
||||
let settings
|
||||
export const applySettings: SSRMiddeware = async (req, _res, next) => {
|
||||
const settings = await getSettings(req.state.store)
|
||||
|
||||
if (res.redirect) {
|
||||
return res
|
||||
}
|
||||
// import language dict
|
||||
const { default: lngDict = {} } = await import(
|
||||
`locales/${settings?.locale || DEFAULT_SETTINGS.locale}.json`
|
||||
)
|
||||
|
||||
if (res.pageMode !== PageMode.PUBLIC) {
|
||||
settings = await getSettings(ctx.req.store)
|
||||
}
|
||||
|
||||
// import language dict
|
||||
const { default: lngDict = {} } = await import(
|
||||
`locales/${settings?.locale}.json`
|
||||
)
|
||||
|
||||
res.props = {
|
||||
...res.props,
|
||||
settings,
|
||||
lngDict,
|
||||
}
|
||||
|
||||
return res
|
||||
req.props = {
|
||||
...req.props,
|
||||
...{ settings, lngDict },
|
||||
}
|
||||
next()
|
||||
}
|
||||
|
@ -1,27 +1,19 @@
|
||||
import { createStore } from 'libs/server/store'
|
||||
import { ApiRequest, ApiResponse, ApiNext } from '../api'
|
||||
import { GetServerSidePropsContext } from 'next'
|
||||
import { ApiRequest, SSRMiddeware } from '../connect'
|
||||
import TreeStore from 'libs/server/tree'
|
||||
|
||||
export function useStore(req: ApiRequest, _res: ApiResponse, next: ApiNext) {
|
||||
export const useStore: SSRMiddeware = async (req, _res, next) => {
|
||||
applyStore(req)
|
||||
|
||||
return next()
|
||||
}
|
||||
|
||||
function applyStore(req: ApiRequest) {
|
||||
req.store = createStore()
|
||||
req.treeStore = new TreeStore(req.store)
|
||||
}
|
||||
export function applyStore(req: ApiRequest) {
|
||||
const store = createStore()
|
||||
|
||||
export function withStore(wrapperHandler: any) {
|
||||
return async function handler(
|
||||
ctx: GetServerSidePropsContext & {
|
||||
req: ApiRequest
|
||||
}
|
||||
) {
|
||||
applyStore(ctx.req)
|
||||
|
||||
return wrapperHandler(ctx)
|
||||
req.state = {
|
||||
...req.state,
|
||||
store,
|
||||
treeStore: new TreeStore(store),
|
||||
}
|
||||
}
|
||||
|
@ -1,41 +1,27 @@
|
||||
import { PageMode } from 'libs/shared/page'
|
||||
import { GetServerSidePropsContext } from 'next'
|
||||
import { ApiRequest } from '../api'
|
||||
import { SSRMiddeware } from '../connect'
|
||||
import { API } from './error'
|
||||
|
||||
// @atlaskit/tree 的依赖
|
||||
const { resetServerContext } = require('react-beautiful-dnd-next')
|
||||
|
||||
export function withTree(wrapperHandler: any) {
|
||||
return async function handler(
|
||||
ctx: GetServerSidePropsContext & {
|
||||
req: ApiRequest
|
||||
export const applyTree: SSRMiddeware = async (req, _res, next) => {
|
||||
resetServerContext()
|
||||
|
||||
let tree
|
||||
|
||||
// todo 分享页面获取指定树结构
|
||||
if (req.props.isLoggedIn) {
|
||||
try {
|
||||
tree = await req.state.treeStore.get()
|
||||
} catch (error) {
|
||||
return API.NOT_FOUND.throw(error.message)
|
||||
}
|
||||
) {
|
||||
const res = await wrapperHandler(ctx)
|
||||
|
||||
resetServerContext()
|
||||
|
||||
let tree
|
||||
|
||||
if (res.redirect) {
|
||||
return res
|
||||
}
|
||||
|
||||
// todo 分享页面获取指定树结构
|
||||
if (res.pageMode !== PageMode.PUBLIC) {
|
||||
try {
|
||||
tree = await ctx.req.treeStore.get()
|
||||
} catch (error) {
|
||||
return API.NOT_FOUND.throw(error.message)
|
||||
}
|
||||
}
|
||||
|
||||
res.props = {
|
||||
...res.props,
|
||||
tree,
|
||||
}
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
req.props = {
|
||||
...req.props,
|
||||
...(tree && { tree }),
|
||||
}
|
||||
|
||||
next()
|
||||
}
|
||||
|
@ -1,23 +1,19 @@
|
||||
import { UserAgentType } from 'libs/web/state/ui/ua'
|
||||
import { GetServerSidePropsContext } from 'next'
|
||||
import { UserAgentType } from 'libs/shared/ua'
|
||||
import UAParser from 'ua-parser-js'
|
||||
import { SSRMiddeware } from '../connect'
|
||||
|
||||
export function withUA(wrapperHandler: any) {
|
||||
return async function handler(ctx: GetServerSidePropsContext) {
|
||||
const res = await wrapperHandler(ctx)
|
||||
const ua = new UAParser(ctx.req.headers['user-agent']).getResult()
|
||||
export const applyUA: SSRMiddeware = (req, _res, next) => {
|
||||
const ua = new UAParser(req.headers['user-agent']).getResult()
|
||||
|
||||
res.props = {
|
||||
...res.props,
|
||||
ua: {
|
||||
isMobile: ['mobile', 'tablet'].includes(ua.device.type || ''),
|
||||
isMobileOnly: ua.device.type === 'mobile',
|
||||
isTablet: ua.device.type === 'tablet',
|
||||
isBrowser: !ua.device.type,
|
||||
isWechat: ua.browser.name?.toLocaleLowerCase() === 'wechat',
|
||||
} as UserAgentType,
|
||||
}
|
||||
|
||||
return res
|
||||
req.props = {
|
||||
...req.props,
|
||||
ua: {
|
||||
isMobile: ['mobile', 'tablet'].includes(ua.device.type || ''),
|
||||
isMobileOnly: ua.device.type === 'mobile',
|
||||
isTablet: ua.device.type === 'tablet',
|
||||
isBrowser: !ua.device.type,
|
||||
isWechat: ua.browser.name?.toLocaleLowerCase() === 'wechat',
|
||||
} as UserAgentType,
|
||||
}
|
||||
next()
|
||||
}
|
||||
|
12
libs/shared/note.ts
Normal file
12
libs/shared/note.ts
Normal file
@ -0,0 +1,12 @@
|
||||
import { NOTE_DELETED, NOTE_SHARED } from './meta'
|
||||
|
||||
export interface NoteModel {
|
||||
id: string
|
||||
title: string
|
||||
pid?: string
|
||||
content?: string
|
||||
pic?: string
|
||||
date?: string
|
||||
deleted: NOTE_DELETED
|
||||
shared: NOTE_SHARED
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
import { moveItemOnTree, mutateTree, TreeData, TreeItem } from '@atlaskit/tree'
|
||||
import { NoteModel } from 'libs/web/state/note'
|
||||
import { NoteModel } from 'libs/shared/note'
|
||||
import { cloneDeep, forEach, pull, reduce, union } from 'lodash'
|
||||
|
||||
export interface TreeItemModel extends TreeItem {
|
||||
|
7
libs/shared/ua.ts
Normal file
7
libs/shared/ua.ts
Normal file
@ -0,0 +1,7 @@
|
||||
export interface UserAgentType {
|
||||
isMobile: boolean
|
||||
isMobileOnly: boolean
|
||||
isTablet: boolean
|
||||
isBrowser: boolean
|
||||
isWechat: boolean
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
import { CRSF_HEADER_KEY } from 'libs/shared/crsf'
|
||||
import { CRSF_HEADER_KEY } from 'libs/shared/const'
|
||||
import { useCallback, useRef, useState } from 'react'
|
||||
import CsrfTokenState from '../state/csrf-token'
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { NoteModel } from 'libs/web/state/note'
|
||||
import { NoteModel } from 'libs/shared/note'
|
||||
import { useCallback } from 'react'
|
||||
import noteCache from '../cache/note'
|
||||
import useFetcher from './fetcher'
|
||||
|
2
libs/web/cache/index.ts
vendored
2
libs/web/cache/index.ts
vendored
@ -1,4 +1,4 @@
|
||||
import { NoteModel } from 'libs/web/state/note'
|
||||
import { NoteModel } from 'libs/shared/note'
|
||||
import localforage from 'localforage'
|
||||
|
||||
export const uiCache = localforage.createInstance({
|
||||
|
2
libs/web/cache/note.ts
vendored
2
libs/web/cache/note.ts
vendored
@ -1,6 +1,6 @@
|
||||
import { TreeModel } from 'libs/shared/tree'
|
||||
import { noteCacheInstance, NoteCacheItem } from 'libs/web/cache'
|
||||
import { NoteModel } from 'libs/web/state/note'
|
||||
import { NoteModel } from 'libs/shared/note'
|
||||
import { keys, pull } from 'lodash'
|
||||
import { removeMarkdown } from '../utils/markdown'
|
||||
|
||||
|
@ -4,17 +4,7 @@ import NoteTreeState from 'libs/web/state/tree'
|
||||
import { NOTE_DELETED, NOTE_SHARED } from 'libs/shared/meta'
|
||||
import useNoteAPI from '../api/note'
|
||||
import noteCache from '../cache/note'
|
||||
|
||||
export interface NoteModel {
|
||||
id: string
|
||||
title: string
|
||||
pid?: string
|
||||
content?: string
|
||||
pic?: string
|
||||
date?: string
|
||||
deleted: NOTE_DELETED
|
||||
shared: NOTE_SHARED
|
||||
}
|
||||
import { NoteModel } from 'libs/shared/note'
|
||||
|
||||
const useNote = (initData?: NoteModel) => {
|
||||
const [note, setNote] = useState<NoteModel | undefined>(initData)
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { NoteModel } from 'libs/shared/note'
|
||||
import { useState, useCallback, MouseEvent } from 'react'
|
||||
import { createContainer } from 'unstated-next'
|
||||
import { NoteModel } from './note'
|
||||
|
||||
const useModalIntance = () => {
|
||||
const [visible, setVisible] = useState(false)
|
||||
|
@ -1,12 +1,12 @@
|
||||
import { useState, useCallback } from 'react'
|
||||
import { createContainer } from 'unstated-next'
|
||||
import NoteTreeState from './tree'
|
||||
import { NoteModel } from './note'
|
||||
import useTrashAPI from '../api/trash'
|
||||
import useTrashAPI from '../api/trash'
|
||||
import noteCache from '../cache/note'
|
||||
import { NOTE_DELETED } from 'libs/shared/meta'
|
||||
import { NoteCacheItem } from '../cache'
|
||||
import { searchNote } from '../utils/search'
|
||||
import { NoteModel } from 'libs/shared/note'
|
||||
|
||||
function useTrash() {
|
||||
const [keyword, setKeyword] = useState<string>()
|
||||
|
@ -2,7 +2,6 @@ import { cloneDeep, isEmpty, map } from 'lodash'
|
||||
import { genId } from 'libs/shared/id'
|
||||
import { useCallback, useEffect, useRef, useState } from 'react'
|
||||
import { createContainer } from 'unstated-next'
|
||||
import { NoteModel } from './note'
|
||||
import TreeActions, {
|
||||
DEFAULT_TREE,
|
||||
movePosition,
|
||||
@ -13,6 +12,7 @@ import useNoteAPI from '../api/note'
|
||||
import noteCache from '../cache/note'
|
||||
import useTreeAPI from '../api/tree'
|
||||
import { NOTE_DELETED } from 'libs/shared/meta'
|
||||
import { NoteModel } from 'libs/shared/note'
|
||||
|
||||
const useNoteTree = (initData: TreeModel = DEFAULT_TREE) => {
|
||||
const { mutate, loading } = useTreeAPI()
|
||||
|
@ -1,22 +1,32 @@
|
||||
import { Settings } from 'libs/shared/settings'
|
||||
import { UserAgentType } from 'libs/shared/ua'
|
||||
import { createContainer } from 'unstated-next'
|
||||
import useSettings from './settings'
|
||||
import useSidebar from './sidebar'
|
||||
import useSplit from './split'
|
||||
import useTitle from './title'
|
||||
import useUA, { UserAgentType } from './ua'
|
||||
|
||||
const DEFAULT_UA: UserAgentType = {
|
||||
isMobile: false,
|
||||
isMobileOnly: false,
|
||||
isTablet: false,
|
||||
isBrowser: true,
|
||||
isWechat: false,
|
||||
}
|
||||
|
||||
interface Props {
|
||||
ua?: UserAgentType
|
||||
settings?: Settings
|
||||
disablePassword?: boolean
|
||||
}
|
||||
function useUI({ ua, settings }: Props = {}) {
|
||||
function useUI({ ua = DEFAULT_UA, settings, disablePassword }: Props = {}) {
|
||||
return {
|
||||
ua: useUA(ua),
|
||||
ua,
|
||||
sidebar: useSidebar(ua?.isMobileOnly ? false : settings?.sidebar_is_fold),
|
||||
split: useSplit(settings?.split_sizes),
|
||||
title: useTitle(),
|
||||
settings: useSettings(settings),
|
||||
disablePassword,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,23 +0,0 @@
|
||||
import { useState } from 'react'
|
||||
|
||||
export interface UserAgentType {
|
||||
isMobile: boolean
|
||||
isMobileOnly: boolean
|
||||
isTablet: boolean
|
||||
isBrowser: boolean
|
||||
isWechat: boolean
|
||||
}
|
||||
|
||||
export default function useUA(
|
||||
initState: UserAgentType = {
|
||||
isMobile: false,
|
||||
isMobileOnly: false,
|
||||
isTablet: false,
|
||||
isBrowser: true,
|
||||
isWechat: false,
|
||||
}
|
||||
) {
|
||||
const [ua] = useState(initState)
|
||||
|
||||
return ua
|
||||
}
|
@ -30,5 +30,7 @@
|
||||
"Sync with system": "mit System synchronisieren",
|
||||
"Dark": "Dunkel",
|
||||
"Light": "Hell",
|
||||
"Current version": "aktuelle Version"
|
||||
"Current version": "aktuelle Version",
|
||||
"Not a public page": "Not a public page"
|
||||
|
||||
}
|
||||
|
@ -30,5 +30,6 @@
|
||||
"Sync with system": "Sync with system",
|
||||
"Dark": "Dark",
|
||||
"Light": "Light",
|
||||
"Current version": "Current version"
|
||||
"Current version": "Current version",
|
||||
"Not a public page": "Not a public page"
|
||||
}
|
||||
|
@ -30,5 +30,6 @@
|
||||
"Sync with system": "Как в системе",
|
||||
"Dark": "Темная",
|
||||
"Light": "Светлая",
|
||||
"Current version": "Текущая версия"
|
||||
"Current version": "Текущая версия",
|
||||
"Not a public page": "Not a public page"
|
||||
}
|
@ -30,5 +30,6 @@
|
||||
"Sync with system": "自动",
|
||||
"Dark": "深色",
|
||||
"Light": "浅色",
|
||||
"Current version": "当前版本"
|
||||
"Current version": "当前版本",
|
||||
"Not a public page": "非公开页面"
|
||||
}
|
||||
|
@ -1,44 +1,61 @@
|
||||
import { NoteModel } from 'libs/web/state/note'
|
||||
import LayoutMain from 'components/layout/layout-main'
|
||||
import { GetServerSideProps, NextPage } from 'next'
|
||||
import { withTree } from 'libs/server/middlewares/tree'
|
||||
import { withUA } from 'libs/server/middlewares/ua'
|
||||
import { TreeModel } from 'libs/shared/tree'
|
||||
import { withSession } from 'libs/server/middlewares/session'
|
||||
import { withStore } from 'libs/server/middlewares/store'
|
||||
import { withSettings } from 'libs/server/middlewares/settings'
|
||||
import { withAuth } from 'libs/server/middlewares/auth'
|
||||
import { withNote } from 'libs/server/middlewares/note'
|
||||
import { applyTree } from 'libs/server/middlewares/tree'
|
||||
import { useSession } from 'libs/server/middlewares/session'
|
||||
import { applySettings } from 'libs/server/middlewares/settings'
|
||||
import { applyAuth, applyRedirectLogin } from 'libs/server/middlewares/auth'
|
||||
import { applyNote } from 'libs/server/middlewares/note'
|
||||
import LayoutPublic from 'components/layout/layout-public'
|
||||
import { PageMode } from 'libs/shared/page'
|
||||
import { EditContainer } from 'components/container/edit-container'
|
||||
import { PostContainer } from 'components/container/post-container'
|
||||
import { withCsrf } from 'libs/server/middlewares/csrf'
|
||||
import { applyCsrf } from 'libs/server/middlewares/csrf'
|
||||
import { ssr, SSRContext, ServerProps } from 'libs/server/connect'
|
||||
import { applyUA } from 'libs/server/middlewares/ua'
|
||||
|
||||
const EditNotePage: NextPage<{
|
||||
tree: TreeModel
|
||||
note?: NoteModel
|
||||
pageMode: PageMode
|
||||
baseURL: string
|
||||
}> = ({ tree, note, pageMode, baseURL }) => {
|
||||
if (PageMode.PUBLIC === pageMode) {
|
||||
export default function EditNotePage({
|
||||
tree,
|
||||
note,
|
||||
pageMode,
|
||||
baseURL,
|
||||
isLoggedIn,
|
||||
}: ServerProps) {
|
||||
if (isLoggedIn) {
|
||||
return (
|
||||
<LayoutPublic tree={tree} note={note}>
|
||||
<PostContainer baseURL={baseURL} />
|
||||
</LayoutPublic>
|
||||
<LayoutMain tree={tree} note={note}>
|
||||
<EditContainer />
|
||||
</LayoutMain>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<LayoutMain tree={tree} note={note}>
|
||||
<EditContainer />
|
||||
</LayoutMain>
|
||||
<LayoutPublic tree={tree} note={note}>
|
||||
<PostContainer pageMode={pageMode} baseURL={baseURL} />
|
||||
</LayoutPublic>
|
||||
)
|
||||
}
|
||||
|
||||
export default EditNotePage
|
||||
export const getServerSideProps = async (
|
||||
ctx: SSRContext & {
|
||||
query: {
|
||||
id: string
|
||||
}
|
||||
}
|
||||
) => {
|
||||
if (!/^[A-Za-z0-9_-]+$/.test(ctx.query.id)) {
|
||||
return { props: {} }
|
||||
}
|
||||
await ssr()
|
||||
.use(useSession)
|
||||
.use(applyAuth)
|
||||
.use(applyNote(ctx.query.id))
|
||||
.use(applyRedirectLogin(ctx.resolvedUrl))
|
||||
.use(applyTree)
|
||||
.use(applySettings)
|
||||
.use(applyCsrf)
|
||||
.use(applyUA)
|
||||
.run(ctx.req, ctx.res)
|
||||
|
||||
export const getServerSideProps: GetServerSideProps = withUA(
|
||||
withSession(
|
||||
withStore(withAuth(withNote(withTree(withSettings(withCsrf(() => ({})))))))
|
||||
)
|
||||
)
|
||||
return {
|
||||
props: ctx.req.props,
|
||||
redirect: ctx.req.redirect,
|
||||
}
|
||||
}
|
||||
|
@ -15,6 +15,7 @@ import { Settings } from 'libs/shared/settings'
|
||||
import I18nProvider from 'libs/web/utils/i18n-provider'
|
||||
import CsrfTokenState from 'libs/web/state/csrf-token'
|
||||
import { muiLocale } from 'locales'
|
||||
import { ServerProps } from 'libs/server/connect'
|
||||
|
||||
const handleRejection = (event: any) => {
|
||||
// react-beautiful-dnd 会捕获到 `ResizeObserver loop limit exceeded`
|
||||
@ -49,7 +50,13 @@ function DocumentHead() {
|
||||
)
|
||||
}
|
||||
|
||||
const AppInner = ({ Component, pageProps }: AppProps) => {
|
||||
const AppInner = ({
|
||||
Component,
|
||||
pageProps,
|
||||
}: {
|
||||
pageProps: ServerProps
|
||||
Component: any
|
||||
}) => {
|
||||
const { resolvedTheme } = useTheme()
|
||||
const settings = pageProps?.settings as Settings
|
||||
const muiTheme = useMemo(
|
||||
@ -94,6 +101,7 @@ const AppInner = ({ Component, pageProps }: AppProps) => {
|
||||
initialState={{
|
||||
ua: pageProps?.ua,
|
||||
settings,
|
||||
disablePassword: pageProps?.disablePassword,
|
||||
}}
|
||||
>
|
||||
<PortalState.Provider>
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { api } from 'libs/server/api'
|
||||
import { api } from 'libs/server/connect'
|
||||
import { getEnv } from 'libs/shared/env'
|
||||
|
||||
export default api().post(async (req, res) => {
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { api } from 'libs/server/api'
|
||||
import { api } from 'libs/server/connect'
|
||||
import { useAuth } from 'libs/server/middlewares/auth'
|
||||
|
||||
export default api()
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { api } from 'libs/server/api'
|
||||
import { api } from 'libs/server/connect'
|
||||
import { useStore } from 'libs/server/middlewares/store'
|
||||
import { getPathFileByName } from 'libs/server/note-path'
|
||||
|
||||
@ -15,7 +15,7 @@ export default api()
|
||||
.use(useStore)
|
||||
.get(async (req, res) => {
|
||||
if (req.query.file) {
|
||||
const signUrl = await req.store.getSignUrl(
|
||||
const signUrl = await req.state.store.getSignUrl(
|
||||
getPathFileByName((req.query.file as string[]).join('/')),
|
||||
expires
|
||||
)
|
||||
|
@ -1,9 +1,9 @@
|
||||
import { api } from 'libs/server/api'
|
||||
import { api } from 'libs/server/connect'
|
||||
import { metaToJson } from 'libs/server/meta'
|
||||
import { useAuth } from 'libs/server/middlewares/auth'
|
||||
import { useStore } from 'libs/server/middlewares/store'
|
||||
import { getPathNoteById } from 'libs/server/note-path'
|
||||
import { NoteModel } from 'libs/web/state/note'
|
||||
import { NoteModel } from 'libs/shared/note'
|
||||
import { StoreProvider } from 'libs/server/store'
|
||||
import { API } from 'libs/server/middlewares/error'
|
||||
import { strCompress } from 'libs/shared/str'
|
||||
@ -35,8 +35,8 @@ export default api()
|
||||
const notePath = getPathNoteById(id)
|
||||
|
||||
await Promise.all([
|
||||
req.store.deleteObject(notePath),
|
||||
req.treeStore.removeItem(id),
|
||||
req.state.store.deleteObject(notePath),
|
||||
req.state.treeStore.removeItem(id),
|
||||
])
|
||||
|
||||
res.end()
|
||||
@ -50,7 +50,7 @@ export default api()
|
||||
})
|
||||
}
|
||||
|
||||
const note = await getNote(req.store, id)
|
||||
const note = await getNote(req.state.store, id)
|
||||
|
||||
res.json(note)
|
||||
})
|
||||
@ -58,13 +58,13 @@ export default api()
|
||||
const id = req.query.id as string
|
||||
const { content } = req.body
|
||||
const notePath = getPathNoteById(id)
|
||||
const oldMeta = await req.store.getObjectMeta(notePath)
|
||||
const oldMeta = await req.state.store.getObjectMeta(notePath)
|
||||
|
||||
if (oldMeta) {
|
||||
oldMeta['date'] = strCompress(new Date().toISOString())
|
||||
}
|
||||
|
||||
await req.store.putObject(notePath, content, {
|
||||
await req.state.store.putObject(notePath, content, {
|
||||
contentType: 'text/markdown',
|
||||
meta: oldMeta,
|
||||
})
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { api } from 'libs/server/api'
|
||||
import { api } from 'libs/server/connect'
|
||||
import { jsonToMeta, metaToJson } from 'libs/server/meta'
|
||||
import { useAuth } from 'libs/server/middlewares/auth'
|
||||
import { useStore } from 'libs/server/middlewares/store'
|
||||
@ -11,7 +11,7 @@ export default api()
|
||||
.post(async (req, res) => {
|
||||
const id = req.body.id || req.query.id
|
||||
const notePath = getPathNoteById(id)
|
||||
const oldMeta = await req.store.getObjectMeta(notePath)
|
||||
const oldMeta = await req.state.store.getObjectMeta(notePath)
|
||||
const oldMetaJson = metaToJson(oldMeta)
|
||||
let meta = jsonToMeta({
|
||||
...req.body,
|
||||
@ -24,11 +24,11 @@ export default api()
|
||||
// 处理删除情况
|
||||
const { deleted } = req.body
|
||||
if (oldMetaJson.deleted !== deleted && deleted === NOTE_DELETED.DELETED) {
|
||||
await req.treeStore.removeItem(id)
|
||||
await req.state.treeStore.removeItem(id)
|
||||
}
|
||||
}
|
||||
|
||||
await req.store.copyObject(notePath, notePath, {
|
||||
await req.state.store.copyObject(notePath, notePath, {
|
||||
meta,
|
||||
contentType: 'text/markdown',
|
||||
})
|
||||
@ -38,7 +38,7 @@ export default api()
|
||||
.get(async (req, res) => {
|
||||
const id = req.body.id || req.query.id
|
||||
const notePath = getPathNoteById(id)
|
||||
const meta = await req.store.getObjectMeta(notePath)
|
||||
const meta = await req.state.store.getObjectMeta(notePath)
|
||||
|
||||
res.json(metaToJson(meta))
|
||||
})
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { genId } from 'libs/shared/id'
|
||||
import { api } from 'libs/server/api'
|
||||
import { api } from 'libs/server/connect'
|
||||
import { jsonToMeta } from 'libs/server/meta'
|
||||
import { useAuth } from 'libs/server/middlewares/auth'
|
||||
import { useStore } from 'libs/server/middlewares/store'
|
||||
@ -14,7 +14,7 @@ export default api()
|
||||
|
||||
if (!id) {
|
||||
id = genId()
|
||||
while (await req.store.hasObject(getPathNoteById(id))) {
|
||||
while (await req.state.store.hasObject(getPathNoteById(id))) {
|
||||
id = genId()
|
||||
}
|
||||
}
|
||||
@ -26,11 +26,11 @@ export default api()
|
||||
}
|
||||
const metaData = jsonToMeta(metaWithModel)
|
||||
|
||||
await req.store.putObject(getPathNoteById(id), content, {
|
||||
await req.state.store.putObject(getPathNoteById(id), content, {
|
||||
contentType: 'text/markdown',
|
||||
meta: metaData,
|
||||
})
|
||||
await req.treeStore.addItem(id, meta.pid)
|
||||
await req.state.treeStore.addItem(id, meta.pid)
|
||||
|
||||
res.json(metaWithModel)
|
||||
})
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { api } from 'libs/server/api'
|
||||
import { api } from 'libs/server/connect'
|
||||
import { useAuth } from 'libs/server/middlewares/auth'
|
||||
import { useStore } from 'libs/server/middlewares/store'
|
||||
import { getPathSettings } from 'libs/server/note-path'
|
||||
@ -23,18 +23,18 @@ export default api()
|
||||
.use(useStore)
|
||||
.post(async (req, res) => {
|
||||
const { body } = req
|
||||
const prev = await getSettings(req.store)
|
||||
const prev = await getSettings(req.state.store)
|
||||
const settings = formatSettings({
|
||||
...prev,
|
||||
...body,
|
||||
})
|
||||
|
||||
await req.store.putObject(getPathSettings(), JSON.stringify(settings))
|
||||
await req.state.store.putObject(getPathSettings(), JSON.stringify(settings))
|
||||
res.status(204).end()
|
||||
})
|
||||
.get(
|
||||
async (req, res): Promise<void> => {
|
||||
const settings = await getSettings(req.store)
|
||||
const settings = await getSettings(req.state.store)
|
||||
|
||||
res.json(settings)
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { api, ApiRequest } from 'libs/server/api'
|
||||
import { api, ApiRequest } from 'libs/server/connect'
|
||||
import { jsonToMeta } from 'libs/server/meta'
|
||||
import { useAuth } from 'libs/server/middlewares/auth'
|
||||
import { useStore } from 'libs/server/middlewares/store'
|
||||
@ -36,13 +36,13 @@ export default api()
|
||||
async function deleteNote(req: ApiRequest, id: string) {
|
||||
const notePath = getPathNoteById(id)
|
||||
|
||||
await req.store.deleteObject(notePath)
|
||||
await req.treeStore.deleteItem(id)
|
||||
await req.state.store.deleteObject(notePath)
|
||||
await req.state.treeStore.deleteItem(id)
|
||||
}
|
||||
|
||||
async function restoreNote(req: ApiRequest, id: string, parentId = 'root') {
|
||||
const notePath = getPathNoteById(id)
|
||||
const oldMeta = await req.store.getObjectMeta(notePath)
|
||||
const oldMeta = await req.state.store.getObjectMeta(notePath)
|
||||
let meta = jsonToMeta({
|
||||
date: new Date().toISOString(),
|
||||
deleted: NOTE_DELETED.NORMAL.toString(),
|
||||
@ -51,9 +51,9 @@ async function restoreNote(req: ApiRequest, id: string, parentId = 'root') {
|
||||
meta = { ...oldMeta, ...meta }
|
||||
}
|
||||
|
||||
await req.store.copyObject(notePath, notePath, {
|
||||
await req.state.store.copyObject(notePath, notePath, {
|
||||
meta,
|
||||
contentType: 'text/markdown',
|
||||
})
|
||||
await req.treeStore.restoreItem(id, parentId)
|
||||
await req.state.treeStore.restoreItem(id, parentId)
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { api } from 'libs/server/api'
|
||||
import { api } from 'libs/server/connect'
|
||||
import { useAuth } from 'libs/server/middlewares/auth'
|
||||
import { useStore } from 'libs/server/middlewares/store'
|
||||
|
||||
@ -6,7 +6,7 @@ export default api()
|
||||
.use(useAuth)
|
||||
.use(useStore)
|
||||
.get(async (req, res) => {
|
||||
res.json(await req.treeStore.get())
|
||||
res.json(await req.state.treeStore.get())
|
||||
})
|
||||
.post(async (req, res) => {
|
||||
const { action, data } = req.body as {
|
||||
@ -16,11 +16,11 @@ export default api()
|
||||
|
||||
switch (action) {
|
||||
case 'move':
|
||||
await req.treeStore.moveItem(data.source, data.destination)
|
||||
await req.state.treeStore.moveItem(data.source, data.destination)
|
||||
break
|
||||
|
||||
case 'mutate':
|
||||
await req.treeStore.mutateItem(data.id, data)
|
||||
await req.state.treeStore.mutateItem(data.id, data)
|
||||
break
|
||||
|
||||
default:
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { api } from 'libs/server/api'
|
||||
import { api } from 'libs/server/connect'
|
||||
import { useAuth } from 'libs/server/middlewares/auth'
|
||||
import { useStore } from 'libs/server/middlewares/store'
|
||||
import { IncomingForm } from 'formidable'
|
||||
@ -35,7 +35,7 @@ export default api()
|
||||
)}${extname(file.name)}`
|
||||
const filePath = getPathFileByName(fileName)
|
||||
|
||||
await req.store.putObject(filePath, buffer, {
|
||||
await req.state.store.putObject(filePath, buffer, {
|
||||
contentType: file.type,
|
||||
headers: {
|
||||
cacheControl:
|
||||
|
@ -1,17 +1,17 @@
|
||||
import LayoutMain from 'components/layout/layout-main'
|
||||
import { GetServerSideProps, GetServerSidePropsContext, NextPage } from 'next'
|
||||
import { withTree } from 'libs/server/middlewares/tree'
|
||||
import { withUA } from 'libs/server/middlewares/ua'
|
||||
import { NextPage } from 'next'
|
||||
import { applyTree } from 'libs/server/middlewares/tree'
|
||||
import { applyUA } from 'libs/server/middlewares/ua'
|
||||
import { TreeModel } from 'libs/shared/tree'
|
||||
import { withSession } from 'libs/server/middlewares/session'
|
||||
import { withStore } from 'libs/server/middlewares/store'
|
||||
import { withSettings } from 'libs/server/middlewares/settings'
|
||||
import { withAuth } from 'libs/server/middlewares/auth'
|
||||
import { useSession } from 'libs/server/middlewares/session'
|
||||
import { applySettings } from 'libs/server/middlewares/settings'
|
||||
import { applyAuth, applyRedirectLogin } from 'libs/server/middlewares/auth'
|
||||
import Link from 'next/link'
|
||||
import UIState from 'libs/web/state/ui'
|
||||
import Router from 'next/router'
|
||||
import { useEffect } from 'react'
|
||||
import { withCsrf } from 'libs/server/middlewares/csrf'
|
||||
import { applyCsrf } from 'libs/server/middlewares/csrf'
|
||||
import { SSRContext, ssr } from 'libs/server/connect'
|
||||
|
||||
const EditNotePage: NextPage<{ tree: TreeModel }> = ({ tree }) => {
|
||||
const { ua } = UIState.useContainer()
|
||||
@ -39,24 +39,30 @@ const EditNotePage: NextPage<{ tree: TreeModel }> = ({ tree }) => {
|
||||
|
||||
export default EditNotePage
|
||||
|
||||
function withIndex(wrapperHandler: any) {
|
||||
return async function handler(ctx: GetServerSidePropsContext) {
|
||||
const res = await wrapperHandler(ctx)
|
||||
const lastVisit = res.props?.settings?.last_visit
|
||||
export const getServerSideProps = async (ctx: SSRContext) => {
|
||||
await ssr()
|
||||
.use(useSession)
|
||||
.use(applyAuth)
|
||||
.use(applyRedirectLogin(ctx.resolvedUrl))
|
||||
.use(applyTree)
|
||||
.use(applySettings)
|
||||
.use(applyCsrf)
|
||||
.use(applyUA)
|
||||
.run(ctx.req, ctx.res)
|
||||
|
||||
if (lastVisit && !res.redirect) {
|
||||
res.redirect = {
|
||||
const lastVisit = ctx.req.props?.settings?.last_visit
|
||||
|
||||
if (lastVisit) {
|
||||
return {
|
||||
redirect: {
|
||||
destination: lastVisit,
|
||||
permanent: false,
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
return res
|
||||
return {
|
||||
props: ctx.req.props,
|
||||
redirect: ctx.req.redirect,
|
||||
}
|
||||
}
|
||||
|
||||
export const getServerSideProps: GetServerSideProps = withUA(
|
||||
withSession(
|
||||
withStore(withAuth(withTree(withIndex(withSettings(withCsrf(() => ({})))))))
|
||||
)
|
||||
)
|
||||
|
@ -1,6 +1,8 @@
|
||||
import { TextField, Button, Snackbar } from '@material-ui/core'
|
||||
import { Alert } from '@material-ui/lab'
|
||||
import { withCsrf } from 'libs/server/middlewares/csrf'
|
||||
import { SSRContext, ssr } from 'libs/server/connect'
|
||||
import { applyCsrf } from 'libs/server/middlewares/csrf'
|
||||
import { useSession } from 'libs/server/middlewares/session'
|
||||
import useFetcher from 'libs/web/api/fetcher'
|
||||
import router from 'next/router'
|
||||
import { FormEvent, useCallback } from 'react'
|
||||
@ -66,4 +68,10 @@ const LoginPage = () => {
|
||||
|
||||
export default LoginPage
|
||||
|
||||
export const getServerSideProps = withCsrf(() => ({}))
|
||||
export const getServerSideProps = async (ctx: SSRContext) => {
|
||||
await ssr().use(useSession).use(applyCsrf).run(ctx.req, ctx.res)
|
||||
|
||||
return {
|
||||
props: ctx.req.props,
|
||||
}
|
||||
}
|
||||
|
@ -1,16 +1,16 @@
|
||||
import LayoutMain from 'components/layout/layout-main'
|
||||
import { GetServerSideProps, NextPage } from 'next'
|
||||
import { withTree } from 'libs/server/middlewares/tree'
|
||||
import { withUA } from 'libs/server/middlewares/ua'
|
||||
import { NextPage } from 'next'
|
||||
import { applyTree } from 'libs/server/middlewares/tree'
|
||||
import { applyUA } from 'libs/server/middlewares/ua'
|
||||
import { TreeModel } from 'libs/shared/tree'
|
||||
import { withSession } from 'libs/server/middlewares/session'
|
||||
import { withStore } from 'libs/server/middlewares/store'
|
||||
import { withSettings } from 'libs/server/middlewares/settings'
|
||||
import { withAuth } from 'libs/server/middlewares/auth'
|
||||
import { useSession } from 'libs/server/middlewares/session'
|
||||
import { applySettings } from 'libs/server/middlewares/settings'
|
||||
import { applyAuth } from 'libs/server/middlewares/auth'
|
||||
import { SettingsForm } from 'components/settings/settings-form'
|
||||
import useI18n from 'libs/web/hooks/use-i18n'
|
||||
import { withCsrf } from 'libs/server/middlewares/csrf'
|
||||
import { applyCsrf } from 'libs/server/middlewares/csrf'
|
||||
import { SettingFooter } from 'components/settings/setting-footer'
|
||||
import { SSRContext, ssr } from 'libs/server/connect'
|
||||
|
||||
const SettingsPage: NextPage<{ tree: TreeModel }> = ({ tree }) => {
|
||||
const { t } = useI18n()
|
||||
@ -33,6 +33,18 @@ const SettingsPage: NextPage<{ tree: TreeModel }> = ({ tree }) => {
|
||||
|
||||
export default SettingsPage
|
||||
|
||||
export const getServerSideProps: GetServerSideProps = withUA(
|
||||
withSession(withStore(withAuth(withTree(withSettings(withCsrf(() => ({})))))))
|
||||
)
|
||||
export const getServerSideProps = async (ctx: SSRContext) => {
|
||||
await ssr()
|
||||
.use(useSession)
|
||||
.use(applyAuth)
|
||||
.use(applyTree)
|
||||
.use(applySettings)
|
||||
.use(applyCsrf)
|
||||
.use(applyUA)
|
||||
.run(ctx.req, ctx.res)
|
||||
|
||||
return {
|
||||
props: ctx.req.props,
|
||||
redirect: ctx.req.redirect,
|
||||
}
|
||||
}
|
||||
|
42
pages/share/[id].tsx
Normal file
42
pages/share/[id].tsx
Normal file
@ -0,0 +1,42 @@
|
||||
import { applyTree } from 'libs/server/middlewares/tree'
|
||||
import { applyUA } from 'libs/server/middlewares/ua'
|
||||
import { applySettings } from 'libs/server/middlewares/settings'
|
||||
import { applyNote } from 'libs/server/middlewares/note'
|
||||
import LayoutPublic from 'components/layout/layout-public'
|
||||
import { PostContainer } from 'components/container/post-container'
|
||||
import { ServerProps, ssr, SSRContext } from 'libs/server/connect'
|
||||
import { useSession } from 'libs/server/middlewares/session'
|
||||
|
||||
export default function SharePage({
|
||||
tree,
|
||||
note,
|
||||
pageMode,
|
||||
baseURL,
|
||||
}: ServerProps) {
|
||||
return (
|
||||
<LayoutPublic tree={tree} note={note}>
|
||||
<PostContainer pageMode={pageMode} baseURL={baseURL} />
|
||||
</LayoutPublic>
|
||||
)
|
||||
}
|
||||
|
||||
export const getServerSideProps = async (
|
||||
ctx: SSRContext & {
|
||||
query: {
|
||||
id: string
|
||||
}
|
||||
}
|
||||
) => {
|
||||
await ssr()
|
||||
.use(useSession)
|
||||
.use(applyNote(ctx.query.id))
|
||||
.use(applyTree)
|
||||
.use(applySettings)
|
||||
.use(applyUA)
|
||||
.run(ctx.req, ctx.res)
|
||||
|
||||
return {
|
||||
props: ctx.req.props,
|
||||
redirect: ctx.req.redirect,
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user