4
.env
|
@ -3,3 +3,7 @@ P_DBPATH="db.sqlite3"
|
|||
P_LOGLEVEL="debug"
|
||||
P_ENABLEGINLOGGER="true"
|
||||
P_PRODUCTION="false"
|
||||
|
||||
# Disabled for development
|
||||
# (not recommended for production environments)
|
||||
P_SECURECOOKIE="false"
|
||||
|
|
2
.github/workflows/codeql.yml
vendored
|
@ -19,7 +19,7 @@ jobs:
|
|||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
|
|
2
.github/workflows/docker-test.yml
vendored
|
@ -8,6 +8,6 @@ jobs:
|
|||
docker-test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- name: Build the Docker image
|
||||
run: docker build . --file Dockerfile
|
||||
|
|
10
.github/workflows/docker.yml
vendored
|
@ -23,15 +23,15 @@ jobs:
|
|||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Docker buildx
|
||||
uses: docker/setup-buildx-action@v2.9.1
|
||||
uses: docker/setup-buildx-action@v3.0.0
|
||||
|
||||
# Login against a Docker registry
|
||||
# https://github.com/docker/login-action
|
||||
- name: Log into registry ${{ env.REGISTRY }}
|
||||
uses: docker/login-action@v2.2.0
|
||||
uses: docker/login-action@v3.0.0
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
|
@ -41,7 +41,7 @@ jobs:
|
|||
# https://github.com/docker/metadata-action
|
||||
- name: Extract Docker metadata
|
||||
id: meta
|
||||
uses: docker/metadata-action@v4.6.0
|
||||
uses: docker/metadata-action@v5.0.0
|
||||
with:
|
||||
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
||||
|
||||
|
@ -49,7 +49,7 @@ jobs:
|
|||
# https://github.com/docker/build-push-action
|
||||
- name: Build and push Docker image
|
||||
id: build-and-push
|
||||
uses: docker/build-push-action@v4.1.1
|
||||
uses: docker/build-push-action@v5.0.0
|
||||
with:
|
||||
context: .
|
||||
push: true
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
FROM golang:1.21.0-alpine3.18 AS builder
|
||||
FROM golang:1.21.3-alpine3.18 AS builder
|
||||
|
||||
RUN apk add gcc
|
||||
RUN apk add musl-dev
|
||||
|
@ -12,7 +12,7 @@ ADD . .
|
|||
|
||||
RUN go build -o main
|
||||
|
||||
FROM alpine:3.18.3
|
||||
FROM alpine:3.18.4
|
||||
|
||||
WORKDIR /app
|
||||
COPY --from=builder /app/main /usr/local/bin/phoenix
|
||||
|
|
5
Makefile
|
@ -5,3 +5,8 @@ fmt:
|
|||
|
||||
vet:
|
||||
go vet ./...
|
||||
|
||||
favicons:
|
||||
convert -background none assets/favicons/favicon.svg -resize 16x16 assets/favicons/favicon-16.png
|
||||
convert -background none assets/favicons/favicon.svg -resize 32x32 assets/favicons/favicon-32.png
|
||||
convert -background none assets/favicons/favicon.svg -resize 180x180 assets/favicons/favicon-180.png
|
||||
|
|
|
@ -10,7 +10,6 @@ body {
|
|||
linear-gradient(90deg, #1d1926 19px, transparent 1px) center,
|
||||
#7a7093;
|
||||
background-size: 20px 20px;
|
||||
font-family: sans-serif;
|
||||
font-size: 16px;
|
||||
color: white;
|
||||
}
|
||||
|
|
Before Width: | Height: | Size: 717 B After Width: | Height: | Size: 883 B |
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.6 KiB |
|
@ -8,6 +8,8 @@
|
|||
version="1.1"
|
||||
id="svg5"
|
||||
xml:space="preserve"
|
||||
sodipodi:docname="favicon.svg"
|
||||
inkscape:version="1.3 (0e150ed6c4, 2023-07-21)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
|
@ -21,14 +23,23 @@
|
|||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
inkscape:document-units="mm"
|
||||
showgrid="false" /><defs
|
||||
showgrid="false"
|
||||
inkscape:zoom="0.82617197"
|
||||
inkscape:cx="511.99994"
|
||||
inkscape:cy="511.99994"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1052"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="layer1" /><defs
|
||||
id="defs2"><filter
|
||||
style="color-interpolation-filters:sRGB"
|
||||
inkscape:label="Drop Shadow"
|
||||
id="filter6175"
|
||||
x="-0.30888888"
|
||||
x="-0.30888889"
|
||||
y="-0.27799866"
|
||||
width="1.6622221"
|
||||
width="1.6622222"
|
||||
height="1.5759987"><feFlood
|
||||
flood-opacity="0.4"
|
||||
flood-color="rgb(0,0,0)"
|
||||
|
@ -65,7 +76,4 @@
|
|||
transform="matrix(6.364052,0,0,6.364052,59.098043,59.097958)"><path
|
||||
d="m 3,9 9,-7 9,7 v 11 a 2,2 0 0 1 -2,2 H 5 A 2,2 0 0 1 3,20 Z"
|
||||
id="path5080"
|
||||
style="stroke:#ffffff;stroke-opacity:1" /><polyline
|
||||
points="9 22 9 12 15 12 15 22"
|
||||
id="polyline5082"
|
||||
style="stroke:#ffffff;stroke-opacity:1" /></g></g></svg>
|
||||
style="stroke:#ffffff;stroke-opacity:1;stroke-width:3.14265188;stroke-dasharray:none" /></g></g></svg>
|
||||
|
|
Before Width: | Height: | Size: 2.4 KiB After Width: | Height: | Size: 2.7 KiB |
|
@ -15,6 +15,12 @@ type Config struct {
|
|||
HeaderAuth bool `default:"false"`
|
||||
DefaultUsername string
|
||||
DefaultPassword string
|
||||
// Controls the "secure" option for a token cookie.
|
||||
SecureCookie bool `default:"true"`
|
||||
|
||||
Title string `default:"Phoenix"`
|
||||
// Any supported css value, embedded directly into every page.
|
||||
FontFamily string `default:"sans-serif"`
|
||||
}
|
||||
|
||||
func GetConfig() (*Config, error) {
|
||||
|
|
12
go.mod
|
@ -8,9 +8,9 @@ require (
|
|||
github.com/joho/godotenv v1.5.1
|
||||
github.com/kelseyhightower/envconfig v1.4.0
|
||||
github.com/sirupsen/logrus v1.9.3
|
||||
golang.org/x/crypto v0.12.0
|
||||
gorm.io/driver/sqlite v1.5.3
|
||||
gorm.io/gorm v1.25.4
|
||||
golang.org/x/crypto v0.14.0
|
||||
gorm.io/driver/sqlite v1.5.4
|
||||
gorm.io/gorm v1.25.5
|
||||
)
|
||||
|
||||
require (
|
||||
|
@ -35,9 +35,9 @@ require (
|
|||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||
github.com/ugorji/go/codec v1.2.11 // indirect
|
||||
golang.org/x/arch v0.3.0 // indirect
|
||||
golang.org/x/net v0.10.0 // indirect
|
||||
golang.org/x/sys v0.11.0 // indirect
|
||||
golang.org/x/text v0.12.0 // indirect
|
||||
golang.org/x/net v0.17.0 // indirect
|
||||
golang.org/x/sys v0.13.0 // indirect
|
||||
golang.org/x/text v0.13.0 // indirect
|
||||
google.golang.org/protobuf v1.30.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
|
24
go.sum
|
@ -76,17 +76,17 @@ github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZ
|
|||
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
||||
golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k=
|
||||
golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
||||
golang.org/x/crypto v0.12.0 h1:tFM/ta59kqch6LlvYnPa0yx5a83cL2nHflFhYKvv9Yk=
|
||||
golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw=
|
||||
golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M=
|
||||
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||
golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc=
|
||||
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
|
||||
golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
|
||||
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
|
||||
golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM=
|
||||
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/text v0.12.0 h1:k+n5B8goJNdU7hSvEtMUz3d1Q6D/XW4COJSJR6fN0mc=
|
||||
golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
|
||||
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
|
||||
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
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=
|
||||
|
@ -97,8 +97,8 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8
|
|||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gorm.io/driver/sqlite v1.5.3 h1:7/0dUgX28KAcopdfbRWWl68Rflh6osa4rDh+m51KL2g=
|
||||
gorm.io/driver/sqlite v1.5.3/go.mod h1:qxAuCol+2r6PannQDpOP1FP6ag3mKi4esLnB/jHed+4=
|
||||
gorm.io/gorm v1.25.4 h1:iyNd8fNAe8W9dvtlgeRI5zSVZPsq3OpcTu37cYcpCmw=
|
||||
gorm.io/gorm v1.25.4/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k=
|
||||
gorm.io/driver/sqlite v1.5.4 h1:IqXwXi8M/ZlPzH/947tn5uik3aYQslP9BVveoax0nV0=
|
||||
gorm.io/driver/sqlite v1.5.4/go.mod h1:qxAuCol+2r6PannQDpOP1FP6ag3mKi4esLnB/jHed+4=
|
||||
gorm.io/gorm v1.25.5 h1:zR9lOiiYf09VNh5Q1gphfyia1JpiClIWG9hQaxB/mls=
|
||||
gorm.io/gorm v1.25.5/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
|
||||
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
# Phoenix
|
||||
|
||||
[![Go Report Card](https://goreportcard.com/badge/github.com/ordinary-dev/phoenix)](https://goreportcard.com/report/github.com/ordinary-dev/phoenix)
|
||||
|
||||
![Screenshot](screenshot.webp)
|
||||
|
||||
Self-hosted start page without the extra stuff.
|
||||
|
@ -24,6 +26,13 @@ Service settings can be set through environment variables.
|
|||
| P_HEADERAUTH | Enable Trusted Header Auth (SSO) | `false` |
|
||||
| P_DEFAULTUSERNAME | Data for the first user. | |
|
||||
| P_DEFAULTPASSWORD | Data for the first user. | |
|
||||
| P_SECURECOOKIE | Controls the "secure" option for a token cookie. | `true` |
|
||||
|
||||
Appearance settings:
|
||||
| Variable | Description | Default |
|
||||
| --- | --- | --- |
|
||||
| P_TITLE | Website title | `Phoenix` |
|
||||
| P_FONTFAMILY | The font used on the site. Inserted directly into css. | `sans-serif` |
|
||||
|
||||
## Docker-compose example
|
||||
```yml
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
{{template "head"}}
|
||||
{{template "head" .}}
|
||||
<link rel="stylesheet" href="assets/css/auth.css" />
|
||||
</head>
|
||||
<body>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
{{template "head"}}
|
||||
{{template "head" .}}
|
||||
<link rel="stylesheet" href="assets/css/error.css" />
|
||||
</head>
|
||||
<body>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
{{define "head"}}
|
||||
<title>Phoenix</title>
|
||||
<title>{{.WebsiteTitle}}</title>
|
||||
<meta charset="utf-8" />
|
||||
<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" />
|
||||
|
@ -8,4 +8,9 @@
|
|||
<link rel="icon" type="image/png" href="/assets/favicons/favicon-32.png" sizes="32x32" />
|
||||
<link rel="icon" type="image/svg+xml" href="/assets/favicons/favicon.svg" />
|
||||
<link rel="apple-touch-icon" href="/assets/favicons/favicon-180.png" />
|
||||
<style>
|
||||
body {
|
||||
font-family: {{.FontFamily}};
|
||||
}
|
||||
</style>
|
||||
{{end}}
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
{{template "head"}}
|
||||
{{template "head" .}}
|
||||
<link rel="stylesheet" href="assets/css/index.css" />
|
||||
</head>
|
||||
<body>
|
||||
<div class="page">
|
||||
<h1>Phoenix</h1>
|
||||
<h1>{{.WebsiteTitle}}</h1>
|
||||
{{if not .groups}}
|
||||
<p>
|
||||
You don't have any links.
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
{{template "head"}}
|
||||
{{template "head" .}}
|
||||
<link rel="stylesheet" href="assets/css/settings.css" />
|
||||
</head>
|
||||
<body>
|
||||
|
@ -9,7 +9,7 @@
|
|||
<a href="/">Main page</a>
|
||||
|
||||
{{range .groups}}
|
||||
<h2>Group "{{.Name}}"</h2>
|
||||
<h2 id="group-{{.ID}}">Group "{{.Name}}"</h2>
|
||||
<div class="row">
|
||||
<form method="POST" action="/api/groups/{{.ID}}/put" class="innerForm">
|
||||
<input
|
||||
|
@ -36,43 +36,43 @@
|
|||
</div>
|
||||
|
||||
{{range .Links}}
|
||||
<div class="row">
|
||||
<form method="POST" action="/api/links/{{.ID}}/put" class="innerForm">
|
||||
<!-- method: put -->
|
||||
<input
|
||||
class="small-row"
|
||||
value="{{ if .Icon }}{{ .Icon }}{{ end }}"
|
||||
name="icon"
|
||||
placeholder="Icon"
|
||||
/>
|
||||
<input
|
||||
class="small-row"
|
||||
value="{{.Name}}"
|
||||
name="linkName"
|
||||
placeholder="Name"
|
||||
required
|
||||
/>
|
||||
<input
|
||||
value="{{.Href}}"
|
||||
name="href"
|
||||
placeholder="Href"
|
||||
required
|
||||
/>
|
||||
<button
|
||||
type="submit"
|
||||
aria-label="Save the link"
|
||||
>
|
||||
<img src="/assets/svg/floppy-disk-solid.svg" width="16px" height="16px" />
|
||||
</button>
|
||||
</form>
|
||||
<form method="POST" action="/api/links/{{.ID}}/delete">
|
||||
<button
|
||||
type="submit"
|
||||
aria-label="Delete the link"
|
||||
>
|
||||
<img src="/assets/svg/trash-solid.svg" width="16px" height="16px" />
|
||||
</button>
|
||||
</form>
|
||||
<div class="row" id="link-{{.ID}}">
|
||||
<form method="POST" action="/api/links/{{.ID}}/put" class="innerForm">
|
||||
<!-- method: put -->
|
||||
<input
|
||||
class="small-row"
|
||||
value="{{ if .Icon }}{{ .Icon }}{{ end }}"
|
||||
name="icon"
|
||||
placeholder="Icon"
|
||||
/>
|
||||
<input
|
||||
class="small-row"
|
||||
value="{{.Name}}"
|
||||
name="linkName"
|
||||
placeholder="Name"
|
||||
required
|
||||
/>
|
||||
<input
|
||||
value="{{.Href}}"
|
||||
name="href"
|
||||
placeholder="Href"
|
||||
required
|
||||
/>
|
||||
<button
|
||||
type="submit"
|
||||
aria-label="Save the link"
|
||||
>
|
||||
<img src="/assets/svg/floppy-disk-solid.svg" width="16px" height="16px" />
|
||||
</button>
|
||||
</form>
|
||||
<form method="POST" action="/api/links/{{.ID}}/delete">
|
||||
<button
|
||||
type="submit"
|
||||
aria-label="Delete the link"
|
||||
>
|
||||
<img src="/assets/svg/trash-solid.svg" width="16px" height="16px" />
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
|
|
186
views/auth.go
|
@ -13,27 +13,33 @@ import (
|
|||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
func ShowRegistrationForm(c *gin.Context, db *gorm.DB) {
|
||||
if database.CountAdmins(db) > 0 {
|
||||
ShowError(c, errors.New("At least 1 user already exists"))
|
||||
return
|
||||
}
|
||||
const TOKEN_LIFETIME_IN_SECONDS = 60 * 60 * 24 * 30
|
||||
|
||||
c.HTML(http.StatusOK, "auth.html.tmpl", gin.H{
|
||||
"title": "Create an account",
|
||||
"description": "To prevent other people from seeing your links, create an account.",
|
||||
"button": "Create",
|
||||
"formAction": "/api/users",
|
||||
})
|
||||
func ShowRegistrationForm(cfg *config.Config, db *gorm.DB) gin.HandlerFunc {
|
||||
return func(ctx *gin.Context) {
|
||||
if database.CountAdmins(db) > 0 {
|
||||
ShowError(ctx, cfg, errors.New("At least 1 user already exists"))
|
||||
return
|
||||
}
|
||||
|
||||
Render(ctx, cfg, http.StatusOK, "auth.html.tmpl", gin.H{
|
||||
"title": "Create an account",
|
||||
"description": "To prevent other people from seeing your links, create an account.",
|
||||
"button": "Create",
|
||||
"formAction": "/api/users",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func ShowLoginForm(c *gin.Context) {
|
||||
c.HTML(http.StatusOK, "auth.html.tmpl", gin.H{
|
||||
"title": "Sign in",
|
||||
"description": "Authorization is required to view this page.",
|
||||
"button": "Sign in",
|
||||
"formAction": "/api/users/signin",
|
||||
})
|
||||
func ShowLoginForm(cfg *config.Config) gin.HandlerFunc {
|
||||
return func(ctx *gin.Context) {
|
||||
Render(ctx, cfg, http.StatusOK, "auth.html.tmpl", gin.H{
|
||||
"title": "Sign in",
|
||||
"description": "Authorization is required to view this page.",
|
||||
"button": "Sign in",
|
||||
"formAction": "/api/users/signin",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Requires the user to log in before viewing the page.
|
||||
|
@ -67,98 +73,104 @@ func RequireAuth(c *gin.Context, cfg *config.Config) (*jwt.RegisteredClaims, err
|
|||
return claims, nil
|
||||
}
|
||||
|
||||
func AuthMiddleware(c *gin.Context, db *gorm.DB, cfg *config.Config) {
|
||||
claims, err := RequireAuth(c, cfg)
|
||||
if err != nil {
|
||||
if cfg.HeaderAuth && c.Request.Header.Get("Remote-User") != "" {
|
||||
// Generate access token.
|
||||
token, err := GetJWTToken(cfg)
|
||||
if err != nil {
|
||||
ShowError(c, err)
|
||||
func AuthMiddleware(db *gorm.DB, cfg *config.Config) gin.HandlerFunc {
|
||||
return func(ctx *gin.Context) {
|
||||
claims, err := RequireAuth(ctx, cfg)
|
||||
if err != nil {
|
||||
if cfg.HeaderAuth && ctx.Request.Header.Get("Remote-User") != "" {
|
||||
// Generate access token.
|
||||
token, err := GetJWTToken(cfg)
|
||||
if err != nil {
|
||||
ShowError(ctx, cfg, err)
|
||||
return
|
||||
}
|
||||
SetTokenCookie(ctx, token, cfg)
|
||||
return
|
||||
}
|
||||
SetTokenCookie(c, token)
|
||||
|
||||
if database.CountAdmins(db) < 1 {
|
||||
ctx.Redirect(http.StatusFound, "/registration")
|
||||
} else {
|
||||
ctx.Redirect(http.StatusFound, "/signin")
|
||||
}
|
||||
ctx.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
if database.CountAdmins(db) < 1 {
|
||||
c.Redirect(http.StatusFound, "/registration")
|
||||
} else {
|
||||
c.Redirect(http.StatusFound, "/signin")
|
||||
// Create a new token if the old one is about to expire
|
||||
if time.Now().Add(time.Second * (TOKEN_LIFETIME_IN_SECONDS / 2)).After(claims.ExpiresAt.Time) {
|
||||
newToken, err := GetJWTToken(cfg)
|
||||
if err != nil {
|
||||
ShowError(ctx, cfg, err)
|
||||
return
|
||||
}
|
||||
SetTokenCookie(ctx, newToken, cfg)
|
||||
}
|
||||
c.Abort()
|
||||
return
|
||||
}
|
||||
|
||||
// Create a new token if the old one is about to expire
|
||||
if time.Now().Add(time.Hour * 24 * 3).After(claims.ExpiresAt.Time) {
|
||||
newToken, err := GetJWTToken(cfg)
|
||||
if err != nil {
|
||||
ShowError(c, err)
|
||||
return
|
||||
}
|
||||
SetTokenCookie(c, newToken)
|
||||
}
|
||||
}
|
||||
|
||||
func GetJWTToken(cfg *config.Config) (string, error) {
|
||||
claims := jwt.RegisteredClaims{
|
||||
ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Hour * 24 * 7)),
|
||||
ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Second * TOKEN_LIFETIME_IN_SECONDS)),
|
||||
}
|
||||
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
|
||||
return token.SignedString([]byte(cfg.SecretKey))
|
||||
}
|
||||
|
||||
func CreateUser(c *gin.Context, db *gorm.DB, cfg *config.Config) {
|
||||
if database.CountAdmins(db) > 0 {
|
||||
ShowError(c, errors.New("At least 1 user already exists"))
|
||||
return
|
||||
}
|
||||
func CreateUser(db *gorm.DB, cfg *config.Config) gin.HandlerFunc {
|
||||
return func(ctx *gin.Context) {
|
||||
if database.CountAdmins(db) > 0 {
|
||||
ShowError(ctx, cfg, errors.New("At least 1 user already exists"))
|
||||
return
|
||||
}
|
||||
|
||||
// Try to create a user.
|
||||
username := c.PostForm("username")
|
||||
password := c.PostForm("password")
|
||||
_, err := database.CreateAdmin(db, username, password)
|
||||
if err != nil {
|
||||
ShowError(c, err)
|
||||
return
|
||||
}
|
||||
// Try to create a user.
|
||||
username := ctx.PostForm("username")
|
||||
password := ctx.PostForm("password")
|
||||
_, err := database.CreateAdmin(db, username, password)
|
||||
if err != nil {
|
||||
ShowError(ctx, cfg, err)
|
||||
return
|
||||
}
|
||||
|
||||
// Generate access token.
|
||||
token, err := GetJWTToken(cfg)
|
||||
if err != nil {
|
||||
ShowError(c, err)
|
||||
return
|
||||
}
|
||||
SetTokenCookie(c, token)
|
||||
// Generate access token.
|
||||
token, err := GetJWTToken(cfg)
|
||||
if err != nil {
|
||||
ShowError(ctx, cfg, err)
|
||||
return
|
||||
}
|
||||
SetTokenCookie(ctx, token, cfg)
|
||||
|
||||
// Redirect to homepage.
|
||||
c.Redirect(http.StatusFound, "/")
|
||||
// Redirect to homepage.
|
||||
ctx.Redirect(http.StatusFound, "/")
|
||||
}
|
||||
}
|
||||
|
||||
func AuthorizeUser(c *gin.Context, db *gorm.DB, cfg *config.Config) {
|
||||
// Check credentials.
|
||||
username := c.PostForm("username")
|
||||
password := c.PostForm("password")
|
||||
_, err := database.AuthorizeAdmin(db, username, password)
|
||||
if err != nil {
|
||||
ShowError(c, err)
|
||||
return
|
||||
}
|
||||
func AuthorizeUser(db *gorm.DB, cfg *config.Config) gin.HandlerFunc {
|
||||
return func(ctx *gin.Context) {
|
||||
// Check credentials.
|
||||
username := ctx.PostForm("username")
|
||||
password := ctx.PostForm("password")
|
||||
_, err := database.AuthorizeAdmin(db, username, password)
|
||||
if err != nil {
|
||||
ShowError(ctx, cfg, err)
|
||||
return
|
||||
}
|
||||
|
||||
// Generate an access token.
|
||||
token, err := GetJWTToken(cfg)
|
||||
if err != nil {
|
||||
ShowError(c, err)
|
||||
return
|
||||
}
|
||||
SetTokenCookie(c, token)
|
||||
// Generate an access token.
|
||||
token, err := GetJWTToken(cfg)
|
||||
if err != nil {
|
||||
ShowError(ctx, cfg, err)
|
||||
return
|
||||
}
|
||||
SetTokenCookie(ctx, token, cfg)
|
||||
|
||||
// Redirect to homepage.
|
||||
c.Redirect(http.StatusFound, "/")
|
||||
// Redirect to homepage.
|
||||
ctx.Redirect(http.StatusFound, "/")
|
||||
}
|
||||
}
|
||||
|
||||
// Save token for one day in cookies
|
||||
func SetTokenCookie(c *gin.Context, token string) {
|
||||
c.SetCookie("phoenix-token", token, 60*60*24, "/", "", false, true)
|
||||
// Save token in cookies
|
||||
func SetTokenCookie(c *gin.Context, token string, cfg *config.Config) {
|
||||
c.SetCookie("phoenix-token", token, TOKEN_LIFETIME_IN_SECONDS, "/", "", cfg.SecureCookie, true)
|
||||
}
|
||||
|
|
|
@ -2,16 +2,13 @@ package views
|
|||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/ordinary-dev/phoenix/config"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func ShowError(c *gin.Context, err error) {
|
||||
c.HTML(
|
||||
http.StatusBadRequest,
|
||||
"error.html.tmpl",
|
||||
gin.H{
|
||||
"error": err.Error(),
|
||||
},
|
||||
)
|
||||
c.Abort()
|
||||
func ShowError(ctx *gin.Context, cfg *config.Config, err error) {
|
||||
Render(ctx, cfg, http.StatusBadRequest, "error.html.tmpl", gin.H{
|
||||
"error": err.Error(),
|
||||
})
|
||||
ctx.Abort()
|
||||
}
|
||||
|
|
|
@ -1,62 +1,70 @@
|
|||
package views
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/ordinary-dev/phoenix/config"
|
||||
"github.com/ordinary-dev/phoenix/database"
|
||||
"gorm.io/gorm"
|
||||
"net/http"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
func CreateGroup(c *gin.Context, db *gorm.DB) {
|
||||
// Save new group to the database.
|
||||
group := database.Group{
|
||||
Name: c.PostForm("groupName"),
|
||||
}
|
||||
if result := db.Create(&group); result.Error != nil {
|
||||
ShowError(c, result.Error)
|
||||
return
|
||||
}
|
||||
func CreateGroup(cfg *config.Config, db *gorm.DB) gin.HandlerFunc {
|
||||
return func(ctx *gin.Context) {
|
||||
// Save new group to the database.
|
||||
group := database.Group{
|
||||
Name: ctx.PostForm("groupName"),
|
||||
}
|
||||
if result := db.Create(&group); result.Error != nil {
|
||||
ShowError(ctx, cfg, result.Error)
|
||||
return
|
||||
}
|
||||
|
||||
// This page is called from the settings, return the user back.
|
||||
c.Redirect(http.StatusFound, "/settings")
|
||||
// This page is called from the settings, return the user back.
|
||||
ctx.Redirect(http.StatusFound, fmt.Sprintf("/settings#group-%v", group.ID))
|
||||
}
|
||||
}
|
||||
|
||||
func UpdateGroup(c *gin.Context, db *gorm.DB) {
|
||||
id, err := strconv.ParseUint(c.Param("id"), 10, 64)
|
||||
if err != nil {
|
||||
ShowError(c, err)
|
||||
return
|
||||
}
|
||||
func UpdateGroup(cfg *config.Config, db *gorm.DB) gin.HandlerFunc {
|
||||
return func(ctx *gin.Context) {
|
||||
id, err := strconv.ParseUint(ctx.Param("id"), 10, 64)
|
||||
if err != nil {
|
||||
ShowError(ctx, cfg, err)
|
||||
return
|
||||
}
|
||||
|
||||
var group database.Group
|
||||
if result := db.First(&group, id); result.Error != nil {
|
||||
ShowError(c, result.Error)
|
||||
return
|
||||
}
|
||||
var group database.Group
|
||||
if result := db.First(&group, id); result.Error != nil {
|
||||
ShowError(ctx, cfg, result.Error)
|
||||
return
|
||||
}
|
||||
|
||||
group.Name = c.PostForm("groupName")
|
||||
if result := db.Save(&group); result.Error != nil {
|
||||
ShowError(c, result.Error)
|
||||
return
|
||||
}
|
||||
group.Name = ctx.PostForm("groupName")
|
||||
if result := db.Save(&group); result.Error != nil {
|
||||
ShowError(ctx, cfg, result.Error)
|
||||
return
|
||||
}
|
||||
|
||||
// This page is called from the settings, return the user back.
|
||||
c.Redirect(http.StatusFound, "/settings")
|
||||
// This page is called from the settings, return the user back.
|
||||
ctx.Redirect(http.StatusFound, fmt.Sprintf("/settings#group-%v", group.ID))
|
||||
}
|
||||
}
|
||||
|
||||
func DeleteGroup(c *gin.Context, db *gorm.DB) {
|
||||
id, err := strconv.ParseUint(c.Param("id"), 10, 64)
|
||||
if err != nil {
|
||||
ShowError(c, err)
|
||||
return
|
||||
}
|
||||
func DeleteGroup(cfg *config.Config, db *gorm.DB) gin.HandlerFunc {
|
||||
return func(ctx *gin.Context) {
|
||||
id, err := strconv.ParseUint(ctx.Param("id"), 10, 64)
|
||||
if err != nil {
|
||||
ShowError(ctx, cfg, err)
|
||||
return
|
||||
}
|
||||
|
||||
if result := db.Delete(&database.Group{}, id); result.Error != nil {
|
||||
ShowError(c, result.Error)
|
||||
return
|
||||
}
|
||||
if result := db.Delete(&database.Group{}, id); result.Error != nil {
|
||||
ShowError(ctx, cfg, result.Error)
|
||||
return
|
||||
}
|
||||
|
||||
// Redirect to settings.
|
||||
c.Redirect(http.StatusFound, "/settings")
|
||||
// Redirect to settings.
|
||||
ctx.Redirect(http.StatusFound, "/settings")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,25 +2,28 @@ package views
|
|||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/ordinary-dev/phoenix/config"
|
||||
"github.com/ordinary-dev/phoenix/database"
|
||||
"gorm.io/gorm"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func ShowMainPage(c *gin.Context, db *gorm.DB) {
|
||||
// Get a list of groups with links
|
||||
var groups []database.Group
|
||||
result := db.
|
||||
Model(&database.Group{}).
|
||||
Preload("Links").
|
||||
Find(&groups)
|
||||
func ShowMainPage(cfg *config.Config, db *gorm.DB) gin.HandlerFunc {
|
||||
return func(ctx *gin.Context) {
|
||||
// Get a list of groups with links
|
||||
var groups []database.Group
|
||||
result := db.
|
||||
Model(&database.Group{}).
|
||||
Preload("Links").
|
||||
Find(&groups)
|
||||
|
||||
if result.Error != nil {
|
||||
ShowError(c, result.Error)
|
||||
return
|
||||
if result.Error != nil {
|
||||
ShowError(ctx, cfg, result.Error)
|
||||
return
|
||||
}
|
||||
|
||||
Render(ctx, cfg, http.StatusOK, "index.html.tmpl", gin.H{
|
||||
"groups": groups,
|
||||
})
|
||||
}
|
||||
|
||||
c.HTML(http.StatusOK, "index.html.tmpl", gin.H{
|
||||
"groups": groups,
|
||||
})
|
||||
}
|
||||
|
|
128
views/links.go
|
@ -1,82 +1,90 @@
|
|||
package views
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/ordinary-dev/phoenix/config"
|
||||
"github.com/ordinary-dev/phoenix/database"
|
||||
"gorm.io/gorm"
|
||||
"net/http"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
func CreateLink(c *gin.Context, db *gorm.DB) {
|
||||
groupID, err := strconv.ParseUint(c.PostForm("groupID"), 10, 32)
|
||||
if err != nil {
|
||||
ShowError(c, err)
|
||||
return
|
||||
}
|
||||
func CreateLink(cfg *config.Config, db *gorm.DB) gin.HandlerFunc {
|
||||
return func(ctx *gin.Context) {
|
||||
groupID, err := strconv.ParseUint(ctx.PostForm("groupID"), 10, 32)
|
||||
if err != nil {
|
||||
ShowError(ctx, cfg, err)
|
||||
return
|
||||
}
|
||||
|
||||
link := database.Link{
|
||||
Name: c.PostForm("linkName"),
|
||||
Href: c.PostForm("href"),
|
||||
GroupID: groupID,
|
||||
}
|
||||
icon := c.PostForm("icon")
|
||||
if icon == "" {
|
||||
link.Icon = nil
|
||||
} else {
|
||||
link.Icon = &icon
|
||||
}
|
||||
if result := db.Create(&link); result.Error != nil {
|
||||
ShowError(c, result.Error)
|
||||
return
|
||||
}
|
||||
link := database.Link{
|
||||
Name: ctx.PostForm("linkName"),
|
||||
Href: ctx.PostForm("href"),
|
||||
GroupID: groupID,
|
||||
}
|
||||
icon := ctx.PostForm("icon")
|
||||
if icon == "" {
|
||||
link.Icon = nil
|
||||
} else {
|
||||
link.Icon = &icon
|
||||
}
|
||||
if result := db.Create(&link); result.Error != nil {
|
||||
ShowError(ctx, cfg, result.Error)
|
||||
return
|
||||
}
|
||||
|
||||
// Redirect to settings.
|
||||
c.Redirect(http.StatusFound, "/settings")
|
||||
// Redirect to settings.
|
||||
ctx.Redirect(http.StatusFound, fmt.Sprintf("/settings#link-%v", link.ID))
|
||||
}
|
||||
}
|
||||
|
||||
func UpdateLink(c *gin.Context, db *gorm.DB) {
|
||||
id, err := strconv.ParseUint(c.Param("id"), 10, 64)
|
||||
if err != nil {
|
||||
ShowError(c, err)
|
||||
return
|
||||
}
|
||||
func UpdateLink(cfg *config.Config, db *gorm.DB) gin.HandlerFunc {
|
||||
return func(ctx *gin.Context) {
|
||||
id, err := strconv.ParseUint(ctx.Param("id"), 10, 64)
|
||||
if err != nil {
|
||||
ShowError(ctx, cfg, err)
|
||||
return
|
||||
}
|
||||
|
||||
var link database.Link
|
||||
if result := db.First(&link, id); result.Error != nil {
|
||||
ShowError(c, err)
|
||||
return
|
||||
}
|
||||
var link database.Link
|
||||
if result := db.First(&link, id); result.Error != nil {
|
||||
ShowError(ctx, cfg, err)
|
||||
return
|
||||
}
|
||||
|
||||
link.Name = c.PostForm("linkName")
|
||||
link.Href = c.PostForm("href")
|
||||
icon := c.PostForm("icon")
|
||||
if icon == "" {
|
||||
link.Icon = nil
|
||||
} else {
|
||||
link.Icon = &icon
|
||||
}
|
||||
if result := db.Save(&link); result.Error != nil {
|
||||
ShowError(c, result.Error)
|
||||
return
|
||||
}
|
||||
link.Name = ctx.PostForm("linkName")
|
||||
link.Href = ctx.PostForm("href")
|
||||
icon := ctx.PostForm("icon")
|
||||
if icon == "" {
|
||||
link.Icon = nil
|
||||
} else {
|
||||
link.Icon = &icon
|
||||
}
|
||||
if result := db.Save(&link); result.Error != nil {
|
||||
ShowError(ctx, cfg, result.Error)
|
||||
return
|
||||
}
|
||||
|
||||
// Redirect to settings.
|
||||
c.Redirect(http.StatusFound, "/settings")
|
||||
// Redirect to settings.
|
||||
ctx.Redirect(http.StatusFound, fmt.Sprintf("/settings#link-%v", link.ID))
|
||||
}
|
||||
}
|
||||
|
||||
func DeleteLink(c *gin.Context, db *gorm.DB) {
|
||||
id, err := strconv.ParseUint(c.Param("id"), 10, 64)
|
||||
if err != nil {
|
||||
ShowError(c, err)
|
||||
return
|
||||
}
|
||||
func DeleteLink(cfg *config.Config, db *gorm.DB) gin.HandlerFunc {
|
||||
return func(ctx *gin.Context) {
|
||||
id, err := strconv.ParseUint(ctx.Param("id"), 10, 64)
|
||||
if err != nil {
|
||||
ShowError(ctx, cfg, err)
|
||||
return
|
||||
}
|
||||
|
||||
if result := db.Delete(&database.Link{}, id); result.Error != nil {
|
||||
ShowError(c, result.Error)
|
||||
return
|
||||
}
|
||||
if result := db.Delete(&database.Link{}, id); result.Error != nil {
|
||||
ShowError(ctx, cfg, result.Error)
|
||||
return
|
||||
}
|
||||
|
||||
// Redirect to settings.
|
||||
c.Redirect(http.StatusFound, "/settings")
|
||||
// Redirect to settings.
|
||||
ctx.Redirect(http.StatusFound, "/settings")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,68 +22,42 @@ func GetGinEngine(cfg *config.Config, db *gorm.DB) *gin.Engine {
|
|||
|
||||
engine.Use(SecurityHeadersMiddleware)
|
||||
|
||||
engine.GET("/signin", func(c *gin.Context) {
|
||||
ShowLoginForm(c)
|
||||
})
|
||||
engine.POST("/api/users/signin", func(c *gin.Context) {
|
||||
AuthorizeUser(c, db, cfg)
|
||||
})
|
||||
engine.GET("/signin", ShowLoginForm(cfg))
|
||||
engine.POST("/api/users/signin", AuthorizeUser(db, cfg))
|
||||
|
||||
engine.GET("/registration", func(c *gin.Context) {
|
||||
ShowRegistrationForm(c, db)
|
||||
})
|
||||
engine.POST("/api/users", func(c *gin.Context) {
|
||||
CreateUser(c, db, cfg)
|
||||
})
|
||||
engine.GET("/registration", ShowRegistrationForm(cfg, db))
|
||||
engine.POST("/api/users", CreateUser(db, cfg))
|
||||
|
||||
// This group requires authorization before viewing.
|
||||
protected := engine.Group("/")
|
||||
protected.Use(func(c *gin.Context) {
|
||||
AuthMiddleware(c, db, cfg)
|
||||
})
|
||||
protected.Use(AuthMiddleware(db, cfg))
|
||||
|
||||
// Main page
|
||||
protected.GET("/", func(c *gin.Context) {
|
||||
ShowMainPage(c, db)
|
||||
})
|
||||
protected.GET("/", ShowMainPage(cfg, db))
|
||||
|
||||
protected.GET("/settings", func(c *gin.Context) {
|
||||
ShowSettings(c, db)
|
||||
})
|
||||
protected.GET("/settings", ShowSettings(cfg, db))
|
||||
|
||||
// Create new group
|
||||
protected.POST("/api/groups", func(c *gin.Context) {
|
||||
CreateGroup(c, db)
|
||||
})
|
||||
protected.POST("/api/groups", CreateGroup(cfg, db))
|
||||
|
||||
// Update group
|
||||
// HTML forms cannot be submitted using PUT or PATCH methods without javascript.
|
||||
protected.POST("/api/groups/:id/put", func(c *gin.Context) {
|
||||
UpdateGroup(c, db)
|
||||
})
|
||||
protected.POST("/api/groups/:id/put", UpdateGroup(cfg, db))
|
||||
|
||||
// Delete group
|
||||
// HTML forms cannot be submitted using the DELETE method without javascript.
|
||||
protected.POST("/api/groups/:id/delete", func(c *gin.Context) {
|
||||
DeleteGroup(c, db)
|
||||
})
|
||||
protected.POST("/api/groups/:id/delete", DeleteGroup(cfg, db))
|
||||
|
||||
// Create new link
|
||||
protected.POST("/api/links", func(c *gin.Context) {
|
||||
CreateLink(c, db)
|
||||
})
|
||||
protected.POST("/api/links", CreateLink(cfg, db))
|
||||
|
||||
// Update link.
|
||||
// HTML forms cannot be submitted using PUT or PATCH methods without javascript.
|
||||
protected.POST("/api/links/:id/put", func(c *gin.Context) {
|
||||
UpdateLink(c, db)
|
||||
})
|
||||
protected.POST("/api/links/:id/put", UpdateLink(cfg, db))
|
||||
|
||||
// Delete link
|
||||
// HTML forms cannot be submitted using the DELETE method without javascript.
|
||||
protected.POST("/api/links/:id/delete", func(c *gin.Context) {
|
||||
DeleteLink(c, db)
|
||||
})
|
||||
protected.POST("/api/links/:id/delete", DeleteLink(cfg, db))
|
||||
|
||||
return engine
|
||||
}
|
||||
|
|
13
views/render.go
Normal file
|
@ -0,0 +1,13 @@
|
|||
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
|
||||
params["FontFamily"] = cfg.FontFamily
|
||||
ctx.HTML(status, templatePath, params)
|
||||
}
|
|
@ -2,25 +2,28 @@ package views
|
|||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/ordinary-dev/phoenix/config"
|
||||
"github.com/ordinary-dev/phoenix/database"
|
||||
"gorm.io/gorm"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func ShowSettings(c *gin.Context, db *gorm.DB) {
|
||||
// Get a list of groups with links
|
||||
var groups []database.Group
|
||||
result := db.
|
||||
Model(&database.Group{}).
|
||||
Preload("Links").
|
||||
Find(&groups)
|
||||
func ShowSettings(cfg *config.Config, db *gorm.DB) gin.HandlerFunc {
|
||||
return func(ctx *gin.Context) {
|
||||
// Get a list of groups with links
|
||||
var groups []database.Group
|
||||
result := db.
|
||||
Model(&database.Group{}).
|
||||
Preload("Links").
|
||||
Find(&groups)
|
||||
|
||||
if result.Error != nil {
|
||||
ShowError(c, result.Error)
|
||||
return
|
||||
if result.Error != nil {
|
||||
ShowError(ctx, cfg, result.Error)
|
||||
return
|
||||
}
|
||||
|
||||
Render(ctx, cfg, http.StatusOK, "settings.html.tmpl", gin.H{
|
||||
"groups": groups,
|
||||
})
|
||||
}
|
||||
|
||||
c.HTML(http.StatusOK, "settings.html.tmpl", gin.H{
|
||||
"groups": groups,
|
||||
})
|
||||
}
|
||||
|
|