mirror of
https://github.com/usememos/memos.git
synced 2025-06-05 22:09:59 +02:00
chore: add user avatar route
This commit is contained in:
@@ -76,13 +76,6 @@ var (
|
|||||||
// The default signal sent by the `kill` command is SIGTERM,
|
// The default signal sent by the `kill` command is SIGTERM,
|
||||||
// which is taken as the graceful shutdown signal for many systems, eg., Kubernetes, Gunicorn.
|
// which is taken as the graceful shutdown signal for many systems, eg., Kubernetes, Gunicorn.
|
||||||
signal.Notify(c, os.Interrupt, syscall.SIGTERM)
|
signal.Notify(c, os.Interrupt, syscall.SIGTERM)
|
||||||
go func() {
|
|
||||||
<-c
|
|
||||||
s.Shutdown(ctx)
|
|
||||||
cancel()
|
|
||||||
}()
|
|
||||||
|
|
||||||
printGreetings()
|
|
||||||
|
|
||||||
if err := s.Start(ctx); err != nil {
|
if err := s.Start(ctx); err != nil {
|
||||||
if err != http.ErrServerClosed {
|
if err != http.ErrServerClosed {
|
||||||
@@ -91,6 +84,14 @@ var (
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
printGreetings()
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
<-c
|
||||||
|
s.Shutdown(ctx)
|
||||||
|
cancel()
|
||||||
|
}()
|
||||||
|
|
||||||
// Wait for CTRL-C.
|
// Wait for CTRL-C.
|
||||||
<-ctx.Done()
|
<-ctx.Done()
|
||||||
},
|
},
|
||||||
|
@@ -1371,6 +1371,41 @@ 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.
|
||||||
@@ -1800,6 +1835,68 @@ definitions:
|
|||||||
expiresAt:
|
expiresAt:
|
||||||
type: string
|
type: string
|
||||||
format: date-time
|
format: date-time
|
||||||
|
apiHttpBody:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
contentType:
|
||||||
|
type: string
|
||||||
|
description: The HTTP Content-Type header value specifying the content type of the body.
|
||||||
|
data:
|
||||||
|
type: string
|
||||||
|
format: byte
|
||||||
|
description: The HTTP request/response body as raw binary.
|
||||||
|
extensions:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
type: object
|
||||||
|
$ref: '#/definitions/protobufAny'
|
||||||
|
description: |-
|
||||||
|
Application specific response metadata. Must be set in the first response
|
||||||
|
for streaming APIs.
|
||||||
|
description: |-
|
||||||
|
Message that represents an arbitrary HTTP body. It should only be used for
|
||||||
|
payload formats that can't be represented as JSON, such as raw binary or
|
||||||
|
an HTML page.
|
||||||
|
|
||||||
|
|
||||||
|
This message can be used both in streaming and non-streaming API methods in
|
||||||
|
the request as well as the response.
|
||||||
|
|
||||||
|
It can be used as a top-level request field, which is convenient if one
|
||||||
|
wants to extract parameters from either the URL or HTTP template into the
|
||||||
|
request fields and also want access to the raw HTTP body.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
message GetResourceRequest {
|
||||||
|
// A unique request id.
|
||||||
|
string request_id = 1;
|
||||||
|
|
||||||
|
// The raw HTTP body is bound to this field.
|
||||||
|
google.api.HttpBody http_body = 2;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
service ResourceService {
|
||||||
|
rpc GetResource(GetResourceRequest)
|
||||||
|
returns (google.api.HttpBody);
|
||||||
|
rpc UpdateResource(google.api.HttpBody)
|
||||||
|
returns (google.protobuf.Empty);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
Example with streaming methods:
|
||||||
|
|
||||||
|
service CaldavService {
|
||||||
|
rpc GetCalendar(stream google.api.HttpBody)
|
||||||
|
returns (stream google.api.HttpBody);
|
||||||
|
rpc UpdateCalendar(stream google.api.HttpBody)
|
||||||
|
returns (stream google.api.HttpBody);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
Use of this type only changes how the request and response bodies are
|
||||||
|
handled, all other features will continue to work unchanged.
|
||||||
apiv1ActivityMemoCommentPayload:
|
apiv1ActivityMemoCommentPayload:
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
@@ -2016,7 +2113,122 @@ definitions:
|
|||||||
properties:
|
properties:
|
||||||
'@type':
|
'@type':
|
||||||
type: string
|
type: string
|
||||||
|
description: |-
|
||||||
|
A URL/resource name that uniquely identifies the type of the serialized
|
||||||
|
protocol buffer message. This string must contain at least
|
||||||
|
one "/" character. The last segment of the URL's path must represent
|
||||||
|
the fully qualified name of the type (as in
|
||||||
|
`path/google.protobuf.Duration`). The name should be in a canonical form
|
||||||
|
(e.g., leading "." is not accepted).
|
||||||
|
|
||||||
|
In practice, teams usually precompile into the binary all types that they
|
||||||
|
expect it to use in the context of Any. However, for URLs which use the
|
||||||
|
scheme `http`, `https`, or no scheme, one can optionally set up a type
|
||||||
|
server that maps type URLs to message definitions as follows:
|
||||||
|
|
||||||
|
* If no scheme is provided, `https` is assumed.
|
||||||
|
* An HTTP GET on the URL must yield a [google.protobuf.Type][]
|
||||||
|
value in binary format, or produce an error.
|
||||||
|
* Applications are allowed to cache lookup results based on the
|
||||||
|
URL, or have them precompiled into a binary to avoid any
|
||||||
|
lookup. Therefore, binary compatibility needs to be preserved
|
||||||
|
on changes to types. (Use versioned type names to manage
|
||||||
|
breaking changes.)
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
Schemes other than `http`, `https` (or the empty scheme) might be
|
||||||
|
used with implementation specific semantics.
|
||||||
additionalProperties: {}
|
additionalProperties: {}
|
||||||
|
description: |-
|
||||||
|
`Any` contains an arbitrary serialized protocol buffer message along with a
|
||||||
|
URL that describes the type of the serialized message.
|
||||||
|
|
||||||
|
Protobuf library provides support to pack/unpack Any values in the form
|
||||||
|
of utility functions or additional generated methods of the Any type.
|
||||||
|
|
||||||
|
Example 1: Pack and unpack a message in C++.
|
||||||
|
|
||||||
|
Foo foo = ...;
|
||||||
|
Any any;
|
||||||
|
any.PackFrom(foo);
|
||||||
|
...
|
||||||
|
if (any.UnpackTo(&foo)) {
|
||||||
|
...
|
||||||
|
}
|
||||||
|
|
||||||
|
Example 2: Pack and unpack a message in Java.
|
||||||
|
|
||||||
|
Foo foo = ...;
|
||||||
|
Any any = Any.pack(foo);
|
||||||
|
...
|
||||||
|
if (any.is(Foo.class)) {
|
||||||
|
foo = any.unpack(Foo.class);
|
||||||
|
}
|
||||||
|
// or ...
|
||||||
|
if (any.isSameTypeAs(Foo.getDefaultInstance())) {
|
||||||
|
foo = any.unpack(Foo.getDefaultInstance());
|
||||||
|
}
|
||||||
|
|
||||||
|
Example 3: Pack and unpack a message in Python.
|
||||||
|
|
||||||
|
foo = Foo(...)
|
||||||
|
any = Any()
|
||||||
|
any.Pack(foo)
|
||||||
|
...
|
||||||
|
if any.Is(Foo.DESCRIPTOR):
|
||||||
|
any.Unpack(foo)
|
||||||
|
...
|
||||||
|
|
||||||
|
Example 4: Pack and unpack a message in Go
|
||||||
|
|
||||||
|
foo := &pb.Foo{...}
|
||||||
|
any, err := anypb.New(foo)
|
||||||
|
if err != nil {
|
||||||
|
...
|
||||||
|
}
|
||||||
|
...
|
||||||
|
foo := &pb.Foo{}
|
||||||
|
if err := any.UnmarshalTo(foo); err != nil {
|
||||||
|
...
|
||||||
|
}
|
||||||
|
|
||||||
|
The pack methods provided by protobuf library will by default use
|
||||||
|
'type.googleapis.com/full.type.name' as the type URL and the unpack
|
||||||
|
methods only use the fully qualified type name after the last '/'
|
||||||
|
in the type URL, for example "foo.bar.com/x/y.z" will yield type
|
||||||
|
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:
|
||||||
|
|
||||||
|
package google.profile;
|
||||||
|
message Person {
|
||||||
|
string first_name = 1;
|
||||||
|
string last_name = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
"@type": "type.googleapis.com/google.profile.Person",
|
||||||
|
"firstName": <string>,
|
||||||
|
"lastName": <string>
|
||||||
|
}
|
||||||
|
|
||||||
|
If the embedded message type is well-known and has a custom JSON
|
||||||
|
representation, that representation will be embedded adding a field
|
||||||
|
`value` which holds the custom JSON in addition to the `@type`
|
||||||
|
field. Example (for message [google.protobuf.Duration][]):
|
||||||
|
|
||||||
|
{
|
||||||
|
"@type": "type.googleapis.com/google.protobuf.Duration",
|
||||||
|
"value": "1.212s"
|
||||||
|
}
|
||||||
v1Activity:
|
v1Activity:
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
|
@@ -6,6 +6,7 @@ import "api/v1/common.proto";
|
|||||||
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";
|
||||||
@@ -26,6 +27,11 @@ 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.
|
||||||
|
rpc GetUserAvatar(GetUserAvatarRequest) returns (google.api.HttpBody) {
|
||||||
|
option (google.api.http) = {get: "/api/v1/{name=users/*}/avatar"};
|
||||||
|
option (google.api.method_signature) = "name";
|
||||||
|
}
|
||||||
// CreateUser creates a new user.
|
// CreateUser creates a new user.
|
||||||
rpc CreateUser(CreateUserRequest) returns (User) {
|
rpc CreateUser(CreateUserRequest) returns (User) {
|
||||||
option (google.api.http) = {
|
option (google.api.http) = {
|
||||||
@@ -137,6 +143,15 @@ message GetUserRequest {
|
|||||||
string name = 1;
|
string name = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
message GetUserAvatarRequest {
|
||||||
|
// The name of the user.
|
||||||
|
// Format: users/{id}
|
||||||
|
string name = 1;
|
||||||
|
|
||||||
|
// The raw HTTP body is bound to this field.
|
||||||
|
google.api.HttpBody http_body = 2;
|
||||||
|
}
|
||||||
|
|
||||||
message CreateUserRequest {
|
message CreateUserRequest {
|
||||||
User user = 1;
|
User user = 1;
|
||||||
}
|
}
|
||||||
|
File diff suppressed because it is too large
Load Diff
@@ -137,6 +137,76 @@ func local_request_UserService_GetUser_0(ctx context.Context, marshaler runtime.
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
filter_UserService_GetUserAvatar_0 = &utilities.DoubleArray{Encoding: map[string]int{"name": 0}, Base: []int{1, 1, 0}, Check: []int{0, 1, 2}}
|
||||||
|
)
|
||||||
|
|
||||||
|
func request_UserService_GetUserAvatar_0(ctx context.Context, marshaler runtime.Marshaler, client UserServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
|
||||||
|
var protoReq GetUserAvatarRequest
|
||||||
|
var metadata runtime.ServerMetadata
|
||||||
|
|
||||||
|
var (
|
||||||
|
val string
|
||||||
|
ok bool
|
||||||
|
err error
|
||||||
|
_ = err
|
||||||
|
)
|
||||||
|
|
||||||
|
val, ok = pathParams["name"]
|
||||||
|
if !ok {
|
||||||
|
return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "name")
|
||||||
|
}
|
||||||
|
|
||||||
|
protoReq.Name, err = runtime.String(val)
|
||||||
|
if err != nil {
|
||||||
|
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "name", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := req.ParseForm(); err != nil {
|
||||||
|
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
|
||||||
|
}
|
||||||
|
if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_UserService_GetUserAvatar_0); err != nil {
|
||||||
|
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
msg, err := client.GetUserAvatar(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
|
||||||
|
return msg, metadata, err
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func local_request_UserService_GetUserAvatar_0(ctx context.Context, marshaler runtime.Marshaler, server UserServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
|
||||||
|
var protoReq GetUserAvatarRequest
|
||||||
|
var metadata runtime.ServerMetadata
|
||||||
|
|
||||||
|
var (
|
||||||
|
val string
|
||||||
|
ok bool
|
||||||
|
err error
|
||||||
|
_ = err
|
||||||
|
)
|
||||||
|
|
||||||
|
val, ok = pathParams["name"]
|
||||||
|
if !ok {
|
||||||
|
return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "name")
|
||||||
|
}
|
||||||
|
|
||||||
|
protoReq.Name, err = runtime.String(val)
|
||||||
|
if err != nil {
|
||||||
|
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "name", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := req.ParseForm(); err != nil {
|
||||||
|
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
|
||||||
|
}
|
||||||
|
if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_UserService_GetUserAvatar_0); err != nil {
|
||||||
|
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
msg, err := server.GetUserAvatar(ctx, &protoReq)
|
||||||
|
return msg, metadata, err
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
func request_UserService_CreateUser_0(ctx context.Context, marshaler runtime.Marshaler, client UserServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
|
func request_UserService_CreateUser_0(ctx context.Context, marshaler runtime.Marshaler, client UserServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
|
||||||
var protoReq CreateUserRequest
|
var protoReq CreateUserRequest
|
||||||
var metadata runtime.ServerMetadata
|
var metadata runtime.ServerMetadata
|
||||||
@@ -732,6 +802,31 @@ func RegisterUserServiceHandlerServer(ctx context.Context, mux *runtime.ServeMux
|
|||||||
|
|
||||||
})
|
})
|
||||||
|
|
||||||
|
mux.Handle("GET", pattern_UserService_GetUserAvatar_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.UserService/GetUserAvatar", runtime.WithHTTPPathPattern("/api/v1/{name=users/*}/avatar"))
|
||||||
|
if err != nil {
|
||||||
|
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
resp, md, err := local_request_UserService_GetUserAvatar_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_UserService_GetUserAvatar_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
mux.Handle("POST", pattern_UserService_CreateUser_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
|
mux.Handle("POST", pattern_UserService_CreateUser_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()
|
||||||
@@ -1039,6 +1134,28 @@ func RegisterUserServiceHandlerClient(ctx context.Context, mux *runtime.ServeMux
|
|||||||
|
|
||||||
})
|
})
|
||||||
|
|
||||||
|
mux.Handle("GET", pattern_UserService_GetUserAvatar_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.UserService/GetUserAvatar", runtime.WithHTTPPathPattern("/api/v1/{name=users/*}/avatar"))
|
||||||
|
if err != nil {
|
||||||
|
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
resp, md, err := request_UserService_GetUserAvatar_0(annotatedContext, inboundMarshaler, client, req, pathParams)
|
||||||
|
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
|
||||||
|
if err != nil {
|
||||||
|
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
forward_UserService_GetUserAvatar_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
mux.Handle("POST", pattern_UserService_CreateUser_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
|
mux.Handle("POST", pattern_UserService_CreateUser_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()
|
||||||
@@ -1225,6 +1342,8 @@ var (
|
|||||||
|
|
||||||
pattern_UserService_GetUser_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 2, 5, 3}, []string{"api", "v1", "users", "name"}, ""))
|
pattern_UserService_GetUser_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 2, 5, 3}, []string{"api", "v1", "users", "name"}, ""))
|
||||||
|
|
||||||
|
pattern_UserService_GetUserAvatar_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 2, 5, 3, 2, 4}, []string{"api", "v1", "users", "name", "avatar"}, ""))
|
||||||
|
|
||||||
pattern_UserService_CreateUser_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"api", "v1", "users"}, ""))
|
pattern_UserService_CreateUser_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2}, []string{"api", "v1", "users"}, ""))
|
||||||
|
|
||||||
pattern_UserService_UpdateUser_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 2, 5, 3}, []string{"api", "v1", "users", "user.name"}, ""))
|
pattern_UserService_UpdateUser_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 2, 5, 3}, []string{"api", "v1", "users", "user.name"}, ""))
|
||||||
@@ -1249,6 +1368,8 @@ var (
|
|||||||
|
|
||||||
forward_UserService_GetUser_0 = runtime.ForwardResponseMessage
|
forward_UserService_GetUser_0 = runtime.ForwardResponseMessage
|
||||||
|
|
||||||
|
forward_UserService_GetUserAvatar_0 = runtime.ForwardResponseMessage
|
||||||
|
|
||||||
forward_UserService_CreateUser_0 = runtime.ForwardResponseMessage
|
forward_UserService_CreateUser_0 = runtime.ForwardResponseMessage
|
||||||
|
|
||||||
forward_UserService_UpdateUser_0 = runtime.ForwardResponseMessage
|
forward_UserService_UpdateUser_0 = runtime.ForwardResponseMessage
|
||||||
|
@@ -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"
|
||||||
@@ -23,6 +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_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"
|
||||||
@@ -43,6 +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.
|
||||||
|
GetUserAvatar(ctx context.Context, in *GetUserAvatarRequest, 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.
|
||||||
@@ -96,6 +100,15 @@ 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) {
|
||||||
|
out := new(httpbody.HttpBody)
|
||||||
|
err := c.cc.Invoke(ctx, UserService_GetUserAvatar_FullMethodName, in, out, opts...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (c *userServiceClient) CreateUser(ctx context.Context, in *CreateUserRequest, opts ...grpc.CallOption) (*User, error) {
|
func (c *userServiceClient) CreateUser(ctx context.Context, in *CreateUserRequest, opts ...grpc.CallOption) (*User, error) {
|
||||||
out := new(User)
|
out := new(User)
|
||||||
err := c.cc.Invoke(ctx, UserService_CreateUser_FullMethodName, in, out, opts...)
|
err := c.cc.Invoke(ctx, UserService_CreateUser_FullMethodName, in, out, opts...)
|
||||||
@@ -178,6 +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.
|
||||||
|
GetUserAvatar(context.Context, *GetUserAvatarRequest) (*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.
|
||||||
@@ -210,6 +225,9 @@ 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) {
|
||||||
|
return nil, status.Errorf(codes.Unimplemented, "method GetUserAvatar 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")
|
||||||
}
|
}
|
||||||
@@ -301,6 +319,24 @@ 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) {
|
||||||
|
in := new(GetUserAvatarRequest)
|
||||||
|
if err := dec(in); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if interceptor == nil {
|
||||||
|
return srv.(UserServiceServer).GetUserAvatar(ctx, in)
|
||||||
|
}
|
||||||
|
info := &grpc.UnaryServerInfo{
|
||||||
|
Server: srv,
|
||||||
|
FullMethod: UserService_GetUserAvatar_FullMethodName,
|
||||||
|
}
|
||||||
|
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||||
|
return srv.(UserServiceServer).GetUserAvatar(ctx, req.(*GetUserAvatarRequest))
|
||||||
|
}
|
||||||
|
return interceptor(ctx, in, info, handler)
|
||||||
|
}
|
||||||
|
|
||||||
func _UserService_CreateUser_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
func _UserService_CreateUser_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||||
in := new(CreateUserRequest)
|
in := new(CreateUserRequest)
|
||||||
if err := dec(in); err != nil {
|
if err := dec(in); err != nil {
|
||||||
@@ -464,6 +500,10 @@ var UserService_ServiceDesc = grpc.ServiceDesc{
|
|||||||
MethodName: "GetUser",
|
MethodName: "GetUser",
|
||||||
Handler: _UserService_GetUser_Handler,
|
Handler: _UserService_GetUser_Handler,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
MethodName: "GetUserAvatar",
|
||||||
|
Handler: _UserService_GetUserAvatar_Handler,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
MethodName: "CreateUser",
|
MethodName: "CreateUser",
|
||||||
Handler: _UserService_CreateUser_Handler,
|
Handler: _UserService_CreateUser_Handler,
|
||||||
|
@@ -12,6 +12,7 @@ var authenticationAllowlistMethods = map[string]bool{
|
|||||||
"/memos.api.v1.AuthService/SignOut": true,
|
"/memos.api.v1.AuthService/SignOut": true,
|
||||||
"/memos.api.v1.AuthService/SignUp": true,
|
"/memos.api.v1.AuthService/SignUp": true,
|
||||||
"/memos.api.v1.UserService/GetUser": true,
|
"/memos.api.v1.UserService/GetUser": true,
|
||||||
|
"/memos.api.v1.UserService/GetUserAvatar": true,
|
||||||
"/memos.api.v1.UserService/SearchUsers": true,
|
"/memos.api.v1.UserService/SearchUsers": true,
|
||||||
"/memos.api.v1.MemoService/ListMemos": true,
|
"/memos.api.v1.MemoService/ListMemos": true,
|
||||||
"/memos.api.v1.MemoService/GetMemo": true,
|
"/memos.api.v1.MemoService/GetMemo": true,
|
||||||
|
@@ -2,8 +2,10 @@ package v1
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/base64"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"regexp"
|
||||||
"slices"
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
@@ -14,6 +16,7 @@ import (
|
|||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"golang.org/x/crypto/bcrypt"
|
"golang.org/x/crypto/bcrypt"
|
||||||
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"
|
||||||
@@ -100,6 +103,39 @@ 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) {
|
||||||
|
userID, err := ExtractUserIDFromName(request.Name)
|
||||||
|
if err != nil {
|
||||||
|
return nil, status.Errorf(codes.InvalidArgument, "invalid user name: %v", err)
|
||||||
|
}
|
||||||
|
user, err := s.Store.GetUser(ctx, &store.FindUser{
|
||||||
|
ID: &userID,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, status.Errorf(codes.Internal, "failed to get user: %v", err)
|
||||||
|
}
|
||||||
|
if user == nil {
|
||||||
|
return nil, status.Errorf(codes.NotFound, "user not found")
|
||||||
|
}
|
||||||
|
if user.AvatarURL == "" {
|
||||||
|
return nil, status.Errorf(codes.NotFound, "avatar not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
imageType, base64Data, err := extractImageInfo(user.AvatarURL)
|
||||||
|
if err != nil {
|
||||||
|
return nil, status.Errorf(codes.Internal, "failed to extract image info: %v", err)
|
||||||
|
}
|
||||||
|
imageData, err := base64.StdEncoding.DecodeString(base64Data)
|
||||||
|
if err != nil {
|
||||||
|
return nil, status.Errorf(codes.Internal, "failed to decode string: %v", err)
|
||||||
|
}
|
||||||
|
httpBody := &httpbody.HttpBody{
|
||||||
|
ContentType: imageType,
|
||||||
|
Data: imageData,
|
||||||
|
}
|
||||||
|
return httpBody, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (s *APIV1Service) CreateUser(ctx context.Context, request *v1pb.CreateUserRequest) (*v1pb.User, error) {
|
func (s *APIV1Service) CreateUser(ctx context.Context, request *v1pb.CreateUserRequest) (*v1pb.User, error) {
|
||||||
currentUser, err := getCurrentUser(ctx, s.Store)
|
currentUser, err := getCurrentUser(ctx, s.Store)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -567,3 +603,14 @@ func findSearchUsersField(callExpr *expr.Expr_Call, filter *SearchUsersFilter) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func extractImageInfo(dataURI string) (string, string, error) {
|
||||||
|
dataURIRegex := regexp.MustCompile(`^data:(?P<type>.+);base64,(?P<base64>.+)`)
|
||||||
|
matches := dataURIRegex.FindStringSubmatch(dataURI)
|
||||||
|
if len(matches) != 3 {
|
||||||
|
return "", "", errors.New("Invalid data URI format")
|
||||||
|
}
|
||||||
|
imageType := matches[1]
|
||||||
|
base64Data := matches[2]
|
||||||
|
return imageType, base64Data, nil
|
||||||
|
}
|
||||||
|
@@ -40,16 +40,17 @@ 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 {
|
||||||
|
return util.HasPrefixes(c.Path(), "/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.
|
||||||
// Reference: https://github.com/labstack/echo/blob/master/middleware/static.go
|
// Reference: https://github.com/labstack/echo/blob/master/middleware/static.go
|
||||||
e.Use(middleware.StaticWithConfig(middleware.StaticConfig{
|
e.Use(middleware.StaticWithConfig(middleware.StaticConfig{
|
||||||
HTML5: true,
|
HTML5: true,
|
||||||
Filesystem: getFileSystem("dist"),
|
Filesystem: getFileSystem("dist"),
|
||||||
Skipper: func(c echo.Context) bool {
|
Skipper: skipper,
|
||||||
return util.HasPrefixes(c.Path(), "/api", "/memos.api.v1", "/robots.txt", "/sitemap.xml", "/m/:name")
|
|
||||||
},
|
|
||||||
}))
|
}))
|
||||||
|
|
||||||
g := e.Group("assets")
|
g := e.Group("assets")
|
||||||
// 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
|
||||||
@@ -68,9 +69,6 @@ func (s *FrontendService) Serve(ctx context.Context, e *echo.Echo) {
|
|||||||
g.Use(middleware.StaticWithConfig(middleware.StaticConfig{
|
g.Use(middleware.StaticWithConfig(middleware.StaticConfig{
|
||||||
HTML5: true,
|
HTML5: true,
|
||||||
Filesystem: getFileSystem("dist/assets"),
|
Filesystem: getFileSystem("dist/assets"),
|
||||||
Skipper: func(c echo.Context) bool {
|
|
||||||
return util.HasPrefixes(c.Path(), "/api", "/memos.api.v1", "/robots.txt", "/sitemap.xml", "/m/:name")
|
|
||||||
},
|
|
||||||
}))
|
}))
|
||||||
|
|
||||||
s.registerRoutes(e)
|
s.registerRoutes(e)
|
||||||
|
@@ -6,7 +6,6 @@ import (
|
|||||||
"log/slog"
|
"log/slog"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
@@ -47,9 +46,6 @@ func NewServer(ctx context.Context, profile *profile.Profile, store *store.Store
|
|||||||
echoServer.HidePort = true
|
echoServer.HidePort = true
|
||||||
s.echoServer = echoServer
|
s.echoServer = echoServer
|
||||||
|
|
||||||
// Register CORS middleware.
|
|
||||||
echoServer.Use(CORSMiddleware(s.Profile.Origins))
|
|
||||||
|
|
||||||
workspaceBasicSetting, err := s.getOrUpsertWorkspaceBasicSetting(ctx)
|
workspaceBasicSetting, err := s.getOrUpsertWorkspaceBasicSetting(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrap(err, "failed to get workspace basic setting")
|
return nil, errors.Wrap(err, "failed to get workspace basic setting")
|
||||||
@@ -116,15 +112,20 @@ func (s *Server) Start(ctx context.Context) error {
|
|||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
go func() {
|
go func() {
|
||||||
httpListener := muxServer.Match(cmux.HTTP1Fast(), cmux.Any())
|
httpListener := muxServer.Match(cmux.HTTP1Fast())
|
||||||
s.echoServer.Listener = httpListener
|
s.echoServer.Listener = httpListener
|
||||||
if err := s.echoServer.Start(address); err != nil {
|
if err := s.echoServer.Start(address); err != nil {
|
||||||
slog.Error("failed to start echo server", err)
|
slog.Error("failed to start echo server", err)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
go func() {
|
||||||
|
if err := muxServer.Serve(); err != nil {
|
||||||
|
slog.Error("mux server listen error", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
s.StartBackgroundRunners(ctx)
|
s.StartBackgroundRunners(ctx)
|
||||||
|
|
||||||
return muxServer.Serve()
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) Shutdown(ctx context.Context) {
|
func (s *Server) Shutdown(ctx context.Context) {
|
||||||
@@ -170,43 +171,3 @@ func (s *Server) getOrUpsertWorkspaceBasicSetting(ctx context.Context) (*storepb
|
|||||||
}
|
}
|
||||||
return workspaceBasicSetting, nil
|
return workspaceBasicSetting, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func grpcRequestSkipper(c echo.Context) bool {
|
|
||||||
return strings.HasPrefix(c.Request().URL.Path, "/memos.api.v1.")
|
|
||||||
}
|
|
||||||
|
|
||||||
func CORSMiddleware(origins []string) echo.MiddlewareFunc {
|
|
||||||
return func(next echo.HandlerFunc) echo.HandlerFunc {
|
|
||||||
return func(c echo.Context) error {
|
|
||||||
if grpcRequestSkipper(c) {
|
|
||||||
return next(c)
|
|
||||||
}
|
|
||||||
|
|
||||||
r := c.Request()
|
|
||||||
w := c.Response().Writer
|
|
||||||
|
|
||||||
requestOrigin := r.Header.Get("Origin")
|
|
||||||
if len(origins) == 0 {
|
|
||||||
w.Header().Set("Access-Control-Allow-Origin", requestOrigin)
|
|
||||||
} else {
|
|
||||||
for _, origin := range origins {
|
|
||||||
if origin == requestOrigin {
|
|
||||||
w.Header().Set("Access-Control-Allow-Origin", origin)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, PATCH, OPTIONS")
|
|
||||||
w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization")
|
|
||||||
w.Header().Set("Access-Control-Allow-Credentials", "true")
|
|
||||||
|
|
||||||
// If it's preflight request, return immediately.
|
|
||||||
if r.Method == "OPTIONS" {
|
|
||||||
w.WriteHeader(http.StatusOK)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return next(c)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
Reference in New Issue
Block a user