feat: add user-defined name to memo

This commit is contained in:
Steven
2024-01-20 23:48:35 +08:00
parent 264e6e6e9c
commit 8382354ef7
19 changed files with 929 additions and 524 deletions

View File

@ -21,10 +21,6 @@ import (
"github.com/usememos/memos/store" "github.com/usememos/memos/store"
) )
var (
usernameMatcher = regexp.MustCompile("^[a-z0-9]([a-z0-9-]{1,30}[a-z0-9])$")
)
type SignIn struct { type SignIn struct {
Username string `json:"username"` Username string `json:"username"`
Password string `json:"password"` Password string `json:"password"`
@ -293,7 +289,7 @@ func (s *APIV1Service) SignUp(c echo.Context) error {
if err != nil { if err != nil {
return echo.NewHTTPError(http.StatusBadRequest, "Failed to find users").SetInternal(err) return echo.NewHTTPError(http.StatusBadRequest, "Failed to find users").SetInternal(err)
} }
if !usernameMatcher.MatchString(strings.ToLower(signup.Username)) { if !util.ResourceNameMatcher.MatchString(strings.ToLower(signup.Username)) {
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid username %s", signup.Username)).SetInternal(err) return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid username %s", signup.Username)).SetInternal(err)
} }

View File

@ -158,7 +158,7 @@ func (s *APIV1Service) CreateUser(c echo.Context) error {
if err := userCreate.Validate(); err != nil { if err := userCreate.Validate(); err != nil {
return echo.NewHTTPError(http.StatusBadRequest, "Invalid user create format").SetInternal(err) return echo.NewHTTPError(http.StatusBadRequest, "Invalid user create format").SetInternal(err)
} }
if !usernameMatcher.MatchString(strings.ToLower(userCreate.Username)) { if !util.ResourceNameMatcher.MatchString(strings.ToLower(userCreate.Username)) {
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid username %s", userCreate.Username)).SetInternal(err) return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid username %s", userCreate.Username)).SetInternal(err)
} }
// Disallow host user to be created. // Disallow host user to be created.
@ -379,7 +379,7 @@ func (s *APIV1Service) UpdateUser(c echo.Context) error {
} }
} }
if request.Username != nil { if request.Username != nil {
if !usernameMatcher.MatchString(strings.ToLower(*request.Username)) { if !util.ResourceNameMatcher.MatchString(strings.ToLower(*request.Username)) {
return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid username %s", *request.Username)).SetInternal(err) return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid username %s", *request.Username)).SetInternal(err)
} }
userUpdate.Username = request.Username userUpdate.Username = request.Username

View File

@ -7,6 +7,7 @@ import (
"time" "time"
"github.com/google/cel-go/cel" "github.com/google/cel-go/cel"
"github.com/lithammer/shortuuid/v4"
"github.com/pkg/errors" "github.com/pkg/errors"
"go.uber.org/zap" "go.uber.org/zap"
expr "google.golang.org/genproto/googleapis/api/expr/v1alpha1" expr "google.golang.org/genproto/googleapis/api/expr/v1alpha1"
@ -16,6 +17,7 @@ import (
apiv1 "github.com/usememos/memos/api/v1" apiv1 "github.com/usememos/memos/api/v1"
"github.com/usememos/memos/internal/log" "github.com/usememos/memos/internal/log"
"github.com/usememos/memos/internal/util"
"github.com/usememos/memos/plugin/gomark/ast" "github.com/usememos/memos/plugin/gomark/ast"
"github.com/usememos/memos/plugin/gomark/parser" "github.com/usememos/memos/plugin/gomark/parser"
"github.com/usememos/memos/plugin/gomark/parser/tokenizer" "github.com/usememos/memos/plugin/gomark/parser/tokenizer"
@ -49,6 +51,7 @@ func (s *APIV2Service) CreateMemo(ctx context.Context, request *apiv2pb.CreateMe
} }
create := &store.Memo{ create := &store.Memo{
ResourceName: shortuuid.New(),
CreatorID: user.ID, CreatorID: user.ID,
Content: request.Content, Content: request.Content,
Visibility: store.Visibility(request.Visibility.String()), Visibility: store.Visibility(request.Visibility.String()),
@ -234,6 +237,27 @@ func (s *APIV2Service) GetMemo(ctx context.Context, request *apiv2pb.GetMemoRequ
return response, nil return response, nil
} }
func (s *APIV2Service) GetMemoByName(ctx context.Context, request *apiv2pb.GetMemoByNameRequest) (*apiv2pb.GetMemoByNameResponse, error) {
memo, err := s.Store.GetMemo(ctx, &store.FindMemo{
ResourceName: &request.Name,
})
if err != nil {
return nil, err
}
if memo == nil {
return nil, status.Errorf(codes.NotFound, "memo not found")
}
memoMessage, err := s.convertMemoFromStore(ctx, memo)
if err != nil {
return nil, errors.Wrap(err, "failed to convert memo")
}
response := &apiv2pb.GetMemoByNameResponse{
Memo: memoMessage,
}
return response, nil
}
func (s *APIV2Service) UpdateMemo(ctx context.Context, request *apiv2pb.UpdateMemoRequest) (*apiv2pb.UpdateMemoResponse, error) { func (s *APIV2Service) UpdateMemo(ctx context.Context, request *apiv2pb.UpdateMemoRequest) (*apiv2pb.UpdateMemoResponse, error) {
if request.UpdateMask == nil || len(request.UpdateMask.Paths) == 0 { if request.UpdateMask == nil || len(request.UpdateMask.Paths) == 0 {
return nil, status.Errorf(codes.InvalidArgument, "update mask is required") return nil, status.Errorf(codes.InvalidArgument, "update mask is required")
@ -282,6 +306,11 @@ func (s *APIV2Service) UpdateMemo(ctx context.Context, request *apiv2pb.UpdateMe
nodes := convertToASTNodes(request.Memo.Nodes) nodes := convertToASTNodes(request.Memo.Nodes)
content := restore.Restore(nodes) content := restore.Restore(nodes)
update.Content = &content update.Content = &content
} else if path == "resource_name" {
update.ResourceName = &request.Memo.Name
if !util.ResourceNameMatcher.MatchString(*update.ResourceName) {
return nil, status.Errorf(codes.InvalidArgument, "invalid resource name")
}
} else if path == "visibility" { } else if path == "visibility" {
visibility := convertVisibilityToStore(request.Memo.Visibility) visibility := convertVisibilityToStore(request.Memo.Visibility)
update.Visibility = &visibility update.Visibility = &visibility
@ -574,6 +603,7 @@ func (s *APIV2Service) convertMemoFromStore(ctx context.Context, memo *store.Mem
return &apiv2pb.Memo{ return &apiv2pb.Memo{
Id: int32(memo.ID), Id: int32(memo.ID),
Name: memo.ResourceName,
RowStatus: convertRowStatusFromStore(memo.RowStatus), RowStatus: convertRowStatusFromStore(memo.RowStatus),
Creator: fmt.Sprintf("%s%s", UserNamePrefix, creator.Username), Creator: fmt.Sprintf("%s%s", UserNamePrefix, creator.Username),
CreatorId: int32(memo.CreatorID), CreatorId: int32(memo.CreatorID),

View File

@ -4,7 +4,6 @@ import (
"context" "context"
"fmt" "fmt"
"net/http" "net/http"
"regexp"
"strings" "strings"
"time" "time"
@ -18,15 +17,12 @@ import (
"google.golang.org/protobuf/types/known/timestamppb" "google.golang.org/protobuf/types/known/timestamppb"
"github.com/usememos/memos/api/auth" "github.com/usememos/memos/api/auth"
"github.com/usememos/memos/internal/util"
apiv2pb "github.com/usememos/memos/proto/gen/api/v2" apiv2pb "github.com/usememos/memos/proto/gen/api/v2"
storepb "github.com/usememos/memos/proto/gen/store" storepb "github.com/usememos/memos/proto/gen/store"
"github.com/usememos/memos/store" "github.com/usememos/memos/store"
) )
var (
usernameMatcher = regexp.MustCompile("^[a-z0-9]([a-z0-9-]{1,30}[a-z0-9])$")
)
func (s *APIV2Service) ListUsers(ctx context.Context, _ *apiv2pb.ListUsersRequest) (*apiv2pb.ListUsersResponse, error) { func (s *APIV2Service) ListUsers(ctx context.Context, _ *apiv2pb.ListUsersRequest) (*apiv2pb.ListUsersResponse, error) {
currentUser, err := getCurrentUser(ctx, s.Store) currentUser, err := getCurrentUser(ctx, s.Store)
if err != nil { if err != nil {
@ -85,7 +81,7 @@ func (s *APIV2Service) CreateUser(ctx context.Context, request *apiv2pb.CreateUs
if err != nil { if err != nil {
return nil, status.Errorf(codes.InvalidArgument, "name is required") return nil, status.Errorf(codes.InvalidArgument, "name is required")
} }
if !usernameMatcher.MatchString(strings.ToLower(username)) { if !util.ResourceNameMatcher.MatchString(strings.ToLower(username)) {
return nil, status.Errorf(codes.InvalidArgument, "invalid username: %s", username) return nil, status.Errorf(codes.InvalidArgument, "invalid username: %s", username)
} }
passwordHash, err := bcrypt.GenerateFromPassword([]byte(request.User.Password), bcrypt.DefaultCost) passwordHash, err := bcrypt.GenerateFromPassword([]byte(request.User.Password), bcrypt.DefaultCost)
@ -141,7 +137,7 @@ func (s *APIV2Service) UpdateUser(ctx context.Context, request *apiv2pb.UpdateUs
} }
for _, field := range request.UpdateMask.Paths { for _, field := range request.UpdateMask.Paths {
if field == "username" { if field == "username" {
if !usernameMatcher.MatchString(strings.ToLower(request.User.Username)) { if !util.ResourceNameMatcher.MatchString(strings.ToLower(request.User.Username)) {
return nil, status.Errorf(codes.InvalidArgument, "invalid username: %s", request.User.Username) return nil, status.Errorf(codes.InvalidArgument, "invalid username: %s", request.User.Username)
} }
update.Username = &request.User.Username update.Username = &request.User.Username

1
go.mod
View File

@ -18,6 +18,7 @@ require (
github.com/joho/godotenv v1.5.1 github.com/joho/godotenv v1.5.1
github.com/labstack/echo/v4 v4.11.4 github.com/labstack/echo/v4 v4.11.4
github.com/lib/pq v1.10.9 github.com/lib/pq v1.10.9
github.com/lithammer/shortuuid/v4 v4.0.0
github.com/microcosm-cc/bluemonday v1.0.26 github.com/microcosm-cc/bluemonday v1.0.26
github.com/pkg/errors v0.9.1 github.com/pkg/errors v0.9.1
github.com/spf13/cobra v1.8.0 github.com/spf13/cobra v1.8.0

2
go.sum
View File

@ -286,6 +286,8 @@ github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM= github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM=
github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4= github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4=
github.com/lithammer/shortuuid/v4 v4.0.0 h1:QRbbVkfgNippHOS8PXDkti4NaWeyYfcBTHtw7k08o4c=
github.com/lithammer/shortuuid/v4 v4.0.0/go.mod h1:Zs8puNcrvf2rV9rTH51ZLLcj7ZXqQI3lv67aw4KiB1Y=
github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ= github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ=
github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=

View File

@ -0,0 +1,7 @@
package util
import "regexp"
var (
ResourceNameMatcher = regexp.MustCompile("^[a-zA-Z0-9]([a-zA-Z0-9-]{1,30}[a-zA-Z0-9])$")
)

View File

@ -31,6 +31,11 @@ service MemoService {
option (google.api.http) = {get: "/api/v2/memos/{id}"}; option (google.api.http) = {get: "/api/v2/memos/{id}"};
option (google.api.method_signature) = "id"; option (google.api.method_signature) = "id";
} }
// GetMemoByName gets a memo by name.
rpc GetMemoByName(GetMemoByNameRequest) returns (GetMemoByNameResponse) {
option (google.api.http) = {get: "/api/v2/memos/{name}"};
option (google.api.method_signature) = "name";
}
// UpdateMemo updates a memo. // UpdateMemo updates a memo.
rpc UpdateMemo(UpdateMemoRequest) returns (UpdateMemoResponse) { rpc UpdateMemo(UpdateMemoRequest) returns (UpdateMemoResponse) {
option (google.api.http) = { option (google.api.http) = {
@ -98,35 +103,39 @@ enum Visibility {
} }
message Memo { message Memo {
// id is the unique auto-incremented id.
int32 id = 1; int32 id = 1;
RowStatus row_status = 2; // name is the user-defined name.
string name = 2;
RowStatus row_status = 3;
// The name of the creator. // The name of the creator.
// Format: users/{username} // Format: users/{username}
string creator = 3; string creator = 4;
int32 creator_id = 4; int32 creator_id = 5;
google.protobuf.Timestamp create_time = 5; google.protobuf.Timestamp create_time = 6;
google.protobuf.Timestamp update_time = 6; google.protobuf.Timestamp update_time = 7;
google.protobuf.Timestamp display_time = 7; google.protobuf.Timestamp display_time = 8;
string content = 8; string content = 9;
repeated Node nodes = 9; repeated Node nodes = 10;
Visibility visibility = 10; Visibility visibility = 11;
bool pinned = 11; bool pinned = 12;
optional int32 parent_id = 12; optional int32 parent_id = 13 [(google.api.field_behavior) = OUTPUT_ONLY];
repeated Resource resources = 13 [(google.api.field_behavior) = OUTPUT_ONLY]; repeated Resource resources = 14 [(google.api.field_behavior) = OUTPUT_ONLY];
repeated MemoRelation relations = 14 [(google.api.field_behavior) = OUTPUT_ONLY]; repeated MemoRelation relations = 15 [(google.api.field_behavior) = OUTPUT_ONLY];
} }
message CreateMemoRequest { message CreateMemoRequest {
@ -163,6 +172,14 @@ message GetMemoResponse {
Memo memo = 1; Memo memo = 1;
} }
message GetMemoByNameRequest {
string name = 1;
}
message GetMemoByNameResponse {
Memo memo = 1;
}
message UpdateMemoRequest { message UpdateMemoRequest {
int32 id = 1; int32 id = 1;

View File

@ -129,6 +129,8 @@
- [CreateMemoResponse](#memos-api-v2-CreateMemoResponse) - [CreateMemoResponse](#memos-api-v2-CreateMemoResponse)
- [DeleteMemoRequest](#memos-api-v2-DeleteMemoRequest) - [DeleteMemoRequest](#memos-api-v2-DeleteMemoRequest)
- [DeleteMemoResponse](#memos-api-v2-DeleteMemoResponse) - [DeleteMemoResponse](#memos-api-v2-DeleteMemoResponse)
- [GetMemoByNameRequest](#memos-api-v2-GetMemoByNameRequest)
- [GetMemoByNameResponse](#memos-api-v2-GetMemoByNameResponse)
- [GetMemoRequest](#memos-api-v2-GetMemoRequest) - [GetMemoRequest](#memos-api-v2-GetMemoRequest)
- [GetMemoResponse](#memos-api-v2-GetMemoResponse) - [GetMemoResponse](#memos-api-v2-GetMemoResponse)
- [GetUserMemosStatsRequest](#memos-api-v2-GetUserMemosStatsRequest) - [GetUserMemosStatsRequest](#memos-api-v2-GetUserMemosStatsRequest)
@ -1864,6 +1866,36 @@
<a name="memos-api-v2-GetMemoByNameRequest"></a>
### GetMemoByNameRequest
| Field | Type | Label | Description |
| ----- | ---- | ----- | ----------- |
| name | [string](#string) | | |
<a name="memos-api-v2-GetMemoByNameResponse"></a>
### GetMemoByNameResponse
| Field | Type | Label | Description |
| ----- | ---- | ----- | ----------- |
| memo | [Memo](#memos-api-v2-Memo) | | |
<a name="memos-api-v2-GetMemoRequest"></a> <a name="memos-api-v2-GetMemoRequest"></a>
### GetMemoRequest ### GetMemoRequest
@ -2072,7 +2104,8 @@
| Field | Type | Label | Description | | Field | Type | Label | Description |
| ----- | ---- | ----- | ----------- | | ----- | ---- | ----- | ----------- |
| id | [int32](#int32) | | | | id | [int32](#int32) | | id is the unique auto-incremented id. |
| name | [string](#string) | | name is the user-defined name. |
| row_status | [RowStatus](#memos-api-v2-RowStatus) | | | | row_status | [RowStatus](#memos-api-v2-RowStatus) | | |
| creator | [string](#string) | | The name of the creator. Format: users/{username} | | creator | [string](#string) | | The name of the creator. Format: users/{username} |
| creator_id | [int32](#int32) | | | | creator_id | [int32](#int32) | | |
@ -2206,6 +2239,7 @@
| CreateMemo | [CreateMemoRequest](#memos-api-v2-CreateMemoRequest) | [CreateMemoResponse](#memos-api-v2-CreateMemoResponse) | CreateMemo creates a memo. | | CreateMemo | [CreateMemoRequest](#memos-api-v2-CreateMemoRequest) | [CreateMemoResponse](#memos-api-v2-CreateMemoResponse) | CreateMemo creates a memo. |
| ListMemos | [ListMemosRequest](#memos-api-v2-ListMemosRequest) | [ListMemosResponse](#memos-api-v2-ListMemosResponse) | ListMemos lists memos with pagination and filter. | | ListMemos | [ListMemosRequest](#memos-api-v2-ListMemosRequest) | [ListMemosResponse](#memos-api-v2-ListMemosResponse) | ListMemos lists memos with pagination and filter. |
| GetMemo | [GetMemoRequest](#memos-api-v2-GetMemoRequest) | [GetMemoResponse](#memos-api-v2-GetMemoResponse) | GetMemo gets a memo by id. | | GetMemo | [GetMemoRequest](#memos-api-v2-GetMemoRequest) | [GetMemoResponse](#memos-api-v2-GetMemoResponse) | GetMemo gets a memo by id. |
| GetMemoByName | [GetMemoByNameRequest](#memos-api-v2-GetMemoByNameRequest) | [GetMemoByNameResponse](#memos-api-v2-GetMemoByNameResponse) | GetMemoByName gets a memo by name. |
| UpdateMemo | [UpdateMemoRequest](#memos-api-v2-UpdateMemoRequest) | [UpdateMemoResponse](#memos-api-v2-UpdateMemoResponse) | UpdateMemo updates a memo. | | UpdateMemo | [UpdateMemoRequest](#memos-api-v2-UpdateMemoRequest) | [UpdateMemoResponse](#memos-api-v2-UpdateMemoResponse) | UpdateMemo updates a memo. |
| DeleteMemo | [DeleteMemoRequest](#memos-api-v2-DeleteMemoRequest) | [DeleteMemoResponse](#memos-api-v2-DeleteMemoResponse) | DeleteMemo deletes a memo by id. | | DeleteMemo | [DeleteMemoRequest](#memos-api-v2-DeleteMemoRequest) | [DeleteMemoResponse](#memos-api-v2-DeleteMemoResponse) | DeleteMemo deletes a memo by id. |
| SetMemoResources | [SetMemoResourcesRequest](#memos-api-v2-SetMemoResourcesRequest) | [SetMemoResourcesResponse](#memos-api-v2-SetMemoResourcesResponse) | SetMemoResources sets resources for a memo. | | SetMemoResources | [SetMemoResourcesRequest](#memos-api-v2-SetMemoResourcesRequest) | [SetMemoResourcesResponse](#memos-api-v2-SetMemoResourcesResponse) | SetMemoResources sets resources for a memo. |

File diff suppressed because it is too large Load Diff

View File

@ -153,6 +153,58 @@ func local_request_MemoService_GetMemo_0(ctx context.Context, marshaler runtime.
} }
func request_MemoService_GetMemoByName_0(ctx context.Context, marshaler runtime.Marshaler, client MemoServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq GetMemoByNameRequest
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)
}
msg, err := client.GetMemoByName(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_MemoService_GetMemoByName_0(ctx context.Context, marshaler runtime.Marshaler, server MemoServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq GetMemoByNameRequest
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)
}
msg, err := server.GetMemoByName(ctx, &protoReq)
return msg, metadata, err
}
func request_MemoService_UpdateMemo_0(ctx context.Context, marshaler runtime.Marshaler, client MemoServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { func request_MemoService_UpdateMemo_0(ctx context.Context, marshaler runtime.Marshaler, client MemoServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq UpdateMemoRequest var protoReq UpdateMemoRequest
var metadata runtime.ServerMetadata var metadata runtime.ServerMetadata
@ -752,6 +804,31 @@ func RegisterMemoServiceHandlerServer(ctx context.Context, mux *runtime.ServeMux
}) })
mux.Handle("GET", pattern_MemoService_GetMemoByName_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.v2.MemoService/GetMemoByName", runtime.WithHTTPPathPattern("/api/v2/memos/{name}"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_MemoService_GetMemoByName_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_MemoService_GetMemoByName_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("PATCH", pattern_MemoService_UpdateMemo_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { mux.Handle("PATCH", pattern_MemoService_UpdateMemo_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()
@ -1084,6 +1161,28 @@ func RegisterMemoServiceHandlerClient(ctx context.Context, mux *runtime.ServeMux
}) })
mux.Handle("GET", pattern_MemoService_GetMemoByName_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.v2.MemoService/GetMemoByName", runtime.WithHTTPPathPattern("/api/v2/memos/{name}"))
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_MemoService_GetMemoByName_0(annotatedContext, inboundMarshaler, client, req, pathParams)
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
if err != nil {
runtime.HTTPError(annotatedContext, mux, outboundMarshaler, w, req, err)
return
}
forward_MemoService_GetMemoByName_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("PATCH", pattern_MemoService_UpdateMemo_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { mux.Handle("PATCH", pattern_MemoService_UpdateMemo_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()
@ -1292,6 +1391,8 @@ var (
pattern_MemoService_GetMemo_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 1, 5, 3}, []string{"api", "v2", "memos", "id"}, "")) pattern_MemoService_GetMemo_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 1, 5, 3}, []string{"api", "v2", "memos", "id"}, ""))
pattern_MemoService_GetMemoByName_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 1, 5, 3}, []string{"api", "v2", "memos", "name"}, ""))
pattern_MemoService_UpdateMemo_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 1, 5, 3}, []string{"api", "v2", "memos", "id"}, "")) pattern_MemoService_UpdateMemo_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 1, 5, 3}, []string{"api", "v2", "memos", "id"}, ""))
pattern_MemoService_DeleteMemo_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 1, 5, 3}, []string{"api", "v2", "memos", "id"}, "")) pattern_MemoService_DeleteMemo_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 1, 5, 3}, []string{"api", "v2", "memos", "id"}, ""))
@ -1318,6 +1419,8 @@ var (
forward_MemoService_GetMemo_0 = runtime.ForwardResponseMessage forward_MemoService_GetMemo_0 = runtime.ForwardResponseMessage
forward_MemoService_GetMemoByName_0 = runtime.ForwardResponseMessage
forward_MemoService_UpdateMemo_0 = runtime.ForwardResponseMessage forward_MemoService_UpdateMemo_0 = runtime.ForwardResponseMessage
forward_MemoService_DeleteMemo_0 = runtime.ForwardResponseMessage forward_MemoService_DeleteMemo_0 = runtime.ForwardResponseMessage

View File

@ -22,6 +22,7 @@ const (
MemoService_CreateMemo_FullMethodName = "/memos.api.v2.MemoService/CreateMemo" MemoService_CreateMemo_FullMethodName = "/memos.api.v2.MemoService/CreateMemo"
MemoService_ListMemos_FullMethodName = "/memos.api.v2.MemoService/ListMemos" MemoService_ListMemos_FullMethodName = "/memos.api.v2.MemoService/ListMemos"
MemoService_GetMemo_FullMethodName = "/memos.api.v2.MemoService/GetMemo" MemoService_GetMemo_FullMethodName = "/memos.api.v2.MemoService/GetMemo"
MemoService_GetMemoByName_FullMethodName = "/memos.api.v2.MemoService/GetMemoByName"
MemoService_UpdateMemo_FullMethodName = "/memos.api.v2.MemoService/UpdateMemo" MemoService_UpdateMemo_FullMethodName = "/memos.api.v2.MemoService/UpdateMemo"
MemoService_DeleteMemo_FullMethodName = "/memos.api.v2.MemoService/DeleteMemo" MemoService_DeleteMemo_FullMethodName = "/memos.api.v2.MemoService/DeleteMemo"
MemoService_SetMemoResources_FullMethodName = "/memos.api.v2.MemoService/SetMemoResources" MemoService_SetMemoResources_FullMethodName = "/memos.api.v2.MemoService/SetMemoResources"
@ -43,6 +44,8 @@ type MemoServiceClient interface {
ListMemos(ctx context.Context, in *ListMemosRequest, opts ...grpc.CallOption) (*ListMemosResponse, error) ListMemos(ctx context.Context, in *ListMemosRequest, opts ...grpc.CallOption) (*ListMemosResponse, error)
// GetMemo gets a memo by id. // GetMemo gets a memo by id.
GetMemo(ctx context.Context, in *GetMemoRequest, opts ...grpc.CallOption) (*GetMemoResponse, error) GetMemo(ctx context.Context, in *GetMemoRequest, opts ...grpc.CallOption) (*GetMemoResponse, error)
// GetMemoByName gets a memo by name.
GetMemoByName(ctx context.Context, in *GetMemoByNameRequest, opts ...grpc.CallOption) (*GetMemoByNameResponse, error)
// UpdateMemo updates a memo. // UpdateMemo updates a memo.
UpdateMemo(ctx context.Context, in *UpdateMemoRequest, opts ...grpc.CallOption) (*UpdateMemoResponse, error) UpdateMemo(ctx context.Context, in *UpdateMemoRequest, opts ...grpc.CallOption) (*UpdateMemoResponse, error)
// DeleteMemo deletes a memo by id. // DeleteMemo deletes a memo by id.
@ -98,6 +101,15 @@ func (c *memoServiceClient) GetMemo(ctx context.Context, in *GetMemoRequest, opt
return out, nil return out, nil
} }
func (c *memoServiceClient) GetMemoByName(ctx context.Context, in *GetMemoByNameRequest, opts ...grpc.CallOption) (*GetMemoByNameResponse, error) {
out := new(GetMemoByNameResponse)
err := c.cc.Invoke(ctx, MemoService_GetMemoByName_FullMethodName, in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *memoServiceClient) UpdateMemo(ctx context.Context, in *UpdateMemoRequest, opts ...grpc.CallOption) (*UpdateMemoResponse, error) { func (c *memoServiceClient) UpdateMemo(ctx context.Context, in *UpdateMemoRequest, opts ...grpc.CallOption) (*UpdateMemoResponse, error) {
out := new(UpdateMemoResponse) out := new(UpdateMemoResponse)
err := c.cc.Invoke(ctx, MemoService_UpdateMemo_FullMethodName, in, out, opts...) err := c.cc.Invoke(ctx, MemoService_UpdateMemo_FullMethodName, in, out, opts...)
@ -189,6 +201,8 @@ type MemoServiceServer interface {
ListMemos(context.Context, *ListMemosRequest) (*ListMemosResponse, error) ListMemos(context.Context, *ListMemosRequest) (*ListMemosResponse, error)
// GetMemo gets a memo by id. // GetMemo gets a memo by id.
GetMemo(context.Context, *GetMemoRequest) (*GetMemoResponse, error) GetMemo(context.Context, *GetMemoRequest) (*GetMemoResponse, error)
// GetMemoByName gets a memo by name.
GetMemoByName(context.Context, *GetMemoByNameRequest) (*GetMemoByNameResponse, error)
// UpdateMemo updates a memo. // UpdateMemo updates a memo.
UpdateMemo(context.Context, *UpdateMemoRequest) (*UpdateMemoResponse, error) UpdateMemo(context.Context, *UpdateMemoRequest) (*UpdateMemoResponse, error)
// DeleteMemo deletes a memo by id. // DeleteMemo deletes a memo by id.
@ -223,6 +237,9 @@ func (UnimplementedMemoServiceServer) ListMemos(context.Context, *ListMemosReque
func (UnimplementedMemoServiceServer) GetMemo(context.Context, *GetMemoRequest) (*GetMemoResponse, error) { func (UnimplementedMemoServiceServer) GetMemo(context.Context, *GetMemoRequest) (*GetMemoResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetMemo not implemented") return nil, status.Errorf(codes.Unimplemented, "method GetMemo not implemented")
} }
func (UnimplementedMemoServiceServer) GetMemoByName(context.Context, *GetMemoByNameRequest) (*GetMemoByNameResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetMemoByName not implemented")
}
func (UnimplementedMemoServiceServer) UpdateMemo(context.Context, *UpdateMemoRequest) (*UpdateMemoResponse, error) { func (UnimplementedMemoServiceServer) UpdateMemo(context.Context, *UpdateMemoRequest) (*UpdateMemoResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method UpdateMemo not implemented") return nil, status.Errorf(codes.Unimplemented, "method UpdateMemo not implemented")
} }
@ -317,6 +334,24 @@ func _MemoService_GetMemo_Handler(srv interface{}, ctx context.Context, dec func
return interceptor(ctx, in, info, handler) return interceptor(ctx, in, info, handler)
} }
func _MemoService_GetMemoByName_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(GetMemoByNameRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(MemoServiceServer).GetMemoByName(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: MemoService_GetMemoByName_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(MemoServiceServer).GetMemoByName(ctx, req.(*GetMemoByNameRequest))
}
return interceptor(ctx, in, info, handler)
}
func _MemoService_UpdateMemo_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { func _MemoService_UpdateMemo_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(UpdateMemoRequest) in := new(UpdateMemoRequest)
if err := dec(in); err != nil { if err := dec(in); err != nil {
@ -498,6 +533,10 @@ var MemoService_ServiceDesc = grpc.ServiceDesc{
MethodName: "GetMemo", MethodName: "GetMemo",
Handler: _MemoService_GetMemo_Handler, Handler: _MemoService_GetMemo_Handler,
}, },
{
MethodName: "GetMemoByName",
Handler: _MemoService_GetMemoByName_Handler,
},
{ {
MethodName: "UpdateMemo", MethodName: "UpdateMemo",
Handler: _MemoService_UpdateMemo_Handler, Handler: _MemoService_UpdateMemo_Handler,

View File

@ -10,11 +10,11 @@ import (
) )
func (d *DB) CreateMemo(ctx context.Context, create *store.Memo) (*store.Memo, error) { func (d *DB) CreateMemo(ctx context.Context, create *store.Memo) (*store.Memo, error) {
fields := []string{"`creator_id`", "`content`", "`visibility`"} fields := []string{"`resource_name`", "`creator_id`", "`content`", "`visibility`"}
placeholder := []string{"?", "?", "?"} placeholder := []string{"?", "?", "?", "?"}
args := []any{create.CreatorID, create.Content, create.Visibility} args := []any{create.ResourceName, create.CreatorID, create.Content, create.Visibility}
stmt := "INSERT INTO memo (" + strings.Join(fields, ", ") + ") VALUES (" + strings.Join(placeholder, ", ") + ") RETURNING `id`, `created_ts`, `updated_ts`, `row_status`" stmt := "INSERT INTO `memo` (" + strings.Join(fields, ", ") + ") VALUES (" + strings.Join(placeholder, ", ") + ") RETURNING `id`, `created_ts`, `updated_ts`, `row_status`"
if err := d.db.QueryRowContext(ctx, stmt, args...).Scan( if err := d.db.QueryRowContext(ctx, stmt, args...).Scan(
&create.ID, &create.ID,
&create.CreatedTs, &create.CreatedTs,
@ -31,29 +31,32 @@ func (d *DB) ListMemos(ctx context.Context, find *store.FindMemo) ([]*store.Memo
where, args := []string{"1 = 1"}, []any{} where, args := []string{"1 = 1"}, []any{}
if v := find.ID; v != nil { if v := find.ID; v != nil {
where, args = append(where, "memo.id = ?"), append(args, *v) where, args = append(where, "`memo`.`id` = ?"), append(args, *v)
}
if v := find.ResourceName; v != nil {
where, args = append(where, "`memo`.`resource_name` = ?"), append(args, *v)
} }
if v := find.CreatorID; v != nil { if v := find.CreatorID; v != nil {
where, args = append(where, "memo.creator_id = ?"), append(args, *v) where, args = append(where, "`memo`.`creator_id` = ?"), append(args, *v)
} }
if v := find.RowStatus; v != nil { if v := find.RowStatus; v != nil {
where, args = append(where, "memo.row_status = ?"), append(args, *v) where, args = append(where, "`memo`.`row_status` = ?"), append(args, *v)
} }
if v := find.CreatedTsBefore; v != nil { if v := find.CreatedTsBefore; v != nil {
where, args = append(where, "memo.created_ts < ?"), append(args, *v) where, args = append(where, "`memo`.`created_ts` < ?"), append(args, *v)
} }
if v := find.CreatedTsAfter; v != nil { if v := find.CreatedTsAfter; v != nil {
where, args = append(where, "memo.created_ts > ?"), append(args, *v) where, args = append(where, "`memo`.`created_ts` > ?"), append(args, *v)
} }
if v := find.UpdatedTsBefore; v != nil { if v := find.UpdatedTsBefore; v != nil {
where, args = append(where, "memo.updated_ts < ?"), append(args, *v) where, args = append(where, "`memo`.`updated_ts` < ?"), append(args, *v)
} }
if v := find.UpdatedTsAfter; v != nil { if v := find.UpdatedTsAfter; v != nil {
where, args = append(where, "memo.updated_ts > ?"), append(args, *v) where, args = append(where, "`memo`.`updated_ts` > ?"), append(args, *v)
} }
if v := find.ContentSearch; len(v) != 0 { if v := find.ContentSearch; len(v) != 0 {
for _, s := range v { for _, s := range v {
where, args = append(where, "memo.content LIKE ?"), append(args, fmt.Sprintf("%%%s%%", s)) where, args = append(where, "`memo`.`content` LIKE ?"), append(args, fmt.Sprintf("%%%s%%", s))
} }
} }
if v := find.VisibilityList; len(v) != 0 { if v := find.VisibilityList; len(v) != 0 {
@ -62,43 +65,43 @@ func (d *DB) ListMemos(ctx context.Context, find *store.FindMemo) ([]*store.Memo
placeholder = append(placeholder, "?") placeholder = append(placeholder, "?")
args = append(args, visibility.String()) args = append(args, visibility.String())
} }
where = append(where, fmt.Sprintf("memo.visibility in (%s)", strings.Join(placeholder, ","))) where = append(where, fmt.Sprintf("`memo`.`visibility` IN (%s)", strings.Join(placeholder, ",")))
} }
if find.ExcludeComments { if find.ExcludeComments {
where = append(where, "parent_id IS NULL") where = append(where, "`parent_id` IS NULL")
} }
orders := []string{} orders := []string{}
if find.OrderByPinned { if find.OrderByPinned {
orders = append(orders, "pinned DESC") orders = append(orders, "`pinned` DESC")
} }
if find.OrderByUpdatedTs { if find.OrderByUpdatedTs {
orders = append(orders, "updated_ts DESC") orders = append(orders, "`updated_ts` DESC")
} else { } else {
orders = append(orders, "created_ts DESC") orders = append(orders, "`created_ts` DESC")
} }
orders = append(orders, "id DESC") orders = append(orders, "`id` DESC")
fields := []string{ fields := []string{
`memo.id AS id`, "`memo`.`id` AS `id`",
`memo.creator_id AS creator_id`, "`memo`.`resource_name` AS `resource_name`",
`memo.created_ts AS created_ts`, "`memo`.`creator_id` AS `creator_id`",
`memo.updated_ts AS updated_ts`, "`memo`.`created_ts` AS `created_ts`",
`memo.row_status AS row_status`, "`memo`.`updated_ts` AS `updated_ts`",
`memo.visibility AS visibility`, "`memo`.`row_status` AS `row_status`",
`IFNULL(memo_organizer.pinned, 0) AS pinned`, "`memo`.`visibility` AS `visibility`",
`memo_relation.related_memo_id AS parent_id`, "IFNULL(`memo_organizer`.`pinned`, 0) AS `pinned`",
"`memo_relation`.`related_memo_id` AS `parent_id`",
} }
if !find.ExcludeContent { if !find.ExcludeContent {
fields = append(fields, `memo.content AS content`) fields = append(fields, "`memo`.`content` AS `content`")
} }
query := `SELECT ` + strings.Join(fields, ", ") + ` query := "SELECT " + strings.Join(fields, ", ") + "FROM `memo` " +
FROM memo "LEFT JOIN `memo_organizer` ON `memo`.`id` = `memo_organizer`.`memo_id` AND `memo`.`creator_id` = `memo_organizer`.`user_id` " +
LEFT JOIN memo_organizer ON memo.id = memo_organizer.memo_id AND memo.creator_id = memo_organizer.user_id "FULL JOIN `memo_relation` ON `memo`.`id` = `memo_relation`.`memo_id` AND `memo_relation`.`type` = \"COMMENT\"" + " " +
FULL JOIN memo_relation ON memo.id = memo_relation.memo_id AND memo_relation.type = "COMMENT" "WHERE " + strings.Join(where, " AND ") + " " +
WHERE ` + strings.Join(where, " AND ") + ` "ORDER BY " + strings.Join(orders, ", ")
ORDER BY ` + strings.Join(orders, ", ")
if find.Limit != nil { if find.Limit != nil {
query = fmt.Sprintf("%s LIMIT %d", query, *find.Limit) query = fmt.Sprintf("%s LIMIT %d", query, *find.Limit)
if find.Offset != nil { if find.Offset != nil {
@ -117,6 +120,7 @@ func (d *DB) ListMemos(ctx context.Context, find *store.FindMemo) ([]*store.Memo
var memo store.Memo var memo store.Memo
dests := []any{ dests := []any{
&memo.ID, &memo.ID,
&memo.ResourceName,
&memo.CreatorID, &memo.CreatorID,
&memo.CreatedTs, &memo.CreatedTs,
&memo.UpdatedTs, &memo.UpdatedTs,
@ -143,24 +147,27 @@ func (d *DB) ListMemos(ctx context.Context, find *store.FindMemo) ([]*store.Memo
func (d *DB) UpdateMemo(ctx context.Context, update *store.UpdateMemo) error { func (d *DB) UpdateMemo(ctx context.Context, update *store.UpdateMemo) error {
set, args := []string{}, []any{} set, args := []string{}, []any{}
if v := update.ResourceName; v != nil {
set, args = append(set, "`resource_name` = ?"), append(args, *v)
}
if v := update.CreatedTs; v != nil { if v := update.CreatedTs; v != nil {
set, args = append(set, "created_ts = ?"), append(args, *v) set, args = append(set, "`created_ts` = ?"), append(args, *v)
} }
if v := update.UpdatedTs; v != nil { if v := update.UpdatedTs; v != nil {
set, args = append(set, "updated_ts = ?"), append(args, *v) set, args = append(set, "`updated_ts` = ?"), append(args, *v)
} }
if v := update.RowStatus; v != nil { if v := update.RowStatus; v != nil {
set, args = append(set, "row_status = ?"), append(args, *v) set, args = append(set, "`row_status` = ?"), append(args, *v)
} }
if v := update.Content; v != nil { if v := update.Content; v != nil {
set, args = append(set, "content = ?"), append(args, *v) set, args = append(set, "`content` = ?"), append(args, *v)
} }
if v := update.Visibility; v != nil { if v := update.Visibility; v != nil {
set, args = append(set, "visibility = ?"), append(args, *v) set, args = append(set, "`visibility` = ?"), append(args, *v)
} }
args = append(args, update.ID) args = append(args, update.ID)
stmt := `UPDATE memo SET ` + strings.Join(set, ", ") + ` WHERE id = ?` stmt := "UPDATE `memo` SET " + strings.Join(set, ", ") + " WHERE `id` = ?"
if _, err := d.db.ExecContext(ctx, stmt, args...); err != nil { if _, err := d.db.ExecContext(ctx, stmt, args...); err != nil {
return err return err
} }
@ -168,8 +175,8 @@ func (d *DB) UpdateMemo(ctx context.Context, update *store.UpdateMemo) error {
} }
func (d *DB) DeleteMemo(ctx context.Context, delete *store.DeleteMemo) error { func (d *DB) DeleteMemo(ctx context.Context, delete *store.DeleteMemo) error {
where, args := []string{"id = ?"}, []any{delete.ID} where, args := []string{"`id` = ?"}, []any{delete.ID}
stmt := `DELETE FROM memo WHERE ` + strings.Join(where, " AND ") stmt := "DELETE FROM `memo` WHERE " + strings.Join(where, " AND ")
result, err := d.db.ExecContext(ctx, stmt, args...) result, err := d.db.ExecContext(ctx, stmt, args...)
if err != nil { if err != nil {
return err return err
@ -186,16 +193,7 @@ func (d *DB) DeleteMemo(ctx context.Context, delete *store.DeleteMemo) error {
} }
func vacuumMemo(ctx context.Context, tx *sql.Tx) error { func vacuumMemo(ctx context.Context, tx *sql.Tx) error {
stmt := ` stmt := "DELETE FROM `memo` WHERE `creator_id` NOT IN (SELECT `id` FROM `user`)"
DELETE FROM
memo
WHERE
creator_id NOT IN (
SELECT
id
FROM
user
)`
_, err := tx.ExecContext(ctx, stmt) _, err := tx.ExecContext(ctx, stmt)
if err != nil { if err != nil {
return err return err

View File

@ -55,6 +55,7 @@ CREATE TABLE user_setting (
-- memo -- memo
CREATE TABLE memo ( CREATE TABLE memo (
id INTEGER PRIMARY KEY AUTOINCREMENT, id INTEGER PRIMARY KEY AUTOINCREMENT,
resource_name TEXT NOT NULL UNIQUE,
creator_id INTEGER NOT NULL, creator_id INTEGER NOT NULL,
created_ts BIGINT NOT NULL DEFAULT (strftime('%s', 'now')), created_ts BIGINT NOT NULL DEFAULT (strftime('%s', 'now')),
updated_ts BIGINT NOT NULL DEFAULT (strftime('%s', 'now')), updated_ts BIGINT NOT NULL DEFAULT (strftime('%s', 'now')),

View File

@ -1,8 +1,14 @@
INSERT INTO INSERT INTO
memo (`id`, `content`, `creator_id`) memo (
`id`,
`resource_name`,
`content`,
`creator_id`
)
VALUES VALUES
( (
1, 1,
"hello",
"#Hello 👋 Welcome to memos.", "#Hello 👋 Welcome to memos.",
101 101
); );
@ -10,6 +16,7 @@ VALUES
INSERT INTO INSERT INTO
memo ( memo (
`id`, `id`,
`resource_name`,
`content`, `content`,
`creator_id`, `creator_id`,
`visibility` `visibility`
@ -17,6 +24,7 @@ INSERT INTO
VALUES VALUES
( (
2, 2,
"todo",
'#TODO '#TODO
- [x] Take more photos about **🌄 sunset**; - [x] Take more photos about **🌄 sunset**;
- [x] Clean the room; - [x] Clean the room;
@ -28,6 +36,7 @@ VALUES
INSERT INTO INSERT INTO
memo ( memo (
`id`, `id`,
`resource_name`,
`content`, `content`,
`creator_id`, `creator_id`,
`visibility` `visibility`
@ -35,6 +44,7 @@ INSERT INTO
VALUES VALUES
( (
3, 3,
"links",
'**[Memos](https://github.com/usememos/memos)**: A lightweight, self-hosted memo hub. Open Source and Free forever. '**[Memos](https://github.com/usememos/memos)**: A lightweight, self-hosted memo hub. Open Source and Free forever.
**[Slash](https://github.com/yourselfhosted/slash)**: An open source, self-hosted bookmarks and link sharing platform. Save and share your links very easily.', **[Slash](https://github.com/yourselfhosted/slash)**: An open source, self-hosted bookmarks and link sharing platform. Save and share your links very easily.',
101, 101,
@ -44,6 +54,7 @@ VALUES
INSERT INTO INSERT INTO
memo ( memo (
`id`, `id`,
`resource_name`,
`content`, `content`,
`creator_id`, `creator_id`,
`visibility` `visibility`
@ -51,6 +62,7 @@ INSERT INTO
VALUES VALUES
( (
4, 4,
"todo2",
'#TODO '#TODO
- [x] Take more photos about **🌄 sunset**; - [x] Take more photos about **🌄 sunset**;
- [ ] Clean the classroom; - [ ] Clean the classroom;
@ -62,6 +74,7 @@ VALUES
INSERT INTO INSERT INTO
memo ( memo (
`id`, `id`,
`resource_name`,
`content`, `content`,
`creator_id`, `creator_id`,
`visibility` `visibility`
@ -69,6 +82,7 @@ INSERT INTO
VALUES VALUES
( (
5, 5,
"words",
'三人行,必有我师焉!👨‍🏫', '三人行,必有我师焉!👨‍🏫',
102, 102,
'PUBLIC' 'PUBLIC'

View File

@ -2,6 +2,9 @@ package store
import ( import (
"context" "context"
"errors"
"github.com/usememos/memos/internal/util"
) )
// Visibility is the type of a visibility. // Visibility is the type of a visibility.
@ -30,6 +33,7 @@ func (v Visibility) String() string {
type Memo struct { type Memo struct {
ID int32 ID int32
ResourceName string
// Standard fields // Standard fields
RowStatus RowStatus RowStatus RowStatus
@ -48,6 +52,7 @@ type Memo struct {
type FindMemo struct { type FindMemo struct {
ID *int32 ID *int32
ResourceName *string
// Standard fields // Standard fields
RowStatus *RowStatus RowStatus *RowStatus
@ -72,6 +77,7 @@ type FindMemo struct {
type UpdateMemo struct { type UpdateMemo struct {
ID int32 ID int32
ResourceName *string
CreatedTs *int64 CreatedTs *int64
UpdatedTs *int64 UpdatedTs *int64
RowStatus *RowStatus RowStatus *RowStatus
@ -84,6 +90,9 @@ type DeleteMemo struct {
} }
func (s *Store) CreateMemo(ctx context.Context, create *Memo) (*Memo, error) { func (s *Store) CreateMemo(ctx context.Context, create *Memo) (*Memo, error) {
if !util.ResourceNameMatcher.MatchString(create.ResourceName) {
return nil, errors.New("resource name is invalid")
}
return s.driver.CreateMemo(ctx, create) return s.driver.CreateMemo(ctx, create)
} }
@ -105,6 +114,9 @@ func (s *Store) GetMemo(ctx context.Context, find *FindMemo) (*Memo, error) {
} }
func (s *Store) UpdateMemo(ctx context.Context, update *UpdateMemo) error { func (s *Store) UpdateMemo(ctx context.Context, update *UpdateMemo) error {
if update.ResourceName != nil && !util.ResourceNameMatcher.MatchString(*update.ResourceName) {
return errors.New("resource name is invalid")
}
return s.driver.UpdateMemo(ctx, update) return s.driver.UpdateMemo(ctx, update)
} }

View File

@ -15,6 +15,7 @@ func TestMemoOrganizerStore(t *testing.T) {
user, err := createTestingHostUser(ctx, ts) user, err := createTestingHostUser(ctx, ts)
require.NoError(t, err) require.NoError(t, err)
memoCreate := &store.Memo{ memoCreate := &store.Memo{
ResourceName: "main-memo",
CreatorID: user.ID, CreatorID: user.ID,
Content: "main memo content", Content: "main memo content",
Visibility: store.Public, Visibility: store.Public,

View File

@ -15,6 +15,7 @@ func TestMemoRelationStore(t *testing.T) {
user, err := createTestingHostUser(ctx, ts) user, err := createTestingHostUser(ctx, ts)
require.NoError(t, err) require.NoError(t, err)
memoCreate := &store.Memo{ memoCreate := &store.Memo{
ResourceName: "main-memo",
CreatorID: user.ID, CreatorID: user.ID,
Content: "main memo content", Content: "main memo content",
Visibility: store.Public, Visibility: store.Public,
@ -23,6 +24,7 @@ func TestMemoRelationStore(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, memoCreate.Content, memo.Content) require.Equal(t, memoCreate.Content, memo.Content)
relatedMemoCreate := &store.Memo{ relatedMemoCreate := &store.Memo{
ResourceName: "related-memo",
CreatorID: user.ID, CreatorID: user.ID,
Content: "related memo content", Content: "related memo content",
Visibility: store.Public, Visibility: store.Public,
@ -31,6 +33,7 @@ func TestMemoRelationStore(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, relatedMemoCreate.Content, relatedMemo.Content) require.Equal(t, relatedMemoCreate.Content, relatedMemo.Content)
commentMemoCreate := &store.Memo{ commentMemoCreate := &store.Memo{
ResourceName: "comment-memo",
CreatorID: user.ID, CreatorID: user.ID,
Content: "comment memo content", Content: "comment memo content",
Visibility: store.Public, Visibility: store.Public,

View File

@ -15,6 +15,7 @@ func TestMemoStore(t *testing.T) {
user, err := createTestingHostUser(ctx, ts) user, err := createTestingHostUser(ctx, ts)
require.NoError(t, err) require.NoError(t, err)
memoCreate := &store.Memo{ memoCreate := &store.Memo{
ResourceName: "test-resource-name",
CreatorID: user.ID, CreatorID: user.ID,
Content: "test_content", Content: "test_content",
Visibility: store.Public, Visibility: store.Public,
@ -67,6 +68,7 @@ func TestDeleteMemoStore(t *testing.T) {
user, err := createTestingHostUser(ctx, ts) user, err := createTestingHostUser(ctx, ts)
require.NoError(t, err) require.NoError(t, err)
memoCreate := &store.Memo{ memoCreate := &store.Memo{
ResourceName: "test-resource-name",
CreatorID: user.ID, CreatorID: user.ID,
Content: "test_content", Content: "test_content",
Visibility: store.Public, Visibility: store.Public,