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:
@@ -2259,7 +2259,7 @@ fields:
|
|||||||
foreign_key_column: null
|
foreign_key_column: null
|
||||||
- collection: sexy_recordings
|
- collection: sexy_recordings
|
||||||
field: duration
|
field: duration
|
||||||
type: integer
|
type: float
|
||||||
meta:
|
meta:
|
||||||
collection: sexy_recordings
|
collection: sexy_recordings
|
||||||
conditions: null
|
conditions: null
|
||||||
@@ -2282,11 +2282,11 @@ fields:
|
|||||||
schema:
|
schema:
|
||||||
name: duration
|
name: duration
|
||||||
table: sexy_recordings
|
table: sexy_recordings
|
||||||
data_type: integer
|
data_type: double precision
|
||||||
default_value: null
|
default_value: null
|
||||||
max_length: null
|
max_length: null
|
||||||
numeric_precision: 32
|
numeric_precision: 53
|
||||||
numeric_scale: 0
|
numeric_scale: null
|
||||||
is_nullable: false
|
is_nullable: false
|
||||||
is_unique: false
|
is_unique: false
|
||||||
is_indexed: false
|
is_indexed: false
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ export default {
|
|||||||
|
|
||||||
// GET /sexy/recordings - List user's recordings
|
// GET /sexy/recordings - List user's recordings
|
||||||
router.get("/recordings", async (req, res) => {
|
router.get("/recordings", async (req, res) => {
|
||||||
const { accountability } = context;
|
const accountability = req.accountability;
|
||||||
if (!accountability?.user) {
|
if (!accountability?.user) {
|
||||||
return res.status(401).json({ error: "Unauthorized" });
|
return res.status(401).json({ error: "Unauthorized" });
|
||||||
}
|
}
|
||||||
@@ -93,7 +93,7 @@ export default {
|
|||||||
|
|
||||||
// GET /sexy/recordings/:id - Get single recording
|
// GET /sexy/recordings/:id - Get single recording
|
||||||
router.get("/recordings/:id", async (req, res) => {
|
router.get("/recordings/:id", async (req, res) => {
|
||||||
const { accountability } = context;
|
const accountability = req.accountability;
|
||||||
if (!accountability?.user) {
|
if (!accountability?.user) {
|
||||||
return res.status(401).json({ error: "Unauthorized" });
|
return res.status(401).json({ error: "Unauthorized" });
|
||||||
}
|
}
|
||||||
@@ -122,7 +122,7 @@ export default {
|
|||||||
|
|
||||||
// POST /sexy/recordings - Create new recording
|
// POST /sexy/recordings - Create new recording
|
||||||
router.post("/recordings", async (req, res) => {
|
router.post("/recordings", async (req, res) => {
|
||||||
const { accountability } = context;
|
const accountability = req.accountability;
|
||||||
if (!accountability?.user) {
|
if (!accountability?.user) {
|
||||||
return res.status(401).json({ error: "Unauthorized" });
|
return res.status(401).json({ error: "Unauthorized" });
|
||||||
}
|
}
|
||||||
@@ -168,13 +168,17 @@ export default {
|
|||||||
|
|
||||||
res.status(201).json(recording);
|
res.status(201).json(recording);
|
||||||
} catch (error: any) {
|
} 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
|
// PATCH /sexy/recordings/:id - Update recording
|
||||||
router.patch("/recordings/:id", async (req, res) => {
|
router.patch("/recordings/:id", async (req, res) => {
|
||||||
const { accountability } = context;
|
const accountability = req.accountability;
|
||||||
if (!accountability?.user) {
|
if (!accountability?.user) {
|
||||||
return res.status(401).json({ error: "Unauthorized" });
|
return res.status(401).json({ error: "Unauthorized" });
|
||||||
}
|
}
|
||||||
@@ -217,7 +221,7 @@ export default {
|
|||||||
|
|
||||||
// DELETE /sexy/recordings/:id - Delete (archive) recording
|
// DELETE /sexy/recordings/:id - Delete (archive) recording
|
||||||
router.delete("/recordings/:id", async (req, res) => {
|
router.delete("/recordings/:id", async (req, res) => {
|
||||||
const { accountability } = context;
|
const accountability = req.accountability;
|
||||||
if (!accountability?.user) {
|
if (!accountability?.user) {
|
||||||
return res.status(401).json({ error: "Unauthorized" });
|
return res.status(401).json({ error: "Unauthorized" });
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -127,7 +127,7 @@ function isActiveLink(link: any) {
|
|||||||
|
|
||||||
<LogoutButton
|
<LogoutButton
|
||||||
user={{
|
user={{
|
||||||
name: authStatus.user!.artist_name,
|
name: authStatus.user!.artist_name || authStatus.user!.email.split('@')[0] || 'User',
|
||||||
avatar: getAssetUrl(authStatus.user!.avatar?.id, 'mini')!,
|
avatar: getAssetUrl(authStatus.user!.avatar?.id, 'mini')!,
|
||||||
email: authStatus.user!.email
|
email: authStatus.user!.email
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -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) {
|
export async function deleteRecording(id: string) {
|
||||||
return loggedApiCall(
|
return loggedApiCall(
|
||||||
"deleteRecording",
|
"deleteRecording",
|
||||||
|
|||||||
@@ -22,8 +22,6 @@ import DeviceCard from "$lib/components/device-card/device-card.svelte";
|
|||||||
import RecordingSaveDialog from "./components/recording-save-dialog.svelte";
|
import RecordingSaveDialog from "./components/recording-save-dialog.svelte";
|
||||||
import type { BluetoothDevice, RecordedEvent, DeviceInfo } from "$lib/types";
|
import type { BluetoothDevice, RecordedEvent, DeviceInfo } from "$lib/types";
|
||||||
import { toast } from "svelte-sonner";
|
import { toast } from "svelte-sonner";
|
||||||
import { customEndpoint } from "@directus/sdk";
|
|
||||||
import { getDirectusInstance } from "$lib/directus";
|
|
||||||
|
|
||||||
const client = new ButtplugClient("Sexy.Art");
|
const client = new ButtplugClient("Sexy.Art");
|
||||||
let connected = $state(client.connected);
|
let connected = $state(client.connected);
|
||||||
@@ -191,25 +189,25 @@ async function handleSaveRecording(data: {
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const directus = getDirectusInstance();
|
const response = await fetch("/api/sexy/recordings", {
|
||||||
await directus.request(
|
method: "POST",
|
||||||
customEndpoint({
|
headers: {
|
||||||
method: "POST",
|
"Content-Type": "application/json",
|
||||||
path: "/sexy/recordings",
|
},
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
title: data.title,
|
title: data.title,
|
||||||
description: data.description,
|
description: data.description,
|
||||||
duration: recordingDuration,
|
duration: recordingDuration,
|
||||||
events: recordedEvents,
|
events: recordedEvents,
|
||||||
device_info: deviceInfo,
|
device_info: deviceInfo,
|
||||||
tags: data.tags,
|
tags: data.tags,
|
||||||
status: "draft",
|
status: "draft",
|
||||||
}),
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
},
|
|
||||||
}),
|
}),
|
||||||
);
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`HTTP error! status: ${response.status}`);
|
||||||
|
}
|
||||||
|
|
||||||
toast.success("Recording saved successfully!");
|
toast.success("Recording saved successfully!");
|
||||||
showSaveDialog = false;
|
showSaveDialog = false;
|
||||||
|
|||||||
Reference in New Issue
Block a user