feat: add access control to channels with various modes

- Introduced AccessMode enum to define channel access levels: Public, PasswordProtected, AccountRequired, and OwnerOnly.
- Updated Channel and ProgrammingBlock entities to include access_mode and access_password_hash fields.
- Enhanced create and update channel functionality to handle access mode and password.
- Implemented access checks in channel routes based on the defined access modes.
- Modified frontend components to support channel creation and editing with access control options.
- Added ChannelPasswordModal for handling password input when accessing restricted channels.
- Updated API calls to include channel and block passwords as needed.
- Created database migrations to add access_mode and access_password_hash columns to channels table.
This commit is contained in:
2026-03-14 01:45:10 +01:00
parent 924e162563
commit 81df6eb8ff
25 changed files with 635 additions and 53 deletions

View File

@@ -98,22 +98,22 @@ export function useActiveSchedule(channelId: string) {
});
}
export function useCurrentBroadcast(channelId: string) {
export function useCurrentBroadcast(channelId: string, channelPassword?: string) {
const { token } = useAuthContext();
return useQuery({
queryKey: ["broadcast", channelId],
queryFn: () => api.schedule.getCurrentBroadcast(channelId, token ?? ""),
queryKey: ["broadcast", channelId, channelPassword],
queryFn: () => api.schedule.getCurrentBroadcast(channelId, token ?? "", channelPassword),
enabled: !!channelId,
refetchInterval: 30_000,
retry: false,
});
}
export function useEpg(channelId: string, from?: string, until?: string) {
export function useEpg(channelId: string, from?: string, until?: string, channelPassword?: string) {
const { token } = useAuthContext();
return useQuery({
queryKey: ["epg", channelId, from, until],
queryFn: () => api.schedule.getEpg(channelId, token ?? "", from, until),
queryKey: ["epg", channelId, from, until, channelPassword],
queryFn: () => api.schedule.getEpg(channelId, token ?? "", from, until, channelPassword),
enabled: !!channelId,
});
}

View File

@@ -130,17 +130,25 @@ export function useStreamUrl(
channelId: string | undefined,
token: string | null,
slotId: string | undefined,
channelPassword?: string,
blockPassword?: string,
) {
return useQuery({
queryKey: ["stream-url", channelId, slotId],
queryKey: ["stream-url", channelId, slotId, channelPassword, blockPassword],
queryFn: async (): Promise<string | null> => {
const params = new URLSearchParams();
if (token) params.set("token", token);
if (channelPassword) params.set("channel_password", channelPassword);
if (blockPassword) params.set("block_password", blockPassword);
const res = await fetch(`/api/stream/${channelId}?${params}`, {
cache: "no-store",
});
if (res.status === 204) return null;
if (!res.ok) throw new Error(`Stream resolve failed: ${res.status}`);
if (!res.ok) {
const body = await res.json().catch(() => ({}));
const msg = body?.error ?? `Stream resolve failed: ${res.status}`;
throw new Error(msg);
}
const { url } = await res.json();
return url as string;
},