Let users browse followers and following lists
Following lists are public; followers are visible to the user and to mutual followers.
Adds GET /users/{id}/followers and /following, exposes isFollowing and canViewFollowers
on the profile, and returns isFollowedByViewer from user search so the web player no
longer calls /is-following per user.
This commit is contained in:
Vendored
+118
-3
@@ -607,6 +607,40 @@ export interface paths {
|
||||
patch?: never;
|
||||
trace?: never;
|
||||
};
|
||||
"/users/{id}/followers": {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path?: never;
|
||||
cookie?: never;
|
||||
};
|
||||
/** Get The Users Following A User. Visible To The User And To Mutual Followers. */
|
||||
get: operations["GetUserFollowers"];
|
||||
put?: never;
|
||||
post?: never;
|
||||
delete?: never;
|
||||
options?: never;
|
||||
head?: never;
|
||||
patch?: never;
|
||||
trace?: never;
|
||||
};
|
||||
"/users/{id}/following": {
|
||||
parameters: {
|
||||
query?: never;
|
||||
header?: never;
|
||||
path?: never;
|
||||
cookie?: never;
|
||||
};
|
||||
/** Get The Users A User Follows. */
|
||||
get: operations["GetUserFollowing"];
|
||||
put?: never;
|
||||
post?: never;
|
||||
delete?: never;
|
||||
options?: never;
|
||||
head?: never;
|
||||
patch?: never;
|
||||
trace?: never;
|
||||
};
|
||||
"/users/{id}/is-following": {
|
||||
parameters: {
|
||||
query?: never;
|
||||
@@ -807,8 +841,8 @@ export interface components {
|
||||
hasNextPage: boolean;
|
||||
hasPreviousPage: boolean;
|
||||
};
|
||||
PaginatedResponseOfUserApplicationResponse: {
|
||||
items: components["schemas"]["UserApplicationResponse"][];
|
||||
PaginatedResponseOfUserSummaryResponse: {
|
||||
items: components["schemas"]["UserSummaryResponse"][];
|
||||
/** Format: int32 */
|
||||
pageNumber: number | string;
|
||||
/** Format: int32 */
|
||||
@@ -976,6 +1010,16 @@ export interface components {
|
||||
followersCount: number | string;
|
||||
/** Format: int32 */
|
||||
followingCount: number | string;
|
||||
isFollowing: boolean;
|
||||
canViewFollowers: boolean;
|
||||
};
|
||||
UserSummaryResponse: {
|
||||
id: string;
|
||||
name: string;
|
||||
firstName: null | string;
|
||||
secondName: null | string;
|
||||
profilePictureUrl: null | string;
|
||||
isFollowedByViewer: boolean;
|
||||
};
|
||||
};
|
||||
responses: never;
|
||||
@@ -2277,7 +2321,7 @@ export interface operations {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content: {
|
||||
"application/json": components["schemas"]["PaginatedResponseOfUserApplicationResponse"];
|
||||
"application/json": components["schemas"]["PaginatedResponseOfUserSummaryResponse"];
|
||||
};
|
||||
};
|
||||
};
|
||||
@@ -2460,6 +2504,77 @@ export interface operations {
|
||||
};
|
||||
};
|
||||
};
|
||||
GetUserFollowers: {
|
||||
parameters: {
|
||||
query?: {
|
||||
pageNumber?: number | string;
|
||||
pageSize?: number | string;
|
||||
};
|
||||
header?: never;
|
||||
path: {
|
||||
id: number | string;
|
||||
};
|
||||
cookie?: never;
|
||||
};
|
||||
requestBody?: never;
|
||||
responses: {
|
||||
/** @description OK */
|
||||
200: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content: {
|
||||
"application/json": components["schemas"]["PaginatedResponseOfUserSummaryResponse"];
|
||||
};
|
||||
};
|
||||
/** @description Forbidden */
|
||||
403: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content?: never;
|
||||
};
|
||||
/** @description Not Found */
|
||||
404: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content?: never;
|
||||
};
|
||||
};
|
||||
};
|
||||
GetUserFollowing: {
|
||||
parameters: {
|
||||
query?: {
|
||||
pageNumber?: number | string;
|
||||
pageSize?: number | string;
|
||||
};
|
||||
header?: never;
|
||||
path: {
|
||||
id: number | string;
|
||||
};
|
||||
cookie?: never;
|
||||
};
|
||||
requestBody?: never;
|
||||
responses: {
|
||||
/** @description OK */
|
||||
200: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content: {
|
||||
"application/json": components["schemas"]["PaginatedResponseOfUserSummaryResponse"];
|
||||
};
|
||||
};
|
||||
/** @description Not Found */
|
||||
404: {
|
||||
headers: {
|
||||
[name: string]: unknown;
|
||||
};
|
||||
content?: never;
|
||||
};
|
||||
};
|
||||
};
|
||||
IsFollowingUser: {
|
||||
parameters: {
|
||||
query?: never;
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
title: string;
|
||||
eyebrow?: string;
|
||||
description?: string;
|
||||
meta?: string;
|
||||
meta?: string | Snippet;
|
||||
cover?: Snippet;
|
||||
actions?: Snippet;
|
||||
align?: 'center' | 'end';
|
||||
@@ -45,8 +45,10 @@
|
||||
{#if description}
|
||||
<p class="mt-3 max-w-2xl text-body text-fg-2">{description}</p>
|
||||
{/if}
|
||||
{#if meta}
|
||||
{#if typeof meta === 'string'}
|
||||
<p class="mt-3 text-sm text-fg-2">{meta}</p>
|
||||
{:else if meta}
|
||||
<p class="mt-3 text-sm text-fg-2">{@render meta()}</p>
|
||||
{/if}
|
||||
</div>
|
||||
{#if actions && !cover}
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
<script lang="ts">
|
||||
import ListRow from './ListRow.svelte';
|
||||
import Avatar from './Avatar.svelte';
|
||||
import FollowButton from './FollowButton.svelte';
|
||||
|
||||
interface Props {
|
||||
user: {
|
||||
id: string;
|
||||
name: string;
|
||||
profilePictureUrl: string | null;
|
||||
isFollowedByViewer: boolean;
|
||||
};
|
||||
canFollow?: boolean;
|
||||
}
|
||||
|
||||
let { user, canFollow = false }: Props = $props();
|
||||
</script>
|
||||
|
||||
<div class="flex items-center gap-3">
|
||||
<ListRow title={user.name} href="/user/{user.id}" size="lg" class="min-w-0 flex-1">
|
||||
{#snippet art()}
|
||||
<Avatar name={user.name} src={user.profilePictureUrl} size="md" />
|
||||
{/snippet}
|
||||
</ListRow>
|
||||
{#if canFollow}
|
||||
<FollowButton following={user.isFollowedByViewer} userId={user.id} class="shrink-0" />
|
||||
{/if}
|
||||
</div>
|
||||
@@ -17,5 +17,6 @@ export const HOME_POPULAR_MAX = 8;
|
||||
export const PLAYLIST_PICKER_PAGE_SIZE = 50;
|
||||
export const LIKED_TRACKS_PAGE_SIZE = 500;
|
||||
export const EXPLORE_USERS_LIMIT = 12;
|
||||
export const FOLLOW_LIST_PAGE_SIZE = 30;
|
||||
export const MENU_WIDTH = 240;
|
||||
export const SUBMENU_WIDTH = 256;
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
import type { createApiClient } from '$lib/server/api';
|
||||
import type { FollowList } from '$lib/types';
|
||||
|
||||
type ApiClient = ReturnType<typeof createApiClient>;
|
||||
|
||||
export function fetchFollowList(
|
||||
api: ApiClient,
|
||||
list: FollowList,
|
||||
id: string,
|
||||
pageNumber: number,
|
||||
pageSize: number
|
||||
) {
|
||||
const options = { params: { path: { id }, query: { pageNumber, pageSize } } };
|
||||
return list === 'followers'
|
||||
? api.GET('/users/{id}/followers', options)
|
||||
: api.GET('/users/{id}/following', options);
|
||||
}
|
||||
@@ -4,3 +4,5 @@ export interface SessionUser {
|
||||
email: string;
|
||||
picture: string;
|
||||
}
|
||||
|
||||
export type FollowList = 'followers' | 'following';
|
||||
|
||||
Reference in New Issue
Block a user