Basic queue for processing

This commit is contained in:
LittleSheep 2024-07-28 21:03:56 +08:00
parent 2a94bb20f8
commit 10879bef14
13 changed files with 140 additions and 635 deletions

View File

@ -14,7 +14,7 @@ And the background consumer will start dealing with the uploaded files.
The background consumer will hash the file and merge the files with same hashcode. The background consumer will hash the file and merge the files with same hashcode.
The background consumer will decode the image and generate ratio and read more info from image file too. The background consumer will decode the image and generate ratio and read more info from image file too.
After the processing done. The consumer will upload the file to the persistent storage like a s3 bucket and remove local cache. After the processing done. The consumer will upload the file to the permanent storage like a s3 bucket and remove local cache.
While the processing, the file record in database will marked to the temporary and load file from the temporary storage. While the processing, the file record in database will marked to the temporary and load file from the temporary storage.
When the processing done, the file record will be updated. When the processing done, the file record will be updated.

2
go.mod
View File

@ -14,7 +14,6 @@ require (
github.com/samber/lo v1.39.0 github.com/samber/lo v1.39.0
github.com/spf13/viper v1.18.2 github.com/spf13/viper v1.18.2
google.golang.org/grpc v1.64.0 google.golang.org/grpc v1.64.0
google.golang.org/protobuf v1.34.2
gorm.io/datatypes v1.2.0 gorm.io/datatypes v1.2.0
gorm.io/driver/postgres v1.5.4 gorm.io/driver/postgres v1.5.4
gorm.io/gorm v1.25.6 gorm.io/gorm v1.25.6
@ -86,6 +85,7 @@ require (
golang.org/x/sys v0.21.0 // indirect golang.org/x/sys v0.21.0 // indirect
golang.org/x/text v0.16.0 // indirect golang.org/x/text v0.16.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240604185151-ef581f913117 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240604185151-ef581f913117 // indirect
google.golang.org/protobuf v1.34.2 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect
gorm.io/driver/mysql v1.5.2 // indirect gorm.io/driver/mysql v1.5.2 // indirect

View File

@ -1,76 +0,0 @@
package grpc
import (
"context"
"fmt"
"git.solsynth.dev/hydrogen/paperclip/pkg/internal/database"
"git.solsynth.dev/hydrogen/paperclip/pkg/proto"
"google.golang.org/protobuf/types/known/emptypb"
"git.solsynth.dev/hydrogen/paperclip/pkg/internal/models"
jsoniter "github.com/json-iterator/go"
"github.com/samber/lo"
)
func (v *Server) GetAttachment(ctx context.Context, request *proto.AttachmentLookupRequest) (*proto.Attachment, error) {
var attachment models.Attachment
tx := database.C.Model(&models.Attachment{})
if request.Id != nil {
tx = tx.Where("id = ?", request.GetId())
}
if request.Uuid != nil {
tx = tx.Where("uuid = ?", request.GetUuid())
}
if request.Usage != nil {
tx = tx.Where("usage = ?", request.GetUsage())
}
if err := tx.First(&attachment).Error; err != nil {
return nil, err
}
rawMetadata, _ := jsoniter.Marshal(attachment.Metadata)
if attachment.AccountID == nil {
attachment.AccountID = lo.ToPtr[uint](0)
}
return &proto.Attachment{
Id: uint64(attachment.ID),
Uuid: attachment.Uuid,
Size: attachment.Size,
Name: attachment.Name,
Alt: attachment.Alternative,
Usage: attachment.Usage,
Mimetype: attachment.MimeType,
Hash: attachment.HashCode,
Destination: attachment.Destination,
Metadata: rawMetadata,
IsMature: attachment.IsMature,
AccountId: uint64(*attachment.AccountID),
}, nil
}
func (v *Server) CheckAttachmentExists(ctx context.Context, request *proto.AttachmentLookupRequest) (*emptypb.Empty, error) {
tx := database.C.Model(&models.Attachment{})
if request.Id != nil {
tx = tx.Where("id = ?", request.GetId())
}
if request.Uuid != nil {
tx = tx.Where("uuid = ?", request.GetUuid())
}
if request.Usage != nil {
tx = tx.Where("usage = ?", request.GetUsage())
}
var count int64
if err := tx.Model(&models.Attachment{}).Count(&count).Error; err != nil {
return nil, err
} else if count == 0 {
return nil, fmt.Errorf("record not found")
}
return &emptypb.Empty{}, nil
}

View File

@ -1,16 +1,15 @@
package grpc package grpc
import ( import (
"git.solsynth.dev/hydrogen/paperclip/pkg/proto" "net"
"github.com/spf13/viper" "github.com/spf13/viper"
"google.golang.org/grpc" "google.golang.org/grpc"
health "google.golang.org/grpc/health/grpc_health_v1" health "google.golang.org/grpc/health/grpc_health_v1"
"google.golang.org/grpc/reflection" "google.golang.org/grpc/reflection"
"net"
) )
type Server struct { type Server struct {
proto.UnimplementedAttachmentsServer
} }
var S *grpc.Server var S *grpc.Server
@ -18,7 +17,6 @@ var S *grpc.Server
func NewGRPC() { func NewGRPC() {
S = grpc.NewServer() S = grpc.NewServer()
proto.RegisterAttachmentsServer(S, &Server{})
health.RegisterHealthServer(S, &Server{}) health.RegisterHealthServer(S, &Server{})
reflection.Register(S) reflection.Register(S)

View File

@ -2,6 +2,13 @@ package models
import "gorm.io/datatypes" import "gorm.io/datatypes"
type AttachmentDst = int8
const (
AttachmentDstTemporary = AttachmentDst(iota)
AttachmentDstPermanent
)
type Attachment struct { type Attachment struct {
BaseModel BaseModel
@ -12,11 +19,11 @@ type Attachment struct {
Usage string `json:"usage"` Usage string `json:"usage"`
MimeType string `json:"mimetype"` MimeType string `json:"mimetype"`
HashCode string `json:"hash"` HashCode string `json:"hash"`
Destination string `json:"destination"` Destination AttachmentDst `json:"destination"`
Metadata datatypes.JSONMap `json:"metadata"` Metadata datatypes.JSONMap `json:"metadata"`
IsMature bool `json:"is_mature"` IsMature bool `json:"is_mature"`
Account *Account `json:"account"` Account Account `json:"account"`
AccountID *uint `json:"account_id"` AccountID uint `json:"account_id"`
} }

View File

@ -25,17 +25,18 @@ func openAttachment(c *fiber.Ctx) error {
return fiber.NewError(fiber.StatusNotFound) return fiber.NewError(fiber.StatusNotFound)
} }
destMap := viper.GetStringMap("destinations") var destMap map[string]any
dest, destOk := destMap[metadata.Destination] if metadata.Destination == models.AttachmentDstTemporary {
if !destOk { destMap = viper.GetStringMap("destinations.temporary")
return fiber.NewError(fiber.StatusInternalServerError, "invalid destination: destination configuration was not found") } else {
destMap = viper.GetStringMap("destinations.permanent")
} }
var destParsed models.BaseDestination var dest models.BaseDestination
rawDest, _ := jsoniter.Marshal(dest) rawDest, _ := jsoniter.Marshal(destMap)
_ = jsoniter.Unmarshal(rawDest, &destParsed) _ = jsoniter.Unmarshal(rawDest, &dest)
switch destParsed.Type { switch dest.Type {
case models.DestinationTypeLocal: case models.DestinationTypeLocal:
var destConfigured models.LocalDestination var destConfigured models.LocalDestination
_ = jsoniter.Unmarshal(rawDest, &destConfigured) _ = jsoniter.Unmarshal(rawDest, &destConfigured)
@ -43,7 +44,6 @@ func openAttachment(c *fiber.Ctx) error {
c.Set(fiber.HeaderContentType, metadata.MimeType) c.Set(fiber.HeaderContentType, metadata.MimeType)
} }
return c.SendFile(filepath.Join(destConfigured.Path, metadata.Uuid), false) return c.SendFile(filepath.Join(destConfigured.Path, metadata.Uuid), false)
case models.DestinationTypeS3: case models.DestinationTypeS3:
var destConfigured models.S3Destination var destConfigured models.S3Destination
_ = jsoniter.Unmarshal(rawDest, &destConfigured) _ = jsoniter.Unmarshal(rawDest, &destConfigured)
@ -54,10 +54,9 @@ func openAttachment(c *fiber.Ctx) error {
destConfigured.Bucket, destConfigured.Bucket,
destConfigured.Endpoint, destConfigured.Endpoint,
url.QueryEscape(filepath.Join(destConfigured.Path, metadata.Uuid)), url.QueryEscape(filepath.Join(destConfigured.Path, metadata.Uuid)),
)) ), fiber.StatusMovedPermanently)
default: default:
return fmt.Errorf("invalid destination: unsupported protocol %s", destParsed.Type) return fmt.Errorf("invalid destination: unsupported protocol %s", dest.Type)
} }
} }
@ -79,8 +78,6 @@ func createAttachment(c *fiber.Ctx) error {
} }
user = lo.ToPtr(c.Locals("user").(models.Account)) user = lo.ToPtr(c.Locals("user").(models.Account))
destName := c.Query("destination", viper.GetString("preferred_destination"))
hash := c.FormValue("hash") hash := c.FormValue("hash")
if len(hash) != 64 { if len(hash) != 64 {
return fiber.NewError(fiber.StatusBadRequest, "please provide a sha-256 hash code, length should be 64 characters") return fiber.NewError(fiber.StatusBadRequest, "please provide a sha-256 hash code, length should be 64 characters")
@ -110,7 +107,7 @@ func createAttachment(c *fiber.Ctx) error {
MimeType: c.FormValue("mimetype"), MimeType: c.FormValue("mimetype"),
Metadata: usermeta, Metadata: usermeta,
IsMature: len(c.FormValue("mature")) > 0, IsMature: len(c.FormValue("mature")) > 0,
Destination: destName, Destination: models.AttachmentDstTemporary,
}) })
if err != nil { if err != nil {
tx.Rollback() tx.Rollback()
@ -118,7 +115,7 @@ func createAttachment(c *fiber.Ctx) error {
} }
if !linked { if !linked {
if err := services.UploadFile(destName, c, file, metadata); err != nil { if err := services.UploadFileToTemporary(c, file, metadata); err != nil {
tx.Rollback() tx.Rollback()
return fiber.NewError(fiber.StatusBadRequest, err.Error()) return fiber.NewError(fiber.StatusBadRequest, err.Error())
} }
@ -176,7 +173,7 @@ func deleteAttachment(c *fiber.Ctx) error {
attachment, err := services.GetAttachmentByID(uint(id)) attachment, err := services.GetAttachmentByID(uint(id))
if err != nil { if err != nil {
return fiber.NewError(fiber.StatusNotFound, err.Error()) return fiber.NewError(fiber.StatusNotFound, err.Error())
} else if attachment.AccountID == nil || *attachment.AccountID != user.ID { } else if attachment.AccountID != user.ID {
return fiber.NewError(fiber.StatusNotFound, "record not created by you") return fiber.NewError(fiber.StatusNotFound, "record not created by you")
} }

View File

@ -0,0 +1,68 @@
package services
import (
"fmt"
"image"
"os"
"path/filepath"
"strings"
"git.solsynth.dev/hydrogen/paperclip/pkg/internal/database"
"git.solsynth.dev/hydrogen/paperclip/pkg/internal/models"
jsoniter "github.com/json-iterator/go"
"github.com/spf13/viper"
_ "image/gif"
_ "image/jpeg"
_ "image/png"
)
var fileAnalyzeQueue = make(chan models.Attachment, 256)
func PublishAnalyzeTask(file models.Attachment) {
fileAnalyzeQueue <- file
}
func AnalyzeAttachment(file models.Attachment) error {
if file.Destination != models.AttachmentDstTemporary {
return fmt.Errorf("attachment isn't in temporary storage, unable to analyze")
}
destMap := viper.GetStringMap("destinations.temporary")
var dest models.LocalDestination
rawDest, _ := jsoniter.Marshal(destMap)
_ = jsoniter.Unmarshal(rawDest, &dest)
dst := filepath.Join(dest.Path, file.Uuid)
if _, err := os.Stat(dst); !os.IsExist(err) {
return fmt.Errorf("attachment doesn't exists in temporary storage")
}
if t := strings.SplitN(file.MimeType, "/", 2)[0]; t == "image" {
// Dealing with image
reader, err := os.Open(dst)
if err != nil {
return fmt.Errorf("unable to open file: %v", err)
}
defer reader.Close()
im, _, err := image.Decode(reader)
if err != nil {
return fmt.Errorf("unable to decode file as an image: %v", err)
}
width := im.Bounds().Dx()
height := im.Bounds().Dy()
ratio := width / height
file.Metadata = map[string]any{
"width": width,
"height": height,
"ratio": ratio,
}
}
if err := database.C.Save(&file).Error; err != nil {
return fmt.Errorf("unable to save file record: %v", err)
}
return nil
}

View File

@ -58,19 +58,13 @@ func NewAttachmentMetadata(tx *gorm.DB, user *models.Account, file *multipart.Fi
exists.Metadata = attachment.Metadata exists.Metadata = attachment.Metadata
attachment = exists attachment = exists
attachment.ID = 0 attachment.ID = 0
attachment.AccountID = user.ID
if user != nil {
attachment.AccountID = &user.ID
}
} else { } else {
// Upload the new file // Upload the new file
attachment.Uuid = uuid.NewString() attachment.Uuid = uuid.NewString()
attachment.Size = file.Size attachment.Size = file.Size
attachment.Name = file.Filename attachment.Name = file.Filename
attachment.AccountID = user.ID
if user != nil {
attachment.AccountID = &user.ID
}
// If the user didn't provide file mimetype manually, we have to detect it // If the user didn't provide file mimetype manually, we have to detect it
if len(attachment.MimeType) == 0 { if len(attachment.MimeType) == 0 {

View File

@ -14,17 +14,18 @@ import (
) )
func DeleteFile(meta models.Attachment) error { func DeleteFile(meta models.Attachment) error {
destMap := viper.GetStringMap("destinations") var destMap map[string]any
dest, destOk := destMap[meta.Destination] if meta.Destination == models.AttachmentDstTemporary {
if !destOk { destMap = viper.GetStringMap("destinations.temporary")
return fmt.Errorf("invalid destination: destination configuration was not found") } else {
destMap = viper.GetStringMap("destinations.permanent")
} }
var destParsed models.BaseDestination var dest models.BaseDestination
rawDest, _ := jsoniter.Marshal(dest) rawDest, _ := jsoniter.Marshal(destMap)
_ = jsoniter.Unmarshal(rawDest, &destParsed) _ = jsoniter.Unmarshal(rawDest, &dest)
switch destParsed.Type { switch dest.Type {
case models.DestinationTypeLocal: case models.DestinationTypeLocal:
var destConfigured models.LocalDestination var destConfigured models.LocalDestination
_ = jsoniter.Unmarshal(rawDest, &destConfigured) _ = jsoniter.Unmarshal(rawDest, &destConfigured)
@ -34,7 +35,7 @@ func DeleteFile(meta models.Attachment) error {
_ = jsoniter.Unmarshal(rawDest, &destConfigured) _ = jsoniter.Unmarshal(rawDest, &destConfigured)
return DeleteFileFromS3(destConfigured, meta) return DeleteFileFromS3(destConfigured, meta)
default: default:
return fmt.Errorf("invalid destination: unsupported protocol %s", destParsed.Type) return fmt.Errorf("invalid destination: unsupported protocol %s", dest.Type)
} }
} }

View File

@ -16,18 +16,31 @@ import (
"github.com/spf13/viper" "github.com/spf13/viper"
) )
func UploadFile(destName string, ctx *fiber.Ctx, file *multipart.FileHeader, meta models.Attachment) error { func UploadFileToTemporary(ctx *fiber.Ctx, file *multipart.FileHeader, meta models.Attachment) error {
destMap := viper.GetStringMap("destinations") destMap := viper.GetStringMap("destinations.temporary")
dest, destOk := destMap[destName]
if !destOk { var dest models.BaseDestination
return fmt.Errorf("invalid destination: destination configuration was not found") rawDest, _ := jsoniter.Marshal(destMap)
_ = jsoniter.Unmarshal(rawDest, &dest)
switch dest.Type {
case models.DestinationTypeLocal:
var destConfigured models.LocalDestination
_ = jsoniter.Unmarshal(rawDest, &destConfigured)
return UploadFileToLocal(destConfigured, ctx, file, meta)
default:
return fmt.Errorf("invalid destination: unsupported protocol %s", dest.Type)
}
} }
var destParsed models.BaseDestination func UploadFileToPermanent(ctx *fiber.Ctx, file *multipart.FileHeader, meta models.Attachment) error {
rawDest, _ := jsoniter.Marshal(dest) destMap := viper.GetStringMap("destinations.permanent")
_ = jsoniter.Unmarshal(rawDest, &destParsed)
switch destParsed.Type { var dest models.BaseDestination
rawDest, _ := jsoniter.Marshal(destMap)
_ = jsoniter.Unmarshal(rawDest, &dest)
switch dest.Type {
case models.DestinationTypeLocal: case models.DestinationTypeLocal:
var destConfigured models.LocalDestination var destConfigured models.LocalDestination
_ = jsoniter.Unmarshal(rawDest, &destConfigured) _ = jsoniter.Unmarshal(rawDest, &destConfigured)
@ -37,7 +50,7 @@ func UploadFile(destName string, ctx *fiber.Ctx, file *multipart.FileHeader, met
_ = jsoniter.Unmarshal(rawDest, &destConfigured) _ = jsoniter.Unmarshal(rawDest, &destConfigured)
return UploadFileToS3(destConfigured, file, meta) return UploadFileToS3(destConfigured, file, meta)
default: default:
return fmt.Errorf("invalid destination: unsupported protocol %s", destParsed.Type) return fmt.Errorf("invalid destination: unsupported protocol %s", dest.Type)
} }
} }

View File

@ -1,349 +0,0 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.33.0
// protoc v5.26.1
// source: attachments.proto
package proto
import (
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
emptypb "google.golang.org/protobuf/types/known/emptypb"
reflect "reflect"
sync "sync"
)
const (
// Verify that this generated code is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
// Verify that runtime/protoimpl is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
type Attachment struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Id uint64 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"`
Uuid string `protobuf:"bytes,2,opt,name=uuid,proto3" json:"uuid,omitempty"`
Size int64 `protobuf:"varint,3,opt,name=size,proto3" json:"size,omitempty"`
Name string `protobuf:"bytes,4,opt,name=name,proto3" json:"name,omitempty"`
Alt string `protobuf:"bytes,5,opt,name=alt,proto3" json:"alt,omitempty"`
Usage string `protobuf:"bytes,6,opt,name=usage,proto3" json:"usage,omitempty"`
Mimetype string `protobuf:"bytes,7,opt,name=mimetype,proto3" json:"mimetype,omitempty"`
Hash string `protobuf:"bytes,8,opt,name=hash,proto3" json:"hash,omitempty"`
Destination string `protobuf:"bytes,9,opt,name=destination,proto3" json:"destination,omitempty"`
Metadata []byte `protobuf:"bytes,10,opt,name=metadata,proto3" json:"metadata,omitempty"`
IsMature bool `protobuf:"varint,11,opt,name=is_mature,json=isMature,proto3" json:"is_mature,omitempty"`
AccountId uint64 `protobuf:"varint,12,opt,name=account_id,json=accountId,proto3" json:"account_id,omitempty"`
}
func (x *Attachment) Reset() {
*x = Attachment{}
if protoimpl.UnsafeEnabled {
mi := &file_attachments_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *Attachment) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*Attachment) ProtoMessage() {}
func (x *Attachment) ProtoReflect() protoreflect.Message {
mi := &file_attachments_proto_msgTypes[0]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use Attachment.ProtoReflect.Descriptor instead.
func (*Attachment) Descriptor() ([]byte, []int) {
return file_attachments_proto_rawDescGZIP(), []int{0}
}
func (x *Attachment) GetId() uint64 {
if x != nil {
return x.Id
}
return 0
}
func (x *Attachment) GetUuid() string {
if x != nil {
return x.Uuid
}
return ""
}
func (x *Attachment) GetSize() int64 {
if x != nil {
return x.Size
}
return 0
}
func (x *Attachment) GetName() string {
if x != nil {
return x.Name
}
return ""
}
func (x *Attachment) GetAlt() string {
if x != nil {
return x.Alt
}
return ""
}
func (x *Attachment) GetUsage() string {
if x != nil {
return x.Usage
}
return ""
}
func (x *Attachment) GetMimetype() string {
if x != nil {
return x.Mimetype
}
return ""
}
func (x *Attachment) GetHash() string {
if x != nil {
return x.Hash
}
return ""
}
func (x *Attachment) GetDestination() string {
if x != nil {
return x.Destination
}
return ""
}
func (x *Attachment) GetMetadata() []byte {
if x != nil {
return x.Metadata
}
return nil
}
func (x *Attachment) GetIsMature() bool {
if x != nil {
return x.IsMature
}
return false
}
func (x *Attachment) GetAccountId() uint64 {
if x != nil {
return x.AccountId
}
return 0
}
type AttachmentLookupRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Id *uint64 `protobuf:"varint,1,opt,name=id,proto3,oneof" json:"id,omitempty"`
Uuid *string `protobuf:"bytes,2,opt,name=uuid,proto3,oneof" json:"uuid,omitempty"`
Usage *string `protobuf:"bytes,3,opt,name=usage,proto3,oneof" json:"usage,omitempty"`
}
func (x *AttachmentLookupRequest) Reset() {
*x = AttachmentLookupRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_attachments_proto_msgTypes[1]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *AttachmentLookupRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*AttachmentLookupRequest) ProtoMessage() {}
func (x *AttachmentLookupRequest) ProtoReflect() protoreflect.Message {
mi := &file_attachments_proto_msgTypes[1]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use AttachmentLookupRequest.ProtoReflect.Descriptor instead.
func (*AttachmentLookupRequest) Descriptor() ([]byte, []int) {
return file_attachments_proto_rawDescGZIP(), []int{1}
}
func (x *AttachmentLookupRequest) GetId() uint64 {
if x != nil && x.Id != nil {
return *x.Id
}
return 0
}
func (x *AttachmentLookupRequest) GetUuid() string {
if x != nil && x.Uuid != nil {
return *x.Uuid
}
return ""
}
func (x *AttachmentLookupRequest) GetUsage() string {
if x != nil && x.Usage != nil {
return *x.Usage
}
return ""
}
var File_attachments_proto protoreflect.FileDescriptor
var file_attachments_proto_rawDesc = []byte{
0x0a, 0x11, 0x61, 0x74, 0x74, 0x61, 0x63, 0x68, 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x2e, 0x70, 0x72,
0x6f, 0x74, 0x6f, 0x12, 0x05, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1b, 0x67, 0x6f, 0x6f, 0x67,
0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x65, 0x6d, 0x70, 0x74,
0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xaa, 0x02, 0x0a, 0x0a, 0x41, 0x74, 0x74, 0x61,
0x63, 0x68, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01,
0x28, 0x04, 0x52, 0x02, 0x69, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x75, 0x75, 0x69, 0x64, 0x18, 0x02,
0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x75, 0x75, 0x69, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x73, 0x69,
0x7a, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x04, 0x73, 0x69, 0x7a, 0x65, 0x12, 0x12,
0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61,
0x6d, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x61, 0x6c, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52,
0x03, 0x61, 0x6c, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x75, 0x73, 0x61, 0x67, 0x65, 0x18, 0x06, 0x20,
0x01, 0x28, 0x09, 0x52, 0x05, 0x75, 0x73, 0x61, 0x67, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x6d, 0x69,
0x6d, 0x65, 0x74, 0x79, 0x70, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6d, 0x69,
0x6d, 0x65, 0x74, 0x79, 0x70, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x68, 0x61, 0x73, 0x68, 0x18, 0x08,
0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x68, 0x61, 0x73, 0x68, 0x12, 0x20, 0x0a, 0x0b, 0x64, 0x65,
0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52,
0x0b, 0x64, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1a, 0x0a, 0x08,
0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08,
0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x1b, 0x0a, 0x09, 0x69, 0x73, 0x5f, 0x6d,
0x61, 0x74, 0x75, 0x72, 0x65, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x69, 0x73, 0x4d,
0x61, 0x74, 0x75, 0x72, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74,
0x5f, 0x69, 0x64, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x04, 0x52, 0x09, 0x61, 0x63, 0x63, 0x6f, 0x75,
0x6e, 0x74, 0x49, 0x64, 0x22, 0x7c, 0x0a, 0x17, 0x41, 0x74, 0x74, 0x61, 0x63, 0x68, 0x6d, 0x65,
0x6e, 0x74, 0x4c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12,
0x13, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x48, 0x00, 0x52, 0x02, 0x69,
0x64, 0x88, 0x01, 0x01, 0x12, 0x17, 0x0a, 0x04, 0x75, 0x75, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01,
0x28, 0x09, 0x48, 0x01, 0x52, 0x04, 0x75, 0x75, 0x69, 0x64, 0x88, 0x01, 0x01, 0x12, 0x19, 0x0a,
0x05, 0x75, 0x73, 0x61, 0x67, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x48, 0x02, 0x52, 0x05,
0x75, 0x73, 0x61, 0x67, 0x65, 0x88, 0x01, 0x01, 0x42, 0x05, 0x0a, 0x03, 0x5f, 0x69, 0x64, 0x42,
0x07, 0x0a, 0x05, 0x5f, 0x75, 0x75, 0x69, 0x64, 0x42, 0x08, 0x0a, 0x06, 0x5f, 0x75, 0x73, 0x61,
0x67, 0x65, 0x32, 0xa6, 0x01, 0x0a, 0x0b, 0x41, 0x74, 0x74, 0x61, 0x63, 0x68, 0x6d, 0x65, 0x6e,
0x74, 0x73, 0x12, 0x44, 0x0a, 0x0d, 0x47, 0x65, 0x74, 0x41, 0x74, 0x74, 0x61, 0x63, 0x68, 0x6d,
0x65, 0x6e, 0x74, 0x12, 0x1e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x41, 0x74, 0x74, 0x61,
0x63, 0x68, 0x6d, 0x65, 0x6e, 0x74, 0x4c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x52, 0x65, 0x71, 0x75,
0x65, 0x73, 0x74, 0x1a, 0x11, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x41, 0x74, 0x74, 0x61,
0x63, 0x68, 0x6d, 0x65, 0x6e, 0x74, 0x22, 0x00, 0x12, 0x51, 0x0a, 0x15, 0x43, 0x68, 0x65, 0x63,
0x6b, 0x41, 0x74, 0x74, 0x61, 0x63, 0x68, 0x6d, 0x65, 0x6e, 0x74, 0x45, 0x78, 0x69, 0x73, 0x74,
0x73, 0x12, 0x1e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x41, 0x74, 0x74, 0x61, 0x63, 0x68,
0x6d, 0x65, 0x6e, 0x74, 0x4c, 0x6f, 0x6f, 0x6b, 0x75, 0x70, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x00, 0x42, 0x09, 0x5a, 0x07, 0x2e,
0x3b, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
}
var (
file_attachments_proto_rawDescOnce sync.Once
file_attachments_proto_rawDescData = file_attachments_proto_rawDesc
)
func file_attachments_proto_rawDescGZIP() []byte {
file_attachments_proto_rawDescOnce.Do(func() {
file_attachments_proto_rawDescData = protoimpl.X.CompressGZIP(file_attachments_proto_rawDescData)
})
return file_attachments_proto_rawDescData
}
var file_attachments_proto_msgTypes = make([]protoimpl.MessageInfo, 2)
var file_attachments_proto_goTypes = []interface{}{
(*Attachment)(nil), // 0: proto.Attachment
(*AttachmentLookupRequest)(nil), // 1: proto.AttachmentLookupRequest
(*emptypb.Empty)(nil), // 2: google.protobuf.Empty
}
var file_attachments_proto_depIdxs = []int32{
1, // 0: proto.Attachments.GetAttachment:input_type -> proto.AttachmentLookupRequest
1, // 1: proto.Attachments.CheckAttachmentExists:input_type -> proto.AttachmentLookupRequest
0, // 2: proto.Attachments.GetAttachment:output_type -> proto.Attachment
2, // 3: proto.Attachments.CheckAttachmentExists:output_type -> google.protobuf.Empty
2, // [2:4] is the sub-list for method output_type
0, // [0:2] is the sub-list for method input_type
0, // [0:0] is the sub-list for extension type_name
0, // [0:0] is the sub-list for extension extendee
0, // [0:0] is the sub-list for field type_name
}
func init() { file_attachments_proto_init() }
func file_attachments_proto_init() {
if File_attachments_proto != nil {
return
}
if !protoimpl.UnsafeEnabled {
file_attachments_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*Attachment); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_attachments_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*AttachmentLookupRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
}
file_attachments_proto_msgTypes[1].OneofWrappers = []interface{}{}
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_attachments_proto_rawDesc,
NumEnums: 0,
NumMessages: 2,
NumExtensions: 0,
NumServices: 1,
},
GoTypes: file_attachments_proto_goTypes,
DependencyIndexes: file_attachments_proto_depIdxs,
MessageInfos: file_attachments_proto_msgTypes,
}.Build()
File_attachments_proto = out.File
file_attachments_proto_rawDesc = nil
file_attachments_proto_goTypes = nil
file_attachments_proto_depIdxs = nil
}

View File

@ -1,147 +0,0 @@
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
// versions:
// - protoc-gen-go-grpc v1.3.0
// - protoc v5.26.1
// source: attachments.proto
package proto
import (
context "context"
grpc "google.golang.org/grpc"
codes "google.golang.org/grpc/codes"
status "google.golang.org/grpc/status"
emptypb "google.golang.org/protobuf/types/known/emptypb"
)
// This is a compile-time assertion to ensure that this generated file
// is compatible with the grpc package it is being compiled against.
// Requires gRPC-Go v1.32.0 or later.
const _ = grpc.SupportPackageIsVersion7
const (
Attachments_GetAttachment_FullMethodName = "/proto.Attachments/GetAttachment"
Attachments_CheckAttachmentExists_FullMethodName = "/proto.Attachments/CheckAttachmentExists"
)
// AttachmentsClient is the client API for Attachments service.
//
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
type AttachmentsClient interface {
GetAttachment(ctx context.Context, in *AttachmentLookupRequest, opts ...grpc.CallOption) (*Attachment, error)
CheckAttachmentExists(ctx context.Context, in *AttachmentLookupRequest, opts ...grpc.CallOption) (*emptypb.Empty, error)
}
type attachmentsClient struct {
cc grpc.ClientConnInterface
}
func NewAttachmentsClient(cc grpc.ClientConnInterface) AttachmentsClient {
return &attachmentsClient{cc}
}
func (c *attachmentsClient) GetAttachment(ctx context.Context, in *AttachmentLookupRequest, opts ...grpc.CallOption) (*Attachment, error) {
out := new(Attachment)
err := c.cc.Invoke(ctx, Attachments_GetAttachment_FullMethodName, in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *attachmentsClient) CheckAttachmentExists(ctx context.Context, in *AttachmentLookupRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) {
out := new(emptypb.Empty)
err := c.cc.Invoke(ctx, Attachments_CheckAttachmentExists_FullMethodName, in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
// AttachmentsServer is the server API for Attachments service.
// All implementations must embed UnimplementedAttachmentsServer
// for forward compatibility
type AttachmentsServer interface {
GetAttachment(context.Context, *AttachmentLookupRequest) (*Attachment, error)
CheckAttachmentExists(context.Context, *AttachmentLookupRequest) (*emptypb.Empty, error)
mustEmbedUnimplementedAttachmentsServer()
}
// UnimplementedAttachmentsServer must be embedded to have forward compatible implementations.
type UnimplementedAttachmentsServer struct {
}
func (UnimplementedAttachmentsServer) GetAttachment(context.Context, *AttachmentLookupRequest) (*Attachment, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetAttachment not implemented")
}
func (UnimplementedAttachmentsServer) CheckAttachmentExists(context.Context, *AttachmentLookupRequest) (*emptypb.Empty, error) {
return nil, status.Errorf(codes.Unimplemented, "method CheckAttachmentExists not implemented")
}
func (UnimplementedAttachmentsServer) mustEmbedUnimplementedAttachmentsServer() {}
// UnsafeAttachmentsServer may be embedded to opt out of forward compatibility for this service.
// Use of this interface is not recommended, as added methods to AttachmentsServer will
// result in compilation errors.
type UnsafeAttachmentsServer interface {
mustEmbedUnimplementedAttachmentsServer()
}
func RegisterAttachmentsServer(s grpc.ServiceRegistrar, srv AttachmentsServer) {
s.RegisterService(&Attachments_ServiceDesc, srv)
}
func _Attachments_GetAttachment_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(AttachmentLookupRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(AttachmentsServer).GetAttachment(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: Attachments_GetAttachment_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(AttachmentsServer).GetAttachment(ctx, req.(*AttachmentLookupRequest))
}
return interceptor(ctx, in, info, handler)
}
func _Attachments_CheckAttachmentExists_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(AttachmentLookupRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(AttachmentsServer).CheckAttachmentExists(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: Attachments_CheckAttachmentExists_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(AttachmentsServer).CheckAttachmentExists(ctx, req.(*AttachmentLookupRequest))
}
return interceptor(ctx, in, info, handler)
}
// Attachments_ServiceDesc is the grpc.ServiceDesc for Attachments service.
// It's only intended for direct use with grpc.RegisterService,
// and not to be introspected or modified (even as a copy)
var Attachments_ServiceDesc = grpc.ServiceDesc{
ServiceName: "proto.Attachments",
HandlerType: (*AttachmentsServer)(nil),
Methods: []grpc.MethodDesc{
{
MethodName: "GetAttachment",
Handler: _Attachments_GetAttachment_Handler,
},
{
MethodName: "CheckAttachmentExists",
Handler: _Attachments_CheckAttachmentExists_Handler,
},
},
Streams: []grpc.StreamDesc{},
Metadata: "attachments.proto",
}

View File

@ -5,7 +5,6 @@ grpc_bind = "0.0.0.0:7443"
domain = "usercontent.solsynth.dev" domain = "usercontent.solsynth.dev"
secret = "LtTjzAGFLshwXhN4ZD4nG5KlMv1MWcsvfv03TSZYnT1VhiAnLIZFTnHUwR0XhGgi" secret = "LtTjzAGFLshwXhN4ZD4nG5KlMv1MWcsvfv03TSZYnT1VhiAnLIZFTnHUwR0XhGgi"
preferred_destination = "local"
accepts_usage = ["p.avatar", "p.banner", "i.attachment", "m.attachment"] accepts_usage = ["p.avatar", "p.banner", "i.attachment", "m.attachment"]
[debug] [debug]
@ -25,11 +24,11 @@ refresh_token_duration = 2592000
dsn = "host=localhost user=postgres password=password dbname=hy_paperclip port=5432 sslmode=disable" dsn = "host=localhost user=postgres password=password dbname=hy_paperclip port=5432 sslmode=disable"
prefix = "paperclip_" prefix = "paperclip_"
[destinations.local] [destinations.temporary]
type = "local" type = "local"
path = "uploads" path = "uploads"
[destinations.s3] [destinations.permanent]
type = "s3" type = "s3"
bucket = "bucket" bucket = "bucket"
endpoint = "s3.ap-east-1.amazonaws.com" endpoint = "s3.ap-east-1.amazonaws.com"