sync: phase 1 backend + frontend from server
This commit is contained in:
+18
-3
@@ -1,12 +1,17 @@
|
||||
const API_BASE = '/api/v1';
|
||||
|
||||
export interface ApiError extends Error {
|
||||
status?: number;
|
||||
retryAfter?: number;
|
||||
}
|
||||
|
||||
async function request<T>(method: string, path: string, body?: unknown): Promise<T> {
|
||||
const headers: Record<string, string> = {};
|
||||
if (body !== undefined) {
|
||||
headers['Content-Type'] = 'application/json';
|
||||
}
|
||||
|
||||
const response = await fetch(`${API_BASE}${path}`, {
|
||||
const response = await fetch(API_BASE + path, {
|
||||
method,
|
||||
headers,
|
||||
credentials: 'include',
|
||||
@@ -14,7 +19,8 @@ async function request<T>(method: string, path: string, body?: unknown): Promise
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
let errorMessage = `Request failed: ${response.status}`;
|
||||
let errorMessage = 'Request failed: ' + response.status;
|
||||
let retryAfter: number | undefined;
|
||||
try {
|
||||
const errorData = await response.json();
|
||||
if (typeof errorData?.message === 'string') {
|
||||
@@ -22,10 +28,19 @@ async function request<T>(method: string, path: string, body?: unknown): Promise
|
||||
} else if (typeof errorData?.error === 'string') {
|
||||
errorMessage = errorData.error;
|
||||
}
|
||||
if (response.status === 429 && typeof errorData?.retry_after === 'number') {
|
||||
retryAfter = errorData.retry_after;
|
||||
}
|
||||
} catch {
|
||||
// ignore parse error
|
||||
}
|
||||
throw new Error(errorMessage);
|
||||
const err = new Error(errorMessage) as ApiError;
|
||||
err.status = response.status;
|
||||
if (response.status === 429) {
|
||||
const ra = response.headers.get('Retry-After');
|
||||
err.retryAfter = retryAfter ?? (ra ? parseInt(ra, 10) : undefined);
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
|
||||
if (response.status === 204) {
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
import { useCallback } from 'react';
|
||||
import { useServerStore } from '../stores/server.ts';
|
||||
import { useAuthStore } from '../stores/auth.ts';
|
||||
import { useRoleStore } from '../stores/role.ts';
|
||||
|
||||
const PERMS = {
|
||||
VIEW_CHANNEL: 1,
|
||||
SEND_MESSAGES: 2,
|
||||
MANAGE_MESSAGES: 4,
|
||||
KICK_MEMBERS: 8,
|
||||
BAN_MEMBERS: 16,
|
||||
MANAGE_SERVER: 32,
|
||||
MANAGE_CHANNELS: 64,
|
||||
ADMINISTRATOR: 128,
|
||||
CONNECT_VOICE: 256,
|
||||
SPEAK_VOICE: 512,
|
||||
SHARE_SCREEN: 1024,
|
||||
} as const;
|
||||
|
||||
export { PERMS };
|
||||
|
||||
export function usePermissions(serverId: string | null) {
|
||||
const user = useAuthStore((s) => s.user);
|
||||
const servers = useServerStore((s) => s.servers);
|
||||
const roles = useRoleStore((s) => s.roles);
|
||||
|
||||
const server = serverId ? servers.find((s) => s.id === serverId) : null;
|
||||
const isOwner = Boolean(server && user && server.ownerId === user.id);
|
||||
|
||||
const memberRoles = roles.filter((r) => r.server_id === serverId);
|
||||
|
||||
const has = useCallback(
|
||||
(flag: number) => {
|
||||
if (!serverId || !user) return false;
|
||||
if (isOwner) return true;
|
||||
const effective = memberRoles.reduce((acc, r) => acc | r.permissions, 0);
|
||||
if ((effective & PERMS.ADMINISTRATOR) !== 0) return true;
|
||||
return (effective & flag) === flag;
|
||||
},
|
||||
[serverId, user, isOwner, memberRoles],
|
||||
);
|
||||
|
||||
return {
|
||||
canKick: has(PERMS.KICK_MEMBERS),
|
||||
canBan: has(PERMS.BAN_MEMBERS),
|
||||
canMute: has(PERMS.MANAGE_MESSAGES),
|
||||
canManageChannels: has(PERMS.MANAGE_CHANNELS),
|
||||
isOwner,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user