fix: recording save functionality and authentication

- Fix artist_name null handling in header component with email fallback
- Fix authentication in recording endpoints to use req.accountability
- Change duration field type from integer to double precision for millisecond precision
- Add createRecording service function with proper authentication
- Update play page to use fetch API for recording saves

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Valknar XXX
2025-10-28 05:16:36 +01:00
parent 5bd2d9c215
commit a252da6d9d
5 changed files with 65 additions and 31 deletions

View File

@@ -2259,7 +2259,7 @@ fields:
foreign_key_column: null
- collection: sexy_recordings
field: duration
type: integer
type: float
meta:
collection: sexy_recordings
conditions: null
@@ -2282,11 +2282,11 @@ fields:
schema:
name: duration
table: sexy_recordings
data_type: integer
data_type: double precision
default_value: null
max_length: null
numeric_precision: 32
numeric_scale: 0
numeric_precision: 53
numeric_scale: null
is_nullable: false
is_unique: false
is_indexed: false

View File

@@ -60,7 +60,7 @@ export default {
// GET /sexy/recordings - List user's recordings
router.get("/recordings", async (req, res) => {
const { accountability } = context;
const accountability = req.accountability;
if (!accountability?.user) {
return res.status(401).json({ error: "Unauthorized" });
}
@@ -93,7 +93,7 @@ export default {
// GET /sexy/recordings/:id - Get single recording
router.get("/recordings/:id", async (req, res) => {
const { accountability } = context;
const accountability = req.accountability;
if (!accountability?.user) {
return res.status(401).json({ error: "Unauthorized" });
}
@@ -122,7 +122,7 @@ export default {
// POST /sexy/recordings - Create new recording
router.post("/recordings", async (req, res) => {
const { accountability } = context;
const accountability = req.accountability;
if (!accountability?.user) {
return res.status(401).json({ error: "Unauthorized" });
}
@@ -168,13 +168,17 @@ export default {
res.status(201).json(recording);
} catch (error: any) {
res.status(500).json({ error: error.message || "Failed to create recording" });
console.error("Failed to create recording:", error);
res.status(500).json({
error: error.message || "Failed to create recording",
details: error.toString()
});
}
});
// PATCH /sexy/recordings/:id - Update recording
router.patch("/recordings/:id", async (req, res) => {
const { accountability } = context;
const accountability = req.accountability;
if (!accountability?.user) {
return res.status(401).json({ error: "Unauthorized" });
}
@@ -217,7 +221,7 @@ export default {
// DELETE /sexy/recordings/:id - Delete (archive) recording
router.delete("/recordings/:id", async (req, res) => {
const { accountability } = context;
const accountability = req.accountability;
if (!accountability?.user) {
return res.status(401).json({ error: "Unauthorized" });
}

View File

@@ -127,7 +127,7 @@ function isActiveLink(link: any) {
<LogoutButton
user={{
name: authStatus.user!.artist_name,
name: authStatus.user!.artist_name || authStatus.user!.email.split('@')[0] || 'User',
avatar: getAssetUrl(authStatus.user!.avatar?.id, 'mini')!,
email: authStatus.user!.email
}}

View File

@@ -566,6 +566,38 @@ export async function getRecordings(fetch?: typeof globalThis.fetch) {
);
}
export async function createRecording(
recording: {
title: string;
description?: string;
duration: number;
events: unknown[];
device_info: unknown[];
tags?: string[];
status?: string;
},
fetch?: typeof globalThis.fetch,
) {
return loggedApiCall(
"createRecording",
async () => {
const directus = getDirectusInstance(fetch);
const response = await directus.request<Recording>(
customEndpoint({
method: "POST",
path: "/sexy/recordings",
body: JSON.stringify(recording),
headers: {
"Content-Type": "application/json",
},
}),
);
return response;
},
{ title: recording.title, eventCount: recording.events.length },
);
}
export async function deleteRecording(id: string) {
return loggedApiCall(
"deleteRecording",

View File

@@ -22,8 +22,6 @@ import DeviceCard from "$lib/components/device-card/device-card.svelte";
import RecordingSaveDialog from "./components/recording-save-dialog.svelte";
import type { BluetoothDevice, RecordedEvent, DeviceInfo } from "$lib/types";
import { toast } from "svelte-sonner";
import { customEndpoint } from "@directus/sdk";
import { getDirectusInstance } from "$lib/directus";
const client = new ButtplugClient("Sexy.Art");
let connected = $state(client.connected);
@@ -191,11 +189,11 @@ async function handleSaveRecording(data: {
}));
try {
const directus = getDirectusInstance();
await directus.request(
customEndpoint({
const response = await fetch("/api/sexy/recordings", {
method: "POST",
path: "/sexy/recordings",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
title: data.title,
description: data.description,
@@ -205,11 +203,11 @@ async function handleSaveRecording(data: {
tags: data.tags,
status: "draft",
}),
headers: {
"Content-Type": "application/json",
},
}),
);
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
toast.success("Recording saved successfully!");
showSaveDialog = false;