Merge branch 'ui' of git.backyard.segmentfault.com:opensource/answer into ui

This commit is contained in:
haitao(lj) 2022-09-29 18:24:38 +08:00
commit bf85f586d8
79 changed files with 1429 additions and 789 deletions

2
.gitignore vendored
View File

@ -10,7 +10,7 @@
/.vscode/*.log /.vscode/*.log
/cmd/answer/*.sh /cmd/answer/*.sh
/cmd/logs /cmd/logs
/configs/* /configs/config-dev.yaml
/go.work* /go.work*
/logs /logs
/ui/build /ui/build

View File

@ -27,11 +27,8 @@ stages:
"compile the golang project": "compile the golang project":
image: golang:1.18 image: golang:1.18
stage: compile-golang stage: compile-golang
before_script:
- export GOPROXY="https://goproxy.cn"
- export GOPRIVATE=git.backyard.segmentfault.com
- sh ./script/prebuild.sh
script: script:
- make generate
- make build - make build
artifacts: artifacts:
paths: paths:

4
CONTRIBUTING.md Normal file
View File

@ -0,0 +1,4 @@
# Contributing to answer
## Coding and documentation Style
## Submitting Modifications

View File

@ -4,12 +4,7 @@ LABEL maintainer="mingcheng<mc@sf.com>"
COPY . /answer COPY . /answer
WORKDIR /answer WORKDIR /answer
RUN make install-ui-packages ui && mv ui/build /tmp
RUN make install-ui-packages ui
RUN mv ui/build /tmp
CMD ls -al /tmp
RUN du -sh /tmp/build
FROM golang:1.18 AS golang-builder FROM golang:1.18 AS golang-builder
LABEL maintainer="aichy" LABEL maintainer="aichy"
@ -23,16 +18,15 @@ ENV GOPRIVATE git.backyard.segmentfault.com
# Build # Build
COPY . ${BUILD_DIR} COPY . ${BUILD_DIR}
WORKDIR ${BUILD_DIR} WORKDIR ${BUILD_DIR}
COPY --from=node-builder /tmp/build ${BUILD_DIR}/web/html COPY --from=node-builder /tmp/build ${BUILD_DIR}/ui/build
CMD ls -al ${BUILD_DIR}/web/html
RUN make clean build && \ RUN make clean build && \
cp answer /usr/bin/answer && \ cp answer /usr/bin/answer && \
cp configs/config.yaml /etc/config.yaml && \
mkdir -p /tmp/cache && chmod 777 /tmp/cache && \ mkdir -p /tmp/cache && chmod 777 /tmp/cache && \
mkdir -p /data/upfiles && chmod 777 /data/upfiles && cp -r i18n /data mkdir /data && chmod 777 /data && cp configs/config.yaml /data/config.yaml && \
mkdir -p /data/upfiles && chmod 777 /data/upfiles && \
mkdir -p /data/i18n && chmod 777 /data/i18n && cp -r i18n/*.yaml /data/i18n
FROM debian:bullseye FROM debian:bullseye
ENV TZ "Asia/Shanghai" ENV TZ "Asia/Shanghai"
RUN sed -i 's/deb.debian.org/mirrors.tuna.tsinghua.edu.cn/g' /etc/apt/sources.list \ RUN sed -i 's/deb.debian.org/mirrors.tuna.tsinghua.edu.cn/g' /etc/apt/sources.list \
&& sed -i 's/security.debian.org/mirrors.tuna.tsinghua.edu.cn/g' /etc/apt/sources.list \ && sed -i 's/security.debian.org/mirrors.tuna.tsinghua.edu.cn/g' /etc/apt/sources.list \
@ -45,9 +39,9 @@ RUN sed -i 's/deb.debian.org/mirrors.tuna.tsinghua.edu.cn/g' /etc/apt/sources.li
COPY --from=golang-builder /data /data COPY --from=golang-builder /data /data
VOLUME /data VOLUME /data
COPY --from=golang-builder /etc/config.yaml /etc/answer.yaml
COPY --from=golang-builder /usr/bin/answer /usr/bin/answer COPY --from=golang-builder /usr/bin/answer /usr/bin/answer
COPY /script/entrypoint.sh /entrypoint.sh
RUN chmod 755 /entrypoint.sh
EXPOSE 80 EXPOSE 80
ENTRYPOINT ["/entrypoint.sh"]
ENTRYPOINT ["dumb-init", "/usr/bin/answer", "-c", "/etc/answer.yaml"]

View File

@ -12,6 +12,11 @@ GO=$(GO_ENV) $(shell which go)
build: build:
@$(GO_ENV) $(GO) build $(GO_FLAGS) -o $(BIN) $(DIR_SRC) @$(GO_ENV) $(GO) build $(GO_FLAGS) -o $(BIN) $(DIR_SRC)
generate:
go get github.com/google/wire/cmd/wire@latest
go generate ./...
go mod tidy
test: test:
@$(GO) test ./... @$(GO) test ./...

View File

@ -1,35 +1,38 @@
# answer ![logo](docs/img/logo.png)
问答社区主项目代码 # Answer - Simple Q&A Community
# Dependence [![LICENSE](https://img.shields.io/badge/License-MIT-green)](https://github.com/segmentfault/answer/blob/master/LICENSE)
github.com/segmentfault/pacman [![Language](https://img.shields.io/badge/Language-Go-blue.svg)](https://golang.org/)
* config-file `viper` https://github.com/spf13/viper [![Language](https://img.shields.io/badge/Language-React-blue.svg)](https://reactjs.org/)
* web `gin` https://gin-gonic.com/zh-cn/
* log `zap` https://github.com/uber-go/zap
* orm `xorm` https://xorm.io/zh/
* redis `go-redis` https://github.com/go-redis/redis
# module ## What is Answer?
- email github.com/jordan-wright/email This is a minimalist open source Q&A community. Users can post questions and others can answer them.
- session github.com/gin-contrib/sessions ![abstract](docs/img/abstract.png)
- Captcha github.com/mojocn/base64Captcha
# Run ## Why?
``` - Help companies build knowledge and Q&A communities better and faster.
cd cmd
export GOPRIVATE=git.backyard.segmentfault.com ## Features
go mod tidy - Produce knowledge by asking and answering questions.
./dev.sh - Maintain knowledge by voting and working together.
## Quick start
### Running with docker-compose
```bash
mkdir answer && cd answer
wget https://github.com/segmentfault/answer/releases/latest/download/docker-compose.yaml
docker-compose up
``` ```
# pprof For more information you can see [INSTALL.md](./INSTALL.md)
``` ## Contributing
# Installation dependency
go get -u github.com/google/pprof Contributions are always welcome!
brew install graphviz
``` See [CONTRIBUTING.md](CONTRIBUTING.md) for ways to get started.
```
pprof -http :8082 http://XXX/debug/pprof/profile\?seconds\=10 ## License
```
[MIT](https://github.com/segmentfault/answer/blob/master/LICENSE)

View File

@ -1,9 +1,38 @@
# Answer 问答社区 ![logo](docs/img/logo.png)
## 功能说明 # Answer - 极简问答社区
## 安装 [![LICENSE](https://img.shields.io/badge/License-MIT-green)](https://github.com/segmentfault/answer/blob/master/LICENSE)
[![Language](https://img.shields.io/badge/Language-Go-blue.svg)](https://golang.org/)
[![Language](https://img.shields.io/badge/Language-React-blue.svg)](https://reactjs.org/)
## 配置 ## 什么是 Answer?
这是一个极简的开源问答社区。用户可以发布问题,其他人可以回答。
![abstract](docs/img/abstract.png)
## 常见问题 ## 目标
- 帮助企业更好更快构建知识问答社区
## 产品功能
- 通过提问、回答方式生产知识
- 通过投票、共同协作方式维护知识
## 快速开始
### 使用 docker-compose 快速搭建
```bash
mkdir answer && cd answer
wget https://github.com/segmentfault/answer/releases/latest/download/docker-compose.yaml
docker-compose up
```
其他安装配置细节请参考 [INSTALL.md](./INSTALL.md)
## 贡献
我们随时欢迎你的贡献!
参考 [CONTRIBUTING.md](CONTRIBUTING.md) 其中的贡献指南
## License
[MIT](https://github.com/segmentfault/answer/blob/master/LICENSE)

581
assets/answer.sql Normal file
View File

@ -0,0 +1,581 @@
-- --------------------------------------------------------
--
-- 表的结构 `activity`
--
CREATE TABLE `activity` (
`id` bigint(20) NOT NULL COMMENT 'Activity ID autoincrement',
`created_at` timestamp NULL DEFAULT NULL COMMENT 'create time',
`updated_at` timestamp NULL DEFAULT NULL COMMENT 'update time',
`user_id` bigint(20) NOT NULL COMMENT 'the user ID that generated the activity or affected by the activity',
`trigger_user_id` bigint(20) NOT NULL DEFAULT '0',
`object_id` bigint(20) NOT NULL DEFAULT '0' COMMENT 'the object ID that affected by the activity',
`activity_type` int(11) NOT NULL COMMENT 'activity type, correspond to config id',
`cancelled` tinyint(4) NOT NULL DEFAULT '0' COMMENT 'mark this activity if cancelled or not,default 0(not cancelled)',
`rank` int(11) NOT NULL DEFAULT '0' COMMENT 'rank of current operating user affected',
`has_rank` tinyint(4) NOT NULL DEFAULT '0' COMMENT 'this activity has rank or not'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='activity';
-- --------------------------------------------------------
--
-- 表的结构 `answer`
--
CREATE TABLE `answer` (
`id` bigint(20) NOT NULL COMMENT 'answer id',
`question_id` bigint(20) NOT NULL DEFAULT '0' COMMENT 'question id',
`user_id` bigint(20) NOT NULL DEFAULT '0' COMMENT 'answer user id',
`original_text` mediumtext NOT NULL COMMENT 'original text',
`parsed_text` mediumtext NOT NULL COMMENT 'parsed text',
`status` int(11) NOT NULL DEFAULT '1' COMMENT ' answer status(available: 1; deleted: 10)',
`adopted` int(11) NOT NULL DEFAULT '1' COMMENT 'adopted (1 failed 2 adopted)',
`comment_count` int(11) NOT NULL DEFAULT '0' COMMENT 'comment count',
`vote_count` int(11) NOT NULL DEFAULT '0' COMMENT 'vote count',
`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
`updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
`revision_id` bigint(20) NOT NULL DEFAULT '0'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='answer';
-- --------------------------------------------------------
--
-- 表的结构 `collection`
--
CREATE TABLE `collection` (
`id` bigint(20) NOT NULL DEFAULT '0' COMMENT 'collection id',
`user_id` bigint(20) NOT NULL DEFAULT '0' COMMENT 'user id',
`object_id` bigint(20) NOT NULL DEFAULT '0' COMMENT 'object id',
`user_collection_group_id` bigint(20) NOT NULL DEFAULT '0' COMMENT 'user collection group id',
`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
`updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='collection';
-- --------------------------------------------------------
--
-- 表的结构 `collection_group`
--
CREATE TABLE `collection_group` (
`id` bigint(20) NOT NULL,
`user_id` bigint(20) NOT NULL DEFAULT '0',
`name` varchar(50) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT 'the collection group name',
`default_group` int(11) NOT NULL DEFAULT '1' COMMENT 'mark this group is default, default 1',
`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
`updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='collection group';
-- --------------------------------------------------------
--
-- 表的结构 `comment`
--
CREATE TABLE `comment` (
`id` bigint(20) NOT NULL COMMENT 'comment id',
`created_at` timestamp NULL DEFAULT NULL COMMENT 'create time',
`updated_at` timestamp NULL DEFAULT NULL COMMENT 'update time',
`user_id` bigint(20) NOT NULL DEFAULT '0' COMMENT 'user id',
`reply_user_id` bigint(20) DEFAULT NULL COMMENT 'reply user id',
`reply_comment_id` bigint(20) DEFAULT NULL COMMENT 'reply comment id',
`object_id` bigint(20) NOT NULL DEFAULT '0' COMMENT 'object id',
`question_id` bigint(20) NOT NULL DEFAULT '0' COMMENT 'question id',
`vote_count` int(11) NOT NULL DEFAULT '0' COMMENT 'user vote amount',
`status` tinyint(4) NOT NULL DEFAULT '0' COMMENT 'comment status(available: 0; deleted: 10)',
`original_text` mediumtext CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL COMMENT 'original comment content',
`parsed_text` mediumtext CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL COMMENT 'parsed comment content'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='comment';
-- --------------------------------------------------------
--
-- 表的结构 `config`
--
CREATE TABLE `config` (
`id` int(11) NOT NULL COMMENT 'config id',
`key` varchar(32) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT 'the config key',
`value` text COLLATE utf8mb4_unicode_ci COMMENT 'the config value, custom data structures and types'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='config';
-- --------------------------------------------------------
--
-- 表的结构 `meta`
--
CREATE TABLE `meta` (
`id` int(10) UNSIGNED NOT NULL COMMENT 'id',
`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'created time',
`updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 'updated time',
`object_id` bigint(20) NOT NULL DEFAULT '0' COMMENT 'object id',
`key` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL COMMENT 'key',
`value` mediumtext CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL COMMENT 'value'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='meta';
-- --------------------------------------------------------
--
-- 表的结构 `notification`
--
CREATE TABLE `notification` (
`id` bigint(20) NOT NULL COMMENT 'notification id',
`user_id` bigint(20) NOT NULL DEFAULT '0' COMMENT 'user_id',
`object_id` bigint(20) NOT NULL DEFAULT '0' COMMENT 'object id',
`content` text NOT NULL COMMENT 'notification content',
`type` int(11) NOT NULL DEFAULT '0' COMMENT '1 inbox 2 achievements',
`is_read` int(11) NOT NULL DEFAULT '1' COMMENT 'read status(unread: 1; read 2)',
`status` int(11) NOT NULL DEFAULT '1' COMMENT 'notification status(normal: 1; delete 2)',
`created_at` timestamp NULL DEFAULT NULL COMMENT 'create time',
`updated_at` timestamp NULL DEFAULT NULL COMMENT 'update time'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='notification';
-- --------------------------------------------------------
--
-- 表的结构 `notification_read`
--
CREATE TABLE `notification_read` (
`id` int(11) NOT NULL COMMENT 'id',
`created_at` timestamp NULL DEFAULT NULL COMMENT 'create time',
`updated_at` timestamp NULL DEFAULT NULL COMMENT 'update time',
`user_id` bigint(20) NOT NULL DEFAULT '0' COMMENT 'user id',
`message_id` bigint(20) NOT NULL DEFAULT '0' COMMENT 'message id',
`is_read` int(11) NOT NULL DEFAULT '1' COMMENT 'read status(unread: 1; read 2)'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='notification read record';
-- --------------------------------------------------------
--
-- 表的结构 `question`
--
CREATE TABLE `question` (
`id` bigint(20) NOT NULL COMMENT 'question id',
`user_id` bigint(20) NOT NULL DEFAULT '0' COMMENT 'user id',
`title` varchar(255) NOT NULL DEFAULT '' COMMENT 'question title',
`original_text` mediumtext NOT NULL COMMENT 'original text',
`parsed_text` mediumtext NOT NULL COMMENT 'parsed text',
`status` int(11) NOT NULL DEFAULT '1' COMMENT ' question status(available: 1; deleted: 10)',
`view_count` int(11) NOT NULL DEFAULT '0' COMMENT 'view count',
`unique_view_count` int(11) NOT NULL DEFAULT '0' COMMENT 'unique view count',
`vote_count` int(11) NOT NULL DEFAULT '0' COMMENT 'vote count',
`answer_count` int(11) NOT NULL DEFAULT '0' COMMENT 'answer count',
`collection_count` int(11) NOT NULL DEFAULT '0' COMMENT 'collection count',
`follow_count` int(11) NOT NULL DEFAULT '0' COMMENT 'follow count',
`accepted_answer_id` bigint(20) NOT NULL DEFAULT '0' COMMENT 'accepted answer id',
`last_answer_id` bigint(20) NOT NULL DEFAULT '0' COMMENT 'last answer id',
`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'create time',
`updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'update time',
`post_update_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'answer the last update time',
`revision_id` bigint(20) NOT NULL DEFAULT '0' COMMENT 'revision id'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='question';
-- --------------------------------------------------------
--
-- 表的结构 `report`
--
CREATE TABLE `report` (
`id` bigint(20) NOT NULL COMMENT 'id',
`created_at` timestamp NULL DEFAULT NULL COMMENT 'create time',
`updated_at` timestamp NULL DEFAULT NULL COMMENT 'update time',
`user_id` bigint(20) NOT NULL COMMENT 'reporter user id',
`object_id` bigint(20) NOT NULL COMMENT 'object id',
`reported_user_id` bigint(20) NOT NULL DEFAULT '0' COMMENT 'reported user id',
`object_type` int(11) NOT NULL DEFAULT '0' COMMENT 'revision type',
`report_type` int(11) NOT NULL DEFAULT '0' COMMENT 'report type',
`content` text NOT NULL COMMENT 'report content',
`flaged_type` int(11) NOT NULL DEFAULT '0',
`flaged_content` text,
`status` int(11) NOT NULL DEFAULT '1' COMMENT 'status(normal: 1; pending:2; delete: 10)'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='report';
-- --------------------------------------------------------
--
-- 表的结构 `revision`
--
CREATE TABLE `revision` (
`id` bigint(20) NOT NULL COMMENT 'id',
`created_at` timestamp NULL DEFAULT NULL COMMENT 'create time',
`updated_at` timestamp NULL DEFAULT NULL COMMENT 'update time',
`user_id` bigint(20) NOT NULL DEFAULT '0' COMMENT 'user id',
`object_type` int(11) NOT NULL DEFAULT '0' COMMENT 'revision type(question: 1; answer 2)',
`object_id` bigint(20) NOT NULL DEFAULT '0' COMMENT 'object id',
`title` varchar(255) NOT NULL DEFAULT '' COMMENT 'title',
`content` text NOT NULL COMMENT 'content',
`log` varchar(255) DEFAULT NULL,
`status` int(11) NOT NULL DEFAULT '1' COMMENT 'revision status(normal: 1; delete 2)'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='revision';
-- --------------------------------------------------------
--
-- 表的结构 `site_info`
--
CREATE TABLE `site_info` (
`id` int(10) UNSIGNED NOT NULL,
`created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'create time',
`updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'update time',
`type` varchar(64) DEFAULT NULL,
`content` mediumtext,
`status` int(11) NOT NULL DEFAULT '1'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- --------------------------------------------------------
--
-- 表的结构 `tag`
--
CREATE TABLE `tag` (
`id` bigint(20) NOT NULL COMMENT 'tag_id',
`created_at` timestamp NULL DEFAULT NULL COMMENT 'create time',
`updated_at` timestamp NULL DEFAULT NULL COMMENT 'update time',
`main_tag_id` bigint(20) NOT NULL DEFAULT '0' COMMENT 'main tag id',
`main_tag_slug_name` varchar(50) NOT NULL DEFAULT '' COMMENT 'main tag slug name',
`slug_name` varchar(50) NOT NULL DEFAULT '' COMMENT 'slug name',
`display_name` varchar(50) NOT NULL DEFAULT '' COMMENT 'display name',
`original_text` mediumtext CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL COMMENT 'original comment content',
`parsed_text` mediumtext CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL COMMENT 'parsed comment content',
`follow_count` int(11) NOT NULL DEFAULT '0' COMMENT 'follow count',
`question_count` int(11) NOT NULL COMMENT 'question count',
`status` int(11) NOT NULL DEFAULT '1' COMMENT 'tag status(available: 1; deleted: 10)',
`revision_id` bigint(20) NOT NULL DEFAULT '0' COMMENT 'revision id'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='tag';
--
-- 转存表中的数据 `tag`
--
INSERT INTO `tag` (`id`, `created_at`, `updated_at`, `main_tag_id`, `main_tag_slug_name`, `slug_name`, `display_name`, `original_text`, `parsed_text`, `follow_count`, `question_count`, `status`, `revision_id`) VALUES
(10030000000000007, '2022-09-07 02:07:16', '2022-09-28 06:56:32', 10030000000000364, 'javascript', 'js', 'Js', 'js', 'Js', 7, 21, 1, 90),
(10030000000000008, '2022-09-07 02:07:16', '2022-09-19 08:40:36', 0, '', 'php', 'PHP', '2122121对外只有3001一个端口群集内有负载策略会分发给不同实例2121211212121212121', '<p>2122121对外只有3001一个端口群集内有负载策略会分发给不同实例2121211212121212121</p>\n', 2, 3, 10, 101),
(10030000000000009, '2022-09-07 02:07:16', '2022-09-28 08:00:48', 0, '', 'go', 'Go', 'abcbcsasasasasa', '<p>abcbcsasasasasa</p>\n', 7, 17, 1, 259),
(10030000000000010, '2022-09-07 02:07:16', '2022-09-28 05:16:00', 0, '', 'apple', 'Apple', '对外只有3001一个端口群集内有负载策略会分发给不同实例', '<p>对外只有3001一个端口群集内有负载策略会分发给不同实例</p>', 5, 3, 1, 0),
(10030000000000113, '2022-09-07 02:07:16', '2022-09-26 08:44:04', 0, '', 'lua', 'lua', '对外只有3001一个端口群集内有负载策略会分发给不同实例', '<p>对外只有3001一个端口群集内有负载策略会分发给不同实例</p>', 4, 4, 1, 0),
(10030000000000114, '2022-09-07 02:07:16', '2022-09-27 09:57:35', 0, '', 'dell', 'dell', '对外只有3001一个端口群集内有负载策略会分发给不同实例', '<p>对外只有3001一个端口群集内有负载策略会分发给不同实例</p>', 3, 0, 1, 0),
(10030000000000118, '2022-09-07 02:07:16', '2022-09-28 08:20:13', 0, '', 'dells', 'dells', '对外只有3001一个端口群集内有负载策略会分发给不同实例', '<p>对外只有3001一个端口群集内有负载策略会分发给不同实例</p>', 2, 2, 1, 0),
(10030000000000183, '2022-09-07 02:07:16', '2022-09-27 08:41:31', 0, '', 'string', 'string', '对外只有3001一个端口群集内有负载策略会分发给不同实例\n\n好不错', '<p>对外只有3001一个端口群集内有负载策略会分发给不同实例</p>\n<p>好不错</p>\n', 2, 22, 1, 186),
(10030000000000311, '2022-09-07 02:07:16', '2022-09-28 07:59:36', 10030000000000009, 'go', 'golang', 'golang', '', '', 4, 6, 1, 0),
(10030000000000312, '2022-09-07 02:12:06', '2022-09-15 08:56:04', 0, '', '算法', '算法', '', '', 1, 0, 1, 0),
(10030000000000314, '2022-09-07 02:14:08', '2022-09-26 06:54:36', 0, '', 'java', 'java', '', '', 1, 6, 1, 0),
(10030000000000316, '2022-09-07 02:14:28', '2022-09-28 08:20:13', 10030000000000009, 'go', 'golang2', 'golang2', 'golang2', 'golang2', 1, 2, 1, 28),
(10030000000000324, '2022-09-07 07:14:19', '2022-09-20 03:27:14', 0, '', 'python', 'python', '', '', 0, 2, 1, 41),
(10030000000000325, '2022-09-07 07:17:52', '2022-09-27 08:41:31', 0, '', 'spring boot', 'spring boot', '', '', 2, 2, 1, 43),
(10030000000000364, '2022-09-08 01:45:26', '2022-09-27 08:41:31', 0, '', 'javascript', 'javascript', 'JavaScript (JS) is a lightweight, interpreted, or just-in-time compiled programming language with first-class functions. While it is most well-known as the scripting language for Web pages, many non-browser environments also use it, such as Node.js, Apache CouchDB and Adobe Acrobat.\n\nJavaScript is a prototype-based, multi-paradigm, single-threaded, dynamic language, supporting object-oriented, imperative, and declarative (e.g. functional programming) styles. Read more about JavaScript. This section is dedicated to the JavaScript language itself, and not the parts that are specific to Web pages or other host environments. For information about APIs that are specific to Web pages, please see Web APIs and DOM.', '<p>JavaScript (JS) is a lightweight, interpreted, or just-in-time compiled programming language with first-class functions. While it is most well-known as the scripting language for Web pages, many non-browser environments also use it, such as Node.js, Apache CouchDB and Adobe Acrobat.</p>\n<p>JavaScript is a prototype-based, multi-paradigm, single-threaded, dynamic language, supporting object-oriented, imperative, and declarative (e.g. functional programming) styles. Read more about JavaScript. This section is dedicated to the JavaScript language itself, and not the parts that are specific to Web pages or other host environments. For information about APIs that are specific to Web pages, please see Web APIs and DOM.</p>\n', 3, 3, 1, 292),
(10030000000000401, '2022-09-08 07:26:38', '2022-09-28 08:20:13', 0, '', 'test', 'test', '111111\ngjsdghjfghjgjfhgasjdhgfhjasg[hello](https://www.baidu.com)\n\n22222222222\n\n3333333\n\n', '<p>111111\ngjsdghjfghjgjfhgasjdhgfhjasg<a href=\"https://www.baidu.com\">hello</a></p>\n<p>22222222222</p>\n<p>3333333</p>\n', 1, 3, 1, 303),
(10030000000000408, '2022-09-08 08:38:03', '2022-09-08 08:38:03', 0, '', 'code ', 'code ', 'code is code', '<p>code is code</p>\n', 0, 0, 1, 70),
(10030000000000428, '2022-09-08 11:44:10', '2022-09-26 06:54:31', 0, '', 'abc', 'abc', 'JavaScript 是一门弱类型的动态脚本语言,支持多种编程范式,包括面向对象和函数式编程,被广泛用于 Web 开发。\n\n一般来说完整的JavaScript包括以下几个部分\n- ECMAScript描述了该语言的语法和基本对象\n- 文档对象模型DOM描述处理网页内容的方法和接口\n- 浏览器对象模型BOM描述与浏览器进行交互的方法和接口\n\n它的基本特点如下\n- 是一种解释性脚本语言(代码不进行预编译)。\n- 主要用来向HTML页面添加交互行为。\n- 可以直接嵌入HTML页面但写成单独的js文件有利于结构和行为的分离。\n\nJavaScript常用来完成以下任务\n- 嵌入动态文本于HTML页面\n- 对浏览器事件作出响应\n- 读写HTML元素\n- 在数据被提交到服务器之前验证数据\n- 检测访客的浏览器信息\n\n![《 Javascript 优点在整个语言中占多大比例?][1]\n\n [1]: http://segmentfault.com/img/bVFXU', '<p>JavaScript 是一门弱类型的动态脚本语言,支持多种编程范式,包括面向对象和函数式编程,被广泛用于 Web 开发。</p>\n<p>一般来说完整的JavaScript包括以下几个部分</p>\n<ul>\n<li>ECMAScript描述了该语言的语法和基本对象</li>\n<li>文档对象模型DOM描述处理网页内容的方法和接口</li>\n<li>浏览器对象模型BOM描述与浏览器进行交互的方法和接口</li>\n</ul>\n<p>它的基本特点如下:</p>\n<ul>\n<li>是一种解释性脚本语言(代码不进行预编译)。</li>\n<li>主要用来向HTML页面添加交互行为。</li>\n<li>可以直接嵌入HTML页面但写成单独的js文件有利于结构和行为的分离。</li>\n</ul>\n<p>JavaScript常用来完成以下任务</p>\n<ul>\n<li>嵌入动态文本于HTML页面</li>\n<li>对浏览器事件作出响应</li>\n<li>读写HTML元素</li>\n<li>在数据被提交到服务器之前验证数据</li>\n<li>检测访客的浏览器信息</li>\n</ul>\n<p><img src=\"http://segmentfault.com/img/bVFXU\" alt=\"《 Javascript 优点在整个语言中占多大比例?\"></p>\n', 0, 0, 1, 113),
(10030000000000507, '2022-09-16 06:32:36', '2022-09-16 08:14:27', 0, '', 'dddddd', 'dddddd', 'ddddddddddddddddd', 'ddddddddddddddddddd', 0, 1, 1, 144),
(10030000000000510, '2022-09-16 07:04:34', '2022-09-16 07:30:43', 0, '', 'vim', 'vim', 'vim', '<p>vim</p>\n', 0, 0, 1, 147),
(10030000000000592, '2022-09-20 10:23:28', '2022-09-27 08:41:31', 0, '', 'android-studio', 'Android-Studio', '', '', 3, 2, 1, 170),
(10030000000000644, '2022-09-23 09:25:39', '2022-09-27 08:41:31', 0, '', 'great', 'great', 'great', '<p>great</p>\n', 2, 1, 1, 272),
(10030000000000687, '2022-09-26 10:48:07', '2022-09-28 01:39:19', 0, '', 'qwer', 'qwerDis', 'qwert', '<p>qwert</p>\n', 0, 0, 1, 316),
(10030000000000688, '2022-09-26 10:49:39', '2022-09-28 08:20:13', 0, '', 'qwers', 'qwerDiss', 'qwerts', 'qwerts', 0, 1, 1, 300),
(10030000000000689, '2022-09-26 10:50:34', '2022-09-28 08:20:13', 10030000000000364, 'javascript', 'jss', '', '', '', 0, 0, 1, 302),
(10030000000000708, '2022-09-27 02:12:03', '2022-09-28 05:00:51', 0, '', 'flutter', 'Flutter', '', '<p>212121212121</p>\n', 3, 1, 1, 305),
(10030000000000712, '2022-09-27 02:32:53', '2022-09-27 10:09:37', 0, '', 'nextjs', 'Nextjs', '', '<p>2121212121</p>\n', 1, 1, 1, 307),
(10030000000000713, '2022-09-27 02:36:29', '2022-09-27 08:41:31', 0, '', 'umijs', 'Umijs', '212121212112121211', '<p>212121212112121211</p>\n', 1, 1, 1, 309),
(10030000000000739, '2022-09-28 01:45:09', '2022-09-28 01:45:18', 0, '', '', '', '', '', 0, 0, 1, 320);
-- --------------------------------------------------------
--
-- 表的结构 `tag_rel`
--
CREATE TABLE `tag_rel` (
`id` bigint(20) NOT NULL COMMENT 'tag_list_id',
`created_at` timestamp NULL DEFAULT NULL COMMENT 'create time',
`updated_at` timestamp NULL DEFAULT NULL COMMENT 'update time',
`object_id` bigint(20) NOT NULL COMMENT 'object_id',
`tag_id` bigint(20) NOT NULL COMMENT 'tag_id',
`status` int(11) NOT NULL DEFAULT '1' COMMENT 'tag_list_status(available: 1; deleted: 10)'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='tag relation';
-- --------------------------------------------------------
--
-- 表的结构 `uniqid`
--
CREATE TABLE `uniqid` (
`id` bigint(20) NOT NULL COMMENT 'uniqid_id',
`uniqid_type` int(11) NOT NULL DEFAULT '0' COMMENT 'uniqid_type'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='uniqid';
-- --------------------------------------------------------
--
-- 表的结构 `user`
--
CREATE TABLE `user` (
`id` bigint(20) NOT NULL COMMENT 'user id',
`created_at` timestamp NULL DEFAULT NULL COMMENT 'create time',
`updated_at` timestamp NULL DEFAULT NULL COMMENT 'update time',
`suspended_at` timestamp NULL DEFAULT NULL COMMENT 'suspended time',
`deleted_at` timestamp NULL DEFAULT NULL COMMENT 'delete time',
`last_login_date` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'last login date',
`username` varchar(50) NOT NULL DEFAULT '' COMMENT 'username',
`pass` varchar(255) NOT NULL DEFAULT '' COMMENT 'password',
`e_mail` varchar(100) NOT NULL COMMENT 'email',
`mail_status` tinyint(4) NOT NULL DEFAULT '2' COMMENT 'mail status(1 pass 2 to be verified)',
`notice_status` int(11) NOT NULL DEFAULT '2' COMMENT 'notice status(1 on 2off)',
`follow_count` int(11) NOT NULL DEFAULT '0' COMMENT 'follow count',
`answer_count` int(11) NOT NULL DEFAULT '0' COMMENT 'answer_count',
`question_count` int(11) NOT NULL DEFAULT '0' COMMENT 'question_count',
`rank` int(11) NOT NULL DEFAULT '0' COMMENT 'rank',
`status` int(11) NOT NULL DEFAULT '1' COMMENT 'user status(available: 0; deleted: 10)',
`authority_group` int(11) NOT NULL DEFAULT '1' COMMENT 'authority group',
`display_name` varchar(50) NOT NULL DEFAULT '' COMMENT 'display name',
`avatar` varchar(255) NOT NULL DEFAULT '' COMMENT 'avatar',
`mobile` varchar(20) NOT NULL COMMENT 'mobile',
`bio` text NOT NULL COMMENT 'bio markdown',
`bio_html` text NOT NULL COMMENT 'bio html',
`website` varchar(255) NOT NULL DEFAULT '' COMMENT 'website',
`location` varchar(100) NOT NULL DEFAULT '' COMMENT 'location',
`ip_info` varchar(255) NOT NULL DEFAULT '' COMMENT 'ip info',
`is_admin` int(11) NOT NULL COMMENT 'admin flag'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='user';
-- --------------------------------------------------------
--
-- 表的结构 `user_group`
--
CREATE TABLE `user_group` (
`id` bigint(20) UNSIGNED NOT NULL COMMENT 'user group id'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='user group';
--
-- 转储表的索引
--
--
-- 表的索引 `activity`
--
ALTER TABLE `activity`
ADD PRIMARY KEY (`id`) USING BTREE,
ADD KEY `post_id` (`object_id`),
ADD KEY `user_id` (`user_id`),
ADD KEY `trigger_user_id` (`trigger_user_id`);
--
-- 表的索引 `answer`
--
ALTER TABLE `answer`
ADD PRIMARY KEY (`id`);
--
-- 表的索引 `collection`
--
ALTER TABLE `collection`
ADD PRIMARY KEY (`id`);
--
-- 表的索引 `collection_group`
--
ALTER TABLE `collection_group`
ADD PRIMARY KEY (`id`);
--
-- 表的索引 `comment`
--
ALTER TABLE `comment`
ADD PRIMARY KEY (`id`) USING BTREE;
--
-- 表的索引 `config`
--
ALTER TABLE `config`
ADD PRIMARY KEY (`id`),
ADD UNIQUE KEY `key` (`key`);
--
-- 表的索引 `meta`
--
ALTER TABLE `meta`
ADD PRIMARY KEY (`id`);
--
-- 表的索引 `notification`
--
ALTER TABLE `notification`
ADD PRIMARY KEY (`id`) USING BTREE,
ADD KEY `idx_objectid` (`object_id`);
--
-- 表的索引 `notification_read`
--
ALTER TABLE `notification_read`
ADD PRIMARY KEY (`id`) USING BTREE;
--
-- 表的索引 `question`
--
ALTER TABLE `question`
ADD PRIMARY KEY (`id`);
--
-- 表的索引 `report`
--
ALTER TABLE `report`
ADD PRIMARY KEY (`id`) USING BTREE;
--
-- 表的索引 `revision`
--
ALTER TABLE `revision`
ADD PRIMARY KEY (`id`) USING BTREE;
--
-- 表的索引 `site_info`
--
ALTER TABLE `site_info`
ADD PRIMARY KEY (`id`);
--
-- 表的索引 `tag`
--
ALTER TABLE `tag`
ADD PRIMARY KEY (`id`) USING BTREE,
ADD UNIQUE KEY `slug_name` (`slug_name`);
--
-- 表的索引 `tag_rel`
--
ALTER TABLE `tag_rel`
ADD PRIMARY KEY (`id`) USING BTREE,
ADD UNIQUE KEY `idx_obj_tag_id` (`object_id`,`tag_id`) USING BTREE,
ADD KEY `idx_questionid` (`object_id`),
ADD KEY `idx_tagid` (`tag_id`) USING BTREE;
--
-- 表的索引 `uniqid`
--
ALTER TABLE `uniqid`
ADD PRIMARY KEY (`id`);
--
-- 表的索引 `user`
--
ALTER TABLE `user`
ADD PRIMARY KEY (`id`) USING BTREE,
ADD UNIQUE KEY `username` (`username`);
--
-- 表的索引 `user_group`
--
ALTER TABLE `user_group`
ADD PRIMARY KEY (`id`),
ADD UNIQUE KEY `id` (`id`);
--
-- 在导出的表使用AUTO_INCREMENT
--
--
-- 使用表AUTO_INCREMENT `activity`
--
ALTER TABLE `activity`
MODIFY `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'Activity ID autoincrement';
--
-- 使用表AUTO_INCREMENT `answer`
--
ALTER TABLE `answer`
MODIFY `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'answer id';
--
-- 使用表AUTO_INCREMENT `collection_group`
--
ALTER TABLE `collection_group`
MODIFY `id` bigint(20) NOT NULL AUTO_INCREMENT;
--
-- 使用表AUTO_INCREMENT `comment`
--
ALTER TABLE `comment`
MODIFY `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'comment id';
--
-- 使用表AUTO_INCREMENT `config`
--
ALTER TABLE `config`
MODIFY `id` int(11) NOT NULL AUTO_INCREMENT COMMENT 'config id';
--
-- 使用表AUTO_INCREMENT `meta`
--
ALTER TABLE `meta`
MODIFY `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT 'id';
--
-- 使用表AUTO_INCREMENT `notification`
--
ALTER TABLE `notification`
MODIFY `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'notification id';
--
-- 使用表AUTO_INCREMENT `notification_read`
--
ALTER TABLE `notification_read`
MODIFY `id` int(11) NOT NULL AUTO_INCREMENT COMMENT 'id';
--
-- 使用表AUTO_INCREMENT `report`
--
ALTER TABLE `report`
MODIFY `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'id';
--
-- 使用表AUTO_INCREMENT `revision`
--
ALTER TABLE `revision`
MODIFY `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'id';
--
-- 使用表AUTO_INCREMENT `site_info`
--
ALTER TABLE `site_info`
MODIFY `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT;
--
-- 使用表AUTO_INCREMENT `tag_rel`
--
ALTER TABLE `tag_rel`
MODIFY `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'tag_list_id';
--
-- 使用表AUTO_INCREMENT `uniqid`
--
ALTER TABLE `uniqid`
MODIFY `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'uniqid_id';
--
-- 使用表AUTO_INCREMENT `user`
--
ALTER TABLE `user`
MODIFY `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'user id';
--
-- 使用表AUTO_INCREMENT `user_group`
--
ALTER TABLE `user_group`
MODIFY `id` bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT 'user group id';
COMMIT;

6
assets/assets.go Normal file
View File

@ -0,0 +1,6 @@
package assets
import _ "embed"
//go:embed answer.sql
var AnswerSql []byte

View File

@ -6,6 +6,7 @@ import (
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/segmentfault/answer/internal/base/conf" "github.com/segmentfault/answer/internal/base/conf"
"github.com/segmentfault/answer/internal/cli"
"github.com/segmentfault/pacman" "github.com/segmentfault/pacman"
"github.com/segmentfault/pacman/contrib/conf/viper" "github.com/segmentfault/pacman/contrib/conf/viper"
"github.com/segmentfault/pacman/contrib/log/zap" "github.com/segmentfault/pacman/contrib/log/zap"
@ -37,6 +38,24 @@ func init() {
func main() { func main() {
flag.Parse() flag.Parse()
args := flag.Args()
if len(args) < 1 {
cli.Usage()
os.Exit(0)
return
}
if args[0] == "init" {
cli.InitConfig()
return
}
if len(args) >= 3 {
if args[0] == "run" && args[1] == "-c" {
confFlag = args[2]
}
}
log.SetLogger(zap.NewLogger( log.SetLogger(zap.NewLogger(
log.ParseLevel(logLevel), zap.WithName(Name), zap.WithPath(logPath), zap.WithCallerFullPath())) log.ParseLevel(logLevel), zap.WithName(Name), zap.WithPath(logPath), zap.WithCallerFullPath()))
@ -55,13 +74,18 @@ func main() {
if err != nil { if err != nil {
panic(err) panic(err)
} }
err = cli.CommandCli.InitDB()
if err != nil {
panic(err)
}
defer cleanup() defer cleanup()
if err := app.Run(); err != nil { if err := app.Run(); err != nil {
panic(err) panic(err)
} }
} }
func newApplication(serverConf *conf.Server, server *gin.Engine) *pacman.Application { func newApplication(serverConf *conf.Server, server *gin.Engine, cli *cli.Cli) *pacman.Application {
return pacman.NewApp( return pacman.NewApp(
pacman.WithName(Name), pacman.WithName(Name),
pacman.WithVersion(Version), pacman.WithVersion(Version),

View File

@ -12,6 +12,7 @@ import (
"github.com/segmentfault/answer/internal/base/middleware" "github.com/segmentfault/answer/internal/base/middleware"
"github.com/segmentfault/answer/internal/base/server" "github.com/segmentfault/answer/internal/base/server"
"github.com/segmentfault/answer/internal/base/translator" "github.com/segmentfault/answer/internal/base/translator"
"github.com/segmentfault/answer/internal/cli"
"github.com/segmentfault/answer/internal/controller" "github.com/segmentfault/answer/internal/controller"
"github.com/segmentfault/answer/internal/controller_backyard" "github.com/segmentfault/answer/internal/controller_backyard"
"github.com/segmentfault/answer/internal/repo" "github.com/segmentfault/answer/internal/repo"
@ -33,6 +34,7 @@ func initApplication(
serviceConf *service_config.ServiceConfig, serviceConf *service_config.ServiceConfig,
logConf log.Logger) (*pacman.Application, func(), error) { logConf log.Logger) (*pacman.Application, func(), error) {
panic(wire.Build( panic(wire.Build(
cli.ProviderSetCli,
server.ProviderSetServer, server.ProviderSetServer,
router.ProviderSetRouter, router.ProviderSetRouter,
controller.ProviderSetController, controller.ProviderSetController,

View File

@ -12,6 +12,7 @@ import (
"github.com/segmentfault/answer/internal/base/middleware" "github.com/segmentfault/answer/internal/base/middleware"
"github.com/segmentfault/answer/internal/base/server" "github.com/segmentfault/answer/internal/base/server"
"github.com/segmentfault/answer/internal/base/translator" "github.com/segmentfault/answer/internal/base/translator"
"github.com/segmentfault/answer/internal/cli"
"github.com/segmentfault/answer/internal/controller" "github.com/segmentfault/answer/internal/controller"
"github.com/segmentfault/answer/internal/controller_backyard" "github.com/segmentfault/answer/internal/controller_backyard"
"github.com/segmentfault/answer/internal/repo" "github.com/segmentfault/answer/internal/repo"
@ -101,13 +102,14 @@ func initApplication(debug bool, serverConf *conf.Server, dbConf *data.Database,
userController := controller.NewUserController(authService, userService, captchaService, emailService, uploaderService) userController := controller.NewUserController(authService, userService, captchaService, emailService, uploaderService)
commentRepo := comment.NewCommentRepo(dataData, uniqueIDRepo) commentRepo := comment.NewCommentRepo(dataData, uniqueIDRepo)
commentCommonRepo := comment.NewCommentCommonRepo(dataData, uniqueIDRepo) commentCommonRepo := comment.NewCommentCommonRepo(dataData, uniqueIDRepo)
userCommon := usercommon.NewUserCommon(userRepo)
answerRepo := repo.NewAnswerRepo(dataData, uniqueIDRepo, userRankRepo, activityRepo) answerRepo := repo.NewAnswerRepo(dataData, uniqueIDRepo, userRankRepo, activityRepo)
questionRepo := repo.NewQuestionRepo(dataData, uniqueIDRepo) questionRepo := repo.NewQuestionRepo(dataData, uniqueIDRepo)
tagRepo := tag.NewTagRepo(dataData, uniqueIDRepo) tagRepo := tag.NewTagRepo(dataData, uniqueIDRepo)
objService := object_info.NewObjService(answerRepo, questionRepo, commentCommonRepo, tagRepo) objService := object_info.NewObjService(answerRepo, questionRepo, commentCommonRepo, tagRepo)
voteRepo := activity_common.NewVoteRepo(dataData, activityRepo) voteRepo := activity_common.NewVoteRepo(dataData, activityRepo)
commentService := comment2.NewCommentService(commentRepo, commentCommonRepo, userRepo, objService, voteRepo) commentService := comment2.NewCommentService(commentRepo, commentCommonRepo, userCommon, objService, voteRepo)
rankService := rank2.NewRankService(userRepo, userRankRepo, objService, configRepo) rankService := rank2.NewRankService(userCommon, userRankRepo, objService, configRepo)
commentController := controller.NewCommentController(commentService, rankService) commentController := controller.NewCommentController(commentService, rankService)
reportRepo := report.NewReportRepo(dataData, uniqueIDRepo) reportRepo := report.NewReportRepo(dataData, uniqueIDRepo)
reportService := report2.NewReportService(reportRepo, objService) reportService := report2.NewReportService(reportRepo, objService)
@ -127,7 +129,6 @@ func initApplication(debug bool, serverConf *conf.Server, dbConf *data.Database,
collectionGroupRepo := collection.NewCollectionGroupRepo(dataData) collectionGroupRepo := collection.NewCollectionGroupRepo(dataData)
tagRelRepo := tag.NewTagListRepo(dataData) tagRelRepo := tag.NewTagListRepo(dataData)
tagCommonService := tagcommon.NewTagCommonService(tagRepo, tagRelRepo, revisionService) tagCommonService := tagcommon.NewTagCommonService(tagRepo, tagRelRepo, revisionService)
userCommon := usercommon.NewUserCommon(userRepo)
collectionCommon := collectioncommon.NewCollectionCommon(collectionRepo) collectionCommon := collectioncommon.NewCollectionCommon(collectionRepo)
answerCommon := answercommon.NewAnswerCommon(answerRepo) answerCommon := answercommon.NewAnswerCommon(answerRepo)
metaRepo := meta.NewMetaRepo(dataData) metaRepo := meta.NewMetaRepo(dataData)
@ -142,10 +143,10 @@ func initApplication(debug bool, serverConf *conf.Server, dbConf *data.Database,
questionController := controller.NewQuestionController(questionService, rankService) questionController := controller.NewQuestionController(questionService, rankService)
answerService := service.NewAnswerService(answerRepo, questionRepo, questionCommon, userCommon, collectionCommon, userRepo, revisionService, answerActivityService, answerCommon, voteRepo) answerService := service.NewAnswerService(answerRepo, questionRepo, questionCommon, userCommon, collectionCommon, userRepo, revisionService, answerActivityService, answerCommon, voteRepo)
answerController := controller.NewAnswerController(answerService, rankService) answerController := controller.NewAnswerController(answerService, rankService)
searchRepo := repo.NewSearchRepo(dataData, uniqueIDRepo, userRepo) searchRepo := repo.NewSearchRepo(dataData, uniqueIDRepo, userCommon)
searchService := service.NewSearchService(searchRepo, tagRepo, userRepo, followRepo) searchService := service.NewSearchService(searchRepo, tagRepo, userCommon, followRepo)
searchController := controller.NewSearchController(searchService) searchController := controller.NewSearchController(searchService)
serviceRevisionService := service.NewRevisionService(revisionRepo, userRepo, questionCommon, answerService) serviceRevisionService := service.NewRevisionService(revisionRepo, userCommon, questionCommon, answerService)
revisionController := controller.NewRevisionController(serviceRevisionService) revisionController := controller.NewRevisionController(serviceRevisionService)
rankController := controller.NewRankController(rankService) rankController := controller.NewRankController(rankService)
commonRepo := common.NewCommonRepo(dataData, uniqueIDRepo) commonRepo := common.NewCommonRepo(dataData, uniqueIDRepo)
@ -169,10 +170,11 @@ func initApplication(debug bool, serverConf *conf.Server, dbConf *data.Database,
notificationController := controller.NewNotificationController(notificationService) notificationController := controller.NewNotificationController(notificationService)
answerAPIRouter := router.NewAnswerAPIRouter(langController, userController, commentController, reportController, voteController, tagController, followController, collectionController, questionController, answerController, searchController, revisionController, rankController, controller_backyardReportController, userBackyardController, reasonController, themeController, siteInfoController, siteinfoController, notificationController) answerAPIRouter := router.NewAnswerAPIRouter(langController, userController, commentController, reportController, voteController, tagController, followController, collectionController, questionController, answerController, searchController, revisionController, rankController, controller_backyardReportController, userBackyardController, reasonController, themeController, siteInfoController, siteinfoController, notificationController)
swaggerRouter := router.NewSwaggerRouter(swaggerConf) swaggerRouter := router.NewSwaggerRouter(swaggerConf)
viewRouter := router.NewViewRouter() uiRouter := router.NewUIRouter()
authUserMiddleware := middleware.NewAuthUserMiddleware(authService) authUserMiddleware := middleware.NewAuthUserMiddleware(authService)
ginEngine := server.NewHTTPServer(debug, staticRouter, answerAPIRouter, swaggerRouter, viewRouter, authUserMiddleware) ginEngine := server.NewHTTPServer(debug, staticRouter, answerAPIRouter, swaggerRouter, uiRouter, authUserMiddleware)
application := newApplication(serverConf, ginEngine) cliCli := cli.NewCli(dataData)
application := newApplication(serverConf, ginEngine, cliCli)
return application, func() { return application, func() {
cleanup2() cleanup2()
cleanup() cleanup()

6
configs/config.go Normal file
View File

@ -0,0 +1,6 @@
package configs
import _ "embed"
//go:embed config.yaml
var Config []byte

19
configs/config.yaml Normal file
View File

@ -0,0 +1,19 @@
server:
http:
addr: 0.0.0.0:80
data:
database:
connection: root:root@tcp(127.0.0.1:3306)/answer
cache:
file_path: "/tmp/cache/cache.db"
i18n:
bundle_dir: "/data/i18n"
swaggerui:
show: true
protocol: http
host: 127.0.0.1
address: ':80'
service_config:
secret_key: "answer"
web_host: "http://127.0.0.1"
upload_path: "./upfiles"

View File

@ -4,4 +4,6 @@ services:
build: build:
context: . context: .
image: github.com/segmentfault/answer image: github.com/segmentfault/answer
volumes:
- ./data:/data
restart: on-failure restart: on-failure

View File

@ -884,7 +884,7 @@ const docTemplate = `{
} }
}, },
"/answer/api/v1/answer/list": { "/answer/api/v1/answer/list": {
"post": { "get": {
"security": [ "security": [
{ {
"ApiKeyAuth": [] "ApiKeyAuth": []
@ -3614,6 +3614,46 @@ const docTemplate = `{
} }
} }
}, },
"/answer/api/v1/user/status": {
"get": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "get user status info",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"User"
],
"summary": "get user status info",
"responses": {
"200": {
"description": "OK",
"schema": {
"allOf": [
{
"$ref": "#/definitions/handler.RespBody"
},
{
"type": "object",
"properties": {
"data": {
"$ref": "#/definitions/schema.GetUserResp"
}
}
}
]
}
}
}
}
},
"/answer/api/v1/vote/down": { "/answer/api/v1/vote/down": {
"post": { "post": {
"security": [ "security": [
@ -4206,6 +4246,10 @@ const docTemplate = `{
"description": "reply user id", "description": "reply user id",
"type": "string" "type": "string"
}, },
"reply_user_status": {
"description": "reply user status",
"type": "string"
},
"reply_username": { "reply_username": {
"description": "reply user username", "description": "reply user username",
"type": "string" "type": "string"
@ -4222,6 +4266,10 @@ const docTemplate = `{
"description": "user id", "description": "user id",
"type": "string" "type": "string"
}, },
"user_status": {
"description": "user status",
"type": "string"
},
"username": { "username": {
"description": "username", "description": "username",
"type": "string" "type": "string"
@ -5464,7 +5512,7 @@ const docTemplate = `{
}, },
"status": { "status": {
"description": "status", "description": "status",
"type": "integer" "type": "string"
}, },
"username": { "username": {
"description": "name", "description": "name",

BIN
docs/img/abstract.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 118 KiB

BIN
docs/img/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.6 KiB

View File

@ -872,7 +872,7 @@
} }
}, },
"/answer/api/v1/answer/list": { "/answer/api/v1/answer/list": {
"post": { "get": {
"security": [ "security": [
{ {
"ApiKeyAuth": [] "ApiKeyAuth": []
@ -3602,6 +3602,46 @@
} }
} }
}, },
"/answer/api/v1/user/status": {
"get": {
"security": [
{
"ApiKeyAuth": []
}
],
"description": "get user status info",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"User"
],
"summary": "get user status info",
"responses": {
"200": {
"description": "OK",
"schema": {
"allOf": [
{
"$ref": "#/definitions/handler.RespBody"
},
{
"type": "object",
"properties": {
"data": {
"$ref": "#/definitions/schema.GetUserResp"
}
}
}
]
}
}
}
}
},
"/answer/api/v1/vote/down": { "/answer/api/v1/vote/down": {
"post": { "post": {
"security": [ "security": [
@ -4194,6 +4234,10 @@
"description": "reply user id", "description": "reply user id",
"type": "string" "type": "string"
}, },
"reply_user_status": {
"description": "reply user status",
"type": "string"
},
"reply_username": { "reply_username": {
"description": "reply user username", "description": "reply user username",
"type": "string" "type": "string"
@ -4210,6 +4254,10 @@
"description": "user id", "description": "user id",
"type": "string" "type": "string"
}, },
"user_status": {
"description": "user status",
"type": "string"
},
"username": { "username": {
"description": "username", "description": "username",
"type": "string" "type": "string"
@ -5452,7 +5500,7 @@
}, },
"status": { "status": {
"description": "status", "description": "status",
"type": "integer" "type": "string"
}, },
"username": { "username": {
"description": "name", "description": "name",

View File

@ -299,6 +299,9 @@ definitions:
reply_user_id: reply_user_id:
description: reply user id description: reply user id
type: string type: string
reply_user_status:
description: reply user status
type: string
reply_username: reply_username:
description: reply user username description: reply user username
type: string type: string
@ -311,6 +314,9 @@ definitions:
user_id: user_id:
description: user id description: user id
type: string type: string
user_status:
description: user status
type: string
username: username:
description: username description: username
type: string type: string
@ -1209,7 +1215,7 @@ definitions:
type: integer type: integer
status: status:
description: status description: status
type: integer type: string
username: username:
description: name description: name
type: string type: string
@ -1877,7 +1883,7 @@ paths:
tags: tags:
- api-answer - api-answer
/answer/api/v1/answer/list: /answer/api/v1/answer/list:
post: get:
consumes: consumes:
- application/json - application/json
description: AnswerList <br> <b>order</b> (default or updated) description: AnswerList <br> <b>order</b> (default or updated)
@ -3529,6 +3535,28 @@ paths:
summary: UserRegisterByEmail summary: UserRegisterByEmail
tags: tags:
- User - User
/answer/api/v1/user/status:
get:
consumes:
- application/json
description: get user status info
produces:
- application/json
responses:
"200":
description: OK
schema:
allOf:
- $ref: '#/definitions/handler.RespBody'
- properties:
data:
$ref: '#/definitions/schema.GetUserResp'
type: object
security:
- ApiKeyAuth: []
summary: get user status info
tags:
- User
/answer/api/v1/vote/down: /answer/api/v1/vote/down:
post: post:
consumes: consumes:

14
go.mod
View File

@ -19,17 +19,17 @@ require (
github.com/jordan-wright/email v4.0.1-0.20210109023952-943e75fe5223+incompatible github.com/jordan-wright/email v4.0.1-0.20210109023952-943e75fe5223+incompatible
github.com/mojocn/base64Captcha v1.3.5 github.com/mojocn/base64Captcha v1.3.5
github.com/segmentfault/pacman v1.0.1 github.com/segmentfault/pacman v1.0.1
github.com/segmentfault/pacman/contrib/cache/memory v0.0.0-20220926035018-18f894415e5b github.com/segmentfault/pacman/contrib/cache/memory v0.0.0-20220929065758-260b3093a347
github.com/segmentfault/pacman/contrib/conf/viper v0.0.0-20220926035018-18f894415e5b github.com/segmentfault/pacman/contrib/conf/viper v0.0.0-20220929065758-260b3093a347
github.com/segmentfault/pacman/contrib/i18n v0.0.0-20220926035018-18f894415e5b github.com/segmentfault/pacman/contrib/i18n v0.0.0-20220929065758-260b3093a347
github.com/segmentfault/pacman/contrib/log/zap v0.0.0-20220926035018-18f894415e5b github.com/segmentfault/pacman/contrib/log/zap v0.0.0-20220929065758-260b3093a347
github.com/segmentfault/pacman/contrib/server/http v0.0.0-20220926035018-18f894415e5b github.com/segmentfault/pacman/contrib/server/http v0.0.0-20220929065758-260b3093a347
github.com/stretchr/testify v1.8.0 github.com/stretchr/testify v1.8.0
github.com/swaggo/files v0.0.0-20220728132757-551d4a08d97a github.com/swaggo/files v0.0.0-20220728132757-551d4a08d97a
github.com/swaggo/gin-swagger v1.5.3 github.com/swaggo/gin-swagger v1.5.3
github.com/swaggo/swag v1.8.6 github.com/swaggo/swag v1.8.6
golang.org/x/crypto v0.0.0-20220926161630-eccd6366d1be golang.org/x/crypto v0.0.0-20220926161630-eccd6366d1be
golang.org/x/net v0.0.0-20220926192436-02166a98028e golang.org/x/net v0.0.0-20220927171203-f486391704dc
xorm.io/builder v0.3.12 xorm.io/builder v0.3.12
xorm.io/core v0.7.3 xorm.io/core v0.7.3
xorm.io/xorm v1.3.2 xorm.io/xorm v1.3.2
@ -76,7 +76,7 @@ require (
go.uber.org/multierr v1.8.0 // indirect go.uber.org/multierr v1.8.0 // indirect
go.uber.org/zap v1.23.0 // indirect go.uber.org/zap v1.23.0 // indirect
golang.org/x/image v0.0.0-20220902085622-e7cb96979f69 // indirect golang.org/x/image v0.0.0-20220902085622-e7cb96979f69 // indirect
golang.org/x/sys v0.0.0-20220926163933-8cfa568d3c25 // indirect golang.org/x/sys v0.0.0-20220928140112-f11e5e49a4ec // indirect
golang.org/x/text v0.3.7 // indirect golang.org/x/text v0.3.7 // indirect
golang.org/x/tools v0.1.12 // indirect golang.org/x/tools v0.1.12 // indirect
google.golang.org/protobuf v1.28.1 // indirect google.golang.org/protobuf v1.28.1 // indirect

21
go.sum
View File

@ -525,14 +525,24 @@ github.com/segmentfault/pacman v1.0.1 h1:GFdvPtNxvVVjnDM4ty02D/+4unHwG9PmjcOZSc2
github.com/segmentfault/pacman v1.0.1/go.mod h1:5lNp5REd8QMThmBUvR3Fi9Y3AsOB4GRq7soCB4QLqOs= github.com/segmentfault/pacman v1.0.1/go.mod h1:5lNp5REd8QMThmBUvR3Fi9Y3AsOB4GRq7soCB4QLqOs=
github.com/segmentfault/pacman/contrib/cache/memory v0.0.0-20220926035018-18f894415e5b h1:jSnRy3z3KVtVuGM2YTZihXwc4zEhW+TvyyJbBm8rjh4= github.com/segmentfault/pacman/contrib/cache/memory v0.0.0-20220926035018-18f894415e5b h1:jSnRy3z3KVtVuGM2YTZihXwc4zEhW+TvyyJbBm8rjh4=
github.com/segmentfault/pacman/contrib/cache/memory v0.0.0-20220926035018-18f894415e5b/go.mod h1:rmf1TCwz67dyM+AmTwSd1BxTo2AOYHj262lP93bOZbs= github.com/segmentfault/pacman/contrib/cache/memory v0.0.0-20220926035018-18f894415e5b/go.mod h1:rmf1TCwz67dyM+AmTwSd1BxTo2AOYHj262lP93bOZbs=
github.com/segmentfault/pacman/contrib/cache/memory v0.0.0-20220929065758-260b3093a347 h1:0xWBBXHHuemzMY61KYJXh7F5FW/4K8g98RYKNXodTCc=
github.com/segmentfault/pacman/contrib/cache/memory v0.0.0-20220929065758-260b3093a347/go.mod h1:rmf1TCwz67dyM+AmTwSd1BxTo2AOYHj262lP93bOZbs=
github.com/segmentfault/pacman/contrib/conf/viper v0.0.0-20220926035018-18f894415e5b h1:Gx3Brm+VMAyBJn4aBsxgKl+EIhFHc/YH5cLGeFHAW4g= github.com/segmentfault/pacman/contrib/conf/viper v0.0.0-20220926035018-18f894415e5b h1:Gx3Brm+VMAyBJn4aBsxgKl+EIhFHc/YH5cLGeFHAW4g=
github.com/segmentfault/pacman/contrib/conf/viper v0.0.0-20220926035018-18f894415e5b/go.mod h1:prPjFam7MyZ5b3S9dcDOt2tMPz6kf7C9c243s9zSwPY= github.com/segmentfault/pacman/contrib/conf/viper v0.0.0-20220926035018-18f894415e5b/go.mod h1:prPjFam7MyZ5b3S9dcDOt2tMPz6kf7C9c243s9zSwPY=
github.com/segmentfault/pacman/contrib/conf/viper v0.0.0-20220929065758-260b3093a347 h1:WpnEbmZFE8FYIgvseX+NJtDgGJlM1KSaKJhoxJywUgo=
github.com/segmentfault/pacman/contrib/conf/viper v0.0.0-20220929065758-260b3093a347/go.mod h1:prPjFam7MyZ5b3S9dcDOt2tMPz6kf7C9c243s9zSwPY=
github.com/segmentfault/pacman/contrib/i18n v0.0.0-20220926035018-18f894415e5b h1:uQmSgcV2w4OVXU6l3bQb9O+cSAVuzDQ9adJArQyFBa4= github.com/segmentfault/pacman/contrib/i18n v0.0.0-20220926035018-18f894415e5b h1:uQmSgcV2w4OVXU6l3bQb9O+cSAVuzDQ9adJArQyFBa4=
github.com/segmentfault/pacman/contrib/i18n v0.0.0-20220926035018-18f894415e5b/go.mod h1:5Afm+OQdau/HQqSOp/ALlSUp0vZsMMMbv//kJhxuoi8= github.com/segmentfault/pacman/contrib/i18n v0.0.0-20220926035018-18f894415e5b/go.mod h1:5Afm+OQdau/HQqSOp/ALlSUp0vZsMMMbv//kJhxuoi8=
github.com/segmentfault/pacman/contrib/i18n v0.0.0-20220929065758-260b3093a347 h1:Q29Ky9ZUGhdLIygfX6jwPYeEa7Wqn8o3f1NJWb8LvvE=
github.com/segmentfault/pacman/contrib/i18n v0.0.0-20220929065758-260b3093a347/go.mod h1:5Afm+OQdau/HQqSOp/ALlSUp0vZsMMMbv//kJhxuoi8=
github.com/segmentfault/pacman/contrib/log/zap v0.0.0-20220926035018-18f894415e5b h1:TaOBmAglooq+qKdnNTK2sy11t26ud7psHFB7/AV7l5U= github.com/segmentfault/pacman/contrib/log/zap v0.0.0-20220926035018-18f894415e5b h1:TaOBmAglooq+qKdnNTK2sy11t26ud7psHFB7/AV7l5U=
github.com/segmentfault/pacman/contrib/log/zap v0.0.0-20220926035018-18f894415e5b/go.mod h1:L4GqtXLoR73obTYqUQIzfkm8NG8pvZafxFb6KZFSSHk= github.com/segmentfault/pacman/contrib/log/zap v0.0.0-20220926035018-18f894415e5b/go.mod h1:L4GqtXLoR73obTYqUQIzfkm8NG8pvZafxFb6KZFSSHk=
github.com/segmentfault/pacman/contrib/log/zap v0.0.0-20220929065758-260b3093a347 h1:7Adjc296AKv32dg88S0T8t9K3+N+PFYLSCctpPnCUr0=
github.com/segmentfault/pacman/contrib/log/zap v0.0.0-20220929065758-260b3093a347/go.mod h1:L4GqtXLoR73obTYqUQIzfkm8NG8pvZafxFb6KZFSSHk=
github.com/segmentfault/pacman/contrib/server/http v0.0.0-20220926035018-18f894415e5b h1:n5n5VPeYGuZCmVppKPgWR/CaINHnL+ipEp9iE1XkcQc= github.com/segmentfault/pacman/contrib/server/http v0.0.0-20220926035018-18f894415e5b h1:n5n5VPeYGuZCmVppKPgWR/CaINHnL+ipEp9iE1XkcQc=
github.com/segmentfault/pacman/contrib/server/http v0.0.0-20220926035018-18f894415e5b/go.mod h1:UjNiOFYv1uGCq1ZCcONaKq4eE7MW3nbgpLqgl8f9N40= github.com/segmentfault/pacman/contrib/server/http v0.0.0-20220926035018-18f894415e5b/go.mod h1:UjNiOFYv1uGCq1ZCcONaKq4eE7MW3nbgpLqgl8f9N40=
github.com/segmentfault/pacman/contrib/server/http v0.0.0-20220929065758-260b3093a347 h1:CfuRhTPK2CBQIZruq5ceuTVthspe8U1FDjWXXI2RWdo=
github.com/segmentfault/pacman/contrib/server/http v0.0.0-20220929065758-260b3093a347/go.mod h1:UjNiOFYv1uGCq1ZCcONaKq4eE7MW3nbgpLqgl8f9N40=
github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4= github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4=
github.com/shopspring/decimal v0.0.0-20200227202807-02e2044944cc/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= github.com/shopspring/decimal v0.0.0-20200227202807-02e2044944cc/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
@ -584,7 +594,6 @@ github.com/swaggo/swag v1.8.6/go.mod h1:jMLeXOOmYyjk8PvHTsXBdrubsNd9gUJTTCzL5iBn
github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE= github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE=
github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ= github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ=
github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/ugorji/go v1.2.7 h1:qYhyWUUd6WbiM+C6JZAUkIJt/1WrjzNHY9+KCIjVqTo=
github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6M= github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6M=
github.com/ugorji/go/codec v1.2.7 h1:YPXUKf7fYbp/y8xloBqZOw2qaVggbfwMlI8WM3wZUJ0= github.com/ugorji/go/codec v1.2.7 h1:YPXUKf7fYbp/y8xloBqZOw2qaVggbfwMlI8WM3wZUJ0=
github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY= github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY=
@ -648,8 +657,6 @@ golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5y
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.0.0-20220924013350-4ba4fb4dd9e7 h1:WJywXQVIb56P2kAvXeMGTIgQ1ZHQxR60+F9dLsodECc=
golang.org/x/crypto v0.0.0-20220924013350-4ba4fb4dd9e7/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.0.0-20220926161630-eccd6366d1be h1:fmw3UbQh+nxngCAHrDCCztao/kbYFnWjoqop8dHx05A= golang.org/x/crypto v0.0.0-20220926161630-eccd6366d1be h1:fmw3UbQh+nxngCAHrDCCztao/kbYFnWjoqop8dHx05A=
golang.org/x/crypto v0.0.0-20220926161630-eccd6366d1be/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220926161630-eccd6366d1be/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
@ -733,10 +740,10 @@ golang.org/x/net v0.0.0-20210421230115-4e50805a0758/go.mod h1:72T/g9IO56b78aLF+1
golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220923203811-8be639271d50 h1:vKyz8L3zkd+xrMeIaBsQ/MNVPVFSffdaU3ZyYlBGFnI=
golang.org/x/net v0.0.0-20220923203811-8be639271d50/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
golang.org/x/net v0.0.0-20220926192436-02166a98028e h1:I51lVG9ykW5AQeTE50sJ0+gJCAF0J78Hf1+1VUCGxDI= golang.org/x/net v0.0.0-20220926192436-02166a98028e h1:I51lVG9ykW5AQeTE50sJ0+gJCAF0J78Hf1+1VUCGxDI=
golang.org/x/net v0.0.0-20220926192436-02166a98028e/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= golang.org/x/net v0.0.0-20220926192436-02166a98028e/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
golang.org/x/net v0.0.0-20220927171203-f486391704dc h1:FxpXZdoBqT8RjqTy6i1E8nXHhW21wK7ptQ/EPIGxzPQ=
golang.org/x/net v0.0.0-20220927171203-f486391704dc/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@ -817,10 +824,10 @@ golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220919091848-fb04ddd9f9c8 h1:h+EGohizhe9XlX18rfpa8k8RAc5XyaeamM+0VHRd4lc=
golang.org/x/sys v0.0.0-20220919091848-fb04ddd9f9c8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220926163933-8cfa568d3c25 h1:nwzwVf0l2Y/lkov/+IYgMMbFyI+QypZDds9RxlSmsFQ= golang.org/x/sys v0.0.0-20220926163933-8cfa568d3c25 h1:nwzwVf0l2Y/lkov/+IYgMMbFyI+QypZDds9RxlSmsFQ=
golang.org/x/sys v0.0.0-20220926163933-8cfa568d3c25/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220926163933-8cfa568d3c25/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220928140112-f11e5e49a4ec h1:BkDtF2Ih9xZ7le9ndzTA7KJow28VbQW3odyk/8drmuI=
golang.org/x/sys v0.0.0-20220928140112-f11e5e49a4ec/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=

6
i18n/i18n.go Normal file
View File

@ -0,0 +1,6 @@
package i18n
import "embed"
//go:embed *.yaml
var I18n embed.FS

View File

@ -13,6 +13,7 @@ const (
UserTokenCacheTime = 7 * 24 * time.Hour UserTokenCacheTime = 7 * 24 * time.Hour
AdminTokenCacheKey = "answer:admin:token:" AdminTokenCacheKey = "answer:admin:token:"
AdminTokenCacheTime = 7 * 24 * time.Hour AdminTokenCacheTime = 7 * 24 * time.Hour
AcceptLanguageFlag = "Accept-Language"
) )
const ( const (

View File

@ -5,6 +5,7 @@ import (
"net/http" "net/http"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/segmentfault/answer/internal/base/constant"
"github.com/segmentfault/answer/internal/base/reason" "github.com/segmentfault/answer/internal/base/reason"
"github.com/segmentfault/answer/internal/base/validator" "github.com/segmentfault/answer/internal/base/validator"
myErrors "github.com/segmentfault/pacman/errors" myErrors "github.com/segmentfault/pacman/errors"
@ -44,13 +45,15 @@ func HandleResponse(ctx *gin.Context, err error, data interface{}) {
// BindAndCheck bind request and check // BindAndCheck bind request and check
func BindAndCheck(ctx *gin.Context, data interface{}) bool { func BindAndCheck(ctx *gin.Context, data interface{}) bool {
lang := GetLang(ctx)
ctx.Set(constant.AcceptLanguageFlag, lang)
if err := ctx.ShouldBind(data); err != nil { if err := ctx.ShouldBind(data); err != nil {
log.Errorf("http_handle BindAndCheck fail, %s", err.Error()) log.Errorf("http_handle BindAndCheck fail, %s", err.Error())
HandleResponse(ctx, myErrors.New(http.StatusBadRequest, reason.RequestFormatError), nil) HandleResponse(ctx, myErrors.New(http.StatusBadRequest, reason.RequestFormatError), nil)
return true return true
} }
errField, err := validator.GetValidatorByLang(GetLang(ctx).Abbr()).Check(data) errField, err := validator.GetValidatorByLang(lang.Abbr()).Check(data)
if err != nil { if err != nil {
HandleResponse(ctx, myErrors.New(http.StatusBadRequest, reason.RequestFormatError).WithMsg(err.Error()), errField) HandleResponse(ctx, myErrors.New(http.StatusBadRequest, reason.RequestFormatError).WithMsg(err.Error()), errField)
return true return true

View File

@ -2,12 +2,13 @@ package handler
import ( import (
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/segmentfault/answer/internal/base/constant"
"github.com/segmentfault/pacman/i18n" "github.com/segmentfault/pacman/i18n"
) )
// GetLang get language from header // GetLang get language from header
func GetLang(ctx *gin.Context) i18n.Language { func GetLang(ctx *gin.Context) i18n.Language {
acceptLanguage := ctx.GetHeader("Accept-Language") acceptLanguage := ctx.GetHeader(constant.AcceptLanguageFlag)
switch i18n.Language(acceptLanguage) { switch i18n.Language(acceptLanguage) {
case i18n.LanguageChinese: case i18n.LanguageChinese:
return i18n.LanguageChinese return i18n.LanguageChinese

View File

@ -17,7 +17,7 @@ type PageCond struct {
} }
// NewPageModel new page model // NewPageModel new page model
func NewPageModel(page, pageSize int, totalRecords int64, records interface{}) *PageModel { func NewPageModel(totalRecords int64, records interface{}) *PageModel {
sliceValue := reflect.Indirect(reflect.ValueOf(records)) sliceValue := reflect.Indirect(reflect.ValueOf(records))
if sliceValue.Kind() != reflect.Slice { if sliceValue.Kind() != reflect.Slice {
panic("not a slice") panic("not a slice")

View File

@ -11,7 +11,7 @@ func NewHTTPServer(debug bool,
staticRouter *router.StaticRouter, staticRouter *router.StaticRouter,
answerRouter *router.AnswerAPIRouter, answerRouter *router.AnswerAPIRouter,
swaggerRouter *router.SwaggerRouter, swaggerRouter *router.SwaggerRouter,
viewRouter *router.ViewRouter, viewRouter *router.UIRouter,
authUserMiddleware *middleware.AuthUserMiddleware) *gin.Engine { authUserMiddleware *middleware.AuthUserMiddleware) *gin.Engine {
if debug { if debug {
@ -22,7 +22,7 @@ func NewHTTPServer(debug bool,
r := gin.New() r := gin.New()
r.GET("/healthz", func(ctx *gin.Context) { ctx.String(200, "OK") }) r.GET("/healthz", func(ctx *gin.Context) { ctx.String(200, "OK") })
viewRouter.RegisterViewRouter(r) viewRouter.Register(r)
rootGroup := r.Group("") rootGroup := r.Group("")
swaggerRouter.Register(rootGroup) swaggerRouter.Register(rootGroup)

93
internal/cli/install.go Normal file
View File

@ -0,0 +1,93 @@
package cli
import (
"bufio"
"fmt"
"os"
"github.com/segmentfault/answer/configs"
"github.com/segmentfault/answer/i18n"
"github.com/segmentfault/answer/pkg/dir"
)
var SuccessMsg = `
answer initialized successfully
`
var HasBeenInitializedMsg = `
Has been initialized.
`
func InitConfig() {
exist, err := PathExists("data/config.yaml")
if err != nil {
fmt.Println(err.Error())
os.Exit(2)
}
if exist {
fmt.Println(HasBeenInitializedMsg)
os.Exit(0)
}
_, err = dir.CreatePathIsNotExist("data")
if err != nil {
fmt.Println(err.Error())
os.Exit(2)
}
WriterFile("data/config.yaml", string(configs.Config))
_, err = dir.CreatePathIsNotExist("data/i18n")
if err != nil {
fmt.Println(err.Error())
os.Exit(2)
}
_, err = dir.CreatePathIsNotExist("data/upfiles")
if err != nil {
fmt.Println(err.Error())
os.Exit(2)
}
i18nList, err := i18n.I18n.ReadDir(".")
if err != nil {
fmt.Println(err.Error())
os.Exit(2)
}
for _, item := range i18nList {
path := fmt.Sprintf("data/i18n/%s", item.Name())
content, err := i18n.I18n.ReadFile(item.Name())
if err != nil {
continue
}
WriterFile(path, string(content))
}
fmt.Println(SuccessMsg)
os.Exit(0)
}
func InitDB() {
}
func WriterFile(filePath, content string) error {
file, err := os.OpenFile(filePath, os.O_WRONLY|os.O_CREATE, 0666)
if err != nil {
return err
}
defer file.Close()
if err != nil {
return err
}
write := bufio.NewWriter(file)
write.WriteString(content)
write.Flush()
return nil
}
func PathExists(path string) (bool, error) {
_, err := os.Stat(path)
if err == nil {
return true, nil
}
if os.IsNotExist(err) {
return false, nil
}
return false, err
}

50
internal/cli/provider.go Normal file
View File

@ -0,0 +1,50 @@
package cli
import (
"bytes"
"github.com/google/wire"
"github.com/segmentfault/answer/assets"
"github.com/segmentfault/answer/internal/base/data"
"github.com/segmentfault/answer/internal/entity"
)
// ProviderSetCli is providers.
var ProviderSetCli = wire.NewSet(NewCli)
type Cli struct {
DataSource *data.Data
}
var CommandCli *Cli
func NewCli(dataSource *data.Data) *Cli {
CommandCli = &Cli{DataSource: dataSource}
return CommandCli
}
// InitDB init db
func (c *Cli) InitDB() (err error) {
// check db connection
err = c.DataSource.DB.Ping()
if err != nil {
return err
}
exist, err := c.DataSource.DB.IsTableExist(&entity.User{})
if err != nil {
return err
}
if exist {
return nil
}
// create table if not exist
s := &bytes.Buffer{}
s.Write(assets.AnswerSql)
_, err = c.DataSource.DB.Import(s)
if err != nil {
return err
}
return nil
}

18
internal/cli/usage.go Normal file
View File

@ -0,0 +1,18 @@
package cli
import "fmt"
var usageDoc = `
answer
USAGE
answer command
COMMANDS
init init answer config, eg:answer init
run config path, eg:answer run -c data/config.yaml
`
func Usage() {
fmt.Println(usageDoc)
}

View File

@ -1,7 +1,6 @@
package controller package controller
import ( import (
"context"
"fmt" "fmt"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
@ -176,23 +175,19 @@ func (ac *AnswerController) Update(ctx *gin.Context) {
// @Produce json // @Produce json
// @Param data body schema.AnswerList true "AnswerList" // @Param data body schema.AnswerList true "AnswerList"
// @Success 200 {string} string "" // @Success 200 {string} string ""
// @Router /answer/api/v1/answer/list [post] // @Router /answer/api/v1/answer/list [get]
func (ac *AnswerController) AnswerList(c *gin.Context) { func (ac *AnswerController) AnswerList(ctx *gin.Context) {
input := new(schema.AnswerList) req := &schema.AnswerList{}
err := c.BindJSON(input) if handler.BindAndCheck(ctx, req) {
if err != nil {
handler.HandleResponse(c, err, nil)
return return
} }
ctx := context.Background() req.LoginUserID = middleware.GetLoginUserIDFromContext(ctx)
userId := middleware.GetLoginUserIDFromContext(c) list, count, err := ac.answerService.SearchList(ctx, req)
input.LoginUserID = userId
list, count, err := ac.answerService.SearchList(ctx, input)
if err != nil { if err != nil {
handler.HandleResponse(c, err, nil) handler.HandleResponse(ctx, err, nil)
return return
} }
handler.HandleResponse(c, nil, gin.H{ handler.HandleResponse(ctx, nil, gin.H{
"list": list, "list": list,
"count": count, "count": count,
}) })

View File

@ -93,16 +93,16 @@ func (nc *NotificationController) ClearIDUnRead(ctx *gin.Context) {
handler.HandleResponse(ctx, err, gin.H{}) handler.HandleResponse(ctx, err, gin.H{})
} }
// GetList // GetList get notification list
// @Summary GetRedDot // @Summary get notification list
// @Description GetRedDot // @Description get notification list
// @Tags Notification // @Tags Notification
// @Accept json // @Accept json
// @Produce json // @Produce json
// @Security ApiKeyAuth // @Security ApiKeyAuth
// @Param page query int false "page size" // @Param page query int false "page size"
// @Param page_size query int false "page size" // @Param page_size query int false "page size"
// @Param type query string false "type" Enums(inbox,achievement) // @Param type query string true "type" Enums(inbox,achievement)
// @Success 200 {object} handler.RespBody // @Success 200 {object} handler.RespBody
// @Router /answer/api/v1/notification/page [get] // @Router /answer/api/v1/notification/page [get]
func (nc *NotificationController) GetList(ctx *gin.Context) { func (nc *NotificationController) GetList(ctx *gin.Context) {
@ -111,9 +111,6 @@ func (nc *NotificationController) GetList(ctx *gin.Context) {
return return
} }
req.UserID = middleware.GetLoginUserIDFromContext(ctx) req.UserID = middleware.GetLoginUserIDFromContext(ctx)
list, count, err := nc.notificationService.GetList(ctx, req) resp, err := nc.notificationService.GetList(ctx, req)
handler.HandleResponse(ctx, err, gin.H{ handler.HandleResponse(ctx, err, resp)
"list": list,
"count": count,
})
} }

View File

@ -80,6 +80,21 @@ func (uc *UserController) GetOtherUserInfoByUsername(ctx *gin.Context) {
handler.HandleResponse(ctx, err, resp) handler.HandleResponse(ctx, err, resp)
} }
// GetUserStatus get user status info
// @Summary get user status info
// @Description get user status info
// @Tags User
// @Accept json
// @Produce json
// @Security ApiKeyAuth
// @Success 200 {object} handler.RespBody{data=schema.GetUserResp}
// @Router /answer/api/v1/user/status [get]
func (uc *UserController) GetUserStatus(ctx *gin.Context) {
userID := middleware.GetLoginUserIDFromContext(ctx)
resp, err := uc.userService.GetUserStatus(ctx, userID)
handler.HandleResponse(ctx, err, resp)
}
// UserEmailLogin godoc // UserEmailLogin godoc
// @Summary UserEmailLogin // @Summary UserEmailLogin
// @Description UserEmailLogin // @Description UserEmailLogin

View File

@ -20,7 +20,7 @@ var CmsAnswerSearchStatus = map[string]int{
type Answer struct { type Answer struct {
ID string `xorm:"not null pk autoincr comment('answer id') BIGINT(20) id"` ID string `xorm:"not null pk autoincr comment('answer id') BIGINT(20) id"`
CreatedAt time.Time `xorm:"created not null default CURRENT_TIMESTAMP TIMESTAMP created_at"` CreatedAt time.Time `xorm:"created not null default CURRENT_TIMESTAMP TIMESTAMP created_at"`
UpdatedAt time.Time `xorm:"updated not null default CURRENT_TIMESTAMP TIMESTAMP updated_at"` UpdatedAt time.Time `xorm:"not null default CURRENT_TIMESTAMP TIMESTAMP updated_at"`
QuestionID string `xorm:"not null default 0 comment('question id') BIGINT(20) question_id"` QuestionID string `xorm:"not null default 0 comment('question id') BIGINT(20) question_id"`
UserID string `xorm:"not null default 0 comment('answer user id') BIGINT(20) user_id"` UserID string `xorm:"not null default 0 comment('answer user id') BIGINT(20) user_id"`
OriginalText string `xorm:"not null comment('original content') MEDIUMTEXT original_text"` OriginalText string `xorm:"not null comment('original content') MEDIUMTEXT original_text"`

View File

@ -24,6 +24,8 @@ type QuestionTag struct {
// Question question // Question question
type Question struct { type Question struct {
ID string `xorm:"not null pk comment('question id') BIGINT(20) id"` ID string `xorm:"not null pk comment('question id') BIGINT(20) id"`
CreatedAt time.Time `xorm:"not null default CURRENT_TIMESTAMP comment('create time') TIMESTAMP created_at"`
UpdatedAt time.Time `xorm:"not null default CURRENT_TIMESTAMP comment('update time') TIMESTAMP updated_at"`
UserID string `xorm:"not null default 0 comment('user id') BIGINT(20) user_id"` UserID string `xorm:"not null default 0 comment('user id') BIGINT(20) user_id"`
Title string `xorm:"not null default '' comment('question title') VARCHAR(255) title"` Title string `xorm:"not null default '' comment('question title') VARCHAR(255) title"`
OriginalText string `xorm:"not null comment('original content') MEDIUMTEXT original_text"` OriginalText string `xorm:"not null comment('original content') MEDIUMTEXT original_text"`
@ -37,8 +39,6 @@ type Question struct {
FollowCount int `xorm:"not null default 0 comment('follow count') INT(11) follow_count"` FollowCount int `xorm:"not null default 0 comment('follow count') INT(11) follow_count"`
AcceptedAnswerID string `xorm:"not null default 0 comment('accepted answer id') BIGINT(20) accepted_answer_id"` AcceptedAnswerID string `xorm:"not null default 0 comment('accepted answer id') BIGINT(20) accepted_answer_id"`
LastAnswerID string `xorm:"not null default 0 comment('last answer id') BIGINT(20) last_answer_id"` LastAnswerID string `xorm:"not null default 0 comment('last answer id') BIGINT(20) last_answer_id"`
CreatedAt time.Time `xorm:"not null default CURRENT_TIMESTAMP comment('create time') TIMESTAMP created_at"`
UpdatedAt time.Time `xorm:"not null default CURRENT_TIMESTAMP comment('update time') TIMESTAMP updated_at"`
PostUpdateTime time.Time `xorm:"default CURRENT_TIMESTAMP comment('answer the last update time') TIMESTAMP post_update_time"` PostUpdateTime time.Time `xorm:"default CURRENT_TIMESTAMP comment('answer the last update time') TIMESTAMP post_update_time"`
RevisionID string `xorm:"not null default 0 BIGINT(20) revision_id"` RevisionID string `xorm:"not null default 0 BIGINT(20) revision_id"`
} }

View File

@ -2,6 +2,7 @@ package repo
import ( import (
"context" "context"
"time"
"github.com/segmentfault/answer/internal/base/constant" "github.com/segmentfault/answer/internal/base/constant"
"github.com/segmentfault/answer/internal/base/data" "github.com/segmentfault/answer/internal/base/data"
@ -77,7 +78,9 @@ func (ar *answerRepo) UpdateAnswer(ctx context.Context, answer *entity.Answer, C
} }
func (ar *answerRepo) UpdateAnswerStatus(ctx context.Context, answer *entity.Answer) (err error) { func (ar *answerRepo) UpdateAnswerStatus(ctx context.Context, answer *entity.Answer) (err error) {
_, err = ar.data.DB.Where("id =?", answer.ID).Cols("status").Update(answer) now := time.Now()
answer.UpdatedAt = now
_, err = ar.data.DB.Where("id =?", answer.ID).Cols("status", "updated_at").Update(answer)
if err != nil { if err != nil {
return errors.InternalServer(reason.DatabaseError).WithError(err).WithStack() return errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
} }
@ -129,7 +132,7 @@ func (ar *answerRepo) UpdateAdopted(ctx context.Context, id string, questionId s
if err != nil { if err != nil {
return err return err
} }
if id != "" { if id != "0" {
data.Adopted = schema.Answer_Adopted_Enable data.Adopted = schema.Answer_Adopted_Enable
_, err = ar.data.DB.Where("id = ?", id).Cols("adopted").Update(&data) _, err = ar.data.DB.Where("id = ?", id).Cols("adopted").Update(&data)
if err != nil { if err != nil {
@ -181,7 +184,7 @@ func (ar *answerRepo) SearchList(ctx context.Context, search *entity.AnswerSearc
session = session.And("user_id = ?", search.UserID) session = session.And("user_id = ?", search.UserID)
} }
if search.Order == entity.Answer_Search_OrderBy_Time { if search.Order == entity.Answer_Search_OrderBy_Time {
session = session.OrderBy("updated_at desc") session = session.OrderBy("created_at desc")
} else if search.Order == entity.Answer_Search_OrderBy_Vote { } else if search.Order == entity.Answer_Search_OrderBy_Vote {
session = session.OrderBy("vote_count desc") session = session.OrderBy("vote_count desc")
} else { } else {
@ -191,7 +194,7 @@ func (ar *answerRepo) SearchList(ctx context.Context, search *entity.AnswerSearc
session = session.And("status = ?", entity.AnswerStatusAvailable) session = session.And("status = ?", entity.AnswerStatusAvailable)
session = session.Limit(search.PageSize, offset) session = session.Limit(search.PageSize, offset)
count, err = session.OrderBy("updated_at desc").FindAndCount(&rows) count, err = session.FindAndCount(&rows)
if err != nil { if err != nil {
return rows, count, errors.InternalServer(reason.DatabaseError).WithError(err).WithStack() return rows, count, errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
} }
@ -216,7 +219,7 @@ func (ar *answerRepo) CmsSearchList(ctx context.Context, search *entity.CmsAnswe
offset := search.Page * search.PageSize offset := search.Page * search.PageSize
session := ar.data.DB.Where("") session := ar.data.DB.Where("")
session = session.And("status =?", search.Status) session = session.And("status =?", search.Status)
session = session.OrderBy("created_at desc") session = session.OrderBy("updated_at desc")
session = session.Limit(search.PageSize, offset) session = session.Limit(search.PageSize, offset)
count, err = session.FindAndCount(&rows) count, err = session.FindAndCount(&rows)
if err != nil { if err != nil {

View File

@ -2,6 +2,7 @@ package repo
import ( import (
"context" "context"
"time"
"github.com/segmentfault/answer/internal/base/constant" "github.com/segmentfault/answer/internal/base/constant"
"github.com/segmentfault/answer/internal/base/data" "github.com/segmentfault/answer/internal/base/data"
@ -92,7 +93,9 @@ func (qr *questionRepo) UpdateCollectionCount(ctx context.Context, questionId st
} }
func (qr *questionRepo) UpdateQuestionStatus(ctx context.Context, question *entity.Question) (err error) { func (qr *questionRepo) UpdateQuestionStatus(ctx context.Context, question *entity.Question) (err error) {
_, err = qr.data.DB.Where("id =?", question.ID).Cols("status").Update(question) now := time.Now()
question.UpdatedAt = now
_, err = qr.data.DB.Where("id =?", question.ID).Cols("status", "updated_at").Update(question)
if err != nil { if err != nil {
return errors.InternalServer(reason.DatabaseError).WithError(err).WithStack() return errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
} }
@ -238,7 +241,7 @@ func (qr *questionRepo) CmsSearchList(ctx context.Context, search *schema.CmsQue
offset := search.Page * search.PageSize offset := search.Page * search.PageSize
session := qr.data.DB.Table("question") session := qr.data.DB.Table("question")
session = session.And("status =?", search.Status) session = session.And("status =?", search.Status)
session = session.OrderBy("created_at desc") session = session.OrderBy("updated_at desc")
session = session.Limit(search.PageSize, offset) session = session.Limit(search.PageSize, offset)
count, err = session.FindAndCount(&rows) count, err = session.FindAndCount(&rows)
if err != nil { if err != nil {

View File

@ -66,7 +66,7 @@ func (rr *reportRepo) GetReportListPage(ctx context.Context, dto schema.GetRepor
} }
// order // order
session.OrderBy("created_at desc") session.OrderBy("updated_at desc")
total, err = pager.Help(dto.Page, dto.PageSize, &reports, cond, session) total, err = pager.Help(dto.Page, dto.PageSize, &reports, cond, session)
if err != nil { if err != nil {

View File

@ -22,16 +22,16 @@ import (
// searchRepo tag repository // searchRepo tag repository
type searchRepo struct { type searchRepo struct {
data *data.Data data *data.Data
userRepo usercommon.UserRepo userCommon *usercommon.UserCommon
uniqueIDRepo unique.UniqueIDRepo uniqueIDRepo unique.UniqueIDRepo
} }
// NewSearchRepo new repository // NewSearchRepo new repository
func NewSearchRepo(data *data.Data, uniqueIDRepo unique.UniqueIDRepo, userRepo usercommon.UserRepo) search_common.SearchRepo { func NewSearchRepo(data *data.Data, uniqueIDRepo unique.UniqueIDRepo, userCommon *usercommon.UserCommon) search_common.SearchRepo {
return &searchRepo{ return &searchRepo{
data: data, data: data,
uniqueIDRepo: uniqueIDRepo, uniqueIDRepo: uniqueIDRepo,
userRepo: userRepo, userCommon: userCommon,
} }
} }
@ -213,9 +213,7 @@ func (sr *searchRepo) SearchAnswers(ctx context.Context, words []string, limitAc
func (sr *searchRepo) parseResult(ctx context.Context, res []map[string][]byte) (resp []schema.SearchResp, err error) { func (sr *searchRepo) parseResult(ctx context.Context, res []map[string][]byte) (resp []schema.SearchResp, err error) {
for _, r := range res { for _, r := range res {
var ( var (
objectKey string objectKey string
uInfo *schema.UserBasicInfo
tags []schema.TagResp tags []schema.TagResp
tagsEntity []entity.Tag tagsEntity []entity.Tag
object schema.SearchObject object schema.SearchObject
@ -228,16 +226,12 @@ func (sr *searchRepo) parseResult(ctx context.Context, res []map[string][]byte)
tp, _ := time.ParseInLocation("2006-01-02 15:04:05", string(r["created_at"]), time.Local) tp, _ := time.ParseInLocation("2006-01-02 15:04:05", string(r["created_at"]), time.Local)
// get user info // get user info
userInfo, exist, e := sr.userRepo.GetByUserID(ctx, string(r["user_id"])) userInfo, _, e := sr.userCommon.GetUserBasicInfoByID(ctx, string(r["user_id"]))
if e != nil { if e != nil {
err = errors.InternalServer(reason.DatabaseError).WithError(e).WithStack() err = errors.InternalServer(reason.DatabaseError).WithError(e).WithStack()
return return
} }
if exist {
uInfo = sr.userBasicInfoFormat(ctx, userInfo)
}
// get tags // get tags
err = sr.data.DB. err = sr.data.DB.
Select("`display_name`,`slug_name`,`main_tag_slug_name`"). Select("`display_name`,`slug_name`,`main_tag_slug_name`").
@ -258,7 +252,7 @@ func (sr *searchRepo) parseResult(ctx context.Context, res []map[string][]byte)
Title: string(r["title"]), Title: string(r["title"]),
Excerpt: cutOutParsedText(string(r["original_text"])), Excerpt: cutOutParsedText(string(r["original_text"])),
CreatedAtParsed: tp.Unix(), CreatedAtParsed: tp.Unix(),
UserInfo: uInfo, UserInfo: userInfo,
Tags: tags, Tags: tags,
VoteCount: converter.StringToInt(string(r["vote_count"])), VoteCount: converter.StringToInt(string(r["vote_count"])),
Accepted: string(r["accepted"]) == "2", Accepted: string(r["accepted"]) == "2",
@ -275,8 +269,8 @@ func (sr *searchRepo) parseResult(ctx context.Context, res []map[string][]byte)
// userBasicInfoFormat // userBasicInfoFormat
func (sr *searchRepo) userBasicInfoFormat(ctx context.Context, dbinfo *entity.User) *schema.UserBasicInfo { func (sr *searchRepo) userBasicInfoFormat(ctx context.Context, dbinfo *entity.User) *schema.UserBasicInfo {
return &schema.UserBasicInfo{ return &schema.UserBasicInfo{
UserId: dbinfo.ID, ID: dbinfo.ID,
UserName: dbinfo.Username, Username: dbinfo.Username,
Rank: dbinfo.Rank, Rank: dbinfo.Rank,
DisplayName: dbinfo.DisplayName, DisplayName: dbinfo.DisplayName,
Avatar: dbinfo.Avatar, Avatar: dbinfo.Avatar,

View File

@ -87,6 +87,7 @@ func (a *AnswerAPIRouter) RegisterUnAuthAnswerAPIRouter(r *gin.RouterGroup) {
r.GET("/comment", a.commentController.GetComment) r.GET("/comment", a.commentController.GetComment)
// user // user
r.GET("/user/status", a.userController.GetUserStatus)
r.GET("/user/action/record", a.userController.ActionRecord) r.GET("/user/action/record", a.userController.ActionRecord)
r.POST("/user/login/email", a.userController.UserEmailLogin) r.POST("/user/login/email", a.userController.UserEmailLogin)
r.POST("/user/register/email", a.userController.UserRegisterByEmail) r.POST("/user/register/email", a.userController.UserRegisterByEmail)
@ -100,7 +101,7 @@ func (a *AnswerAPIRouter) RegisterUnAuthAnswerAPIRouter(r *gin.RouterGroup) {
//answer //answer
r.GET("/answer/info", a.answerController.Get) r.GET("/answer/info", a.answerController.Get)
r.POST("/answer/list", a.answerController.AnswerList) r.GET("/answer/page", a.answerController.AnswerList)
r.GET("/personal/answer/page", a.questionController.UserAnswerList) r.GET("/personal/answer/page", a.questionController.UserAnswerList)
//question //question

View File

@ -3,4 +3,4 @@ package router
import "github.com/google/wire" import "github.com/google/wire"
// ProviderSetRouter is providers. // ProviderSetRouter is providers.
var ProviderSetRouter = wire.NewSet(NewAnswerAPIRouter, NewSwaggerRouter, NewStaticRouter, NewViewRouter) var ProviderSetRouter = wire.NewSet(NewAnswerAPIRouter, NewSwaggerRouter, NewStaticRouter, NewUIRouter)

79
internal/router/ui.go Normal file
View File

@ -0,0 +1,79 @@
package router
import (
"embed"
"fmt"
"github.com/gin-gonic/gin"
"github.com/segmentfault/answer/ui"
"github.com/segmentfault/pacman/log"
"io/fs"
"net/http"
"os"
)
const UIIndexFilePath = "build/index.html"
const UIStaticPath = "build/static"
// UIRouter is an interface that provides ui static file routers
type UIRouter struct {
}
// NewUIRouter creates a new UIRouter instance with the embed resources
func NewUIRouter() *UIRouter {
return &UIRouter{}
}
// _resource is an interface that provides static file, it's a private interface
type _resource struct {
fs embed.FS
}
// Open to implement the interface by http.FS required
func (r *_resource) Open(name string) (fs.File, error) {
name = fmt.Sprintf(UIStaticPath+"/%s", name)
log.Debugf("open static path %s", name)
return r.fs.Open(name)
}
// Register a new static resource which generated by ui directory
func (a *UIRouter) Register(r *gin.Engine) {
staticPath := os.Getenv("ANSWER_STATIC_PATH")
// if ANSWER_STATIC_PATH is set and not empty, ignore embed resource
if staticPath != "" {
info, err := os.Stat(staticPath)
if err != nil || !info.IsDir() {
log.Error(err)
} else {
log.Debugf("registering static path %s", staticPath)
r.LoadHTMLGlob(staticPath + "/*.html")
r.Static("/static", staticPath+"/static")
r.NoRoute(func(c *gin.Context) {
c.HTML(http.StatusOK, "index.html", gin.H{})
})
// return immediately if the static path is set
return
}
}
// handle the static file by default ui static files
r.StaticFS("/static", http.FS(&_resource{
fs: ui.Build,
}))
// specify the not router for default routes and redirect
r.NoRoute(func(c *gin.Context) {
index, err := ui.Build.ReadFile(UIIndexFilePath)
if err != nil {
log.Error(err)
c.Status(http.StatusNotFound)
return
}
c.Header("content-type", "text/html;charset=utf-8")
c.String(http.StatusOK, string(index))
})
}

View File

@ -0,0 +1,36 @@
package router
import (
"github.com/gin-gonic/gin"
"github.com/stretchr/testify/assert"
"net/http"
"net/http/httptest"
"testing"
)
func TestUIRouter_Register(t *testing.T) {
r := gin.Default()
NewUIRouter().Register(r)
w := httptest.NewRecorder()
req, _ := http.NewRequest("GET", "/", nil)
r.ServeHTTP(w, req)
assert.Equal(t, http.StatusOK, w.Code)
}
func TestUIRouter_Static(t *testing.T) {
r := gin.Default()
NewUIRouter().Register(r)
w := httptest.NewRecorder()
req, _ := http.NewRequest("GET", "/static/version.txt", nil)
r.ServeHTTP(w, req)
assert.Equal(t, http.StatusOK, w.Code)
assert.Equal(t, "OK", w.Body.String())
}

View File

@ -1,67 +0,0 @@
package router
import (
"embed"
"errors"
"io/fs"
"net/http"
"os"
"path"
"path/filepath"
"strings"
"github.com/gin-gonic/gin"
"github.com/segmentfault/answer/web"
)
// RegisterViewRouter
type ViewRouter struct {
}
// NewRegisterViewRouter
func NewViewRouter() *ViewRouter {
return &ViewRouter{}
}
type Resource struct {
fs embed.FS
path string
}
func NewResource() *Resource {
return &Resource{
fs: web.Static,
path: "html",
}
}
func (r *Resource) Open(name string) (fs.File, error) {
if filepath.Separator != '/' && strings.ContainsRune(name, filepath.Separator) {
return nil, errors.New("http: invalid character in file path")
}
fullName := filepath.Join(r.path, filepath.FromSlash(path.Clean("/static/"+name)))
file, err := r.fs.Open(fullName)
return file, err
}
func (a *ViewRouter) RegisterViewRouter(r *gin.Engine) {
//export answer_html_static_path="../../web/static"
//export answer_html_page_path="../../web"
static := os.Getenv("answer_html_static_path")
index := os.Getenv("answer_html_page_path")
if len(static) > 0 && len(index) > 0 {
r.LoadHTMLGlob(index + "/*.html")
r.Static("/static", static)
r.NoRoute(func(c *gin.Context) {
c.HTML(http.StatusOK, "index.html", gin.H{})
})
return
} else {
r.StaticFS("/static", http.FS(NewResource()))
r.NoRoute(func(c *gin.Context) {
c.Header("content-type", "text/html;charset=utf-8")
c.String(200, string(web.Html))
})
}
}

View File

@ -1,95 +1,13 @@
package schema package schema
import (
"time"
)
// RemoveAnswerReq delete answer request // RemoveAnswerReq delete answer request
type RemoveAnswerReq struct { type RemoveAnswerReq struct {
// answer id // answer id
ID string `validate:"required" comment:"answer id" json:"id"` ID string `validate:"required" json:"id"`
// user id // user id
UserID string `json:"-"` UserID string `json:"-"`
} }
// GetAnswerListReq get answer list all request
type GetAnswerListReq struct {
// question id
QuestionID int64 `validate:"omitempty" comment:"question id" form:"question_id"`
// answer user id
UserID int64 `validate:"omitempty" comment:"answer user id" form:"user_id"`
// content markdown
Content string `validate:"omitempty" comment:"content markdown" form:"content"`
// content html
Html string `validate:"omitempty" comment:"content html" form:"html"`
// answer status(available: 1; deleted: 10)
Status int `validate:"omitempty" comment:" answer status(available: 1; deleted: 10)" form:"status"`
// adopted (1 failed 2 adopted)
Adopted int `validate:"omitempty" comment:"adopted (1 failed 2 adopted)" form:"adopted"`
// comment count
CommentCount int `validate:"omitempty" comment:"comment count" form:"comment_count"`
// vote count
VoteCount int `validate:"omitempty" comment:"vote count" form:"vote_count"`
//
CreateTime time.Time `validate:"omitempty" comment:"" form:"create_time"`
//
UpdateTime time.Time `validate:"omitempty" comment:"" form:"update_time"`
}
// GetAnswerWithPageReq get answer list page request
type GetAnswerWithPageReq struct {
// page
Page int `validate:"omitempty,min=1" form:"page"`
// page size
PageSize int `validate:"omitempty,min=1" form:"page_size"`
// question id
QuestionID int64 `validate:"omitempty" comment:"question id" form:"question_id"`
// answer user id
UserID int64 `validate:"omitempty" comment:"answer user id" form:"user_id"`
// content markdown
Content string `validate:"omitempty" comment:"content markdown" form:"content"`
// content html
Html string `validate:"omitempty" comment:"content html" form:"html"`
// answer status(available: 1; deleted: 10)
Status int `validate:"omitempty" comment:" answer status(available: 1; deleted: 10)" form:"status"`
// adopted (1 failed 2 adopted)
Adopted int `validate:"omitempty" comment:"adopted (1 failed 2 adopted)" form:"adopted"`
// comment count
CommentCount int `validate:"omitempty" comment:"comment count" form:"comment_count"`
// vote count
VoteCount int `validate:"omitempty" comment:"vote count" form:"vote_count"`
//
CreateTime time.Time `validate:"omitempty" comment:"" form:"create_time"`
//
UpdateTime time.Time `validate:"omitempty" comment:"" form:"update_time"`
}
// GetAnswerResp get answer response
type GetAnswerResp struct {
// answer id
ID int64 `json:"id"`
// question id
QuestionID int64 `json:"question_id"`
// answer user id
UserID int64 `json:"user_id"`
// content markdown
Content string `json:"content"`
// content html
Html string `json:"html"`
// answer status(available: 1; deleted: 10)
Status int `json:"status"`
// adopted (1 failed 2 adopted)
Adopted int `json:"adopted"`
// comment count
CommentCount int `json:"comment_count"`
// vote count
VoteCount int `json:"vote_count"`
//
CreateTime time.Time `json:"create_time"`
//
UpdateTime time.Time `json:"update_time"`
}
const ( const (
Answer_Adopted_Failed = 1 Answer_Adopted_Failed = 1
Answer_Adopted_Enable = 2 Answer_Adopted_Enable = 2
@ -113,10 +31,10 @@ type AnswerUpdateReq struct {
} }
type AnswerList struct { type AnswerList struct {
QuestionId string `json:"question_id" ` // question_id QuestionId string `json:"question_id" form:"question_id"` // question_id
Order string `json:"order" ` // 1 Default 2 time Order string `json:"order" form:"order"` // 1 Default 2 time
Page int `json:"page" form:"page"` //Query number of pages Page int `json:"page" form:"page"` //Query number of pages
PageSize int `json:"page_size" form:"page_size"` //Search page size PageSize int `json:"page_size" form:"page_size"` //Search page size
LoginUserID string `json:"-" ` LoginUserID string `json:"-" `
} }

View File

@ -9,16 +9,16 @@ type UpdateUserStatusReq struct {
} }
const ( const (
Normal = "normal" UserNormal = "normal"
Suspended = "suspended" UserSuspended = "suspended"
Deleted = "deleted" UserDeleted = "deleted"
Inactive = "inactive" UserInactive = "inactive"
) )
func (r *UpdateUserStatusReq) IsNormal() bool { return r.Status == Normal } func (r *UpdateUserStatusReq) IsNormal() bool { return r.Status == UserNormal }
func (r *UpdateUserStatusReq) IsSuspended() bool { return r.Status == Suspended } func (r *UpdateUserStatusReq) IsSuspended() bool { return r.Status == UserSuspended }
func (r *UpdateUserStatusReq) IsDeleted() bool { return r.Status == Deleted } func (r *UpdateUserStatusReq) IsDeleted() bool { return r.Status == UserDeleted }
func (r *UpdateUserStatusReq) IsInactive() bool { return r.Status == Inactive } func (r *UpdateUserStatusReq) IsInactive() bool { return r.Status == UserInactive }
// GetUserPageReq get user list page request // GetUserPageReq get user list page request
type GetUserPageReq struct { type GetUserPageReq struct {
@ -34,9 +34,9 @@ type GetUserPageReq struct {
Status string `validate:"omitempty,oneof=suspended deleted inactive" form:"status"` Status string `validate:"omitempty,oneof=suspended deleted inactive" form:"status"`
} }
func (r *GetUserPageReq) IsSuspended() bool { return r.Status == Suspended } func (r *GetUserPageReq) IsSuspended() bool { return r.Status == UserSuspended }
func (r *GetUserPageReq) IsDeleted() bool { return r.Status == Deleted } func (r *GetUserPageReq) IsDeleted() bool { return r.Status == UserDeleted }
func (r *GetUserPageReq) IsInactive() bool { return r.Status == Inactive } func (r *GetUserPageReq) IsInactive() bool { return r.Status == UserInactive }
// GetUserPageResp get user response // GetUserPageResp get user response
type GetUserPageResp struct { type GetUserPageResp struct {

View File

@ -43,12 +43,6 @@ type AddCollectionGroupReq struct {
UpdateTime time.Time `validate:"required" comment:"" json:"update_time"` UpdateTime time.Time `validate:"required" comment:"" json:"update_time"`
} }
// RemoveCollectionGroupReq delete collection group request
type RemoveCollectionGroupReq struct {
//
ID int64 `validate:"required" comment:"" json:"id"`
}
// UpdateCollectionGroupReq update collection group request // UpdateCollectionGroupReq update collection group request
type UpdateCollectionGroupReq struct { type UpdateCollectionGroupReq struct {
// //
@ -65,38 +59,6 @@ type UpdateCollectionGroupReq struct {
UpdateTime time.Time `validate:"omitempty" comment:"" json:"update_time"` UpdateTime time.Time `validate:"omitempty" comment:"" json:"update_time"`
} }
// GetCollectionGroupListReq get collection group list all request
type GetCollectionGroupListReq struct {
//
UserID int64 `validate:"omitempty" comment:"" form:"user_id"`
// the collection group name
Name string `validate:"omitempty,gt=0,lte=50" comment:"the collection group name" form:"name"`
// mark this group is default, default 1
DefaultGroup int `validate:"omitempty" comment:"mark this group is default, default 1" form:"default_group"`
//
CreateTime time.Time `validate:"omitempty" comment:"" form:"create_time"`
//
UpdateTime time.Time `validate:"omitempty" comment:"" form:"update_time"`
}
// GetCollectionGroupWithPageReq get collection group list page request
type GetCollectionGroupWithPageReq struct {
// page
Page int `validate:"omitempty,min=1" form:"page"`
// page size
PageSize int `validate:"omitempty,min=1" form:"page_size"`
//
UserID int64 `validate:"omitempty" comment:"" form:"user_id"`
// the collection group name
Name string `validate:"omitempty,gt=0,lte=50" comment:"the collection group name" form:"name"`
// mark this group is default, default 1
DefaultGroup int `validate:"omitempty" comment:"mark this group is default, default 1" form:"default_group"`
//
CreateTime time.Time `validate:"omitempty" comment:"" form:"create_time"`
//
UpdateTime time.Time `validate:"omitempty" comment:"" form:"update_time"`
}
// GetCollectionGroupResp get collection group response // GetCollectionGroupResp get collection group response
type GetCollectionGroupResp struct { type GetCollectionGroupResp struct {
// //

View File

@ -1,87 +0,0 @@
package schema
import "time"
// AddCollectionReq add collection request
type AddCollectionReq struct {
// user id
UserID int64 `validate:"required" comment:"user id" json:"user_id"`
// object id
ObjectID int64 `validate:"required" comment:"object id" json:"object_id"`
// user collection group id
UserCollectionGroupID int64 `validate:"required" comment:"user collection group id" json:"user_collection_group_id"`
//
CreateTime time.Time `validate:"required" comment:"" json:"create_time"`
//
UpdateTime time.Time `validate:"required" comment:"" json:"update_time"`
}
// RemoveCollectionReq delete collection request
type RemoveCollectionReq struct {
// collection id
ID int64 `validate:"required" comment:"collection id" json:"id"`
}
// UpdateCollectionReq update collection request
type UpdateCollectionReq struct {
// collection id
ID int64 `validate:"required" comment:"collection id" json:"id"`
// user id
UserID int64 `validate:"omitempty" comment:"user id" json:"user_id"`
// object id
ObjectID int64 `validate:"omitempty" comment:"object id" json:"object_id"`
// user collection group id
UserCollectionGroupID int64 `validate:"omitempty" comment:"user collection group id" json:"user_collection_group_id"`
//
CreateTime time.Time `validate:"omitempty" comment:"" json:"create_time"`
//
UpdateTime time.Time `validate:"omitempty" comment:"" json:"update_time"`
}
// GetCollectionListReq get collection list all request
type GetCollectionListReq struct {
// user id
UserID int64 `validate:"omitempty" comment:"user id" form:"user_id"`
// object id
ObjectID int64 `validate:"omitempty" comment:"object id" form:"object_id"`
// user collection group id
UserCollectionGroupID int64 `validate:"omitempty" comment:"user collection group id" form:"user_collection_group_id"`
//
CreateTime time.Time `validate:"omitempty" comment:"" form:"create_time"`
//
UpdateTime time.Time `validate:"omitempty" comment:"" form:"update_time"`
}
// GetCollectionWithPageReq get collection list page request
type GetCollectionWithPageReq struct {
// page
Page int `validate:"omitempty,min=1" form:"page"`
// page size
PageSize int `validate:"omitempty,min=1" form:"page_size"`
// user id
UserID int64 `validate:"omitempty" comment:"user id" form:"user_id"`
// object id
ObjectID int64 `validate:"omitempty" comment:"object id" form:"object_id"`
// user collection group id
UserCollectionGroupID int64 `validate:"omitempty" comment:"user collection group id" form:"user_collection_group_id"`
//
CreateTime time.Time `validate:"omitempty" comment:"" form:"create_time"`
//
UpdateTime time.Time `validate:"omitempty" comment:"" form:"update_time"`
}
// GetCollectionResp get collection response
type GetCollectionResp struct {
// collection id
ID int64 `json:"id"`
// user id
UserID int64 `json:"user_id"`
// object id
ObjectID int64 `json:"object_id"`
// user collection group id
UserCollectionGroupID int64 `json:"user_collection_group_id"`
//
CreateTime time.Time `json:"create_time"`
//
UpdateTime time.Time `json:"update_time"`
}

View File

@ -109,6 +109,8 @@ type GetCommentResp struct {
UserDisplayName string `json:"user_display_name"` UserDisplayName string `json:"user_display_name"`
// user avatar // user avatar
UserAvatar string `json:"user_avatar"` UserAvatar string `json:"user_avatar"`
// user status
UserStatus string `json:"user_status"`
// reply user id // reply user id
ReplyUserID string `json:"reply_user_id"` ReplyUserID string `json:"reply_user_id"`
@ -118,6 +120,8 @@ type GetCommentResp struct {
ReplyUserDisplayName string `json:"reply_user_display_name"` ReplyUserDisplayName string `json:"reply_user_display_name"`
// reply comment id // reply comment id
ReplyCommentID string `json:"reply_comment_id"` ReplyCommentID string `json:"reply_comment_id"`
// reply user status
ReplyUserStatus string `json:"reply_user_status"`
// MemberActions // MemberActions
MemberActions []*PermissionMemberAction `json:"member_actions"` MemberActions []*PermissionMemberAction `json:"member_actions"`

View File

@ -166,7 +166,7 @@ type CmsQuestionSearch struct {
Page int `json:"page" form:"page"` //Query number of pages Page int `json:"page" form:"page"` //Query number of pages
PageSize int `json:"page_size" form:"page_size"` //Search page size PageSize int `json:"page_size" form:"page_size"` //Search page size
Status int `json:"-" form:"-"` Status int `json:"-" form:"-"`
StatusStr string `json:"status" form:"status"` //Status 1 Available 2 closed 10 Deleted StatusStr string `json:"status" form:"status"` //Status 1 Available 2 closed 10 UserDeleted
} }
type AdminSetQuestionStatusRequest struct { type AdminSetQuestionStatusRequest struct {

View File

@ -76,7 +76,12 @@ func (r *GetUserResp) GetFromUserEntity(userInfo *entity.User) {
if ok { if ok {
r.Status = statusShow r.Status = statusShow
} }
}
// GetUserStatusResp get user status info
type GetUserStatusResp struct {
// user status
Status string `json:"status"`
} }
// GetOtherUserInfoByUsernameResp get user response // GetOtherUserInfoByUsernameResp get user response
@ -161,7 +166,7 @@ const (
var UserStatusShow = map[int]string{ var UserStatusShow = map[int]string{
1: "normal", 1: "normal",
9: "forbidden", 9: "forbidden",
10: "delete", 10: "deleted",
} }
var UserStatusShowMsg = map[int]string{ var UserStatusShowMsg = map[int]string{
1: "", 1: "",
@ -277,15 +282,15 @@ type ActionRecordResp struct {
} }
type UserBasicInfo struct { type UserBasicInfo struct {
UserId string `json:"-" ` // user_id ID string `json:"-" ` // user_id
UserName string `json:"username" ` // name Username string `json:"username" ` // name
Rank int `json:"rank" ` // rank Rank int `json:"rank" ` // rank
DisplayName string `json:"display_name"` // display_name DisplayName string `json:"display_name"` // display_name
Avatar string `json:"avatar" ` // avatar Avatar string `json:"avatar" ` // avatar
Website string `json:"website" ` // website Website string `json:"website" ` // website
Location string `json:"location" ` // location Location string `json:"location" ` // location
IpInfo string `json:"ip_info"` // ip info IpInfo string `json:"ip_info"` // ip info
Status int `json:"status"` // status Status string `json:"status"` // status
} }
type GetOtherUserInfoByUsernameReq struct { type GetOtherUserInfoByUsernameReq struct {

View File

@ -191,16 +191,25 @@ func (as *AnswerService) Update(ctx context.Context, req *schema.AnswerUpdateReq
// UpdateAdopted // UpdateAdopted
func (as *AnswerService) UpdateAdopted(ctx context.Context, req *schema.AnswerAdoptedReq) error { func (as *AnswerService) UpdateAdopted(ctx context.Context, req *schema.AnswerAdoptedReq) error {
if req.AnswerID == "" {
req.AnswerID = "0"
}
if req.UserID == "" { if req.UserID == "" {
return nil return nil
} }
newAnswerInfo, exist, err := as.answerRepo.GetByID(ctx, req.AnswerID) newAnswerInfo := &entity.Answer{}
if err != nil { newAnswerInfoexist := false
return err var err error
}
if !exist { if req.AnswerID != "0" {
return errors.BadRequest(reason.AnswerNotFound) newAnswerInfo, newAnswerInfoexist, err = as.answerRepo.GetByID(ctx, req.AnswerID)
if err != nil {
return err
}
if !newAnswerInfoexist {
return errors.BadRequest(reason.AnswerNotFound)
}
} }
questionInfo, exist, err := as.questionRepo.GetQuestion(ctx, req.QuestionID) questionInfo, exist, err := as.questionRepo.GetQuestion(ctx, req.QuestionID)
@ -250,12 +259,14 @@ func (as *AnswerService) updateAnswerRank(ctx context.Context, userID string,
log.Error(err) log.Error(err)
} }
} }
if newAnswerInfo.ID != "" {
err := as.answerActivityService.AcceptAnswer( err := as.answerActivityService.AcceptAnswer(
ctx, newAnswerInfo.ID, questionInfo.UserID, newAnswerInfo.UserID, newAnswerInfo.UserID == userID) ctx, newAnswerInfo.ID, questionInfo.UserID, newAnswerInfo.UserID, newAnswerInfo.UserID == userID)
if err != nil { if err != nil {
log.Error(err) log.Error(err)
}
} }
} }
func (as *AnswerService) Get(ctx context.Context, answerID, loginUserId string) (*schema.AnswerInfo, *schema.QuestionInfo, bool, error) { func (as *AnswerService) Get(ctx context.Context, answerID, loginUserId string) (*schema.AnswerInfo, *schema.QuestionInfo, bool, error) {
@ -413,7 +424,7 @@ func (as *AnswerService) notificationUpdateAnswer(ctx context.Context, questionU
Type: schema.NotificationTypeInbox, Type: schema.NotificationTypeInbox,
ObjectID: answerID, ObjectID: answerID,
} }
msg.ObjectType = constant.QuestionObjectType msg.ObjectType = constant.AnswerObjectType
msg.NotificationAction = constant.UpdateAnswer msg.NotificationAction = constant.UpdateAnswer
notice_queue.AddNotification(msg) notice_queue.AddNotification(msg)
} }
@ -425,7 +436,7 @@ func (as *AnswerService) notificationAnswerTheQuestion(ctx context.Context, ques
Type: schema.NotificationTypeInbox, Type: schema.NotificationTypeInbox,
ObjectID: answerID, ObjectID: answerID,
} }
msg.ObjectType = constant.QuestionObjectType msg.ObjectType = constant.AnswerObjectType
msg.NotificationAction = constant.AnswerTheQuestion msg.NotificationAction = constant.AnswerTheQuestion
notice_queue.AddNotification(msg) notice_queue.AddNotification(msg)
} }

View File

@ -32,7 +32,7 @@ type CommentRepo interface {
type CommentService struct { type CommentService struct {
commentRepo CommentRepo commentRepo CommentRepo
commentCommonRepo comment_common.CommentCommonRepo commentCommonRepo comment_common.CommentCommonRepo
userRepo usercommon.UserRepo userCommon *usercommon.UserCommon
voteCommon activity_common.VoteRepo voteCommon activity_common.VoteRepo
objectInfoService *object_info.ObjService objectInfoService *object_info.ObjService
} }
@ -61,13 +61,13 @@ func (c *CommentQuery) GetOrderBy() string {
func NewCommentService( func NewCommentService(
commentRepo CommentRepo, commentRepo CommentRepo,
commentCommonRepo comment_common.CommentCommonRepo, commentCommonRepo comment_common.CommentCommonRepo,
userRepo usercommon.UserRepo, userCommon *usercommon.UserCommon,
objectInfoService *object_info.ObjService, objectInfoService *object_info.ObjService,
voteCommon activity_common.VoteRepo) *CommentService { voteCommon activity_common.VoteRepo) *CommentService {
return &CommentService{ return &CommentService{
commentRepo: commentRepo, commentRepo: commentRepo,
commentCommonRepo: commentCommonRepo, commentCommonRepo: commentCommonRepo,
userRepo: userRepo, userCommon: userCommon,
voteCommon: voteCommon, voteCommon: voteCommon,
objectInfoService: objectInfoService, objectInfoService: objectInfoService,
} }
@ -124,19 +124,20 @@ func (cs *CommentService) AddComment(ctx context.Context, req *schema.AddComment
// get reply user info // get reply user info
if len(resp.ReplyUserID) > 0 { if len(resp.ReplyUserID) > 0 {
replyUser, exist, err := cs.userRepo.GetByUserID(ctx, resp.ReplyUserID) replyUser, exist, err := cs.userCommon.GetUserBasicInfoByID(ctx, resp.ReplyUserID)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if exist { if exist {
resp.ReplyUsername = replyUser.Username resp.ReplyUsername = replyUser.Username
resp.ReplyUserDisplayName = replyUser.DisplayName resp.ReplyUserDisplayName = replyUser.DisplayName
resp.ReplyUserStatus = replyUser.Status
} }
cs.notificationCommentReply(ctx, replyUser.ID, objInfo.QuestionID, req.UserID) cs.notificationCommentReply(ctx, replyUser.ID, objInfo.QuestionID, req.UserID)
} }
// get user info // get user info
userInfo, exist, err := cs.userRepo.GetByUserID(ctx, resp.UserID) userInfo, exist, err := cs.userCommon.GetUserBasicInfoByID(ctx, resp.UserID)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -144,6 +145,7 @@ func (cs *CommentService) AddComment(ctx context.Context, req *schema.AddComment
resp.Username = userInfo.Username resp.Username = userInfo.Username
resp.UserDisplayName = userInfo.DisplayName resp.UserDisplayName = userInfo.DisplayName
resp.UserAvatar = userInfo.Avatar resp.UserAvatar = userInfo.Avatar
resp.UserStatus = userInfo.Status
} }
return resp, nil return resp, nil
} }
@ -191,7 +193,7 @@ func (cs *CommentService) GetComment(ctx context.Context, req *schema.GetComment
// get comment user info // get comment user info
if len(resp.UserID) > 0 { if len(resp.UserID) > 0 {
commentUser, exist, err := cs.userRepo.GetByUserID(ctx, resp.UserID) commentUser, exist, err := cs.userCommon.GetUserBasicInfoByID(ctx, resp.UserID)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -199,18 +201,20 @@ func (cs *CommentService) GetComment(ctx context.Context, req *schema.GetComment
resp.Username = commentUser.Username resp.Username = commentUser.Username
resp.UserDisplayName = commentUser.DisplayName resp.UserDisplayName = commentUser.DisplayName
resp.UserAvatar = commentUser.Avatar resp.UserAvatar = commentUser.Avatar
resp.UserStatus = commentUser.Status
} }
} }
// get reply user info // get reply user info
if len(resp.ReplyUserID) > 0 { if len(resp.ReplyUserID) > 0 {
replyUser, exist, err := cs.userRepo.GetByUserID(ctx, resp.ReplyUserID) replyUser, exist, err := cs.userCommon.GetUserBasicInfoByID(ctx, resp.ReplyUserID)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if exist { if exist {
resp.ReplyUsername = replyUser.Username resp.ReplyUsername = replyUser.Username
resp.ReplyUserDisplayName = replyUser.DisplayName resp.ReplyUserDisplayName = replyUser.DisplayName
resp.ReplyUserStatus = replyUser.Status
} }
} }
@ -249,7 +253,7 @@ func (cs *CommentService) GetCommentWithPage(ctx context.Context, req *schema.Ge
// get comment user info // get comment user info
if len(commentResp.UserID) > 0 { if len(commentResp.UserID) > 0 {
commentUser, exist, err := cs.userRepo.GetByUserID(ctx, commentResp.UserID) commentUser, exist, err := cs.userCommon.GetUserBasicInfoByID(ctx, commentResp.UserID)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -257,18 +261,20 @@ func (cs *CommentService) GetCommentWithPage(ctx context.Context, req *schema.Ge
commentResp.Username = commentUser.Username commentResp.Username = commentUser.Username
commentResp.UserDisplayName = commentUser.DisplayName commentResp.UserDisplayName = commentUser.DisplayName
commentResp.UserAvatar = commentUser.Avatar commentResp.UserAvatar = commentUser.Avatar
commentResp.UserStatus = commentUser.Status
} }
} }
// get reply user info // get reply user info
if len(commentResp.ReplyUserID) > 0 { if len(commentResp.ReplyUserID) > 0 {
replyUser, exist, err := cs.userRepo.GetByUserID(ctx, commentResp.ReplyUserID) replyUser, exist, err := cs.userCommon.GetUserBasicInfoByID(ctx, commentResp.ReplyUserID)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if exist { if exist {
commentResp.ReplyUsername = replyUser.Username commentResp.ReplyUsername = replyUser.Username
commentResp.ReplyUserDisplayName = replyUser.DisplayName commentResp.ReplyUserDisplayName = replyUser.DisplayName
commentResp.ReplyUserStatus = replyUser.Status
} }
} }
@ -278,7 +284,7 @@ func (cs *CommentService) GetCommentWithPage(ctx context.Context, req *schema.Ge
commentResp.MemberActions = permission.GetCommentPermission(req.UserID, commentResp.UserID) commentResp.MemberActions = permission.GetCommentPermission(req.UserID, commentResp.UserID)
resp = append(resp, commentResp) resp = append(resp, commentResp)
} }
return pager.NewPageModel(req.Page, req.PageSize, total, resp), nil return pager.NewPageModel(total, resp), nil
} }
func (cs *CommentService) checkCommentWhetherOwner(ctx context.Context, userID, commentID string) error { func (cs *CommentService) checkCommentWhetherOwner(ctx context.Context, userID, commentID string) error {
@ -305,7 +311,7 @@ func (cs *CommentService) checkIsVote(ctx context.Context, userID, commentID str
func (cs *CommentService) GetCommentPersonalWithPage(ctx context.Context, req *schema.GetCommentPersonalWithPageReq) ( func (cs *CommentService) GetCommentPersonalWithPage(ctx context.Context, req *schema.GetCommentPersonalWithPageReq) (
pageModel *pager.PageModel, err error) { pageModel *pager.PageModel, err error) {
if len(req.Username) > 0 { if len(req.Username) > 0 {
userInfo, exist, err := cs.userRepo.GetByUsername(ctx, req.Username) userInfo, exist, err := cs.userCommon.GetUserBasicInfoByUserName(ctx, req.Username)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -348,7 +354,7 @@ func (cs *CommentService) GetCommentPersonalWithPage(ctx context.Context, req *s
} }
resp = append(resp, commentResp) resp = append(resp, commentResp)
} }
return pager.NewPageModel(req.Page, req.PageSize, total, resp), nil return pager.NewPageModel(total, resp), nil
} }
func (cs *CommentService) notificationQuestionComment(ctx context.Context, questionUserID, commentID, commentUserID string) { func (cs *CommentService) notificationQuestionComment(ctx context.Context, questionUserID, commentID, commentUserID string) {
@ -389,7 +395,7 @@ func (cs *CommentService) notificationCommentReply(ctx context.Context, replyUse
func (cs *CommentService) notificationMention(ctx context.Context, mentionUsernameList []string, commentID, commentUserID string) { func (cs *CommentService) notificationMention(ctx context.Context, mentionUsernameList []string, commentID, commentUserID string) {
for _, username := range mentionUsernameList { for _, username := range mentionUsernameList {
userInfo, exist, err := cs.userRepo.GetByUsername(ctx, username) userInfo, exist, err := cs.userCommon.GetUserBasicInfoByUserName(ctx, username)
if err != nil { if err != nil {
log.Error(err) log.Error(err)
continue continue

View File

@ -97,5 +97,5 @@ func (ns *NotificationReadService) GetNotificationReadWithPage(ctx context.Conte
resp := &[]schema.GetNotificationReadResp{} resp := &[]schema.GetNotificationReadResp{}
_ = copier.Copy(resp, notificationReads) _ = copier.Copy(resp, notificationReads)
return pager.NewPageModel(page, pageSize, total, resp), nil return pager.NewPageModel(total, resp), nil
} }

View File

@ -5,9 +5,13 @@ import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"github.com/segmentfault/answer/internal/base/constant"
"github.com/segmentfault/answer/internal/base/data" "github.com/segmentfault/answer/internal/base/data"
"github.com/segmentfault/answer/internal/base/pager"
"github.com/segmentfault/answer/internal/base/translator"
"github.com/segmentfault/answer/internal/schema" "github.com/segmentfault/answer/internal/schema"
notficationcommon "github.com/segmentfault/answer/internal/service/notification_common" notficationcommon "github.com/segmentfault/answer/internal/service/notification_common"
"github.com/segmentfault/pacman/i18n"
"github.com/segmentfault/pacman/log" "github.com/segmentfault/pacman/log"
) )
@ -91,30 +95,33 @@ func (ns *NotificationService) ClearIDUnRead(ctx context.Context, userID string,
return nil return nil
} }
func (ns *NotificationService) GetList(ctx context.Context, search *schema.NotificationSearch) ([]*schema.NotificationContent, int64, error) { func (ns *NotificationService) GetList(ctx context.Context, search *schema.NotificationSearch) (
list := make([]*schema.NotificationContent, 0) pageModel *pager.PageModel, err error) {
resp := make([]*schema.NotificationContent, 0)
searchType, ok := schema.NotificationType[search.TypeStr] searchType, ok := schema.NotificationType[search.TypeStr]
if !ok { if !ok {
return list, 0, nil return pager.NewPageModel(0, resp), nil
} }
search.Type = searchType search.Type = searchType
dblist, count, err := ns.notificationRepo.SearchList(ctx, search) notifications, count, err := ns.notificationRepo.SearchList(ctx, search)
if err != nil { if err != nil {
return list, count, err return nil, err
} }
for _, dbitem := range dblist { for _, notificationInfo := range notifications {
item := &schema.NotificationContent{} item := &schema.NotificationContent{}
err := json.Unmarshal([]byte(dbitem.Content), item) err := json.Unmarshal([]byte(notificationInfo.Content), item)
if err != nil { if err != nil {
log.Error("NotificationContent Unmarshal Error", err.Error()) log.Error("NotificationContent Unmarshal Error", err.Error())
continue continue
} }
item.ID = dbitem.ID lang, _ := ctx.Value(constant.AcceptLanguageFlag).(i18n.Language)
item.UpdateTime = dbitem.UpdatedAt.Unix() item.NotificationAction = translator.GlobalTrans.Tr(lang, item.NotificationAction)
if dbitem.IsRead == schema.NotificationRead { item.ID = notificationInfo.ID
item.UpdateTime = notificationInfo.UpdatedAt.Unix()
if notificationInfo.IsRead == schema.NotificationRead {
item.IsRead = true item.IsRead = true
} }
list = append(list, item) resp = append(resp, item)
} }
return list, count, nil return pager.NewPageModel(count, resp), nil
} }

View File

@ -3,12 +3,13 @@ package questioncommon
import ( import (
"context" "context"
"encoding/json" "encoding/json"
"fmt"
"time" "time"
"github.com/segmentfault/answer/internal/base/reason"
"github.com/segmentfault/answer/internal/service/activity_common" "github.com/segmentfault/answer/internal/service/activity_common"
"github.com/segmentfault/answer/internal/service/config" "github.com/segmentfault/answer/internal/service/config"
"github.com/segmentfault/answer/internal/service/meta" "github.com/segmentfault/answer/internal/service/meta"
"github.com/segmentfault/pacman/errors"
"github.com/segmentfault/answer/internal/entity" "github.com/segmentfault/answer/internal/entity"
"github.com/segmentfault/answer/internal/schema" "github.com/segmentfault/answer/internal/schema"
@ -139,7 +140,7 @@ func (qs *QuestionCommon) Info(ctx context.Context, questionId string, loginUser
return showinfo, err return showinfo, err
} }
if !has { if !has {
return showinfo, fmt.Errorf("the question could not be found") return showinfo, errors.BadRequest(reason.QuestionNotFound)
} }
showinfo = qs.ShowFormat(ctx, dbinfo) showinfo = qs.ShowFormat(ctx, dbinfo)

View File

@ -265,7 +265,7 @@ func (qs *QuestionService) SearchUserList(ctx context.Context, userName, order s
search.Order = order search.Order = order
search.Page = page search.Page = page
search.PageSize = pageSize search.PageSize = pageSize
search.UserID = userinfo.UserId search.UserID = userinfo.ID
questionlist, count, err := qs.SearchList(ctx, search, loginUserID) questionlist, count, err := qs.SearchList(ctx, search, loginUserID)
if err != nil { if err != nil {
return userlist, 0, err return userlist, 0, err
@ -289,7 +289,7 @@ func (qs *QuestionService) SearchUserAnswerList(ctx context.Context, userName, o
return userAnswerlist, 0, nil return userAnswerlist, 0, nil
} }
answersearch := &entity.AnswerSearch{} answersearch := &entity.AnswerSearch{}
answersearch.UserID = userinfo.UserId answersearch.UserID = userinfo.ID
answersearch.PageSize = pageSize answersearch.PageSize = pageSize
answersearch.Page = page answersearch.Page = page
if order == "newest" { if order == "newest" {
@ -337,7 +337,7 @@ func (qs *QuestionService) SearchUserCollectionList(ctx context.Context, page, p
return list, 0, nil return list, 0, nil
} }
collectionSearch := &entity.CollectionSearch{} collectionSearch := &entity.CollectionSearch{}
collectionSearch.UserID = userinfo.UserId collectionSearch.UserID = userinfo.ID
collectionSearch.Page = page collectionSearch.Page = page
collectionSearch.PageSize = pageSize collectionSearch.PageSize = pageSize
collectionlist, count, err := qs.collectionCommon.SearchList(ctx, collectionSearch) collectionlist, count, err := qs.collectionCommon.SearchList(ctx, collectionSearch)
@ -384,13 +384,13 @@ func (qs *QuestionService) SearchUserTopList(ctx context.Context, userName strin
search.Order = "score" search.Order = "score"
search.Page = 0 search.Page = 0
search.PageSize = 5 search.PageSize = 5
search.UserID = userinfo.UserId search.UserID = userinfo.ID
questionlist, _, err := qs.SearchList(ctx, search, loginUserID) questionlist, _, err := qs.SearchList(ctx, search, loginUserID)
if err != nil { if err != nil {
return userQuestionlist, userAnswerlist, err return userQuestionlist, userAnswerlist, err
} }
answersearch := &entity.AnswerSearch{} answersearch := &entity.AnswerSearch{}
answersearch.UserID = userinfo.UserId answersearch.UserID = userinfo.ID
answersearch.PageSize = 5 answersearch.PageSize = 5
answersearch.Order = entity.Answer_Search_OrderBy_Vote answersearch.Order = entity.Answer_Search_OrderBy_Vote
questionIDs := make([]string, 0) questionIDs := make([]string, 0)
@ -494,7 +494,7 @@ func (qs *QuestionService) SearchList(ctx context.Context, req *schema.QuestionS
if !exist { if !exist {
return list, 0, err return list, 0, err
} }
req.UserID = userinfo.UserId req.UserID = userinfo.ID
} }
questionList, count, err := qs.questionRepo.SearchList(ctx, req) questionList, count, err := qs.questionRepo.SearchList(ctx, req)
if err != nil { if err != nil {

View File

@ -47,7 +47,7 @@ type UserRankRepo interface {
// RankService rank service // RankService rank service
type RankService struct { type RankService struct {
userRepo usercommon.UserRepo userCommon *usercommon.UserCommon
configRepo config.ConfigRepo configRepo config.ConfigRepo
userRankRepo UserRankRepo userRankRepo UserRankRepo
objectInfoService *object_info.ObjService objectInfoService *object_info.ObjService
@ -55,12 +55,12 @@ type RankService struct {
// NewRankService new rank service // NewRankService new rank service
func NewRankService( func NewRankService(
userRepo usercommon.UserRepo, userCommon *usercommon.UserCommon,
userRankRepo UserRankRepo, userRankRepo UserRankRepo,
objectInfoService *object_info.ObjService, objectInfoService *object_info.ObjService,
configRepo config.ConfigRepo) *RankService { configRepo config.ConfigRepo) *RankService {
return &RankService{ return &RankService{
userRepo: userRepo, userCommon: userCommon,
configRepo: configRepo, configRepo: configRepo,
userRankRepo: userRankRepo, userRankRepo: userRankRepo,
objectInfoService: objectInfoService, objectInfoService: objectInfoService,
@ -74,7 +74,7 @@ func (rs *RankService) CheckRankPermission(ctx context.Context, userID string, a
} }
// get the rank of the current user // get the rank of the current user
userInfo, exist, err := rs.userRepo.GetByUserID(ctx, userID) userInfo, exist, err := rs.userCommon.GetUserBasicInfoByID(ctx, userID)
if err != nil { if err != nil {
return false, err return false, err
} }
@ -101,7 +101,7 @@ func (rs *RankService) CheckRankPermission(ctx context.Context, userID string, a
func (rs *RankService) GetRankPersonalWithPage(ctx context.Context, req *schema.GetRankPersonalWithPageReq) ( func (rs *RankService) GetRankPersonalWithPage(ctx context.Context, req *schema.GetRankPersonalWithPageReq) (
pageModel *pager.PageModel, err error) { pageModel *pager.PageModel, err error) {
if len(req.Username) > 0 { if len(req.Username) > 0 {
userInfo, exist, err := rs.userRepo.GetByUsername(ctx, req.Username) userInfo, exist, err := rs.userCommon.GetUserBasicInfoByUserName(ctx, req.Username)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -140,5 +140,5 @@ func (rs *RankService) GetRankPersonalWithPage(ctx context.Context, req *schema.
} }
resp = append(resp, commentResp) resp = append(resp, commentResp)
} }
return pager.NewPageModel(req.Page, req.PageSize, total, resp), nil return pager.NewPageModel(total, resp), nil
} }

View File

@ -2,9 +2,10 @@ package report_backyard
import ( import (
"context" "context"
"github.com/segmentfault/answer/internal/service/config"
"strings" "strings"
"github.com/segmentfault/answer/internal/service/config"
"github.com/jinzhu/copier" "github.com/jinzhu/copier"
"github.com/segmentfault/answer/internal/base/pager" "github.com/segmentfault/answer/internal/base/pager"
"github.com/segmentfault/answer/internal/base/reason" "github.com/segmentfault/answer/internal/base/reason"
@ -93,7 +94,7 @@ func (rs *ReportBackyardService) ListReportPage(ctx context.Context, dto schema.
} }
rs.parseObject(ctx, &resp) rs.parseObject(ctx, &resp)
return pager.NewPageModel(dto.Page, dto.PageSize, total, resp), nil return pager.NewPageModel(total, resp), nil
} }
// HandleReported handle the reported object // HandleReported handle the reported object

View File

@ -13,25 +13,24 @@ import (
"github.com/segmentfault/answer/internal/service/revision" "github.com/segmentfault/answer/internal/service/revision"
usercommon "github.com/segmentfault/answer/internal/service/user_common" usercommon "github.com/segmentfault/answer/internal/service/user_common"
"github.com/segmentfault/pacman/errors" "github.com/segmentfault/pacman/errors"
"github.com/segmentfault/pacman/log"
) )
// RevisionService user service // RevisionService user service
type RevisionService struct { type RevisionService struct {
revisionRepo revision.RevisionRepo revisionRepo revision.RevisionRepo
userRepo usercommon.UserRepo userCommon *usercommon.UserCommon
questionCommon *questioncommon.QuestionCommon questionCommon *questioncommon.QuestionCommon
answerService *AnswerService answerService *AnswerService
} }
func NewRevisionService( func NewRevisionService(
revisionRepo revision.RevisionRepo, revisionRepo revision.RevisionRepo,
userRepo usercommon.UserRepo, userCommon *usercommon.UserCommon,
questionCommon *questioncommon.QuestionCommon, questionCommon *questioncommon.QuestionCommon,
answerService *AnswerService) *RevisionService { answerService *AnswerService) *RevisionService {
return &RevisionService{ return &RevisionService{
revisionRepo: revisionRepo, revisionRepo: revisionRepo,
userRepo: userRepo, userCommon: userCommon,
questionCommon: questionCommon, questionCommon: questionCommon,
answerService: answerService, answerService: answerService,
} }
@ -70,7 +69,6 @@ func (rs *RevisionService) GetRevisionList(ctx context.Context, req *schema.GetR
) )
resp = []schema.GetRevisionResp{} resp = []schema.GetRevisionResp{}
_ = copier.Copy(&rev, req) _ = copier.Copy(&rev, req)
revs, err = rs.revisionRepo.GetRevisionList(ctx, &rev) revs, err = rs.revisionRepo.GetRevisionList(ctx, &rev)
@ -80,29 +78,24 @@ func (rs *RevisionService) GetRevisionList(ctx context.Context, req *schema.GetR
for _, r := range revs { for _, r := range revs {
var ( var (
userInfo *entity.User uinfo schema.UserBasicInfo
uinfo schema.UserBasicInfo item schema.GetRevisionResp
item schema.GetRevisionResp
exists bool
) )
_ = copier.Copy(&item, r) _ = copier.Copy(&item, r)
rs.parseItem(ctx, &item) rs.parseItem(ctx, &item)
// get user info // get user info
userInfo, exists, err = rs.userRepo.GetByUserID(ctx, item.UserID) userInfo, exists, e := rs.userCommon.GetUserBasicInfoByID(ctx, item.UserID)
if err != nil { if e != nil {
return return nil, e
} }
if exists { if exists {
err = copier.Copy(&uinfo, userInfo) err = copier.Copy(&uinfo, userInfo)
item.UserInfo = uinfo item.UserInfo = uinfo
} }
resp = append(resp, item) resp = append(resp, item)
} }
return return
} }
@ -125,7 +118,6 @@ func (rs *RevisionService) parseItem(ctx context.Context, item *schema.GetRevisi
} }
questionInfo = rs.questionCommon.ShowFormat(ctx, &question) questionInfo = rs.questionCommon.ShowFormat(ctx, &question)
item.ContentParsed = questionInfo item.ContentParsed = questionInfo
log.Error(item.ContentParsed)
case constant.ObjectTypeStrMapping["answer"]: case constant.ObjectTypeStrMapping["answer"]:
err = json.Unmarshal([]byte(item.Content), &answer) err = json.Unmarshal([]byte(item.Content), &answer)
if err != nil { if err != nil {
@ -156,6 +148,5 @@ func (rs *RevisionService) parseItem(ctx context.Context, item *schema.GetRevisi
if err != nil { if err != nil {
item.ContentParsed = item.Content item.ContentParsed = item.Content
} }
item.CreatedAtParsed = item.CreatedAt.Unix() item.CreatedAtParsed = item.CreatedAt.Unix()
} }

View File

@ -5,25 +5,24 @@ import (
"regexp" "regexp"
"strings" "strings"
"github.com/segmentfault/answer/internal/entity"
"github.com/segmentfault/answer/internal/schema" "github.com/segmentfault/answer/internal/schema"
"github.com/segmentfault/answer/internal/service/search_common" "github.com/segmentfault/answer/internal/service/search_common"
usercommon "github.com/segmentfault/answer/internal/service/user_common" usercommon "github.com/segmentfault/answer/internal/service/user_common"
) )
type AuthorSearch struct { type AuthorSearch struct {
repo search_common.SearchRepo repo search_common.SearchRepo
userRepo usercommon.UserRepo userCommon *usercommon.UserCommon
exp string exp string
w string w string
page int page int
size int size int
} }
func NewAuthorSearch(repo search_common.SearchRepo, userRepo usercommon.UserRepo) *AuthorSearch { func NewAuthorSearch(repo search_common.SearchRepo, userCommon *usercommon.UserCommon) *AuthorSearch {
return &AuthorSearch{ return &AuthorSearch{
repo: repo, repo: repo,
userRepo: userRepo, userCommon: userCommon,
} }
} }
@ -37,9 +36,6 @@ func (s *AuthorSearch) Parse(dto *schema.SearchDTO) (ok bool) {
p, p,
me, me,
name string name string
user *entity.User
has bool
err error
) )
exp = "" exp = ""
q = dto.Query q = dto.Query
@ -51,8 +47,7 @@ func (s *AuthorSearch) Parse(dto *schema.SearchDTO) (ok bool) {
res := re.FindStringSubmatch(q) res := re.FindStringSubmatch(q)
if len(res) == 2 { if len(res) == 2 {
name = res[1] name = res[1]
user, has, err = s.userRepo.GetByUsername(nil, name) user, has, err := s.userCommon.GetUserBasicInfoByUserName(nil, name)
if err == nil && has { if err == nil && has {
exp = user.ID exp = user.ID
trimLen := len(res[0]) trimLen := len(res[0])

View File

@ -35,14 +35,14 @@ type SearchService struct {
func NewSearchService( func NewSearchService(
searchRepo search_common.SearchRepo, searchRepo search_common.SearchRepo,
tagRepo tagcommon.TagRepo, tagRepo tagcommon.TagRepo,
userRepo usercommon.UserRepo, userCommon *usercommon.UserCommon,
followCommon activity_common.FollowRepo, followCommon activity_common.FollowRepo,
) *SearchService { ) *SearchService {
return &SearchService{ return &SearchService{
searchRepo: searchRepo, searchRepo: searchRepo,
tagSearch: search.NewTagSearch(searchRepo, tagRepo, followCommon), tagSearch: search.NewTagSearch(searchRepo, tagRepo, followCommon),
withinSearch: search.NewWithinSearch(searchRepo), withinSearch: search.NewWithinSearch(searchRepo),
authorSearch: search.NewAuthorSearch(searchRepo, userRepo), authorSearch: search.NewAuthorSearch(searchRepo, userCommon),
scoreSearch: search.NewScoreSearch(searchRepo), scoreSearch: search.NewScoreSearch(searchRepo),
answersSearch: search.NewAnswersSearch(searchRepo), answersSearch: search.NewAnswersSearch(searchRepo),
acceptedAnswerSearch: search.NewAcceptedAnswerSearch(searchRepo), acceptedAnswerSearch: search.NewAcceptedAnswerSearch(searchRepo),

View File

@ -358,7 +358,7 @@ func (ts *TagService) GetTagWithPage(ctx context.Context, req *schema.GetTagWith
UpdatedAt: tag.UpdatedAt.Unix(), UpdatedAt: tag.UpdatedAt.Unix(),
}) })
} }
return pager.NewPageModel(page, pageSize, total, resp), nil return pager.NewPageModel(total, resp), nil
} }
// checkTagIsFollow get tag list page // checkTagIsFollow get tag list page

View File

@ -108,17 +108,17 @@ func (us *UserBackyardService) GetUserPage(ctx context.Context, req *schema.GetU
Avatar: u.Avatar, Avatar: u.Avatar,
} }
if u.Status == entity.UserStatusDeleted { if u.Status == entity.UserStatusDeleted {
t.Status = schema.Deleted t.Status = schema.UserDeleted
t.DeletedAt = u.DeletedAt.Unix() t.DeletedAt = u.DeletedAt.Unix()
} else if u.Status == entity.UserStatusSuspended { } else if u.Status == entity.UserStatusSuspended {
t.Status = schema.Suspended t.Status = schema.UserSuspended
t.SuspendedAt = u.SuspendedAt.Unix() t.SuspendedAt = u.SuspendedAt.Unix()
} else if u.MailStatus == entity.EmailStatusToBeVerified { } else if u.MailStatus == entity.EmailStatusToBeVerified {
t.Status = schema.Inactive t.Status = schema.UserInactive
} else { } else {
t.Status = schema.Normal t.Status = schema.UserNormal
} }
resp = append(resp, t) resp = append(resp, t)
} }
return pager.NewPageModel(req.Page, req.PageSize, total, resp), nil return pager.NewPageModel(total, resp), nil
} }

View File

@ -35,12 +35,12 @@ func NewUserCommon(userRepo UserRepo) *UserCommon {
} }
func (us *UserCommon) GetUserBasicInfoByID(ctx context.Context, ID string) (*schema.UserBasicInfo, bool, error) { func (us *UserCommon) GetUserBasicInfoByID(ctx context.Context, ID string) (*schema.UserBasicInfo, bool, error) {
dbInfo, has, err := us.userRepo.GetByUserID(ctx, ID) userInfo, exist, err := us.userRepo.GetByUserID(ctx, ID)
if err != nil { if err != nil {
return nil, has, err return nil, exist, err
} }
info := us.UserBasicInfoFormat(ctx, dbInfo) info := us.UserBasicInfoFormat(ctx, userInfo)
return info, has, nil return info, exist, nil
} }
func (us *UserCommon) GetUserBasicInfoByUserName(ctx context.Context, username string) (*schema.UserBasicInfo, bool, error) { func (us *UserCommon) GetUserBasicInfoByUserName(ctx context.Context, username string) (*schema.UserBasicInfo, bool, error) {
@ -74,16 +74,20 @@ func (us *UserCommon) BatchUserBasicInfoByID(ctx context.Context, IDs []string)
} }
// UserBasicInfoFormat // UserBasicInfoFormat
func (us *UserCommon) UserBasicInfoFormat(ctx context.Context, dbinfo *entity.User) *schema.UserBasicInfo { func (us *UserCommon) UserBasicInfoFormat(ctx context.Context, userInfo *entity.User) *schema.UserBasicInfo {
info := new(schema.UserBasicInfo) userBasicInfo := &schema.UserBasicInfo{}
info.UserId = dbinfo.ID userBasicInfo.ID = userInfo.ID
info.UserName = dbinfo.Username userBasicInfo.Username = userInfo.Username
info.Rank = dbinfo.Rank userBasicInfo.Rank = userInfo.Rank
info.DisplayName = dbinfo.DisplayName userBasicInfo.DisplayName = userInfo.DisplayName
info.Avatar = dbinfo.Avatar userBasicInfo.Avatar = userInfo.Avatar
info.Website = dbinfo.Website userBasicInfo.Website = userInfo.Website
info.Location = dbinfo.Location userBasicInfo.Location = userInfo.Location
info.IpInfo = dbinfo.IPInfo userBasicInfo.IpInfo = userInfo.IPInfo
info.Status = dbinfo.Status userBasicInfo.Status = schema.UserStatusShow[userInfo.Status]
return info if userBasicInfo.Status == schema.UserDeleted {
userBasicInfo.Avatar = ""
userBasicInfo.DisplayName = "Anonymous"
}
return userBasicInfo
} }

View File

@ -97,5 +97,5 @@ func (us *UserGroupService) GetUserGroupWithPage(ctx context.Context, req *schem
resp := &[]schema.GetUserGroupResp{} resp := &[]schema.GetUserGroupResp{}
_ = copier.Copy(resp, userGroups) _ = copier.Copy(resp, userGroups)
return pager.NewPageModel(page, pageSize, total, resp), nil return pager.NewPageModel(total, resp), nil
} }

View File

@ -63,6 +63,25 @@ func (us *UserService) GetUserInfoByUserID(ctx context.Context, token, userID st
return resp, nil return resp, nil
} }
// GetUserStatus get user info by user id
func (us *UserService) GetUserStatus(ctx context.Context, userID string) (resp *schema.GetUserStatusResp, err error) {
resp = &schema.GetUserStatusResp{}
if len(userID) == 0 {
return resp, nil
}
userInfo, exist, err := us.userRepo.GetByUserID(ctx, userID)
if err != nil {
return nil, err
}
if !exist {
return nil, errors.BadRequest(reason.UserNotFound)
}
resp = &schema.GetUserStatusResp{
Status: schema.UserStatusShow[userInfo.Status],
}
return resp, nil
}
func (us *UserService) GetOtherUserInfoByUsername(ctx context.Context, username string) ( func (us *UserService) GetOtherUserInfoByUsername(ctx context.Context, username string) (
resp *schema.GetOtherUserInfoResp, err error) { resp *schema.GetOtherUserInfoResp, err error) {
userInfo, exist, err := us.userRepo.GetByUsername(ctx, username) userInfo, exist, err := us.userRepo.GetByUsername(ctx, username)

View File

@ -193,5 +193,5 @@ func (vs *VoteService) ListUserVotes(ctx context.Context, req schema.GetVoteWith
resp = append(resp, item) resp = append(resp, item)
} }
return pager.NewPageModel(req.Page, req.PageSize, total, resp), err return pager.NewPageModel(total, resp), err
} }

View File

@ -1,75 +0,0 @@
package captcha
import (
"fmt"
"image/color"
"sync"
"github.com/mojocn/base64Captcha"
)
var store base64Captcha.Store
var once sync.Once
func NewCaptcha() {
once.Do(func() {
//var err error
//RedisDb, err = arch.App.Cache.GetQuestion("cache")
//if err != nil {
// store = base64Captcha.DefaultMemStore
// return
//}
//var ctx = context.Background()
//_, err = RedisDb.Ping(ctx).Result()
//
//if err != nil {
// store = base64Captcha.DefaultMemStore
// return
//}
store = RedisStore{}
})
}
// CaptchaClient
type CaptchaClient struct {
}
// NewCaptchaClient
func NewCaptchaClient() *CaptchaClient {
return &CaptchaClient{}
}
func MakeCaptcha() (id, b64s string, err error) {
var driver base64Captcha.Driver
//Configure the parameters of the CAPTCHA
driverString := base64Captcha.DriverString{
Height: 40,
Width: 100,
NoiseCount: 0,
ShowLineOptions: 2 | 4,
Length: 4,
Source: "1234567890qwertyuioplkjhgfdsazxcvbnm",
BgColor: &color.RGBA{R: 3, G: 102, B: 214, A: 125},
Fonts: []string{"wqy-microhei.ttc"},
}
//ConvertFonts Load fonts by name
driver = driverString.ConvertFonts()
//Create Captcha
captcha := base64Captcha.NewCaptcha(driver, store)
//Generate
id, b64s, err = captcha.Generate()
return id, b64s, err
}
// VerifyCaptcha Verification code
func VerifyCaptcha(id string, VerifyValue string) bool {
fmt.Println(id, VerifyValue)
if store.Verify(id, VerifyValue, true) {
//verify successfully
return true
} else {
//Verification failed
return false
}
}

View File

@ -1,46 +0,0 @@
package captcha
import (
"context"
"fmt"
"github.com/segmentfault/pacman/contrib/cache/memory"
)
const CAPTCHA = "captcha:"
var RedisDb = memory.NewCache()
type RedisStore struct {
}
func (r RedisStore) Set(id string, value string) error {
key := CAPTCHA + id
ctx := context.Background()
err := RedisDb.SetString(ctx, key, value, 2)
return err
}
func (r RedisStore) Get(id string, clear bool) string {
key := CAPTCHA + id
ctx := context.Background()
val, err := RedisDb.GetString(ctx, key)
if err != nil {
fmt.Println(err)
return ""
}
if clear {
err := RedisDb.Del(ctx, key)
if err != nil {
fmt.Println(err)
return ""
}
}
return val
}
func (r RedisStore) Verify(id, answer string, clear bool) bool {
v := RedisStore{}.Get(id, clear)
fmt.Println("key:" + id + ";value:" + v + ";answer:" + answer)
return v == answer
}

View File

@ -1,103 +0,0 @@
package email
import (
"bytes"
"fmt"
"net/smtp"
"text/template"
"github.com/jordan-wright/email"
"github.com/segmentfault/pacman/log"
)
// EmailClient
type EmailClient struct {
email *email.Email
config *Config
}
// Config .
type Config struct {
WebName string `json:"web_name"`
WebHost string `json:"web_host"`
SecretKey string `json:"secret_key"`
UserSessionKey string `json:"user_session_key"`
EmailFrom string `json:"email_from"`
EmailFromPass string `json:"email_from_pass"`
EmailFromHostname string `json:"email_from_hostname"`
EmailFromSMTP string `json:"email_from_smtp"`
EmailFromName string `json:"email_from_name"`
RegisterTitle string `json:"register_title"`
RegisterBody string `json:"register_body"`
PassResetTitle string `json:"pass_reset_title"`
PassResetBody string `json:"pass_reset_body"`
}
// NewEmailClient
func NewEmailClient() *EmailClient {
return &EmailClient{
email: email.NewEmail(),
}
}
func (s *EmailClient) Send(ToEmail, Title, Body string) {
from := s.config.EmailFrom
fromPass := s.config.EmailFromPass
fromName := s.config.EmailFromName
fromSmtp := s.config.EmailFromSMTP
fromHostName := s.config.EmailFromHostname
s.email.From = fmt.Sprintf("%s <%s>", fromName, from)
s.email.To = []string{ToEmail}
s.email.Subject = Title
s.email.HTML = []byte(Body)
err := s.email.Send(fromSmtp, smtp.PlainAuth("", from, fromPass, fromHostName))
if err != nil {
log.Error(err)
}
}
func (s *EmailClient) RegisterTemplate(RegisterUrl string) (Title, Body string, err error) {
webName := s.config.WebName
templateData := RegisterTemplateData{webName, RegisterUrl}
tmpl, err := template.New("register_title").Parse(s.config.RegisterTitle)
if err != nil {
return "", "", err
}
title := new(bytes.Buffer)
body := new(bytes.Buffer)
err = tmpl.Execute(title, templateData)
if err != nil {
return "", "", err
}
tmpl, err = template.New("register_body").Parse(s.config.RegisterBody)
err = tmpl.Execute(body, templateData)
if err != nil {
return "", "", err
}
return title.String(), body.String(), nil
}
func (s *EmailClient) PassResetTemplate(PassResetUrl string) (Title, Body string, err error) {
webName := s.config.WebName
templateData := PassResetTemplateData{webName, PassResetUrl}
tmpl, err := template.New("pass_reset_title").Parse(s.config.PassResetTitle)
if err != nil {
return "", "", err
}
title := new(bytes.Buffer)
body := new(bytes.Buffer)
err = tmpl.Execute(title, templateData)
if err != nil {
return "", "", err
}
tmpl, err = template.New("pass_reset_body").Parse(s.config.PassResetBody)
err = tmpl.Execute(body, templateData)
if err != nil {
return "", "", err
}
return title.String(), body.String(), nil
}

View File

@ -1,11 +0,0 @@
package email
type RegisterTemplateData struct {
SiteName string
RegisterUrl string
}
type PassResetTemplateData struct {
SiteName string
PassResetUrl string
}

5
script/entrypoint.sh Executable file
View File

@ -0,0 +1,5 @@
#!/bin/bash
if [ ! -f "/data/config.yaml" ]; then
/usr/bin/answer init
fi
/usr/bin/answer run -c /data/config.yaml

View File

@ -18,7 +18,7 @@ const Index: FC<Props> = ({
}) => { }) => {
return ( return (
<div className={`text-secondary ${className}`}> <div className={`text-secondary ${className}`}>
{data.status !== 'deleted' ? ( {data?.status !== 'deleted' ? (
<Link to={`/users/${data?.username}`}> <Link to={`/users/${data?.username}`}>
{showAvatar && ( {showAvatar && (
<Avatar avatar={data?.avatar} size={avatarSize} className="me-1" /> <Avatar avatar={data?.avatar} size={avatarSize} className="me-1" />

9
ui/static.go Normal file
View File

@ -0,0 +1,9 @@
package ui
import (
"embed"
_ "embed"
)
//go:embed build
var Build embed.FS

View File

@ -1,9 +0,0 @@
package web
import "embed"
//go:embed html/index.html
var Html []byte
//go:embed html/static
var Static embed.FS