Implement featured/pinned posts

master
teknomunk 10 months ago
parent 7382a916e8
commit d158fed722

@ -281,6 +281,32 @@ bool handle_favorite( struct http_request* req, struct status* s )
return true;
}
// route: GET /api/v1/statuses/%d{id}/pin
bool handle_pin( struct http_request* req, struct status* s )
{
struct account* owner = account_from_id( owner_account_id );
account_pin_status( owner, s );
status_save(s);
show_status( req, s );
account_free(owner);
return true;
}
// route: GET /api/v1/statuses/%d{id}/unpin
bool handle_unpin( struct http_request* req, struct status* s )
{
struct account* owner = account_from_id( owner_account_id );
account_unpin_status( owner, s );
status_save(s);
show_status( req, s );
account_free(owner);
return true;
}
bool http_request_route_id( struct http_request* req, int* id )
{
char* id_str = http_request_route_get_dir_or_file(req);
@ -338,6 +364,10 @@ bool route_statuses( struct http_request* req )
if( handle_unbookmark( req, s ) ) { goto success; }
} else if( http_request_route_term( req, "favourite" ) ) {
if( handle_favorite( req, s ) ) { goto success; }
} else if( http_request_route_term( req, "pin" ) ) {
if( handle_pin( req, s ) ) { goto success; }
} else if( http_request_route_term( req, "unpin" ) ) {
if( handle_unpin( req, s ) ) { goto success; }
}
goto failed;

@ -7,6 +7,7 @@
// Model
#include "model/timeline.h"
#include "model/status.h"
#include "model/account.h"
// Controller
#include "controller/api/status.h"
@ -15,6 +16,8 @@
static bool handle_timeline_internal( struct http_request* req, struct timeline* tl )
{
struct timeline* pinned_timeline = NULL;
if( !tl ) {
http_request_send_headers( req, 200, "application/json", true );
FILE* f = http_request_get_response_body( req );
@ -66,6 +69,14 @@ static bool handle_timeline_internal( struct http_request* req, struct timeline*
.count = 0
};
if( params.pinned ) {
char pinned_timeline_path[512];
snprintf( pinned_timeline_path,512, "%s/pinned", tl->path );
tl = pinned_timeline = timeline_from_path( pinned_timeline_path );
}
printf( "timeline path = %s\n", tl->path );
int offset = 0;
int count = 0;
int max_loops = 5;
@ -103,6 +114,7 @@ load_statuses:
done:
show_statuses( req, show.items, show.count );
timeline_free( pinned_timeline );
for( int i = 0; i < show.count; ++i ) {
status_free( show.items[i] );

@ -13,26 +13,17 @@
#include <stdio.h>
#include <string.h>
static bool handle_featured( struct http_request* req )
struct pager
{
struct account* owner_account = account_from_id(0);
http_request_add_header( req, "Access-Control-Allow-Origin", "*" );
http_request_send_headers( req, 200, "application/activity+json", true );
FILE* f = http_request_get_response_body(req);
#include "src/view/owner/featured.json.inc"
void* data;
struct ap_object* (*get_root)( void* );
struct ap_object* (*get_page)( void*, int );
};
account_free( owner_account );
return true;
}
static bool handle_followers( struct http_request* req )
static bool handle_paged_collection( struct http_request* req, struct pager* pg )
{
bool result = false;
struct account* owner_account = account_from_id(0);
if( http_request_route_term(req,"") ) {
struct ap_object* obj = account_ap_followers(owner_account);
struct ap_object* obj = pg->get_root( pg->data );
http_request_add_header( req, "Access-Control-Allow-Origin", "*" );
http_request_send_headers( req, 200, "application/activity+json", true );
@ -43,12 +34,12 @@ static bool handle_followers( struct http_request* req )
printf( "/page-\n" );
char* page_str = http_request_route_get_dir_or_file(req);
int page = -1;
if( !page_str ) { goto failed; }
if( 1 != sscanf(page_str,"%d",&page) ) { goto failed; }
if( page < 0 ) { goto failed; }
if( !page_str ) { return false; }
if( 1 != sscanf(page_str,"%d",&page) ) { return false; }
if( page < 0 ) { return false; }
struct ap_object* obj = account_ap_followers_page(owner_account,page);
if( !obj ) { goto failed; }
struct ap_object* obj = pg->get_page( pg->data, page );
if( !obj ) { return false; }
http_request_add_header( req, "Access-Control-Allow-Origin", "*" );
http_request_send_headers( req, 200, "application/activity+json", true );
@ -56,63 +47,49 @@ static bool handle_followers( struct http_request* req )
ap_object_write_to_FILE( obj, f );
ap_object_free(obj);
}
goto success;
return true;
}
cleanup:
account_free( owner_account );
static bool handle_featured( struct http_request* req )
{
struct account* owner_account = account_from_id(0);
struct pager pg = {
.data = owner_account,
.get_root = (void*)account_ap_featured,
.get_page = (void*)account_ap_featured_page,
};
bool result = handle_paged_collection(req,&pg);
account_free( owner_account );
return result;
success:
result = true;
goto cleanup;
failed:
printf( "! failed\n" );
result = false;
goto cleanup;
}
static bool handle_following( struct http_request* req )
static bool handle_followers( struct http_request* req )
{
printf( "handle_following\n" );
bool result = false;
struct account* owner_account = account_from_id(0);
if( http_request_route_term(req,"") ) {
struct ap_object* obj = account_ap_following(owner_account);
struct pager pg = {
.data = owner_account,
.get_root = (void*)account_ap_followers,
.get_page = (void*)account_ap_followers_page,
};
http_request_add_header( req, "Access-Control-Allow-Origin", "*" );
http_request_send_headers( req, 200, "application/activity+json", true );
FILE* f = http_request_get_response_body(req);
ap_object_write_to_FILE( obj, f );
ap_object_free(obj);
} else if( http_request_route( req, "/page-" ) ) {
printf( "/page-\n" );
char* page_str = http_request_route_get_dir_or_file(req);
int page = -1;
if( !page_str ) { goto failed; }
if( 1 != sscanf(page_str,"%d",&page) ) { goto failed; }
if( page < 0 ) { goto failed; }
struct ap_object* obj = account_ap_following_page(owner_account,page);
if( !obj ) { goto failed; }
bool result = handle_paged_collection(req,&pg);
account_free( owner_account );
return result;
}
static bool handle_following( struct http_request* req )
{
struct account* owner_account = account_from_id(0);
http_request_add_header( req, "Access-Control-Allow-Origin", "*" );
http_request_send_headers( req, 200, "application/activity+json", true );
FILE* f = http_request_get_response_body(req);
ap_object_write_to_FILE( obj, f );
ap_object_free(obj);
}
struct pager pg = {
.data = owner_account,
.get_root = (void*)account_ap_following,
.get_page = (void*)account_ap_following_page,
};
goto success;
success:
result = true;
goto cleanup;
failed:
printf( "! failed\n" );
result = false;
goto cleanup;
cleanup:
bool result = handle_paged_collection(req,&pg);
account_free( owner_account );
return result;
}

@ -18,6 +18,8 @@
#include "model/notification.h"
#include "model/fetch.h"
#include "model/webfinger.h"
#include "model/timeline.h"
#include "model/activity.h"
// View
#include "view/api/Relationship.h"
@ -321,6 +323,7 @@ static void create_account_skeleton( int account_id )
// Make sure the account directory exists
mkdir( format( b, 512, "data/accounts/%d", account_id ), 0755 );
mkdir( format( b, 512, "data/accounts/%d/timeline", account_id ), 0755 );
mkdir( format( b, 512, "data/accounts/%d/timeline/pinned", account_id ), 0755 );
fs_list_set( format( b, 512, "data/accounts/%d/timeline/HEAD", account_id ), 0 );
}
@ -607,4 +610,58 @@ bool account_does_follow( struct account* a, int account_id )
return result;
}
void account_pin_status( struct account* a, struct status* s )
{
char buffer[512];
snprintf( buffer,sizeof(buffer), "data/accounts/%d/timeline/pinned", a->id );
s->pinned = true;
struct timeline* pinned = timeline_from_path( buffer );
timeline_add_post( pinned, s );
timeline_free(pinned);
// TODO: federate an Add(Note) activity
struct ap_object* act = ap_object_new();
activity_allocate_local_id(act);
act->type = ap_Add;
act->object.tag = apaot_ref;
act->object.ref = strdup( s->url );
act->actor = strdup( a->account_url );
act->target = aformat( "https://%s/owner/collections/featured", g_server->domain );
char* str;
str = aformat("https://%s/owner/followers", g_server->domain );
array_append( &act->to, sizeof(str), &str );
ap_object_write_to_FILE( act, stdout );
activity_deliver( act );
ap_object_free(act);
}
void account_unpin_status( struct account* a, struct status* s )
{
char buffer[512];
snprintf( buffer,sizeof(buffer), "data/accounts/%d/timeline/pinned", a->id );
s->pinned = false;
struct timeline* pinned = timeline_from_path( buffer );
timeline_remove_post( pinned, s );
timeline_free(pinned);
// TODO: federate an Add(Note) activity
struct ap_object* act = ap_object_new();
activity_allocate_local_id(act);
act->type = ap_Remove;
act->object.tag = apaot_ref;
act->object.ref = strdup( s->url );
act->actor = strdup( a->account_url );
act->target = aformat( "https://%s/owner/collections/featured", g_server->domain );
char* str;
str = aformat("https://%s/owner/followers", g_server->domain );
array_append( &act->to, sizeof(str), &str );
ap_object_write_to_FILE( act, stdout );
activity_deliver( act );
ap_object_free(act);
}

@ -126,6 +126,8 @@ struct ap_object* account_ap_followers( struct account* a );
struct ap_object* account_ap_followers_page( struct account* a, int page );
struct ap_object* account_ap_following( struct account* a );
struct ap_object* account_ap_following_page( struct account* a, int page );
struct ap_object* account_ap_featured( struct account* a );
struct ap_object* account_ap_featured_page( struct account* a, int page );
// Local actions
void account_add_follower( struct account* a, struct account* follower );
@ -141,4 +143,6 @@ struct status* account_announce( struct account* a, struct status* original_post
void account_follow( struct account* a, struct account* to_follow );
void account_unfollow( struct account* a, struct account* to_unfollow );
void account_update( struct account* a );
void account_pin_status( struct account* a, struct status* s );
void account_unpin_status( struct account* a, struct status* s );

@ -10,6 +10,7 @@
#include "model/server.h"
#include "model/status.h"
#include "model/activity.h"
#include "model/timeline.h"
// Standard Library
#include <stdlib.h>
@ -192,6 +193,17 @@ static struct ap_object* account_list_page( int page, char* part_of, const char*
if( page >= page_count ) { return NULL; }
struct ap_object* o = activity_new_local_activity();
o->type = ap_OrderedCollectionPage;
o->published = time(NULL);
o->part_of = part_of;
o->id = aformat( page_format, page );
if( page > 0 ) {
o->prev = aformat( page_format, page - 1 );
}
if( page < page_count - 1 ) {
o->next.tag = apaot_ref;
o->next.ref = aformat( page_format, page + 1 );
}
struct {
char** items;
@ -216,18 +228,6 @@ static struct ap_object* account_list_page( int page, char* part_of, const char*
}
free( values.items );
o->type = ap_OrderedCollectionPage;
o->published = time(NULL);
o->part_of = part_of;
o->id = aformat( page_format, page );
if( page > 0 ) {
o->prev = aformat( page_format, page - 1 );
}
if( page < page_count - 1 ) {
o->next.tag = apaot_ref;
o->next.ref = aformat( page_format, page + 1 );
}
return o;
}
@ -293,3 +293,54 @@ struct ap_object* account_ap_following_page( struct account* a, int page )
return account_list_page( page, part_of, page_format, trie_filename );
}
struct ap_object* account_ap_featured( struct account* a )
{
struct ap_object* o = activity_new_local_activity();
o->type = ap_OrderedCollection;
o->published = time(NULL);
o->part_of = aformat( "https://%s/owner/collections/featured", g_server->domain );
o->id = strdup(o->part_of);
o->first.tag = apaot_object;
o->first.ptr = account_ap_featured_page( a, 0 );
char buffer[512];
o->total_items = ffdb_trie_count( format( buffer,512, "data/accounts/%d/timeline/pinned", a->id ) );
return o;
}
struct ap_object* account_ap_featured_page( struct account* a, int page )
{
char buffer[512];
snprintf( buffer,512, "data/accounts/%d/timeline/pinned", a->id );
struct timeline* tl = timeline_from_path( buffer );
if( !tl ) { return NULL; }
enum { items_per_page = 20 };
struct status* statuses[items_per_page];
int count = timeline_load_statuses( tl, page * items_per_page, items_per_page, statuses );
if( count == 0 ) {
timeline_free(tl);
return NULL;
}
struct ap_object* o = activity_new_local_activity();
o->type = ap_OrderedCollectionPage;
o->part_of = aformat( "https://%s/owner/collections/featured", g_server->domain );
o->id = aformat( "%s/page-%d", o->part_of, page);
o->next.tag = apaot_ref;
o->next.ref = aformat( "%s/page-%d", o->part_of, page+1 );
if( page > 0 ) {
o->prev = aformat( "%s/page-%d", o->part_of, page-11 );
}
for( int i = 0; i < count; ++i ) {
struct ap_object_ptr_or_ref r;
r.tag = apaot_ref;
r.ref = strdup(statuses[i]->url);
array_append( &o->collection_items, sizeof(r), &r );
status_free(statuses[i]);
}
timeline_free(tl);
return o;
}

@ -506,7 +506,12 @@ struct json_object_field api_Status_layout[] = {
.array_item_type = &Mention_type,
},
JSON_FIELD_FIXED_BOOL( muted, false ),
JSON_FIELD_FIXED_BOOL( pinned, false ),
{
.key = "pinned",
.offset = offsetof( OBJ_TYPE, pinned ),
.type = &json_field_bool,
.allow_drop_empty = true,
},
{
.key = "pleroma",
.offset = 0,

Loading…
Cancel
Save