Feed and profile cards now show one slim line (small artwork, title, artist, Open button) with the progress bar and reactions. The arrow opens the rest: album, your other services, Share, and lyrics. Lyrics come from LRCLIB (open source; LRCLIB_URL can point at a self-hosted copy, or be empty to turn lyrics off). The server looks each song up once and caches it, including misses (retried after a week). Timed lyrics follow along with the song; otherwise the first lines show with "All lyrics". Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
62 lines
2.5 KiB
TypeScript
62 lines
2.5 KiB
TypeScript
import 'dotenv/config';
|
|
import { z } from 'zod';
|
|
|
|
const optional = z
|
|
.string()
|
|
.optional()
|
|
.transform((v) => (v ? v : undefined));
|
|
|
|
const schema = z.object({
|
|
NODE_ENV: z.string().default('development'),
|
|
DATABASE_URL: z.string().default('postgres://musicshare:musicshare@localhost:5447/musicshare'),
|
|
REDIS_URL: z.string().default('redis://localhost:6380'),
|
|
PORT: z.coerce.number().default(4000),
|
|
WEB_URL: z.string().default('http://localhost:3737'),
|
|
API_PUBLIC_URL: z.string().default('http://127.0.0.1:4000'),
|
|
COOKIE_SECURE: z
|
|
.string()
|
|
.default('false')
|
|
.transform((v) => v === 'true'),
|
|
TOKEN_ENCRYPTION_KEY: z
|
|
.string()
|
|
.refine((v) => Buffer.from(v, 'base64').length === 32, 'TOKEN_ENCRYPTION_KEY must be 32 bytes, base64-encoded'),
|
|
SPOTIFY_CLIENT_ID: optional,
|
|
SPOTIFY_CLIENT_SECRET: optional,
|
|
LASTFM_API_KEY: optional,
|
|
/** Enables "Sign in with Last.fm" (web auth). Without it, users type their username. */
|
|
LASTFM_SHARED_SECRET: optional,
|
|
ODESLI_API_KEY: optional,
|
|
/** Lyrics come from LRCLIB (open source, self-hostable). Point this at your own instance, or set it empty to turn lyrics off. */
|
|
LRCLIB_URL: z
|
|
.string()
|
|
.default('https://lrclib.net')
|
|
.transform((v) => v.replace(/\/+$/, '')),
|
|
/** Apple Music (MusicKit): Team ID, MusicKit key ID, and the key's .p8 contents (newlines may be written as \n). */
|
|
APPLE_TEAM_ID: optional,
|
|
APPLE_MUSICKIT_KEY_ID: optional,
|
|
APPLE_MUSICKIT_PRIVATE_KEY: optional,
|
|
/** Identifies this app to Plex. Keep it stable: changing it invalidates users' Plex authorizations. */
|
|
PLEX_CLIENT_ID: z.string().default('musicshare-dev'),
|
|
/**
|
|
* Allow Jellyfin/Plex server URLs on private networks. Only enable for local
|
|
* dev or self-hosting: in a hosted deployment it lets users make the server
|
|
* request internal addresses (SSRF).
|
|
*/
|
|
/** Outgoing email (password resets). Without SMTP_HOST, emails are only logged. */
|
|
SMTP_HOST: optional,
|
|
SMTP_PORT: z.coerce.number().default(465),
|
|
SMTP_USER: optional,
|
|
SMTP_PASSWORD: optional,
|
|
MAIL_FROM: z.string().default('musicshare <app@musicshare.binarygnome.com>'),
|
|
ALLOW_PRIVATE_SERVER_URLS: z
|
|
.string()
|
|
.default('false')
|
|
.transform((v) => v === 'true'),
|
|
});
|
|
|
|
export const env = schema.parse(process.env);
|
|
|
|
export const spotifyConfigured = Boolean(env.SPOTIFY_CLIENT_ID && env.SPOTIFY_CLIENT_SECRET);
|
|
export const appleMusicConfigured = Boolean(env.APPLE_TEAM_ID && env.APPLE_MUSICKIT_KEY_ID && env.APPLE_MUSICKIT_PRIVATE_KEY);
|
|
export const mailConfigured = Boolean(env.SMTP_HOST);
|