// 首页或是列表页 func pagePosts(w http.ResponseWriter, r *http.Request) { info, err := getInfo() if err != nil { logs.Error("pagePosts:", err) pageHttpStatusCode(w, r, http.StatusInternalServerError) return } page := conv.MustInt(r.FormValue("page"), 1) if page == 1 { info.Canonical = app.URL(app.HomeURL()) } else if page > 1 { // 为1的时候,不需要prev info.Canonical = app.URL(app.PostsURL(page)) info.PrevPage = &Anchor{Title: "上一页", Link: app.PostsURL(page - 1)} } if page*opt.SidebarSize < info.PostSize { info.NextPage = &Anchor{Title: "下一页", Link: app.PostsURL(page + 1)} } posts, err := getPosts(page - 1) if err != nil { logs.Error("pagePosts:", err) pageHttpStatusCode(w, r, http.StatusInternalServerError) return } data := map[string]interface{}{ "info": info, "posts": posts, } render(w, r, "posts", data, map[string]string{"Content-Type": "text/html"}) }
func adminSetPostState(w http.ResponseWriter, r *http.Request, state int) { id, ok := util.ParamID(w, r, "id") if !ok { return } p := &models.Post{ID: id} if err := db.Select(p); err != nil { logs.Error("adminSetPostState:", err) util.RenderJSON(w, http.StatusInternalServerError, nil, nil) return } // 不可能存在状态值为0的文章,出现此值,表明数据库没有该条记录 if p.State == models.PostStateAll { util.RenderJSON(w, http.StatusNotFound, nil, nil) return } p = &models.Post{ID: id, State: state} if _, err := db.Update(p); err != nil { logs.Error("adminSetPostState:", err) util.RenderJSON(w, http.StatusInternalServerError, nil, nil) return } if err := stats.UpdatePostsSize(); err != nil { logs.Error("admin.adminSetPostState:", err) } lastUpdated() util.RenderJSON(w, http.StatusCreated, "{}", nil) }
// /tags func pageTags(w http.ResponseWriter, r *http.Request) { info, err := getInfo() if err != nil { logs.Error("pageTags:", err) pageHttpStatusCode(w, r, http.StatusInternalServerError) return } info.Canonical = app.URL(app.TagsURL()) info.Title = "标签" sql := `SELECT {id} AS {ID}, {name} AS {Name}, {title} AS {Title} FROM #tags` rows, err := db.Query(true, sql) if err != nil { logs.Error("pageTags:", err) pageHttpStatusCode(w, r, http.StatusInternalServerError) return } defer rows.Close() tags := make([]*Tag, 0, 100) if _, err = fetch.Obj(&tags, rows); err != nil { logs.Error("pageTags:", err) pageHttpStatusCode(w, r, http.StatusInternalServerError) return } data := map[string]interface{}{"info": info, "tags": tags} render(w, r, "tags", data, map[string]string{"Content-Type": "text/html"}) }
// @api get /admin/api/posts 获取文章列表 // @apiQuery page int 页码,从0开始 // @apiQuery size int 显示尺寸 // @apiQuery state int 状态 // @apiGroup admin // // @apiSuccess ok 200 // @apiParam count int 符合条件的所有记录数量,不包含page和size条件 // @apiParam posts array 当前页的记录数量 func adminGetPosts(w http.ResponseWriter, r *http.Request) { var page, size, state int var ok bool if state, ok = util.QueryInt(w, r, "state", models.CommentStateAll); !ok { return } sql := db.SQL().Table("#posts") if state != models.PostStateAll { sql.And("{state}=?", state) } count, err := sql.Count(true) if err != nil { logs.Error("adminGetPosts:", err) util.RenderJSON(w, http.StatusInternalServerError, nil, nil) return } if page, ok = util.QueryInt(w, r, "page", 0); !ok { return } if size, ok = util.QueryInt(w, r, "size", opt.PageSize); !ok { return } sql.Limit(size, page*size) maps, err := sql.SelectMapString(true, "*") if err != nil { logs.Error("adminGetPosts:", err) util.RenderJSON(w, http.StatusInternalServerError, nil, nil) return } util.RenderJSON(w, http.StatusOK, map[string]interface{}{"count": count, "posts": maps}, nil) }
// @api put /admin/api/themes/current 更改当前的主题 // @apiGroup admin // // @apiRequest json // @apiHeader Authorization xxx // @apiParam value string 新值 // // @apiSuccess 200 OK func adminPutCurrentTheme(w http.ResponseWriter, r *http.Request) { v := &struct { Value string `json:"value"` }{} if !util.ReadJSON(w, r, v) { return } if len(v.Value) == 0 { util.RenderJSON(w, http.StatusBadRequest, &util.ErrorResult{Message: "必须指定一个值!"}, nil) return } if err := app.SetOption("theme", v.Value, false); err != nil { logs.Error("adminPutTheme:", err) util.RenderJSON(w, http.StatusInternalServerError, nil, nil) return } if err := front.Switch(v.Value); err != nil { logs.Error("adminPutTheme:", err) util.RenderJSON(w, http.StatusInternalServerError, nil, nil) return } lastUpdated() util.RenderJSON(w, http.StatusNoContent, nil, nil) }
// /tags/1.html func pageTag(w http.ResponseWriter, r *http.Request) { tagName, ok := util.ParamString(w, r, "id") if !ok { return } tagName = strings.TrimSuffix(tagName, opt.Suffix) sql := `SELECT {id} AS {ID}, {name} AS {Name}, {title} AS {Title}, {description} AS {Description} FROM #tags WHERE {name}=?` rows, err := db.Query(true, sql, tagName) if err != nil { logs.Error("pageTag:", err) pageHttpStatusCode(w, r, http.StatusInternalServerError) return } defer rows.Close() tag := &Tag{} if _, err = fetch.Obj(tag, rows); err != nil { logs.Error("pageTag:", err) pageHttpStatusCode(w, r, http.StatusInternalServerError) return } info, err := getInfo() if err != nil { logs.Error("pageTag:", err) pageHttpStatusCode(w, r, http.StatusInternalServerError) return } info.Canonical = app.URL(tag.Permalink()) info.Title = tag.Title page := conv.MustInt(r.FormValue("page"), 1) if page < 1 { // 不能小于1 page = 1 } else if page > 1 { // 为1的时候,不需要prev info.PrevPage = &Anchor{Title: "上一页", Link: app.TagURL(tagName, page-1)} } if page*opt.SidebarSize < tag.Count() { info.NextPage = &Anchor{Title: "下一页", Link: app.TagURL(tagName, page+1)} } posts, err := getTagPosts(page-1, tag.ID) if err != nil { logs.Error("pageTag:", err) pageHttpStatusCode(w, r, http.StatusInternalServerError) return } data := map[string]interface{}{ "info": info, "tag": tag, "posts": posts, } render(w, r, "tag", data, map[string]string{"Content-Type": "text/html"}) }
// @api put /admin/api/tags/{id} 修改某id的标签内容 // @apiParam id int 需要修改的标签id // @apiGroup admin // // @apiRequest json // @apiHeader Authorization xxx // @apiParam name string 唯一名称 // @apiParam title string 显示的标题 // @apiParam description string 描述信息,可以是html // @apiExample json // { // "name": "tag-1", // "title":"标签1", // "description": "<h1>desc</h1>" // } // // @apiSuccess 204 no content // // @apiError 400 bad request // @apiParam message string 错误信息 // @apiParam detail array 说细的错误信息,用于描述哪个字段有错 // @apiExample json // { // "message": "格式错误", // "detail":[ // {"title":"不能包含特殊字符"}, // {"name": "已经存在同名"} // ] // } func adminPutTag(w http.ResponseWriter, r *http.Request) { t := &models.Tag{} if !util.ReadJSON(w, r, t) { return } // 检测是否为空 errs := &util.ErrorResult{Message: "格式错误", Detail: map[string]string{}} if len(t.Name) == 0 { errs.Add("name", "不能为空") } if len(t.Title) == 0 { errs.Add("title", "不能为空") } if errs.HasErrors() { util.RenderJSON(w, http.StatusBadRequest, errs, nil) return } var ok bool t.ID, ok = util.ParamID(w, r, "id") if !ok { return } // 检测是否存在同名 titleExists, nameExists, err := tagIsExists(t) if err != nil { logs.Error("adminPutTag:", err) util.RenderJSON(w, http.StatusInternalServerError, nil, nil) return } if titleExists { errs.Add("title", "与已有标签同名") } if nameExists { errs.Add("name", "与已有标签同名") } if errs.HasErrors() { util.RenderJSON(w, http.StatusBadRequest, errs, nil) return } if _, err := db.Update(t); err != nil { logs.Error("adminPutTag:", err) util.RenderJSON(w, http.StatusInternalServerError, nil, nil) return } lastUpdated() util.RenderJSON(w, http.StatusNoContent, nil, nil) }
// @api get /admin/api/posts/{id} 获取某一篇文章的详细内容 // @apiGroup admin // // @apiRequest json // @apiHeader Authorization xxx // // @apiSuccess 200 OK // @apiParam id int id值 // @apiParam name string 唯一名称,可以为空 // @apiParam title string 标题 // @apiParam summary string 文章摘要 // @apiParam content string 文章内容 // @apiParam state int 状态 // @apiParam order int 排序 // @apiParam created int 创建时间 // @apiParam modified int 修改时间 // @apiParam template string 所使用的模板 // @apiParam allowPing bool 允许ping // @apiParam allowComment bool 允许评论 // @apiParam tags array 关联的标签。 func adminGetPost(w http.ResponseWriter, r *http.Request) { id, ok := util.ParamID(w, r, "id") if !ok { return } p := &models.Post{ID: id} if err := db.Select(p); err != nil { logs.Error("adminGetPost:", err) util.RenderJSON(w, http.StatusInternalServerError, nil, nil) return } tags, err := getPostTags(id) if err != nil { logs.Error("adminGetPost:", err) util.RenderJSON(w, http.StatusInternalServerError, nil, nil) return } obj := &struct { ID int64 `json:"id"` Name string `json:"name"` Title string `json:"title"` Summary string `json:"summary"` Content string `json:"content"` State int `json:"state"` Order int `json:"order"` Created int64 `json:"created"` Modified int64 `json:"modified"` Template string `json:"template"` AllowPing bool `json:"allowPing"` AllowComment bool `json:"allowComment"` Tags []*models.Tag `json:"tags"` }{ ID: p.ID, Name: p.Name, Title: p.Title, Summary: p.Summary, Content: p.Content, State: p.State, Order: p.Order, Created: p.Created, Modified: p.Modified, Template: p.Template, AllowPing: p.AllowPing, AllowComment: p.AllowComment, Tags: tags, } util.RenderJSON(w, http.StatusOK, obj, nil) }
// @api post /admin/api/tags 添加新标签 // @apiGroup admin // // @apiRequest json // @apiHeader Authorization xxx // @apiParam name string 唯一名称 // @apiParam title string 显示的标题 // @apiParam description string 描述信息,可以是html // @apiExample json // { // "name": "tag-1", // "title":"标签1", // "description": "<h1>desc</h1>" // } // // @apiSuccess 201 created // @apiError 400 bad request // @apiParam message string 错误信息 // @apiParam detail array 说细的错误信息,用于描述哪个字段有错 // @apiExample json // { // "message": "格式错误", // "detail":[ // {"title":"不能包含特殊字符"}, // {"name": "已经存在同名"} // ] // } func adminPostTag(w http.ResponseWriter, r *http.Request) { t := &models.Tag{} if !util.ReadJSON(w, r, t) { return } errs := &util.ErrorResult{Message: "格式错误"} if t.ID != 0 { errs.Add("id", "不允许的字段") } if len(t.Title) == 0 { errs.Add("title", "不能为空") } if len(t.Name) == 0 { errs.Add("name", "不能为空") } if errs.HasErrors() { util.RenderJSON(w, http.StatusBadRequest, errs, nil) return } t.ID = 0 titleExists, nameExists, err := tagIsExists(t) if err != nil { logs.Error("adminPostTag:", err) util.RenderJSON(w, http.StatusInternalServerError, nil, nil) return } if titleExists { errs.Add("title", "已有同名字体段") } if nameExists { errs.Add("name", "已有同名字体段") } if errs.HasErrors() { util.RenderJSON(w, http.StatusBadRequest, errs, nil) return } if _, err := db.Insert(t); err != nil { logs.Error("adminPostTag:", err) util.RenderJSON(w, http.StatusInternalServerError, nil, nil) return } lastUpdated() util.RenderJSON(w, http.StatusCreated, "{}", nil) }
// @api patch /admin/api/options/{key} 修改设置项的值 // @apiParam key string 需要修改项的key // @apiGroup admin // // @apiRequest json // @apiHeader Authorization xxx // @apiParam value string 新值 // @apiExample json // { "value": "abcdef" } // @apiSuccess 204 no content func adminPatchOption(w http.ResponseWriter, r *http.Request) { key, ok := util.ParamString(w, r, "key") if !ok { return } if _, found := app.GetOption(key); !found { util.RenderJSON(w, http.StatusNotFound, nil, nil) return } data := &struct { Value string `json:"value"` }{} if !util.ReadJSON(w, r, data) { return } if err := app.SetOption(key, data.Value, false); err != nil { logs.Error("adminPatchOption:", err) util.RenderJSON(w, http.StatusInternalServerError, nil, nil) return } lastUpdated() util.RenderJSON(w, http.StatusNoContent, nil, nil) }
// @api put /admin/api/password 理发密码 // @apiGroup admin // @apiRequest json // @apiHeader Authorization xxx // @apiParam old string 旧密码 // @apiParam new string 新密码 // @apiExample json // { // "old": "123", // "new": "456" // } // // @apiSuccess 204 no content func adminChangePassword(w http.ResponseWriter, r *http.Request) { l := &struct { Old string `json:"old"` New string `json:"new"` }{} if !util.ReadJSON(w, r, l) { return } errs := &util.ErrorResult{Message: "提交数据错误", Detail: map[string]string{}} if len(l.New) == 0 { errs.Add("new", "新密码不能为空") } if opt.Password != app.Password(l.Old) { errs.Add("old", "旧密码错误") } if len(errs.Detail) > 0 { util.RenderJSON(w, http.StatusBadRequest, errs, nil) return } o := &models.Option{Key: "password", Value: app.Password(l.New)} if _, err := db.Update(o); err != nil { logs.Error("adminChangePassword:", err) util.RenderJSON(w, http.StatusInternalServerError, nil, nil) return } opt.Password = o.Value util.RenderJSON(w, http.StatusNoContent, nil, nil) }
// 初始化路由项 func initRoute() error { m, err := web.NewModule("feed") if err != nil { return err } m.GetFunc("/sitemap.xml", func(w http.ResponseWriter, r *http.Request) { sitemapMutex.Lock() defer sitemapMutex.Unlock() if _, err := w.Write(sitemap.Bytes()); err != nil { logs.Error("feed.initRoute.route-/sitemap.xml:", err) w.WriteHeader(http.StatusNotFound) // 若是出错,给客户端的信息提示为404 } }) // NOTE:若修改此路由,请同时修改sitemap.xml中的相对应的.xsl路径 m.GetFunc("/sitemap.xsl", func(w http.ResponseWriter, r *http.Request) { if _, err := w.Write(static.Sitemap); err != nil { logs.Error("feed.initRoute.route-/sitemap.xsl:", err) w.WriteHeader(http.StatusNotFound) } }) m.GetFunc("/rss.xml", func(w http.ResponseWriter, r *http.Request) { rssMutex.Lock() defer rssMutex.Unlock() if _, err := w.Write(rss.Bytes()); err != nil { logs.Error("feed.initRoute.route-/rss.xml:", err) w.WriteHeader(http.StatusNotFound) } }) m.GetFunc("/atom.xml", func(w http.ResponseWriter, r *http.Request) { atomMutex.Lock() defer atomMutex.Unlock() if _, err := w.Write(atom.Bytes()); err != nil { logs.Error("feed.initRoute.route-/atom.xml:", err) w.WriteHeader(http.StatusNotFound) } }) return nil }
// 输出指定模板 func render(w http.ResponseWriter, r *http.Request, name string, data interface{}, headers map[string]string) { if cfg.Debug { // 调试状态下,实时加载模板 if err := Switch(currentTheme); err != nil { logs.Error("front.render:", err) } } for key, val := range headers { w.Header().Set(key, val) } err := tpl.ExecuteTemplate(w, name, data) if err != nil { logs.Error("front.Render:", err) pageHttpStatusCode(w, r, http.StatusInternalServerError) return } }
// @api put /admin/api/feed/sitemap 重新生成sitemap // @apiGroup admin // // @apiRequest json // @apiHeader Authorization xxx // // @apiSuccess 200 Ok func adminPutSitemap(w http.ResponseWriter, r *http.Request) { err := feed.BuildSitemap() if err != nil { logs.Error(err) util.RenderJSON(w, http.StatusInternalServerError, nil, nil) return } util.RenderJSON(w, http.StatusOK, "{}", nil) }
// 获取与当前文章相关的标签。 func (p *Post) Tags() []*Tag { sql := `SELECT t.{name} AS {Name}, t.{title} AS {Title} FROM #relationships AS r LEFT JOIN #tags AS t on t.{id}=r.{tagID} WHERE r.{postID}=?` rows, err := db.Query(true, sql, p.ID) if err != nil { logs.Error("front.Post.Tags:", err) return nil } defer rows.Close() tags := make([]*Tag, 0, 5) if _, err = fetch.Obj(&tags, rows); err != nil { logs.Error("front.Post.Tags:", err) return nil } return tags }
// @api delete /admin/api/comments/{id} 删除某条评论 // @apiParam id int 评论的id值 // @apiGroup admin // // @apiSuccess 204 no content func adminDeleteComment(w http.ResponseWriter, r *http.Request) { id, ok := util.ParamID(w, r, "id") if !ok { return } c := &models.Comment{ID: id} if _, err := db.Delete(c); err != nil { logs.Error("adminDeleteComment:", err) util.RenderJSON(w, http.StatusInternalServerError, nil, nil) return } if err := stats.UpdateCommentsSize(); err != nil { logs.Error("admin.adminDeleteComment:", err) } lastUpdated() util.RenderJSON(w, http.StatusNoContent, nil, nil) }
// @api put /admin/api/comments/{id} 修改评论,只能修改管理员发布的评论 // @apiParam id int 需要修改的评论id // @apiGroup admin // // @apiRequest json // @apiParam content string 新的评论内容 // @apiExample json // { "content", "content..." } // // @apiSuccess 200 ok func adminPutComment(w http.ResponseWriter, r *http.Request) { id, ok := util.ParamID(w, r, "id") if !ok { return } c := &models.Comment{ID: id} cnt, err := db.Count(c) if err != nil { logs.Error("putComment:", err) util.RenderJSON(w, http.StatusInternalServerError, nil, nil) return } if cnt == 0 { util.RenderJSON(w, http.StatusNotFound, nil, nil) return } ct := &struct { Content string `json:"content"` }{} if !util.ReadJSON(w, r, ct) { return } c.Content = ct.Content if _, err = db.Update(c); err != nil { logs.Error("putComment", err) util.RenderJSON(w, http.StatusInternalServerError, nil, nil) return } if err := stats.UpdateCommentsSize(); err != nil { logs.Error("admin.adminPutComment:", err) } lastUpdated() util.RenderJSON(w, http.StatusNoContent, nil, nil) }
// @api post /admin/api/login 登录 // @apiGroup admin // // @apiRequest json // @apiParam password string 登录密码 // @apiExample json // { "password": "******" } // // @apiSuccess 201 // @apiHeader Cache-Control:no-cache // @apiHeader Pragma:no-cache // @apiParam token string 登录凭证; // @apiExample json // { "token": "adfwerqeqaeqe313aa" } func adminPostLogin(w http.ResponseWriter, r *http.Request) { inst := &struct { Password string `json:"password"` }{} if !util.ReadJSON(w, r, inst) { return } if app.Password(inst.Password) != opt.Password { util.RenderJSON(w, http.StatusUnauthorized, nil, nil) return } ret := make([]byte, 64) n, err := io.ReadFull(rand.Reader, ret) if err != nil { logs.Error("login:无法产生一个随机的token", err) util.RenderJSON(w, http.StatusInternalServerError, nil, nil) return } if n == 0 { logs.Error("login:无法产生一个随机的token") util.RenderJSON(w, http.StatusInternalServerError, nil, nil) return } token = utils.MD5(string(ret)) if len(token) == 0 { logs.Error("login:无法正确生成登录的token") util.RenderJSON(w, http.StatusInternalServerError, nil, nil) return } // 记录日志出错,仅输出错误内容,但不返回500错误。 if err = writeLastLogs(r); err != nil { logs.Error("login:"******"token": token}, nil) }
// @api get /admin/api/tags 获取所有标签信息 // @apiGroup admin // // @apiRequest json // @apiheader Authorization xxx // // @apiSuccess 200 OK // @apiParam tags array 所有标签的列表 func adminGetTags(w http.ResponseWriter, r *http.Request) { sql := `SELECT m.{name}, m.{title}, m.{description}, m.{id},count(r.{tagID}) AS {count} FROM #tags AS m LEFT JOIN #relationships AS r ON m.{id}=r.{tagID} GROUP BY m.{id}` rows, err := db.Query(true, sql) if err != nil { logs.Error("getTags:", err) util.RenderJSON(w, http.StatusInternalServerError, nil, nil) return } maps, err := fetch.MapString(false, rows) rows.Close() if err != nil { logs.Error("getTags:", err) util.RenderJSON(w, http.StatusInternalServerError, nil, nil) return } util.RenderJSON(w, http.StatusOK, map[string]interface{}{"tags": maps}, nil) }
// 评论数量 func (p *Post) CommentsSize() int { if size, found := stats.Posts[p.ID]; found { return size } c := &models.Comment{PostID: p.ID, State: models.CommentStateApproved} size, err := db.Count(c) if err != nil { logs.Error("front.Post.CommentsSize:", err) return 0 } stats.Posts[p.ID] = size return size }
// @api post /admin/api/comments 提交新评论 // @apiGroup admin // // @apiRequest json // @apiParam parent int 评论的父级内容 // @apiParam postID int 评论的文章 // @apiParam content string 评论的内容 // // @apiSuccess 201 created func adminPostComment(w http.ResponseWriter, r *http.Request) { c := &struct { Parent int64 `json:"parent"` PostID int64 `json:"postID"` Content string `json:"content"` }{} if !util.ReadJSON(w, r, c) { return } comm := &models.Comment{ Parent: c.Parent, PostID: c.PostID, Content: c.Content, State: models.CommentStateApproved, IP: "", Agent: "", Created: time.Now().Unix(), IsAdmin: true, AuthorURL: opt.SiteURL, AuthorName: opt.ScreenName, AuthorEmail: opt.Email, } if _, err := db.Insert(comm); err != nil { logs.Error("adminPostComment:", err) util.RenderJSON(w, http.StatusInternalServerError, nil, nil) return } if err := stats.UpdateCommentsSize(); err != nil { logs.Error("admin.adminPostComment:", err) } lastUpdated() util.RenderJSON(w, http.StatusCreated, nil, nil) }
// @api delete /admin/api/tags/{id} 删除该id的标签,也将被从relationships表中删除。 // @apiParam id int 需要删除的标签id // @apiGroup admin // // @apiRequest json // @apiHeader Authorization xxx // // @apiSuccess 204 no content func adminDeleteTag(w http.ResponseWriter, r *http.Request) { id, ok := util.ParamID(w, r, "id") if !ok { return } tx, err := db.Begin() if err != nil { logs.Error("adminDeleteMeta:", err) util.RenderJSON(w, http.StatusInternalServerError, nil, nil) return } if _, err := tx.Delete(&models.Tag{ID: id}); err != nil { logs.Error("adminDeleteMeta:", err) util.RenderJSON(w, http.StatusInternalServerError, nil, nil) return } // 删除与之对应的关联数据。 sql := "DELETE FROM #relationships WHERE {tagID}=?" if _, err := tx.Exec(true, sql, id); err != nil { logs.Error("adminDeleteMeta:", err) util.RenderJSON(w, http.StatusInternalServerError, nil, nil) return } if err := tx.Commit(); err != nil { logs.Error("adminDeleteMeta:", err) tx.Rollback() util.RenderJSON(w, http.StatusInternalServerError, nil, nil) return } lastUpdated() util.RenderJSON(w, http.StatusNoContent, nil, nil) }
// @api get /api/posts/{id}/comments // @apiQuery page int 页码 // @apiGroup front // // @apiSuccess 200 OK // @apiParam comments array 当前页的评论 func frontGetPostComments(w http.ResponseWriter, r *http.Request) { id, ok := util.ParamID(w, r, "id") if !ok { return } p := &models.Post{ID: id} if err := db.Select(p); err != nil { logs.Error("frontGetPostComments:", err) util.RenderJSON(w, http.StatusInternalServerError, nil, nil) return } if p.State != models.PostStatePublished { util.RenderJSON(w, http.StatusNotFound, nil, nil) return } sql := db.Where("{postID}=?", id). And("{state}=?", models.CommentStateApproved). Table("#comments") var page int if page, ok = util.QueryInt(w, r, "page", 0); !ok { return } sql.Limit(opt.PageSize, page*opt.PageSize) maps, err := sql.SelectMap(true, "*") if err != nil { logs.Error("frontGetComments:", err) util.RenderJSON(w, http.StatusInternalServerError, nil, nil) return } util.RenderJSON(w, http.StatusOK, map[string]interface{}{"comments": maps}, nil) }
// @api get /admin/api/tags/{id} 获取指定id的标签内容 // @apiParam id int 标签的id // @apiGroup admin // // @apiSuccess 200 OK // @apiParam id int 标签的id // @apiParam name string 标签的唯一名称,可能为空 // @apiParam title string 标签名称 // @apiParam description string 对标签的详细描述 func adminGetTag(w http.ResponseWriter, r *http.Request) { id, ok := util.ParamID(w, r, "id") if !ok { return } t := &models.Tag{ID: id} if err := db.Select(t); err != nil { logs.Error("adminGetTag:", err) util.RenderJSON(w, http.StatusInternalServerError, nil, nil) return } util.RenderJSON(w, http.StatusOK, t, nil) }
// 返回文章的评论信息。 func (p *Post) Comments() []*Comment { sql := `SELECT {id} AS {ID}, {created} AS {Created}, {agent} AS {Agent}, {content} AS {Content}, {isAdmin} AS {IsAdmin}, {authorName} AS {AuthorName}, {authorURL} AS {AuthorURL}, {postID} AS {PostID} FROM #comments WHERE {postID}=? AND {state}=? ORDER BY {created} ` if opt.CommentOrder == app.CommentOrderDesc { sql += `DESC ` } rows, err := db.Query(true, sql, p.ID, models.CommentStateApproved) if err != nil { logs.Error("front.Post.Comment:", err) return nil } defer rows.Close() comments := make([]*Comment, 0, opt.PageSize) if _, err := fetch.Obj(&comments, rows); err != nil { logs.Error("front.Post.Comment:", err) return nil } return comments }
// @api delete /admin/api/posts/{id} 删除文章 // @apiGroup admin // // @apiRequest json // @apiHeader Authorization xxx // // @apiSuccess 204 no content func adminDeletePost(w http.ResponseWriter, r *http.Request) { id, ok := util.ParamID(w, r, "id") if !ok { return } tx, err := db.Begin() if err != nil { logs.Error("deletePost:", err) util.RenderJSON(w, http.StatusInternalServerError, nil, nil) tx.Rollback() return } // 删除文章 sql := "DELETE FROM #posts WHERE {id}=?" if _, err := tx.Exec(true, sql, id); err != nil { logs.Error("deletePost:", err) util.RenderJSON(w, http.StatusInternalServerError, nil, nil) tx.Rollback() return } // 删除评论 sql = "DELETE FROM #comments WHERE {postID}=?" if _, err := tx.Exec(true, sql, id); err != nil { logs.Error("deletePost:", err) util.RenderJSON(w, http.StatusInternalServerError, nil, nil) tx.Rollback() return } //删除关联数据 sql = "DELETE FROM #relationships WHERE {postID}=?" if _, err := tx.Exec(true, sql, id); err != nil { logs.Error("deletePost:", err) util.RenderJSON(w, http.StatusInternalServerError, nil, nil) tx.Rollback() return } if err := tx.Commit(); err != nil { tx.Rollback() logs.Error("deletePost:", err) util.RenderJSON(w, http.StatusInternalServerError, nil, nil) return } if err := stats.UpdatePostsSize(); err != nil { logs.Error("admin.adminDeletePost:", err) } lastUpdated() util.RenderJSON(w, http.StatusNoContent, nil, nil) }
// @api get /admin/api/media 获取所有的文件列表 // @apiQuery parent string 上一级目录,相对于cfg.UploadDir设置项。 // @apiGroup admin // // @apiSuccess 200 成功获取列表 // @apiParam files array 文件列表 func adminGetMedia(w http.ResponseWriter, r *http.Request) { parent := r.FormValue("parent") if len(parent) == 0 { parent = "/" } if strings.Index(parent, "..") >= 0 { util.RenderJSON(w, http.StatusBadRequest, &util.ErrorResult{Message: "格式错误"}, nil) return } parent = cfg.UploadDir + parent fs, err := ioutil.ReadDir(parent) if err != nil { logs.Error("admin.adminGetMeida:", err) util.RenderJSON(w, http.StatusInternalServerError, nil, nil) return } type fileInfo struct { Name string `json:"name"` Type string `json:"type"` } list := make([]*fileInfo, 0, len(fs)) for _, file := range fs { typ := "file" if file.IsDir() { typ = "dir" } suffix := strings.ToLower(filepath.Ext(file.Name())) if suffix == ".jpeg" || suffix == ".jpg" || suffix == ".png" || suffix == ".svg" || suffix == ".gif" { typ = "image" } list = append(list, &fileInfo{Name: file.Name(), Type: typ}) } util.RenderJSON(w, http.StatusOK, map[string]interface{}{"list": list}, nil) }
// @api post /api/posts/{id}/comments 提交新评论 // @apiGroup front // // @apiRequest json // @apiParam parent int 评论的父级内容 // @apiParam postID int 评论的文章 // @apiParam content string 评论的内容 // @apiParam authorName string 评论的作者 // @apiParam authorURL string 评论作者的网站地址,可为空 // @apiParam authorEmail string 评论作者的邮箱 // // @apiSuccess 201 created func frontPostPostComment(w http.ResponseWriter, r *http.Request) { c := &struct { Parent int64 `json:"parent"` PostID int64 `json:"postID"` Content string `json:"content"` AuthorName string `json:"authorName"` AuthorURL string `json:"authorURL"` AuthorEmail string `json:"authorEmail"` }{} if !util.ReadJSON(w, r, c) { return } // 判断文章状态 if c.PostID <= 0 { util.RenderJSON(w, http.StatusNotFound, nil, nil) return } p := &models.Post{ID: c.PostID} if err := db.Select(p); err != nil { logs.Error("forntPostPostComment:", err) util.RenderJSON(w, http.StatusInternalServerError, nil, nil) return } if (len(p.Title) == 0 && len(p.Content) == 0) || p.State != models.PostStatePublished { util.RenderJSON(w, http.StatusNotFound, nil, nil) return } if !p.AllowComment { util.RenderJSON(w, http.StatusMethodNotAllowed, nil, nil) return } // 判断提交数据的状态 errs := &util.ErrorResult{} if c.Parent < 0 { errs.Detail["parent"] = "无效的parent" } if len(c.Content) == 0 { errs.Detail["content"] = "content不能为空" } if len(c.AuthorURL) > 0 && !is.URL(c.AuthorURL) { errs.Detail["authorURL"] = "无效的authorURL" } if !is.Email(c.AuthorEmail) { errs.Detail["authorEmail"] = "无效的authorEmail" } if len(c.AuthorName) == 0 { errs.Detail["authorName"] = "authorName不能为空" } c.AuthorName = html.EscapeString(c.AuthorName) // url只提取其host部分,其余的都去掉 u, err := url.Parse(c.AuthorURL) if err != nil { logs.Error("frontPostComment:", err) util.RenderJSON(w, http.StatusInternalServerError, nil, nil) return } c.AuthorURL = u.Scheme + ":" + u.Host c.Content = html.EscapeString(c.Content) c.Content = strings.Replace(c.Content, "\n", "<br />", -1) comm := &models.Comment{ PostID: c.PostID, Parent: c.Parent, AuthorURL: c.AuthorURL, AuthorName: c.AuthorName, AuthorEmail: c.AuthorEmail, Content: c.Content, Created: time.Now().Unix(), State: models.CommentStateWaiting, IP: r.RemoteAddr, Agent: r.UserAgent(), IsAdmin: false, } if _, err := db.Insert(comm); err != nil { logs.Error("frontPostComment:", err) util.RenderJSON(w, http.StatusInternalServerError, nil, nil) return } util.RenderJSON(w, http.StatusCreated, nil, nil) }
// @api post /admin/api/posts 新建文章 // @apiGroup admin // // @apiRequest json // @apiHeader Authorization xxx // @apiParam name string 唯一名称,可以为空 // @apiParam title string 标题 // @apiParam summary string 文章摘要 // @apiParam content string 文章内容 // @apiParam state int 状态 // @apiParam order int 排序 // @apiParam template string 所使用的模板 // @apiParam allowPing bool 允许ping // @apiParam allowComment bool 允许评论 // @apiParam tags array 关联的标签 // // @apiSuccess 201 created func adminPostPost(w http.ResponseWriter, r *http.Request) { p := &struct { Name string `json:"name"` Title string `json:"title"` Summary string `json:"summary"` Content string `json:"content"` State int `json:"state"` Order int `json:"order"` Template string `json:"template"` AllowPing bool `json:"allowPing"` AllowComment bool `json:"allowComment"` Tags []int64 `json:"tags"` }{} if !util.ReadJSON(w, r, p) { return } t := time.Now().Unix() pp := &models.Post{ Name: p.Name, Title: p.Title, Summary: p.Summary, Content: p.Content, State: p.State, Order: p.Order, Template: p.Template, AllowPing: p.AllowPing, AllowComment: p.AllowComment, Created: t, Modified: t, } tx, err := db.Begin() if err != nil { logs.Error("adminPostPost:", err) util.RenderJSON(w, http.StatusInternalServerError, nil, nil) return } // 插入文章 result, err := tx.Insert(pp) if err != nil { tx.Rollback() logs.Error("adminPostPost:", err) util.RenderJSON(w, http.StatusInternalServerError, nil, nil) return } postID, err := result.LastInsertId() if err != nil { tx.Rollback() logs.Error("adminPostPost:", err) util.RenderJSON(w, http.StatusInternalServerError, nil, nil) return } // 插入relationship rs := make([]interface{}, 0, len(p.Tags)) for _, tag := range p.Tags { rs = append(rs, &models.Relationship{PostID: postID, TagID: tag}) } if err := tx.MultInsert(rs...); err != nil { tx.Rollback() logs.Error("adminPostPost:", err) util.RenderJSON(w, http.StatusInternalServerError, nil, nil) return } // commit if err := tx.Commit(); err != nil { tx.Rollback() logs.Error("adminPostPost:", err) util.RenderJSON(w, http.StatusInternalServerError, nil, nil) return } lastUpdated() util.RenderJSON(w, http.StatusCreated, "{}", nil) }
// @api put /admin/api/posts/{id} 修改文章 // @apiGroup admin // // @apiRequest json // @apiHeader Authorization xxx // @apiParam name string 唯一名称,可以为空 // @apiParam title string 标题 // @apiParam summary string 文章摘要 // @apiParam content string 文章内容 // @apiParam state int 状态 // @apiParam order int 排序 // @apiParam template string 所使用的模板 // @apiParam allowPing bool 允许ping // @apiParam allowComment bool 允许评论 // @apiParam tags array 关联的标签 // // @apiSuccess 200 no content func adminPutPost(w http.ResponseWriter, r *http.Request) { id, ok := util.ParamID(w, r, "id") if !ok { return } p := &struct { Name string `json:"name"` Title string `json:"title"` Summary string `json:"summary"` Content string `json:"content"` State int `json:"state"` Order int `json:"order"` Template string `json:"template"` AllowPing bool `json:"allowPing"` AllowComment bool `json:"allowComment"` Tags []int64 `json:"tags"` }{} if !util.ReadJSON(w, r, p) { return } op := &models.Post{ID: id} if err := db.Select(op); err != nil { logs.Error("adminPutPost-0:", err) util.RenderJSON(w, http.StatusInternalServerError, nil, nil) return } pp := &models.Post{ ID: id, Name: p.Name, Title: p.Title, Summary: p.Summary, Content: p.Content, State: p.State, Order: p.Order, Template: p.Template, AllowPing: p.AllowPing, AllowComment: p.AllowComment, Modified: time.Now().Unix(), Created: op.Created, } // TODO 是否有必要检测标签是否真实存在 tx, err := db.Begin() if err != nil { logs.Error("adminPutPost-1:", err) util.RenderJSON(w, http.StatusInternalServerError, nil, nil) tx.Rollback() return } // 更新文档内容 if _, err := tx.UpdateZero(pp); err != nil { logs.Error("adminPutPost-2:", err) util.RenderJSON(w, http.StatusInternalServerError, nil, nil) tx.Rollback() return } // 删除旧的关联内容 sql := "DELETE FROM #relationships WHERE {postID}=?" if _, err := tx.Exec(true, sql, pp.ID); err != nil { logs.Error("adminPutPost-3:", err) util.RenderJSON(w, http.StatusInternalServerError, nil, nil) tx.Rollback() return } // 添加新的关联 if len(p.Tags) > 0 { rs := make([]interface{}, 0, len(p.Tags)) for _, tag := range p.Tags { rs = append(rs, &models.Relationship{TagID: tag, PostID: pp.ID}) } if err := tx.MultInsert(rs...); err != nil { logs.Error("adminPutPost-4:", err) util.RenderJSON(w, http.StatusInternalServerError, nil, nil) tx.Rollback() return } } if err := tx.Commit(); err != nil { logs.Error("adminPutPost-5:", err) tx.Rollback() return } if err := stats.UpdatePostsSize(); err != nil { logs.Error("admin.adminPutPost:", err) } lastUpdated() util.RenderJSON(w, http.StatusNoContent, nil, nil) }