Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 37 additions & 0 deletions db_schema/migrations/15_anchor_impressions.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
-- Create table for storing aggregate impression metrics
CREATE TABLE IF NOT EXISTS anchorImpressions (
account_id INTEGER NOT NULL,
date_start DATE NOT NULL,
date_end DATE NOT NULL,
total_impressions INTEGER NOT NULL,
total_considerations INTEGER NOT NULL,
total_streams INTEGER NOT NULL,
considerations_conversion_rate DECIMAL(10,5),
streams_conversion_rate DECIMAL(10,5),
PRIMARY KEY (account_id, date_start, date_end)
);

-- Create table for daily impression data
CREATE TABLE IF NOT EXISTS anchorImpressionsDaily (
account_id INTEGER NOT NULL,
date DATE NOT NULL,
impressions INTEGER NOT NULL,
PRIMARY KEY (account_id, date)
);

-- Create table for impression sources breakdown
CREATE TABLE IF NOT EXISTS anchorImpressionsSources (
account_id INTEGER NOT NULL,
date_start DATE NOT NULL,
date_end DATE NOT NULL,
source_id VARCHAR(32) NOT NULL,
source_name VARCHAR(64) NOT NULL,
impression_count INTEGER NOT NULL,
PRIMARY KEY (account_id, date_start, date_end, source_id)
);

-- Add index for querying date ranges efficiently on sources table
CREATE INDEX idx_impression_sources_date ON anchorImpressionsSources(account_id, date_start, date_end);

-- Record the migration
INSERT INTO migrations (migration_id, migration_name) VALUES (15, 'anchor impressions');
2 changes: 1 addition & 1 deletion db_schema/schema.sql
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ CREATE TABLE IF NOT EXISTS migrations (
-- -----------------------------------------
-- IMPORTANT: this is the schema version
-- ID has to be incremented for each change
INSERT INTO migrations (migration_id, migration_name) VALUES (14, 'genericHoster');
INSERT INTO migrations (migration_id, migration_name) VALUES (15, 'anchor impressions');
-- -----------------------------------------

CREATE TABLE IF NOT EXISTS events (
Expand Down
202 changes: 202 additions & 0 deletions fixtures/anchorImpressions.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,202 @@
{
"provider": "anchor",
"version": 1,
"retrieved": "2023-05-05T16:20:56.696026",
"meta": {
"show": "123abcde",
"endpoint": "impressions"
},
"range": {
"start": "2023-05-01",
"end": "2023-05-04"
},
"data": {
"impressionsFunnel": {
"data": {
"value": {
"counts": [
{
"id": "impressions",
"count": 4571
},
{
"id": "considerations",
"count": 731,
"conversionPercent": 0.15992
},
{
"id": "streams",
"count": 381,
"conversionPercent": 0.5212
}
]
},
"isDistributedToSpotify": true
},
"error": null
},
"impressions": {
"data": {
"value": 30868,
"isDistributedToSpotify": true
},
"error": null
},
"dailyImpressions": {
"data": {
"value": [
{
"date": 1732233600,
"value": 1145
},
{
"date": 1732320000,
"value": 860
},
{
"date": 1732406400,
"value": 846
},
{
"date": 1732492800,
"value": 952
},
{
"date": 1732579200,
"value": 1054
},
{
"date": 1732665600,
"value": 1079
},
{
"date": 1732752000,
"value": 915
},
{
"date": 1732838400,
"value": 1047
},
{
"date": 1732924800,
"value": 974
},
{
"date": 1733011200,
"value": 1050
},
{
"date": 1733097600,
"value": 849
},
{
"date": 1733184000,
"value": 873
},
{
"date": 1733270400,
"value": 1449
},
{
"date": 1733356800,
"value": 983
},
{
"date": 1733443200,
"value": 1036
},
{
"date": 1733529600,
"value": 857
},
{
"date": 1733616000,
"value": 955
},
{
"date": 1733702400,
"value": 1115
},
{
"date": 1733788800,
"value": 1261
},
{
"date": 1733875200,
"value": 1100
},
{
"date": 1733961600,
"value": 979
},
{
"date": 1734048000,
"value": 1381
},
{
"date": 1734134400,
"value": 882
},
{
"date": 1734220800,
"value": 1335
},
{
"date": 1734307200,
"value": 956
},
{
"date": 1734393600,
"value": 1146
},
{
"date": 1734480000,
"value": 967
},
{
"date": 1734566400,
"value": 932
},
{
"date": 1734652800,
"value": 1000
},
{
"date": 1734739200,
"value": 890
}
],
"isDistributedToSpotify": true
},
"error": null
},
"impressionsBySource": {
"data": {
"value": [
{
"id": "home",
"value": 9082,
"displayName": "Spotify Home"
},
{
"id": "search",
"value": 20849,
"displayName": "Spotify Search"
},
{
"id": "library",
"value": 936,
"displayName": "Spotify Library"
},
{
"id": "other",
"value": 1,
"displayName": "Other Spotify features"
}
],
"isDistributedToSpotify": true
},
"error": null
}
}
}
16 changes: 16 additions & 0 deletions src/api/connectors/AnchorConnector.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import podcastEpisodeSchema from '../../schema/anchor/podcastEpisode.json'
import totalPlaysSchema from '../../schema/anchor/totalPlays.json'
import totalPlaysByEpisodeSchema from '../../schema/anchor/totalPlaysByEpisode.json'
import uniqueListenersSchema from '../../schema/anchor/uniqueListeners.json'
import impressionsSchema from '../../schema/anchor/impressions.json'

import { ConnectorPayload } from '../../types/connector'
import {
Expand All @@ -37,6 +38,7 @@ import {
RawAnchorTotalPlaysByEpisodeData,
RawAnchorUniqueListenersData,
RawAnchorEpisodesPageData,
RawAnchorImpressionData,
} from '../../types/provider/anchor'
import { AnchorRepository } from '../../db/AnchorRepository'
import { isArray } from 'mathjs'
Expand Down Expand Up @@ -254,6 +256,20 @@ class AnchorConnector implements ConnectorHandler {
accountId,
payload.data.data as RawAnchorUniqueListenersData
)
} else if (endpoint == 'impressions') {
validateJsonApiPayload(impressionsSchema, rawPayload)
if (!rawPayload.range) {
throw new PayloadError('Range is required for impressions endpoint')
}
if (!isDataPayload(payload.data)) {
throw new PayloadError('Incorrect payload data type')
}
await this.repo.storeImpressions(
accountId,
rawPayload.range.start,
rawPayload.range.end,
payload.data.data as RawAnchorImpressionData
)
} else {
throw new PayloadError(
`Unknown endpoint in meta: ${rawPayload.meta.endpoint}`
Expand Down
87 changes: 87 additions & 0 deletions src/db/AnchorRepository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {
RawAnchorTotalPlaysByEpisodeData,
RawAnchorUniqueListenersData,
RawAnchorEpisodesPageData,
RawAnchorImpressionData,
} from '../types/provider/anchor'

const getDateDBString = (date: Date): string => {
Expand Down Expand Up @@ -511,6 +512,92 @@ class AnchorRepository {

return queryPromise
}

async storeImpressions(
accountId: number,
startDate: string,
endDate: string,
data: RawAnchorImpressionData
): Promise<any> {
const promises: Promise<any>[] = []

// Store aggregate impression data
if (data.impressions?.data?.value) {
const replaceStmt = `REPLACE INTO anchorImpressions (
account_id,
date_start,
date_end,
total_impressions,
total_considerations,
total_streams,
considerations_conversion_rate,
streams_conversion_rate
) VALUES (?,?,?,?,?,?,?,?)`

const funnel = data.impressionsFunnel.data.value.counts
const considerations = funnel.find((x) => x.id === 'considerations')
const streams = funnel.find((x) => x.id === 'streams')

promises.push(
this.pool.query(replaceStmt, [
accountId,
startDate,
endDate,
data.impressions.data.value,
considerations?.count || 0,
streams?.count || 0,
considerations?.conversionPercent || 0,
streams?.conversionPercent || 0,
])
)
}

// Store daily impressions
if (data.dailyImpressions?.data?.value) {
const dailyStmt = `REPLACE INTO anchorImpressionsDaily (
account_id,
date,
impressions
) VALUES (?,?,?)`

data.dailyImpressions.data.value.forEach((daily) => {
promises.push(
this.pool.query(dailyStmt, [
accountId,
getDateFromTimestamp(daily.date),
daily.value,
])
)
})
}

// Store impression sources
if (data.impressionsBySource?.data?.value) {
const sourceStmt = `REPLACE INTO anchorImpressionsSources (
account_id,
date_start,
date_end,
source_id,
source_name,
impression_count
) VALUES (?,?,?,?,?,?)`

data.impressionsBySource.data.value.forEach((source) => {
promises.push(
this.pool.query(sourceStmt, [
accountId,
startDate,
endDate,
source.id,
source.displayName,
source.value,
])
)
})
}

return Promise.all(promises)
}
}

export { AnchorRepository }
3 changes: 3 additions & 0 deletions src/db/DBInitializer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,9 @@ class DBInitializer {
}
const migrationIdGoal = this.getMigrationGoal()

console.log('Latest migration id:', latestMigrationId)
console.log('Migration id goal:', migrationIdGoal)

if (latestMigrationId >= migrationIdGoal) {
return
}
Expand Down
Loading
Loading