Implement poll creation, poll post federation and handle votes

master
teknomunk 8 months ago
parent 91d6eeb530
commit 5616f70088

@ -1 +1 @@
Subproject commit 4257ba45412070f3d6a7ac2bd42b4f488b4c5761
Subproject commit 904a471760e1990a6505034764154e0eb6c3817d

@ -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(&params,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(&params,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, &params ) ) { 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;

@ -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 ) {

@ -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;
}

@ -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 )
{

@ -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 )
{

@ -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 );

Loading…
Cancel
Save