Implement API endpoint for voting in polls and have local model updated to match

master
teknomunk 8 months ago
parent 91ad748f89
commit 91d6eeb530

@ -10,6 +10,7 @@
// Model
#include "model/server.h"
#include "model/status.h"
#include "model/status/poll.h"
#include "model/status/react.h"
#include "model/account.h"
#include "model/media.h"
@ -18,6 +19,7 @@
// View
#include "view/api/Account.h"
#include "view/api/Status.h"
#include "view/api/Poll.h"
// Controller
#include "controller/api/client_apps.h"
@ -381,3 +383,122 @@ failed:
result = false;
goto cleanup;
}
// Route: /api/v1/polls/%d{id}/votes
bool handle_votes( struct http_request* req, struct status* s )
{
bool result = false;
struct account* owner = account_from_id( owner_account_id );
// Enforce preconditions
if( !s ) { goto failed; }
if( !owner ) { goto failed; }
if( !s->poll ) { goto failed; }
printf( "Processing poll votes...\n" );
struct params_t
{
struct {
char** items;
int count;
} choices;
} params;
memset(&params,0,sizeof(params));
#define OBJ_TYPE struct params_t
static struct json_object_field layout[] = {
JSON_FIELD_ARRAY_OF_STRINGS( choices, true ),
JSON_FIELD_END,
};
#undef OBJ_TYPE
FILE* data = http_request_get_request_data( req );
if( !json_read_object_layout_from_FILE( data, layout, &params ) ) {
printf( "Unable to read params\n" );
goto failed;
}
struct {
int* items;
int count;
} choices_ints;
memset(&choices_ints,0,sizeof(choices_ints));
// Make sure all options selected exist before voting
for( int i = 0; i < params.choices.count; ++i ) {
int idx = atoi(params.choices.items[i]);
array_append( &choices_ints, sizeof(idx), &idx );
}
if( status_poll_add_vote( s, owner, &choices_ints ) ) {
status_save(s);
http_request_send_headers( req, 200, "application/json", true );
FILE* f = http_request_get_response_body( req );
api_Poll_write( s->poll, f, 0 );
goto success;
}
printf( "Unable to add votes\n" );
goto failed;
cleanup:
for( int i = 0; i < params.choices.count; ++i ) {
free( params.choices.items[i] );
}
free(params.choices.items );
return result;
success:
result = true;
goto cleanup;
failed:
result = false;
goto cleanup;
}
// Route: /api/v1/polls/%d{id}/
bool route_polls( struct http_request* req )
{
bool result = false;
struct status* s = NULL;
if( http_request_route_term( req, "" ) ) {
if( http_request_route_method( req, "POST" ) ) {
if( !check_authentication_header(req) ) { return false; }
struct account* owner = account_from_id(owner_account_id);
bool res = handle_post(req, owner);
account_free(owner);
return res;
}
return false;
}
if( !http_request_route( req, "/" ) ) { return false; }
int id = -1;
if( !http_request_route_id( req, &id ) ) { goto failed; }
s = status_from_id(id);
if( !s ) { goto failed; }
// Make sure the status has a poll
if( !s->poll ) { goto failed; }
if( http_request_route_term( req, "votes" ) ) {
if( handle_votes( req, s ) ) { goto success; }
}
goto failed;
cleanup:
status_free(s);
return result;
success:
result = true;
goto cleanup;
failed:
result = false;
goto cleanup;
}

@ -16,4 +16,5 @@ bool handle_repost( struct http_request* req, struct status* s );
bool handle_show_bookmarks( struct http_request* req );
bool route_statuses( struct http_request* req );
bool route_polls( struct http_request* req );

@ -373,6 +373,8 @@ bool route_mastodon_api( struct http_request* req )
return route_custom_emojis(req);
} else if( http_request_route( req, "statuses" ) ) {
return route_statuses(req);
} else if( http_request_route( req, "polls" ) ) {
return route_polls(req);
}
if( !check_authentication_header(req) ) {

@ -146,8 +146,9 @@ struct status* status_from_id( unsigned int id )
return NULL;
}
// Force the poll id to be the same as the status id
if( s->poll ) {
printf( "Has poll data\n" );
s->poll->id = s->id;
}
// Convert media to media2
@ -372,10 +373,7 @@ static void sync_poll( struct status* s, void* poll_data_ptr )
}
// Rebuild votes_count
s->poll->votes_count = 0;
for( int i = 0; i < s->poll->options.count; ++i ) {
s->poll->votes_count += s->poll->options.items[i]->votes.count;
}
status_poll_update_vote_count( s->poll );
}
// TODO: Move to src/model/status/ap_sync.c
@ -464,7 +462,7 @@ bool status_sync_from_activity_pub( struct status* s, struct ap_object* act )
poll = malloc(sizeof(*poll));
memset(poll,0,sizeof(*poll));
poll->id = 1;
poll->id = s->id;
poll->multiple_choice = false;
s->poll = poll;
@ -477,7 +475,7 @@ bool status_sync_from_activity_pub( struct status* s, struct ap_object* act )
poll = malloc(sizeof(*poll));
memset(poll,0,sizeof(*poll));
poll->id = 1;
poll->id = s->id;
poll->multiple_choice = true;
s->poll = poll;

@ -1,6 +1,10 @@
#include "poll.h"
#include "model/account.h"
#include "model/emoji.h"
#include "model/status.h"
#include "collections/array.h"
#include <stdlib.h>
#include <stddef.h>
@ -18,6 +22,8 @@ JSON_FIELD_TYPE_OBJECT_LAYOUT_WITH_DEFAULTS( status_poll_option );
#define OBJ_TYPE struct status_poll
struct json_object_field status_poll_layout[] = {
JSON_FIELD_INTEGER( id, false ),
JSON_FIELD_INTEGER( votes_count, false ),
JSON_FIELD_ARRAY_OF_INTS( own_votes, false ),
JSON_FIELD_BOOL( multiple_choice, false ),
JSON_FIELD_DATETIME( expires_at, false ),
JSON_FIELD_BOOL( voted, false ),
@ -48,6 +54,79 @@ void status_poll_free( struct status_poll* p )
}
free( p->emoji.items );
free(p->own_votes.items);
free(p);
}
bool status_poll_has_option( struct status_poll* p, int idx )
{
if( idx < 0 ) { return false; }
return idx < p->options.count;
}
bool status_poll_add_vote( struct status* s, struct account* a, void* choices_ptr )
{
// Verify preconditions
if( !s ) { return false; }
if( !a ) { return false; }
if( !choices_ptr ) { return false; }
struct {
int* items;
int count;
} *choices = choices_ptr;
// Validate vote choices
for( int i = 0; i < choices->count; ++i ) {
if( !status_poll_has_option( s->poll, choices->items[i] ) ) {
printf( "Poll doesn't have option %d\n", choices->items[i] );
return false;
}
}
// Update model
for( int i = 0; i < choices->count; ++i ) {
int choice = choices->items[i];
struct status_poll_option* option = s->poll->options.items[choice];
int id = a->id;
bool should_add = true;
if( id != poll_vote_unknown_id ) {
for( int j = 0; j < option->votes.count; ++j ) {
if( option->votes.items[j] == id ) {
should_add = false;
break;
}
}
}
if( should_add ) {
array_append( &option->votes, sizeof(id), &id );
if( a->id == owner_account_id ) {
array_append( &s->poll->own_votes, sizeof(choice), &choice );
}
}
}
// Federate response if account is owner
if( a->id == owner_account_id ) {
s->poll->voted = true;
// https://www.w3.org/TR/activitystreams-vocabulary/#questions
// See Example 153
// TODO: Create federation object
//struct ap_object* obj;
}
status_poll_update_vote_count( s->poll );
return true;
}
void status_poll_update_vote_count( struct status_poll* poll )
{
poll->votes_count = 0;
for( int i = 0; i < poll->options.count; ++i ) {
poll->votes_count += poll->options.items[i]->votes.count;
}
}

@ -5,6 +5,8 @@
#include <time.h>
struct emoji;
struct account;
struct status;
struct status_poll_option
{
@ -23,6 +25,12 @@ struct status_poll
bool multiple_choice;
int votes_count;
bool voted;
struct {
int* items;
int count;
} own_votes;
struct {
struct status_poll_option** items;
int count;
@ -37,4 +45,7 @@ struct status_poll
extern struct json_field_type status_poll_type;
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 );
void status_poll_update_vote_count( struct status_poll* poll );

@ -2,6 +2,7 @@
#include "model/status/poll.h"
#include "json/json.h"
#include "json/layout.h"
extern struct json_field_type api_Emoji_type;
@ -45,6 +46,7 @@ struct json_object_field api_Poll_layout[] = {
.required = true,
.type = &json_field_bool,
},
JSON_FIELD_ARRAY_OF_INTS( own_votes, true ),
JSON_FIELD_INTEGER( votes_count, true ),
JSON_FIELD_FIXED_NULL( voters_count ),
JSON_FIELD_BOOL( voted, true ),
@ -73,3 +75,13 @@ struct json_field_type api_Poll_type = {
.type_string = "Poll",
};
void api_Poll_write( struct status_poll* poll, FILE* f, int indent )
{
struct json_writer jw = {
.f = f,
.indentation = "\t",
.indent = indent,
};
json_write_pretty_object_layout( &jw, api_Poll_layout, poll );
}

@ -1,6 +1,10 @@
#pragma once
#include <stdio.h>
struct status_poll;
struct json_field_type;
extern struct json_field_type api_Poll_type;
void api_Poll_write( struct status_poll* poll, FILE* f, int indent );

Loading…
Cancel
Save