This commit is contained in:
Benedek László 2024-06-13 14:43:09 +02:00
parent 922b60f76a
commit 133b7749d4
10 changed files with 152 additions and 53 deletions

View File

@ -9,7 +9,7 @@ import (
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
) )
func Listen(address string) { func Listen(address string, staticPath string) {
router := gin.Default() router := gin.Default()
router.GET("/info", read.Info) router.GET("/info", read.Info)
@ -17,10 +17,16 @@ func Listen(address string) {
router.POST("/set", write.Pixel) router.POST("/set", write.Pixel)
router.OPTIONS("/set", func(c *gin.Context) { router.OPTIONS("/set", func(c *gin.Context) {
c.Header("Access-Control-Allow-Origin", "*") c.Header("Access-Control-Allow-Origin", "*")
c.Header("Access-Control-Allow-Headers", "*") c.Header("Vary", "Origin")
c.Header("Access-Control-Allow-Headers", "content-type")
c.Header("Access-Control-Allow-Methods", "*") c.Header("Access-Control-Allow-Methods", "*")
c.Header("Access-Control-Allow-Credentials", "true")
}) })
if staticPath != "" {
router.Static("/static", staticPath)
}
server := &http.Server{ server := &http.Server{
Addr: address, Addr: address,
Handler: router, Handler: router,

View File

@ -1,6 +1,8 @@
package read package read
import ( import (
"errors"
"git.tek.govt.hu/dowerx/place/api/structs" "git.tek.govt.hu/dowerx/place/api/structs"
"git.tek.govt.hu/dowerx/place/config" "git.tek.govt.hu/dowerx/place/config"
"git.tek.govt.hu/dowerx/place/storage" "git.tek.govt.hu/dowerx/place/storage"
@ -35,5 +37,5 @@ func Tile(c *gin.Context) {
} }
func Continuous(c *gin.Context) { func Continuous(c *gin.Context) {
panic(errors.New("unimplomented"))
} }

View File

@ -4,13 +4,15 @@ import (
"errors" "errors"
"git.tek.govt.hu/dowerx/place/api/structs" "git.tek.govt.hu/dowerx/place/api/structs"
"git.tek.govt.hu/dowerx/place/auth"
"git.tek.govt.hu/dowerx/place/config"
"git.tek.govt.hu/dowerx/place/storage" "git.tek.govt.hu/dowerx/place/storage"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
) )
func Pixel(c *gin.Context) { var timeout int = config.GetConfig().Timeout
// TODO: apply timeout using cookies
func Pixel(c *gin.Context) {
var info struct { var info struct {
structs.Color structs.Color
structs.Coordinates structs.Coordinates
@ -20,6 +22,17 @@ func Pixel(c *gin.Context) {
return return
} }
cookie, err := c.Cookie("place")
if err != nil {
cookie = auth.SetCookie(timeout)
c.SetCookie("place", cookie, timeout, "/", "", false, false)
} else {
if auth.GetCookie(cookie) {
c.Status(403)
return
}
}
if err := storage.SetPixel(info.X, info.Y, &info); err != nil { if err := storage.SetPixel(info.X, info.Y, &info); err != nil {
c.AbortWithError(500, err) c.AbortWithError(500, err)
return return

51
auth/auth.go Normal file
View File

@ -0,0 +1,51 @@
package auth
import (
"context"
"math/rand"
"time"
"github.com/redis/go-redis/v9"
)
var ctx context.Context
var rdb *redis.Client
func generateToken(length int) string {
validRunes := []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ123456789")
token := make([]rune, length)
for i := range token {
token[i] = validRunes[rand.Intn(len(validRunes))]
}
return string(token)
}
func Connect(options *redis.Options) {
ctx = context.Background()
rdb = redis.NewClient(options)
if err := rdb.Ping(ctx).Err(); err != nil {
panic(err)
}
}
func Close() {
rdb.Close()
}
func SetCookie(timeout int) string {
cookie := generateToken(32)
err := rdb.Set(ctx, cookie, cookie, time.Duration(timeout)*time.Second).Err()
if err != nil {
panic(err)
}
return cookie
}
func GetCookie(cookie string) bool {
resp := rdb.Exists(ctx, cookie)
if resp.Err() != nil {
panic(resp.Err())
}
return resp.Val() == 1
}

View File

@ -9,6 +9,7 @@ import (
type Config struct { type Config struct {
Address string Address string
StoragePath string StoragePath string
StaticPath string
TileSize int TileSize int
CanvasSize int CanvasSize int
SaveFrequency int SaveFrequency int
@ -23,6 +24,7 @@ func GetConfig() Config {
conf = &Config{} conf = &Config{}
flag.StringVar(&conf.Address, "address", ":8080", "API base") flag.StringVar(&conf.Address, "address", ":8080", "API base")
flag.StringVar(&conf.StoragePath, "storage", "/data", "image storage path") flag.StringVar(&conf.StoragePath, "storage", "/data", "image storage path")
flag.StringVar(&conf.StaticPath, "static", "/static", "web static path")
flag.IntVar(&conf.TileSize, "tile_size", 128, "width of a tile") flag.IntVar(&conf.TileSize, "tile_size", 128, "width of a tile")
flag.IntVar(&conf.CanvasSize, "canvas_size", 4, "width of the canvas (in tiles)") flag.IntVar(&conf.CanvasSize, "canvas_size", 4, "width of the canvas (in tiles)")
flag.IntVar(&conf.SaveFrequency, "save", 60, "seconds between saves") flag.IntVar(&conf.SaveFrequency, "save", 60, "seconds between saves")

View File

@ -0,0 +1,6 @@
services:
redis:
image: redis:alpine
network_mode: host
environment:
- REDIS_ARGS=--requirepass redis

10
main.go
View File

@ -2,13 +2,21 @@ package main
import ( import (
"git.tek.govt.hu/dowerx/place/api" "git.tek.govt.hu/dowerx/place/api"
"git.tek.govt.hu/dowerx/place/auth"
"git.tek.govt.hu/dowerx/place/config" "git.tek.govt.hu/dowerx/place/config"
"git.tek.govt.hu/dowerx/place/storage" "git.tek.govt.hu/dowerx/place/storage"
) )
func main() { func main() {
conf := config.GetConfig() conf := config.GetConfig()
storage.Load(conf.StoragePath, conf.CanvasSize, conf.TileSize) storage.Load(conf.StoragePath, conf.CanvasSize, conf.TileSize)
defer storage.Save()
storage.StartSaves(conf.SaveFrequency) storage.StartSaves(conf.SaveFrequency)
api.Listen(conf.Address)
auth.Connect(&conf.Redis)
defer auth.Close()
api.Listen(conf.Address, conf.StaticPath)
} }

View File

@ -6,9 +6,8 @@ import (
"fmt" "fmt"
"image" "image"
"image/color" "image/color"
"image/draw"
"log" "log"
"os" "path/filepath"
"time" "time"
) )
@ -26,10 +25,6 @@ func (err *WrongSizeError) Error() string {
} }
func Load(path string, canvasSize int, tileSize int) { func Load(path string, canvasSize int, tileSize int) {
if err := os.Chdir(path); err != nil {
panic(err)
}
tiles = make([][]tile, canvasSize) tiles = make([][]tile, canvasSize)
for i := range tiles { for i := range tiles {
tiles[i] = make([]tile, canvasSize) tiles[i] = make([]tile, canvasSize)
@ -37,31 +32,16 @@ func Load(path string, canvasSize int, tileSize int) {
for y := range tiles { for y := range tiles {
for x := range tiles[y] { for x := range tiles[y] {
tiles[y][x].image = image.NewRGBA(image.Rect(0, 0, tileSize, tileSize)) err := tiles[y][x].load(filepath.Join(path, fmt.Sprintf("%d-%d.png", x, y)), tileSize)
filename := fmt.Sprintf("%d-%d.png", x, y)
file, err := os.Open(filename)
if err != nil { if err != nil {
tiles[y][x].fill() tiles[y][x].fill()
if err = tiles[y][x].save(filename); err != nil { if err = tiles[y][x].save(); err != nil {
panic(err) panic(err)
} }
log.Printf("Created tile (%d-%d)\n", x, y) log.Printf("Created tile (%d-%d)\n", x, y)
} else { } else {
img, _, err := image.Decode(file) log.Printf("Loaded tile (%d-%d)\n", x, y)
if err != nil {
panic(err)
}
draw.Draw(tiles[y][x].image, img.Bounds(), img, img.Bounds().Min, draw.Src)
} }
defer file.Close()
if tiles[y][x].image.Bounds().Size().X != tileSize && tiles[y][x].image.Bounds().Size().Y != tileSize {
panic(WrongSizeError{ExpectedSize: tileSize, ActualSize: tiles[y][x].image.Bounds().Size(), X: x, Y: y})
}
tiles[y][x].dirty = false
} }
} }
} }
@ -70,9 +50,7 @@ func Save() {
for y := range tiles { for y := range tiles {
for x := range tiles[y] { for x := range tiles[y] {
if tiles[y][x].dirty { if tiles[y][x].dirty {
filename := fmt.Sprintf("%d-%d.png", x, y) if err := tiles[y][x].save(); err != nil {
if err := tiles[y][x].save(filename); err != nil {
panic(err) panic(err)
} }
} }

View File

@ -10,11 +10,32 @@ import (
type tile struct { type tile struct {
dirty bool dirty bool
path string
image *image.RGBA image *image.RGBA
} }
func (t *tile) save(path string) error { func (t *tile) load(path string, tileSize int) error {
file, err := os.OpenFile(path, os.O_WRONLY, 0644) t.image = image.NewRGBA(image.Rect(0, 0, tileSize, tileSize))
t.path = path
file, err := os.Open(path)
if err != nil {
return err
}
defer file.Close()
img, _, err := image.Decode(file)
if err != nil {
return err
}
draw.Draw(t.image, img.Bounds(), img, img.Bounds().Min, draw.Src)
t.dirty = false
return nil
}
func (t *tile) save() error {
file, err := os.OpenFile(t.path, os.O_WRONLY|os.O_CREATE, 0644)
if err != nil { if err != nil {
return err return err
} }

View File

@ -17,25 +17,37 @@ async function setPixel(canvas, ctx, x, y, c) {
x = x - bounds.left; x = x - bounds.left;
y = y - bounds.top; y = y - bounds.top;
try { let response = await fetch(`${apiBase}/set`, {
await fetch(`${apiBase}/set`, { method: "POST",
method: "POST", credentials: 'include',
headers: { headers: {
'Content-Type': 'application/json' 'Content-Type': 'application/json'
}, },
body: JSON.stringify({ body: JSON.stringify({
x: x, x: x,
y: y, y: y,
r: c.r, r: c.r,
g: c.g, g: c.g,
b: c.b b: c.b
}) })
}); });
ctx.fillStyle = `rgb(${c.r}, ${c.g}, ${c.b})`; console.log(response);
ctx.fillRect(x, y, 1, 1);
} catch { switch (response.status) {
alert(`failed to set pixel at ${x}-${y} to ${c}`); case 200:
ctx.fillStyle = `rgb(${c.r}, ${c.g}, ${c.b})`;
ctx.fillRect(x, y, 1, 1);
break;
case 400:
alert("missing parameters in request");
break;
case 403:
alert("not allowed to set another pixel yet");
break;
case 500:
alert(`failed to set pixel at ${x}-${y} to ${c}`);
break;
} }
} }