answer/internal/controller/template_controller.go

469 lines
13 KiB
Go
Raw Normal View History

2022-11-29 15:48:26 +08:00
package controller
2022-11-29 17:20:28 +08:00
import (
2022-12-02 16:16:49 +08:00
"encoding/json"
2022-11-30 18:10:38 +08:00
"fmt"
2022-11-30 14:45:23 +08:00
"html/template"
2022-11-29 17:20:28 +08:00
"net/http"
"regexp"
2022-12-06 12:17:45 +08:00
"strings"
2022-11-30 18:10:38 +08:00
"time"
2022-11-29 17:20:28 +08:00
2022-12-08 11:15:45 +08:00
"github.com/answerdev/answer/internal/base/constant"
2022-11-30 16:18:23 +08:00
"github.com/answerdev/answer/internal/base/handler"
2022-11-30 12:13:26 +08:00
templaterender "github.com/answerdev/answer/internal/controller/template_render"
"github.com/answerdev/answer/internal/schema"
2022-11-30 17:48:35 +08:00
"github.com/answerdev/answer/internal/service/siteinfo_common"
2022-12-12 15:20:20 +08:00
"github.com/answerdev/answer/pkg/converter"
2022-12-02 16:42:40 +08:00
"github.com/answerdev/answer/pkg/htmltext"
2022-12-09 16:51:33 +08:00
"github.com/answerdev/answer/pkg/obj"
2022-11-29 17:20:28 +08:00
"github.com/answerdev/answer/ui"
"github.com/gin-gonic/gin"
2022-11-30 17:48:35 +08:00
"github.com/segmentfault/pacman/log"
2022-11-29 17:20:28 +08:00
)
2022-11-29 15:48:26 +08:00
type TemplateController struct {
2022-11-30 12:13:26 +08:00
scriptPath string
cssPath string
templateRenderController *templaterender.TemplateRenderController
2022-11-30 17:48:35 +08:00
siteInfoService *siteinfo_common.SiteInfoCommonService
2022-11-29 15:48:26 +08:00
}
// NewTemplateController new controller
2022-11-30 12:13:26 +08:00
func NewTemplateController(
templateRenderController *templaterender.TemplateRenderController,
2022-11-30 17:48:35 +08:00
siteInfoService *siteinfo_common.SiteInfoCommonService,
2022-11-30 12:13:26 +08:00
) *TemplateController {
2022-11-29 17:20:28 +08:00
script, css := GetStyle()
return &TemplateController{
2022-11-30 12:13:26 +08:00
scriptPath: script,
cssPath: css,
templateRenderController: templateRenderController,
2022-11-30 17:48:35 +08:00
siteInfoService: siteInfoService,
2022-11-29 17:20:28 +08:00
}
}
func GetStyle() (script, css string) {
file, err := ui.Build.ReadFile("build/index.html")
if err != nil {
return
}
scriptRegexp := regexp.MustCompile(`<script defer="defer" src="(.*)"></script>`)
scriptData := scriptRegexp.FindStringSubmatch(string(file))
cssRegexp := regexp.MustCompile(`<link href="(.*)" rel="stylesheet">`)
cssListData := cssRegexp.FindStringSubmatch(string(file))
if len(scriptData) == 2 {
script = scriptData[1]
}
if len(cssListData) == 2 {
css = cssListData[1]
}
return
}
2022-11-30 18:10:38 +08:00
func (tc *TemplateController) SiteInfo(ctx *gin.Context) *schema.TemplateSiteInfoResp {
2022-11-30 17:48:35 +08:00
var err error
2022-11-30 18:10:38 +08:00
resp := &schema.TemplateSiteInfoResp{}
2022-11-30 17:48:35 +08:00
resp.General, err = tc.siteInfoService.GetSiteGeneral(ctx)
if err != nil {
log.Error(err)
}
resp.Interface, err = tc.siteInfoService.GetSiteInterface(ctx)
if err != nil {
log.Error(err)
}
resp.Branding, err = tc.siteInfoService.GetSiteBranding(ctx)
if err != nil {
log.Error(err)
}
2022-12-13 16:55:23 +08:00
resp.SiteSeo, err = tc.siteInfoService.GetSiteSeo(ctx)
if err != nil {
log.Error(err)
}
2022-11-30 18:10:38 +08:00
resp.Year = fmt.Sprintf("%d", time.Now().Year())
2022-11-30 17:48:35 +08:00
return resp
}
2022-11-29 17:20:28 +08:00
// Index question list
func (tc *TemplateController) Index(ctx *gin.Context) {
req := &schema.QuestionSearch{
Order: "newest",
}
2022-11-30 17:47:40 +08:00
if handler.BindAndCheck(ctx, req) {
2022-12-01 14:20:41 +08:00
tc.Page404(ctx)
2022-11-30 17:47:40 +08:00
return
}
var page = req.Page
2022-11-30 17:47:40 +08:00
data, count, err := tc.templateRenderController.Index(ctx, req)
if err != nil {
tc.Page404(ctx)
return
}
2022-12-05 11:27:37 +08:00
2022-12-01 14:20:41 +08:00
siteInfo := tc.SiteInfo(ctx)
siteInfo.Canonical = fmt.Sprintf("%s", siteInfo.General.SiteUrl)
2022-12-09 17:10:15 +08:00
UrlUseTitle := false
2022-12-13 16:55:23 +08:00
if siteInfo.SiteSeo.PermaLink == schema.PermaLinkQuestionIDAndTitle {
2022-12-09 17:10:15 +08:00
UrlUseTitle = true
}
2022-12-12 14:09:14 +08:00
siteInfo.Title = ""
2022-12-05 11:27:37 +08:00
tc.html(ctx, http.StatusOK, "question.html", siteInfo, gin.H{
2022-12-09 17:10:15 +08:00
"data": data,
"useTitle": UrlUseTitle,
"page": templaterender.Paginator(page, req.PageSize, count),
2022-12-13 17:58:48 +08:00
"path": "questions",
2022-12-01 14:20:41 +08:00
})
}
func (tc *TemplateController) QuestionList(ctx *gin.Context) {
req := &schema.QuestionSearch{
Order: "newest",
}
2022-12-01 14:20:41 +08:00
if handler.BindAndCheck(ctx, req) {
tc.Page404(ctx)
return
}
var page = req.Page
data, count, err := tc.templateRenderController.Index(ctx, req)
if err != nil {
tc.Page404(ctx)
return
}
siteInfo := tc.SiteInfo(ctx)
siteInfo.Canonical = fmt.Sprintf("%s/questions", siteInfo.General.SiteUrl)
2022-12-05 11:27:37 +08:00
2022-12-09 17:10:15 +08:00
UrlUseTitle := false
2022-12-13 16:55:23 +08:00
if siteInfo.SiteSeo.PermaLink == schema.PermaLinkQuestionIDAndTitle {
2022-12-09 17:10:15 +08:00
UrlUseTitle = true
}
2022-12-12 14:18:37 +08:00
siteInfo.Title = fmt.Sprintf("Questions - %s", siteInfo.General.Name)
2022-12-05 11:27:37 +08:00
tc.html(ctx, http.StatusOK, "question.html", siteInfo, gin.H{
2022-12-09 17:10:15 +08:00
"data": data,
"useTitle": UrlUseTitle,
"page": templaterender.Paginator(page, req.PageSize, count),
2022-11-29 17:20:28 +08:00
})
}
2022-12-13 17:58:48 +08:00
func (tc *TemplateController) QuestionInfo301Jump(ctx *gin.Context, siteInfo *schema.TemplateSiteInfoResp, correctTitle bool) (jump bool, url string) {
2022-12-09 16:51:33 +08:00
id := ctx.Param("id")
title := ctx.Param("title")
titleIsAnswerID := false
objectType, objectTypeerr := obj.GetObjectTypeStrByObjectID(title)
if objectTypeerr == nil {
if objectType == constant.AnswerObjectType {
titleIsAnswerID = true
}
}
url = fmt.Sprintf("%s/questions/%s", siteInfo.General.SiteUrl, id)
2022-12-13 16:55:23 +08:00
if siteInfo.SiteSeo.PermaLink == schema.PermaLinkQuestionID {
2022-12-09 16:51:33 +08:00
//not have title
if titleIsAnswerID || len(title) == 0 {
return false, ""
}
return true, url
} else {
//have title
2022-12-13 17:58:48 +08:00
if len(title) > 0 && !titleIsAnswerID && correctTitle {
2022-12-09 16:51:33 +08:00
return false, ""
}
detail, err := tc.templateRenderController.QuestionDetail(ctx, id)
if err != nil {
tc.Page404(ctx)
return
}
url = fmt.Sprintf("%s/%s", url, htmltext.UrlTitle(detail.Title))
return true, url
}
}
2022-11-29 17:20:28 +08:00
// QuestionInfo question and answers info
func (tc *TemplateController) QuestionInfo(ctx *gin.Context) {
id := ctx.Param("id")
2022-12-13 17:58:48 +08:00
title := ctx.Param("title")
2022-11-29 17:20:28 +08:00
answerid := ctx.Param("answerid")
2022-12-01 15:30:02 +08:00
2022-12-13 17:58:48 +08:00
correctTitle := false
2022-12-09 16:51:33 +08:00
2022-12-01 15:30:02 +08:00
detail, err := tc.templateRenderController.QuestionDetail(ctx, id)
if err != nil {
tc.Page404(ctx)
return
}
2022-12-13 17:58:48 +08:00
encodeTitle := htmltext.UrlTitle(detail.Title)
if encodeTitle == title {
correctTitle = true
}
siteInfo := tc.SiteInfo(ctx)
jump, jumpurl := tc.QuestionInfo301Jump(ctx, siteInfo, correctTitle)
if jump {
ctx.Redirect(http.StatusMovedPermanently, jumpurl)
return
}
2022-12-01 15:30:02 +08:00
// answers
answerReq := &schema.AnswerListReq{
QuestionID: id,
Order: "",
Page: 1,
PageSize: 999,
UserID: "",
2022-12-01 15:30:02 +08:00
}
2022-12-02 16:16:49 +08:00
answers, answerCount, err := tc.templateRenderController.AnswerList(ctx, answerReq)
2022-12-01 15:30:02 +08:00
if err != nil {
tc.Page404(ctx)
return
}
// comments
objectIDs := []string{id}
for _, answer := range answers {
objectIDs = append(objectIDs, answer.ID)
}
comments, err := tc.templateRenderController.CommentList(ctx, objectIDs)
if err != nil {
tc.Page404(ctx)
return
}
2022-12-02 16:16:49 +08:00
siteInfo.Canonical = fmt.Sprintf("%s/questions/%s/%s", siteInfo.General.SiteUrl, id, encodeTitle)
2022-12-13 16:55:23 +08:00
if siteInfo.SiteSeo.PermaLink == schema.PermaLinkQuestionID {
2022-12-02 17:27:30 +08:00
siteInfo.Canonical = fmt.Sprintf("%s/questions/%s", siteInfo.General.SiteUrl, id)
}
2022-12-02 16:16:49 +08:00
jsonLD := &schema.QAPageJsonLD{}
jsonLD.Context = "https://schema.org"
jsonLD.Type = "QAPage"
jsonLD.MainEntity.Type = "Question"
jsonLD.MainEntity.Name = detail.Title
2022-12-09 11:42:20 +08:00
jsonLD.MainEntity.Text = detail.HTML
2022-12-02 16:16:49 +08:00
jsonLD.MainEntity.AnswerCount = int(answerCount)
jsonLD.MainEntity.UpvoteCount = detail.VoteCount
jsonLD.MainEntity.DateCreated = time.Unix(detail.CreateTime, 0)
jsonLD.MainEntity.Author.Type = "Person"
jsonLD.MainEntity.Author.Name = detail.UserInfo.DisplayName
answerList := make([]*schema.SuggestedAnswerItem, 0)
for _, answer := range answers {
2022-12-09 11:15:23 +08:00
if answer.Adopted == schema.AnswerAdoptedEnable {
2022-12-13 17:58:48 +08:00
acceptedAnswerItem := &schema.AcceptedAnswerItem{}
acceptedAnswerItem.Type = "Answer"
acceptedAnswerItem.Text = answer.HTML
acceptedAnswerItem.DateCreated = time.Unix(answer.CreateTime, 0)
acceptedAnswerItem.UpvoteCount = answer.VoteCount
acceptedAnswerItem.URL = fmt.Sprintf("%s/%s", siteInfo.Canonical, answer.ID)
acceptedAnswerItem.Author.Type = "Person"
acceptedAnswerItem.Author.Name = answer.UserInfo.DisplayName
jsonLD.MainEntity.AcceptedAnswer = acceptedAnswerItem
2022-12-09 11:15:23 +08:00
} else {
item := &schema.SuggestedAnswerItem{}
item.Type = "Answer"
2022-12-09 11:42:20 +08:00
item.Text = answer.HTML
2022-12-09 11:15:23 +08:00
item.DateCreated = time.Unix(answer.CreateTime, 0)
item.UpvoteCount = answer.VoteCount
item.URL = fmt.Sprintf("%s/%s", siteInfo.Canonical, answer.ID)
item.Author.Type = "Person"
item.Author.Name = answer.UserInfo.DisplayName
answerList = append(answerList, item)
}
2022-12-02 16:16:49 +08:00
}
jsonLD.MainEntity.SuggestedAnswer = answerList
jsonLDStr, err := json.Marshal(jsonLD)
if err == nil {
siteInfo.JsonLD = `<script data-react-helmet="true" type="application/ld+json">` + string(jsonLDStr) + ` </script>`
}
2022-12-01 15:30:02 +08:00
2022-12-06 12:17:45 +08:00
siteInfo.Description = htmltext.FetchExcerpt(detail.HTML, "...", 240)
tags := make([]string, 0)
for _, tag := range detail.Tags {
tags = append(tags, tag.DisplayName)
}
siteInfo.Keywords = strings.Replace(strings.Trim(fmt.Sprint(tags), "[]"), " ", ",", -1)
2022-12-12 14:18:37 +08:00
siteInfo.Title = fmt.Sprintf("%s - %s", detail.Title, siteInfo.General.Name)
2022-12-05 11:27:37 +08:00
tc.html(ctx, http.StatusOK, "question-detail.html", siteInfo, gin.H{
"id": id,
"answerid": answerid,
"detail": detail,
"answers": answers,
"comments": comments,
2022-11-29 17:20:28 +08:00
})
}
// TagList tags list
func (tc *TemplateController) TagList(ctx *gin.Context) {
2022-11-30 16:18:23 +08:00
req := &schema.GetTagWithPageReq{}
if handler.BindAndCheck(ctx, req) {
return
}
data, err := tc.templateRenderController.TagList(ctx, req)
if err != nil {
tc.Page404(ctx)
2022-11-30 16:18:23 +08:00
return
}
page := templaterender.Paginator(req.Page, req.PageSize, data.Count)
2022-12-05 11:27:37 +08:00
2022-12-01 14:20:41 +08:00
siteInfo := tc.SiteInfo(ctx)
siteInfo.Canonical = fmt.Sprintf("%s/tags", siteInfo.General.SiteUrl)
2022-12-12 14:18:37 +08:00
siteInfo.Title = fmt.Sprintf("%s - %s", "Tags", siteInfo.General.Name)
2022-12-05 11:27:37 +08:00
tc.html(ctx, http.StatusOK, "tags.html", siteInfo, gin.H{
"page": page,
"data": data,
2022-11-29 17:20:28 +08:00
})
2022-11-29 15:48:26 +08:00
}
2022-11-29 17:20:28 +08:00
// TagInfo taginfo
func (tc *TemplateController) TagInfo(ctx *gin.Context) {
tag := ctx.Param("tag")
2022-11-30 19:00:42 +08:00
req := &schema.GetTamplateTagInfoReq{}
2022-11-30 19:14:14 +08:00
if handler.BindAndCheck(ctx, req) {
tc.Page404(ctx)
2022-11-30 19:14:14 +08:00
return
}
nowPage := req.Page
2022-11-30 17:39:19 +08:00
req.Name = tag
2022-11-30 19:00:42 +08:00
taginifo, questionList, questionCount, err := tc.templateRenderController.TagInfo(ctx, req)
2022-11-30 17:39:19 +08:00
if err != nil {
tc.Page404(ctx)
2022-11-30 17:39:19 +08:00
return
}
2022-11-30 19:14:14 +08:00
page := templaterender.Paginator(nowPage, req.PageSize, questionCount)
2022-12-05 11:27:37 +08:00
2022-12-01 14:20:41 +08:00
siteInfo := tc.SiteInfo(ctx)
siteInfo.Canonical = fmt.Sprintf("%s/tags/%s", siteInfo.General.SiteUrl, tag)
2022-12-06 12:17:45 +08:00
siteInfo.Description = htmltext.FetchExcerpt(taginifo.ParsedText, "...", 240)
2022-12-09 10:43:54 +08:00
if len(taginifo.ParsedText) == 0 {
siteInfo.Description = "The tag has no description."
}
2022-12-06 12:17:45 +08:00
siteInfo.Keywords = taginifo.DisplayName
2022-12-09 17:10:15 +08:00
UrlUseTitle := false
2022-12-13 16:55:23 +08:00
if siteInfo.SiteSeo.PermaLink == schema.PermaLinkQuestionIDAndTitle {
2022-12-09 17:10:15 +08:00
UrlUseTitle = true
}
2022-12-12 14:29:13 +08:00
siteInfo.Title = fmt.Sprintf("'%s' Questions - %s", taginifo.DisplayName, siteInfo.General.Name)
2022-12-05 11:27:37 +08:00
tc.html(ctx, http.StatusOK, "tag-detail.html", siteInfo, gin.H{
2022-11-30 19:00:42 +08:00
"tag": taginifo,
"questionList": questionList,
"questionCount": questionCount,
2022-12-09 17:10:15 +08:00
"useTitle": UrlUseTitle,
2022-11-30 19:14:14 +08:00
"page": page,
2022-11-29 17:20:28 +08:00
})
}
// UserInfo user info
func (tc *TemplateController) UserInfo(ctx *gin.Context) {
2022-11-30 14:25:03 +08:00
username := ctx.Param("username")
2022-12-08 11:15:45 +08:00
if username == "" {
tc.Page404(ctx)
return
}
exist := constant.ExistInPathIgnore(username)
if exist {
file, err := ui.Build.ReadFile("build/index.html")
if err != nil {
log.Error(err)
tc.Page404(ctx)
return
}
ctx.Header("content-type", "text/html;charset=utf-8")
ctx.String(http.StatusOK, string(file))
return
}
2022-11-30 12:13:26 +08:00
req := &schema.GetOtherUserInfoByUsernameReq{}
2022-11-30 14:25:03 +08:00
req.Username = username
userinfo, err := tc.templateRenderController.UserInfo(ctx, req)
2022-11-30 18:15:21 +08:00
if !userinfo.Has {
tc.Page404(ctx)
2022-11-30 18:15:21 +08:00
return
}
2022-11-30 14:25:03 +08:00
if err != nil {
tc.Page404(ctx)
2022-11-30 12:13:26 +08:00
return
}
2022-12-01 14:20:41 +08:00
siteInfo := tc.SiteInfo(ctx)
siteInfo.Canonical = fmt.Sprintf("%s/users/%s", siteInfo.General.SiteUrl, username)
2022-12-12 14:18:37 +08:00
siteInfo.Title = fmt.Sprintf("%s - %s", username, siteInfo.General.Name)
2022-12-05 11:27:37 +08:00
tc.html(ctx, http.StatusOK, "homepage.html", siteInfo, gin.H{
"userinfo": userinfo,
"bio": template.HTML(userinfo.Info.BioHTML),
2022-11-29 17:20:28 +08:00
})
2022-12-07 11:11:14 +08:00
2022-11-29 17:20:28 +08:00
}
2022-11-30 11:23:04 +08:00
func (tc *TemplateController) Page404(ctx *gin.Context) {
tc.html(ctx, http.StatusNotFound, "404.html", tc.SiteInfo(ctx), gin.H{})
2022-11-30 11:23:04 +08:00
}
2022-12-05 11:27:37 +08:00
func (tc *TemplateController) html(ctx *gin.Context, code int, tpl string, siteInfo *schema.TemplateSiteInfoResp, data gin.H) {
data["siteinfo"] = siteInfo
2022-12-07 15:52:53 +08:00
data["scriptPath"] = tc.scriptPath
2022-12-05 11:27:37 +08:00
data["cssPath"] = tc.cssPath
2022-12-06 12:17:45 +08:00
data["keywords"] = siteInfo.Keywords
if siteInfo.Description == "" {
siteInfo.Description = siteInfo.General.Description
}
2022-12-12 13:54:27 +08:00
data["title"] = siteInfo.Title
if siteInfo.Title == "" {
data["title"] = siteInfo.General.Name
}
2022-12-06 12:17:45 +08:00
data["description"] = siteInfo.Description
data["language"] = handler.GetLang(ctx)
2022-12-05 11:27:37 +08:00
data["timezone"] = siteInfo.Interface.TimeZone
2022-12-13 17:58:48 +08:00
_, ok := data["path"]
if !ok {
data["path"] = ""
}
2022-12-05 11:27:37 +08:00
ctx.HTML(code, tpl, data)
}
2022-12-12 15:20:20 +08:00
func (tc *TemplateController) Sitemap(ctx *gin.Context) {
if tc.checkPrivateMode(ctx) {
tc.Page404(ctx)
return
}
2022-12-12 16:33:08 +08:00
tc.templateRenderController.Sitemap(ctx)
2022-12-12 15:39:12 +08:00
}
func (tc *TemplateController) SitemapPage(ctx *gin.Context) {
if tc.checkPrivateMode(ctx) {
tc.Page404(ctx)
return
}
2022-12-12 15:20:20 +08:00
page := 0
pageParam := ctx.Param("page")
pageRegexp := regexp.MustCompile(`question-(.*).xml`)
pageStr := pageRegexp.FindStringSubmatch(pageParam)
if len(pageStr) != 2 {
tc.Page404(ctx)
return
}
page = converter.StringToInt(pageStr[1])
if page == 0 {
tc.Page404(ctx)
return
}
2022-12-12 16:35:46 +08:00
err := tc.templateRenderController.SitemapPage(ctx, page)
2022-12-12 15:39:12 +08:00
if err != nil {
tc.Page404(ctx)
return
}
2022-12-12 15:20:20 +08:00
}
func (tc *TemplateController) checkPrivateMode(ctx *gin.Context) bool {
resp, err := tc.siteInfoService.GetSiteLogin(ctx)
if err != nil {
log.Error(err)
return false
}
if resp.LoginRequired {
return true
}
return false
}