update api.go, config.yml, handler.go, middleware.go, repository.go

This commit is contained in:
redtea committed 2024-10-22 12:00:00 +03:00
1 parent 43505548f7
commit f99ac8ffb4
5 files changed
+415

No files matched your search

+6
View File
@@ -0,0 +1,6 @@
port: "8013"
db:
host: "mysql-pl-wa10.joinserver.xyz"
port: "3306"
username: "u45925_HjjJCz8EVf"
dbname: "s45925_tentula"
+286
View File
@@ -0,0 +1,286 @@
package handler
import (
"esforum"
"esforum/pkg/util"
"net/http"
"strconv"
"github.com/gin-gonic/gin"
)
type threadRequest struct {
Title string `json:"title" binding:"required,min=3,max=100"`
}
type postRequest struct {
Title string `json:"title" binding:"required,min=3,max=100"`
Content string `json:"content" binding:"required,min=5"`
Thread int64 `json:"thread" binding:"required"`
}
// thread godoc
// @Summary Create new thread (only for admins)
// @Tags thread
// @Produce json
// @Param thread body handler.threadRequest true "Thread data in JSON"
// @Param Authorization header string true "JWT Bearer token (authorization)"
// @Success 200 {object} esforum.Thread "created thread"
// @Router /api/thread [post]
func (h *Handler) create(c *gin.Context) {
var input threadRequest
if !util.MatchRole(c, "admin") {
util.NewErrorResponse(c, http.StatusForbidden, "no perms")
return
}
if err := c.BindJSON(&input); err != nil {
util.NewErrorResponse(c, http.StatusBadRequest, err.Error())
return
}
thread, err := h.repo.CreateThread(input.Title)
if err != nil {
util.NewErrorResponse(c, http.StatusBadRequest, err.Error())
return
}
c.JSON(http.StatusOK, thread)
}
// thread/delete godoc
// @Summary Delete thread by id (only for admins)
// @Tags thread
// @Produce json
// @Param id path int true "id of thread"
// @Param Authorization header string true "JWT Bearer token (authorization)"
// @Success 200
// @Router /api/thread/{id} [delete]
func (h *Handler) delete(c *gin.Context) {
id, err := strconv.ParseInt(c.Param("id"), 10, 64)
if err != nil {
util.NewErrorResponse(c, http.StatusBadRequest, "invalid id format")
return
}
_, err = h.repo.GetThread(id)
if err != nil {
util.NewErrorResponse(c, http.StatusBadRequest, err.Error())
return
}
current, exist := c.Get(userCtx)
if !exist {
util.NewErrorResponse(c, http.StatusUnauthorized, "user not found in context")
return
}
currentUser, err := h.services.AuthorizeById(current.(int64))
if (err != nil) || (currentUser.Access != "admin") {
util.NewErrorResponse(c, http.StatusForbidden, "no permissions")
return
}
err = h.repo.DeleteThread(id)
if err != nil {
util.NewErrorResponse(c, http.StatusInternalServerError, err.Error())
return
}
c.JSON(http.StatusOK, gin.H{"status": "ok"})
}
// createPost godoc
// @Summary Create new post
// @Tags post
// @Produce json
// @Param post body handler.postRequest true "Post data in JSON"
// @Param Authorization header string true "JWT Bearer token (authorization)"
// @Success 200 {object} esforum.Post "created post"
// @Router /api/post [post]
func (h *Handler) createPost(c *gin.Context) {
var input postRequest
if err := c.BindJSON(&input); err != nil {
util.NewErrorResponse(c, http.StatusBadRequest, err.Error())
return
}
// Проверяем существование треда
_, err := h.repo.GetThread(input.Thread)
if err != nil {
util.NewErrorResponse(c, http.StatusBadRequest, "thread does not exist")
return
}
post, err := h.repo.CreatePost(input.Title, input.Content, input.Thread)
if err != nil {
util.NewErrorResponse(c, http.StatusInternalServerError, err.Error())
return
}
c.JSON(http.StatusOK, post)
}
// deletePost godoc
// @Summary Delete post by id
// @Tags post
// @Produce json
// @Param id path int true "id of post"
// @Param Authorization header string true "JWT Bearer token (authorization)"
// @Success 200
// @Router /api/post/{id} [delete]
func (h *Handler) deletePost(c *gin.Context) {
id, err := strconv.ParseInt(c.Param("id"), 10, 64)
if err != nil {
util.NewErrorResponse(c, http.StatusBadRequest, "invalid id format")
return
}
post, err := h.repo.GetPost(id)
if err != nil {
util.NewErrorResponse(c, http.StatusBadRequest, err.Error())
return
}
// Только админ может удалять посты
if !util.MatchRole(c, "admin") {
util.NewErrorResponse(c, http.StatusForbidden, "no permissions")
return
}
err = h.repo.DeletePost(id)
if err != nil {
util.NewErrorResponse(c, http.StatusInternalServerError, err.Error())
return
}
c.JSON(http.StatusOK, gin.H{"status": "ok", "deleted_post_id": post.Id})
}
// getThread godoc
// @Summary Get thread by id
// @Tags thread
// @Produce json
// @Param id path int true "id of thread"
// @Success 200 {object} esforum.Thread
// @Router /api/guest/thread/{id} [get]
func (h *Handler) getThread(c *gin.Context) {
id, err := strconv.ParseInt(c.Param("id"), 10, 64)
if err != nil {
util.NewErrorResponse(c, http.StatusBadRequest, "invalid id format")
return
}
thread, err := h.repo.GetThread(id)
if err != nil {
util.NewErrorResponse(c, http.StatusNotFound, "thread not found")
return
}
c.JSON(http.StatusOK, thread)
}
// getAllThreads godoc
// @Summary Get all threads
// @Tags thread
// @Produce json
// @Success 200 {array} esforum.Thread
// @Router /api/guest/thread/all [get]
func (h *Handler) getAllThreads(c *gin.Context) {
// Получаем параметры для пагинации
startParam := c.DefaultQuery("start", "0")
limitParam := c.DefaultQuery("limit", "20")
start, err := strconv.Atoi(startParam)
if err != nil || start < 0 {
start = 0
}
limit, err := strconv.Atoi(limitParam)
if err != nil || limit < 1 || limit > 100 {
limit = 20
}
// Получаем ID всех тредов с учетом пагинации
threadIds := h.repo.GetThreads(start, limit)
// Получаем общее количество тредов
totalThreads := h.repo.GetThreadsCount()
// Получаем информацию о каждом треде
threads := make([]esforum.Thread, 0, len(threadIds))
for _, threadId := range threadIds {
thread, err := h.repo.GetThread(threadId)
if err == nil {
threads = append(threads, *thread)
}
}
c.JSON(http.StatusOK, gin.H{
"threads": threads,
"total": totalThreads,
"start": start,
"limit": limit,
})
}
// getThreadPosts godoc
// @Summary Get posts in thread
// @Tags post
// @Produce json
// @Param id path int true "id of thread"
// @Success 200 {array} esforum.Post
// @Router /api/guest/thread/{id}/posts [get]
func (h *Handler) getThreadPosts(c *gin.Context) {
id, err := strconv.ParseInt(c.Param("id"), 10, 64)
if err != nil {
util.NewErrorResponse(c, http.StatusBadRequest, "invalid id format")
return
}
// Проверяем существование треда
_, err = h.repo.GetThread(id)
if err != nil {
util.NewErrorResponse(c, http.StatusNotFound, "thread not found")
return
}
// Получаем все посты треда
// По умолчанию первые 20 постов
postIds := h.repo.FindPostsByThread(id, 0, 20)
posts := make([]esforum.Post, 0, len(postIds))
for _, postId := range postIds {
post, err := h.repo.GetPost(postId)
if err == nil {
posts = append(posts, *post)
}
}
c.JSON(http.StatusOK, posts)
}
// getPost godoc
// @Summary Get post by id
// @Tags post
// @Produce json
// @Param id path int true "id of post"
// @Success 200 {object} esforum.Post
// @Router /api/guest/post/{id} [get]
func (h *Handler) getPost(c *gin.Context) {
id, err := strconv.ParseInt(c.Param("id"), 10, 64)
if err != nil {
util.NewErrorResponse(c, http.StatusBadRequest, "invalid id format")
return
}
post, err := h.repo.GetPost(id)
if err != nil {
util.NewErrorResponse(c, http.StatusNotFound, "post not found")
return
}
c.JSON(http.StatusOK, post)
}
+56
View File
@@ -0,0 +1,56 @@
package handler
import (
"esforum/docs"
"esforum/pkg/repository"
"esforum/pkg/service"
"github.com/gin-gonic/gin"
swaggerFiles "github.com/swaggo/files"
ginSwagger "github.com/swaggo/gin-swagger"
)
type Handler struct {
services *service.Service
repo *repository.Repository
}
func NewHandler(services *service.Service, repo *repository.Repository) *Handler {
return &Handler{services: services, repo: repo}
}
// @title EasyStartup Forum
// @version 1.0
// @description Forum service for EasyStartup.
//
// @contact.name API Support
// @contact.email red.tea.dev@gmail.com
//
// @host forum.easystartup.su
// @BasePath /api
//
func (h *Handler) InitRoutes() *gin.Engine {
router := gin.New()
docs.SwaggerInfo.BasePath = "/"
guest := router.Group("/api/guest")
{
guest.GET("thread/:id", h.getThread)
guest.GET("thread/all", h.getAllThreads)
guest.GET("thread/:id/posts", h.getThreadPosts)
guest.GET("post/:id", h.getPost)
}
api := router.Group("/api", h.userIdentity)
{
api.POST("thread", h.create)
api.DELETE("thread/:id", h.delete)
api.POST("post", h.createPost)
api.DELETE("post/:id", h.deletePost)
}
router.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))
return router
}
+30
View File
@@ -0,0 +1,30 @@
package handler
import (
"esforum/pkg/util"
"net/http"
"github.com/gin-gonic/gin"
)
const (
authorizationHeader = "Authorization"
userCtx = "UserInfo"
userHeader = "UserHeader"
)
func (h *Handler) userIdentity(c *gin.Context) {
header := c.GetHeader(authorizationHeader)
if header == "" {
util.NewErrorResponse(c, http.StatusUnauthorized, "empty auth header")
return
}
user, err := h.services.Authorization.Authorize(header)
if err != nil {
util.NewErrorResponse(c, http.StatusUnauthorized, err.Error())
}
c.Set(userCtx, user.Id)
c.Set(userHeader, header)
}
+37
View File
@@ -0,0 +1,37 @@
package repository
import (
"esforum"
"gorm.io/gorm"
)
type Post interface {
CreatePost(title string, content string, thread int64) (*esforum.Post, error)
GetPost(id int64) (*esforum.Post, error)
DeletePost(id int64) error
GetPosts(start, limit int) []int64
FindPostsByThread(thread int64, start, limit int) []int64
GetPostsCount() int64
}
type Thread interface {
CreateThread(title string) (*esforum.Thread, error)
GetThread(id int64) (*esforum.Thread, error)
FindThread(title string) (*esforum.Thread, error)
DeleteThread(id int64) error
GetThreads(start, limit int) []int64
GetThreadsCount() int64
}
type Repository struct {
Post
Thread
}
func NewRepository(db *gorm.DB) *Repository {
return &Repository{
Post: NewPostSchema(db),
Thread: NewThreadSchema(db),
}
}