mirror of
https://github.com/Alexander-D-Karpov/webring.git
synced 2026-03-16 22:07:41 +03:00
Compare commits
8 Commits
v20251216-
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
65d4d3c3fc | ||
| 12fdd0ba64 | |||
| f774fa993f | |||
| d4607434d4 | |||
| 7ed6a23b63 | |||
|
|
d324c9490d | ||
| 499c4a807b | |||
| bd346671b8 |
|
|
@ -10,4 +10,6 @@ SESSION_SECURE_COOKIE=true # Set to true if using HTTPS, false for HTTP
|
|||
CSRF_AUTH_KEY=your_csrf_auth_key
|
||||
CSRF_TRUSTED_ORIGINS=
|
||||
REQUIRE_LOGIN_FOR_SUBMIT=false
|
||||
CHECKER_WORKERS=5
|
||||
CHECKER_WORKERS=5
|
||||
CHECKER_DOWN_THRESHOLD=3
|
||||
MESSAGES_DIR=messages
|
||||
|
|
@ -30,6 +30,7 @@ WORKDIR /root/
|
|||
# Copy the binary from builder stage
|
||||
COPY --from=builder /build/webring .
|
||||
COPY --from=builder /build/docs ./docs
|
||||
COPY --from=builder /build/messages ./messages
|
||||
|
||||
# Create media directory
|
||||
RUN mkdir -p media
|
||||
|
|
|
|||
45
README.md
45
README.md
|
|
@ -7,6 +7,10 @@ This project is a webring relay service built with Go. It manages a list of webs
|
|||
- Dashboard for managing websites in the webring
|
||||
- Automatic uptime checking of websites (with proxy support)
|
||||
- API endpoints for navigating the webring
|
||||
- Telegram authentication and user management
|
||||
- Site submission and update request workflow with admin approval
|
||||
- Telegram notifications for status changes, submissions and approvals
|
||||
- Customizable notification messages via template files
|
||||
- Basic authentication for the dashboard
|
||||
|
||||
## Prerequisites
|
||||
|
|
@ -17,7 +21,6 @@ This project is a webring relay service built with Go. It manages a list of webs
|
|||
## Installation
|
||||
|
||||
edit .env to set correct path to database
|
||||
|
||||
```
|
||||
go install -tags 'postgres' github.com/golang-migrate/migrate/v4/cmd/migrate@latest
|
||||
go mod tidy
|
||||
|
|
@ -26,31 +29,51 @@ make migrate-up
|
|||
```
|
||||
|
||||
## Local Run
|
||||
|
||||
```
|
||||
go run cmd/server/main.go
|
||||
```
|
||||
|
||||
or download prebuild version
|
||||
|
||||
```
|
||||
wget https://github.com/Alexander-D-Karpov/webring/releases/latest/download/webring
|
||||
chmod +x webring
|
||||
./webring
|
||||
```
|
||||
|
||||
## Customizing Notification Messages
|
||||
|
||||
Telegram notification templates live in the `messages/` directory (configurable via `MESSAGES_DIR` env var).
|
||||
Each file is plain text with Go template syntax and MarkdownV2 formatting.
|
||||
|
||||
To customize a message, edit the corresponding `.txt` file.
|
||||
|
||||
Available templates:
|
||||
|
||||
| File | Event |
|
||||
|-----------------------------|---------------------------------------------|
|
||||
| `new_request_create.txt` | Admin notification: new site submitted |
|
||||
| `new_request_update.txt` | Admin notification: site update requested |
|
||||
| `approved_create.txt` | User notification: site submission approved |
|
||||
| `approved_update.txt` | User notification: site update approved |
|
||||
| `declined_create.txt` | User notification: site submission declined |
|
||||
| `declined_update.txt` | User notification: site update declined |
|
||||
| `admin_approved_create.txt` | Other admins: site creation approved |
|
||||
| `admin_approved_update.txt` | Other admins: site update approved |
|
||||
| `admin_declined_create.txt` | Other admins: site creation declined |
|
||||
| `admin_declined_update.txt` | Other admins: site update declined |
|
||||
| `site_online.txt` | Owner notification: site back online |
|
||||
| `site_offline.txt` | Owner notification: site went offline |
|
||||
|
||||
## Usage
|
||||
|
||||
- Access the dashboard at `http://localhost:8080/dashboard` (use the credentials set in your `.env` file)
|
||||
- API endpoints:
|
||||
- Next site: `GET /{slug}/next/data`
|
||||
- Previous site: `GET /{slug}/prev/data`
|
||||
- Random site: `GET /{slug}/random/data`
|
||||
- Full data for a site: `GET /{slug}/data`
|
||||
- Next site: `GET /{slug}/next/data`
|
||||
- Previous site: `GET /{slug}/prev/data`
|
||||
- Random site: `GET /{slug}/random/data`
|
||||
- Full data for a site: `GET /{slug}/data`
|
||||
- Redirect endpoints:
|
||||
- Visit site: `GET /{slug}`
|
||||
- Next site: `GET /{slug}/next`
|
||||
- Previous site: `GET /{slug}/prev`
|
||||
- Random site: `GET /{slug}/random`
|
||||
- Visit site: `GET /{slug}`
|
||||
- Next site: `GET /{slug}/next`
|
||||
- Previous site: `GET /{slug}/prev`
|
||||
- Random site: `GET /{slug}/random`
|
||||
|
|
@ -21,6 +21,7 @@ import (
|
|||
"webring/internal/dashboard"
|
||||
"webring/internal/database"
|
||||
"webring/internal/public"
|
||||
"webring/internal/telegram"
|
||||
"webring/internal/uptime"
|
||||
"webring/internal/user"
|
||||
|
||||
|
|
@ -101,6 +102,12 @@ func main() {
|
|||
}
|
||||
}()
|
||||
|
||||
messagesDir := os.Getenv("MESSAGES_DIR")
|
||||
if messagesDir == "" {
|
||||
messagesDir = "messages"
|
||||
}
|
||||
telegram.InitTemplates(messagesDir)
|
||||
|
||||
startBackgroundServices(db)
|
||||
|
||||
r := mux.NewRouter()
|
||||
|
|
|
|||
10
go.mod
10
go.mod
|
|
@ -3,15 +3,15 @@ module webring
|
|||
go 1.25.0
|
||||
|
||||
require (
|
||||
github.com/PuerkitoBio/goquery v1.9.2
|
||||
github.com/PuerkitoBio/goquery v1.11.0
|
||||
github.com/gorilla/csrf v1.7.3
|
||||
github.com/gorilla/mux v1.8.1
|
||||
github.com/joho/godotenv v1.5.1
|
||||
github.com/lib/pq v1.10.9
|
||||
github.com/lib/pq v1.11.2
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/andybalholm/cascadia v1.3.2 // indirect
|
||||
github.com/andybalholm/cascadia v1.3.3 // indirect
|
||||
github.com/gorilla/securecookie v1.1.2 // indirect
|
||||
golang.org/x/net v0.24.0 // indirect
|
||||
)
|
||||
golang.org/x/net v0.50.0 // indirect
|
||||
)
|
||||
|
|
|
|||
42
go.sum
42
go.sum
|
|
@ -1,7 +1,12 @@
|
|||
github.com/PuerkitoBio/goquery v1.9.2 h1:4/wZksC3KgkQw7SQgkKotmKljk0M6V8TUvA8Wb4yPeE=
|
||||
github.com/PuerkitoBio/goquery v1.9.2/go.mod h1:GHPCaP0ODyyxqcNoFGYlAprUFH81NuRPd0GX3Zu2Mvk=
|
||||
github.com/PuerkitoBio/goquery v1.11.0 h1:jZ7pwMQXIITcUXNH83LLk+txlaEy6NVOfTuP43xxfqw=
|
||||
github.com/PuerkitoBio/goquery v1.11.0/go.mod h1:wQHgxUOU3JGuj3oD/QFfxUdlzW6xPHfqyHre6VMY4DQ=
|
||||
github.com/andybalholm/cascadia v1.3.2 h1:3Xi6Dw5lHF15JtdcmAHD3i1+T8plmv7BQ/nsViSLyss=
|
||||
github.com/andybalholm/cascadia v1.3.2/go.mod h1:7gtRlve5FxPPgIgX36uWBX58OdBsSS6lUvCFb+h7KvU=
|
||||
github.com/andybalholm/cascadia v1.3.3 h1:AG2YHrzJIm4BZ19iwJ/DAua6Btl3IwJX+VI4kktS1LM=
|
||||
github.com/andybalholm/cascadia v1.3.3/go.mod h1:xNd9bqTn98Ln4DwST8/nG+H0yuB8Hmgu1YHNnWw0GeA=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
|
||||
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/gorilla/csrf v1.7.3 h1:BHWt6FTLZAb2HtWT5KDBf6qgpZzvtbp9QWDRKZMXJC0=
|
||||
|
|
@ -14,21 +19,41 @@ github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
|
|||
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
|
||||
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
|
||||
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||
github.com/lib/pq v1.11.2 h1:x6gxUeu39V0BHZiugWe8LXZYZ+Utk7hSJGThs8sdzfs=
|
||||
github.com/lib/pq v1.11.2/go.mod h1:/p+8NSbOcwzAEI7wiMXFlgydTwcgTr3OSKMsD2BitpA=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
|
||||
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
|
||||
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
|
||||
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
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-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
|
||||
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||
golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
|
||||
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
|
||||
golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w=
|
||||
golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8=
|
||||
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
|
||||
golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
|
||||
golang.org/x/net v0.50.0 h1:ucWh9eiCGyDR3vtzso0WMQinm2Dnt8cFMuQa9K33J60=
|
||||
golang.org/x/net v0.50.0/go.mod h1:UgoSli3F/pBgdJBHCTc+tp3gmrU4XswgGRgtnwWTfyM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/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/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
|
||||
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
|
|
@ -36,17 +61,34 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc
|
|||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
|
||||
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.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY=
|
||||
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
||||
golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
|
||||
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
|
||||
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
|
||||
golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
|
||||
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.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
||||
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/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
|
||||
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -77,6 +77,7 @@ func RegisterHandlers(r *mux.Router, db *sql.DB) {
|
|||
adminRouter.HandleFunc("/update/{id}", updateSiteHandler(db)).Methods("POST")
|
||||
adminRouter.HandleFunc("/reorder/{id}/{direction}", reorderSiteHandler(db)).Methods("POST")
|
||||
adminRouter.HandleFunc("/move/{id}/{position}", moveSiteHandler(db)).Methods("POST")
|
||||
adminRouter.HandleFunc("/toggle-enabled/{id}", toggleEnabledHandler(db)).Methods("POST")
|
||||
}
|
||||
|
||||
func renderTemplate(w http.ResponseWriter, name string, data interface{}) error {
|
||||
|
|
@ -172,8 +173,9 @@ func addSiteHandler(db *sql.DB) http.HandlerFunc {
|
|||
}
|
||||
}
|
||||
|
||||
_, err = db.Exec("INSERT INTO sites (id, slug, name, url, display_order, user_id) VALUES ($1, $2, $3, $4, $5, $6)",
|
||||
id, slug, name, url, maxDisplayOrder+1, userID)
|
||||
_, err = db.Exec("INSERT INTO sites (id, slug, name, url, display_order, user_id, enabled) "+
|
||||
"VALUES ($1, $2, $3, $4, $5, $6, $7)",
|
||||
id, slug, name, url, maxDisplayOrder+1, userID, true)
|
||||
if err != nil {
|
||||
var pqErr *pq.Error
|
||||
if errors.As(err, &pqErr) && pqErr.Code.Name() == uniqueViolation {
|
||||
|
|
@ -306,6 +308,21 @@ func updateSiteHandler(db *sql.DB) http.HandlerFunc {
|
|||
}
|
||||
}
|
||||
|
||||
func toggleEnabledHandler(db *sql.DB) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
id := mux.Vars(r)["id"]
|
||||
|
||||
_, err := db.Exec("UPDATE sites SET enabled = NOT enabled WHERE id = $1", id)
|
||||
if err != nil {
|
||||
log.Printf("Error toggling site enabled status: %v", err)
|
||||
http.Error(w, "Error toggling site status", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
http.Redirect(w, r, "/admin", http.StatusSeeOther)
|
||||
}
|
||||
}
|
||||
|
||||
func reorderSiteHandler(db *sql.DB) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
idStr := mux.Vars(r)["id"]
|
||||
|
|
@ -528,7 +545,7 @@ func moveSiteHandler(db *sql.DB) http.HandlerFunc {
|
|||
func getAllSites(db *sql.DB) ([]models.Site, error) {
|
||||
rows, err := db.QueryContext(
|
||||
context.Background(), `
|
||||
SELECT s.id, s.slug, s.name, s.url, s.is_up, s.last_check, s.favicon, s.user_id, u.telegram_username
|
||||
SELECT s.id, s.slug, s.name, s.url, s.is_up, s.enabled, s.last_check, s.favicon, s.user_id, u.telegram_username
|
||||
FROM sites s
|
||||
LEFT JOIN users u ON s.user_id = u.id
|
||||
ORDER BY s.display_order
|
||||
|
|
@ -547,7 +564,7 @@ func getAllSites(db *sql.DB) ([]models.Site, error) {
|
|||
var site models.Site
|
||||
var telegramUsername sql.NullString
|
||||
scanErr := rows.Scan(&site.ID, &site.Slug, &site.Name, &site.URL, &site.IsUp,
|
||||
&site.LastCheck, &site.Favicon, &site.UserID, &telegramUsername)
|
||||
&site.Enabled, &site.LastCheck, &site.Favicon, &site.UserID, &telegramUsername)
|
||||
if scanErr != nil {
|
||||
return nil, scanErr
|
||||
}
|
||||
|
|
|
|||
|
|
@ -69,6 +69,7 @@
|
|||
<th class="col-url">URL</th>
|
||||
<th class="col-telegram">TELEGRAM</th>
|
||||
<th class="col-status">STATUS</th>
|
||||
<th class="col-enabled">ENABLED</th>
|
||||
<th class="col-ping">PING</th>
|
||||
<th class="col-actions">ACTIONS</th>
|
||||
</tr>
|
||||
|
|
@ -97,6 +98,7 @@
|
|||
<span class="status-badge new">New</span>
|
||||
</td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td>
|
||||
<div class="actions">
|
||||
<button type="submit" form="form-new" class="btn btn-primary btn-sm">
|
||||
|
|
@ -169,6 +171,18 @@
|
|||
<span class="status-badge down">DOWN</span>
|
||||
{{end}}
|
||||
</td>
|
||||
<td>
|
||||
<form action="/admin/toggle-enabled/{{.ID}}" method="POST" style="display: inline;">
|
||||
{{csrfField $.Request}}
|
||||
<button type="submit" class="btn btn-sm {{if .Enabled}}btn-success{{else}}btn-warning{{end}}" title="{{if .Enabled}}Click to disable{{else}}Click to enable{{end}}">
|
||||
{{if .Enabled}}
|
||||
<i class="ri-check-line"></i>
|
||||
{{else}}
|
||||
<i class="ri-close-line"></i>
|
||||
{{end}}
|
||||
</button>
|
||||
</form>
|
||||
</td>
|
||||
<td class="ping-cell">
|
||||
{{if .LastCheck}}
|
||||
<span class="ping-value">{{.LastCheck}}</span>
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ type Site struct {
|
|||
Name string `json:"name"`
|
||||
URL string `json:"url"`
|
||||
IsUp bool `json:"is_up"`
|
||||
Enabled bool `json:"enabled"`
|
||||
LastCheck float64 `json:"last_check"`
|
||||
Favicon *string `json:"favicon"`
|
||||
UserID *int `json:"user_id"`
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ import (
|
|||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"html"
|
||||
"html/template"
|
||||
"log"
|
||||
"net/http"
|
||||
|
|
@ -46,8 +45,7 @@ func InitTemplates(t *template.Template) {
|
|||
}
|
||||
|
||||
func sanitizeInput(input string) string {
|
||||
trimmed := strings.TrimSpace(input)
|
||||
return html.EscapeString(trimmed)
|
||||
return strings.TrimSpace(input)
|
||||
}
|
||||
|
||||
func sanitizeURL(input string) string {
|
||||
|
|
@ -55,7 +53,7 @@ func sanitizeURL(input string) string {
|
|||
if !strings.HasPrefix(trimmed, "http://") && !strings.HasPrefix(trimmed, "https://") {
|
||||
trimmed = "https://" + trimmed
|
||||
}
|
||||
return html.EscapeString(trimmed)
|
||||
return trimmed
|
||||
}
|
||||
|
||||
func RegisterHandlers(r *mux.Router, db *sql.DB) {
|
||||
|
|
@ -383,7 +381,8 @@ func getOrCreateAnonymousAdminUser(db *sql.DB) (int, error) {
|
|||
}
|
||||
|
||||
func getRespondingSites(db *sql.DB) ([]models.PublicSite, error) {
|
||||
rows, err := db.Query("SELECT slug, name, url, favicon FROM sites WHERE is_up = true ORDER BY display_order")
|
||||
rows, err := db.Query("SELECT slug, name, url, favicon FROM sites " +
|
||||
"WHERE is_up = true AND enabled = true ORDER BY display_order")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
279
internal/telegram/templates.go
Normal file
279
internal/telegram/templates.go
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
6
messages/admin_approved_create.txt
Normal file
6
messages/admin_approved_create.txt
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
*Request Approved*
|
||||
|
||||
*Admin:* {{.AdminName}}
|
||||
*Action:* Approved site creation
|
||||
*User:* {{.UserName}}
|
||||
*Site:* {{.SiteName}}
|
||||
13
messages/admin_approved_update.txt
Normal file
13
messages/admin_approved_update.txt
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
*Update Approved*
|
||||
|
||||
*Admin:* {{.AdminName}}
|
||||
*Action:* Approved site update
|
||||
*User:* {{.UserName}}
|
||||
*Site:* {{.SiteName}}
|
||||
{{- if .Changes}}
|
||||
|
||||
*Changes:*
|
||||
{{- range .Changes}}
|
||||
• *{{.Key}}:* {{.Value}}
|
||||
{{- end}}
|
||||
{{- end}}
|
||||
6
messages/admin_declined_create.txt
Normal file
6
messages/admin_declined_create.txt
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
*Request Declined*
|
||||
|
||||
*Admin:* {{.AdminName}}
|
||||
*Action:* Declined site creation
|
||||
*User:* {{.UserName}}
|
||||
*Site:* {{.SiteName}}
|
||||
6
messages/admin_declined_update.txt
Normal file
6
messages/admin_declined_update.txt
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
*Update Declined*
|
||||
|
||||
*Admin:* {{.AdminName}}
|
||||
*Action:* Declined site update
|
||||
*User:* {{.UserName}}
|
||||
*Site:* {{.SiteName}}
|
||||
7
messages/approved_create.txt
Normal file
7
messages/approved_create.txt
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
*Request Approved*
|
||||
|
||||
Your site submission has been approved!
|
||||
|
||||
*Site:* {{.SiteName}}
|
||||
|
||||
Your site is now part of the webring.
|
||||
10
messages/approved_update.txt
Normal file
10
messages/approved_update.txt
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
*Update Approved*
|
||||
|
||||
Your site update request has been approved and the changes have been applied.
|
||||
{{- if .Changes}}
|
||||
|
||||
*Applied changes:*
|
||||
{{- range .Changes}}
|
||||
• *{{.Key}}:* {{.Value}}
|
||||
{{- end}}
|
||||
{{- end}}
|
||||
5
messages/declined_create.txt
Normal file
5
messages/declined_create.txt
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
*Request Declined*
|
||||
|
||||
Your site submission request for *{{.SiteName}}* has been declined by an administrator.
|
||||
|
||||
If you have questions, please contact the webring administrator.
|
||||
5
messages/declined_update.txt
Normal file
5
messages/declined_update.txt
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
*Update Request Declined*
|
||||
|
||||
Your update request for *{{.SiteName}}* has been declined by an administrator\.
|
||||
|
||||
If you have questions, please contact the webring administrator\.
|
||||
8
messages/new_request_create.txt
Normal file
8
messages/new_request_create.txt
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
*New Site Submission Request*
|
||||
|
||||
*User:* {{.UserName}}
|
||||
*Slug:* `{{.Slug}}`
|
||||
*Site Name:* {{.SiteName}}
|
||||
*URL:* {{.URL}}
|
||||
|
||||
*Submitted:* {{.Date}}
|
||||
11
messages/new_request_update.txt
Normal file
11
messages/new_request_update.txt
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
*Site Update Request*
|
||||
|
||||
*User:* {{.UserName}}
|
||||
*Site:* {{.SiteName}} (`{{.SiteSlug}}`)
|
||||
|
||||
*Changes:*
|
||||
{{- range .Changes}}
|
||||
• *{{.Key}}:* {{.Value}}
|
||||
{{- end}}
|
||||
|
||||
*Submitted:* {{.Date}}
|
||||
3
messages/site_offline.txt
Normal file
3
messages/site_offline.txt
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
*Site Status: Offline*
|
||||
|
||||
Your site *{{.SiteName}}* is currently not responding after {{.DownThreshold}} consecutive checks. Please check your server.
|
||||
3
messages/site_online.txt
Normal file
3
messages/site_online.txt
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
*Site Status: Online*
|
||||
|
||||
Your site *{{.SiteName}}* is now responding and back online.
|
||||
1
migrations/014_add_enabled_column.down.sql
Normal file
1
migrations/014_add_enabled_column.down.sql
Normal file
|
|
@ -0,0 +1 @@
|
|||
ALTER TABLE sites DROP COLUMN enabled;
|
||||
1
migrations/014_add_enabled_column.up.sql
Normal file
1
migrations/014_add_enabled_column.up.sql
Normal file
|
|
@ -0,0 +1 @@
|
|||
ALTER TABLE sites ADD COLUMN enabled BOOLEAN NOT NULL DEFAULT true;
|
||||
|
|
@ -5,7 +5,7 @@
|
|||
src = ../.;
|
||||
subPackages = [ "cmd/server" ];
|
||||
|
||||
vendorHash = "sha256-bwCfn3AEWKJmsy8FTkLqtx5VXIjOZ7Nux6wAogeb9JM=";
|
||||
vendorHash = "sha256-l8JA0MKEEngPb5R4r3Xd0MhB8Ah2x1mwREgPmqF1D+I=";
|
||||
|
||||
postInstall = ''
|
||||
mv $out/bin/server $out/bin/webring-server
|
||||
|
|
|
|||
|
|
@ -1527,4 +1527,9 @@ input[type=url] {
|
|||
.error-banner-text {
|
||||
font-size: 0.8125rem;
|
||||
}
|
||||
}
|
||||
|
||||
.col-enabled {
|
||||
width: 5rem;
|
||||
text-align: center;
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user