Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions api/api/versions.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@
{
"version": "v1",
"status": "active",
"release_date": "2025-06-28T00:59:10.212737+05:30",
"release_date": "2025-06-30T20:48:43.246107+05:30",
"end_of_life": "0001-01-01T00:00:00Z",
"changes": ["Initial API version"]
"changes": [
"Initial API version"
]
}
]
}
}
2 changes: 1 addition & 1 deletion api/doc/openapi.json

Large diffs are not rendered by default.

20 changes: 13 additions & 7 deletions api/internal/routes.go
Original file line number Diff line number Diff line change
Expand Up @@ -222,11 +222,17 @@ func (router *Router) Routes() {
featureFlagStorage := &feature_flags_storage.FeatureFlagStorage{DB: router.app.Store.DB, Ctx: router.app.Ctx}
featureFlagService := feature_flags_service.NewFeatureFlagService(featureFlagStorage, l, router.app.Ctx)
featureFlagController := feature_flags_controller.NewFeatureFlagController(featureFlagService, l, router.app.Ctx, router.cache)
featureFlagGroup := fuego.Group(server, apiV1.Path+"/feature-flags")
fuego.Use(featureFlagGroup, func(next http.Handler) http.Handler {

// We need to allow everyone to read feature flags
featureFlagReadGroup := fuego.Group(server, apiV1.Path+"/feature-flags")
featureFlagWriteGroup := fuego.Group(server, apiV1.Path+"/feature-flags")

// Apply RBAC middleware only to write operations (update feature flags)
fuego.Use(featureFlagWriteGroup, func(next http.Handler) http.Handler {
return middleware.RBACMiddleware(next, router.app, "feature_flags")
})
router.FeatureFlagRoutes(featureFlagGroup, featureFlagController)

router.FeatureFlagRoutes(featureFlagReadGroup, featureFlagWriteGroup, featureFlagController)

containerController := container.NewContainerController(router.app.Store, router.app.Ctx, l, notificationManager)
containerGroup := fuego.Group(server, apiV1.Path+"/container")
Expand Down Expand Up @@ -383,10 +389,10 @@ func (router *Router) UpdateRoutes(s *fuego.Server, updateController *update.Upd
fuego.Post(s, "", updateController.PerformUpdate)
}

func (router *Router) FeatureFlagRoutes(s *fuego.Server, featureFlagController *feature_flags_controller.FeatureFlagController) {
fuego.Get(s, "", featureFlagController.GetFeatureFlags)
fuego.Put(s, "", featureFlagController.UpdateFeatureFlag)
fuego.Get(s, "/check", featureFlagController.IsFeatureEnabled)
func (router *Router) FeatureFlagRoutes(readGroup *fuego.Server, writeGroup *fuego.Server, featureFlagController *feature_flags_controller.FeatureFlagController) {
fuego.Get(readGroup, "", featureFlagController.GetFeatureFlags)
fuego.Put(writeGroup, "", featureFlagController.UpdateFeatureFlag)
fuego.Get(readGroup, "/check", featureFlagController.IsFeatureEnabled)
}

func (router *Router) ContainerRoutes(s *fuego.Server, containerController *container.ContainerController) {
Expand Down
139 changes: 76 additions & 63 deletions view/app/settings/general/components/AccountSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import {
SelectValue
} from '@/components/ui/select';
import { Switch } from '@/components/ui/switch';
import { RBACGuard } from '@/components/rbac/RBACGuard';

interface AccountSectionProps {
username: string;
Expand Down Expand Up @@ -118,12 +119,14 @@ function AccountSection({
}}
placeholder={t('settings.account.username.placeholder')}
/>
<Button
onClick={handleUsernameChange}
disabled={isLoading || username === user?.username}
>
{t('settings.account.username.update')}
</Button>
<RBACGuard resource="user" action="update">
<Button
onClick={handleUsernameChange}
disabled={isLoading || username === user?.username}
>
{t('settings.account.username.update')}
</Button>
</RBACGuard>
</div>

{usernameError && <p className="text-sm text-red-500">{usernameError}</p>}
Expand Down Expand Up @@ -155,18 +158,20 @@ function AccountSection({
{t('settings.account.email.notVerified.description')}
</AlertDescription>
</Alert>
<Button
onClick={handleSendVerification}
disabled={isSendingVerification || verificationSent}
variant="outline"
className="w-full"
>
{isSendingVerification
? t('settings.account.email.notVerified.sending')
: verificationSent
? t('settings.account.email.notVerified.sent')
: t('settings.account.email.notVerified.sendButton')}
</Button>
<RBACGuard resource="user" action="update">
<Button
onClick={handleSendVerification}
disabled={isSendingVerification || verificationSent}
variant="outline"
className="w-full"
>
{isSendingVerification
? t('settings.account.email.notVerified.sending')
: verificationSent
? t('settings.account.email.notVerified.sent')
: t('settings.account.email.notVerified.sendButton')}
</Button>
</RBACGuard>
{verificationError && <p className="text-sm text-red-500">{verificationError}</p>}
{verificationSent && (
<Alert variant="default">
Expand Down Expand Up @@ -194,44 +199,48 @@ function AccountSection({
<p className="text-muted-foreground text-sm">
{t('settings.account.preferences.appearance')}
</p>
<ModeToggle />
<RBACGuard resource="user" action="update">
<ModeToggle />
</RBACGuard>
</div>
<div className="flex justify-between items-center">
<p className="text-muted-foreground text-sm">{t('settings.preferences.font')}</p>
<Select
value={userSettings.font_family || 'outfit'}
onValueChange={handleFontChange}
disabled={isUpdatingFont}
>
<SelectTrigger className="w-[180px]">
<SelectValue placeholder={t('settings.preferences.font')} />
</SelectTrigger>
<SelectContent>
<SelectItem value="geist">{t('settings.preferences.fontOptions.geist')}</SelectItem>
<SelectItem value="inter">{t('settings.preferences.fontOptions.inter')}</SelectItem>
<SelectItem value="roboto">
{t('settings.preferences.fontOptions.roboto')}
</SelectItem>
<SelectItem value="poppins">
{t('settings.preferences.fontOptions.poppins')}
</SelectItem>
<SelectItem value="montserrat">
{t('settings.preferences.fontOptions.montserrat')}
</SelectItem>
<SelectItem value="space-grotesk">
{t('settings.preferences.fontOptions.spaceGrotesk')}
</SelectItem>
<SelectItem value="outfit">
{t('settings.preferences.fontOptions.outfit')}
</SelectItem>
<SelectItem value="jakarta">
{t('settings.preferences.fontOptions.jakarta')}
</SelectItem>
<SelectItem value="system">
{t('settings.preferences.fontOptions.system')}
</SelectItem>
</SelectContent>
</Select>
<RBACGuard resource="user" action="update">
<Select
value={userSettings.font_family || 'outfit'}
onValueChange={handleFontChange}
disabled={isUpdatingFont}
>
<SelectTrigger className="w-[180px]">
<SelectValue placeholder={t('settings.preferences.font')} />
</SelectTrigger>
<SelectContent>
<SelectItem value="geist">{t('settings.preferences.fontOptions.geist')}</SelectItem>
<SelectItem value="inter">{t('settings.preferences.fontOptions.inter')}</SelectItem>
<SelectItem value="roboto">
{t('settings.preferences.fontOptions.roboto')}
</SelectItem>
<SelectItem value="poppins">
{t('settings.preferences.fontOptions.poppins')}
</SelectItem>
<SelectItem value="montserrat">
{t('settings.preferences.fontOptions.montserrat')}
</SelectItem>
<SelectItem value="space-grotesk">
{t('settings.preferences.fontOptions.spaceGrotesk')}
</SelectItem>
<SelectItem value="outfit">
{t('settings.preferences.fontOptions.outfit')}
</SelectItem>
<SelectItem value="jakarta">
{t('settings.preferences.fontOptions.jakarta')}
</SelectItem>
<SelectItem value="system">
{t('settings.preferences.fontOptions.system')}
</SelectItem>
</SelectContent>
</Select>
</RBACGuard>
</div>
</CardContent>
</Card>
Expand All @@ -245,11 +254,13 @@ function AccountSection({
<CardContent>
<div className="flex items-center justify-between">
<p className="text-sm text-gray-500">{t('settings.preferences.language.select')}</p>
<LanguageSwitcher
handleLanguageChange={handleLanguageChange}
isUpdatingLanguage={isUpdatingLanguage}
userSettings={userSettings}
/>
<RBACGuard resource="user" action="update">
<LanguageSwitcher
handleLanguageChange={handleLanguageChange}
isUpdatingLanguage={isUpdatingLanguage}
userSettings={userSettings}
/>
</RBACGuard>
</div>
</CardContent>
</Card>
Expand All @@ -263,11 +274,13 @@ function AccountSection({
<CardContent>
<div className="flex items-center justify-between">
<p className="text-sm text-gray-500">{t('settings.preferences.autoUpdate.select')}</p>
<Switch
checked={userSettings.auto_update}
onCheckedChange={handleAutoUpdateChange}
disabled={isUpdatingAutoUpdate}
/>
<RBACGuard resource="user" action="update">
<Switch
checked={userSettings.auto_update}
onCheckedChange={handleAutoUpdateChange}
disabled={isUpdatingAutoUpdate}
/>
</RBACGuard>
</div>
</CardContent>
</Card>
Expand Down
13 changes: 8 additions & 5 deletions view/app/settings/general/components/AvatarSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/com
import UploadAvatar from '@/components/ui/upload_avatar';
import { User } from '@/redux/types/user';
import { useTranslation } from '@/hooks/use-translation';
import { RBACGuard } from '@/components/rbac/RBACGuard';

interface AvatarSectionProps {
onImageChange: (imageUrl: string | null) => void;
Expand All @@ -20,11 +21,13 @@ function AvatarSection({ onImageChange, user }: AvatarSectionProps) {
<CardDescription>{t('settings.account.avatar.description')}</CardDescription>
</CardHeader>
<CardContent className="flex flex-col items-center pt-6">
<UploadAvatar
onImageChange={onImageChange}
username={user?.username}
initialImage={user?.avatar}
/>
<RBACGuard resource="user" action="update">
<UploadAvatar
onImageChange={onImageChange}
username={user?.username}
initialImage={user?.avatar}
/>
</RBACGuard>
</CardContent>
</Card>
</div>
Expand Down
87 changes: 46 additions & 41 deletions view/app/settings/general/components/FeatureFlagsSettings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
} from '@/redux/services/feature-flags/featureFlagsApi';
import { Separator } from '@/components/ui/separator';
import { FeatureFlag, FeatureName, featureGroups } from '@/types/feature-flags';
import { RBACGuard } from '@/components/rbac/RBACGuard';

export default function FeatureFlagsSettings() {
const { t } = useTranslation();
Expand Down Expand Up @@ -54,48 +55,52 @@ export default function FeatureFlagsSettings() {
const groupedFeatures = getGroupedFeatures();

return (
<TabsContent value="feature-flags" className="space-y-6 mt-4">
<Card>
<CardHeader>
<CardTitle>{t('settings.featureFlags.title')}</CardTitle>
<CardDescription>{t('settings.featureFlags.description')}</CardDescription>
</CardHeader>
<CardContent className="space-y-6">
{Array.from(groupedFeatures.entries()).map(([group, features], index) => (
<div key={group} className="space-y-4">
<div className="space-y-2">
<h3 className="text-lg font-semibold">
{t(`settings.featureFlags.groups.${group}.title`)}
</h3>
</div>
<div className="space-y-4">
{features?.map((feature) => (
<div
key={feature.feature_name}
className="flex items-center justify-between p-2 rounded-lg"
>
<div className="space-y-1">
<h4 className="text-sm font-medium">
{t(`settings.featureFlags.features.${feature.feature_name}.title`)}
</h4>
<p className="text-sm text-muted-foreground">
{t(`settings.featureFlags.features.${feature.feature_name}.description`)}
</p>
<RBACGuard resource="feature-flags" action="read">
<TabsContent value="feature-flags" className="space-y-6 mt-4">
<Card>
<CardHeader>
<CardTitle>{t('settings.featureFlags.title')}</CardTitle>
<CardDescription>{t('settings.featureFlags.description')}</CardDescription>
</CardHeader>
<CardContent className="space-y-6">
{Array.from(groupedFeatures.entries()).map(([group, features], index) => (
<div key={group} className="space-y-4">
<div className="space-y-2">
<h3 className="text-lg font-semibold">
{t(`settings.featureFlags.groups.${group}.title`)}
</h3>
</div>
<div className="space-y-4">
{features?.map((feature) => (
<div
key={feature.feature_name}
className="flex items-center justify-between p-2 rounded-lg"
>
<div className="space-y-1">
<h4 className="text-sm font-medium">
{t(`settings.featureFlags.features.${feature.feature_name}.title`)}
</h4>
<p className="text-sm text-muted-foreground">
{t(`settings.featureFlags.features.${feature.feature_name}.description`)}
</p>
</div>
<RBACGuard resource="feature-flags" action="update">
<Switch
checked={feature.is_enabled}
onCheckedChange={(checked) =>
handleToggleFeature(feature.feature_name, checked)
}
/>
</RBACGuard>
</div>
<Switch
checked={feature.is_enabled}
onCheckedChange={(checked) =>
handleToggleFeature(feature.feature_name, checked)
}
/>
</div>
))}
))}
</div>
{index !== groupedFeatures.size - 1 && <Separator />}
</div>
{index !== groupedFeatures.size - 1 && <Separator />}
</div>
))}
</CardContent>
</Card>
</TabsContent>
))}
</CardContent>
</Card>
</TabsContent>
</RBACGuard>
);
}
19 changes: 11 additions & 8 deletions view/app/settings/general/components/SecuritySection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { TabsContent } from '@/components/ui/tabs';
import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert';
import { useTranslation } from '@/hooks/use-translation';
import { TwoFactorSetup } from '@/app/settings/general/components/TwoFactorSetup';
import { RBACGuard } from '@/components/rbac/RBACGuard';

interface SecuritySectionProps {
emailSent: boolean;
Expand Down Expand Up @@ -50,14 +51,16 @@ function SecuritySection({
</AlertDescription>
</Alert>
) : (
<Button
onClick={handlePasswordResetRequest}
disabled={isLoading}
variant="outline"
className="w-full lg:w-auto"
>
{t('settings.security.password.reset.button')}
</Button>
<RBACGuard resource="user" action="update">
<Button
onClick={handlePasswordResetRequest}
disabled={isLoading}
variant="outline"
className="w-full lg:w-auto"
>
{t('settings.security.password.reset.button')}
</Button>
</RBACGuard>
)}
</CardContent>
</Card>
Expand Down
Loading
Loading