12 Commits
88 changed files with 991 additions and 1288 deletions
+1 -1
View File
@@ -4,7 +4,7 @@ tmp_dir = "tmp"
[build] [build]
args_bin = [] args_bin = []
bin = "tmp\\main.exe" bin = "tmp/main.exe"
cmd = "go build -o ./tmp/main.exe ./main" cmd = "go build -o ./tmp/main.exe ./main"
delay = 1000 delay = 1000
exclude_dir = ["assets", "tmp", "vendor", "testdata", "api\\static\\tinymce", "api\\static\\bootstrap-icons", "api\\static\\uploadedImages"] exclude_dir = ["assets", "tmp", "vendor", "testdata", "api\\static\\tinymce", "api\\static\\bootstrap-icons", "api\\static\\uploadedImages"]
+1 -1
View File
@@ -3,7 +3,7 @@ HOST=example.com
PORT=8080 PORT=8080
JWT_SECRET=abc123 JWT_SECRET=abc123
CORS_ALLOWED_ORIGINS=* CORS_ALLOWED_ORIGINS=*
DB_FILE=./gocms.db DB_FILE=./renewcms.db
DOCKER_DB_FOLDER=./data DOCKER_DB_FOLDER=./data
SMTP_EMAIL=a@b.c SMTP_EMAIL=a@b.c
SMTP_PASSWORD=12341234 SMTP_PASSWORD=12341234
+3 -3
View File
@@ -1,4 +1,4 @@
# GoCMS # RenewCMS
## 🚧 This project is under development ! ## 🚧 This project is under development !
@@ -42,11 +42,11 @@ 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 |
| DB_FILE | string | path to the sqlite db file | required, can be at the root but name still required (e.g. `./gocms.db`) ; have to end up with `.db` | | DB_FILE | string | path to the sqlite db file | required, can be at the root but name still required (e.g. `./RenewCMS.db`) ; have to end up with `.db` |
| DOCKER_DB_FOLDER | string | path to the sqlite db file's folder | required with docker, this will basically be the host machine folder (e.g. `./data`) that contains the sqlite db file | | DOCKER_DB_FOLDER | string | path to the sqlite db file's folder | required with docker, this will basically be the host machine folder (e.g. `./data`) that contains the sqlite db file |
| SMTP_EMAIL | string | sender email | required | | SMTP_EMAIL | string | sender email | required |
| SMTP_PASSWORD | string | smtp account password | required | | SMTP_PASSWORD | string | smtp account password | required |
@@ -0,0 +1,140 @@
package gateways
import (
entity "RenewCMS/adapters/secondary/gateways/models"
domain "RenewCMS/domain/article"
"RenewCMS/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{}
@@ -1,9 +1,9 @@
package gateways package gateways
import ( import (
entity "GoCMS/adapters/secondary/gateways/models" entity "RenewCMS/adapters/secondary/gateways/models"
"GoCMS/domain/gateways" "RenewCMS/domain/gateways"
domain "GoCMS/domain/image" domain "RenewCMS/domain/image"
"errors" "errors"
"github.com/google/uuid" "github.com/google/uuid"
"gorm.io/gorm" "gorm.io/gorm"
@@ -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,
) )
@@ -2,11 +2,11 @@
<html lang="en"> <html lang="en">
<body> <body>
<h1> <h1>
<img src="{{.Host}}/static/gocms-favicon-128.png" alt="G" style="width: 2.5rem"/> <img src="{{.Host}}/static/renewcms-favicon-64.png" alt="G" style="width: 2.5rem"/>
GoCMS RenewCMS
</h1> </h1>
<main> <main>
<p>You or someone tried to create a GoCMS admin account with this e-mail address. If you are not responsible for <p>You or someone tried to create a RenewCMS admin account with this e-mail address. If you are not responsible for
this, please do not validate the account creation.</p> this, please do not validate the account creation.</p>
<p><b>If you want to validate the account creation, please <a <p><b>If you want to validate the account creation, please <a
href="{{.Host}}/register/validate?c={{.VerificationCode}}" target="_blank">click HERE</a>.</b> href="{{.Host}}/register/validate?c={{.VerificationCode}}" target="_blank">click HERE</a>.</b>
@@ -2,11 +2,11 @@
<html lang="en"> <html lang="en">
<body> <body>
<h1> <h1>
<img src="{{.Host}}/static/gocms-favicon-128.png" alt="G" style="width: 2.5rem"/> <img src="{{.Host}}/static/renewcms-favicon-64.png" alt="G" style="width: 2.5rem"/>
GoCMS RenewCMS
</h1> </h1>
<main> <main>
<p>You or someone tried to create reset your GoCMS account with this e-mail address. If you are not responsible for <p>You or someone tried to create reset your RenewCMS account with this e-mail address. If you are not responsible for
this, please do not validate the password reset.</p> this, please do not validate the password reset.</p>
<p><b>If you want to reset your password, please <a <p><b>If you want to reset your password, please <a
href="{{.Host}}/register/reset/validate?c={{.VerificationCode}}&email={{.Email}}" target="_blank">click HERE</a>.</b> href="{{.Host}}/register/reset/validate?c={{.VerificationCode}}&email={{.Email}}" target="_blank">click HERE</a>.</b>
+10 -5
View File
@@ -1,14 +1,15 @@
package gateways package gateways
import ( import (
"GoCMS/domain/gateways" "RenewCMS/domain/gateways"
"bytes" "bytes"
"embed" "embed"
"gopkg.in/gomail.v2"
"html/template" "html/template"
"log" "log"
"os" "os"
"strconv" "strconv"
"gopkg.in/gomail.v2"
) )
type MailRepository struct{} type MailRepository struct{}
@@ -16,7 +17,11 @@ type MailRepository struct{}
//go:embed mail/templates/* //go:embed mail/templates/*
var mailTemplateFiles embed.FS var mailTemplateFiles embed.FS
func (m MailRepository) Send(receiverAddress string, templateName string, data interface{}) error { func NewMailRepository() *MailRepository {
return &MailRepository{}
}
func (m MailRepository) Send(receiverAddress string, templateName string, data any) error {
from := os.Getenv("SMTP_EMAIL") from := os.Getenv("SMTP_EMAIL")
password := os.Getenv("SMTP_PASSWORD") password := os.Getenv("SMTP_PASSWORD")
smtpHost := os.Getenv("SMTP_HOST") smtpHost := os.Getenv("SMTP_HOST")
@@ -39,12 +44,12 @@ func (m MailRepository) Send(receiverAddress string, templateName string, data i
msg := gomail.NewMessage() msg := gomail.NewMessage()
msg.SetHeaders(map[string][]string{ msg.SetHeaders(map[string][]string{
"From": {"GoCMS <" + from + ">"}, "From": {"RenewCMS <" + from + ">"},
"To": {receiverAddress}, "To": {receiverAddress},
"MIME-version": {"1.0"}, "MIME-version": {"1.0"},
"Content-Type": {"text/html"}, "Content-Type": {"text/html"},
"charset": {"UTF-8"}, "charset": {"UTF-8"},
"Subject": {"GoCMS | Action required"}, "Subject": {"RenewCMS | Action required"},
}) })
msg.SetBody("text/html", body.String()) msg.SetBody("text/html", body.String())
@@ -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"`
+3 -2
View File
@@ -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,7 +1,7 @@
package gateways package gateways
import ( import (
"GoCMS/domain/gateways" "RenewCMS/domain/gateways"
"bytes" "bytes"
"embed" "embed"
"html/template" "html/template"
@@ -16,7 +16,7 @@ func NewPageRepository() *PageRepository {
return &PageRepository{} return &PageRepository{}
} }
func (p *PageRepository) Get(name string, data interface{}) ([]byte, error) { func (p *PageRepository) Get(name string, data any) ([]byte, error) {
var processedHTML bytes.Buffer var processedHTML bytes.Buffer
tmpl, err := template.ParseFS(webTemplateFiles, "web/templates/"+name+".html") tmpl, err := template.ParseFS(webTemplateFiles, "web/templates/"+name+".html")
if err != nil { if err != nil {
@@ -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{}
@@ -1,10 +1,10 @@
package gateways package gateways
import ( import (
entity "GoCMS/adapters/secondary/gateways/models" entity "RenewCMS/adapters/secondary/gateways/models"
"GoCMS/domain/gateways" "RenewCMS/domain/gateways"
"GoCMS/domain/user" "RenewCMS/domain/user"
domain "GoCMS/domain/user" domain "RenewCMS/domain/user"
"golang.org/x/crypto/bcrypt" "golang.org/x/crypto/bcrypt"
"gorm.io/gorm" "gorm.io/gorm"
) )
@@ -1,7 +1,7 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
<title>GoCMS | Create post</title> <title>RenewCMS | 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>
@@ -1,7 +1,7 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
<title>GoCMS | Post edition</title> <title>RenewCMS | 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);
@@ -1,7 +1,7 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
<title>GoCMS | Posts</title> <title>RenewCMS | 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>
@@ -1,9 +1,9 @@
<nav class="navbar navbar-expand-lg bg-body-tertiary"> <nav class="navbar navbar-expand-lg bg-body-tertiary">
<div class="container"> <div class="container">
<div class="d-flex gap-2 justify-content-center align-items-center"> <div class="d-flex gap-2 justify-content-center align-items-center">
<img src="/static/gocms-favicon-128.png" alt="Logo" style="width: 2.5rem"/> <img src="/static/renewcms-favicon-64.png" alt="Logo" style="width: 2.5rem"/>
<a class="navbar-brand h1 my-auto" href="https://github.com/Floriansylvain/GoCMS" target="_blank"> <a class="navbar-brand h1 my-auto" href="https://github.com/Floriansylvain/RenewCMS" target="_blank">
GoCMS RenewCMS
</a> </a>
</div> </div>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav"
@@ -16,16 +16,13 @@
<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>
</li> </li>
</ul> </ul>
<ul class="navbar-nav"> <ul class="navbar-nav">
<li class="nav-item">
<a class="nav-link text-body-tertiary text-decoration-underline" href="https://www.paypal.com/donate/?hosted_button_id=S6LUFUYK84CYY">support me!</a>
</li>
<li class="nav-item"> <li class="nav-item">
<a class="nav-link disconnect-link" href="/logout">Log out</a> <a class="nav-link disconnect-link" href="/logout">Log out</a>
</li> </li>
@@ -1,16 +1,16 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
<title>GoCMS | Home</title> <title>RenewCMS | Home</title>
{{.Head}} {{.Head}}
</head> </head>
<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>Accueil</h1> <h1>Home</h1>
<p>Welcome on GoCMS !</p> <p>Welcome on RenewCMS !</p>
<a href="/post"> <a href="/article">
<button class="btn btn-outline-primary">Posts &raquo;</button> <button class="btn btn-outline-primary">Articles &raquo;</button>
</a> </a>
</div> </div>
</body> </body>
@@ -1,7 +1,7 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
<title>GoCMS | Home</title> <title>RenewCMS | Home</title>
{{.Head}} {{.Head}}
</head> </head>
<body class="text-dark"> <body class="text-dark">
@@ -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": "&lt;p&gt;?&lt;/p&gt;\r\n&lt;p&gt;&lt;img src=\"../../static/uploadedImages/649691a8-4f2e-48ca-9abe-27a60621d2e4.png\" "body": "&lt;p&gt;?&lt;/p&gt;\r\n&lt;p&gt;&lt;img src=\"../../static/uploadedImages/649691a8-4f2e-48ca-9abe-27a60621d2e4.png\"
alt=\"\" width=\"391\" height=\"465\"&gt;&lt;/p&gt;", alt=\"\" width=\"391\" height=\"465\"&gt;&lt;/p&gt;",
"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"
} }
@@ -1,7 +1,7 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
<title>GoCMS | Login</title> <title>RenewCMS | Login</title>
{{.Head}} {{.Head}}
<style> <style>
@@ -14,7 +14,7 @@
<div class="container"> <div class="container">
<div class="d-flex flex-column m-auto form-container"> <div class="d-flex flex-column m-auto form-container">
<div> <div>
<h1>GoCMS</h1> <h1>RenewCMS</h1>
<h2>Login</h2> <h2>Login</h2>
</div> </div>
<form action="login" method="POST" class="mt-5" id="loginForm"> <form action="login" method="POST" class="mt-5" id="loginForm">
@@ -1,7 +1,7 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
<title>GoCMS | Password reset</title> <title>RenewCMS | Password reset</title>
{{.Head}} {{.Head}}
<style> <style>
@@ -14,7 +14,7 @@
<div class="container"> <div class="container">
<div class="d-flex flex-column m-auto form-container"> <div class="d-flex flex-column m-auto form-container">
<div> <div>
<h1>GoCMS</h1> <h1>RenewCMS</h1>
<h2>Admin account password reset</h2> <h2>Admin account password reset</h2>
</div> </div>
<form action="request" method="POST" class="mt-5" id="registerForm"> <form action="request" method="POST" class="mt-5" id="registerForm">
@@ -1,7 +1,7 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
<title>GoCMS | Password reset</title> <title>RenewCMS | Password reset</title>
{{.Head}} {{.Head}}
<style> <style>
@@ -14,7 +14,7 @@
<div class="container"> <div class="container">
<div class="d-flex flex-column m-auto form-container"> <div class="d-flex flex-column m-auto form-container">
<div> <div>
<h1>GoCMS</h1> <h1>RenewCMS</h1>
<h2>Admin account password reset</h2> <h2>Admin account password reset</h2>
</div> </div>
<form action="validate" method="POST" class="mt-5" id="registerForm"> <form action="validate" method="POST" class="mt-5" id="registerForm">
@@ -1,7 +1,7 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
<title>GoCMS | Setup</title> <title>RenewCMS | Setup</title>
{{.Head}} {{.Head}}
<style> <style>
@@ -14,7 +14,7 @@
<div class="container"> <div class="container">
<div class="d-flex flex-column m-auto form-container"> <div class="d-flex flex-column m-auto form-container">
<div> <div>
<h1>GoCMS</h1> <h1>RenewCMS</h1>
<h2>Admin account creation</h2> <h2>Admin account creation</h2>
</div> </div>
<form action="register" method="POST" class="mt-5" id="registerForm"> <form action="register" method="POST" class="mt-5" id="registerForm">
@@ -1,7 +1,7 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
<title>GoCMS | Setup</title> <title>RenewCMS | Setup</title>
{{.Head}} {{.Head}}
<style> <style>
@@ -14,7 +14,7 @@
<div class="container"> <div class="container">
<div class="d-flex flex-column m-auto form-container"> <div class="d-flex flex-column m-auto form-container">
<div> <div>
<h1>GoCMS</h1> <h1>RenewCMS</h1>
<h2>Verify your email</h2> <h2>Verify your email</h2>
</div> </div>
<p>An e-mail with the validation link was sent to the address you just registered.</p> <p>An e-mail with the validation link was sent to the address you just registered.</p>
@@ -1,7 +1,7 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
<title>GoCMS | Setup</title> <title>RenewCMS | Setup</title>
{{.Head}} {{.Head}}
<style> <style>
@@ -14,7 +14,7 @@
<div class="container"> <div class="container">
<div class="d-flex flex-column gap-5 m-auto form-container"> <div class="d-flex flex-column gap-5 m-auto form-container">
<div> <div>
<h1>GoCMS</h1> <h1>RenewCMS</h1>
<h2>E-mail verification</h2> <h2>E-mail verification</h2>
</div> </div>
{{ if .PageError.IsError }} {{ if .PageError.IsError }}
+99
View File
@@ -0,0 +1,99 @@
package article
import (
"RenewCMS/api"
"RenewCMS/api/controllers/auth"
"RenewCMS/domain/article"
"RenewCMS/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
}
+13 -24
View File
@@ -1,9 +1,9 @@
package auth package auth
import ( import (
"GoCMS/api" "RenewCMS/api"
"GoCMS/domain/user" "RenewCMS/domain/user"
"GoCMS/useCases" "RenewCMS/useCases"
"encoding/json" "encoding/json"
"net/http" "net/http"
"os" "os"
@@ -11,7 +11,6 @@ import (
"github.com/go-chi/chi/v5" "github.com/go-chi/chi/v5"
"github.com/go-chi/jwtauth/v5" "github.com/go-chi/jwtauth/v5"
"github.com/google/uuid"
"golang.org/x/crypto/bcrypt" "golang.org/x/crypto/bcrypt"
) )
@@ -29,12 +28,11 @@ 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."
func SetJwtCookie(w *http.ResponseWriter, userId uint32) error { func SetJwtCookie(w *http.ResponseWriter, userId uint32) error {
_, tokenString, err := Token.Encode(map[string]interface{}{"user_id": userId}) _, tokenString, err := Token.Encode(map[string]any{"user_id": userId})
if err != nil { if err != nil {
return err return err
} }
@@ -103,19 +101,6 @@ func GetUserFromCredentials(credentials LoginCredentials) (user.User, error) {
return dbUser, nil return dbUser, nil
} }
func GetNewUser(newUserCredentials RegisterCredentials, verificationCode string) (user.User, error) {
createdUser, err := api.Container.CreateUserUseCase.CreateUser(useCases.CreateUserCommand{
Username: newUserCredentials.Username,
Password: newUserCredentials.Password,
Email: newUserCredentials.Email,
VerificationCode: verificationCode,
})
if err != nil {
return user.User{}, err
}
return createdUser, nil
}
func login(w http.ResponseWriter, r *http.Request) { func login(w http.ResponseWriter, r *http.Request) {
var credentials LoginCredentials var credentials LoginCredentials
@@ -134,11 +119,12 @@ func login(w http.ResponseWriter, r *http.Request) {
dbUser, err := GetUserFromCredentials(credentials) dbUser, err := GetUserFromCredentials(credentials)
if err != nil { if err != nil {
http.Error(w, LogsErrorMessage, http.StatusForbidden) http.Error(w, LogsErrorMessage, http.StatusForbidden)
return
} }
_ = SetJwtCookie(&w, dbUser.ID) _ = SetJwtCookie(&w, dbUser.ID)
message, _ := json.Marshal(map[string]interface{}{"message": "User logged in! HTTPonly jwt cookie created"}) message, _ := json.Marshal(map[string]any{"message": "User logged in! HTTPonly jwt cookie created"})
_, _ = w.Write(message) _, _ = w.Write(message)
} }
@@ -161,8 +147,11 @@ func register(w http.ResponseWriter, r *http.Request) {
return return
} }
verificationCode := uuid.NewString() createdUser, err := api.Container.CreateUserUseCase.CreateUser(useCases.CreateUserCommand{
createdUser, err := GetNewUser(credentials, verificationCode) Username: credentials.Username,
Password: credentials.Password,
Email: credentials.Email,
})
if err != nil { if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError) http.Error(w, err.Error(), http.StatusInternalServerError)
return return
@@ -170,7 +159,7 @@ func register(w http.ResponseWriter, r *http.Request) {
_ = SetJwtCookie(&w, createdUser.ID) _ = SetJwtCookie(&w, createdUser.ID)
message, _ := json.Marshal(map[string]interface{}{"message": "User registered! HTTPonly jwt cookie created"}) message, _ := json.Marshal(map[string]any{"message": "User registered! HTTPonly jwt cookie created"})
_, _ = w.Write(message) _, _ = w.Write(message)
} }
@@ -188,7 +177,7 @@ func RemoveJwtCookie(w http.ResponseWriter) {
func logout(w http.ResponseWriter, _ *http.Request) { func logout(w http.ResponseWriter, _ *http.Request) {
RemoveJwtCookie(w) RemoveJwtCookie(w)
message, _ := json.Marshal(map[string]interface{}{"message": "User logged out! HTTPonly jwt cookie deleted"}) message, _ := json.Marshal(map[string]any{"message": "User logged out! HTTPonly jwt cookie deleted"})
_, _ = w.Write(message) _, _ = w.Write(message)
} }
+3 -3
View File
@@ -1,7 +1,7 @@
package image package image
import ( import (
"GoCMS/api" "RenewCMS/api"
"encoding/json" "encoding/json"
"net/http" "net/http"
"strconv" "strconv"
@@ -27,13 +27,13 @@ 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
} }
newJson := map[string]interface{}{"location": newImage.Path} newJson := map[string]any{"location": newImage.Path}
newJsonBytes, _ := json.Marshal(newJson) newJsonBytes, _ := json.Marshal(newJson)
_, _ = w.Write(newJsonBytes) _, _ = w.Write(newJsonBytes)
+58
View File
@@ -0,0 +1,58 @@
package pages
import (
"RenewCMS/api"
"RenewCMS/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)
}
@@ -1,8 +1,8 @@
package pages package pages
import ( import (
"GoCMS/api" "RenewCMS/api"
"GoCMS/api/controllers/post" "RenewCMS/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)
} }
+75
View File
@@ -0,0 +1,75 @@
package pages
import (
"RenewCMS/api"
"RenewCMS/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 (
"RenewCMS/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)
}
+31
View File
@@ -0,0 +1,31 @@
package pages
import (
"RenewCMS/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)
}
+2 -2
View File
@@ -1,14 +1,14 @@
package pages package pages
import ( import (
"GoCMS/api" "RenewCMS/api"
"html/template" "html/template"
"net/http" "net/http"
) )
func GetHomePage(w http.ResponseWriter, _ *http.Request) { func GetHomePage(w http.ResponseWriter, _ *http.Request) {
navbarTmpl, _ := api.Container.GetPageUseCase.GetPage("componentNavbar", nil) navbarTmpl, _ := api.Container.GetPageUseCase.GetPage("componentNavbar", nil)
homeTmpl, _ := api.Container.GetPageUseCase.GetPage("home", map[string]interface{}{ homeTmpl, _ := api.Container.GetPageUseCase.GetPage("home", map[string]any{
"Navbar": template.HTML(navbarTmpl), "Navbar": template.HTML(navbarTmpl),
"Head": headTmpl, "Head": headTmpl,
}) })
+3 -3
View File
@@ -1,15 +1,15 @@
package pages package pages
import ( import (
"GoCMS/api" "RenewCMS/api"
"html/template" "html/template"
"net/http" "net/http"
"os" "os"
) )
func GetPageIntegration(w http.ResponseWriter, r *http.Request) { func GetPageIntegration(w http.ResponseWriter, _ *http.Request) {
navbarTmpl, _ := api.Container.GetPageUseCase.GetPage("componentNavbar", nil) navbarTmpl, _ := api.Container.GetPageUseCase.GetPage("componentNavbar", nil)
templ, _ := api.Container.GetPageUseCase.GetPage("integration", map[string]interface{}{ templ, _ := api.Container.GetPageUseCase.GetPage("integration", map[string]any{
"Navbar": template.HTML(navbarTmpl), "Navbar": template.HTML(navbarTmpl),
"Head": headTmpl, "Head": headTmpl,
"Host": os.Getenv("HOST"), "Host": os.Getenv("HOST"),
+3 -3
View File
@@ -1,8 +1,8 @@
package pages package pages
import ( import (
"GoCMS/api" "RenewCMS/api"
"GoCMS/api/controllers/auth" "RenewCMS/api/controllers/auth"
"net/http" "net/http"
"net/url" "net/url"
) )
@@ -35,7 +35,7 @@ func GetLoginPageHandler(loginPage *LoginPage) http.HandlerFunc {
} }
success, _ := url.QueryUnescape(r.URL.Query().Get("success")) success, _ := url.QueryUnescape(r.URL.Query().Get("success"))
failure, _ := url.QueryUnescape(r.URL.Query().Get("failure")) failure, _ := url.QueryUnescape(r.URL.Query().Get("failure"))
bs, _ := api.Container.GetPageUseCase.GetPage("login", map[string]interface{}{ bs, _ := api.Container.GetPageUseCase.GetPage("login", map[string]any{
"PageError": loginPage.PageError, "PageError": loginPage.PageError,
"Username": loginPage.Username, "Username": loginPage.Username,
"Head": headTmpl, "Head": headTmpl,
+12 -12
View File
@@ -1,9 +1,9 @@
package pages package pages
import ( import (
"GoCMS/api" "RenewCMS/api"
"GoCMS/api/controllers/auth" "RenewCMS/api/controllers/auth"
"GoCMS/api/controllers/image" "RenewCMS/api/controllers/image"
"html/template" "html/template"
"net/http" "net/http"
"os" "os"
@@ -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,7 +1,7 @@
package pages package pages
import ( import (
"GoCMS/api" "RenewCMS/api"
"net/http" "net/http"
"os" "os"
@@ -18,7 +18,7 @@ func GetPasswordResetRequest(w http.ResponseWriter, r *http.Request) {
success := r.URL.Query().Get("success") success := r.URL.Query().Get("success")
email := r.URL.Query().Get("email") email := r.URL.Query().Get("email")
bs, _ := api.Container.GetPageUseCase.GetPage("passwordResetRequest", map[string]interface{}{ bs, _ := api.Container.GetPageUseCase.GetPage("passwordResetRequest", map[string]any{
"Head": headTmpl, "Head": headTmpl,
"Email": email, "Email": email,
"Success": success, "Success": success,
@@ -1,7 +1,7 @@
package pages package pages
import ( import (
"GoCMS/api" "RenewCMS/api"
"net/http" "net/http"
"golang.org/x/crypto/bcrypt" "golang.org/x/crypto/bcrypt"
@@ -16,7 +16,7 @@ func GetPasswordResetValidate(w http.ResponseWriter, r *http.Request) {
if failure != "" { if failure != "" {
pageError = NewPageError(failure) pageError = NewPageError(failure)
} }
template, _ := api.Container.GetPageUseCase.GetPage("passwordResetValidate", map[string]interface{}{ template, _ := api.Container.GetPageUseCase.GetPage("passwordResetValidate", map[string]any{
"Head": headTmpl, "Head": headTmpl,
"Error": pageError, "Error": pageError,
"Email": r.URL.Query().Get("email"), "Email": r.URL.Query().Get("email"),
-58
View File
@@ -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]interface{}{
"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)
}
-75
View File
@@ -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]interface{}{
"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)
}
-31
View File
@@ -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]interface{}
for _, post := range posts {
formattedPosts = append(formattedPosts, map[string]interface{}{
"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]interface{}{
"Navbar": template.HTML(navbarTmpl),
"Head": headTmpl,
"Posts": formattedPosts,
})
_, _ = w.Write(postsTmpl)
}
+10 -8
View File
@@ -1,12 +1,11 @@
package pages package pages
import ( import (
"GoCMS/api" "RenewCMS/api"
"GoCMS/api/controllers/auth" "RenewCMS/api/controllers/auth"
"RenewCMS/useCases"
"net/http" "net/http"
"os" "os"
"github.com/google/uuid"
) )
type RegisterPageError struct { type RegisterPageError struct {
@@ -44,7 +43,7 @@ func GetRegisterPageHandler(registerPage *RegisterPage) http.HandlerFunc {
PostRegisterPage(w, r) PostRegisterPage(w, r)
return return
} }
bs, err := api.Container.GetPageUseCase.GetPage("register", map[string]interface{}{ bs, err := api.Container.GetPageUseCase.GetPage("register", map[string]any{
"PageError": registerPage.PageError, "PageError": registerPage.PageError,
"Username": registerPage.Username, "Username": registerPage.Username,
"Email": registerPage.Email, "Email": registerPage.Email,
@@ -80,8 +79,11 @@ func PostRegisterPage(w http.ResponseWriter, r *http.Request) {
return return
} }
verificationCode := uuid.NewString() createdUser, err := api.Container.CreateUserUseCase.CreateUser(useCases.CreateUserCommand{
createdUser, err := auth.GetNewUser(credentials, verificationCode) Username: credentials.Username,
Password: credentials.Password,
Email: credentials.Email,
})
if err != nil { if err != nil {
r.Method = http.MethodGet r.Method = http.MethodGet
GetRegisterPageHandler(&RegisterPage{ GetRegisterPageHandler(&RegisterPage{
@@ -98,7 +100,7 @@ func PostRegisterPage(w http.ResponseWriter, r *http.Request) {
_ = api.Container.SendMailUseCase.SendMail(createdUser.Email, "mailValidation", map[string]string{ _ = api.Container.SendMailUseCase.SendMail(createdUser.Email, "mailValidation", map[string]string{
"Host": os.Getenv("HOST"), "Host": os.Getenv("HOST"),
"VerificationCode": verificationCode, "VerificationCode": createdUser.VerificationCode,
}) })
_ = auth.SetJwtCookie(&w, createdUser.ID) _ = auth.SetJwtCookie(&w, createdUser.ID)
+3 -3
View File
@@ -1,8 +1,8 @@
package pages package pages
import ( import (
"GoCMS/api" "RenewCMS/api"
"GoCMS/api/controllers/auth" "RenewCMS/api/controllers/auth"
"log" "log"
"net/http" "net/http"
@@ -29,7 +29,7 @@ func PostRegisterPendingPage(w http.ResponseWriter, r *http.Request) {
} }
func GetRegisterPendingPage(w http.ResponseWriter, _ *http.Request) { func GetRegisterPendingPage(w http.ResponseWriter, _ *http.Request) {
registerPendingTmpl, _ := api.Container.GetPageUseCase.GetPage("registerPending", map[string]interface{}{ registerPendingTmpl, _ := api.Container.GetPageUseCase.GetPage("registerPending", map[string]any{
"Head": headTmpl, "Head": headTmpl,
}) })
_, _ = w.Write(registerPendingTmpl) _, _ = w.Write(registerPendingTmpl)
+3 -3
View File
@@ -1,8 +1,8 @@
package pages package pages
import ( import (
"GoCMS/api" "RenewCMS/api"
"GoCMS/api/controllers/auth" "RenewCMS/api/controllers/auth"
"net/http" "net/http"
"time" "time"
@@ -38,7 +38,7 @@ func GetRegisterValidatePage(w http.ResponseWriter, r *http.Request) {
} }
} }
registerValidateTmpl, _ := api.Container.GetPageUseCase.GetPage("registerValidate", map[string]interface{}{ registerValidateTmpl, _ := api.Container.GetPageUseCase.GetPage("registerValidate", map[string]any{
"Head": headTmpl, "Head": headTmpl,
"PageError": NewPageError(errorMessage), "PageError": NewPageError(errorMessage),
}) })
-99
View File
@@ -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
}
+55 -36
View File
@@ -1,21 +1,23 @@
package api package api
import ( import (
"GoCMS/adapters/secondary/gateways/models" "RenewCMS/adapters/secondary/gateways"
"GoCMS/useCases" "RenewCMS/adapters/secondary/gateways/models"
"github.com/glebarez/sqlite" domainGateways "RenewCMS/domain/gateways"
"go.uber.org/dig" "RenewCMS/useCases"
"gorm.io/gorm"
"os" "os"
"path/filepath" "path/filepath"
"github.com/glebarez/sqlite"
"gorm.io/gorm"
) )
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
@@ -27,6 +29,14 @@ type UseCases struct {
DeleteImageUseCase *useCases.DeleteImageUseCase DeleteImageUseCase *useCases.DeleteImageUseCase
} }
type Repositories struct {
ArticleRepo domainGateways.IArticleRepository
UserRepo domainGateways.IUserRepository
ImageRepo domainGateways.IImageRepository
MailRepo domainGateways.IMailRepository
PageRepo domainGateways.IPageRepository
}
var Container *UseCases var Container *UseCases
func getDb() *gorm.DB { func getDb() *gorm.DB {
@@ -38,36 +48,45 @@ func getDb() *gorm.DB {
if err != nil { if err != nil {
panic("Unable to open the database: " + err.Error()) panic("Unable to open the database: " + err.Error())
} }
if err := db.AutoMigrate(&models.Article{}, &models.User{}); err != nil {
panic("Failed to migrate database: " + err.Error())
}
return db return db
} }
func InitContainer() { func getRepositories(db *gorm.DB) *Repositories {
digContainer := dig.New() return &Repositories{
ArticleRepo: gateways.NewArticleRepository(db),
UserRepo: gateways.NewUserRepository(db),
ImageRepo: gateways.NewImageRepository(db),
MailRepo: gateways.NewMailRepository(),
PageRepo: gateways.NewPageRepository(),
}
}
database := getDb() func getUseCases(repos *Repositories) *UseCases {
_ = database.AutoMigrate(&models.Post{}, &models.User{})
_ = digContainer.Provide(func() *gorm.DB { return database })
_ = digContainer.Provide(func(db *gorm.DB) *UseCases {
return &UseCases{ return &UseCases{
CreatePostUseCase: useCases.NewCreatePostUseCase(db), CreateArticleUseCase: useCases.NewCreateArticleUseCase(repos.ArticleRepo),
GetPostUseCase: useCases.NewGetPostUseCase(db), GetArticleUseCase: useCases.NewGetArticleUseCase(repos.ArticleRepo),
ListPostsUseCase: useCases.NewListPostsUseCase(db), ListArticlesUseCase: useCases.NewListArticlesUseCase(repos.ArticleRepo),
UpdatePostUseCase: useCases.NewUpdatePostUseCase(db), UpdateArticleUseCase: useCases.NewUpdateArticleUseCase(repos.ArticleRepo),
DeletePostUseCase: useCases.NewDeletePostUseCase(db), DeleteArticleUseCase: useCases.NewDeleteArticleUseCase(repos.ArticleRepo),
GetUserUseCase: useCases.NewGetUserUseCase(db), GetUserUseCase: useCases.NewGetUserUseCase(repos.UserRepo),
CreateUserUseCase: useCases.NewCreateUserUseCase(db), CreateUserUseCase: useCases.NewCreateUserUseCase(repos.UserRepo),
UpdateUserUseCase: useCases.NewUpdateUserUseCase(db), UpdateUserUseCase: useCases.NewUpdateUserUseCase(repos.UserRepo),
DeleteUserUseCase: useCases.NewDeleteUserUseCase(db), DeleteUserUseCase: useCases.NewDeleteUserUseCase(repos.UserRepo),
ListUsersUseCase: useCases.NewListUsersUseCase(db), ListUsersUseCase: useCases.NewListUsersUseCase(repos.UserRepo),
GetPageUseCase: useCases.NewGetPageUseCase(), GetPageUseCase: useCases.NewGetPageUseCase(repos.PageRepo),
SendMailUseCase: useCases.NewSendMailUseCase(), SendMailUseCase: useCases.NewSendMailUseCase(repos.MailRepo),
CreateImageUseCase: useCases.NewCreateImageUseCase(db), CreateImageUseCase: useCases.NewCreateImageUseCase(repos.ImageRepo),
DeleteImageUseCase: useCases.NewDeleteImageUseCase(db), DeleteImageUseCase: useCases.NewDeleteImageUseCase(repos.ImageRepo),
}
})
err := digContainer.Invoke(func(useCases *UseCases) { Container = useCases })
if err != nil {
panic("Unable to invoke container: " + err.Error())
} }
} }
func InitContainer() {
db := getDb()
repos := getRepositories(db)
Container = getUseCases(repos)
}
BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 472 B

+1 -1
View File
@@ -7,6 +7,6 @@ services:
- ${DOCKER_DB_FOLDER}:/app/db/ - ${DOCKER_DB_FOLDER}:/app/db/
environment: environment:
- PORT=8080 - PORT=8080
- DB_FILE=/app/db/gocms.db - DB_FILE=/app/db/RenewCMS.db
env_file: env_file:
- ./.env - ./.env
@@ -1,12 +1,12 @@
package post package article
import ( import (
entity "GoCMS/adapters/secondary/gateways/models" entity "RenewCMS/adapters/secondary/gateways/models"
domain "GoCMS/domain/image" domain "RenewCMS/domain/image"
"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,
+16
View File
@@ -0,0 +1,16 @@
package gateways
import (
"RenewCMS/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 -1
View File
@@ -1,7 +1,7 @@
package gateways package gateways
import ( import (
"GoCMS/domain/image" "RenewCMS/domain/image"
"mime/multipart" "mime/multipart"
) )
+1 -1
View File
@@ -1,5 +1,5 @@
package gateways package gateways
type IMailRepository interface { type IMailRepository interface {
Send(receiverAddress string, templateName string, data interface{}) error Send(receiverAddress string, templateName string, data any) error
} }
+1 -1
View File
@@ -1,5 +1,5 @@
package gateways package gateways
type IPageRepository interface { type IPageRepository interface {
Get(name string, data interface{}) ([]byte, error) Get(name string, data any) ([]byte, error)
} }
-16
View File
@@ -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
}
+1 -1
View File
@@ -1,6 +1,6 @@
package gateways package gateways
import "GoCMS/domain/user" import "RenewCMS/domain/user"
type IUserRepository interface { type IUserRepository interface {
Get(id uint32) (user.User, error) Get(id uint32) (user.User, error)
+3 -3
View File
@@ -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,
} }
+19 -28
View File
@@ -1,63 +1,54 @@
module GoCMS module RenewCMS
go 1.23.0 go 1.25.4
toolchain go1.24.1
require ( require (
github.com/MadAppGang/httplog v1.3.0 github.com/MadAppGang/httplog v1.3.0
github.com/glebarez/sqlite v1.11.0 github.com/glebarez/sqlite v1.11.0
github.com/go-chi/cors v1.2.1 github.com/go-chi/cors v1.2.2
github.com/go-chi/jwtauth/v5 v5.3.2 github.com/go-chi/jwtauth/v5 v5.3.3
github.com/go-playground/validator/v10 v10.25.0 github.com/go-playground/validator/v10 v10.28.0
github.com/google/uuid v1.6.0 github.com/google/uuid v1.6.0
github.com/joho/godotenv v1.5.1 github.com/joho/godotenv v1.5.1
github.com/stretchr/testify v1.10.0 golang.org/x/crypto v0.44.0
go.uber.org/dig v1.18.1
golang.org/x/crypto v0.36.0
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df
gorm.io/gorm v1.25.12 gorm.io/gorm v1.31.1
) )
require ( require (
github.com/TylerBrock/colorjson v0.0.0-20200706003622-8a50f05110d2 // indirect github.com/TylerBrock/colorjson v0.0.0-20200706003622-8a50f05110d2 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 // indirect github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect github.com/dustin/go-humanize v1.0.1 // indirect
github.com/fatih/color v1.18.0 // indirect github.com/fatih/color v1.18.0 // indirect
github.com/gabriel-vasile/mimetype v1.4.8 // indirect github.com/gabriel-vasile/mimetype v1.4.11 // indirect
github.com/glebarez/go-sqlite v1.22.0 // indirect github.com/glebarez/go-sqlite v1.22.0 // indirect
github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/goccy/go-json v0.10.5 // indirect github.com/goccy/go-json v0.10.5 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.5 // indirect github.com/jinzhu/now v1.1.5 // indirect
github.com/kr/text v0.2.0 // indirect
github.com/leodido/go-urn v1.4.0 // indirect github.com/leodido/go-urn v1.4.0 // indirect
github.com/lestrrat-go/blackmagic v1.0.2 // indirect github.com/lestrrat-go/blackmagic v1.0.4 // indirect
github.com/lestrrat-go/httpcc v1.0.1 // indirect github.com/lestrrat-go/httpcc v1.0.1 // indirect
github.com/lestrrat-go/httprc v1.0.6 // indirect github.com/lestrrat-go/httprc v1.0.6 // indirect
github.com/lestrrat-go/iter v1.0.2 // indirect github.com/lestrrat-go/iter v1.0.2 // indirect
github.com/lestrrat-go/jwx/v2 v2.1.4 // indirect github.com/lestrrat-go/jwx/v2 v2.1.6 // indirect
github.com/lestrrat-go/option v1.0.1 // indirect github.com/lestrrat-go/option v1.0.1 // indirect
github.com/ncruces/go-strftime v0.1.9 // indirect github.com/ncruces/go-strftime v1.0.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
github.com/segmentio/asm v1.2.0 // indirect github.com/segmentio/asm v1.2.1 // indirect
golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 // indirect golang.org/x/exp v0.0.0-20251113190631-e25ba8c21ef6 // indirect
golang.org/x/net v0.37.0 // indirect golang.org/x/text v0.31.0 // indirect
golang.org/x/text v0.23.0 // indirect
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect modernc.org/libc v1.67.0 // indirect
modernc.org/libc v1.61.13 // indirect
modernc.org/mathutil v1.7.1 // indirect modernc.org/mathutil v1.7.1 // indirect
modernc.org/memory v1.8.2 // indirect modernc.org/memory v1.11.0 // indirect
modernc.org/sqlite v1.36.0 // indirect modernc.org/sqlite v1.40.1 // indirect
) )
require ( require (
github.com/go-chi/chi/v5 v5.2.1 github.com/go-chi/chi/v5 v5.2.3
github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-colorable v0.1.14 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-isatty v0.0.20 // indirect
golang.org/x/sys v0.31.0 // indirect golang.org/x/sys v0.38.0 // indirect
) )
+56 -61
View File
@@ -2,7 +2,6 @@ github.com/MadAppGang/httplog v1.3.0 h1:1XU54TO8kiqTeO+7oZLKAM3RP/cJ7SadzslRcKsp
github.com/MadAppGang/httplog v1.3.0/go.mod h1:gpYEdkjh/Cda6YxtDy4AB7KY+fR7mb3SqBZw74A5hJ4= github.com/MadAppGang/httplog v1.3.0/go.mod h1:gpYEdkjh/Cda6YxtDy4AB7KY+fR7mb3SqBZw74A5hJ4=
github.com/TylerBrock/colorjson v0.0.0-20200706003622-8a50f05110d2 h1:ZBbLwSJqkHBuFDA6DUhhse0IGJ7T5bemHyNILUjvOq4= github.com/TylerBrock/colorjson v0.0.0-20200706003622-8a50f05110d2 h1:ZBbLwSJqkHBuFDA6DUhhse0IGJ7T5bemHyNILUjvOq4=
github.com/TylerBrock/colorjson v0.0.0-20200706003622-8a50f05110d2/go.mod h1:VSw57q4QFiWDbRnjdX8Cb3Ow0SFncRw+bA/ofY6Q83w= github.com/TylerBrock/colorjson v0.0.0-20200706003622-8a50f05110d2/go.mod h1:VSw57q4QFiWDbRnjdX8Cb3Ow0SFncRw+bA/ofY6Q83w=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@@ -12,32 +11,34 @@ github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkp
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM= github.com/gabriel-vasile/mimetype v1.4.11 h1:AQvxbp830wPhHTqc1u7nzoLT+ZFxGY7emj5DR5DYFik=
github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8= github.com/gabriel-vasile/mimetype v1.4.11/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s=
github.com/glebarez/go-sqlite v1.22.0 h1:uAcMJhaA6r3LHMTFgP0SifzgXg46yJkgxqyuyec+ruQ= github.com/glebarez/go-sqlite v1.22.0 h1:uAcMJhaA6r3LHMTFgP0SifzgXg46yJkgxqyuyec+ruQ=
github.com/glebarez/go-sqlite v1.22.0/go.mod h1:PlBIdHe0+aUEFn+r2/uthrWq4FxbzugL0L8Li6yQJbc= github.com/glebarez/go-sqlite v1.22.0/go.mod h1:PlBIdHe0+aUEFn+r2/uthrWq4FxbzugL0L8Li6yQJbc=
github.com/glebarez/sqlite v1.11.0 h1:wSG0irqzP6VurnMEpFGer5Li19RpIRi2qvQz++w0GMw= github.com/glebarez/sqlite v1.11.0 h1:wSG0irqzP6VurnMEpFGer5Li19RpIRi2qvQz++w0GMw=
github.com/glebarez/sqlite v1.11.0/go.mod h1:h8/o8j5wiAsqSPoWELDUdJXhjAhsVliSn7bWZjOhrgQ= github.com/glebarez/sqlite v1.11.0/go.mod h1:h8/o8j5wiAsqSPoWELDUdJXhjAhsVliSn7bWZjOhrgQ=
github.com/go-chi/chi/v5 v5.2.1 h1:KOIHODQj58PmL80G2Eak4WdvUzjSJSm0vG72crDCqb8= github.com/go-chi/chi/v5 v5.2.3 h1:WQIt9uxdsAbgIYgid+BpYc+liqQZGMHRaUwp0JUcvdE=
github.com/go-chi/chi/v5 v5.2.1/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops= github.com/go-chi/chi/v5 v5.2.3/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops=
github.com/go-chi/cors v1.2.1 h1:xEC8UT3Rlp2QuWNEr4Fs/c2EAGVKBwy/1vHx3bppil4= github.com/go-chi/cors v1.2.2 h1:Jmey33TE+b+rB7fT8MUy1u0I4L+NARQlK6LhzKPSyQE=
github.com/go-chi/cors v1.2.1/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58= github.com/go-chi/cors v1.2.2/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58=
github.com/go-chi/jwtauth/v5 v5.3.2 h1:s+ON3ATyyMs3Me0kqyuua6Rwu+2zqIIkL0GCaMarwvs= github.com/go-chi/jwtauth/v5 v5.3.3 h1:50Uzmacu35/ZP9ER2Ht6SazwPsnLQ9LRJy6zTZJpHEo=
github.com/go-chi/jwtauth/v5 v5.3.2/go.mod h1:O4QvPRuZLZghl9WvfVaON+ARfGzpD2PBX/QY5vUz7aQ= github.com/go-chi/jwtauth/v5 v5.3.3/go.mod h1:O4QvPRuZLZghl9WvfVaON+ARfGzpD2PBX/QY5vUz7aQ=
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
github.com/go-playground/validator/v10 v10.25.0 h1:5Dh7cjvzR7BRZadnsVOzPhWsrwUr0nmsZJxEAnFLNO8= github.com/go-playground/validator/v10 v10.28.0 h1:Q7ibns33JjyW48gHkuFT91qX48KG0ktULL6FgHdG688=
github.com/go-playground/validator/v10 v10.25.0/go.mod h1:GGzBIJMuE98Ic/kJsBXbz1x/7cByt++cQ+YOuDM5wus= github.com/go-playground/validator/v10 v10.28.0/go.mod h1:GoI6I1SjPBh9p7ykNE/yj3fFYbyDOpwMn5KXd+m2hUU=
github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4= github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd h1:gbpYu9NMq8jhDVbvlGkMFWCjLFlqqEZjEmObmhUy6Vo= github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e h1:ijClszYn+mADRFY17kjQEVQ1XRhq2/JR1M3sGqeJoxs=
github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw= github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
github.com/hokaccha/go-prettyjson v0.0.0-20211117102719-0474bc63780f h1:7LYC+Yfkj3CTRcShK0KOL/w6iTiKyqqBA9a41Wnggw8= github.com/hokaccha/go-prettyjson v0.0.0-20211117102719-0474bc63780f h1:7LYC+Yfkj3CTRcShK0KOL/w6iTiKyqqBA9a41Wnggw8=
github.com/hokaccha/go-prettyjson v0.0.0-20211117102719-0474bc63780f/go.mod h1:pFlLw2CfqZiIBOx6BuCeRLCrfxBJipTY0nIOF/VbGcI= github.com/hokaccha/go-prettyjson v0.0.0-20211117102719-0474bc63780f/go.mod h1:pFlLw2CfqZiIBOx6BuCeRLCrfxBJipTY0nIOF/VbGcI=
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
@@ -46,92 +47,86 @@ github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
github.com/lestrrat-go/blackmagic v1.0.2 h1:Cg2gVSc9h7sz9NOByczrbUvLopQmXrfFx//N+AkAr5k= github.com/lestrrat-go/blackmagic v1.0.4 h1:IwQibdnf8l2KoO+qC3uT4OaTWsW7tuRQXy9TRN9QanA=
github.com/lestrrat-go/blackmagic v1.0.2/go.mod h1:UrEqBzIR2U6CnzVyUtfM6oZNMt/7O7Vohk2J0OGSAtU= github.com/lestrrat-go/blackmagic v1.0.4/go.mod h1:6AWFyKNNj0zEXQYfTMPfZrAXUWUfTIZ5ECEUEJaijtw=
github.com/lestrrat-go/httpcc v1.0.1 h1:ydWCStUeJLkpYyjLDHihupbn2tYmZ7m22BGkcvZZrIE= github.com/lestrrat-go/httpcc v1.0.1 h1:ydWCStUeJLkpYyjLDHihupbn2tYmZ7m22BGkcvZZrIE=
github.com/lestrrat-go/httpcc v1.0.1/go.mod h1:qiltp3Mt56+55GPVCbTdM9MlqhvzyuL6W/NMDA8vA5E= github.com/lestrrat-go/httpcc v1.0.1/go.mod h1:qiltp3Mt56+55GPVCbTdM9MlqhvzyuL6W/NMDA8vA5E=
github.com/lestrrat-go/httprc v1.0.6 h1:qgmgIRhpvBqexMJjA/PmwSvhNk679oqD1RbovdCGW8k= github.com/lestrrat-go/httprc v1.0.6 h1:qgmgIRhpvBqexMJjA/PmwSvhNk679oqD1RbovdCGW8k=
github.com/lestrrat-go/httprc v1.0.6/go.mod h1:mwwz3JMTPBjHUkkDv/IGJ39aALInZLrhBp0X7KGUZlo= github.com/lestrrat-go/httprc v1.0.6/go.mod h1:mwwz3JMTPBjHUkkDv/IGJ39aALInZLrhBp0X7KGUZlo=
github.com/lestrrat-go/iter v1.0.2 h1:gMXo1q4c2pHmC3dn8LzRhJfP1ceCbgSiT9lUydIzltI= github.com/lestrrat-go/iter v1.0.2 h1:gMXo1q4c2pHmC3dn8LzRhJfP1ceCbgSiT9lUydIzltI=
github.com/lestrrat-go/iter v1.0.2/go.mod h1:Momfcq3AnRlRjI5b5O8/G5/BvpzrhoFTZcn06fEOPt4= github.com/lestrrat-go/iter v1.0.2/go.mod h1:Momfcq3AnRlRjI5b5O8/G5/BvpzrhoFTZcn06fEOPt4=
github.com/lestrrat-go/jwx/v2 v2.1.4 h1:uBCMmJX8oRZStmKuMMOFb0Yh9xmEMgNJLgjuKKt4/qc= github.com/lestrrat-go/jwx/v2 v2.1.6 h1:hxM1gfDILk/l5ylers6BX/Eq1m/pnxe9NBwW6lVfecA=
github.com/lestrrat-go/jwx/v2 v2.1.4/go.mod h1:nWRbDFR1ALG2Z6GJbBXzfQaYyvn751KuuyySN2yR6is= github.com/lestrrat-go/jwx/v2 v2.1.6/go.mod h1:Y722kU5r/8mV7fYDifjug0r8FK8mZdw0K0GpJw/l8pU=
github.com/lestrrat-go/option v1.0.1 h1:oAzP2fvZGQKWkvHa1/SAcFolBEca1oN+mQ7eooNBEYU= github.com/lestrrat-go/option v1.0.1 h1:oAzP2fvZGQKWkvHa1/SAcFolBEca1oN+mQ7eooNBEYU=
github.com/lestrrat-go/option v1.0.1/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I= github.com/lestrrat-go/option v1.0.1/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I=
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4= github.com/ncruces/go-strftime v1.0.0 h1:HMFp8mLCTPp341M/ZnA4qaf7ZlsbTc+miZjCLOFAw7w=
github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls= github.com/ncruces/go-strftime v1.0.0/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
github.com/segmentio/asm v1.2.0 h1:9BQrFxC+YOHJlTlHGkTrFWf59nbL3XnCoFLTwDCI7ys= github.com/segmentio/asm v1.2.1 h1:DTNbBqs57ioxAD4PrArqftgypG4/qNpXoJx8TVXxPR0=
github.com/segmentio/asm v1.2.0/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs= github.com/segmentio/asm v1.2.1/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
go.uber.org/dig v1.18.1 h1:rLww6NuajVjeQn+49u5NcezUJEGwd5uXmyoCKW2g5Es= golang.org/x/crypto v0.44.0 h1:A97SsFvM3AIwEEmTBiaxPPTYpDC47w720rdiiUvgoAU=
go.uber.org/dig v1.18.1/go.mod h1:Us0rSJiThwCv2GteUN0Q7OKvU7n5J4dxZ9JKUXozFdE= golang.org/x/crypto v0.44.0/go.mod h1:013i+Nw79BMiQiMsOPcVCB5ZIJbYkerPrGnOa00tvmc=
golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= golang.org/x/exp v0.0.0-20251113190631-e25ba8c21ef6 h1:zfMcR1Cs4KNuomFFgGefv5N0czO2XZpUbxGUy8i8ug0=
golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= golang.org/x/exp v0.0.0-20251113190631-e25ba8c21ef6/go.mod h1:46edojNIoXTNOhySWIWdix628clX9ODXwPsQuG6hsK0=
golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 h1:nDVHiLt8aIbd/VzvPWN6kSOPE7+F/fNFDSXLVYkE/Iw= golang.org/x/mod v0.30.0 h1:fDEXFVZ/fmCKProc/yAXXUijritrDzahmwwefnjoPFk=
golang.org/x/exp v0.0.0-20250305212735-054e65f0b394/go.mod h1:sIifuuw/Yco/y6yb6+bDNfyeQ/MdPUy/hKEMYQV17cM= golang.org/x/mod v0.30.0/go.mod h1:lAsf5O2EvJeSFMiBxXDki7sCgAxEUcZHXoXMKT4GJKc=
golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU= golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I=
golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww= golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/net v0.37.0 h1:1zLorHbz+LYj7MQlSf1+2tPIIgibq2eL5xkrGk6f+2c=
golang.org/x/net v0.37.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw=
golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=
golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=
golang.org/x/tools v0.31.0 h1:0EedkvKDbh+qistFTd0Bcwe/YLh4vHwWEkiI0toFIBU= golang.org/x/tools v0.39.0 h1:ik4ho21kwuQln40uelmciQPp9SipgNDdrafrYA4TmQQ=
golang.org/x/tools v0.31.0/go.mod h1:naFTU+Cev749tSJRXJlna0T3WxKvb1kWEx15xA4SdmQ= golang.org/x/tools v0.39.0/go.mod h1:JnefbkDPyD8UU2kI5fuf8ZX4/yUeh9W877ZeBONxUqQ=
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc h1:2gGKlE2+asNV9m7xrywl36YYNnBG5ZQ0r/BOOxqPpmk= gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc h1:2gGKlE2+asNV9m7xrywl36YYNnBG5ZQ0r/BOOxqPpmk=
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk= gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df h1:n7WqCuqOuCbNr617RXOY0AWRXxgwEyPp2z+p0+hgMuE= gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df h1:n7WqCuqOuCbNr617RXOY0AWRXxgwEyPp2z+p0+hgMuE=
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df/go.mod h1:LRQQ+SO6ZHR7tOkpBDuZnXENFzX8qRjMDMyPD6BRkCw= gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df/go.mod h1:LRQQ+SO6ZHR7tOkpBDuZnXENFzX8qRjMDMyPD6BRkCw=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gorm.io/gorm v1.25.12 h1:I0u8i2hWQItBq1WfE0o2+WuL9+8L21K9e2HHSTE/0f8= gorm.io/gorm v1.31.1 h1:7CA8FTFz/gRfgqgpeKIBcervUn3xSyPUmr6B2WXJ7kg=
gorm.io/gorm v1.25.12/go.mod h1:xh7N7RHfYlNc5EmcI/El95gXusucDrQnHXe0+CgWcLQ= gorm.io/gorm v1.31.1/go.mod h1:XyQVbO2k6YkOis7C2437jSit3SsDK72s7n7rsSHd+Gs=
modernc.org/cc/v4 v4.24.4 h1:TFkx1s6dCkQpd6dKurBNmpo+G8Zl4Sq/ztJ+2+DEsh0= modernc.org/cc/v4 v4.27.1 h1:9W30zRlYrefrDV2JE2O8VDtJ1yPGownxciz5rrbQZis=
modernc.org/cc/v4 v4.24.4/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0= modernc.org/cc/v4 v4.27.1/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0=
modernc.org/ccgo/v4 v4.23.16 h1:Z2N+kk38b7SfySC1ZkpGLN2vthNJP1+ZzGZIlH7uBxo= modernc.org/ccgo/v4 v4.30.1 h1:4r4U1J6Fhj98NKfSjnPUN7Ze2c6MnAdL0hWw6+LrJpc=
modernc.org/ccgo/v4 v4.23.16/go.mod h1:nNma8goMTY7aQZQNTyN9AIoJfxav4nvTnvKThAeMDdo= modernc.org/ccgo/v4 v4.30.1/go.mod h1:bIOeI1JL54Utlxn+LwrFyjCx2n2RDiYEaJVSrgdrRfM=
modernc.org/fileutil v1.3.0 h1:gQ5SIzK3H9kdfai/5x41oQiKValumqNTDXMvKo62HvE= modernc.org/fileutil v1.3.40 h1:ZGMswMNc9JOCrcrakF1HrvmergNLAmxOPjizirpfqBA=
modernc.org/fileutil v1.3.0/go.mod h1:XatxS8fZi3pS8/hKG2GH/ArUogfxjpEKs3Ku3aK4JyQ= modernc.org/fileutil v1.3.40/go.mod h1:HxmghZSZVAz/LXcMNwZPA/DRrQZEVP9VX0V4LQGQFOc=
modernc.org/gc/v2 v2.6.3 h1:aJVhcqAte49LF+mGveZ5KPlsp4tdGdAOT4sipJXADjw= modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI=
modernc.org/gc/v2 v2.6.3/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito= modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito=
modernc.org/libc v1.61.13 h1:3LRd6ZO1ezsFiX1y+bHd1ipyEHIJKvuprv0sLTBwLW8= modernc.org/gc/v3 v3.1.1 h1:k8T3gkXWY9sEiytKhcgyiZ2L0DTyCQ/nvX+LoCljoRE=
modernc.org/libc v1.61.13/go.mod h1:8F/uJWL/3nNil0Lgt1Dpz+GgkApWh04N3el3hxJcA6E= modernc.org/gc/v3 v3.1.1/go.mod h1:HFK/6AGESC7Ex+EZJhJ2Gni6cTaYpSMmU/cT9RmlfYY=
modernc.org/goabi0 v0.2.0 h1:HvEowk7LxcPd0eq6mVOAEMai46V+i7Jrj13t4AzuNks=
modernc.org/goabi0 v0.2.0/go.mod h1:CEFRnnJhKvWT1c1JTI3Avm+tgOWbkOu5oPA8eH8LnMI=
modernc.org/libc v1.67.0 h1:QzL4IrKab2OFmxA3/vRYl0tLXrIamwrhD6CKD4WBVjQ=
modernc.org/libc v1.67.0/go.mod h1:QvvnnJ5P7aitu0ReNpVIEyesuhmDLQ8kaEoyMjIFZJA=
modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU= modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU=
modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg= modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg=
modernc.org/memory v1.8.2 h1:cL9L4bcoAObu4NkxOlKWBWtNHIsnnACGF/TbqQ6sbcI= modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI=
modernc.org/memory v1.8.2/go.mod h1:ZbjSvMO5NQ1A2i3bWeDiVMxIorXwdClKE/0SZ+BMotU= modernc.org/memory v1.11.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw=
modernc.org/opt v0.1.4 h1:2kNGMRiUjrp4LcaPuLY2PzUfqM/w9N23quVwhKt5Qm8= modernc.org/opt v0.1.4 h1:2kNGMRiUjrp4LcaPuLY2PzUfqM/w9N23quVwhKt5Qm8=
modernc.org/opt v0.1.4/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns= modernc.org/opt v0.1.4/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns=
modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w= modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w=
modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE= modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE=
modernc.org/sqlite v1.36.0 h1:EQXNRn4nIS+gfsKeUTymHIz1waxuv5BzU7558dHSfH8= modernc.org/sqlite v1.40.1 h1:VfuXcxcUWWKRBuP8+BR9L7VnmusMgBNNnBYGEe9w/iY=
modernc.org/sqlite v1.36.0/go.mod h1:7MPwH7Z6bREicF9ZVUR78P1IKuxfZ8mRIDHD0iD+8TU= modernc.org/sqlite v1.40.1/go.mod h1:9fjQZ0mB1LLP0GYrp39oOJXx/I2sxEnZtzCmEQIKvGE=
modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0= modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0=
modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A= modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A=
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
BIN
View File
Binary file not shown.
+1 -1
View File
@@ -1,6 +1,6 @@
package main package main
import "GoCMS/main/server" import "RenewCMS/main/server"
func main() { func main() {
router := server.InitServer() router := server.InitServer()
+4 -4
View File
@@ -1,9 +1,9 @@
package route package route
import ( import (
"GoCMS/api/controllers/auth" "RenewCMS/api/controllers/article"
"GoCMS/api/controllers/pages" "RenewCMS/api/controllers/auth"
"GoCMS/api/controllers/post" "RenewCMS/api/controllers/pages"
"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())
+6 -5
View File
@@ -1,13 +1,14 @@
package server package server
import ( import (
"GoCMS/api" "RenewCMS/api"
"GoCMS/main/route" "RenewCMS/main/route"
"fmt" "fmt"
"github.com/go-chi/chi/v5"
"github.com/joho/godotenv"
"net/http" "net/http"
"os" "os"
"github.com/go-chi/chi/v5"
"github.com/joho/godotenv"
) )
var possibleEnvFileLocations = []string{".env", "../.env"} var possibleEnvFileLocations = []string{".env", "../.env"}
@@ -52,7 +53,7 @@ func InitServer() *chi.Mux {
} }
func StartServer(router *chi.Mux) error { func StartServer(router *chi.Mux) error {
fmt.Println("Server starting on :" + os.Getenv("PORT")) fmt.Println("Server starting on http://" + os.Getenv("HOST"))
err := http.ListenAndServe(":"+os.Getenv("PORT"), router) err := http.ListenAndServe(":"+os.Getenv("PORT"), router)
return err return err
} }
+16 -18
View File
@@ -1,30 +1,28 @@
#!/bin/sh #!/bin/sh
export GOARCH=amd64
OUTPUT_DIR=./bin OUTPUT_DIR=./bin
GO_FILE_PATH="./main/main.go" GO_FILE_PATH="./main/main.go"
PROGRAM_NAME=GoCMS PROGRAM_NAME=RenewCMS
# Compile for Windows platforms=("windows/amd64" "windows/arm64" "linux/amd64" "linux/arm64" "darwin/amd64" "darwin/arm64")
GOOS=windows go build -o "$OUTPUT_DIR/${PROGRAM_NAME}_windows.exe" "$GO_FILE_PATH"
if [ $? -ne 0 ]; then for platform in "${platforms[@]}"
echo "Compilation for Windows failed." do
exit $? platform_split=(${platform//\// })
GOOS=${platform_split[0]}
GOARCH=${platform_split[1]}
output_name="$OUTPUT_DIR/${PROGRAM_NAME}_${GOOS}_${GOARCH}"
if [ "$GOOS" = "windows" ]; then
output_name+='.exe'
fi fi
# Compile for Linux echo "Building for $GOOS/$GOARCH..."
GOOS=linux go build -o "$OUTPUT_DIR/${PROGRAM_NAME}_linux" "$GO_FILE_PATH" env GOOS=$GOOS GOARCH=$GOARCH go build -o "$output_name" "$GO_FILE_PATH"
if [ $? -ne 0 ]; then if [ $? -ne 0 ]; then
echo "Compilation for Linux failed." echo "Compilation for $GOOS/$GOARCH failed."
exit $?
fi
# Compile for macOS
GOOS=darwin go build -o "$OUTPUT_DIR/${PROGRAM_NAME}_darwin" "$GO_FILE_PATH"
if [ $? -ne 0 ]; then
echo "Compilation for macOS failed."
exit $? exit $?
fi fi
done
echo "Compilation successful for all platforms." echo "Compilation successful for all platforms."
-163
View File
@@ -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
View File
@@ -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]interface{}{"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
}
+26
View File
@@ -0,0 +1,26 @@
package useCases
import (
"RenewCMS/domain/article"
"RenewCMS/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,
))
}
+5 -8
View File
@@ -1,20 +1,17 @@
package useCases package useCases
import ( import (
"GoCMS/adapters/secondary/gateways" "RenewCMS/domain/gateways"
"GoCMS/domain/image" "RenewCMS/domain/image"
"gorm.io/gorm"
"mime/multipart" "mime/multipart"
) )
type CreateImageUseCase struct { type CreateImageUseCase struct {
imageRepository gateways.ImageRepository imageRepository gateways.IImageRepository
} }
func NewCreateImageUseCase(db *gorm.DB) *CreateImageUseCase { func NewCreateImageUseCase(imageRepository gateways.IImageRepository) *CreateImageUseCase {
return &CreateImageUseCase{ return &CreateImageUseCase{imageRepository}
imageRepository: *gateways.NewImageRepository(db),
}
} }
func (g *CreateImageUseCase) CreateImage(file multipart.File, fileHeader multipart.FileHeader) (image.Image, error) { func (g *CreateImageUseCase) CreateImage(file multipart.File, fileHeader multipart.FileHeader) (image.Image, error) {
-29
View File
@@ -1,29 +0,0 @@
package useCases
import (
"GoCMS/adapters/secondary/gateways"
"GoCMS/domain/post"
"gorm.io/gorm"
)
type CreatePostUseCase struct {
postRepository gateways.PostRepository
}
type CreatePostCommand struct {
Title string
Body string
}
func NewCreatePostUseCase(db *gorm.DB) *CreatePostUseCase {
return &CreatePostUseCase{
postRepository: *gateways.NewPostRepository(db),
}
}
func (g *CreatePostUseCase) CreatePost(createPost CreatePostCommand) (post.Post, error) {
return g.postRepository.Create(post.FromApi(
createPost.Title,
createPost.Body,
))
}
+17 -11
View File
@@ -1,34 +1,40 @@
package useCases package useCases
import ( import (
"GoCMS/adapters/secondary/gateways" "RenewCMS/domain/gateways"
"GoCMS/domain/user" "RenewCMS/domain/user"
"gorm.io/gorm"
"github.com/google/uuid"
) )
type CreateUserUseCase struct { type CreateUserUseCase struct {
userRepository gateways.UserRepository userRepository gateways.IUserRepository
} }
type CreateUserCommand struct { type CreateUserCommand struct {
Username string Username string
Password string Password string
Email string Email string
VerificationCode string
} }
func NewCreateUserUseCase(db *gorm.DB) *CreateUserUseCase { func NewCreateUserUseCase(userRepository gateways.IUserRepository) *CreateUserUseCase {
return &CreateUserUseCase{ return &CreateUserUseCase{userRepository}
userRepository: *gateways.NewUserRepository(db),
}
} }
func (g *CreateUserUseCase) CreateUser(createUser CreateUserCommand) (user.User, error) { func (g *CreateUserUseCase) CreateUser(createUser CreateUserCommand) (user.User, error) {
return g.userRepository.Create(user.FromApi( rawUuid := uuid.NewString()
createdUser, err := g.userRepository.Create(user.FromApi(
createUser.Username, createUser.Username,
createUser.Password, createUser.Password,
"", "",
createUser.Email, createUser.Email,
createUser.VerificationCode, rawUuid,
)) ))
if err != nil {
return user.User{}, err
}
createdUser.VerificationCode = rawUuid
return createdUser, nil
} }
+17
View File
@@ -0,0 +1,17 @@
package useCases
import (
"RenewCMS/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)
}
+4 -7
View File
@@ -1,18 +1,15 @@
package useCases package useCases
import ( import (
"GoCMS/adapters/secondary/gateways" "RenewCMS/domain/gateways"
"gorm.io/gorm"
) )
type DeleteImageUseCase struct { type DeleteImageUseCase struct {
imageRepository gateways.ImageRepository imageRepository gateways.IImageRepository
} }
func NewDeleteImageUseCase(db *gorm.DB) *DeleteImageUseCase { func NewDeleteImageUseCase(imageRepository gateways.IImageRepository) *DeleteImageUseCase {
return &DeleteImageUseCase{ return &DeleteImageUseCase{imageRepository}
imageRepository: *gateways.NewImageRepository(db),
}
} }
func (g *DeleteImageUseCase) DeleteImage(imageId uint32) error { func (g *DeleteImageUseCase) DeleteImage(imageId uint32) error {
-20
View File
@@ -1,20 +0,0 @@
package useCases
import (
"GoCMS/adapters/secondary/gateways"
"gorm.io/gorm"
)
type DeletePostUseCase struct {
postRepository gateways.PostRepository
}
func NewDeletePostUseCase(db *gorm.DB) *DeletePostUseCase {
return &DeletePostUseCase{
postRepository: *gateways.NewPostRepository(db),
}
}
func (g *DeletePostUseCase) DeletePost(userId uint32) error {
return g.postRepository.Delete(userId)
}
+4 -7
View File
@@ -1,18 +1,15 @@
package useCases package useCases
import ( import (
"GoCMS/adapters/secondary/gateways" "RenewCMS/domain/gateways"
"gorm.io/gorm"
) )
type DeleteUserUseCase struct { type DeleteUserUseCase struct {
userRepository gateways.UserRepository userRepository gateways.IUserRepository
} }
func NewDeleteUserUseCase(db *gorm.DB) *DeleteUserUseCase { func NewDeleteUserUseCase(userRepository gateways.IUserRepository) *DeleteUserUseCase {
return &DeleteUserUseCase{ return &DeleteUserUseCase{userRepository}
userRepository: *gateways.NewUserRepository(db),
}
} }
func (g *DeleteUserUseCase) DeleteUser(userId uint32) error { func (g *DeleteUserUseCase) DeleteUser(userId uint32) error {
+22
View File
@@ -0,0 +1,22 @@
package useCases
import (
"RenewCMS/domain/article"
"RenewCMS/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)
}
+5 -7
View File
@@ -1,19 +1,17 @@
package useCases package useCases
import ( import (
"GoCMS/adapters/secondary/gateways" "RenewCMS/domain/gateways"
) )
type GetPageUseCase struct { type GetPageUseCase struct {
pageRepository gateways.PageRepository pageRepository gateways.IPageRepository
} }
func NewGetPageUseCase() *GetPageUseCase { func NewGetPageUseCase(pageRepository gateways.IPageRepository) *GetPageUseCase {
return &GetPageUseCase{ return &GetPageUseCase{pageRepository}
pageRepository: *gateways.NewPageRepository(),
}
} }
func (g *GetPageUseCase) GetPage(name string, data interface{}) ([]byte, error) { func (g *GetPageUseCase) GetPage(name string, data any) ([]byte, error) {
return g.pageRepository.Get(name, data) return g.pageRepository.Get(name, data)
} }
-25
View File
@@ -1,25 +0,0 @@
package useCases
import (
"GoCMS/adapters/secondary/gateways"
"GoCMS/domain/post"
"gorm.io/gorm"
)
type GetPostUseCase struct {
postRepository gateways.PostRepository
}
func NewGetPostUseCase(db *gorm.DB) *GetPostUseCase {
return &GetPostUseCase{
postRepository: *gateways.NewPostRepository(db),
}
}
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)
}
+5 -8
View File
@@ -1,19 +1,16 @@
package useCases package useCases
import ( import (
"GoCMS/adapters/secondary/gateways" "RenewCMS/domain/gateways"
"GoCMS/domain/user" "RenewCMS/domain/user"
"gorm.io/gorm"
) )
type GetUserUseCase struct { type GetUserUseCase struct {
userRepository gateways.UserRepository userRepository gateways.IUserRepository
} }
func NewGetUserUseCase(db *gorm.DB) *GetUserUseCase { func NewGetUserUseCase(userRepository gateways.IUserRepository) *GetUserUseCase {
return &GetUserUseCase{ return &GetUserUseCase{userRepository}
userRepository: *gateways.NewUserRepository(db),
}
} }
func (g *GetUserUseCase) GetUser(id uint32) (user.User, error) { func (g *GetUserUseCase) GetUser(id uint32) (user.User, error) {
+18
View File
@@ -0,0 +1,18 @@
package useCases
import (
"RenewCMS/domain/article"
"RenewCMS/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()
}
-21
View File
@@ -1,21 +0,0 @@
package useCases
import (
"GoCMS/adapters/secondary/gateways"
"GoCMS/domain/post"
"gorm.io/gorm"
)
type ListPostsUseCase struct {
postRepository gateways.PostRepository
}
func NewListPostsUseCase(db *gorm.DB) *ListPostsUseCase {
return &ListPostsUseCase{
postRepository: *gateways.NewPostRepository(db),
}
}
func (g *ListPostsUseCase) ListPosts() []post.Post {
return g.postRepository.GetAll()
}
+5 -8
View File
@@ -1,19 +1,16 @@
package useCases package useCases
import ( import (
"GoCMS/adapters/secondary/gateways" "RenewCMS/domain/gateways"
"GoCMS/domain/user" "RenewCMS/domain/user"
"gorm.io/gorm"
) )
type ListUsersUseCase struct { type ListUsersUseCase struct {
userRepository gateways.UserRepository userRepository gateways.IUserRepository
} }
func NewListUsersUseCase(db *gorm.DB) *ListUsersUseCase { func NewListUsersUseCase(userRepository gateways.IUserRepository) *ListUsersUseCase {
return &ListUsersUseCase{ return &ListUsersUseCase{userRepository}
userRepository: *gateways.NewUserRepository(db),
}
} }
func (g *ListUsersUseCase) ListUsers() []user.User { func (g *ListUsersUseCase) ListUsers() []user.User {
+5 -5
View File
@@ -1,17 +1,17 @@
package useCases package useCases
import ( import (
"GoCMS/adapters/secondary/gateways" "RenewCMS/domain/gateways"
) )
type SendMailUseCase struct { type SendMailUseCase struct {
mailRepository gateways.MailRepository mailRepository gateways.IMailRepository
} }
func NewSendMailUseCase() *SendMailUseCase { func NewSendMailUseCase(mailRepository gateways.IMailRepository) *SendMailUseCase {
return &SendMailUseCase{} return &SendMailUseCase{mailRepository}
} }
func (g *SendMailUseCase) SendMail(receiverAddress string, templateName string, data interface{}) error { func (g *SendMailUseCase) SendMail(receiverAddress string, templateName string, data any) error {
return g.mailRepository.Send(receiverAddress, templateName, data) return g.mailRepository.Send(receiverAddress, templateName, data)
} }
+26
View File
@@ -0,0 +1,26 @@
package useCases
import (
"RenewCMS/domain/article"
"RenewCMS/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)
}
-29
View File
@@ -1,29 +0,0 @@
package useCases
import (
"GoCMS/adapters/secondary/gateways"
"GoCMS/domain/post"
"gorm.io/gorm"
)
type UpdatePostUseCase struct {
postRepository gateways.PostRepository
}
func NewUpdatePostUseCase(db *gorm.DB) *UpdatePostUseCase {
return &UpdatePostUseCase{
postRepository: *gateways.NewPostRepository(db),
}
}
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)
}
+5 -8
View File
@@ -1,19 +1,16 @@
package useCases package useCases
import ( import (
"GoCMS/adapters/secondary/gateways" "RenewCMS/domain/gateways"
"GoCMS/domain/user" "RenewCMS/domain/user"
"gorm.io/gorm"
) )
type UpdateUserUseCase struct { type UpdateUserUseCase struct {
userRepository gateways.UserRepository userRepository gateways.IUserRepository
} }
func NewUpdateUserUseCase(db *gorm.DB) *UpdateUserUseCase { func NewUpdateUserUseCase(userRepository gateways.IUserRepository) *UpdateUserUseCase {
return &UpdateUserUseCase{ return &UpdateUserUseCase{userRepository}
userRepository: *gateways.NewUserRepository(db),
}
} }
func (g *UpdateUserUseCase) UpdateVerificationStatus(userId uint32, isVerified bool) (user.User, error) { func (g *UpdateUserUseCase) UpdateVerificationStatus(userId uint32, isVerified bool) (user.User, error) {