Skip to content

Commit

Permalink
refactor: rev-based moderation caching
Browse files Browse the repository at this point in the history
  • Loading branch information
mary-ext committed Dec 24, 2023
1 parent 59b6400 commit 2800611
Show file tree
Hide file tree
Showing 9 changed files with 103 additions and 36 deletions.
32 changes: 23 additions & 9 deletions app/api/moderation/decisions/post.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
// @todo: move this to ~/com as it's now making use of SharedPreferences

import { sequal } from '~/utils/dequal.ts';

import type { SignalizedPost } from '../../stores/posts.ts';

import {
type ModerationCause,
type ModerationDecision,
decideLabelModeration,
decideMutedKeywordModeration,
decideMutedPermanentModeration,
Expand All @@ -12,23 +15,34 @@ import {
} from '../action.ts';
import { PreferenceWarn } from '../enums.ts';

import { isProfileTempMuted, type SharedPreferencesObject } from '~/com/components/SharedPreferences.tsx';
import { type SharedPreferencesObject, isProfileTempMuted } from '~/com/components/SharedPreferences.tsx';

export const getPostModDecision = (post: SignalizedPost, opts: SharedPreferencesObject) => {
const { moderation, filters } = opts;
type ModerationResult = { d: ModerationDecision | null; c: unknown[] };
const cached = new WeakMap<SignalizedPost, ModerationResult>();

export const getPostModDecision = (post: SignalizedPost, opts: SharedPreferencesObject) => {
const labels = post.labels.value;
const text = post.record.value.text;

const authorDid = post.author.did;
const isMuted = post.author.viewer.muted.value;

const accu: ModerationCause[] = [];
const key: unknown[] = [labels, text, isMuted, opts.rev];

let res = cached.get(post);

if (!res || !sequal(res.c, key)) {
const { moderation, filters } = opts;

const accu: ModerationCause[] = [];

decideLabelModeration(accu, labels, authorDid, moderation);
decideMutedPermanentModeration(accu, isMuted);
decideMutedTemporaryModeration(accu, isProfileTempMuted(filters, authorDid));
decideMutedKeywordModeration(accu, text, PreferenceWarn, moderation);

decideLabelModeration(accu, labels, authorDid, moderation);
decideMutedPermanentModeration(accu, isMuted);
decideMutedTemporaryModeration(accu, isProfileTempMuted(filters, authorDid));
decideMutedKeywordModeration(accu, text, PreferenceWarn, moderation);
cached.set(post, (res = { d: finalizeModeration(accu), c: key }));
}

return finalizeModeration(accu);
return res.d;
};
36 changes: 25 additions & 11 deletions app/api/moderation/decisions/quote.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
// @todo: move this to ~/com as it's now making use of SharedPreferences

import { sequal } from '~/utils/dequal.ts';

import type { Records, UnionOf } from '../../atp-schema.ts';

import {
type ModerationCause,
type ModerationDecision,
decideLabelModeration,
decideMutedKeywordModeration,
decideMutedPermanentModeration,
Expand All @@ -17,21 +20,32 @@ import { type SharedPreferencesObject, isProfileTempMuted } from '~/com/componen
type EmbeddedPostRecord = UnionOf<'app.bsky.embed.record#viewRecord'>;
type PostRecord = Records['app.bsky.feed.post'];

type ModerationResult = { d: ModerationDecision | null; c: unknown[] };
const cached = new WeakMap<EmbeddedPostRecord, ModerationResult>();

export const getQuoteModDecision = (quote: EmbeddedPostRecord, opts: SharedPreferencesObject) => {
const { moderation, filters } = opts;
const key = [quote, opts.rev];

let res = cached.get(quote);

if (!res || !sequal(res.c, key)) {
const { moderation, filters } = opts;

const labels = quote.labels;
const text = (quote.value as PostRecord).text;

const labels = quote.labels;
const text = (quote.value as PostRecord).text;
const authorDid = quote.author.did;
const isMuted = quote.author.viewer?.muted;

const authorDid = quote.author.did;
const isMuted = quote.author.viewer?.muted;
const accu: ModerationCause[] = [];

const accu: ModerationCause[] = [];
decideLabelModeration(accu, labels, authorDid, moderation);
decideMutedPermanentModeration(accu, isMuted);
decideMutedTemporaryModeration(accu, isProfileTempMuted(filters, authorDid));
decideMutedKeywordModeration(accu, text, PreferenceWarn, moderation);

decideLabelModeration(accu, labels, authorDid, moderation);
decideMutedPermanentModeration(accu, isMuted);
decideMutedTemporaryModeration(accu, isProfileTempMuted(filters, authorDid));
decideMutedKeywordModeration(accu, text, PreferenceWarn, moderation);
cached.set(quote, (res = { d: finalizeModeration(accu), c: key }));
}

return finalizeModeration(accu);
return res.d;
};
9 changes: 9 additions & 0 deletions app/com/components/SharedPreferences.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import type { FilterPreferences, LanguagePreferences } from '~/api/types.ts';
import type { ModerationOpts } from '~/api/moderation/types.ts';

export interface SharedPreferencesObject {
/** Used as a cache-busting mechanism */
rev: number;
moderation: ModerationOpts;
filters: FilterPreferences;
language: LanguagePreferences;
Expand All @@ -24,3 +26,10 @@ export const isProfileTempMuted = (prefs: FilterPreferences, actor: DID): number
const date = prefs.tempMutes[actor];
return date !== undefined && Date.now() < date ? date : null;
};

export const useBustRevCache = () => {
const prefs = useSharedPreferences();
return () => {
prefs.rev = ~~(Math.random() * 1024);
};
};
18 changes: 13 additions & 5 deletions app/com/components/dialogs/MuteConfirmDialog.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { type JSX, createSignal, createMemo } from 'solid-js';
import { type JSX, createSignal, createMemo, batch } from 'solid-js';

import { type InfiniteData, useQueryClient } from '@pkg/solid-query';

Expand All @@ -21,7 +21,7 @@ import { Select } from '~/com/primitives/select.ts';
import DialogOverlay from './DialogOverlay.tsx';

import TakingActionNotice from '../views/TakingActionNotice.tsx';
import { isProfileTempMuted } from '../SharedPreferences.tsx';
import { isProfileTempMuted, useBustRevCache } from '../SharedPreferences.tsx';

import DefaultListAvatar from '../../assets/default-list-avatar.svg?url';

Expand Down Expand Up @@ -95,6 +95,7 @@ const renderMuteConfirmDialog = (
forceTempMute?: boolean,
) => {
const queryClient = useQueryClient();
const bustRev = useBustRevCache();

const isTempMuted = isProfileTempMuted(filters, profile.did);
const isMuted = profile.viewer.muted.value || isTempMuted;
Expand All @@ -114,7 +115,11 @@ const renderMuteConfirmDialog = (
if (isMuted) {
if (isTempMuted) {
const tempMutes = filters.tempMutes;
delete tempMutes[did];

batch(() => {
delete tempMutes[did];
bustRev();
});
} else {
updateProfileMute(profile, false);
}
Expand All @@ -128,9 +133,12 @@ const renderMuteConfirmDialog = (
updateProfileMute(profile, true);
} else {
const date = Date.now() + parsedDuration;

const tempMutes = filters.tempMutes;
tempMutes[did] = date;

batch(() => {
tempMutes[did] = date;
bustRev();
});
}

const updateTimeline = produceTimelineFilter(did);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,8 +74,6 @@ const TemporaryMutesView = () => {
}
}

console.log({ prev, next });

return next;
}, []);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import type { ModerationLabelOpts } from '~/api/moderation/types.ts';

import { assert } from '~/utils/misc.ts';

import { preferences } from '~/desktop/globals/settings.ts';
import { bustRevisionCache, preferences } from '~/desktop/globals/settings.ts';

import { Flyout, offsetlessMiddlewares } from '~/com/components/Flyout.tsx';

Expand Down Expand Up @@ -288,6 +288,8 @@ const renderOptionsScreen = (opts: ModerationLabelOpts, global: boolean) => {
const k = label.k;
labels[k] = next;
}

bustRevisionCache();
});
}}
>
Expand All @@ -307,7 +309,10 @@ const renderOptionsScreen = (opts: ModerationLabelOpts, global: boolean) => {
global={global}
value={labels[k]}
onChange={(next) => {
labels[k] = next;
batch(() => {
labels[k] = next;
bustRevisionCache();
});
}}
>
<button class={`${labelItem} items-start justify-between gap-4 pl-8`}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,8 +97,6 @@ const TemporaryMutesView = () => {
}
}

console.log({ prev, next });

return next;
}, []);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import type { ModerationFilterKeywordOpts } from '~/api/moderation/types.ts';
import { createRadioModel, model } from '~/utils/input.ts';
import { getUniqueId } from '~/utils/misc.ts';

import { preferences } from '~/desktop/globals/settings.ts';
import { bustRevisionCache, preferences } from '~/desktop/globals/settings.ts';

import { Button } from '~/com/primitives/button.ts';
import { IconButton } from '~/com/primitives/icon-button.ts';
Expand Down Expand Up @@ -87,6 +87,7 @@ const KeywordFilterFormView = () => {
});
}

bustRevisionCache();
router.move({ type: VIEW_KEYWORD_FILTERS });
});
};
Expand Down Expand Up @@ -215,6 +216,7 @@ const KeywordFilterFormView = () => {
filters.splice(index, 1);
}

bustRevisionCache();
router.move({ type: VIEW_KEYWORD_FILTERS });
});
}}
Expand Down
27 changes: 23 additions & 4 deletions app/desktop/globals/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,18 @@ import { PreferenceWarn } from '~/api/moderation/enums.ts';
import type { ModerationOpts } from '~/api/moderation/types.ts';
import type { FilterPreferences, LanguagePreferences } from '~/api/types.ts';

import { getCurrentTid } from '~/api/utils/tid.ts';

import { createReactiveLocalStorage } from '~/utils/storage.ts';

import type { SharedPreferencesObject } from '~/com/components/SharedPreferences.tsx';

import { type DeckConfig, type PaneConfig, PaneSize, SpecificPaneSize } from './panes.ts';
import { getCurrentTid } from '~/api/utils/tid.ts';

export interface PreferencesSchema {
$version: 2;
$version: 3;
/** Used for cache-busting moderation filters */
rev: number;
/** Onboarding mode */
onboarding: boolean;
/** Deck configuration */
Expand Down Expand Up @@ -41,7 +44,8 @@ const PREF_KEY = 'rantai_prefs';
export const preferences = createReactiveLocalStorage<PreferencesSchema>(PREF_KEY, (version, prev) => {
if (version === 0) {
const object: PreferencesSchema = {
$version: 2,
$version: 3,
rev: 0,
onboarding: true,
decks: [],
ui: {
Expand Down Expand Up @@ -106,15 +110,26 @@ export const preferences = createReactiveLocalStorage<PreferencesSchema>(PREF_KE
_prev.a11y = {
warnNoMediaAlt: true,
};
}

if (version < 3) {
const _prev = prev as PreferencesSchema;

_prev.$version = 2;
_prev.rev = 0;
_prev.$version = 3;
}

return prev;
});

export const createSharedPreferencesObject = (): SharedPreferencesObject => {
return {
get rev() {
return preferences.rev;
},
set rev(next) {
preferences.rev = next;
},
// ModerationOpts contains internal state properties, we don't want them
// to be reflected back into persisted storage.
moderation: {
Expand All @@ -125,6 +140,10 @@ export const createSharedPreferencesObject = (): SharedPreferencesObject => {
};
};

export const bustRevisionCache = () => {
preferences.rev = ~~(Math.random() * 1024);
};

export const addPane = <T extends PaneConfig>(
deck: DeckConfig,
partial: Omit<T, 'id' | 'size' | 'title'>,
Expand Down

0 comments on commit 2800611

Please sign in to comment.