mirror of
https://github.com/Floriansylvain/RenewCMS.git
synced 2026-08-19 19:53:21 +02:00
Compare commits
123
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4a6209a8db | ||
|
|
aee595dee9 | ||
|
|
9ecaa80fcf | ||
|
|
fbbce403e2 | ||
|
|
9c80cb9885 | ||
|
|
ce787d26ff | ||
|
|
76245c47c9 | ||
|
|
5282770caa | ||
|
|
b50efae8a8 | ||
|
|
e0a6706d4e | ||
|
|
0eba18e367 | ||
|
|
61a84201d8 | ||
|
|
6796ce63cb | ||
|
|
99812ddba4 | ||
|
|
0238c7511d | ||
|
|
d7ee0a488c | ||
|
|
c57394d4fd | ||
|
|
f47f145b6c | ||
|
|
83a1dec8ae | ||
|
|
b470bfcc67 | ||
|
|
cb9a431488 | ||
|
|
2292a8ef34 | ||
|
|
a77a5ad314 | ||
|
|
d895477b8c | ||
|
|
6563db43dc | ||
|
|
6fb666fc89 | ||
|
|
4d6054e22b | ||
|
|
7e29c08ea9 | ||
|
|
c69876a704 | ||
|
|
0759ab6d70 | ||
|
|
e4c2230b64 | ||
|
|
727418c8e6 | ||
|
|
f46737399f | ||
|
|
26984980b7 | ||
|
|
c25fb1b0a3 | ||
|
|
eb98a1003e | ||
|
|
46b816a8af | ||
|
|
ad1692d168 | ||
|
|
bcf3c19702 | ||
|
|
bfc310aeab | ||
|
|
adcd6fc08a | ||
|
|
ed5d52a506 | ||
|
|
5e9098aa20 | ||
|
|
7d2795a9e0 | ||
|
|
1b6690802f | ||
|
|
c1317e9f21 | ||
|
|
92c6d06d00 | ||
|
|
900ad3e8b8 | ||
|
|
24eefb743b | ||
|
|
29fce9a241 | ||
|
|
7097d71f45 | ||
|
|
a35e7c4283 | ||
|
|
aabdbfae95 | ||
|
|
6c54c9607c | ||
|
|
9c4d4fa87c | ||
|
|
a331c9173b | ||
|
|
830b062a76 | ||
|
|
3b57961946 | ||
|
|
c929acd4ab | ||
|
|
37647e60d9 | ||
|
|
1a75615be4 | ||
|
|
4d6a799c5d | ||
|
|
ec14eee439 | ||
|
|
8015639db5 | ||
|
|
d146363246 | ||
|
|
00a4e464ff | ||
|
|
dca17a744d | ||
|
|
2eae066a24 | ||
|
|
c90fa26c73 | ||
|
|
33cd947958 | ||
|
|
2805425317 | ||
|
|
411adc02b0 | ||
|
|
c663bcf4a9 | ||
|
|
777666a92f | ||
|
|
55d9e3bfc1 | ||
|
|
7d071ebc2e | ||
|
|
c2b64bf0fa | ||
|
|
ea942355ab | ||
|
|
e113c79a6d | ||
|
|
509329951f | ||
|
|
5bd4b4769f | ||
|
|
554fdc1989 | ||
|
|
46491a688b | ||
|
|
d91dc256a2 | ||
|
|
60e25cea2f | ||
|
|
a785483220 | ||
|
|
b414a07f6d | ||
|
|
245bf2fbf4 | ||
|
|
64ecbc6995 | ||
|
|
c6bd7731cc | ||
|
|
2525a64d0a | ||
|
|
04ebbfda87 | ||
|
|
5c61796313 | ||
|
|
e575062209 | ||
|
|
1539e06ec8 | ||
|
|
869fb3f145 | ||
|
|
7e8b415cab | ||
|
|
047e14df60 | ||
|
|
8faf3f8d9d | ||
|
|
542bcded3e | ||
|
|
46daadb8e4 | ||
|
|
0466da2d4b | ||
|
|
318fd0f53a | ||
|
|
2ddabe2222 | ||
|
|
d953a66f22 | ||
|
|
4b918aa2fa | ||
|
|
7611738b48 | ||
|
|
366ae190ff | ||
|
|
96a64097c4 | ||
|
|
0c139b1f72 | ||
|
|
209555498f | ||
|
|
61f221e49c | ||
|
|
1d1adfdfc7 | ||
|
|
836847e9b2 | ||
|
|
e3246e337d | ||
|
|
dad0c906f0 | ||
|
|
91d82b0ad7 | ||
|
|
f33f53ca76 | ||
|
|
e258dccc1c | ||
|
|
195c247235 | ||
|
|
5b4b5a8622 | ||
|
|
4187f0b2e2 | ||
|
|
1328758984 |
@@ -0,0 +1,51 @@
|
||||
root = "."
|
||||
testdata_dir = "testdata"
|
||||
tmp_dir = "tmp"
|
||||
|
||||
[build]
|
||||
args_bin = []
|
||||
bin = "tmp/main.exe"
|
||||
cmd = "go build -o ./tmp/main.exe ./cmd/server"
|
||||
delay = 1000
|
||||
exclude_dir = ["assets", "tmp", "vendor", "testdata", "api\\static\\tinymce", "api\\static\\bootstrap-icons", "api\\static\\uploadedImages"]
|
||||
exclude_file = []
|
||||
exclude_regex = ["_test.go"]
|
||||
exclude_unchanged = false
|
||||
follow_symlink = false
|
||||
full_bin = ""
|
||||
include_dir = []
|
||||
include_ext = ["go", "tpl", "tmpl", "html"]
|
||||
include_file = []
|
||||
kill_delay = "0s"
|
||||
log = "build-errors.log"
|
||||
poll = false
|
||||
poll_interval = 0
|
||||
post_cmd = []
|
||||
pre_cmd = []
|
||||
rerun = false
|
||||
rerun_delay = 500
|
||||
send_interrupt = false
|
||||
stop_on_error = false
|
||||
|
||||
[color]
|
||||
app = ""
|
||||
build = "yellow"
|
||||
main = "magenta"
|
||||
runner = "green"
|
||||
watcher = "cyan"
|
||||
|
||||
[log]
|
||||
main_only = false
|
||||
time = false
|
||||
|
||||
[misc]
|
||||
clean_on_exit = false
|
||||
|
||||
[proxy]
|
||||
app_port = 0
|
||||
enabled = false
|
||||
proxy_port = 0
|
||||
|
||||
[screen]
|
||||
clear_on_rebuild = false
|
||||
keep_scroll = true
|
||||
@@ -0,0 +1,12 @@
|
||||
ENVIRONMENT=development
|
||||
HOST=example.com
|
||||
PORT=8080
|
||||
JWT_SECRET=abc123
|
||||
CORS_ALLOWED_ORIGINS=*
|
||||
DB_FILE=./renewcms.db
|
||||
DOCKER_DB_FOLDER=./data
|
||||
UPLOAD_DIR=./api/static/uploadedImages/
|
||||
SMTP_EMAIL=a@b.c
|
||||
SMTP_PASSWORD=12341234
|
||||
SMTP_HOST=mail.service.abc
|
||||
SMTP_PORT=465
|
||||
@@ -0,0 +1 @@
|
||||
api/static/** linguist-vendored
|
||||
@@ -0,0 +1,40 @@
|
||||
# This workflow will build a golang project
|
||||
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-go
|
||||
|
||||
name: Go
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: ["main"]
|
||||
pull_request:
|
||||
branches: ["main"]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
environment: test
|
||||
env:
|
||||
ENVIRONMENT: ${{ secrets.ENVIRONMENT }}
|
||||
HOST: ${{ secrets.HOST }}
|
||||
PORT: ${{ secrets.PORT }}
|
||||
JWT_SECRET: ${{ secrets.JWT_SECRET }}
|
||||
CORS_ALLOWED_ORIGINS: ${{ secrets.CORS_ALLOWED_ORIGINS }}
|
||||
DB_FILE: ${{ secrets.DB_FILE }}
|
||||
DOCKER_DB_FOLDER: ${{ secrets.DOCKER_DB_FOLDER }}
|
||||
SMTP_EMAIL: ${{ secrets.SMTP_EMAIL }}
|
||||
SMTP_PASSWORD: ${{ secrets.SMTP_PASSWORD }}
|
||||
SMTP_HOST: ${{ secrets.SMTP_HOST }}
|
||||
SMTP_PORT: ${{ secrets.SMTP_PORT }}
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version: "1.23.0"
|
||||
|
||||
- name: Build
|
||||
run: go build -v ./...
|
||||
|
||||
- name: Test
|
||||
run: go test -v ./...
|
||||
+15
-28
@@ -1,31 +1,18 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
|
||||
node_modules
|
||||
.DS_Store
|
||||
dist
|
||||
dist-ssr
|
||||
coverage
|
||||
*.local
|
||||
|
||||
/cypress/videos/
|
||||
/cypress/screenshots/
|
||||
|
||||
# Editor directories and files
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
# IDE
|
||||
.idea
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
.vscode
|
||||
|
||||
mongodbdata
|
||||
# DB
|
||||
*.db
|
||||
|
||||
# Build
|
||||
bin
|
||||
dist/
|
||||
|
||||
# Environment
|
||||
.env
|
||||
|
||||
# Generated files
|
||||
tmp/
|
||||
uploadedImages/
|
||||
*.exe
|
||||
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
FROM golang:1.20.5-alpine
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY go.mod go.sum ./
|
||||
COPY adapters ./adapters
|
||||
COPY api ./api
|
||||
COPY domain ./domain
|
||||
COPY main ./main
|
||||
COPY useCases ./useCases
|
||||
COPY .env ./.env
|
||||
|
||||
RUN go mod download
|
||||
RUN go build -o startServer ./main
|
||||
|
||||
CMD ["./startServer"]
|
||||
@@ -1,14 +0,0 @@
|
||||
FROM golang:1.19
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY go.mod ./
|
||||
COPY go.sum ./
|
||||
RUN go mod download
|
||||
|
||||
ADD cmd ./cmd/
|
||||
ADD internal ./internal/
|
||||
COPY .env ./
|
||||
RUN go build -o ./GohCMS/ ./...
|
||||
|
||||
CMD ["./GohCMS/cmd"]
|
||||
@@ -1,12 +0,0 @@
|
||||
FROM node:latest as build-stage
|
||||
WORKDIR /app
|
||||
COPY ./web/admin-gui/package*.json ./
|
||||
COPY ./.env ./
|
||||
RUN npm install
|
||||
COPY ./web/admin-gui/ .
|
||||
RUN npm run build
|
||||
|
||||
FROM nginx as production-stage
|
||||
RUN mkdir /app
|
||||
COPY --from=build-stage /app/dist /app
|
||||
COPY ./web/admin-gui/nginx.conf /etc/nginx/nginx.conf
|
||||
@@ -1,4 +1,4 @@
|
||||
# GohCMS
|
||||
# RenewCMS
|
||||
|
||||
## 🚧 This project is under development !
|
||||
|
||||
@@ -10,29 +10,48 @@ Don't forget to setup the [Environment variables](#environment-variables)!
|
||||
|
||||
### Use with Docker
|
||||
|
||||
To **run** the app, run the command
|
||||
#### Compose
|
||||
|
||||
```shell
|
||||
docker-compose up
|
||||
The easiest way to run the project is to use docker-compose.
|
||||
You still need to set up the environment variables, but you can use the `.env` file for that.
|
||||
Note that some supplementary variables are required for docker-compose to work, like `DOCKER_DB_FOLDER`.
|
||||
|
||||
```bash
|
||||
docker compose up --build -d
|
||||
```
|
||||
|
||||
### Build and run it yourself
|
||||
|
||||
TODO
|
||||
#### Build
|
||||
|
||||
There are 2 scripts to build the project:
|
||||
|
||||
- `super_build.sh`
|
||||
- `super_build.bat`
|
||||
|
||||
They will build the project for windows, linux and macOS. The binaries will be in the `bin` folder.
|
||||
You can also really just use `go build` to build the project for your current OS.
|
||||
|
||||
#### Run
|
||||
|
||||
Once the project is built, you can simply run the binary for your OS. Setting the environment variables is
|
||||
of course required, but not necessarily via the `.env` file.
|
||||
|
||||
### Environment variables
|
||||
|
||||
- ./env:
|
||||
|
||||
```
|
||||
APP_API_ADDRESS=http://example.com
|
||||
APP_FRONT_ADDRESS=http://example.com
|
||||
APP_BASE_API_PATH=/example
|
||||
APP_BASE_FRONT_PATH=/example
|
||||
APP_API_PORT=1234
|
||||
APP_FRONT_PORT=1234
|
||||
APP_JWT_SECRET=secret
|
||||
```
|
||||
| Name | Type | Description | Comment |
|
||||
|----------------------|--------|:----------------------------------------------|-----------------------------------------------------------------------------------------------------------------------|
|
||||
| ENVIRONMENT | string | environment the API is running in | required, `development` or `production` |
|
||||
| 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 |
|
||||
| JWT_SECRET | string | secret for the jwt auth | required |
|
||||
| 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. `./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 |
|
||||
| SMTP_EMAIL | string | sender email | required |
|
||||
| SMTP_PASSWORD | string | smtp account password | required |
|
||||
| SMTP_HOST | string | smtp server address | required |
|
||||
| SMTP_PORT | int | smtp server port | required |
|
||||
|
||||
## API Usage
|
||||
|
||||
@@ -41,3 +60,7 @@ TODO
|
||||
## Demo
|
||||
|
||||
TODO
|
||||
|
||||
## TODOs
|
||||
|
||||
- Cancel button when email validation pending or expired
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
domainArticle "RenewCMS/internal/domain/article"
|
||||
domainImage "RenewCMS/internal/domain/image"
|
||||
domainMail "RenewCMS/internal/domain/mail"
|
||||
domainUser "RenewCMS/internal/domain/user"
|
||||
"RenewCMS/internal/infrastructure/mailer"
|
||||
"RenewCMS/internal/infrastructure/persistence/models"
|
||||
persistence "RenewCMS/internal/infrastructure/persistence/repositories"
|
||||
"RenewCMS/internal/infrastructure/useCases"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/glebarez/sqlite"
|
||||
"github.com/go-chi/jwtauth/v5"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type UseCases struct {
|
||||
Token *jwtauth.JWTAuth
|
||||
CreateArticleUseCase *useCases.CreateArticleUseCase
|
||||
GetArticleUseCase *useCases.GetArticleUseCase
|
||||
ListArticlesUseCase *useCases.ListArticlesUseCase
|
||||
UpdateArticleUseCase *useCases.UpdateArticleUseCase
|
||||
DeleteArticleUseCase *useCases.DeleteArticleUseCase
|
||||
GetUserUseCase *useCases.GetUserUseCase
|
||||
CreateUserUseCase *useCases.CreateUserUseCase
|
||||
UpdateUserUseCase *useCases.UpdateUserUseCase
|
||||
DeleteUserUseCase *useCases.DeleteUserUseCase
|
||||
ListUsersUseCase *useCases.ListUsersUseCase
|
||||
SendMailUseCase *useCases.SendMailUseCase
|
||||
CreateImageUseCase *useCases.CreateImageUseCase
|
||||
DeleteImageUseCase *useCases.DeleteImageUseCase
|
||||
}
|
||||
|
||||
type Repositories struct {
|
||||
ArticleRepo domainArticle.Repository
|
||||
UserRepo domainUser.Repository
|
||||
ImageRepo domainImage.Repository
|
||||
MailRepo domainMail.Repository
|
||||
}
|
||||
|
||||
func getDb() *gorm.DB {
|
||||
dbName := os.Getenv("DB_FILE")
|
||||
if err := os.MkdirAll(filepath.Dir(dbName), os.ModePerm); err != nil {
|
||||
panic("Unable to create necessary subdirectories: " + err.Error())
|
||||
}
|
||||
db, err := gorm.Open(sqlite.Open(dbName), &gorm.Config{})
|
||||
if err != nil {
|
||||
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
|
||||
}
|
||||
|
||||
func getRepositories(db *gorm.DB) *Repositories {
|
||||
return &Repositories{
|
||||
ArticleRepo: persistence.NewArticleRepository(db),
|
||||
UserRepo: persistence.NewUserRepository(db),
|
||||
ImageRepo: persistence.NewImageRepository(db),
|
||||
MailRepo: mailer.NewMailRepository(),
|
||||
}
|
||||
}
|
||||
|
||||
func getUseCases(repos *Repositories) *UseCases {
|
||||
return &UseCases{
|
||||
Token: jwtauth.New("HS256", []byte(os.Getenv("JWT_SECRET")), nil),
|
||||
CreateArticleUseCase: useCases.NewCreateArticleUseCase(repos.ArticleRepo),
|
||||
GetArticleUseCase: useCases.NewGetArticleUseCase(repos.ArticleRepo),
|
||||
ListArticlesUseCase: useCases.NewListArticlesUseCase(repos.ArticleRepo),
|
||||
UpdateArticleUseCase: useCases.NewUpdateArticleUseCase(repos.ArticleRepo),
|
||||
DeleteArticleUseCase: useCases.NewDeleteArticleUseCase(repos.ArticleRepo),
|
||||
GetUserUseCase: useCases.NewGetUserUseCase(repos.UserRepo),
|
||||
CreateUserUseCase: useCases.NewCreateUserUseCase(repos.UserRepo),
|
||||
UpdateUserUseCase: useCases.NewUpdateUserUseCase(repos.UserRepo),
|
||||
DeleteUserUseCase: useCases.NewDeleteUserUseCase(repos.UserRepo),
|
||||
ListUsersUseCase: useCases.NewListUsersUseCase(repos.UserRepo),
|
||||
SendMailUseCase: useCases.NewSendMailUseCase(repos.MailRepo),
|
||||
CreateImageUseCase: useCases.NewCreateImageUseCase(repos.ImageRepo),
|
||||
DeleteImageUseCase: useCases.NewDeleteImageUseCase(repos.ImageRepo),
|
||||
}
|
||||
}
|
||||
|
||||
func InitContainer() *UseCases {
|
||||
db := getDb()
|
||||
repos := getRepositories(db)
|
||||
return getUseCases(repos)
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
package article
|
||||
|
||||
import (
|
||||
"RenewCMS/api/pkg/apperr"
|
||||
validator "RenewCMS/api/pkg/validator"
|
||||
domain "RenewCMS/internal/domain/article"
|
||||
"RenewCMS/internal/infrastructure/useCases"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
)
|
||||
|
||||
type Handler struct {
|
||||
CreateUseCase *useCases.CreateArticleUseCase
|
||||
GetUseCase *useCases.GetArticleUseCase
|
||||
ListUseCase *useCases.ListArticlesUseCase
|
||||
DeleteUseCase *useCases.DeleteArticleUseCase
|
||||
}
|
||||
|
||||
type PostArticle struct {
|
||||
Title string `json:"title" validate:"required,min=3,max=50"`
|
||||
Body string `json:"body" validate:"required,max=10000"`
|
||||
}
|
||||
|
||||
func NewHandler(create *useCases.CreateArticleUseCase, get *useCases.GetArticleUseCase, list *useCases.ListArticlesUseCase, delete *useCases.DeleteArticleUseCase) *Handler {
|
||||
return &Handler{
|
||||
CreateUseCase: create,
|
||||
GetUseCase: get,
|
||||
ListUseCase: list,
|
||||
DeleteUseCase: delete,
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Handler) GetArticle(w http.ResponseWriter, r *http.Request) {
|
||||
id, err := strconv.ParseUint(chi.URLParam(r, "id"), 10, 32)
|
||||
if err != nil {
|
||||
http.Error(w, apperr.IdUint32ErrorMessage, http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
article, err := h.GetUseCase.GetOnlineArticle(uint32(id))
|
||||
if err != nil {
|
||||
if errors.Is(err, domain.ErrArticleNotFound) {
|
||||
http.Error(w, "Article not found", http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
http.Error(w, "Internal server error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
articleJson, err := json.Marshal(article)
|
||||
if err != nil {
|
||||
http.Error(w, "Failed to encode response", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
if _, err := w.Write(articleJson); err != nil {
|
||||
fmt.Printf("Failed to write response: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Handler) PostArticle(w http.ResponseWriter, r *http.Request) {
|
||||
var localArticle PostArticle
|
||||
if err := json.NewDecoder(r.Body).Decode(&localArticle); err != nil {
|
||||
http.Error(w, apperr.BodyErrorMessage, http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
if err := validator.Validate.Struct(localArticle); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
createdArticle, err := h.CreateUseCase.CreateArticle(useCases.CreateArticleCommand{
|
||||
Title: localArticle.Title,
|
||||
Body: localArticle.Body,
|
||||
})
|
||||
if err != nil {
|
||||
http.Error(w, "Failed to create article", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
articleJson, err := json.Marshal(createdArticle)
|
||||
if err != nil {
|
||||
http.Error(w, "Failed to encode response", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusCreated)
|
||||
if _, err := w.Write(articleJson); err != nil {
|
||||
log.Printf("Failed to write response: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Handler) ListArticles(w http.ResponseWriter, _ *http.Request) {
|
||||
articles, err := h.ListUseCase.ListOnlineArticles()
|
||||
if err != nil {
|
||||
http.Error(w, "Failed to retrieve articles", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
articlesJson, err := json.Marshal(articles)
|
||||
if err != nil {
|
||||
http.Error(w, "Failed to encode response", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
if _, err := w.Write(articlesJson); err != nil {
|
||||
log.Printf("Failed to write response: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Handler) DeleteArticle(w http.ResponseWriter, r *http.Request) {
|
||||
id, err := strconv.ParseUint(chi.URLParam(r, "id"), 10, 32)
|
||||
if err != nil {
|
||||
http.Error(w, apperr.IdUint32ErrorMessage, http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.DeleteUseCase.DeleteArticle(uint32(id)); err != nil {
|
||||
http.Error(w, "Failed to delete article", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
}
|
||||
@@ -0,0 +1,196 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"RenewCMS/api/pkg/apperr"
|
||||
validator "RenewCMS/api/pkg/validator"
|
||||
"RenewCMS/internal/domain/user"
|
||||
"RenewCMS/internal/infrastructure/useCases"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/go-chi/jwtauth/v5"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
)
|
||||
|
||||
type Handler struct {
|
||||
Token *jwtauth.JWTAuth
|
||||
GetUseCase *useCases.GetUserUseCase
|
||||
ListUseCase *useCases.ListUsersUseCase
|
||||
CreateUseCase *useCases.CreateUserUseCase
|
||||
}
|
||||
|
||||
type RegisterCredentials struct {
|
||||
Username string `json:"username" validate:"required,min=3,max=20"`
|
||||
Password string `json:"password" validate:"required,min=8"`
|
||||
Email string `json:"email" validate:"required,email"`
|
||||
}
|
||||
|
||||
type LoginCredentials struct {
|
||||
Username string `json:"username" validate:"required,min=3,max=20"`
|
||||
Password string `json:"password" validate:"required,min=8"`
|
||||
}
|
||||
|
||||
func shouldCookieBeSecure() bool {
|
||||
return os.Getenv("ENVIRONMENT") == "production"
|
||||
}
|
||||
|
||||
func removeJwtCookie(w http.ResponseWriter) {
|
||||
http.SetCookie(w, &http.Cookie{
|
||||
Name: "jwt",
|
||||
Value: "",
|
||||
Expires: time.Now(),
|
||||
MaxAge: -1,
|
||||
Secure: shouldCookieBeSecure(),
|
||||
HttpOnly: true,
|
||||
Path: "/",
|
||||
})
|
||||
}
|
||||
|
||||
func NewHandler(token *jwtauth.JWTAuth, get *useCases.GetUserUseCase, list *useCases.ListUsersUseCase, create *useCases.CreateUserUseCase) *Handler {
|
||||
return &Handler{
|
||||
Token: token,
|
||||
GetUseCase: get,
|
||||
ListUseCase: list,
|
||||
CreateUseCase: create,
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Handler) SetJwtCookie(w *http.ResponseWriter, userId uint32) error {
|
||||
_, tokenString, err := h.Token.Encode(map[string]any{"user_id": userId})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
http.SetCookie(*w, &http.Cookie{
|
||||
Name: "jwt",
|
||||
Value: tokenString,
|
||||
Expires: time.Now().Add(24 * time.Hour),
|
||||
Secure: shouldCookieBeSecure(),
|
||||
HttpOnly: true,
|
||||
Path: "/",
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *Handler) SomeUsersVerified() bool {
|
||||
users := h.ListUseCase.ListUsers()
|
||||
for _, localUser := range users {
|
||||
if localUser.IsVerified {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (h *Handler) IsUserTableEmpty() bool {
|
||||
users := h.ListUseCase.ListUsers()
|
||||
return len(users) == 0
|
||||
}
|
||||
|
||||
func (h *Handler) IsLoggedIn(r *http.Request) bool {
|
||||
token, err := jwtauth.VerifyRequest(
|
||||
h.Token,
|
||||
r,
|
||||
jwtauth.TokenFromCookie,
|
||||
jwtauth.TokenFromHeader,
|
||||
jwtauth.TokenFromQuery)
|
||||
return token != nil && err == nil
|
||||
}
|
||||
|
||||
func (h *Handler) IsVerified(r *http.Request) bool {
|
||||
token, err := jwtauth.VerifyRequest(
|
||||
h.Token,
|
||||
r,
|
||||
jwtauth.TokenFromCookie,
|
||||
jwtauth.TokenFromHeader,
|
||||
jwtauth.TokenFromQuery)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
userId := token.PrivateClaims()["user_id"].(float64)
|
||||
currentUser, _ := h.GetUseCase.GetUser(uint32(userId))
|
||||
return currentUser.IsVerified
|
||||
}
|
||||
|
||||
func (h *Handler) GetUserFromCredentials(credentials LoginCredentials) (user.User, error) {
|
||||
dbUser, err := h.GetUseCase.GetUserByUsername(credentials.Username)
|
||||
if err != nil {
|
||||
return user.User{}, err
|
||||
}
|
||||
|
||||
err = bcrypt.CompareHashAndPassword([]byte(dbUser.Password), []byte(credentials.Password))
|
||||
if err != nil {
|
||||
return user.User{}, err
|
||||
}
|
||||
|
||||
return dbUser, nil
|
||||
}
|
||||
|
||||
func (h *Handler) Login(w http.ResponseWriter, r *http.Request) {
|
||||
var credentials LoginCredentials
|
||||
|
||||
err := json.NewDecoder(r.Body).Decode(&credentials)
|
||||
if err != nil {
|
||||
http.Error(w, apperr.BodyErrorMessage, http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
err = validator.Validate.Struct(credentials)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
dbUser, err := h.GetUserFromCredentials(credentials)
|
||||
if err != nil {
|
||||
http.Error(w, apperr.LogsErrorMessage, http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
|
||||
_ = h.SetJwtCookie(&w, dbUser.ID)
|
||||
|
||||
message, _ := json.Marshal(map[string]any{"message": "User logged in! HTTPonly jwt cookie created"})
|
||||
_, _ = w.Write(message)
|
||||
}
|
||||
|
||||
func (h *Handler) Register(w http.ResponseWriter, r *http.Request) {
|
||||
if h.SomeUsersVerified() {
|
||||
http.Error(w, "You are not allowed to create a user. Log in or reset database.", http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
|
||||
var credentials RegisterCredentials
|
||||
err := json.NewDecoder(r.Body).Decode(&credentials)
|
||||
if err != nil {
|
||||
http.Error(w, apperr.BodyErrorMessage, http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
err = validator.Validate.Struct(credentials)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
createdUser, err := h.CreateUseCase.CreateUser(useCases.CreateUserCommand{
|
||||
Username: credentials.Username,
|
||||
Password: credentials.Password,
|
||||
Email: credentials.Email,
|
||||
})
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
_ = h.SetJwtCookie(&w, createdUser.ID)
|
||||
|
||||
message, _ := json.Marshal(map[string]any{"message": "User registered! HTTPonly jwt cookie created"})
|
||||
_, _ = w.Write(message)
|
||||
}
|
||||
|
||||
func (h *Handler) Logout(w http.ResponseWriter, _ *http.Request) {
|
||||
removeJwtCookie(w)
|
||||
message, _ := json.Marshal(map[string]any{"message": "User logged out! HTTPonly jwt cookie deleted"})
|
||||
_, _ = w.Write(message)
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
package frontend
|
||||
|
||||
import (
|
||||
"RenewCMS/internal/infrastructure/assets"
|
||||
"io"
|
||||
"io/fs"
|
||||
"net/http"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
)
|
||||
|
||||
type spaHandler struct {
|
||||
staticFS fs.FS
|
||||
modTime time.Time
|
||||
}
|
||||
|
||||
func NewFrontendRouter() http.Handler {
|
||||
r := chi.NewRouter()
|
||||
|
||||
distFS, _ := fs.Sub(assets.DistEmbed, "dist")
|
||||
handler := &spaHandler{
|
||||
staticFS: distFS,
|
||||
modTime: time.Now(),
|
||||
}
|
||||
|
||||
r.Get("/*", handler.ServeHTTP)
|
||||
return r
|
||||
}
|
||||
|
||||
func (h *spaHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
path := h.cleanPath(r.URL.Path)
|
||||
if h.fileExists(path) {
|
||||
h.serveAsset(w, r, path)
|
||||
return
|
||||
}
|
||||
h.serveIndex(w, r)
|
||||
}
|
||||
|
||||
func (h *spaHandler) cleanPath(path string) string {
|
||||
return strings.TrimPrefix(filepath.Clean(path), "/")
|
||||
}
|
||||
|
||||
func (h *spaHandler) fileExists(path string) bool {
|
||||
if path == "" || path == "." {
|
||||
return false
|
||||
}
|
||||
f, err := h.staticFS.Open(path)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
defer f.Close()
|
||||
return true
|
||||
}
|
||||
|
||||
func (h *spaHandler) serveAsset(w http.ResponseWriter, r *http.Request, path string) {
|
||||
w.Header().Set("Cache-Control", "public, max-age=31536000, immutable")
|
||||
http.FileServer(http.FS(h.staticFS)).ServeHTTP(w, r)
|
||||
}
|
||||
|
||||
func (h *spaHandler) serveIndex(w http.ResponseWriter, r *http.Request) {
|
||||
f, err := h.staticFS.Open("index.html")
|
||||
if err != nil {
|
||||
http.Error(w, "Index not found", http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
content, ok := f.(io.ReadSeeker)
|
||||
if !ok {
|
||||
http.Error(w, "File not seekable", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Set("Cache-Control", "no-store, no-cache, must-revalidate")
|
||||
http.ServeContent(w, r, "index.html", h.modTime, content)
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
package image
|
||||
|
||||
import (
|
||||
"RenewCMS/internal/domain/image"
|
||||
"RenewCMS/internal/infrastructure/useCases"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
)
|
||||
|
||||
type Handler struct {
|
||||
CreateUseCase *useCases.CreateImageUseCase
|
||||
UpdateUseCase *useCases.UpdateArticleUseCase
|
||||
}
|
||||
|
||||
func NewHandler(create *useCases.CreateImageUseCase, update *useCases.UpdateArticleUseCase) *Handler {
|
||||
return &Handler{
|
||||
CreateUseCase: create,
|
||||
UpdateUseCase: update,
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Handler) PostImage(w http.ResponseWriter, r *http.Request) {
|
||||
_ = r.ParseForm()
|
||||
|
||||
id := chi.URLParam(r, "id")
|
||||
idInt, _ := strconv.Atoi(id)
|
||||
|
||||
file, fileHeader, err := r.FormFile("file")
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
newImage, err := h.CreateUseCase.CreateImage(image.ImageInput{
|
||||
Content: file,
|
||||
Filename: fileHeader.Filename,
|
||||
Size: fileHeader.Size,
|
||||
ContentType: fileHeader.Header.Get("Content-Type"),
|
||||
})
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
err = h.UpdateUseCase.AddImage(uint32(idInt), newImage.ID)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
newJson := map[string]any{"location": newImage.Path}
|
||||
newJsonBytes, _ := json.Marshal(newJson)
|
||||
|
||||
_, _ = w.Write(newJsonBytes)
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package middleware
|
||||
|
||||
import "net/http"
|
||||
|
||||
func JsonContentTypeMiddleware(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
next.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
package apperr
|
||||
|
||||
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 IdUint32ErrorMessage = "The server expects the ID to be in the format of an unsigned 32-bit integer (uint32)."
|
||||
@@ -0,0 +1,9 @@
|
||||
package validator
|
||||
|
||||
import "github.com/go-playground/validator/v10"
|
||||
|
||||
var Validate *validator.Validate
|
||||
|
||||
func InitValidator() {
|
||||
Validate = validator.New()
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"RenewCMS/api/handlers/article"
|
||||
"RenewCMS/api/handlers/auth"
|
||||
"RenewCMS/api/handlers/frontend"
|
||||
"RenewCMS/api/middleware"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/MadAppGang/httplog"
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/go-chi/cors"
|
||||
"github.com/go-chi/jwtauth/v5"
|
||||
)
|
||||
|
||||
func enableLoggingIfDev(r *chi.Mux, routerName string) {
|
||||
if os.Getenv("ENVIRONMENT") == "development" {
|
||||
r.Use(httplog.LoggerWithName(routerName))
|
||||
}
|
||||
}
|
||||
|
||||
func GetHelloWorld(w http.ResponseWriter, _ *http.Request) {
|
||||
msg, _ := json.Marshal(map[string]string{"message": "Hello World"})
|
||||
_, _ = w.Write(msg)
|
||||
}
|
||||
|
||||
func NewArticleRouter(container *UseCases) http.Handler {
|
||||
r := chi.NewRouter()
|
||||
|
||||
articleHandler := article.NewHandler(container.CreateArticleUseCase, container.GetArticleUseCase, container.ListArticlesUseCase, container.DeleteArticleUseCase)
|
||||
|
||||
r.Get("/{id}", articleHandler.GetArticle)
|
||||
r.Post("/", articleHandler.PostArticle)
|
||||
r.Get("/", articleHandler.ListArticles)
|
||||
r.Delete("/{id}", articleHandler.DeleteArticle)
|
||||
return r
|
||||
}
|
||||
|
||||
func NewAuthRouter(container *UseCases) http.Handler {
|
||||
r := chi.NewRouter()
|
||||
|
||||
authHandler := auth.NewHandler(container.Token, container.GetUserUseCase, container.ListUsersUseCase, container.CreateUserUseCase)
|
||||
|
||||
r.Post("/register", authHandler.Register)
|
||||
r.Post("/login", authHandler.Login)
|
||||
r.Post("/logout", authHandler.Logout)
|
||||
return r
|
||||
}
|
||||
|
||||
func InitBackendRoutes(container *UseCases) *chi.Mux {
|
||||
r := chi.NewRouter()
|
||||
|
||||
enableLoggingIfDev(r, "backend")
|
||||
|
||||
r.Use(middleware.JsonContentTypeMiddleware)
|
||||
r.Get("/", GetHelloWorld)
|
||||
r.Group(func(r chi.Router) {
|
||||
r.Use(jwtauth.Verifier(container.Token))
|
||||
r.Use(jwtauth.Authenticator(container.Token))
|
||||
r.Mount("/article", NewArticleRouter(container))
|
||||
})
|
||||
r.Mount("/auth", NewAuthRouter(container))
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
func InitFrontendRoutes() *chi.Mux {
|
||||
r := chi.NewRouter()
|
||||
|
||||
enableLoggingIfDev(r, "frontend")
|
||||
|
||||
r.Mount("/", frontend.NewFrontendRouter())
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
func InitRoutes(container *UseCases) *chi.Mux {
|
||||
backend := InitBackendRoutes(container)
|
||||
frontend := InitFrontendRoutes()
|
||||
|
||||
apiRouter := chi.NewRouter()
|
||||
apiRouter.Use(cors.Handler(cors.Options{
|
||||
AllowedOrigins: strings.Split(os.Getenv("CORS_ALLOWED_ORIGINS"), ";"),
|
||||
AllowedMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"},
|
||||
AllowedHeaders: []string{"Accept", "Authorization", "Content-Type", "X-CSRF-Token"},
|
||||
ExposedHeaders: []string{"Link"},
|
||||
AllowCredentials: false,
|
||||
MaxAge: 300,
|
||||
}))
|
||||
apiRouter.Mount("/v1", backend)
|
||||
apiRouter.Mount("/", frontend)
|
||||
|
||||
return apiRouter
|
||||
}
|
||||
-99
@@ -1,99 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/Floriansylvain/GohCMS/internal/api"
|
||||
"github.com/Floriansylvain/GohCMS/internal/articles"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/joho/godotenv"
|
||||
)
|
||||
|
||||
var (
|
||||
ginMode string
|
||||
apiPort string
|
||||
apiBaseUrl string
|
||||
frontAddress string
|
||||
)
|
||||
|
||||
func initEnvVariables() {
|
||||
if godotenv.Load() != nil {
|
||||
panic("Error loading .env file.")
|
||||
}
|
||||
|
||||
ginMode = os.Getenv("APP_GIN_MODE")
|
||||
apiPort = os.Getenv("APP_API_PORT")
|
||||
apiBaseUrl = os.Getenv("APP_BASE_API_PATH")
|
||||
frontAddress = os.Getenv("APP_FRONT_ADDRESS")
|
||||
}
|
||||
|
||||
func initJWT() {
|
||||
errInit := api.AuthMiddleware.MiddlewareInit()
|
||||
if errInit != nil {
|
||||
fmt.Printf(errInit.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func initBasicRoutes(r *gin.RouterGroup) {
|
||||
r.POST("/login/", api.AuthMiddleware.LoginHandler)
|
||||
r.POST("/logout/", api.AuthMiddleware.LogoutHandler)
|
||||
r.GET("/ping/", api.Ping)
|
||||
}
|
||||
|
||||
func corsMiddleware(c *gin.Context) {
|
||||
c.Writer.Header().Set("Access-Control-Allow-Origin", frontAddress)
|
||||
c.Writer.Header().Set("Access-Control-Allow-Credentials", "true")
|
||||
c.Writer.Header().Set("Access-Control-Allow-Headers", "Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization, accept, origin, Cache-Control, X-Requested-With")
|
||||
c.Writer.Header().Set("Access-Control-Allow-Methods", "POST, OPTIONS, GET, PUT")
|
||||
if c.Request.Method == "OPTIONS" {
|
||||
c.AbortWithStatus(204)
|
||||
return
|
||||
}
|
||||
c.Next()
|
||||
}
|
||||
|
||||
func jwtProxyMiddleware(c *gin.Context) {
|
||||
jwtToken, _ := c.Cookie("jwt")
|
||||
c.Request.Header.Set("Authorization", fmt.Sprintf("Bearer %v", jwtToken))
|
||||
c.Next()
|
||||
}
|
||||
|
||||
func initArticlesRoutes(r *gin.RouterGroup) {
|
||||
articlesRouter := r.Group("/articles")
|
||||
articlesRouter.Use(corsMiddleware, api.AuthMiddleware.MiddlewareFunc())
|
||||
|
||||
articlesRouter.GET("/", articles.Get)
|
||||
articlesRouter.GET("/:id", articles.GetUnique)
|
||||
articlesRouter.POST("/:id", articles.Add)
|
||||
articlesRouter.PUT("/:id", articles.Edit)
|
||||
articlesRouter.DELETE("/:id", articles.Delete)
|
||||
}
|
||||
|
||||
func initGin() {
|
||||
r := gin.Default()
|
||||
r.Use(jwtProxyMiddleware, corsMiddleware)
|
||||
|
||||
var router *gin.RouterGroup
|
||||
if apiBaseUrl != "" {
|
||||
router = r.Group(apiBaseUrl)
|
||||
} else {
|
||||
router = &r.RouterGroup
|
||||
}
|
||||
|
||||
if ginMode == "release" {
|
||||
gin.SetMode(ginMode)
|
||||
api.AuthMiddleware.SecureCookie = true
|
||||
}
|
||||
|
||||
initBasicRoutes(router)
|
||||
initArticlesRoutes(router)
|
||||
|
||||
r.Run(":" + apiPort)
|
||||
}
|
||||
|
||||
func main() {
|
||||
initEnvVariables()
|
||||
initGin()
|
||||
initJWT()
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"RenewCMS/api"
|
||||
validator "RenewCMS/api/pkg/validator"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/joho/godotenv"
|
||||
)
|
||||
|
||||
var possibleEnvFileLocations = []string{".env", "../.env"}
|
||||
var envVarsToLoad = []string{
|
||||
"HOST",
|
||||
"PORT",
|
||||
"JWT_SECRET",
|
||||
"ENVIRONMENT",
|
||||
"CORS_ALLOWED_ORIGINS",
|
||||
"DB_FILE",
|
||||
"UPLOAD_DIR",
|
||||
"SMTP_EMAIL",
|
||||
"SMTP_PASSWORD",
|
||||
"SMTP_HOST",
|
||||
"SMTP_PORT",
|
||||
}
|
||||
|
||||
func initEnvVariables() {
|
||||
var err error
|
||||
for _, envLocation := range possibleEnvFileLocations {
|
||||
err = godotenv.Load(envLocation)
|
||||
if err == nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
fmt.Println("WARNING: Could not load any .env file")
|
||||
}
|
||||
|
||||
for _, envVar := range envVarsToLoad {
|
||||
if _, ok := os.LookupEnv(envVar); !ok {
|
||||
panic(fmt.Sprintf("Environment variable %s is not set", envVar))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func initServer() *chi.Mux {
|
||||
initEnvVariables()
|
||||
|
||||
container := api.InitContainer()
|
||||
validator.InitValidator()
|
||||
|
||||
return api.InitRoutes(container)
|
||||
}
|
||||
|
||||
func startServer(router *chi.Mux) error {
|
||||
fmt.Println("Server starting on http://" + os.Getenv("HOST"))
|
||||
err := http.ListenAndServe(":"+os.Getenv("PORT"), router)
|
||||
return err
|
||||
}
|
||||
|
||||
func main() {
|
||||
router := initServer()
|
||||
err := startServer(router)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
services:
|
||||
api:
|
||||
build: .
|
||||
ports:
|
||||
- "${PORT}:8080"
|
||||
volumes:
|
||||
- ${DOCKER_DB_FOLDER}:/app/db/
|
||||
environment:
|
||||
- PORT=8080
|
||||
- DB_FILE=/app/db/RenewCMS.db
|
||||
env_file:
|
||||
- ./.env
|
||||
@@ -1,38 +0,0 @@
|
||||
services:
|
||||
backend:
|
||||
build:
|
||||
context: ./
|
||||
dockerfile: ./Dockerfile.backend
|
||||
image: gohcms-backend
|
||||
env_file:
|
||||
- .env
|
||||
ports:
|
||||
- "${APP_API_PORT}:${APP_API_PORT}"
|
||||
networks:
|
||||
- back-net
|
||||
frontend:
|
||||
build:
|
||||
context: ./
|
||||
dockerfile: ./Dockerfile.frontend
|
||||
image: gohcms-frontend
|
||||
env_file:
|
||||
- .env
|
||||
ports:
|
||||
- "${APP_FRONT_PORT}:80"
|
||||
networks:
|
||||
- front-net
|
||||
db:
|
||||
image: mongo:6.0.2
|
||||
# ports:
|
||||
# - "27017:27017"
|
||||
environment:
|
||||
- MONGO_INITDB_DATABASE=gohcms
|
||||
volumes:
|
||||
- ./mongodbdata:/data/db
|
||||
- ./mongo-init.js:/docker-entrypoint-initdb.d/mongo-init.js
|
||||
networks:
|
||||
- back-net
|
||||
|
||||
networks:
|
||||
front-net:
|
||||
back-net:
|
||||
@@ -1,47 +1,54 @@
|
||||
module github.com/Floriansylvain/GohCMS
|
||||
module RenewCMS
|
||||
|
||||
go 1.19
|
||||
go 1.25.4
|
||||
|
||||
require (
|
||||
github.com/appleboy/gin-jwt/v2 v2.9.1
|
||||
github.com/gin-gonic/gin v1.9.0
|
||||
github.com/MadAppGang/httplog v1.3.0
|
||||
github.com/glebarez/sqlite v1.11.0
|
||||
github.com/go-chi/cors v1.2.2
|
||||
github.com/go-chi/jwtauth/v5 v5.3.3
|
||||
github.com/go-playground/validator/v10 v10.28.0
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/joho/godotenv v1.5.1
|
||||
go.mongodb.org/mongo-driver v1.11.2
|
||||
golang.org/x/crypto v0.44.0
|
||||
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df
|
||||
gorm.io/gorm v1.31.1
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/bytedance/sonic v1.8.1 // indirect
|
||||
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect
|
||||
github.com/gin-contrib/sse v0.1.0 // indirect
|
||||
github.com/TylerBrock/colorjson v0.0.0-20200706003622-8a50f05110d2 // indirect
|
||||
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 // indirect
|
||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||
github.com/fatih/color v1.18.0 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.11 // indirect
|
||||
github.com/glebarez/go-sqlite v1.22.0 // indirect
|
||||
github.com/go-playground/locales v0.14.1 // indirect
|
||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||
github.com/go-playground/validator/v10 v10.11.2 // indirect
|
||||
github.com/goccy/go-json v0.10.0 // indirect
|
||||
github.com/golang-jwt/jwt/v4 v4.5.0 // indirect
|
||||
github.com/golang/snappy v0.0.4 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/klauspost/compress v1.15.15 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.2.4 // indirect
|
||||
github.com/leodido/go-urn v1.2.1 // indirect
|
||||
github.com/mattn/go-isatty v0.0.17 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/montanaflynn/stats v0.7.0 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.0.6 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||
github.com/ugorji/go/codec v1.2.10 // indirect
|
||||
github.com/xdg-go/pbkdf2 v1.0.0 // indirect
|
||||
github.com/xdg-go/scram v1.1.2 // indirect
|
||||
github.com/xdg-go/stringprep v1.0.4 // indirect
|
||||
github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a // indirect
|
||||
golang.org/x/arch v0.2.0 // indirect
|
||||
golang.org/x/crypto v0.6.0 // indirect
|
||||
golang.org/x/net v0.7.0 // indirect
|
||||
golang.org/x/sync v0.1.0 // indirect
|
||||
golang.org/x/sys v0.5.0 // indirect
|
||||
golang.org/x/text v0.7.0 // indirect
|
||||
google.golang.org/protobuf v1.28.1 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
github.com/goccy/go-json v0.10.5 // indirect
|
||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||
github.com/jinzhu/now v1.1.5 // indirect
|
||||
github.com/leodido/go-urn v1.4.0 // indirect
|
||||
github.com/lestrrat-go/blackmagic v1.0.4 // indirect
|
||||
github.com/lestrrat-go/httpcc v1.0.1 // indirect
|
||||
github.com/lestrrat-go/httprc v1.0.6 // indirect
|
||||
github.com/lestrrat-go/iter v1.0.2 // indirect
|
||||
github.com/lestrrat-go/jwx/v2 v2.1.6 // indirect
|
||||
github.com/lestrrat-go/option v1.0.1 // indirect
|
||||
github.com/ncruces/go-strftime v1.0.0 // indirect
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
||||
github.com/segmentio/asm v1.2.1 // indirect
|
||||
golang.org/x/exp v0.0.0-20251113190631-e25ba8c21ef6 // indirect
|
||||
golang.org/x/text v0.31.0 // indirect
|
||||
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
|
||||
modernc.org/libc v1.67.0 // indirect
|
||||
modernc.org/mathutil v1.7.1 // indirect
|
||||
modernc.org/memory v1.11.0 // indirect
|
||||
modernc.org/sqlite v1.40.1 // indirect
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/go-chi/chi/v5 v5.2.3
|
||||
github.com/mattn/go-colorable v0.1.14 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
golang.org/x/sys v0.38.0 // indirect
|
||||
)
|
||||
|
||||
@@ -1,221 +1,133 @@
|
||||
github.com/appleboy/gin-jwt/v2 v2.9.1 h1:l29et8iLW6omcHltsOP6LLk4s3v4g2FbFs0koxGWVZs=
|
||||
github.com/appleboy/gin-jwt/v2 v2.9.1/go.mod h1:jwcPZJ92uoC9nOUTOKWoN/f6JZOgMSKlFSHw5/FrRUk=
|
||||
github.com/appleboy/gofight/v2 v2.1.2 h1:VOy3jow4vIK8BRQJoC/I9muxyYlJ2yb9ht2hZoS3rf4=
|
||||
github.com/appleboy/gofight/v2 v2.1.2/go.mod h1:frW+U1QZEdDgixycTj4CygQ48yLTUhplt43+Wczp3rw=
|
||||
github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=
|
||||
github.com/bytedance/sonic v1.8.1 h1:NqAHCaGaTzro0xMmnTCLUyRlbEP6r8MCA1cJUrH3Pu4=
|
||||
github.com/bytedance/sonic v1.8.1/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U=
|
||||
github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY=
|
||||
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams=
|
||||
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/MadAppGang/httplog v1.3.0 h1:1XU54TO8kiqTeO+7oZLKAM3RP/cJ7SadzslRcKspVHo=
|
||||
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/go.mod h1:VSw57q4QFiWDbRnjdX8Cb3Ow0SFncRw+bA/ofY6Q83w=
|
||||
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/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
|
||||
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
||||
github.com/gin-gonic/gin v1.8.1/go.mod h1:ji8BvRH1azfM+SYow9zQ6SZMvR8qOMZHmsCuWR9tTTk=
|
||||
github.com/gin-gonic/gin v1.8.2 h1:UzKToD9/PoFj/V4rvlKqTRKnQYyz8Sc1MJlv4JHPtvY=
|
||||
github.com/gin-gonic/gin v1.8.2/go.mod h1:qw5AYuDrzRTnhvusDsrov+fDIxp9Dleuu12h8nfB398=
|
||||
github.com/gin-gonic/gin v1.9.0 h1:OjyFBKICoexlu99ctXNR2gg+c5pKrKMuyjgARg9qeY8=
|
||||
github.com/gin-gonic/gin v1.9.0/go.mod h1:W1Me9+hsUSyj3CePGrd1/QrKJMSJ1Tu/0hFEH89961k=
|
||||
github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A=
|
||||
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 h1:NMZiJj8QnKe1LgsbDayM4UoHwbvwDRwnI3hwNaAHRnc=
|
||||
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0/go.mod h1:ZXNYxsqcloTdSy/rNShjYzMhyjf0LaoftYK0p+A3h40=
|
||||
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||
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/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
|
||||
github.com/gabriel-vasile/mimetype v1.4.11 h1:AQvxbp830wPhHTqc1u7nzoLT+ZFxGY7emj5DR5DYFik=
|
||||
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/go.mod h1:PlBIdHe0+aUEFn+r2/uthrWq4FxbzugL0L8Li6yQJbc=
|
||||
github.com/glebarez/sqlite v1.11.0 h1:wSG0irqzP6VurnMEpFGer5Li19RpIRi2qvQz++w0GMw=
|
||||
github.com/glebarez/sqlite v1.11.0/go.mod h1:h8/o8j5wiAsqSPoWELDUdJXhjAhsVliSn7bWZjOhrgQ=
|
||||
github.com/go-chi/chi/v5 v5.2.3 h1:WQIt9uxdsAbgIYgid+BpYc+liqQZGMHRaUwp0JUcvdE=
|
||||
github.com/go-chi/chi/v5 v5.2.3/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops=
|
||||
github.com/go-chi/cors v1.2.2 h1:Jmey33TE+b+rB7fT8MUy1u0I4L+NARQlK6LhzKPSyQE=
|
||||
github.com/go-chi/cors v1.2.2/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58=
|
||||
github.com/go-chi/jwtauth/v5 v5.3.3 h1:50Uzmacu35/ZP9ER2Ht6SazwPsnLQ9LRJy6zTZJpHEo=
|
||||
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/locales v0.14.0 h1:u50s323jtVGugKlcYeyzC0etD1HifMjqmJqb8WugfUU=
|
||||
github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs=
|
||||
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/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
|
||||
github.com/go-playground/universal-translator v0.18.0 h1:82dyy6p4OuJq4/CByFNOn/jYrnRPArHwAcmLoJZxyho=
|
||||
github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA=
|
||||
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/validator/v10 v10.10.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXSGrTK4nAUsbPlLADvpJkos=
|
||||
github.com/go-playground/validator/v10 v10.11.1 h1:prmOlTVv+YjZjmRmNSF3VmspqJIxJWXmqUsHwfTRRkQ=
|
||||
github.com/go-playground/validator/v10 v10.11.1/go.mod h1:i+3WkQ1FvaUjjxh1kSvIA4dMGDBiPU55YFDl0WbKdWU=
|
||||
github.com/go-playground/validator/v10 v10.11.2 h1:q3SHpufmypg+erIExEKUmsgmhDTyhcJ38oeKGACXohU=
|
||||
github.com/go-playground/validator/v10 v10.11.2/go.mod h1:NieE624vt4SCTJtD87arVLvdmjPAeV8BQlHtMnw9D7s=
|
||||
github.com/goccy/go-json v0.9.7/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||
github.com/goccy/go-json v0.10.0 h1:mXKd9Qw4NuzShiRlOXKews24ufknHO7gx30lsDyokKA=
|
||||
github.com/goccy/go-json v0.10.0/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||
github.com/golang-jwt/jwt/v4 v4.4.3 h1:Hxl6lhQFj4AnOX6MLrsCb/+7tCj7DxP7VA+2rDIq5AU=
|
||||
github.com/golang-jwt/jwt/v4 v4.4.3/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
|
||||
github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg=
|
||||
github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
|
||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
|
||||
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/joho/godotenv v1.4.0 h1:3l4+N6zfMWnkbPEXKng2o2/MR5mSwTrBih4ZEkkz1lg=
|
||||
github.com/joho/godotenv v1.4.0/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
|
||||
github.com/go-playground/validator/v10 v10.28.0 h1:Q7ibns33JjyW48gHkuFT91qX48KG0ktULL6FgHdG688=
|
||||
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/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
|
||||
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e h1:ijClszYn+mADRFY17kjQEVQ1XRhq2/JR1M3sGqeJoxs=
|
||||
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/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/go.mod h1:pFlLw2CfqZiIBOx6BuCeRLCrfxBJipTY0nIOF/VbGcI=
|
||||
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
|
||||
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
||||
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
|
||||
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/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
|
||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||
github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
|
||||
github.com/klauspost/compress v1.15.13 h1:NFn1Wr8cfnenSJSA46lLq4wHCcBzKTSjnBIexDMMOV0=
|
||||
github.com/klauspost/compress v1.15.13/go.mod h1:QPwzmACJjUTFsnSHH934V6woptycfrDDJnH7hvFVbGM=
|
||||
github.com/klauspost/compress v1.15.15 h1:EF27CXIuDsYJ6mmvtBRlEuB2UVOqHG1tAXgZ7yIO+lw=
|
||||
github.com/klauspost/compress v1.15.15/go.mod h1:ZcK2JAFqKOpnBlxcLsJzYfrS9X1akm9fHZNnD9+Vo/4=
|
||||
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||
github.com/klauspost/cpuid/v2 v2.2.4 h1:acbojRNwl3o09bUq+yDCtZFc1aiwaAAxtcn8YkZXnvk=
|
||||
github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
|
||||
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
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.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w=
|
||||
github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY=
|
||||
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
|
||||
github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ=
|
||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng=
|
||||
github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||
github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc=
|
||||
github.com/montanaflynn/stats v0.6.6 h1:Duep6KMIDpY4Yo11iFsvyqJDyfzLF9+sndUKT+v64GQ=
|
||||
github.com/montanaflynn/stats v0.6.6/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow=
|
||||
github.com/montanaflynn/stats v0.7.0 h1:r3y12KyNxj/Sb/iOE46ws+3mS1+MZca1wlHQFPsY/JU=
|
||||
github.com/montanaflynn/stats v0.7.0/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow=
|
||||
github.com/pelletier/go-toml/v2 v2.0.1/go.mod h1:r9LEWfGN8R5k0VXJ+0BkIe7MYkRdwZOjgMj2KwnJFUo=
|
||||
github.com/pelletier/go-toml/v2 v2.0.6 h1:nrzqCb7j9cDFj2coyLNLaZuJTLjWjlaz6nvTvIwycIU=
|
||||
github.com/pelletier/go-toml/v2 v2.0.6/go.mod h1:eumQOmlWiOPt5WriQQqoM5y18pDHwha2N+QD+EUNTek=
|
||||
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
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/lestrrat-go/blackmagic v1.0.4 h1:IwQibdnf8l2KoO+qC3uT4OaTWsW7tuRQXy9TRN9QanA=
|
||||
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/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/go.mod h1:mwwz3JMTPBjHUkkDv/IGJ39aALInZLrhBp0X7KGUZlo=
|
||||
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/jwx/v2 v2.1.6 h1:hxM1gfDILk/l5ylers6BX/Eq1m/pnxe9NBwW6lVfecA=
|
||||
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/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I=
|
||||
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-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/ncruces/go-strftime v1.0.0 h1:HMFp8mLCTPp341M/ZnA4qaf7ZlsbTc+miZjCLOFAw7w=
|
||||
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/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
|
||||
github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8=
|
||||
github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
|
||||
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/segmentio/asm v1.2.1 h1:DTNbBqs57ioxAD4PrArqftgypG4/qNpXoJx8TVXxPR0=
|
||||
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.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/tidwall/gjson v1.14.3 h1:9jvXn7olKEHU1S9vwoMGliaT8jq1vJ7IH/n9zD9Dnlw=
|
||||
github.com/tidwall/gjson v1.14.3/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
||||
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
|
||||
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
|
||||
github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
|
||||
github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs=
|
||||
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
||||
github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6M=
|
||||
github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY=
|
||||
github.com/ugorji/go/codec v1.2.8 h1:sgBJS6COt0b/P40VouWKdseidkDgHxYGm0SAglUHfP0=
|
||||
github.com/ugorji/go/codec v1.2.8/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
|
||||
github.com/ugorji/go/codec v1.2.10 h1:eimT6Lsr+2lzmSZxPhLFoOWFmQqwk0fllJJ5hEbTXtQ=
|
||||
github.com/ugorji/go/codec v1.2.10/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
|
||||
github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c=
|
||||
github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
|
||||
github.com/xdg-go/scram v1.1.1/go.mod h1:RaEWvsqvNKKvBPvcKeFjrG2cJqOkHTiyTpzz23ni57g=
|
||||
github.com/xdg-go/scram v1.1.2 h1:FHX5I5B4i4hKRVRBCFRxq1iQRej7WO3hhBuJf+UUySY=
|
||||
github.com/xdg-go/scram v1.1.2/go.mod h1:RT/sEzTbU5y00aCK8UOx6R7YryM0iF1N2MOmC3kKLN4=
|
||||
github.com/xdg-go/stringprep v1.0.3/go.mod h1:W3f5j4i+9rC0kuIEJL0ky1VpHXQU3ocBgklLGvcBnW8=
|
||||
github.com/xdg-go/stringprep v1.0.4 h1:XLI/Ng3O1Atzq0oBs3TWm+5ZVgkq2aqdlvP9JtoZ6c8=
|
||||
github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM=
|
||||
github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA=
|
||||
github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a h1:fZHgsYlfvtyqToslyjUt3VOPF4J7aK/3MPcK7xp3PDk=
|
||||
github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a/go.mod h1:ul22v+Nro/R083muKhosV54bj5niojjWZvU8xrevuH4=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
go.mongodb.org/mongo-driver v1.11.1 h1:QP0znIRTuL0jf1oBQoAoM0C6ZJfBK4kx0Uumtv1A7w8=
|
||||
go.mongodb.org/mongo-driver v1.11.1/go.mod h1:s7p5vEtfbeR1gYi6pnj3c3/urpbLv2T5Sfd6Rp2HBB8=
|
||||
go.mongodb.org/mongo-driver v1.11.2 h1:+1v2rDQUWNcGW7/7E0Jvdz51V38XXxJfhzbV17aNHCw=
|
||||
go.mongodb.org/mongo-driver v1.11.2/go.mod h1:s7p5vEtfbeR1gYi6pnj3c3/urpbLv2T5Sfd6Rp2HBB8=
|
||||
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
||||
golang.org/x/arch v0.2.0 h1:W1sUEHXiJTfjaFJ5SLo0N6lZn+0eO5gWD1MFeTGqQEY=
|
||||
golang.org/x/arch v0.2.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.4.0 h1:UVQgzMY87xqpKNgb+kDsll2Igd33HszWHFLmpaRMq/8=
|
||||
golang.org/x/crypto v0.4.0/go.mod h1:3quD/ATkf6oY+rnes5c3ExXTbLc8mueNue5/DoinL80=
|
||||
golang.org/x/crypto v0.6.0 h1:qfktjS5LUO+fFKeJXZ+ikTRijMmljikvG68fpMMruSc=
|
||||
golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.3.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE=
|
||||
golang.org/x/net v0.4.0 h1:Q5QPcMlvfxFTAPV0+07Xz/MpK9NTXu2VDUuy0FeMfaU=
|
||||
golang.org/x/net v0.4.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE=
|
||||
golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g=
|
||||
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.3.0 h1:w8ZOecv6NaNa/zC8944JTU3vz4u6Lagfk4RPQxv92NQ=
|
||||
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
|
||||
golang.org/x/text v0.5.0 h1:OLmvp0KP+FVG99Ct/qFiL/Fhk4zp4QQnZ7b2U+5piUM=
|
||||
golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w=
|
||||
google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
golang.org/x/crypto v0.44.0 h1:A97SsFvM3AIwEEmTBiaxPPTYpDC47w720rdiiUvgoAU=
|
||||
golang.org/x/crypto v0.44.0/go.mod h1:013i+Nw79BMiQiMsOPcVCB5ZIJbYkerPrGnOa00tvmc=
|
||||
golang.org/x/exp v0.0.0-20251113190631-e25ba8c21ef6 h1:zfMcR1Cs4KNuomFFgGefv5N0czO2XZpUbxGUy8i8ug0=
|
||||
golang.org/x/exp v0.0.0-20251113190631-e25ba8c21ef6/go.mod h1:46edojNIoXTNOhySWIWdix628clX9ODXwPsQuG6hsK0=
|
||||
golang.org/x/mod v0.30.0 h1:fDEXFVZ/fmCKProc/yAXXUijritrDzahmwwefnjoPFk=
|
||||
golang.org/x/mod v0.30.0/go.mod h1:lAsf5O2EvJeSFMiBxXDki7sCgAxEUcZHXoXMKT4GJKc=
|
||||
golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I=
|
||||
golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
|
||||
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=
|
||||
golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=
|
||||
golang.org/x/tools v0.39.0 h1:ik4ho21kwuQln40uelmciQPp9SipgNDdrafrYA4TmQQ=
|
||||
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/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
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/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
|
||||
gorm.io/gorm v1.31.1 h1:7CA8FTFz/gRfgqgpeKIBcervUn3xSyPUmr6B2WXJ7kg=
|
||||
gorm.io/gorm v1.31.1/go.mod h1:XyQVbO2k6YkOis7C2437jSit3SsDK72s7n7rsSHd+Gs=
|
||||
modernc.org/cc/v4 v4.27.1 h1:9W30zRlYrefrDV2JE2O8VDtJ1yPGownxciz5rrbQZis=
|
||||
modernc.org/cc/v4 v4.27.1/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0=
|
||||
modernc.org/ccgo/v4 v4.30.1 h1:4r4U1J6Fhj98NKfSjnPUN7Ze2c6MnAdL0hWw6+LrJpc=
|
||||
modernc.org/ccgo/v4 v4.30.1/go.mod h1:bIOeI1JL54Utlxn+LwrFyjCx2n2RDiYEaJVSrgdrRfM=
|
||||
modernc.org/fileutil v1.3.40 h1:ZGMswMNc9JOCrcrakF1HrvmergNLAmxOPjizirpfqBA=
|
||||
modernc.org/fileutil v1.3.40/go.mod h1:HxmghZSZVAz/LXcMNwZPA/DRrQZEVP9VX0V4LQGQFOc=
|
||||
modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI=
|
||||
modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito=
|
||||
modernc.org/gc/v3 v3.1.1 h1:k8T3gkXWY9sEiytKhcgyiZ2L0DTyCQ/nvX+LoCljoRE=
|
||||
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/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg=
|
||||
modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI=
|
||||
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/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns=
|
||||
modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w=
|
||||
modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE=
|
||||
modernc.org/sqlite v1.40.1 h1:VfuXcxcUWWKRBuP8+BR9L7VnmusMgBNNnBYGEe9w/iY=
|
||||
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/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A=
|
||||
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
|
||||
modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
|
||||
|
||||
@@ -1,58 +0,0 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/Floriansylvain/GohCMS/internal/database"
|
||||
jwt "github.com/appleboy/gin-jwt/v2"
|
||||
"github.com/gin-gonic/gin"
|
||||
"go.mongodb.org/mongo-driver/bson"
|
||||
)
|
||||
|
||||
type User struct {
|
||||
Email string `json:"email" bson:"email"`
|
||||
Password string `json:"password" bson:"password"`
|
||||
}
|
||||
|
||||
var UsersLocation = database.Location{Database: "gohcms", Collection: "users"}
|
||||
|
||||
var AuthMiddleware, _ = jwt.New(&jwt.GinJWTMiddleware{
|
||||
Realm: "GohCMS",
|
||||
Key: []byte(os.Getenv("APP_JWT_SECRET")),
|
||||
SendCookie: true,
|
||||
CookieHTTPOnly: true,
|
||||
CookieSameSite: http.SameSiteStrictMode,
|
||||
Timeout: time.Hour,
|
||||
MaxRefresh: time.Hour,
|
||||
LoginResponse: JWTLoginResponse,
|
||||
Authenticator: JWTAuthenticator,
|
||||
})
|
||||
|
||||
func JWTLoginResponse(c *gin.Context, code int, message string, expire time.Time) {
|
||||
if code == http.StatusOK {
|
||||
c.JSON(code, gin.H{"code": code, "message": "Successfully logged in!", "expire": expire.Format(time.RFC3339)})
|
||||
} else {
|
||||
c.JSON(code, gin.H{"code": code, "message": "Something wrong has happened."})
|
||||
}
|
||||
}
|
||||
|
||||
func JWTAuthenticator(c *gin.Context) (interface{}, error) {
|
||||
var user = User{}
|
||||
err := c.BindJSON(&user)
|
||||
if err != nil {
|
||||
return nil, errors.New("wrong credentials json format.")
|
||||
}
|
||||
|
||||
_, err = database.GetUniqueDocument(UsersLocation, bson.D{
|
||||
{Key: "email", Value: user.Email},
|
||||
{Key: "password", Value: user.Password},
|
||||
})
|
||||
if err != nil {
|
||||
return nil, errors.New("wrong email or password.")
|
||||
}
|
||||
|
||||
return gin.H{"email": user.Email}, nil
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func SendBadRequest(c *gin.Context, message string) {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"message": message, "code": 400})
|
||||
}
|
||||
|
||||
func SendOk(c *gin.Context, message string) {
|
||||
c.JSON(http.StatusOK, gin.H{"message": message, "code": 200})
|
||||
}
|
||||
|
||||
func SendForbidden(c *gin.Context, message string) {
|
||||
c.JSON(http.StatusForbidden, gin.H{"message": message, "code": 403})
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func Ping(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, gin.H{"message": "pong"})
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
package articles
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/Floriansylvain/GohCMS/internal/api"
|
||||
"github.com/Floriansylvain/GohCMS/internal/database"
|
||||
"github.com/gin-gonic/gin"
|
||||
"go.mongodb.org/mongo-driver/bson"
|
||||
)
|
||||
|
||||
func Add(c *gin.Context) {
|
||||
var article Article
|
||||
article.TitleID = c.Params.ByName("id")
|
||||
if c.BindJSON(&article) != nil {
|
||||
api.SendBadRequest(c, "Could not correctly parse the article.")
|
||||
return
|
||||
}
|
||||
|
||||
document, err := bson.Marshal(article)
|
||||
if err != nil {
|
||||
api.SendBadRequest(c, "Could not correctly marshal the article.")
|
||||
return
|
||||
}
|
||||
|
||||
documents, _ := database.GetDocuments(articlesLocation, map[string]any{})
|
||||
if IsArticleIdAlreadyUsed(article.TitleID, documents) {
|
||||
api.SendBadRequest(c, fmt.Sprintf("Article ID '%v' already used.", article.TitleID))
|
||||
return
|
||||
}
|
||||
|
||||
err = database.PushDocument(articlesLocation, document)
|
||||
if err != nil {
|
||||
api.SendBadRequest(c, fmt.Sprintf(`Could not insert document(s) into DB: %v`, err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
api.SendOk(c, "Article successfully added!")
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
package articles
|
||||
|
||||
import (
|
||||
"github.com/Floriansylvain/GohCMS/internal/database"
|
||||
"github.com/gin-gonic/gin"
|
||||
"go.mongodb.org/mongo-driver/bson"
|
||||
)
|
||||
|
||||
type Article struct {
|
||||
TitleID string `json:"titleID" bson:"titleID"`
|
||||
Title string `json:"title" bson:"title"`
|
||||
Date int64 `json:"date" bson:"date"`
|
||||
Content gin.H `json:"content" bson:"content"`
|
||||
Tags []string `json:"tags" bson:"tags"`
|
||||
Online bool `json:"online" bson:"online"`
|
||||
}
|
||||
|
||||
var articlesLocation = database.Location{Database: "gohcms", Collection: "articles"}
|
||||
|
||||
func ParseArticlesFromBytesToArray(documents [][]byte) []Article {
|
||||
var articles = []Article{}
|
||||
|
||||
for i := 0; i < len(documents); i++ {
|
||||
var newArticle Article
|
||||
bson.Unmarshal(documents[i], &newArticle)
|
||||
articles = append(articles, newArticle)
|
||||
}
|
||||
|
||||
return articles
|
||||
}
|
||||
|
||||
func IsArticleIdAlreadyUsed(id string, documents [][]byte) bool {
|
||||
for i := 0; i < len(documents); i++ {
|
||||
var newArticle Article
|
||||
bson.Unmarshal(documents[i], &newArticle)
|
||||
if newArticle.TitleID == id {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
package articles
|
||||
|
||||
import (
|
||||
"log"
|
||||
"testing"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"go.mongodb.org/mongo-driver/bson"
|
||||
)
|
||||
|
||||
var Article1, _ = bson.Marshal(Article{
|
||||
TitleID: "test_article_1",
|
||||
Date: 1671482492,
|
||||
Content: gin.H{},
|
||||
Tags: []string{"blog"},
|
||||
Online: false,
|
||||
})
|
||||
|
||||
var Article2, _ = bson.Marshal(Article{
|
||||
TitleID: "test_article_2",
|
||||
Date: 1671899112,
|
||||
Content: gin.H{},
|
||||
Tags: []string{"blog"},
|
||||
Online: false,
|
||||
})
|
||||
|
||||
var BSONConvertedArticles = [][]byte{Article1, Article2}
|
||||
|
||||
func TestGetAllArticlesBusiness(t *testing.T) {
|
||||
documents := ParseArticlesFromBytesToArray(BSONConvertedArticles)
|
||||
article := documents[0]
|
||||
|
||||
if article.TitleID != "test_article_1" {
|
||||
log.Fatalf(`Excepted "test_article_1" as IdName, found "%v"`, article.TitleID)
|
||||
} else if article.Date != 1671482492 {
|
||||
log.Fatalf(`Excepted 1671482492 as Date, found "%v"`, article.Date)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsArticleIdAlreadyUsed(t *testing.T) {
|
||||
if IsArticleIdAlreadyUsed("test_article_1", BSONConvertedArticles) == false {
|
||||
log.Fatalf(`Excepted true, found false`)
|
||||
} else if IsArticleIdAlreadyUsed("test_article_3", BSONConvertedArticles) == true {
|
||||
log.Fatalf(`Excepted false, found true`)
|
||||
}
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
package articles
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/Floriansylvain/GohCMS/internal/api"
|
||||
"github.com/Floriansylvain/GohCMS/internal/database"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func Delete(c *gin.Context) {
|
||||
id := c.Params.ByName("id")
|
||||
|
||||
deleteCount, err := database.DeleteDocument(articlesLocation, map[string]any{"titleID": id})
|
||||
if err != nil {
|
||||
api.SendBadRequest(c, fmt.Sprintf(`Could not delete document(s) from DB: %v`, err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
if deleteCount != 0 {
|
||||
api.SendOk(c, fmt.Sprintf("%d article(s) was/were successfully deleted!", deleteCount))
|
||||
} else {
|
||||
api.SendOk(c, "No articles were deleted.")
|
||||
}
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
package articles
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/Floriansylvain/GohCMS/internal/api"
|
||||
"github.com/Floriansylvain/GohCMS/internal/database"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func Edit(c *gin.Context) {
|
||||
id := c.Params.ByName("id")
|
||||
|
||||
var articleUpdate database.DocumentUpdate
|
||||
articleUpdate.Filter = map[string]any{"titleID": id}
|
||||
c.BindJSON(&articleUpdate.Update)
|
||||
|
||||
editCount, err := database.EditDocument(articlesLocation, articleUpdate)
|
||||
if err != nil {
|
||||
api.SendBadRequest(c, fmt.Sprintf(`Could not edit document(s) from DB: %v`, err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
if editCount != 0 {
|
||||
api.SendOk(c, fmt.Sprintf("%d article(s) was/were successfully edited!", editCount))
|
||||
} else {
|
||||
api.SendOk(c, "No articles were edited.")
|
||||
}
|
||||
}
|
||||
@@ -1,102 +0,0 @@
|
||||
package articles
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"net/http"
|
||||
"os"
|
||||
"strconv"
|
||||
|
||||
"github.com/Floriansylvain/GohCMS/internal/api"
|
||||
"github.com/Floriansylvain/GohCMS/internal/database"
|
||||
"github.com/gin-gonic/gin"
|
||||
"go.mongodb.org/mongo-driver/bson"
|
||||
)
|
||||
|
||||
func getApiFullUrl() string {
|
||||
return fmt.Sprintf("%v%v", os.Getenv("APP_API_ADDRESS"), os.Getenv("APP_BASE_API_PATH"))
|
||||
}
|
||||
|
||||
func getArticleSkipTakeFullUrl(skip uint64, take uint64) string {
|
||||
return fmt.Sprintf("%varticles?skip=%v&take=%v", getApiFullUrl(), skip+take, take)
|
||||
}
|
||||
|
||||
func getBuiltGetResponse(articles []Article, skip uint64, take uint64) map[string]any {
|
||||
articlesCap := uint64(len(articles))
|
||||
|
||||
var normTake uint64
|
||||
if skip+take > articlesCap {
|
||||
normTake = articlesCap
|
||||
} else {
|
||||
normTake = skip + take
|
||||
}
|
||||
|
||||
slicedArticles := articles[skip:normTake]
|
||||
total := len(slicedArticles)
|
||||
return map[string]any{
|
||||
"content": slicedArticles,
|
||||
"total": total,
|
||||
"pagination": map[string]any{
|
||||
"skip": skip,
|
||||
"take": take,
|
||||
"links": map[string]any{
|
||||
"next": getArticleSkipTakeFullUrl(skip+take, take),
|
||||
"previous": getArticleSkipTakeFullUrl(skip-take, take),
|
||||
},
|
||||
},
|
||||
"last_page": math.Ceil(float64(articlesCap) / float64(take)),
|
||||
}
|
||||
}
|
||||
|
||||
func parseUintQueryParam(c *gin.Context, param string, defaultValue uint64) (uint64, error) {
|
||||
if c.Query(param) == "" {
|
||||
return defaultValue, nil
|
||||
}
|
||||
value, err := strconv.ParseUint(c.Query(param), 10, 0)
|
||||
return value, err
|
||||
}
|
||||
|
||||
func getArticleIdFilter(titleID string) map[string]any {
|
||||
filter := map[string]any{}
|
||||
if titleID != "" {
|
||||
filter["titleID"] = titleID
|
||||
}
|
||||
return filter
|
||||
}
|
||||
|
||||
func Get(c *gin.Context) {
|
||||
skip, skipErr := parseUintQueryParam(c, "skip", 0)
|
||||
take, takeErr := parseUintQueryParam(c, "take", 10)
|
||||
if skipErr != nil || takeErr != nil {
|
||||
api.SendBadRequest(c, "Take and Skip query parameters must be positive numbers.")
|
||||
return
|
||||
}
|
||||
|
||||
documents, _ := database.GetDocuments(articlesLocation, map[string]any{})
|
||||
articlesArray := ParseArticlesFromBytesToArray(documents)
|
||||
articlesArrayLength := uint64(len(articlesArray))
|
||||
if skip >= articlesArrayLength || take == 0 {
|
||||
c.JSON(http.StatusOK, map[string]any{
|
||||
"content": []string{},
|
||||
"total": "0",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, getBuiltGetResponse(articlesArray, skip, take))
|
||||
}
|
||||
|
||||
func GetUnique(c *gin.Context) {
|
||||
titleID := c.Params.ByName("id")
|
||||
|
||||
article, err := database.GetUniqueDocument(articlesLocation, getArticleIdFilter(titleID))
|
||||
if err != nil {
|
||||
api.SendBadRequest(c, fmt.Sprintf("The ID '%v' doesn't match any article.", titleID))
|
||||
return
|
||||
}
|
||||
|
||||
var parsedArticle Article
|
||||
bson.Unmarshal(article, &parsedArticle)
|
||||
|
||||
c.JSON(http.StatusOK, parsedArticle)
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
package database
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"go.mongodb.org/mongo-driver/mongo"
|
||||
"go.mongodb.org/mongo-driver/mongo/options"
|
||||
)
|
||||
|
||||
type Location struct {
|
||||
Database string
|
||||
Collection string
|
||||
}
|
||||
|
||||
func GetNewClient() *mongo.Client {
|
||||
client, err := mongo.Connect(
|
||||
context.TODO(),
|
||||
options.Client().ApplyURI("mongodb://db:27017/"))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return client
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
package database
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
)
|
||||
|
||||
func DeleteDocument(location Location, filter interface{}) (int64, error) {
|
||||
client := GetNewClient()
|
||||
collection := client.Database(location.Database).Collection(location.Collection)
|
||||
defer client.Disconnect(context.TODO())
|
||||
|
||||
result, err := collection.DeleteOne(context.TODO(), filter)
|
||||
if err != nil {
|
||||
return 0, errors.New("something is wrong with filter to delete the document.")
|
||||
}
|
||||
|
||||
return result.DeletedCount, nil
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
package database
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type DocumentUpdate struct {
|
||||
Filter gin.H `json:"filter"`
|
||||
Update gin.H `json:"update"`
|
||||
}
|
||||
|
||||
func EditDocument(location Location, jsons DocumentUpdate) (int64, error) {
|
||||
client := GetNewClient()
|
||||
collection := client.Database(location.Database).Collection(location.Collection)
|
||||
defer client.Disconnect(context.TODO())
|
||||
|
||||
result, err := collection.UpdateOne(context.TODO(), jsons.Filter, gin.H{"$set": jsons.Update})
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if result.MatchedCount == 0 {
|
||||
return 0, errors.New("cannot find document matching filter.")
|
||||
}
|
||||
|
||||
return result.ModifiedCount, nil
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
package database
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
)
|
||||
|
||||
func GetDocuments(location Location, filter interface{}) ([][]byte, error) {
|
||||
client := GetNewClient()
|
||||
collection := client.Database(location.Database).Collection(location.Collection)
|
||||
defer client.Disconnect(context.TODO())
|
||||
|
||||
var results [][]byte
|
||||
|
||||
cursor, err := collection.Find(context.TODO(), filter)
|
||||
if err != nil {
|
||||
return results, errors.New("something is wrong with filter to find the document.")
|
||||
}
|
||||
for cursor.TryNext(context.TODO()) {
|
||||
results = append(results, cursor.Current)
|
||||
}
|
||||
return results, nil
|
||||
}
|
||||
|
||||
func GetUniqueDocument(location Location, filter interface{}) ([]byte, error) {
|
||||
client := GetNewClient()
|
||||
collection := client.Database(location.Database).Collection(location.Collection)
|
||||
defer client.Disconnect(context.TODO())
|
||||
|
||||
singleResult := collection.FindOne(context.TODO(), filter)
|
||||
if singleResult.Err() != nil {
|
||||
return nil, errors.New("no document was found, check the filter or the location.")
|
||||
}
|
||||
return singleResult.DecodeBytes()
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
package database
|
||||
|
||||
import "context"
|
||||
|
||||
func PushDocument(location Location, document interface{}) error {
|
||||
client := GetNewClient()
|
||||
collection := client.Database(location.Database).Collection(location.Collection)
|
||||
defer client.Disconnect(context.TODO())
|
||||
|
||||
_, err := collection.InsertOne(context.TODO(), document)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package article
|
||||
|
||||
import (
|
||||
domain "RenewCMS/internal/domain/image"
|
||||
"errors"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrInvalidTitle = errors.New("article's title length should be between 3 and 200 caracters")
|
||||
ErrArticleNotFound = errors.New("article not found")
|
||||
ErrArticleOffline = errors.New("article is offline")
|
||||
)
|
||||
|
||||
type Article struct {
|
||||
ID uint32 `json:"id"`
|
||||
Title string `json:"title"`
|
||||
Body string `json:"body"`
|
||||
Images []*domain.Image `json:"images"`
|
||||
IsOnline bool `json:"is_online"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package article
|
||||
|
||||
type Filters struct {
|
||||
IsOnline *bool
|
||||
}
|
||||
|
||||
type Repository interface {
|
||||
FindByFilters(filters Filters) ([]Article, error)
|
||||
Get(id uint32) (Article, error)
|
||||
GetByName(name string) (Article, error)
|
||||
GetAll() []Article
|
||||
Create(post Article) (Article, error)
|
||||
UpdateBody(id uint32, body string) (Article, error)
|
||||
UpdateIsOnline(id uint32, isOnline bool) (Article, error)
|
||||
Delete(id uint32) error
|
||||
AddImage(postId uint32, imageId uint32) error
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package image
|
||||
|
||||
import (
|
||||
"io"
|
||||
"time"
|
||||
)
|
||||
|
||||
type ImageInput struct {
|
||||
Content io.Reader
|
||||
Filename string
|
||||
Size int64
|
||||
ContentType string
|
||||
}
|
||||
|
||||
type Image struct {
|
||||
ID uint32 `json:"id"`
|
||||
Path string `json:"path"`
|
||||
ArticleID uint32 `json:"article_id"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
package image
|
||||
|
||||
type Repository interface {
|
||||
Create(input ImageInput) (Image, error)
|
||||
Delete(id uint32) error
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
package mail
|
||||
|
||||
type Repository interface {
|
||||
Send(receiverAddress string, templateName string, data any) error
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package user
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
type User struct {
|
||||
ID uint32 `json:"id"`
|
||||
Username string `json:"username"`
|
||||
Password string `json:"password"`
|
||||
PasswordResetCode string `json:"password_reset_code"`
|
||||
Email string `json:"email"`
|
||||
IsVerified bool `json:"is_verified"`
|
||||
VerificationCode string `json:"verification_code"`
|
||||
VerificationExpiration time.Time `json:"verification_expiration"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package user
|
||||
|
||||
type Repository interface {
|
||||
Get(id uint32) (User, error)
|
||||
GetByUsername(username string) (User, error)
|
||||
GetByEmail(email string) (User, error)
|
||||
GetAll() []User
|
||||
Create(user User) (User, error)
|
||||
Delete(id uint32) error
|
||||
UpdateVerificationStatus(userId uint32, isVerified bool) (User, error)
|
||||
UpdatePassword(userId uint32, password string) (User, error)
|
||||
UpdatePasswordResetCode(userId uint32, code string) (User, error)
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
package assets
|
||||
|
||||
import "embed"
|
||||
|
||||
//go:embed all:dist
|
||||
var DistEmbed embed.FS
|
||||
@@ -0,0 +1,64 @@
|
||||
package mailer
|
||||
|
||||
import (
|
||||
domain "RenewCMS/internal/domain/mail"
|
||||
"bytes"
|
||||
"embed"
|
||||
"html/template"
|
||||
"log"
|
||||
"os"
|
||||
"strconv"
|
||||
|
||||
"gopkg.in/gomail.v2"
|
||||
)
|
||||
|
||||
type MailRepository struct{}
|
||||
|
||||
//go:embed templates/*
|
||||
var mailTemplateFiles embed.FS
|
||||
|
||||
func NewMailRepository() *MailRepository {
|
||||
return &MailRepository{}
|
||||
}
|
||||
|
||||
func (m MailRepository) Send(receiverAddress string, templateName string, data any) error {
|
||||
from := os.Getenv("SMTP_EMAIL")
|
||||
password := os.Getenv("SMTP_PASSWORD")
|
||||
smtpHost := os.Getenv("SMTP_HOST")
|
||||
smtpPort, _ := strconv.Atoi(os.Getenv("SMTP_PORT"))
|
||||
|
||||
d := gomail.NewDialer(smtpHost, smtpPort, from, password)
|
||||
|
||||
tmpl, err := template.ParseFS(mailTemplateFiles, "mail/templates/"+templateName+".html")
|
||||
if err != nil {
|
||||
log.Println("error when template.ParseFS:", err)
|
||||
return err
|
||||
}
|
||||
|
||||
var body bytes.Buffer
|
||||
err = tmpl.Execute(&body, data)
|
||||
if err != nil {
|
||||
log.Println("error when tmpl.Execute:", err)
|
||||
return err
|
||||
}
|
||||
|
||||
msg := gomail.NewMessage()
|
||||
msg.SetHeaders(map[string][]string{
|
||||
"From": {"RenewCMS <" + from + ">"},
|
||||
"To": {receiverAddress},
|
||||
"MIME-version": {"1.0"},
|
||||
"Content-Type": {"text/html"},
|
||||
"charset": {"UTF-8"},
|
||||
"Subject": {"RenewCMS | Action required"},
|
||||
})
|
||||
msg.SetBody("text/html", body.String())
|
||||
|
||||
if err := d.DialAndSend(msg); err != nil {
|
||||
log.Println("error when sending email:", err)
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
var _ domain.Repository = &MailRepository{}
|
||||
@@ -0,0 +1,18 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<body>
|
||||
<h1>
|
||||
<img src="{{.Host}}/static/renewcms-favicon-64.png" alt="G" style="width: 2.5rem"/>
|
||||
RenewCMS
|
||||
</h1>
|
||||
<main>
|
||||
<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>
|
||||
<p><b>If you want to validate the account creation, please <a
|
||||
href="{{.Host}}/register/validate?c={{.VerificationCode}}" target="_blank">click HERE</a>.</b>
|
||||
</p>
|
||||
<p>If you can't click the link:</p>
|
||||
<p>{{.Host}}/register/validate?c={{.VerificationCode}}</p>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,18 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<body>
|
||||
<h1>
|
||||
<img src="{{.Host}}/static/renewcms-favicon-64.png" alt="G" style="width: 2.5rem"/>
|
||||
RenewCMS
|
||||
</h1>
|
||||
<main>
|
||||
<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>
|
||||
<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>
|
||||
</p>
|
||||
<p>If you can't click the link:</p>
|
||||
<p>{{.Host}}/register/reset/validate?c={{.VerificationCode}}&email={{.Email}}</p>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,42 @@
|
||||
package mappers
|
||||
|
||||
import (
|
||||
"RenewCMS/internal/domain/article"
|
||||
"RenewCMS/internal/domain/image"
|
||||
"RenewCMS/internal/infrastructure/persistence/models"
|
||||
)
|
||||
|
||||
func ArticleToDomain(m models.Article) article.Article {
|
||||
domainImages := make([]*image.Image, len(m.Images))
|
||||
for i, img := range m.Images {
|
||||
domainImg := ImageToDomain(*img)
|
||||
domainImages[i] = &domainImg
|
||||
}
|
||||
|
||||
return article.Article{
|
||||
ID: m.ID,
|
||||
Title: m.Title,
|
||||
Body: m.Body,
|
||||
Images: domainImages,
|
||||
IsOnline: m.IsOnline,
|
||||
CreatedAt: m.CreatedAt,
|
||||
UpdatedAt: m.UpdatedAt,
|
||||
}
|
||||
}
|
||||
|
||||
func ArticleToModel(a article.Article) models.Article {
|
||||
modelImages := make([]*models.Image, len(a.Images))
|
||||
for i, img := range a.Images {
|
||||
modelImage := ImageToModel(*img)
|
||||
modelImages[i] = &modelImage
|
||||
}
|
||||
|
||||
return models.Article{
|
||||
Title: a.Title,
|
||||
Body: a.Body,
|
||||
Images: modelImages,
|
||||
IsOnline: a.IsOnline,
|
||||
CreatedAt: a.CreatedAt,
|
||||
UpdatedAt: a.UpdatedAt,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package mappers
|
||||
|
||||
import (
|
||||
"RenewCMS/internal/domain/image"
|
||||
"RenewCMS/internal/infrastructure/persistence/models"
|
||||
)
|
||||
|
||||
func ImageToDomain(imageModel models.Image) image.Image {
|
||||
return image.Image{
|
||||
ID: imageModel.ID,
|
||||
Path: imageModel.Path,
|
||||
ArticleID: imageModel.ArticleID,
|
||||
CreatedAt: imageModel.CreatedAt,
|
||||
UpdatedAt: imageModel.UpdatedAt,
|
||||
}
|
||||
}
|
||||
|
||||
func ImageToModel(imageDomain image.Image) models.Image {
|
||||
return models.Image{
|
||||
Path: imageDomain.Path,
|
||||
ArticleID: imageDomain.ArticleID,
|
||||
CreatedAt: imageDomain.CreatedAt,
|
||||
UpdatedAt: imageDomain.UpdatedAt,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package mappers
|
||||
|
||||
import (
|
||||
"RenewCMS/internal/domain/user"
|
||||
"RenewCMS/internal/infrastructure/persistence/models"
|
||||
)
|
||||
|
||||
func UserToDomain(m models.User) user.User {
|
||||
return user.User{
|
||||
ID: m.ID,
|
||||
Username: m.Username,
|
||||
Password: m.Password,
|
||||
PasswordResetCode: m.PasswordResetCode,
|
||||
Email: m.Email,
|
||||
IsVerified: m.IsVerified,
|
||||
VerificationCode: m.VerificationCode,
|
||||
VerificationExpiration: m.VerificationExpiration,
|
||||
CreatedAt: m.CreatedAt,
|
||||
UpdatedAt: m.UpdatedAt,
|
||||
}
|
||||
}
|
||||
|
||||
func UserToModel(a user.User) models.User {
|
||||
return models.User{
|
||||
Username: a.Username,
|
||||
Password: a.Password,
|
||||
PasswordResetCode: a.PasswordResetCode,
|
||||
Email: a.Email,
|
||||
IsVerified: a.IsVerified,
|
||||
VerificationCode: a.VerificationCode,
|
||||
VerificationExpiration: a.VerificationExpiration,
|
||||
CreatedAt: a.CreatedAt,
|
||||
UpdatedAt: a.UpdatedAt,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type Article struct {
|
||||
gorm.Model
|
||||
ID uint32 `gorm:"primary_key;auto_increment;not_null"`
|
||||
Title string
|
||||
Body string
|
||||
Images []*Image `gorm:"many2many:article_images;"`
|
||||
IsOnline bool `gorm:"not_null;default:false"`
|
||||
CreatedAt time.Time `gorm:"autoCreateTime"`
|
||||
UpdatedAt time.Time `gorm:"autoUpdateTime"`
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type Image struct {
|
||||
gorm.Model
|
||||
ID uint32 `gorm:"primary_key;auto_increment;not_null"`
|
||||
Path string
|
||||
ArticleID uint32
|
||||
CreatedAt time.Time `gorm:"autoCreateTime"`
|
||||
UpdatedAt time.Time `gorm:"autoUpdateTime"`
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type User struct {
|
||||
gorm.Model
|
||||
ID uint32 `gorm:"primaryKey;autoIncrement"`
|
||||
Username string `gorm:"unique;not null"`
|
||||
Password string `gorm:"not null"`
|
||||
PasswordResetCode string `gorm:"unique"`
|
||||
Email string `gorm:"unique;not null"`
|
||||
IsVerified bool `gorm:"default=false;not null"`
|
||||
VerificationCode string `gorm:"unique"`
|
||||
VerificationExpiration time.Time
|
||||
CreatedAt time.Time `gorm:"autoCreateTime"`
|
||||
UpdatedAt time.Time `gorm:"autoUpdateTime"`
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
package persistence
|
||||
|
||||
import (
|
||||
domain "RenewCMS/internal/domain/article"
|
||||
"RenewCMS/internal/infrastructure/persistence/mappers"
|
||||
entity "RenewCMS/internal/infrastructure/persistence/models"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type ArticleRepository struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
func NewArticleRepository(db *gorm.DB) *ArticleRepository {
|
||||
return &ArticleRepository{db}
|
||||
}
|
||||
|
||||
func (a *ArticleRepository) FindByFilters(filters domain.Filters) ([]domain.Article, error) {
|
||||
query := a.db.Model(&entity.Article{})
|
||||
|
||||
if filters.IsOnline != nil {
|
||||
query = query.Where("is_online = ?", *filters.IsOnline)
|
||||
}
|
||||
|
||||
var entityArticles []entity.Article
|
||||
err := query.Find(&entityArticles).Error
|
||||
|
||||
domainArticles := make([]domain.Article, 0, len(entityArticles))
|
||||
for _, entityArticle := range entityArticles {
|
||||
domainArticles = append(domainArticles, mappers.ArticleToDomain(entityArticle))
|
||||
}
|
||||
|
||||
return domainArticles, err
|
||||
}
|
||||
|
||||
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 mappers.ArticleToDomain(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 mappers.ArticleToDomain(article), nil
|
||||
}
|
||||
|
||||
func (a *ArticleRepository) Create(article domain.Article) (domain.Article, error) {
|
||||
creationResult := a.db.Create(&entity.Article{
|
||||
Title: article.Title,
|
||||
Body: article.Body,
|
||||
IsOnline: article.IsOnline,
|
||||
})
|
||||
if creationResult.Error != nil {
|
||||
return domain.Article{}, creationResult.Error
|
||||
}
|
||||
|
||||
var createdArticle entity.Article
|
||||
creationResult.Scan(&createdArticle)
|
||||
|
||||
return mappers.ArticleToDomain(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, mappers.ArticleToDomain(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 := mappers.ArticleToDomain(localArticle)
|
||||
|
||||
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 mappers.ArticleToDomain(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 _ domain.Repository = &ArticleRepository{}
|
||||
@@ -0,0 +1,80 @@
|
||||
package persistence
|
||||
|
||||
import (
|
||||
domain "RenewCMS/internal/domain/image"
|
||||
"RenewCMS/internal/infrastructure/persistence/mappers"
|
||||
entity "RenewCMS/internal/infrastructure/persistence/models"
|
||||
"errors"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type ImageRepository struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
func NewImageRepository(db *gorm.DB) *ImageRepository {
|
||||
return &ImageRepository{db}
|
||||
}
|
||||
|
||||
var contentTypeExtensions = map[string]string{
|
||||
"image/png": ".png",
|
||||
"image/jpeg": ".jpeg",
|
||||
"image/webp": ".webp",
|
||||
"image/svg+xml": ".svg",
|
||||
}
|
||||
|
||||
func (i ImageRepository) Create(input domain.ImageInput) (domain.Image, error) {
|
||||
uploadDir := os.Getenv("UPLOAD_DIR")
|
||||
|
||||
fileBytes, err := io.ReadAll(input.Content)
|
||||
if err != nil {
|
||||
return domain.Image{}, err
|
||||
}
|
||||
|
||||
err = os.MkdirAll(uploadDir, 0755)
|
||||
if err != nil {
|
||||
return domain.Image{}, err
|
||||
}
|
||||
|
||||
extension := contentTypeExtensions[input.ContentType]
|
||||
if extension == "" {
|
||||
return domain.Image{}, errors.New("the file must be a PNG, JPEG, WEBP, or SVG image")
|
||||
}
|
||||
|
||||
newName := uuid.NewString() + extension
|
||||
finalPath := filepath.Join(uploadDir, newName)
|
||||
err = os.WriteFile(finalPath, fileBytes, 0666)
|
||||
if err != nil {
|
||||
return domain.Image{}, err
|
||||
}
|
||||
|
||||
newImage := i.db.Create(&domain.Image{Path: "/static/uploadedImages/" + newName})
|
||||
var createdImage entity.Image
|
||||
newImage.Scan(&createdImage)
|
||||
|
||||
return mappers.ImageToDomain(createdImage), nil
|
||||
}
|
||||
|
||||
func (i ImageRepository) Delete(id uint32) error {
|
||||
uploadDir := os.Getenv("UPLOAD_DIR")
|
||||
var entityImage entity.Image
|
||||
err := i.db.Model(&entity.Image{}).First(&entityImage, id).Error
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fileName := filepath.Base(entityImage.Path)
|
||||
err = os.Remove(filepath.Join(uploadDir, fileName))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return i.db.Delete(&entity.Image{}, id).Error
|
||||
}
|
||||
|
||||
var _ domain.Repository = &ImageRepository{}
|
||||
@@ -0,0 +1,138 @@
|
||||
package persistence
|
||||
|
||||
import (
|
||||
domainUser "RenewCMS/internal/domain/user"
|
||||
"RenewCMS/internal/infrastructure/persistence/mappers"
|
||||
entity "RenewCMS/internal/infrastructure/persistence/models"
|
||||
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type UserRepository struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
func NewUserRepository(db *gorm.DB) *UserRepository {
|
||||
return &UserRepository{db}
|
||||
}
|
||||
|
||||
func (u *UserRepository) Get(id uint32) (domainUser.User, error) {
|
||||
var localUser entity.User
|
||||
err := u.db.Model(&entity.User{}).First(&localUser, id).Error
|
||||
if err != nil {
|
||||
return domainUser.User{}, err
|
||||
}
|
||||
|
||||
return mappers.UserToDomain(localUser), nil
|
||||
}
|
||||
|
||||
func (u *UserRepository) Create(user domainUser.User) (domainUser.User, error) {
|
||||
hashedPassword, _ := bcrypt.GenerateFromPassword([]byte(user.Password), 12)
|
||||
hashedVerificationCode, _ := bcrypt.GenerateFromPassword([]byte(user.VerificationCode), 12)
|
||||
|
||||
creationResult := u.db.Create(&entity.User{
|
||||
Username: user.Username,
|
||||
Password: string(hashedPassword),
|
||||
Email: user.Email,
|
||||
VerificationCode: string(hashedVerificationCode),
|
||||
VerificationExpiration: user.VerificationExpiration,
|
||||
})
|
||||
if creationResult.Error != nil {
|
||||
return domainUser.User{}, creationResult.Error
|
||||
}
|
||||
|
||||
var createdUser entity.User
|
||||
creationResult.Scan(&createdUser)
|
||||
|
||||
return mappers.UserToDomain(createdUser),
|
||||
nil
|
||||
}
|
||||
|
||||
func (u *UserRepository) Delete(id uint32) error {
|
||||
return u.db.Delete(&domainUser.User{}, id).Error
|
||||
}
|
||||
|
||||
func (u *UserRepository) GetAll() []domainUser.User {
|
||||
var users []entity.User
|
||||
u.db.Model(&entity.User{}).Find(&users)
|
||||
|
||||
var domainUsers []domainUser.User
|
||||
for _, localUser := range users {
|
||||
domainUsers = append(domainUsers, mappers.UserToDomain(localUser))
|
||||
}
|
||||
|
||||
return domainUsers
|
||||
}
|
||||
|
||||
func (u *UserRepository) GetByUsername(username string) (domainUser.User, error) {
|
||||
var localUser entity.User
|
||||
err := u.db.Model(&entity.User{}).Where("username = ?", username).First(&localUser).Error
|
||||
if err != nil {
|
||||
return domainUser.User{}, err
|
||||
}
|
||||
|
||||
return mappers.UserToDomain(localUser), nil
|
||||
}
|
||||
|
||||
func (u *UserRepository) GetByEmail(email string) (domainUser.User, error) {
|
||||
var localUser entity.User
|
||||
err := u.db.Model(&entity.User{}).Where("email = ?", email).First(&localUser).Error
|
||||
if err != nil {
|
||||
return domainUser.User{}, err
|
||||
}
|
||||
|
||||
return mappers.UserToDomain(localUser), nil
|
||||
}
|
||||
|
||||
func (u *UserRepository) UpdateVerificationStatus(userId uint32, isVerified bool) (domainUser.User, error) {
|
||||
var localUser entity.User
|
||||
err := u.db.Model(&entity.User{}).First(&localUser, userId).Error
|
||||
if err != nil {
|
||||
return domainUser.User{}, err
|
||||
}
|
||||
|
||||
localUser.IsVerified = isVerified
|
||||
err = u.db.Save(&localUser).Error
|
||||
if err != nil {
|
||||
return domainUser.User{}, err
|
||||
}
|
||||
|
||||
return mappers.UserToDomain(localUser), nil
|
||||
}
|
||||
|
||||
func (u *UserRepository) UpdatePassword(userId uint32, password string) (domainUser.User, error) {
|
||||
var localUser entity.User
|
||||
err := u.db.Model(&entity.User{}).First(&localUser, userId).Error
|
||||
if err != nil {
|
||||
return domainUser.User{}, err
|
||||
}
|
||||
|
||||
hashedPassword, _ := bcrypt.GenerateFromPassword([]byte(password), 12)
|
||||
localUser.Password = string(hashedPassword)
|
||||
err = u.db.Save(&localUser).Error
|
||||
if err != nil {
|
||||
return domainUser.User{}, err
|
||||
}
|
||||
|
||||
return mappers.UserToDomain(localUser), nil
|
||||
}
|
||||
|
||||
func (u *UserRepository) UpdatePasswordResetCode(userId uint32, code string) (domainUser.User, error) {
|
||||
var localUser entity.User
|
||||
err := u.db.Model(&entity.User{}).First(&localUser, userId).Error
|
||||
if err != nil {
|
||||
return domainUser.User{}, err
|
||||
}
|
||||
|
||||
hashedCode, _ := bcrypt.GenerateFromPassword([]byte(code), 12)
|
||||
localUser.PasswordResetCode = string(hashedCode)
|
||||
err = u.db.Save(&localUser).Error
|
||||
if err != nil {
|
||||
return domainUser.User{}, err
|
||||
}
|
||||
|
||||
return mappers.UserToDomain(localUser), nil
|
||||
}
|
||||
|
||||
var _ domainUser.Repository = &UserRepository{}
|
||||
@@ -0,0 +1,30 @@
|
||||
package useCases
|
||||
|
||||
import (
|
||||
"RenewCMS/internal/domain/article"
|
||||
)
|
||||
|
||||
type CreateArticleUseCase struct {
|
||||
articleRepository article.Repository
|
||||
}
|
||||
|
||||
type CreateArticleCommand struct {
|
||||
Title string
|
||||
Body string
|
||||
}
|
||||
|
||||
func NewCreateArticleUseCase(articleRepository article.Repository) *CreateArticleUseCase {
|
||||
return &CreateArticleUseCase{articleRepository}
|
||||
}
|
||||
|
||||
func (g *CreateArticleUseCase) CreateArticle(createArticle CreateArticleCommand) (article.Article, error) {
|
||||
if len(createArticle.Title) < 3 || len(createArticle.Title) > 200 {
|
||||
return article.Article{}, article.ErrInvalidTitle
|
||||
}
|
||||
|
||||
return g.articleRepository.Create(article.Article{
|
||||
Title: createArticle.Title,
|
||||
Body: createArticle.Body,
|
||||
IsOnline: false,
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package useCases
|
||||
|
||||
import (
|
||||
"RenewCMS/internal/domain/image"
|
||||
)
|
||||
|
||||
type CreateImageUseCase struct {
|
||||
imageRepository image.Repository
|
||||
}
|
||||
|
||||
func NewCreateImageUseCase(imageRepository image.Repository) *CreateImageUseCase {
|
||||
return &CreateImageUseCase{imageRepository}
|
||||
}
|
||||
|
||||
func (g *CreateImageUseCase) CreateImage(input image.ImageInput) (image.Image, error) {
|
||||
return g.imageRepository.Create(input)
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package useCases
|
||||
|
||||
import (
|
||||
"RenewCMS/internal/domain/user"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
type CreateUserUseCase struct {
|
||||
userRepository user.Repository
|
||||
}
|
||||
|
||||
type CreateUserCommand struct {
|
||||
Username string
|
||||
Password string
|
||||
Email string
|
||||
}
|
||||
|
||||
func NewCreateUserUseCase(userRepository user.Repository) *CreateUserUseCase {
|
||||
return &CreateUserUseCase{userRepository}
|
||||
}
|
||||
|
||||
func (g *CreateUserUseCase) CreateUser(createUser CreateUserCommand) (user.User, error) {
|
||||
rawUuid := uuid.NewString()
|
||||
|
||||
createdUser, err := g.userRepository.Create(user.User{
|
||||
Username: createUser.Username,
|
||||
Password: createUser.Password,
|
||||
PasswordResetCode: "",
|
||||
Email: createUser.Email,
|
||||
IsVerified: false,
|
||||
VerificationCode: rawUuid,
|
||||
VerificationExpiration: time.Now().Add(2 * time.Hour),
|
||||
})
|
||||
if err != nil {
|
||||
return user.User{}, err
|
||||
}
|
||||
|
||||
createdUser.VerificationCode = rawUuid
|
||||
return createdUser, nil
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package useCases
|
||||
|
||||
import "RenewCMS/internal/domain/article"
|
||||
|
||||
type DeleteArticleUseCase struct {
|
||||
articleRepository article.Repository
|
||||
}
|
||||
|
||||
func NewDeleteArticleUseCase(articleRepository article.Repository) *DeleteArticleUseCase {
|
||||
return &DeleteArticleUseCase{articleRepository}
|
||||
}
|
||||
|
||||
func (g *DeleteArticleUseCase) DeleteArticle(userId uint32) error {
|
||||
return g.articleRepository.Delete(userId)
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package useCases
|
||||
|
||||
import "RenewCMS/internal/domain/image"
|
||||
|
||||
type DeleteImageUseCase struct {
|
||||
imageRepository image.Repository
|
||||
}
|
||||
|
||||
func NewDeleteImageUseCase(imageRepository image.Repository) *DeleteImageUseCase {
|
||||
return &DeleteImageUseCase{imageRepository}
|
||||
}
|
||||
|
||||
func (g *DeleteImageUseCase) DeleteImage(imageId uint32) error {
|
||||
return g.imageRepository.Delete(imageId)
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package useCases
|
||||
|
||||
import "RenewCMS/internal/domain/user"
|
||||
|
||||
type DeleteUserUseCase struct {
|
||||
userRepository user.Repository
|
||||
}
|
||||
|
||||
func NewDeleteUserUseCase(userRepository user.Repository) *DeleteUserUseCase {
|
||||
return &DeleteUserUseCase{userRepository}
|
||||
}
|
||||
|
||||
func (g *DeleteUserUseCase) DeleteUser(userId uint32) error {
|
||||
return g.userRepository.Delete(userId)
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package useCases
|
||||
|
||||
import "RenewCMS/internal/domain/article"
|
||||
|
||||
type GetArticleUseCase struct {
|
||||
articleRepository article.Repository
|
||||
}
|
||||
|
||||
func NewGetArticleUseCase(articleRepository article.Repository) *GetArticleUseCase {
|
||||
return &GetArticleUseCase{articleRepository}
|
||||
}
|
||||
|
||||
func (u *GetArticleUseCase) GetOnlineArticle(id uint32) (article.Article, error) {
|
||||
art, err := u.articleRepository.Get(id)
|
||||
if err != nil {
|
||||
return article.Article{}, err
|
||||
}
|
||||
|
||||
if !art.IsOnline {
|
||||
return article.Article{}, article.ErrArticleNotFound
|
||||
}
|
||||
|
||||
return art, nil
|
||||
}
|
||||
|
||||
func (u *GetArticleUseCase) GetArticle(id uint32) (article.Article, error) {
|
||||
return u.articleRepository.Get(id)
|
||||
}
|
||||
|
||||
func (g *GetArticleUseCase) GetArticleByName(name string) (article.Article, error) {
|
||||
return g.articleRepository.GetByName(name)
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package useCases
|
||||
|
||||
import "RenewCMS/internal/domain/user"
|
||||
|
||||
type GetUserUseCase struct {
|
||||
userRepository user.Repository
|
||||
}
|
||||
|
||||
func NewGetUserUseCase(userRepository user.Repository) *GetUserUseCase {
|
||||
return &GetUserUseCase{userRepository}
|
||||
}
|
||||
|
||||
func (g *GetUserUseCase) GetUser(id uint32) (user.User, error) {
|
||||
return g.userRepository.Get(id)
|
||||
}
|
||||
|
||||
func (g *GetUserUseCase) GetUserByUsername(username string) (user.User, error) {
|
||||
return g.userRepository.GetByUsername(username)
|
||||
}
|
||||
|
||||
func (g *GetUserUseCase) GetUserByEmail(email string) (user.User, error) {
|
||||
return g.userRepository.GetByEmail(email)
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package useCases
|
||||
|
||||
import "RenewCMS/internal/domain/article"
|
||||
|
||||
type ListArticlesUseCase struct {
|
||||
articleRepository article.Repository
|
||||
}
|
||||
|
||||
func NewListArticlesUseCase(articleRepository article.Repository) *ListArticlesUseCase {
|
||||
return &ListArticlesUseCase{articleRepository}
|
||||
}
|
||||
|
||||
func (u *ListArticlesUseCase) ListOnlineArticles() ([]article.Article, error) {
|
||||
online := true
|
||||
return u.articleRepository.FindByFilters(article.Filters{
|
||||
IsOnline: &online,
|
||||
})
|
||||
}
|
||||
|
||||
func (u *ListArticlesUseCase) ListAllArticles() ([]article.Article, error) {
|
||||
return u.articleRepository.FindByFilters(article.Filters{})
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package useCases
|
||||
|
||||
import "RenewCMS/internal/domain/user"
|
||||
|
||||
type ListUsersUseCase struct {
|
||||
userRepository user.Repository
|
||||
}
|
||||
|
||||
func NewListUsersUseCase(userRepository user.Repository) *ListUsersUseCase {
|
||||
return &ListUsersUseCase{userRepository}
|
||||
}
|
||||
|
||||
func (g *ListUsersUseCase) ListUsers() []user.User {
|
||||
return g.userRepository.GetAll()
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package useCases
|
||||
|
||||
import "RenewCMS/internal/domain/mail"
|
||||
|
||||
type SendMailUseCase struct {
|
||||
mailRepository mail.Repository
|
||||
}
|
||||
|
||||
func NewSendMailUseCase(mailRepository mail.Repository) *SendMailUseCase {
|
||||
return &SendMailUseCase{mailRepository}
|
||||
}
|
||||
|
||||
func (g *SendMailUseCase) SendMail(receiverAddress string, templateName string, data any) error {
|
||||
return g.mailRepository.Send(receiverAddress, templateName, data)
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package useCases
|
||||
|
||||
import "RenewCMS/internal/domain/article"
|
||||
|
||||
type UpdateArticleUseCase struct {
|
||||
articleRepository article.Repository
|
||||
}
|
||||
|
||||
func NewUpdateArticleUseCase(articleRepository article.Repository) *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)
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package useCases
|
||||
|
||||
import "RenewCMS/internal/domain/user"
|
||||
|
||||
type UpdateUserUseCase struct {
|
||||
userRepository user.Repository
|
||||
}
|
||||
|
||||
func NewUpdateUserUseCase(userRepository user.Repository) *UpdateUserUseCase {
|
||||
return &UpdateUserUseCase{userRepository}
|
||||
}
|
||||
|
||||
func (g *UpdateUserUseCase) UpdateVerificationStatus(userId uint32, isVerified bool) (user.User, error) {
|
||||
return g.userRepository.UpdateVerificationStatus(userId, isVerified)
|
||||
}
|
||||
|
||||
func (g *UpdateUserUseCase) UpdatePasswordResetCode(userId uint32, code string) (user.User, error) {
|
||||
return g.userRepository.UpdatePasswordResetCode(userId, code)
|
||||
}
|
||||
|
||||
func (g *UpdateUserUseCase) UpdatePassword(userId uint32, password string) (user.User, error) {
|
||||
return g.userRepository.UpdatePassword(userId, password)
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
const conn = new Mongo()
|
||||
const db = conn.getDB("gohcms")
|
||||
|
||||
db.createCollection('articles', {
|
||||
validator: {
|
||||
$jsonSchema: {
|
||||
required: ['titleID', 'title', 'content', 'date', 'tags', 'online'],
|
||||
properties: {
|
||||
_id: {
|
||||
bsonType: 'objectId'
|
||||
},
|
||||
titleID: {
|
||||
bsonType: 'string',
|
||||
pattern: '^[-a-z]+$'
|
||||
},
|
||||
title: {
|
||||
bsonType: 'string'
|
||||
},
|
||||
content: {
|
||||
bsonType: 'object'
|
||||
},
|
||||
date: {
|
||||
bsonType: 'number'
|
||||
},
|
||||
tags: {
|
||||
bsonType: 'array'
|
||||
},
|
||||
online: {
|
||||
bsonType: 'bool'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
db.createCollection('users', {
|
||||
validator: {
|
||||
$jsonSchema: {
|
||||
required: ['email', 'password'],
|
||||
properties: {
|
||||
_id: {
|
||||
bsonType: 'objectId'
|
||||
},
|
||||
email: {
|
||||
bsonType: 'string'
|
||||
},
|
||||
password: {
|
||||
bsonType: 'string'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
db.users.insertOne({
|
||||
email: "root",
|
||||
password: "root"
|
||||
})
|
||||
@@ -0,0 +1,41 @@
|
||||
#!/bin/sh
|
||||
|
||||
OUTPUT_DIR=./bin
|
||||
GO_FILE_PATH="./cmd/server/main.go"
|
||||
PROGRAM_NAME=RenewCMS
|
||||
WEB_DIR="./web"
|
||||
|
||||
echo "Starting frontend build in $WEB_DIR..."
|
||||
if [ -d "$WEB_DIR" ]; then
|
||||
(cd "$WEB_DIR" && bun run build)
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "Frontend build failed. Aborting process."
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
echo "Error: Directory $WEB_DIR not found."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Frontend build completed successfully."
|
||||
echo "----------------------------------------"
|
||||
|
||||
GOOS=$(go env GOOS)
|
||||
GOARCH=$(go env GOARCH)
|
||||
|
||||
output_name="$OUTPUT_DIR/${PROGRAM_NAME}_${GOOS}_${GOARCH}"
|
||||
if [ "$GOOS" = "windows" ]; then
|
||||
output_name+='.exe'
|
||||
fi
|
||||
|
||||
echo "Detected platform: $GOOS/$GOARCH"
|
||||
echo "Building for $GOOS/$GOARCH..."
|
||||
|
||||
env GOOS=$GOOS GOARCH=$GOARCH go build -o "$output_name" "$GO_FILE_PATH"
|
||||
|
||||
if [ $? -eq 0 ]; then
|
||||
echo "Compilation successful: $output_name"
|
||||
else
|
||||
echo "Compilation for $GOOS/$GOARCH failed."
|
||||
exit 1
|
||||
fi
|
||||
@@ -0,0 +1,27 @@
|
||||
#!/bin/sh
|
||||
|
||||
OUTPUT_DIR=./bin
|
||||
GO_FILE_PATH="./cmd/server/main.go"
|
||||
PROGRAM_NAME=RenewCMS
|
||||
platforms=("windows/amd64" "windows/arm64" "linux/amd64" "linux/arm64" "darwin/amd64" "darwin/arm64")
|
||||
|
||||
for platform in "${platforms[@]}"
|
||||
do
|
||||
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
|
||||
|
||||
echo "Building for $GOOS/$GOARCH..."
|
||||
env GOOS=$GOOS GOARCH=$GOARCH go build -o "$output_name" "$GO_FILE_PATH"
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "Compilation for $GOOS/$GOARCH failed."
|
||||
exit $?
|
||||
fi
|
||||
done
|
||||
|
||||
echo "Compilation successful for all platforms."
|
||||
@@ -0,0 +1,99 @@
|
||||
package test
|
||||
|
||||
import (
|
||||
"RenewCMS/internal/domain/article"
|
||||
"RenewCMS/internal/infrastructure/useCases"
|
||||
"errors"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type mockArticleRepo struct {
|
||||
articles map[uint32]article.Article
|
||||
}
|
||||
|
||||
func (m *mockArticleRepo) FindByFilters(f article.Filters) ([]article.Article, error) {
|
||||
var results []article.Article
|
||||
for _, a := range m.articles {
|
||||
if f.IsOnline != nil && a.IsOnline == *f.IsOnline {
|
||||
results = append(results, a)
|
||||
}
|
||||
}
|
||||
return results, nil
|
||||
}
|
||||
|
||||
func (m *mockArticleRepo) Get(id uint32) (article.Article, error) {
|
||||
a, ok := m.articles[id]
|
||||
if !ok {
|
||||
return article.Article{}, article.ErrArticleNotFound
|
||||
}
|
||||
return a, nil
|
||||
}
|
||||
|
||||
func (m *mockArticleRepo) Create(a article.Article) (article.Article, error) {
|
||||
a.ID = uint32(len(m.articles) + 1)
|
||||
m.articles[a.ID] = a
|
||||
return a, nil
|
||||
}
|
||||
|
||||
func (m *mockArticleRepo) Delete(id uint32) error {
|
||||
delete(m.articles, id)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *mockArticleRepo) GetByName(n string) (article.Article, error) { return article.Article{}, nil }
|
||||
func (m *mockArticleRepo) GetAll() []article.Article { return nil }
|
||||
func (m *mockArticleRepo) UpdateBody(id uint32, b string) (article.Article, error) {
|
||||
return article.Article{}, nil
|
||||
}
|
||||
func (m *mockArticleRepo) UpdateIsOnline(id uint32, o bool) (article.Article, error) {
|
||||
return article.Article{}, nil
|
||||
}
|
||||
func (m *mockArticleRepo) AddImage(p, i uint32) error { return nil }
|
||||
|
||||
var _ article.Repository = &mockArticleRepo{}
|
||||
|
||||
func TestCreateArticle(t *testing.T) {
|
||||
repo := &mockArticleRepo{articles: make(map[uint32]article.Article)}
|
||||
uc := useCases.NewCreateArticleUseCase(repo)
|
||||
|
||||
t.Run("Valid Title", func(t *testing.T) {
|
||||
cmd := useCases.CreateArticleCommand{Title: "Valid Title", Body: "Content"}
|
||||
res, err := uc.CreateArticle(cmd)
|
||||
if err != nil {
|
||||
t.Fatalf("Expected no error, got %v", err)
|
||||
}
|
||||
if res.Title != cmd.Title {
|
||||
t.Errorf("Expected %s, got %s", cmd.Title, res.Title)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("Short Title", func(t *testing.T) {
|
||||
cmd := useCases.CreateArticleCommand{Title: "Ab", Body: "Content"}
|
||||
_, err := uc.CreateArticle(cmd)
|
||||
if !errors.Is(err, article.ErrInvalidTitle) {
|
||||
t.Errorf("Expected ErrInvalidTitle")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestGetOnlineArticle(t *testing.T) {
|
||||
repo := &mockArticleRepo{articles: map[uint32]article.Article{
|
||||
1: {ID: 1, IsOnline: true},
|
||||
2: {ID: 2, IsOnline: false},
|
||||
}}
|
||||
uc := useCases.NewGetArticleUseCase(repo)
|
||||
|
||||
t.Run("Article Online", func(t *testing.T) {
|
||||
_, err := uc.GetOnlineArticle(1)
|
||||
if err != nil {
|
||||
t.Errorf("Expected article to be found")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("Article Offline", func(t *testing.T) {
|
||||
_, err := uc.GetOnlineArticle(2)
|
||||
if !errors.Is(err, article.ErrArticleNotFound) {
|
||||
t.Errorf("Offline article should return not found")
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
package test
|
||||
|
||||
import (
|
||||
"RenewCMS/internal/domain/image"
|
||||
"RenewCMS/internal/infrastructure/useCases"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type mockImageRepo struct {
|
||||
images map[uint32]image.Image
|
||||
}
|
||||
|
||||
func (m *mockImageRepo) Create(input image.ImageInput) (image.Image, error) {
|
||||
newId := uint32(len(m.images) + 1)
|
||||
img := image.Image{
|
||||
ID: newId,
|
||||
Path: "/static/" + input.Filename,
|
||||
}
|
||||
m.images[newId] = img
|
||||
return img, nil
|
||||
}
|
||||
|
||||
func (m *mockImageRepo) Delete(id uint32) error {
|
||||
delete(m.images, id)
|
||||
return nil
|
||||
}
|
||||
|
||||
var _ image.Repository = &mockImageRepo{}
|
||||
|
||||
func TestImageUseCases(t *testing.T) {
|
||||
repo := &mockImageRepo{images: make(map[uint32]image.Image)}
|
||||
createUc := useCases.NewCreateImageUseCase(repo)
|
||||
deleteUc := useCases.NewDeleteImageUseCase(repo)
|
||||
|
||||
t.Run("Create Image", func(t *testing.T) {
|
||||
input := image.ImageInput{
|
||||
Content: strings.NewReader("fake-image-data"),
|
||||
Filename: "test.png",
|
||||
ContentType: "image/png",
|
||||
}
|
||||
|
||||
res, err := createUc.CreateImage(input)
|
||||
if err != nil {
|
||||
t.Fatalf("CreateImage failed: %v", err)
|
||||
}
|
||||
if res.ID == 0 {
|
||||
t.Errorf("Expected valid ID, got 0")
|
||||
}
|
||||
if !strings.Contains(res.Path, "test.png") {
|
||||
t.Errorf("Path mismatch: %s", res.Path)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("Delete Image", func(t *testing.T) {
|
||||
repo.images[99] = image.Image{ID: 99, Path: "/static/delete-me.png"}
|
||||
|
||||
err := deleteUc.DeleteImage(99)
|
||||
if err != nil {
|
||||
t.Fatalf("DeleteImage failed: %v", err)
|
||||
}
|
||||
|
||||
if _, exists := repo.images[99]; exists {
|
||||
t.Errorf("Image 99 should have been deleted")
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
package test
|
||||
|
||||
import (
|
||||
"RenewCMS/internal/domain/mail"
|
||||
"RenewCMS/internal/infrastructure/useCases"
|
||||
"errors"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type mockMailRepo struct {
|
||||
SentEmails []struct {
|
||||
Address string
|
||||
Template string
|
||||
Data any
|
||||
}
|
||||
ShouldFail bool
|
||||
}
|
||||
|
||||
func (m *mockMailRepo) Send(receiverAddress string, templateName string, data any) error {
|
||||
if m.ShouldFail {
|
||||
return errors.New("smtp error")
|
||||
}
|
||||
m.SentEmails = append(m.SentEmails, struct {
|
||||
Address string
|
||||
Template string
|
||||
Data any
|
||||
}{receiverAddress, templateName, data})
|
||||
return nil
|
||||
}
|
||||
|
||||
var _ mail.Repository = &mockMailRepo{}
|
||||
|
||||
func TestSendMail(t *testing.T) {
|
||||
repo := &mockMailRepo{}
|
||||
uc := useCases.NewSendMailUseCase(repo)
|
||||
|
||||
t.Run("Successful Email Send", func(t *testing.T) {
|
||||
repo.ShouldFail = false
|
||||
err := uc.SendMail("user@example.com", "welcome", "User123")
|
||||
if err != nil {
|
||||
t.Fatalf("Expected no error, got %v", err)
|
||||
}
|
||||
if len(repo.SentEmails) != 1 {
|
||||
t.Errorf("Expected 1 email to be sent, got %d", len(repo.SentEmails))
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("Failed Email Send", func(t *testing.T) {
|
||||
repo.ShouldFail = true
|
||||
err := uc.SendMail("fail@example.com", "reset", nil)
|
||||
if err == nil {
|
||||
t.Errorf("Expected an error but got nil")
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
package test
|
||||
|
||||
import (
|
||||
"RenewCMS/internal/domain/user"
|
||||
"RenewCMS/internal/infrastructure/useCases"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type mockUserRepo struct {
|
||||
users map[uint32]user.User
|
||||
}
|
||||
|
||||
func (m *mockUserRepo) Create(u user.User) (user.User, error) {
|
||||
u.ID = uint32(len(m.users) + 1)
|
||||
m.users[u.ID] = u
|
||||
return u, nil
|
||||
}
|
||||
|
||||
func (m *mockUserRepo) Get(id uint32) (user.User, error) {
|
||||
u, ok := m.users[id]
|
||||
if !ok {
|
||||
return user.User{}, nil
|
||||
}
|
||||
return u, nil
|
||||
}
|
||||
|
||||
func (m *mockUserRepo) GetAll() []user.User {
|
||||
var list []user.User
|
||||
for _, u := range m.users {
|
||||
list = append(list, u)
|
||||
}
|
||||
return list
|
||||
}
|
||||
|
||||
func (m *mockUserRepo) UpdateVerificationStatus(id uint32, v bool) (user.User, error) {
|
||||
u := m.users[id]
|
||||
u.IsVerified = v
|
||||
m.users[id] = u
|
||||
return u, nil
|
||||
}
|
||||
|
||||
func (m *mockUserRepo) GetByUsername(n string) (user.User, error) { return user.User{}, nil }
|
||||
func (m *mockUserRepo) GetByEmail(e string) (user.User, error) { return user.User{}, nil }
|
||||
func (m *mockUserRepo) Delete(id uint32) error { return nil }
|
||||
func (m *mockUserRepo) UpdatePassword(id uint32, p string) (user.User, error) {
|
||||
return user.User{}, nil
|
||||
}
|
||||
func (m *mockUserRepo) UpdatePasswordResetCode(id uint32, c string) (user.User, error) {
|
||||
return user.User{}, nil
|
||||
}
|
||||
|
||||
var _ user.Repository = &mockUserRepo{}
|
||||
|
||||
func TestCreateUser(t *testing.T) {
|
||||
repo := &mockUserRepo{users: make(map[uint32]user.User)}
|
||||
uc := useCases.NewCreateUserUseCase(repo)
|
||||
|
||||
cmd := useCases.CreateUserCommand{
|
||||
Username: "testuser",
|
||||
Password: "password123",
|
||||
Email: "test@example.com",
|
||||
}
|
||||
|
||||
res, err := uc.CreateUser(cmd)
|
||||
if err != nil {
|
||||
t.Fatalf("User creation failed: %v", err)
|
||||
}
|
||||
if res.Username != cmd.Username {
|
||||
t.Errorf("Username mismatch")
|
||||
}
|
||||
if res.VerificationCode == "" {
|
||||
t.Errorf("Verification code was not generated")
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdateUserVerification(t *testing.T) {
|
||||
repo := &mockUserRepo{users: map[uint32]user.User{
|
||||
1: {ID: 1, IsVerified: false},
|
||||
}}
|
||||
uc := useCases.NewUpdateUserUseCase(repo)
|
||||
|
||||
res, err := uc.UpdateVerificationStatus(1, true)
|
||||
if err != nil {
|
||||
t.Fatalf("Update failed: %v", err)
|
||||
}
|
||||
if !res.IsVerified {
|
||||
t.Errorf("User should be verified")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
[*.{js,jsx,mjs,cjs,ts,tsx,mts,cts,vue,css,scss,sass,less,styl}]
|
||||
charset = utf-8
|
||||
indent_size = 4
|
||||
indent_style = tab
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
end_of_line = lf
|
||||
max_line_length = 100
|
||||
@@ -0,0 +1 @@
|
||||
* text=auto eol=lf
|
||||
@@ -0,0 +1,36 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
|
||||
node_modules
|
||||
.DS_Store
|
||||
dist
|
||||
dist-ssr
|
||||
coverage
|
||||
*.local
|
||||
|
||||
# Editor directories and files
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
.idea
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
|
||||
*.tsbuildinfo
|
||||
|
||||
.eslintcache
|
||||
|
||||
# Cypress
|
||||
/cypress/videos/
|
||||
/cypress/screenshots/
|
||||
|
||||
# Vitest
|
||||
__screenshots__/
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/prettierrc",
|
||||
"semi": false,
|
||||
"singleQuote": true,
|
||||
"useTabs": true
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
# .
|
||||
|
||||
This template should help get you started developing with Vue 3 in Vite.
|
||||
|
||||
## Recommended IDE Setup
|
||||
|
||||
[VS Code](https://code.visualstudio.com/) + [Vue (Official)](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur).
|
||||
|
||||
## Recommended Browser Setup
|
||||
|
||||
- Chromium-based browsers (Chrome, Edge, Brave, etc.):
|
||||
- [Vue.js devtools](https://chromewebstore.google.com/detail/vuejs-devtools/nhdogjmejiglipccpnnnanhbledajbpd)
|
||||
- [Turn on Custom Object Formatter in Chrome DevTools](http://bit.ly/object-formatters)
|
||||
- Firefox:
|
||||
- [Vue.js devtools](https://addons.mozilla.org/en-US/firefox/addon/vue-js-devtools/)
|
||||
- [Turn on Custom Object Formatter in Firefox DevTools](https://fxdx.dev/firefox-devtools-custom-object-formatters/)
|
||||
|
||||
## Type Support for `.vue` Imports in TS
|
||||
|
||||
TypeScript cannot handle type information for `.vue` imports by default, so we replace the `tsc` CLI with `vue-tsc` for type checking. In editors, we need [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) to make the TypeScript language service aware of `.vue` types.
|
||||
|
||||
## Customize configuration
|
||||
|
||||
See [Vite Configuration Reference](https://vite.dev/config/).
|
||||
|
||||
## Project Setup
|
||||
|
||||
```sh
|
||||
bun install
|
||||
```
|
||||
|
||||
### Compile and Hot-Reload for Development
|
||||
|
||||
```sh
|
||||
bun dev
|
||||
```
|
||||
|
||||
### Type-Check, Compile and Minify for Production
|
||||
|
||||
```sh
|
||||
bun run build
|
||||
```
|
||||
|
||||
### Lint with [ESLint](https://eslint.org/)
|
||||
|
||||
```sh
|
||||
bun lint
|
||||
```
|
||||
Vendored
-2
@@ -1,2 +0,0 @@
|
||||
/// <reference types="vite/client" />
|
||||
declare const __APP_ENV__: Record<string, string>
|
||||
@@ -1,13 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Admin panel</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="module" src="/src/main.ts"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,30 +0,0 @@
|
||||
user nginx;
|
||||
worker_processes 1;
|
||||
error_log /var/log/nginx/error.log warn;
|
||||
pid /var/run/nginx.pid;
|
||||
events {
|
||||
worker_connections 1024;
|
||||
}
|
||||
http {
|
||||
include /etc/nginx/mime.types;
|
||||
default_type application/octet-stream;
|
||||
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
|
||||
'$status $body_bytes_sent "$http_referer" '
|
||||
'"$http_user_agent" "$http_x_forwarded_for"';
|
||||
access_log /var/log/nginx/access.log main;
|
||||
sendfile on;
|
||||
keepalive_timeout 65;
|
||||
server {
|
||||
listen 80;
|
||||
server_name localhost;
|
||||
location / {
|
||||
root /app;
|
||||
index index.html;
|
||||
try_files $uri $uri/ /index.html;
|
||||
}
|
||||
error_page 500 502 503 504 /50x.html;
|
||||
location = /50x.html {
|
||||
root /usr/share/nginx/html;
|
||||
}
|
||||
}
|
||||
}
|
||||
Generated
-3264
File diff suppressed because it is too large
Load Diff
@@ -1,28 +0,0 @@
|
||||
{
|
||||
"name": "admin-gui",
|
||||
"version": "0.0.0",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "run-p type-check build-only",
|
||||
"preview": "vite preview --port 4173",
|
||||
"build-only": "vite build",
|
||||
"type-check": "vue-tsc --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"@tinymce/tinymce-vue": "^4.0.7",
|
||||
"pinia": "^2.0.28",
|
||||
"tabulator-tables": "^5.4.3",
|
||||
"vue": "^3.2.38",
|
||||
"vue-router": "^4.1.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^16.11.56",
|
||||
"@types/tabulator-tables": "^5.4.2",
|
||||
"@vitejs/plugin-vue": "^3.0.3",
|
||||
"@vue/tsconfig": "^0.1.3",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"typescript": "~4.7.4",
|
||||
"vite": "^3.0.9",
|
||||
"vue-tsc": "^1.0.5"
|
||||
}
|
||||
}
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 4.2 KiB |
File diff suppressed because one or more lines are too long
@@ -1,3 +0,0 @@
|
||||
This is where language files should be placed.
|
||||
|
||||
Please DO NOT translate these directly use this service: https://www.transifex.com/projects/p/tinymce/
|
||||
@@ -1,412 +0,0 @@
|
||||
/*!
|
||||
* TinyMCE Language Pack
|
||||
*
|
||||
* Copyright (c) 2022 Ephox Corporation DBA Tiny Technologies, Inc.
|
||||
* Licensed under the Tiny commercial license. See https://www.tiny.cloud/legal/
|
||||
*/
|
||||
tinymce.addI18n('fr_FR', {
|
||||
"Redo": "Rétablir",
|
||||
"Undo": "Annuler",
|
||||
"Cut": "Couper",
|
||||
"Copy": "Copier",
|
||||
"Paste": "Coller",
|
||||
"Select all": "Sélectionner tout",
|
||||
"New document": "Nouveau document",
|
||||
"Ok": "OK",
|
||||
"Cancel": "Annuler",
|
||||
"Visual aids": "Aides visuelles",
|
||||
"Bold": "Gras",
|
||||
"Italic": "Italique",
|
||||
"Underline": "Souligné",
|
||||
"Strikethrough": "Barré",
|
||||
"Superscript": "Exposant",
|
||||
"Subscript": "Indice",
|
||||
"Clear formatting": "Effacer la mise en forme",
|
||||
"Remove": "Retiré",
|
||||
"Align left": "Aligner à gauche",
|
||||
"Align center": "Centrer",
|
||||
"Align right": "Aligner à droite",
|
||||
"No alignment": "Aucun alignement",
|
||||
"Justify": "Justifier",
|
||||
"Bullet list": "Liste à puces",
|
||||
"Numbered list": "Liste numérotée",
|
||||
"Decrease indent": "Réduire le retrait",
|
||||
"Increase indent": "Augmenter le retrait",
|
||||
"Close": "Fermer",
|
||||
"Formats": "Formats",
|
||||
"Your browser doesn't support direct access to the clipboard. Please use the Ctrl+X/C/V keyboard shortcuts instead.": "Votre navigateur ne supporte pas l’accès direct au presse-papiers. Merci d'utiliser les raccourcis clavier Ctrl+X/C/V.",
|
||||
"Headings": "Titres",
|
||||
"Heading 1": "Titre 1",
|
||||
"Heading 2": "Titre 2",
|
||||
"Heading 3": "Titre 3",
|
||||
"Heading 4": "Titre 4",
|
||||
"Heading 5": "Titre 5",
|
||||
"Heading 6": "Titre 6",
|
||||
"Preformatted": "Préformaté",
|
||||
"Div": "Div",
|
||||
"Pre": "Préformaté",
|
||||
"Code": "Code",
|
||||
"Paragraph": "Paragraphe",
|
||||
"Blockquote": "Bloc de citation",
|
||||
"Inline": "En ligne",
|
||||
"Blocks": "Blocs",
|
||||
"Paste is now in plain text mode. Contents will now be pasted as plain text until you toggle this option off.": "Le collage est maintenant en mode texte brut. Les contenus seront collés sans retenir les formatages jusqu'à ce que vous désactivez cette option.",
|
||||
"Fonts": "Polices",
|
||||
"Font sizes": "Tailles de police",
|
||||
"Class": "Classe",
|
||||
"Browse for an image": "Rechercher une image",
|
||||
"OR": "OU",
|
||||
"Drop an image here": "Déposer une image ici",
|
||||
"Upload": "Télécharger",
|
||||
"Uploading image": "Téléversement d'une image",
|
||||
"Block": "Bloc",
|
||||
"Align": "Aligner",
|
||||
"Default": "Par défaut",
|
||||
"Circle": "Cercle",
|
||||
"Disc": "Disque",
|
||||
"Square": "Carré",
|
||||
"Lower Alpha": "Alphabet en minuscules",
|
||||
"Lower Greek": "Alphabet grec en minuscules",
|
||||
"Lower Roman": "Chiffre romain inférieur",
|
||||
"Upper Alpha": "Alphabet en majuscules",
|
||||
"Upper Roman": "Chiffre romain supérieur",
|
||||
"Anchor...": "Ancre...",
|
||||
"Anchor": "Ancre",
|
||||
"Name": "Nom",
|
||||
"ID": "ID",
|
||||
"ID should start with a letter, followed only by letters, numbers, dashes, dots, colons or underscores.": "L'ID doit commencer par une lettre, suivie uniquement par des lettres, numéros, tirets, points, deux-points et underscores.",
|
||||
"You have unsaved changes are you sure you want to navigate away?": "Vous avez des modifications non enregistrées, êtes-vous sûr de vouloir quitter la page ?",
|
||||
"Restore last draft": "Restaurer le dernier brouillon",
|
||||
"Special character...": "Caractère spécial...",
|
||||
"Special Character": "Caractère spécial",
|
||||
"Source code": "Code source",
|
||||
"Insert/Edit code sample": "Insérer / modifier une exemple de code",
|
||||
"Language": "Langue",
|
||||
"Code sample...": "Exemple de code...",
|
||||
"Left to right": "De gauche à droite",
|
||||
"Right to left": "De droite à gauche",
|
||||
"Title": "Titre",
|
||||
"Fullscreen": "Plein écran",
|
||||
"Action": "Action",
|
||||
"Shortcut": "Raccourci",
|
||||
"Help": "Aide",
|
||||
"Address": "Adresse",
|
||||
"Focus to menubar": "Mettre le focus sur la barre de menu",
|
||||
"Focus to toolbar": "Mettre le focus sur la barre d'outils",
|
||||
"Focus to element path": "Mettre le focus sur le chemin vers l'élément",
|
||||
"Focus to contextual toolbar": "Mettre le focus sur la barre d'outils contextuelle",
|
||||
"Insert link (if link plugin activated)": "Insérer un lien (si le plug-in link est activé)",
|
||||
"Save (if save plugin activated)": "Enregistrer (si le plug-in save est activé)",
|
||||
"Find (if searchreplace plugin activated)": "Rechercher (si le plug-in searchreplace est activé)",
|
||||
"Plugins installed ({0}):": "Plug-ins installés ({0}) :",
|
||||
"Premium plugins:": "Plug-ins premium :",
|
||||
"Learn more...": "En savoir plus...",
|
||||
"You are using {0}": "Vous utilisez {0}",
|
||||
"Plugins": "Plug-ins",
|
||||
"Handy Shortcuts": "Raccourcis utiles",
|
||||
"Horizontal line": "Ligne horizontale",
|
||||
"Insert/edit image": "Insérer/modifier image",
|
||||
"Alternative description": "Description alternative",
|
||||
"Accessibility": "Accessibilité",
|
||||
"Image is decorative": "L'image est décorative",
|
||||
"Source": "Source",
|
||||
"Dimensions": "Dimensions",
|
||||
"Constrain proportions": "Limiter les proportions",
|
||||
"General": "Général",
|
||||
"Advanced": "Options avancées",
|
||||
"Style": "Style",
|
||||
"Vertical space": "Espace vertical",
|
||||
"Horizontal space": "Espace horizontal",
|
||||
"Border": "Bordure",
|
||||
"Insert image": "Insérer une image",
|
||||
"Image...": "Image...",
|
||||
"Image list": "Liste des images",
|
||||
"Resize": "Redimensionner",
|
||||
"Insert date/time": "Insérer date/heure",
|
||||
"Date/time": "Date/heure",
|
||||
"Insert/edit link": "Insérer/modifier lien",
|
||||
"Text to display": "Texte à afficher",
|
||||
"Url": "URL",
|
||||
"Open link in...": "Ouvrir le lien dans...",
|
||||
"Current window": "Fenêtre active",
|
||||
"None": "Aucun",
|
||||
"New window": "Nouvelle fenêtre",
|
||||
"Open link": "Ouvrir le lien",
|
||||
"Remove link": "Enlever le lien",
|
||||
"Anchors": "Ancres",
|
||||
"Link...": "Lien...",
|
||||
"Paste or type a link": "Coller ou taper un lien",
|
||||
"The URL you entered seems to be an email address. Do you want to add the required mailto: prefix?": "L'URL que vous avez saisi semble être une adresse e-mail. Souhaitez-vous y ajouter le préfixe requis mailto: ?",
|
||||
"The URL you entered seems to be an external link. Do you want to add the required http:// prefix?": "L'URL que vous avez saisi semble être un lien externe. Souhaitez-vous y ajouter le préfixe requis mailto: ?",
|
||||
"The URL you entered seems to be an external link. Do you want to add the required https:// prefix?": "L'URL que vous avez saisie semble être un lien externe. Voulez-vous ajouter le préfixe https:// requis ?",
|
||||
"Link list": "Liste des liens",
|
||||
"Insert video": "Insérer une vidéo",
|
||||
"Insert/edit video": "Insérer/modifier une vidéo",
|
||||
"Insert/edit media": "Insérer/modifier un média",
|
||||
"Alternative source": "Source alternative",
|
||||
"Alternative source URL": "URL de la source alternative",
|
||||
"Media poster (Image URL)": "Affiche de média (URL de l'image)",
|
||||
"Paste your embed code below:": "Collez votre code incorporé ci-dessous :",
|
||||
"Embed": "Incorporer",
|
||||
"Media...": "Média...",
|
||||
"Nonbreaking space": "Espace insécable",
|
||||
"Page break": "Saut de page",
|
||||
"Paste as text": "Coller comme texte",
|
||||
"Preview": "Aperçu",
|
||||
"Print": "Imprimer",
|
||||
"Print...": "Imprimer...",
|
||||
"Save": "Enregistrer",
|
||||
"Find": "Rechercher",
|
||||
"Replace with": "Remplacer par",
|
||||
"Replace": "Remplacer",
|
||||
"Replace all": "Remplacer tout",
|
||||
"Previous": "Précédente",
|
||||
"Next": "Suivante",
|
||||
"Find and Replace": "Trouver et remplacer",
|
||||
"Find and replace...": "Trouver et remplacer...",
|
||||
"Could not find the specified string.": "Impossible de trouver la chaîne spécifiée.",
|
||||
"Match case": "Respecter la casse",
|
||||
"Find whole words only": "Mot entier",
|
||||
"Find in selection": "Trouver dans la sélection",
|
||||
"Insert table": "Insérer un tableau",
|
||||
"Table properties": "Propriétés du tableau",
|
||||
"Delete table": "Supprimer le tableau",
|
||||
"Cell": "Cellule",
|
||||
"Row": "Ligne",
|
||||
"Column": "Colonne",
|
||||
"Cell properties": "Propriétés de la cellule",
|
||||
"Merge cells": "Fusionner les cellules",
|
||||
"Split cell": "Diviser la cellule",
|
||||
"Insert row before": "Insérer une ligne avant",
|
||||
"Insert row after": "Insérer une ligne après",
|
||||
"Delete row": "Supprimer la ligne",
|
||||
"Row properties": "Propriétés de la ligne",
|
||||
"Cut row": "Couper la ligne",
|
||||
"Cut column": "Couper la colonne",
|
||||
"Copy row": "Copier la ligne",
|
||||
"Copy column": "Copier la colonne",
|
||||
"Paste row before": "Coller la ligne avant",
|
||||
"Paste column before": "Coller la colonne avant",
|
||||
"Paste row after": "Coller la ligne après",
|
||||
"Paste column after": "Coller la colonne après",
|
||||
"Insert column before": "Insérer une colonne avant",
|
||||
"Insert column after": "Insérer une colonne après",
|
||||
"Delete column": "Supprimer la colonne",
|
||||
"Cols": "Colonnes",
|
||||
"Rows": "Lignes",
|
||||
"Width": "Largeur",
|
||||
"Height": "Hauteur",
|
||||
"Cell spacing": "Espacement entre les cellules",
|
||||
"Cell padding": "Marge intérieure des cellules",
|
||||
"Row clipboard actions": "Actions du presse-papiers des lignes",
|
||||
"Column clipboard actions": "Actions du presse-papiers des colonnes",
|
||||
"Table styles": "Style tableau",
|
||||
"Cell styles": "Type de cellule",
|
||||
"Column header": "En-tête de colonne",
|
||||
"Row header": "En-tête de ligne",
|
||||
"Table caption": "Légende de tableau",
|
||||
"Caption": "Légende",
|
||||
"Show caption": "Afficher une légende",
|
||||
"Left": "Gauche",
|
||||
"Center": "Centre",
|
||||
"Right": "Droite",
|
||||
"Cell type": "Type de cellule",
|
||||
"Scope": "Étendue",
|
||||
"Alignment": "Alignement",
|
||||
"Horizontal align": "Alignement horizontal",
|
||||
"Vertical align": "Alignement vertical",
|
||||
"Top": "En haut",
|
||||
"Middle": "Au milieu",
|
||||
"Bottom": "En bas",
|
||||
"Header cell": "Cellule d'en-tête",
|
||||
"Row group": "Groupe de lignes",
|
||||
"Column group": "Groupe de colonnes",
|
||||
"Row type": "Type de ligne",
|
||||
"Header": "En-tête",
|
||||
"Body": "Corps",
|
||||
"Footer": "Pied de page",
|
||||
"Border color": "Couleur de bordure",
|
||||
"Solid": "Trait continu",
|
||||
"Dotted": "Pointillé",
|
||||
"Dashed": "Tirets",
|
||||
"Double": "Deux traits continus",
|
||||
"Groove": "Sculpté",
|
||||
"Ridge": "Extrudé",
|
||||
"Inset": "Incrusté",
|
||||
"Outset": "Relief",
|
||||
"Hidden": "Masqué",
|
||||
"Insert template...": "Insérer un modèle...",
|
||||
"Templates": "Modèles",
|
||||
"Template": "Modèle",
|
||||
"Insert Template": "Insérer le modèle",
|
||||
"Text color": "Couleur du texte",
|
||||
"Background color": "Couleur d'arrière-plan",
|
||||
"Custom...": "Personnalisée...",
|
||||
"Custom color": "Couleur personnalisée",
|
||||
"No color": "Aucune couleur",
|
||||
"Remove color": "Supprimer la couleur",
|
||||
"Show blocks": "Afficher les blocs",
|
||||
"Show invisible characters": "Afficher les caractères invisibles",
|
||||
"Word count": "Nombre de mots",
|
||||
"Count": "Total",
|
||||
"Document": "Document",
|
||||
"Selection": "Sélection",
|
||||
"Words": "Mots",
|
||||
"Words: {0}": "Mots : {0}",
|
||||
"{0} words": "{0} mots",
|
||||
"File": "Fichier",
|
||||
"Edit": "Modifier",
|
||||
"Insert": "Insérer",
|
||||
"View": "Afficher",
|
||||
"Format": "Format",
|
||||
"Table": "Tableau",
|
||||
"Tools": "Outils",
|
||||
"Powered by {0}": "Avec {0}",
|
||||
"Rich Text Area. Press ALT-F9 for menu. Press ALT-F10 for toolbar. Press ALT-0 for help": "Zone de Texte Riche. Appuyez sur ALT-F9 pour le menu. Appuyez sur ALT-F10 pour la barre d'outils. Appuyez sur ALT-0 pour l'aide",
|
||||
"Image title": "Titre d'image",
|
||||
"Border width": "Épaisseur de la bordure",
|
||||
"Border style": "Style de la bordure",
|
||||
"Error": "Erreur",
|
||||
"Warn": "Avertir",
|
||||
"Valid": "Valide",
|
||||
"To open the popup, press Shift+Enter": "Pour ouvrir la popup, appuyez sur Maj+Entrée",
|
||||
"Rich Text Area": "Zone de Texte Riche",
|
||||
"Rich Text Area. Press ALT-0 for help.": "Zone de Texte Riche. Appuyez sur ALT-0 pour l'aide.",
|
||||
"System Font": "Police système",
|
||||
"Failed to upload image: {0}": "Échec d'envoi de l'image : {0}",
|
||||
"Failed to load plugin: {0} from url {1}": "Échec de chargement du plug-in : {0} à partir de l’URL {1}",
|
||||
"Failed to load plugin url: {0}": "Échec de chargement de l'URL du plug-in : {0}",
|
||||
"Failed to initialize plugin: {0}": "Échec d'initialisation du plug-in : {0}",
|
||||
"example": "exemple",
|
||||
"Search": "Rechercher",
|
||||
"All": "Tout",
|
||||
"Currency": "Devise",
|
||||
"Text": "Texte",
|
||||
"Quotations": "Citations",
|
||||
"Mathematical": "Opérateurs mathématiques",
|
||||
"Extended Latin": "Latin étendu",
|
||||
"Symbols": "Symboles",
|
||||
"Arrows": "Flèches",
|
||||
"User Defined": "Défini par l'utilisateur",
|
||||
"dollar sign": "Symbole dollar",
|
||||
"currency sign": "Symbole devise",
|
||||
"euro-currency sign": "Symbole euro",
|
||||
"colon sign": "Symbole colón",
|
||||
"cruzeiro sign": "Symbole cruzeiro",
|
||||
"french franc sign": "Symbole franc français",
|
||||
"lira sign": "Symbole lire",
|
||||
"mill sign": "Symbole millième",
|
||||
"naira sign": "Symbole naira",
|
||||
"peseta sign": "Symbole peseta",
|
||||
"rupee sign": "Symbole roupie",
|
||||
"won sign": "Symbole won",
|
||||
"new sheqel sign": "Symbole nouveau chékel",
|
||||
"dong sign": "Symbole dong",
|
||||
"kip sign": "Symbole kip",
|
||||
"tugrik sign": "Symbole tougrik",
|
||||
"drachma sign": "Symbole drachme",
|
||||
"german penny symbol": "Symbole pfennig",
|
||||
"peso sign": "Symbole peso",
|
||||
"guarani sign": "Symbole guarani",
|
||||
"austral sign": "Symbole austral",
|
||||
"hryvnia sign": "Symbole hryvnia",
|
||||
"cedi sign": "Symbole cedi",
|
||||
"livre tournois sign": "Symbole livre tournois",
|
||||
"spesmilo sign": "Symbole spesmilo",
|
||||
"tenge sign": "Symbole tenge",
|
||||
"indian rupee sign": "Symbole roupie indienne",
|
||||
"turkish lira sign": "Symbole lire turque",
|
||||
"nordic mark sign": "Symbole du mark nordique",
|
||||
"manat sign": "Symbole manat",
|
||||
"ruble sign": "Symbole rouble",
|
||||
"yen character": "Sinogramme Yen",
|
||||
"yuan character": "Sinogramme Yuan",
|
||||
"yuan character, in hong kong and taiwan": "Sinogramme Yuan, Hong Kong et Taiwan",
|
||||
"yen/yuan character variant one": "Sinogramme Yen/Yuan, première variante",
|
||||
"Emojis": "Émojis",
|
||||
"Emojis...": "Émojis...",
|
||||
"Loading emojis...": "Chargement des emojis...",
|
||||
"Could not load emojis": "Impossible de charger les emojis",
|
||||
"People": "Personnes",
|
||||
"Animals and Nature": "Animaux & nature",
|
||||
"Food and Drink": "Nourriture & boissons",
|
||||
"Activity": "Activité",
|
||||
"Travel and Places": "Voyages & lieux",
|
||||
"Objects": "Objets",
|
||||
"Flags": "Drapeaux",
|
||||
"Characters": "Caractères",
|
||||
"Characters (no spaces)": "Caractères (espaces non compris)",
|
||||
"{0} characters": "{0} caractères",
|
||||
"Error: Form submit field collision.": "Erreur : conflit de champs lors de la soumission du formulaire.",
|
||||
"Error: No form element found.": "Erreur : aucun élément de formulaire trouvé.",
|
||||
"Color swatch": "Échantillon de couleurs",
|
||||
"Color Picker": "Sélecteur de couleurs",
|
||||
"Invalid hex color code: {0}": "Code couleur hexadécimal invalide : {0}",
|
||||
"Invalid input": "Entrée invalide",
|
||||
"R": "R",
|
||||
"Red component": "Composant rouge",
|
||||
"G": "V",
|
||||
"Green component": "Composant vert",
|
||||
"B": "B",
|
||||
"Blue component": "Composant bleu",
|
||||
"#": "#",
|
||||
"Hex color code": "Code couleur hexadécimal",
|
||||
"Range 0 to 255": "Plage de 0 à 255",
|
||||
"Turquoise": "Turquoise",
|
||||
"Green": "Vert",
|
||||
"Blue": "Bleu",
|
||||
"Purple": "Violet",
|
||||
"Navy Blue": "Bleu marine",
|
||||
"Dark Turquoise": "Turquoise foncé",
|
||||
"Dark Green": "Vert foncé",
|
||||
"Medium Blue": "Bleu moyen",
|
||||
"Medium Purple": "Violet moyen",
|
||||
"Midnight Blue": "Bleu de minuit",
|
||||
"Yellow": "Jaune",
|
||||
"Orange": "Orange",
|
||||
"Red": "Rouge",
|
||||
"Light Gray": "Gris clair",
|
||||
"Gray": "Gris",
|
||||
"Dark Yellow": "Jaune foncé",
|
||||
"Dark Orange": "Orange foncé",
|
||||
"Dark Red": "Rouge foncé",
|
||||
"Medium Gray": "Gris moyen",
|
||||
"Dark Gray": "Gris foncé",
|
||||
"Light Green": "Vert clair",
|
||||
"Light Yellow": "Jaune clair",
|
||||
"Light Red": "Rouge clair",
|
||||
"Light Purple": "Violet clair",
|
||||
"Light Blue": "Bleu clair",
|
||||
"Dark Purple": "Violet foncé",
|
||||
"Dark Blue": "Bleu foncé",
|
||||
"Black": "Noir",
|
||||
"White": "Blanc",
|
||||
"Switch to or from fullscreen mode": "Passer en ou quitter le mode plein écran",
|
||||
"Open help dialog": "Ouvrir la boîte de dialogue d'aide",
|
||||
"history": "historique",
|
||||
"styles": "styles",
|
||||
"formatting": "mise en forme",
|
||||
"alignment": "alignement",
|
||||
"indentation": "retrait",
|
||||
"Font": "Police",
|
||||
"Size": "Taille",
|
||||
"More...": "Plus...",
|
||||
"Select...": "Sélectionner...",
|
||||
"Preferences": "Préférences",
|
||||
"Yes": "Oui",
|
||||
"No": "Non",
|
||||
"Keyboard Navigation": "Navigation au clavier",
|
||||
"Version": "Version",
|
||||
"Code view": "Affichage du code",
|
||||
"Open popup menu for split buttons": "Ouvrir le menu contextuel pour les boutons partagés",
|
||||
"List Properties": "Propriétés de la liste",
|
||||
"List properties...": "Lister les propriétés...",
|
||||
"Start list at number": "Liste de départ au numéro",
|
||||
"Line height": "Hauteur de la ligne",
|
||||
"Dropped file type is not supported": "Le type de fichier déposé n'est pas pris en charge",
|
||||
"Loading...": "Chargement...",
|
||||
"ImageProxy HTTP error: Rejected request": "Erreur HTTP d'ImageProxy : Requête rejetée",
|
||||
"ImageProxy HTTP error: Could not find Image Proxy": "Erreur HTTP d'ImageProxy : Impossible de trouver ImageProxy",
|
||||
"ImageProxy HTTP error: Incorrect Image Proxy URL": "Erreur HTTP d'ImageProxy : URL de ImageProxy incorrecte",
|
||||
"ImageProxy HTTP error: Unknown ImageProxy error": "Erreur HTTP d'ImageProxy : Erreur ImageProxy inconnue"
|
||||
});
|
||||
@@ -1,21 +0,0 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2022 Ephox Corporation DBA Tiny Technologies, Inc.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
File diff suppressed because one or more lines are too long
@@ -1,4 +0,0 @@
|
||||
/**
|
||||
* TinyMCE version 6.3.1 (2022-12-06)
|
||||
*/
|
||||
!function(){"use strict";var t=tinymce.util.Tools.resolve("tinymce.PluginManager");const e=(t,e,r)=>{const s="UL"===e?"InsertUnorderedList":"InsertOrderedList";t.execCommand(s,!1,!1===r?null:{"list-style-type":r})},r=t=>e=>e.options.get(t),s=r("advlist_number_styles"),n=r("advlist_bullet_styles"),l=t=>null==t,i=t=>!l(t);var o=tinymce.util.Tools.resolve("tinymce.util.Tools");class a{constructor(t,e){this.tag=t,this.value=e}static some(t){return new a(!0,t)}static none(){return a.singletonNone}fold(t,e){return this.tag?e(this.value):t()}isSome(){return this.tag}isNone(){return!this.tag}map(t){return this.tag?a.some(t(this.value)):a.none()}bind(t){return this.tag?t(this.value):a.none()}exists(t){return this.tag&&t(this.value)}forall(t){return!this.tag||t(this.value)}filter(t){return!this.tag||t(this.value)?this:a.none()}getOr(t){return this.tag?this.value:t}or(t){return this.tag?this:t}getOrThunk(t){return this.tag?this.value:t()}orThunk(t){return this.tag?this:t()}getOrDie(t){if(this.tag)return this.value;throw new Error(null!=t?t:"Called getOrDie on None")}static from(t){return i(t)?a.some(t):a.none()}getOrNull(){return this.tag?this.value:null}getOrUndefined(){return this.value}each(t){this.tag&&t(this.value)}toArray(){return this.tag?[this.value]:[]}toString(){return this.tag?`some(${this.value})`:"none()"}}a.singletonNone=new a(!1);const u=t=>i(t)&&/^(TH|TD)$/.test(t.nodeName),d=t=>l(t)||"default"===t?"":t,g=(t,e)=>r=>{const s=s=>{r.setActive(((t,e,r)=>{const s=((t,e)=>{for(let r=0;r<t.length;r++)if(e(t[r]))return r;return-1})(e.parents,u),n=-1!==s?e.parents.slice(0,s):e.parents,l=o.grep(n,(t=>e=>i(e)&&/^(OL|UL|DL)$/.test(e.nodeName)&&((t,e)=>t.dom.isChildOf(e,t.getBody()))(t,e))(t));return l.length>0&&l[0].nodeName===r})(t,s,e)),r.setEnabled(!((t,e)=>{const r=t.dom.getParent(e,"ol,ul,dl");return((t,e)=>null!==e&&"false"===t.dom.getContentEditableParent(e))(t,r)})(t,s.element))};return t.on("NodeChange",s),()=>t.off("NodeChange",s)},h=(t,r,s,n,l,i)=>{i.length>1?((t,r,s,n,l,i)=>{t.ui.registry.addSplitButton(r,{tooltip:s,icon:"OL"===l?"ordered-list":"unordered-list",presets:"listpreview",columns:3,fetch:t=>{t(o.map(i,(t=>{const e="OL"===l?"num":"bull",r="disc"===t||"decimal"===t?"default":t,s=d(t),n=(t=>t.replace(/\-/g," ").replace(/\b\w/g,(t=>t.toUpperCase())))(t);return{type:"choiceitem",value:s,icon:"list-"+e+"-"+r,text:n}})))},onAction:()=>t.execCommand(n),onItemAction:(r,s)=>{e(t,l,s)},select:e=>{const r=(t=>{const e=t.dom.getParent(t.selection.getNode(),"ol,ul"),r=t.dom.getStyle(e,"listStyleType");return a.from(r)})(t);return r.map((t=>e===t)).getOr(!1)},onSetup:g(t,l)})})(t,r,s,n,l,i):((t,r,s,n,l,i)=>{t.ui.registry.addToggleButton(r,{active:!1,tooltip:s,icon:"OL"===l?"ordered-list":"unordered-list",onSetup:g(t,l),onAction:()=>t.queryCommandState(n)||""===i?t.execCommand(n):e(t,l,i)})})(t,r,s,n,l,d(i[0]))};t.add("advlist",(t=>{t.hasPlugin("lists")?((t=>{const e=t.options.register;e("advlist_number_styles",{processor:"string[]",default:"default,lower-alpha,lower-greek,lower-roman,upper-alpha,upper-roman".split(",")}),e("advlist_bullet_styles",{processor:"string[]",default:"default,circle,square".split(",")})})(t),(t=>{h(t,"numlist","Numbered list","InsertOrderedList","OL",s(t)),h(t,"bullist","Bullet list","InsertUnorderedList","UL",n(t))})(t),(t=>{t.addCommand("ApplyUnorderedListStyle",((r,s)=>{e(t,"UL",s["list-style-type"])})),t.addCommand("ApplyOrderedListStyle",((r,s)=>{e(t,"OL",s["list-style-type"])}))})(t)):console.error("Please use the Lists plugin together with the Advanced List plugin.")}))}();
|
||||
@@ -1,4 +0,0 @@
|
||||
/**
|
||||
* TinyMCE version 6.3.1 (2022-12-06)
|
||||
*/
|
||||
!function(){"use strict";var e=tinymce.util.Tools.resolve("tinymce.PluginManager"),t=tinymce.util.Tools.resolve("tinymce.dom.RangeUtils"),o=tinymce.util.Tools.resolve("tinymce.util.Tools");const n=("allow_html_in_named_anchor",e=>e.options.get("allow_html_in_named_anchor"));const a="a:not([href])",r=e=>!e,i=e=>e.getAttribute("id")||e.getAttribute("name")||"",l=e=>(e=>"a"===e.nodeName.toLowerCase())(e)&&!e.getAttribute("href")&&""!==i(e),s=e=>e.dom.getParent(e.selection.getStart(),a),d=(e,a)=>{const r=s(e);r?((e,t,o)=>{o.removeAttribute("name"),o.id=t,e.addVisual(),e.undoManager.add()})(e,a,r):((e,a)=>{e.undoManager.transact((()=>{n(e)||e.selection.collapse(!0),e.selection.isCollapsed()?e.insertContent(e.dom.createHTML("a",{id:a})):((e=>{const n=e.dom;t(n).walk(e.selection.getRng(),(e=>{o.each(e,(e=>{var t;l(t=e)&&!t.firstChild&&n.remove(e,!1)}))}))})(e),e.formatter.remove("namedAnchor",void 0,void 0,!0),e.formatter.apply("namedAnchor",{value:a}),e.addVisual())}))})(e,a),e.focus()},c=e=>(e=>r(e.attr("href"))&&!r(e.attr("id")||e.attr("name")))(e)&&!e.firstChild,m=e=>t=>{for(let o=0;o<t.length;o++){const n=t[o];c(n)&&n.attr("contenteditable",e)}};e.add("anchor",(e=>{(e=>{(0,e.options.register)("allow_html_in_named_anchor",{processor:"boolean",default:!1})})(e),(e=>{e.on("PreInit",(()=>{e.parser.addNodeFilter("a",m("false")),e.serializer.addNodeFilter("a",m(null))}))})(e),(e=>{e.addCommand("mceAnchor",(()=>{(e=>{const t=(e=>{const t=s(e);return t?i(t):""})(e);e.windowManager.open({title:"Anchor",size:"normal",body:{type:"panel",items:[{name:"id",type:"input",label:"ID",placeholder:"example"}]},buttons:[{type:"cancel",name:"cancel",text:"Cancel"},{type:"submit",name:"save",text:"Save",primary:!0}],initialData:{id:t},onSubmit:t=>{((e,t)=>/^[A-Za-z][A-Za-z0-9\-:._]*$/.test(t)?(d(e,t),!0):(e.windowManager.alert("ID should start with a letter, followed only by letters, numbers, dashes, dots, colons or underscores."),!1))(e,t.getData().id)&&t.close()}})})(e)}))})(e),(e=>{const t=()=>e.execCommand("mceAnchor");e.ui.registry.addToggleButton("anchor",{icon:"bookmark",tooltip:"Anchor",onAction:t,onSetup:t=>e.selection.selectorChangedWithUnbind("a:not([href])",t.setActive).unbind}),e.ui.registry.addMenuItem("anchor",{icon:"bookmark",text:"Anchor...",onAction:t})})(e),e.on("PreInit",(()=>{(e=>{e.formatter.register("namedAnchor",{inline:"a",selector:a,remove:"all",split:!0,deep:!0,attributes:{id:"%value"},onmatch:(e,t,o)=>l(e)})})(e)}))}))}();
|
||||
@@ -1,4 +0,0 @@
|
||||
/**
|
||||
* TinyMCE version 6.3.1 (2022-12-06)
|
||||
*/
|
||||
!function(){"use strict";var e=tinymce.util.Tools.resolve("tinymce.PluginManager");const t=e=>t=>t.options.get(e),n=t("autolink_pattern"),o=t("link_default_target"),r=t("link_default_protocol"),a=t("allow_unsafe_link_target"),s=("string",e=>"string"===(e=>{const t=typeof e;return null===e?"null":"object"===t&&Array.isArray(e)?"array":"object"===t&&(n=o=e,(r=String).prototype.isPrototypeOf(n)||(null===(a=o.constructor)||void 0===a?void 0:a.name)===r.name)?"string":t;var n,o,r,a})(e));const l=(void 0,e=>undefined===e);const i=e=>!(e=>null==e)(e),c=Object.hasOwnProperty,d=e=>"\ufeff"===e;var u=tinymce.util.Tools.resolve("tinymce.dom.TextSeeker");const f=e=>/^[(\[{ \u00a0]$/.test(e),g=(e,t,n)=>{for(let o=t-1;o>=0;o--){const t=e.charAt(o);if(!d(t)&&n(t))return o}return-1},m=(e,t)=>{var o;const a=e.schema.getVoidElements(),s=n(e),{dom:i,selection:d}=e;if(null!==i.getParent(d.getNode(),"a[href]"))return null;const m=d.getRng(),k=u(i,(e=>{return i.isBlock(e)||(t=a,n=e.nodeName.toLowerCase(),c.call(t,n))||"false"===i.getContentEditable(e);var t,n})),{container:p,offset:y}=((e,t)=>{let n=e,o=t;for(;1===n.nodeType&&n.childNodes[o];)n=n.childNodes[o],o=3===n.nodeType?n.data.length:n.childNodes.length;return{container:n,offset:o}})(m.endContainer,m.endOffset),h=null!==(o=i.getParent(p,i.isBlock))&&void 0!==o?o:i.getRoot(),w=k.backwards(p,y+t,((e,t)=>{const n=e.data,o=g(n,t,(r=f,e=>!r(e)));var r,a;return-1===o||(a=n[o],/[?!,.;:]/.test(a))?o:o+1}),h);if(!w)return null;let v=w.container;const _=k.backwards(w.container,w.offset,((e,t)=>{v=e;const n=g(e.data,t,f);return-1===n?n:n+1}),h),A=i.createRng();_?A.setStart(_.container,_.offset):A.setStart(v,0),A.setEnd(w.container,w.offset);const C=A.toString().replace(/\uFEFF/g,"").match(s);if(C){let t=C[0];return $="www.",(b=t).length>=$.length&&b.substr(0,0+$.length)===$?t=r(e)+"://"+t:((e,t,n=0,o)=>{const r=e.indexOf(t,n);return-1!==r&&(!!l(o)||r+t.length<=o)})(t,"@")&&!(e=>/^([A-Za-z][A-Za-z\d.+-]*:\/\/)|mailto:/.test(e))(t)&&(t="mailto:"+t),{rng:A,url:t}}var b,$;return null},k=(e,t)=>{const{dom:n,selection:r}=e,{rng:l,url:i}=t,c=r.getBookmark();r.setRng(l);const d="createlink",u={command:d,ui:!1,value:i};if(!e.dispatch("BeforeExecCommand",u).isDefaultPrevented()){e.getDoc().execCommand(d,!1,i),e.dispatch("ExecCommand",u);const t=o(e);if(s(t)){const o=r.getNode();n.setAttrib(o,"target",t),"_blank"!==t||a(e)||n.setAttrib(o,"rel","noopener")}}r.moveToBookmark(c),e.nodeChanged()},p=e=>{const t=m(e,-1);i(t)&&k(e,t)},y=p;e.add("autolink",(e=>{(e=>{const t=e.options.register;t("autolink_pattern",{processor:"regexp",default:new RegExp("^"+/(?:[A-Za-z][A-Za-z\d.+-]{0,14}:\/\/(?:[-.~*+=!&;:'%@?^${}(),\w]+@)?|www\.|[-;:&=+$,.\w]+@)[A-Za-z\d-]+(?:\.[A-Za-z\d-]+)*(?::\d+)?(?:\/(?:[-.~*+=!;:'%@$(),\/\w]*[-~*+=%@$()\/\w])?)?(?:\?(?:[-.~*+=!&;:'%@?^${}(),\/\w]+))?(?:#(?:[-.~*+=!&;:'%@?^${}(),\/\w]+))?/g.source+"$","i")}),t("link_default_target",{processor:"string"}),t("link_default_protocol",{processor:"string",default:"https"})})(e),(e=>{e.on("keydown",(t=>{13!==t.keyCode||t.isDefaultPrevented()||(e=>{const t=m(e,0);i(t)&&k(e,t)})(e)})),e.on("keyup",(t=>{32===t.keyCode?p(e):(48===t.keyCode&&t.shiftKey||221===t.keyCode)&&y(e)}))})(e)}))}();
|
||||
@@ -1,4 +0,0 @@
|
||||
/**
|
||||
* TinyMCE version 6.3.1 (2022-12-06)
|
||||
*/
|
||||
!function(){"use strict";var e=tinymce.util.Tools.resolve("tinymce.PluginManager"),t=tinymce.util.Tools.resolve("tinymce.Env");const o=e=>t=>t.options.get(e),n=o("min_height"),s=o("max_height"),i=o("autoresize_overflow_padding"),r=o("autoresize_bottom_margin"),l=(e,t)=>{const o=e.getBody();o&&(o.style.overflowY=t?"":"hidden",t||(o.scrollTop=0))},a=(e,t,o,n)=>{var s;const i=parseInt(null!==(s=e.getStyle(t,o,n))&&void 0!==s?s:"",10);return isNaN(i)?0:i},g=(e,o,i)=>{var c;const u=e.dom,d=e.getDoc();if(!d)return;if((e=>e.plugins.fullscreen&&e.plugins.fullscreen.isFullscreen())(e))return void l(e,!0);const f=d.documentElement,m=r(e),p=null!==(c=n(e))&&void 0!==c?c:e.getElement().offsetHeight;let h=p;const v=a(u,f,"margin-top",!0),y=a(u,f,"margin-bottom",!0);let C=f.offsetHeight+v+y+m;C<0&&(C=0);const S=e.getContainer().offsetHeight-e.getContentAreaContainer().offsetHeight;C+S>p&&(h=C+S);const z=s(e);if(z&&h>z?(h=z,l(e,!0)):l(e,!1),h!==o.get()){const n=h-o.get();if(u.setStyle(e.getContainer(),"height",h+"px"),o.set(h),(e=>{e.dispatch("ResizeEditor")})(e),t.browser.isSafari()&&(t.os.isMacOS()||t.os.isiOS())){const t=e.getWin();t.scrollTo(t.pageXOffset,t.pageYOffset)}e.hasFocus()&&(e=>{if("setcontent"===(null==e?void 0:e.type.toLowerCase())){const t=e;return!0===t.selection||!0===t.paste}return!1})(i)&&e.selection.scrollIntoView(),(t.browser.isSafari()||t.browser.isChromium())&&n<0&&g(e,o,i)}};e.add("autoresize",(e=>{if((e=>{const t=e.options.register;t("autoresize_overflow_padding",{processor:"number",default:1}),t("autoresize_bottom_margin",{processor:"number",default:50})})(e),e.options.isSet("resize")||e.options.set("resize",!1),!e.inline){const t=(e=>{let t=0;return{get:()=>t,set:e=>{t=e}}})();((e,t)=>{e.addCommand("mceAutoResize",(()=>{g(e,t)}))})(e,t),((e,t)=>{e.on("init",(()=>{const t=i(e),o=e.dom;o.setStyles(e.getDoc().documentElement,{height:"auto"}),o.setStyles(e.getBody(),{paddingLeft:t,paddingRight:t,"min-height":0})})),e.on("NodeChange SetContent keyup FullscreenStateChanged ResizeContent",(o=>{g(e,t,o)}))})(e,t)}}))}();
|
||||
@@ -1,4 +0,0 @@
|
||||
/**
|
||||
* TinyMCE version 6.3.1 (2022-12-06)
|
||||
*/
|
||||
!function(){"use strict";var t=tinymce.util.Tools.resolve("tinymce.PluginManager");const e=("string",t=>"string"===(t=>{const e=typeof t;return null===t?"null":"object"===e&&Array.isArray(t)?"array":"object"===e&&(r=o=t,(a=String).prototype.isPrototypeOf(r)||(null===(s=o.constructor)||void 0===s?void 0:s.name)===a.name)?"string":e;var r,o,a,s})(t));const r=(void 0,t=>undefined===t);var o=tinymce.util.Tools.resolve("tinymce.util.Delay"),a=tinymce.util.Tools.resolve("tinymce.util.LocalStorage"),s=tinymce.util.Tools.resolve("tinymce.util.Tools");const n=t=>{const e=/^(\d+)([ms]?)$/.exec(t);return(e&&e[2]?{s:1e3,m:6e4}[e[2]]:1)*parseInt(t,10)},i=t=>e=>e.options.get(t),u=i("autosave_ask_before_unload"),l=i("autosave_restore_when_empty"),c=i("autosave_interval"),d=i("autosave_retention"),m=t=>{const e=document.location;return t.options.get("autosave_prefix").replace(/{path}/g,e.pathname).replace(/{query}/g,e.search).replace(/{hash}/g,e.hash).replace(/{id}/g,t.id)},v=(t,e)=>{if(r(e))return t.dom.isEmpty(t.getBody());{const r=s.trim(e);if(""===r)return!0;{const e=(new DOMParser).parseFromString(r,"text/html");return t.dom.isEmpty(e)}}},f=t=>{var e;const r=parseInt(null!==(e=a.getItem(m(t)+"time"))&&void 0!==e?e:"0",10)||0;return!((new Date).getTime()-r>d(t)&&(p(t,!1),1))},p=(t,e)=>{const r=m(t);a.removeItem(r+"draft"),a.removeItem(r+"time"),!1!==e&&(t=>{t.dispatch("RemoveDraft")})(t)},g=t=>{const e=m(t);!v(t)&&t.isDirty()&&(a.setItem(e+"draft",t.getContent({format:"raw",no_events:!0})),a.setItem(e+"time",(new Date).getTime().toString()),(t=>{t.dispatch("StoreDraft")})(t))},y=t=>{var e;const r=m(t);f(t)&&(t.setContent(null!==(e=a.getItem(r+"draft"))&&void 0!==e?e:"",{format:"raw"}),(t=>{t.dispatch("RestoreDraft")})(t))};var D=tinymce.util.Tools.resolve("tinymce.EditorManager");const h=t=>e=>{e.setEnabled(f(t));const r=()=>e.setEnabled(f(t));return t.on("StoreDraft RestoreDraft RemoveDraft",r),()=>t.off("StoreDraft RestoreDraft RemoveDraft",r)};t.add("autosave",(t=>((t=>{const r=t.options.register,o=t=>{const r=e(t);return r?{value:n(t),valid:r}:{valid:!1,message:"Must be a string."}};r("autosave_ask_before_unload",{processor:"boolean",default:!0}),r("autosave_prefix",{processor:"string",default:"tinymce-autosave-{path}{query}{hash}-{id}-"}),r("autosave_restore_when_empty",{processor:"boolean",default:!1}),r("autosave_interval",{processor:o,default:"30s"}),r("autosave_retention",{processor:o,default:"20m"})})(t),(t=>{t.editorManager.on("BeforeUnload",(t=>{let e;s.each(D.get(),(t=>{t.plugins.autosave&&t.plugins.autosave.storeDraft(),!e&&t.isDirty()&&u(t)&&(e=t.translate("You have unsaved changes are you sure you want to navigate away?"))})),e&&(t.preventDefault(),t.returnValue=e)}))})(t),(t=>{(t=>{const e=c(t);o.setEditorInterval(t,(()=>{g(t)}),e)})(t);const e=()=>{(t=>{t.undoManager.transact((()=>{y(t),p(t)})),t.focus()})(t)};t.ui.registry.addButton("restoredraft",{tooltip:"Restore last draft",icon:"restore-draft",onAction:e,onSetup:h(t)}),t.ui.registry.addMenuItem("restoredraft",{text:"Restore last draft",icon:"restore-draft",onAction:e,onSetup:h(t)})})(t),t.on("init",(()=>{l(t)&&t.dom.isEmpty(t.getBody())&&y(t)})),(t=>({hasDraft:()=>f(t),storeDraft:()=>g(t),restoreDraft:()=>y(t),removeDraft:e=>p(t,e),isEmpty:e=>v(t,e)}))(t))))}();
|
||||
File diff suppressed because one or more lines are too long
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user