Compare commits
7 commits
04284acc13
...
81aa06d716
Author | SHA1 | Date | |
---|---|---|---|
![]() |
81aa06d716 | ||
![]() |
997774c5de | ||
![]() |
264739fd76 | ||
![]() |
bc42a2bb75 | ||
![]() |
1c5eca8f72 | ||
![]() |
698f3b1a02 | ||
![]() |
b48a610e90 |
7 changed files with 90 additions and 42 deletions
1
api.md
1
api.md
|
@ -9,4 +9,5 @@
|
|||
| POST | /api/upload | Upload file | Upload key |
|
||||
| DELETE | /api/:id | Delete file | Upload key |
|
||||
| GET | /api/:id | Get file | Upload key |
|
||||
| GET | /api/:id/meta | Get file metadata | None |
|
||||
| GET | /api/list | Get a list of uploaded files | Session key |
|
||||
|
|
19
go.mod
19
go.mod
|
@ -3,8 +3,17 @@ module stereo.cat/backend
|
|||
go 1.24.2
|
||||
|
||||
require (
|
||||
github.com/cristalhq/base64 v0.1.2
|
||||
github.com/galdor/go-thumbhash v1.0.0
|
||||
github.com/gin-gonic/gin v1.10.0
|
||||
github.com/gofrs/uuid v4.4.0+incompatible
|
||||
github.com/golang-jwt/jwt/v5 v5.2.2
|
||||
github.com/h2non/filetype v1.1.3
|
||||
github.com/joho/godotenv v1.5.1
|
||||
github.com/minio/minio-go/v7 v7.0.93
|
||||
golang.org/x/crypto v0.37.0
|
||||
gorm.io/driver/postgres v1.5.11
|
||||
gorm.io/driver/sqlite v1.5.7
|
||||
gorm.io/gorm v1.26.0
|
||||
)
|
||||
|
||||
|
@ -12,7 +21,6 @@ require (
|
|||
github.com/bytedance/sonic v1.13.2 // indirect
|
||||
github.com/bytedance/sonic/loader v0.2.4 // indirect
|
||||
github.com/cloudwego/base64x v0.1.5 // indirect
|
||||
github.com/cristalhq/base64 v0.1.2 // indirect
|
||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.9 // indirect
|
||||
github.com/gin-contrib/sse v1.1.0 // indirect
|
||||
|
@ -21,10 +29,7 @@ require (
|
|||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||
github.com/go-playground/validator/v10 v10.26.0 // indirect
|
||||
github.com/goccy/go-json v0.10.5 // indirect
|
||||
github.com/gofrs/uuid v4.4.0+incompatible // indirect
|
||||
github.com/golang-jwt/jwt/v5 v5.2.2 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/h2non/filetype v1.1.3 // indirect
|
||||
github.com/jackc/pgpassfile v1.0.0 // indirect
|
||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
|
||||
github.com/jackc/pgx/v5 v5.7.4 // indirect
|
||||
|
@ -36,12 +41,10 @@ require (
|
|||
github.com/klauspost/cpuid/v2 v2.2.10 // indirect
|
||||
github.com/kr/pretty v0.3.1 // indirect
|
||||
github.com/leodido/go-urn v1.4.0 // indirect
|
||||
github.com/lithammer/shortuuid/v4 v4.2.0 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/mattn/go-sqlite3 v1.14.28 // indirect
|
||||
github.com/minio/crc64nvme v1.0.1 // indirect
|
||||
github.com/minio/md5-simd v1.1.2 // indirect
|
||||
github.com/minio/minio-go/v7 v7.0.93 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
|
||||
|
@ -52,14 +55,10 @@ require (
|
|||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||
github.com/ugorji/go/codec v1.2.12 // indirect
|
||||
golang.org/x/arch v0.16.0 // indirect
|
||||
golang.org/x/crypto v0.37.0 // indirect
|
||||
golang.org/x/net v0.39.0 // indirect
|
||||
golang.org/x/sync v0.14.0 // indirect
|
||||
golang.org/x/sys v0.32.0 // indirect
|
||||
golang.org/x/text v0.24.0 // indirect
|
||||
google.golang.org/protobuf v1.36.6 // indirect
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
gorm.io/driver/postgres v1.5.11 // indirect
|
||||
gorm.io/driver/sqlite v1.5.7 // indirect
|
||||
)
|
||||
|
|
7
go.sum
7
go.sum
|
@ -16,6 +16,8 @@ github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkp
|
|||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||
github.com/gabriel-vasile/mimetype v1.4.9 h1:5k+WDwEsD9eTLL8Tz3L0VnmVh9QxGjRmjBvAG7U/oYY=
|
||||
github.com/gabriel-vasile/mimetype v1.4.9/go.mod h1:WnSQhFKJuBlRyLiKohA/2DtIlPFAbguNaG7QCHcyGok=
|
||||
github.com/galdor/go-thumbhash v1.0.0 h1:Q7xSnaDvSC91SuNmQI94JuUVHva29FDdA4/PkV0EHjU=
|
||||
github.com/galdor/go-thumbhash v1.0.0/go.mod h1:gEK2wZqIxS2W4mXNf48lPl6HWjX0vWsH1LpK/cU74Ho=
|
||||
github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w=
|
||||
github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM=
|
||||
github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU=
|
||||
|
@ -66,17 +68,12 @@ github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa02
|
|||
github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE=
|
||||
github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
|
||||
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
|
||||
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
|
||||
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
|
||||
github.com/lithammer/shortuuid/v4 v4.2.0 h1:LMFOzVB3996a7b8aBuEXxqOBflbfPQAiVzkIcHO0h8c=
|
||||
github.com/lithammer/shortuuid/v4 v4.2.0/go.mod h1:D5noHZ2oFw/YaKCfGy0YxyE7M0wMbezmMjPdhyEFe6Y=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-sqlite3 v1.14.28 h1:ThEiQrnbtumT+QMknw63Befp/ce/nUPgBPMlRFEum7A=
|
||||
|
|
|
@ -19,11 +19,18 @@ package routes
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"io"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"image"
|
||||
_ "image/gif"
|
||||
_ "image/jpeg"
|
||||
_ "image/png"
|
||||
|
||||
"github.com/galdor/go-thumbhash"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/gofrs/uuid"
|
||||
"github.com/golang-jwt/jwt/v5"
|
||||
|
@ -78,18 +85,35 @@ func RegisterFileRoutes(cfg *types.StereoConfig, api *gin.RouterGroup) {
|
|||
}
|
||||
|
||||
fileType, err := filetype.Match(buf)
|
||||
|
||||
if err != nil {
|
||||
types.ErrorReaderOpen.Throw(c, err)
|
||||
types.ErrorInvalidFile.Throw(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
mime := fileType.MIME.Value
|
||||
|
||||
fileMeta := types.File{
|
||||
Owner: uid,
|
||||
Name: file.Filename,
|
||||
CreatedAt: time.Now(),
|
||||
Size: int64(len(buf)),
|
||||
Mime: fileType.MIME.Value,
|
||||
Mime: mime,
|
||||
}
|
||||
|
||||
if filetype.IsImage(buf) {
|
||||
img, _, err := image.Decode(bytes.NewReader(buf))
|
||||
if err != nil {
|
||||
types.ErrorInvalidFile.Throw(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
hashBytes := thumbhash.EncodeImage(img)
|
||||
// encode the byte[] into a string
|
||||
hash := base64.StdEncoding.EncodeToString(hashBytes)
|
||||
|
||||
fileMeta.Hash = hash
|
||||
fileMeta.Width = img.Bounds().Dx()
|
||||
fileMeta.Height = img.Bounds().Dy()
|
||||
}
|
||||
|
||||
if err := cfg.Database.Create(&fileMeta).Error; err != nil {
|
||||
|
@ -176,13 +200,38 @@ func RegisterFileRoutes(cfg *types.StereoConfig, api *gin.RouterGroup) {
|
|||
c.DataFromReader(200, file.Size, file.Mime, object, nil)
|
||||
})
|
||||
|
||||
api.GET("/:id/meta", func(c *gin.Context) {
|
||||
fileID := c.Param("id")
|
||||
fileID = strings.TrimSpace(fileID)
|
||||
|
||||
var file *types.File
|
||||
id, err := uuid.FromString(fileID)
|
||||
if err != nil {
|
||||
types.ErrorInvalidFile.Throw(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
if err := cfg.Database.First(&file, id).Error; err != nil {
|
||||
types.ErrorFileNotFound.Throw(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
if file == nil {
|
||||
types.ErrorFileNotFound.Throw(c, nil)
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(200, file)
|
||||
})
|
||||
|
||||
api.GET("/list", session.SessionMiddleware(cfg.JWTSecret), func(c *gin.Context) {
|
||||
claims := c.MustGet("claims").(jwt.MapClaims)
|
||||
user := claims["user"].(auth.User)
|
||||
|
||||
var files []types.File
|
||||
|
||||
if c.Query("page") == "" || c.Query("size") == "" {
|
||||
var files []types.File
|
||||
if err := cfg.Database.Where("owner = ?", user.ID).Find(&files).Error; err != nil {
|
||||
if err := cfg.Database.Where("owner = ?", user.ID).Order("created_at DESC").Find(&files).Error; err != nil {
|
||||
types.ErrorDatabase.Throw(c, err)
|
||||
return
|
||||
}
|
||||
|
@ -206,12 +255,12 @@ func RegisterFileRoutes(cfg *types.StereoConfig, api *gin.RouterGroup) {
|
|||
return
|
||||
}
|
||||
|
||||
var files []types.File
|
||||
offset := (pageNum - 1) * sizeNum
|
||||
if offset < 0 {
|
||||
offset = 0
|
||||
}
|
||||
if err := cfg.Database.Where("owner = ?", user.ID).Offset(offset).Limit(sizeNum).Find(&files).Error; err != nil {
|
||||
|
||||
if err := cfg.Database.Where("owner = ?", user.ID).Order("created_at DESC").Offset(offset).Limit(sizeNum).Find(&files).Error; err != nil {
|
||||
types.ErrorDatabase.Throw(c, err)
|
||||
return
|
||||
}
|
||||
|
|
|
@ -32,12 +32,12 @@ type TokenResponse struct {
|
|||
}
|
||||
|
||||
type User struct {
|
||||
ID string `json:"id" gorm:"primaryKey"`
|
||||
Username string `json:"username"`
|
||||
Blacklisted bool `json:"blacklisted"`
|
||||
Email string `json:"email"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
HashedApiKey string `json:"hashed_api_key"`
|
||||
ID string `json:"id" gorm:"primaryKey"`
|
||||
Username string `json:"username"`
|
||||
Blacklisted bool `json:"blacklisted"`
|
||||
Email string `json:"email"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
HashedApiKey string `json:"hashed_api_key"`
|
||||
}
|
||||
|
||||
type AvatarDecorationData struct {
|
||||
|
|
|
@ -1,23 +1,22 @@
|
|||
/*
|
||||
Copyright (C) 2025 hexlocation (hex@iwakura.rip) & grngxd (grng@iwakura.rip)
|
||||
Copyright (C) 2025 hexlocation (hex@iwakura.rip) & grngxd (grng@iwakura.rip)
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package types
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
|
@ -37,7 +36,7 @@ func (e *StereoError) Throw(c *gin.Context, err error) {
|
|||
if err != nil {
|
||||
c.Error(err)
|
||||
} else {
|
||||
c.Error(errors.New(fmt.Sprintf("Got an error with code: %v", e.Code)))
|
||||
c.Error(fmt.Errorf("got an error with code: %v", e.Code))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -53,7 +53,10 @@ type File struct {
|
|||
Owner string `gorm:"not null;index"`
|
||||
Size int64 `gorm:"not null;type:bigint"`
|
||||
CreatedAt time.Time `gorm:"autoCreateTime"`
|
||||
Mime string
|
||||
Mime string `gorm:"type:text"`
|
||||
Hash string `gorm:"type:text"`
|
||||
Width int
|
||||
Height int
|
||||
}
|
||||
|
||||
func (f *File) BeforeCreate(tx *gorm.DB) (err error) {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue