Skip to content

Commit 7e3ef70

Browse files
author
Your Name
committed
feat: share links, mobile lyrics, double-tap play, hardening (v1.6.1)
Closes #121, #125, #136, #138. Partially addresses #139, #25, #108, #30. Share links: generate shareable URLs for playlists/tracks/albums with public playback page, token-based access, expiry, and play limits. Mobile lyrics: replace album art with scrollable lyrics view when active. Synced lyrics auto-scroll; plain lyrics freely scrollable. Mobile double-tap: custom touch handler with 300ms window across all 7 track list components. Desktop double-click preserved. Security: path traversal containment in getLocalImagePath/getResizedImagePath, stream error handling via streamFileWithRangeSupport, scoped JSON body limit. Enrichment: sequential audio/vibe phases (no simultaneous ML models), heroUrl preservation in manual enrichment, scanner deep-to-shallow iteration. UI: playlist inline rename, player queue/add-to-playlist buttons, error toasts on silent catch blocks, activity panel listener stability, dead handleSeek wrappers removed, query key standardization.
1 parent e87cebf commit 7e3ef70

File tree

38 files changed

+1903
-332
lines changed

38 files changed

+1903
-332
lines changed

CHANGELOG.md

Lines changed: 68 additions & 42 deletions
Large diffs are not rendered by default.

backend/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "kima-backend",
3-
"version": "1.6.0",
3+
"version": "1.6.1",
44
"description": "Kima backend API server",
55
"license": "GPL-3.0",
66
"repository": {
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
-- CreateTable
2+
CREATE TABLE "share_links" (
3+
"id" TEXT NOT NULL,
4+
"token" TEXT NOT NULL,
5+
"entityType" TEXT NOT NULL,
6+
"entityId" TEXT NOT NULL,
7+
"createdBy" TEXT NOT NULL,
8+
"expiresAt" TIMESTAMP(3),
9+
"maxPlays" INTEGER,
10+
"playCount" INTEGER NOT NULL DEFAULT 0,
11+
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
12+
13+
CONSTRAINT "share_links_pkey" PRIMARY KEY ("id")
14+
);
15+
16+
-- CreateIndex
17+
CREATE UNIQUE INDEX "share_links_token_key" ON "share_links"("token");
18+
19+
-- CreateIndex
20+
CREATE INDEX "share_links_entityType_entityId_idx" ON "share_links"("entityType", "entityId");
21+
22+
-- AddForeignKey
23+
ALTER TABLE "share_links" ADD CONSTRAINT "share_links_createdBy_fkey" FOREIGN KEY ("createdBy") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;

backend/prisma/schema.prisma

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -720,6 +720,7 @@ model User {
720720
unavailableAlbums UnavailableAlbum[]
721721
discoverConfig UserDiscoverConfig?
722722
settings UserSettings?
723+
shareLinks ShareLink[]
723724
}
724725

725726
model UserDiscoverConfig {
@@ -816,6 +817,22 @@ model TrackLyrics {
816817
@@map("track_lyrics")
817818
}
818819

820+
model ShareLink {
821+
id String @id @default(cuid())
822+
token String @unique
823+
entityType String
824+
entityId String
825+
createdBy String
826+
expiresAt DateTime?
827+
maxPlays Int?
828+
playCount Int @default(0)
829+
createdAt DateTime @default(now())
830+
user User @relation(fields: [createdBy], references: [id], onDelete: Cascade)
831+
832+
@@index([entityType, entityId])
833+
@@map("share_links")
834+
}
835+
819836
enum AlbumLocation {
820837
LIBRARY
821838
DISCOVER

backend/src/index.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ import analysisRoutes from "./routes/analysis";
3939
import releasesRoutes from "./routes/releases";
4040
import vibeRoutes from "./routes/vibe";
4141
import systemRoutes from "./routes/system";
42+
import shareRoutes from "./routes/share";
4243
import eventsRoutes from "./routes/events";
4344
import { subsonicRouter } from "./routes/subsonic/index";
4445
import { dataCacheService } from "./services/dataCache";
@@ -94,7 +95,14 @@ app.use(
9495
credentials: true,
9596
})
9697
);
97-
app.use(express.json({ limit: "1mb" })); // Increased from 100KB default to support large queue payloads
98+
const defaultJsonParser = express.json({ limit: "1mb" });
99+
const largeJsonParser = express.json({ limit: "5mb" });
100+
app.use((req, res, next) => {
101+
if (req.path.startsWith("/api/playback-state")) {
102+
return largeJsonParser(req, res, next);
103+
}
104+
return defaultJsonParser(req, res, next);
105+
});
98106

99107
// Session
100108
// Trust proxy for reverse proxy setups (nginx, traefik, etc.)
@@ -130,6 +138,9 @@ app.use("/api/auth", authRoutes);
130138
app.use("/api/onboarding/register", authLimiter);
131139
app.use("/api/onboarding", onboardingRoutes);
132140

141+
// Public share routes (GET/stream are unauthenticated; POST/DELETE self-protect with requireAuth)
142+
app.use("/api/share", shareRoutes);
143+
133144
// Apply general API rate limiting to all API routes
134145
app.use("/api/api-keys", apiLimiter, apiKeysRoutes);
135146
app.use("/api/device-link", apiLimiter, deviceLinkRoutes);

backend/src/routes/playbackState.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ router.post("/", requireAuth, async (req, res) => {
5858
// Filter out any invalid items first
5959
try {
6060
safeQueue = queue
61-
.slice(0, 100)
61+
.slice(0, 2000)
6262
.filter((item: any) => item && item.id) // Must have at least an ID
6363
.map((item: any) => ({
6464
id: String(item.id || ""),

0 commit comments

Comments
 (0)