diff --git a/apps/api/src/subscribers/index.ts b/apps/api/src/subscribers/index.ts index 8410f27c..019d7ecb 100644 --- a/apps/api/src/subscribers/index.ts +++ b/apps/api/src/subscribers/index.ts @@ -1,6 +1,10 @@ import { Context } from "context"; import { onNewPlaylist } from "./playlist"; +import { onNewTrack } from "./track"; +import { onNewUser } from "./user"; export default function subscribe(ctx: Context) { onNewPlaylist(ctx); + onNewTrack(ctx); + onNewUser(ctx); } diff --git a/apps/api/src/subscribers/track.ts b/apps/api/src/subscribers/track.ts new file mode 100644 index 00000000..ff5545bf --- /dev/null +++ b/apps/api/src/subscribers/track.ts @@ -0,0 +1,51 @@ +import chalk from "chalk"; +import { Context } from "context"; +import { eq } from "drizzle-orm"; +import _ from "lodash"; +import { StringCodec } from "nats"; +import tables from "schema"; + +export function onNewTrack(ctx: Context) { + const sc = StringCodec(); + const sub = ctx.nc.subscribe("rocksky.track"); + (async () => { + for await (const m of sub) { + const payload: { + track: { xata_id: string }; + artist_album: { + artist_id: { xata_id: string }; + album_id: { xata_id: string }; + }; + } = JSON.parse(sc.decode(m.data)); + + const [tracks, artists, albums] = await Promise.all([ + ctx.db + .select() + .from(tables.tracks) + .where(eq(tables.tracks.id, payload.track.xata_id)) + .execute(), + ctx.db + .select() + .from(tables.artists) + .where(eq(tables.artists.id, payload.artist_album.artist_id.xata_id)) + .execute(), + ctx.db + .select() + .from(tables.albums) + .where(eq(tables.albums.id, payload.artist_album.album_id.xata_id)) + .execute(), + ]); + + console.log(`New track: ${chalk.cyan(_.get(tracks, "0.title"))}`); + + await Promise.all([ + ctx.meilisearch.post(`indexes/albums/documents?primaryKey=id`, albums), + ctx.meilisearch.post( + `indexes/artists/documents?primaryKey=id`, + artists + ), + ctx.meilisearch.post(`indexes/tracks/documents?primaryKey=id`, tracks), + ]); + } + })(); +} diff --git a/apps/api/src/subscribers/user.ts b/apps/api/src/subscribers/user.ts new file mode 100644 index 00000000..d03702aa --- /dev/null +++ b/apps/api/src/subscribers/user.ts @@ -0,0 +1,30 @@ +import chalk from "chalk"; +import { Context } from "context"; +import { eq } from "drizzle-orm"; +import _ from "lodash"; +import { StringCodec } from "nats"; +import tables from "schema"; + +export function onNewUser(ctx: Context) { + const sc = StringCodec(); + const sub = ctx.nc.subscribe("rocksky.user"); + (async () => { + for await (const m of sub) { + const payload: { + xata_id: string; + } = JSON.parse(sc.decode(m.data)); + const results = await ctx.db + .select() + .from(tables.users) + .where(eq(tables.users.id, payload.xata_id)) + .execute(); + + console.log(`New user: ${chalk.cyan(_.get(results, "0.handle"))}`); + + await ctx.meilisearch.post( + `/indexes/users/documents?primaryKey=id`, + results + ); + } + })(); +} diff --git a/apps/web/src/hooks/useSearch.tsx b/apps/web/src/hooks/useSearch.tsx index f4f6cfca..b4b23477 100644 --- a/apps/web/src/hooks/useSearch.tsx +++ b/apps/web/src/hooks/useSearch.tsx @@ -1,20 +1,7 @@ import { useMutation } from "@tanstack/react-query"; -import axios from "axios"; import { search } from "../api/search"; -import { API_URL } from "../consts"; export const useSearchMutation = () => useMutation({ mutationFn: (query: string) => search(query), }); - -function useSearch() { - const search = async (query: string) => { - const response = await axios.get(`${API_URL}/search?q=${query}&size=100`); - return response.data; - }; - - return { search }; -} - -export default useSearch; diff --git a/apps/web/src/layouts/Search/Search.tsx b/apps/web/src/layouts/Search/Search.tsx index c74ca510..f0759d88 100644 --- a/apps/web/src/layouts/Search/Search.tsx +++ b/apps/web/src/layouts/Search/Search.tsx @@ -5,14 +5,14 @@ import { Search as SearchIcon } from "@styled-icons/evaicons-solid"; import { Input } from "baseui/input"; import { PLACEMENT, Popover } from "baseui/popover"; import _ from "lodash"; -import { useCallback, useEffect, useState } from "react"; +import { useEffect, useState } from "react"; import { Controller, useForm } from "react-hook-form"; import { Link as DefaultLink } from "react-router"; import z from "zod"; import Artist from "../../components/Icons/Artist"; import Disc from "../../components/Icons/Disc"; import Track from "../../components/Icons/Track"; -import useSearch, { useSearchMutation } from "../../hooks/useSearch"; +import { useSearchMutation } from "../../hooks/useSearch"; const Link = styled(DefaultLink)` color: initial; @@ -28,11 +28,9 @@ const schema = z.object({ }); function Search() { - const [results, setResults] = useState([]); - + const [results, setResults] = useState([]); const { mutate, data } = useSearchMutation(); - const { search } = useSearch(); const { control, formState: { errors }, @@ -47,15 +45,9 @@ function Search() { const keyword = watch("keyword"); - // eslint-disable-next-line react-hooks/exhaustive-deps - const debouncedSearch = useCallback( - _.debounce(async (keyword) => { - mutate(keyword); - const data = await search(keyword); - setResults(data.records); - }, 300), - [search] - ); + const debouncedSearch = _.debounce(async (keyword) => { + mutate(keyword); + }, 200); useEffect(() => { if (keyword.length === 0) { @@ -66,7 +58,13 @@ function Search() { // eslint-disable-next-line react-hooks/exhaustive-deps }, [keyword]); - console.log(">> data", data); + useEffect(() => { + if (data && data.hits) { + setResults(data.hits); + } else { + setResults([]); + } + }, [data]); return ( <> @@ -83,56 +81,53 @@ function Search() { <> {results.map((item: any) => ( <> - {item.table === "users" && ( - + {item._federation.indexUid === "users" && ( +
{item.record.display_name}
- {item.record.display_name} + {item.displayName}
- @{item.record.handle} + @{item.handle}
)} - {item.record.uri && - (item.record.name || item.record.title) && - item.record.type !== "users" && ( + {item.uri && + (item.name || item.title) && + item._federation.indexUid !== "users" && (
- {item.table === "artists" && - item.record.picture && ( + {item._federation.indexUid === "artists" && + item.picture && ( {item.record.name} )} - {item.table === "artists" && - !item.record.picture && ( + {item._federation.indexUid === "artists" && + !item.picture && (
@@ -140,17 +135,17 @@ function Search() {
)} - {item.table === "albums" && - item.record.album_art && ( + {item._federation.indexUid === "albums" && + item.albumArt && ( {item.record.title} )} - {item.table === "albums" && - !item.record.album_art && ( + {item._federation.indexUid === "albums" && + !item.albumArt && (
)} - {item.table === "tracks" && - item.record.album_art && ( + {item._federation.indexUid === "tracks" && + item.albumArt && ( {item.record.title} )} - {item.table === "tracks" && - !item.record.album_art && ( + {item._federation.indexUid === "tracks" && + !item.albumArt && (
@@ -180,19 +175,19 @@ function Search() { )}
- {item.record.name || item.record.title} + {item.name || item.title}
- {item.table === "tracks" && ( + {item._federation.indexUid === "tracks" && (
Track
)} - {item.table === "albums" && ( + {item._federation.indexUid === "albums" && (
Album
)} - {item.table === "artists" && ( + {item._federation.indexUid === "artists" && (
Artist
@@ -201,63 +196,64 @@ function Search() {
)} - {!item.record.uri && - (item.record.name || item.record.title) && - item.tables !== "users" && ( + {!item.uri && + (item.name || item.title) && + item._federation.indexUid !== "users" && (
- {item.table === "artists" && - item.record.picture && ( + {item._federation.indexUid === "artists" && + item.picture && ( {item.record.name} )} - {item.table === "artists" && - !item.record.picture && ( + {item._federation.indexUid === "artists" && + !item.picture && (
)} - {item.table === "albums" && - item.record.album_art && ( + {item._federation.indexUid === "albums" && + item.albumArt && ( {item.record.title} )} - {item.table === "tracks" && ( + {item._federation.indexUid === "tracks" && ( {item.record.title} )} {["artists", "albums", "tracks"].includes( - item.table + item._federation.indexUid ) && (
- {item.record.name || item.record.title} + {item.name || item.title}
- {item.table === "tracks" && ( + {item._federation.indexUid === "tracks" && (
Track
)} - {item.table === "albums" && ( + {item._federation.indexUid === "albums" && (
Album
)} - {item.table === "artists" && ( + {item._federation.indexUid === + "artists" && (
Artist