Commit ff175bbb authored by Steven's avatar Steven

refactor: update resource binary request handler

parent 6d3d71df
......@@ -1371,41 +1371,6 @@ paths:
type: string
tags:
- UserService
/api/v1/{name}/avatar:
get:
summary: GetUserAvatar gets the avatar of a user.
operationId: UserService_GetUserAvatar
responses:
"200":
description: A successful response.
schema:
$ref: '#/definitions/apiHttpBody'
default:
description: An unexpected error response.
schema:
$ref: '#/definitions/googlerpcStatus'
parameters:
- name: name
description: |-
The name of the user.
Format: users/{id}
in: path
required: true
type: string
pattern: users/[^/]+
- name: httpBody.contentType
description: The HTTP Content-Type header value specifying the content type of the body.
in: query
required: false
type: string
- name: httpBody.data
description: The HTTP request/response body as raw binary.
in: query
required: false
type: string
format: byte
tags:
- UserService
/api/v1/{name}/comments:
get:
summary: ListMemoComments lists comments for a memo.
......@@ -1790,6 +1755,105 @@ paths:
format: date-time
tags:
- UserService
/o/r/{uid}:
get:
summary: GetResourceBinary returns a resource binary by name.
operationId: ResourceService_GetResourceBinary
responses:
"200":
description: A successful response.
schema:
$ref: '#/definitions/apiHttpBody'
default:
description: An unexpected error response.
schema:
$ref: '#/definitions/googlerpcStatus'
parameters:
- name: uid
description: The user defined id of the resource.
in: path
required: true
type: string
- name: name
description: |-
The name of the resource.
Format: resources/{id}
id is the system generated unique identifier.
in: query
required: false
type: string
tags:
- ResourceService
/o/{name}/avatar:
get:
summary: GetUserAvatarBinary gets the avatar of a user.
operationId: UserService_GetUserAvatarBinary2
responses:
"200":
description: A successful response.
schema:
$ref: '#/definitions/apiHttpBody'
default:
description: An unexpected error response.
schema:
$ref: '#/definitions/googlerpcStatus'
parameters:
- name: name
description: |-
The name of the user.
Format: users/{id}
in: path
required: true
type: string
pattern: users/[^/]+
- name: httpBody.contentType
description: The HTTP Content-Type header value specifying the content type of the body.
in: query
required: false
type: string
- name: httpBody.data
description: The HTTP request/response body as raw binary.
in: query
required: false
type: string
format: byte
tags:
- UserService
/{name}/avatar:
get:
summary: GetUserAvatarBinary gets the avatar of a user.
operationId: UserService_GetUserAvatarBinary
responses:
"200":
description: A successful response.
schema:
$ref: '#/definitions/apiHttpBody'
default:
description: An unexpected error response.
schema:
$ref: '#/definitions/googlerpcStatus'
parameters:
- name: name
description: |-
The name of the user.
Format: users/{id}
in: path
required: true
type: string
pattern: users/[^/]+
- name: httpBody.contentType
description: The HTTP Content-Type header value specifying the content type of the body.
in: query
required: false
type: string
- name: httpBody.data
description: The HTTP request/response body as raw binary.
in: query
required: false
type: string
format: byte
tags:
- UserService
definitions:
MemoServiceSetMemoRelationsBody:
type: object
......@@ -2137,8 +2201,7 @@ definitions:
Note: this functionality is not currently available in the official
protobuf release, and it is not used for type URLs beginning with
type.googleapis.com. As of May 2023, there are no widely used type server
implementations and no plans to implement one.
type.googleapis.com.
Schemes other than `http`, `https` (or the empty scheme) might be
used with implementation specific semantics.
......@@ -2203,7 +2266,7 @@ definitions:
name "y.z".
JSON
====
The JSON representation of an `Any` value uses the regular
representation of the deserialized, embedded message, with an
additional field `@type` which contains the type URL. Example:
......
......@@ -5,6 +5,7 @@ package memos.api.v1;
import "google/api/annotations.proto";
import "google/api/client.proto";
import "google/api/field_behavior.proto";
import "google/api/httpbody.proto";
import "google/protobuf/empty.proto";
import "google/protobuf/field_mask.proto";
import "google/protobuf/timestamp.proto";
......@@ -32,6 +33,11 @@ service ResourceService {
option (google.api.http) = {get: "/api/v1/{name=resources/*}"};
option (google.api.method_signature) = "name";
}
// GetResourceBinary returns a resource binary by name.
rpc GetResourceBinary(GetResourceBinaryRequest) returns (google.api.HttpBody) {
option (google.api.http) = {get: "/o/r/{uid}"};
option (google.api.method_signature) = "name,uid";
}
// UpdateResource updates a resource.
rpc UpdateResource(UpdateResourceRequest) returns (Resource) {
option (google.api.http) = {
......@@ -98,6 +104,16 @@ message GetResourceRequest {
string name = 1;
}
message GetResourceBinaryRequest {
// The name of the resource.
// Format: resources/{id}
// id is the system generated unique identifier.
string name = 1;
// The user defined id of the resource.
string uid = 2;
}
message UpdateResourceRequest {
Resource resource = 1;
......
......@@ -27,9 +27,13 @@ service UserService {
option (google.api.http) = {get: "/api/v1/{name=users/*}"};
option (google.api.method_signature) = "name";
}
// GetUserAvatar gets the avatar of a user.
rpc GetUserAvatar(GetUserAvatarRequest) returns (google.api.HttpBody) {
option (google.api.http) = {get: "/api/v1/{name=users/*}/avatar"};
// GetUserAvatarBinary gets the avatar of a user.
rpc GetUserAvatarBinary(GetUserAvatarBinaryRequest) returns (google.api.HttpBody) {
option (google.api.http) = {
get: "/{name=users/*}/avatar"
additional_bindings: {get: "/o/{name=users/*}/avatar"}
};
option (google.api.method_signature) = "name";
}
// CreateUser creates a new user.
......@@ -143,7 +147,7 @@ message GetUserRequest {
string name = 1;
}
message GetUserAvatarRequest {
message GetUserAvatarBinaryRequest {
// The name of the user.
// Format: users/{id}
string name = 1;
......
This diff is collapsed.
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.33.0
// protoc-gen-go v1.34.0
// protoc (unknown)
// source: api/v1/auth_service.proto
......
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.33.0
// protoc-gen-go v1.34.0
// protoc (unknown)
// source: api/v1/common.proto
......
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.33.0
// protoc-gen-go v1.34.0
// protoc (unknown)
// source: api/v1/idp_service.proto
......
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.33.0
// protoc-gen-go v1.34.0
// protoc (unknown)
// source: api/v1/inbox_service.proto
......
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.33.0
// protoc-gen-go v1.34.0
// protoc (unknown)
// source: api/v1/markdown_service.proto
......
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.33.0
// protoc-gen-go v1.34.0
// protoc (unknown)
// source: api/v1/memo_relation_service.proto
......
This diff is collapsed.
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.33.0
// protoc-gen-go v1.34.0
// protoc (unknown)
// source: api/v1/reaction_service.proto
......
This diff is collapsed.
......@@ -163,6 +163,76 @@ func local_request_ResourceService_GetResource_0(ctx context.Context, marshaler
}
var (
filter_ResourceService_GetResourceBinary_0 = &utilities.DoubleArray{Encoding: map[string]int{"uid": 0}, Base: []int{1, 1, 0}, Check: []int{0, 1, 2}}
)
func request_ResourceService_GetResourceBinary_0(ctx context.Context, marshaler runtime.Marshaler, client ResourceServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq GetResourceBinaryRequest
var metadata runtime.ServerMetadata
var (
val string
ok bool
err error
_ = err
)
val, ok = pathParams["uid"]
if !ok {
return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "uid")
}
protoReq.Uid, err = runtime.String(val)
if err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "uid", err)
}
if err := req.ParseForm(); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_ResourceService_GetResourceBinary_0); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := client.GetResourceBinary(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_ResourceService_GetResourceBinary_0(ctx context.Context, marshaler runtime.Marshaler, server ResourceServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq GetResourceBinaryRequest
var metadata runtime.ServerMetadata
var (
val string
ok bool
err error
_ = err
)
val, ok = pathParams["uid"]
if !ok {
return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "uid")
}
protoReq.Uid, err = runtime.String(val)
if err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "uid", err)
}
if err := req.ParseForm(); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_ResourceService_GetResourceBinary_0); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := server.GetResourceBinary(ctx, &protoReq)
return msg, metadata, err
}
var (
filter_ResourceService_UpdateResource_0 = &utilities.DoubleArray{Encoding: map[string]int{"resource": 0, "name": 1}, Base: []int{1, 2, 1, 0, 0}, Check: []int{0, 1, 2, 3, 2}}
)
......@@ -421,6 +491,31 @@ func RegisterResourceServiceHandlerServer(ctx context.Context, mux *runtime.Serv
})
mux.Handle("GET", pattern_ResourceService_GetResourceBinary_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
var stream runtime.ServerTransportStream
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateIncomingContext(ctx, mux, req, "/memos.api.v1.ResourceService/GetResourceBinary", runtime.WithHTTPPathPattern("/o/r/{uid}"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_ResourceService_GetResourceBinary_0(annotatedContext, inboundMarshaler, server, req, pathParams)
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_ResourceService_GetResourceBinary_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("PATCH", pattern_ResourceService_UpdateResource_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
......@@ -600,6 +695,28 @@ func RegisterResourceServiceHandlerClient(ctx context.Context, mux *runtime.Serv
})
mux.Handle("GET", pattern_ResourceService_GetResourceBinary_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
var err error
var annotatedContext context.Context
annotatedContext, err = runtime.AnnotateContext(ctx, mux, req, "/memos.api.v1.ResourceService/GetResourceBinary", runtime.WithHTTPPathPattern("/o/r/{uid}"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_ResourceService_GetResourceBinary_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_ResourceService_GetResourceBinary_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("PATCH", pattern_ResourceService_UpdateResource_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
......@@ -656,6 +773,8 @@ var (
pattern_ResourceService_GetResource_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 2, 5, 3}, []string{"api", "v1", "resources", "name"}, ""))
pattern_ResourceService_GetResourceBinary_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 1, 0, 4, 1, 5, 2}, []string{"o", "r", "uid"}, ""))
pattern_ResourceService_UpdateResource_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 2, 5, 3}, []string{"api", "v1", "resources", "resource.name"}, ""))
pattern_ResourceService_DeleteResource_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 2, 5, 3}, []string{"api", "v1", "resources", "name"}, ""))
......@@ -670,6 +789,8 @@ var (
forward_ResourceService_GetResource_0 = runtime.ForwardResponseMessage
forward_ResourceService_GetResourceBinary_0 = runtime.ForwardResponseMessage
forward_ResourceService_UpdateResource_0 = runtime.ForwardResponseMessage
forward_ResourceService_DeleteResource_0 = runtime.ForwardResponseMessage
......
......@@ -8,6 +8,7 @@ package apiv1
import (
context "context"
httpbody "google.golang.org/genproto/googleapis/api/httpbody"
grpc "google.golang.org/grpc"
codes "google.golang.org/grpc/codes"
status "google.golang.org/grpc/status"
......@@ -24,6 +25,7 @@ const (
ResourceService_ListResources_FullMethodName = "/memos.api.v1.ResourceService/ListResources"
ResourceService_SearchResources_FullMethodName = "/memos.api.v1.ResourceService/SearchResources"
ResourceService_GetResource_FullMethodName = "/memos.api.v1.ResourceService/GetResource"
ResourceService_GetResourceBinary_FullMethodName = "/memos.api.v1.ResourceService/GetResourceBinary"
ResourceService_UpdateResource_FullMethodName = "/memos.api.v1.ResourceService/UpdateResource"
ResourceService_DeleteResource_FullMethodName = "/memos.api.v1.ResourceService/DeleteResource"
)
......@@ -40,6 +42,8 @@ type ResourceServiceClient interface {
SearchResources(ctx context.Context, in *SearchResourcesRequest, opts ...grpc.CallOption) (*SearchResourcesResponse, error)
// GetResource returns a resource by name.
GetResource(ctx context.Context, in *GetResourceRequest, opts ...grpc.CallOption) (*Resource, error)
// GetResourceBinary returns a resource binary by name.
GetResourceBinary(ctx context.Context, in *GetResourceBinaryRequest, opts ...grpc.CallOption) (*httpbody.HttpBody, error)
// UpdateResource updates a resource.
UpdateResource(ctx context.Context, in *UpdateResourceRequest, opts ...grpc.CallOption) (*Resource, error)
// DeleteResource deletes a resource by name.
......@@ -90,6 +94,15 @@ func (c *resourceServiceClient) GetResource(ctx context.Context, in *GetResource
return out, nil
}
func (c *resourceServiceClient) GetResourceBinary(ctx context.Context, in *GetResourceBinaryRequest, opts ...grpc.CallOption) (*httpbody.HttpBody, error) {
out := new(httpbody.HttpBody)
err := c.cc.Invoke(ctx, ResourceService_GetResourceBinary_FullMethodName, in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *resourceServiceClient) UpdateResource(ctx context.Context, in *UpdateResourceRequest, opts ...grpc.CallOption) (*Resource, error) {
out := new(Resource)
err := c.cc.Invoke(ctx, ResourceService_UpdateResource_FullMethodName, in, out, opts...)
......@@ -120,6 +133,8 @@ type ResourceServiceServer interface {
SearchResources(context.Context, *SearchResourcesRequest) (*SearchResourcesResponse, error)
// GetResource returns a resource by name.
GetResource(context.Context, *GetResourceRequest) (*Resource, error)
// GetResourceBinary returns a resource binary by name.
GetResourceBinary(context.Context, *GetResourceBinaryRequest) (*httpbody.HttpBody, error)
// UpdateResource updates a resource.
UpdateResource(context.Context, *UpdateResourceRequest) (*Resource, error)
// DeleteResource deletes a resource by name.
......@@ -143,6 +158,9 @@ func (UnimplementedResourceServiceServer) SearchResources(context.Context, *Sear
func (UnimplementedResourceServiceServer) GetResource(context.Context, *GetResourceRequest) (*Resource, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetResource not implemented")
}
func (UnimplementedResourceServiceServer) GetResourceBinary(context.Context, *GetResourceBinaryRequest) (*httpbody.HttpBody, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetResourceBinary not implemented")
}
func (UnimplementedResourceServiceServer) UpdateResource(context.Context, *UpdateResourceRequest) (*Resource, error) {
return nil, status.Errorf(codes.Unimplemented, "method UpdateResource not implemented")
}
......@@ -234,6 +252,24 @@ func _ResourceService_GetResource_Handler(srv interface{}, ctx context.Context,
return interceptor(ctx, in, info, handler)
}
func _ResourceService_GetResourceBinary_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(GetResourceBinaryRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(ResourceServiceServer).GetResourceBinary(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: ResourceService_GetResourceBinary_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(ResourceServiceServer).GetResourceBinary(ctx, req.(*GetResourceBinaryRequest))
}
return interceptor(ctx, in, info, handler)
}
func _ResourceService_UpdateResource_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(UpdateResourceRequest)
if err := dec(in); err != nil {
......@@ -293,6 +329,10 @@ var ResourceService_ServiceDesc = grpc.ServiceDesc{
MethodName: "GetResource",
Handler: _ResourceService_GetResource_Handler,
},
{
MethodName: "GetResourceBinary",
Handler: _ResourceService_GetResourceBinary_Handler,
},
{
MethodName: "UpdateResource",
Handler: _ResourceService_UpdateResource_Handler,
......
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.33.0
// protoc-gen-go v1.34.0
// protoc (unknown)
// source: api/v1/tag_service.proto
......
This diff is collapsed.
This diff is collapsed.
......@@ -24,7 +24,7 @@ const (
UserService_ListUsers_FullMethodName = "/memos.api.v1.UserService/ListUsers"
UserService_SearchUsers_FullMethodName = "/memos.api.v1.UserService/SearchUsers"
UserService_GetUser_FullMethodName = "/memos.api.v1.UserService/GetUser"
UserService_GetUserAvatar_FullMethodName = "/memos.api.v1.UserService/GetUserAvatar"
UserService_GetUserAvatarBinary_FullMethodName = "/memos.api.v1.UserService/GetUserAvatarBinary"
UserService_CreateUser_FullMethodName = "/memos.api.v1.UserService/CreateUser"
UserService_UpdateUser_FullMethodName = "/memos.api.v1.UserService/UpdateUser"
UserService_DeleteUser_FullMethodName = "/memos.api.v1.UserService/DeleteUser"
......@@ -45,8 +45,8 @@ type UserServiceClient interface {
SearchUsers(ctx context.Context, in *SearchUsersRequest, opts ...grpc.CallOption) (*SearchUsersResponse, error)
// GetUser gets a user by name.
GetUser(ctx context.Context, in *GetUserRequest, opts ...grpc.CallOption) (*User, error)
// GetUserAvatar gets the avatar of a user.
GetUserAvatar(ctx context.Context, in *GetUserAvatarRequest, opts ...grpc.CallOption) (*httpbody.HttpBody, error)
// GetUserAvatarBinary gets the avatar of a user.
GetUserAvatarBinary(ctx context.Context, in *GetUserAvatarBinaryRequest, opts ...grpc.CallOption) (*httpbody.HttpBody, error)
// CreateUser creates a new user.
CreateUser(ctx context.Context, in *CreateUserRequest, opts ...grpc.CallOption) (*User, error)
// UpdateUser updates a user.
......@@ -100,9 +100,9 @@ func (c *userServiceClient) GetUser(ctx context.Context, in *GetUserRequest, opt
return out, nil
}
func (c *userServiceClient) GetUserAvatar(ctx context.Context, in *GetUserAvatarRequest, opts ...grpc.CallOption) (*httpbody.HttpBody, error) {
func (c *userServiceClient) GetUserAvatarBinary(ctx context.Context, in *GetUserAvatarBinaryRequest, opts ...grpc.CallOption) (*httpbody.HttpBody, error) {
out := new(httpbody.HttpBody)
err := c.cc.Invoke(ctx, UserService_GetUserAvatar_FullMethodName, in, out, opts...)
err := c.cc.Invoke(ctx, UserService_GetUserAvatarBinary_FullMethodName, in, out, opts...)
if err != nil {
return nil, err
}
......@@ -191,8 +191,8 @@ type UserServiceServer interface {
SearchUsers(context.Context, *SearchUsersRequest) (*SearchUsersResponse, error)
// GetUser gets a user by name.
GetUser(context.Context, *GetUserRequest) (*User, error)
// GetUserAvatar gets the avatar of a user.
GetUserAvatar(context.Context, *GetUserAvatarRequest) (*httpbody.HttpBody, error)
// GetUserAvatarBinary gets the avatar of a user.
GetUserAvatarBinary(context.Context, *GetUserAvatarBinaryRequest) (*httpbody.HttpBody, error)
// CreateUser creates a new user.
CreateUser(context.Context, *CreateUserRequest) (*User, error)
// UpdateUser updates a user.
......@@ -225,8 +225,8 @@ func (UnimplementedUserServiceServer) SearchUsers(context.Context, *SearchUsersR
func (UnimplementedUserServiceServer) GetUser(context.Context, *GetUserRequest) (*User, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetUser not implemented")
}
func (UnimplementedUserServiceServer) GetUserAvatar(context.Context, *GetUserAvatarRequest) (*httpbody.HttpBody, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetUserAvatar not implemented")
func (UnimplementedUserServiceServer) GetUserAvatarBinary(context.Context, *GetUserAvatarBinaryRequest) (*httpbody.HttpBody, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetUserAvatarBinary not implemented")
}
func (UnimplementedUserServiceServer) CreateUser(context.Context, *CreateUserRequest) (*User, error) {
return nil, status.Errorf(codes.Unimplemented, "method CreateUser not implemented")
......@@ -319,20 +319,20 @@ func _UserService_GetUser_Handler(srv interface{}, ctx context.Context, dec func
return interceptor(ctx, in, info, handler)
}
func _UserService_GetUserAvatar_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(GetUserAvatarRequest)
func _UserService_GetUserAvatarBinary_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(GetUserAvatarBinaryRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(UserServiceServer).GetUserAvatar(ctx, in)
return srv.(UserServiceServer).GetUserAvatarBinary(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: UserService_GetUserAvatar_FullMethodName,
FullMethod: UserService_GetUserAvatarBinary_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(UserServiceServer).GetUserAvatar(ctx, req.(*GetUserAvatarRequest))
return srv.(UserServiceServer).GetUserAvatarBinary(ctx, req.(*GetUserAvatarBinaryRequest))
}
return interceptor(ctx, in, info, handler)
}
......@@ -501,8 +501,8 @@ var UserService_ServiceDesc = grpc.ServiceDesc{
Handler: _UserService_GetUser_Handler,
},
{
MethodName: "GetUserAvatar",
Handler: _UserService_GetUserAvatar_Handler,
MethodName: "GetUserAvatarBinary",
Handler: _UserService_GetUserAvatarBinary_Handler,
},
{
MethodName: "CreateUser",
......
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.33.0
// protoc-gen-go v1.34.0
// protoc (unknown)
// source: api/v1/webhook_service.proto
......
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.33.0
// protoc-gen-go v1.34.0
// protoc (unknown)
// source: api/v1/workspace_service.proto
......
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.33.0
// protoc-gen-go v1.34.0
// protoc (unknown)
// source: store/activity.proto
......
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.33.0
// protoc-gen-go v1.34.0
// protoc (unknown)
// source: store/idp.proto
......
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.33.0
// protoc-gen-go v1.34.0
// protoc (unknown)
// source: store/inbox.proto
......
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.33.0
// protoc-gen-go v1.34.0
// protoc (unknown)
// source: store/reaction.proto
......
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.33.0
// protoc-gen-go v1.34.0
// protoc (unknown)
// source: store/user_setting.proto
......
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.33.0
// protoc-gen-go v1.34.0
// protoc (unknown)
// source: store/workspace_setting.proto
......
......@@ -5,6 +5,7 @@ import (
"context"
"encoding/binary"
"fmt"
"io"
"net/url"
"os"
"path/filepath"
......@@ -16,6 +17,7 @@ import (
"github.com/lithammer/shortuuid/v4"
"github.com/pkg/errors"
expr "google.golang.org/genproto/googleapis/api/expr/v1alpha1"
"google.golang.org/genproto/googleapis/api/httpbody"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"google.golang.org/protobuf/types/known/emptypb"
......@@ -159,6 +161,71 @@ func (s *APIV1Service) GetResource(ctx context.Context, request *v1pb.GetResourc
return s.convertResourceFromStore(ctx, resource), nil
}
func (s *APIV1Service) GetResourceBinary(ctx context.Context, request *v1pb.GetResourceBinaryRequest) (*httpbody.HttpBody, error) {
resourceFind := &store.FindResource{
GetBlob: true,
}
if request.Name != "" {
id, err := ExtractResourceIDFromName(request.Name)
if err != nil {
return nil, status.Errorf(codes.InvalidArgument, "invalid resource id: %v", err)
}
resourceFind.ID = &id
}
if request.Uid != "" {
resourceFind.UID = &request.Uid
}
resource, err := s.Store.GetResource(ctx, resourceFind)
if err != nil {
return nil, status.Errorf(codes.Internal, "failed to get resource: %v", err)
}
if resource == nil {
return nil, status.Errorf(codes.NotFound, "resource not found")
}
// Check the related memo visibility.
if resource.MemoID != nil {
memo, err := s.Store.GetMemo(ctx, &store.FindMemo{
ID: resource.MemoID,
})
if err != nil {
return nil, status.Errorf(codes.Internal, "failed to find memo by ID: %v", resource.MemoID)
}
if memo != nil && memo.Visibility != store.Public {
user, err := getCurrentUser(ctx, s.Store)
if err != nil {
return nil, status.Errorf(codes.Internal, "failed to get current user: %v", err)
}
if memo.Visibility == store.Private && user.ID != resource.CreatorID {
return nil, status.Errorf(codes.Unauthenticated, "unauthorized access")
}
}
}
blob := resource.Blob
if resource.InternalPath != "" {
resourcePath := filepath.FromSlash(resource.InternalPath)
if !filepath.IsAbs(resourcePath) {
resourcePath = filepath.Join(s.Profile.Data, resourcePath)
}
file, err := os.Open(resourcePath)
if err != nil {
return nil, status.Errorf(codes.Internal, "failed to open the file: %v", err)
}
defer file.Close()
blob, err = io.ReadAll(file)
if err != nil {
return nil, status.Errorf(codes.Internal, "failed to read the file: %v", err)
}
}
httpBody := &httpbody.HttpBody{
ContentType: resource.Type,
Data: blob,
}
return httpBody, nil
}
func (s *APIV1Service) UpdateResource(ctx context.Context, request *v1pb.UpdateResourceRequest) (*v1pb.Resource, error) {
id, err := ExtractResourceIDFromName(request.Resource.Name)
if err != nil {
......
......@@ -103,7 +103,7 @@ func (s *APIV1Service) GetUser(ctx context.Context, request *v1pb.GetUserRequest
return convertUserFromStore(user), nil
}
func (s *APIV1Service) GetUserAvatar(ctx context.Context, request *v1pb.GetUserAvatarRequest) (*httpbody.HttpBody, error) {
func (s *APIV1Service) GetUserAvatarBinary(ctx context.Context, request *v1pb.GetUserAvatarBinaryRequest) (*httpbody.HttpBody, error) {
userID, err := ExtractUserIDFromName(request.Name)
if err != nil {
return nil, status.Errorf(codes.InvalidArgument, "invalid user name: %v", err)
......@@ -506,7 +506,7 @@ func (s *APIV1Service) UpsertAccessTokenToStore(ctx context.Context, user *store
}
func convertUserFromStore(user *store.User) *v1pb.User {
return &v1pb.User{
userpb := &v1pb.User{
Name: fmt.Sprintf("%s%d", UserNamePrefix, user.ID),
Id: user.ID,
RowStatus: convertRowStatusFromStore(user.RowStatus),
......@@ -519,6 +519,11 @@ func convertUserFromStore(user *store.User) *v1pb.User {
AvatarUrl: user.AvatarURL,
Description: user.Description,
}
// Use the avatar URL instead of raw base64 image data to reduce the response size.
if user.AvatarURL != "" {
userpb.AvatarUrl = fmt.Sprintf("/o/%s/avatar", userpb.Name)
}
return userpb
}
func convertUserRoleFromStore(role store.Role) v1pb.User_Role {
......
......@@ -111,7 +111,7 @@ func (s *APIV1Service) RegisterGateway(ctx context.Context, echoServer *echo.Ech
if err := v1pb.RegisterIdentityProviderServiceHandler(context.Background(), gwMux, conn); err != nil {
return err
}
echoServer.Any("/api/v1/*", echo.WrapHandler(gwMux))
echoServer.Any("*", echo.WrapHandler(gwMux))
// GRPC web proxy.
options := []grpcweb.Option{
......
......@@ -41,7 +41,7 @@ func NewFrontendService(profile *profile.Profile, store *store.Store) *FrontendS
func (s *FrontendService) Serve(ctx context.Context, e *echo.Echo) {
skipper := func(c echo.Context) bool {
return util.HasPrefixes(c.Path(), "/api", "/memos.api.v1", "/robots.txt", "/sitemap.xml", "/m/:name")
return util.HasPrefixes(c.Path(), "/o/", "/api/", "/memos.api.v1", "/robots.txt", "/sitemap.xml", "/m/:name")
}
// Use echo static middleware to serve the built dist folder.
......@@ -55,9 +55,6 @@ func (s *FrontendService) Serve(ctx context.Context, e *echo.Echo) {
// Use echo gzip middleware to compress the response.
// Reference: https://echo.labstack.com/docs/middleware/gzip
g.Use(middleware.GzipWithConfig(middleware.GzipConfig{
Skipper: func(c echo.Context) bool {
return util.HasPrefixes(c.Path(), "/api", "/memos.api.v1", "/robots.txt", "/sitemap.xml", "/m/:name")
},
Level: 5,
}))
g.Use(func(next echo.HandlerFunc) echo.HandlerFunc {
......
package resource
import (
"bytes"
"fmt"
"io"
"log/slog"
"net/http"
"os"
"path/filepath"
"strings"
"sync/atomic"
"time"
"github.com/disintegration/imaging"
"github.com/labstack/echo/v4"
"github.com/pkg/errors"
"github.com/usememos/memos/internal/util"
"github.com/usememos/memos/server/profile"
"github.com/usememos/memos/server/route/api/auth"
"github.com/usememos/memos/store"
)
const (
// thumbnailImagePath is the directory to store image thumbnails.
thumbnailImagePath = ".thumbnail_cache"
)
type ResourceService struct {
Profile *profile.Profile
Store *store.Store
}
func NewResourceService(profile *profile.Profile, store *store.Store) *ResourceService {
return &ResourceService{
Profile: profile,
Store: store,
}
}
func (s *ResourceService) RegisterRoutes(g *echo.Group) {
g.GET("/r/:uid", s.streamResource)
g.GET("/r/:uid/*", s.streamResource)
}
func (s *ResourceService) streamResource(c echo.Context) error {
ctx := c.Request().Context()
uid := c.Param("uid")
resource, err := s.Store.GetResource(ctx, &store.FindResource{
UID: &uid,
GetBlob: true,
})
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, fmt.Sprintf("Failed to find resource by uid: %s", uid)).SetInternal(err)
}
if resource == nil {
return echo.NewHTTPError(http.StatusNotFound, fmt.Sprintf("Resource not found: %s", uid))
}
// Check the related memo visibility.
if resource.MemoID != nil {
memo, err := s.Store.GetMemo(ctx, &store.FindMemo{
ID: resource.MemoID,
})
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, fmt.Sprintf("Failed to find memo by ID: %v", resource.MemoID)).SetInternal(err)
}
if memo != nil && memo.Visibility != store.Public {
userID, ok := c.Get(auth.UserIDContextKey).(int32)
if !ok || (memo.Visibility == store.Private && userID != resource.CreatorID) {
return echo.NewHTTPError(http.StatusUnauthorized, "Resource visibility not match")
}
}
}
blob := resource.Blob
if resource.InternalPath != "" {
resourcePath := filepath.FromSlash(resource.InternalPath)
if !filepath.IsAbs(resourcePath) {
resourcePath = filepath.Join(s.Profile.Data, resourcePath)
}
src, err := os.Open(resourcePath)
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, fmt.Sprintf("Failed to open the local resource: %s", resourcePath)).SetInternal(err)
}
defer src.Close()
blob, err = io.ReadAll(src)
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, fmt.Sprintf("Failed to read the local resource: %s", resourcePath)).SetInternal(err)
}
}
if c.QueryParam("thumbnail") == "1" && util.HasPrefixes(resource.Type, "image/png", "image/jpeg") {
ext := filepath.Ext(resource.Filename)
thumbnailPath := filepath.Join(s.Profile.Data, thumbnailImagePath, fmt.Sprintf("%d%s", resource.ID, ext))
thumbnailBlob, err := getOrGenerateThumbnailImage(blob, thumbnailPath)
if err != nil {
slog.Warn("failed to get or generate thumbnail image", err)
} else {
blob = thumbnailBlob
}
}
c.Response().Writer.Header().Set(echo.HeaderCacheControl, "max-age=3600")
c.Response().Writer.Header().Set(echo.HeaderContentSecurityPolicy, "default-src 'none'; script-src 'none'; img-src 'self'; media-src 'self'; sandbox;")
c.Response().Writer.Header().Set("Content-Disposition", fmt.Sprintf(`filename="%s"`, resource.Filename))
resourceType := strings.ToLower(resource.Type)
if strings.HasPrefix(resourceType, "text") {
resourceType = echo.MIMETextPlainCharsetUTF8
} else if strings.HasPrefix(resourceType, "video") || strings.HasPrefix(resourceType, "audio") {
http.ServeContent(c.Response(), c.Request(), resource.Filename, time.Unix(resource.UpdatedTs, 0), bytes.NewReader(blob))
return nil
}
return c.Stream(http.StatusOK, resourceType, bytes.NewReader(blob))
}
var availableGeneratorAmount int32 = 32
func getOrGenerateThumbnailImage(srcBlob []byte, dstPath string) ([]byte, error) {
if _, err := os.Stat(dstPath); err != nil {
if !errors.Is(err, os.ErrNotExist) {
return nil, errors.Wrap(err, "failed to check thumbnail image stat")
}
if atomic.LoadInt32(&availableGeneratorAmount) <= 0 {
return nil, errors.New("not enough available generator amount")
}
atomic.AddInt32(&availableGeneratorAmount, -1)
defer func() {
atomic.AddInt32(&availableGeneratorAmount, 1)
}()
reader := bytes.NewReader(srcBlob)
src, err := imaging.Decode(reader, imaging.AutoOrientation(true))
if err != nil {
return nil, errors.Wrap(err, "failed to decode thumbnail image")
}
thumbnailImage := imaging.Resize(src, 512, 0, imaging.Lanczos)
dstDir := filepath.Dir(dstPath)
if err := os.MkdirAll(dstDir, os.ModePerm); err != nil {
return nil, errors.Wrap(err, "failed to create thumbnail dir")
}
if err := imaging.Save(thumbnailImage, dstPath); err != nil {
return nil, errors.Wrap(err, "failed to resize thumbnail image")
}
}
dstFile, err := os.Open(dstPath)
if err != nil {
return nil, errors.Wrap(err, "failed to open the local resource")
}
defer dstFile.Close()
dstBlob, err := io.ReadAll(dstFile)
if err != nil {
return nil, errors.Wrap(err, "failed to read the local resource")
}
return dstBlob, nil
}
......@@ -16,10 +16,8 @@ import (
storepb "github.com/usememos/memos/proto/gen/store"
"github.com/usememos/memos/server/profile"
"github.com/usememos/memos/server/route/api/auth"
apiv1 "github.com/usememos/memos/server/route/api/v1"
"github.com/usememos/memos/server/route/frontend"
"github.com/usememos/memos/server/route/resource"
"github.com/usememos/memos/server/route/rss"
versionchecker "github.com/usememos/memos/server/service/version_checker"
"github.com/usememos/memos/store"
......@@ -70,15 +68,6 @@ func NewServer(ctx context.Context, profile *profile.Profile, store *store.Store
rootGroup := echoServer.Group("")
// Register public routes.
publicGroup := rootGroup.Group("/o")
publicGroup.Use(func(next echo.HandlerFunc) echo.HandlerFunc {
return auth.JWTMiddleware(s.Store, next, s.Secret)
})
// Create and register resource public routes.
resource.NewResourceService(s.Profile, s.Store).RegisterRoutes(publicGroup)
// Create and register RSS routes.
rss.NewRSSService(s.Profile, s.Store).RegisterRoutes(rootGroup)
......
......@@ -4,14 +4,12 @@ import { useTranslation } from "react-i18next";
import { Outlet } from "react-router-dom";
import useLocalStorage from "react-use/lib/useLocalStorage";
import { getSystemColorScheme } from "./helpers/utils";
import useNavigateTo from "./hooks/useNavigateTo";
import { useCommonContext } from "./layouts/CommonContextProvider";
import { useUserStore, useWorkspaceSettingStore } from "./store/v1";
import { WorkspaceGeneralSetting, WorkspaceSettingKey } from "./types/proto/store/workspace_setting";
const App = () => {
const { i18n } = useTranslation();
const navigateTo = useNavigateTo();
const { mode, setMode } = useColorScheme();
const workspaceSettingStore = useWorkspaceSettingStore();
const userStore = useUserStore();
......@@ -28,7 +26,7 @@ const App = () => {
// Redirect to sign up page if no instance owner.
useEffect(() => {
if (!workspaceProfile.owner) {
navigateTo("/auth/signup");
window.location.href = "/auth/signup";
}
}, [workspaceProfile.owner]);
......
......@@ -6,13 +6,11 @@ import useLocalStorage from "react-use/lib/useLocalStorage";
import Icon from "@/components/Icon";
import Navigation from "@/components/Navigation";
import useCurrentUser from "@/hooks/useCurrentUser";
import useNavigateTo from "@/hooks/useNavigateTo";
import useResponsiveWidth from "@/hooks/useResponsiveWidth";
import Loading from "@/pages/Loading";
import { Routes } from "@/router";
const HomeLayout = () => {
const navigateTo = useNavigateTo();
const location = useLocation();
const { sm } = useResponsiveWidth();
const currentUser = useCurrentUser();
......@@ -27,9 +25,10 @@ const HomeLayout = () => {
location.pathname,
)
) {
navigateTo(Routes.EXPLORE);
window.location.href = Routes.EXPLORE;
return;
}
setInitialized(true);
}, []);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment