1
0
Fork 0
mirror of https://github.com/anyproto/anytype-heart.git synced 2025-06-10 01:51:07 +09:00

GO-4969: Switch to PropertyEntry and update details building, move description to properties

This commit is contained in:
Jannis Metrikat 2025-04-27 17:29:40 +02:00
parent 49f321aa24
commit 5fd171130b
No known key found for this signature in database
GPG key ID: B223CAC5AAF85615
7 changed files with 193 additions and 58 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -183,10 +183,6 @@ components:
description: The body of the object
example: This is the body of the object. Markdown syntax is supported here.
type: string
description:
description: The description of the object
example: This is a description of the object.
type: string
icon:
$ref: '#/components/schemas/object.Icon'
name:
@ -194,11 +190,11 @@ components:
example: My object
type: string
properties:
additionalProperties: {}
description: The properties to set on the object
example:
'{"property_key"': ' "value"}'
type: object
items:
$ref: '#/components/schemas/object.PropertyEntry'
type: array
uniqueItems: false
source:
description: The source url, only applicable for bookmarks
example: https://bookmark-source.com
@ -300,7 +296,7 @@ components:
type: string
file:
description: The file of the icon
example: http://127.0.0.1:31006/image/bafybeieptz5hvcy6txplcvphjbbh5yjc2zqhmihs3owkh5oab4ezauzqay
example: bafybeieptz5hvcy6txplcvphjbbh5yjc2zqhmihs3owkh5oab4ezauzqay
type: string
format:
$ref: '#/components/schemas/object.IconFormat'
@ -503,6 +499,69 @@ components:
example: https://example.com
type: string
type: object
object.PropertyEntry:
properties:
checkbox:
description: The checkbox value, if applicable
enum:
- true
- false
example: true
type: boolean
date:
description: The date value, if applicable
example: "2025-02-14T12:34:56Z"
type: string
email:
description: The email value, if applicable
example: example@example.com
type: string
files:
description: The file references, if applicable
example:
- '[''fileId'']'
items:
type: string
type: array
uniqueItems: false
key:
description: The key of the property
example: last_modified_date
type: string
multi_select:
description: The multi-select values, if applicable
items:
type: string
type: array
uniqueItems: false
number:
description: The number value, if applicable
example: 42
type: number
objects:
description: The object references, if applicable
example:
- '[''objectId'']'
items:
type: string
type: array
uniqueItems: false
phone:
description: The phone number value, if applicable
example: "+1234567890"
type: string
select:
description: The select value, if applicable
type: string
text:
description: The text value, if applicable
example: Some text...
type: string
url:
description: The url value, if applicable
example: https://example.com
type: string
type: object
object.PropertyFormat:
description: The format of the property used for filtering
enum:
@ -653,10 +712,6 @@ components:
type: object
object.UpdateObjectRequest:
properties:
description:
description: The description to set for the object
example: This is a description of the object.
type: string
icon:
$ref: '#/components/schemas/object.Icon'
name:
@ -664,11 +719,11 @@ components:
example: My object
type: string
properties:
additionalProperties: {}
description: The properties to update on the object
example:
'{"property_key"': ' "value"}'
type: object
description: The properties to set on the object
items:
$ref: '#/components/schemas/object.PropertyEntry'
type: array
uniqueItems: false
type: object
object.UpdatePropertyRequest:
properties:

View file

@ -60,11 +60,11 @@ func (c *Color) UnmarshalJSON(data []byte) error {
}
type Icon struct {
Format IconFormat `json:"format" enums:"emoji,file,icon" example:"emoji"` // The type of the icon
Emoji *string `json:"emoji,omitempty" example:"📄"` // The emoji of the icon
File *string `json:"file,omitempty" example:"http://127.0.0.1:31006/image/bafybeieptz5hvcy6txplcvphjbbh5yjc2zqhmihs3owkh5oab4ezauzqay"` // The file of the icon
Name *string `json:"name,omitempty" example:"document"` // The name of the icon
Color *Color `json:"color,omitempty" example:"yellow" enums:"grey,yellow,orange,red,pink,purple,blue,ice,teal,lime"` // The color of the icon
Format IconFormat `json:"format" enums:"emoji,file,icon" example:"emoji"` // The type of the icon
Emoji *string `json:"emoji,omitempty" example:"📄"` // The emoji of the icon
File *string `json:"file,omitempty" example:"bafybeieptz5hvcy6txplcvphjbbh5yjc2zqhmihs3owkh5oab4ezauzqay"` // The file of the icon
Name *string `json:"name,omitempty" example:"document"` // The name of the icon
Color *Color `json:"color,omitempty" example:"yellow" enums:"grey,yellow,orange,red,pink,purple,blue,ice,teal,lime"` // The color of the icon
}
var iconOptionToColor = map[float64]Color{
@ -114,10 +114,14 @@ func ColorPtr(c Color) *Color {
return &c
}
// isEmoji returns true if every rune in s is in the Unicode 'Symbol, Other' category.
func IsEmoji(s string) bool {
if s == "" {
return false
}
for _, r := range s {
if !unicode.Is(unicode.So, r) {
if unicode.Is(unicode.Cf, r) || unicode.Is(unicode.So, r) || unicode.Is(unicode.Sk, r) {
continue
} else {
return false
}
}

View file

@ -38,21 +38,19 @@ func (pf *PropertyFormat) UnmarshalJSON(data []byte) error {
}
type CreateObjectRequest struct {
Name string `json:"name" binding:"required" example:"My object"` // The name of the object
Icon Icon `json:"icon"` // The icon of the object
Description string `json:"description" example:"This is a description of the object."` // The description of the object
Body string `json:"body" example:"This is the body of the object. Markdown syntax is supported here."` // The body of the object
Source string `json:"source" example:"https://bookmark-source.com"` // The source url, only applicable for bookmarks
TemplateId string `json:"template_id" example:"bafyreictrp3obmnf6dwejy5o4p7bderaaia4bdg2psxbfzf44yya5uutge"` // The id of the template to use
TypeKey string `json:"type_key" binding:"required" example:"ot-page"` // The key of the type of object to create
Properties map[string]interface{} `json:"properties" example:"{\"property_key\": \"value\"}"` // The properties to set on the object
Name string `json:"name" binding:"required" example:"My object"` // The name of the object
Icon Icon `json:"icon"` // The icon of the object
Body string `json:"body" example:"This is the body of the object. Markdown syntax is supported here."` // The body of the object
Source string `json:"source" example:"https://bookmark-source.com"` // The source url, only applicable for bookmarks
TemplateId string `json:"template_id" example:"bafyreictrp3obmnf6dwejy5o4p7bderaaia4bdg2psxbfzf44yya5uutge"` // The id of the template to use
TypeKey string `json:"type_key" binding:"required" example:"ot-page"` // The key of the type of object to create
Properties []PropertyEntry `json:"properties"` // The properties to set on the object
}
type UpdateObjectRequest struct {
Name string `json:"name" example:"My object"` // The name of the object
Icon Icon `json:"icon"` // The icon to set for the object
Description string `json:"description" example:"This is a description of the object."` // The description to set for the object
Properties map[string]interface{} `json:"properties" example:"{\"property_key\": \"value\"}"` // The properties to update on the object
Name string `json:"name" example:"My object"` // The name of the object
Icon Icon `json:"icon"` // The icon to set for the object
Properties []PropertyEntry `json:"properties"` // The properties to set for the object
}
type ObjectResponse struct {
@ -156,6 +154,21 @@ type Property struct {
Objects []string `json:"objects,omitempty" example:"['objectId']"` // The object references, if applicable
}
type PropertyEntry struct {
Key string `json:"key" example:"last_modified_date"` // The key of the property
Text *string `json:"text,omitempty" example:"Some text..."` // The text value, if applicable
Number *float64 `json:"number,omitempty" example:"42"` // The number value, if applicable
Select *string `json:"select,omitempty"` // The select value, if applicable
MultiSelect []string `json:"multi_select,omitempty"` // The multi-select values, if applicable
Date *string `json:"date,omitempty" example:"2025-02-14T12:34:56Z"` // The date value, if applicable
Files []string `json:"files,omitempty" example:"['fileId']"` // The file references, if applicable
Checkbox *bool `json:"checkbox,omitempty" example:"true" enums:"true,false"` // The checkbox value, if applicable
Url *string `json:"url,omitempty" example:"https://example.com"` // The url value, if applicable
Email *string `json:"email,omitempty" example:"example@example.com"` // The email value, if applicable
Phone *string `json:"phone,omitempty" example:"+1234567890"` // The phone number value, if applicable
Objects []string `json:"objects,omitempty" example:"['objectId']"` // The object references, if applicable
}
type TagResponse struct {
Tag Tag `json:"tag"` // The tag
}

View file

@ -217,7 +217,7 @@ func (s *service) CreateObject(ctx context.Context, spaceId string, request Crea
}
// ObjectRelationAddFeatured if description was set
if request.Description != "" {
if details.Fields[bundle.RelationKeyDescription.String()] != nil {
relAddFeatResp := s.mw.ObjectRelationAddFeatured(ctx, &pb.RpcObjectRelationAddFeaturedRequest{
ContextId: objectId,
Relations: []string{bundle.RelationKeyDescription.String()},
@ -287,7 +287,7 @@ func (s *service) buildObjectDetails(ctx context.Context, spaceId string, reques
iconFields := map[string]*types.Value{}
if request.Icon.Emoji != nil {
if !IsEmoji(*request.Icon.Emoji) {
if len(*request.Icon.Emoji) > 0 && !IsEmoji(*request.Icon.Emoji) {
return nil, util.ErrBadInput("icon emoji is not valid")
}
iconFields[bundle.RelationKeyIconEmoji.String()] = pbtypes.String(*request.Icon.Emoji)
@ -296,16 +296,15 @@ func (s *service) buildObjectDetails(ctx context.Context, spaceId string, reques
}
fields := map[string]*types.Value{
bundle.RelationKeyName.String(): pbtypes.String(s.sanitizedString(request.Name)),
bundle.RelationKeyDescription.String(): pbtypes.String(s.sanitizedString(request.Description)),
bundle.RelationKeySource.String(): pbtypes.String(s.sanitizedString(request.Source)),
bundle.RelationKeyOrigin.String(): pbtypes.Int64(int64(model.ObjectOrigin_api)),
bundle.RelationKeyName.String(): pbtypes.String(s.sanitizedString(request.Name)),
bundle.RelationKeySource.String(): pbtypes.String(s.sanitizedString(request.Source)),
bundle.RelationKeyOrigin.String(): pbtypes.Int64(int64(model.ObjectOrigin_api)),
}
for k, v := range iconFields {
fields[k] = v
}
if request.Properties == nil {
if len(request.Properties) == 0 {
return &types.Struct{Fields: fields}, nil
}
@ -314,25 +313,89 @@ func (s *service) buildObjectDetails(ctx context.Context, spaceId string, reques
return nil, err
}
for key, val := range request.Properties {
for _, entry := range request.Properties {
key := entry.Key
rk := FromPropertyApiKey(key)
if _, isExcluded := excludedSystemProperties[rk]; isExcluded {
continue
}
if slices.Contains(bundle.LocalAndDerivedRelationKeys, domain.RelationKey(key)) {
return nil, util.ErrBadInput("property '" + key + "' cannot be set directly")
}
if prop, ok := propertyMap[rk]; ok {
sanitized, err := s.sanitizeAndValidatePropertyValue(ctx, spaceId, key, prop.Format, val, prop)
if err != nil {
return nil, err
}
fields[rk] = pbtypes.ToValue(sanitized)
} else {
return nil, errors.New("unknown property '" + key + "' must be a string")
prop, ok := propertyMap[rk]
if !ok {
return nil, errors.New("unknown property '" + key + "'")
}
var raw interface{}
switch prop.Format {
case PropertyFormatText:
if entry.Text == nil {
return nil, util.ErrBadInput("property '" + key + "' requires a text value")
}
raw = *entry.Text
case PropertyFormatNumber:
if entry.Number == nil {
return nil, util.ErrBadInput("property '" + key + "' requires a number value")
}
raw = *entry.Number
case PropertyFormatSelect:
if entry.Select == nil {
return nil, util.ErrBadInput("property '" + key + "' requires a select value")
}
raw = *entry.Select
case PropertyFormatMultiSelect:
ids := make([]interface{}, len(entry.MultiSelect))
for i, tagId := range entry.MultiSelect {
ids[i] = tagId
}
raw = ids
case PropertyFormatDate:
if entry.Date == nil {
return nil, util.ErrBadInput("property '" + key + "' requires a date value")
}
raw = *entry.Date
case PropertyFormatCheckbox:
if entry.Checkbox == nil {
return nil, util.ErrBadInput("property '" + key + "' requires a boolean value")
}
raw = *entry.Checkbox
case PropertyFormatUrl:
if entry.Url == nil {
return nil, util.ErrBadInput("property '" + key + "' requires a URL value")
}
raw = *entry.Url
case PropertyFormatEmail:
if entry.Email == nil {
return nil, util.ErrBadInput("property '" + key + "' requires an email value")
}
raw = *entry.Email
case PropertyFormatPhone:
if entry.Phone == nil {
return nil, util.ErrBadInput("property '" + key + "' requires a phone value")
}
raw = *entry.Phone
case PropertyFormatObjects, PropertyFormatFiles:
var list []string
if prop.Format == PropertyFormatFiles {
list = entry.Files
} else {
list = entry.Objects
}
ids := make([]interface{}, len(list))
for i, id := range list {
ids[i] = id
}
raw = ids
default:
return nil, util.ErrBadInput("unsupported property format: " + string(prop.Format))
}
sanitized, err := s.sanitizeAndValidatePropertyValue(ctx, spaceId, key, prop.Format, raw, prop)
if err != nil {
return nil, err
}
fields[rk] = pbtypes.ToValue(sanitized)
}
return &types.Struct{Fields: fields}, nil

View file

@ -265,7 +265,7 @@ func (s *service) sanitizeAndValidatePropertyValue(ctx context.Context, spaceId
id, ok := value.(string)
id = s.sanitizedString(id)
if !ok {
return nil, util.ErrBadInput("property '" + key + "' must be a string (option id)")
return nil, util.ErrBadInput("property '" + key + "' must be a string (tag id)")
}
if !s.isValidSelectOption(ctx, spaceId, property, id) {
return nil, util.ErrBadInput("invalid select option for '" + key + "': " + id)