Commit ff175bbb authored by Steven's avatar Steven

refactor: update resource binary request handler

parent 6d3d71df
...@@ -1371,41 +1371,6 @@ paths: ...@@ -1371,41 +1371,6 @@ paths:
type: string type: string
tags: tags:
- UserService - 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: /api/v1/{name}/comments:
get: get:
summary: ListMemoComments lists comments for a memo. summary: ListMemoComments lists comments for a memo.
...@@ -1790,6 +1755,105 @@ paths: ...@@ -1790,6 +1755,105 @@ paths:
format: date-time format: date-time
tags: tags:
- UserService - 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: definitions:
MemoServiceSetMemoRelationsBody: MemoServiceSetMemoRelationsBody:
type: object type: object
...@@ -2137,8 +2201,7 @@ definitions: ...@@ -2137,8 +2201,7 @@ definitions:
Note: this functionality is not currently available in the official Note: this functionality is not currently available in the official
protobuf release, and it is not used for type URLs beginning with 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 type.googleapis.com.
implementations and no plans to implement one.
Schemes other than `http`, `https` (or the empty scheme) might be Schemes other than `http`, `https` (or the empty scheme) might be
used with implementation specific semantics. used with implementation specific semantics.
...@@ -2173,7 +2236,7 @@ definitions: ...@@ -2173,7 +2236,7 @@ definitions:
foo = any.unpack(Foo.getDefaultInstance()); foo = any.unpack(Foo.getDefaultInstance());
} }
Example 3: Pack and unpack a message in Python. Example 3: Pack and unpack a message in Python.
foo = Foo(...) foo = Foo(...)
any = Any() any = Any()
...@@ -2183,7 +2246,7 @@ definitions: ...@@ -2183,7 +2246,7 @@ definitions:
any.Unpack(foo) any.Unpack(foo)
... ...
Example 4: Pack and unpack a message in Go Example 4: Pack and unpack a message in Go
foo := &pb.Foo{...} foo := &pb.Foo{...}
any, err := anypb.New(foo) any, err := anypb.New(foo)
...@@ -2203,7 +2266,7 @@ definitions: ...@@ -2203,7 +2266,7 @@ definitions:
name "y.z". name "y.z".
JSON JSON
====
The JSON representation of an `Any` value uses the regular The JSON representation of an `Any` value uses the regular
representation of the deserialized, embedded message, with an representation of the deserialized, embedded message, with an
additional field `@type` which contains the type URL. Example: additional field `@type` which contains the type URL. Example:
......
...@@ -5,6 +5,7 @@ package memos.api.v1; ...@@ -5,6 +5,7 @@ package memos.api.v1;
import "google/api/annotations.proto"; import "google/api/annotations.proto";
import "google/api/client.proto"; import "google/api/client.proto";
import "google/api/field_behavior.proto"; import "google/api/field_behavior.proto";
import "google/api/httpbody.proto";
import "google/protobuf/empty.proto"; import "google/protobuf/empty.proto";
import "google/protobuf/field_mask.proto"; import "google/protobuf/field_mask.proto";
import "google/protobuf/timestamp.proto"; import "google/protobuf/timestamp.proto";
...@@ -32,6 +33,11 @@ service ResourceService { ...@@ -32,6 +33,11 @@ service ResourceService {
option (google.api.http) = {get: "/api/v1/{name=resources/*}"}; option (google.api.http) = {get: "/api/v1/{name=resources/*}"};
option (google.api.method_signature) = "name"; 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. // UpdateResource updates a resource.
rpc UpdateResource(UpdateResourceRequest) returns (Resource) { rpc UpdateResource(UpdateResourceRequest) returns (Resource) {
option (google.api.http) = { option (google.api.http) = {
...@@ -98,6 +104,16 @@ message GetResourceRequest { ...@@ -98,6 +104,16 @@ message GetResourceRequest {
string name = 1; 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 { message UpdateResourceRequest {
Resource resource = 1; Resource resource = 1;
......
...@@ -27,9 +27,13 @@ service UserService { ...@@ -27,9 +27,13 @@ service UserService {
option (google.api.http) = {get: "/api/v1/{name=users/*}"}; option (google.api.http) = {get: "/api/v1/{name=users/*}"};
option (google.api.method_signature) = "name"; option (google.api.method_signature) = "name";
} }
// GetUserAvatar gets the avatar of a user. // GetUserAvatarBinary gets the avatar of a user.
rpc GetUserAvatar(GetUserAvatarRequest) returns (google.api.HttpBody) { rpc GetUserAvatarBinary(GetUserAvatarBinaryRequest) returns (google.api.HttpBody) {
option (google.api.http) = {get: "/api/v1/{name=users/*}/avatar"}; option (google.api.http) = {
get: "/{name=users/*}/avatar"
additional_bindings: {get: "/o/{name=users/*}/avatar"}
};
option (google.api.method_signature) = "name"; option (google.api.method_signature) = "name";
} }
// CreateUser creates a new user. // CreateUser creates a new user.
...@@ -143,7 +147,7 @@ message GetUserRequest { ...@@ -143,7 +147,7 @@ message GetUserRequest {
string name = 1; string name = 1;
} }
message GetUserAvatarRequest { message GetUserAvatarBinaryRequest {
// The name of the user. // The name of the user.
// Format: users/{id} // Format: users/{id}
string name = 1; string name = 1;
......
This diff is collapsed.
// Code generated by protoc-gen-go. DO NOT EDIT. // Code generated by protoc-gen-go. DO NOT EDIT.
// versions: // versions:
// protoc-gen-go v1.33.0 // protoc-gen-go v1.34.0
// protoc (unknown) // protoc (unknown)
// source: api/v1/auth_service.proto // source: api/v1/auth_service.proto
......
// Code generated by protoc-gen-go. DO NOT EDIT. // Code generated by protoc-gen-go. DO NOT EDIT.
// versions: // versions:
// protoc-gen-go v1.33.0 // protoc-gen-go v1.34.0
// protoc (unknown) // protoc (unknown)
// source: api/v1/common.proto // source: api/v1/common.proto
......
// Code generated by protoc-gen-go. DO NOT EDIT. // Code generated by protoc-gen-go. DO NOT EDIT.
// versions: // versions:
// protoc-gen-go v1.33.0 // protoc-gen-go v1.34.0
// protoc (unknown) // protoc (unknown)
// source: api/v1/idp_service.proto // source: api/v1/idp_service.proto
......
// Code generated by protoc-gen-go. DO NOT EDIT. // Code generated by protoc-gen-go. DO NOT EDIT.
// versions: // versions:
// protoc-gen-go v1.33.0 // protoc-gen-go v1.34.0
// protoc (unknown) // protoc (unknown)
// source: api/v1/inbox_service.proto // source: api/v1/inbox_service.proto
......
// Code generated by protoc-gen-go. DO NOT EDIT. // Code generated by protoc-gen-go. DO NOT EDIT.
// versions: // versions:
// protoc-gen-go v1.33.0 // protoc-gen-go v1.34.0
// protoc (unknown) // protoc (unknown)
// source: api/v1/markdown_service.proto // source: api/v1/markdown_service.proto
......
// Code generated by protoc-gen-go. DO NOT EDIT. // Code generated by protoc-gen-go. DO NOT EDIT.
// versions: // versions:
// protoc-gen-go v1.33.0 // protoc-gen-go v1.34.0
// protoc (unknown) // protoc (unknown)
// source: api/v1/memo_relation_service.proto // source: api/v1/memo_relation_service.proto
......
This diff is collapsed.
// Code generated by protoc-gen-go. DO NOT EDIT. // Code generated by protoc-gen-go. DO NOT EDIT.
// versions: // versions:
// protoc-gen-go v1.33.0 // protoc-gen-go v1.34.0
// protoc (unknown) // protoc (unknown)
// source: api/v1/reaction_service.proto // source: api/v1/reaction_service.proto
......
This diff is collapsed.
...@@ -163,6 +163,76 @@ func local_request_ResourceService_GetResource_0(ctx context.Context, marshaler ...@@ -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 ( 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}} 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 ...@@ -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) { mux.Handle("PATCH", pattern_ResourceService_UpdateResource_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context()) ctx, cancel := context.WithCancel(req.Context())
defer cancel() defer cancel()
...@@ -600,6 +695,28 @@ func RegisterResourceServiceHandlerClient(ctx context.Context, mux *runtime.Serv ...@@ -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) { mux.Handle("PATCH", pattern_ResourceService_UpdateResource_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context()) ctx, cancel := context.WithCancel(req.Context())
defer cancel() defer cancel()
...@@ -656,6 +773,8 @@ var ( ...@@ -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_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_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"}, "")) 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 ( ...@@ -670,6 +789,8 @@ var (
forward_ResourceService_GetResource_0 = runtime.ForwardResponseMessage forward_ResourceService_GetResource_0 = runtime.ForwardResponseMessage
forward_ResourceService_GetResourceBinary_0 = runtime.ForwardResponseMessage
forward_ResourceService_UpdateResource_0 = runtime.ForwardResponseMessage forward_ResourceService_UpdateResource_0 = runtime.ForwardResponseMessage
forward_ResourceService_DeleteResource_0 = runtime.ForwardResponseMessage forward_ResourceService_DeleteResource_0 = runtime.ForwardResponseMessage
......
...@@ -8,6 +8,7 @@ package apiv1 ...@@ -8,6 +8,7 @@ package apiv1
import ( import (
context "context" context "context"
httpbody "google.golang.org/genproto/googleapis/api/httpbody"
grpc "google.golang.org/grpc" grpc "google.golang.org/grpc"
codes "google.golang.org/grpc/codes" codes "google.golang.org/grpc/codes"
status "google.golang.org/grpc/status" status "google.golang.org/grpc/status"
...@@ -20,12 +21,13 @@ import ( ...@@ -20,12 +21,13 @@ import (
const _ = grpc.SupportPackageIsVersion7 const _ = grpc.SupportPackageIsVersion7
const ( const (
ResourceService_CreateResource_FullMethodName = "/memos.api.v1.ResourceService/CreateResource" ResourceService_CreateResource_FullMethodName = "/memos.api.v1.ResourceService/CreateResource"
ResourceService_ListResources_FullMethodName = "/memos.api.v1.ResourceService/ListResources" ResourceService_ListResources_FullMethodName = "/memos.api.v1.ResourceService/ListResources"
ResourceService_SearchResources_FullMethodName = "/memos.api.v1.ResourceService/SearchResources" ResourceService_SearchResources_FullMethodName = "/memos.api.v1.ResourceService/SearchResources"
ResourceService_GetResource_FullMethodName = "/memos.api.v1.ResourceService/GetResource" ResourceService_GetResource_FullMethodName = "/memos.api.v1.ResourceService/GetResource"
ResourceService_UpdateResource_FullMethodName = "/memos.api.v1.ResourceService/UpdateResource" ResourceService_GetResourceBinary_FullMethodName = "/memos.api.v1.ResourceService/GetResourceBinary"
ResourceService_DeleteResource_FullMethodName = "/memos.api.v1.ResourceService/DeleteResource" ResourceService_UpdateResource_FullMethodName = "/memos.api.v1.ResourceService/UpdateResource"
ResourceService_DeleteResource_FullMethodName = "/memos.api.v1.ResourceService/DeleteResource"
) )
// ResourceServiceClient is the client API for ResourceService service. // ResourceServiceClient is the client API for ResourceService service.
...@@ -40,6 +42,8 @@ type ResourceServiceClient interface { ...@@ -40,6 +42,8 @@ type ResourceServiceClient interface {
SearchResources(ctx context.Context, in *SearchResourcesRequest, opts ...grpc.CallOption) (*SearchResourcesResponse, error) SearchResources(ctx context.Context, in *SearchResourcesRequest, opts ...grpc.CallOption) (*SearchResourcesResponse, error)
// GetResource returns a resource by name. // GetResource returns a resource by name.
GetResource(ctx context.Context, in *GetResourceRequest, opts ...grpc.CallOption) (*Resource, error) 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 updates a resource.
UpdateResource(ctx context.Context, in *UpdateResourceRequest, opts ...grpc.CallOption) (*Resource, error) UpdateResource(ctx context.Context, in *UpdateResourceRequest, opts ...grpc.CallOption) (*Resource, error)
// DeleteResource deletes a resource by name. // DeleteResource deletes a resource by name.
...@@ -90,6 +94,15 @@ func (c *resourceServiceClient) GetResource(ctx context.Context, in *GetResource ...@@ -90,6 +94,15 @@ func (c *resourceServiceClient) GetResource(ctx context.Context, in *GetResource
return out, nil 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) { func (c *resourceServiceClient) UpdateResource(ctx context.Context, in *UpdateResourceRequest, opts ...grpc.CallOption) (*Resource, error) {
out := new(Resource) out := new(Resource)
err := c.cc.Invoke(ctx, ResourceService_UpdateResource_FullMethodName, in, out, opts...) err := c.cc.Invoke(ctx, ResourceService_UpdateResource_FullMethodName, in, out, opts...)
...@@ -120,6 +133,8 @@ type ResourceServiceServer interface { ...@@ -120,6 +133,8 @@ type ResourceServiceServer interface {
SearchResources(context.Context, *SearchResourcesRequest) (*SearchResourcesResponse, error) SearchResources(context.Context, *SearchResourcesRequest) (*SearchResourcesResponse, error)
// GetResource returns a resource by name. // GetResource returns a resource by name.
GetResource(context.Context, *GetResourceRequest) (*Resource, error) 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 updates a resource.
UpdateResource(context.Context, *UpdateResourceRequest) (*Resource, error) UpdateResource(context.Context, *UpdateResourceRequest) (*Resource, error)
// DeleteResource deletes a resource by name. // DeleteResource deletes a resource by name.
...@@ -143,6 +158,9 @@ func (UnimplementedResourceServiceServer) SearchResources(context.Context, *Sear ...@@ -143,6 +158,9 @@ func (UnimplementedResourceServiceServer) SearchResources(context.Context, *Sear
func (UnimplementedResourceServiceServer) GetResource(context.Context, *GetResourceRequest) (*Resource, error) { func (UnimplementedResourceServiceServer) GetResource(context.Context, *GetResourceRequest) (*Resource, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetResource not implemented") 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) { func (UnimplementedResourceServiceServer) UpdateResource(context.Context, *UpdateResourceRequest) (*Resource, error) {
return nil, status.Errorf(codes.Unimplemented, "method UpdateResource not implemented") return nil, status.Errorf(codes.Unimplemented, "method UpdateResource not implemented")
} }
...@@ -234,6 +252,24 @@ func _ResourceService_GetResource_Handler(srv interface{}, ctx context.Context, ...@@ -234,6 +252,24 @@ func _ResourceService_GetResource_Handler(srv interface{}, ctx context.Context,
return interceptor(ctx, in, info, handler) 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) { func _ResourceService_UpdateResource_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(UpdateResourceRequest) in := new(UpdateResourceRequest)
if err := dec(in); err != nil { if err := dec(in); err != nil {
...@@ -293,6 +329,10 @@ var ResourceService_ServiceDesc = grpc.ServiceDesc{ ...@@ -293,6 +329,10 @@ var ResourceService_ServiceDesc = grpc.ServiceDesc{
MethodName: "GetResource", MethodName: "GetResource",
Handler: _ResourceService_GetResource_Handler, Handler: _ResourceService_GetResource_Handler,
}, },
{
MethodName: "GetResourceBinary",
Handler: _ResourceService_GetResourceBinary_Handler,
},
{ {
MethodName: "UpdateResource", MethodName: "UpdateResource",
Handler: _ResourceService_UpdateResource_Handler, Handler: _ResourceService_UpdateResource_Handler,
......
// Code generated by protoc-gen-go. DO NOT EDIT. // Code generated by protoc-gen-go. DO NOT EDIT.
// versions: // versions:
// protoc-gen-go v1.33.0 // protoc-gen-go v1.34.0
// protoc (unknown) // protoc (unknown)
// source: api/v1/tag_service.proto // source: api/v1/tag_service.proto
......
This diff is collapsed.
This diff is collapsed.
...@@ -24,7 +24,7 @@ const ( ...@@ -24,7 +24,7 @@ const (
UserService_ListUsers_FullMethodName = "/memos.api.v1.UserService/ListUsers" UserService_ListUsers_FullMethodName = "/memos.api.v1.UserService/ListUsers"
UserService_SearchUsers_FullMethodName = "/memos.api.v1.UserService/SearchUsers" UserService_SearchUsers_FullMethodName = "/memos.api.v1.UserService/SearchUsers"
UserService_GetUser_FullMethodName = "/memos.api.v1.UserService/GetUser" 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_CreateUser_FullMethodName = "/memos.api.v1.UserService/CreateUser"
UserService_UpdateUser_FullMethodName = "/memos.api.v1.UserService/UpdateUser" UserService_UpdateUser_FullMethodName = "/memos.api.v1.UserService/UpdateUser"
UserService_DeleteUser_FullMethodName = "/memos.api.v1.UserService/DeleteUser" UserService_DeleteUser_FullMethodName = "/memos.api.v1.UserService/DeleteUser"
...@@ -45,8 +45,8 @@ type UserServiceClient interface { ...@@ -45,8 +45,8 @@ type UserServiceClient interface {
SearchUsers(ctx context.Context, in *SearchUsersRequest, opts ...grpc.CallOption) (*SearchUsersResponse, error) SearchUsers(ctx context.Context, in *SearchUsersRequest, opts ...grpc.CallOption) (*SearchUsersResponse, error)
// GetUser gets a user by name. // GetUser gets a user by name.
GetUser(ctx context.Context, in *GetUserRequest, opts ...grpc.CallOption) (*User, error) GetUser(ctx context.Context, in *GetUserRequest, opts ...grpc.CallOption) (*User, error)
// GetUserAvatar gets the avatar of a user. // GetUserAvatarBinary gets the avatar of a user.
GetUserAvatar(ctx context.Context, in *GetUserAvatarRequest, opts ...grpc.CallOption) (*httpbody.HttpBody, error) GetUserAvatarBinary(ctx context.Context, in *GetUserAvatarBinaryRequest, opts ...grpc.CallOption) (*httpbody.HttpBody, error)
// CreateUser creates a new user. // CreateUser creates a new user.
CreateUser(ctx context.Context, in *CreateUserRequest, opts ...grpc.CallOption) (*User, error) CreateUser(ctx context.Context, in *CreateUserRequest, opts ...grpc.CallOption) (*User, error)
// UpdateUser updates a user. // UpdateUser updates a user.
...@@ -100,9 +100,9 @@ func (c *userServiceClient) GetUser(ctx context.Context, in *GetUserRequest, opt ...@@ -100,9 +100,9 @@ func (c *userServiceClient) GetUser(ctx context.Context, in *GetUserRequest, opt
return out, nil 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) 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 { if err != nil {
return nil, err return nil, err
} }
...@@ -191,8 +191,8 @@ type UserServiceServer interface { ...@@ -191,8 +191,8 @@ type UserServiceServer interface {
SearchUsers(context.Context, *SearchUsersRequest) (*SearchUsersResponse, error) SearchUsers(context.Context, *SearchUsersRequest) (*SearchUsersResponse, error)
// GetUser gets a user by name. // GetUser gets a user by name.
GetUser(context.Context, *GetUserRequest) (*User, error) GetUser(context.Context, *GetUserRequest) (*User, error)
// GetUserAvatar gets the avatar of a user. // GetUserAvatarBinary gets the avatar of a user.
GetUserAvatar(context.Context, *GetUserAvatarRequest) (*httpbody.HttpBody, error) GetUserAvatarBinary(context.Context, *GetUserAvatarBinaryRequest) (*httpbody.HttpBody, error)
// CreateUser creates a new user. // CreateUser creates a new user.
CreateUser(context.Context, *CreateUserRequest) (*User, error) CreateUser(context.Context, *CreateUserRequest) (*User, error)
// UpdateUser updates a user. // UpdateUser updates a user.
...@@ -225,8 +225,8 @@ func (UnimplementedUserServiceServer) SearchUsers(context.Context, *SearchUsersR ...@@ -225,8 +225,8 @@ func (UnimplementedUserServiceServer) SearchUsers(context.Context, *SearchUsersR
func (UnimplementedUserServiceServer) GetUser(context.Context, *GetUserRequest) (*User, error) { func (UnimplementedUserServiceServer) GetUser(context.Context, *GetUserRequest) (*User, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetUser not implemented") return nil, status.Errorf(codes.Unimplemented, "method GetUser not implemented")
} }
func (UnimplementedUserServiceServer) GetUserAvatar(context.Context, *GetUserAvatarRequest) (*httpbody.HttpBody, error) { func (UnimplementedUserServiceServer) GetUserAvatarBinary(context.Context, *GetUserAvatarBinaryRequest) (*httpbody.HttpBody, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetUserAvatar not implemented") return nil, status.Errorf(codes.Unimplemented, "method GetUserAvatarBinary not implemented")
} }
func (UnimplementedUserServiceServer) CreateUser(context.Context, *CreateUserRequest) (*User, error) { func (UnimplementedUserServiceServer) CreateUser(context.Context, *CreateUserRequest) (*User, error) {
return nil, status.Errorf(codes.Unimplemented, "method CreateUser not implemented") 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 ...@@ -319,20 +319,20 @@ func _UserService_GetUser_Handler(srv interface{}, ctx context.Context, dec func
return interceptor(ctx, in, info, handler) return interceptor(ctx, in, info, handler)
} }
func _UserService_GetUserAvatar_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { func _UserService_GetUserAvatarBinary_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(GetUserAvatarRequest) in := new(GetUserAvatarBinaryRequest)
if err := dec(in); err != nil { if err := dec(in); err != nil {
return nil, err return nil, err
} }
if interceptor == nil { if interceptor == nil {
return srv.(UserServiceServer).GetUserAvatar(ctx, in) return srv.(UserServiceServer).GetUserAvatarBinary(ctx, in)
} }
info := &grpc.UnaryServerInfo{ info := &grpc.UnaryServerInfo{
Server: srv, Server: srv,
FullMethod: UserService_GetUserAvatar_FullMethodName, FullMethod: UserService_GetUserAvatarBinary_FullMethodName,
} }
handler := func(ctx context.Context, req interface{}) (interface{}, error) { 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) return interceptor(ctx, in, info, handler)
} }
...@@ -501,8 +501,8 @@ var UserService_ServiceDesc = grpc.ServiceDesc{ ...@@ -501,8 +501,8 @@ var UserService_ServiceDesc = grpc.ServiceDesc{
Handler: _UserService_GetUser_Handler, Handler: _UserService_GetUser_Handler,
}, },
{ {
MethodName: "GetUserAvatar", MethodName: "GetUserAvatarBinary",
Handler: _UserService_GetUserAvatar_Handler, Handler: _UserService_GetUserAvatarBinary_Handler,
}, },
{ {
MethodName: "CreateUser", MethodName: "CreateUser",
......
// Code generated by protoc-gen-go. DO NOT EDIT. // Code generated by protoc-gen-go. DO NOT EDIT.
// versions: // versions:
// protoc-gen-go v1.33.0 // protoc-gen-go v1.34.0
// protoc (unknown) // protoc (unknown)
// source: api/v1/webhook_service.proto // source: api/v1/webhook_service.proto
......
// Code generated by protoc-gen-go. DO NOT EDIT. // Code generated by protoc-gen-go. DO NOT EDIT.
// versions: // versions:
// protoc-gen-go v1.33.0 // protoc-gen-go v1.34.0
// protoc (unknown) // protoc (unknown)
// source: api/v1/workspace_service.proto // source: api/v1/workspace_service.proto
......
// Code generated by protoc-gen-go. DO NOT EDIT. // Code generated by protoc-gen-go. DO NOT EDIT.
// versions: // versions:
// protoc-gen-go v1.33.0 // protoc-gen-go v1.34.0
// protoc (unknown) // protoc (unknown)
// source: store/activity.proto // source: store/activity.proto
......
// Code generated by protoc-gen-go. DO NOT EDIT. // Code generated by protoc-gen-go. DO NOT EDIT.
// versions: // versions:
// protoc-gen-go v1.33.0 // protoc-gen-go v1.34.0
// protoc (unknown) // protoc (unknown)
// source: store/idp.proto // source: store/idp.proto
......
// Code generated by protoc-gen-go. DO NOT EDIT. // Code generated by protoc-gen-go. DO NOT EDIT.
// versions: // versions:
// protoc-gen-go v1.33.0 // protoc-gen-go v1.34.0
// protoc (unknown) // protoc (unknown)
// source: store/inbox.proto // source: store/inbox.proto
......
// Code generated by protoc-gen-go. DO NOT EDIT. // Code generated by protoc-gen-go. DO NOT EDIT.
// versions: // versions:
// protoc-gen-go v1.33.0 // protoc-gen-go v1.34.0
// protoc (unknown) // protoc (unknown)
// source: store/reaction.proto // source: store/reaction.proto
......
// Code generated by protoc-gen-go. DO NOT EDIT. // Code generated by protoc-gen-go. DO NOT EDIT.
// versions: // versions:
// protoc-gen-go v1.33.0 // protoc-gen-go v1.34.0
// protoc (unknown) // protoc (unknown)
// source: store/user_setting.proto // source: store/user_setting.proto
......
// Code generated by protoc-gen-go. DO NOT EDIT. // Code generated by protoc-gen-go. DO NOT EDIT.
// versions: // versions:
// protoc-gen-go v1.33.0 // protoc-gen-go v1.34.0
// protoc (unknown) // protoc (unknown)
// source: store/workspace_setting.proto // source: store/workspace_setting.proto
......
...@@ -5,6 +5,7 @@ import ( ...@@ -5,6 +5,7 @@ import (
"context" "context"
"encoding/binary" "encoding/binary"
"fmt" "fmt"
"io"
"net/url" "net/url"
"os" "os"
"path/filepath" "path/filepath"
...@@ -16,6 +17,7 @@ import ( ...@@ -16,6 +17,7 @@ import (
"github.com/lithammer/shortuuid/v4" "github.com/lithammer/shortuuid/v4"
"github.com/pkg/errors" "github.com/pkg/errors"
expr "google.golang.org/genproto/googleapis/api/expr/v1alpha1" 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/codes"
"google.golang.org/grpc/status" "google.golang.org/grpc/status"
"google.golang.org/protobuf/types/known/emptypb" "google.golang.org/protobuf/types/known/emptypb"
...@@ -159,6 +161,71 @@ func (s *APIV1Service) GetResource(ctx context.Context, request *v1pb.GetResourc ...@@ -159,6 +161,71 @@ func (s *APIV1Service) GetResource(ctx context.Context, request *v1pb.GetResourc
return s.convertResourceFromStore(ctx, resource), nil 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) { func (s *APIV1Service) UpdateResource(ctx context.Context, request *v1pb.UpdateResourceRequest) (*v1pb.Resource, error) {
id, err := ExtractResourceIDFromName(request.Resource.Name) id, err := ExtractResourceIDFromName(request.Resource.Name)
if err != nil { if err != nil {
......
...@@ -103,7 +103,7 @@ func (s *APIV1Service) GetUser(ctx context.Context, request *v1pb.GetUserRequest ...@@ -103,7 +103,7 @@ func (s *APIV1Service) GetUser(ctx context.Context, request *v1pb.GetUserRequest
return convertUserFromStore(user), nil 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) userID, err := ExtractUserIDFromName(request.Name)
if err != nil { if err != nil {
return nil, status.Errorf(codes.InvalidArgument, "invalid user name: %v", err) return nil, status.Errorf(codes.InvalidArgument, "invalid user name: %v", err)
...@@ -506,7 +506,7 @@ func (s *APIV1Service) UpsertAccessTokenToStore(ctx context.Context, user *store ...@@ -506,7 +506,7 @@ func (s *APIV1Service) UpsertAccessTokenToStore(ctx context.Context, user *store
} }
func convertUserFromStore(user *store.User) *v1pb.User { func convertUserFromStore(user *store.User) *v1pb.User {
return &v1pb.User{ userpb := &v1pb.User{
Name: fmt.Sprintf("%s%d", UserNamePrefix, user.ID), Name: fmt.Sprintf("%s%d", UserNamePrefix, user.ID),
Id: user.ID, Id: user.ID,
RowStatus: convertRowStatusFromStore(user.RowStatus), RowStatus: convertRowStatusFromStore(user.RowStatus),
...@@ -519,6 +519,11 @@ func convertUserFromStore(user *store.User) *v1pb.User { ...@@ -519,6 +519,11 @@ func convertUserFromStore(user *store.User) *v1pb.User {
AvatarUrl: user.AvatarURL, AvatarUrl: user.AvatarURL,
Description: user.Description, 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 { func convertUserRoleFromStore(role store.Role) v1pb.User_Role {
......
...@@ -111,7 +111,7 @@ func (s *APIV1Service) RegisterGateway(ctx context.Context, echoServer *echo.Ech ...@@ -111,7 +111,7 @@ func (s *APIV1Service) RegisterGateway(ctx context.Context, echoServer *echo.Ech
if err := v1pb.RegisterIdentityProviderServiceHandler(context.Background(), gwMux, conn); err != nil { if err := v1pb.RegisterIdentityProviderServiceHandler(context.Background(), gwMux, conn); err != nil {
return err return err
} }
echoServer.Any("/api/v1/*", echo.WrapHandler(gwMux)) echoServer.Any("*", echo.WrapHandler(gwMux))
// GRPC web proxy. // GRPC web proxy.
options := []grpcweb.Option{ options := []grpcweb.Option{
......
...@@ -41,7 +41,7 @@ func NewFrontendService(profile *profile.Profile, store *store.Store) *FrontendS ...@@ -41,7 +41,7 @@ func NewFrontendService(profile *profile.Profile, store *store.Store) *FrontendS
func (s *FrontendService) Serve(ctx context.Context, e *echo.Echo) { func (s *FrontendService) Serve(ctx context.Context, e *echo.Echo) {
skipper := func(c echo.Context) bool { 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. // Use echo static middleware to serve the built dist folder.
...@@ -55,9 +55,6 @@ func (s *FrontendService) Serve(ctx context.Context, e *echo.Echo) { ...@@ -55,9 +55,6 @@ func (s *FrontendService) Serve(ctx context.Context, e *echo.Echo) {
// Use echo gzip middleware to compress the response. // Use echo gzip middleware to compress the response.
// Reference: https://echo.labstack.com/docs/middleware/gzip // Reference: https://echo.labstack.com/docs/middleware/gzip
g.Use(middleware.GzipWithConfig(middleware.GzipConfig{ 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, Level: 5,
})) }))
g.Use(func(next echo.HandlerFunc) echo.HandlerFunc { 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 ( ...@@ -16,10 +16,8 @@ import (
storepb "github.com/usememos/memos/proto/gen/store" storepb "github.com/usememos/memos/proto/gen/store"
"github.com/usememos/memos/server/profile" "github.com/usememos/memos/server/profile"
"github.com/usememos/memos/server/route/api/auth"
apiv1 "github.com/usememos/memos/server/route/api/v1" apiv1 "github.com/usememos/memos/server/route/api/v1"
"github.com/usememos/memos/server/route/frontend" "github.com/usememos/memos/server/route/frontend"
"github.com/usememos/memos/server/route/resource"
"github.com/usememos/memos/server/route/rss" "github.com/usememos/memos/server/route/rss"
versionchecker "github.com/usememos/memos/server/service/version_checker" versionchecker "github.com/usememos/memos/server/service/version_checker"
"github.com/usememos/memos/store" "github.com/usememos/memos/store"
...@@ -70,15 +68,6 @@ func NewServer(ctx context.Context, profile *profile.Profile, store *store.Store ...@@ -70,15 +68,6 @@ func NewServer(ctx context.Context, profile *profile.Profile, store *store.Store
rootGroup := echoServer.Group("") 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. // Create and register RSS routes.
rss.NewRSSService(s.Profile, s.Store).RegisterRoutes(rootGroup) rss.NewRSSService(s.Profile, s.Store).RegisterRoutes(rootGroup)
......
...@@ -4,14 +4,12 @@ import { useTranslation } from "react-i18next"; ...@@ -4,14 +4,12 @@ import { useTranslation } from "react-i18next";
import { Outlet } from "react-router-dom"; import { Outlet } from "react-router-dom";
import useLocalStorage from "react-use/lib/useLocalStorage"; import useLocalStorage from "react-use/lib/useLocalStorage";
import { getSystemColorScheme } from "./helpers/utils"; import { getSystemColorScheme } from "./helpers/utils";
import useNavigateTo from "./hooks/useNavigateTo";
import { useCommonContext } from "./layouts/CommonContextProvider"; import { useCommonContext } from "./layouts/CommonContextProvider";
import { useUserStore, useWorkspaceSettingStore } from "./store/v1"; import { useUserStore, useWorkspaceSettingStore } from "./store/v1";
import { WorkspaceGeneralSetting, WorkspaceSettingKey } from "./types/proto/store/workspace_setting"; import { WorkspaceGeneralSetting, WorkspaceSettingKey } from "./types/proto/store/workspace_setting";
const App = () => { const App = () => {
const { i18n } = useTranslation(); const { i18n } = useTranslation();
const navigateTo = useNavigateTo();
const { mode, setMode } = useColorScheme(); const { mode, setMode } = useColorScheme();
const workspaceSettingStore = useWorkspaceSettingStore(); const workspaceSettingStore = useWorkspaceSettingStore();
const userStore = useUserStore(); const userStore = useUserStore();
...@@ -28,7 +26,7 @@ const App = () => { ...@@ -28,7 +26,7 @@ const App = () => {
// Redirect to sign up page if no instance owner. // Redirect to sign up page if no instance owner.
useEffect(() => { useEffect(() => {
if (!workspaceProfile.owner) { if (!workspaceProfile.owner) {
navigateTo("/auth/signup"); window.location.href = "/auth/signup";
} }
}, [workspaceProfile.owner]); }, [workspaceProfile.owner]);
......
...@@ -6,13 +6,11 @@ import useLocalStorage from "react-use/lib/useLocalStorage"; ...@@ -6,13 +6,11 @@ import useLocalStorage from "react-use/lib/useLocalStorage";
import Icon from "@/components/Icon"; import Icon from "@/components/Icon";
import Navigation from "@/components/Navigation"; import Navigation from "@/components/Navigation";
import useCurrentUser from "@/hooks/useCurrentUser"; import useCurrentUser from "@/hooks/useCurrentUser";
import useNavigateTo from "@/hooks/useNavigateTo";
import useResponsiveWidth from "@/hooks/useResponsiveWidth"; import useResponsiveWidth from "@/hooks/useResponsiveWidth";
import Loading from "@/pages/Loading"; import Loading from "@/pages/Loading";
import { Routes } from "@/router"; import { Routes } from "@/router";
const HomeLayout = () => { const HomeLayout = () => {
const navigateTo = useNavigateTo();
const location = useLocation(); const location = useLocation();
const { sm } = useResponsiveWidth(); const { sm } = useResponsiveWidth();
const currentUser = useCurrentUser(); const currentUser = useCurrentUser();
...@@ -27,9 +25,10 @@ const HomeLayout = () => { ...@@ -27,9 +25,10 @@ const HomeLayout = () => {
location.pathname, location.pathname,
) )
) { ) {
navigateTo(Routes.EXPLORE); window.location.href = Routes.EXPLORE;
return; return;
} }
setInitialized(true); 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