Skip to content

Commit

Permalink
log announcement impressions #466
Browse files Browse the repository at this point in the history
  • Loading branch information
pleary committed Oct 25, 2024
1 parent 80dd33b commit 54aaaff
Show file tree
Hide file tree
Showing 6 changed files with 492 additions and 7 deletions.
3 changes: 3 additions & 0 deletions lib/controllers/v1/announcements_controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ const { announcements } = require( "inaturalistjs" );
const InaturalistAPI = require( "../../inaturalist_api" );
const pgClient = require( "../../pg_client" );
const Announcement = require( "../../models/announcement" );
const AnnouncementImpression = require( "../../models/announcement_impression" );
const Site = require( "../../models/site" );
const util = require( "../../util" );

Expand Down Expand Up @@ -148,6 +149,8 @@ const AnnouncementsController = class AnnouncementsController {
"exclude_donor_end_date"
] )
) );

await AnnouncementImpression.createAnnouncementImpressions( req, announcementRows );
return {
total_results: announcementRows.length,
page: 1,
Expand Down
38 changes: 35 additions & 3 deletions lib/logstasher.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,20 @@ const Logstasher = class Logstasher {
return clientip || req.connection.remoteAddress;
}

static ipFromRequest( req ) {
if ( _.isEmpty( req ) || _.isEmpty( req.headers ) ) {
return null;
}
let ip;
_.each( req.headers, ( v, k ) => {
if ( ip || !_.includes( Logstasher.headersWithIPs, k.toLowerCase( ) ) ) {
return;
}
ip = Logstasher.originalIPInList( v );
} );
return ip || req.connection.remoteAddress;
}

static languages( payload ) {
if ( !payload.accept_language ) { return null; }
// there may be multiple variations of languages, plus other junk
Expand Down Expand Up @@ -89,10 +103,14 @@ const Logstasher = class Logstasher {
return _.cloneDeep( payload );
}

static afterRequestPayload( req, res, duration ) {
static afterRequestPayload( req, res = null, duration = null ) {
const payload = { };
payload.status_code = res.statusCode;
payload.duration = duration;
if ( res ) {
payload.status_code = res.statusCode;
}
if ( duration ) {
payload.duration = duration;
}
payload.params = req.params;
payload.route = req.route ? req.route.path : null;
payload.logged_in = !!req.userSession;
Expand Down Expand Up @@ -143,6 +161,20 @@ const Logstasher = class Logstasher {
Logstasher.logWriteStream( ).write( `${JSON.stringify( errorPayload )}\n` );
}
}

static writeAnnouncementImpressionLog( req, announcementID ) {
if ( !req || !announcementID ) {
return;
}
if ( Logstasher.logWriteStream( ) ) {
const beforePayload = Logstasher.beforeRequestPayload( req );
const afterPayload = Logstasher.afterRequestPayload( req );
const payload = Object.assign( beforePayload, afterPayload );
payload.subtype = "AnnouncementImpression";
payload.model_id = announcementID;
Logstasher.logWriteStream( ).write( `${JSON.stringify( payload )}\n` );
}
}
};

// using hyphens here, as express req.headers user hyphens
Expand Down
93 changes: 93 additions & 0 deletions lib/models/announcement_impression.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
const _ = require( "lodash" );
const squel = require( "safe-squel" );
const Model = require( "./model" );
const pgClient = require( "../pg_client" );
const Logstasher = require( "../logstasher" );

const AnnouncementImpression = class AnnouncementImpression extends Model {
static async createAnnouncementImpressions( req, announcements ) {
if ( _.isEmpty( announcements ) ) {
return;
}

await Promise.all( announcements.map( async announcement => {
Logstasher.writeAnnouncementImpressionLog( req, announcement.id );
await AnnouncementImpression.createDatabaseAnnouncementImpression( req, announcement );
} ) );
}

static async createDatabaseAnnouncementImpression( req, announcement ) {
if ( _.isEmpty( announcement ) ) {
return;
}

const requestIP = Logstasher.ipFromRequest( req );
let queryClauses = squel.expr( )
.and( "announcement_id = ?", announcement.id )
.and( "platform_type = ?", "mobile" );
if ( req.userSession ) {
queryClauses = queryClauses
.and( "user_id = ?", req.userSession.user_id );
const existingImpression = await AnnouncementImpression
.fetchFirstAnnouncemenImpressiontMatchingQuery( queryClauses );
if ( existingImpression ) {
const updateQuery = squel.update( )
.table( "announcement_impressions" )
.set( "impressions_count", existingImpression.impressions_count + 1 )
.set( "request_ip", requestIP )
.set( "updated_at", squel.str( "NOW()" ) )
.where( "id = ?", existingImpression.id );
await pgClient.query( updateQuery.toString( ) );
return;
}
const insertQuery = squel.insert()
.into( "announcement_impressions" )
.set( "announcement_id", announcement.id )
.set( "platform_type", "mobile" )
.set( "user_id", req.userSession.user_id )
.set( "request_ip", requestIP )
.set( "impressions_count", 1 )
.set( "created_at", squel.str( "NOW()" ) )
.set( "updated_at", squel.str( "NOW()" ) );
await pgClient.query( insertQuery.toString( ) );
return;
}

queryClauses = queryClauses
.and( "request_ip = ?", requestIP );
const existingImpression = await AnnouncementImpression
.fetchFirstAnnouncemenImpressiontMatchingQuery( queryClauses );
if ( existingImpression ) {
const updateQuery = squel.update( )
.table( "announcement_impressions" )
.set( "impressions_count", existingImpression.impressions_count + 1 )
.set( "updated_at", squel.str( "NOW()" ) )
.where( "id = ?", existingImpression.id );
await pgClient.query( updateQuery.toString( ) );
return;
}
const insertQuery = squel.insert()
.into( "announcement_impressions" )
.set( "announcement_id", announcement.id )
.set( "platform_type", "mobile" )
.set( "request_ip", requestIP )
.set( "impressions_count", 1 )
.set( "created_at", squel.str( "NOW()" ) )
.set( "updated_at", squel.str( "NOW()" ) );
await pgClient.query( insertQuery.toString( ) );
}

static async fetchFirstAnnouncemenImpressiontMatchingQuery( queryClauses ) {
const existingImpressionQuery = squel.select( )
.field( "*" )
.from( "announcement_impressions" )
.where( queryClauses );
const { rows } = await pgClient.query( existingImpressionQuery.toString( ) );
if ( _.isEmpty( rows ) ) {
return null;
}
return rows[0];
}
};

module.exports = AnnouncementImpression;
Loading

0 comments on commit 54aaaff

Please sign in to comment.