123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396 |
- package admin
- import (
- "context"
- "strconv"
- "time"
- "git.dmitriygnatenko.ru/dima/go-common/logger"
- "github.com/go-playground/validator/v10"
- "github.com/gofiber/fiber/v2"
- "github.com/golang-jwt/jwt/v4"
- helper "git.dmitriygnatenko.ru/dima/dmitriygnatenko-v2/internal/helpers/i18n"
- "git.dmitriygnatenko.ru/dima/dmitriygnatenko-v2/internal/helpers/request"
- "git.dmitriygnatenko.ru/dima/dmitriygnatenko-v2/internal/mapper"
- "git.dmitriygnatenko.ru/dima/dmitriygnatenko-v2/internal/models"
- "git.dmitriygnatenko.ru/dima/dmitriygnatenko-v2/internal/services/handler"
- "git.dmitriygnatenko.ru/dima/dmitriygnatenko-v2/internal/services/i18n"
- )
- type (
- CacheService interface {
- Delete(key string)
- }
- TransactionManager interface {
- ReadCommitted(context.Context, func(ctx context.Context) error) error
- }
- ArticleRepository interface {
- GetAll(ctx context.Context) ([]models.Article, error)
- GetByURL(ctx context.Context, url string) (*models.Article, error)
- GetByID(ctx context.Context, id uint64) (*models.Article, error)
- Add(ctx context.Context, m models.Article) (uint64, error)
- Update(ctx context.Context, m models.Article) error
- Delete(ctx context.Context, id uint64) error
- }
- TagRepository interface {
- GetAll(ctx context.Context) ([]models.Tag, error)
- IsUsed(ctx context.Context, id uint64) (bool, error)
- GetByArticleID(ctx context.Context, id uint64) ([]models.Tag, error)
- GetByURL(ctx context.Context, tag string) (*models.Tag, error)
- GetByID(ctx context.Context, id uint64) (*models.Tag, error)
- Add(ctx context.Context, m models.Tag) error
- Update(ctx context.Context, m models.Tag) error
- Delete(ctx context.Context, id uint64) error
- }
- ArticleTagRepository interface {
- Add(ctx context.Context, id uint64, tagIDs []uint64) error
- Delete(ctx context.Context, id uint64, tagIDs []uint64) error
- DeleteByArticleID(ctx context.Context, id uint64) error
- }
- AuthService interface {
- GeneratePasswordHash(password string) (string, error)
- IsCorrectPassword(password string, hash string) bool
- GenerateToken(user models.User) (string, error)
- GetClaims(fctx *fiber.Ctx) jwt.MapClaims
- }
- ConfigService interface {
- JWTCookie() string
- JWTLifeTime() time.Duration
- }
- UserRepository interface {
- Get(ctx context.Context, username string) (*models.User, error)
- Add(ctx context.Context, username string, password string) (uint64, error)
- UpdatePassword(ctx context.Context, id uint64, newPassword string) error
- }
- )
- func ArticleHandler(articleRepository ArticleRepository) fiber.Handler {
- return func(ctx *fiber.Ctx) error {
- articles, err := articleRepository.GetAll(ctx.Context())
- if err != nil {
- logger.Error(ctx.Context(), err.Error())
- return err
- }
- return ctx.Render("admin/article", fiber.Map{
- "articles": mapper.ToArticleDTOList(ctx, articles),
- "section": "article",
- }, "admin/_layout")
- }
- }
- func AddArticleHandler(
- tm TransactionManager,
- articleRepository ArticleRepository,
- tagRepository TagRepository,
- articleTagRepository ArticleTagRepository,
- ) fiber.Handler {
- return func(fctx *fiber.Ctx) error {
- ctx := fctx.Context()
- lang := i18n.Language(fctx)
- var validate = validator.New()
- validateErrors := make(map[string]string)
- trans, err := helper.GetDefaultTranslator(validate)
- if err != nil {
- logger.Error(ctx, err.Error())
- return err
- }
- form := models.ArticleForm{
- ActiveTags: make(map[uint64]bool),
- }
- tags, err := tagRepository.GetAll(ctx)
- if err != nil {
- logger.Error(ctx, err.Error())
- return err
- }
- tagsDTO := mapper.ToTagDTOList(tags)
- if fctx.Method() == fiber.MethodPost {
- if err = fctx.BodyParser(&form); err != nil {
- logger.Info(ctx, err.Error())
- return err
- }
- if err = validate.Struct(form); err != nil {
- validateErrors = helper.FormatValidateErrors(err, trans)
- }
- if res, _ := articleRepository.GetByURL(ctx, form.URL); res != nil {
- validateErrors["ArticleForm.URL"] = i18n.T(lang, "admin_err_article_exists")
- }
- tagIDs := make([]uint64, 0, len(form.Tags))
- for i := range form.Tags {
- tagID, tagErr := strconv.ParseUint(form.Tags[i], 10, 64)
- if tagErr != nil {
- logger.Error(ctx, tagErr.Error())
- return tagErr
- }
- tagIDs = append(tagIDs, tagID)
- }
- for i := range tagIDs {
- form.ActiveTags[tagIDs[i]] = true
- }
- if len(validateErrors) == 0 {
- articleModel, err := mapper.ToArticle(form)
- if err != nil {
- logger.Error(ctx, err.Error())
- return err
- }
- err = tm.ReadCommitted(ctx, func(ctx context.Context) error {
- articleID, txErr := articleRepository.Add(ctx, *articleModel)
- if txErr != nil {
- return txErr
- }
- if len(form.Tags) > 0 {
- if txErr = articleTagRepository.Add(ctx, articleID, tagIDs); txErr != nil {
- return txErr
- }
- }
- return nil
- })
- if err != nil {
- logger.Error(ctx, err.Error())
- return err
- }
- return fctx.Redirect("/admin")
- }
- }
- return fctx.Render("admin/article_edit", fiber.Map{
- "form": form,
- "errors": validateErrors,
- "tags": tagsDTO,
- "section": "article",
- "title": i18n.T(lang, "admin_add_article_title"),
- "show_apply": false,
- }, "admin/_layout")
- }
- }
- func EditArticleHandler(
- tm TransactionManager,
- articleRepository ArticleRepository,
- tagRepository TagRepository,
- articleTagRepository ArticleTagRepository,
- cacheService CacheService,
- ) fiber.Handler {
- return func(fctx *fiber.Ctx) error {
- ctx := fctx.Context()
- lang := i18n.Language(fctx)
- var validate = validator.New()
- validateErrors := make(map[string]string)
- trans, err := helper.GetDefaultTranslator(validate)
- if err != nil {
- logger.Error(ctx, err.Error())
- return err
- }
- id, err := request.ConvertToUint64(fctx, "id")
- if err != nil {
- logger.Error(ctx, err.Error())
- return err
- }
- article, err := articleRepository.GetByID(ctx, id)
- if err != nil {
- logger.Error(ctx, err.Error())
- return err
- }
- if article == nil {
- return fiber.ErrNotFound
- }
- articleTags, err := tagRepository.GetByArticleID(ctx, id)
- if err != nil {
- logger.Error(ctx, err.Error())
- return err
- }
- tags, err := tagRepository.GetAll(ctx)
- if err != nil {
- logger.Error(ctx, err.Error())
- return err
- }
- tagsDTO := mapper.ToTagDTOList(tags)
- var form *models.ArticleForm
- if fctx.Method() == fiber.MethodGet {
- form = mapper.ToArticleForm(*article, articleTags)
- } else if fctx.Method() == fiber.MethodPost {
- form = &models.ArticleForm{
- ID: id,
- ActiveTags: make(map[uint64]bool),
- }
- if err = fctx.BodyParser(form); err != nil {
- logger.Info(ctx, err.Error())
- return err
- }
- if err = validate.Struct(*form); err != nil {
- validateErrors = helper.FormatValidateErrors(err, trans)
- }
- if res, _ := articleRepository.GetByURL(ctx, form.URL); res != nil {
- if res.ID != id {
- validateErrors["ArticleForm.URL"] = i18n.T(lang, "admin_err_article_exists")
- }
- }
- tagIDs := make([]uint64, 0, len(form.Tags))
- for i := range form.Tags {
- tagID, tagErr := strconv.ParseUint(form.Tags[i], 10, 64)
- if tagErr != nil {
- return tagErr
- }
- tagIDs = append(tagIDs, tagID)
- }
- for i := range tagIDs {
- form.ActiveTags[tagIDs[i]] = true
- }
- if len(validateErrors) == 0 {
- articleModel, err := mapper.ToArticle(*form)
- if err != nil {
- logger.Error(ctx, err.Error())
- return err
- }
- var tagsToAdd, tagsToDelete []uint64
- oldTagsMap := make(map[int]struct{}, len(articleTags))
- for i := range articleTags {
- oldTagsMap[int(articleTags[i].ID)] = struct{}{}
- if _, ok := form.ActiveTags[articleTags[i].ID]; !ok {
- tagsToDelete = append(tagsToDelete, articleTags[i].ID)
- }
- }
- for i := range tagIDs {
- if _, ok := oldTagsMap[int(tagIDs[i])]; !ok {
- tagsToAdd = append(tagsToAdd, tagIDs[i])
- }
- }
- err = tm.ReadCommitted(ctx, func(ctx context.Context) error {
- txErr := articleRepository.Update(ctx, *articleModel)
- if txErr != nil {
- return txErr
- }
- if len(tagsToAdd) > 0 {
- if txErr = articleTagRepository.Add(ctx, id, tagsToAdd); txErr != nil {
- return txErr
- }
- }
- if len(tagsToDelete) > 0 {
- if txErr = articleTagRepository.Delete(ctx, id, tagsToDelete); txErr != nil {
- return txErr
- }
- }
- return nil
- })
- if err != nil {
- logger.Error(ctx, err.Error())
- return err
- }
- cacheService.Delete(handler.ArticleCacheKey + string(i18n.Ru) + article.URL)
- if fctx.FormValue("action", "save") == "save" {
- return fctx.Redirect("/admin")
- }
- }
- }
- return fctx.Render("admin/article_edit", fiber.Map{
- "form": form,
- "errors": validateErrors,
- "tags": tagsDTO,
- "show_apply": true,
- "section": "article",
- "title": i18n.T(lang, "admin_edit_article_title"),
- }, "admin/_layout")
- }
- }
- func DeleteArticleHandler(
- tm TransactionManager,
- articleRepository ArticleRepository,
- articleTagRepository ArticleTagRepository,
- cacheService CacheService,
- ) fiber.Handler {
- return func(fctx *fiber.Ctx) error {
- ctx := fctx.Context()
- id, err := request.ConvertToUint64(fctx, "id")
- if err != nil {
- logger.Error(ctx, err.Error())
- return err
- }
- article, err := articleRepository.GetByID(ctx, id)
- if err != nil {
- logger.Error(ctx, err.Error())
- return err
- }
- if fctx.Method() == fiber.MethodPost {
- err = tm.ReadCommitted(ctx, func(ctx context.Context) error {
- txErr := articleTagRepository.DeleteByArticleID(ctx, id)
- if txErr != nil {
- return txErr
- }
- txErr = articleRepository.Delete(ctx, id)
- if txErr != nil {
- return txErr
- }
- return nil
- })
- if err != nil {
- logger.Error(ctx, err.Error())
- return err
- }
- cacheService.Delete(handler.ArticleCacheKey + string(i18n.Ru) + article.URL)
- return fctx.Redirect("/admin")
- }
- return fctx.Render("admin/article_delete", fiber.Map{
- "article": article.Title,
- "section": "article",
- }, "admin/_layout")
- }
- }
|