Skip to content

Commit ac0c933

Browse files
committed
Add Content Settings
1 parent 2cef159 commit ac0c933

10 files changed

Lines changed: 249 additions & 10 deletions

File tree

app/Http/Controllers/Api/AccountController.php

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -958,6 +958,41 @@ public function disablePushNotifications(Request $request)
958958
return $this->success();
959959
}
960960

961+
public function getContentSettings(Request $request)
962+
{
963+
$user = $request->user();
964+
965+
$res = [
966+
'hide_ai' => $user->hide_ai,
967+
'hide_sensitive' => $user->hide_sensitive,
968+
];
969+
970+
return $this->data($res);
971+
}
972+
973+
public function updateContentSettings(Request $request)
974+
{
975+
$validated = $request->validate([
976+
'hide_ai' => 'sometimes|boolean',
977+
'hide_sensitive' => 'sometimes|boolean',
978+
]);
979+
980+
$user = $request->user();
981+
982+
$changes = [
983+
'old' => $user->only(['hide_ai', 'hide_sensitive']),
984+
'new' => $validated,
985+
];
986+
987+
app(UserAuditLogService::class)->logAccountContentSettingsUpdate($user, $changes);
988+
989+
$user->update($validated);
990+
$user->profile->update($validated);
991+
992+
return $this->success();
993+
994+
}
995+
961996
public function profileLinkStore(Request $request)
962997
{
963998
$user = $request->user();

app/Models/Profile.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,8 @@
5858
* @property int $can_create_starter_kits
5959
* @property int $can_use_starter_kits
6060
* @property int $can_report
61+
* @property int $hide_ai
62+
* @property int $hide_sensitive
6163
* @property int $manuallyApprovesFollowers
6264
* @property-read \App\Models\User|null $user
6365
* @property-read \Illuminate\Database\Eloquent\Collection<int, \App\Models\Video> $videos
@@ -190,11 +192,15 @@ class Profile extends Model
190192
'can_create_starter_kits',
191193
'can_use_starter_kits',
192194
'can_report',
195+
'hide_ai',
196+
'hide_sensitive',
193197
];
194198

195199
protected $guarded = [];
196200

197201
protected $casts = [
202+
'hide_ai' => 'boolean',
203+
'hide_sensitive' => 'boolean',
198204
'status' => 'integer',
199205
'links' => 'array',
200206
'local' => 'boolean',

app/Models/User.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@
3434
* @property int $can_create_starter_kits
3535
* @property int $can_use_starter_kits
3636
* @property int $can_report
37+
* @property int $hide_ai
38+
* @property int $hide_sensitive
3739
* @property string|null $remember_token
3840
* @property \Illuminate\Support\Carbon|null $created_at
3941
* @property \Illuminate\Support\Carbon|null $updated_at
@@ -173,6 +175,8 @@ class User extends Authenticatable implements OAuthenticatable
173175
'apple_id',
174176
'register_source',
175177
'has_atom',
178+
'hide_ai',
179+
'hide_sensitive',
176180
];
177181

178182
protected $hidden = [
@@ -217,6 +221,8 @@ protected function casts(): array
217221
{
218222
return [
219223
'has_2fa' => 'boolean',
224+
'hide_ai' => 'boolean',
225+
'hide_sensitive' => 'boolean',
220226
'email_verified_at' => 'datetime',
221227
'last_active_at' => 'datetime',
222228
'password' => 'hashed',

app/Services/UserAuditLogService.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,16 @@ public function logAccountDisabledPushNotifications(
144144
]);
145145
}
146146

147+
public function logAccountContentSettingsUpdate(
148+
User|int $user,
149+
array $changedFields = [],
150+
): UserAuditLog {
151+
return $this->log($user, 'content_settings', [
152+
'old' => $changedFields['old'],
153+
'new' => $changedFields['new'],
154+
]);
155+
}
156+
147157
public function logAccountStarterKitSettings(
148158
User|int $user,
149159
array $changedFields = [],
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
<?php
2+
3+
use Illuminate\Database\Migrations\Migration;
4+
use Illuminate\Database\Schema\Blueprint;
5+
use Illuminate\Support\Facades\Schema;
6+
7+
return new class extends Migration
8+
{
9+
/**
10+
* Run the migrations.
11+
*/
12+
public function up(): void
13+
{
14+
Schema::table('users', function (Blueprint $table) {
15+
$table->boolean('hide_ai')->default(false);
16+
$table->boolean('hide_sensitive')->default(false);
17+
});
18+
19+
Schema::table('profiles', function (Blueprint $table) {
20+
$table->boolean('hide_ai')->default(false);
21+
$table->boolean('hide_sensitive')->default(false);
22+
});
23+
}
24+
25+
/**
26+
* Reverse the migrations.
27+
*/
28+
public function down(): void
29+
{
30+
Schema::table('users', function (Blueprint $table) {
31+
$table->dropColumn('hide_ai');
32+
$table->dropColumn('hide_sensitive');
33+
});
34+
35+
Schema::table('profiles', function (Blueprint $table) {
36+
$table->dropColumn('hide_ai');
37+
$table->dropColumn('hide_sensitive');
38+
});
39+
}
40+
};

resources/js/components/Settings/SettingsSidebar.vue

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,8 @@ import {
4141
MoonIcon,
4242
LockClosedIcon,
4343
ShareIcon,
44-
CheckBadgeIcon
44+
CheckBadgeIcon,
45+
VideoCameraIcon
4546
} from '@heroicons/vue/24/outline'
4647
4748
const { t } = useI18n()
@@ -58,6 +59,11 @@ const menuItems = ref([
5859
icon: MoonIcon,
5960
path: '/dashboard/appearance'
6061
},
62+
{
63+
name: 'Content',
64+
icon: VideoCameraIcon,
65+
path: '/dashboard/content'
66+
},
6167
{
6268
name: t('settings.privacy'),
6369
icon: EyeSlashIcon,

resources/js/pages/admin/ProfileShow.vue

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1027,7 +1027,8 @@ import {
10271027
UserIcon,
10281028
UserPlusIcon,
10291029
UsersIcon,
1030-
VideoCameraIcon
1030+
VideoCameraIcon,
1031+
Cog6ToothIcon
10311032
} from '@heroicons/vue/24/outline'
10321033
import DropdownDivider from '@/components/DropdownDivider.vue'
10331034
import AdminSendEmailModal from '@/components/Admin/AdminSendEmailModal.vue'
@@ -1179,7 +1180,8 @@ const userAuditTypeLabels = {
11791180
profile_links_add: 'New Profile Link',
11801181
profile_links_delete: 'Profile Link Deleted',
11811182
push_notifications_enabled: 'Push Notifications enabled',
1182-
push_notifications_disabled: 'Push Notifications disabled'
1183+
push_notifications_disabled: 'Push Notifications disabled',
1184+
content_settings: 'Content settings updated'
11831185
}
11841186
11851187
const userAuditTypeIcons = {
@@ -1198,13 +1200,15 @@ const userAuditTypeIcons = {
11981200
profile_links_add: LinkIcon,
11991201
profile_links_delete: LinkIcon,
12001202
push_notifications_enabled: BellAlertIcon,
1201-
push_notifications_disabled: BellSlashIcon
1203+
push_notifications_disabled: BellSlashIcon,
1204+
content_settings: Cog6ToothIcon
12021205
}
12031206
12041207
const userAuditTypeStyles = {
12051208
atom_enabled: 'bg-blue-50 text-blue-700 dark:bg-blue-900/30 dark:text-blue-300',
12061209
atom_disabled: 'bg-blue-50 text-blue-700 dark:bg-blue-900/30 dark:text-blue-300',
12071210
avatar_updated: 'bg-blue-50 text-blue-700 dark:bg-blue-900/30 dark:text-blue-300',
1211+
content_settings: 'bg-blue-50 text-blue-700 dark:bg-blue-900/30 dark:text-blue-300',
12081212
avatar_deleted: 'bg-amber-50 text-amber-700 dark:bg-amber-900/30 dark:text-amber-300',
12091213
profile_links_delete: 'bg-amber-50 text-amber-700 dark:bg-amber-900/30 dark:text-amber-300',
12101214
password_changed: 'bg-red-50 text-red-700 dark:bg-red-900/30 dark:text-red-300',
@@ -1666,6 +1670,30 @@ const UserAuditDiff = {
16661670
)
16671671
}
16681672
1673+
if (e.type === 'content_settings') {
1674+
const contentSettingsType = Object.hasOwn(e.value.new, 'hide_sensitive')
1675+
? 'nsfw'
1676+
: 'ai'
1677+
if (contentSettingsType === 'nsfw') {
1678+
return rowWrap(
1679+
inlineRow([
1680+
monoLabel('Hide Sensitive:'),
1681+
badge(false, e.value.old.hide_sensitive),
1682+
arrow(),
1683+
badge(true, e.value.new.hide_sensitive)
1684+
])
1685+
)
1686+
}
1687+
return rowWrap(
1688+
inlineRow([
1689+
monoLabel('Hide AI:'),
1690+
badge(false, e.value.old.hide_ai),
1691+
arrow(),
1692+
badge(true, e.value.new.hide_ai)
1693+
])
1694+
)
1695+
}
1696+
16691697
if (e.type === 'starter_kit_state') {
16701698
return rowWrap(
16711699
inlineRow([
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
<template>
2+
<SettingsLayout>
3+
<div class="p-6">
4+
<h1 class="text-2xl font-semibold tracking-tight mb-6 dark:text-gray-300">
5+
Content preferences
6+
</h1>
7+
<hr class="border-gray-300 dark:border-gray-700" />
8+
9+
<section v-if="!loaded" class="my-8">
10+
<Spinner />
11+
</section>
12+
13+
<section v-else class="my-8">
14+
<div class="flex flex-col gap-3">
15+
<div class="bg-white rounded-lg shadow-sm dark:bg-gray-800">
16+
<div class="px-4 py-6 flex items-center justify-between">
17+
<div class="flex flex-col max-w-[60%]">
18+
<h3 class="font-medium mb-2 dark:text-gray-300">
19+
Hide AI content in feeds
20+
</h3>
21+
<p class="text-xs text-gray-500 font-light">
22+
Control your exposure to content partially or fully authored by
23+
AI.
24+
</p>
25+
</div>
26+
<ToggleSwitch v-model="hideAI" />
27+
</div>
28+
</div>
29+
<div class="bg-white rounded-lg shadow-sm dark:bg-gray-800">
30+
<div class="px-4 py-6 flex items-center justify-between">
31+
<div class="flex flex-col max-w-[60%]">
32+
<h3 class="font-medium mb-2 dark:text-gray-300">
33+
Hide Sensitive Content in feeds
34+
</h3>
35+
<p class="text-xs text-gray-500 font-light">
36+
Allow sensitive content to be included in feeds, behind a
37+
content warning.
38+
</p>
39+
</div>
40+
<ToggleSwitch v-model="hideSensitive" />
41+
</div>
42+
</div>
43+
</div>
44+
</section>
45+
</div>
46+
</SettingsLayout>
47+
</template>
48+
49+
<script setup>
50+
import { onMounted, ref, watch } from 'vue'
51+
import SettingsLayout from '~/layouts/SettingsLayout.vue'
52+
import ToggleSwitch from '@/components/Form/ToggleSwitch.vue'
53+
54+
const apiClient = useApiClient()
55+
const hideAI = ref(false)
56+
const hideSensitive = ref(false)
57+
const loaded = ref(false)
58+
59+
watch(
60+
() => hideAI.value,
61+
async (newVal, oldVal) => {
62+
if (!loaded.value) {
63+
return
64+
}
65+
if (oldVal === null) return
66+
try {
67+
await apiClient.post('/api/v1/account/settings/content', {
68+
hide_ai: newVal
69+
})
70+
} catch {}
71+
}
72+
)
73+
74+
watch(
75+
() => hideSensitive.value,
76+
async (newVal, oldVal) => {
77+
if (!loaded.value) {
78+
return
79+
}
80+
if (oldVal === null) return
81+
try {
82+
await apiClient.post('/api/v1/account/settings/content', {
83+
hide_sensitive: newVal
84+
})
85+
} catch {}
86+
}
87+
)
88+
89+
const fetchContentSettings = async () => {
90+
try {
91+
await apiClient.get('/api/v1/account/settings/content').then((res) => {
92+
hideAI.value = res.data.data.hide_ai
93+
hideSensitive.value = res.data.data.hide_sensitive
94+
})
95+
} catch {
96+
} finally {
97+
await nextTick()
98+
loaded.value = true
99+
}
100+
}
101+
102+
onMounted(async () => {
103+
await fetchContentSettings()
104+
await nextTick()
105+
})
106+
</script>

resources/js/routes/index.js

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -435,12 +435,12 @@ const router = createRouter({
435435
// component: () => import('~/pages/dashboard/screen-time.vue'),
436436
// meta: { requiresAuth: true }
437437
// },
438-
// {
439-
// path: "/dashboard/preferences",
440-
// name: "dashboardPref",
441-
// component: () => import("~/pages/dashboard/preferences.vue"),
442-
// meta: { requiresAuth: true },
443-
// },
438+
{
439+
path: '/dashboard/content',
440+
name: 'dashboardAccountContentPref',
441+
component: () => import('~/pages/dashboard/account/AccountContent.vue'),
442+
meta: { requiresAuth: true }
443+
},
444444
{
445445
path: '/admin',
446446
component: () => import('~/layouts/AdminLayout.vue'),

routes/api.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,8 @@
213213
Route::post('/v1/account/settings/email/resend', [EmailChangeController::class, 'resendEmailChange'])->middleware(['auth:web,api']);
214214
Route::post('/v1/account/settings/account/disable', [SettingsController::class, 'confirmDisableAccount'])->middleware(['auth:web,api']);
215215
Route::post('/v1/account/settings/account/delete', [SettingsController::class, 'confirmDeleteAccount'])->middleware(['auth:web,api']);
216+
Route::get('/v1/account/settings/content', [AccountController::class, 'getContentSettings'])->middleware(['auth:web,api']);
217+
Route::post('/v1/account/settings/content', [AccountController::class, 'updateContentSettings'])->middleware(['auth:web,api']);
216218
Route::get('/v1/account/settings/links', [AccountController::class, 'getProfileLinks'])->middleware(['auth:web,api']);
217219
Route::post('/v1/account/settings/links/add', [AccountController::class, 'profileLinkStore'])->middleware(['auth:web,api']);
218220
Route::post('/v1/account/settings/links/delete/{id}', [AccountController::class, 'removeProfileLink'])->middleware(['auth:web,api']);

0 commit comments

Comments
 (0)