Skip to content
Merged
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
5 changes: 5 additions & 0 deletions .changeset/listener-cities.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@audius/sdk': minor
---

Add `users.getListenerCities` for an artist's top listener cities
1 change: 1 addition & 0 deletions packages/common/src/api/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,7 @@ export * from './tan-query/users/useUsers'
export * from './tan-query/users/useUser'
export * from './tan-query/users/useUserTracksByHandle'
export * from './tan-query/users/useUserTrackDownloadCountTotal'
export * from './tan-query/users/useListenerCities'
export * from './tan-query/users/useProfileUser'
export * from './tan-query/users/useOtherChatUsers'

Expand Down
1 change: 1 addition & 0 deletions packages/common/src/api/tan-query/queryKeys.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ export const QUERY_KEYS = {
userPlaylists: 'userPlaylists',
userAlbums: 'userAlbums',
userTracksDownloadCount: 'userTracksDownloadCount',
listenerCities: 'listenerCities',
collection: 'collection',
collections: 'collections',
collectionByPermalink: 'collectionByPermalink',
Expand Down
51 changes: 51 additions & 0 deletions packages/common/src/api/tan-query/users/useListenerCities.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { Id, ListenerCity } from '@audius/sdk'
import { useQuery } from '@tanstack/react-query'

import { useQueryContext } from '~/api/tan-query/utils'
import { ID } from '~/models'

import { QUERY_KEYS } from '../queryKeys'
import { QueryKey, QueryOptions } from '../types'

export type ListenerCitiesTime = 'week' | 'month' | 'all'

type UseListenerCitiesArgs = {
userId: ID | null | undefined
time: ListenerCitiesTime
limit?: number
}

export const getListenerCitiesQueryKey = ({
userId,
time,
limit
}: UseListenerCitiesArgs) =>
[QUERY_KEYS.listenerCities, userId, { time, limit }] as unknown as QueryKey<
ListenerCity[]
>

/**
* Cities whose listeners played the artist's tracks the most. Only the
* artist or one of their managers can read this.
*/
export const useListenerCities = (
{ userId, time, limit = 10 }: UseListenerCitiesArgs,
options?: QueryOptions
) => {
const { audiusSdk } = useQueryContext()

return useQuery({
queryKey: getListenerCitiesQueryKey({ userId, time, limit }),
queryFn: async () => {
const sdk = await audiusSdk()
const { data = [] } = await sdk.users.getListenerCities({
id: Id.parse(userId),
time,
limit
})
return data
},
...options,
enabled: options?.enabled !== false && !!userId
})
}
10 changes: 7 additions & 3 deletions packages/common/src/services/remote-config/feature-flags.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ export enum FeatureFlags {
BULK_TRACK_EDIT = 'bulk_track_edit',
WEEKLY_ARTIST_RECAP = 'weekly_artist_recap',
TRACK_FEEDBACK_REQUESTS = 'track_feedback_requests',
GROUP_MESSAGES = 'group_messages'
GROUP_MESSAGES = 'group_messages',
DASHBOARD_TOP_CITIES = 'dashboard_top_cities'
}

type FlagDefaults = Record<FeatureFlags, boolean>
Expand All @@ -42,7 +43,8 @@ export const environmentFlagDefaults: Record<
[FeatureFlags.BULK_TRACK_EDIT]: true,
[FeatureFlags.WEEKLY_ARTIST_RECAP]: true,
[FeatureFlags.TRACK_FEEDBACK_REQUESTS]: true,
[FeatureFlags.GROUP_MESSAGES]: true
[FeatureFlags.GROUP_MESSAGES]: true,
[FeatureFlags.DASHBOARD_TOP_CITIES]: true
},
production: {}
}
Expand Down Expand Up @@ -89,5 +91,7 @@ export const flagDefaults: FlagDefaults = {
// "Looking for feedback" toggle, badge, and Explore section.
[FeatureFlags.TRACK_FEEDBACK_REQUESTS]: false,
// Creating group DMs. Existing groups always render.
[FeatureFlags.GROUP_MESSAGES]: false
[FeatureFlags.GROUP_MESSAGES]: false,
// Top cities card on the web Artist Dashboard.
[FeatureFlags.DASHBOARD_TOP_CITIES]: false
}
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,8 @@ models/ListenCount.ts
models/ListenStreakReminderNotification.ts
models/ListenStreakReminderNotificationAction.ts
models/ListenStreakReminderNotificationActionData.ts
models/ListenerCitiesResponse.ts
models/ListenerCity.ts
models/ManagedUser.ts
models/ManagedUsersResponse.ts
models/ManagersResponse.ts
Expand Down
79 changes: 79 additions & 0 deletions packages/sdk/src/sdk/api/generated/default/apis/UsersApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import type {
GetSupporters,
GetSupporting,
HistoryResponse,
ListenerCitiesResponse,
ManagedUsersResponse,
ManagersResponse,
MutualFollowersResponse,
Expand Down Expand Up @@ -122,6 +123,8 @@ import {
GetSupportingToJSON,
HistoryResponseFromJSON,
HistoryResponseToJSON,
ListenerCitiesResponseFromJSON,
ListenerCitiesResponseToJSON,
ManagedUsersResponseFromJSON,
ManagedUsersResponseToJSON,
ManagersResponseFromJSON,
Expand Down Expand Up @@ -348,6 +351,15 @@ export interface GetGranteeUsersRequest {
userId?: string;
}

export interface GetListenerCitiesRequest {
id: string;
time?: GetListenerCitiesTimeEnum;
limit?: number;
userId?: string;
encodedDataMessage?: string;
encodedDataSignature?: string;
}

export interface GetManagedUsersRequest {
id: string;
isApproved?: boolean;
Expand Down Expand Up @@ -1931,6 +1943,64 @@ export class UsersApi extends runtime.BaseAPI {
return await response.value();
}

/**
* @hidden
* Gets the cities whose listeners played the user\'s tracks the most. Plays by the user and plays with no known city are not counted. Locations come from the listener\'s IP address and can be wrong for VPN and mobile carrier traffic. Only the user or one of their managers can call this.
*/
async getListenerCitiesRaw(params: GetListenerCitiesRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<runtime.ApiResponse<ListenerCitiesResponse>> {
if (params.id === null || params.id === undefined) {
throw new runtime.RequiredError('id','Required parameter params.id was null or undefined when calling getListenerCities.');
}

const queryParameters: any = {};

if (params.time !== undefined) {
queryParameters['time'] = params.time;
}

if (params.limit !== undefined) {
queryParameters['limit'] = params.limit;
}

if (params.userId !== undefined) {
queryParameters['user_id'] = params.userId;
}

const headerParameters: runtime.HTTPHeaders = {};

if (params.encodedDataMessage !== undefined && params.encodedDataMessage !== null) {
headerParameters['Encoded-Data-Message'] = String(params.encodedDataMessage);
}

if (params.encodedDataSignature !== undefined && params.encodedDataSignature !== null) {
headerParameters['Encoded-Data-Signature'] = String(params.encodedDataSignature);
}

if (!headerParameters["Authorization"] && this.configuration && this.configuration.accessToken) {
const token = await this.configuration.accessToken("OAuth2", ["read"]);
if (token) {
headerParameters["Authorization"] = token;
}
}

const response = await this.request({
path: `/users/{id}/listener_cities`.replace(`{${"id"}}`, encodeURIComponent(String(params.id))),
method: 'GET',
headers: headerParameters,
query: queryParameters,
}, initOverrides);

return new runtime.JSONApiResponse(response, (jsonValue) => ListenerCitiesResponseFromJSON(jsonValue));
}

/**
* Gets the cities whose listeners played the user\'s tracks the most. Plays by the user and plays with no known city are not counted. Locations come from the listener\'s IP address and can be wrong for VPN and mobile carrier traffic. Only the user or one of their managers can call this.
*/
async getListenerCities(params: GetListenerCitiesRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<ListenerCitiesResponse> {
const response = await this.getListenerCitiesRaw(params, initOverrides);
return await response.value();
}

/**
* @hidden
* Gets a list of users managed by the given user
Expand Down Expand Up @@ -5373,6 +5443,15 @@ export const GetContestsByUserStatusEnum = {
All: 'all'
} as const;
export type GetContestsByUserStatusEnum = typeof GetContestsByUserStatusEnum[keyof typeof GetContestsByUserStatusEnum];
/**
* @export
*/
export const GetListenerCitiesTimeEnum = {
Week: 'week',
Month: 'month',
All: 'all'
} as const;
export type GetListenerCitiesTimeEnum = typeof GetListenerCitiesTimeEnum[keyof typeof GetListenerCitiesTimeEnum];
/**
* @export
*/
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
/* tslint:disable */
/* eslint-disable */
// @ts-nocheck
/**
* Audius API
*
* The version of the OpenAPI document: 1.0
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/

import { exists, mapValues } from '../runtime';
import type { ListenerCity } from './ListenerCity';
import {
ListenerCityFromJSON,
ListenerCityFromJSONTyped,
ListenerCityToJSON,
} from './ListenerCity';

/**
*
* @export
* @interface ListenerCitiesResponse
*/
export interface ListenerCitiesResponse {
/**
*
* @type {Array<ListenerCity>}
* @memberof ListenerCitiesResponse
*/
data?: Array<ListenerCity>;
}

/**
* Check if a given object implements the ListenerCitiesResponse interface.
*/
export function instanceOfListenerCitiesResponse(value: object): value is ListenerCitiesResponse {
let isInstance = true;

return isInstance;
}

export function ListenerCitiesResponseFromJSON(json: any): ListenerCitiesResponse {
return ListenerCitiesResponseFromJSONTyped(json, false);
}

export function ListenerCitiesResponseFromJSONTyped(json: any, ignoreDiscriminator: boolean): ListenerCitiesResponse {
if ((json === undefined) || (json === null)) {
return json;
}
return {

'data': !exists(json, 'data') ? undefined : ((json['data'] as Array<any>).map(ListenerCityFromJSON)),
};
}

export function ListenerCitiesResponseToJSON(value?: ListenerCitiesResponse | null): any {
if (value === undefined) {
return undefined;
}
if (value === null) {
return null;
}
return {

'data': value.data === undefined ? undefined : ((value.data as Array<any>).map(ListenerCityToJSON)),
};
}

93 changes: 93 additions & 0 deletions packages/sdk/src/sdk/api/generated/default/models/ListenerCity.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
/* tslint:disable */
/* eslint-disable */
// @ts-nocheck
/**
* Audius API
*
* The version of the OpenAPI document: 1.0
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/

import { exists, mapValues } from '../runtime';
/**
*
* @export
* @interface ListenerCity
*/
export interface ListenerCity {
/**
*
* @type {string}
* @memberof ListenerCity
*/
city: string;
/**
* Region or state name, empty when unknown
* @type {string}
* @memberof ListenerCity
*/
region: string;
/**
* Country name, empty when unknown
* @type {string}
* @memberof ListenerCity
*/
country: string;
/**
*
* @type {number}
* @memberof ListenerCity
*/
playCount: number;
}

/**
* Check if a given object implements the ListenerCity interface.
*/
export function instanceOfListenerCity(value: object): value is ListenerCity {
let isInstance = true;
isInstance = isInstance && "city" in value && value["city"] !== undefined;
isInstance = isInstance && "region" in value && value["region"] !== undefined;
isInstance = isInstance && "country" in value && value["country"] !== undefined;
isInstance = isInstance && "playCount" in value && value["playCount"] !== undefined;

return isInstance;
}

export function ListenerCityFromJSON(json: any): ListenerCity {
return ListenerCityFromJSONTyped(json, false);
}

export function ListenerCityFromJSONTyped(json: any, ignoreDiscriminator: boolean): ListenerCity {
if ((json === undefined) || (json === null)) {
return json;
}
return {

'city': json['city'],
'region': json['region'],
'country': json['country'],
'playCount': json['play_count'],
};
}

export function ListenerCityToJSON(value?: ListenerCity | null): any {
if (value === undefined) {
return undefined;
}
if (value === null) {
return null;
}
return {

'city': value.city,
'region': value.region,
'country': value.country,
'play_count': value.playCount,
};
}

Loading
Loading