feat: role system
This commit is contained in:
25
src/shared/store/AuthStore/api.ts
Normal file
25
src/shared/store/AuthStore/api.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import { languageInstance } from "@shared";
|
||||
import { User, UserCity } from "../UserStore";
|
||||
|
||||
export const getMeApi = async (): Promise<User> => {
|
||||
const response = await languageInstance("ru").get("/auth/me");
|
||||
return response.data as User;
|
||||
};
|
||||
|
||||
export const getMeCitiesApi = async (): Promise<{
|
||||
ru: UserCity[];
|
||||
en: UserCity[];
|
||||
zh: UserCity[];
|
||||
}> => {
|
||||
const [ru, en, zh] = await Promise.all([
|
||||
languageInstance("ru").get("/auth/me"),
|
||||
languageInstance("en").get("/auth/me"),
|
||||
languageInstance("zh").get("/auth/me"),
|
||||
]);
|
||||
|
||||
return {
|
||||
ru: ((ru.data as User).cities ?? []),
|
||||
en: ((en.data as User).cities ?? []),
|
||||
zh: ((zh.data as User).cities ?? []),
|
||||
};
|
||||
};
|
||||
@@ -1,15 +1,13 @@
|
||||
import { API_URL, decodeJWT } from "@shared";
|
||||
import { API_URL, decodeJWT, mobxFetch } from "@shared";
|
||||
import { canRead as checkCanRead, canWrite as checkCanWrite } from "../../lib/permissions";
|
||||
import { makeAutoObservable, runInAction } from "mobx";
|
||||
import axios, { AxiosError } from "axios";
|
||||
import { User, UserCity } from "../UserStore";
|
||||
import { getMeApi, getMeCitiesApi } from "./api";
|
||||
|
||||
type LoginResponse = {
|
||||
token: string;
|
||||
user: {
|
||||
id: number;
|
||||
name: string;
|
||||
email: string;
|
||||
is_admin: boolean;
|
||||
};
|
||||
user: Pick<User, "id" | "name" | "email" | "is_admin" | "cities">;
|
||||
};
|
||||
|
||||
class AuthStore {
|
||||
@@ -48,7 +46,7 @@ class AuthStore {
|
||||
{
|
||||
email,
|
||||
password,
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
const data = response.data;
|
||||
@@ -89,6 +87,78 @@ class AuthStore {
|
||||
get user() {
|
||||
return this.payload?.user;
|
||||
}
|
||||
|
||||
get isAdmin(): boolean {
|
||||
return (
|
||||
this.me?.is_admin === true ||
|
||||
(this.me?.roles ?? []).includes("admin")
|
||||
);
|
||||
}
|
||||
|
||||
me: User | null = null;
|
||||
meLoading = false;
|
||||
meError: string | null = null;
|
||||
|
||||
meCities: { ru: UserCity[]; en: UserCity[]; zh: UserCity[] } = {
|
||||
ru: [],
|
||||
en: [],
|
||||
zh: [],
|
||||
};
|
||||
|
||||
getMeAction = mobxFetch<void, User, AuthStore>({
|
||||
store: this,
|
||||
value: "me",
|
||||
loading: "meLoading",
|
||||
error: "meError",
|
||||
fn: getMeApi,
|
||||
onSuccess: () => {
|
||||
this.fetchMeCities();
|
||||
},
|
||||
});
|
||||
|
||||
fetchMeCities = async () => {
|
||||
const cities = await getMeCitiesApi();
|
||||
runInAction(() => {
|
||||
this.meCities = cities;
|
||||
});
|
||||
};
|
||||
|
||||
canWrite = (resource: string): boolean => {
|
||||
const roles = this.me?.roles ?? [];
|
||||
|
||||
if (roles.includes("admin")) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (resource === "map") {
|
||||
return roles.some((role) =>
|
||||
["routes_rw", "stations_rw", "sights_rw"].includes(role),
|
||||
);
|
||||
}
|
||||
|
||||
return checkCanWrite(roles, resource);
|
||||
};
|
||||
|
||||
hasRole = (role: string): boolean => {
|
||||
const roles = this.me?.roles ?? [];
|
||||
return roles.includes("admin") || roles.includes(role);
|
||||
};
|
||||
|
||||
canRead = (resource: string): boolean => {
|
||||
if (resource === "map") {
|
||||
return this.canWrite("map");
|
||||
}
|
||||
return checkCanRead(this.me?.roles, resource);
|
||||
};
|
||||
|
||||
canAccess = (permission: string): boolean => {
|
||||
// If permission looks like a concrete role (e.g. snapshot_create/snapshot_rw),
|
||||
// check it as-is; otherwise treat it as a resource name.
|
||||
if (permission.includes("_")) {
|
||||
return this.hasRole(permission);
|
||||
}
|
||||
return this.canRead(permission);
|
||||
};
|
||||
}
|
||||
|
||||
export const authStore = new AuthStore();
|
||||
|
||||
Reference in New Issue
Block a user