Add the ability to change the title

This commit is contained in:
Ivan R. 2023-11-01 23:18:33 +05:00
parent 113d5860ff
commit 00dcd04eb7
No known key found for this signature in database
GPG key ID: 56C7BAAE859B302C
12 changed files with 84 additions and 62 deletions

View file

@ -17,6 +17,8 @@ type Config struct {
DefaultPassword string DefaultPassword string
// Controls the "secure" option for a token cookie. // Controls the "secure" option for a token cookie.
SecureCookie bool `default:"true"` SecureCookie bool `default:"true"`
Title string `default:"Phoenix"`
} }
func GetConfig() (*Config, error) { func GetConfig() (*Config, error) {

View file

@ -28,6 +28,11 @@ Service settings can be set through environment variables.
| P_DEFAULTPASSWORD | Data for the first user. | | | P_DEFAULTPASSWORD | Data for the first user. | |
| P_SECURECOOKIE | Controls the "secure" option for a token cookie. | `true` | | P_SECURECOOKIE | Controls the "secure" option for a token cookie. | `true` |
Appearance settings:
| Variable | Description | Default |
| --- | --- | --- |
| P_TITLE | Website title | Phoenix |
## Docker-compose example ## Docker-compose example
```yml ```yml
services: services:

View file

@ -1,5 +1,5 @@
{{define "head"}} {{define "head"}}
<title>Phoenix</title> <title>{{.WebsiteTitle}}</title>
<meta charset="utf-8" /> <meta charset="utf-8" />
<meta name="description" content="A minimalistic start page with your collection of links to important sites." /> <meta name="description" content="A minimalistic start page with your collection of links to important sites." />
<meta name="viewport" content="width=device-width, initial-scale=1" /> <meta name="viewport" content="width=device-width, initial-scale=1" />

View file

@ -6,7 +6,7 @@
</head> </head>
<body> <body>
<div class="page"> <div class="page">
<h1>Phoenix</h1> <h1>{{.WebsiteTitle}}</h1>
{{if not .groups}} {{if not .groups}}
<p> <p>
You don't have any links. You don't have any links.

View file

@ -15,14 +15,14 @@ import (
const TOKEN_LIFETIME_IN_SECONDS = 60 * 60 * 24 * 30 const TOKEN_LIFETIME_IN_SECONDS = 60 * 60 * 24 * 30
func ShowRegistrationForm(db *gorm.DB) gin.HandlerFunc { func ShowRegistrationForm(cfg *config.Config, db *gorm.DB) gin.HandlerFunc {
return func(ctx *gin.Context) { return func(ctx *gin.Context) {
if database.CountAdmins(db) > 0 { if database.CountAdmins(db) > 0 {
ShowError(ctx, errors.New("At least 1 user already exists")) ShowError(ctx, cfg, errors.New("At least 1 user already exists"))
return return
} }
ctx.HTML(http.StatusOK, "auth.html.tmpl", gin.H{ Render(ctx, cfg, http.StatusOK, "auth.html.tmpl", gin.H{
"title": "Create an account", "title": "Create an account",
"description": "To prevent other people from seeing your links, create an account.", "description": "To prevent other people from seeing your links, create an account.",
"button": "Create", "button": "Create",
@ -31,14 +31,16 @@ func ShowRegistrationForm(db *gorm.DB) gin.HandlerFunc {
} }
} }
func ShowLoginForm(ctx *gin.Context) { func ShowLoginForm(cfg *config.Config) gin.HandlerFunc {
ctx.HTML(http.StatusOK, "auth.html.tmpl", gin.H{ return func(ctx *gin.Context) {
Render(ctx, cfg, http.StatusOK, "auth.html.tmpl", gin.H{
"title": "Sign in", "title": "Sign in",
"description": "Authorization is required to view this page.", "description": "Authorization is required to view this page.",
"button": "Sign in", "button": "Sign in",
"formAction": "/api/users/signin", "formAction": "/api/users/signin",
}) })
} }
}
// Requires the user to log in before viewing the page. // Requires the user to log in before viewing the page.
// Returns error if the user is not authorized. // Returns error if the user is not authorized.
@ -79,7 +81,7 @@ func AuthMiddleware(db *gorm.DB, cfg *config.Config) gin.HandlerFunc {
// Generate access token. // Generate access token.
token, err := GetJWTToken(cfg) token, err := GetJWTToken(cfg)
if err != nil { if err != nil {
ShowError(ctx, err) ShowError(ctx, cfg, err)
return return
} }
SetTokenCookie(ctx, token, cfg) SetTokenCookie(ctx, token, cfg)
@ -99,7 +101,7 @@ func AuthMiddleware(db *gorm.DB, cfg *config.Config) gin.HandlerFunc {
if time.Now().Add(time.Second * (TOKEN_LIFETIME_IN_SECONDS / 2)).After(claims.ExpiresAt.Time) { if time.Now().Add(time.Second * (TOKEN_LIFETIME_IN_SECONDS / 2)).After(claims.ExpiresAt.Time) {
newToken, err := GetJWTToken(cfg) newToken, err := GetJWTToken(cfg)
if err != nil { if err != nil {
ShowError(ctx, err) ShowError(ctx, cfg, err)
return return
} }
SetTokenCookie(ctx, newToken, cfg) SetTokenCookie(ctx, newToken, cfg)
@ -118,7 +120,7 @@ func GetJWTToken(cfg *config.Config) (string, error) {
func CreateUser(db *gorm.DB, cfg *config.Config) gin.HandlerFunc { func CreateUser(db *gorm.DB, cfg *config.Config) gin.HandlerFunc {
return func(ctx *gin.Context) { return func(ctx *gin.Context) {
if database.CountAdmins(db) > 0 { if database.CountAdmins(db) > 0 {
ShowError(ctx, errors.New("At least 1 user already exists")) ShowError(ctx, cfg, errors.New("At least 1 user already exists"))
return return
} }
@ -127,14 +129,14 @@ func CreateUser(db *gorm.DB, cfg *config.Config) gin.HandlerFunc {
password := ctx.PostForm("password") password := ctx.PostForm("password")
_, err := database.CreateAdmin(db, username, password) _, err := database.CreateAdmin(db, username, password)
if err != nil { if err != nil {
ShowError(ctx, err) ShowError(ctx, cfg, err)
return return
} }
// Generate access token. // Generate access token.
token, err := GetJWTToken(cfg) token, err := GetJWTToken(cfg)
if err != nil { if err != nil {
ShowError(ctx, err) ShowError(ctx, cfg, err)
return return
} }
SetTokenCookie(ctx, token, cfg) SetTokenCookie(ctx, token, cfg)
@ -151,14 +153,14 @@ func AuthorizeUser(db *gorm.DB, cfg *config.Config) gin.HandlerFunc {
password := ctx.PostForm("password") password := ctx.PostForm("password")
_, err := database.AuthorizeAdmin(db, username, password) _, err := database.AuthorizeAdmin(db, username, password)
if err != nil { if err != nil {
ShowError(ctx, err) ShowError(ctx, cfg, err)
return return
} }
// Generate an access token. // Generate an access token.
token, err := GetJWTToken(cfg) token, err := GetJWTToken(cfg)
if err != nil { if err != nil {
ShowError(ctx, err) ShowError(ctx, cfg, err)
return return
} }
SetTokenCookie(ctx, token, cfg) SetTokenCookie(ctx, token, cfg)

View file

@ -2,16 +2,13 @@ package views
import ( import (
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/ordinary-dev/phoenix/config"
"net/http" "net/http"
) )
func ShowError(ctx *gin.Context, err error) { func ShowError(ctx *gin.Context, cfg *config.Config, err error) {
ctx.HTML( Render(ctx, cfg, http.StatusBadRequest, "error.html.tmpl", gin.H{
http.StatusBadRequest,
"error.html.tmpl",
gin.H{
"error": err.Error(), "error": err.Error(),
}, })
)
ctx.Abort() ctx.Abort()
} }

View file

@ -3,20 +3,21 @@ package views
import ( import (
"fmt" "fmt"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/ordinary-dev/phoenix/config"
"github.com/ordinary-dev/phoenix/database" "github.com/ordinary-dev/phoenix/database"
"gorm.io/gorm" "gorm.io/gorm"
"net/http" "net/http"
"strconv" "strconv"
) )
func CreateGroup(db *gorm.DB) gin.HandlerFunc { func CreateGroup(cfg *config.Config, db *gorm.DB) gin.HandlerFunc {
return func(ctx *gin.Context) { return func(ctx *gin.Context) {
// Save new group to the database. // Save new group to the database.
group := database.Group{ group := database.Group{
Name: ctx.PostForm("groupName"), Name: ctx.PostForm("groupName"),
} }
if result := db.Create(&group); result.Error != nil { if result := db.Create(&group); result.Error != nil {
ShowError(ctx, result.Error) ShowError(ctx, cfg, result.Error)
return return
} }
@ -25,23 +26,23 @@ func CreateGroup(db *gorm.DB) gin.HandlerFunc {
} }
} }
func UpdateGroup(db *gorm.DB) gin.HandlerFunc { func UpdateGroup(cfg *config.Config, db *gorm.DB) gin.HandlerFunc {
return func(ctx *gin.Context) { return func(ctx *gin.Context) {
id, err := strconv.ParseUint(ctx.Param("id"), 10, 64) id, err := strconv.ParseUint(ctx.Param("id"), 10, 64)
if err != nil { if err != nil {
ShowError(ctx, err) ShowError(ctx, cfg, err)
return return
} }
var group database.Group var group database.Group
if result := db.First(&group, id); result.Error != nil { if result := db.First(&group, id); result.Error != nil {
ShowError(ctx, result.Error) ShowError(ctx, cfg, result.Error)
return return
} }
group.Name = ctx.PostForm("groupName") group.Name = ctx.PostForm("groupName")
if result := db.Save(&group); result.Error != nil { if result := db.Save(&group); result.Error != nil {
ShowError(ctx, result.Error) ShowError(ctx, cfg, result.Error)
return return
} }
@ -50,16 +51,16 @@ func UpdateGroup(db *gorm.DB) gin.HandlerFunc {
} }
} }
func DeleteGroup(db *gorm.DB) gin.HandlerFunc { func DeleteGroup(cfg *config.Config, db *gorm.DB) gin.HandlerFunc {
return func(ctx *gin.Context) { return func(ctx *gin.Context) {
id, err := strconv.ParseUint(ctx.Param("id"), 10, 64) id, err := strconv.ParseUint(ctx.Param("id"), 10, 64)
if err != nil { if err != nil {
ShowError(ctx, err) ShowError(ctx, cfg, err)
return return
} }
if result := db.Delete(&database.Group{}, id); result.Error != nil { if result := db.Delete(&database.Group{}, id); result.Error != nil {
ShowError(ctx, result.Error) ShowError(ctx, cfg, result.Error)
return return
} }

View file

@ -2,12 +2,13 @@ package views
import ( import (
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/ordinary-dev/phoenix/config"
"github.com/ordinary-dev/phoenix/database" "github.com/ordinary-dev/phoenix/database"
"gorm.io/gorm" "gorm.io/gorm"
"net/http" "net/http"
) )
func ShowMainPage(db *gorm.DB) gin.HandlerFunc { func ShowMainPage(cfg *config.Config, db *gorm.DB) gin.HandlerFunc {
return func(ctx *gin.Context) { return func(ctx *gin.Context) {
// Get a list of groups with links // Get a list of groups with links
var groups []database.Group var groups []database.Group
@ -17,11 +18,11 @@ func ShowMainPage(db *gorm.DB) gin.HandlerFunc {
Find(&groups) Find(&groups)
if result.Error != nil { if result.Error != nil {
ShowError(ctx, result.Error) ShowError(ctx, cfg, result.Error)
return return
} }
ctx.HTML(http.StatusOK, "index.html.tmpl", gin.H{ Render(ctx, cfg, http.StatusOK, "index.html.tmpl", gin.H{
"groups": groups, "groups": groups,
}) })
} }

View file

@ -3,17 +3,18 @@ package views
import ( import (
"fmt" "fmt"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/ordinary-dev/phoenix/config"
"github.com/ordinary-dev/phoenix/database" "github.com/ordinary-dev/phoenix/database"
"gorm.io/gorm" "gorm.io/gorm"
"net/http" "net/http"
"strconv" "strconv"
) )
func CreateLink(db *gorm.DB) gin.HandlerFunc { func CreateLink(cfg *config.Config, db *gorm.DB) gin.HandlerFunc {
return func(ctx *gin.Context) { return func(ctx *gin.Context) {
groupID, err := strconv.ParseUint(ctx.PostForm("groupID"), 10, 32) groupID, err := strconv.ParseUint(ctx.PostForm("groupID"), 10, 32)
if err != nil { if err != nil {
ShowError(ctx, err) ShowError(ctx, cfg, err)
return return
} }
@ -29,7 +30,7 @@ func CreateLink(db *gorm.DB) gin.HandlerFunc {
link.Icon = &icon link.Icon = &icon
} }
if result := db.Create(&link); result.Error != nil { if result := db.Create(&link); result.Error != nil {
ShowError(ctx, result.Error) ShowError(ctx, cfg, result.Error)
return return
} }
@ -38,17 +39,17 @@ func CreateLink(db *gorm.DB) gin.HandlerFunc {
} }
} }
func UpdateLink(db *gorm.DB) gin.HandlerFunc { func UpdateLink(cfg *config.Config, db *gorm.DB) gin.HandlerFunc {
return func(ctx *gin.Context) { return func(ctx *gin.Context) {
id, err := strconv.ParseUint(ctx.Param("id"), 10, 64) id, err := strconv.ParseUint(ctx.Param("id"), 10, 64)
if err != nil { if err != nil {
ShowError(ctx, err) ShowError(ctx, cfg, err)
return return
} }
var link database.Link var link database.Link
if result := db.First(&link, id); result.Error != nil { if result := db.First(&link, id); result.Error != nil {
ShowError(ctx, err) ShowError(ctx, cfg, err)
return return
} }
@ -61,7 +62,7 @@ func UpdateLink(db *gorm.DB) gin.HandlerFunc {
link.Icon = &icon link.Icon = &icon
} }
if result := db.Save(&link); result.Error != nil { if result := db.Save(&link); result.Error != nil {
ShowError(ctx, result.Error) ShowError(ctx, cfg, result.Error)
return return
} }
@ -70,16 +71,16 @@ func UpdateLink(db *gorm.DB) gin.HandlerFunc {
} }
} }
func DeleteLink(db *gorm.DB) gin.HandlerFunc { func DeleteLink(cfg *config.Config, db *gorm.DB) gin.HandlerFunc {
return func(ctx *gin.Context) { return func(ctx *gin.Context) {
id, err := strconv.ParseUint(ctx.Param("id"), 10, 64) id, err := strconv.ParseUint(ctx.Param("id"), 10, 64)
if err != nil { if err != nil {
ShowError(ctx, err) ShowError(ctx, cfg, err)
return return
} }
if result := db.Delete(&database.Link{}, id); result.Error != nil { if result := db.Delete(&database.Link{}, id); result.Error != nil {
ShowError(ctx, result.Error) ShowError(ctx, cfg, result.Error)
return return
} }

View file

@ -22,10 +22,10 @@ func GetGinEngine(cfg *config.Config, db *gorm.DB) *gin.Engine {
engine.Use(SecurityHeadersMiddleware) engine.Use(SecurityHeadersMiddleware)
engine.GET("/signin", ShowLoginForm) engine.GET("/signin", ShowLoginForm(cfg))
engine.POST("/api/users/signin", AuthorizeUser(db, cfg)) engine.POST("/api/users/signin", AuthorizeUser(db, cfg))
engine.GET("/registration", ShowRegistrationForm(db)) engine.GET("/registration", ShowRegistrationForm(cfg, db))
engine.POST("/api/users", CreateUser(db, cfg)) engine.POST("/api/users", CreateUser(db, cfg))
// This group requires authorization before viewing. // This group requires authorization before viewing.
@ -33,31 +33,31 @@ func GetGinEngine(cfg *config.Config, db *gorm.DB) *gin.Engine {
protected.Use(AuthMiddleware(db, cfg)) protected.Use(AuthMiddleware(db, cfg))
// Main page // Main page
protected.GET("/", ShowMainPage(db)) protected.GET("/", ShowMainPage(cfg, db))
protected.GET("/settings", ShowSettings(db)) protected.GET("/settings", ShowSettings(cfg, db))
// Create new group // Create new group
protected.POST("/api/groups", CreateGroup(db)) protected.POST("/api/groups", CreateGroup(cfg, db))
// Update group // Update group
// HTML forms cannot be submitted using PUT or PATCH methods without javascript. // HTML forms cannot be submitted using PUT or PATCH methods without javascript.
protected.POST("/api/groups/:id/put", UpdateGroup(db)) protected.POST("/api/groups/:id/put", UpdateGroup(cfg, db))
// Delete group // Delete group
// HTML forms cannot be submitted using the DELETE method without javascript. // HTML forms cannot be submitted using the DELETE method without javascript.
protected.POST("/api/groups/:id/delete", DeleteGroup(db)) protected.POST("/api/groups/:id/delete", DeleteGroup(cfg, db))
// Create new link // Create new link
protected.POST("/api/links", CreateLink(db)) protected.POST("/api/links", CreateLink(cfg, db))
// Update link. // Update link.
// HTML forms cannot be submitted using PUT or PATCH methods without javascript. // HTML forms cannot be submitted using PUT or PATCH methods without javascript.
protected.POST("/api/links/:id/put", UpdateLink(db)) protected.POST("/api/links/:id/put", UpdateLink(cfg, db))
// Delete link // Delete link
// HTML forms cannot be submitted using the DELETE method without javascript. // HTML forms cannot be submitted using the DELETE method without javascript.
protected.POST("/api/links/:id/delete", DeleteLink(db)) protected.POST("/api/links/:id/delete", DeleteLink(cfg, db))
return engine return engine
} }

12
views/render.go Normal file
View file

@ -0,0 +1,12 @@
package views
import (
"github.com/gin-gonic/gin"
"github.com/ordinary-dev/phoenix/config"
)
// Fill in the necessary parameters from the settings and output html.
func Render(ctx *gin.Context, cfg *config.Config, status int, templatePath string, params map[string]any) {
params["WebsiteTitle"] = cfg.Title
ctx.HTML(status, templatePath, params)
}

View file

@ -2,12 +2,13 @@ package views
import ( import (
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/ordinary-dev/phoenix/config"
"github.com/ordinary-dev/phoenix/database" "github.com/ordinary-dev/phoenix/database"
"gorm.io/gorm" "gorm.io/gorm"
"net/http" "net/http"
) )
func ShowSettings(db *gorm.DB) gin.HandlerFunc { func ShowSettings(cfg *config.Config, db *gorm.DB) gin.HandlerFunc {
return func(ctx *gin.Context) { return func(ctx *gin.Context) {
// Get a list of groups with links // Get a list of groups with links
var groups []database.Group var groups []database.Group
@ -17,11 +18,11 @@ func ShowSettings(db *gorm.DB) gin.HandlerFunc {
Find(&groups) Find(&groups)
if result.Error != nil { if result.Error != nil {
ShowError(ctx, result.Error) ShowError(ctx, cfg, result.Error)
return return
} }
ctx.HTML(http.StatusOK, "settings.html.tmpl", gin.H{ Render(ctx, cfg, http.StatusOK, "settings.html.tmpl", gin.H{
"groups": groups, "groups": groups,
}) })
} }