Map track coauthors, coauthor invites and invitations

This commit is contained in:
2026-09-25 02:17:13 +02:00
parent 8a5b1bd353
commit 2b2f4093fb
3 changed files with 88 additions and 6 deletions
+39 -3
View File
@@ -5,6 +5,8 @@ import {
toCount,
toDatedTrack,
toLikedTrack,
toCoauthorInvitation,
toLibraryTrack,
toListeningStats,
toMix,
toPage,
@@ -25,7 +27,7 @@ const trackDto: components['schemas']['TrackApplicationResponse'] = {
tags: [],
isExplicit: true,
isPublished: true,
coauthors: []
coauthors: [{ id: 'u2', name: 'Guest' }]
};
describe('toTrack', () => {
@@ -37,7 +39,8 @@ describe('toTrack', () => {
ownerUserId: '42',
explicit: true,
duration: 183.5,
listensCount: 12
listensCount: 12,
coauthors: [{ id: 'u2', name: 'Guest' }]
});
});
it('falls back to 0 for invalid numbers', () => {
@@ -71,7 +74,8 @@ describe('toMix', () => {
ownerUserId: null,
explicit: false,
duration: 60,
listensCount: 3
listensCount: 3,
coauthors: []
}
]);
});
@@ -168,3 +172,35 @@ describe('toListeningStats', () => {
});
});
});
describe('toLibraryTrack', () => {
it('keeps the coauthor invites with their status', () => {
const track = toLibraryTrack({
...trackDto,
albums: [],
coauthorInvites: [
{ userId: 'u2', name: 'Guest', status: 'Accepted' },
{ userId: 'u3', name: 'Other', status: 'Pending' }
]
});
expect(track.coauthorInvites).toEqual([
{ userId: 'u2', name: 'Guest', status: 'Accepted' },
{ userId: 'u3', name: 'Other', status: 'Pending' }
]);
expect(track.coauthors).toEqual([{ id: 'u2', name: 'Guest' }]);
});
});
describe('toCoauthorInvitation', () => {
it('copies the invitation fields', () => {
const dto = {
id: 'i1',
trackId: 't1',
trackTitle: 'Song',
artist: null,
ownerUserId: '42',
invitedAt: '2026-01-02T00:00:00Z'
};
expect(toCoauthorInvitation(dto)).toEqual(dto);
});
});
+25 -3
View File
@@ -1,6 +1,8 @@
import type { components } from '$lib/api/schema';
import type {
Album,
CoauthorInvitation,
CoauthorInvite,
LibraryTrack,
TrackStatusCounts,
LikedTrack,
@@ -13,6 +15,8 @@ import type {
type TrackDto = components['schemas']['TrackApplicationResponse'];
type UserTrackDto = components['schemas']['UserTrackApplicationResponse'];
type CoauthorInviteDto = components['schemas']['TrackCoauthorInviteReference'];
type CoauthorInvitationDto = components['schemas']['CoauthorInvitationResponse'];
type StatusCountsDto = components['schemas']['TrackStatusCountsResponse'];
type AlbumDto = components['schemas']['AlbumApplicationResponse'];
type PlaylistDto = components['schemas']['PlayListApplicationResponse'];
@@ -34,7 +38,8 @@ export function toTrack(dto: TrackDto): Track {
ownerUserId: dto.ownerUserId,
explicit: dto.isExplicit,
duration: Number(dto.duration) || 0,
listensCount: Number(dto.listensCount) || 0
listensCount: Number(dto.listensCount) || 0,
coauthors: dto.coauthors.map(({ id, name }) => ({ id, name }))
};
}
@@ -42,6 +47,21 @@ export function toDatedTrack(dto: TrackDto): Track & { date: string } {
return { ...toTrack(dto), date: dto.createdAt };
}
export function toCoauthorInvite({ userId, name, status }: CoauthorInviteDto): CoauthorInvite {
return { userId, name, status };
}
export function toCoauthorInvitation(dto: CoauthorInvitationDto): CoauthorInvitation {
return {
id: dto.id,
trackId: dto.trackId,
trackTitle: dto.trackTitle,
artist: dto.artist,
ownerUserId: dto.ownerUserId,
invitedAt: dto.invitedAt
};
}
export function toLibraryTrack(dto: UserTrackDto): LibraryTrack {
return {
...toDatedTrack(dto),
@@ -49,7 +69,8 @@ export function toLibraryTrack(dto: UserTrackDto): LibraryTrack {
genres: dto.tags,
albums: dto.albums,
albumTitle: dto.albums.length > 0 ? dto.albums.map((album) => album.title).join(', ') : null,
published: dto.isPublished
published: dto.isPublished,
coauthorInvites: dto.coauthorInvites.map(toCoauthorInvite)
};
}
@@ -75,7 +96,8 @@ export function toMixTrack(item: MixItemDto): Track {
ownerUserId: null,
explicit: false,
duration: Number(item.durationSeconds) || 0,
listensCount: Number(item.listensCount) || 0
listensCount: Number(item.listensCount) || 0,
coauthors: []
};
}
+24
View File
@@ -7,6 +7,28 @@ export interface SessionUser {
export type FollowList = 'followers' | 'following';
export interface TrackCoauthor {
id: string;
name: string;
}
export type CoauthorStatus = 'Pending' | 'Accepted' | 'Rejected';
export interface CoauthorInvite {
userId: string;
name: string;
status: CoauthorStatus;
}
export interface CoauthorInvitation {
id: string;
trackId: string;
trackTitle: string;
artist: string | null;
ownerUserId: string;
invitedAt: string;
}
export interface Track {
id: string;
title: string;
@@ -15,6 +37,7 @@ export interface Track {
explicit: boolean;
duration: number;
listensCount: number;
coauthors: TrackCoauthor[];
}
export interface GenreOption {
@@ -31,6 +54,7 @@ export interface LibraryTrack extends Track {
albumTitle: string | null;
albums: { id: string; title: string }[];
published: boolean;
coauthorInvites: CoauthorInvite[];
}
export interface TrackStatusCounts {