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
74 changes: 73 additions & 1 deletion packages/backend/src/graphql/resolvers/playlists/queries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,76 @@ function convertLitUpHoldsStringToMap(litUpHolds: string, board: BoardName): Rec
}

export const playlistQueries = {
/**
* Get all playlists owned by the authenticated user across all boards
*/
allUserPlaylists: async (
_: unknown,
_args: unknown,
ctx: ConnectionContext
): Promise<unknown[]> => {
requireAuthenticated(ctx);

const userId = ctx.userId!;

// Get all user's playlists across all boards
const userPlaylists = await db
.select({
id: dbSchema.playlists.id,
uuid: dbSchema.playlists.uuid,
boardType: dbSchema.playlists.boardType,
layoutId: dbSchema.playlists.layoutId,
name: dbSchema.playlists.name,
description: dbSchema.playlists.description,
isPublic: dbSchema.playlists.isPublic,
color: dbSchema.playlists.color,
icon: dbSchema.playlists.icon,
createdAt: dbSchema.playlists.createdAt,
updatedAt: dbSchema.playlists.updatedAt,
role: dbSchema.playlistOwnership.role,
})
.from(dbSchema.playlists)
.innerJoin(
dbSchema.playlistOwnership,
eq(dbSchema.playlistOwnership.playlistId, dbSchema.playlists.id)
)
.where(eq(dbSchema.playlistOwnership.userId, userId))
.orderBy(desc(dbSchema.playlists.updatedAt));

// Get climb counts for each playlist
const playlistIds = userPlaylists.map(p => p.id);

const climbCounts =
playlistIds.length > 0
? await db
.select({
playlistId: dbSchema.playlistClimbs.playlistId,
count: sql<number>`count(*)::int`,
})
.from(dbSchema.playlistClimbs)
.where(inArray(dbSchema.playlistClimbs.playlistId, playlistIds))
.groupBy(dbSchema.playlistClimbs.playlistId)
: [];

const countMap = new Map(climbCounts.map(c => [c.playlistId.toString(), c.count]));

return userPlaylists.map(p => ({
id: p.id.toString(),
uuid: p.uuid,
boardType: p.boardType,
layoutId: p.layoutId,
name: p.name,
description: p.description,
isPublic: p.isPublic,
color: p.color,
icon: p.icon,
createdAt: p.createdAt.toISOString(),
updatedAt: p.updatedAt.toISOString(),
climbCount: countMap.get(p.id.toString()) || 0,
userRole: p.role,
}));
},

/**
* Get all playlists owned by the authenticated user for a specific board and layout
*/
Expand Down Expand Up @@ -361,7 +431,9 @@ export const playlistQueries = {
tables.climbStats,
and(
eq(tables.climbStats.climbUuid, dbSchema.playlistClimbs.climbUuid),
eq(tables.climbStats.angle, sql`COALESCE(${dbSchema.playlistClimbs.angle}, ${tables.climbStats.angle})`)
// Only join stats when we have a specific angle in the playlist item
// When angle is NULL (Aurora-synced), stats won't match - this prevents duplicates
eq(tables.climbStats.angle, dbSchema.playlistClimbs.angle)
)
)
.leftJoin(
Expand Down
2 changes: 2 additions & 0 deletions packages/shared-schema/src/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -472,6 +472,8 @@ export const typeDefs = /* GraphQL */ `
# Playlist Queries (require auth)
# ============================================

# Get all playlists for current user (across all boards)
allUserPlaylists: [Playlist!]!
# Get current user's playlists for a board+layout
userPlaylists(input: GetUserPlaylistsInput!): [Playlist!]!
# Get a specific playlist by ID (checks ownership/access)
Expand Down
48 changes: 48 additions & 0 deletions packages/web/app/lib/__generated__/product-sizes-data.ts

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

14 changes: 14 additions & 0 deletions packages/web/app/lib/graphql/operations/playlists.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,16 @@ export const PLAYLIST_FIELDS = gql`
}
`;

// Get all user's playlists across all boards
export const GET_ALL_USER_PLAYLISTS = gql`
${PLAYLIST_FIELDS}
query GetAllUserPlaylists {
allUserPlaylists {
...PlaylistFields
}
}
`;

// Get user's playlists for a board+layout
export const GET_USER_PLAYLISTS = gql`
${PLAYLIST_FIELDS}
Expand Down Expand Up @@ -137,6 +147,10 @@ export interface Playlist {
userRole?: string;
}

export interface GetAllUserPlaylistsQueryResponse {
allUserPlaylists: Playlist[];
}

export interface GetUserPlaylistsInput {
boardType: string;
layoutId: number;
Expand Down
29 changes: 29 additions & 0 deletions packages/web/app/playlists/[playlist_uuid]/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import React from 'react';
import { getServerSession } from 'next-auth/next';
import { Metadata } from 'next';
import { authOptions } from '@/app/lib/auth/auth-options';
import PlaylistViewContent from './playlist-view-content';
import styles from './playlist-view.module.css';

export const metadata: Metadata = {
title: 'Playlist | Boardsesh',
description: 'View playlist details and climbs',
};

type PlaylistViewPageParams = {
playlist_uuid: string;
};

export default async function PlaylistViewPage(props: { params: Promise<PlaylistViewPageParams> }) {
const params = await props.params;
const session = await getServerSession(authOptions);

return (
<div className={styles.pageContainer}>
<PlaylistViewContent
playlistUuid={params.playlist_uuid}
currentUserId={session?.user?.id}
/>
</div>
);
}
Loading
Loading