mirror of
https://github.com/Damillora/Shioriko.git
synced 2024-11-22 04:17:33 +00:00
feat: similarity search
This commit is contained in:
parent
03f7de692a
commit
064f61cc89
4
go.mod
4
go.mod
@ -3,14 +3,16 @@ module github.com/Damillora/Shioriko
|
|||||||
go 1.16
|
go 1.16
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
github.com/corona10/goimagehash v1.0.3
|
||||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible
|
github.com/dgrijalva/jwt-go v3.2.0+incompatible
|
||||||
|
github.com/disintegration/imaging v1.6.2
|
||||||
github.com/gin-contrib/cors v1.3.1
|
github.com/gin-contrib/cors v1.3.1
|
||||||
github.com/gin-contrib/static v0.0.1
|
github.com/gin-contrib/static v0.0.1
|
||||||
github.com/gin-gonic/gin v1.7.1
|
github.com/gin-gonic/gin v1.7.1
|
||||||
github.com/go-playground/validator/v10 v10.6.0
|
github.com/go-playground/validator/v10 v10.6.0
|
||||||
github.com/google/uuid v1.2.0
|
github.com/google/uuid v1.2.0
|
||||||
github.com/h2non/bimg v1.1.5
|
|
||||||
golang.org/x/crypto v0.0.0-20210506145944-38f3c27a63bf
|
golang.org/x/crypto v0.0.0-20210506145944-38f3c27a63bf
|
||||||
|
golang.org/x/image v0.0.0-20210504121937-7319ad40d33e
|
||||||
gorm.io/driver/postgres v1.1.0
|
gorm.io/driver/postgres v1.1.0
|
||||||
gorm.io/gorm v1.21.9
|
gorm.io/gorm v1.21.9
|
||||||
)
|
)
|
||||||
|
9
go.sum
9
go.sum
@ -40,6 +40,8 @@ github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7
|
|||||||
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||||
github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||||
github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
|
github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
|
||||||
|
github.com/corona10/goimagehash v1.0.3 h1:NZM518aKLmoNluluhfHGxT3LGOnrojrxhGn63DR/CZA=
|
||||||
|
github.com/corona10/goimagehash v1.0.3/go.mod h1:VkvE0mLn84L4aF8vCb6mafVajEb6QYMHl2ZJLn0mOGI=
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
||||||
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
|
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
@ -47,6 +49,8 @@ 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/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
|
github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
|
||||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
|
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
|
||||||
|
github.com/disintegration/imaging v1.6.2 h1:w1LecBlG2Lnp8B3jk5zSuNqd7b4DXhcjwek1ei82L+c=
|
||||||
|
github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4=
|
||||||
github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
||||||
github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs=
|
github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs=
|
||||||
github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU=
|
github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU=
|
||||||
@ -273,6 +277,8 @@ github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzE
|
|||||||
github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w=
|
github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w=
|
||||||
github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w=
|
github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w=
|
||||||
github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
|
github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
|
||||||
|
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ=
|
||||||
|
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8=
|
||||||
github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs=
|
github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs=
|
||||||
github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA=
|
github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA=
|
||||||
github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo=
|
github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo=
|
||||||
@ -392,6 +398,9 @@ golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm
|
|||||||
golang.org/x/crypto v0.0.0-20210506145944-38f3c27a63bf h1:B2n+Zi5QeYRDAEodEu72OS36gmTWjgpXr2+cWcBW90o=
|
golang.org/x/crypto v0.0.0-20210506145944-38f3c27a63bf h1:B2n+Zi5QeYRDAEodEu72OS36gmTWjgpXr2+cWcBW90o=
|
||||||
golang.org/x/crypto v0.0.0-20210506145944-38f3c27a63bf/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
|
golang.org/x/crypto v0.0.0-20210506145944-38f3c27a63bf/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
|
||||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
|
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||||
|
golang.org/x/image v0.0.0-20210504121937-7319ad40d33e h1:PzJMNfFQx+QO9hrC1GwZ4BoPGeNGhfeQEgcQFArEjPk=
|
||||||
|
golang.org/x/image v0.0.0-20210504121937-7319ad40d33e/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||||
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||||
|
@ -1,17 +1,27 @@
|
|||||||
package app
|
package app
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"image"
|
||||||
|
_ "image/gif"
|
||||||
|
"image/jpeg"
|
||||||
|
_ "image/jpeg"
|
||||||
|
_ "image/png"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
|
_ "golang.org/x/image/webp"
|
||||||
|
|
||||||
"github.com/Damillora/Shioriko/pkg/config"
|
"github.com/Damillora/Shioriko/pkg/config"
|
||||||
"github.com/Damillora/Shioriko/pkg/database"
|
"github.com/Damillora/Shioriko/pkg/database"
|
||||||
"github.com/Damillora/Shioriko/pkg/middleware"
|
"github.com/Damillora/Shioriko/pkg/middleware"
|
||||||
"github.com/Damillora/Shioriko/pkg/models"
|
"github.com/Damillora/Shioriko/pkg/models"
|
||||||
|
"github.com/Damillora/Shioriko/pkg/services"
|
||||||
|
"github.com/corona10/goimagehash"
|
||||||
|
"github.com/disintegration/imaging"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"github.com/h2non/bimg"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func InitializeBlobRoutes(g *gin.Engine) {
|
func InitializeBlobRoutes(g *gin.Engine) {
|
||||||
@ -67,6 +77,87 @@ func uploadBlob(c *gin.Context) {
|
|||||||
os.Mkdir(filepath.Join(dataDir, "thumbnail", folder1, folder2), 0755)
|
os.Mkdir(filepath.Join(dataDir, "thumbnail", folder1, folder2), 0755)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
previewFilename := id + ".jpg"
|
||||||
|
previewFilePath := filepath.Join(dataDir, "preview", folder1, folder2, previewFilename)
|
||||||
|
thumbnailFilePath := filepath.Join(dataDir, "thumbnail", folder1, folder2, previewFilename)
|
||||||
|
|
||||||
|
fileObj, _ := file.Open()
|
||||||
|
originalImage, _, err := image.Decode(fileObj)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(http.StatusBadRequest, models.ErrorResponse{
|
||||||
|
Code: http.StatusBadRequest,
|
||||||
|
Message: err.Error(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
width := originalImage.Bounds().Dx()
|
||||||
|
height := originalImage.Bounds().Dy()
|
||||||
|
|
||||||
|
hash, err := goimagehash.PerceptionHash(originalImage)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(http.StatusBadRequest, models.ErrorResponse{
|
||||||
|
Code: http.StatusBadRequest,
|
||||||
|
Message: err.Error(),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
hashInt := hash.GetHash()
|
||||||
|
|
||||||
|
similarPosts, err := services.SimilaritySearch(hashInt)
|
||||||
|
|
||||||
|
hashSlice := make([]byte, 8)
|
||||||
|
binary.LittleEndian.PutUint64(hashSlice, hashInt)
|
||||||
|
|
||||||
|
previewImage := imaging.Resize(originalImage, 1000, 0, imaging.Lanczos)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(http.StatusBadRequest, models.ErrorResponse{
|
||||||
|
Code: http.StatusBadRequest,
|
||||||
|
Message: err.Error(),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
thumbnailImage := imaging.Resize(originalImage, 300, 0, imaging.Lanczos)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(http.StatusBadRequest, models.ErrorResponse{
|
||||||
|
Code: http.StatusBadRequest,
|
||||||
|
Message: err.Error(),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
previewFile, err := os.Create(previewFilePath)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(http.StatusBadRequest, models.ErrorResponse{
|
||||||
|
Code: http.StatusBadRequest,
|
||||||
|
Message: err.Error(),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
thumbnailFile, err := os.Create(thumbnailFilePath)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(http.StatusBadRequest, models.ErrorResponse{
|
||||||
|
Code: http.StatusBadRequest,
|
||||||
|
Message: err.Error(),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = jpeg.Encode(previewFile, previewImage, nil)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(http.StatusBadRequest, models.ErrorResponse{
|
||||||
|
Code: http.StatusBadRequest,
|
||||||
|
Message: err.Error(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
err = jpeg.Encode(thumbnailFile, thumbnailImage, nil)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(http.StatusBadRequest, models.ErrorResponse{
|
||||||
|
Code: http.StatusBadRequest,
|
||||||
|
Message: err.Error(),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
filename := id + filepath.Ext(file.Filename)
|
filename := id + filepath.Ext(file.Filename)
|
||||||
filePath := filepath.Join(dataDir, folder1, folder2, filename)
|
filePath := filepath.Join(dataDir, folder1, folder2, filename)
|
||||||
err = c.SaveUploadedFile(file, filePath)
|
err = c.SaveUploadedFile(file, filePath)
|
||||||
@ -75,60 +166,7 @@ func uploadBlob(c *gin.Context) {
|
|||||||
Code: http.StatusBadRequest,
|
Code: http.StatusBadRequest,
|
||||||
Message: err.Error(),
|
Message: err.Error(),
|
||||||
})
|
})
|
||||||
}
|
return
|
||||||
|
|
||||||
previewFilename := id + ".webp"
|
|
||||||
previewFilePath := filepath.Join(dataDir, "preview", folder1, folder2, previewFilename)
|
|
||||||
thumbnailFilePath := filepath.Join(dataDir, "thumbnail", folder1, folder2, previewFilename)
|
|
||||||
|
|
||||||
buffer, err := bimg.Read(filePath)
|
|
||||||
if err != nil {
|
|
||||||
c.JSON(http.StatusBadRequest, models.ErrorResponse{
|
|
||||||
Code: http.StatusBadRequest,
|
|
||||||
Message: err.Error(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
image := bimg.NewImage(buffer)
|
|
||||||
metadata, err := image.Metadata()
|
|
||||||
if err != nil {
|
|
||||||
c.JSON(http.StatusBadRequest, models.ErrorResponse{
|
|
||||||
Code: http.StatusBadRequest,
|
|
||||||
Message: err.Error(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
width := metadata.Size.Width
|
|
||||||
height := metadata.Size.Height
|
|
||||||
|
|
||||||
previewImage, err := image.Resize(1000, 0)
|
|
||||||
if err != nil {
|
|
||||||
c.JSON(http.StatusBadRequest, models.ErrorResponse{
|
|
||||||
Code: http.StatusBadRequest,
|
|
||||||
Message: err.Error(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
thumbnailImage, err := image.Resize(300, 0)
|
|
||||||
if err != nil {
|
|
||||||
c.JSON(http.StatusBadRequest, models.ErrorResponse{
|
|
||||||
Code: http.StatusBadRequest,
|
|
||||||
Message: err.Error(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
err = bimg.Write(previewFilePath, previewImage)
|
|
||||||
if err != nil {
|
|
||||||
c.JSON(http.StatusBadRequest, models.ErrorResponse{
|
|
||||||
Code: http.StatusBadRequest,
|
|
||||||
Message: err.Error(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
err = bimg.Write(thumbnailFilePath, thumbnailImage)
|
|
||||||
if err != nil {
|
|
||||||
c.JSON(http.StatusBadRequest, models.ErrorResponse{
|
|
||||||
Code: http.StatusBadRequest,
|
|
||||||
Message: err.Error(),
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
blob := database.Blob{
|
blob := database.Blob{
|
||||||
@ -138,13 +176,29 @@ func uploadBlob(c *gin.Context) {
|
|||||||
ThumbnailFilePath: filepath.Join("thumbnail", folder1, folder2, previewFilename),
|
ThumbnailFilePath: filepath.Join("thumbnail", folder1, folder2, previewFilename),
|
||||||
Width: width,
|
Width: width,
|
||||||
Height: height,
|
Height: height,
|
||||||
|
Hash1: hashSlice[0:2],
|
||||||
|
Hash2: hashSlice[2:4],
|
||||||
|
Hash3: hashSlice[4:6],
|
||||||
|
Hash4: hashSlice[6:8],
|
||||||
}
|
}
|
||||||
|
|
||||||
database.DB.Create(&blob)
|
database.DB.Create(&blob)
|
||||||
|
|
||||||
|
if len(similarPosts) > 0 {
|
||||||
|
c.JSON(http.StatusOK,
|
||||||
|
models.BlobSimilarResponse{
|
||||||
|
ID: id,
|
||||||
|
Width: width,
|
||||||
|
Height: height,
|
||||||
|
Similar: similarPosts,
|
||||||
|
})
|
||||||
|
return
|
||||||
|
} else {
|
||||||
c.JSON(http.StatusOK, models.BlobResponse{
|
c.JSON(http.StatusOK, models.BlobResponse{
|
||||||
ID: id,
|
ID: id,
|
||||||
Width: width,
|
Width: width,
|
||||||
Height: height,
|
Height: height,
|
||||||
})
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,6 +11,10 @@ type Blob struct {
|
|||||||
ThumbnailFilePath string
|
ThumbnailFilePath string
|
||||||
Width int
|
Width int
|
||||||
Height int
|
Height int
|
||||||
|
Hash1 []byte
|
||||||
|
Hash2 []byte
|
||||||
|
Hash3 []byte
|
||||||
|
Hash4 []byte
|
||||||
CreatedAt time.Time
|
CreatedAt time.Time
|
||||||
UpdatedAt time.Time
|
UpdatedAt time.Time
|
||||||
}
|
}
|
||||||
|
@ -17,3 +17,8 @@ type PostListItem struct {
|
|||||||
ImageThumbnailPath string `json:"thumbnail_path"`
|
ImageThumbnailPath string `json:"thumbnail_path"`
|
||||||
Tags []string `json:"tags"`
|
Tags []string `json:"tags"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type PostSimilarityListItem struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
Distance int `json:"distance"`
|
||||||
|
}
|
||||||
|
@ -20,6 +20,12 @@ type BlobResponse struct {
|
|||||||
Height int `json:"height"`
|
Height int `json:"height"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type BlobSimilarResponse struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
Width int `json:"width"`
|
||||||
|
Height int `json:"height"`
|
||||||
|
Similar []PostSimilarityListItem `json:"similar"`
|
||||||
|
}
|
||||||
type PostPaginationResponse struct {
|
type PostPaginationResponse struct {
|
||||||
CurrentPage int `json:"currentPage"`
|
CurrentPage int `json:"currentPage"`
|
||||||
PostCount int `json:"postCount"`
|
PostCount int `json:"postCount"`
|
||||||
|
@ -2,7 +2,6 @@ package services
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"log"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/Damillora/Shioriko/pkg/config"
|
"github.com/Damillora/Shioriko/pkg/config"
|
||||||
@ -13,7 +12,7 @@ import (
|
|||||||
|
|
||||||
func Login(username string, password string) *database.User {
|
func Login(username string, password string) *database.User {
|
||||||
user := GetUserFromUsername(username)
|
user := GetUserFromUsername(username)
|
||||||
log.Println(user.Username)
|
|
||||||
err := bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(password))
|
err := bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(password))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil
|
return nil
|
||||||
|
48
pkg/services/blob.go
Normal file
48
pkg/services/blob.go
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
package services
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
|
||||||
|
"github.com/Damillora/Shioriko/pkg/database"
|
||||||
|
"github.com/Damillora/Shioriko/pkg/models"
|
||||||
|
"github.com/corona10/goimagehash"
|
||||||
|
)
|
||||||
|
|
||||||
|
func SimilaritySearch(originalHashInt uint64) ([]models.PostSimilarityListItem, error) {
|
||||||
|
originalHash := goimagehash.NewImageHash(originalHashInt, goimagehash.PHash)
|
||||||
|
|
||||||
|
hashSlice := make([]byte, 8)
|
||||||
|
binary.LittleEndian.PutUint64(hashSlice, originalHashInt)
|
||||||
|
|
||||||
|
var blobs []database.Blob
|
||||||
|
database.DB.
|
||||||
|
Joins("inner join posts on posts.blob_id = blobs.id").
|
||||||
|
Where("hash1 = ?", hashSlice[0:2]).
|
||||||
|
Or("hash2 = ?", hashSlice[2:4]).
|
||||||
|
Or("hash3 = ?", hashSlice[4:6]).
|
||||||
|
Or("hash4 = ?", hashSlice[6:8]).
|
||||||
|
Find(&blobs)
|
||||||
|
|
||||||
|
posts := make([]models.PostSimilarityListItem, 0)
|
||||||
|
for _, blob := range blobs {
|
||||||
|
hash2 := append(blob.Hash1, blob.Hash2...)
|
||||||
|
hash3 := append(hash2, blob.Hash3...)
|
||||||
|
hash4 := append(hash3, blob.Hash4...)
|
||||||
|
|
||||||
|
hashInt := binary.LittleEndian.Uint64(hash4)
|
||||||
|
|
||||||
|
compareHash := goimagehash.NewImageHash(hashInt, goimagehash.PHash)
|
||||||
|
|
||||||
|
distance, _ := compareHash.Distance(originalHash)
|
||||||
|
if distance < 1 {
|
||||||
|
var post database.Post
|
||||||
|
database.DB.Where("blob_id = ?", blob.ID).Find(&post)
|
||||||
|
posts = append(posts, models.PostSimilarityListItem{
|
||||||
|
ID: post.ID,
|
||||||
|
Distance: distance,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return posts, nil
|
||||||
|
|
||||||
|
}
|
@ -1,8 +1,6 @@
|
|||||||
package services
|
package services
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"log"
|
|
||||||
|
|
||||||
"github.com/Damillora/Shioriko/pkg/database"
|
"github.com/Damillora/Shioriko/pkg/database"
|
||||||
"github.com/Damillora/Shioriko/pkg/models"
|
"github.com/Damillora/Shioriko/pkg/models"
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
@ -18,7 +16,7 @@ func GetPostAll(page int) []database.Post {
|
|||||||
|
|
||||||
func GetPostTags(page int, tagSyntax []string) []database.Post {
|
func GetPostTags(page int, tagSyntax []string) []database.Post {
|
||||||
tags, err := ParseReadTags(tagSyntax)
|
tags, err := ParseReadTags(tagSyntax)
|
||||||
log.Println(tags)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return []database.Post{}
|
return []database.Post{}
|
||||||
}
|
}
|
||||||
|
@ -103,3 +103,15 @@ export async function postUpdate(id, { source_url, tags }) {
|
|||||||
})
|
})
|
||||||
return response.data;
|
return response.data;
|
||||||
}
|
}
|
||||||
|
export async function postDelete({id}) {
|
||||||
|
const endpoint = url + "/api/post/"+id;
|
||||||
|
const response = await axios({
|
||||||
|
url: endpoint,
|
||||||
|
method: "DELETE",
|
||||||
|
headers: {
|
||||||
|
'Authorization': 'Bearer ' + current_token,
|
||||||
|
},
|
||||||
|
withCredentials: true,
|
||||||
|
})
|
||||||
|
return response.status == 200;
|
||||||
|
}
|
@ -1,8 +1,8 @@
|
|||||||
<script>
|
<script>
|
||||||
import { onMount } from "svelte";
|
import { onMount } from "svelte";
|
||||||
import TagLink from "../TagLink.svelte";
|
import TagLink from "../TagLink.svelte";
|
||||||
import { getPost, postCreate } from "../api.js";
|
import { getPost, postCreate, postDelete } from "../api.js";
|
||||||
import { Link } from "svelte-routing";
|
import { Link, navigate } from "svelte-routing";
|
||||||
export let id;
|
export let id;
|
||||||
let post;
|
let post;
|
||||||
const getData = async () => {
|
const getData = async () => {
|
||||||
@ -20,6 +20,19 @@
|
|||||||
onMount(() => {
|
onMount(() => {
|
||||||
getData();
|
getData();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
let modal_shown = false;
|
||||||
|
|
||||||
|
const deletePost = async () => {
|
||||||
|
toggleModal();
|
||||||
|
const success = await postDelete({ id });
|
||||||
|
if (success) {
|
||||||
|
navigate("/posts");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const toggleModal = () => {
|
||||||
|
modal_shown = !modal_shown;
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<section class="hero is-primary">
|
<section class="hero is-primary">
|
||||||
@ -42,6 +55,10 @@
|
|||||||
class="button is-primary"
|
class="button is-primary"
|
||||||
to="/post/edit/{post.id}">Edit</Link
|
to="/post/edit/{post.id}">Edit</Link
|
||||||
>
|
>
|
||||||
|
<button
|
||||||
|
on:click|preventDefault={toggleModal}
|
||||||
|
class="button is-danger">Delete</button
|
||||||
|
>
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
Uploader: {post.uploader}
|
Uploader: {post.uploader}
|
||||||
@ -87,4 +104,27 @@
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="modal" class:is-active={modal_shown}>
|
||||||
|
<div class="modal-background" />
|
||||||
|
<div class="modal-content">
|
||||||
|
Are you sure to delete post {post.id}?
|
||||||
|
</div>
|
||||||
|
<div class="modal-card">
|
||||||
|
<header class="modal-card-head">
|
||||||
|
<p class="modal-card-title">Delete post?</p>
|
||||||
|
<button class="delete" aria-label="close" />
|
||||||
|
</header>
|
||||||
|
<section class="modal-card-body" />
|
||||||
|
<footer class="modal-card-foot">
|
||||||
|
<button
|
||||||
|
on:click|preventDefault={deletePost}
|
||||||
|
class="button is-danger">Delete</button
|
||||||
|
>
|
||||||
|
<button class="button" on:click|preventDefault={toggleModal}
|
||||||
|
>Cancel</button
|
||||||
|
>
|
||||||
|
</footer>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
@ -1,12 +1,13 @@
|
|||||||
<script>
|
<script>
|
||||||
import { uploadBlob, postCreate } from "../api.js";
|
import { uploadBlob, postCreate } from "../api.js";
|
||||||
import { navigate } from "svelte-routing";
|
import { navigate, Link } from "svelte-routing";
|
||||||
import Tags from "svelte-tags-input";
|
import Tags from "svelte-tags-input";
|
||||||
import AuthRequired from "../AuthRequired.svelte";
|
import AuthRequired from "../AuthRequired.svelte";
|
||||||
|
|
||||||
let currentProgress = 0;
|
let currentProgress = 0;
|
||||||
|
|
||||||
let fileName = "";
|
let fileName = "";
|
||||||
|
let similar = [];
|
||||||
|
|
||||||
let form = {
|
let form = {
|
||||||
blob_id: "",
|
blob_id: "",
|
||||||
@ -21,9 +22,13 @@
|
|||||||
|
|
||||||
const onFileChange = async (e) => {
|
const onFileChange = async (e) => {
|
||||||
fileName = "";
|
fileName = "";
|
||||||
|
similar = [];
|
||||||
var file = e.target.files[0];
|
var file = e.target.files[0];
|
||||||
if (file) {
|
if (file) {
|
||||||
var response = await uploadBlob({ file, onProgress });
|
var response = await uploadBlob({ file, onProgress });
|
||||||
|
if (response.similar) {
|
||||||
|
similar = response.similar;
|
||||||
|
}
|
||||||
form.blob_id = response.id;
|
form.blob_id = response.id;
|
||||||
fileName = file.name;
|
fileName = file.name;
|
||||||
}
|
}
|
||||||
@ -31,7 +36,7 @@
|
|||||||
|
|
||||||
const onTagChange = (value) => {
|
const onTagChange = (value) => {
|
||||||
form.tags = value.detail.tags;
|
form.tags = value.detail.tags;
|
||||||
}
|
};
|
||||||
|
|
||||||
const onSubmit = async () => {
|
const onSubmit = async () => {
|
||||||
const response = await postCreate(form);
|
const response = await postCreate(form);
|
||||||
@ -75,6 +80,17 @@
|
|||||||
{#if fileName !== ""}
|
{#if fileName !== ""}
|
||||||
<p class="help">{fileName} uploaded</p>
|
<p class="help">{fileName} uploaded</p>
|
||||||
{/if}
|
{/if}
|
||||||
|
{#if similar.length > 0}
|
||||||
|
<p class="help">
|
||||||
|
Similar posts:
|
||||||
|
{#each similar as post, i}
|
||||||
|
<Link to="/post/{post.id}">{post.id}</Link>
|
||||||
|
{#if i < similar.length - 1}
|
||||||
|
,
|
||||||
|
{/if}
|
||||||
|
{/each}
|
||||||
|
</p>
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<label for="source" class="label">Source URL</label>
|
<label for="source" class="label">Source URL</label>
|
||||||
@ -91,7 +107,7 @@
|
|||||||
<div class="field">
|
<div class="field">
|
||||||
<label for="tags" class="label">Tags</label>
|
<label for="tags" class="label">Tags</label>
|
||||||
<div class="control" id="tags">
|
<div class="control" id="tags">
|
||||||
<Tags addKeys={[9,32]} on:tags={onTagChange} />
|
<Tags addKeys={[9, 32]} on:tags={onTagChange} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="control">
|
<div class="control">
|
||||||
|
Loading…
Reference in New Issue
Block a user