refactor: rewrite api and view

This commit is contained in:
Simon Vieille 2025-08-27 14:58:49 +02:00
commit 62801fd487
Signed by: deblan
GPG key ID: 579388D585F70417
14 changed files with 147 additions and 147 deletions

View file

@ -2,8 +2,7 @@ CGO_ENABLED = 0
GO_ARCH_AMD = amd64
GO_OS_LINUX = linux
EXECUTABLE_SERVER = budget-go
EXECUTABLE_CMD = budget-go-client
EXECUTABLE_SERVER = owncast-webhook
CC = go build
CFLAGS = -trimpath
@ -11,9 +10,7 @@ LDFLAGS = -d -s -w -extldflags=-static
GCFLAGS = all=
ASMFLAGS = all=
all: build
#docker
.PHONY:
rice:
@ -23,16 +20,12 @@ rice:
front:
NODE_ENV=prod ./node_modules/.bin/webpack
.PHONY:
tpl:
TEMPL_EXPERIMENT=rawgo templ generate
lint:
npm run lint || true
npm run format
.PHONY:
build: tpl front rice
build: front rice
CGO_ENABLED=$(CGO_ENABLED) \
GOARCH=$(GO_ARCH_AMD) \
GOOS=$(GO_OS_LINUX) \

View file

@ -1,10 +1,14 @@
package chat
import (
"fmt"
"github.com/labstack/echo/v4"
"gitnet.fr/deblan/owncast-webhook/backend/store"
"gitnet.fr/deblan/owncast-webhook/backend/view"
tpl "gitnet.fr/deblan/owncast-webhook/backend/view/template/chat"
"gitnet.fr/deblan/owncast-webhook/backend/webhook"
. "maragu.dev/gomponents"
. "maragu.dev/gomponents/components"
. "maragu.dev/gomponents/html"
)
type Controller struct {
@ -18,8 +22,57 @@ func New(e *echo.Echo) *Controller {
return &c
}
var messages []webhook.Message
func (ctrl *Controller) Messages(c echo.Context) error {
return view.Render(c, 200, tpl.Messages("Chat - Messages", messages))
page := HTML5(HTML5Props{
Title: "Chat",
Language: "fr",
Head: []Node{
Group(view.EntrypointCss("main")),
Link(Rel("icon"), Type("image/x-icon"), Href(view.Asset("static/img/favicon.png"))),
},
Body: []Node{
ID("chat"),
Div(
Class("messages"),
Map(store.GetMessageStore().All(), func(message store.MessageInterface) Node {
var containerStyle Node
var userStyle Node
if message.Origin() == store.MessageOriginOwncast {
msg := message.(store.OwncastMessage)
containerStyle = StyleAttr(fmt.Sprintf(
"border-color: var(--theme-color-users-%d)",
msg.WebhookMessage.User.DisplayColor,
))
userStyle = StyleAttr(fmt.Sprintf(
"color: var(--theme-color-users-%d)",
msg.WebhookMessage.User.DisplayColor,
))
}
return Div(
Class("message"),
ID(message.ID()),
containerStyle,
Div(
Class("message-user"),
userStyle,
Text(message.Author()),
),
Div(
Class("message-body"),
Raw(message.Content()),
),
)
}),
),
Group(view.EntrypointJs("main")),
},
})
page.Render(c.Response().Writer)
return nil
}

View file

@ -1,10 +1,10 @@
package owncast
import (
"net/http"
"github.com/labstack/echo/v4"
"gitnet.fr/deblan/owncast-webhook/backend/store"
"gitnet.fr/deblan/owncast-webhook/backend/view"
tpl "gitnet.fr/deblan/owncast-webhook/backend/view/template/chat"
"gitnet.fr/deblan/owncast-webhook/backend/webhook"
)
@ -19,24 +19,16 @@ func New(e *echo.Echo) *Controller {
return &c
}
var messages []webhook.Message
func (ctrl *Controller) Messages(c echo.Context) error {
return view.Render(c, 200, tpl.Messages("Chat - Messages", messages))
}
func (ctrl *Controller) ChatMessage(c echo.Context) error {
value := new(webhook.WebhookNewMessage)
if err := c.Bind(value); err != nil {
return c.JSON(400, nil)
return c.JSON(http.StatusBadRequest, map[string]string{"status": "ko"})
}
store.GetMessageStore().Add(store.OwncastMessage{
WebhookMessage: value.EventData,
})
messages = append(messages, value.EventData)
return c.JSON(200, nil)
return c.JSON(http.StatusCreated, map[string]string{"status": "ok"})
}

View file

@ -3,8 +3,10 @@ package router
import (
"github.com/labstack/echo/v4"
"gitnet.fr/deblan/owncast-webhook/backend/controller/chat"
"gitnet.fr/deblan/owncast-webhook/backend/controller/webhook/owncast"
)
func RegisterControllers(e *echo.Echo) {
chat.New(e)
owncast.New(e)
}

View file

@ -1,7 +1,5 @@
package store
import "gitnet.fr/deblan/owncast-webhook/backend/webhook"
type MessageOrigin int
var messageStore *MessageStore
@ -32,28 +30,9 @@ func GetMessageStore() *MessageStore {
}
type MessageInterface interface {
IsVisible() bool
GetOrigin() MessageOrigin
GetAuthor() string
GetContent() string
}
type OwncastMessage struct {
WebhookMessage webhook.Message
}
func (o *OwncastMessage) IsVisible() bool {
return o.WebhookMessage.Visible
}
func (o *OwncastMessage) GetOrigin() MessageOrigin {
return MessageOriginTwitch
}
func (o *OwncastMessage) GetAuthor() string {
return o.WebhookMessage.User.DisplayName
}
func (o *OwncastMessage) GetContent() string {
return o.WebhookMessage.GetBody()
ID() string
Visible() bool
Origin() MessageOrigin
Author() string
Content() string
}

View file

@ -0,0 +1,39 @@
package store
import (
"fmt"
"strings"
"gitnet.fr/deblan/owncast-webhook/backend/webhook"
"gitnet.fr/deblan/owncast-webhook/config"
)
type OwncastMessage struct {
WebhookMessage webhook.Message
}
func (o OwncastMessage) ID() string {
return o.WebhookMessage.Id
}
func (o OwncastMessage) Visible() bool {
return o.WebhookMessage.Visible
}
func (o OwncastMessage) Origin() MessageOrigin {
return MessageOriginOwncast
}
func (o OwncastMessage) Author() string {
return o.WebhookMessage.User.DisplayName
}
func (o OwncastMessage) Content() string {
content := strings.ReplaceAll(
o.WebhookMessage.Body,
`<img src="`,
fmt.Sprintf(`<img src="%s`, config.Get().Owncast.BaseUrl),
)
return content
}

View file

@ -1,21 +0,0 @@
package template
import "gitnet.fr/deblan/owncast-webhook/backend/view"
templ Fav(url string) {
<link rel="icon" type="image/x-icon" href={ url }>
}
templ Head(title string) {
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
@Fav(view.Asset("static/img/favicon.png"))
<title>{ title }</title>
@templ.Raw(view.EntrypointCss("main"))
</head>
}
templ JS() {
@templ.Raw(view.EntrypointJs("main"))
}

View file

@ -1,48 +0,0 @@
package chat
import (
"gitnet.fr/deblan/owncast-webhook/backend/view/template"
"gitnet.fr/deblan/owncast-webhook/backend/webhook"
"fmt"
)
func getMessageUserStyle(message webhook.Message) templ.Attributes {
return templ.Attributes{
"style": fmt.Sprintf("color: var(--theme-color-users-%d)", message.User.DisplayColor),
}
}
func getMessageStyle(message webhook.Message) templ.Attributes {
return templ.Attributes{
"style": fmt.Sprintf("border-color: var(--theme-color-users-%d)", message.User.DisplayColor),
}
}
templ Message(message webhook.Message) {
<div class="message" id={ message.Id } { getMessageStyle(message)... }>
<div class="message-user" { getMessageUserStyle(message)... }>
{ message.User.DisplayName } $
</div>
<div class="message-body">
@templ.Raw(message.GetBody())
</div>
</div>
}
templ Messages(title string, messages []webhook.Message) {
<!doctype html>
<html id="chat">
@template.Head(title)
<body>
<div class="messages">
for _, message := range messages {
if message.Visible {
@Message(message)
}
}
</div>
@template.JS()
</body>
</html>
}

View file

@ -3,11 +3,11 @@ package view
import (
"embed"
"encoding/json"
"fmt"
"strings"
"github.com/a-h/templ"
"github.com/labstack/echo/v4"
. "maragu.dev/gomponents"
. "maragu.dev/gomponents/html"
)
var (
@ -64,24 +64,30 @@ func entrypointFiles(app, category string) []string {
return files
}
func EntrypointJs(app string) string {
func EntrypointJs(app string) []Node {
files := entrypointFiles(app, "js")
results := []string{}
var results []Node
for _, file := range files {
results = append(results, fmt.Sprintf(`<script src="%s"></script>`, file))
results = append(
results,
Script(Src(file)),
)
}
return strings.Join(results, "")
return results
}
func EntrypointCss(app string) string {
func EntrypointCss(app string) []Node {
files := entrypointFiles(app, "css")
results := []string{}
var results []Node
for _, file := range files {
results = append(results, fmt.Sprintf(`<link rel="stylesheet" href="%s" />`, file))
results = append(
results,
Link(Rel("stylesheet"), Href(file)),
)
}
return strings.Join(results, "")
return results
}

View file

@ -1,7 +1,6 @@
package webhook
import (
"strings"
"time"
)
@ -26,12 +25,6 @@ type Message struct {
User MessageUser `json:"user"`
}
func (m *Message) GetBody() string {
m.Body = strings.ReplaceAll(m.Body, `<img src="`, `<img src="https://deblan.tv`)
return m.Body
}
// {
// "type": "CHAT",
// "eventData": {

View file

@ -5,9 +5,8 @@ export TEMPL_EXPERIMENT=rawgo
while true; do
rm -f cmd/server/rice-box.go
./node_modules/.bin/webpack
make tpl
screen -S budget -d -m go run ./cmd/server
notify-send "Budget" "Ready!"
screen -S owncastwh -d -m go run ./cmd/server
notify-send "Owncast Webhook" "Ready!"
inotifywait -r . -e close_write
screen -X -S budget quit
done

View file

@ -12,6 +12,15 @@ type Config struct {
Address string
Port int
}
Owncast struct {
BaseUrl string
}
Twitch struct {
ClientId string
ClientSecret string
WebhookUrl string
WebhookSecret string
}
}
var config *Config
@ -34,4 +43,11 @@ func (c *Config) Load(file string) {
config.Server.Address = cfg.Section("server").Key("address").String()
config.Server.Port, _ = cfg.Section("server").Key("port").Int()
config.Server.BaseUrl = cfg.Section("server").Key("base_url").String()
config.Owncast.BaseUrl = cfg.Section("owncast").Key("base_url").String()
config.Twitch.ClientId = cfg.Section("twitch").Key("client_id").String()
config.Twitch.ClientSecret = cfg.Section("twitch").Key("client_secret").String()
config.Twitch.WebhookSecret = cfg.Section("twitch").Key("webhook_secret").String()
config.Twitch.WebhookUrl = cfg.Section("twitch").Key("webhook_url").String()
}

3
go.mod
View file

@ -5,14 +5,13 @@ go 1.23.0
require (
github.com/GeertJohan/go.rice v1.0.3
github.com/a-h/templ v0.2.778
github.com/gabriel-vasile/mimetype v1.4.5
github.com/go-playground/validator v9.31.0+incompatible
github.com/labstack/echo/v4 v4.12.0
gopkg.in/ini.v1 v1.67.0
maragu.dev/gomponents v1.2.0
)
require (
github.com/LinneB/twitchwh v0.1.0 // indirect
github.com/daaku/go.zipexe v1.0.2 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect

6
go.sum
View file

@ -1,8 +1,6 @@
github.com/GeertJohan/go.incremental v1.0.0/go.mod h1:6fAjUhbVuX1KcMD3c8TEgVUqmo4seqhv0i0kdATSkM0=
github.com/GeertJohan/go.rice v1.0.3 h1:k5viR+xGtIhF61125vCE1cmJ5957RQGXG6dmbaWZSmI=
github.com/GeertJohan/go.rice v1.0.3/go.mod h1:XVdrU4pW00M4ikZed5q56tPf1v2KwnIKeIdc9CBYNt4=
github.com/LinneB/twitchwh v0.1.0 h1:c9zdl3tGksINmxn5DzbjpWmGvSVmBsux9kE/hQURE5I=
github.com/LinneB/twitchwh v0.1.0/go.mod h1:w+6OI4wgFtrZmZ9yZN28tZMiVq5b4iXDXk6T9XNshTI=
github.com/a-h/templ v0.2.778 h1:VzhOuvWECrwOec4790lcLlZpP4Iptt5Q4K9aFxQmtaM=
github.com/a-h/templ v0.2.778/go.mod h1:lq48JXoUvuQrU0VThrK31yFwdRjTCnIE5bcPCM9IP1w=
github.com/akavel/rsrc v0.8.0/go.mod h1:uLoCtb9J+EyAqh+26kdrTgmzRBFPGOolLWKpdxkKq+c=
@ -10,8 +8,6 @@ github.com/daaku/go.zipexe v1.0.2 h1:Zg55YLYTr7M9wjKn8SY/WcpuuEi+kR2u4E8RhvpyXmk
github.com/daaku/go.zipexe v1.0.2/go.mod h1:5xWogtqlYnfBXkSB1o9xysukNP9GTvaNkqzUZbt3Bw8=
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/gabriel-vasile/mimetype v1.4.5 h1:J7wGKdGu33ocBOhGy0z653k/lFKLFDPJMG8Gql0kxn4=
github.com/gabriel-vasile/mimetype v1.4.5/go.mod h1:ibHel+/kbxn9x2407k1izTA1S81ku1z/DlgOW2QE0M4=
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.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
@ -62,3 +58,5 @@ gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
maragu.dev/gomponents v1.2.0 h1:H7/N5htz1GCnhu0HB1GasluWeU2rJZOYztVEyN61iTc=
maragu.dev/gomponents v1.2.0/go.mod h1:oEDahza2gZoXDoDHhw8jBNgH+3UR5ni7Ur648HORydM=