diff --git a/config/dev.yaml b/config/dev.yaml
index 66572641..b0e3900a 100644
--- a/config/dev.yaml
+++ b/config/dev.yaml
@@ -1,16 +1,3 @@
-# 以下为前台动态渲染的配置,修改后,无需打包,重启服务即可
-dynamic:
- # 输入 logo 的图片地址
- logo: ''
- appName: '云策文档'
- appDescription: '云策文档是一款开源知识管理工具。通过独立的知识库空间,结构化地组织在线协作文档,实现知识的积累与沉淀,促进知识的复用与流通。'
- appKeywords: '云策文档,协作,文档,fantasticit,https://github.com/fantasticit/think'
- # 版权信息,可以是 HTML
- copyrightInformation: '
Develop by fantasticit
'
- # 外部跳转按钮配置
- externalButtonText: 'Github'
- externalButtonURL: 'https://github.com/fantasticit/think'
-
# 开发环境配置
client:
port: 5001
diff --git a/config/docker-prod-sample.yaml b/config/docker-prod-sample.yaml
index bbe45e47..e60c2c9b 100644
--- a/config/docker-prod-sample.yaml
+++ b/config/docker-prod-sample.yaml
@@ -1,16 +1,3 @@
-# 以下为前台动态渲染的配置,修改后,无需打包,重启服务即可
-dynamic:
- # 输入 logo 的图片地址
- logo: ''
- appName: '云策文档'
- appDescription: '云策文档是一款开源知识管理工具。通过独立的知识库空间,结构化地组织在线协作文档,实现知识的积累与沉淀,促进知识的复用与流通。'
- appKeywords: '云策文档,协作,文档,fantasticit,https://github.com/fantasticit/think'
- # 版权信息,可以是 HTML
- copyrightInformation: 'Develop by fantasticit
'
- # 外部跳转按钮配置
- externalButtonText: 'Github'
- externalButtonURL: 'https://github.com/fantasticit/think'
-
# 开发环境配置
client:
port: 5001
diff --git a/dynamic.yaml b/dynamic.yaml
new file mode 100644
index 00000000..d3d34347
--- /dev/null
+++ b/dynamic.yaml
@@ -0,0 +1,12 @@
+# 以下为前台动态渲染的配置,修改后,无需打包,重启服务即可
+dynamic:
+ # 输入 logo 的图片地址
+ logo: ''
+ appName: '云策文档'
+ appDescription: '云策文档是一款开源知识管理工具。通过独立的知识库空间,结构化地组织在线协作文档,实现知识的积累与沉淀,促进知识的复用与流通。'
+ appKeywords: '云策文档,协作,文档,fantasticit,https://github.com/fantasticit/think'
+ # 版权信息,可以是 HTML
+ copyrightInformation: 'Develop by fantasticit
'
+ # 外部跳转按钮配置
+ externalButtonText: 'Github'
+ externalButtonURL: 'https://github.com/fantasticit/think'
diff --git a/packages/client/src/components/admin/system-config/ad/index.tsx b/packages/client/src/components/admin/system-config/ad/index.tsx
new file mode 100644
index 00000000..d5434bf4
--- /dev/null
+++ b/packages/client/src/components/admin/system-config/ad/index.tsx
@@ -0,0 +1,141 @@
+import React, { useCallback, useRef, useState } from 'react';
+
+import { Avatar, Button, Form, Image, Modal, Space, Table } from '@douyinfe/semi-ui';
+import { FormApi } from '@douyinfe/semi-ui/lib/es/form';
+
+import { AdType } from '@think/domains';
+
+import { DataRender } from 'components/data-render';
+import { Upload } from 'components/upload';
+import { useAd } from 'data/ad';
+
+const { Option } = Form.Select;
+
+const AdTypeTextMap = {
+ [AdType.shareDocCover]: '分享文档主图位',
+ [AdType.shareDocAside]: '分享文档侧边位',
+};
+
+const columns = [
+ {
+ title: '类型',
+ dataIndex: 'type',
+ render: (type) => {
+ return AdTypeTextMap[type];
+ },
+ },
+ {
+ title: '广告图',
+ dataIndex: 'cover',
+ render: (url) => {
+ return (
+
+
+
+ );
+ },
+ },
+ {
+ title: '广告链接',
+ dataIndex: 'url',
+ },
+];
+
+export const Ad = () => {
+ const { data, loading, error, createAd, deleteAd } = useAd();
+ const [visible, toggleVisible] = useState(false);
+ const $form = useRef();
+ const [currentCover, setCurrentCover] = useState('');
+
+ const setCover = useCallback((url) => {
+ $form.current.setValue('cover', url);
+ setCurrentCover(url);
+ }, []);
+
+ const handleOk = useCallback(() => {
+ $form.current.validate().then((values) => {
+ if (!values.email) {
+ delete values.email;
+ }
+ createAd(values).then(() => {
+ $form.current.reset();
+ setCover('');
+ toggleVisible(false);
+ });
+ });
+ }, [createAd, setCover, toggleVisible]);
+
+ return (
+ (
+
+
+
{
+ toggleVisible(false);
+ }}
+ style={{ maxWidth: '96vw' }}
+ onOk={handleOk}
+ >
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
{
+ return (
+
+ );
+ },
+ },
+ ]}
+ dataSource={data}
+ pagination={false}
+ />
+
+ )}
+ />
+ );
+};
diff --git a/packages/client/src/components/admin/system-config/index.tsx b/packages/client/src/components/admin/system-config/index.tsx
index 8cc4228b..11b7b816 100644
--- a/packages/client/src/components/admin/system-config/index.tsx
+++ b/packages/client/src/components/admin/system-config/index.tsx
@@ -2,6 +2,7 @@ import React from 'react';
import { TabPane, Tabs } from '@douyinfe/semi-ui';
+import { Ad } from './ad';
import { Mail } from './mail';
import { System } from './system';
@@ -13,6 +14,7 @@ interface IProps {
const TitleMap = {
base: '系统管理',
mail: '邮箱服务',
+ ad: '广告管理',
};
export const SystemConfig: React.FC = ({ tab, onNavigate }) => {
@@ -25,6 +27,10 @@ export const SystemConfig: React.FC = ({ tab, onNavigate }) => {
+
+
+
+
);
};
diff --git a/packages/client/src/components/admin/system-config/mail/index.tsx b/packages/client/src/components/admin/system-config/mail/index.tsx
index 968dae5d..dc0a9d00 100644
--- a/packages/client/src/components/admin/system-config/mail/index.tsx
+++ b/packages/client/src/components/admin/system-config/mail/index.tsx
@@ -35,6 +35,8 @@ export const Mail = () => {
closeIcon={null}
/>
+ {/*
+ // @ts-ignore */}
{
error={error}
normalContent={() => (
+ {/*
+ // @ts-ignore */}
div {
+ height: 100%;
+ }
+ }
+
.contentWrap {
flex: 1;
overflow: hidden;
diff --git a/packages/client/src/components/document/reader/public/index.tsx b/packages/client/src/components/document/reader/public/index.tsx
index 945aa199..6ba12a55 100644
--- a/packages/client/src/components/document/reader/public/index.tsx
+++ b/packages/client/src/components/document/reader/public/index.tsx
@@ -1,28 +1,40 @@
-import { Breadcrumb, Button, Form, Layout, Nav, Skeleton, Space, Tooltip, Typography } from '@douyinfe/semi-ui';
-import { IconRoute } from '@douyinfe/semi-icons';
-import { FormApi } from '@douyinfe/semi-ui/lib/es/form';
-import { DataRender } from 'components/data-render';
-import { Divider } from 'components/divider';
-import { DocumentStyle } from 'components/document/style';
-import { LogoImage, LogoText } from 'components/logo';
-import { Seo } from 'components/seo';
-import { Theme } from 'components/theme';
-import { User } from 'components/user';
-import { usePublicDocumentDetail } from 'data/document';
-import { useMount } from 'hooks/use-mount';
-import { IsOnMobile } from 'hooks/use-on-mobile';
-import { SecureDocumentIllustration } from 'illustrations/secure-document';
-import Link from 'next/link';
-import React, { useCallback, useMemo, useRef } from 'react';
-import { createPortal } from 'react-dom';
-import { CollaborationEditor } from 'tiptap/editor';
+import {
+ Breadcrumb,
+ Button,
+ Form,
+ Layout,
+ Nav,
+ Skeleton,
+ Space,
+ Tooltip,
+ Typography,
+ Carousel,
+} from "@douyinfe/semi-ui";
+import { IconRoute } from "@douyinfe/semi-icons";
+import { FormApi } from "@douyinfe/semi-ui/lib/es/form";
+import { DataRender } from "components/data-render";
+import { Divider } from "components/divider";
+import { DocumentStyle } from "components/document/style";
+import { LogoImage, LogoText } from "components/logo";
+import { Seo } from "components/seo";
+import { Theme } from "components/theme";
+import { User } from "components/user";
+import { usePublicDocumentDetail } from "data/document";
+import { useMount } from "hooks/use-mount";
+import { IsOnMobile } from "hooks/use-on-mobile";
+import { SecureDocumentIllustration } from "illustrations/secure-document";
+import Link from "next/link";
+import React, { useCallback, useMemo, useRef } from "react";
+import { createPortal } from "react-dom";
+import { CollaborationEditor } from "tiptap/editor";
-import { Author } from '../author';
-import styles from './index.module.scss';
-import Router from 'next/router';
-import { useRouterQuery } from 'hooks/use-router-query';
-import { IDocument, IWiki } from '@think/domains';
-import { DocumentFullscreen } from 'components/document/fullscreen';
+import { Author } from "../author";
+import styles from "./index.module.scss";
+import Router from "next/router";
+import { useRouterQuery } from "hooks/use-router-query";
+import { AdType, IDocument, IWiki } from "@think/domains";
+import { DocumentFullscreen } from "components/document/fullscreen";
+import { useAd } from "data/ad";
const { Header } = Layout;
const { Text } = Typography;
@@ -32,18 +44,25 @@ interface IProps {
hideLogo?: boolean;
}
-export const DocumentPublicReader: React.FC = ({ documentId, hideLogo = false }) => {
+export const DocumentPublicReader: React.FC = ({
+ documentId,
+ hideLogo = false,
+}) => {
const $form = useRef();
const mounted = useMount();
- const { wikiId: currentWikiId } = useRouterQuery<{ wikiId: IWiki['id']; documentId: IDocument['id'] }>();
+ const { wikiId: currentWikiId } = useRouterQuery<{
+ wikiId: IWiki["id"];
+ documentId: IDocument["id"];
+ }>();
const { data, loading, error, query } = usePublicDocumentDetail(documentId);
+ const { data: ads } = useAd();
const { isMobile } = IsOnMobile.useHook();
-
+
const renderAuthor = useCallback(
(element) => {
if (!document) return null;
- const target = element && element.querySelector('.ProseMirror .title');
+ const target = element && element.querySelector(".ProseMirror .title");
if (target) {
return createPortal(, target);
@@ -62,27 +81,42 @@ export const DocumentPublicReader: React.FC = ({ documentId, hideLogo =
const toPublicWikiOrDocumentURL = useCallback(() => {
Router.push({
- pathname: currentWikiId ? '/share/document/[documentId]' : '/share/wiki/[wikiId]/document/[documentId]',
- query: currentWikiId ? { documentId } : { wikiId: data.wikiId, documentId },
+ pathname: currentWikiId
+ ? "/share/document/[documentId]"
+ : "/share/wiki/[wikiId]/document/[documentId]",
+ query: currentWikiId
+ ? { documentId }
+ : { wikiId: data.wikiId, documentId },
});
}, [currentWikiId, documentId, data]);
+ console.log(ads || []);
+
const content = useMemo(() => {
if (error) {
// @ts-ignore
if (error.statusCode === 400) {
return (
-
+
+
@@ -94,16 +128,16 @@ export const DocumentPublicReader: React.FC
= ({ documentId, hideLogo =
return (
- {(error && (error as Error).message) || '未知错误'}
+ {(error && (error as Error).message) || "未知错误"}
);
@@ -144,9 +178,14 @@ export const DocumentPublicReader: React.FC = ({ documentId, hideLogo =
}
footer={
- {!isMobile && data && }
-
- } onClick={toPublicWikiOrDocumentURL} />
+ {!isMobile && data && }
+
+ }
+ onClick={toPublicWikiOrDocumentURL}
+ />
@@ -158,11 +197,20 @@ export const DocumentPublicReader: React.FC = ({ documentId, hideLogo =
} loading={true} />}
+ loadingContent={
+ }
+ loading={true}
+ />
+ }
normalContent={() => (
-
+
{data?.wiki?.name}
@@ -172,9 +220,38 @@ export const DocumentPublicReader: React.FC = ({ documentId, hideLogo =
/>
+
+ {(ads || [])
+ .filter((ad) => ad.type === AdType.shareDocCover)
+ .map((ad) => {
+ return (
+
+ );
+ })}
+
{content}
+
+
+
+ {(ads || [])
+ .filter((ad) => ad.type === AdType.shareDocAside)
+ .map((ad) => {
+ return (
+
+ );
+ })}
+
+
);
};
diff --git a/packages/client/src/data/ad.ts b/packages/client/src/data/ad.ts
new file mode 100644
index 00000000..367dffb6
--- /dev/null
+++ b/packages/client/src/data/ad.ts
@@ -0,0 +1,42 @@
+import { useCallback } from 'react';
+import { useQuery } from 'react-query';
+
+import { AdApiDefinition, IAd } from '@think/domains';
+
+import { HttpClient } from 'services/http-client';
+
+export const useAd = () => {
+ const { data, error, isLoading, refetch } = useQuery(AdApiDefinition.getAll.client(), () =>
+ HttpClient.request({
+ method: AdApiDefinition.getAll.method,
+ url: AdApiDefinition.getAll.client(),
+ })
+ );
+
+ const createAd = useCallback(
+ async (data: Partial) => {
+ const ret = await HttpClient.request({
+ method: AdApiDefinition.create.method,
+ url: AdApiDefinition.create.client(),
+ data,
+ });
+ refetch();
+ return ret;
+ },
+ [refetch]
+ );
+
+ const deleteAd = useCallback(
+ async (id: IAd['id']) => {
+ const ret = await HttpClient.request({
+ method: AdApiDefinition.deleteById.method,
+ url: AdApiDefinition.deleteById.client(id),
+ });
+ refetch();
+ return ret;
+ },
+ [refetch]
+ );
+
+ return { data, error, loading: isLoading, refresh: refetch, createAd, deleteAd };
+};
diff --git a/packages/client/src/tiptap/editor/collaboration/collaboration/editor.tsx b/packages/client/src/tiptap/editor/collaboration/collaboration/editor.tsx
index c8e5d5a4..903fd701 100644
--- a/packages/client/src/tiptap/editor/collaboration/collaboration/editor.tsx
+++ b/packages/client/src/tiptap/editor/collaboration/collaboration/editor.tsx
@@ -169,6 +169,7 @@ export const EditorInstance = forwardRef((props: IProps, ref) => {
<>
{(!online || status === 'disconnected') && (
{
{/* FIXME:需要菜单栏但是无法编辑,则认为进入了编辑模式但是没有编辑权限,也许有更好的判断 */}
{!editable && menubar && (
+ // @ts-ignore
)}
diff --git a/packages/config/lib/index.js b/packages/config/lib/index.js
index 2e0dec48..20e7a847 100644
--- a/packages/config/lib/index.js
+++ b/packages/config/lib/index.js
@@ -20,9 +20,9 @@ function getConfig() {
exports.getConfig = getConfig;
function getRuntimeConfig() {
var env = process.env.NODE_ENV || 'development';
- var filePath = path.join(__dirname, '../../../../../../config', "".concat(FILE_ENV_NAME[env], ".yaml"));
+ var filePath = path.join(__dirname, '../../../../../../', "dynamic.yaml");
if (!fs.existsSync(filePath)) {
- filePath = path.join(__dirname, '../../../config', "".concat(FILE_ENV_NAME[env], ".yaml"));
+ filePath = path.join(__dirname, '../../../config', "dynamic.yaml");
}
if (!fs.existsSync(filePath)) {
throw new Error("Can not find config file: ".concat(filePath));
diff --git a/packages/config/package.json b/packages/config/package.json
index 9d084fe1..05d684d5 100644
--- a/packages/config/package.json
+++ b/packages/config/package.json
@@ -21,5 +21,8 @@
],
"dependencies": {
"js-yaml": "^4.1.0"
+ },
+ "devDependencies": {
+ "typescript": "^4.8.4"
}
-}
\ No newline at end of file
+}
diff --git a/packages/config/src/index.ts b/packages/config/src/index.ts
index 704c23a6..9a87668c 100644
--- a/packages/config/src/index.ts
+++ b/packages/config/src/index.ts
@@ -23,10 +23,10 @@ export function getConfig() {
export function getRuntimeConfig() {
const env = process.env.NODE_ENV || 'development';
- let filePath = path.join(__dirname, '../../../../../../config', `${FILE_ENV_NAME[env]}.yaml`);
+ let filePath = path.join(__dirname, '../../../../../../', `dynamic.yaml`);
if (!fs.existsSync(filePath)) {
- filePath = path.join(__dirname, '../../../config', `${FILE_ENV_NAME[env]}.yaml`);
+ filePath = path.join(__dirname, '../../../config', `dynamic.yaml`);
}
if (!fs.existsSync(filePath)) {
diff --git a/packages/constants/package.json b/packages/constants/package.json
index 1bf83a6b..4db957f6 100644
--- a/packages/constants/package.json
+++ b/packages/constants/package.json
@@ -19,6 +19,6 @@
"src"
],
"devDependencies": {
- "typescript": "^4.5.5"
+ "typescript": "^4.8.4"
}
-}
\ No newline at end of file
+}
diff --git a/packages/domains/lib/api/ad.d.ts b/packages/domains/lib/api/ad.d.ts
new file mode 100644
index 00000000..f6b6c5d0
--- /dev/null
+++ b/packages/domains/lib/api/ad.d.ts
@@ -0,0 +1,23 @@
+export declare const AdApiDefinition: {
+ /**
+ * 获取用户
+ */
+ getAll: {
+ method: "get";
+ server: "";
+ client: () => string;
+ };
+ create: {
+ method: "post";
+ server: "";
+ client: () => string;
+ };
+ /**
+ * 获取组织内加星的知识库
+ */
+ deleteById: {
+ method: "delete";
+ server: ":id";
+ client: (id: any) => string;
+ };
+};
diff --git a/packages/domains/lib/api/ad.js b/packages/domains/lib/api/ad.js
new file mode 100644
index 00000000..61fefa20
--- /dev/null
+++ b/packages/domains/lib/api/ad.js
@@ -0,0 +1,26 @@
+"use strict";
+exports.__esModule = true;
+exports.AdApiDefinition = void 0;
+exports.AdApiDefinition = {
+ /**
+ * 获取用户
+ */
+ getAll: {
+ method: 'get',
+ server: '',
+ client: function () { return '/ad'; }
+ },
+ create: {
+ method: 'post',
+ server: '',
+ client: function () { return '/ad'; }
+ },
+ /**
+ * 获取组织内加星的知识库
+ */
+ deleteById: {
+ method: 'delete',
+ server: ':id',
+ client: function (id) { return "/ad/".concat(id); }
+ }
+};
diff --git a/packages/domains/lib/api/index.d.ts b/packages/domains/lib/api/index.d.ts
index 8e000d7f..3606fca8 100644
--- a/packages/domains/lib/api/index.d.ts
+++ b/packages/domains/lib/api/index.d.ts
@@ -1,3 +1,4 @@
+export * from './ad';
export * from './user';
export * from './wiki';
export * from './document';
diff --git a/packages/domains/lib/api/index.js b/packages/domains/lib/api/index.js
index 04b1be15..42cffc43 100644
--- a/packages/domains/lib/api/index.js
+++ b/packages/domains/lib/api/index.js
@@ -1,7 +1,11 @@
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
- Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
+ var desc = Object.getOwnPropertyDescriptor(m, k);
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
+ desc = { enumerable: true, get: function() { return m[k]; } };
+ }
+ Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
@@ -10,6 +14,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
};
exports.__esModule = true;
+__exportStar(require("./ad"), exports);
__exportStar(require("./user"), exports);
__exportStar(require("./wiki"), exports);
__exportStar(require("./document"), exports);
diff --git a/packages/domains/lib/index.js b/packages/domains/lib/index.js
index 7cb1843b..ae75a183 100644
--- a/packages/domains/lib/index.js
+++ b/packages/domains/lib/index.js
@@ -1,7 +1,11 @@
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
- Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
+ var desc = Object.getOwnPropertyDescriptor(m, k);
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
+ desc = { enumerable: true, get: function() { return m[k]; } };
+ }
+ Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
diff --git a/packages/domains/lib/models/ad.d.ts b/packages/domains/lib/models/ad.d.ts
new file mode 100644
index 00000000..cf777db5
--- /dev/null
+++ b/packages/domains/lib/models/ad.d.ts
@@ -0,0 +1,10 @@
+export declare enum AdType {
+ shareDocCover = "shareDocCover",
+ shareDocAside = "shareDocAside"
+}
+export interface IAd {
+ id: string;
+ type: AdType;
+ cover: string;
+ url: string;
+}
diff --git a/packages/domains/lib/models/ad.js b/packages/domains/lib/models/ad.js
new file mode 100644
index 00000000..4b48bb4e
--- /dev/null
+++ b/packages/domains/lib/models/ad.js
@@ -0,0 +1,8 @@
+"use strict";
+exports.__esModule = true;
+exports.AdType = void 0;
+var AdType;
+(function (AdType) {
+ AdType["shareDocCover"] = "shareDocCover";
+ AdType["shareDocAside"] = "shareDocAside";
+})(AdType = exports.AdType || (exports.AdType = {}));
diff --git a/packages/domains/lib/models/index.d.ts b/packages/domains/lib/models/index.d.ts
index c2dd9441..37c705ca 100644
--- a/packages/domains/lib/models/index.d.ts
+++ b/packages/domains/lib/models/index.d.ts
@@ -1,3 +1,4 @@
+export * from './ad';
export * from './user';
export * from './wiki';
export * from './document';
diff --git a/packages/domains/lib/models/index.js b/packages/domains/lib/models/index.js
index f96093bc..93bf428a 100644
--- a/packages/domains/lib/models/index.js
+++ b/packages/domains/lib/models/index.js
@@ -1,7 +1,11 @@
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
- Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
+ var desc = Object.getOwnPropertyDescriptor(m, k);
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
+ desc = { enumerable: true, get: function() { return m[k]; } };
+ }
+ Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
@@ -10,6 +14,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
};
exports.__esModule = true;
+__exportStar(require("./ad"), exports);
__exportStar(require("./user"), exports);
__exportStar(require("./wiki"), exports);
__exportStar(require("./document"), exports);
diff --git a/packages/domains/package.json b/packages/domains/package.json
index c025383c..c3468b2b 100644
--- a/packages/domains/package.json
+++ b/packages/domains/package.json
@@ -19,6 +19,6 @@
"src"
],
"devDependencies": {
- "typescript": "^4.5.5"
+ "typescript": "^4.8.4"
}
-}
\ No newline at end of file
+}
diff --git a/packages/domains/src/api/ad.ts b/packages/domains/src/api/ad.ts
new file mode 100644
index 00000000..918d76f5
--- /dev/null
+++ b/packages/domains/src/api/ad.ts
@@ -0,0 +1,25 @@
+export const AdApiDefinition = {
+ /**
+ * 获取用户
+ */
+ getAll: {
+ method: 'get' as const,
+ server: '' as const,
+ client: () => '/ad',
+ },
+
+ create: {
+ method: 'post' as const,
+ server: '' as const,
+ client: () => '/ad',
+ },
+
+ /**
+ * 获取组织内加星的知识库
+ */
+ deleteById: {
+ method: 'delete' as const,
+ server: ':id' as const,
+ client: (id) => `/ad/${id}`,
+ },
+};
diff --git a/packages/domains/src/api/index.ts b/packages/domains/src/api/index.ts
index 8e000d7f..3606fca8 100644
--- a/packages/domains/src/api/index.ts
+++ b/packages/domains/src/api/index.ts
@@ -1,3 +1,4 @@
+export * from './ad';
export * from './user';
export * from './wiki';
export * from './document';
diff --git a/packages/domains/src/models/ad.ts b/packages/domains/src/models/ad.ts
new file mode 100644
index 00000000..d00a21da
--- /dev/null
+++ b/packages/domains/src/models/ad.ts
@@ -0,0 +1,11 @@
+export enum AdType {
+ shareDocCover = 'shareDocCover',
+ shareDocAside = 'shareDocAside',
+}
+
+export interface IAd {
+ id: string;
+ type: AdType;
+ cover: string;
+ url: string;
+}
diff --git a/packages/domains/src/models/index.ts b/packages/domains/src/models/index.ts
index c2dd9441..37c705ca 100644
--- a/packages/domains/src/models/index.ts
+++ b/packages/domains/src/models/index.ts
@@ -1,3 +1,4 @@
+export * from './ad';
export * from './user';
export * from './wiki';
export * from './document';
diff --git a/packages/server/src/app.module.ts b/packages/server/src/app.module.ts
index 05f96ec9..f77de94c 100644
--- a/packages/server/src/app.module.ts
+++ b/packages/server/src/app.module.ts
@@ -6,6 +6,7 @@ import { LoggerModule } from 'nestjs-pino';
import { getConfig } from '@think/config';
+import { AdEntity } from '@entities/ad.entity';
import { AuthEntity } from '@entities/auth.entity';
import { CommentEntity } from '@entities/comment.entity';
import { DocumentEntity } from '@entities/document.entity';
@@ -18,6 +19,7 @@ import { UserEntity } from '@entities/user.entity';
import { WikiEntity } from '@entities/wiki.entity';
import { IS_PRODUCTION } from '@helpers/env.helper';
import { getLogFileName, ONE_DAY } from '@helpers/log.helper';
+import { AdModule } from '@modules/ad.module';
import { AuthModule } from '@modules/auth.module';
import { CommentModule } from '@modules/comment.module';
import { DocumentModule } from '@modules/document.module';
@@ -46,6 +48,7 @@ const ENTITIES = [
MessageEntity,
TemplateEntity,
SystemEntity,
+ AdEntity,
];
const MODULES = [
@@ -62,6 +65,7 @@ const MODULES = [
ViewModule,
VerifyModule,
SystemModule,
+ AdModule,
];
@Module({
diff --git a/packages/server/src/controllers/ad.controller.ts b/packages/server/src/controllers/ad.controller.ts
new file mode 100644
index 00000000..cc9794a5
--- /dev/null
+++ b/packages/server/src/controllers/ad.controller.ts
@@ -0,0 +1,48 @@
+import {
+ Body,
+ ClassSerializerInterceptor,
+ Controller,
+ Delete,
+ Get,
+ HttpCode,
+ HttpStatus,
+ Param,
+ Post,
+ Request,
+ UseGuards,
+ UseInterceptors,
+} from '@nestjs/common';
+
+import { AdApiDefinition } from '@think/domains';
+
+import { AdDto } from '@dtos/ad.dto';
+import { JwtGuard } from '@guard/jwt.guard';
+import { AdService } from '@services/ad.service';
+
+@Controller('ad')
+export class AdController {
+ constructor(private readonly adService: AdService) {}
+
+ @UseInterceptors(ClassSerializerInterceptor)
+ @Post(AdApiDefinition.create.server)
+ @HttpCode(HttpStatus.CREATED)
+ @UseGuards(JwtGuard)
+ async addAd(@Request() req, @Body() data: AdDto) {
+ return await this.adService.addAd(req.user, data);
+ }
+
+ @UseInterceptors(ClassSerializerInterceptor)
+ @Get(AdApiDefinition.getAll.server)
+ @HttpCode(HttpStatus.OK)
+ async getAds() {
+ return await this.adService.getAds();
+ }
+
+ @UseInterceptors(ClassSerializerInterceptor)
+ @Delete(AdApiDefinition.deleteById.server)
+ @HttpCode(HttpStatus.OK)
+ @UseGuards(JwtGuard)
+ async deleteAd(@Request() req, @Param('id') id) {
+ return await this.adService.deleteAd(req.user, id);
+ }
+}
diff --git a/packages/server/src/dtos/ad.dto.ts b/packages/server/src/dtos/ad.dto.ts
new file mode 100644
index 00000000..0e0f4bbd
--- /dev/null
+++ b/packages/server/src/dtos/ad.dto.ts
@@ -0,0 +1,17 @@
+import { AdType } from '@think/domains';
+
+import { IsNotEmpty, IsString } from 'class-validator';
+
+export class AdDto {
+ @IsString({ message: '广告位类型错误(正确类型为:String)' })
+ @IsNotEmpty({ message: '广告位不能为空' })
+ type: AdType;
+
+ @IsString({ message: '广告封面图类型错误(正确类型为:String)' })
+ @IsNotEmpty({ message: '广告封面图不能为空' })
+ cover: string;
+
+ @IsString({ message: '广告跳转链接类型错误(正确类型为:String)' })
+ @IsNotEmpty({ message: '广告跳转链接不能为空' })
+ url: string;
+}
diff --git a/packages/server/src/entities/ad.entity.ts b/packages/server/src/entities/ad.entity.ts
new file mode 100644
index 00000000..943a7505
--- /dev/null
+++ b/packages/server/src/entities/ad.entity.ts
@@ -0,0 +1,37 @@
+import { AdType } from '@think/domains';
+
+import { Column, CreateDateColumn, Entity, PrimaryGeneratedColumn, UpdateDateColumn } from 'typeorm';
+
+@Entity('ad')
+export class AdEntity {
+ @PrimaryGeneratedColumn('uuid')
+ id: string;
+
+ @Column({
+ type: 'enum',
+ enum: AdType,
+ default: AdType.shareDocAside,
+ comment: '权限类型',
+ })
+ public type: AdType;
+
+ @Column({ type: 'mediumtext', comment: '广告封面图' })
+ cover: string;
+
+ @Column({ type: 'mediumtext', comment: '跳转链接', default: null, charset: 'utf8mb4' })
+ url: string;
+
+ @CreateDateColumn({
+ type: 'timestamp',
+ name: 'createdAt',
+ comment: '创建时间',
+ })
+ createdAt: Date;
+
+ @UpdateDateColumn({
+ type: 'timestamp',
+ name: 'updatedAt',
+ comment: '更新时间',
+ })
+ updatedAt: Date;
+}
diff --git a/packages/server/src/modules/ad.module.ts b/packages/server/src/modules/ad.module.ts
new file mode 100644
index 00000000..b387e73e
--- /dev/null
+++ b/packages/server/src/modules/ad.module.ts
@@ -0,0 +1,16 @@
+import { forwardRef, Module } from '@nestjs/common';
+import { TypeOrmModule } from '@nestjs/typeorm';
+
+import { AdController } from '@controllers/ad.controller';
+import { AdEntity } from '@entities/ad.entity';
+import { AdService } from '@services/ad.service';
+
+import { UserModule } from './user.module';
+
+@Module({
+ imports: [TypeOrmModule.forFeature([AdEntity]), forwardRef(() => UserModule)],
+ providers: [AdService],
+ exports: [AdService],
+ controllers: [AdController],
+})
+export class AdModule {}
diff --git a/packages/server/src/services/ad.service.ts b/packages/server/src/services/ad.service.ts
new file mode 100644
index 00000000..2c69f551
--- /dev/null
+++ b/packages/server/src/services/ad.service.ts
@@ -0,0 +1,47 @@
+import { forwardRef, HttpException, HttpStatus, Inject, Injectable } from '@nestjs/common';
+import { InjectRepository } from '@nestjs/typeorm';
+
+import { AdDto } from '@dtos/ad.dto';
+import { AdEntity } from '@entities/ad.entity';
+import { UserEntity } from '@entities/user.entity';
+import { Repository } from 'typeorm';
+
+import { UserService } from './user.service';
+
+@Injectable()
+export class AdService {
+ constructor(
+ @Inject(forwardRef(() => UserService))
+ private readonly userService: UserService,
+
+ @InjectRepository(AdEntity)
+ private readonly adRepo: Repository
+ ) {}
+
+ private async check(user: UserEntity) {
+ const currentUser = await this.userService.findById(user.id);
+
+ if (!currentUser.isSystemAdmin) {
+ throw new HttpException('您无权操作', HttpStatus.FORBIDDEN);
+ }
+ }
+
+ async addAd(user: UserEntity, dto: AdDto) {
+ await this.check(user);
+
+ const data = await this.adRepo.create(dto);
+ const res = await this.adRepo.save(data);
+ return res;
+ }
+
+ async getAds() {
+ const data = await this.adRepo.find();
+ return data;
+ }
+
+ async deleteAd(user: UserEntity, id: string) {
+ await this.check(user);
+
+ return await this.adRepo.delete(id);
+ }
+}
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 03117fde..7829cacf 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -424,18 +424,22 @@ importers:
js-yaml:
specifier: ^4.1.0
version: 4.1.0
+ devDependencies:
+ typescript:
+ specifier: ^4.8.4
+ version: 4.8.4
packages/constants:
devDependencies:
typescript:
- specifier: ^4.5.5
- version: 4.5.5
+ specifier: ^4.8.4
+ version: 4.8.4
packages/domains:
devDependencies:
typescript:
- specifier: ^4.5.5
- version: 4.5.5
+ specifier: ^4.8.4
+ version: 4.8.4
packages/server:
dependencies: