diff --git a/src/ap b/src/ap index 4257ba4..904a471 160000 --- a/src/ap +++ b/src/ap @@ -1 +1 @@ -Subproject commit 4257ba45412070f3d6a7ac2bd42b4f488b4c5761 +Subproject commit 904a471760e1990a6505034764154e0eb6c3817d diff --git a/src/controller/api/status.c b/src/controller/api/status.c index 316693d..4e466cd 100644 --- a/src/controller/api/status.c +++ b/src/controller/api/status.c @@ -102,6 +102,23 @@ bool handle_post( struct http_request* req, struct account* a ) bool result = false; struct status* s = NULL; + struct poll_params_t + { + int expires_in; + bool multiple; + struct { + char** items; + int count; + } options; + }; + #define OBJ_TYPE struct poll_params_t + static struct json_object_field poll_params_layout[] = { + JSON_FIELD_INTEGER( expires_in, true ), + JSON_FIELD_BOOL( multiple, false ), + JSON_FIELD_ARRAY_OF_STRINGS( options, false ), + JSON_FIELD_END, + }; + #undef OBJ_TYPE struct params_t { struct { @@ -111,24 +128,14 @@ bool handle_post( struct http_request* req, struct account* a ) bool sensitive; + struct poll_params_t poll; + char* status; char* visibility; char* in_reply_to_id; char* quote_id; char* spoiler_text; } params; - memset(¶ms,0,sizeof(params)); - - const char* indempotency_key = http_request_get_header( req, "Idempotency-Key" ); - if( indempotency_key ) { - char* existing = ffdb_trie_get( "data/indempotency", indempotency_key ); - if( existing ) { - s = status_from_id( atoi(existing) ); - free( existing ); - goto success; - } - } - #define OBJ_TYPE struct params_t static struct json_object_field layout[] = { JSON_FIELD_ARRAY_OF_STRINGS( media_ids, false ), @@ -138,10 +145,29 @@ bool handle_post( struct http_request* req, struct account* a ) JSON_FIELD_STRING( in_reply_to_id, false ), JSON_FIELD_STRING( quote_id, false ), JSON_FIELD_STRING( spoiler_text, false ), + { + .key = "poll", + .offset = offsetof( OBJ_TYPE, poll ), + .required = false, + .type = &json_field_object_composite, + .composite_layout = poll_params_layout, + }, JSON_FIELD_END, }; #undef OBJ_TYPE + memset(¶ms,0,sizeof(params)); + + const char* indempotency_key = http_request_get_header( req, "Idempotency-Key" ); + if( indempotency_key ) { + char* existing = ffdb_trie_get( "data/indempotency", indempotency_key ); + if( existing ) { + s = status_from_id( atoi(existing) ); + free( existing ); + goto success; + } + } + FILE* data = http_request_get_request_data( req ); if( !json_read_object_layout_from_FILE( data, layout, ¶ms ) ) { goto failed; } @@ -151,6 +177,28 @@ bool handle_post( struct http_request* req, struct account* a ) s->published = time(NULL); s->source = strdup( params.status ); + // Handle poll + if( params.poll.options.count > 0 ) { + struct status_poll* poll; + poll = malloc(sizeof(*poll)); + memset(poll,0,sizeof(*poll)); + s->poll = poll; + + poll->expires_at = time(NULL) + params.poll.expires_in; + + for( int i = 0; i < params.poll.options.count; ++i ) { + struct status_poll_option* option; + option = malloc(sizeof(*option)); + memset(option,0,sizeof(*option)); + + option->title = params.poll.options.items[i]; + params.poll.options.items[i] = NULL; + + array_append( &poll->options, sizeof(option), &option ); + } + free( params.poll.options.items ); + } + if( 0 == strcmp( "direct", params.visibility ) ) { s->visibility = status_visibility_direct; } @@ -487,7 +535,9 @@ bool route_polls( struct http_request* req ) if( !s->poll ) { goto failed; } if( http_request_route_term( req, "votes" ) ) { - if( handle_votes( req, s ) ) { goto success; } + if( http_request_route_method( req, "POST" ) ) { + if( handle_votes( req, s ) ) { goto success; } + } } goto failed; diff --git a/src/controller/inbox.c b/src/controller/inbox.c index 5704d04..da321e1 100644 --- a/src/controller/inbox.c +++ b/src/controller/inbox.c @@ -11,6 +11,7 @@ // Model #include "model/server.h" #include "model/status.h" +#include "model/status/poll.h" #include "model/account.h" #include "model/notification.h" #include "model/inbox_envelope.h" @@ -413,6 +414,28 @@ static bool route_create( struct ap_object* act ) goto discard; } + // Check if this is a poll vote + if( obj->in_reply_to ) { + struct status* parent = status_from_uri( obj->in_reply_to ); + if( parent && !parent->remote && parent->poll ) { + struct account* voter = account_from_uri_or_fetch( obj->actor ); + + if( voter ) { + printf( "voter is %d (%s)\n", voter->id, voter->account_url ); + } + + // This is a poll vote + if( status_poll_add_vote_by_name( parent, voter, obj->name ) ) { + status_save( parent ); + printf( "Handled vote\n" ); + } + + account_free(voter); + status_free(parent); + goto discard; + } + } + // Create local status s = status_from_uri( obj->id ); if( !s ) { diff --git a/src/model/activity.c b/src/model/activity.c index e872a10..4fcf879 100644 --- a/src/model/activity.c +++ b/src/model/activity.c @@ -12,6 +12,7 @@ #include "model/server.h" #include "model/account.h" #include "model/status.h" +#include "model/status/poll.h" #include "model/emoji.h" #include "model/media.h" #include "model/outbox_envelope.h" @@ -343,6 +344,43 @@ struct ap_object* activity_create_Note( struct status* s ) account_free(mentioned); } + // Poll data + if( s->poll ) { + struct { + struct ap_object_ptr_or_ref* items; + int count; + } *options_list; + if( s->poll->multiple_choice ) { + options_list = (void*)&act->any_of; + } else { + options_list = (void*)&act->one_of; + } + + // Change from Note to Question if this post includes a poll + act->type = ap_Question; + act->closed = s->poll->expires_at; + act->end_time = s->poll->expires_at; + + for( int i = 0; i < s->poll->options.count; ++i ) { + struct status_poll_option* poll_option = s->poll->options.items[i]; + struct ap_object_ptr_or_ref option; + option.tag = apaot_object; + option.ptr = ap_object_new(); + option.ptr->type = ap_Note; + option.ptr->name = strdup( poll_option->title ); + + //if( poll_option->votes.count > 0 ) { + struct ap_object* replies = ap_object_new(); + option.ptr->replies.tag = apaot_object; + option.ptr->replies.ptr = replies; + replies->type = ap_Collection; + replies->total_items = poll_option->votes.count; + //} + + array_append( options_list, sizeof(option), &option ); + } + } + return act; } diff --git a/src/model/status.c b/src/model/status.c index 90c4e14..5a07102 100644 --- a/src/model/status.c +++ b/src/model/status.c @@ -238,23 +238,6 @@ struct status* status_from_uri( const char* uri ) } return NULL; - - /* - int id = -1; - if( !hash_index_get( "data/statuses/uri", uri, &id ) ) { return NULL; } - - s = status_from_id(id); - - // Convert from hash_index to trie - if( s ) { - char id_str[32]; - snprintf( id_str,32, "%d", s->id ); - ffdb_trie_set( "data/statuses/by-uri", s->url, id_str ); - hash_index_remove( "data/statuses/uri", uri ); - } - - return s; - */ } void status_add_reply( struct status* s, struct status* child ) { diff --git a/src/model/status/poll.c b/src/model/status/poll.c index 40de1eb..68519b7 100644 --- a/src/model/status/poll.c +++ b/src/model/status/poll.c @@ -84,6 +84,7 @@ bool status_poll_add_vote( struct status* s, struct account* a, void* choices_pt } // Update model + printf( "There are %d choices in this vote\n", choices->count ); for( int i = 0; i < choices->count; ++i ) { int choice = choices->items[i]; struct status_poll_option* option = s->poll->options.items[choice]; @@ -93,6 +94,7 @@ bool status_poll_add_vote( struct status* s, struct account* a, void* choices_pt if( id != poll_vote_unknown_id ) { for( int j = 0; j < option->votes.count; ++j ) { if( option->votes.items[j] == id ) { + printf( "Account %d has laready voted this option\n", id ); should_add = false; break; } @@ -113,7 +115,7 @@ bool status_poll_add_vote( struct status* s, struct account* a, void* choices_pt // https://www.w3.org/TR/activitystreams-vocabulary/#questions // See Example 153 - // TODO: Create federation object + printf( "TODO: Create federation object\n" ); //struct ap_object* obj; } @@ -121,6 +123,34 @@ bool status_poll_add_vote( struct status* s, struct account* a, void* choices_pt return true; } +bool status_poll_add_vote_by_name( struct status* s, struct account* a, const char* name ) +{ + if( !s ) { return false; } + if( !s->poll ) { return false; } + if( !a ) { return false; } + if( !name ) { return false; } + + int option_index = -1; + struct { + int* items; + int count; + } choices = { + .items = &option_index, + .count = 1, + }; + + for( int i = 0; i < s->poll->options.count; ++i ) { + struct status_poll_option* option = s->poll->options.items[i]; + + if( 0 == strcmp( option->title, name ) ) { + option_index = i; + goto have_option_index; + } + } + return false; +have_option_index: + return status_poll_add_vote( s, a, &choices ); +} void status_poll_update_vote_count( struct status_poll* poll ) { diff --git a/src/model/status/poll.h b/src/model/status/poll.h index fcffcb6..d22578e 100644 --- a/src/model/status/poll.h +++ b/src/model/status/poll.h @@ -47,5 +47,6 @@ void status_poll_option_free( struct status_poll_option* o ); void status_poll_free( struct status_poll* p ); bool status_poll_has_option( struct status_poll* p, int idx ); bool status_poll_add_vote( struct status* s, struct account* a, void* choices ); +bool status_poll_add_vote_by_name( struct status* s, struct account* a, const char* name ); void status_poll_update_vote_count( struct status_poll* poll );