Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Demo] Update app to use data masking #189

Draft
wants to merge 64 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
64 commits
Select commit Hold shift + click to select a range
3cf10d5
Upgrade to alpha of client and codegen
jerelmiller Nov 13, 2024
4187e58
Enable masking
jerelmiller Nov 13, 2024
68728b6
Use alpha of typescript codegen
jerelmiller Nov 13, 2024
81b32fe
Rerun codegen
jerelmiller Nov 13, 2024
fc7bc55
Use release candidate of apollo
jerelmiller Nov 13, 2024
4690a0b
Run codemod to apply unmask
jerelmiller Nov 14, 2024
66fd21a
Update docker compose command
jerelmiller Nov 14, 2024
4f2321c
Remove rogue comma
jerelmiller Nov 14, 2024
2f44104
Fix issue reading error extensions
jerelmiller Nov 14, 2024
5e2e971
Enable data masking
jerelmiller Nov 14, 2024
f2a2c75
Temp remove masking warnings until fix is applied
jerelmiller Nov 14, 2024
7817962
Rerun codegen with unmask
jerelmiller Nov 14, 2024
92eddb1
Enable data masking in TypeScript globally
jerelmiller Nov 14, 2024
27fbad9
Convert PlaylistTile to use useFragment
jerelmiller Nov 14, 2024
984dbe6
Convert CurrentUserMenu to use useFragment and data masking
jerelmiller Nov 14, 2024
4858105
Migrate device popover to data masking
jerelmiller Nov 14, 2024
e0beaf7
Temp install pr version
jerelmiller Nov 14, 2024
88e99c9
Add back migrate mode
jerelmiller Nov 14, 2024
a24c3ed
Update PlaybackItemProgressBar to use data masking
jerelmiller Nov 14, 2024
8219f88
Migrate track prop from TrackPlaybackDetails
jerelmiller Nov 14, 2024
1cde56b
Reoganize TrackPlaybackDetails to select from parent
jerelmiller Nov 14, 2024
1c69695
Update PlaylistSidebarLink to use data masking
jerelmiller Nov 14, 2024
97e471f
Temp upgrade to useFragment with null support
jerelmiller Nov 14, 2024
282d5a3
Revert to original PlaybackItemProgressBar implementation
jerelmiller Nov 14, 2024
c630fa9
Add data masking to PlaybackItemProgressBar
jerelmiller Nov 14, 2024
fe62077
Combine EpisodePlaybackDetails and TrackPlaybackDetails into single c…
jerelmiller Nov 15, 2024
65b8359
Select devices in Playbar to fix typescript issue
jerelmiller Nov 15, 2024
ca313ec
Select progressMs from Playbar
jerelmiller Nov 15, 2024
952a2d0
Update LikeControl to use data masking
jerelmiller Nov 15, 2024
2fe70d2
Pass playbackItemId to LikeControl instead of using fragment
jerelmiller Nov 15, 2024
9607a66
Upgrade to another patch version
jerelmiller Nov 15, 2024
9b3acb5
Remove unmask from PlaybackStateSubscriber
jerelmiller Nov 15, 2024
78f4cd4
Remove unused import
jerelmiller Nov 15, 2024
c2cc5f8
Convert AlbumTracksTable to use data masking
jerelmiller Nov 15, 2024
cadccb6
Use data masking for AlbumTracksTable and AlbumTrackTitleCell
jerelmiller Nov 15, 2024
d490983
Convert AlbumTile to data masking
jerelmiller Nov 15, 2024
3961a7b
Convert ArtistTile to data masking
jerelmiller Nov 15, 2024
ac87093
Keep regular unmask for fragment defined as common fragment
jerelmiller Nov 15, 2024
665b7fc
Rework artist top track and use data masking
jerelmiller Nov 15, 2024
340ab63
Use TypedDocumentNode for playlist route
jerelmiller Nov 15, 2024
1bdc669
Convert TrackNumberCell to use data masking
jerelmiller Nov 15, 2024
aad8746
Update PlaylistTitleCell to use data masking and simplify props
jerelmiller Nov 15, 2024
630b4cd
Fix bug with playlist route
jerelmiller Nov 15, 2024
0e5dfa6
Fix bug on track route due to refactor of AlbumTopTrack
jerelmiller Nov 15, 2024
edb277b
Fix masked data dependency in track route
jerelmiller Nov 15, 2024
a4a5573
Fix issue with fragment not added properly in AlbumTracksTable
jerelmiller Nov 15, 2024
aa944e2
Remove unneeded field from query
jerelmiller Nov 15, 2024
1097927
Create a useCurrentUserId fragment
jerelmiller Nov 15, 2024
b400903
Create a LikedTracksSidebarLink component
jerelmiller Nov 15, 2024
a616111
Create a SavedEpisodesSidebarLink
jerelmiller Nov 15, 2024
0e1c0de
Simplify API of PlaylistSidebarLink
jerelmiller Nov 15, 2024
65e0dcb
Update to next rc release
jerelmiller Nov 15, 2024
fe90940
Update TrackTitleCell to use data masking
jerelmiller Nov 15, 2024
e79b407
Add additional fields required by the query
jerelmiller Nov 15, 2024
8bbcf14
Remove unused import
jerelmiller Nov 15, 2024
50747be
Update EpisodeDetailsCell to use data masking
jerelmiller Nov 15, 2024
fc89274
Update EpisodeRemainingDuration to use data masking
jerelmiller Nov 15, 2024
f546bb8
Use data masking in search route
jerelmiller Nov 15, 2024
f1bfbaa
Update episode page to use data masking
jerelmiller Nov 15, 2024
d5578bc
Update collection/albums route to use data masking
jerelmiller Nov 15, 2024
c10fba5
Add some masking to collection/playlists
jerelmiller Nov 15, 2024
4e67730
Use masking on collection/artists route
jerelmiller Nov 15, 2024
0436250
Remove migrate mode from settings query
jerelmiller Nov 15, 2024
176a258
Remove unused import
jerelmiller Nov 15, 2024
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
2 changes: 1 addition & 1 deletion client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"node": ">=18"
},
"dependencies": {
"@apollo/client": "^3.9.5",
"@apollo/client": "^3.12.0-rc.1",
"@apollo/persisted-query-lists": "^1.0.0",
"@radix-ui/react-context-menu": "^2.1.3",
"@radix-ui/react-dialog": "^1.0.3",
Expand Down
7 changes: 7 additions & 0 deletions client/src/@types/@apollo/client.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import '@apollo/client';

declare module '@apollo/client' {
interface DataMasking {
enabled: true;
}
}
1 change: 1 addition & 0 deletions client/src/apollo/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ const httpLink = createHttpLink({
});

const client = new ApolloClient({
dataMasking: true,
link: from([httpAuthLink, persistedQueries, httpLink]),
connectToDevTools: true,
name: 'Spotify Showcase Website',
Expand Down
36 changes: 26 additions & 10 deletions client/src/components/AlbumTile.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,20 @@
import { gql } from '@apollo/client';
import { AlbumTile_album as Album } from '../types/api';
import {
FragmentType,
TypedDocumentNode,
gql,
useFragment,
} from '@apollo/client';
import { AlbumTile_album } from '../types/api';
import { capitalize } from '../utils/string';
import { yearOfRelease } from '../utils/releaseDate';
import MediaTile from './MediaTile';
import { fragmentRegistry } from '../apollo/fragmentRegistry';

interface AlbumTileProps {
album: Album;
album: FragmentType<AlbumTile_album>;
}

fragmentRegistry.register(gql`
const AlbumTileFragment: TypedDocumentNode<AlbumTile_album> = gql`
fragment AlbumTile_album on Album {
id
name
Expand All @@ -22,17 +27,28 @@ fragmentRegistry.register(gql`
url
}
}
`);
`;

fragmentRegistry.register(AlbumTileFragment);

const AlbumTile = ({ album }: AlbumTileProps) => {
const { data, complete } = useFragment({
fragment: AlbumTileFragment,
from: album,
});

if (!complete) {
return null;
}

return (
<MediaTile to={`/albums/${album.id}`}>
<MediaTile.CoverPhoto image={album.images[0]} />
<MediaTile to={`/albums/${data.id}`}>
<MediaTile.CoverPhoto image={data.images[0]} />
<div className="flex flex-col">
<MediaTile.Title>{album.name}</MediaTile.Title>
<MediaTile.Title>{data.name}</MediaTile.Title>
<MediaTile.Details>
<span>{yearOfRelease(album.releaseDate)}</span>
<span>{capitalize(album.albumType.toLowerCase())}</span>
<span>{yearOfRelease(data.releaseDate)}</span>
<span>{capitalize(data.albumType.toLowerCase())}</span>
</MediaTile.Details>
</div>
</MediaTile>
Expand Down
47 changes: 37 additions & 10 deletions client/src/components/AlbumTrackTitleCell.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,14 @@
import { gql } from '@apollo/client';
import {
FragmentType,
TypedDocumentNode,
gql,
useFragment,
} from '@apollo/client';
import usePlaybackState from '../hooks/usePlaybackState';
import {
AlbumTrackTitleCell_album as Album,
AlbumTrackTitleCell_album,
AlbumTrackTitleCell_track,
AlbumTrackTitleCell_playbackState as PlaybackState,
AlbumTrackTitleCell_track as Track,
} from '../types/api';
Expand All @@ -12,8 +19,8 @@ import Flex from './Flex';
import { fragmentRegistry } from '../apollo/fragmentRegistry';

interface AlbumTrackTitleCellProps {
album: Album;
track: Track;
album: FragmentType<Album>;
track: FragmentType<Track>;
}

const PLAYBACK_STATE_FRAGMENT = gql`
Expand All @@ -28,11 +35,13 @@ const PLAYBACK_STATE_FRAGMENT = gql`
}
`;

fragmentRegistry.register(gql`
const AlbumTrackTitleCellAlbumFragment: TypedDocumentNode<AlbumTrackTitleCell_album> = gql`
fragment AlbumTrackTitleCell_album on Album {
uri
}
`;

const AlbumTrackTitleCellTrackFragment: TypedDocumentNode<AlbumTrackTitleCell_track> = gql`
fragment AlbumTrackTitleCell_track on Track {
id
name
Expand All @@ -43,28 +52,46 @@ fragmentRegistry.register(gql`
name
}
}
`);
`;

fragmentRegistry.register(
AlbumTrackTitleCellAlbumFragment,
AlbumTrackTitleCellTrackFragment
);

const AlbumTrackTitleCell = ({ album, track }: AlbumTrackTitleCellProps) => {
const { data: albumData, complete: albumComplete } = useFragment({
fragment: AlbumTrackTitleCellAlbumFragment,
from: album,
});
const { data: trackData, complete: trackComplete } = useFragment({
fragment: AlbumTrackTitleCellTrackFragment,
from: track,
});

const playbackState = usePlaybackState<PlaybackState>({
fragment: PLAYBACK_STATE_FRAGMENT,
});

const isPlayingInAlbum = playbackState?.context?.uri === album.uri;
const isCurrentTrack = track.uri === playbackState?.item?.uri;
if (!trackComplete || !albumComplete) {
return null;
}

const isPlayingInAlbum = playbackState?.context?.uri === albumData.uri;
const isCurrentTrack = trackData.uri === playbackState?.item?.uri;

return (
<Flex direction="column" gap="0.5">
<span
className="text-base"
color={isCurrentTrack && isPlayingInAlbum ? 'themeLight' : 'primary'}
>
{track.name}
{trackData.name}
</span>
<Flex gap="0.5rem" alignItems="center">
{track.explicit && <ExplicitBadge />}
{trackData.explicit && <ExplicitBadge />}
<CommaSeparatedList>
{track.artists.map((artist) => (
{trackData.artists.map((artist) => (
<EntityLink
className="text-muted transition-colors duration-[0.15s] hover:text-primary"
key={artist.id}
Expand Down
133 changes: 76 additions & 57 deletions client/src/components/AlbumTracksTable.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
import { gql } from '@apollo/client';
import {
FragmentType,
TypedDocumentNode,
gql,
useFragment,
} from '@apollo/client';
import { createColumnHelper } from '@tanstack/react-table';
import { Clock } from 'lucide-react';
import { Get } from 'type-fest';
Expand All @@ -17,15 +22,15 @@ import { fragmentRegistry } from '../apollo/fragmentRegistry';
type Track = NonNullable<Get<Album, 'tracks.edges[0].node'>>;

interface AlbumTracksTableProps {
album: Album;
album: FragmentType<Album>;
tracksContains: Map<string, boolean>;
}

interface AlbumTracksTableMeta {
tracksContains: Map<string, boolean>;
}

fragmentRegistry.register(gql`
const AlbumTracksTableFragment: TypedDocumentNode<Album> = gql`
fragment AlbumTracksTable_album on Album {
id
uri
Expand All @@ -35,90 +40,104 @@ fragmentRegistry.register(gql`
id
uri
durationMs
trackNumber
artists {
id
name
}

...AlbumTrackTitleCell_track
...TrackNumberCell_track
}
}
}

...AlbumTrackTitleCell_album
}
`);
`;

fragmentRegistry.register(AlbumTracksTableFragment);

const columnHelper = createColumnHelper<Track>();

const AlbumTracksTable = ({ album, tracksContains }: AlbumTracksTableProps) => {
const { data, complete } = useFragment({
fragment: AlbumTracksTableFragment,
from: album,
});
const [resumePlayback] = useResumePlaybackMutation();

const columns = useMemo(
() => [
columnHelper.accessor((track) => track, {
header: '#',
meta: { shrink: true },
cell: (info) => {
return (
<TrackNumberCell
context={album}
track={info.getValue()}
position={info.row.index}
/>
);
},
}),
columnHelper.display({
id: 'title',
header: 'Title',
cell: (info) => {
return (
<AlbumTrackTitleCell album={album} track={info.row.original} />
);
},
}),
columnHelper.display({
id: 'liked',
header: '',
cell: (info) => {
const { tracksContains } = info.table.options
.meta as unknown as AlbumTracksTableMeta;

const track = info.row.original;
const liked = tracksContains.get(track.id) ?? false;

return <TrackLikeButtonCell liked={liked} track={track} />;
},
meta: {
shrink: true,
},
}),
columnHelper.accessor('durationMs', {
header: () => <Clock size="1rem" />,
cell: (info) => <Duration durationMs={info.getValue()} />,
meta: {
headerAlign: 'right',
shrink: true,
},
}),
],
[album]
() =>
complete
? [
columnHelper.accessor((track) => track, {
header: '#',
meta: { shrink: true },
cell: (info) => {
return (
<TrackNumberCell
context={data}
track={info.getValue()}
position={info.row.index}
/>
);
},
}),
columnHelper.display({
id: 'title',
header: 'Title',
cell: (info) => {
return (
<AlbumTrackTitleCell album={data} track={info.row.original} />
);
},
}),
columnHelper.display({
id: 'liked',
header: '',
cell: (info) => {
const { tracksContains } = info.table.options
.meta as unknown as AlbumTracksTableMeta;

const track = info.row.original;
const liked = tracksContains.get(track.id) ?? false;

return <TrackLikeButtonCell liked={liked} track={track} />;
},
meta: {
shrink: true,
},
}),
columnHelper.accessor('durationMs', {
header: () => <Clock size="1rem" />,
cell: (info) => <Duration durationMs={info.getValue()} />,
meta: {
headerAlign: 'right',
shrink: true,
},
}),
]
: [],
[data, complete]
);

if (!complete) {
return null;
}

return (
<Table
enableRowSelection
enableMultiSelect
enableRangeSelect
columns={columns}
data={album.tracks?.edges.map((edge) => edge.node) ?? []}
data={data.tracks?.edges.map((edge) => edge.node) ?? []}
meta={{ tracksContains }}
onDoubleClickRow={(row) => {
const track = row.original;

resumePlayback({
contextUri: album.uri,
contextUri: data.uri,
offset: { uri: track.uri },
});
}}
Expand Down Expand Up @@ -165,7 +184,7 @@ const AlbumTracksTable = ({ album, tracksContains }: AlbumTracksTableProps) => {
Share
</ContextMenu.SubMenu>
<ContextMenu.Separator />
<ContextMenuAction.OpenDesktopApp uri={track.uri} context={album} />
<ContextMenuAction.OpenDesktopApp uri={track.uri} context={data} />
</>
);
}}
Expand Down
Loading
Loading