From f469db5a283b02c5aa76e09b7103475c48da6919 Mon Sep 17 00:00:00 2001 From: Gabe Farrell Date: Thu, 20 Nov 2025 22:43:06 -0500 Subject: [PATCH] feat: gate all status behind login --- client/app/components/LastPlays.tsx | 2 + client/app/components/TopAlbums.tsx | 93 ++++++++++++++++----------- client/app/components/TopArtists.tsx | 81 +++++++++++++----------- client/app/components/TopTracks.tsx | 95 ++++++++++++++++------------ engine/routes.go | 32 ++++++---- internal/cfg/cfg.go | 12 ++++ 6 files changed, 188 insertions(+), 127 deletions(-) diff --git a/client/app/components/LastPlays.tsx b/client/app/components/LastPlays.tsx index c6687b4..af95bf0 100644 --- a/client/app/components/LastPlays.tsx +++ b/client/app/components/LastPlays.tsx @@ -72,6 +72,8 @@ export default function LastPlays(props: Props) { return

Error: {error.message}

; } + if (!data.items) return; + const listens = items ?? data.items; let params = ""; diff --git a/client/app/components/TopAlbums.tsx b/client/app/components/TopAlbums.tsx index 4ae87bd..6f034c5 100644 --- a/client/app/components/TopAlbums.tsx +++ b/client/app/components/TopAlbums.tsx @@ -1,42 +1,63 @@ -import { useQuery } from "@tanstack/react-query" -import ArtistLinks from "./ArtistLinks" -import { getTopAlbums, getTopTracks, imageUrl, type getItemsArgs } from "api/api" -import { Link } from "react-router" -import TopListSkeleton from "./skeletons/TopListSkeleton" -import TopItemList from "./TopItemList" +import { useQuery } from "@tanstack/react-query"; +import ArtistLinks from "./ArtistLinks"; +import { + getTopAlbums, + getTopTracks, + imageUrl, + type getItemsArgs, +} from "api/api"; +import { Link } from "react-router"; +import TopListSkeleton from "./skeletons/TopListSkeleton"; +import TopItemList from "./TopItemList"; interface Props { - limit: number, - period: string, - artistId?: Number + limit: number; + period: string; + artistId?: Number; } -export default function TopAlbums (props: Props) { - - const { isPending, isError, data, error } = useQuery({ - queryKey: ['top-albums', {limit: props.limit, period: props.period, artistId: props.artistId, page: 0 }], - queryFn: ({ queryKey }) => getTopAlbums(queryKey[1] as getItemsArgs), - }) - - if (isPending) { - return ( -
-

Top Albums

-

Loading...

-
- ) - } - if (isError) { - return

Error:{error.message}

- } +export default function TopAlbums(props: Props) { + const { isPending, isError, data, error } = useQuery({ + queryKey: [ + "top-albums", + { + limit: props.limit, + period: props.period, + artistId: props.artistId, + page: 0, + }, + ], + queryFn: ({ queryKey }) => getTopAlbums(queryKey[1] as getItemsArgs), + }); + if (isPending) { return ( -
-

Top Albums

-
- - {data.items.length < 1 ? 'Nothing to show' : ''} -
-
- ) -} \ No newline at end of file +
+

Top Albums

+

Loading...

+
+ ); + } + if (isError) { + return

Error:{error.message}

; + } + if (!data.items) return; + + return ( +
+

+ + Top Albums + +

+
+ + {data.items.length < 1 ? "Nothing to show" : ""} +
+
+ ); +} diff --git a/client/app/components/TopArtists.tsx b/client/app/components/TopArtists.tsx index 1c7b719..fbe83ee 100644 --- a/client/app/components/TopArtists.tsx +++ b/client/app/components/TopArtists.tsx @@ -1,43 +1,50 @@ -import { useQuery } from "@tanstack/react-query" -import ArtistLinks from "./ArtistLinks" -import { getTopArtists, imageUrl, type getItemsArgs } from "api/api" -import { Link } from "react-router" -import TopListSkeleton from "./skeletons/TopListSkeleton" -import TopItemList from "./TopItemList" +import { useQuery } from "@tanstack/react-query"; +import ArtistLinks from "./ArtistLinks"; +import { getTopArtists, imageUrl, type getItemsArgs } from "api/api"; +import { Link } from "react-router"; +import TopListSkeleton from "./skeletons/TopListSkeleton"; +import TopItemList from "./TopItemList"; interface Props { - limit: number, - period: string, - artistId?: Number - albumId?: Number + limit: number; + period: string; + artistId?: Number; + albumId?: Number; } -export default function TopArtists (props: Props) { - - const { isPending, isError, data, error } = useQuery({ - queryKey: ['top-artists', {limit: props.limit, period: props.period, page: 0 }], - queryFn: ({ queryKey }) => getTopArtists(queryKey[1] as getItemsArgs), - }) - - if (isPending) { - return ( -
-

Top Artists

-

Loading...

-
- ) - } - if (isError) { - return

Error:{error.message}

- } +export default function TopArtists(props: Props) { + const { isPending, isError, data, error } = useQuery({ + queryKey: [ + "top-artists", + { limit: props.limit, period: props.period, page: 0 }, + ], + queryFn: ({ queryKey }) => getTopArtists(queryKey[1] as getItemsArgs), + }); + if (isPending) { return ( -
-

Top Artists

-
- - {data.items.length < 1 ? 'Nothing to show' : ''} -
-
- ) -} \ No newline at end of file +
+

Top Artists

+

Loading...

+
+ ); + } + if (isError) { + return

Error:{error.message}

; + } + if (!data.items) return; + + return ( +
+

+ + Top Artists + +

+
+ + {data.items.length < 1 ? "Nothing to show" : ""} +
+
+ ); +} diff --git a/client/app/components/TopTracks.tsx b/client/app/components/TopTracks.tsx index b1d14c7..5dc3950 100644 --- a/client/app/components/TopTracks.tsx +++ b/client/app/components/TopTracks.tsx @@ -1,50 +1,63 @@ -import { useQuery } from "@tanstack/react-query" -import ArtistLinks from "./ArtistLinks" -import { getTopTracks, imageUrl, type getItemsArgs } from "api/api" -import { Link } from "react-router" -import TopListSkeleton from "./skeletons/TopListSkeleton" -import { useEffect } from "react" -import TopItemList from "./TopItemList" +import { useQuery } from "@tanstack/react-query"; +import ArtistLinks from "./ArtistLinks"; +import { getTopTracks, imageUrl, type getItemsArgs } from "api/api"; +import { Link } from "react-router"; +import TopListSkeleton from "./skeletons/TopListSkeleton"; +import { useEffect } from "react"; +import TopItemList from "./TopItemList"; interface Props { - limit: number, - period: string, - artistId?: Number - albumId?: Number + limit: number; + period: string; + artistId?: Number; + albumId?: Number; } const TopTracks = (props: Props) => { + const { isPending, isError, data, error } = useQuery({ + queryKey: [ + "top-tracks", + { + limit: props.limit, + period: props.period, + artist_id: props.artistId, + album_id: props.albumId, + page: 0, + }, + ], + queryFn: ({ queryKey }) => getTopTracks(queryKey[1] as getItemsArgs), + }); - const { isPending, isError, data, error } = useQuery({ - queryKey: ['top-tracks', {limit: props.limit, period: props.period, artist_id: props.artistId, album_id: props.albumId, page: 0}], - queryFn: ({ queryKey }) => getTopTracks(queryKey[1] as getItemsArgs), - }) + if (isPending) { + return ( +
+

Top Tracks

+

Loading...

+
+ ); + } + if (isError) { + return

Error:{error.message}

; + } + if (!data.items) return; - if (isPending) { - return ( -
-

Top Tracks

-

Loading...

-
- ) - } - if (isError) { - return

Error:{error.message}

- } - - let params = '' - params += props.artistId ? `&artist_id=${props.artistId}` : '' - params += props.albumId ? `&album_id=${props.albumId}` : '' + let params = ""; + params += props.artistId ? `&artist_id=${props.artistId}` : ""; + params += props.albumId ? `&album_id=${props.albumId}` : ""; - return ( -
-

Top Tracks

-
- - {data.items.length < 1 ? 'Nothing to show' : ''} -
-
- ) -} + return ( +
+

+ + Top Tracks + +

+
+ + {data.items.length < 1 ? "Nothing to show" : ""} +
+
+ ); +}; -export default TopTracks \ No newline at end of file +export default TopTracks; diff --git a/engine/routes.go b/engine/routes.go index e218752..e792e25 100644 --- a/engine/routes.go +++ b/engine/routes.go @@ -36,19 +36,25 @@ func bindRoutes( r.Route("/apis/web/v1", func(r chi.Router) { r.Get("/config", handlers.GetCfgHandler()) - r.Get("/artist", handlers.GetArtistHandler(db)) - r.Get("/artists", handlers.GetArtistsForItemHandler(db)) - r.Get("/album", handlers.GetAlbumHandler(db)) - r.Get("/track", handlers.GetTrackHandler(db)) - r.Get("/top-tracks", handlers.GetTopTracksHandler(db)) - r.Get("/top-albums", handlers.GetTopAlbumsHandler(db)) - r.Get("/top-artists", handlers.GetTopArtistsHandler(db)) - r.Get("/listens", handlers.GetListensHandler(db)) - r.Get("/listen-activity", handlers.GetListenActivityHandler(db)) - r.Get("/now-playing", handlers.NowPlayingHandler(db)) - r.Get("/stats", handlers.StatsHandler(db)) - r.Get("/search", handlers.SearchHandler(db)) - r.Get("/aliases", handlers.GetAliasesHandler(db)) + + r.Group(func(r chi.Router) { + if cfg.LoginGate() { + r.Use(middleware.ValidateSession(db)) + } + r.Get("/artist", handlers.GetArtistHandler(db)) + r.Get("/artists", handlers.GetArtistsForItemHandler(db)) + r.Get("/album", handlers.GetAlbumHandler(db)) + r.Get("/track", handlers.GetTrackHandler(db)) + r.Get("/top-tracks", handlers.GetTopTracksHandler(db)) + r.Get("/top-albums", handlers.GetTopAlbumsHandler(db)) + r.Get("/top-artists", handlers.GetTopArtistsHandler(db)) + r.Get("/listens", handlers.GetListensHandler(db)) + r.Get("/listen-activity", handlers.GetListenActivityHandler(db)) + r.Get("/now-playing", handlers.NowPlayingHandler(db)) + r.Get("/stats", handlers.StatsHandler(db)) + r.Get("/search", handlers.SearchHandler(db)) + r.Get("/aliases", handlers.GetAliasesHandler(db)) + }) r.Post("/logout", handlers.LogoutHandler(db)) if !cfg.RateLimitDisabled() { r.With(httprate.Limit( diff --git a/internal/cfg/cfg.go b/internal/cfg/cfg.go index 8f40a36..9e537eb 100644 --- a/internal/cfg/cfg.go +++ b/internal/cfg/cfg.go @@ -47,6 +47,7 @@ const ( IMPORT_AFTER_UNIX_ENV = "KOITO_IMPORT_AFTER_UNIX" FETCH_IMAGES_DURING_IMPORT_ENV = "KOITO_FETCH_IMAGES_DURING_IMPORT" ARTIST_SEPARATORS_ENV = "KOITO_ARTIST_SEPARATORS_REGEX" + LOGIN_GATE_ENV = "KOITO_LOGIN_GATE" ) type config struct { @@ -83,6 +84,7 @@ type config struct { importBefore time.Time importAfter time.Time artistSeparators []*regexp.Regexp + loginGate bool } var ( @@ -204,6 +206,10 @@ func loadConfig(getenv func(string) string, version string) (*config, error) { cfg.artistSeparators = []*regexp.Regexp{regexp.MustCompile(`\s+ยท\s+`)} } + if strings.ToLower(getenv(LOGIN_GATE_ENV)) == "true" { + cfg.loginGate = true + } + switch strings.ToLower(getenv(LOG_LEVEL_ENV)) { case "debug": cfg.logLevel = 0 @@ -409,3 +415,9 @@ func ArtistSeparators() []*regexp.Regexp { defer lock.RUnlock() return globalConfig.artistSeparators } + +func LoginGate() bool { + lock.RLock() + defer lock.RUnlock() + return globalConfig.loginGate +}