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:
parent
49f321aa24
commit
5fd171130b
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
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue