From 8c38047407bda7ff4f7a5a02fe360dfad42abbfe Mon Sep 17 00:00:00 2001 From: Eli Mallon Date: Thu, 1 Oct 2026 16:11:41 -0700 Subject: [PATCH] auto-vod: store the setting in place.stream.server.settings Drop the bespoke place.stream.server.getPreferences/putPreferences endpoints and the statedb user_preferences table. The setting is now an autoPublishVods field on the existing settings record for this node, read from its indexed copy (model.ServerSettings) like livestreamRecording, and the app and web toggles write it with createServerSettingsRecord. Unlike livestreamRecording it is opt-in: only an explicit true turns it on. --- .maestro/logged-in/auto-publish-vods.yaml | 4 +- .../settings/privacy-category-settings.tsx | 99 +++++-------------- .../content/docs/lex-reference/openapi.json | 77 --------------- .../server/place-stream-server-defs.md | 27 ----- .../place-stream-server-getpreferences.md | 59 ----------- .../place-stream-server-putpreferences.md | 83 ---------------- .../server/place-stream-server-settings.md | 13 ++- js/e2e-web/flows/auto-publish-vods.spec.ts | 28 +++--- js/i18n/locales/en-US/settings.ftl | 1 - js/i18n/public/locales/en-US/settings.json | 1 - js/web/public/locales/en-US/settings.json | 1 - js/web/src/routes/settings/privacy.tsx | 43 +------- lexicons/place/stream/server/defs.json | 11 --- .../place/stream/server/getPreferences.json | 23 ----- .../place/stream/server/putPreferences.json | 36 ------- lexicons/place/stream/server/settings.json | 4 + pkg/atproto/auto_publish_vod_test.go | 5 +- pkg/placestream/serverdefs.go | 27 ----- pkg/placestream/servergetpreferences.go | 50 ---------- pkg/placestream/serverputpreferences.go | 76 -------------- pkg/placestream/serversettings.go | 2 + pkg/spxrpc/preferences.go | 42 -------- pkg/spxrpc/stubs.go | 32 ------ pkg/statedb/auto_publish_vod.go | 35 +++++-- pkg/statedb/auto_publish_vod_test.go | 47 ++++----- pkg/statedb/statedb.go | 1 - pkg/statedb/user_preferences.go | 56 ----------- 27 files changed, 110 insertions(+), 773 deletions(-) delete mode 100644 js/docs/src/content/docs/lex-reference/server/place-stream-server-getpreferences.md delete mode 100644 js/docs/src/content/docs/lex-reference/server/place-stream-server-putpreferences.md delete mode 100644 lexicons/place/stream/server/getPreferences.json delete mode 100644 lexicons/place/stream/server/putPreferences.json delete mode 100644 pkg/placestream/servergetpreferences.go delete mode 100644 pkg/placestream/serverputpreferences.go delete mode 100644 pkg/spxrpc/preferences.go delete mode 100644 pkg/statedb/user_preferences.go diff --git a/.maestro/logged-in/auto-publish-vods.yaml b/.maestro/logged-in/auto-publish-vods.yaml index c975d9563..fb167740e 100644 --- a/.maestro/logged-in/auto-publish-vods.yaml +++ b/.maestro/logged-in/auto-publish-vods.yaml @@ -1,8 +1,8 @@ appId: ${APP_ID} --- # The runner configures the harness and signs in before any logged-in flow. -# Automatic VOD publishing is a preference the node keeps for the account, so -# the toggle has to come back on after a relaunch, and off again after the +# Automatic VOD publishing is saved to the account's settings record, so the +# toggle has to come back on after a relaunch, and off again after the # next one. The harness node has no beta-invite issuer, so the account is in # the VOD beta that gates the toggle. Web: js/e2e-web/flows/auto-publish-vods.spec.ts. - launchApp diff --git a/js/app/components/settings/privacy-category-settings.tsx b/js/app/components/settings/privacy-category-settings.tsx index d0d8cfe70..1c0c7e38a 100644 --- a/js/app/components/settings/privacy-category-settings.tsx +++ b/js/app/components/settings/privacy-category-settings.tsx @@ -2,77 +2,16 @@ import { MenuContainer, MenuGroup, useBetaStatus, - useToast, View, zero, } from "@streamplace/components"; -import { usePDSAgent } from "@streamplace/components/src/streamplace-store/xrpc"; -import { useEffect, useRef, useState } from "react"; +import { useEffect } from "react"; import { useTranslation } from "react-i18next"; import { ScrollView } from "react-native"; import { useStore } from "store"; import { useIsReady, useServerSettings, useStreamplaceUrl } from "store/hooks"; -import { place } from "streamplace"; import { SettingToggle } from "./components/setting-toggle"; -// Automatic VOD publishing is a preference the node keeps itself -// (place.stream.server.getPreferences), not part of the settings record. -function AutoPublishVodsToggle({ host }: { host: string }) { - const { t } = useTranslation(["settings", "common"]); - const toast = useToast(); - const agent = usePDSAgent(); - const [enabled, setEnabled] = useState(null); - // One write at a time: overlapping writes could finish out of order and - // leave the switch showing the opposite of the last tap. - const saving = useRef(false); - - useEffect(() => { - if (!agent) return; - agent.client - .call(place.stream.server.getPreferences) - .then((res) => setEnabled(res.preferences.autoPublishVods)) - .catch((err) => console.error("Failed to load preferences:", err)); - }, [agent]); - - if (enabled === null) { - return null; - } - - const handleChange = async (value: boolean) => { - if (!agent || saving.current) return; - saving.current = true; - setEnabled(value); - try { - const res = await agent.client.call(place.stream.server.putPreferences, { - autoPublishVods: value, - }); - setEnabled(res.preferences.autoPublishVods); - } catch (err) { - console.error("Failed to update preferences:", err); - setEnabled(!value); - toast.show( - t("common:error"), - err instanceof Error && err.message - ? err.message - : t("auto-publish-vods-update-failed"), - { variant: "error" }, - ); - } finally { - saving.current = false; - } - }; - - return ( - - ); -} - export function PrivacyCategorySettings() { const { t } = useTranslation("settings"); const isReady = useIsReady(); @@ -87,6 +26,8 @@ export function PrivacyCategorySettings() { const debugRecordingOn = serverSettings?.debugRecording === true; // Defaults on (unlike debugRecording): only an explicit `false` turns it off. const livestreamRecordingOn = serverSettings?.livestreamRecording !== false; + // Opt-in: only an explicit `true` turns it on. + const autoPublishVodsOn = serverSettings?.autoPublishVods === true; // The livestream-recording and automatic VOD publishing toggles are only // meaningful for accounts in the VOD beta — the node won't record anyone // else regardless of these flags — so we only surface them to them. @@ -115,17 +56,29 @@ export function PrivacyCategorySettings() { }} /> {vodBetaStatus === "granted" && ( - { - createServerSettingsRecord({ livestreamRecording: value }); - }} - /> - )} - {vodBetaStatus === "granted" && ( - + <> + { + createServerSettingsRecord({ + livestreamRecording: value, + }); + }} + /> + { + createServerSettingsRecord({ autoPublishVods: value }); + }} + testID="settings-auto-publish-vods" + /> + )} diff --git a/js/docs/src/content/docs/lex-reference/openapi.json b/js/docs/src/content/docs/lex-reference/openapi.json index 891d67aea..3e4268ca5 100644 --- a/js/docs/src/content/docs/lex-reference/openapi.json +++ b/js/docs/src/content/docs/lex-reference/openapi.json @@ -771,31 +771,6 @@ } } }, - "/xrpc/place.stream.server.getPreferences": { - "get": { - "summary": "Get the authenticated user's private preferences on this node.", - "operationId": "place.stream.server.getPreferences", - "tags": ["place.stream.server"], - "responses": { - "200": { - "description": "Success", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "preferences": { - "$ref": "#/components/schemas/place.stream.server.defs_preferences" - } - }, - "required": ["preferences"] - } - } - } - } - } - } - }, "/xrpc/place.stream.server.getServerTime": { "get": { "summary": "Get the current server time for client clock synchronization", @@ -1019,47 +994,6 @@ ] } }, - "/xrpc/place.stream.server.putPreferences": { - "post": { - "summary": "Update the authenticated user's private preferences on this node. Omitted fields keep their current values.", - "operationId": "place.stream.server.putPreferences", - "tags": ["place.stream.server"], - "responses": { - "200": { - "description": "Success", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "preferences": { - "$ref": "#/components/schemas/place.stream.server.defs_preferences" - } - }, - "required": ["preferences"] - } - } - } - } - }, - "requestBody": { - "required": true, - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "autoPublishVods": { - "type": "boolean", - "description": "Whether the node publishes a VOD of each of the user's recorded livestreams as soon as the livestream ends." - } - } - } - } - } - } - } - }, "/xrpc/place.stream.server.updateWebhook": { "post": { "summary": "Update an existing webhook configuration.", @@ -7371,17 +7305,6 @@ }, "required": ["id", "url", "events", "active", "createdAt"] }, - "place.stream.server.defs_preferences": { - "type": "object", - "description": "A user's private preferences on this Streamplace node. The node stores them itself; they are not a record in the user's repository.", - "properties": { - "autoPublishVods": { - "type": "boolean", - "description": "Whether the node publishes a VOD of each of the user's recorded livestreams as soon as the livestream ends." - } - }, - "required": ["autoPublishVods"] - }, "place.stream.server.defs_storage": { "type": "object", "description": "S3 storage configuration for backups.", diff --git a/js/docs/src/content/docs/lex-reference/server/place-stream-server-defs.md b/js/docs/src/content/docs/lex-reference/server/place-stream-server-defs.md index a9292df5b..55aa8671c 100644 --- a/js/docs/src/content/docs/lex-reference/server/place-stream-server-defs.md +++ b/js/docs/src/content/docs/lex-reference/server/place-stream-server-defs.md @@ -68,22 +68,6 @@ S3 storage configuration for backups. --- - - -### `preferences` - -**Type:** `object` - -A user's private preferences on this Streamplace node. The node stores them itself; they are not a record in the user's repository. - -**Properties:** - -| Name | Type | Req'd | Description | Constraints | -| ----------------- | --------- | ----- | ----------------------------------------------------------------------------------------------------------- | ----------- | -| `autoPublishVods` | `boolean` | ✅ | Whether the node publishes a VOD of each of the user's recorded livestreams as soon as the livestream ends. | | - ---- - ## Lexicon Source ```json @@ -211,17 +195,6 @@ A user's private preferences on this Streamplace node. The node stores them itse "description": "Whether backup storage is currently active." } } - }, - "preferences": { - "type": "object", - "description": "A user's private preferences on this Streamplace node. The node stores them itself; they are not a record in the user's repository.", - "required": ["autoPublishVods"], - "properties": { - "autoPublishVods": { - "type": "boolean", - "description": "Whether the node publishes a VOD of each of the user's recorded livestreams as soon as the livestream ends." - } - } } } } diff --git a/js/docs/src/content/docs/lex-reference/server/place-stream-server-getpreferences.md b/js/docs/src/content/docs/lex-reference/server/place-stream-server-getpreferences.md deleted file mode 100644 index 98778d8e1..000000000 --- a/js/docs/src/content/docs/lex-reference/server/place-stream-server-getpreferences.md +++ /dev/null @@ -1,59 +0,0 @@ ---- -title: place.stream.server.getPreferences -description: Reference for the place.stream.server.getPreferences lexicon ---- - -**Lexicon Version:** 1 - -## Definitions - - - -### `main` - -**Type:** `query` - -Get the authenticated user's private preferences on this node. - -**Parameters:** _(None defined)_ - -**Output:** - -- **Encoding:** `application/json` -- **Schema:** - -**Schema Type:** `object` - -| Name | Type | Req'd | Description | Constraints | -| ------------- | --------------------------------------------------------------------------------------------- | ----- | ----------- | ----------- | -| `preferences` | [`place.stream.server.defs#preferences`](/lex-reference/place-stream-server-defs#preferences) | ✅ | | | - ---- - -## Lexicon Source - -```json -{ - "lexicon": 1, - "id": "place.stream.server.getPreferences", - "defs": { - "main": { - "type": "query", - "description": "Get the authenticated user's private preferences on this node.", - "output": { - "encoding": "application/json", - "schema": { - "type": "object", - "required": ["preferences"], - "properties": { - "preferences": { - "type": "ref", - "ref": "place.stream.server.defs#preferences" - } - } - } - } - } - } -} -``` diff --git a/js/docs/src/content/docs/lex-reference/server/place-stream-server-putpreferences.md b/js/docs/src/content/docs/lex-reference/server/place-stream-server-putpreferences.md deleted file mode 100644 index 3d32c1dd3..000000000 --- a/js/docs/src/content/docs/lex-reference/server/place-stream-server-putpreferences.md +++ /dev/null @@ -1,83 +0,0 @@ ---- -title: place.stream.server.putPreferences -description: Reference for the place.stream.server.putPreferences lexicon ---- - -**Lexicon Version:** 1 - -## Definitions - - - -### `main` - -**Type:** `procedure` - -Update the authenticated user's private preferences on this node. Omitted fields keep their current values. - -**Parameters:** _(None defined)_ - -**Input:** - -- **Encoding:** `application/json` -- **Schema:** - -**Schema Type:** `object` - -| Name | Type | Req'd | Description | Constraints | -| ----------------- | --------- | ----- | ----------------------------------------------------------------------------------------------------------- | ----------- | -| `autoPublishVods` | `boolean` | ❌ | Whether the node publishes a VOD of each of the user's recorded livestreams as soon as the livestream ends. | | - -**Output:** - -- **Encoding:** `application/json` -- **Schema:** - -**Schema Type:** `object` - -| Name | Type | Req'd | Description | Constraints | -| ------------- | --------------------------------------------------------------------------------------------- | ----- | ----------- | ----------- | -| `preferences` | [`place.stream.server.defs#preferences`](/lex-reference/place-stream-server-defs#preferences) | ✅ | | | - ---- - -## Lexicon Source - -```json -{ - "lexicon": 1, - "id": "place.stream.server.putPreferences", - "defs": { - "main": { - "type": "procedure", - "description": "Update the authenticated user's private preferences on this node. Omitted fields keep their current values.", - "input": { - "encoding": "application/json", - "schema": { - "type": "object", - "required": [], - "properties": { - "autoPublishVods": { - "type": "boolean", - "description": "Whether the node publishes a VOD of each of the user's recorded livestreams as soon as the livestream ends." - } - } - } - }, - "output": { - "encoding": "application/json", - "schema": { - "type": "object", - "required": ["preferences"], - "properties": { - "preferences": { - "type": "ref", - "ref": "place.stream.server.defs#preferences" - } - } - } - } - } - } -} -``` diff --git a/js/docs/src/content/docs/lex-reference/server/place-stream-server-settings.md b/js/docs/src/content/docs/lex-reference/server/place-stream-server-settings.md index 1ba064e8a..93f2233cd 100644 --- a/js/docs/src/content/docs/lex-reference/server/place-stream-server-settings.md +++ b/js/docs/src/content/docs/lex-reference/server/place-stream-server-settings.md @@ -19,10 +19,11 @@ Record containing user settings for a particular Streamplace node **Record Properties:** -| Name | Type | Req'd | Description | Constraints | -| --------------------- | --------- | ----- | ------------------------------------------------------------------------------- | ----------- | -| `debugRecording` | `boolean` | ❌ | Whether this node may archive your livestream for improving the service | | -| `livestreamRecording` | `boolean` | ❌ | Whether this node should record your livestreams into VODs that you can publish | | +| Name | Type | Req'd | Description | Constraints | +| --------------------- | --------- | ----- | ------------------------------------------------------------------------------------------------ | ----------- | +| `debugRecording` | `boolean` | ❌ | Whether this node may archive your livestream for improving the service | | +| `livestreamRecording` | `boolean` | ❌ | Whether this node should record your livestreams into VODs that you can publish | | +| `autoPublishVods` | `boolean` | ❌ | Whether this node should publish the VOD of each of your recorded livestreams as soon as it ends | | --- @@ -48,6 +49,10 @@ Record containing user settings for a particular Streamplace node "livestreamRecording": { "type": "boolean", "description": "Whether this node should record your livestreams into VODs that you can publish" + }, + "autoPublishVods": { + "type": "boolean", + "description": "Whether this node should publish the VOD of each of your recorded livestreams as soon as it ends" } } } diff --git a/js/e2e-web/flows/auto-publish-vods.spec.ts b/js/e2e-web/flows/auto-publish-vods.spec.ts index 605452410..032e88b16 100644 --- a/js/e2e-web/flows/auto-publish-vods.spec.ts +++ b/js/e2e-web/flows/auto-publish-vods.spec.ts @@ -1,29 +1,27 @@ import { expect, test, type Page } from "@playwright/test"; import { loginThroughPds } from "./login"; -// Automatic VOD publishing is a preference the node keeps for the logged-in -// account (place.stream.server.putPreferences), not a record in their repo. -// Flip it in Privacy & Security and back, reloading each time so only the -// node's stored value can set the toggle. The harness node has no beta-invite -// issuer, so the test account is in the VOD beta that gates the toggle. It -// records no streams (no S3), so publishing itself is covered by the Go tests -// in pkg/statedb. Mobile: .maestro/logged-in/auto-publish-vods.yaml. +// Automatic VOD publishing is the autoPublishVods field of the account's +// place.stream.server.settings record for this node. Flip it in Privacy & +// Security and back, reloading each time so only the saved record can set the +// toggle. The harness node has no beta-invite issuer, so the test account is +// in the VOD beta that gates the toggle. It records no streams (no S3), so +// publishing itself is covered by the Go tests in pkg/statedb and +// pkg/atproto. Mobile: .maestro/logged-in/auto-publish-vods.yaml. const HTTPS_URL = process.env.SERVER_HTTPS_URL; test.skip(!HTTPS_URL, "harness started without its HTTPS hostnames"); async function setAutoPublishVods(page: Page, on: boolean) { const toggle = page.getByTestId("settings-auto-publish-vods"); - const saved = page.waitForResponse( - (response) => - new URL(response.url()).pathname === - "/xrpc/place.stream.server.putPreferences", + const saved = page.waitForResponse((response) => + new URL(response.url()).pathname.endsWith( + "/xrpc/com.atproto.repo.putRecord", + ), ); await toggle.click(); const response = await saved; - expect(response.ok(), `putPreferences returned ${response.status()}`).toBe( - true, - ); + expect(response.ok(), `putRecord returned ${response.status()}`).toBe(true); await expect(toggle).toHaveAttribute("aria-checked", String(on)); await page.reload(); @@ -34,7 +32,7 @@ async function setAutoPublishVods(page: Page, on: boolean) { ); } -test("auto-publish-vods: the preference is kept by the node", async ({ +test("auto-publish-vods: the setting is saved to the settings record", async ({ page, }) => { await loginThroughPds(page); diff --git a/js/i18n/locales/en-US/settings.ftl b/js/i18n/locales/en-US/settings.ftl index 9c9d3f8cc..40a75ac96 100644 --- a/js/i18n/locales/en-US/settings.ftl +++ b/js/i18n/locales/en-US/settings.ftl @@ -46,7 +46,6 @@ livestream-recording-title = Record my livestreams into VODs on { $host } livestream-recording-description = When enabled, your livestreams are saved so you can finalize and publish them as videos auto-publish-vods-title = Publish my livestreams as VODs automatically auto-publish-vods-description = When a recorded livestream ends, { $host } publishes it as a video right away, without waiting for you to finalize it -auto-publish-vods-update-failed = Failed to update automatic VOD publishing ## Key Management manage-keys = Manage Keys diff --git a/js/i18n/public/locales/en-US/settings.json b/js/i18n/public/locales/en-US/settings.json index ee67d6529..f0e9db5e7 100644 --- a/js/i18n/public/locales/en-US/settings.json +++ b/js/i18n/public/locales/en-US/settings.json @@ -34,7 +34,6 @@ "livestream-recording-description": "When enabled, your livestreams are saved so you can finalize and publish them as videos", "auto-publish-vods-title": "Publish my livestreams as VODs automatically", "auto-publish-vods-description": "When a recorded livestream ends, {$host} publishes it as a video right away, without waiting for you to finalize it", - "auto-publish-vods-update-failed": "Failed to update automatic VOD publishing", "manage-keys": "Manage Keys", "settings-title": "Settings", "error": "Error", diff --git a/js/web/public/locales/en-US/settings.json b/js/web/public/locales/en-US/settings.json index ee67d6529..f0e9db5e7 100644 --- a/js/web/public/locales/en-US/settings.json +++ b/js/web/public/locales/en-US/settings.json @@ -34,7 +34,6 @@ "livestream-recording-description": "When enabled, your livestreams are saved so you can finalize and publish them as videos", "auto-publish-vods-title": "Publish my livestreams as VODs automatically", "auto-publish-vods-description": "When a recorded livestream ends, {$host} publishes it as a video right away, without waiting for you to finalize it", - "auto-publish-vods-update-failed": "Failed to update automatic VOD publishing", "manage-keys": "Manage Keys", "settings-title": "Settings", "error": "Error", diff --git a/js/web/src/routes/settings/privacy.tsx b/js/web/src/routes/settings/privacy.tsx index bba22ef06..ee8224c2d 100644 --- a/js/web/src/routes/settings/privacy.tsx +++ b/js/web/src/routes/settings/privacy.tsx @@ -5,11 +5,9 @@ import { useSession } from "@/lib/session"; import { createFileRoute } from "@tanstack/react-router"; import { useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; -import { place } from "streamplace"; import { useStore } from "../../lib/store"; import { useIsReady, - usePDSAgent, useServerSettings, useStreamplaceUrl, } from "../../lib/store/hooks"; @@ -30,10 +28,6 @@ function PrivacySettings() { ); const [saving, setSaving] = useState(false); const [error, setError] = useState(null); - const agent = usePDSAgent(); - // Automatic VOD publishing is a preference the node keeps itself - // (place.stream.server.getPreferences), not part of the settings record. - const [autoPublishVods, setAutoPublishVods] = useState(null); const isAuthenticated = sessionState.status === "authenticated"; @@ -46,22 +40,17 @@ function PrivacySettings() { if (isReady && isAuthenticated) getServerSettingsFromPDS(); }, [isReady, isAuthenticated]); - useEffect(() => { - if (!agent || !isAuthenticated) return; - agent.client - .call(place.stream.server.getPreferences) - .then((res) => setAutoPublishVods(res.preferences.autoPublishVods)) - .catch((err) => console.error("Failed to load preferences:", err)); - }, [agent, isAuthenticated]); - const debugRecordingOn = serverSettings?.debugRecording === true; // Defaults on (unlike debugRecording): only an explicit `false` turns it off. const livestreamRecordingOn = serverSettings?.livestreamRecording !== false; + // Opt-in: only an explicit `true` turns it on. + const autoPublishVodsOn = serverSettings?.autoPublishVods === true; const u = new URL(url); const handleToggle = async (patch: { debugRecording?: boolean; livestreamRecording?: boolean; + autoPublishVods?: boolean; }) => { if (!isAuthenticated || saving) return; setSaving(true); @@ -80,23 +69,6 @@ function PrivacySettings() { } }; - const handleAutoPublishVodsToggle = async (value: boolean) => { - if (!agent || saving) return; - setSaving(true); - setError(null); - try { - const res = await agent.client.call(place.stream.server.putPreferences, { - autoPublishVods: value, - }); - setAutoPublishVods(res.preferences.autoPublishVods); - } catch (error) { - console.error("Failed to update preferences:", error); - setError(t("auto-publish-vods-update-failed")); - } finally { - setSaving(false); - } - }; - return (

@@ -149,11 +121,6 @@ function PrivacySettings() { />

- - )} - - {vodBetaStatus === "granted" && autoPublishVods !== null && ( -
@@ -165,8 +132,8 @@ function PrivacySettings() {
handleToggle({ autoPublishVods: v })} disabled={!isAuthenticated || saving} /> diff --git a/lexicons/place/stream/server/defs.json b/lexicons/place/stream/server/defs.json index 3b85510e0..5d5f0edc7 100644 --- a/lexicons/place/stream/server/defs.json +++ b/lexicons/place/stream/server/defs.json @@ -119,17 +119,6 @@ "description": "Whether backup storage is currently active." } } - }, - "preferences": { - "type": "object", - "description": "A user's private preferences on this Streamplace node. The node stores them itself; they are not a record in the user's repository.", - "required": ["autoPublishVods"], - "properties": { - "autoPublishVods": { - "type": "boolean", - "description": "Whether the node publishes a VOD of each of the user's recorded livestreams as soon as the livestream ends." - } - } } } } diff --git a/lexicons/place/stream/server/getPreferences.json b/lexicons/place/stream/server/getPreferences.json deleted file mode 100644 index 24f374d71..000000000 --- a/lexicons/place/stream/server/getPreferences.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "lexicon": 1, - "id": "place.stream.server.getPreferences", - "defs": { - "main": { - "type": "query", - "description": "Get the authenticated user's private preferences on this node.", - "output": { - "encoding": "application/json", - "schema": { - "type": "object", - "required": ["preferences"], - "properties": { - "preferences": { - "type": "ref", - "ref": "place.stream.server.defs#preferences" - } - } - } - } - } - } -} diff --git a/lexicons/place/stream/server/putPreferences.json b/lexicons/place/stream/server/putPreferences.json deleted file mode 100644 index db666b92b..000000000 --- a/lexicons/place/stream/server/putPreferences.json +++ /dev/null @@ -1,36 +0,0 @@ -{ - "lexicon": 1, - "id": "place.stream.server.putPreferences", - "defs": { - "main": { - "type": "procedure", - "description": "Update the authenticated user's private preferences on this node. Omitted fields keep their current values.", - "input": { - "encoding": "application/json", - "schema": { - "type": "object", - "required": [], - "properties": { - "autoPublishVods": { - "type": "boolean", - "description": "Whether the node publishes a VOD of each of the user's recorded livestreams as soon as the livestream ends." - } - } - } - }, - "output": { - "encoding": "application/json", - "schema": { - "type": "object", - "required": ["preferences"], - "properties": { - "preferences": { - "type": "ref", - "ref": "place.stream.server.defs#preferences" - } - } - } - } - } - } -} diff --git a/lexicons/place/stream/server/settings.json b/lexicons/place/stream/server/settings.json index cfd50cb80..db83fcb04 100644 --- a/lexicons/place/stream/server/settings.json +++ b/lexicons/place/stream/server/settings.json @@ -17,6 +17,10 @@ "livestreamRecording": { "type": "boolean", "description": "Whether this node should record your livestreams into VODs that you can publish" + }, + "autoPublishVods": { + "type": "boolean", + "description": "Whether this node should publish the VOD of each of your recorded livestreams as soon as it ends" } } } diff --git a/pkg/atproto/auto_publish_vod_test.go b/pkg/atproto/auto_publish_vod_test.go index 55fc4bb96..98976e3fa 100644 --- a/pkg/atproto/auto_publish_vod_test.go +++ b/pkg/atproto/auto_publish_vod_test.go @@ -31,7 +31,10 @@ func TestAutoPublishVODScheduledWhenSeenEnding(t *testing.T) { })) optIn := func(on bool) { t.Helper() - require.NoError(t, atsync.StatefulDB.PutUserPreferences(ctx, &statedb.UserPreferences{RepoDID: did, AutoPublishVODs: on})) + var buf bytes.Buffer + require.NoError(t, (&placestream.ServerSettings{AutoPublishVods: &on}).MarshalCBOR(&buf)) + rec := buf.Bytes() + require.NoError(t, mod.UpdateServerSettings(ctx, &model.ServerSettings{Server: atsync.CLI.BroadcasterHost, RepoDID: did, Record: &rec})) } // Fixed times, so indexing the same record again is byte-identical. diff --git a/pkg/placestream/serverdefs.go b/pkg/placestream/serverdefs.go index 07c6b7fed..b568b9388 100644 --- a/pkg/placestream/serverdefs.go +++ b/pkg/placestream/serverdefs.go @@ -11,33 +11,6 @@ import ( cbg "github.com/whyrusleeping/cbor-gen" ) -// ServerDefs_Preferences is a "preferences" in the place.stream.server.defs schema. -// -// A user's private preferences on this Streamplace node. The node stores them itself; they are not a record in the user's repository. -type ServerDefs_Preferences struct { - LexiconTypeID string `json:"$type,omitempty"` - // autoPublishVods: Whether the node publishes a VOD of each of the user's recorded livestreams as soon as the livestream ends. - AutoPublishVods bool `json:"autoPublishVods"` -} - -// RecordTypeID implements glex.Record. -func (t *ServerDefs_Preferences) RecordTypeID() string { return "place.stream.server.defs#preferences" } - -func (t *ServerDefs_Preferences) MarshalCBOR(w io.Writer) error { - if t == nil { - _, err := w.Write(cbg.CborNull) - return err - } - // stamp $type on a copy so marshal never mutates the record - cp := *t - cp.LexiconTypeID = "place.stream.server.defs#preferences" - return glex.MarshalCBOR(w, &cp) -} - -func (t *ServerDefs_Preferences) UnmarshalCBOR(r io.Reader) error { - return glex.UnmarshalCBOR(r, t) -} - // ServerDefs_RewriteRule is a "rewriteRule" in the place.stream.server.defs schema. type ServerDefs_RewriteRule struct { LexiconTypeID string `json:"$type,omitempty"` diff --git a/pkg/placestream/servergetpreferences.go b/pkg/placestream/servergetpreferences.go deleted file mode 100644 index ae88e8de3..000000000 --- a/pkg/placestream/servergetpreferences.go +++ /dev/null @@ -1,50 +0,0 @@ -// Code generated by glex; DO NOT EDIT. - -// Lexicon schema: place.stream.server.getPreferences - -package placestream - -import ( - "context" - "io" - - glex "github.com/streamplace/glex/runtime" - cbg "github.com/whyrusleeping/cbor-gen" -) - -type ServerGetPreferences_Output struct { - LexiconTypeID string `json:"$type,omitempty"` - Preferences ServerDefs_Preferences `json:"preferences"` -} - -// RecordTypeID implements glex.Record. -func (t *ServerGetPreferences_Output) RecordTypeID() string { - return "place.stream.server.getPreferences" -} - -func (t *ServerGetPreferences_Output) MarshalCBOR(w io.Writer) error { - if t == nil { - _, err := w.Write(cbg.CborNull) - return err - } - // stamp $type on a copy so marshal never mutates the record - cp := *t - cp.LexiconTypeID = "place.stream.server.getPreferences" - return glex.MarshalCBOR(w, &cp) -} - -func (t *ServerGetPreferences_Output) UnmarshalCBOR(r io.Reader) error { - return glex.UnmarshalCBOR(r, t) -} - -// ServerGetPreferences calls the XRPC method "place.stream.server.getPreferences". -// -// Get the authenticated user's private preferences on this node. -func ServerGetPreferences(ctx context.Context, c glex.LexClient) (*ServerGetPreferences_Output, error) { - var out ServerGetPreferences_Output - - if err := c.LexDo(ctx, glex.Query, "", "place.stream.server.getPreferences", nil, nil, &out); err != nil { - return nil, err - } - return &out, nil -} diff --git a/pkg/placestream/serverputpreferences.go b/pkg/placestream/serverputpreferences.go deleted file mode 100644 index f808ebad5..000000000 --- a/pkg/placestream/serverputpreferences.go +++ /dev/null @@ -1,76 +0,0 @@ -// Code generated by glex; DO NOT EDIT. - -// Lexicon schema: place.stream.server.putPreferences - -package placestream - -import ( - "context" - "io" - - glex "github.com/streamplace/glex/runtime" - cbg "github.com/whyrusleeping/cbor-gen" -) - -type ServerPutPreferences_Input struct { - LexiconTypeID string `json:"$type,omitempty"` - // autoPublishVods: Whether the node publishes a VOD of each of the user's recorded livestreams as soon as the livestream ends. - AutoPublishVods *bool `json:"autoPublishVods,omitempty"` -} - -// RecordTypeID implements glex.Record. -func (t *ServerPutPreferences_Input) RecordTypeID() string { - return "place.stream.server.putPreferences" -} - -func (t *ServerPutPreferences_Input) MarshalCBOR(w io.Writer) error { - if t == nil { - _, err := w.Write(cbg.CborNull) - return err - } - // stamp $type on a copy so marshal never mutates the record - cp := *t - cp.LexiconTypeID = "place.stream.server.putPreferences" - return glex.MarshalCBOR(w, &cp) -} - -func (t *ServerPutPreferences_Input) UnmarshalCBOR(r io.Reader) error { - return glex.UnmarshalCBOR(r, t) -} - -type ServerPutPreferences_Output struct { - LexiconTypeID string `json:"$type,omitempty"` - Preferences ServerDefs_Preferences `json:"preferences"` -} - -// RecordTypeID implements glex.Record. -func (t *ServerPutPreferences_Output) RecordTypeID() string { - return "place.stream.server.putPreferences" -} - -func (t *ServerPutPreferences_Output) MarshalCBOR(w io.Writer) error { - if t == nil { - _, err := w.Write(cbg.CborNull) - return err - } - // stamp $type on a copy so marshal never mutates the record - cp := *t - cp.LexiconTypeID = "place.stream.server.putPreferences" - return glex.MarshalCBOR(w, &cp) -} - -func (t *ServerPutPreferences_Output) UnmarshalCBOR(r io.Reader) error { - return glex.UnmarshalCBOR(r, t) -} - -// ServerPutPreferences calls the XRPC method "place.stream.server.putPreferences". -// -// Update the authenticated user's private preferences on this node. Omitted fields keep their current values. -func ServerPutPreferences(ctx context.Context, c glex.LexClient, input *ServerPutPreferences_Input) (*ServerPutPreferences_Output, error) { - var out ServerPutPreferences_Output - - if err := c.LexDo(ctx, glex.Procedure, "application/json", "place.stream.server.putPreferences", nil, input, &out); err != nil { - return nil, err - } - return &out, nil -} diff --git a/pkg/placestream/serversettings.go b/pkg/placestream/serversettings.go index 9b06d772b..d3808a5eb 100644 --- a/pkg/placestream/serversettings.go +++ b/pkg/placestream/serversettings.go @@ -19,6 +19,8 @@ func init() { // Record containing user settings for a particular Streamplace node type ServerSettings struct { LexiconTypeID string `json:"$type,omitempty"` + // autoPublishVods: Whether this node should publish the VOD of each of your recorded livestreams as soon as it ends + AutoPublishVods *bool `json:"autoPublishVods,omitempty"` // debugRecording: Whether this node may archive your livestream for improving the service DebugRecording *bool `json:"debugRecording,omitempty"` // livestreamRecording: Whether this node should record your livestreams into VODs that you can publish diff --git a/pkg/spxrpc/preferences.go b/pkg/spxrpc/preferences.go deleted file mode 100644 index 03f86c797..000000000 --- a/pkg/spxrpc/preferences.go +++ /dev/null @@ -1,42 +0,0 @@ -package spxrpc - -import ( - "context" - "net/http" - - "github.com/labstack/echo/v4" - "github.com/streamplace/oatproxy/pkg/oatproxy" - "stream.place/streamplace/pkg/log" - placestream "stream.place/streamplace/pkg/placestream" -) - -func (s *Server) handlePlaceStreamServerGetPreferences(ctx context.Context) (*placestream.ServerGetPreferences_Output, error) { - session, _ := oatproxy.GetOAuthSession(ctx) - if session == nil { - return nil, echo.NewHTTPError(http.StatusUnauthorized, "oauth session not found") - } - prefs, err := s.statefulDB.GetUserPreferences(ctx, session.DID) - if err != nil { - return nil, echo.NewHTTPError(http.StatusInternalServerError, "get preferences: "+err.Error()) - } - return &placestream.ServerGetPreferences_Output{Preferences: prefs.ToLexicon()}, nil -} - -func (s *Server) handlePlaceStreamServerPutPreferences(ctx context.Context, input *placestream.ServerPutPreferences_Input) (*placestream.ServerPutPreferences_Output, error) { - session, _ := oatproxy.GetOAuthSession(ctx) - if session == nil { - return nil, echo.NewHTTPError(http.StatusUnauthorized, "oauth session not found") - } - prefs, err := s.statefulDB.GetUserPreferences(ctx, session.DID) - if err != nil { - return nil, echo.NewHTTPError(http.StatusInternalServerError, "get preferences: "+err.Error()) - } - if input.AutoPublishVods != nil { - prefs.AutoPublishVODs = *input.AutoPublishVods - } - if err := s.statefulDB.PutUserPreferences(ctx, prefs); err != nil { - return nil, echo.NewHTTPError(http.StatusInternalServerError, "save preferences: "+err.Error()) - } - log.Log(ctx, "preferences updated", "did", session.DID, "autoPublishVods", prefs.AutoPublishVODs) - return &placestream.ServerPutPreferences_Output{Preferences: prefs.ToLexicon()}, nil -} diff --git a/pkg/spxrpc/stubs.go b/pkg/spxrpc/stubs.go index e8712a8e3..11f8c795e 100644 --- a/pkg/spxrpc/stubs.go +++ b/pkg/spxrpc/stubs.go @@ -475,12 +475,10 @@ func (s *Server) RegisterHandlersPlacestream(e *echo.Echo) error { e.POST("/xrpc/place.stream.server.createWebhook", s.HandlePlaceStreamServerCreateWebhook) e.POST("/xrpc/place.stream.server.deleteStorage", s.HandlePlaceStreamServerDeleteStorage) e.POST("/xrpc/place.stream.server.deleteWebhook", s.HandlePlaceStreamServerDeleteWebhook) - e.GET("/xrpc/place.stream.server.getPreferences", s.HandlePlaceStreamServerGetPreferences) e.GET("/xrpc/place.stream.server.getServerTime", s.HandlePlaceStreamServerGetServerTime) e.GET("/xrpc/place.stream.server.getStorage", s.HandlePlaceStreamServerGetStorage) e.GET("/xrpc/place.stream.server.getWebhook", s.HandlePlaceStreamServerGetWebhook) e.GET("/xrpc/place.stream.server.listWebhooks", s.HandlePlaceStreamServerListWebhooks) - e.POST("/xrpc/place.stream.server.putPreferences", s.HandlePlaceStreamServerPutPreferences) e.POST("/xrpc/place.stream.server.updateWebhook", s.HandlePlaceStreamServerUpdateWebhook) e.POST("/xrpc/place.stream.server.upsertStorage", s.HandlePlaceStreamServerUpsertStorage) e.POST("/xrpc/place.stream.vod.createDraft", s.HandlePlaceStreamVodCreateDraft) @@ -1453,19 +1451,6 @@ func (s *Server) HandlePlaceStreamServerDeleteWebhook(c echo.Context) error { return c.JSON(200, out) } -func (s *Server) HandlePlaceStreamServerGetPreferences(c echo.Context) error { - ctx, span := otel.Tracer("server").Start(c.Request().Context(), "HandlePlaceStreamServerGetPreferences") - defer span.End() - var out *placestream.ServerGetPreferences_Output - var handleErr error - // func (s *Server) handlePlaceStreamServerGetPreferences(ctx context.Context) (*placestream.ServerGetPreferences_Output, error) - out, handleErr = s.handlePlaceStreamServerGetPreferences(ctx) - if handleErr != nil { - return handleErr - } - return c.JSON(200, out) -} - func (s *Server) HandlePlaceStreamServerGetServerTime(c echo.Context) error { ctx, span := otel.Tracer("server").Start(c.Request().Context(), "HandlePlaceStreamServerGetServerTime") defer span.End() @@ -1537,23 +1522,6 @@ func (s *Server) HandlePlaceStreamServerListWebhooks(c echo.Context) error { return c.JSON(200, out) } -func (s *Server) HandlePlaceStreamServerPutPreferences(c echo.Context) error { - ctx, span := otel.Tracer("server").Start(c.Request().Context(), "HandlePlaceStreamServerPutPreferences") - defer span.End() - var body placestream.ServerPutPreferences_Input - if err := c.Bind(&body); err != nil { - return err - } - var out *placestream.ServerPutPreferences_Output - var handleErr error - // func (s *Server) handlePlaceStreamServerPutPreferences(ctx context.Context,body *placestream.ServerPutPreferences_Input) (*placestream.ServerPutPreferences_Output, error) - out, handleErr = s.handlePlaceStreamServerPutPreferences(ctx, &body) - if handleErr != nil { - return handleErr - } - return c.JSON(200, out) -} - func (s *Server) HandlePlaceStreamServerUpdateWebhook(c echo.Context) error { ctx, span := otel.Tracer("server").Start(c.Request().Context(), "HandlePlaceStreamServerUpdateWebhook") defer span.End() diff --git a/pkg/statedb/auto_publish_vod.go b/pkg/statedb/auto_publish_vod.go index 3715ca326..8c68bea3f 100644 --- a/pkg/statedb/auto_publish_vod.go +++ b/pkg/statedb/auto_publish_vod.go @@ -12,8 +12,9 @@ import ( ) // AutoPublishVODTask publishes the VOD of a livestream the node has just seen -// end, for a streamer who had UserPreferences.AutoPublishVODs on then and -// still has it on when the task runs. It is queued by ScheduleAutoPublishVOD. +// end, for a streamer who had autoPublishVods on in their +// place.stream.server.settings for this node then, and still has it on when +// the task runs. It is queued by ScheduleAutoPublishVOD. type AutoPublishVODTask struct { LivestreamURI string `json:"livestreamURI"` // Waits counts the passes that found part of the recording still being @@ -38,14 +39,28 @@ const ( // node has just seen end, if its streamer has automatic VOD publishing on. // Once per livestream: scheduling it again is a no-op. func (state *StatefulDB) ScheduleAutoPublishVOD(ctx context.Context, repoDID, livestreamURI string) error { - prefs, err := state.GetUserPreferences(ctx, repoDID) + on, err := state.autoPublishVODsOn(ctx, repoDID) + if err != nil || !on { + return err + } + return state.enqueueAutoPublishVOD(ctx, AutoPublishVODTask{LivestreamURI: livestreamURI}) +} + +// autoPublishVODsOn reports whether the streamer has autoPublishVods on in +// their settings record for this node. Off unless set: it is opt-in. +func (state *StatefulDB) autoPublishVODsOn(ctx context.Context, repoDID string) (bool, error) { + settings, err := state.model.GetServerSettings(ctx, state.CLI.BroadcasterHost, repoDID) if err != nil { - return fmt.Errorf("get user preferences: %w", err) + return false, fmt.Errorf("get server settings: %w", err) } - if !prefs.AutoPublishVODs { - return nil + if settings == nil { + return false, nil } - return state.enqueueAutoPublishVOD(ctx, AutoPublishVODTask{LivestreamURI: livestreamURI}) + rec, err := settings.ToStreamplaceServerSettings() + if err != nil { + return false, fmt.Errorf("decode server settings: %w", err) + } + return rec.AutoPublishVods != nil && *rec.AutoPublishVods, nil } func (state *StatefulDB) enqueueAutoPublishVOD(ctx context.Context, t AutoPublishVODTask) error { @@ -80,11 +95,11 @@ func (state *StatefulDB) processAutoPublishVODTask(ctx context.Context, task *Ap } // The streamer may have turned it off since the livestream ended. - prefs, err := state.GetUserPreferences(ctx, ls.RepoDID) + on, err := state.autoPublishVODsOn(ctx, ls.RepoDID) if err != nil { - return fmt.Errorf("get user preferences: %w", err) + return err } - if !prefs.AutoPublishVODs { + if !on { log.Log(ctx, "automatic VOD publishing was turned off after the livestream ended; skipping") return state.CompleteTask(ctx, task.ID) } diff --git a/pkg/statedb/auto_publish_vod_test.go b/pkg/statedb/auto_publish_vod_test.go index 626e3d429..23812b8e3 100644 --- a/pkg/statedb/auto_publish_vod_test.go +++ b/pkg/statedb/auto_publish_vod_test.go @@ -1,6 +1,7 @@ package statedb import ( + "bytes" "context" "encoding/json" "strings" @@ -8,37 +9,24 @@ import ( "time" "github.com/stretchr/testify/require" + "stream.place/streamplace/pkg/model" "stream.place/streamplace/pkg/placestream" ) -func TestUserPreferences(t *testing.T) { - WithAllDatabases(t, func(state *StatefulDB) { - ctx := context.Background() - prefs, err := state.GetUserPreferences(ctx, "did:plc:prefs") - require.NoError(t, err) - require.False(t, prefs.AutoPublishVODs, "off until the user opts in") - - prefs.AutoPublishVODs = true - require.NoError(t, state.PutUserPreferences(ctx, prefs)) - prefs, err = state.GetUserPreferences(ctx, "did:plc:prefs") - require.NoError(t, err) - require.True(t, prefs.AutoPublishVODs) - - prefs.AutoPublishVODs = false - require.NoError(t, state.PutUserPreferences(ctx, prefs), "saving over existing preferences") - prefs, err = state.GetUserPreferences(ctx, "did:plc:prefs") - require.NoError(t, err) - require.False(t, prefs.AutoPublishVODs) - - other, err := state.GetUserPreferences(ctx, "did:plc:other") - require.NoError(t, err) - require.False(t, other.AutoPublishVODs, "one user's preferences are not another's") - }) -} - +// setAutoPublishVODs indexes did's place.stream.server.settings record for +// this node with autoPublishVods set. func setAutoPublishVODs(t *testing.T, state *StatefulDB, did string, on bool) { t.Helper() - require.NoError(t, state.PutUserPreferences(context.Background(), &UserPreferences{RepoDID: did, AutoPublishVODs: on})) + var buf bytes.Buffer + require.NoError(t, (&placestream.ServerSettings{AutoPublishVods: &on}).MarshalCBOR(&buf)) + rec := buf.Bytes() + // Replace rather than save over: these tests' CLI has no broadcaster + // host, and Save inserts when a primary key (server) is empty. + ctx := context.Background() + require.NoError(t, state.model.DeleteServerSettings(ctx, state.CLI.BroadcasterHost, did)) + require.NoError(t, state.model.UpdateServerSettings(ctx, &model.ServerSettings{ + Server: state.CLI.BroadcasterHost, RepoDID: did, Record: &rec, + })) } // endedLivestream seeds an ended livestream record for did, a streamer opted @@ -86,8 +74,13 @@ func runAutoPublish(t *testing.T, state *StatefulDB, did, uri string) { func TestScheduleAutoPublishVOD(t *testing.T) { WithAllDatabases(t, func(state *StatefulDB) { ctx := context.Background() + uri := endedLivestream(t, state, "did:plc:nosettings") + require.NoError(t, state.model.DeleteServerSettings(ctx, state.CLI.BroadcasterHost, "did:plc:nosettings")) + require.NoError(t, state.ScheduleAutoPublishVOD(ctx, "did:plc:nosettings", uri)) + require.Empty(t, pendingTasks(t, state, TaskAutoPublishVOD), "off for a streamer with no settings record: it is opt-in") + did := "did:plc:optedout" - uri := endedLivestream(t, state, did) + uri = endedLivestream(t, state, did) setAutoPublishVODs(t, state, did, false) require.NoError(t, state.ScheduleAutoPublishVOD(ctx, did, uri)) require.Empty(t, pendingTasks(t, state, TaskAutoPublishVOD), "nothing for a streamer who has it off when the livestream ends") diff --git a/pkg/statedb/statedb.go b/pkg/statedb/statedb.go index 4956d7d7e..2785bd175 100644 --- a/pkg/statedb/statedb.go +++ b/pkg/statedb/statedb.go @@ -86,7 +86,6 @@ var StatefulDBModels = []any{ Upload{}, DraftVideo{}, CDNLogPart{}, - UserPreferences{}, } var NoPostgresDatabaseCode = "3D000" diff --git a/pkg/statedb/user_preferences.go b/pkg/statedb/user_preferences.go deleted file mode 100644 index 99cf78558..000000000 --- a/pkg/statedb/user_preferences.go +++ /dev/null @@ -1,56 +0,0 @@ -package statedb - -import ( - "context" - "errors" - "time" - - "gorm.io/gorm" - "gorm.io/gorm/clause" - placestream "stream.place/streamplace/pkg/placestream" -) - -// UserPreferences are a user's private preferences on this node. Unlike the -// place.stream.server.settings record, they are not in the user's repo: the -// node keeps them here and serves them over place.stream.server.getPreferences -// and putPreferences. -type UserPreferences struct { - RepoDID string `gorm:"column:repo_did;primarykey"` - // AutoPublishVODs publishes the VOD of each of the user's recorded - // livestreams as soon as the livestream ends. - AutoPublishVODs bool `gorm:"column:auto_publish_vods;not null;default:false"` - CreatedAt time.Time `gorm:"column:created_at"` - UpdatedAt time.Time `gorm:"column:updated_at"` -} - -func (p *UserPreferences) TableName() string { - return "user_preferences" -} - -// GetUserPreferences returns repoDID's preferences, or the defaults if they -// have never set any. -func (state *StatefulDB) GetUserPreferences(ctx context.Context, repoDID string) (*UserPreferences, error) { - var prefs UserPreferences - err := state.DB.WithContext(ctx).Where("repo_did = ?", repoDID).First(&prefs).Error - if errors.Is(err, gorm.ErrRecordNotFound) { - return &UserPreferences{RepoDID: repoDID}, nil - } - if err != nil { - return nil, err - } - return &prefs, nil -} - -// PutUserPreferences creates or replaces a user's preferences. -func (state *StatefulDB) PutUserPreferences(ctx context.Context, prefs *UserPreferences) error { - return state.DB.WithContext(ctx).Clauses(clause.OnConflict{ - Columns: []clause.Column{{Name: "repo_did"}}, - DoUpdates: clause.AssignmentColumns([]string{"auto_publish_vods", "updated_at"}), - }).Create(prefs).Error -} - -func (p *UserPreferences) ToLexicon() placestream.ServerDefs_Preferences { - return placestream.ServerDefs_Preferences{ - AutoPublishVods: p.AutoPublishVODs, - } -} -- knotmirror 0.0.1