mirror of
https://github.com/Floriansylvain/RenewCMS.git
synced 2026-08-19 11:43:22 +02:00
refac: renamed 'post' -> 'article'
This commit is contained in:
@@ -42,7 +42,7 @@ of course required, but not necessarily via the `.env` file.
|
|||||||
| Name | Type | Description | Comment |
|
| Name | Type | Description | Comment |
|
||||||
|----------------------|--------|:----------------------------------------------|-----------------------------------------------------------------------------------------------------------------------|
|
|----------------------|--------|:----------------------------------------------|-----------------------------------------------------------------------------------------------------------------------|
|
||||||
| ENVIRONMENT | string | environment the API is running in | required, `development` or `production` |
|
| ENVIRONMENT | string | environment the API is running in | required, `development` or `production` |
|
||||||
| HOST | string | the host domain name for emails and callbacks | required |
|
| HOST | string | the host domain name for emails and callbacks | required, if `localhost`: add the port (e.g. `localhost:8080`) |
|
||||||
| PORT | int | port the API will use | required |
|
| PORT | int | port the API will use | required |
|
||||||
| JWT_SECRET | string | secret for the jwt auth | required |
|
| JWT_SECRET | string | secret for the jwt auth | required |
|
||||||
| CORS_ALLOWED_ORIGINS | string | allowed origins for CORS | required, semicolon separated list |
|
| CORS_ALLOWED_ORIGINS | string | allowed origins for CORS | required, semicolon separated list |
|
||||||
|
|||||||
@@ -0,0 +1,140 @@
|
|||||||
|
package gateways
|
||||||
|
|
||||||
|
import (
|
||||||
|
entity "GoCMS/adapters/secondary/gateways/models"
|
||||||
|
domain "GoCMS/domain/article"
|
||||||
|
"GoCMS/domain/gateways"
|
||||||
|
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ArticleRepository struct {
|
||||||
|
db *gorm.DB
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewArticleRepository(db *gorm.DB) *ArticleRepository {
|
||||||
|
return &ArticleRepository{db}
|
||||||
|
}
|
||||||
|
|
||||||
|
func mapArticleToDomain(article entity.Article) domain.Article {
|
||||||
|
return domain.FromDb(article.ID, article.Title, article.Body, article.Images, article.IsOnline, article.CreatedAt, article.UpdatedAt)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *ArticleRepository) Get(id uint32) (domain.Article, error) {
|
||||||
|
var article entity.Article
|
||||||
|
err := a.db.Model(&entity.Article{}).Preload("Images").First(&article, id).Error
|
||||||
|
if err != nil {
|
||||||
|
return domain.Article{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return mapArticleToDomain(article), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *ArticleRepository) GetByName(name string) (domain.Article, error) {
|
||||||
|
var article entity.Article
|
||||||
|
err := a.db.Model(&entity.Article{}).Where("title = ?", name).First(&article).Error
|
||||||
|
if err != nil {
|
||||||
|
return domain.Article{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return mapArticleToDomain(article), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *ArticleRepository) Create(article domain.Article) (domain.Article, error) {
|
||||||
|
creationResult := a.db.Create(&entity.Article{
|
||||||
|
Title: article.Title,
|
||||||
|
Body: article.Body,
|
||||||
|
})
|
||||||
|
if creationResult.Error != nil {
|
||||||
|
return domain.Article{}, creationResult.Error
|
||||||
|
}
|
||||||
|
|
||||||
|
var createdArticle entity.Article
|
||||||
|
creationResult.Scan(&createdArticle)
|
||||||
|
|
||||||
|
return mapArticleToDomain(createdArticle), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *ArticleRepository) GetAll() []domain.Article {
|
||||||
|
var articles []entity.Article
|
||||||
|
err := a.db.Model(&entity.Article{}).Find(&articles).Error
|
||||||
|
if err != nil {
|
||||||
|
return []domain.Article{}
|
||||||
|
}
|
||||||
|
|
||||||
|
var domainArticles = make([]domain.Article, 0)
|
||||||
|
for _, article := range articles {
|
||||||
|
domainArticles = append(domainArticles, mapArticleToDomain(article))
|
||||||
|
}
|
||||||
|
|
||||||
|
return domainArticles
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *ArticleRepository) UpdateBody(id uint32, body string) (domain.Article, error) {
|
||||||
|
var localArticle entity.Article
|
||||||
|
err := a.db.Model(&entity.Article{}).First(&localArticle, id).Error
|
||||||
|
if err != nil {
|
||||||
|
return domain.Article{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
localArticle.Body = body
|
||||||
|
err = a.db.Save(&localArticle).Error
|
||||||
|
if err != nil {
|
||||||
|
return domain.Article{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
newArticle := domain.FromDb(
|
||||||
|
localArticle.ID,
|
||||||
|
localArticle.Title,
|
||||||
|
localArticle.Body,
|
||||||
|
localArticle.Images,
|
||||||
|
localArticle.IsOnline,
|
||||||
|
localArticle.CreatedAt,
|
||||||
|
localArticle.UpdatedAt,
|
||||||
|
)
|
||||||
|
|
||||||
|
return newArticle, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *ArticleRepository) UpdateIsOnline(id uint32, isOnline bool) (domain.Article, error) {
|
||||||
|
var localArticle entity.Article
|
||||||
|
err := a.db.Model(&entity.Article{}).First(&localArticle, id).Error
|
||||||
|
if err != nil {
|
||||||
|
return domain.Article{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
localArticle.IsOnline = isOnline
|
||||||
|
err = a.db.Save(&localArticle).Error
|
||||||
|
if err != nil {
|
||||||
|
return domain.Article{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return mapArticleToDomain(localArticle), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *ArticleRepository) Delete(id uint32) error {
|
||||||
|
return a.db.Delete(&entity.Article{}, id).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *ArticleRepository) AddImage(articleId uint32, imageId uint32) error {
|
||||||
|
var localArticle entity.Article
|
||||||
|
err := a.db.Model(&entity.Article{}).First(&localArticle, articleId).Error
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var localImage entity.Image
|
||||||
|
err = a.db.Model(&entity.Image{}).First(&localImage, imageId).Error
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = a.db.Model(&localArticle).Association("Images").Append(&localImage)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ gateways.IArticleRepository = &ArticleRepository{}
|
||||||
@@ -30,7 +30,7 @@ func mapImageToDomain(image entity.Image) domain.Image {
|
|||||||
return domain.FromDB(
|
return domain.FromDB(
|
||||||
image.ID,
|
image.ID,
|
||||||
image.Path,
|
image.Path,
|
||||||
image.PostID,
|
image.ArticleID,
|
||||||
image.CreatedAt,
|
image.CreatedAt,
|
||||||
image.UpdatedAt,
|
image.UpdatedAt,
|
||||||
)
|
)
|
||||||
|
|||||||
+4
-3
@@ -1,16 +1,17 @@
|
|||||||
package models
|
package models
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"gorm.io/gorm"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"gorm.io/gorm"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Post struct {
|
type Article struct {
|
||||||
gorm.Model
|
gorm.Model
|
||||||
ID uint32 `gorm:"primary_key;auto_increment;not_null"`
|
ID uint32 `gorm:"primary_key;auto_increment;not_null"`
|
||||||
Title string
|
Title string
|
||||||
Body string
|
Body string
|
||||||
Images []*Image `gorm:"many2many:post_images;"`
|
Images []*Image `gorm:"many2many:article_images;"`
|
||||||
IsOnline bool `gorm:"not_null;default:false"`
|
IsOnline bool `gorm:"not_null;default:false"`
|
||||||
CreatedAt time.Time `gorm:"autoCreateTime"`
|
CreatedAt time.Time `gorm:"autoCreateTime"`
|
||||||
UpdatedAt time.Time `gorm:"autoUpdateTime"`
|
UpdatedAt time.Time `gorm:"autoUpdateTime"`
|
||||||
@@ -1,15 +1,16 @@
|
|||||||
package models
|
package models
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"gorm.io/gorm"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"gorm.io/gorm"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Image struct {
|
type Image struct {
|
||||||
gorm.Model
|
gorm.Model
|
||||||
ID uint32 `gorm:"primary_key;auto_increment;not_null"`
|
ID uint32 `gorm:"primary_key;auto_increment;not_null"`
|
||||||
Path string
|
Path string
|
||||||
PostID uint32
|
ArticleID uint32
|
||||||
CreatedAt time.Time `gorm:"autoCreateTime"`
|
CreatedAt time.Time `gorm:"autoCreateTime"`
|
||||||
UpdatedAt time.Time `gorm:"autoUpdateTime"`
|
UpdatedAt time.Time `gorm:"autoUpdateTime"`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,139 +0,0 @@
|
|||||||
package gateways
|
|
||||||
|
|
||||||
import (
|
|
||||||
entity "GoCMS/adapters/secondary/gateways/models"
|
|
||||||
"GoCMS/domain/gateways"
|
|
||||||
domain "GoCMS/domain/post"
|
|
||||||
"gorm.io/gorm"
|
|
||||||
)
|
|
||||||
|
|
||||||
type PostRepository struct {
|
|
||||||
db *gorm.DB
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewPostRepository(db *gorm.DB) *PostRepository {
|
|
||||||
return &PostRepository{db}
|
|
||||||
}
|
|
||||||
|
|
||||||
func mapPostToDomain(post entity.Post) domain.Post {
|
|
||||||
return domain.FromDb(post.ID, post.Title, post.Body, post.Images, post.IsOnline, post.CreatedAt, post.UpdatedAt)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *PostRepository) Get(id uint32) (domain.Post, error) {
|
|
||||||
var post entity.Post
|
|
||||||
err := a.db.Model(&entity.Post{}).Preload("Images").First(&post, id).Error
|
|
||||||
if err != nil {
|
|
||||||
return domain.Post{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return mapPostToDomain(post), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *PostRepository) GetByName(name string) (domain.Post, error) {
|
|
||||||
var post entity.Post
|
|
||||||
err := a.db.Model(&entity.Post{}).Where("title = ?", name).First(&post).Error
|
|
||||||
if err != nil {
|
|
||||||
return domain.Post{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return mapPostToDomain(post), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *PostRepository) Create(post domain.Post) (domain.Post, error) {
|
|
||||||
creationResult := a.db.Create(&entity.Post{
|
|
||||||
Title: post.Title,
|
|
||||||
Body: post.Body,
|
|
||||||
})
|
|
||||||
if creationResult.Error != nil {
|
|
||||||
return domain.Post{}, creationResult.Error
|
|
||||||
}
|
|
||||||
|
|
||||||
var createdPost entity.Post
|
|
||||||
creationResult.Scan(&createdPost)
|
|
||||||
|
|
||||||
return mapPostToDomain(createdPost), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *PostRepository) GetAll() []domain.Post {
|
|
||||||
var posts []entity.Post
|
|
||||||
err := a.db.Model(&entity.Post{}).Find(&posts).Error
|
|
||||||
if err != nil {
|
|
||||||
return []domain.Post{}
|
|
||||||
}
|
|
||||||
|
|
||||||
var domainPosts = make([]domain.Post, 0)
|
|
||||||
for _, post := range posts {
|
|
||||||
domainPosts = append(domainPosts, mapPostToDomain(post))
|
|
||||||
}
|
|
||||||
|
|
||||||
return domainPosts
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *PostRepository) UpdateBody(id uint32, body string) (domain.Post, error) {
|
|
||||||
var localPost entity.Post
|
|
||||||
err := a.db.Model(&entity.Post{}).First(&localPost, id).Error
|
|
||||||
if err != nil {
|
|
||||||
return domain.Post{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
localPost.Body = body
|
|
||||||
err = a.db.Save(&localPost).Error
|
|
||||||
if err != nil {
|
|
||||||
return domain.Post{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
newPost := domain.FromDb(
|
|
||||||
localPost.ID,
|
|
||||||
localPost.Title,
|
|
||||||
localPost.Body,
|
|
||||||
localPost.Images,
|
|
||||||
localPost.IsOnline,
|
|
||||||
localPost.CreatedAt,
|
|
||||||
localPost.UpdatedAt,
|
|
||||||
)
|
|
||||||
|
|
||||||
return newPost, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *PostRepository) UpdateIsOnline(id uint32, isOnline bool) (domain.Post, error) {
|
|
||||||
var localPost entity.Post
|
|
||||||
err := a.db.Model(&entity.Post{}).First(&localPost, id).Error
|
|
||||||
if err != nil {
|
|
||||||
return domain.Post{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
localPost.IsOnline = isOnline
|
|
||||||
err = a.db.Save(&localPost).Error
|
|
||||||
if err != nil {
|
|
||||||
return domain.Post{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return mapPostToDomain(localPost), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *PostRepository) Delete(id uint32) error {
|
|
||||||
return a.db.Delete(&entity.Post{}, id).Error
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *PostRepository) AddImage(postId uint32, imageId uint32) error {
|
|
||||||
var localPost entity.Post
|
|
||||||
err := a.db.Model(&entity.Post{}).First(&localPost, postId).Error
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
var localImage entity.Image
|
|
||||||
err = a.db.Model(&entity.Image{}).First(&localImage, imageId).Error
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = a.db.Model(&localPost).Association("Images").Append(&localImage)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ gateways.IPostRepository = &PostRepository{}
|
|
||||||
+14
-14
@@ -1,7 +1,7 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<title>GoCMS | Create post</title>
|
<title>GoCMS | Create article</title>
|
||||||
{{.Head}}
|
{{.Head}}
|
||||||
<style>
|
<style>
|
||||||
form {
|
form {
|
||||||
@@ -12,27 +12,27 @@
|
|||||||
<body class="text-dark">
|
<body class="text-dark">
|
||||||
{{.Navbar}}
|
{{.Navbar}}
|
||||||
<div class="container mt-3 text-black-50">
|
<div class="container mt-3 text-black-50">
|
||||||
<h1>Post - creation</h1>
|
<h1>Article - creation</h1>
|
||||||
<p>Create a new post</p>
|
<p>Create a new article</p>
|
||||||
<form action="create" method="post" class="d-flex align-items-center flex-column gap-3" id="createPostForm">
|
<form action="create" method="post" class="d-flex align-items-center flex-column gap-3" id="createArticleForm">
|
||||||
<div class="form-floating w-100">
|
<div class="form-floating w-100">
|
||||||
<input type="text" class="form-control {{ if .PageError.IsError }} is-invalid {{ end }}" id="name"
|
<input type="text" class="form-control {{ if .PageError.IsError }} is-invalid {{ end }}" id="name"
|
||||||
name="name" placeholder="Chose the post name" value="{{ .Name }}">
|
name="name" placeholder="Chose the article name" value="{{ .Name }}">
|
||||||
<label for="name">Name</label>
|
<label for="name">Name</label>
|
||||||
<div class="invalid-feedback">{{ .PageError.Message }}</div>
|
<div class="invalid-feedback">{{ .PageError.Message }}</div>
|
||||||
</div>
|
</div>
|
||||||
<button id="createPostButton" class="btn btn-primary w-100" disabled type="submit">
|
<button id="createArticleButton" class="btn btn-primary w-100" disabled type="submit">
|
||||||
<span class="createPostFormButtonLoading visually-hidden spinner-border spinner-border-sm"
|
<span class="createArticleFormButtonLoading visually-hidden spinner-border spinner-border-sm"
|
||||||
role="status"></span>
|
role="status"></span>
|
||||||
<span class="createPostFormButtonDefault">Create</span>
|
<span class="createArticleFormButtonDefault">Create</span>
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
const button = document.querySelector("#createPostButton")
|
const button = document.querySelector("#createArticleButton")
|
||||||
const inputs = document.querySelectorAll('input')
|
const inputs = document.querySelectorAll('input')
|
||||||
const form = document.querySelector("#createPostForm")
|
const form = document.querySelector("#createArticleForm")
|
||||||
|
|
||||||
function formFieldsEmpty() {
|
function formFieldsEmpty() {
|
||||||
return Array.from(inputs).some((input) => input.value === "")
|
return Array.from(inputs).some((input) => input.value === "")
|
||||||
@@ -44,11 +44,11 @@
|
|||||||
|
|
||||||
function setButtonLoading() {
|
function setButtonLoading() {
|
||||||
button.classList.add("disabled")
|
button.classList.add("disabled")
|
||||||
button.querySelector(".createPostFormButtonDefault").classList.add("visually-hidden")
|
button.querySelector(".createArticleFormButtonDefault").classList.add("visually-hidden")
|
||||||
button.querySelector(".createPostFormButtonLoading").classList.remove("visually-hidden")
|
button.querySelector(".createArticleFormButtonLoading").classList.remove("visually-hidden")
|
||||||
}
|
}
|
||||||
|
|
||||||
function onCreatePostFormSubmit(event) {
|
function onCreateArticleFormSubmit(event) {
|
||||||
setButtonLoading()
|
setButtonLoading()
|
||||||
event.target.submit()
|
event.target.submit()
|
||||||
}
|
}
|
||||||
@@ -57,7 +57,7 @@
|
|||||||
if (event.target.tagName === "INPUT") setButtonDisabled()
|
if (event.target.tagName === "INPUT") setButtonDisabled()
|
||||||
}
|
}
|
||||||
|
|
||||||
form.addEventListener('submit', onCreatePostFormSubmit)
|
form.addEventListener('submit', onCreateArticleFormSubmit)
|
||||||
window.addEventListener('input', onInput)
|
window.addEventListener('input', onInput)
|
||||||
setButtonDisabled()
|
setButtonDisabled()
|
||||||
</script>
|
</script>
|
||||||
+6
-6
@@ -1,7 +1,7 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<title>GoCMS | Post edition</title>
|
<title>GoCMS | Article edition</title>
|
||||||
{{.Head}}
|
{{.Head}}
|
||||||
</head>
|
</head>
|
||||||
<body class="text-dark vh-100 d-flex flex-column">
|
<body class="text-dark vh-100 d-flex flex-column">
|
||||||
@@ -9,8 +9,8 @@
|
|||||||
<form class="container mt-3 text-black-50 d-flex flex-column h-100" action="edit" method="post">
|
<form class="container mt-3 text-black-50 d-flex flex-column h-100" action="edit" method="post">
|
||||||
<div class="d-flex align-items-center justify-content-between">
|
<div class="d-flex align-items-center justify-content-between">
|
||||||
<div>
|
<div>
|
||||||
<h1>Post - edition</h1>
|
<h1>Article - edition</h1>
|
||||||
<label for="postBody">Edition</label>
|
<label for="articleBody">Edition</label>
|
||||||
</div>
|
</div>
|
||||||
{{ if .Alert.Message }}
|
{{ if .Alert.Message }}
|
||||||
<div class="alert {{ if .Alert.IsError }} alert-danger {{ else }} alert-success {{ end }} alert-dismissible"
|
<div class="alert {{ if .Alert.IsError }} alert-danger {{ else }} alert-success {{ end }} alert-dismissible"
|
||||||
@@ -30,14 +30,14 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="text-black-50 d-flex align-items-center justify-content-center h-100 py-3">
|
<div class="text-black-50 d-flex align-items-center justify-content-center h-100 py-3">
|
||||||
<textarea id="postBody" name="postBody">{{.Post.Body}}</textarea>
|
<textarea id="articleBody" name="articleBody">{{.Article.Body}}</textarea>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<script src="/static/tinymce/js/tinymce/tinymce.min.js"></script>
|
<script src="/static/tinymce/js/tinymce/tinymce.min.js"></script>
|
||||||
<script>
|
<script>
|
||||||
tinymce.init({
|
tinymce.init({
|
||||||
selector: '#postBody',
|
selector: '#articleBody',
|
||||||
promotion: false,
|
promotion: false,
|
||||||
plugins: 'image lists visualblocks',
|
plugins: 'image lists visualblocks',
|
||||||
toolbar: 'undo redo | formatselect | bold italic underline strikethrough | alignleft aligncenter alignright alignjustify | bullist numlist | outdent indent | removeformat | image',
|
toolbar: 'undo redo | formatselect | bold italic underline strikethrough | alignleft aligncenter alignright alignjustify | bullist numlist | outdent indent | removeformat | image',
|
||||||
@@ -52,7 +52,7 @@
|
|||||||
images_upload_handler: (blobInfo, progress) => new Promise((resolve, reject) => {
|
images_upload_handler: (blobInfo, progress) => new Promise((resolve, reject) => {
|
||||||
const xhr = new XMLHttpRequest();
|
const xhr = new XMLHttpRequest();
|
||||||
xhr.withCredentials = false;
|
xhr.withCredentials = false;
|
||||||
xhr.open('POST', "/post/{{.Post.ID}}/image/create");
|
xhr.open('POST', "/article/{{.Article.ID}}/image/create");
|
||||||
|
|
||||||
xhr.upload.onprogress = (e) => {
|
xhr.upload.onprogress = (e) => {
|
||||||
progress(e.loaded / e.total * 100);
|
progress(e.loaded / e.total * 100);
|
||||||
+20
-20
@@ -1,7 +1,7 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<title>GoCMS | Posts</title>
|
<title>GoCMS | Articles</title>
|
||||||
{{.Head}}
|
{{.Head}}
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
@@ -15,8 +15,8 @@
|
|||||||
<body class="text-dark">
|
<body class="text-dark">
|
||||||
{{.Navbar}}
|
{{.Navbar}}
|
||||||
<div class="container mt-3 text-black-50">
|
<div class="container mt-3 text-black-50">
|
||||||
<h1>Posts</h1>
|
<h1>Articles</h1>
|
||||||
<p>List of your posts</p>
|
<p>List of your articles</p>
|
||||||
<table class="table table-hover">
|
<table class="table table-hover">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
@@ -30,59 +30,59 @@
|
|||||||
<tbody>
|
<tbody>
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan="5" class="text-center">
|
<td colspan="5" class="text-center">
|
||||||
<form action="post/create" method="get">
|
<form action="article/create" method="get">
|
||||||
<button class="btn btn-sm btn-link w-100 h-100" type="submit">Create a new post...</button>
|
<button class="btn btn-sm btn-link w-100 h-100" type="submit">Create a new article...</button>
|
||||||
</form>
|
</form>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{{ range $post := .Posts }}
|
{{ range $article := .Articles }}
|
||||||
<tr>
|
<tr>
|
||||||
<td>{{ $post.Title }}</td>
|
<td>{{ $article.Title }}</td>
|
||||||
<td class="date">{{ $post.CreatedAt }}</td>
|
<td class="date">{{ $article.CreatedAt }}</td>
|
||||||
<td class="date">{{ $post.UpdatedAt }}</td>
|
<td class="date">{{ $article.UpdatedAt }}</td>
|
||||||
<td>{{ if $post.IsOnline }}🟢 Online{{ else }}🟠 Offline{{ end }}</td>
|
<td>{{ if $article.IsOnline }}🟢 Online{{ else }}🟠 Offline{{ end }}</td>
|
||||||
<td>
|
<td>
|
||||||
<a href="/post/{{ $post.ID }}/edit" class="btn btn-outline-primary btn-sm">
|
<a href="/article/{{ $article.ID }}/edit" class="btn btn-outline-primary btn-sm">
|
||||||
<svg width="12px" height="12px" fill="currentColor">
|
<svg width="12px" height="12px" fill="currentColor">
|
||||||
<use xlink:href="/static/bootstrap-icons.svg#pen"/>
|
<use xlink:href="/static/bootstrap-icons.svg#pen"/>
|
||||||
</svg>
|
</svg>
|
||||||
</a>
|
</a>
|
||||||
{{ if $post.IsOnline }}
|
{{ if $article.IsOnline }}
|
||||||
<a href="/post/{{ $post.ID }}/unpublish" class="btn btn-outline-info btn-sm">
|
<a href="/article/{{ $article.ID }}/unpublish" class="btn btn-outline-info btn-sm">
|
||||||
<svg width="12px" height="12px" fill="currentColor">
|
<svg width="12px" height="12px" fill="currentColor">
|
||||||
<use xlink:href="/static/bootstrap-icons.svg#eye-slash"/>
|
<use xlink:href="/static/bootstrap-icons.svg#eye-slash"/>
|
||||||
</svg>
|
</svg>
|
||||||
</a>
|
</a>
|
||||||
{{ else }}
|
{{ else }}
|
||||||
<a href="/post/{{ $post.ID }}/publish" class="btn btn-outline-info btn-sm">
|
<a href="/article/{{ $article.ID }}/publish" class="btn btn-outline-info btn-sm">
|
||||||
<svg width="12px" height="12px" fill="currentColor">
|
<svg width="12px" height="12px" fill="currentColor">
|
||||||
<use xlink:href="/static/bootstrap-icons.svg#eye"/>
|
<use xlink:href="/static/bootstrap-icons.svg#eye"/>
|
||||||
</svg>
|
</svg>
|
||||||
</a>
|
</a>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
<a href="/post/{{ $post.Title }}/delete" class="btn btn-outline-danger btn-sm" data-bs-toggle="modal"
|
<a href="/article/{{ $article.Title }}/delete" class="btn btn-outline-danger btn-sm" data-bs-toggle="modal"
|
||||||
data-bs-target="#{{ $post.ID }}">
|
data-bs-target="#{{ $article.ID }}">
|
||||||
<svg width=" 12px" height="12px" fill="currentColor">
|
<svg width=" 12px" height="12px" fill="currentColor">
|
||||||
<use xlink:href="/static/bootstrap-icons.svg#trash"/>
|
<use xlink:href="/static/bootstrap-icons.svg#trash"/>
|
||||||
</svg>
|
</svg>
|
||||||
</a>
|
</a>
|
||||||
<div class="modal fade" id="{{ $post.ID }}" tabindex="-1" aria-labelledby="{{ $post.ID }}Label"
|
<div class="modal fade" id="{{ $article.ID }}" tabindex="-1" aria-labelledby="{{ $article.ID }}Label"
|
||||||
aria-hidden="true">
|
aria-hidden="true">
|
||||||
<div class="modal-dialog">
|
<div class="modal-dialog">
|
||||||
<div class="modal-content">
|
<div class="modal-content">
|
||||||
<div class="modal-header">
|
<div class="modal-header">
|
||||||
<h1 class="modal-title fs-5" id="{{ $post.ID }}Label">Delete post</h1>
|
<h1 class="modal-title fs-5" id="{{ $article.ID }}Label">Delete post</h1>
|
||||||
<button type="button" class="btn-close" data-bs-dismiss="modal"
|
<button type="button" class="btn-close" data-bs-dismiss="modal"
|
||||||
aria-label="Close"></button>
|
aria-label="Close"></button>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<p>You are about to delete the post <b>« {{ $post.Title }} »</b>, this is definitive,
|
<p>You are about to delete the post <b>« {{ $article.Title }} »</b>, this is definitive,
|
||||||
are you sure?</p>
|
are you sure?</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
<button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">Cancel
|
<button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">Cancel
|
||||||
</button>
|
</button>
|
||||||
<a href="/post/{{$post.ID}}/delete" class="btn btn-danger">Delete</a>
|
<a href="/article/{{$article.ID}}/delete" class="btn btn-danger">Delete</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -16,7 +16,7 @@
|
|||||||
<a class="nav-link" href="/home">Home</a>
|
<a class="nav-link" href="/home">Home</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link" href="/post">Posts</a>
|
<a class="nav-link" href="/article">Articles</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link" href="/integration">Integration</a>
|
<a class="nav-link" href="/integration">Integration</a>
|
||||||
|
|||||||
@@ -9,8 +9,8 @@
|
|||||||
<div class="container mt-3 text-black-50">
|
<div class="container mt-3 text-black-50">
|
||||||
<h1>Home</h1>
|
<h1>Home</h1>
|
||||||
<p>Welcome on GoCMS !</p>
|
<p>Welcome on GoCMS !</p>
|
||||||
<a href="/post">
|
<a href="/article">
|
||||||
<button class="btn btn-outline-primary">Posts »</button>
|
<button class="btn btn-outline-primary">Articles »</button>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
@@ -12,19 +12,19 @@
|
|||||||
<div class="mt-5">
|
<div class="mt-5">
|
||||||
<section>
|
<section>
|
||||||
<h2>Introduction</h2>
|
<h2>Introduction</h2>
|
||||||
<p>This documentation explains how to integrate our headless CMS API to retrieve post data via the <code>/post</code>
|
<p>This documentation explains how to integrate our headless CMS API to retrieve article data via the <code>/article</code>
|
||||||
route.</p>
|
route.</p>
|
||||||
</section>
|
</section>
|
||||||
<section>
|
<section>
|
||||||
<h2>/v1/post</h2>
|
<h2>/v1/article</h2>
|
||||||
<p>The <code>{{ .Host }}/v1/post</code> route returns a list of posts in JSON format. Each post has the
|
<p>The <code>{{ .Host }}/v1/article</code> route returns a list of articles in JSON format. Each article has the
|
||||||
following
|
following
|
||||||
structure:</p>
|
structure:</p>
|
||||||
<pre>
|
<pre>
|
||||||
<code>[
|
<code>[
|
||||||
{
|
{
|
||||||
"id": 12,
|
"id": 12,
|
||||||
"title": "This is a post!",
|
"title": "This is a article!",
|
||||||
"body": "<p>?</p>\r\n<p><img src=\"../../static/uploadedImages/649691a8-4f2e-48ca-9abe-27a60621d2e4.png\"
|
"body": "<p>?</p>\r\n<p><img src=\"../../static/uploadedImages/649691a8-4f2e-48ca-9abe-27a60621d2e4.png\"
|
||||||
alt=\"\" width=\"391\" height=\"465\"></p>",
|
alt=\"\" width=\"391\" height=\"465\"></p>",
|
||||||
"created_at": "2024-06-13T15:30:55.3563238+02:00",
|
"created_at": "2024-06-13T15:30:55.3563238+02:00",
|
||||||
@@ -34,21 +34,21 @@
|
|||||||
</pre>
|
</pre>
|
||||||
</section>
|
</section>
|
||||||
<section>
|
<section>
|
||||||
<h2>/v1/post/{id}</h2>
|
<h2>/v1/article/{id}</h2>
|
||||||
<p>The <code>{{ .Host }}/v1/post/{id}</code> route returns a single post in JSON format. The post has the
|
<p>The <code>{{ .Host }}/v1/article/{id}</code> route returns a single article in JSON format. The article has the
|
||||||
following
|
following
|
||||||
structure:</p>
|
structure:</p>
|
||||||
<pre>
|
<pre>
|
||||||
<code>{
|
<code>{
|
||||||
"id": 12,
|
"id": 12,
|
||||||
"title": "ravioli sans le ra et le i",
|
"title": "ravioli",
|
||||||
"body": "<\p>?<\/p>\r\n<\p><\img src=\"../../static/uploadedImages/649691a8-4f2e-48ca-9abe-27a60621d2e4.png\" alt=\"\"
|
"body": "<\p>?<\/p>\r\n<\p><\img src=\"../../static/uploadedImages/649691a8-4f2e-48ca-9abe-27a60621d2e4.png\" alt=\"\"
|
||||||
width=\"391\" height=\"465\"><\/p>",
|
width=\"391\" height=\"465\"><\/p>",
|
||||||
"images": [
|
"images": [
|
||||||
{
|
{
|
||||||
"id": 17,
|
"id": 17,
|
||||||
"path": "/static/uploadedImages/649691a8-4f2e-48ca-9abe-27a60621d2e4.png",
|
"path": "/static/uploadedImages/649691a8-4f2e-48ca-9abe-27a60621d2e4.png",
|
||||||
"post_id": 0,
|
"article_id": 0,
|
||||||
"created_at": "2024-06-13T15:31:09.728359+02:00",
|
"created_at": "2024-06-13T15:31:09.728359+02:00",
|
||||||
"updated_at": "2024-06-13T15:31:09.728359+02:00"
|
"updated_at": "2024-06-13T15:31:09.728359+02:00"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,99 @@
|
|||||||
|
package article
|
||||||
|
|
||||||
|
import (
|
||||||
|
"GoCMS/api"
|
||||||
|
"GoCMS/api/controllers/auth"
|
||||||
|
"GoCMS/domain/article"
|
||||||
|
"GoCMS/useCases"
|
||||||
|
"encoding/json"
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/go-chi/chi/v5"
|
||||||
|
)
|
||||||
|
|
||||||
|
type PostArticle struct {
|
||||||
|
Title string `json:"title" validate:"required,min=3,max=50"`
|
||||||
|
Body string `json:"body" validate:"required,max=10000"`
|
||||||
|
}
|
||||||
|
|
||||||
|
const IdUint32ErrorMessage = "The server expects the ID to be in the format of an unsigned 32-bit integer (uint32)."
|
||||||
|
|
||||||
|
func getArticle(w http.ResponseWriter, r *http.Request) {
|
||||||
|
id, err := strconv.ParseUint(chi.URLParam(r, "id"), 10, 32)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, IdUint32ErrorMessage, http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
localArticle, err := api.Container.GetArticleUseCase.GetArticle(uint32(id))
|
||||||
|
if err != nil || !localArticle.IsOnline {
|
||||||
|
http.Error(w, "The requested resource, identified by its unique ID, could not be found on the server.", http.StatusNotFound)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
articleJson, _ := json.Marshal(localArticle)
|
||||||
|
_, _ = w.Write(articleJson)
|
||||||
|
}
|
||||||
|
|
||||||
|
func postArticle(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var localArticle PostArticle
|
||||||
|
err := json.NewDecoder(r.Body).Decode(&localArticle)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, auth.BodyErrorMessage, http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = api.Validate.Struct(localArticle)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
createdArticle, err := api.Container.CreateArticleUseCase.CreateArticle(useCases.CreateArticleCommand{
|
||||||
|
Title: localArticle.Title,
|
||||||
|
Body: localArticle.Body,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
articleJson, _ := json.Marshal(createdArticle)
|
||||||
|
|
||||||
|
_, _ = w.Write(articleJson)
|
||||||
|
}
|
||||||
|
|
||||||
|
func listArticles(w http.ResponseWriter, _ *http.Request) {
|
||||||
|
articles := api.Container.ListArticlesUseCase.ListArticles()
|
||||||
|
onlineArticles := make([]article.Article, 0)
|
||||||
|
for _, localArticle := range articles {
|
||||||
|
if localArticle.IsOnline {
|
||||||
|
onlineArticles = append(onlineArticles, localArticle)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
articlesJson, _ := json.Marshal(onlineArticles)
|
||||||
|
_, _ = w.Write(articlesJson)
|
||||||
|
}
|
||||||
|
|
||||||
|
func deleteArticle(w http.ResponseWriter, r *http.Request) {
|
||||||
|
id, err := strconv.ParseUint(chi.URLParam(r, "id"), 10, 32)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, IdUint32ErrorMessage, http.StatusBadRequest)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = api.Container.DeleteArticleUseCase.DeleteArticle(uint32(id))
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusNotFound)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, _ = w.Write([]byte("article deleted"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewArticleRouter() http.Handler {
|
||||||
|
r := chi.NewRouter()
|
||||||
|
r.Get("/{id}", getArticle)
|
||||||
|
r.Post("/", postArticle)
|
||||||
|
r.Get("/", listArticles)
|
||||||
|
r.Delete("/{id}", deleteArticle)
|
||||||
|
return r
|
||||||
|
}
|
||||||
@@ -29,7 +29,6 @@ type LoginCredentials struct {
|
|||||||
var shouldCookieBeSecure = os.Getenv("ENVIRONMENT") == "production"
|
var shouldCookieBeSecure = os.Getenv("ENVIRONMENT") == "production"
|
||||||
var Token *jwtauth.JWTAuth
|
var Token *jwtauth.JWTAuth
|
||||||
|
|
||||||
// TODO Move into its own file or package that handles api errors
|
|
||||||
const LogsErrorMessage = "Access to the requested resource is forbidden due to incorrect password and/or username."
|
const LogsErrorMessage = "Access to the requested resource is forbidden due to incorrect password and/or username."
|
||||||
const BodyErrorMessage = "The request cannot be processed due to a mismatch in the format of the body."
|
const BodyErrorMessage = "The request cannot be processed due to a mismatch in the format of the body."
|
||||||
|
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ func PostImage(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
err = api.Container.UpdatePostUseCase.AddImage(uint32(idInt), newImage.ID)
|
err = api.Container.UpdateArticleUseCase.AddImage(uint32(idInt), newImage.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -0,0 +1,58 @@
|
|||||||
|
package pages
|
||||||
|
|
||||||
|
import (
|
||||||
|
"GoCMS/api"
|
||||||
|
"GoCMS/useCases"
|
||||||
|
"html/template"
|
||||||
|
"net/http"
|
||||||
|
"regexp"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ArticleCreatePageError struct {
|
||||||
|
IsError bool
|
||||||
|
Message string
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetArticleCreatePageTemplate(postName string, errorMessage string) ([]byte, error) {
|
||||||
|
navbarTmpl, _ := api.Container.GetPageUseCase.GetPage("componentNavbar", nil)
|
||||||
|
return api.Container.GetPageUseCase.GetPage("articleCreate", map[string]any{
|
||||||
|
"Navbar": template.HTML(navbarTmpl),
|
||||||
|
"Head": headTmpl,
|
||||||
|
"PageError": ArticleCreatePageError{
|
||||||
|
IsError: errorMessage != "",
|
||||||
|
Message: errorMessage,
|
||||||
|
},
|
||||||
|
"Name": postName,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func PostArticleCreatePage(w http.ResponseWriter, r *http.Request) {
|
||||||
|
_ = r.ParseForm()
|
||||||
|
|
||||||
|
postName := r.FormValue("name")
|
||||||
|
pattern := regexp.MustCompile("^[a-zA-Z0-9À-ÖØ-öø-ÿĀ-ſḀ-ỿ ]{3,50}$")
|
||||||
|
|
||||||
|
if !pattern.MatchString(postName) {
|
||||||
|
postsTmpl, _ := GetArticleCreatePageTemplate(postName, "Name should be alphanumeric, and between 3 and 50 characters.")
|
||||||
|
_, _ = w.Write(postsTmpl)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
post, err := api.Container.CreateArticleUseCase.CreateArticle(useCases.CreateArticleCommand{
|
||||||
|
Title: postName,
|
||||||
|
Body: "",
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
postsTmpl, _ := GetArticleCreatePageTemplate(postName, "Something went wrong when creating the article, please contact admin.")
|
||||||
|
_, _ = w.Write(postsTmpl)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
http.Redirect(w, r, "/article/"+strconv.Itoa(int(post.ID))+"/edit", http.StatusSeeOther)
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetArticleCreatePage(w http.ResponseWriter, _ *http.Request) {
|
||||||
|
postsTmpl, _ := GetArticleCreatePageTemplate("", "")
|
||||||
|
_, _ = w.Write(postsTmpl)
|
||||||
|
}
|
||||||
@@ -2,7 +2,7 @@ package pages
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"GoCMS/api"
|
"GoCMS/api"
|
||||||
"GoCMS/api/controllers/post"
|
"GoCMS/api/controllers/article"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
@@ -10,21 +10,21 @@ import (
|
|||||||
"github.com/go-chi/chi/v5"
|
"github.com/go-chi/chi/v5"
|
||||||
)
|
)
|
||||||
|
|
||||||
func GetPostDeletePage(w http.ResponseWriter, r *http.Request) {
|
func GetArticleDeletePage(w http.ResponseWriter, r *http.Request) {
|
||||||
id, err := strconv.ParseUint(chi.URLParam(r, "id"), 10, 32)
|
id, err := strconv.ParseUint(chi.URLParam(r, "id"), 10, 32)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, post.IdUint32ErrorMessage, http.StatusBadRequest)
|
http.Error(w, article.IdUint32ErrorMessage, http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
localPost, err := api.Container.GetPostUseCase.GetPost(uint32(id))
|
localArticle, err := api.Container.GetArticleUseCase.GetArticle(uint32(id))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, err.Error(), http.StatusNotFound)
|
http.Error(w, err.Error(), http.StatusNotFound)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Println(localPost.Images)
|
fmt.Println(localArticle.Images)
|
||||||
for _, image := range localPost.Images {
|
for _, image := range localArticle.Images {
|
||||||
err = api.Container.DeleteImageUseCase.DeleteImage(image.ID)
|
err = api.Container.DeleteImageUseCase.DeleteImage(image.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, err.Error(), http.StatusNotFound)
|
http.Error(w, err.Error(), http.StatusNotFound)
|
||||||
@@ -32,11 +32,11 @@ func GetPostDeletePage(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
err = api.Container.DeletePostUseCase.DeletePost(uint32(id))
|
err = api.Container.DeleteArticleUseCase.DeleteArticle(uint32(id))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, http.StatusText(400), http.StatusBadRequest)
|
http.Error(w, http.StatusText(400), http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
http.Redirect(w, r, "/post", http.StatusSeeOther)
|
http.Redirect(w, r, "/article", http.StatusSeeOther)
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,75 @@
|
|||||||
|
package pages
|
||||||
|
|
||||||
|
import (
|
||||||
|
"GoCMS/api"
|
||||||
|
"GoCMS/domain/article"
|
||||||
|
"html/template"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/go-chi/chi/v5"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ArticleEditPageAlert struct {
|
||||||
|
IsError bool
|
||||||
|
Message string
|
||||||
|
}
|
||||||
|
|
||||||
|
func getArticleEditPageTemplate(post article.Article, alert ArticleEditPageAlert) []byte {
|
||||||
|
navbarTmpl, _ := api.Container.GetPageUseCase.GetPage("componentNavbar", nil)
|
||||||
|
articleTmpl, _ := api.Container.GetPageUseCase.GetPage("articleEdit", map[string]any{
|
||||||
|
"Navbar": template.HTML(navbarTmpl),
|
||||||
|
"Head": headTmpl,
|
||||||
|
"Article": post,
|
||||||
|
"Alert": alert,
|
||||||
|
"Secured": os.Getenv("ENVIRONMENT") == "production",
|
||||||
|
})
|
||||||
|
return articleTmpl
|
||||||
|
}
|
||||||
|
|
||||||
|
func PostArticleEditPage(w http.ResponseWriter, r *http.Request) {
|
||||||
|
articleID := chi.URLParam(r, "id")
|
||||||
|
articleIDint, err := strconv.Atoi(articleID)
|
||||||
|
if err != nil {
|
||||||
|
_, _ = w.Write(getArticleEditPageTemplate(article.Article{}, ArticleEditPageAlert{
|
||||||
|
IsError: true,
|
||||||
|
Message: "Could not find the requested article.",
|
||||||
|
}))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
_ = r.ParseForm()
|
||||||
|
articleBody := r.FormValue("articleBody")
|
||||||
|
|
||||||
|
getArticle, _ := api.Container.GetArticleUseCase.GetArticle(uint32(articleIDint))
|
||||||
|
updatedArticle, err := api.Container.UpdateArticleUseCase.UpdateBody(getArticle.ID, articleBody)
|
||||||
|
if err != nil {
|
||||||
|
_, _ = w.Write(getArticleEditPageTemplate(article.Article{Body: articleBody}, ArticleEditPageAlert{
|
||||||
|
IsError: true,
|
||||||
|
Message: "Could not save the article: " + err.Error(),
|
||||||
|
}))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
_, _ = w.Write(getArticleEditPageTemplate(updatedArticle, ArticleEditPageAlert{
|
||||||
|
IsError: false,
|
||||||
|
Message: "Article successfully edited!",
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetArticleEditPage(w http.ResponseWriter, r *http.Request) {
|
||||||
|
articleID := chi.URLParam(r, "id")
|
||||||
|
articleIDint, err := strconv.Atoi(articleID)
|
||||||
|
if err != nil {
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
getArticle, _ := api.Container.GetArticleUseCase.GetArticle(uint32(articleIDint))
|
||||||
|
|
||||||
|
_, _ = w.Write(getArticleEditPageTemplate(getArticle, ArticleEditPageAlert{
|
||||||
|
IsError: false,
|
||||||
|
Message: "",
|
||||||
|
}))
|
||||||
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
package pages
|
||||||
|
|
||||||
|
import (
|
||||||
|
"GoCMS/api"
|
||||||
|
"errors"
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/go-chi/chi/v5"
|
||||||
|
)
|
||||||
|
|
||||||
|
func updateIsOnline(articleId string, isOnline bool) (error, int) {
|
||||||
|
articleIdInt, err := strconv.Atoi(articleId)
|
||||||
|
if err != nil {
|
||||||
|
return errors.New("the server expects the ID to be in the format of an unsigned 32-bit integer (uint32)"), http.StatusBadRequest
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = api.Container.UpdateArticleUseCase.UpdateIsOnline(uint32(articleIdInt), isOnline)
|
||||||
|
if err != nil {
|
||||||
|
return errors.New("the requested resource, identified by its unique ID, could not be found on the server"), http.StatusNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, http.StatusOK
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetArticleUnpublishPage(w http.ResponseWriter, r *http.Request) {
|
||||||
|
articleId := chi.URLParam(r, "id")
|
||||||
|
err, statusCode := updateIsOnline(articleId, false)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, err.Error(), statusCode)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
http.Redirect(w, r, "/article", http.StatusSeeOther)
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetArticlePublishPage(w http.ResponseWriter, r *http.Request) {
|
||||||
|
articleId := chi.URLParam(r, "id")
|
||||||
|
err, statusCode := updateIsOnline(articleId, true)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, err.Error(), statusCode)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
http.Redirect(w, r, "/article", http.StatusSeeOther)
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
package pages
|
||||||
|
|
||||||
|
import (
|
||||||
|
"GoCMS/api"
|
||||||
|
"html/template"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
func GetArticlesPage(w http.ResponseWriter, _ *http.Request) {
|
||||||
|
articles := api.Container.ListArticlesUseCase.ListArticles()
|
||||||
|
|
||||||
|
var formattedArticles []map[string]any
|
||||||
|
for _, article := range articles {
|
||||||
|
formattedArticles = append(formattedArticles, map[string]any{
|
||||||
|
"ID": article.ID,
|
||||||
|
"Title": article.Title,
|
||||||
|
"IsOnline": article.IsOnline,
|
||||||
|
"CreatedAt": article.CreatedAt.Format("2006-01-02 15:04:05"),
|
||||||
|
"UpdatedAt": article.UpdatedAt.Format("2006-01-02 15:04:05"),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
navbarTmpl, _ := api.Container.GetPageUseCase.GetPage("componentNavbar", nil)
|
||||||
|
articlesTmpl, _ := api.Container.GetPageUseCase.GetPage("articles", map[string]any{
|
||||||
|
"Navbar": template.HTML(navbarTmpl),
|
||||||
|
"Head": headTmpl,
|
||||||
|
"Articles": formattedArticles,
|
||||||
|
})
|
||||||
|
|
||||||
|
_, _ = w.Write(articlesTmpl)
|
||||||
|
}
|
||||||
@@ -140,20 +140,20 @@ func NewPageRouter() http.Handler {
|
|||||||
|
|
||||||
r.Get("/home", GetHomePage)
|
r.Get("/home", GetHomePage)
|
||||||
|
|
||||||
r.Get("/post", GetPostsPage)
|
r.Get("/article", GetArticlesPage)
|
||||||
|
|
||||||
r.Get("/post/{id}/edit", GetPostEditPage)
|
r.Get("/article/{id}/edit", GetArticleEditPage)
|
||||||
r.Post("/post/{id}/edit", PostPostEditPage)
|
r.Post("/article/{id}/edit", PostArticleEditPage)
|
||||||
|
|
||||||
r.Get("/post/{id}/delete", GetPostDeletePage)
|
r.Get("/article/{id}/delete", GetArticleDeletePage)
|
||||||
|
|
||||||
r.Get("/post/create", GetPostCreatePage)
|
r.Get("/article/create", GetArticleCreatePage)
|
||||||
r.Post("/post/create", PostPostCreatePage)
|
r.Post("/article/create", PostArticleCreatePage)
|
||||||
|
|
||||||
r.Post("/post/{id}/image/create", image.PostImage)
|
r.Post("/article/{id}/image/create", image.PostImage)
|
||||||
|
|
||||||
r.Get("/post/{id}/publish", GetPostPublishPage)
|
r.Get("/article/{id}/publish", GetArticlePublishPage)
|
||||||
r.Get("/post/{id}/unpublish", GetPostUnpublishPage)
|
r.Get("/article/{id}/unpublish", GetArticleUnpublishPage)
|
||||||
|
|
||||||
r.Get("/integration", GetPageIntegration)
|
r.Get("/integration", GetPageIntegration)
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,58 +0,0 @@
|
|||||||
package pages
|
|
||||||
|
|
||||||
import (
|
|
||||||
"GoCMS/api"
|
|
||||||
"GoCMS/useCases"
|
|
||||||
"html/template"
|
|
||||||
"net/http"
|
|
||||||
"regexp"
|
|
||||||
"strconv"
|
|
||||||
)
|
|
||||||
|
|
||||||
type PostCreatePageError struct {
|
|
||||||
IsError bool
|
|
||||||
Message string
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetPostCreatePageTemplate(postName string, errorMessage string) ([]byte, error) {
|
|
||||||
navbarTmpl, _ := api.Container.GetPageUseCase.GetPage("componentNavbar", nil)
|
|
||||||
return api.Container.GetPageUseCase.GetPage("postCreate", map[string]any{
|
|
||||||
"Navbar": template.HTML(navbarTmpl),
|
|
||||||
"Head": headTmpl,
|
|
||||||
"PageError": PostCreatePageError{
|
|
||||||
IsError: errorMessage != "",
|
|
||||||
Message: errorMessage,
|
|
||||||
},
|
|
||||||
"Name": postName,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func PostPostCreatePage(w http.ResponseWriter, r *http.Request) {
|
|
||||||
_ = r.ParseForm()
|
|
||||||
|
|
||||||
postName := r.FormValue("name")
|
|
||||||
pattern := regexp.MustCompile("^[a-zA-Z0-9À-ÖØ-öø-ÿĀ-ſḀ-ỿ ]{3,50}$")
|
|
||||||
|
|
||||||
if !pattern.MatchString(postName) {
|
|
||||||
postsTmpl, _ := GetPostCreatePageTemplate(postName, "Name should be alphanumeric, and between 3 and 50 characters.")
|
|
||||||
_, _ = w.Write(postsTmpl)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
post, err := api.Container.CreatePostUseCase.CreatePost(useCases.CreatePostCommand{
|
|
||||||
Title: postName,
|
|
||||||
Body: "",
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
postsTmpl, _ := GetPostCreatePageTemplate(postName, "Something went wrong when creating the post, please contact admin.")
|
|
||||||
_, _ = w.Write(postsTmpl)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
http.Redirect(w, r, "/post/"+strconv.Itoa(int(post.ID))+"/edit", http.StatusSeeOther)
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetPostCreatePage(w http.ResponseWriter, _ *http.Request) {
|
|
||||||
postsTmpl, _ := GetPostCreatePageTemplate("", "")
|
|
||||||
_, _ = w.Write(postsTmpl)
|
|
||||||
}
|
|
||||||
@@ -1,75 +0,0 @@
|
|||||||
package pages
|
|
||||||
|
|
||||||
import (
|
|
||||||
"GoCMS/api"
|
|
||||||
"GoCMS/domain/post"
|
|
||||||
"html/template"
|
|
||||||
"net/http"
|
|
||||||
"os"
|
|
||||||
"strconv"
|
|
||||||
|
|
||||||
"github.com/go-chi/chi/v5"
|
|
||||||
)
|
|
||||||
|
|
||||||
type PostEditPageAlert struct {
|
|
||||||
IsError bool
|
|
||||||
Message string
|
|
||||||
}
|
|
||||||
|
|
||||||
func getPostEditPageTemplate(post post.Post, alert PostEditPageAlert) []byte {
|
|
||||||
navbarTmpl, _ := api.Container.GetPageUseCase.GetPage("componentNavbar", nil)
|
|
||||||
postTmpl, _ := api.Container.GetPageUseCase.GetPage("postEdit", map[string]any{
|
|
||||||
"Navbar": template.HTML(navbarTmpl),
|
|
||||||
"Head": headTmpl,
|
|
||||||
"Post": post,
|
|
||||||
"Alert": alert,
|
|
||||||
"Secured": os.Getenv("ENVIRONMENT") == "production",
|
|
||||||
})
|
|
||||||
return postTmpl
|
|
||||||
}
|
|
||||||
|
|
||||||
func PostPostEditPage(w http.ResponseWriter, r *http.Request) {
|
|
||||||
postID := chi.URLParam(r, "id")
|
|
||||||
postIDint, err := strconv.Atoi(postID)
|
|
||||||
if err != nil {
|
|
||||||
_, _ = w.Write(getPostEditPageTemplate(post.Post{}, PostEditPageAlert{
|
|
||||||
IsError: true,
|
|
||||||
Message: "Could not find the requested post.",
|
|
||||||
}))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
_ = r.ParseForm()
|
|
||||||
postBody := r.FormValue("postBody")
|
|
||||||
|
|
||||||
getPost, _ := api.Container.GetPostUseCase.GetPost(uint32(postIDint))
|
|
||||||
updatedPost, err := api.Container.UpdatePostUseCase.UpdateBody(getPost.ID, postBody)
|
|
||||||
if err != nil {
|
|
||||||
_, _ = w.Write(getPostEditPageTemplate(post.Post{Body: postBody}, PostEditPageAlert{
|
|
||||||
IsError: true,
|
|
||||||
Message: "Could not save the post: " + err.Error(),
|
|
||||||
}))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
_, _ = w.Write(getPostEditPageTemplate(updatedPost, PostEditPageAlert{
|
|
||||||
IsError: false,
|
|
||||||
Message: "Post successfully edited!",
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetPostEditPage(w http.ResponseWriter, r *http.Request) {
|
|
||||||
postID := chi.URLParam(r, "id")
|
|
||||||
postIDint, err := strconv.Atoi(postID)
|
|
||||||
if err != nil {
|
|
||||||
w.WriteHeader(http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
getPost, _ := api.Container.GetPostUseCase.GetPost(uint32(postIDint))
|
|
||||||
|
|
||||||
_, _ = w.Write(getPostEditPageTemplate(getPost, PostEditPageAlert{
|
|
||||||
IsError: false,
|
|
||||||
Message: "",
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
package pages
|
|
||||||
|
|
||||||
import (
|
|
||||||
"GoCMS/api"
|
|
||||||
"errors"
|
|
||||||
"net/http"
|
|
||||||
"strconv"
|
|
||||||
|
|
||||||
"github.com/go-chi/chi/v5"
|
|
||||||
)
|
|
||||||
|
|
||||||
func updateIsOnline(postId string, isOnline bool) (error, int) {
|
|
||||||
postIdInt, err := strconv.Atoi(postId)
|
|
||||||
if err != nil {
|
|
||||||
return errors.New("the server expects the ID to be in the format of an unsigned 32-bit integer (uint32)"), http.StatusBadRequest
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = api.Container.UpdatePostUseCase.UpdateIsOnline(uint32(postIdInt), isOnline)
|
|
||||||
if err != nil {
|
|
||||||
return errors.New("the requested resource, identified by its unique ID, could not be found on the server"), http.StatusNotFound
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, http.StatusOK
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetPostUnpublishPage(w http.ResponseWriter, r *http.Request) {
|
|
||||||
postId := chi.URLParam(r, "id")
|
|
||||||
err, statusCode := updateIsOnline(postId, false)
|
|
||||||
if err != nil {
|
|
||||||
http.Error(w, err.Error(), statusCode)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
http.Redirect(w, r, "/post", http.StatusSeeOther)
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetPostPublishPage(w http.ResponseWriter, r *http.Request) {
|
|
||||||
postId := chi.URLParam(r, "id")
|
|
||||||
err, statusCode := updateIsOnline(postId, true)
|
|
||||||
if err != nil {
|
|
||||||
http.Error(w, err.Error(), statusCode)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
http.Redirect(w, r, "/post", http.StatusSeeOther)
|
|
||||||
}
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
package pages
|
|
||||||
|
|
||||||
import (
|
|
||||||
"GoCMS/api"
|
|
||||||
"html/template"
|
|
||||||
"net/http"
|
|
||||||
)
|
|
||||||
|
|
||||||
func GetPostsPage(w http.ResponseWriter, _ *http.Request) {
|
|
||||||
posts := api.Container.ListPostsUseCase.ListPosts()
|
|
||||||
|
|
||||||
var formattedPosts []map[string]any
|
|
||||||
for _, post := range posts {
|
|
||||||
formattedPosts = append(formattedPosts, map[string]any{
|
|
||||||
"ID": post.ID,
|
|
||||||
"Title": post.Title,
|
|
||||||
"IsOnline": post.IsOnline,
|
|
||||||
"CreatedAt": post.CreatedAt.Format("2006-01-02 15:04:05"),
|
|
||||||
"UpdatedAt": post.UpdatedAt.Format("2006-01-02 15:04:05"),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
navbarTmpl, _ := api.Container.GetPageUseCase.GetPage("componentNavbar", nil)
|
|
||||||
postsTmpl, _ := api.Container.GetPageUseCase.GetPage("posts", map[string]any{
|
|
||||||
"Navbar": template.HTML(navbarTmpl),
|
|
||||||
"Head": headTmpl,
|
|
||||||
"Posts": formattedPosts,
|
|
||||||
})
|
|
||||||
|
|
||||||
_, _ = w.Write(postsTmpl)
|
|
||||||
}
|
|
||||||
@@ -1,99 +0,0 @@
|
|||||||
package post
|
|
||||||
|
|
||||||
import (
|
|
||||||
"GoCMS/api"
|
|
||||||
"GoCMS/api/controllers/auth"
|
|
||||||
"GoCMS/domain/post"
|
|
||||||
"GoCMS/useCases"
|
|
||||||
"encoding/json"
|
|
||||||
"net/http"
|
|
||||||
"strconv"
|
|
||||||
|
|
||||||
"github.com/go-chi/chi/v5"
|
|
||||||
)
|
|
||||||
|
|
||||||
type PostPost struct {
|
|
||||||
Title string `json:"title" validate:"required,min=3,max=50"`
|
|
||||||
Body string `json:"body" validate:"required,max=10000"`
|
|
||||||
}
|
|
||||||
|
|
||||||
const IdUint32ErrorMessage = "The server expects the ID to be in the format of an unsigned 32-bit integer (uint32)."
|
|
||||||
|
|
||||||
func getPost(w http.ResponseWriter, r *http.Request) {
|
|
||||||
id, err := strconv.ParseUint(chi.URLParam(r, "id"), 10, 32)
|
|
||||||
if err != nil {
|
|
||||||
http.Error(w, IdUint32ErrorMessage, http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
localPost, err := api.Container.GetPostUseCase.GetPost(uint32(id))
|
|
||||||
if err != nil || !localPost.IsOnline {
|
|
||||||
http.Error(w, "The requested resource, identified by its unique ID, could not be found on the server.", http.StatusNotFound)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
postJson, _ := json.Marshal(localPost)
|
|
||||||
_, _ = w.Write(postJson)
|
|
||||||
}
|
|
||||||
|
|
||||||
func postPost(w http.ResponseWriter, r *http.Request) {
|
|
||||||
var localPost PostPost
|
|
||||||
err := json.NewDecoder(r.Body).Decode(&localPost)
|
|
||||||
if err != nil {
|
|
||||||
http.Error(w, auth.BodyErrorMessage, http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
err = api.Validate.Struct(localPost)
|
|
||||||
if err != nil {
|
|
||||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
createdPost, err := api.Container.CreatePostUseCase.CreatePost(useCases.CreatePostCommand{
|
|
||||||
Title: localPost.Title,
|
|
||||||
Body: localPost.Body,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
postJson, _ := json.Marshal(createdPost)
|
|
||||||
|
|
||||||
_, _ = w.Write(postJson)
|
|
||||||
}
|
|
||||||
|
|
||||||
func listPosts(w http.ResponseWriter, _ *http.Request) {
|
|
||||||
posts := api.Container.ListPostsUseCase.ListPosts()
|
|
||||||
onlinePosts := make([]post.Post, 0)
|
|
||||||
for _, localPost := range posts {
|
|
||||||
if localPost.IsOnline {
|
|
||||||
onlinePosts = append(onlinePosts, localPost)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
postsJson, _ := json.Marshal(onlinePosts)
|
|
||||||
_, _ = w.Write(postsJson)
|
|
||||||
}
|
|
||||||
|
|
||||||
func deletePost(w http.ResponseWriter, r *http.Request) {
|
|
||||||
id, err := strconv.ParseUint(chi.URLParam(r, "id"), 10, 32)
|
|
||||||
if err != nil {
|
|
||||||
http.Error(w, IdUint32ErrorMessage, http.StatusBadRequest)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = api.Container.DeletePostUseCase.DeletePost(uint32(id))
|
|
||||||
if err != nil {
|
|
||||||
http.Error(w, err.Error(), http.StatusNotFound)
|
|
||||||
}
|
|
||||||
|
|
||||||
_, _ = w.Write([]byte("post deleted"))
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewPostRouter() http.Handler {
|
|
||||||
r := chi.NewRouter()
|
|
||||||
r.Get("/{id}", getPost)
|
|
||||||
r.Post("/", postPost)
|
|
||||||
r.Get("/", listPosts)
|
|
||||||
r.Delete("/{id}", deletePost)
|
|
||||||
return r
|
|
||||||
}
|
|
||||||
+39
-39
@@ -13,28 +13,28 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type UseCases struct {
|
type UseCases struct {
|
||||||
CreatePostUseCase *useCases.CreatePostUseCase
|
CreateArticleUseCase *useCases.CreateArticleUseCase
|
||||||
GetPostUseCase *useCases.GetPostUseCase
|
GetArticleUseCase *useCases.GetArticleUseCase
|
||||||
ListPostsUseCase *useCases.ListPostsUseCase
|
ListArticlesUseCase *useCases.ListArticlesUseCase
|
||||||
UpdatePostUseCase *useCases.UpdatePostUseCase
|
UpdateArticleUseCase *useCases.UpdateArticleUseCase
|
||||||
DeletePostUseCase *useCases.DeletePostUseCase
|
DeleteArticleUseCase *useCases.DeleteArticleUseCase
|
||||||
GetUserUseCase *useCases.GetUserUseCase
|
GetUserUseCase *useCases.GetUserUseCase
|
||||||
CreateUserUseCase *useCases.CreateUserUseCase
|
CreateUserUseCase *useCases.CreateUserUseCase
|
||||||
UpdateUserUseCase *useCases.UpdateUserUseCase
|
UpdateUserUseCase *useCases.UpdateUserUseCase
|
||||||
DeleteUserUseCase *useCases.DeleteUserUseCase
|
DeleteUserUseCase *useCases.DeleteUserUseCase
|
||||||
ListUsersUseCase *useCases.ListUsersUseCase
|
ListUsersUseCase *useCases.ListUsersUseCase
|
||||||
GetPageUseCase *useCases.GetPageUseCase
|
GetPageUseCase *useCases.GetPageUseCase
|
||||||
SendMailUseCase *useCases.SendMailUseCase
|
SendMailUseCase *useCases.SendMailUseCase
|
||||||
CreateImageUseCase *useCases.CreateImageUseCase
|
CreateImageUseCase *useCases.CreateImageUseCase
|
||||||
DeleteImageUseCase *useCases.DeleteImageUseCase
|
DeleteImageUseCase *useCases.DeleteImageUseCase
|
||||||
}
|
}
|
||||||
|
|
||||||
type Repositories struct {
|
type Repositories struct {
|
||||||
PostRepo domainGateways.IPostRepository
|
ArticleRepo domainGateways.IArticleRepository
|
||||||
UserRepo domainGateways.IUserRepository
|
UserRepo domainGateways.IUserRepository
|
||||||
ImageRepo domainGateways.IImageRepository
|
ImageRepo domainGateways.IImageRepository
|
||||||
MailRepo domainGateways.IMailRepository
|
MailRepo domainGateways.IMailRepository
|
||||||
PageRepo domainGateways.IPageRepository
|
PageRepo domainGateways.IPageRepository
|
||||||
}
|
}
|
||||||
|
|
||||||
var Container *UseCases
|
var Container *UseCases
|
||||||
@@ -49,7 +49,7 @@ func getDb() *gorm.DB {
|
|||||||
panic("Unable to open the database: " + err.Error())
|
panic("Unable to open the database: " + err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := db.AutoMigrate(&models.Post{}, &models.User{}); err != nil {
|
if err := db.AutoMigrate(&models.Article{}, &models.User{}); err != nil {
|
||||||
panic("Failed to migrate database: " + err.Error())
|
panic("Failed to migrate database: " + err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -58,30 +58,30 @@ func getDb() *gorm.DB {
|
|||||||
|
|
||||||
func initRepositories(db *gorm.DB) *Repositories {
|
func initRepositories(db *gorm.DB) *Repositories {
|
||||||
return &Repositories{
|
return &Repositories{
|
||||||
PostRepo: gateways.NewPostRepository(db),
|
ArticleRepo: gateways.NewArticleRepository(db),
|
||||||
UserRepo: gateways.NewUserRepository(db),
|
UserRepo: gateways.NewUserRepository(db),
|
||||||
ImageRepo: gateways.NewImageRepository(db),
|
ImageRepo: gateways.NewImageRepository(db),
|
||||||
MailRepo: gateways.NewMailRepository(),
|
MailRepo: gateways.NewMailRepository(),
|
||||||
PageRepo: gateways.NewPageRepository(),
|
PageRepo: gateways.NewPageRepository(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func initUseCases(repos *Repositories) *UseCases {
|
func initUseCases(repos *Repositories) *UseCases {
|
||||||
return &UseCases{
|
return &UseCases{
|
||||||
CreatePostUseCase: useCases.NewCreatePostUseCase(repos.PostRepo),
|
CreateArticleUseCase: useCases.NewCreateArticleUseCase(repos.ArticleRepo),
|
||||||
GetPostUseCase: useCases.NewGetPostUseCase(repos.PostRepo),
|
GetArticleUseCase: useCases.NewGetArticleUseCase(repos.ArticleRepo),
|
||||||
ListPostsUseCase: useCases.NewListPostsUseCase(repos.PostRepo),
|
ListArticlesUseCase: useCases.NewListArticlesUseCase(repos.ArticleRepo),
|
||||||
UpdatePostUseCase: useCases.NewUpdatePostUseCase(repos.PostRepo),
|
UpdateArticleUseCase: useCases.NewUpdateArticleUseCase(repos.ArticleRepo),
|
||||||
DeletePostUseCase: useCases.NewDeletePostUseCase(repos.PostRepo),
|
DeleteArticleUseCase: useCases.NewDeleteArticleUseCase(repos.ArticleRepo),
|
||||||
GetUserUseCase: useCases.NewGetUserUseCase(repos.UserRepo),
|
GetUserUseCase: useCases.NewGetUserUseCase(repos.UserRepo),
|
||||||
CreateUserUseCase: useCases.NewCreateUserUseCase(repos.UserRepo),
|
CreateUserUseCase: useCases.NewCreateUserUseCase(repos.UserRepo),
|
||||||
UpdateUserUseCase: useCases.NewUpdateUserUseCase(repos.UserRepo),
|
UpdateUserUseCase: useCases.NewUpdateUserUseCase(repos.UserRepo),
|
||||||
DeleteUserUseCase: useCases.NewDeleteUserUseCase(repos.UserRepo),
|
DeleteUserUseCase: useCases.NewDeleteUserUseCase(repos.UserRepo),
|
||||||
ListUsersUseCase: useCases.NewListUsersUseCase(repos.UserRepo),
|
ListUsersUseCase: useCases.NewListUsersUseCase(repos.UserRepo),
|
||||||
GetPageUseCase: useCases.NewGetPageUseCase(repos.PageRepo),
|
GetPageUseCase: useCases.NewGetPageUseCase(repos.PageRepo),
|
||||||
SendMailUseCase: useCases.NewSendMailUseCase(repos.MailRepo),
|
SendMailUseCase: useCases.NewSendMailUseCase(repos.MailRepo),
|
||||||
CreateImageUseCase: useCases.NewCreateImageUseCase(repos.ImageRepo),
|
CreateImageUseCase: useCases.NewCreateImageUseCase(repos.ImageRepo),
|
||||||
DeleteImageUseCase: useCases.NewDeleteImageUseCase(repos.ImageRepo),
|
DeleteImageUseCase: useCases.NewDeleteImageUseCase(repos.ImageRepo),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
package post
|
package article
|
||||||
|
|
||||||
import (
|
import (
|
||||||
entity "GoCMS/adapters/secondary/gateways/models"
|
entity "GoCMS/adapters/secondary/gateways/models"
|
||||||
@@ -6,7 +6,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Post struct {
|
type Article struct {
|
||||||
ID uint32 `json:"id"`
|
ID uint32 `json:"id"`
|
||||||
Title string `json:"title"`
|
Title string `json:"title"`
|
||||||
Body string `json:"body"`
|
Body string `json:"body"`
|
||||||
@@ -19,8 +19,8 @@ type Post struct {
|
|||||||
func FromApi(
|
func FromApi(
|
||||||
title string,
|
title string,
|
||||||
body string,
|
body string,
|
||||||
) Post {
|
) Article {
|
||||||
return Post{
|
return Article{
|
||||||
Title: title,
|
Title: title,
|
||||||
Body: body,
|
Body: body,
|
||||||
}
|
}
|
||||||
@@ -34,19 +34,19 @@ func FromDb(
|
|||||||
isOnline bool,
|
isOnline bool,
|
||||||
createdAt time.Time,
|
createdAt time.Time,
|
||||||
updatedAt time.Time,
|
updatedAt time.Time,
|
||||||
) Post {
|
) Article {
|
||||||
domainImages := make([]*domain.Image, len(images))
|
domainImages := make([]*domain.Image, len(images))
|
||||||
for i, img := range images {
|
for i, img := range images {
|
||||||
domainImage := domain.FromDB(
|
domainImage := domain.FromDB(
|
||||||
img.ID,
|
img.ID,
|
||||||
img.Path,
|
img.Path,
|
||||||
img.PostID,
|
img.ArticleID,
|
||||||
img.CreatedAt,
|
img.CreatedAt,
|
||||||
img.UpdatedAt,
|
img.UpdatedAt,
|
||||||
)
|
)
|
||||||
domainImages[i] = &domainImage
|
domainImages[i] = &domainImage
|
||||||
}
|
}
|
||||||
return Post{
|
return Article{
|
||||||
ID: id,
|
ID: id,
|
||||||
Title: title,
|
Title: title,
|
||||||
Body: body,
|
Body: body,
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package gateways
|
||||||
|
|
||||||
|
import (
|
||||||
|
"GoCMS/domain/article"
|
||||||
|
)
|
||||||
|
|
||||||
|
type IArticleRepository interface {
|
||||||
|
Get(id uint32) (article.Article, error)
|
||||||
|
GetByName(name string) (article.Article, error)
|
||||||
|
GetAll() []article.Article
|
||||||
|
Create(post article.Article) (article.Article, error)
|
||||||
|
UpdateBody(id uint32, body string) (article.Article, error)
|
||||||
|
UpdateIsOnline(id uint32, isOnline bool) (article.Article, error)
|
||||||
|
Delete(id uint32) error
|
||||||
|
AddImage(postId uint32, imageId uint32) error
|
||||||
|
}
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
package gateways
|
|
||||||
|
|
||||||
import (
|
|
||||||
"GoCMS/domain/post"
|
|
||||||
)
|
|
||||||
|
|
||||||
type IPostRepository interface {
|
|
||||||
Get(id uint32) (post.Post, error)
|
|
||||||
GetByName(name string) (post.Post, error)
|
|
||||||
GetAll() []post.Post
|
|
||||||
Create(post post.Post) (post.Post, error)
|
|
||||||
UpdateBody(id uint32, body string) (post.Post, error)
|
|
||||||
UpdateIsOnline(id uint32, isOnline bool) (post.Post, error)
|
|
||||||
Delete(id uint32) error
|
|
||||||
AddImage(postId uint32, imageId uint32) error
|
|
||||||
}
|
|
||||||
@@ -7,16 +7,16 @@ import (
|
|||||||
type Image struct {
|
type Image struct {
|
||||||
ID uint32 `json:"id"`
|
ID uint32 `json:"id"`
|
||||||
Path string `json:"path"`
|
Path string `json:"path"`
|
||||||
PostID uint32 `json:"post_id"`
|
ArticleID uint32 `json:"article_id"`
|
||||||
CreatedAt time.Time `json:"created_at"`
|
CreatedAt time.Time `json:"created_at"`
|
||||||
UpdatedAt time.Time `json:"updated_at"`
|
UpdatedAt time.Time `json:"updated_at"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func FromDB(id uint32, path string, postId uint32, createdAt time.Time, updatedAt time.Time) Image {
|
func FromDB(id uint32, path string, articleId uint32, createdAt time.Time, updatedAt time.Time) Image {
|
||||||
return Image{
|
return Image{
|
||||||
ID: id,
|
ID: id,
|
||||||
Path: path,
|
Path: path,
|
||||||
PostID: postId,
|
ArticleID: articleId,
|
||||||
CreatedAt: createdAt,
|
CreatedAt: createdAt,
|
||||||
UpdatedAt: updatedAt,
|
UpdatedAt: updatedAt,
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-2
@@ -1,9 +1,9 @@
|
|||||||
package route
|
package route
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"GoCMS/api/controllers/article"
|
||||||
"GoCMS/api/controllers/auth"
|
"GoCMS/api/controllers/auth"
|
||||||
"GoCMS/api/controllers/pages"
|
"GoCMS/api/controllers/pages"
|
||||||
"GoCMS/api/controllers/post"
|
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
@@ -49,7 +49,7 @@ func InitBackendRoutes() *chi.Mux {
|
|||||||
r.Group(func(r chi.Router) {
|
r.Group(func(r chi.Router) {
|
||||||
r.Use(jwtauth.Verifier(auth.Token))
|
r.Use(jwtauth.Verifier(auth.Token))
|
||||||
r.Use(jwtauth.Authenticator(auth.Token))
|
r.Use(jwtauth.Authenticator(auth.Token))
|
||||||
r.Mount("/post", post.NewPostRouter())
|
r.Mount("/article", article.NewArticleRouter())
|
||||||
})
|
})
|
||||||
r.Mount("/auth", auth.NewAuthRouter())
|
r.Mount("/auth", auth.NewAuthRouter())
|
||||||
|
|
||||||
|
|||||||
@@ -1,163 +0,0 @@
|
|||||||
package test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"GoCMS/domain/post"
|
|
||||||
"bytes"
|
|
||||||
"encoding/json"
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"io"
|
|
||||||
"net/http"
|
|
||||||
"strconv"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
var TestCreatePostSuccess = func(t *testing.T) {
|
|
||||||
jsonBody, err := json.Marshal(map[string]string{
|
|
||||||
"title": "Test Title",
|
|
||||||
"body": "Test Body",
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
r, _ := ApiRequest("POST", "/post", bytes.NewBuffer(jsonBody))
|
|
||||||
|
|
||||||
var response post.Post
|
|
||||||
bd, err := io.ReadAll(r.Body)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = json.Unmarshal(bd, &response)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
assert.Equal(t, http.StatusOK, r.StatusCode)
|
|
||||||
assert.Equal(t, "Test Title", response.Title)
|
|
||||||
assert.Equal(t, "Test Body", response.Body)
|
|
||||||
}
|
|
||||||
|
|
||||||
var TestCreatePostFailTitleMissing = func(t *testing.T) {
|
|
||||||
jsonBody, err := json.Marshal(map[string]string{
|
|
||||||
"body": "Test Body",
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
r, _ := ApiRequest("POST", "/post", bytes.NewBuffer(jsonBody))
|
|
||||||
|
|
||||||
assert.Equal(t, http.StatusBadRequest, r.StatusCode)
|
|
||||||
}
|
|
||||||
|
|
||||||
var TestCreatePostTitleTooShort = func(t *testing.T) {
|
|
||||||
jsonBody, err := json.Marshal(map[string]string{
|
|
||||||
"title": "Te",
|
|
||||||
"body": "Test Body",
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
r, _ := ApiRequest("POST", "/post", bytes.NewBuffer(jsonBody))
|
|
||||||
|
|
||||||
assert.Equal(t, http.StatusBadRequest, r.StatusCode)
|
|
||||||
}
|
|
||||||
|
|
||||||
var TestGetPostSuccess = func(t *testing.T) {
|
|
||||||
var createdPost post.Post
|
|
||||||
var postToCreate = post.Post{
|
|
||||||
Title: "Test Title",
|
|
||||||
Body: "Test Body",
|
|
||||||
}
|
|
||||||
db := GetDb()
|
|
||||||
db.Create(&postToCreate).Scan(&createdPost)
|
|
||||||
|
|
||||||
r, _ := ApiRequest("GET", "/post/"+strconv.Itoa(int(createdPost.ID)), nil)
|
|
||||||
|
|
||||||
var response post.Post
|
|
||||||
bd, err := io.ReadAll(r.Body)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = json.Unmarshal(bd, &response)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
assert.Equal(t, http.StatusOK, r.StatusCode)
|
|
||||||
assert.Equal(t, createdPost.ID, response.ID)
|
|
||||||
assert.Equal(t, createdPost.Title, response.Title)
|
|
||||||
assert.Equal(t, createdPost.Body, response.Body)
|
|
||||||
}
|
|
||||||
|
|
||||||
var TestGetAllPostsSuccess = func(t *testing.T) {
|
|
||||||
var createdPost post.Post
|
|
||||||
var postToCreate = post.Post{
|
|
||||||
Title: "Test Title",
|
|
||||||
Body: "Test Body",
|
|
||||||
}
|
|
||||||
db := GetDb()
|
|
||||||
db.Create(&postToCreate).Scan(&createdPost)
|
|
||||||
|
|
||||||
r, _ := ApiRequest("GET", "/post", nil)
|
|
||||||
|
|
||||||
var response []post.Post
|
|
||||||
bd, err := io.ReadAll(r.Body)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = json.Unmarshal(bd, &response)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
assert.Equal(t, http.StatusOK, r.StatusCode)
|
|
||||||
assert.Equal(t, createdPost.Title, response[0].Title)
|
|
||||||
assert.Equal(t, createdPost.Body, response[0].Body)
|
|
||||||
}
|
|
||||||
|
|
||||||
var TestDeletePostSuccess = func(t *testing.T) {
|
|
||||||
var createdPost post.Post
|
|
||||||
var postToCreate = post.Post{
|
|
||||||
Title: "Test Title",
|
|
||||||
Body: "Test Body",
|
|
||||||
}
|
|
||||||
db := GetDb()
|
|
||||||
db.Create(&postToCreate).Scan(&createdPost)
|
|
||||||
|
|
||||||
r, _ := ApiRequest("DELETE", "/post/"+strconv.Itoa(int(createdPost.ID)), nil)
|
|
||||||
|
|
||||||
assert.Equal(t, http.StatusOK, r.StatusCode)
|
|
||||||
}
|
|
||||||
|
|
||||||
var TestPostCreate = func(t *testing.T) {
|
|
||||||
t.Run("Should return a post with the given title and body", TestCreatePostSuccess)
|
|
||||||
t.Run("Should return an error if the title is missing", TestCreatePostFailTitleMissing)
|
|
||||||
t.Run("Should return an error if the title is too short", TestCreatePostTitleTooShort)
|
|
||||||
}
|
|
||||||
|
|
||||||
var TestPostGet = func(t *testing.T) {
|
|
||||||
t.Run("Should return a post with the given id", TestGetPostSuccess)
|
|
||||||
}
|
|
||||||
|
|
||||||
var TestPostGetAll = func(t *testing.T) {
|
|
||||||
t.Run("Should return all posts", TestGetAllPostsSuccess)
|
|
||||||
}
|
|
||||||
|
|
||||||
var TestPostDelete = func(t *testing.T) {
|
|
||||||
t.Run("Should return success", TestDeletePostSuccess)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestPost(t *testing.T) {
|
|
||||||
StartServerIfNotAlready()
|
|
||||||
WaitForServer()
|
|
||||||
|
|
||||||
t.Run("Create", TestPostCreate)
|
|
||||||
t.Run("Get", TestPostGet)
|
|
||||||
t.Run("GetAll", TestPostGetAll)
|
|
||||||
t.Run("Delete", TestPostDelete)
|
|
||||||
}
|
|
||||||
-105
@@ -1,105 +0,0 @@
|
|||||||
package test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"GoCMS/adapters/secondary/gateways/models"
|
|
||||||
"GoCMS/api/controllers/auth"
|
|
||||||
"GoCMS/main/server"
|
|
||||||
"io"
|
|
||||||
"net/http"
|
|
||||||
"os"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/glebarez/sqlite"
|
|
||||||
"gorm.io/gorm"
|
|
||||||
)
|
|
||||||
|
|
||||||
const testDbFile = "test.db"
|
|
||||||
|
|
||||||
var ApiUrl string
|
|
||||||
var AuthorizationCookie *http.Cookie
|
|
||||||
var HttpClient = http.Client{}
|
|
||||||
|
|
||||||
func GetDb() *gorm.DB {
|
|
||||||
db, err := gorm.Open(sqlite.Open(testDbFile), &gorm.Config{})
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
_ = db.AutoMigrate(&models.Post{}, &models.User{})
|
|
||||||
return db
|
|
||||||
}
|
|
||||||
|
|
||||||
func StartServerIfNotAlready() {
|
|
||||||
_, err := http.Get(ApiUrl)
|
|
||||||
if err == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
_ = os.Remove(testDbFile)
|
|
||||||
_ = os.Setenv("DB_FILE", testDbFile)
|
|
||||||
go func(url *string) {
|
|
||||||
router := server.InitServer()
|
|
||||||
*url = "http://localhost:" + os.Getenv("PORT") + "/v1"
|
|
||||||
err := server.StartServer(router)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
}(&ApiUrl)
|
|
||||||
}
|
|
||||||
|
|
||||||
func getAuthorizationCookie(userId uint32) *http.Cookie {
|
|
||||||
_, tokenString, err := auth.Token.Encode(map[string]any{"user_id": userId})
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
return &http.Cookie{
|
|
||||||
Name: "jwt",
|
|
||||||
Value: tokenString,
|
|
||||||
Expires: time.Now().Add(24 * time.Hour),
|
|
||||||
Secure: false,
|
|
||||||
HttpOnly: true,
|
|
||||||
Path: "/",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func SetAuthorizationCookieIfNotAlready(r *http.Request) {
|
|
||||||
if AuthorizationCookie != nil {
|
|
||||||
r.AddCookie(AuthorizationCookie)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
db := GetDb()
|
|
||||||
user := models.User{
|
|
||||||
Username: "testuser",
|
|
||||||
Password: "testpassword",
|
|
||||||
Email: "testemail@a.com",
|
|
||||||
}
|
|
||||||
var createdUser models.User
|
|
||||||
db.Create(&user).Scan(&createdUser)
|
|
||||||
AuthorizationCookie = getAuthorizationCookie(createdUser.ID)
|
|
||||||
r.AddCookie(AuthorizationCookie)
|
|
||||||
}
|
|
||||||
|
|
||||||
func WaitForServer() {
|
|
||||||
for {
|
|
||||||
time.Sleep(100 * time.Millisecond)
|
|
||||||
if ApiUrl == "" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
_, err := http.Get(ApiUrl)
|
|
||||||
if err == nil {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func ApiRequest(method string, route string, body io.Reader) (*http.Response, error) {
|
|
||||||
request, err := http.NewRequest(method, ApiUrl+route, body)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
SetAuthorizationCookieIfNotAlready(request)
|
|
||||||
|
|
||||||
response, err := HttpClient.Do(request)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return response, nil
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
package useCases
|
||||||
|
|
||||||
|
import (
|
||||||
|
"GoCMS/domain/article"
|
||||||
|
"GoCMS/domain/gateways"
|
||||||
|
)
|
||||||
|
|
||||||
|
type CreateArticleUseCase struct {
|
||||||
|
articleRepository gateways.IArticleRepository
|
||||||
|
}
|
||||||
|
|
||||||
|
type CreateArticleCommand struct {
|
||||||
|
Title string
|
||||||
|
Body string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewCreateArticleUseCase(articleRepository gateways.IArticleRepository) *CreateArticleUseCase {
|
||||||
|
return &CreateArticleUseCase{articleRepository}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *CreateArticleUseCase) CreateArticle(createArticle CreateArticleCommand) (article.Article, error) {
|
||||||
|
return g.articleRepository.Create(article.FromApi(
|
||||||
|
createArticle.Title,
|
||||||
|
createArticle.Body,
|
||||||
|
))
|
||||||
|
}
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
package useCases
|
|
||||||
|
|
||||||
import (
|
|
||||||
"GoCMS/domain/gateways"
|
|
||||||
"GoCMS/domain/post"
|
|
||||||
)
|
|
||||||
|
|
||||||
type CreatePostUseCase struct {
|
|
||||||
postRepository gateways.IPostRepository
|
|
||||||
}
|
|
||||||
|
|
||||||
type CreatePostCommand struct {
|
|
||||||
Title string
|
|
||||||
Body string
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewCreatePostUseCase(postRepository gateways.IPostRepository) *CreatePostUseCase {
|
|
||||||
return &CreatePostUseCase{postRepository}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (g *CreatePostUseCase) CreatePost(createPost CreatePostCommand) (post.Post, error) {
|
|
||||||
return g.postRepository.Create(post.FromApi(
|
|
||||||
createPost.Title,
|
|
||||||
createPost.Body,
|
|
||||||
))
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
package useCases
|
||||||
|
|
||||||
|
import (
|
||||||
|
"GoCMS/domain/gateways"
|
||||||
|
)
|
||||||
|
|
||||||
|
type DeleteArticleUseCase struct {
|
||||||
|
articleRepository gateways.IArticleRepository
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewDeleteArticleUseCase(articleRepository gateways.IArticleRepository) *DeleteArticleUseCase {
|
||||||
|
return &DeleteArticleUseCase{articleRepository}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *DeleteArticleUseCase) DeleteArticle(userId uint32) error {
|
||||||
|
return g.articleRepository.Delete(userId)
|
||||||
|
}
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
package useCases
|
|
||||||
|
|
||||||
import (
|
|
||||||
"GoCMS/domain/gateways"
|
|
||||||
)
|
|
||||||
|
|
||||||
type DeletePostUseCase struct {
|
|
||||||
postRepository gateways.IPostRepository
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewDeletePostUseCase(postRepository gateways.IPostRepository) *DeletePostUseCase {
|
|
||||||
return &DeletePostUseCase{postRepository}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (g *DeletePostUseCase) DeletePost(userId uint32) error {
|
|
||||||
return g.postRepository.Delete(userId)
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
package useCases
|
||||||
|
|
||||||
|
import (
|
||||||
|
"GoCMS/domain/article"
|
||||||
|
"GoCMS/domain/gateways"
|
||||||
|
)
|
||||||
|
|
||||||
|
type GetArticleUseCase struct {
|
||||||
|
articleRepository gateways.IArticleRepository
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewGetArticleUseCase(articleRepository gateways.IArticleRepository) *GetArticleUseCase {
|
||||||
|
return &GetArticleUseCase{articleRepository}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *GetArticleUseCase) GetArticle(id uint32) (article.Article, error) {
|
||||||
|
return g.articleRepository.Get(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *GetArticleUseCase) GetArticleByName(name string) (article.Article, error) {
|
||||||
|
return g.articleRepository.GetByName(name)
|
||||||
|
}
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
package useCases
|
|
||||||
|
|
||||||
import (
|
|
||||||
"GoCMS/domain/gateways"
|
|
||||||
"GoCMS/domain/post"
|
|
||||||
)
|
|
||||||
|
|
||||||
type GetPostUseCase struct {
|
|
||||||
postRepository gateways.IPostRepository
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewGetPostUseCase(postRepository gateways.IPostRepository) *GetPostUseCase {
|
|
||||||
return &GetPostUseCase{postRepository}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (g *GetPostUseCase) GetPost(id uint32) (post.Post, error) {
|
|
||||||
return g.postRepository.Get(id)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (g *GetPostUseCase) GetPostByName(name string) (post.Post, error) {
|
|
||||||
return g.postRepository.GetByName(name)
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
package useCases
|
||||||
|
|
||||||
|
import (
|
||||||
|
"GoCMS/domain/article"
|
||||||
|
"GoCMS/domain/gateways"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ListArticlesUseCase struct {
|
||||||
|
articleRepository gateways.IArticleRepository
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewListArticlesUseCase(articleRepository gateways.IArticleRepository) *ListArticlesUseCase {
|
||||||
|
return &ListArticlesUseCase{articleRepository}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *ListArticlesUseCase) ListArticles() []article.Article {
|
||||||
|
return g.articleRepository.GetAll()
|
||||||
|
}
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
package useCases
|
|
||||||
|
|
||||||
import (
|
|
||||||
"GoCMS/domain/gateways"
|
|
||||||
"GoCMS/domain/post"
|
|
||||||
)
|
|
||||||
|
|
||||||
type ListPostsUseCase struct {
|
|
||||||
postRepository gateways.IPostRepository
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewListPostsUseCase(postRepository gateways.IPostRepository) *ListPostsUseCase {
|
|
||||||
return &ListPostsUseCase{postRepository}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (g *ListPostsUseCase) ListPosts() []post.Post {
|
|
||||||
return g.postRepository.GetAll()
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
package useCases
|
||||||
|
|
||||||
|
import (
|
||||||
|
"GoCMS/domain/article"
|
||||||
|
"GoCMS/domain/gateways"
|
||||||
|
)
|
||||||
|
|
||||||
|
type UpdateArticleUseCase struct {
|
||||||
|
articleRepository gateways.IArticleRepository
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewUpdateArticleUseCase(articleRepository gateways.IArticleRepository) *UpdateArticleUseCase {
|
||||||
|
return &UpdateArticleUseCase{articleRepository}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *UpdateArticleUseCase) UpdateBody(id uint32, body string) (article.Article, error) {
|
||||||
|
return g.articleRepository.UpdateBody(id, body)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *UpdateArticleUseCase) AddImage(articleId uint32, imageId uint32) error {
|
||||||
|
return g.articleRepository.AddImage(articleId, imageId)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *UpdateArticleUseCase) UpdateIsOnline(id uint32, isOnline bool) (article.Article, error) {
|
||||||
|
return g.articleRepository.UpdateIsOnline(id, isOnline)
|
||||||
|
}
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
package useCases
|
|
||||||
|
|
||||||
import (
|
|
||||||
"GoCMS/domain/gateways"
|
|
||||||
"GoCMS/domain/post"
|
|
||||||
)
|
|
||||||
|
|
||||||
type UpdatePostUseCase struct {
|
|
||||||
postRepository gateways.IPostRepository
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewUpdatePostUseCase(postRepository gateways.IPostRepository) *UpdatePostUseCase {
|
|
||||||
return &UpdatePostUseCase{postRepository}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (g *UpdatePostUseCase) UpdateBody(id uint32, body string) (post.Post, error) {
|
|
||||||
return g.postRepository.UpdateBody(id, body)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (g *UpdatePostUseCase) AddImage(postId uint32, imageId uint32) error {
|
|
||||||
return g.postRepository.AddImage(postId, imageId)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (g *UpdatePostUseCase) UpdateIsOnline(id uint32, isOnline bool) (post.Post, error) {
|
|
||||||
return g.postRepository.UpdateIsOnline(id, isOnline)
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user