So many things

This commit is contained in:
2026-02-17 21:52:59 -06:00
parent 8e225aa254
commit a99c9f5102
27 changed files with 5398 additions and 123 deletions
+2
View File
@@ -8,6 +8,8 @@ export const optima = {
credential: (await import("./optima-api/modules/credentials")).credential,
credentialType: (await import("./optima-api/modules/credentialTypes"))
.credentialType,
role: (await import("./optima-api/modules/roles")).role,
permission: (await import("./optima-api/modules/permissions")).permission,
user,
};
/**
+17 -1
View File
@@ -1,8 +1,16 @@
import api from "../axios";
export const company = {
async fetch(accessToken: string, id: string) {
async fetch(
accessToken: string,
id: string,
options?: { includeAddress?: boolean },
) {
const params: Record<string, string> = {};
if (options?.includeAddress) params.includeAddress = "true";
const company = await api.get(`/v1/company/companies/${id}`, {
params,
headers: {
Authorization: `Bearer ${accessToken}`,
},
@@ -27,6 +35,14 @@ export const company = {
return companies.data;
},
async count(accessToken: string) {
const response = await api.get("/v1/company/count", {
headers: {
Authorization: `Bearer ${accessToken}`,
},
});
return response.data.data.count;
},
async fetchConfigurations(accessToken: string, id: string) {
const configurations = await api.get(
`/v1/company/companies/${id}/configurations`,
+47
View File
@@ -0,0 +1,47 @@
import api from "../axios";
export interface PermissionNode {
node: string;
description: string;
usedIn: string[];
dependencies?: string[];
}
export interface PermissionCategory {
name: string;
description: string;
permissions: PermissionNode[];
}
export interface PermissionsCategorized {
[category: string]: PermissionCategory;
}
export const permission = {
async fetchCategorized(accessToken: string) {
const response = await api.get("/v1/permissions", {
headers: {
Authorization: `Bearer ${accessToken}`,
},
});
return response.data;
},
async fetchFlat(accessToken: string) {
const response = await api.get("/v1/permissions/nodes", {
headers: {
Authorization: `Bearer ${accessToken}`,
},
});
return response.data;
},
async fetchByCategory(accessToken: string, category: string) {
const response = await api.get(`/v1/permissions/${category}`, {
headers: {
Authorization: `Bearer ${accessToken}`,
},
});
return response.data;
},
};
+104
View File
@@ -0,0 +1,104 @@
import api from "../axios";
export interface Role {
id: string;
title: string;
moniker: string;
permissions: string[];
createdAt: string;
updatedAt: string;
}
export const role = {
async fetchMany(accessToken: string) {
const response = await api.get("/v1/role", {
headers: {
Authorization: `Bearer ${accessToken}`,
},
});
return response.data;
},
async fetch(accessToken: string, identifier: string) {
const response = await api.get(`/v1/role/${identifier}`, {
headers: {
Authorization: `Bearer ${accessToken}`,
},
});
return response.data;
},
async create(
accessToken: string,
data: Omit<Role, "id" | "createdAt" | "updatedAt">,
) {
const response = await api.post("/v1/role", data, {
headers: {
Authorization: `Bearer ${accessToken}`,
},
});
return response.data;
},
async update(
accessToken: string,
identifier: string,
updates: Partial<Omit<Role, "id" | "createdAt" | "updatedAt">>,
) {
const response = await api.patch(`/v1/role/${identifier}`, updates, {
headers: {
Authorization: `Bearer ${accessToken}`,
},
});
return response.data;
},
async delete(accessToken: string, identifier: string) {
const response = await api.delete(`/v1/role/${identifier}`, {
headers: {
Authorization: `Bearer ${accessToken}`,
},
});
return response.data;
},
async addPermissions(
accessToken: string,
identifier: string,
permissions: string[],
) {
const response = await api.post(
`/v1/role/${identifier}/permissions`,
{ permissions },
{
headers: {
Authorization: `Bearer ${accessToken}`,
},
},
);
return response.data;
},
async removePermissions(
accessToken: string,
identifier: string,
permissions: string[],
) {
const response = await api.delete(`/v1/role/${identifier}/permissions`, {
headers: {
Authorization: `Bearer ${accessToken}`,
},
data: { permissions },
});
return response.data;
},
async fetchUsers(accessToken: string, identifier: string) {
const response = await api.get(`/v1/role/${identifier}/users`, {
headers: {
Authorization: `Bearer ${accessToken}`,
},
});
return response.data;
},
};
+51
View File
@@ -0,0 +1,51 @@
import { optima } from "$lib";
export type PermissionMap = Record<string, boolean>;
/**
* Check multiple permissions for the current user and return a map of
* permission → boolean. Designed to be called from any +page.server.ts
* or +layout.server.ts load function.
*
* @example
* ```ts
* const perms = await checkPermissions(accessToken, [
* "company.fetch.address",
* "credential.create",
* ]);
* // perms => { "company.fetch.address": true, "credential.create": false }
* ```
*/
export async function checkPermissions(
accessToken: string,
permissions: string[],
): Promise<PermissionMap> {
if (!permissions.length) return {};
try {
const result = await optima.user.checkPermissions(accessToken, permissions);
const results: Array<{ permission: string; hasPermission: boolean }> =
result?.data?.results ?? [];
return results.reduce<PermissionMap>((map, entry) => {
map[entry.permission] = entry.hasPermission === true;
return map;
}, {});
} catch (err) {
console.error("Permission check failed:", err);
// Default every requested permission to false on failure
return permissions.reduce<PermissionMap>((map, p) => {
map[p] = false;
return map;
}, {});
}
}
/**
* Convenience helper — returns true when a specific permission is
* granted inside a PermissionMap.
*/
export function hasPermission(map: PermissionMap, permission: string): boolean {
return map[permission] === true;
}
+39
View File
@@ -0,0 +1,39 @@
import { writable } from "svelte/store";
import { browser } from "$app/environment";
type Theme = "light" | "dark";
function createThemeStore() {
const initial: Theme = browser
? ((localStorage.getItem("theme") as Theme) ?? "dark")
: "dark";
const { subscribe, set, update } = writable<Theme>(initial);
function applyTheme(theme: Theme) {
if (browser) {
document.documentElement.setAttribute("data-theme", theme);
localStorage.setItem("theme", theme);
}
}
// Apply on init
if (browser) applyTheme(initial);
return {
subscribe,
toggle() {
update((current) => {
const next = current === "dark" ? "light" : "dark";
applyTheme(next);
return next;
});
},
set(theme: Theme) {
applyTheme(theme);
set(theme);
},
};
}
export const theme = createThemeStore();