diff --git a/api/chunk/v1alpha1/api.pb.go b/api/chunk/v1alpha1/api.pb.go index a53f29ad..485e6b44 100644 --- a/api/chunk/v1alpha1/api.pb.go +++ b/api/chunk/v1alpha1/api.pb.go @@ -917,6 +917,95 @@ func (x *GetSupportedMinecraftVersionsResponse) GetVersions() []string { return nil } +type UploadThumbnailRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + ChunkId string `protobuf:"bytes,1,opt,name=chunk_id,json=chunkId,proto3" json:"chunk_id,omitempty"` + Image []byte `protobuf:"bytes,2,opt,name=image,proto3" json:"image,omitempty"` +} + +func (x *UploadThumbnailRequest) Reset() { + *x = UploadThumbnailRequest{} + mi := &file_chunk_v1alpha1_api_proto_msgTypes[18] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *UploadThumbnailRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UploadThumbnailRequest) ProtoMessage() {} + +func (x *UploadThumbnailRequest) ProtoReflect() protoreflect.Message { + mi := &file_chunk_v1alpha1_api_proto_msgTypes[18] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use UploadThumbnailRequest.ProtoReflect.Descriptor instead. +func (*UploadThumbnailRequest) Descriptor() ([]byte, []int) { + return file_chunk_v1alpha1_api_proto_rawDescGZIP(), []int{18} +} + +func (x *UploadThumbnailRequest) GetChunkId() string { + if x != nil { + return x.ChunkId + } + return "" +} + +func (x *UploadThumbnailRequest) GetImage() []byte { + if x != nil { + return x.Image + } + return nil +} + +type UploadThumbnailResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *UploadThumbnailResponse) Reset() { + *x = UploadThumbnailResponse{} + mi := &file_chunk_v1alpha1_api_proto_msgTypes[19] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *UploadThumbnailResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*UploadThumbnailResponse) ProtoMessage() {} + +func (x *UploadThumbnailResponse) ProtoReflect() protoreflect.Message { + mi := &file_chunk_v1alpha1_api_proto_msgTypes[19] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use UploadThumbnailResponse.ProtoReflect.Descriptor instead. +func (*UploadThumbnailResponse) Descriptor() ([]byte, []int) { + return file_chunk_v1alpha1_api_proto_rawDescGZIP(), []int{19} +} + var File_chunk_v1alpha1_api_proto protoreflect.FileDescriptor var file_chunk_v1alpha1_api_proto_rawDesc = []byte{ @@ -1013,70 +1102,83 @@ var file_chunk_v1alpha1_api_proto_rawDesc = []byte{ 0x6f, 0x72, 0x74, 0x65, 0x64, 0x4d, 0x69, 0x6e, 0x65, 0x63, 0x72, 0x61, 0x66, 0x74, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, - 0x52, 0x08, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x32, 0x84, 0x07, 0x0a, 0x0c, 0x43, - 0x68, 0x75, 0x6e, 0x6b, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x56, 0x0a, 0x0b, 0x43, - 0x72, 0x65, 0x61, 0x74, 0x65, 0x43, 0x68, 0x75, 0x6e, 0x6b, 0x12, 0x22, 0x2e, 0x63, 0x68, 0x75, - 0x6e, 0x6b, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, - 0x74, 0x65, 0x43, 0x68, 0x75, 0x6e, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, + 0x52, 0x08, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0x49, 0x0a, 0x16, 0x55, 0x70, + 0x6c, 0x6f, 0x61, 0x64, 0x54, 0x68, 0x75, 0x6d, 0x62, 0x6e, 0x61, 0x69, 0x6c, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x12, 0x19, 0x0a, 0x08, 0x63, 0x68, 0x75, 0x6e, 0x6b, 0x5f, 0x69, 0x64, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x63, 0x68, 0x75, 0x6e, 0x6b, 0x49, 0x64, 0x12, + 0x14, 0x0a, 0x05, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, + 0x69, 0x6d, 0x61, 0x67, 0x65, 0x22, 0x19, 0x0a, 0x17, 0x55, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x54, + 0x68, 0x75, 0x6d, 0x62, 0x6e, 0x61, 0x69, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x32, 0xe8, 0x07, 0x0a, 0x0c, 0x43, 0x68, 0x75, 0x6e, 0x6b, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, + 0x65, 0x12, 0x56, 0x0a, 0x0b, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x43, 0x68, 0x75, 0x6e, 0x6b, + 0x12, 0x22, 0x2e, 0x63, 0x68, 0x75, 0x6e, 0x6b, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, + 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x43, 0x68, 0x75, 0x6e, 0x6b, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e, 0x63, 0x68, 0x75, 0x6e, 0x6b, 0x2e, 0x76, 0x31, 0x61, + 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x43, 0x68, 0x75, 0x6e, + 0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x4d, 0x0a, 0x08, 0x47, 0x65, 0x74, + 0x43, 0x68, 0x75, 0x6e, 0x6b, 0x12, 0x1f, 0x2e, 0x63, 0x68, 0x75, 0x6e, 0x6b, 0x2e, 0x76, 0x31, + 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x43, 0x68, 0x75, 0x6e, 0x6b, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x63, 0x68, 0x75, 0x6e, 0x6b, 0x2e, 0x76, + 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x43, 0x68, 0x75, 0x6e, 0x6b, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x56, 0x0a, 0x0b, 0x55, 0x70, 0x64, 0x61, + 0x74, 0x65, 0x43, 0x68, 0x75, 0x6e, 0x6b, 0x12, 0x22, 0x2e, 0x63, 0x68, 0x75, 0x6e, 0x6b, 0x2e, + 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x43, + 0x68, 0x75, 0x6e, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e, 0x63, 0x68, + 0x75, 0x6e, 0x6b, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x55, 0x70, 0x64, + 0x61, 0x74, 0x65, 0x43, 0x68, 0x75, 0x6e, 0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x53, 0x0a, 0x0a, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x68, 0x75, 0x6e, 0x6b, 0x73, 0x12, 0x21, 0x2e, 0x63, 0x68, 0x75, 0x6e, 0x6b, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, - 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x43, 0x68, 0x75, 0x6e, 0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x12, 0x4d, 0x0a, 0x08, 0x47, 0x65, 0x74, 0x43, 0x68, 0x75, 0x6e, 0x6b, 0x12, - 0x1f, 0x2e, 0x63, 0x68, 0x75, 0x6e, 0x6b, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, - 0x2e, 0x47, 0x65, 0x74, 0x43, 0x68, 0x75, 0x6e, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x20, 0x2e, 0x63, 0x68, 0x75, 0x6e, 0x6b, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, - 0x31, 0x2e, 0x47, 0x65, 0x74, 0x43, 0x68, 0x75, 0x6e, 0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x12, 0x56, 0x0a, 0x0b, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x43, 0x68, 0x75, 0x6e, - 0x6b, 0x12, 0x22, 0x2e, 0x63, 0x68, 0x75, 0x6e, 0x6b, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, - 0x61, 0x31, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x43, 0x68, 0x75, 0x6e, 0x6b, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e, 0x63, 0x68, 0x75, 0x6e, 0x6b, 0x2e, 0x76, 0x31, - 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x43, 0x68, 0x75, - 0x6e, 0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x53, 0x0a, 0x0a, 0x4c, 0x69, - 0x73, 0x74, 0x43, 0x68, 0x75, 0x6e, 0x6b, 0x73, 0x12, 0x21, 0x2e, 0x63, 0x68, 0x75, 0x6e, 0x6b, - 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x68, - 0x75, 0x6e, 0x6b, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x22, 0x2e, 0x63, 0x68, - 0x75, 0x6e, 0x6b, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x4c, 0x69, 0x73, - 0x74, 0x43, 0x68, 0x75, 0x6e, 0x6b, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, - 0x59, 0x0a, 0x0c, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x46, 0x6c, 0x61, 0x76, 0x6f, 0x72, 0x12, - 0x23, 0x2e, 0x63, 0x68, 0x75, 0x6e, 0x6b, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, - 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x46, 0x6c, 0x61, 0x76, 0x6f, 0x72, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x1a, 0x24, 0x2e, 0x63, 0x68, 0x75, 0x6e, 0x6b, 0x2e, 0x76, 0x31, 0x61, - 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x46, 0x6c, 0x61, 0x76, - 0x6f, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x6e, 0x0a, 0x13, 0x43, 0x72, - 0x65, 0x61, 0x74, 0x65, 0x46, 0x6c, 0x61, 0x76, 0x6f, 0x72, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, - 0x6e, 0x12, 0x2a, 0x2e, 0x63, 0x68, 0x75, 0x6e, 0x6b, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, - 0x61, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x46, 0x6c, 0x61, 0x76, 0x6f, 0x72, 0x56, - 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2b, 0x2e, - 0x63, 0x68, 0x75, 0x6e, 0x6b, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x43, - 0x72, 0x65, 0x61, 0x74, 0x65, 0x46, 0x6c, 0x61, 0x76, 0x6f, 0x72, 0x56, 0x65, 0x72, 0x73, 0x69, - 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x6b, 0x0a, 0x12, 0x42, 0x75, - 0x69, 0x6c, 0x64, 0x46, 0x6c, 0x61, 0x76, 0x6f, 0x72, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, - 0x12, 0x29, 0x2e, 0x63, 0x68, 0x75, 0x6e, 0x6b, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, - 0x31, 0x2e, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x46, 0x6c, 0x61, 0x76, 0x6f, 0x72, 0x56, 0x65, 0x72, - 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2a, 0x2e, 0x63, 0x68, - 0x75, 0x6e, 0x6b, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x42, 0x75, 0x69, - 0x6c, 0x64, 0x46, 0x6c, 0x61, 0x76, 0x6f, 0x72, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x59, 0x0a, 0x0c, 0x47, 0x65, 0x74, 0x55, 0x70, - 0x6c, 0x6f, 0x61, 0x64, 0x55, 0x52, 0x4c, 0x12, 0x23, 0x2e, 0x63, 0x68, 0x75, 0x6e, 0x6b, 0x2e, - 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x55, 0x70, 0x6c, 0x6f, - 0x61, 0x64, 0x55, 0x52, 0x4c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x24, 0x2e, 0x63, - 0x68, 0x75, 0x6e, 0x6b, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x47, 0x65, - 0x74, 0x55, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x55, 0x52, 0x4c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x12, 0x8c, 0x01, 0x0a, 0x1d, 0x47, 0x65, 0x74, 0x53, 0x75, 0x70, 0x70, 0x6f, 0x72, - 0x74, 0x65, 0x64, 0x4d, 0x69, 0x6e, 0x65, 0x63, 0x72, 0x61, 0x66, 0x74, 0x56, 0x65, 0x72, 0x73, - 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x34, 0x2e, 0x63, 0x68, 0x75, 0x6e, 0x6b, 0x2e, 0x76, 0x31, 0x61, - 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x53, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, - 0x65, 0x64, 0x4d, 0x69, 0x6e, 0x65, 0x63, 0x72, 0x61, 0x66, 0x74, 0x56, 0x65, 0x72, 0x73, 0x69, - 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x35, 0x2e, 0x63, 0x68, 0x75, + 0x4c, 0x69, 0x73, 0x74, 0x43, 0x68, 0x75, 0x6e, 0x6b, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x22, 0x2e, 0x63, 0x68, 0x75, 0x6e, 0x6b, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, + 0x61, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x43, 0x68, 0x75, 0x6e, 0x6b, 0x73, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x59, 0x0a, 0x0c, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x46, + 0x6c, 0x61, 0x76, 0x6f, 0x72, 0x12, 0x23, 0x2e, 0x63, 0x68, 0x75, 0x6e, 0x6b, 0x2e, 0x76, 0x31, + 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x46, 0x6c, 0x61, + 0x76, 0x6f, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x24, 0x2e, 0x63, 0x68, 0x75, + 0x6e, 0x6b, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, + 0x74, 0x65, 0x46, 0x6c, 0x61, 0x76, 0x6f, 0x72, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x6e, 0x0a, 0x13, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x46, 0x6c, 0x61, 0x76, 0x6f, 0x72, + 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x2a, 0x2e, 0x63, 0x68, 0x75, 0x6e, 0x6b, 0x2e, + 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x46, + 0x6c, 0x61, 0x76, 0x6f, 0x72, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x1a, 0x2b, 0x2e, 0x63, 0x68, 0x75, 0x6e, 0x6b, 0x2e, 0x76, 0x31, 0x61, 0x6c, + 0x70, 0x68, 0x61, 0x31, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x46, 0x6c, 0x61, 0x76, 0x6f, + 0x72, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x6b, 0x0a, 0x12, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x46, 0x6c, 0x61, 0x76, 0x6f, 0x72, 0x56, + 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x29, 0x2e, 0x63, 0x68, 0x75, 0x6e, 0x6b, 0x2e, 0x76, + 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x46, 0x6c, 0x61, + 0x76, 0x6f, 0x72, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x2a, 0x2e, 0x63, 0x68, 0x75, 0x6e, 0x6b, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, + 0x61, 0x31, 0x2e, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x46, 0x6c, 0x61, 0x76, 0x6f, 0x72, 0x56, 0x65, + 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x59, 0x0a, + 0x0c, 0x47, 0x65, 0x74, 0x55, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x55, 0x52, 0x4c, 0x12, 0x23, 0x2e, + 0x63, 0x68, 0x75, 0x6e, 0x6b, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x47, + 0x65, 0x74, 0x55, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x55, 0x52, 0x4c, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x24, 0x2e, 0x63, 0x68, 0x75, 0x6e, 0x6b, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, + 0x68, 0x61, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x55, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x55, 0x52, 0x4c, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x8c, 0x01, 0x0a, 0x1d, 0x47, 0x65, 0x74, + 0x53, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x4d, 0x69, 0x6e, 0x65, 0x63, 0x72, 0x61, + 0x66, 0x74, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x34, 0x2e, 0x63, 0x68, 0x75, 0x6e, 0x6b, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x47, 0x65, 0x74, 0x53, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x4d, 0x69, 0x6e, 0x65, 0x63, 0x72, 0x61, 0x66, - 0x74, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x42, 0x5e, 0x0a, 0x28, 0x63, 0x68, 0x75, 0x6e, 0x6b, 0x73, 0x2e, 0x73, 0x70, 0x61, 0x63, - 0x65, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x65, 0x78, 0x70, 0x6c, 0x6f, 0x72, 0x65, 0x72, 0x2e, 0x63, - 0x68, 0x75, 0x6e, 0x6b, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x5a, 0x32, 0x67, - 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x73, 0x70, 0x61, 0x63, 0x65, 0x63, - 0x68, 0x75, 0x6e, 0x6b, 0x73, 0x2f, 0x65, 0x78, 0x70, 0x6c, 0x6f, 0x72, 0x65, 0x72, 0x2f, 0x61, - 0x70, 0x69, 0x2f, 0x63, 0x68, 0x75, 0x6e, 0x6b, 0x2f, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, - 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x74, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x35, 0x2e, 0x63, 0x68, 0x75, 0x6e, 0x6b, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, + 0x31, 0x2e, 0x47, 0x65, 0x74, 0x53, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x4d, 0x69, + 0x6e, 0x65, 0x63, 0x72, 0x61, 0x66, 0x74, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x62, 0x0a, 0x0f, 0x55, 0x70, 0x6c, 0x6f, 0x61, + 0x64, 0x54, 0x68, 0x75, 0x6d, 0x62, 0x6e, 0x61, 0x69, 0x6c, 0x12, 0x26, 0x2e, 0x63, 0x68, 0x75, + 0x6e, 0x6b, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x55, 0x70, 0x6c, 0x6f, + 0x61, 0x64, 0x54, 0x68, 0x75, 0x6d, 0x62, 0x6e, 0x61, 0x69, 0x6c, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x27, 0x2e, 0x63, 0x68, 0x75, 0x6e, 0x6b, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, + 0x68, 0x61, 0x31, 0x2e, 0x55, 0x70, 0x6c, 0x6f, 0x61, 0x64, 0x54, 0x68, 0x75, 0x6d, 0x62, 0x6e, + 0x61, 0x69, 0x6c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x5e, 0x0a, 0x28, 0x63, + 0x68, 0x75, 0x6e, 0x6b, 0x73, 0x2e, 0x73, 0x70, 0x61, 0x63, 0x65, 0x2e, 0x61, 0x70, 0x69, 0x2e, + 0x65, 0x78, 0x70, 0x6c, 0x6f, 0x72, 0x65, 0x72, 0x2e, 0x63, 0x68, 0x75, 0x6e, 0x6b, 0x2e, 0x76, + 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x5a, 0x32, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, + 0x63, 0x6f, 0x6d, 0x2f, 0x73, 0x70, 0x61, 0x63, 0x65, 0x63, 0x68, 0x75, 0x6e, 0x6b, 0x73, 0x2f, + 0x65, 0x78, 0x70, 0x6c, 0x6f, 0x72, 0x65, 0x72, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x63, 0x68, 0x75, + 0x6e, 0x6b, 0x2f, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x33, } var ( @@ -1091,7 +1193,7 @@ func file_chunk_v1alpha1_api_proto_rawDescGZIP() []byte { return file_chunk_v1alpha1_api_proto_rawDescData } -var file_chunk_v1alpha1_api_proto_msgTypes = make([]protoimpl.MessageInfo, 18) +var file_chunk_v1alpha1_api_proto_msgTypes = make([]protoimpl.MessageInfo, 20) var file_chunk_v1alpha1_api_proto_goTypes = []any{ (*CreateChunkRequest)(nil), // 0: chunk.v1alpha1.CreateChunkRequest (*CreateChunkResponse)(nil), // 1: chunk.v1alpha1.CreateChunkResponse @@ -1111,22 +1213,24 @@ var file_chunk_v1alpha1_api_proto_goTypes = []any{ (*GetUploadURLResponse)(nil), // 15: chunk.v1alpha1.GetUploadURLResponse (*GetSupportedMinecraftVersionsRequest)(nil), // 16: chunk.v1alpha1.GetSupportedMinecraftVersionsRequest (*GetSupportedMinecraftVersionsResponse)(nil), // 17: chunk.v1alpha1.GetSupportedMinecraftVersionsResponse - (*Chunk)(nil), // 18: chunk.v1alpha1.Chunk - (*Flavor)(nil), // 19: chunk.v1alpha1.Flavor - (*FlavorVersion)(nil), // 20: chunk.v1alpha1.FlavorVersion - (*FileHashes)(nil), // 21: chunk.v1alpha1.FileHashes + (*UploadThumbnailRequest)(nil), // 18: chunk.v1alpha1.UploadThumbnailRequest + (*UploadThumbnailResponse)(nil), // 19: chunk.v1alpha1.UploadThumbnailResponse + (*Chunk)(nil), // 20: chunk.v1alpha1.Chunk + (*Flavor)(nil), // 21: chunk.v1alpha1.Flavor + (*FlavorVersion)(nil), // 22: chunk.v1alpha1.FlavorVersion + (*FileHashes)(nil), // 23: chunk.v1alpha1.FileHashes } var file_chunk_v1alpha1_api_proto_depIdxs = []int32{ - 18, // 0: chunk.v1alpha1.CreateChunkResponse.chunk:type_name -> chunk.v1alpha1.Chunk - 18, // 1: chunk.v1alpha1.GetChunkResponse.chunk:type_name -> chunk.v1alpha1.Chunk - 18, // 2: chunk.v1alpha1.UpdateChunkResponse.chunk:type_name -> chunk.v1alpha1.Chunk - 18, // 3: chunk.v1alpha1.ListChunksResponse.chunks:type_name -> chunk.v1alpha1.Chunk - 19, // 4: chunk.v1alpha1.CreateFlavorResponse.flavor:type_name -> chunk.v1alpha1.Flavor - 20, // 5: chunk.v1alpha1.CreateFlavorVersionRequest.version:type_name -> chunk.v1alpha1.FlavorVersion - 20, // 6: chunk.v1alpha1.CreateFlavorVersionResponse.version:type_name -> chunk.v1alpha1.FlavorVersion - 21, // 7: chunk.v1alpha1.CreateFlavorVersionResponse.changed_files:type_name -> chunk.v1alpha1.FileHashes - 21, // 8: chunk.v1alpha1.CreateFlavorVersionResponse.removed_files:type_name -> chunk.v1alpha1.FileHashes - 21, // 9: chunk.v1alpha1.CreateFlavorVersionResponse.added_files:type_name -> chunk.v1alpha1.FileHashes + 20, // 0: chunk.v1alpha1.CreateChunkResponse.chunk:type_name -> chunk.v1alpha1.Chunk + 20, // 1: chunk.v1alpha1.GetChunkResponse.chunk:type_name -> chunk.v1alpha1.Chunk + 20, // 2: chunk.v1alpha1.UpdateChunkResponse.chunk:type_name -> chunk.v1alpha1.Chunk + 20, // 3: chunk.v1alpha1.ListChunksResponse.chunks:type_name -> chunk.v1alpha1.Chunk + 21, // 4: chunk.v1alpha1.CreateFlavorResponse.flavor:type_name -> chunk.v1alpha1.Flavor + 22, // 5: chunk.v1alpha1.CreateFlavorVersionRequest.version:type_name -> chunk.v1alpha1.FlavorVersion + 22, // 6: chunk.v1alpha1.CreateFlavorVersionResponse.version:type_name -> chunk.v1alpha1.FlavorVersion + 23, // 7: chunk.v1alpha1.CreateFlavorVersionResponse.changed_files:type_name -> chunk.v1alpha1.FileHashes + 23, // 8: chunk.v1alpha1.CreateFlavorVersionResponse.removed_files:type_name -> chunk.v1alpha1.FileHashes + 23, // 9: chunk.v1alpha1.CreateFlavorVersionResponse.added_files:type_name -> chunk.v1alpha1.FileHashes 0, // 10: chunk.v1alpha1.ChunkService.CreateChunk:input_type -> chunk.v1alpha1.CreateChunkRequest 2, // 11: chunk.v1alpha1.ChunkService.GetChunk:input_type -> chunk.v1alpha1.GetChunkRequest 4, // 12: chunk.v1alpha1.ChunkService.UpdateChunk:input_type -> chunk.v1alpha1.UpdateChunkRequest @@ -1136,17 +1240,19 @@ var file_chunk_v1alpha1_api_proto_depIdxs = []int32{ 12, // 16: chunk.v1alpha1.ChunkService.BuildFlavorVersion:input_type -> chunk.v1alpha1.BuildFlavorVersionRequest 14, // 17: chunk.v1alpha1.ChunkService.GetUploadURL:input_type -> chunk.v1alpha1.GetUploadURLRequest 16, // 18: chunk.v1alpha1.ChunkService.GetSupportedMinecraftVersions:input_type -> chunk.v1alpha1.GetSupportedMinecraftVersionsRequest - 1, // 19: chunk.v1alpha1.ChunkService.CreateChunk:output_type -> chunk.v1alpha1.CreateChunkResponse - 3, // 20: chunk.v1alpha1.ChunkService.GetChunk:output_type -> chunk.v1alpha1.GetChunkResponse - 5, // 21: chunk.v1alpha1.ChunkService.UpdateChunk:output_type -> chunk.v1alpha1.UpdateChunkResponse - 7, // 22: chunk.v1alpha1.ChunkService.ListChunks:output_type -> chunk.v1alpha1.ListChunksResponse - 9, // 23: chunk.v1alpha1.ChunkService.CreateFlavor:output_type -> chunk.v1alpha1.CreateFlavorResponse - 11, // 24: chunk.v1alpha1.ChunkService.CreateFlavorVersion:output_type -> chunk.v1alpha1.CreateFlavorVersionResponse - 13, // 25: chunk.v1alpha1.ChunkService.BuildFlavorVersion:output_type -> chunk.v1alpha1.BuildFlavorVersionResponse - 15, // 26: chunk.v1alpha1.ChunkService.GetUploadURL:output_type -> chunk.v1alpha1.GetUploadURLResponse - 17, // 27: chunk.v1alpha1.ChunkService.GetSupportedMinecraftVersions:output_type -> chunk.v1alpha1.GetSupportedMinecraftVersionsResponse - 19, // [19:28] is the sub-list for method output_type - 10, // [10:19] is the sub-list for method input_type + 18, // 19: chunk.v1alpha1.ChunkService.UploadThumbnail:input_type -> chunk.v1alpha1.UploadThumbnailRequest + 1, // 20: chunk.v1alpha1.ChunkService.CreateChunk:output_type -> chunk.v1alpha1.CreateChunkResponse + 3, // 21: chunk.v1alpha1.ChunkService.GetChunk:output_type -> chunk.v1alpha1.GetChunkResponse + 5, // 22: chunk.v1alpha1.ChunkService.UpdateChunk:output_type -> chunk.v1alpha1.UpdateChunkResponse + 7, // 23: chunk.v1alpha1.ChunkService.ListChunks:output_type -> chunk.v1alpha1.ListChunksResponse + 9, // 24: chunk.v1alpha1.ChunkService.CreateFlavor:output_type -> chunk.v1alpha1.CreateFlavorResponse + 11, // 25: chunk.v1alpha1.ChunkService.CreateFlavorVersion:output_type -> chunk.v1alpha1.CreateFlavorVersionResponse + 13, // 26: chunk.v1alpha1.ChunkService.BuildFlavorVersion:output_type -> chunk.v1alpha1.BuildFlavorVersionResponse + 15, // 27: chunk.v1alpha1.ChunkService.GetUploadURL:output_type -> chunk.v1alpha1.GetUploadURLResponse + 17, // 28: chunk.v1alpha1.ChunkService.GetSupportedMinecraftVersions:output_type -> chunk.v1alpha1.GetSupportedMinecraftVersionsResponse + 19, // 29: chunk.v1alpha1.ChunkService.UploadThumbnail:output_type -> chunk.v1alpha1.UploadThumbnailResponse + 20, // [20:30] is the sub-list for method output_type + 10, // [10:20] is the sub-list for method input_type 10, // [10:10] is the sub-list for extension type_name 10, // [10:10] is the sub-list for extension extendee 0, // [0:10] is the sub-list for field type_name @@ -1164,7 +1270,7 @@ func file_chunk_v1alpha1_api_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_chunk_v1alpha1_api_proto_rawDesc, NumEnums: 0, - NumMessages: 18, + NumMessages: 20, NumExtensions: 0, NumServices: 1, }, diff --git a/api/chunk/v1alpha1/api.proto b/api/chunk/v1alpha1/api.proto index bce8d156..32213f8f 100644 --- a/api/chunk/v1alpha1/api.proto +++ b/api/chunk/v1alpha1/api.proto @@ -120,6 +120,16 @@ service ChunkService { rpc GetUploadURL(GetUploadURLRequest) returns (GetUploadURLResponse); rpc GetSupportedMinecraftVersions(GetSupportedMinecraftVersionsRequest) returns (GetSupportedMinecraftVersionsResponse); + + // UploadThumbnail uploads the given PNG image. Formats other than PNG are not supported. + // + // Defined error codes: + // - INVALID_ARGUMENT: + // - chunk id is invalid + // - thumbnail image must be PNG + // - thumbnail size must be 512x512 pixels + // - thumbnail size too big + rpc UploadThumbnail(UploadThumbnailRequest) returns (UploadThumbnailResponse); } message CreateChunkRequest { @@ -211,4 +221,14 @@ message GetSupportedMinecraftVersionsRequest { message GetSupportedMinecraftVersionsResponse { repeated string versions = 1; -} \ No newline at end of file +} + +message UploadThumbnailRequest { + string chunk_id = 1; + + // image is the raw image bytes + bytes image = 2; +} + +message UploadThumbnailResponse { +} diff --git a/api/chunk/v1alpha1/api_grpc.pb.go b/api/chunk/v1alpha1/api_grpc.pb.go index 8e0c0611..5ea6f11a 100644 --- a/api/chunk/v1alpha1/api_grpc.pb.go +++ b/api/chunk/v1alpha1/api_grpc.pb.go @@ -46,6 +46,7 @@ const ( ChunkService_BuildFlavorVersion_FullMethodName = "/chunk.v1alpha1.ChunkService/BuildFlavorVersion" ChunkService_GetUploadURL_FullMethodName = "/chunk.v1alpha1.ChunkService/GetUploadURL" ChunkService_GetSupportedMinecraftVersions_FullMethodName = "/chunk.v1alpha1.ChunkService/GetSupportedMinecraftVersions" + ChunkService_UploadThumbnail_FullMethodName = "/chunk.v1alpha1.ChunkService/UploadThumbnail" ) // ChunkServiceClient is the client API for ChunkService service. @@ -144,6 +145,7 @@ type ChunkServiceClient interface { // documentation of GetUploadURLRequest GetUploadURL(ctx context.Context, in *GetUploadURLRequest, opts ...grpc.CallOption) (*GetUploadURLResponse, error) GetSupportedMinecraftVersions(ctx context.Context, in *GetSupportedMinecraftVersionsRequest, opts ...grpc.CallOption) (*GetSupportedMinecraftVersionsResponse, error) + UploadThumbnail(ctx context.Context, in *UploadThumbnailRequest, opts ...grpc.CallOption) (*UploadThumbnailResponse, error) } type chunkServiceClient struct { @@ -244,6 +246,16 @@ func (c *chunkServiceClient) GetSupportedMinecraftVersions(ctx context.Context, return out, nil } +func (c *chunkServiceClient) UploadThumbnail(ctx context.Context, in *UploadThumbnailRequest, opts ...grpc.CallOption) (*UploadThumbnailResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(UploadThumbnailResponse) + err := c.cc.Invoke(ctx, ChunkService_UploadThumbnail_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + // ChunkServiceServer is the server API for ChunkService service. // All implementations must embed UnimplementedChunkServiceServer // for forward compatibility. @@ -340,6 +352,7 @@ type ChunkServiceServer interface { // documentation of GetUploadURLRequest GetUploadURL(context.Context, *GetUploadURLRequest) (*GetUploadURLResponse, error) GetSupportedMinecraftVersions(context.Context, *GetSupportedMinecraftVersionsRequest) (*GetSupportedMinecraftVersionsResponse, error) + UploadThumbnail(context.Context, *UploadThumbnailRequest) (*UploadThumbnailResponse, error) mustEmbedUnimplementedChunkServiceServer() } @@ -377,6 +390,9 @@ func (UnimplementedChunkServiceServer) GetUploadURL(context.Context, *GetUploadU func (UnimplementedChunkServiceServer) GetSupportedMinecraftVersions(context.Context, *GetSupportedMinecraftVersionsRequest) (*GetSupportedMinecraftVersionsResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method GetSupportedMinecraftVersions not implemented") } +func (UnimplementedChunkServiceServer) UploadThumbnail(context.Context, *UploadThumbnailRequest) (*UploadThumbnailResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method UploadThumbnail not implemented") +} func (UnimplementedChunkServiceServer) mustEmbedUnimplementedChunkServiceServer() {} func (UnimplementedChunkServiceServer) testEmbeddedByValue() {} @@ -560,6 +576,24 @@ func _ChunkService_GetSupportedMinecraftVersions_Handler(srv interface{}, ctx co return interceptor(ctx, in, info, handler) } +func _ChunkService_UploadThumbnail_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(UploadThumbnailRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ChunkServiceServer).UploadThumbnail(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: ChunkService_UploadThumbnail_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ChunkServiceServer).UploadThumbnail(ctx, req.(*UploadThumbnailRequest)) + } + return interceptor(ctx, in, info, handler) +} + // ChunkService_ServiceDesc is the grpc.ServiceDesc for ChunkService service. // It's only intended for direct use with grpc.RegisterService, // and not to be introspected or modified (even as a copy) @@ -603,6 +637,10 @@ var ChunkService_ServiceDesc = grpc.ServiceDesc{ MethodName: "GetSupportedMinecraftVersions", Handler: _ChunkService_GetSupportedMinecraftVersions_Handler, }, + { + MethodName: "UploadThumbnail", + Handler: _ChunkService_UploadThumbnail_Handler, + }, }, Streams: []grpc.StreamDesc{}, Metadata: "chunk/v1alpha1/api.proto", diff --git a/api/user/v1alpha1/api.pb.go b/api/user/v1alpha1/api.pb.go index 0d7263d3..4ec903ae 100644 --- a/api/user/v1alpha1/api.pb.go +++ b/api/user/v1alpha1/api.pb.go @@ -259,11 +259,13 @@ var file_user_v1alpha1_api_proto_rawDesc = []byte{ 0x72, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x33, 0x5a, 0x31, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, - 0x63, 0x6f, 0x6d, 0x2f, 0x73, 0x70, 0x61, 0x63, 0x65, 0x63, 0x68, 0x75, 0x6e, 0x6b, 0x73, 0x2f, - 0x65, 0x78, 0x70, 0x6c, 0x6f, 0x72, 0x65, 0x72, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x75, 0x73, 0x65, - 0x72, 0x2f, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, - 0x6f, 0x33, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x5c, 0x0a, 0x27, 0x63, 0x68, 0x75, 0x6e, 0x6b, 0x73, 0x2e, + 0x73, 0x70, 0x61, 0x63, 0x65, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x65, 0x78, 0x70, 0x6c, 0x6f, 0x72, + 0x65, 0x72, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, + 0x5a, 0x31, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x73, 0x70, 0x61, + 0x63, 0x65, 0x63, 0x68, 0x75, 0x6e, 0x6b, 0x73, 0x2f, 0x65, 0x78, 0x70, 0x6c, 0x6f, 0x72, 0x65, + 0x72, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x75, 0x73, 0x65, 0x72, 0x2f, 0x76, 0x31, 0x61, 0x6c, 0x70, + 0x68, 0x61, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( diff --git a/api/user/v1alpha1/types.pb.go b/api/user/v1alpha1/types.pb.go index 47d8a86a..c0eba61f 100644 --- a/api/user/v1alpha1/types.pb.go +++ b/api/user/v1alpha1/types.pb.go @@ -126,11 +126,13 @@ var file_user_v1alpha1_types_proto_rawDesc = []byte{ 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x75, 0x70, 0x64, - 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x42, 0x33, 0x5a, 0x31, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, - 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x73, 0x70, 0x61, 0x63, 0x65, 0x63, 0x68, 0x75, 0x6e, 0x6b, 0x73, - 0x2f, 0x65, 0x78, 0x70, 0x6c, 0x6f, 0x72, 0x65, 0x72, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x75, 0x73, - 0x65, 0x72, 0x2f, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, - 0x74, 0x6f, 0x33, + 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x42, 0x5c, 0x0a, 0x27, 0x63, 0x68, 0x75, 0x6e, 0x6b, 0x73, + 0x2e, 0x73, 0x70, 0x61, 0x63, 0x65, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x65, 0x78, 0x70, 0x6c, 0x6f, + 0x72, 0x65, 0x72, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, + 0x31, 0x5a, 0x31, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x73, 0x70, + 0x61, 0x63, 0x65, 0x63, 0x68, 0x75, 0x6e, 0x6b, 0x73, 0x2f, 0x65, 0x78, 0x70, 0x6c, 0x6f, 0x72, + 0x65, 0x72, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x75, 0x73, 0x65, 0x72, 0x2f, 0x76, 0x31, 0x61, 0x6c, + 0x70, 0x68, 0x61, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( diff --git a/cmd/cli/cli b/cmd/cli/cli new file mode 100755 index 00000000..d23f7b3f Binary files /dev/null and b/cmd/cli/cli differ diff --git a/cmd/controlplane/main.go b/cmd/controlplane/main.go index bba272f7..a60ad1cd 100644 --- a/cmd/controlplane/main.go +++ b/cmd/controlplane/main.go @@ -59,6 +59,7 @@ func main() { apiTokenIssuer = fs.String("api-token-issuer", "", "issuer to use for api tokens issued by the control plane. this value will also be set as the tokens audience.") //nolint:lll apiTokenExpiry = fs.Duration("api-token-expiry", 10*time.Minute, "expiry of api tokens issued by the control plane") //nolint:lll apiTokenSigningKey = fs.String("api-token-signing-key", "", "key used to sign api tokens issued by the control plane") //nolint:lll + thumbnailMaxSizeKB = fs.Int("thumbnail-max-size-kb", 1000, "max size a thumbnail can be in kilobytes") //nolint:lll ) if err := ff.Parse(fs, os.Args[1:], ff.WithEnvVarPrefix("CONTROLPLANE"), @@ -88,6 +89,7 @@ func main() { APITokenIssuer: *apiTokenIssuer, APITokenExpiry: *apiTokenExpiry, APITokenSigningKey: *apiTokenSigningKey, + ThumbnailMaxSizeKB: *thumbnailMaxSizeKB, } ctx = context.Background() server = controlplane.NewServer(logger, cfg) diff --git a/controlplane/chunk/chunk.go b/controlplane/chunk/chunk.go index 2fba9d32..79095a81 100644 --- a/controlplane/chunk/chunk.go +++ b/controlplane/chunk/chunk.go @@ -19,15 +19,21 @@ package chunk import ( + "bytes" "context" "errors" "fmt" + "image" + "io" "unicode/utf8" "github.com/spacechunks/explorer/controlplane/authz" + "github.com/spacechunks/explorer/controlplane/blob" "github.com/spacechunks/explorer/controlplane/contextkey" apierrs "github.com/spacechunks/explorer/controlplane/errors" "github.com/spacechunks/explorer/controlplane/resource" + + _ "image/png" ) func (s *svc) CreateChunk(ctx context.Context, chunk resource.Chunk) (resource.Chunk, error) { @@ -67,16 +73,8 @@ func (s *svc) UpdateChunk(ctx context.Context, new resource.Chunk) (resource.Chu return resource.Chunk{}, fmt.Errorf("get chunk: %w", err) } - actorID, ok := ctx.Value(contextkey.ActorID).(string) - if !ok { - return resource.Chunk{}, errors.New("actor_id not found in context") - } - - if err := s.access.AccessAuthorized( - ctx, - authz.WithOwnershipRule(actorID, authz.ChunkResourceDef(old.ID)), - ); err != nil { - return resource.Chunk{}, fmt.Errorf("access: %w", err) + if err := s.authorized(ctx, old.ID); err != nil { + return resource.Chunk{}, fmt.Errorf("authorize: %w", err) } if new.Name != "" { @@ -111,6 +109,47 @@ func (s *svc) GetSupportedMinecraftVersions(ctx context.Context) ([]string, erro return s.repo.SupportedMinecraftVersions(ctx) } +func (s *svc) UpdateThumbnail(ctx context.Context, chunkID string, imgData []byte) error { + if err := s.authorized(ctx, chunkID); err != nil { + return fmt.Errorf("authorize: %w", err) + } + + cfg, _, err := image.DecodeConfig(bytes.NewBuffer(imgData)) + if err != nil { + if errors.Is(err, image.ErrFormat) { + return apierrs.ErrInvalidThumbnailFormat + } + return fmt.Errorf("decode config: %w", err) + } + + if cfg.Width != 512 && cfg.Height != 512 { + return apierrs.ErrInvalidThumbnailDimensions + } + + if len(imgData)/1000 > s.cfg.ThumbnailMaxSizeKB { + return apierrs.ErrInvalidThumbnailSize + } + + obj := blob.Object{ + Data: nopReadSeekCloser{bytes.NewReader(imgData)}, + } + + h, err := obj.Hash() + if err != nil { + return fmt.Errorf("hash: %w", err) + } + + if err := s.repo.UpdateThumbnail(ctx, chunkID, h); err != nil { + return fmt.Errorf("db: %w", err) + } + + if err := s.s3Store.Put(ctx, blob.CASKeyPrefix, []blob.Object{obj}); err != nil { + return fmt.Errorf("put image: %w", err) + } + + return nil +} + func validateChunkFields(chunk resource.Chunk) error { // FIXME: // - remove hardcoded limits for tags @@ -129,3 +168,25 @@ func validateChunkFields(chunk resource.Chunk) error { return nil } + +func (s *svc) authorized(ctx context.Context, chunkID string) error { + actorID, ok := ctx.Value(contextkey.ActorID).(string) + if !ok { + return errors.New("actor_id not found in context") + } + + if err := s.access.AccessAuthorized( + ctx, + authz.WithOwnershipRule(actorID, authz.ChunkResourceDef(chunkID)), + ); err != nil { + return fmt.Errorf("access: %w", err) + } + + return nil +} + +type nopReadSeekCloser struct { + io.ReadSeeker +} + +func (nopReadSeekCloser) Close() error { return nil } diff --git a/controlplane/chunk/flavor_test.go b/controlplane/chunk/flavor_test.go index c79aa3e5..dc93e556 100644 --- a/controlplane/chunk/flavor_test.go +++ b/controlplane/chunk/flavor_test.go @@ -20,6 +20,8 @@ package chunk_test import ( "context" + "log/slog" + "os" "testing" "github.com/spacechunks/explorer/controlplane/chunk" @@ -88,7 +90,14 @@ func TestCreateFlavor(t *testing.T) { ctx = context.Background() mockRepo = mock.NewMockChunkRepository(t) mockAccess = mock.NewMockAuthzAccessEvaluator(t) - svc = chunk.NewService(mockRepo, nil, nil, mockAccess, chunk.Config{}) + svc = chunk.NewService( + slog.New(slog.NewTextHandler(os.Stdout, nil)), + mockRepo, + nil, + nil, + mockAccess, + chunk.Config{}, + ) ) ctx = context.WithValue(ctx, contextkey.ActorID, "blabla") @@ -299,7 +308,14 @@ func TestCreateFlavorVersion(t *testing.T) { ctx = context.Background() mockAccess = mock.NewMockAuthzAccessEvaluator(t) mockRepo = mock.NewMockChunkRepository(t) - svc = chunk.NewService(mockRepo, nil, nil, mockAccess, chunk.Config{}) + svc = chunk.NewService( + slog.New(slog.NewTextHandler(os.Stdout, nil)), + mockRepo, + nil, + nil, + mockAccess, + chunk.Config{}, + ) ) ctx = context.WithValue(ctx, contextkey.ActorID, "blabla") diff --git a/controlplane/chunk/repository.go b/controlplane/chunk/repository.go index 6ee2cabd..1a4b97d2 100644 --- a/controlplane/chunk/repository.go +++ b/controlplane/chunk/repository.go @@ -52,4 +52,5 @@ type Repository interface { UpdateFlavorVersionPresignedURLData(ctx context.Context, flavorVersionID string, date time.Time, url string) error SupportedMinecraftVersions(ctx context.Context) ([]string, error) MinecraftVersionExists(context.Context, string) (bool, error) + UpdateThumbnail(ctx context.Context, chunkID string, imgHash string) error } diff --git a/controlplane/chunk/server.go b/controlplane/chunk/server.go index a916a203..324dc959 100644 --- a/controlplane/chunk/server.go +++ b/controlplane/chunk/server.go @@ -220,3 +220,17 @@ func (s *Server) GetSupportedMinecraftVersions( Versions: versions, }, nil } + +func (s *Server) UploadThumbnail( + ctx context.Context, + req *chunkv1alpha1.UploadThumbnailRequest, +) (*chunkv1alpha1.UploadThumbnailResponse, error) { + if _, err := uuid.Parse(req.ChunkId); err != nil { + return nil, apierrs.ErrInvalidChunkID + } + + if err := s.service.UpdateThumbnail(ctx, req.ChunkId, req.Image); err != nil { + return nil, err + } + return &chunkv1alpha1.UploadThumbnailResponse{}, nil +} diff --git a/controlplane/chunk/service.go b/controlplane/chunk/service.go index 15e06b2a..bc829bfb 100644 --- a/controlplane/chunk/service.go +++ b/controlplane/chunk/service.go @@ -20,6 +20,7 @@ package chunk import ( "context" + "log/slog" "time" "github.com/spacechunks/explorer/controlplane/authz" @@ -42,6 +43,7 @@ type Service interface { BuildFlavorVersion(ctx context.Context, versionID string) error GetUploadURL(ctx context.Context, flavorVersionID string, tarballHash string) (string, error) GetSupportedMinecraftVersions(ctx context.Context) ([]string, error) + UpdateThumbnail(ctx context.Context, chunkID string, imageData []byte) error } type Config struct { @@ -49,9 +51,11 @@ type Config struct { BaseImage string Bucket string PresignedURLExpiry time.Duration + ThumbnailMaxSizeKB int } type svc struct { + logger *slog.Logger repo Repository jobClient job.Client s3Store blob.S3Store @@ -60,6 +64,7 @@ type svc struct { } func NewService( + logger *slog.Logger, repo Repository, jobClient job.Client, s3Store blob.S3Store, @@ -67,6 +72,7 @@ func NewService( cfg Config, ) Service { return &svc{ + logger: logger, repo: repo, jobClient: jobClient, s3Store: s3Store, diff --git a/controlplane/config.go b/controlplane/config.go index 1321553a..ef22cb8e 100644 --- a/controlplane/config.go +++ b/controlplane/config.go @@ -41,4 +41,5 @@ type Config struct { APITokenIssuer string APITokenExpiry time.Duration APITokenSigningKey string + ThumbnailMaxSizeKB int } diff --git a/controlplane/errors/error.go b/controlplane/errors/error.go index 0c719ded..4245c311 100644 --- a/controlplane/errors/error.go +++ b/controlplane/errors/error.go @@ -49,12 +49,15 @@ var ( */ var ( - ErrChunkNotFound = New(codes.NotFound, "chunk does not exist") - ErrTooManyTags = New(codes.InvalidArgument, "too many tags") - ErrNameTooLong = New(codes.InvalidArgument, "name is too long") - ErrDescriptionTooLong = New(codes.InvalidArgument, "description is too long") - ErrInvalidChunkID = New(codes.InvalidArgument, "chunk id is invalid") - ErrInvalidName = New(codes.InvalidArgument, "name is invalid") + ErrChunkNotFound = New(codes.NotFound, "chunk does not exist") + ErrTooManyTags = New(codes.InvalidArgument, "too many tags") + ErrNameTooLong = New(codes.InvalidArgument, "name is too long") + ErrDescriptionTooLong = New(codes.InvalidArgument, "description is too long") + ErrInvalidChunkID = New(codes.InvalidArgument, "chunk id is invalid") + ErrInvalidName = New(codes.InvalidArgument, "name is invalid") + ErrInvalidThumbnailFormat = New(codes.InvalidArgument, "thumbnail image must be png") + ErrInvalidThumbnailDimensions = New(codes.InvalidArgument, "thumbnail must be 512x512 pixels") + ErrInvalidThumbnailSize = New(codes.InvalidArgument, "thumbnail size too big") ) /* diff --git a/controlplane/postgres/chunk.go b/controlplane/postgres/chunk.go index c7e4137b..8441c618 100644 --- a/controlplane/postgres/chunk.go +++ b/controlplane/postgres/chunk.go @@ -29,6 +29,7 @@ import ( "github.com/google/uuid" "github.com/jackc/pgx/v5" + "github.com/jackc/pgx/v5/pgtype" apierrs "github.com/spacechunks/explorer/controlplane/errors" "github.com/spacechunks/explorer/controlplane/postgres/query" "github.com/spacechunks/explorer/controlplane/resource" @@ -237,6 +238,22 @@ func (db *DB) MinecraftVersionExists(ctx context.Context, version string) (bool, return ret, nil } +func (db *DB) UpdateThumbnail(ctx context.Context, chunkID string, imgHash string) error { + if err := db.do(ctx, func(q *query.Queries) error { + return q.UpdateChunkThumbnail(ctx, query.UpdateChunkThumbnailParams{ + ID: chunkID, + ThumbnailHash: pgtype.Text{ + String: imgHash, + Valid: true, + }, + }) + }); err != nil { + return err + } + + return nil +} + func (db *DB) getChunkByID(ctx context.Context, q *query.Queries, id string) (resource.Chunk, error) { rows, err := q.GetChunkByID(ctx, id) if err != nil { diff --git a/controlplane/postgres/migrations/20251101180433_add_minecraft_version_to_flavor_version.sql b/controlplane/postgres/migrations/20251101180433_add_minecraft_version_to_flavor_version.sql index 7fc47b33..5ea137ad 100644 --- a/controlplane/postgres/migrations/20251101180433_add_minecraft_version_to_flavor_version.sql +++ b/controlplane/postgres/migrations/20251101180433_add_minecraft_version_to_flavor_version.sql @@ -2,4 +2,4 @@ ALTER TABLE flavor_versions ADD COLUMN minecraft_version VARCHAR REFERENCES minecraft_versions(version) -- migrate:down -ยด + diff --git a/controlplane/postgres/migrations/20260223165838_thumbnail_hash_column.sql b/controlplane/postgres/migrations/20260223165838_thumbnail_hash_column.sql new file mode 100644 index 00000000..07b5ad09 --- /dev/null +++ b/controlplane/postgres/migrations/20260223165838_thumbnail_hash_column.sql @@ -0,0 +1,5 @@ +-- migrate:up +ALTER TABLE chunks ADD COLUMN thumbnail_hash VARCHAR(16); +ALTER TABLE chunks ADD COLUMN thumbnail_updated_at TIMESTAMPTZ DEFAULT now() NOT NULL; + +-- migrate:down diff --git a/controlplane/postgres/query.sql b/controlplane/postgres/query.sql index 9a97aac1..93fa5e3f 100644 --- a/controlplane/postgres/query.sql +++ b/controlplane/postgres/query.sql @@ -52,6 +52,12 @@ SELECT u.* FROM users u LEFT JOIN chunks c ON c.owner_id = u.id WHERE c.id = $1; +-- name: UpdateChunkThumbnail :exec +UPDATE chunks SET + thumbnail_hash = $1, + thumbnail_updated_at = now() +WHERE id = $2; + /* * FLAVORS */ diff --git a/controlplane/postgres/query/models.go b/controlplane/postgres/query/models.go index c8113114..d7f94b97 100644 --- a/controlplane/postgres/query/models.go +++ b/controlplane/postgres/query/models.go @@ -160,13 +160,15 @@ type Blob struct { } type Chunk struct { - ID string - Name string - Description string - Tags []string - CreatedAt time.Time - UpdatedAt time.Time - OwnerID string + ID string + Name string + Description string + Tags []string + CreatedAt time.Time + UpdatedAt time.Time + OwnerID string + ThumbnailHash pgtype.Text + ThumbnailUpdatedAt time.Time } type Flavor struct { diff --git a/controlplane/postgres/query/query.sql.go b/controlplane/postgres/query/query.sql.go index 48b63147..6215857f 100644 --- a/controlplane/postgres/query/query.sql.go +++ b/controlplane/postgres/query/query.sql.go @@ -417,7 +417,7 @@ func (q *Queries) FlavorVersionHashByID(ctx context.Context, id string) (string, } const getChunkByID = `-- name: GetChunkByID :many -SELECT c.id, c.name, description, tags, c.created_at, c.updated_at, owner_id, f.id, chunk_id, f.name, f.created_at, f.updated_at, v.id, flavor_id, hash, change_hash, build_status, version, files_uploaded, prev_version_id, v.created_at, presigned_url_expiry_date, presigned_url, minecraft_version, flavor_version_id, file_hash, file_path, vf.created_at, u.id, nickname, email, u.created_at, u.updated_at FROM chunks c +SELECT c.id, c.name, description, tags, c.created_at, c.updated_at, owner_id, thumbnail_hash, thumbnail_updated_at, f.id, chunk_id, f.name, f.created_at, f.updated_at, v.id, flavor_id, hash, change_hash, build_status, version, files_uploaded, prev_version_id, v.created_at, presigned_url_expiry_date, presigned_url, minecraft_version, flavor_version_id, file_hash, file_path, vf.created_at, u.id, nickname, email, u.created_at, u.updated_at FROM chunks c LEFT JOIN flavors f ON f.chunk_id = c.id LEFT JOIN flavor_versions v ON v.flavor_id = f.id LEFT JOIN flavor_version_files vf ON vf.flavor_version_id = v.id @@ -433,6 +433,8 @@ type GetChunkByIDRow struct { CreatedAt time.Time UpdatedAt time.Time OwnerID string + ThumbnailHash pgtype.Text + ThumbnailUpdatedAt time.Time ID_2 *string ChunkID *string Name_2 pgtype.Text @@ -479,6 +481,8 @@ func (q *Queries) GetChunkByID(ctx context.Context, id string) ([]GetChunkByIDRo &i.CreatedAt, &i.UpdatedAt, &i.OwnerID, + &i.ThumbnailHash, + &i.ThumbnailUpdatedAt, &i.ID_2, &i.ChunkID, &i.Name_2, @@ -517,7 +521,7 @@ func (q *Queries) GetChunkByID(ctx context.Context, id string) ([]GetChunkByIDRo } const getInstance = `-- name: GetInstance :many -SELECT i.id, i.chunk_id, flavor_version_id, node_id, port, state, i.created_at, i.updated_at, i.owner_id, v.id, flavor_id, hash, change_hash, build_status, version, files_uploaded, prev_version_id, v.created_at, presigned_url_expiry_date, presigned_url, minecraft_version, c.id, c.name, description, tags, c.created_at, c.updated_at, c.owner_id, f.id, f.chunk_id, f.name, f.created_at, f.updated_at, n.id, n.name, address, checkpoint_api_endpoint, n.created_at, u.id, nickname, email, u.created_at, u.updated_at FROM instances i +SELECT i.id, i.chunk_id, flavor_version_id, node_id, port, state, i.created_at, i.updated_at, i.owner_id, v.id, flavor_id, hash, change_hash, build_status, version, files_uploaded, prev_version_id, v.created_at, presigned_url_expiry_date, presigned_url, minecraft_version, c.id, c.name, description, tags, c.created_at, c.updated_at, c.owner_id, thumbnail_hash, thumbnail_updated_at, f.id, f.chunk_id, f.name, f.created_at, f.updated_at, n.id, n.name, address, checkpoint_api_endpoint, n.created_at, u.id, nickname, email, u.created_at, u.updated_at FROM instances i JOIN flavor_versions v ON i.flavor_version_id = v.id JOIN chunks c ON i.chunk_id = c.id JOIN flavors f ON f.chunk_id = c.id @@ -555,6 +559,8 @@ type GetInstanceRow struct { CreatedAt_3 time.Time UpdatedAt_2 time.Time OwnerID_2 string + ThumbnailHash pgtype.Text + ThumbnailUpdatedAt time.Time ID_4 string ChunkID_2 string Name_2 string @@ -610,6 +616,8 @@ func (q *Queries) GetInstance(ctx context.Context, id string) ([]GetInstanceRow, &i.CreatedAt_3, &i.UpdatedAt_2, &i.OwnerID_2, + &i.ThumbnailHash, + &i.ThumbnailUpdatedAt, &i.ID_4, &i.ChunkID_2, &i.Name_2, @@ -637,7 +645,7 @@ func (q *Queries) GetInstance(ctx context.Context, id string) ([]GetInstanceRow, } const getInstancesByNodeID = `-- name: GetInstancesByNodeID :many -SELECT i.id, chunk_id, flavor_version_id, node_id, port, state, i.created_at, i.updated_at, i.owner_id, v.id, flavor_id, hash, change_hash, build_status, version, files_uploaded, prev_version_id, v.created_at, presigned_url_expiry_date, presigned_url, minecraft_version, c.id, c.name, description, tags, c.created_at, c.updated_at, c.owner_id, n.id, n.name, address, checkpoint_api_endpoint, n.created_at, u.id, nickname, email, u.created_at, u.updated_at FROM instances i +SELECT i.id, chunk_id, flavor_version_id, node_id, port, state, i.created_at, i.updated_at, i.owner_id, v.id, flavor_id, hash, change_hash, build_status, version, files_uploaded, prev_version_id, v.created_at, presigned_url_expiry_date, presigned_url, minecraft_version, c.id, c.name, description, tags, c.created_at, c.updated_at, c.owner_id, thumbnail_hash, thumbnail_updated_at, n.id, n.name, address, checkpoint_api_endpoint, n.created_at, u.id, nickname, email, u.created_at, u.updated_at FROM instances i JOIN flavor_versions v ON i.flavor_version_id = v.id JOIN chunks c ON i.chunk_id = c.id JOIN nodes n ON i.node_id = n.id @@ -674,6 +682,8 @@ type GetInstancesByNodeIDRow struct { CreatedAt_3 time.Time UpdatedAt_2 time.Time OwnerID_2 string + ThumbnailHash pgtype.Text + ThumbnailUpdatedAt time.Time ID_4 string Name_2 string Address netip.Addr @@ -724,6 +734,8 @@ func (q *Queries) GetInstancesByNodeID(ctx context.Context, nodeID string) ([]Ge &i.CreatedAt_3, &i.UpdatedAt_2, &i.OwnerID_2, + &i.ThumbnailHash, + &i.ThumbnailUpdatedAt, &i.ID_4, &i.Name_2, &i.Address, @@ -771,7 +783,7 @@ func (q *Queries) LatestFlavorVersionByFlavorID(ctx context.Context, flavorID st } const listChunks = `-- name: ListChunks :many -SELECT c.id, c.name, description, tags, c.created_at, c.updated_at, owner_id, f.id, chunk_id, f.name, f.created_at, f.updated_at, v.id, flavor_id, hash, change_hash, build_status, version, files_uploaded, prev_version_id, v.created_at, presigned_url_expiry_date, presigned_url, minecraft_version, flavor_version_id, file_hash, file_path, vf.created_at, u.id, nickname, email, u.created_at, u.updated_at FROM chunks c +SELECT c.id, c.name, description, tags, c.created_at, c.updated_at, owner_id, thumbnail_hash, thumbnail_updated_at, f.id, chunk_id, f.name, f.created_at, f.updated_at, v.id, flavor_id, hash, change_hash, build_status, version, files_uploaded, prev_version_id, v.created_at, presigned_url_expiry_date, presigned_url, minecraft_version, flavor_version_id, file_hash, file_path, vf.created_at, u.id, nickname, email, u.created_at, u.updated_at FROM chunks c LEFT JOIN flavors f ON f.chunk_id = c.id LEFT JOIN flavor_versions v ON v.flavor_id = f.id LEFT JOIN flavor_version_files vf ON vf.flavor_version_id = v.id @@ -786,6 +798,8 @@ type ListChunksRow struct { CreatedAt time.Time UpdatedAt time.Time OwnerID string + ThumbnailHash pgtype.Text + ThumbnailUpdatedAt time.Time ID_2 *string ChunkID *string Name_2 pgtype.Text @@ -831,6 +845,8 @@ func (q *Queries) ListChunks(ctx context.Context) ([]ListChunksRow, error) { &i.CreatedAt, &i.UpdatedAt, &i.OwnerID, + &i.ThumbnailHash, + &i.ThumbnailUpdatedAt, &i.ID_2, &i.ChunkID, &i.Name_2, @@ -942,7 +958,7 @@ func (q *Queries) ListFlavorsByChunkID(ctx context.Context, chunkID string) ([]L } const listInstances = `-- name: ListInstances :many -SELECT i.id, i.chunk_id, flavor_version_id, node_id, port, state, i.created_at, i.updated_at, i.owner_id, v.id, flavor_id, hash, change_hash, build_status, version, files_uploaded, prev_version_id, v.created_at, presigned_url_expiry_date, presigned_url, minecraft_version, c.id, c.name, description, tags, c.created_at, c.updated_at, c.owner_id, f.id, f.chunk_id, f.name, f.created_at, f.updated_at, n.id, n.name, address, checkpoint_api_endpoint, n.created_at, u.id, nickname, email, u.created_at, u.updated_at FROM instances i +SELECT i.id, i.chunk_id, flavor_version_id, node_id, port, state, i.created_at, i.updated_at, i.owner_id, v.id, flavor_id, hash, change_hash, build_status, version, files_uploaded, prev_version_id, v.created_at, presigned_url_expiry_date, presigned_url, minecraft_version, c.id, c.name, description, tags, c.created_at, c.updated_at, c.owner_id, thumbnail_hash, thumbnail_updated_at, f.id, f.chunk_id, f.name, f.created_at, f.updated_at, n.id, n.name, address, checkpoint_api_endpoint, n.created_at, u.id, nickname, email, u.created_at, u.updated_at FROM instances i JOIN flavor_versions v ON i.flavor_version_id = v.id JOIN chunks c ON i.chunk_id = c.id JOIN flavors f ON f.chunk_id = c.id @@ -979,6 +995,8 @@ type ListInstancesRow struct { CreatedAt_3 time.Time UpdatedAt_2 time.Time OwnerID_2 string + ThumbnailHash pgtype.Text + ThumbnailUpdatedAt time.Time ID_4 string ChunkID_2 string Name_2 string @@ -1034,6 +1052,8 @@ func (q *Queries) ListInstances(ctx context.Context) ([]ListInstancesRow, error) &i.CreatedAt_3, &i.UpdatedAt_2, &i.OwnerID_2, + &i.ThumbnailHash, + &i.ThumbnailUpdatedAt, &i.ID_4, &i.ChunkID_2, &i.Name_2, @@ -1131,6 +1151,23 @@ func (q *Queries) UpdateChunk(ctx context.Context, arg UpdateChunkParams) error return err } +const updateChunkThumbnail = `-- name: UpdateChunkThumbnail :exec +UPDATE chunks SET + thumbnail_hash = $1, + thumbnail_updated_at = now() +WHERE id = $2 +` + +type UpdateChunkThumbnailParams struct { + ThumbnailHash pgtype.Text + ID string +} + +func (q *Queries) UpdateChunkThumbnail(ctx context.Context, arg UpdateChunkThumbnailParams) error { + _, err := q.db.Exec(ctx, updateChunkThumbnail, arg.ThumbnailHash, arg.ID) + return err +} + const updateFlavorVersionBuildStatus = `-- name: UpdateFlavorVersionBuildStatus :exec UPDATE flavor_versions SET build_status = $1 WHERE id = $2 ` diff --git a/controlplane/postgres/schema.sql b/controlplane/postgres/schema.sql index f3dc6275..13f8225e 100644 --- a/controlplane/postgres/schema.sql +++ b/controlplane/postgres/schema.sql @@ -1,5 +1,5 @@ -- Dumped from database version 17.2 (Debian 17.2-1.pgdg120+1) --- Dumped by pg_dump version 17.6 +-- Dumped by pg_dump version 17.8 SET statement_timeout = 0; SET lock_timeout = 0; @@ -104,7 +104,9 @@ CREATE TABLE public.chunks ( tags character varying(25)[] NOT NULL, created_at timestamp with time zone DEFAULT now() NOT NULL, updated_at timestamp with time zone DEFAULT now() NOT NULL, - owner_id uuid NOT NULL + owner_id uuid NOT NULL, + thumbnail_hash character varying(16), + thumbnail_updated_at timestamp with time zone DEFAULT now() NOT NULL ); @@ -625,4 +627,5 @@ INSERT INTO public.schema_migrations (version) VALUES ('20251101204811'), ('20251105222512'), ('20251112170710'), - ('20251112171323'); + ('20251112171323'), + ('20260223165838'); diff --git a/controlplane/server.go b/controlplane/server.go index c0e56e00..ed26769d 100644 --- a/controlplane/server.go +++ b/controlplane/server.go @@ -153,6 +153,7 @@ func (s *Server) Run(ctx context.Context) error { ) userServer = user.NewServer(userService) chunkService = chunk.NewService( + s.logger.With("component", "chunk-service"), db, db, blobStore, @@ -162,6 +163,7 @@ func (s *Server) Run(ctx context.Context) error { BaseImage: s.cfg.BaseImage, Bucket: s.cfg.Bucket, PresignedURLExpiry: s.cfg.PresignedURLExpiry, + ThumbnailMaxSizeKB: s.cfg.ThumbnailMaxSizeKB, }) chunkServer = chunk.NewServer(chunkService) insService = instance.NewService(s.logger, db, db, chunkService) diff --git a/internal/mock/authz_access_evaluator.go b/internal/mock/authz_access_evaluator.go index 4f6be316..4b05510c 100644 --- a/internal/mock/authz_access_evaluator.go +++ b/internal/mock/authz_access_evaluator.go @@ -6,7 +6,6 @@ import ( context "context" authz "github.com/spacechunks/explorer/controlplane/authz" - mock "github.com/stretchr/testify/mock" ) diff --git a/internal/mock/cache_snapshot_cache.go b/internal/mock/cache_snapshot_cache.go index d6923764..ea884cca 100644 --- a/internal/mock/cache_snapshot_cache.go +++ b/internal/mock/cache_snapshot_cache.go @@ -6,10 +6,8 @@ import ( context "context" cache "github.com/envoyproxy/go-control-plane/pkg/cache/v3" - - mock "github.com/stretchr/testify/mock" - stream "github.com/envoyproxy/go-control-plane/pkg/server/stream/v3" + mock "github.com/stretchr/testify/mock" ) // MockCacheSnapshotCache is an autogenerated mock type for the SnapshotCache type diff --git a/internal/mock/chunk_repository.go b/internal/mock/chunk_repository.go index e59c8b4b..37025473 100644 --- a/internal/mock/chunk_repository.go +++ b/internal/mock/chunk_repository.go @@ -4,11 +4,10 @@ package mock import ( context "context" + time "time" resource "github.com/spacechunks/explorer/controlplane/resource" mock "github.com/stretchr/testify/mock" - - time "time" ) // MockChunkRepository is an autogenerated mock type for the Repository type @@ -973,6 +972,54 @@ func (_c *MockChunkRepository_UpdateFlavorVersionPresignedURLData_Call) RunAndRe return _c } +// UpdateThumbnail provides a mock function with given fields: ctx, chunkID, imgHash +func (_m *MockChunkRepository) UpdateThumbnail(ctx context.Context, chunkID string, imgHash string) error { + ret := _m.Called(ctx, chunkID, imgHash) + + if len(ret) == 0 { + panic("no return value specified for UpdateThumbnail") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, string, string) error); ok { + r0 = rf(ctx, chunkID, imgHash) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// MockChunkRepository_UpdateThumbnail_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'UpdateThumbnail' +type MockChunkRepository_UpdateThumbnail_Call struct { + *mock.Call +} + +// UpdateThumbnail is a helper method to define mock.On call +// - ctx context.Context +// - chunkID string +// - imgHash string +func (_e *MockChunkRepository_Expecter) UpdateThumbnail(ctx interface{}, chunkID interface{}, imgHash interface{}) *MockChunkRepository_UpdateThumbnail_Call { + return &MockChunkRepository_UpdateThumbnail_Call{Call: _e.mock.On("UpdateThumbnail", ctx, chunkID, imgHash)} +} + +func (_c *MockChunkRepository_UpdateThumbnail_Call) Run(run func(ctx context.Context, chunkID string, imgHash string)) *MockChunkRepository_UpdateThumbnail_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(string), args[2].(string)) + }) + return _c +} + +func (_c *MockChunkRepository_UpdateThumbnail_Call) Return(_a0 error) *MockChunkRepository_UpdateThumbnail_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *MockChunkRepository_UpdateThumbnail_Call) RunAndReturn(run func(context.Context, string, string) error) *MockChunkRepository_UpdateThumbnail_Call { + _c.Call.Return(run) + return _c +} + // NewMockChunkRepository creates a new instance of MockChunkRepository. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. // The first argument is typically a *testing.T value. func NewMockChunkRepository(t interface { diff --git a/internal/mock/cni_handler.go b/internal/mock/cni_handler.go index 99adf23f..d7ae451c 100644 --- a/internal/mock/cni_handler.go +++ b/internal/mock/cni_handler.go @@ -3,10 +3,10 @@ package mock import ( + net "net" + datapath "github.com/spacechunks/explorer/internal/datapath" mock "github.com/stretchr/testify/mock" - - net "net" ) // MockCniHandler is an autogenerated mock type for the Handler type diff --git a/internal/mock/cri_service.go b/internal/mock/cri_service.go index 4b3287cb..451baaa8 100644 --- a/internal/mock/cri_service.go +++ b/internal/mock/cri_service.go @@ -6,10 +6,8 @@ import ( context "context" cri "github.com/spacechunks/explorer/platformd/cri" - grpc "google.golang.org/grpc" - mock "github.com/stretchr/testify/mock" - + grpc "google.golang.org/grpc" v1 "k8s.io/cri-api/pkg/apis/runtime/v1" ) diff --git a/internal/mock/image_service.go b/internal/mock/image_service.go index 37f788de..65d826a1 100644 --- a/internal/mock/image_service.go +++ b/internal/mock/image_service.go @@ -5,9 +5,8 @@ package mock import ( context "context" - mock "github.com/stretchr/testify/mock" - v1 "github.com/google/go-containerregistry/pkg/v1" + mock "github.com/stretchr/testify/mock" ) // MockImageService is an autogenerated mock type for the Service type diff --git a/internal/mock/job_client.go b/internal/mock/job_client.go index 30fa2e60..2d99028b 100644 --- a/internal/mock/job_client.go +++ b/internal/mock/job_client.go @@ -5,9 +5,8 @@ package mock import ( context "context" - mock "github.com/stretchr/testify/mock" - river "github.com/riverqueue/river" + mock "github.com/stretchr/testify/mock" ) // MockJobClient is an autogenerated mock type for the Client type diff --git a/internal/mock/v1_image_service_client.go b/internal/mock/v1_image_service_client.go index c5996d78..1e4cb7ba 100644 --- a/internal/mock/v1_image_service_client.go +++ b/internal/mock/v1_image_service_client.go @@ -5,10 +5,8 @@ package mock import ( context "context" - grpc "google.golang.org/grpc" - mock "github.com/stretchr/testify/mock" - + grpc "google.golang.org/grpc" v1 "k8s.io/cri-api/pkg/apis/runtime/v1" ) diff --git a/internal/mock/v1_runtime_service_client.go b/internal/mock/v1_runtime_service_client.go index bdbd074e..ce5a8507 100644 --- a/internal/mock/v1_runtime_service_client.go +++ b/internal/mock/v1_runtime_service_client.go @@ -5,10 +5,8 @@ package mock import ( context "context" - grpc "google.golang.org/grpc" - mock "github.com/stretchr/testify/mock" - + grpc "google.golang.org/grpc" v1 "k8s.io/cri-api/pkg/apis/runtime/v1" ) diff --git a/internal/mock/v1alpha1_checkpoint_service_client.go b/internal/mock/v1alpha1_checkpoint_service_client.go index c102bafd..67b0e839 100644 --- a/internal/mock/v1alpha1_checkpoint_service_client.go +++ b/internal/mock/v1alpha1_checkpoint_service_client.go @@ -5,11 +5,9 @@ package mock import ( context "context" - grpc "google.golang.org/grpc" - - mock "github.com/stretchr/testify/mock" - v1alpha1 "github.com/spacechunks/explorer/api/platformd/checkpoint/v1alpha1" + mock "github.com/stretchr/testify/mock" + grpc "google.golang.org/grpc" ) // MockV1alpha1CheckpointServiceClient is an autogenerated mock type for the CheckpointServiceClient type diff --git a/internal/mock/v1alpha1_instance_service_client.go b/internal/mock/v1alpha1_instance_service_client.go index 93bf2c7f..5582ec85 100644 --- a/internal/mock/v1alpha1_instance_service_client.go +++ b/internal/mock/v1alpha1_instance_service_client.go @@ -5,11 +5,9 @@ package mock import ( context "context" - grpc "google.golang.org/grpc" - - mock "github.com/stretchr/testify/mock" - v1alpha1 "github.com/spacechunks/explorer/api/instance/v1alpha1" + mock "github.com/stretchr/testify/mock" + grpc "google.golang.org/grpc" ) // MockV1alpha1InstanceServiceClient is an autogenerated mock type for the InstanceServiceClient type diff --git a/internal/mock/v1alpha1_proxy_service_client.go b/internal/mock/v1alpha1_proxy_service_client.go index 20ee4fbd..f65a0730 100644 --- a/internal/mock/v1alpha1_proxy_service_client.go +++ b/internal/mock/v1alpha1_proxy_service_client.go @@ -5,11 +5,9 @@ package mock import ( context "context" - grpc "google.golang.org/grpc" - - mock "github.com/stretchr/testify/mock" - v1alpha1 "github.com/spacechunks/explorer/api/platformd/proxy/v1alpha1" + mock "github.com/stretchr/testify/mock" + grpc "google.golang.org/grpc" ) // MockV1alpha1ProxyServiceClient is an autogenerated mock type for the ProxyServiceClient type diff --git a/internal/mock/v1alpha2_workload_service_client.go b/internal/mock/v1alpha2_workload_service_client.go index be6042eb..2e24fb06 100644 --- a/internal/mock/v1alpha2_workload_service_client.go +++ b/internal/mock/v1alpha2_workload_service_client.go @@ -5,11 +5,9 @@ package mock import ( context "context" - grpc "google.golang.org/grpc" - - mock "github.com/stretchr/testify/mock" - v1alpha2 "github.com/spacechunks/explorer/api/platformd/workload/v1alpha2" + mock "github.com/stretchr/testify/mock" + grpc "google.golang.org/grpc" ) // MockV1alpha2WorkloadServiceClient is an autogenerated mock type for the WorkloadServiceClient type diff --git a/internal/mock/workload_service.go b/internal/mock/workload_service.go index c95e0a01..63d2ca11 100644 --- a/internal/mock/workload_service.go +++ b/internal/mock/workload_service.go @@ -6,9 +6,8 @@ import ( context "context" status "github.com/spacechunks/explorer/platformd/status" - mock "github.com/stretchr/testify/mock" - workload "github.com/spacechunks/explorer/platformd/workload" + mock "github.com/stretchr/testify/mock" ) // MockWorkloadService is an autogenerated mock type for the Service type diff --git a/internal/mock/xds_map.go b/internal/mock/xds_map.go index 394cc79a..66fe2c46 100644 --- a/internal/mock/xds_map.go +++ b/internal/mock/xds_map.go @@ -6,10 +6,8 @@ import ( context "context" cache "github.com/envoyproxy/go-control-plane/pkg/cache/v3" - - mock "github.com/stretchr/testify/mock" - xds "github.com/spacechunks/explorer/platformd/proxy/xds" + mock "github.com/stretchr/testify/mock" ) // MockXdsMap is an autogenerated mock type for the Map type diff --git a/test/fixture/control_plane.go b/test/fixture/control_plane.go index 6755ec99..53dafa11 100644 --- a/test/fixture/control_plane.go +++ b/test/fixture/control_plane.go @@ -139,6 +139,7 @@ func (c ControlPlane) Run(t *testing.T, opts ...ControlPlaneRunOption) { APITokenIssuer: APITokenIssuer, APITokenExpiry: 5 * time.Second, APITokenSigningKey: keyPem.String(), + ThumbnailMaxSizeKB: 100, }) ) diff --git a/test/fixture/s3.go b/test/fixture/s3.go index 31a9b972..5c27e565 100644 --- a/test/fixture/s3.go +++ b/test/fixture/s3.go @@ -21,6 +21,7 @@ package fixture import ( "bytes" "context" + "errors" "net/http" "os" "testing" @@ -29,6 +30,7 @@ import ( awscfg "github.com/aws/aws-sdk-go-v2/config" "github.com/aws/aws-sdk-go-v2/credentials" "github.com/aws/aws-sdk-go-v2/service/s3" + "github.com/aws/smithy-go" "github.com/johannesboyne/gofakes3" "github.com/johannesboyne/gofakes3/backend/s3mem" "github.com/stretchr/testify/require" @@ -80,8 +82,10 @@ func NewS3Client(t *testing.T, ctx context.Context) *s3.Client { } func (f FakeS3) UploadObject(t *testing.T, key string, data []byte) { - ctx := context.Background() - c := NewS3Client(t, ctx) + var ( + ctx = context.Background() + c = NewS3Client(t, ctx) + ) _, err := c.PutObject(ctx, &s3.PutObjectInput{ Bucket: aws.String(Bucket), @@ -90,3 +94,23 @@ func (f FakeS3) UploadObject(t *testing.T, key string, data []byte) { }) require.NoError(t, err) } + +func (f FakeS3) RequireObjectExists(t *testing.T, key string) { + var ( + ctx = context.Background() + c = NewS3Client(t, ctx) + ) + + _, err := c.HeadObject(ctx, &s3.HeadObjectInput{ + Bucket: aws.String(Bucket), + Key: aws.String(key), + }) + if err != nil { + var s3err smithy.APIError + if errors.As(err, &s3err) && s3err.ErrorCode() == "NotFound" { + t.Fatalf("object %s does not exist", key) + return + } + t.Fatalf("head object failed: %v", err) + } +} diff --git a/test/functional/controlplane/changeset.tar.gz b/test/functional/controlplane/changeset.tar.gz index 659a1efd..35b71d0b 100644 Binary files a/test/functional/controlplane/changeset.tar.gz and b/test/functional/controlplane/changeset.tar.gz differ diff --git a/test/functional/controlplane/chunk_api_test.go b/test/functional/controlplane/chunk_api_test.go index 2e200190..55854dac 100644 --- a/test/functional/controlplane/chunk_api_test.go +++ b/test/functional/controlplane/chunk_api_test.go @@ -45,6 +45,7 @@ import ( "github.com/spacechunks/explorer/test/fixture" "github.com/spacechunks/explorer/test/functional/controlplane/testdata" "github.com/stretchr/testify/require" + "github.com/zeebo/xxh3" "google.golang.org/protobuf/testing/protocmp" ) @@ -1024,3 +1025,90 @@ func TestUserCannotGetUploadURLForFlavorVersionWhereHeIsNotOwnerOf(t *testing.T) require.ErrorIs(t, err, apierrs.ErrPermissionDenied.GRPCStatus().Err()) } + +func TestUploadChunkThumbnailSanityChecks(t *testing.T) { + tests := []struct { + name string + image []byte + err error + }{ + { + name: "invalid thumbnail dimensions too big", + image: testdata.InvalidThumbnailDimensionsTooBig, + err: apierrs.ErrInvalidThumbnailDimensions.GRPCStatus().Err(), + }, + { + name: "invalid thumbnail dimensions too small", + image: testdata.InvalidThumbnailDimensionsTooSmall, + err: apierrs.ErrInvalidThumbnailDimensions.GRPCStatus().Err(), + }, + { + name: "invalid thumbnail wrong format", + image: testdata.InvalidThumbnailWrongFormat, + err: apierrs.ErrInvalidThumbnailFormat.GRPCStatus().Err(), + }, + { + name: "invalid thumbnail size too big", + image: testdata.InvalidThumbnailSizeTooBig, + err: apierrs.ErrInvalidThumbnailSize.GRPCStatus().Err(), + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var ( + ctx = context.Background() + cp = fixture.NewControlPlane(t) + c = fixture.Chunk() + u = fixture.User() + ) + + fixture.RunFakeS3(t) + cp.Run(t) + cp.Postgres.CreateChunk(t, &c, fixture.CreateOptionsAll) + cp.Postgres.CreateUser(t, &u) + cp.AddUserAPIKey(t, &ctx, u) + + client := cp.ChunkClient(t) + + _, err := client.UploadThumbnail(ctx, &chunkv1alpha1.UploadThumbnailRequest{ + ChunkId: c.ID, + Image: tt.image, + }) + + if tt.err != nil { + require.ErrorIs(t, err, tt.err) + return + } + + require.NoError(t, err) + }) + } +} + +func TestThumbnailActuallyUploadedToS3(t *testing.T) { + var ( + ctx = context.Background() + cp = fixture.NewControlPlane(t) + c = fixture.Chunk() + u = fixture.User() + ) + + fakes3 := fixture.RunFakeS3(t) + cp.Run(t) + cp.Postgres.CreateChunk(t, &c, fixture.CreateOptionsAll) + cp.Postgres.CreateUser(t, &u) + cp.AddUserAPIKey(t, &ctx, u) + + client := cp.ChunkClient(t) + + _, err := client.UploadThumbnail(ctx, &chunkv1alpha1.UploadThumbnailRequest{ + ChunkId: c.ID, + Image: testdata.ValidThumbnail, + }) + + h := fmt.Sprintf("%x", xxh3.Hash(testdata.ValidThumbnail)) + require.NoError(t, err) + + fakes3.RequireObjectExists(t, blob.CASKeyPrefix+"/"+h) +} diff --git a/test/functional/controlplane/testdata/embed.go b/test/functional/controlplane/testdata/embed.go index 8f5ce854..632a0165 100644 --- a/test/functional/controlplane/testdata/embed.go +++ b/test/functional/controlplane/testdata/embed.go @@ -51,6 +51,21 @@ var FullChangeSetFile []byte //go:embed add_testfile_changeset.tar.gz var AddTestFileChangeSet []byte +//go:embed valid_thumbnail.png +var ValidThumbnail []byte + +//go:embed invalid_thumbnail_dimensions_too_big.png +var InvalidThumbnailDimensionsTooBig []byte + +//go:embed invalid_thumbnail_dimensions_too_small.png +var InvalidThumbnailDimensionsTooSmall []byte + +//go:embed invalid_thumbnail_wrong_format.jpg +var InvalidThumbnailWrongFormat []byte + +//go:embed invalid_thumbnail_size_too_big.png +var InvalidThumbnailSizeTooBig []byte + func ComputeFileHashes(t *testing.T, dir string) []file.Hash { hashes := make([]file.Hash, 0) err := filepath.WalkDir(dir, func(path string, d fs.DirEntry, err error) error { diff --git a/test/functional/controlplane/testdata/invalid_thumbnail_dimensions_too_big.png b/test/functional/controlplane/testdata/invalid_thumbnail_dimensions_too_big.png new file mode 100644 index 00000000..cef54c27 Binary files /dev/null and b/test/functional/controlplane/testdata/invalid_thumbnail_dimensions_too_big.png differ diff --git a/test/functional/controlplane/testdata/invalid_thumbnail_dimensions_too_small.png b/test/functional/controlplane/testdata/invalid_thumbnail_dimensions_too_small.png new file mode 100644 index 00000000..b66e4de4 Binary files /dev/null and b/test/functional/controlplane/testdata/invalid_thumbnail_dimensions_too_small.png differ diff --git a/test/functional/controlplane/testdata/invalid_thumbnail_size_too_big.png b/test/functional/controlplane/testdata/invalid_thumbnail_size_too_big.png new file mode 100644 index 00000000..85611510 Binary files /dev/null and b/test/functional/controlplane/testdata/invalid_thumbnail_size_too_big.png differ diff --git a/test/functional/controlplane/testdata/invalid_thumbnail_wrong_format.jpg b/test/functional/controlplane/testdata/invalid_thumbnail_wrong_format.jpg new file mode 100644 index 00000000..d715a4f9 Binary files /dev/null and b/test/functional/controlplane/testdata/invalid_thumbnail_wrong_format.jpg differ diff --git a/test/functional/controlplane/testdata/valid_thumbnail.png b/test/functional/controlplane/testdata/valid_thumbnail.png new file mode 100644 index 00000000..37b5de43 Binary files /dev/null and b/test/functional/controlplane/testdata/valid_thumbnail.png differ diff --git a/test/functional/database/chunk_repository_test.go b/test/functional/database/chunk_repository_test.go index 61e54121..127bc350 100644 --- a/test/functional/database/chunk_repository_test.go +++ b/test/functional/database/chunk_repository_test.go @@ -119,3 +119,31 @@ func TestInsertJob(t *testing.T) { nil, ) } + +func TestUpdateThumbnail(t *testing.T) { + var ( + ctx = context.Background() + pg = fixture.NewPostgres() + ) + pg.Run(t, ctx) + + var ( + expectedHash = "some-hash" + c = fixture.Chunk() + ) + + pg.CreateChunk(t, &c, fixture.CreateOptionsAll) + + err := pg.DB.UpdateThumbnail(ctx, c.ID, expectedHash) + require.NoError(t, err) + + var ( + actualHash = "" + q = `SELECT thumbnail_hash from chunks where id = $1` + ) + + err = pg.Pool.QueryRow(ctx, q, c.ID).Scan(&actualHash) + require.NoError(t, err) + + cmp.Diff(actualHash, expectedHash) +}