Your music, beautifully tracked. All yours. (coming soon) teal.fm
teal-fm atproto

Merge pull request #64 from teal-fm/prettier-format

prettier format & build fix

authored by mmatt.net and committed by GitHub 1e609c92 6ee615ca

Changed files
+144 -160
apps
packages
db
lexicons
src
types
fm
teal
alpha
+7 -6
README.md
··· 26 26 27 27 Open http://localhost:3000/ with your browser to see the home page. You will need to login with Bluesky to test the posting functionality of the app. Note: if the redirect back to the app after you login isn't working correctly, you may need to replace the `127.0.0.1` with `localhost`. 28 28 29 - 30 29 ### Running the full stack in docker for development 30 + 31 31 _Still a work in progress and may have some hiccups_ 32 32 33 - #### 1. Set up publicly accessible endpoints. 33 + #### 1. Set up publicly accessible endpoints. 34 + 34 35 It is recommended if you are running aqua to make a publicly accessible endpoint for the app to post to. You can do that a couple of ways: 35 36 36 - * Set up the traditional port forward on your router 37 - * Use a tool like [ngrok](https://ngrok.com/) with the command `ngrok http 8080` or [Cloudflare tunnels](https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/get-started/create-remote-tunnel/) (follow the 2a. portion of the guide when you get to that point). 37 + - Set up the traditional port forward on your router 38 + - Use a tool like [ngrok](https://ngrok.com/) with the command `ngrok http 8080` or [Cloudflare tunnels](https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/get-started/create-remote-tunnel/) (follow the 2a. portion of the guide when you get to that point). 38 39 39 40 If you do the cloudflare tunnels for amethyst as well, 40 41 you will also need ··· 42 43 Although you can just run it locally and it will work. 43 44 44 45 #### 2. Set up .envs 46 + 45 47 1. copy [.env.template](.env.template) and name it [.env](.env). The docker build will pull everything from this `.env`. There are notes in the [.env.template](.env.template) on what some of the values should be. 46 48 2. Follow the instructions in [piper](https://github.com/teal-fm/piper) to set up environment variables for the music scraper. But name it `.env.air` 47 49 48 50 #### 3. Run docker 51 + 49 52 1. Make sure docker and docker compose is installed 50 53 2. It is recommended to run 'docker compose -f compose.dev.yml pull' to pull the latest remote images before running the docker compose command. 51 54 3. Run `docker compose -f compose.dev.yml up -d` 52 55 53 - 54 56 And that's it! You should have the full teal.fm stack running locally. Now if you are working on aqua you can do `docker container stop aqua-app` and run that locally during development while everything else is running in docker. 55 -
+3 -3
apps/amethyst/app/(tabs)/(stamp)/stamp/index.tsx
··· 86 86 }} 87 87 /> 88 88 {/* Search Form */} 89 - <View className="flex gap-2 max-w-2xl w-screen px-4"> 90 - <Text className="font-bold text-lg">Search for a track</Text> 89 + <View className="flex w-screen max-w-2xl gap-2 px-4"> 90 + <Text className="text-lg font-bold">Search for a track</Text> 91 91 <Input 92 92 placeholder="Track name..." 93 93 value={searchFields.track} ··· 124 124 } 125 125 }} 126 126 /> 127 - <View className="flex-row gap-2 mt-2"> 127 + <View className="mt-2 flex-row gap-2"> 128 128 <Button 129 129 className="flex-1" 130 130 onPress={handleSearch}
+11 -10
apps/amethyst/app/(tabs)/(stamp)/stamp/submit.tsx
··· 1 + import { useContext, useEffect, useState } from "react"; 2 + import { Image, Switch, View } from "react-native"; 3 + import { Redirect, Stack, useRouter } from "expo-router"; 1 4 import { ExternalLink } from "@/components/ExternalLink"; 2 5 import VerticalPlayView from "@/components/play/verticalPlayView"; 3 6 import { Button } from "@/components/ui/button"; 4 7 import { Text } from "@/components/ui/text"; 8 + import { Textarea } from "@/components/ui/textarea"; 5 9 import { MusicBrainzRecording, PlaySubmittedData } from "@/lib/oldStamp"; 10 + import { cn } from "@/lib/utils"; 6 11 import { useStore } from "@/stores/mainStore"; 7 12 import { 8 13 Agent, ··· 10 15 ComAtprotoRepoCreateRecord, 11 16 RichText, 12 17 } from "@atproto/api"; 13 - import { Redirect, Stack, useRouter } from "expo-router"; 14 - import { useContext, useEffect, useState } from "react"; 15 - import { Image, Switch, View } from "react-native"; 16 18 17 - import { Textarea } from "@/components/ui/textarea"; 18 - import { cn } from "@/lib/utils"; 19 19 import { Artist } from "@teal/lexicons/src/types/fm/teal/alpha/feed/defs"; 20 20 import { 21 21 Record as PlayRecord, 22 22 validateRecord, 23 23 } from "@teal/lexicons/src/types/fm/teal/alpha/feed/play"; 24 + 24 25 import { StampContext, StampContextValue, StampStep } from "./_layout"; 25 26 26 27 type CardyBResponse = { ··· 341 342 </Text> 342 343 </View> 343 344 344 - <View className="flex-col gap-4 items-center w-full"> 345 + <View className="w-full flex-col items-center gap-4"> 345 346 {blueskyEmbedCard && shareWithBluesky ? ( 346 347 <View className="w-full gap-2"> 347 348 <Text className="text-center text-sm text-muted-foreground"> ··· 372 373 ) 373 374 )} 374 375 {shareWithBluesky && ( 375 - <View className="text-sm text-muted-foreground w-full items-end"> 376 + <View className="w-full items-end text-sm text-muted-foreground"> 376 377 <Textarea 377 - className="w-full p-2 pb-4 border border-border rounded-md text-card-foreground bg-card min-h-[100px] max-h-[200px]" 378 + className="max-h-[200px] min-h-[100px] w-full rounded-md border border-border bg-card p-2 pb-4 text-card-foreground" 378 379 multiline 379 380 value={blueskyPostText} 380 381 onChangeText={setBlueskyPostText} ··· 382 383 /> 383 384 <Text 384 385 className={cn( 385 - "text-sm text-muted-foreground text-center absolute bottom-1 right-2", 386 + "absolute bottom-1 right-2 text-center text-sm text-muted-foreground", 386 387 blueskyPostText.length > 150 387 388 ? "text-gray-600 dark:text-gray-300" 388 389 : "", ··· 393 394 </Text> 394 395 </View> 395 396 )} 396 - <View className="flex-row gap-2 items-center"> 397 + <View className="flex-row items-center gap-2"> 397 398 <Switch 398 399 value={shareWithBluesky} 399 400 onValueChange={setShareWithBluesky}
+6 -6
apps/amethyst/app/(tabs)/_layout.tsx
··· 1 1 import React from "react"; 2 + import { Pressable } from "react-native"; 3 + import { Link, Tabs } from "expo-router"; 4 + import useIsMobile from "@/hooks/useIsMobile"; 5 + //import useIsMobile from "@/hooks/useIsMobile"; 6 + import { useStore } from "@/stores/mainStore"; 2 7 import { 3 8 FilePen, 4 9 Home, ··· 7 12 Settings, 8 13 type LucideIcon, 9 14 } from "lucide-react-native"; 10 - import { Link, Tabs } from "expo-router"; 11 - import { Pressable } from "react-native"; 15 + import { useColorScheme } from "nativewind"; 12 16 13 17 import Colors from "../../constants/Colors"; 14 18 import { Icon, iconWithClassName } from "../../lib/icons/iconWithClassName"; 15 - //import useIsMobile from "@/hooks/useIsMobile"; 16 - import { useStore } from "@/stores/mainStore"; 17 - import { useColorScheme } from "nativewind"; 18 19 import AuthOptions from "../auth/options"; 19 - import useIsMobile from "@/hooks/useIsMobile"; 20 20 21 21 function TabBarIcon(props: { name: LucideIcon; color: string }) { 22 22 const Name = props.name;
+3 -3
apps/amethyst/app/(tabs)/profile/[handle].tsx
··· 1 + import { useEffect, useState } from "react"; 2 + import { ActivityIndicator, ScrollView, View } from "react-native"; 3 + import { Stack, useLocalSearchParams } from "expo-router"; 1 4 import ActorView from "@/components/actor/actorView"; 2 5 import { Text } from "@/components/ui/text"; 3 6 import { resolveHandle } from "@/lib/atp/pid"; 4 7 import { useStore } from "@/stores/mainStore"; 5 - import { Stack, useLocalSearchParams } from "expo-router"; 6 - import { useEffect, useState } from "react"; 7 - import { ActivityIndicator, ScrollView, View } from "react-native"; 8 8 9 9 export default function Handle() { 10 10 let { handle } = useLocalSearchParams();
+7 -8
apps/amethyst/app/(tabs)/settings/index.tsx
··· 1 + import React, { useState } from "react"; 2 + import { ScrollView, Switch, View } from "react-native"; 3 + import { Link, Stack } from "expo-router"; 1 4 import { Button } from "@/components/ui/button"; 5 + import { Input } from "@/components/ui/input"; 2 6 import { Text } from "@/components/ui/text"; 3 7 import { useColorScheme } from "@/lib/useColorScheme"; 4 8 import { cn } from "@/lib/utils"; 5 - import { Link, Stack } from "expo-router"; 6 - import React, { useState } from "react"; 7 - import { ScrollView, Switch, View } from "react-native"; 8 - 9 - import { Input } from "@/components/ui/input"; 10 9 import pkg from "@/package.json"; 11 10 import { useStore } from "@/stores/mainStore"; 12 11 ··· 102 101 return ( 103 102 <View className="items-start gap-2 pt-2"> 104 103 <Text className="text-base font-semibold">{text}</Text> 105 - <View className="flex-row items-center justify-around gap-1 w-full bg-muted h-10 px-1 rounded-xl"> 104 + <View className="h-10 w-full flex-row items-center justify-around gap-1 rounded-xl bg-muted px-1"> 106 105 {values.map(({ label, value }) => ( 107 106 <Button 108 107 key={value} ··· 146 145 return ( 147 146 <View className="items-start gap-2 pt-2"> 148 147 <Text className="text-base font-semibold">{labelText}</Text> 149 - <View className="flex-row gap-2 w-full items-center"> 148 + <View className="w-full flex-row items-center gap-2"> 150 149 <Input 151 - className="border border-muted-foreground/50 bg-transparent text-foreground h-10 w-full rounded-md px-3 py-2 text-base" 150 + className="h-10 w-full rounded-md border border-muted-foreground/50 bg-transparent px-3 py-2 text-base text-foreground" 152 151 value={inputValue} 153 152 onChangeText={setInputValue} // Update internal state on change 154 153 placeholder={placeholder}
+2 -2
apps/amethyst/app/+not-found.tsx
··· 1 - import { Link, Stack } from "expo-router"; 1 + import React from "react"; 2 2 import { StyleSheet, View } from "react-native"; 3 + import { Link, Stack } from "expo-router"; 3 4 4 - import React from "react"; 5 5 import { Text } from "../components/ui/text"; 6 6 7 7 export default function NotFoundScreen() {
+3 -3
apps/amethyst/app/_layout.tsx
··· 20 20 21 21 import "../global.css"; 22 22 23 - import { BottomSheetModalProvider } from "@gorhom/bottom-sheet"; 24 23 import { SafeAreaView, View } from "react-native"; 24 + import { BottomSheetModalProvider } from "@gorhom/bottom-sheet"; 25 25 26 26 let defaultFamily = (weight: string) => { 27 27 return { ··· 85 85 } 86 86 87 87 return ( 88 - <SafeAreaView className="flex-1 flex flex-row min-h-screen justify-center bg-background"> 89 - <View className="max-w-2xl flex flex-1 border-x bg-background border-muted-foreground/20"> 88 + <SafeAreaView className="flex min-h-screen flex-1 flex-row justify-center bg-background"> 89 + <View className="flex max-w-2xl flex-1 border-x border-muted-foreground/20 bg-background"> 90 90 <RootLayoutNav /> 91 91 </View> 92 92 </SafeAreaView>
+18 -20
apps/amethyst/app/auth/login.tsx
··· 1 - import { Button } from "@/components/ui/button"; 2 - import { Input } from "@/components/ui/input"; 3 - import { Text } from "@/components/ui/text"; 4 - import { Icon } from "@/lib/icons/iconWithClassName"; 5 - import { capFirstLetter, cn } from "@/lib/utils"; 6 - import { Link, router, Stack } from "expo-router"; 7 - import { openAuthSessionAsync } from "expo-web-browser"; 8 - import { AlertCircle, AtSign, Check, ChevronRight } from "lucide-react-native"; 9 1 import React, { useCallback, useEffect, useRef, useState } from "react"; // Added useCallback, useRef 10 2 import { Platform, TextInput, View } from "react-native"; 11 - import { SafeAreaView } from "react-native-safe-area-context"; 12 - 13 - import { resolveFromIdentity } from "@/lib/atp/pid"; 14 - import { useStore } from "@/stores/mainStore"; 15 - 16 - import { FontAwesome6, MaterialCommunityIcons } from "@expo/vector-icons"; 17 3 import Animated, { 18 4 interpolate, 19 5 useAnimatedStyle, 20 6 useSharedValue, 21 7 withTiming, 22 8 } from "react-native-reanimated"; 9 + import { SafeAreaView } from "react-native-safe-area-context"; 10 + import { Link, router, Stack } from "expo-router"; 11 + import { openAuthSessionAsync } from "expo-web-browser"; 12 + import { Button } from "@/components/ui/button"; 13 + import { Input } from "@/components/ui/input"; 14 + import { Text } from "@/components/ui/text"; 15 + import { resolveFromIdentity } from "@/lib/atp/pid"; 16 + import { Icon } from "@/lib/icons/iconWithClassName"; 17 + import { capFirstLetter, cn } from "@/lib/utils"; 18 + import { useStore } from "@/stores/mainStore"; 19 + import { FontAwesome6, MaterialCommunityIcons } from "@expo/vector-icons"; 20 + import { AlertCircle, AtSign, Check, ChevronRight } from "lucide-react-native"; 23 21 24 22 type Url = URL; 25 23 ··· 218 216 headerShown: false, 219 217 }} 220 218 /> 221 - <View className="justify-center align-center p-8 gap-4 pb-32 max-w-lg w-screen"> 219 + <View className="align-center w-screen max-w-lg justify-center gap-4 p-8 pb-32"> 222 220 <View className="flex items-center"> 223 221 <Icon icon={AtSign} className="color-bsky" name="at" size={64} /> 224 222 </View> ··· 250 248 <Animated.View style={messageContainerAnimatedStyle}> 251 249 <View 252 250 className={cn( 253 - "p-2 -mt-7 rounded-xl border border-border transition-all duration-300", 251 + "-mt-7 rounded-xl border border-border p-2 transition-all duration-300", 254 252 isSelected ? "pt-9" : "pt-8", 255 253 pdsUrl !== null 256 254 ? pdsUrl.hostname.includes("bsky.network") ··· 263 261 <Text> 264 262 PDS:{" "} 265 263 {pdsUrl.hostname.includes("bsky.network") && ( 266 - <View className="gap-0.5 pr-0.5 flex-row"> 264 + <View className="flex-row gap-0.5 pr-0.5"> 267 265 <Icon 268 266 icon={FontAwesome6} 269 267 className="color-bsky" ··· 286 284 <Text className="justify-baseline px-1"> 287 285 <Icon 288 286 icon={AlertCircle} 289 - className="mr-1 inline -mt-0.5 text-xs" 287 + className="-mt-0.5 mr-1 inline text-xs" 290 288 size={24} 291 289 /> 292 290 {pdsResolutionError} 293 291 </Text> 294 292 ) : ( 295 - <Text className="text-muted-foreground px-1"> 293 + <Text className="px-1 text-muted-foreground"> 296 294 Resolving PDS... 297 295 </Text> 298 296 )} 299 297 </View> 300 298 </Animated.View> 301 299 </View> 302 - <View className="flex flex-row justify-between items-center"> 300 + <View className="flex flex-row items-center justify-between"> 303 301 <Link href="https://bsky.app/signup" asChild> 304 302 <Button variant="link" className="p-0"> 305 303 <Text className="text-md text-secondary">
+4 -4
apps/amethyst/app/offline.tsx
··· 1 1 import { View } from "react-native"; 2 + import { Link, Stack } from "expo-router"; 2 3 import { Text } from "@/components/ui/text"; 3 4 import { Icon } from "@/lib/icons/iconWithClassName"; 4 5 import { CloudOff } from "lucide-react-native"; 5 - import { Link, Stack } from "expo-router"; 6 6 7 7 export default function Offline() { 8 8 return ( 9 - <View className="flex-1 justify-center items-center gap-2"> 9 + <View className="flex-1 items-center justify-center gap-2"> 10 10 <Stack.Screen 11 11 options={{ 12 12 title: "Can't Connect", 13 13 headerBackButtonDisplayMode: "minimal", 14 14 }} 15 15 /> 16 - <View className="max-w-md justify-center items-center"> 16 + <View className="max-w-md items-center justify-center"> 17 17 <Icon icon={CloudOff} size={64} /> 18 18 <Text className="text-center">Oups! Can’t connect to teal.fm.</Text> 19 19 <Text className="text-center"> 20 20 Try again in a few seconds, or you can change the AppView{" "} 21 21 <Link href="/settings"> 22 - <Text className="text-blue-600 dark:text-blue-400 border-b border-border"> 22 + <Text className="border-b border-border text-blue-600 dark:text-blue-400"> 23 23 in Settings 24 24 </Text> 25 25 </Link>
+5 -3
apps/amethyst/components/play/actorPlaysView.tsx
··· 1 - import { useStore } from "@/stores/mainStore"; 2 - import { OutputSchema as ActorFeedResponse } from "@teal/lexicons/src/types/fm/teal/alpha/feed/getActorFeed"; 3 1 import { useEffect, useState } from "react"; 4 2 import { ScrollView } from "react-native"; 5 3 import { Text } from "@/components/ui/text"; 6 - import PlayView from "./playView"; 4 + import { useStore } from "@/stores/mainStore"; 7 5 import { Agent } from "@atproto/api"; 6 + 7 + import { OutputSchema as ActorFeedResponse } from "@teal/lexicons/src/types/fm/teal/alpha/feed/getActorFeed"; 8 + 9 + import PlayView from "./playView"; 8 10 9 11 interface ActorPlaysViewProps { 10 12 repo: string | undefined;
+2 -2
apps/amethyst/components/play/playView.tsx
··· 28 28 {trackTitle} 29 29 </Text> 30 30 {artistName && ( 31 - <Text className="text-sm text-left text-foreground line-clamp-1 overflow-ellipsis"> 31 + <Text className="line-clamp-1 overflow-ellipsis text-left text-sm text-foreground"> 32 32 {artistName} 33 33 </Text> 34 34 )} 35 35 {dateListened && ( 36 - <Text className="text-sm text-left text-muted-foreground line-clamp-1 overflow-ellipsis"> 36 + <Text className="line-clamp-1 overflow-ellipsis text-left text-sm text-muted-foreground"> 37 37 played {timeAgo(dateListened)} 38 38 </Text> 39 39 )}
+2 -1
apps/amethyst/components/ui/textarea.tsx
··· 1 1 import * as React from "react"; 2 2 import { TextInput, type TextInputProps } from "react-native"; 3 + 3 4 import { cn } from "~/lib/utils"; 4 5 5 6 const Textarea = React.forwardRef< ··· 20 21 <TextInput 21 22 ref={ref} 22 23 className={cn( 23 - "font-sans web:flex min-h-[80px] w-full rounded-md border border-input bg-background px-3 py-2 text-base lg:text-sm native:text-lg native:leading-[1.25] text-foreground web:ring-offset-background placeholder:text-muted-foreground web:focus-visible:outline-none web:focus-visible:ring-2 web:focus-visible:ring-ring web:focus-visible:ring-offset-2", 24 + "native:text-lg native:leading-[1.25] min-h-[80px] w-full rounded-md border border-input bg-background px-3 py-2 font-sans text-base text-foreground placeholder:text-muted-foreground web:flex web:ring-offset-background web:focus-visible:outline-none web:focus-visible:ring-2 web:focus-visible:ring-ring web:focus-visible:ring-offset-2 lg:text-sm", 24 25 props.editable === false && "opacity-50 web:cursor-not-allowed", 25 26 className, 26 27 )}
+1 -1
apps/amethyst/readme.md
··· 15 15 16 16 set `EXPO_PUBLIC_DID_WEB` to the domain aqua is running on. 17 17 For example if it's running at `aqua.teal.fm` it would be 18 - `EXPO_PUBLIC_DID_WEB=did:web:aqua.teal.fm`. This is how amethyst sends requests to the appview. 18 + `EXPO_PUBLIC_DID_WEB=did:web:aqua.teal.fm`. This is how amethyst sends requests to the appview. 19 19 20 20 TODO
+1 -1
apps/amethyst/stores/preferenceSlice.tsx
··· 11 11 return { 12 12 colorTheme: "system", 13 13 setColorTheme: (theme) => set({ colorTheme: theme }), 14 - tealDid: process.env.EXPO_PUBLIC_DID_WEB ?? 'did:web:rina.z.teal.fm', 14 + tealDid: process.env.EXPO_PUBLIC_DID_WEB ?? "did:web:rina.z.teal.fm", 15 15 setTealDid: (url) => set({ tealDid: url }), 16 16 }; 17 17 };
+1 -1
apps/aqua/src/index.ts
··· 84 84 (info) => { 85 85 logger.info( 86 86 `Listening on ${ 87 - info.address == '::1' 87 + info.address == "::1" 88 88 ? `http://${env.HOST}` 89 89 : // TODO: below should probably be https:// 90 90 // but i just want to ctrl click in the terminal
+4 -4
apps/aqua/src/lib/env.ts
··· 1 - import dotenv from 'dotenv'; 1 + import process from "node:process"; 2 + import dotenv from "dotenv"; 2 3 import dotenvExpand from "dotenv-expand"; 3 - import { cleanEnv, host, port, str, testOnly } from 'envalid'; 4 - import process from 'node:process'; 4 + import { cleanEnv, host, port, str, testOnly } from "envalid"; 5 5 6 6 dotenvExpand.expand(dotenv.config()); 7 7 // in case our .env file is in the root folder 8 - dotenvExpand.expand(dotenv.config({ path: './../../.env' })); 8 + dotenvExpand.expand(dotenv.config({ path: "./../../.env" })); 9 9 10 10 export const env = cleanEnv(process.env, { 11 11 NODE_ENV: str({
+2 -1
apps/aqua/src/xrpc/feed/getActorFeed.ts
··· 1 1 import { TealContext } from "@/ctx"; 2 + import { and, desc, eq, lt, sql } from "drizzle-orm"; 3 + 2 4 import { artists, db, plays, playToArtists } from "@teal/db"; 3 - import { eq, and, lt, desc, sql } from "drizzle-orm"; 4 5 import { OutputSchema } from "@teal/lexicons/src/types/fm/teal/alpha/feed/getActorFeed"; 5 6 6 7 export default async function getActorFeed(c: TealContext) {
+3 -2
apps/aqua/src/xrpc/route.ts
··· 1 1 import { EnvWithCtx } from "@/ctx"; 2 2 import { Hono } from "hono"; 3 3 import { logger } from "hono/logger"; 4 - import getPlay from "./feed/getPlay"; 5 - import getActorFeed from "./feed/getActorFeed"; 4 + 6 5 import getProfile from "./actor/getProfile"; 7 6 import searchActors from "./actor/searchActors"; 7 + import getActorFeed from "./feed/getActorFeed"; 8 + import getPlay from "./feed/getPlay"; 8 9 9 10 // mount this on /xrpc 10 11 const app = new Hono<EnvWithCtx>();
+2 -2
package.json
··· 10 10 "fix": "biome lint --apply . && biome format --write . && biome check . --apply", 11 11 "nuke": "rimraf node_modules */*/node_modules", 12 12 "lex:gen-server": "turbo lex:gen-server", 13 - "format": "prettier --write ." 14 - "db:migrate": "cd ./packages/db && drizzle-kit migrate"= 13 + "format": "prettier --write .", 14 + "db:migrate": "cd ./packages/db && drizzle-kit migrate" 15 15 }, 16 16 "dependencies": { 17 17 "@atproto/oauth-client": "^0.3.8",
+10 -29
packages/db/.drizzle/meta/0006_snapshot.json
··· 65 65 "name": "play_to_artists_artist_mbid_artists_mbid_fk", 66 66 "tableFrom": "play_to_artists", 67 67 "tableTo": "artists", 68 - "columnsFrom": [ 69 - "artist_mbid" 70 - ], 71 - "columnsTo": [ 72 - "mbid" 73 - ], 68 + "columnsFrom": ["artist_mbid"], 69 + "columnsTo": ["mbid"], 74 70 "onDelete": "no action", 75 71 "onUpdate": "no action" 76 72 }, ··· 78 74 "name": "play_to_artists_play_uri_plays_uri_fk", 79 75 "tableFrom": "play_to_artists", 80 76 "tableTo": "plays", 81 - "columnsFrom": [ 82 - "play_uri" 83 - ], 84 - "columnsTo": [ 85 - "uri" 86 - ], 77 + "columnsFrom": ["play_uri"], 78 + "columnsTo": ["uri"], 87 79 "onDelete": "no action", 88 80 "onUpdate": "no action" 89 81 } ··· 91 83 "compositePrimaryKeys": { 92 84 "play_to_artists_play_uri_artist_mbid_pk": { 93 85 "name": "play_to_artists_play_uri_artist_mbid_pk", 94 - "columns": [ 95 - "play_uri", 96 - "artist_mbid" 97 - ] 86 + "columns": ["play_uri", "artist_mbid"] 98 87 } 99 88 }, 100 89 "uniqueConstraints": {}, ··· 204 193 "name": "plays_recording_mbid_recordings_mbid_fk", 205 194 "tableFrom": "plays", 206 195 "tableTo": "recordings", 207 - "columnsFrom": [ 208 - "recording_mbid" 209 - ], 210 - "columnsTo": [ 211 - "mbid" 212 - ], 196 + "columnsFrom": ["recording_mbid"], 197 + "columnsTo": ["mbid"], 213 198 "onDelete": "no action", 214 199 "onUpdate": "no action" 215 200 }, ··· 217 202 "name": "plays_release_mbid_releases_mbid_fk", 218 203 "tableFrom": "plays", 219 204 "tableTo": "releases", 220 - "columnsFrom": [ 221 - "release_mbid" 222 - ], 223 - "columnsTo": [ 224 - "mbid" 225 - ], 205 + "columnsFrom": ["release_mbid"], 206 + "columnsTo": ["mbid"], 226 207 "onDelete": "no action", 227 208 "onUpdate": "no action" 228 209 } ··· 599 580 "schemas": {}, 600 581 "tables": {} 601 582 } 602 - } 583 + }
+2 -4
packages/db/drizzle.config.ts
··· 1 - import { defineConfig } from "drizzle-kit"; 2 1 import dotenv from "dotenv"; 3 2 import dotenvExpand from "dotenv-expand"; 3 + import { defineConfig } from "drizzle-kit"; 4 4 5 5 //Loads from root .env 6 - dotenvExpand.expand(dotenv.config({ path: '../../.env' })); 6 + dotenvExpand.expand(dotenv.config({ path: "../../.env" })); 7 7 //Or can be overridden by .env in the current folder 8 8 dotenvExpand.expand(dotenv.config()); 9 - 10 - 11 9 12 10 export default defineConfig({ 13 11 dialect: "postgresql",
+21 -21
packages/lexicons/src/lexicons.ts
··· 432 432 description: "The unix timestamp of when the item was recorded", 433 433 }, 434 434 expiry: { 435 - type: 'string', 436 - format: 'datetime', 435 + type: "string", 436 + format: "datetime", 437 437 description: 438 - 'The unix timestamp of the expiry time of the item. If unavailable, default to 10 minutes past the start time.', 438 + "The unix timestamp of the expiry time of the item. If unavailable, default to 10 minutes past the start time.", 439 439 }, 440 440 item: { 441 441 type: "ref", ··· 453 453 "This lexicon is in a not officially released state. It is subject to change. | Misc. items related to feeds.", 454 454 defs: { 455 455 playView: { 456 - type: 'object', 457 - required: ['trackName', 'artists'], 456 + type: "object", 457 + required: ["trackName", "artists"], 458 458 properties: { 459 459 trackName: { 460 460 type: "string", ··· 476 476 description: "The length of the track in seconds", 477 477 }, 478 478 artists: { 479 - type: 'array', 479 + type: "array", 480 480 items: { 481 - type: 'ref', 482 - ref: 'lex:fm.teal.alpha.feed.defs#artist', 481 + type: "ref", 482 + ref: "lex:fm.teal.alpha.feed.defs#artist", 483 483 }, 484 - description: 'Array of artists in order of original appearance.', 484 + description: "Array of artists in order of original appearance.", 485 485 }, 486 486 releaseName: { 487 487 type: "string", ··· 521 521 }, 522 522 }, 523 523 artist: { 524 - type: 'object', 525 - required: ['artistName'], 524 + type: "object", 525 + required: ["artistName"], 526 526 properties: { 527 527 artistName: { 528 - type: 'string', 528 + type: "string", 529 529 minLength: 1, 530 530 maxLength: 256, 531 531 maxGraphemes: 2560, 532 - description: 'The name of the artist', 532 + description: "The name of the artist", 533 533 }, 534 534 artistMbId: { 535 - type: 'string', 536 - description: 'The Musicbrainz ID of the artist', 535 + type: "string", 536 + description: "The Musicbrainz ID of the artist", 537 537 }, 538 538 }, 539 539 }, ··· 635 635 type: "record", 636 636 key: "tid", 637 637 record: { 638 - type: 'object', 639 - required: ['trackName'], 638 + type: "object", 639 + required: ["trackName"], 640 640 properties: { 641 641 trackName: { 642 642 type: "string", ··· 677 677 "Array of Musicbrainz artist IDs. Prefer using 'artists'.", 678 678 }, 679 679 artists: { 680 - type: 'array', 680 + type: "array", 681 681 items: { 682 - type: 'ref', 683 - ref: 'lex:fm.teal.alpha.feed.defs#artist', 682 + type: "ref", 683 + ref: "lex:fm.teal.alpha.feed.defs#artist", 684 684 }, 685 - description: 'Array of artists in order of original appearance.', 685 + description: "Array of artists in order of original appearance.", 686 686 }, 687 687 releaseName: { 688 688 type: "string",
+4 -4
packages/lexicons/src/types/fm/teal/alpha/actor/status.ts
··· 10 10 11 11 export interface Record { 12 12 /** The unix timestamp of when the item was recorded */ 13 - time: string 13 + time: string; 14 14 /** The unix timestamp of the expiry time of the item. If unavailable, default to 10 minutes past the start time. */ 15 - expiry?: string 16 - item: FmTealAlphaFeedDefs.PlayView 17 - [k: string]: unknown 15 + expiry?: string; 16 + item: FmTealAlphaFeedDefs.PlayView; 17 + [k: string]: unknown; 18 18 } 19 19 20 20 export function isRecord(v: unknown): v is Record {
+9 -9
packages/lexicons/src/types/fm/teal/alpha/feed/defs.ts
··· 15 15 /** The Musicbrainz recording ID of the track */ 16 16 recordingMbId?: string; 17 17 /** The length of the track in seconds */ 18 - duration?: number 18 + duration?: number; 19 19 /** Array of artists in order of original appearance. */ 20 - artists: Artist[] 20 + artists: Artist[]; 21 21 /** The name of the release/album */ 22 22 releaseName?: string; 23 23 /** The Musicbrainz release ID */ ··· 49 49 50 50 export interface Artist { 51 51 /** The name of the artist */ 52 - artistName: string 52 + artistName: string; 53 53 /** The Musicbrainz ID of the artist */ 54 - artistMbId?: string 55 - [k: string]: unknown 54 + artistMbId?: string; 55 + [k: string]: unknown; 56 56 } 57 57 58 58 export function isArtist(v: unknown): v is Artist { 59 59 return ( 60 60 isObj(v) && 61 - hasProp(v, '$type') && 62 - v.$type === 'fm.teal.alpha.feed.defs#artist' 63 - ) 61 + hasProp(v, "$type") && 62 + v.$type === "fm.teal.alpha.feed.defs#artist" 63 + ); 64 64 } 65 65 66 66 export function validateArtist(v: unknown): ValidationResult { 67 - return lexicons.validate('fm.teal.alpha.feed.defs#artist', v) 67 + return lexicons.validate("fm.teal.alpha.feed.defs#artist", v); 68 68 }
+10 -9
packages/lexicons/src/types/fm/teal/alpha/feed/play.ts
··· 1 1 /** 2 2 * GENERATED CODE - DO NOT MODIFY 3 3 */ 4 - import { ValidationResult, BlobRef } from '@atproto/lexicon' 5 - import { lexicons } from '../../../../../lexicons' 6 - import { isObj, hasProp } from '../../../../../util' 7 - import { CID } from 'multiformats/cid' 8 - import * as FmTealAlphaFeedDefs from './defs' 4 + import { BlobRef, ValidationResult } from "@atproto/lexicon"; 5 + import { CID } from "multiformats/cid"; 6 + 7 + import { lexicons } from "../../../../../lexicons"; 8 + import { hasProp, isObj } from "../../../../../util"; 9 + import * as FmTealAlphaFeedDefs from "./defs"; 9 10 10 11 export interface Record { 11 12 /** The name of the track */ ··· 15 16 /** The Musicbrainz recording ID of the track */ 16 17 recordingMbId?: string; 17 18 /** The length of the track in seconds */ 18 - duration?: number 19 + duration?: number; 19 20 /** Array of artist names in order of original appearance. Prefer using 'artists'. */ 20 - artistNames?: string[] 21 + artistNames?: string[]; 21 22 /** Array of Musicbrainz artist IDs. Prefer using 'artists'. */ 22 - artistMbIds?: string[] 23 + artistMbIds?: string[]; 23 24 /** Array of artists in order of original appearance. */ 24 - artists?: FmTealAlphaFeedDefs.Artist[] 25 + artists?: FmTealAlphaFeedDefs.Artist[]; 25 26 /** The name of the release/album */ 26 27 releaseName?: string; 27 28 /** The Musicbrainz release ID */
+1 -1
pnpm-lock.yaml
··· 347 347 devDependencies: 348 348 '@types/node': 349 349 specifier: ^20.17.6 350 - version: 20.17.14 350 + version: 20.17.10 351 351 dotenv-expand: 352 352 specifier: ^12.0.2 353 353 version: 12.0.2