diff --git a/.env.example b/.env.example index e9f330d..88325ce 100644 --- a/.env.example +++ b/.env.example @@ -1,3 +1,5 @@ +POSTGRES_URL="postgres://user:pass@localhost:5432/test" + REDIS_ADDRESS="localhost:16379" IMGPROXY_URL="http://localhost:18080" diff --git a/go.mod b/go.mod index 3987194..73c30cf 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module otomadb.com/images go 1.21.1 require ( + github.com/jackc/pgx/v5 v5.4.3 github.com/labstack/echo/v4 v4.11.1 github.com/redis/go-redis/v9 v9.2.0 ) @@ -11,6 +12,8 @@ require ( github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect github.com/golang-jwt/jwt v3.2.2+incompatible // indirect + github.com/jackc/pgpassfile v1.0.0 // indirect + github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect github.com/labstack/gommon v0.4.0 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.19 // indirect diff --git a/go.sum b/go.sum index 50bcd58..b93f3a5 100644 --- a/go.sum +++ b/go.sum @@ -11,6 +11,12 @@ github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/r github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY= github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= +github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= +github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= +github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk= +github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= +github.com/jackc/pgx/v5 v5.4.3 h1:cxFyXhxlvAifxnkKKdlxv8XqUf59tDlYjnV5YYfsJJY= +github.com/jackc/pgx/v5 v5.4.3/go.mod h1:Ig06C2Vu0t5qXC60W8sqIthScaEnFvojjj9dSljmHRA= github.com/labstack/echo/v4 v4.11.1 h1:dEpLU2FLg4UVmvCGPuk/APjlH6GDpbEPti61srUUUs4= github.com/labstack/echo/v4 v4.11.1/go.mod h1:YuYRTSM3CHs2ybfrL8Px48bO6BAnYIN4l8wSTMP6BDQ= github.com/labstack/gommon v0.4.0 h1:y7cvthEAEbU0yHOf4axH8ZG2NH8knB9iNSoTO8dyIk8= @@ -27,6 +33,7 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN github.com/redis/go-redis/v9 v9.2.0 h1:zwMdX0A4eVzse46YN18QhuDiM4uf3JmkOB4VZrdt5uI= github.com/redis/go-redis/v9 v9.2.0/go.mod h1:hdY0cQFCN4fnSYT6TkisLufl/4W5UIXyv0b/CLO2V2M= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= diff --git a/mad_primary_thumbnail.go b/mad_primary_thumbnail.go new file mode 100644 index 0000000..b5baa0a --- /dev/null +++ b/mad_primary_thumbnail.go @@ -0,0 +1,80 @@ +package main + +import ( + "encoding/base64" + "fmt" + "net/http" + "os" + "strconv" + "time" + + "github.com/jackc/pgx/v5" + "github.com/labstack/echo/v4" + "github.com/redis/go-redis/v9" +) + +func MADPrimaryThumbnail(c echo.Context) error { + serial, err := strconv.Atoi(c.Param("serial")) + if err != nil { + c.Logger().Error(err) + return c.String(http.StatusBadRequest, "Bad request") + } + + scale := c.FormValue("scale") + width, height, err := ParseScale(scale) + if err != nil { + return c.String(http.StatusBadRequest, err.Error()) + } + + rdb := redis.NewClient(&redis.Options{Addr: os.Getenv("REDIS_ADDRESS")}) + defer rdb.Close() + + // Redisにキャッシュがあればリダイレクト + redisKey := fmt.Sprintf("mad_primary_thumbnail_%d_%d_%d", serial, width, height) + cachedUrl, err := rdb.Get(ctx, redisKey).Result() + if err != nil && err != redis.Nil { + c.Logger().Error(err) + return c.String(http.StatusInternalServerError, "Internal Server Error") + } + if cachedUrl != "" { + return c.Redirect(http.StatusTemporaryRedirect, cachedUrl) + } + + conn, err := pgx.Connect(ctx, os.Getenv("POSTGRES_URL")) + if err != nil { + c.Logger().Error(err) + c.String(http.StatusInternalServerError, "Internal Server Error") + } + defer conn.Close(ctx) + + var url string + q := `SELECT t."imageUrl" FROM "VideoThumbnail" t JOIN "Video" v ON t."videoId" = v."id" WHERE v."serial" = $1 AND t."isPrimary" = TRUE` + err = conn.QueryRow(ctx, q, serial).Scan(&url) + if err != nil { + c.Logger().Error(err) + return c.String(http.StatusInternalServerError, "Internal Server Error") + } + + // 画像の存在チェック + c.Logger().Debug(url) + resp, err := http.Head(url) + if err != nil { + return c.String(http.StatusInternalServerError, "Internal Server Error") + } + if resp.StatusCode != http.StatusOK { + c.Logger().Warn(url, resp.StatusCode) + return c.String(http.StatusNotFound, "Not Found") + } + + proxiedUrl := SignURL( + fmt.Sprintf("/rs:fit:%d:%d:1:1/background:000000/%s", width, height, base64.URLEncoding.EncodeToString([]byte(url)))) + c.Logger().Debug(proxiedUrl) + + // Redisにキャッシュ + err = rdb.Set(ctx, redisKey, proxiedUrl, time.Duration(600*time.Second)).Err() + if err != nil { + c.Logger().Error(err) + } + + return c.Redirect(http.StatusTemporaryRedirect, proxiedUrl) +} diff --git a/main.go b/main.go index 6d2f84c..36bc5b2 100644 --- a/main.go +++ b/main.go @@ -14,6 +14,8 @@ func main() { e.Debug = true e.Use(middleware.Logger()) + e.GET("/mads/:serial/primary", MADPrimaryThumbnail) + e.GET("/original/youtube/:vid", OriginalYoutubeThumbnail) e.GET("/original/nicovideo/:vid", OriginalNicovideoThumbnail) e.GET("/original/bilibili/:vid", OriginalBilibiliThumbnail) diff --git a/misc.go b/misc.go index f9cc80a..8ac5e01 100644 --- a/misc.go +++ b/misc.go @@ -20,3 +20,14 @@ func ParseSize(size string) (width, height int, err error) { return width, height, nil } + +func ParseScale(scale string) (width, height int, err error) { + switch scale { + case "ogp": + return 700, 400, nil + case "large": + return 960, 720, nil + default: + return 0, 0, fmt.Errorf("invalid scale") + } +}