Compare commits
35 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 3d7db8b132 | |||
| cf16c8af49 | |||
| f8639c9eee | |||
| 3779cdc379 | |||
| 97b6c45bc0 | |||
| 2f2d95a510 | |||
| 21abf1a9bd | |||
| 13e7d2e6ae | |||
| 075074456b | |||
| 9b0ce047a6 | |||
| d531e1ca83 | |||
| db9b722929 | |||
| 06e021f8a1 | |||
| da6e0311d8 | |||
| 3c89f24189 | |||
| 70284bc14e | |||
| 987a1c8a6a | |||
| 6d951e426d | |||
| cdae4d47a4 | |||
| b7637334a6 | |||
| 3bdf562e44 | |||
| 3ab443790c | |||
| 7466bbdeac | |||
| 628dc35dea | |||
| 24d0c066fd | |||
| 7748e6171b | |||
| 4524c0258a | |||
| 8ee7dc15e5 | |||
| d96f18e6cf | |||
| e76caa68f1 | |||
| 1bf0acdf39 | |||
| 4be36e6ca0 | |||
| 935c7296f6 | |||
| 916e5a7ada | |||
| 573afbaeca |
@@ -0,0 +1,37 @@
|
|||||||
|
version: "3"
|
||||||
|
|
||||||
|
services:
|
||||||
|
pgsql:
|
||||||
|
image: postgres:17
|
||||||
|
restart: unless-stopped
|
||||||
|
environment:
|
||||||
|
POSTGRES_USER: optima
|
||||||
|
POSTGRES_PASSWORD: 123web123
|
||||||
|
POSTGRES_DB: optima
|
||||||
|
volumes:
|
||||||
|
- ./postgres:/var/lib/postgresql/data
|
||||||
|
ports:
|
||||||
|
- 5432:5432
|
||||||
|
|
||||||
|
redis:
|
||||||
|
image: redis:6.2-alpine
|
||||||
|
restart: always
|
||||||
|
ports:
|
||||||
|
- "6379:6379"
|
||||||
|
command: redis-server --save 20 1 --loglevel warning --requirepass iamatotallysecurepassworddonttestmebrox
|
||||||
|
volumes:
|
||||||
|
- ./redis:/data
|
||||||
|
|
||||||
|
adminer:
|
||||||
|
image: adminer
|
||||||
|
restart: unless-stopped
|
||||||
|
ports:
|
||||||
|
- 8080:8080
|
||||||
|
depends_on:
|
||||||
|
- pgsql
|
||||||
|
redisinsight:
|
||||||
|
image: redis/redisinsight:latest
|
||||||
|
container_name: redisinsight
|
||||||
|
ports:
|
||||||
|
- "5540:5540"
|
||||||
|
restart: unless-stopped
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
node_modules
|
|
||||||
.svelte-kit
|
|
||||||
build
|
|
||||||
out
|
|
||||||
.vite
|
|
||||||
.env
|
|
||||||
.env.*
|
|
||||||
*.dmg
|
|
||||||
*.zip
|
|
||||||
e2e
|
|
||||||
electron
|
|
||||||
forge.config.ts
|
|
||||||
forge.env.d.ts
|
|
||||||
vite.main.config.ts
|
|
||||||
vite.preload.config.ts
|
|
||||||
playwright.config.ts
|
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"action": "created",
|
||||||
|
"release": {
|
||||||
|
"tag_name": "v0.0.0-test"
|
||||||
|
}
|
||||||
|
}
|
||||||
+177
-113
@@ -1,157 +1,221 @@
|
|||||||
# Copilot Instructions for ttscm-ui
|
# Copilot / AI Agent Instructions for optima-api
|
||||||
|
|
||||||
## Project Overview
|
Purpose: make AI coding agents immediately productive in this repository by describing architecture, conventions, workflows, and helpful code pointers.
|
||||||
|
|
||||||
**ttscm-ui** is an Electron desktop application built with **SvelteKit**, TypeScript, and Vite. It connects to the Optima API for credential and company management. The app uses standard SvelteKit routing for single-page navigation and bun for package management with patches applied to SvelteKit.
|
---
|
||||||
|
|
||||||
## Architecture Layers
|
## Big picture
|
||||||
|
|
||||||
### Electron Architecture
|
This is a TypeScript API service (runs on **Bun**) using the **Hono** framework. The HTTP surface is implemented in `src/api` where small route-handler files are mounted on a versioned router in `src/api/server.ts` (see the `/v1` mount). The typical request flow is:
|
||||||
|
|
||||||
- **`electron/main.ts`**: Main process—creates/manages windows, handles file system access. Loads preload script and serves the renderer.
|
```
|
||||||
- **`electron/preload.ts`**: Currently empty bridge between main and renderer processes. Extend here to expose secure IPC handlers if needed.
|
server.ts → routers/<domain>Router.ts → api/<domain>/*.ts (route handlers) → managers/<domain>.ts → controllers (domain models) / modules / generated/prisma
|
||||||
- **`forge.config.ts`**: Electron Forge configuration with Vite plugin for building main, preload, and renderer targets.
|
|
||||||
|
|
||||||
### Frontend (SvelteKit)
|
|
||||||
|
|
||||||
- **`src/routes/`**: SvelteKit file-based routing with standard pathname router.
|
|
||||||
- `(auth)` group: Authentication pages (login)
|
|
||||||
- `(secure)` group: Protected pages requiring auth
|
|
||||||
- **`src/lib/`**: Reusable modules
|
|
||||||
- `optima-api/`: API client abstraction with modular endpoints (auth, companies, credentials, etc.)
|
|
||||||
- `axios.ts`: Base axios instance with `PUBLIC_API_URL` env variable
|
|
||||||
- **`src/components/`**: Reusable Svelte components (modals, spinners, error boundaries)
|
|
||||||
|
|
||||||
### API Communication
|
|
||||||
|
|
||||||
The `$lib/index.ts` exports `optima` object aggregating all API modules. Example:
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
export const optima = {
|
|
||||||
auth: (await import("./optima-api/modules/auth")).auth,
|
|
||||||
company: (await import("./optima-api/modules/companies")).company,
|
|
||||||
// etc.
|
|
||||||
};
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Each module (e.g., `auth.ts`) exports functions that call the API using a custom axios instance.
|
Keep each layer focused:
|
||||||
|
|
||||||
## Key Conventions
|
- **Route handler files** (`src/api/<domain>/*.ts`) — define individual endpoints using the `createRoute()` utility, handle request validation with Zod, call managers, and return `apiResponse.*` results.
|
||||||
|
- **Routers** (`src/api/routers/*`) — aggregate route handler files from a domain's `index.ts` and re-mount them under a single prefix. Mounted in `src/api/server.ts`.
|
||||||
|
- **Managers** (`src/managers/*`) — thin domain/persistence layers that wrap `generated/prisma` calls and other I/O. Managers instantiate and return **controller** instances as domain objects.
|
||||||
|
- **Controllers** (`src/controllers/*`) — **domain model classes** (e.g., `CompanyController`, `CredentialController`, `UserController`) that encapsulate entity state and domain methods. They are NOT request handlers. Managers create and return controller instances.
|
||||||
|
- **Modules** (`src/modules/*`) — shared utilities, external API clients (ConnectWise, UniFi, Microsoft), credential helpers, permission utilities, and tools.
|
||||||
|
|
||||||
### Routing
|
---
|
||||||
|
|
||||||
- **Use standard SvelteKit routing with `/` prefix** (e.g., `href="/credentials"`)
|
## Runtime / tooling
|
||||||
- Routes in `src/routes/` map to `/path` at runtime
|
|
||||||
- Do NOT use hash-based routing (`#/` routes)
|
|
||||||
|
|
||||||
### API Module Pattern
|
The project runs on **Bun**. DB tooling uses **Prisma**; the generated client lives under `generated/prisma` (do NOT edit generated files). Key scripts in `package.json`:
|
||||||
|
|
||||||
Create API modules in `src/lib/optima-api/modules/` following this pattern:
|
- `dev` — `NODE_ENV=development bun --watch src/index.ts` (start dev server with hot reload)
|
||||||
|
- `db:gen` — `prisma generate`
|
||||||
|
- `db:push` — `prisma migrate dev --skip-generate`
|
||||||
|
- `utils:dev` — `docker compose -f .docker/docker-compose.yml up --build`
|
||||||
|
- `utils:gen_private_keys` — `bun ./utils/genPrivateKeys`
|
||||||
|
- `utils:create_admin_role` — `bun ./utils/createAdminRole`
|
||||||
|
- `utils:assign_user_role` — `bun ./utils/assignUserRole`
|
||||||
|
|
||||||
```typescript
|
## Data layer
|
||||||
// Example: credentials.ts
|
|
||||||
export const credential = {
|
Prisma schema is at `prisma/schema.prisma`. The app imports the generated Prisma client from `generated/prisma/client.ts` (or `generated/prisma/browser.ts` for browser type contexts). The shared `prisma` instance is exported from `src/constants.ts`. Always run `npm run db:gen` after updating `schema.prisma`.
|
||||||
async fetchCredentials(api: AxiosInstance) {
|
|
||||||
// Implementation
|
## Shared constants (`src/constants.ts`)
|
||||||
},
|
|
||||||
};
|
This file exports critical shared instances used across the codebase:
|
||||||
|
|
||||||
|
- `prisma` — the PrismaClient instance (via `@prisma/adapter-pg`)
|
||||||
|
- `PORT`, session/token durations, private/public keys for JWT signing
|
||||||
|
- `msalClient` — Microsoft MSAL client for OAuth
|
||||||
|
- `connectWiseApi` — Axios instance for ConnectWise API
|
||||||
|
- `unifi` — `UnifiClient` instance for UniFi controller interaction
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Route handler pattern (`createRoute`)
|
||||||
|
|
||||||
|
Every route handler file uses the `createRoute()` utility from `src/modules/api-utils/createRoute.ts`. It creates a self-contained Hono sub-app for a single endpoint:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
export default createRoute(
|
||||||
|
"get", // HTTP method
|
||||||
|
["/companies"], // path(s)
|
||||||
|
async (c) => {
|
||||||
|
/* handler */
|
||||||
|
}, // request handler
|
||||||
|
authMiddleware({ permissions: ["company.fetch.many"] }), // middleware (spread)
|
||||||
|
);
|
||||||
```
|
```
|
||||||
|
|
||||||
Export as a named object, then import/aggregate in `src/lib/index.ts`.
|
Route files live in `src/api/<domain>/*.ts`. Each domain folder has an `index.ts` that re-exports all route modules. Router files (`src/api/routers/<domain>Router.ts`) import from the domain's index and auto-mount all routes:
|
||||||
|
|
||||||
### Environment Variables
|
```ts
|
||||||
|
import * as companyRoutes from "../companies";
|
||||||
- `PUBLIC_API_URL`: API base URL, used in `src/lib/optima-api/axios.ts`
|
const companyRouter = new Hono();
|
||||||
- Prefixed with `PUBLIC_` to be accessible in client code
|
Object.values(companyRoutes).map((r) => companyRouter.route("/", r));
|
||||||
|
export default companyRouter;
|
||||||
### File Organization
|
|
||||||
|
|
||||||
- Components go in `src/components/` (e.g., modals, spinners)
|
|
||||||
- Page-specific logic in `src/routes/[route]/+page.svelte` and `+page.server.ts`
|
|
||||||
- Styles in `src/styles/` with Tailwind CSS + TailwindCSS vite plugin
|
|
||||||
- Tests colocated: `*.spec.ts` or `*.test.ts` next to source files
|
|
||||||
|
|
||||||
## Development Workflow
|
|
||||||
|
|
||||||
### Installation & Setup
|
|
||||||
|
|
||||||
```bash
|
|
||||||
bun install
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Uses bun with SvelteKit patches (see `patches/` directory).
|
`src/api/server.ts` then mounts each router under `/v1`:
|
||||||
|
|
||||||
### Running in Development
|
```ts
|
||||||
|
v1.route("/company", require("./routers/companyRouter").default);
|
||||||
```bash
|
|
||||||
bun start
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Electron Forge + Vite handles dev server and hot module replacement (HMR). Dev tools open automatically. Main window loads `/login` first.
|
## Routing & domain organization
|
||||||
|
|
||||||
### Building & Packaging
|
The `server.ts` file mounts these routers under `/v1`:
|
||||||
|
|
||||||
- **Build for production**: `bun run package` → outputs to `out/` directory
|
- `/teapot` — health check
|
||||||
- **Create distributable**: `bun run make` → creates installers (configure in `forge.config.ts`)
|
- `/auth` — Microsoft OAuth flow (`src/api/auth/*`)
|
||||||
- **Check types & lint**: `bun run check` (runs svelte-kit sync + svelte-check)
|
- `/user` — user routes (`src/api/user/*`) including `@me` sub-routes
|
||||||
|
- `/company` — company routes (`src/api/companies/*`)
|
||||||
|
- `/credential` — credential routes (`src/api/credentials/*`)
|
||||||
|
- `/credential-type` — credential type routes (`src/api/credential-types/*`)
|
||||||
|
- `/role` — role management (`src/api/roles/*`)
|
||||||
|
- `/permissions` — permission node queries (`src/api/permissions/*`)
|
||||||
|
- `/unifi` — UniFi integration (`src/api/unifi/*` with `sites/` and `site/` sub-folders)
|
||||||
|
|
||||||
### Testing
|
---
|
||||||
|
|
||||||
#### Unit Tests (Vitest)
|
## API layout & conventions (how to add a new endpoint)
|
||||||
|
|
||||||
```bash
|
1. **Add route handler files**: Create `src/api/<domain>/<action>.ts` files, each exporting a default `createRoute(...)` call. Validate input with Zod inside the handler, call managers for business logic, and return responses via `apiResponse.*`.
|
||||||
bun run test:unit
|
2. **Add domain index**: Create `src/api/<domain>/index.ts` that re-exports all route modules from the folder.
|
||||||
```
|
3. **Add router**: Create `src/api/routers/<domain>Router.ts` that imports all routes from the domain's index and mounts them. Mount it in `src/api/server.ts` under `v1`.
|
||||||
|
4. **Add manager**: Create `src/managers/<domains>.ts` (plural filename) for persistence/domain logic. Use the `prisma` instance from `src/constants.ts`; do not import Prisma directly in route handlers. Managers should instantiate and return controller instances when the domain warrants it.
|
||||||
|
5. **Add controller** (if needed): Create `src/controllers/<Domain>Controller.ts` (singular filename) as a **class** that encapsulates entity state, domain methods, and a `toJson()` serializer. Controllers are domain model objects — they do NOT handle HTTP requests.
|
||||||
|
6. **Add modules/types**: If needed, add helpers to `src/modules/*` and runtime types to `src/types/*`.
|
||||||
|
7. **Middleware & auth**: Use `authMiddleware()` from `src/api/middleware/authorization.ts` as the last argument to `createRoute()`. It accepts `{ permissions?: string[], scopes?: string[], forbiddenAuthTypes?: string[] }`. Follow existing token/session patterns from `src/controllers/SessionController.ts` and `src/Errors/*`.
|
||||||
|
8. **Error handling**: Throw repository-specific errors from `src/Errors/*` (include `status`, `name`, `message`, optional `cause`) and let `src/api/server.ts` map them via `apiResponse.error`.
|
||||||
|
9. **Naming conventions**: plural manager filenames (`companies.ts`), singular controller class names (`CompanyController.ts`), descriptive route handler filenames (`fetchAll.ts`, `create.ts`, `update.ts`).
|
||||||
|
|
||||||
- Client tests: `src/**/*.svelte.{test,spec}.{js,ts}` (jsdom environment)
|
---
|
||||||
- Server tests: `src/**/*.{test,spec}.{js,ts}` excluding svelte tests (node environment)
|
|
||||||
- Setup: `vitest-setup-client.ts` mocks `window.matchMedia` for Svelte 5 + jsdom compatibility
|
|
||||||
|
|
||||||
#### E2E Tests (Playwright)
|
## Examples & notable files
|
||||||
|
|
||||||
```bash
|
- `src/api/server.ts` — mounts `v1`, registers `cors`, central `onError` handler and `notFound` response.
|
||||||
bun run test:e2e
|
- `src/api/companies/fetchAll.ts` — canonical example of a route handler file using `createRoute()`, `authMiddleware()`, managers, and `apiResponse`.
|
||||||
```
|
- `src/api/credentials/create.ts` — example of Zod validation inside a route handler.
|
||||||
|
- `src/api/routers/companyRouter.ts` — canonical router that auto-mounts all route modules from a domain folder.
|
||||||
|
- `src/api/user/@me/*` — nested sub-route example (user's own profile endpoints).
|
||||||
|
- `src/controllers/CompanyController.ts` — example domain model class with methods like `refreshFromCW()`, `fetchCwData()`, and `toJson()`.
|
||||||
|
- `src/controllers/CredentialController.ts` — example of a richer domain model class with field validation, secure value handling, and sub-credential support.
|
||||||
|
- `src/managers/companies.ts` — example manager calling Prisma and returning `CompanyController` instances.
|
||||||
|
- `src/modules/api-utils/createRoute.ts` — the `createRoute()` utility used by every route handler.
|
||||||
|
- `src/modules/cw-utils/*` — external ConnectWise API integrations; keep interfaces stable and return domain objects consumed by managers.
|
||||||
|
- `src/modules/unifi-api/UnifiClient.ts` — UniFi controller API client class with methods for sites, WLANs, devices, networks, etc.
|
||||||
|
- `src/modules/credentials/*` — credential field validation, secure value encryption/decryption, and type definitions.
|
||||||
|
- `src/constants.ts` — shared instances (`prisma`, API clients, keys, durations).
|
||||||
|
|
||||||
- Tests in `e2e/` directory
|
---
|
||||||
- Config: `playwright.config.ts` (builds and previews before testing)
|
|
||||||
|
|
||||||
#### Run All Tests
|
## Validation & errors
|
||||||
|
|
||||||
```bash
|
Zod is used for input validation **inside route handler files** (not controllers). Zod errors are handled centrally in `src/api/server.ts` via `apiResponse.zodError`. Application errors use custom error classes in `src/Errors/*` (e.g., `AuthenticationError.ts`, `GenericError.ts`, `AuthorizationError.ts`, `InsufficientPermission.ts`). When creating errors, follow the shape used in existing errors (include `status`, `name`, `message`, and optional `cause`).
|
||||||
bun run test
|
|
||||||
```
|
|
||||||
|
|
||||||
Runs unit tests first, then e2e.
|
## Response pattern
|
||||||
|
|
||||||
## Critical Integration Points
|
Use the `apiResponse` helpers in `src/modules/api-utils/apiResponse.ts` for formatting all HTTP responses:
|
||||||
|
|
||||||
### IPC (Electron Main ↔ Renderer)
|
- `apiResponse.successful(message, data?, meta?)` — 200
|
||||||
|
- `apiResponse.created(message, data?)` — 201
|
||||||
|
- `apiResponse.error(err)` — reads `status` from the error
|
||||||
|
- `apiResponse.internalError()` — 500
|
||||||
|
- `apiResponse.zodError(err)` — 400
|
||||||
|
|
||||||
**Status**: Preload script is currently empty. If file system access is needed, define IPC handlers in `electron/main.ts` and expose them via `electron/preload.ts` to renderer process.
|
## Auth & external integrations
|
||||||
|
|
||||||
### API Authentication
|
Microsoft OAuth flow is under `src/api/auth/*` and `src/modules/fetchMicrosoftUser.ts`. If touching authentication, follow existing redirect/refresh patterns in `src/api/auth` and preserve token-refresh semantics.
|
||||||
|
|
||||||
- Auth flow starts in `src/lib/optima-api/modules/auth.ts` with `fetchAuthRedirectUri()`
|
## ConnectWise integration
|
||||||
- TODO: Enforce auth checks on every page change (see `src/lib/index.ts` comment)
|
|
||||||
|
|
||||||
### Build Artifacts
|
Utilities for ConnectWise interactions live in `src/modules/cw-utils/*` (e.g., `configurations/fetchCompanyConfigurations.ts`, `fetchCompany.ts`, `fetchAllCompanies.ts`). These modules call external APIs via the `connectWiseApi` Axios instance from `src/constants.ts` and return domain data; preserve the module contracts (input types and returned shapes) when refactoring.
|
||||||
|
|
||||||
- **Dev server URL**: `MAIN_WINDOW_VITE_DEV_SERVER_URL` (injected by Electron Forge)
|
## UniFi integration
|
||||||
- **Output**: Rendered app built to `.vite/renderer/main_window/` (configured in `svelte.config.js`)
|
|
||||||
|
|
||||||
## Common Patterns & Gotchas
|
The `UnifiClient` class in `src/modules/unifi-api/UnifiClient.ts` wraps all UniFi controller API interactions (login, sites, WLANs, devices, networks, AP groups, WLAN groups, speed profiles, PPSKs). The shared instance is exported from `src/constants.ts` as `unifi`. UniFi route handlers live in `src/api/unifi/` with `sites/` (multi-site operations) and `site/` (single-site operations) sub-folders.
|
||||||
|
|
||||||
1. **Standard Routing**: Use standard SvelteKit routing with `/` prefix (e.g., `href="/credentials"`) — do NOT use hash-based routing
|
## Generated files and CI
|
||||||
2. **SvelteKit Patches**: Project patches SvelteKit in `patches/` to work around issues. When updating SvelteKit, verify patches still apply.
|
|
||||||
3. **Async Components**: Svelte 5 has `experimental.async: true` enabled; be aware of async component patterns.
|
|
||||||
4. **Tailwind**: Uses `@tailwindcss/vite` plugin; configure utilities in tailwind config if needed.
|
|
||||||
5. **API Error Handling**: API modules throw errors with descriptive messages. Use `$lib/errorHandler.ts` for consistent error formatting.
|
|
||||||
|
|
||||||
## Useful Entry Points for Navigation
|
`generated/` is a build artifact. Do not modify. When updating Prisma models, run `npm run db:gen` and commit changes to `generated/prisma` only if that's the established workflow.
|
||||||
|
|
||||||
- **Frontend Layout**: [src/routes/+layout.svelte](src/routes/+layout.svelte) (main shell, sidebar, header)
|
---
|
||||||
- **API Abstraction**: [src/lib/optima-api/axios.ts](src/lib/optima-api/axios.ts) (base client)
|
|
||||||
- **API Modules**: [src/lib/optima-api/modules/](src/lib/optima-api/modules/) (auth, companies, credentials, etc.)
|
## Coding conventions & patterns specific to this repo
|
||||||
- **App Entry**: [src/app.html](src/app.html)
|
|
||||||
- **Electron Main**: [electron/main.ts](electron/main.ts)
|
- Prefer the existing layered architecture: route handlers → managers → controllers (domain models) / modules.
|
||||||
|
- Use the `createRoute()` utility for all route handler files.
|
||||||
|
- Use the `apiResponse` helpers for all HTTP responses.
|
||||||
|
- Throw or propagate repository-specific custom errors (from `src/Errors/*`) rather than returning bare objects.
|
||||||
|
- Keep TypeScript types in `src/types/*` and use Zod for runtime checks inside route handlers.
|
||||||
|
- **Avoid `else` statements** — prefer ternary operators, early returns, or other control flow patterns. Only use `else` if there is absolutely no other way.
|
||||||
|
- ES module syntax (`export default`, `import`) is used throughout. The `require()` calls in `server.ts` are for lazy loading but all modules use `export default`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Local dev / quick checks
|
||||||
|
|
||||||
|
- Start dev server: `npm run dev`
|
||||||
|
- Regenerate Prisma client: `npm run db:gen`
|
||||||
|
- Apply DB migrations locally: `npm run db:push`
|
||||||
|
- Docker dev utilities: `npm run utils:dev`
|
||||||
|
- Generate private keys: `npm run utils:gen_private_keys`
|
||||||
|
- Create admin role: `npm run utils:create_admin_role`
|
||||||
|
- Assign user role: `npm run utils:assign_user_role`
|
||||||
|
|
||||||
|
## When editing generated or infra files
|
||||||
|
|
||||||
|
If you need to change `generated/prisma/*` (rare), explain why in the PR and show commands used to regenerate.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Documentation sync (IMPORTANT)
|
||||||
|
|
||||||
|
Whenever you add, remove, or modify API routes or permission nodes, you **must** update all three of the following files to keep them in sync:
|
||||||
|
|
||||||
|
1. `src/types/PermissionNodes.ts` — the single source of truth for all permission node definitions, categories, descriptions, and `usedIn` references.
|
||||||
|
2. `PERMISSIONS.md` — human-readable documentation of all permission nodes; must strictly reflect the data in `PermissionNodes.ts`.
|
||||||
|
3. `API_ROUTES.md` — comprehensive documentation of all API routes, including method, path, auth requirements, permissions, request/response examples.
|
||||||
|
|
||||||
|
Always verify that new routes have their required permissions listed in `PermissionNodes.ts`, that `PERMISSIONS.md` tables match the TS file exactly, and that `API_ROUTES.md` includes full documentation for every mounted route. Run through all three files at the end of any route or permission change to catch discrepancies.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Field-level permission gating (`processObjectValuePerms`)
|
||||||
|
|
||||||
|
Some routes use `processObjectValuePerms` from `src/modules/permission-utils/processObjectPermissions.ts` to filter response objects on a per-field basis. When this pattern is used, every key of the response object becomes a permission node in the form `<scope>.<field>` (e.g., `unifi.site.wifi.read.passphrase`). Only fields whose corresponding permission the user holds are included in the response.
|
||||||
|
|
||||||
|
There is also `processObjectPermMap` in the same file, which returns a `Record<key, boolean>` indicating which fields the user has permission for (useful for UI gating).
|
||||||
|
|
||||||
|
**When documenting a route that uses field-level gating, you must:**
|
||||||
|
|
||||||
|
1. Note in `API_ROUTES.md` that the route uses field-level gating, explain the behaviour, and list every `<scope>.<field>` permission node in a collapsible table.
|
||||||
|
2. Add a `unifi.site.wifi.read`-style parent permission node in `PermissionNodes.ts` with a `fieldLevelPermissions` array listing every `<scope>.<field>` node.
|
||||||
|
3. Add matching rows/notes to `PERMISSIONS.md` including the full list of field-level nodes.
|
||||||
|
|
||||||
|
**Current routes using field-level gating:**
|
||||||
|
|
||||||
|
- `GET /v1/unifi/site/:id/wifi` — scope `unifi.site.wifi.read`, gates every field on the `WlanConf` object.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
If anything here is unclear or you'd like more examples (e.g., a walk-through adding a route handler + manager + controller), tell me which area to expand and I'll iterate.
|
||||||
|
|||||||
@@ -5,8 +5,8 @@ on:
|
|||||||
types: [created]
|
types: [created]
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build-server:
|
build:
|
||||||
name: Build Server Image
|
name: Build
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
@@ -22,88 +22,54 @@ jobs:
|
|||||||
username: ${{ github.repository_owner }}
|
username: ${{ github.repository_owner }}
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
- name: Checkout source code
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Build and push the Docker image
|
- name: Build and push the Docker image
|
||||||
uses: docker/build-push-action@v6
|
uses: docker/build-push-action@v6
|
||||||
with:
|
with:
|
||||||
context: .
|
|
||||||
push: true
|
push: true
|
||||||
build-args: |
|
target: runtime
|
||||||
PUBLIC_API_URL=https://opt-api.osdci.net
|
|
||||||
tags: |
|
tags: |
|
||||||
ghcr.io/project-optima/ttscm-ui:latest
|
ghcr.io/project-optima/ttscm-api:latest
|
||||||
ghcr.io/project-optima/ttscm-ui:${{ github.event.release.tag_name }}
|
ghcr.io/project-optima/ttscm-api:${{ github.event.release.tag_name }}
|
||||||
|
|
||||||
build-desktop-macos:
|
- name: Build and push the migration image
|
||||||
name: Build Desktop (macOS)
|
uses: docker/build-push-action@v6
|
||||||
runs-on: macos-latest
|
with:
|
||||||
permissions:
|
push: true
|
||||||
contents: write
|
target: migration
|
||||||
|
tags: |
|
||||||
|
ghcr.io/project-optima/ttscm-api-migrate:latest
|
||||||
|
ghcr.io/project-optima/ttscm-api-migrate:${{ github.event.release.tag_name }}
|
||||||
|
|
||||||
|
migrate:
|
||||||
|
name: Run Migrations
|
||||||
|
needs: [build]
|
||||||
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
|
- name: Set the Kubernetes context
|
||||||
|
uses: azure/k8s-set-context@v2
|
||||||
|
with:
|
||||||
|
method: kubeconfig
|
||||||
|
kubeconfig: ${{ secrets.KUBECONFIG }}
|
||||||
|
|
||||||
- name: Checkout source code
|
- name: Checkout source code
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Set up Node.js
|
- name: Delete previous migration job if exists
|
||||||
uses: actions/setup-node@v4
|
run: kubectl delete job -n optima -l app=prisma-migrate --ignore-not-found
|
||||||
with:
|
|
||||||
node-version: 22
|
|
||||||
|
|
||||||
- name: Install bun
|
- name: Apply migration job
|
||||||
uses: oven-sh/setup-bun@v2
|
run: |
|
||||||
|
TAG=${{ github.event.release.tag_name }}
|
||||||
|
sed "s/RELEASE_TAG/${TAG}/g" kubernetes/migration-job.yaml | kubectl apply -f -
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Wait for migration to complete
|
||||||
run: bun install --frozen-lockfile
|
run: |
|
||||||
|
TAG=${{ github.event.release.tag_name }}
|
||||||
- name: Rebuild native modules
|
kubectl wait --for=condition=complete --timeout=120s -n optima job/prisma-migrate-${TAG}
|
||||||
run: npm rebuild
|
|
||||||
|
|
||||||
- name: Build macOS distributables
|
|
||||||
run: bun run make:macos
|
|
||||||
env:
|
|
||||||
PUBLIC_API_URL: https://opt-api.osdci.net
|
|
||||||
|
|
||||||
- name: Upload macOS artifacts to release
|
|
||||||
uses: softprops/action-gh-release@v2
|
|
||||||
with:
|
|
||||||
files: |
|
|
||||||
out/make/**/*.dmg
|
|
||||||
out/make/**/*.zip
|
|
||||||
|
|
||||||
build-desktop-windows:
|
|
||||||
name: Build Desktop (Windows)
|
|
||||||
runs-on: windows-latest
|
|
||||||
permissions:
|
|
||||||
contents: write
|
|
||||||
steps:
|
|
||||||
- name: Checkout source code
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Set up Node.js
|
|
||||||
uses: actions/setup-node@v4
|
|
||||||
with:
|
|
||||||
node-version: 22
|
|
||||||
|
|
||||||
- name: Install dependencies
|
|
||||||
run: npm install
|
|
||||||
|
|
||||||
- name: Build Windows distributables
|
|
||||||
run: npm run make -- --platform win32
|
|
||||||
env:
|
|
||||||
PUBLIC_API_URL: https://opt-api.osdci.net
|
|
||||||
|
|
||||||
- name: Upload Windows artifacts to release
|
|
||||||
uses: softprops/action-gh-release@v2
|
|
||||||
with:
|
|
||||||
files: |
|
|
||||||
out/make/**/*.exe
|
|
||||||
out/make/**/*.nupkg
|
|
||||||
out/make/**/*.msi
|
|
||||||
|
|
||||||
deploy:
|
deploy:
|
||||||
name: Deploy
|
name: Deploy
|
||||||
needs: [build-server]
|
needs: [migrate]
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Set the Kubernetes context
|
- name: Set the Kubernetes context
|
||||||
@@ -134,4 +100,4 @@ jobs:
|
|||||||
kubernetes/deployment.yaml
|
kubernetes/deployment.yaml
|
||||||
kubernetes/ingress.yaml
|
kubernetes/ingress.yaml
|
||||||
images: |
|
images: |
|
||||||
ghcr.io/project-optima/ttscm-ui:${{ github.event.release.tag_name }}
|
ghcr.io/project-optima/ttscm-api:${{ github.event.release.tag_name }}
|
||||||
|
|||||||
+142
-19
@@ -1,29 +1,152 @@
|
|||||||
test-results
|
# Logs
|
||||||
node_modules
|
logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
lerna-debug.log*
|
||||||
|
|
||||||
# Output
|
# Diagnostic reports (https://nodejs.org/api/report.html)
|
||||||
.output
|
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
|
||||||
.vercel
|
|
||||||
.netlify
|
|
||||||
.wrangler
|
|
||||||
/.svelte-kit
|
|
||||||
/build
|
|
||||||
|
|
||||||
# OS
|
# Runtime data
|
||||||
.DS_Store
|
pids
|
||||||
Thumbs.db
|
*.pid
|
||||||
|
*.seed
|
||||||
|
*.pid.lock
|
||||||
|
|
||||||
# Env
|
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||||
|
lib-cov
|
||||||
|
|
||||||
|
# Coverage directory used by tools like istanbul
|
||||||
|
coverage
|
||||||
|
*.lcov
|
||||||
|
|
||||||
|
# nyc test coverage
|
||||||
|
.nyc_output
|
||||||
|
|
||||||
|
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
|
||||||
|
.grunt
|
||||||
|
|
||||||
|
# Bower dependency directory (https://bower.io/)
|
||||||
|
bower_components
|
||||||
|
|
||||||
|
# node-waf configuration
|
||||||
|
.lock-wscript
|
||||||
|
|
||||||
|
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
||||||
|
build/Release
|
||||||
|
|
||||||
|
# Dependency directories
|
||||||
|
node_modules/
|
||||||
|
jspm_packages/
|
||||||
|
|
||||||
|
# Snowpack dependency directory (https://snowpack.dev/)
|
||||||
|
web_modules/
|
||||||
|
|
||||||
|
# TypeScript cache
|
||||||
|
*.tsbuildinfo
|
||||||
|
|
||||||
|
# Optional npm cache directory
|
||||||
|
.npm
|
||||||
|
|
||||||
|
# Optional eslint cache
|
||||||
|
.eslintcache
|
||||||
|
|
||||||
|
# Optional stylelint cache
|
||||||
|
.stylelintcache
|
||||||
|
|
||||||
|
# Optional REPL history
|
||||||
|
.node_repl_history
|
||||||
|
|
||||||
|
# Output of 'npm pack'
|
||||||
|
*.tgz
|
||||||
|
|
||||||
|
# Yarn Integrity file
|
||||||
|
.yarn-integrity
|
||||||
|
|
||||||
|
# dotenv environment variable files
|
||||||
.env
|
.env
|
||||||
.env.*
|
.env.*
|
||||||
!.env.example
|
!.env.example
|
||||||
!.env.test
|
|
||||||
|
|
||||||
# Vite
|
# parcel-bundler cache (https://parceljs.org/)
|
||||||
|
.cache
|
||||||
|
.parcel-cache
|
||||||
|
|
||||||
|
# Next.js build output
|
||||||
|
.next
|
||||||
|
out
|
||||||
|
|
||||||
|
# Nuxt.js build / generate output
|
||||||
|
.nuxt
|
||||||
|
dist
|
||||||
|
|
||||||
|
# Gatsby files
|
||||||
|
.cache/
|
||||||
|
# Comment in the public line in if your project uses Gatsby and not Next.js
|
||||||
|
# https://nextjs.org/blog/next-9-1#public-directory-support
|
||||||
|
# public
|
||||||
|
|
||||||
|
# vuepress build output
|
||||||
|
.vuepress/dist
|
||||||
|
|
||||||
|
# vuepress v2.x temp and cache directory
|
||||||
|
.temp
|
||||||
|
.cache
|
||||||
|
|
||||||
|
# Sveltekit cache directory
|
||||||
|
.svelte-kit/
|
||||||
|
|
||||||
|
# vitepress build output
|
||||||
|
**/.vitepress/dist
|
||||||
|
|
||||||
|
# vitepress cache directory
|
||||||
|
**/.vitepress/cache
|
||||||
|
|
||||||
|
# Docusaurus cache and generated files
|
||||||
|
.docusaurus
|
||||||
|
|
||||||
|
# Serverless directories
|
||||||
|
.serverless/
|
||||||
|
|
||||||
|
# FuseBox cache
|
||||||
|
.fusebox/
|
||||||
|
|
||||||
|
# DynamoDB Local files
|
||||||
|
.dynamodb/
|
||||||
|
|
||||||
|
# Firebase cache directory
|
||||||
|
.firebase/
|
||||||
|
|
||||||
|
# TernJS port file
|
||||||
|
.tern-port
|
||||||
|
|
||||||
|
# Stores VSCode versions used for testing VSCode extensions
|
||||||
|
.vscode-test
|
||||||
|
|
||||||
|
# yarn v3
|
||||||
|
.pnp.*
|
||||||
|
.yarn/*
|
||||||
|
!.yarn/patches
|
||||||
|
!.yarn/plugins
|
||||||
|
!.yarn/releases
|
||||||
|
!.yarn/sdks
|
||||||
|
!.yarn/versions
|
||||||
|
|
||||||
|
# Vite logs files
|
||||||
vite.config.js.timestamp-*
|
vite.config.js.timestamp-*
|
||||||
vite.config.ts.timestamp-*
|
vite.config.ts.timestamp-*
|
||||||
.vite
|
|
||||||
|
|
||||||
out
|
.permissions.key
|
||||||
tailwindcss-*.log
|
.refreshToken.key
|
||||||
pnpm-lock.yaml
|
.secureValues.key
|
||||||
|
.accessToken.key
|
||||||
|
|
||||||
|
public-keys/
|
||||||
|
production-keys/
|
||||||
|
|
||||||
|
microsoft-oauth.json
|
||||||
|
|
||||||
|
.docker/postgres
|
||||||
|
.docker/redis
|
||||||
|
|||||||
+3325
File diff suppressed because it is too large
Load Diff
+54
-16
@@ -1,32 +1,70 @@
|
|||||||
FROM oven/bun:latest AS base
|
# ---- Stage 1: Install dependencies ----
|
||||||
|
FROM oven/bun:1 AS deps
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
# Install dependencies
|
|
||||||
COPY package.json bun.lock ./
|
COPY package.json bun.lock ./
|
||||||
COPY patches ./patches
|
RUN bun install --frozen-lockfile --production
|
||||||
|
|
||||||
|
# ---- Stage 2: Build ----
|
||||||
|
FROM oven/bun:1 AS build
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Copy dependency manifests and install all deps (including dev)
|
||||||
|
COPY package.json bun.lock ./
|
||||||
RUN bun install --frozen-lockfile
|
RUN bun install --frozen-lockfile
|
||||||
|
|
||||||
# Build the SvelteKit app with adapter-node
|
# Copy source code and supporting files
|
||||||
COPY . .
|
COPY src/ src/
|
||||||
|
COPY prisma/ prisma/
|
||||||
|
COPY prisma.config.ts tsconfig.json ./
|
||||||
|
|
||||||
ARG PUBLIC_API_URL=https://opt-api.osdci.net
|
# Generate Prisma client (dummy URL — generate only needs the schema, not a real DB)
|
||||||
ENV PUBLIC_API_URL=$PUBLIC_API_URL
|
RUN DATABASE_URL="postgresql://dummy:dummy@localhost:5432/dummy" bunx prisma generate
|
||||||
|
|
||||||
RUN bun run build:server
|
# Compile to a standalone executable
|
||||||
|
RUN NODE_ENV=production bun build src/index.ts \
|
||||||
|
--compile \
|
||||||
|
--minify \
|
||||||
|
--target=bun-linux-x64 \
|
||||||
|
--outfile=server
|
||||||
|
|
||||||
# Production image
|
# ---- Stage 3: Production image ----
|
||||||
FROM node:22-alpine AS production
|
FROM ubuntu:22.04 AS runtime
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
COPY --from=base /app/build ./build
|
# Install minimal runtime dependencies (CA certs for HTTPS calls)
|
||||||
COPY --from=base /app/package.json ./
|
RUN apt-get update && \
|
||||||
|
apt-get install -y --no-install-recommends ca-certificates && \
|
||||||
|
rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
# Copy the compiled binary from the build stage
|
||||||
|
COPY --from=build /app/server ./server
|
||||||
|
|
||||||
|
# Copy Prisma artifacts needed at runtime
|
||||||
|
COPY --from=build /app/generated/ ./generated/
|
||||||
|
COPY --from=build /app/prisma/ ./prisma/
|
||||||
|
COPY --from=build /app/prisma.config.ts ./prisma.config.ts
|
||||||
|
|
||||||
|
# Copy production node_modules (Prisma adapter needs native bindings)
|
||||||
|
COPY --from=deps /app/node_modules/ ./node_modules/
|
||||||
|
|
||||||
ENV NODE_ENV=production
|
ENV NODE_ENV=production
|
||||||
ENV PORT=3000
|
|
||||||
ENV ORIGIN=https://optima.osdci.net
|
|
||||||
|
|
||||||
EXPOSE 3000
|
EXPOSE 3000
|
||||||
|
|
||||||
CMD ["node", "build/index.js"]
|
CMD ["./server"]
|
||||||
|
|
||||||
|
# ---- Stage 4: Migration runner ----
|
||||||
|
FROM oven/bun:1 AS migration
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
COPY package.json bun.lock ./
|
||||||
|
RUN bun install --frozen-lockfile
|
||||||
|
|
||||||
|
COPY prisma/ prisma/
|
||||||
|
COPY prisma.config.ts ./
|
||||||
|
|
||||||
|
CMD ["bunx", "prisma", "migrate", "deploy"]
|
||||||
+195
@@ -0,0 +1,195 @@
|
|||||||
|
# Permission Nodes
|
||||||
|
|
||||||
|
This document lists all known permission nodes in the optima-api application, categorized by resource type.
|
||||||
|
|
||||||
|
## Permission System Overview
|
||||||
|
|
||||||
|
The permission system uses a dot-notation format: `resource.action[.modifier]`
|
||||||
|
|
||||||
|
### Special Tokens
|
||||||
|
|
||||||
|
The permission validator supports special tokens for flexible permission management:
|
||||||
|
|
||||||
|
- **Asterisk (\*)**: Matches the token and all following tokens (e.g., `credential.*` grants all credential permissions)
|
||||||
|
- **Question Mark (?)**: Matches only the specific token (single character wildcard)
|
||||||
|
- **Inclusive List ([a,b,c])**: Matches only the tokens in the list
|
||||||
|
- **Exclusive List (<a,b,c>)**: Matches all tokens except those in the list
|
||||||
|
|
||||||
|
### Global Permissions
|
||||||
|
|
||||||
|
- `*` - Full access to all resources and actions (typically assigned to administrator role)
|
||||||
|
|
||||||
|
## Permission Nodes by Resource
|
||||||
|
|
||||||
|
### Company Permissions
|
||||||
|
|
||||||
|
| Permission Node | Description | Used In |
|
||||||
|
| ------------------------------ | ------------------------------------------------------------------- | ------------------------------------------------------------------------------------ |
|
||||||
|
| `company.fetch` | Fetch a single company | [src/api/companies/[id]/fetch.ts](src/api/companies/[id]/fetch.ts) |
|
||||||
|
| `company.fetch.address` | View company address information (requires `company.fetch` as well) | [src/api/companies/[id]/fetch.ts](src/api/companies/[id]/fetch.ts) |
|
||||||
|
| `company.fetch.contacts` | View all company contacts (requires `company.fetch` as well) | [src/api/companies/[id]/fetch.ts](src/api/companies/[id]/fetch.ts) |
|
||||||
|
| `company.fetch.many` | Fetch multiple companies | [src/api/companies/fetchAll.ts](src/api/companies/fetchAll.ts) |
|
||||||
|
| `company.fetch.configurations` | Fetch company configurations (requires `company.fetch` as well) | [src/api/companies/[id]/configurations.ts](src/api/companies/[id]/configurations.ts) |
|
||||||
|
|
||||||
|
### Credential Permissions
|
||||||
|
|
||||||
|
| Permission Node | Description | Used In |
|
||||||
|
| ----------------------------------- | -------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||||
|
| `credential.create` | Create a new credential | [src/api/credentials/create.ts](src/api/credentials/create.ts) |
|
||||||
|
| `credential.fetch` | Fetch a single credential | [src/api/credentials/fetch.ts](src/api/credentials/fetch.ts) |
|
||||||
|
| `credential.fetch.many` | Fetch multiple credentials | [src/api/credentials/fetchByCompany.ts](src/api/credentials/fetchByCompany.ts) |
|
||||||
|
| `credential.update` | Update a credential | [src/api/credentials/update.ts](src/api/credentials/update.ts) |
|
||||||
|
| `credential.delete` | Delete a credential | [src/api/credentials/delete.ts](src/api/credentials/delete.ts) |
|
||||||
|
| `credential.fields.fetch` | Fetch credential fields (requires `credential.fetch` as well) | [src/api/credentials/fetchFields.ts](src/api/credentials/fetchFields.ts) |
|
||||||
|
| `credential.fields.update` | Update credential fields (requires `credential.update` as well) | [src/api/credentials/updateFields.ts](src/api/credentials/updateFields.ts) |
|
||||||
|
| `credential.secure_values.read` | Read secure values of a credential (requires `credential.fetch` as well) | [src/api/credentials/readSecureValues.ts](src/api/credentials/readSecureValues.ts), [src/api/credentials/readSecureValue.ts](src/api/credentials/readSecureValue.ts) |
|
||||||
|
| `credential.sub_credentials.fetch` | Fetch sub-credentials of a parent credential (requires `credential.fetch` as well) | [src/api/credentials/fetchSubCredentials.ts](src/api/credentials/fetchSubCredentials.ts) |
|
||||||
|
| `credential.sub_credentials.create` | Create a sub-credential on a parent credential (requires `credential.fetch` as well) | [src/api/credentials/addSubCredential.ts](src/api/credentials/addSubCredential.ts) |
|
||||||
|
| `credential.sub_credentials.delete` | Remove a sub-credential from a parent credential (requires `credential.fetch` as well) | [src/api/credentials/removeSubCredential.ts](src/api/credentials/removeSubCredential.ts) |
|
||||||
|
|
||||||
|
### Credential Type Permissions
|
||||||
|
|
||||||
|
| Permission Node | Description | Used In |
|
||||||
|
| ---------------------------- | ------------------------------- | ---------------------------------------------------------------------------- |
|
||||||
|
| `credential_type.create` | Create a new credential type | [src/api/credential-types/create.ts](src/api/credential-types/create.ts) |
|
||||||
|
| `credential_type.fetch` | Fetch a single credential type | [src/api/credential-types/fetch.ts](src/api/credential-types/fetch.ts) |
|
||||||
|
| `credential_type.fetch.many` | Fetch multiple credential types | [src/api/credential-types/fetchAll.ts](src/api/credential-types/fetchAll.ts) |
|
||||||
|
| `credential_type.update` | Update a credential type | [src/api/credential-types/update.ts](src/api/credential-types/update.ts) |
|
||||||
|
| `credential_type.delete` | Delete a credential type | [src/api/credential-types/delete.ts](src/api/credential-types/delete.ts) |
|
||||||
|
|
||||||
|
### Role Permissions
|
||||||
|
|
||||||
|
| Permission Node | Description | Used In | Dependencies |
|
||||||
|
| --------------- | ------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------ |
|
||||||
|
| `role.create` | Create a new role | [src/api/roles/create.ts](src/api/roles/create.ts) | |
|
||||||
|
| `role.read` | Fetch a single role or view role information | [src/api/roles/fetch.ts](src/api/roles/fetch.ts) | |
|
||||||
|
| `role.list` | Fetch all roles | [src/api/roles/fetchAll.ts](src/api/roles/fetchAll.ts) | `role.read` |
|
||||||
|
| `role.modify` | Update role properties or manage role permissions | [src/api/roles/update.ts](src/api/roles/update.ts), [src/api/roles/addPermissions.ts](src/api/roles/addPermissions.ts), [src/api/roles/removePermissions.ts](src/api/roles/removePermissions.ts) | |
|
||||||
|
| `role.delete` | Delete a role | [src/api/roles/delete.ts](src/api/roles/delete.ts) | |
|
||||||
|
| `user.read` | View users assigned to a role | [src/api/roles/getUsers.ts](src/api/roles/getUsers.ts) | `role.read` |
|
||||||
|
|
||||||
|
### User Permissions
|
||||||
|
|
||||||
|
| Permission Node | Description | Used In | Dependencies |
|
||||||
|
| ------------------------ | ------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------ |
|
||||||
|
| `user.read` | Read user information | [src/api/user/@me/fetch.ts](src/api/user/@me/fetch.ts) | |
|
||||||
|
| `user.write` | Update user information | [src/api/user/@me/update.ts](src/api/user/@me/update.ts) | |
|
||||||
|
| `user.read.other` | Read other users' information | [src/api/user/fetch.ts](src/api/user/fetch.ts), [src/api/user/fetchRoles.ts](src/api/user/fetchRoles.ts), [src/api/user/checkPermission.ts](src/api/user/checkPermission.ts) | |
|
||||||
|
| `user.list.other` | List all users | [src/api/user/fetchAll.ts](src/api/user/fetchAll.ts) | `user.read.other` |
|
||||||
|
| `user.write.other` | Update other users' information | [src/api/user/update.ts](src/api/user/update.ts) | |
|
||||||
|
| `user.roles.other` | Modify roles assigned to other users | [src/api/user/update.ts](src/api/user/update.ts) | `user.write.other` |
|
||||||
|
| `user.permissions.other` | Modify direct permissions assigned to other users | [src/api/user/update.ts](src/api/user/update.ts) | `user.write.other` |
|
||||||
|
| `user.delete.other` | Delete other users | [src/api/user/delete.ts](src/api/user/delete.ts) | |
|
||||||
|
|
||||||
|
### Permission Routes
|
||||||
|
|
||||||
|
Permissions required for accessing the permission node definitions API.
|
||||||
|
|
||||||
|
| Permission Node | Description | Used In |
|
||||||
|
| --------------- | ------------------------------------------------ | -------------------------------------------------------------------------------- |
|
||||||
|
| `role.read` | Fetch all permission nodes organized by category | [src/api/permissions/fetchAll.ts](src/api/permissions/fetchAll.ts) |
|
||||||
|
| `role.read` | Fetch a flat list of all permission nodes | [src/api/permissions/fetchNodes.ts](src/api/permissions/fetchNodes.ts) |
|
||||||
|
| `role.read` | Fetch permission nodes by category | [src/api/permissions/fetchByCategory.ts](src/api/permissions/fetchByCategory.ts) |
|
||||||
|
|
||||||
|
### UI Navigation Permissions
|
||||||
|
|
||||||
|
Permissions for controlling navigation visibility on the frontend.
|
||||||
|
|
||||||
|
| Permission Node | Description | Usage Pattern |
|
||||||
|
| ---------------------- | -------------------------------------------------------------------- | ------------------------------------------------- |
|
||||||
|
| `ui.navigation.*.view` | View specific navigation sections (e.g., `ui.navigation.admin.view`) | Control which navigation menu items are displayed |
|
||||||
|
|
||||||
|
### Admin UI Permissions
|
||||||
|
|
||||||
|
Admin-specific UI permissions that control visibility and data loading for admin sub-tabs.
|
||||||
|
|
||||||
|
| Permission Node | Description | Usage Pattern |
|
||||||
|
| ----------------------------- | ------------------------------------------------ | ------------------------------------------ |
|
||||||
|
| `admin.users.view` | Show the Users tab and load user data | Show/hide users tab, allow user list fetch |
|
||||||
|
| `admin.roles.view` | Show the Roles tab and load role data | Show/hide roles tab, allow role list fetch |
|
||||||
|
| `admin.credential-types.view` | Show the Credential Types tab and load type data | Show/hide types tab, allow type list fetch |
|
||||||
|
|
||||||
|
#### Notes on UI Permissions
|
||||||
|
|
||||||
|
- **Client-side validation is not secure**: Always enforce permissions on the API level. UI permissions only control visibility and user experience.
|
||||||
|
- **Combine with API permissions**: A user with an admin UI permission should also have the corresponding API permission (e.g., `role.list`) to actually load data.
|
||||||
|
- **Use wildcards for flexibility**: Grant `ui.navigation.*.view` to allow all navigation sections.
|
||||||
|
|
||||||
|
### UniFi Permissions
|
||||||
|
|
||||||
|
Permissions for accessing and managing UniFi network infrastructure. The `unifi.access` permission is a gate permission required for **all** UniFi routes.
|
||||||
|
|
||||||
|
| Permission Node | Description | Used In | Dependencies |
|
||||||
|
| ------------------------------ | --------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------- |
|
||||||
|
| `unifi.access` | Gate permission for the entire UniFi API — required for all | [src/api/unifi/sites/fetchAll.ts](src/api/unifi/sites/fetchAll.ts), [src/api/unifi/sites/sync.ts](src/api/unifi/sites/sync.ts), [src/api/unifi/site/fetch.ts](src/api/unifi/site/fetch.ts), [src/api/unifi/site/overview.ts](src/api/unifi/site/overview.ts), [src/api/unifi/site/devices.ts](src/api/unifi/site/devices.ts), [src/api/unifi/site/wifi/fetchAll.ts](src/api/unifi/site/wifi/fetchAll.ts), [src/api/unifi/site/wifi/update.ts](src/api/unifi/site/wifi/update.ts), [src/api/unifi/site/networks.ts](src/api/unifi/site/networks.ts), [src/api/unifi/site/link.ts](src/api/unifi/site/link.ts), [src/api/unifi/site/unlink.ts](src/api/unifi/site/unlink.ts), [src/api/companies/[id]/unifiSites.ts](src/api/companies/[id]/unifiSites.ts), [src/api/unifi/sites/create.ts](src/api/unifi/sites/create.ts) | |
|
||||||
|
| `unifi.sites.create` | Create a new site on the UniFi controller | [src/api/unifi/sites/create.ts](src/api/unifi/sites/create.ts) | `unifi.access` |
|
||||||
|
| `unifi.sites.fetch` | Fetch a single UniFi site | [src/api/unifi/site/fetch.ts](src/api/unifi/site/fetch.ts) | `unifi.access` |
|
||||||
|
| `unifi.sites.fetch.many` | Fetch all UniFi sites | [src/api/unifi/sites/fetchAll.ts](src/api/unifi/sites/fetchAll.ts) | `unifi.access` |
|
||||||
|
| `unifi.sites.sync` | Sync sites from the UniFi controller into the database | [src/api/unifi/sites/sync.ts](src/api/unifi/sites/sync.ts) | `unifi.access` |
|
||||||
|
| `unifi.sites.link` | Link or unlink a UniFi site to/from a company | [src/api/unifi/site/link.ts](src/api/unifi/site/link.ts), [src/api/unifi/site/unlink.ts](src/api/unifi/site/unlink.ts) | `unifi.access` |
|
||||||
|
| `unifi.site.overview` | View live site overview from the UniFi controller | [src/api/unifi/site/overview.ts](src/api/unifi/site/overview.ts) | `unifi.access` |
|
||||||
|
| `unifi.site.devices` | View live device list from the UniFi controller | [src/api/unifi/site/devices.ts](src/api/unifi/site/devices.ts) | `unifi.access` |
|
||||||
|
| `unifi.site.wifi` | View WiFi networks (WLANs) from the UniFi controller | [src/api/unifi/site/wifi/fetchAll.ts](src/api/unifi/site/wifi/fetchAll.ts) | `unifi.access` |
|
||||||
|
| `unifi.site.wifi.read` | Field-level gate for WiFi response data (see note below) | [src/api/unifi/site/wifi/fetchAll.ts](src/api/unifi/site/wifi/fetchAll.ts) | `unifi.access`, `unifi.site.wifi` |
|
||||||
|
| `unifi.site.wifi.read.<field>` | Read a specific field from WiFi response (e.g. `unifi.site.wifi.read.passphrase`) | [src/api/unifi/site/wifi/fetchAll.ts](src/api/unifi/site/wifi/fetchAll.ts) | `unifi.access`, `unifi.site.wifi`, `unifi.site.wifi.read` |
|
||||||
|
| `unifi.site.wifi.update` | Update a WiFi network on the UniFi controller | [src/api/unifi/site/wifi/update.ts](src/api/unifi/site/wifi/update.ts) | `unifi.access`, `unifi.site.wifi` |
|
||||||
|
|
||||||
|
#### Field-Level Permission Gating (`unifi.site.wifi.read`)
|
||||||
|
|
||||||
|
The WiFi fetch route uses `processObjectValuePerms` to filter each WLAN object on a per-field basis. For every key on the `WlanConf` response object, the system checks `unifi.site.wifi.read.<key>`. Only fields the user has permission for are included in the response. Use `unifi.site.wifi.read.*` to grant access to all fields.
|
||||||
|
|
||||||
|
**Available field-level nodes:**
|
||||||
|
|
||||||
|
`unifi.site.wifi.read.id`, `unifi.site.wifi.read.name`, `unifi.site.wifi.read.siteId`, `unifi.site.wifi.read.enabled`, `unifi.site.wifi.read.security`, `unifi.site.wifi.read.wpaMode`, `unifi.site.wifi.read.wpaEnc`, `unifi.site.wifi.read.wpa3Support`, `unifi.site.wifi.read.wpa3Transition`, `unifi.site.wifi.read.wpa3FastRoaming`, `unifi.site.wifi.read.wpa3Enhanced192`, `unifi.site.wifi.read.passphrase`, `unifi.site.wifi.read.passphraseAutogenerated`, `unifi.site.wifi.read.hideSSID`, `unifi.site.wifi.read.isGuest`, `unifi.site.wifi.read.band`, `unifi.site.wifi.read.bands`, `unifi.site.wifi.read.networkconfId`, `unifi.site.wifi.read.usergroupId`, `unifi.site.wifi.read.apGroupIds`, `unifi.site.wifi.read.apGroupMode`, `unifi.site.wifi.read.pmfMode`, `unifi.site.wifi.read.groupRekey`, `unifi.site.wifi.read.dtimMode`, `unifi.site.wifi.read.dtimNg`, `unifi.site.wifi.read.dtimNa`, `unifi.site.wifi.read.dtim6e`, `unifi.site.wifi.read.l2Isolation`, `unifi.site.wifi.read.fastRoamingEnabled`, `unifi.site.wifi.read.bssTransition`, `unifi.site.wifi.read.uapsdEnabled`, `unifi.site.wifi.read.iappEnabled`, `unifi.site.wifi.read.proxyArp`, `unifi.site.wifi.read.mcastenhanceEnabled`, `unifi.site.wifi.read.macFilterEnabled`, `unifi.site.wifi.read.macFilterPolicy`, `unifi.site.wifi.read.macFilterList`, `unifi.site.wifi.read.radiusDasEnabled`, `unifi.site.wifi.read.radiusMacAuthEnabled`, `unifi.site.wifi.read.radiusMacaclFormat`, `unifi.site.wifi.read.minrateSettingPreference`, `unifi.site.wifi.read.minrateNgEnabled`, `unifi.site.wifi.read.minrateNgDataRateKbps`, `unifi.site.wifi.read.minrateNgAdvertisingRates`, `unifi.site.wifi.read.minrateNaEnabled`, `unifi.site.wifi.read.minrateNaDataRateKbps`, `unifi.site.wifi.read.minrateNaAdvertisingRates`, `unifi.site.wifi.read.settingPreference`, `unifi.site.wifi.read.no2ghzOui`, `unifi.site.wifi.read.privatePreSharedKeysEnabled`, `unifi.site.wifi.read.privatePreSharedKeys`, `unifi.site.wifi.read.saeGroups`, `unifi.site.wifi.read.saePsk`, `unifi.site.wifi.read.schedule`, `unifi.site.wifi.read.scheduleWithDuration`, `unifi.site.wifi.read.bcFilterList`, `unifi.site.wifi.read.externalId`
|
||||||
|
| `unifi.site.networks` | View network configurations from the UniFi controller | [src/api/unifi/site/networks.ts](src/api/unifi/site/networks.ts) | `unifi.access` |
|
||||||
|
| `unifi.site.wlan-groups` | View WLAN groups (AP broadcasting groups) from the UniFi controller for a site | [src/api/unifi/site/wlanGroups.ts](src/api/unifi/site/wlanGroups.ts), [src/api/unifi/site/wlanGroupsCreate.ts](src/api/unifi/site/wlanGroupsCreate.ts) | `unifi.access` |
|
||||||
|
| `unifi.site.wlan-groups.create` | Create a new WLAN group (AP broadcasting group) on the UniFi controller | [src/api/unifi/site/wlanGroupsCreate.ts](src/api/unifi/site/wlanGroupsCreate.ts) | `unifi.access`, `unifi.site.wlan-groups` |
|
||||||
|
| `unifi.site.access-points` | View access points (UAPs only) from the UniFi controller for a site | [src/api/unifi/site/accessPoints.ts](src/api/unifi/site/accessPoints.ts) | `unifi.access` |
|
||||||
|
| `unifi.site.ap-groups` | View AP groups — shows which APs are grouped together for SSID broadcasting | [src/api/unifi/site/apGroups.ts](src/api/unifi/site/apGroups.ts) | `unifi.access` |
|
||||||
|
| `unifi.site.wifi-limits` | View WiFi SSID limits per AP per radio band | [src/api/unifi/site/wifiLimits.ts](src/api/unifi/site/wifiLimits.ts) | `unifi.access` |
|
||||||
|
| `unifi.site.speed-profiles` | View speed limit profiles (user groups) from the UniFi controller | [src/api/unifi/site/speedProfilesFetchAll.ts](src/api/unifi/site/speedProfilesFetchAll.ts), [src/api/unifi/site/speedProfilesCreate.ts](src/api/unifi/site/speedProfilesCreate.ts) | `unifi.access` |
|
||||||
|
| `unifi.site.speed-profiles.create` | Create a new speed limit profile (user group) on the UniFi controller | [src/api/unifi/site/speedProfilesCreate.ts](src/api/unifi/site/speedProfilesCreate.ts) | `unifi.access`, `unifi.site.speed-profiles` |
|
||||||
|
| `unifi.site.wifi.ppsk` | View private pre-shared keys (PPSKs) for a specific WiFi network | [src/api/unifi/site/wifi/ppskFetchAll.ts](src/api/unifi/site/wifi/ppskFetchAll.ts), [src/api/unifi/site/wifi/ppskCreate.ts](src/api/unifi/site/wifi/ppskCreate.ts) | `unifi.access`, `unifi.site.wifi` |
|
||||||
|
| `unifi.site.wifi.ppsk.create` | Create a private pre-shared key on a specific WiFi network | [src/api/unifi/site/wifi/ppskCreate.ts](src/api/unifi/site/wifi/ppskCreate.ts) | `unifi.access`, `unifi.site.wifi`, `unifi.site.wifi.ppsk` |
|
||||||
|
|
||||||
|
## Permission Issuers
|
||||||
|
|
||||||
|
Permissions can be issued by different sources:
|
||||||
|
|
||||||
|
- `roles` - Permissions granted through role assignment
|
||||||
|
- `user` - Permissions granted directly to a user
|
||||||
|
- `api_key` - Permissions associated with an API key
|
||||||
|
|
||||||
|
## Permission Validation
|
||||||
|
|
||||||
|
The authorization middleware ([src/api/middleware/authorization.ts](src/api/middleware/authorization.ts)) enforces permissions by:
|
||||||
|
|
||||||
|
1. Extracting the authorization header (Bearer token or API Key)
|
||||||
|
2. Validating the session/token
|
||||||
|
3. Checking if the user has all required permissions for the route
|
||||||
|
4. Throwing an `InsufficentPermission` error (403) if any required permission is missing
|
||||||
|
|
||||||
|
## Usage Example
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Require single permission
|
||||||
|
authMiddleware({ permissions: ["credential.fetch"] })
|
||||||
|
|
||||||
|
// Require multiple permissions (all must be satisfied)
|
||||||
|
authMiddleware({
|
||||||
|
permissions: ["credential.fetch", "credential.secure_values.read"]
|
||||||
|
})
|
||||||
|
|
||||||
|
// Administrator role with wildcard permission
|
||||||
|
{
|
||||||
|
moniker: "administrator",
|
||||||
|
permissions: ["*"] // Grants all permissions
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
- Multiple permissions in a single route require **all** permissions to be satisfied (AND logic)
|
||||||
|
- The `*` wildcard permission grants access to everything in the application
|
||||||
|
- Permissions are signed using JWT with a private key for integrity
|
||||||
|
- Permission validation supports pattern matching for flexible permission management
|
||||||
@@ -1,116 +1,2 @@
|
|||||||
# SveltronKit
|
# ttsci-api
|
||||||
|
The Api for the TTS Credentials Manager
|
||||||
A minimal template for building Electron apps with SvelteKit.
|
|
||||||
|
|
||||||
Includes native support for Typscript and uses Electron's official recommended Electron Forge for packaging.
|
|
||||||
|
|
||||||
Everything you can do in SvelteKit, you can do in SveltronKit; meaning that you can use component
|
|
||||||
libraries like [Shadcn-Svelte](https://next.shadcn-svelte.com/).
|
|
||||||
|
|
||||||
> [!IMPORTANT]
|
|
||||||
> This template uses SvelteKit's [hash router](https://svelte.dev/docs/kit/configuration#router) to
|
|
||||||
> create a single-page app. The only difference you'll have to look out for is to start all your routed
|
|
||||||
> links with `#/` instead of `/`.
|
|
||||||
|
|
||||||
## Dependencies & Frameworks
|
|
||||||
|
|
||||||
- [SvelteKit](https://kit.svelte.dev/)
|
|
||||||
- [Electron](https://www.electronjs.org/)
|
|
||||||
- [Electron Forge](https://www.electronforge.io/)
|
|
||||||
- [TypeScript](https://www.typescriptlang.org/)
|
|
||||||
- [TailwindCSS](https://tailwindcss.com/)
|
|
||||||
|
|
||||||
> [!NOTE]
|
|
||||||
> I've included TailwindCSS in this template because I use it in my own projects, but you can remove
|
|
||||||
> it easily if you don't want it.
|
|
||||||
|
|
||||||
## Getting Started
|
|
||||||
|
|
||||||
> [!WARNING]
|
|
||||||
> This project uses [`bun`](https://bun.sh/) and uses [patching](https://bun.sh/docs/install/patch) to work
|
|
||||||
> around some issues with SvelteKit. When this [PR](https://github.com/sveltejs/kit/pull/13812) merges,
|
|
||||||
> you can remove the patching and use the latest version of SvelteKit.
|
|
||||||
|
|
||||||
Start by installing the dependencies:
|
|
||||||
|
|
||||||
```
|
|
||||||
bun install
|
|
||||||
```
|
|
||||||
|
|
||||||
**Development:**
|
|
||||||
|
|
||||||
```
|
|
||||||
bun start
|
|
||||||
```
|
|
||||||
|
|
||||||
[Electron Forge](https://www.electronforge.io/) with the [Vite plugin](https://www.electronforge.io/plugins/vite)
|
|
||||||
will take care of running the development server and building the app for you. You don't need to run
|
|
||||||
`vite dev` or `vite build` yourself. This also means that it supports hot module replacement (HMR).
|
|
||||||
|
|
||||||
**Production:**
|
|
||||||
|
|
||||||
```
|
|
||||||
bun run package
|
|
||||||
```
|
|
||||||
|
|
||||||
This will build the app and you can find the output in the `out` directory. You can run the production
|
|
||||||
app by opening the `.app` file in the `out` directory. This will not create your app's installer
|
|
||||||
for distribution though.
|
|
||||||
|
|
||||||
To create a distributable installer, you can use:
|
|
||||||
|
|
||||||
```
|
|
||||||
bun run make
|
|
||||||
```
|
|
||||||
|
|
||||||
This will create a distributable installer for your app. You can configure this in the `makers` section
|
|
||||||
in `forge.config.ts`. Reference the [makers documentation](https://www.electronforge.io/makers) for more
|
|
||||||
information.
|
|
||||||
|
|
||||||
# Electron Crash Course
|
|
||||||
|
|
||||||
> [!NOTE]
|
|
||||||
> This is a super simplified version of the Electron documentation meant to give you a general idea
|
|
||||||
> of how Electron works and how each file corresponds to responsibilities in Electron. For a more
|
|
||||||
> accurate description of how Electron works, you can refer to the [official documentation](https://www.electronjs.org/docs).
|
|
||||||
|
|
||||||
I found that most of the problems I encountered when setting up Electron were because I didn't know
|
|
||||||
how Electron works and that the documentation was too dense to get up to speed with, so I'll include
|
|
||||||
a crash course here. _I will be making a lot of analogies to web development_ as it seems like a lot
|
|
||||||
of people who are new to Electron come from web development.
|
|
||||||
|
|
||||||
Because everything in Electron is client based, you'll need to host your own server if you want to
|
|
||||||
access any sensitive logic like a database or authentication, etc.
|
|
||||||
|
|
||||||
## main.ts
|
|
||||||
|
|
||||||
This file defines what the main process will do. The process runs your app. It's the one that
|
|
||||||
creates and manages windows and also has permissions to access the file system. You also define
|
|
||||||
"_signals_"/"_endpoints_", through IPC, that let the renderer process (browser that runs your app)
|
|
||||||
can "_call_" to interact with the file system.
|
|
||||||
|
|
||||||
By default, Electron will block off file system access to the renderer process as a security measure,
|
|
||||||
which is the reason why you need to use IPC to interact with the file system.
|
|
||||||
|
|
||||||
## preload.ts
|
|
||||||
|
|
||||||
Think about this as a "bridge" or a "network"/"proxy" between the main process and the renderer process.
|
|
||||||
You specify what functions that the renderer process can call and these functions will usually be
|
|
||||||
interacting with the file system through the main process.
|
|
||||||
|
|
||||||
## renderer
|
|
||||||
|
|
||||||
The renderer process is the browser that runs your app. Just treat this like another SvelteKit app.
|
|
||||||
|
|
||||||
## Overview
|
|
||||||
|
|
||||||
```mermaid
|
|
||||||
flowchart LR
|
|
||||||
subgraph main[Main Process]
|
|
||||||
electron
|
|
||||||
end
|
|
||||||
subgraph renderer[Renderer Process]
|
|
||||||
browser
|
|
||||||
end
|
|
||||||
electron <-- preload --> renderer
|
|
||||||
```
|
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
meta {
|
||||||
|
name: Check User Permission
|
||||||
|
type: http
|
||||||
|
seq: 3
|
||||||
|
}
|
||||||
|
|
||||||
|
post {
|
||||||
|
url: {{baseUrl}}/v1/user/@me/check-permission
|
||||||
|
body: json
|
||||||
|
auth: none
|
||||||
|
}
|
||||||
|
|
||||||
|
headers {
|
||||||
|
Content-Type: application/json
|
||||||
|
}
|
||||||
|
|
||||||
|
body:json {
|
||||||
|
{
|
||||||
|
"permissions": ["user.read", "company.create", "credential.write"]
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
meta {
|
||||||
|
name: Fetch Company Pages.
|
||||||
|
type: http
|
||||||
|
seq: 2
|
||||||
|
}
|
||||||
|
|
||||||
|
get {
|
||||||
|
url:
|
||||||
|
body: none
|
||||||
|
auth: inherit
|
||||||
|
}
|
||||||
|
|
||||||
|
settings {
|
||||||
|
encodeUrl: true
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
meta {
|
||||||
|
name: Teapot
|
||||||
|
type: http
|
||||||
|
seq: 1
|
||||||
|
}
|
||||||
|
|
||||||
|
get {
|
||||||
|
url: http://localhost:3000/v1/teapot
|
||||||
|
body: none
|
||||||
|
auth: inherit
|
||||||
|
}
|
||||||
|
|
||||||
|
settings {
|
||||||
|
encodeUrl: true
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"version": "1",
|
||||||
|
"name": "optima",
|
||||||
|
"type": "collection",
|
||||||
|
"ignore": [
|
||||||
|
"node_modules",
|
||||||
|
".git"
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"version": "1",
|
||||||
|
"name": "optima",
|
||||||
|
"type": "collection",
|
||||||
|
"ignore": ["node_modules", ".git"]
|
||||||
|
}
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
import { expect, test } from '@playwright/test';
|
|
||||||
|
|
||||||
test('home page has expected h1', async ({ page }) => {
|
|
||||||
await page.goto('/');
|
|
||||||
await expect(page.locator('h1')).toBeVisible();
|
|
||||||
});
|
|
||||||
@@ -1,54 +0,0 @@
|
|||||||
import { app, BrowserWindow } from "electron";
|
|
||||||
import path from "node:path";
|
|
||||||
import started from "electron-squirrel-startup";
|
|
||||||
|
|
||||||
// Handle creating/removing shortcuts on Windows when installing/uninstalling.
|
|
||||||
if (started) {
|
|
||||||
app.quit();
|
|
||||||
}
|
|
||||||
|
|
||||||
const PRODUCTION_URL = "https://optima.osdci.net";
|
|
||||||
|
|
||||||
const createWindow = () => {
|
|
||||||
const mainWindow = new BrowserWindow({
|
|
||||||
width: 1200,
|
|
||||||
height: 800,
|
|
||||||
webPreferences: {
|
|
||||||
preload: path.join(import.meta.dirname, "preload.js"),
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (MAIN_WINDOW_VITE_DEV_SERVER_URL) {
|
|
||||||
mainWindow.loadURL(`${MAIN_WINDOW_VITE_DEV_SERVER_URL}/login`);
|
|
||||||
mainWindow.webContents.on("did-frame-finish-load", () => {
|
|
||||||
mainWindow.webContents.openDevTools({ mode: "detach" });
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
mainWindow.loadURL(PRODUCTION_URL);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// This method will be called when Electron has finished
|
|
||||||
// initialization and is ready to create browser windows.
|
|
||||||
// Some APIs can only be used after this event occurs.
|
|
||||||
app.on("ready", createWindow);
|
|
||||||
|
|
||||||
// Quit when all windows are closed, except on macOS. There, it's common
|
|
||||||
// for applications and their menu bar to stay active until the user quits
|
|
||||||
// explicitly with Cmd + Q.
|
|
||||||
app.on("window-all-closed", () => {
|
|
||||||
if (process.platform !== "darwin") {
|
|
||||||
app.quit();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
app.on("activate", () => {
|
|
||||||
// On OS X it's common to re-create a window in the app when the
|
|
||||||
// dock icon is clicked and there are no other windows open.
|
|
||||||
if (BrowserWindow.getAllWindows().length === 0) {
|
|
||||||
createWindow();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// In this file you can include the rest of your app's specific main process
|
|
||||||
// code. You can also put them in separate files and import them here.
|
|
||||||
@@ -1,61 +0,0 @@
|
|||||||
import type { ForgeConfig } from "@electron-forge/shared-types";
|
|
||||||
import { MakerSquirrel } from "@electron-forge/maker-squirrel";
|
|
||||||
import { MakerZIP } from "@electron-forge/maker-zip";
|
|
||||||
import { MakerDMG } from "@electron-forge/maker-dmg";
|
|
||||||
import { MakerDeb } from "@electron-forge/maker-deb";
|
|
||||||
import { MakerRpm } from "@electron-forge/maker-rpm";
|
|
||||||
import { VitePlugin } from "@electron-forge/plugin-vite";
|
|
||||||
import { FusesPlugin } from "@electron-forge/plugin-fuses";
|
|
||||||
import { FuseV1Options, FuseVersion } from "@electron/fuses";
|
|
||||||
|
|
||||||
const config: ForgeConfig = {
|
|
||||||
packagerConfig: {
|
|
||||||
asar: true,
|
|
||||||
},
|
|
||||||
rebuildConfig: {},
|
|
||||||
makers: [
|
|
||||||
new MakerSquirrel({}),
|
|
||||||
new MakerZIP({}, ["darwin"]),
|
|
||||||
new MakerDMG({}),
|
|
||||||
new MakerRpm({}),
|
|
||||||
new MakerDeb({}),
|
|
||||||
],
|
|
||||||
plugins: [
|
|
||||||
new VitePlugin({
|
|
||||||
// `build` can specify multiple entry builds, which can be Main process, Preload scripts, Worker process, etc.
|
|
||||||
// If you are familiar with Vite configuration, it will look really familiar.
|
|
||||||
build: [
|
|
||||||
{
|
|
||||||
// `entry` is just an alias for `build.lib.entry` in the corresponding file of `config`.
|
|
||||||
entry: "electron/main.ts",
|
|
||||||
config: "vite.main.config.ts",
|
|
||||||
target: "main",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
entry: "electron/preload.ts",
|
|
||||||
config: "vite.preload.config.ts",
|
|
||||||
target: "preload",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
renderer: [
|
|
||||||
{
|
|
||||||
name: "main_window",
|
|
||||||
config: "vite.config.ts",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
}),
|
|
||||||
// Fuses are used to enable/disable various Electron functionality
|
|
||||||
// at package time, before code signing the application
|
|
||||||
new FusesPlugin({
|
|
||||||
version: FuseVersion.V1,
|
|
||||||
[FuseV1Options.RunAsNode]: false,
|
|
||||||
[FuseV1Options.EnableCookieEncryption]: true,
|
|
||||||
[FuseV1Options.EnableNodeOptionsEnvironmentVariable]: false,
|
|
||||||
[FuseV1Options.EnableNodeCliInspectArguments]: false,
|
|
||||||
[FuseV1Options.EnableEmbeddedAsarIntegrityValidation]: true,
|
|
||||||
[FuseV1Options.OnlyLoadAppFromAsar]: true,
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
export default config;
|
|
||||||
Vendored
-1
@@ -1 +0,0 @@
|
|||||||
/// <reference types="@electron-forge/plugin-vite/forge-vite-env" />
|
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
|
||||||
|
/* !!! This is code generated by Prisma. Do not edit directly. !!! */
|
||||||
|
/* eslint-disable */
|
||||||
|
// biome-ignore-all lint: generated file
|
||||||
|
// @ts-nocheck
|
||||||
|
/*
|
||||||
|
* This file should be your main import to use Prisma-related types and utilities in a browser.
|
||||||
|
* Use it to get access to models, enums, and input types.
|
||||||
|
*
|
||||||
|
* This file does not contain a `PrismaClient` class, nor several other helpers that are intended as server-side only.
|
||||||
|
* See `client.ts` for the standard, server-side entry point.
|
||||||
|
*
|
||||||
|
* 🟢 You can import this file directly.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import * as Prisma from './internal/prismaNamespaceBrowser.ts'
|
||||||
|
export { Prisma }
|
||||||
|
export * as $Enums from './enums.ts'
|
||||||
|
export * from './enums.ts';
|
||||||
|
/**
|
||||||
|
* Model Session
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
export type Session = Prisma.SessionModel
|
||||||
|
/**
|
||||||
|
* Model User
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
export type User = Prisma.UserModel
|
||||||
|
/**
|
||||||
|
* Model Role
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
export type Role = Prisma.RoleModel
|
||||||
|
/**
|
||||||
|
* Model UnifiSite
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
export type UnifiSite = Prisma.UnifiSiteModel
|
||||||
|
/**
|
||||||
|
* Model Company
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
export type Company = Prisma.CompanyModel
|
||||||
|
/**
|
||||||
|
* Model CatalogItem
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
export type CatalogItem = Prisma.CatalogItemModel
|
||||||
|
/**
|
||||||
|
* Model CredentialType
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
export type CredentialType = Prisma.CredentialTypeModel
|
||||||
|
/**
|
||||||
|
* Model SecureValue
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
export type SecureValue = Prisma.SecureValueModel
|
||||||
|
/**
|
||||||
|
* Model Credential
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
export type Credential = Prisma.CredentialModel
|
||||||
@@ -0,0 +1,86 @@
|
|||||||
|
|
||||||
|
/* !!! This is code generated by Prisma. Do not edit directly. !!! */
|
||||||
|
/* eslint-disable */
|
||||||
|
// biome-ignore-all lint: generated file
|
||||||
|
// @ts-nocheck
|
||||||
|
/*
|
||||||
|
* This file should be your main import to use Prisma. Through it you get access to all the models, enums, and input types.
|
||||||
|
* If you're looking for something you can import in the client-side of your application, please refer to the `browser.ts` file instead.
|
||||||
|
*
|
||||||
|
* 🟢 You can import this file directly.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import * as process from 'node:process'
|
||||||
|
import * as path from 'node:path'
|
||||||
|
import { fileURLToPath } from 'node:url'
|
||||||
|
globalThis['__dirname'] = path.dirname(fileURLToPath(import.meta.url))
|
||||||
|
|
||||||
|
import * as runtime from "@prisma/client/runtime/client"
|
||||||
|
import * as $Enums from "./enums.ts"
|
||||||
|
import * as $Class from "./internal/class.ts"
|
||||||
|
import * as Prisma from "./internal/prismaNamespace.ts"
|
||||||
|
|
||||||
|
export * as $Enums from './enums.ts'
|
||||||
|
export * from "./enums.ts"
|
||||||
|
/**
|
||||||
|
* ## Prisma Client
|
||||||
|
*
|
||||||
|
* Type-safe database client for TypeScript
|
||||||
|
* @example
|
||||||
|
* ```
|
||||||
|
* const prisma = new PrismaClient()
|
||||||
|
* // Fetch zero or more Sessions
|
||||||
|
* const sessions = await prisma.session.findMany()
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* Read more in our [docs](https://pris.ly/d/client).
|
||||||
|
*/
|
||||||
|
export const PrismaClient = $Class.getPrismaClientClass()
|
||||||
|
export type PrismaClient<LogOpts extends Prisma.LogLevel = never, OmitOpts extends Prisma.PrismaClientOptions["omit"] = Prisma.PrismaClientOptions["omit"], ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = $Class.PrismaClient<LogOpts, OmitOpts, ExtArgs>
|
||||||
|
export { Prisma }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Model Session
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
export type Session = Prisma.SessionModel
|
||||||
|
/**
|
||||||
|
* Model User
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
export type User = Prisma.UserModel
|
||||||
|
/**
|
||||||
|
* Model Role
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
export type Role = Prisma.RoleModel
|
||||||
|
/**
|
||||||
|
* Model UnifiSite
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
export type UnifiSite = Prisma.UnifiSiteModel
|
||||||
|
/**
|
||||||
|
* Model Company
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
export type Company = Prisma.CompanyModel
|
||||||
|
/**
|
||||||
|
* Model CatalogItem
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
export type CatalogItem = Prisma.CatalogItemModel
|
||||||
|
/**
|
||||||
|
* Model CredentialType
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
export type CredentialType = Prisma.CredentialTypeModel
|
||||||
|
/**
|
||||||
|
* Model SecureValue
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
export type SecureValue = Prisma.SecureValueModel
|
||||||
|
/**
|
||||||
|
* Model Credential
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
export type Credential = Prisma.CredentialModel
|
||||||
@@ -0,0 +1,524 @@
|
|||||||
|
|
||||||
|
/* !!! This is code generated by Prisma. Do not edit directly. !!! */
|
||||||
|
/* eslint-disable */
|
||||||
|
// biome-ignore-all lint: generated file
|
||||||
|
// @ts-nocheck
|
||||||
|
/*
|
||||||
|
* This file exports various common sort, input & filter types that are not directly linked to a particular model.
|
||||||
|
*
|
||||||
|
* 🟢 You can import this file directly.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type * as runtime from "@prisma/client/runtime/client"
|
||||||
|
import * as $Enums from "./enums.ts"
|
||||||
|
import type * as Prisma from "./internal/prismaNamespace.ts"
|
||||||
|
|
||||||
|
|
||||||
|
export type StringFilter<$PrismaModel = never> = {
|
||||||
|
equals?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
in?: string[] | Prisma.ListStringFieldRefInput<$PrismaModel>
|
||||||
|
notIn?: string[] | Prisma.ListStringFieldRefInput<$PrismaModel>
|
||||||
|
lt?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
lte?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
gt?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
gte?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
contains?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
startsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
endsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
mode?: Prisma.QueryMode
|
||||||
|
not?: Prisma.NestedStringFilter<$PrismaModel> | string
|
||||||
|
}
|
||||||
|
|
||||||
|
export type DateTimeFilter<$PrismaModel = never> = {
|
||||||
|
equals?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||||
|
in?: Date[] | string[] | Prisma.ListDateTimeFieldRefInput<$PrismaModel>
|
||||||
|
notIn?: Date[] | string[] | Prisma.ListDateTimeFieldRefInput<$PrismaModel>
|
||||||
|
lt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||||
|
lte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||||
|
gt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||||
|
gte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||||
|
not?: Prisma.NestedDateTimeFilter<$PrismaModel> | Date | string
|
||||||
|
}
|
||||||
|
|
||||||
|
export type BoolFilter<$PrismaModel = never> = {
|
||||||
|
equals?: boolean | Prisma.BooleanFieldRefInput<$PrismaModel>
|
||||||
|
not?: Prisma.NestedBoolFilter<$PrismaModel> | boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export type DateTimeNullableFilter<$PrismaModel = never> = {
|
||||||
|
equals?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel> | null
|
||||||
|
in?: Date[] | string[] | Prisma.ListDateTimeFieldRefInput<$PrismaModel> | null
|
||||||
|
notIn?: Date[] | string[] | Prisma.ListDateTimeFieldRefInput<$PrismaModel> | null
|
||||||
|
lt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||||
|
lte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||||
|
gt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||||
|
gte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||||
|
not?: Prisma.NestedDateTimeNullableFilter<$PrismaModel> | Date | string | null
|
||||||
|
}
|
||||||
|
|
||||||
|
export type SortOrderInput = {
|
||||||
|
sort: Prisma.SortOrder
|
||||||
|
nulls?: Prisma.NullsOrder
|
||||||
|
}
|
||||||
|
|
||||||
|
export type StringWithAggregatesFilter<$PrismaModel = never> = {
|
||||||
|
equals?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
in?: string[] | Prisma.ListStringFieldRefInput<$PrismaModel>
|
||||||
|
notIn?: string[] | Prisma.ListStringFieldRefInput<$PrismaModel>
|
||||||
|
lt?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
lte?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
gt?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
gte?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
contains?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
startsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
endsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
mode?: Prisma.QueryMode
|
||||||
|
not?: Prisma.NestedStringWithAggregatesFilter<$PrismaModel> | string
|
||||||
|
_count?: Prisma.NestedIntFilter<$PrismaModel>
|
||||||
|
_min?: Prisma.NestedStringFilter<$PrismaModel>
|
||||||
|
_max?: Prisma.NestedStringFilter<$PrismaModel>
|
||||||
|
}
|
||||||
|
|
||||||
|
export type DateTimeWithAggregatesFilter<$PrismaModel = never> = {
|
||||||
|
equals?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||||
|
in?: Date[] | string[] | Prisma.ListDateTimeFieldRefInput<$PrismaModel>
|
||||||
|
notIn?: Date[] | string[] | Prisma.ListDateTimeFieldRefInput<$PrismaModel>
|
||||||
|
lt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||||
|
lte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||||
|
gt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||||
|
gte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||||
|
not?: Prisma.NestedDateTimeWithAggregatesFilter<$PrismaModel> | Date | string
|
||||||
|
_count?: Prisma.NestedIntFilter<$PrismaModel>
|
||||||
|
_min?: Prisma.NestedDateTimeFilter<$PrismaModel>
|
||||||
|
_max?: Prisma.NestedDateTimeFilter<$PrismaModel>
|
||||||
|
}
|
||||||
|
|
||||||
|
export type BoolWithAggregatesFilter<$PrismaModel = never> = {
|
||||||
|
equals?: boolean | Prisma.BooleanFieldRefInput<$PrismaModel>
|
||||||
|
not?: Prisma.NestedBoolWithAggregatesFilter<$PrismaModel> | boolean
|
||||||
|
_count?: Prisma.NestedIntFilter<$PrismaModel>
|
||||||
|
_min?: Prisma.NestedBoolFilter<$PrismaModel>
|
||||||
|
_max?: Prisma.NestedBoolFilter<$PrismaModel>
|
||||||
|
}
|
||||||
|
|
||||||
|
export type DateTimeNullableWithAggregatesFilter<$PrismaModel = never> = {
|
||||||
|
equals?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel> | null
|
||||||
|
in?: Date[] | string[] | Prisma.ListDateTimeFieldRefInput<$PrismaModel> | null
|
||||||
|
notIn?: Date[] | string[] | Prisma.ListDateTimeFieldRefInput<$PrismaModel> | null
|
||||||
|
lt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||||
|
lte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||||
|
gt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||||
|
gte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||||
|
not?: Prisma.NestedDateTimeNullableWithAggregatesFilter<$PrismaModel> | Date | string | null
|
||||||
|
_count?: Prisma.NestedIntNullableFilter<$PrismaModel>
|
||||||
|
_min?: Prisma.NestedDateTimeNullableFilter<$PrismaModel>
|
||||||
|
_max?: Prisma.NestedDateTimeNullableFilter<$PrismaModel>
|
||||||
|
}
|
||||||
|
|
||||||
|
export type StringNullableFilter<$PrismaModel = never> = {
|
||||||
|
equals?: string | Prisma.StringFieldRefInput<$PrismaModel> | null
|
||||||
|
in?: string[] | Prisma.ListStringFieldRefInput<$PrismaModel> | null
|
||||||
|
notIn?: string[] | Prisma.ListStringFieldRefInput<$PrismaModel> | null
|
||||||
|
lt?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
lte?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
gt?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
gte?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
contains?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
startsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
endsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
mode?: Prisma.QueryMode
|
||||||
|
not?: Prisma.NestedStringNullableFilter<$PrismaModel> | string | null
|
||||||
|
}
|
||||||
|
|
||||||
|
export type StringNullableWithAggregatesFilter<$PrismaModel = never> = {
|
||||||
|
equals?: string | Prisma.StringFieldRefInput<$PrismaModel> | null
|
||||||
|
in?: string[] | Prisma.ListStringFieldRefInput<$PrismaModel> | null
|
||||||
|
notIn?: string[] | Prisma.ListStringFieldRefInput<$PrismaModel> | null
|
||||||
|
lt?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
lte?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
gt?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
gte?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
contains?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
startsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
endsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
mode?: Prisma.QueryMode
|
||||||
|
not?: Prisma.NestedStringNullableWithAggregatesFilter<$PrismaModel> | string | null
|
||||||
|
_count?: Prisma.NestedIntNullableFilter<$PrismaModel>
|
||||||
|
_min?: Prisma.NestedStringNullableFilter<$PrismaModel>
|
||||||
|
_max?: Prisma.NestedStringNullableFilter<$PrismaModel>
|
||||||
|
}
|
||||||
|
|
||||||
|
export type IntFilter<$PrismaModel = never> = {
|
||||||
|
equals?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
||||||
|
in?: number[] | Prisma.ListIntFieldRefInput<$PrismaModel>
|
||||||
|
notIn?: number[] | Prisma.ListIntFieldRefInput<$PrismaModel>
|
||||||
|
lt?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
||||||
|
lte?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
||||||
|
gt?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
||||||
|
gte?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
||||||
|
not?: Prisma.NestedIntFilter<$PrismaModel> | number
|
||||||
|
}
|
||||||
|
|
||||||
|
export type IntWithAggregatesFilter<$PrismaModel = never> = {
|
||||||
|
equals?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
||||||
|
in?: number[] | Prisma.ListIntFieldRefInput<$PrismaModel>
|
||||||
|
notIn?: number[] | Prisma.ListIntFieldRefInput<$PrismaModel>
|
||||||
|
lt?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
||||||
|
lte?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
||||||
|
gt?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
||||||
|
gte?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
||||||
|
not?: Prisma.NestedIntWithAggregatesFilter<$PrismaModel> | number
|
||||||
|
_count?: Prisma.NestedIntFilter<$PrismaModel>
|
||||||
|
_avg?: Prisma.NestedFloatFilter<$PrismaModel>
|
||||||
|
_sum?: Prisma.NestedIntFilter<$PrismaModel>
|
||||||
|
_min?: Prisma.NestedIntFilter<$PrismaModel>
|
||||||
|
_max?: Prisma.NestedIntFilter<$PrismaModel>
|
||||||
|
}
|
||||||
|
|
||||||
|
export type IntNullableFilter<$PrismaModel = never> = {
|
||||||
|
equals?: number | Prisma.IntFieldRefInput<$PrismaModel> | null
|
||||||
|
in?: number[] | Prisma.ListIntFieldRefInput<$PrismaModel> | null
|
||||||
|
notIn?: number[] | Prisma.ListIntFieldRefInput<$PrismaModel> | null
|
||||||
|
lt?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
||||||
|
lte?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
||||||
|
gt?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
||||||
|
gte?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
||||||
|
not?: Prisma.NestedIntNullableFilter<$PrismaModel> | number | null
|
||||||
|
}
|
||||||
|
|
||||||
|
export type FloatFilter<$PrismaModel = never> = {
|
||||||
|
equals?: number | Prisma.FloatFieldRefInput<$PrismaModel>
|
||||||
|
in?: number[] | Prisma.ListFloatFieldRefInput<$PrismaModel>
|
||||||
|
notIn?: number[] | Prisma.ListFloatFieldRefInput<$PrismaModel>
|
||||||
|
lt?: number | Prisma.FloatFieldRefInput<$PrismaModel>
|
||||||
|
lte?: number | Prisma.FloatFieldRefInput<$PrismaModel>
|
||||||
|
gt?: number | Prisma.FloatFieldRefInput<$PrismaModel>
|
||||||
|
gte?: number | Prisma.FloatFieldRefInput<$PrismaModel>
|
||||||
|
not?: Prisma.NestedFloatFilter<$PrismaModel> | number
|
||||||
|
}
|
||||||
|
|
||||||
|
export type IntNullableWithAggregatesFilter<$PrismaModel = never> = {
|
||||||
|
equals?: number | Prisma.IntFieldRefInput<$PrismaModel> | null
|
||||||
|
in?: number[] | Prisma.ListIntFieldRefInput<$PrismaModel> | null
|
||||||
|
notIn?: number[] | Prisma.ListIntFieldRefInput<$PrismaModel> | null
|
||||||
|
lt?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
||||||
|
lte?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
||||||
|
gt?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
||||||
|
gte?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
||||||
|
not?: Prisma.NestedIntNullableWithAggregatesFilter<$PrismaModel> | number | null
|
||||||
|
_count?: Prisma.NestedIntNullableFilter<$PrismaModel>
|
||||||
|
_avg?: Prisma.NestedFloatNullableFilter<$PrismaModel>
|
||||||
|
_sum?: Prisma.NestedIntNullableFilter<$PrismaModel>
|
||||||
|
_min?: Prisma.NestedIntNullableFilter<$PrismaModel>
|
||||||
|
_max?: Prisma.NestedIntNullableFilter<$PrismaModel>
|
||||||
|
}
|
||||||
|
|
||||||
|
export type FloatWithAggregatesFilter<$PrismaModel = never> = {
|
||||||
|
equals?: number | Prisma.FloatFieldRefInput<$PrismaModel>
|
||||||
|
in?: number[] | Prisma.ListFloatFieldRefInput<$PrismaModel>
|
||||||
|
notIn?: number[] | Prisma.ListFloatFieldRefInput<$PrismaModel>
|
||||||
|
lt?: number | Prisma.FloatFieldRefInput<$PrismaModel>
|
||||||
|
lte?: number | Prisma.FloatFieldRefInput<$PrismaModel>
|
||||||
|
gt?: number | Prisma.FloatFieldRefInput<$PrismaModel>
|
||||||
|
gte?: number | Prisma.FloatFieldRefInput<$PrismaModel>
|
||||||
|
not?: Prisma.NestedFloatWithAggregatesFilter<$PrismaModel> | number
|
||||||
|
_count?: Prisma.NestedIntFilter<$PrismaModel>
|
||||||
|
_avg?: Prisma.NestedFloatFilter<$PrismaModel>
|
||||||
|
_sum?: Prisma.NestedFloatFilter<$PrismaModel>
|
||||||
|
_min?: Prisma.NestedFloatFilter<$PrismaModel>
|
||||||
|
_max?: Prisma.NestedFloatFilter<$PrismaModel>
|
||||||
|
}
|
||||||
|
|
||||||
|
export type JsonFilter<$PrismaModel = never> =
|
||||||
|
| Prisma.PatchUndefined<
|
||||||
|
Prisma.Either<Required<JsonFilterBase<$PrismaModel>>, Exclude<keyof Required<JsonFilterBase<$PrismaModel>>, 'path'>>,
|
||||||
|
Required<JsonFilterBase<$PrismaModel>>
|
||||||
|
>
|
||||||
|
| Prisma.OptionalFlat<Omit<Required<JsonFilterBase<$PrismaModel>>, 'path'>>
|
||||||
|
|
||||||
|
export type JsonFilterBase<$PrismaModel = never> = {
|
||||||
|
equals?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel> | Prisma.JsonNullValueFilter
|
||||||
|
path?: string[]
|
||||||
|
mode?: Prisma.QueryMode | Prisma.EnumQueryModeFieldRefInput<$PrismaModel>
|
||||||
|
string_contains?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
string_starts_with?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
string_ends_with?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
array_starts_with?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel> | null
|
||||||
|
array_ends_with?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel> | null
|
||||||
|
array_contains?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel> | null
|
||||||
|
lt?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel>
|
||||||
|
lte?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel>
|
||||||
|
gt?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel>
|
||||||
|
gte?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel>
|
||||||
|
not?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel> | Prisma.JsonNullValueFilter
|
||||||
|
}
|
||||||
|
|
||||||
|
export type JsonWithAggregatesFilter<$PrismaModel = never> =
|
||||||
|
| Prisma.PatchUndefined<
|
||||||
|
Prisma.Either<Required<JsonWithAggregatesFilterBase<$PrismaModel>>, Exclude<keyof Required<JsonWithAggregatesFilterBase<$PrismaModel>>, 'path'>>,
|
||||||
|
Required<JsonWithAggregatesFilterBase<$PrismaModel>>
|
||||||
|
>
|
||||||
|
| Prisma.OptionalFlat<Omit<Required<JsonWithAggregatesFilterBase<$PrismaModel>>, 'path'>>
|
||||||
|
|
||||||
|
export type JsonWithAggregatesFilterBase<$PrismaModel = never> = {
|
||||||
|
equals?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel> | Prisma.JsonNullValueFilter
|
||||||
|
path?: string[]
|
||||||
|
mode?: Prisma.QueryMode | Prisma.EnumQueryModeFieldRefInput<$PrismaModel>
|
||||||
|
string_contains?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
string_starts_with?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
string_ends_with?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
array_starts_with?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel> | null
|
||||||
|
array_ends_with?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel> | null
|
||||||
|
array_contains?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel> | null
|
||||||
|
lt?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel>
|
||||||
|
lte?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel>
|
||||||
|
gt?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel>
|
||||||
|
gte?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel>
|
||||||
|
not?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel> | Prisma.JsonNullValueFilter
|
||||||
|
_count?: Prisma.NestedIntFilter<$PrismaModel>
|
||||||
|
_min?: Prisma.NestedJsonFilter<$PrismaModel>
|
||||||
|
_max?: Prisma.NestedJsonFilter<$PrismaModel>
|
||||||
|
}
|
||||||
|
|
||||||
|
export type NestedStringFilter<$PrismaModel = never> = {
|
||||||
|
equals?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
in?: string[] | Prisma.ListStringFieldRefInput<$PrismaModel>
|
||||||
|
notIn?: string[] | Prisma.ListStringFieldRefInput<$PrismaModel>
|
||||||
|
lt?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
lte?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
gt?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
gte?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
contains?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
startsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
endsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
not?: Prisma.NestedStringFilter<$PrismaModel> | string
|
||||||
|
}
|
||||||
|
|
||||||
|
export type NestedDateTimeFilter<$PrismaModel = never> = {
|
||||||
|
equals?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||||
|
in?: Date[] | string[] | Prisma.ListDateTimeFieldRefInput<$PrismaModel>
|
||||||
|
notIn?: Date[] | string[] | Prisma.ListDateTimeFieldRefInput<$PrismaModel>
|
||||||
|
lt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||||
|
lte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||||
|
gt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||||
|
gte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||||
|
not?: Prisma.NestedDateTimeFilter<$PrismaModel> | Date | string
|
||||||
|
}
|
||||||
|
|
||||||
|
export type NestedBoolFilter<$PrismaModel = never> = {
|
||||||
|
equals?: boolean | Prisma.BooleanFieldRefInput<$PrismaModel>
|
||||||
|
not?: Prisma.NestedBoolFilter<$PrismaModel> | boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export type NestedDateTimeNullableFilter<$PrismaModel = never> = {
|
||||||
|
equals?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel> | null
|
||||||
|
in?: Date[] | string[] | Prisma.ListDateTimeFieldRefInput<$PrismaModel> | null
|
||||||
|
notIn?: Date[] | string[] | Prisma.ListDateTimeFieldRefInput<$PrismaModel> | null
|
||||||
|
lt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||||
|
lte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||||
|
gt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||||
|
gte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||||
|
not?: Prisma.NestedDateTimeNullableFilter<$PrismaModel> | Date | string | null
|
||||||
|
}
|
||||||
|
|
||||||
|
export type NestedStringWithAggregatesFilter<$PrismaModel = never> = {
|
||||||
|
equals?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
in?: string[] | Prisma.ListStringFieldRefInput<$PrismaModel>
|
||||||
|
notIn?: string[] | Prisma.ListStringFieldRefInput<$PrismaModel>
|
||||||
|
lt?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
lte?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
gt?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
gte?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
contains?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
startsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
endsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
not?: Prisma.NestedStringWithAggregatesFilter<$PrismaModel> | string
|
||||||
|
_count?: Prisma.NestedIntFilter<$PrismaModel>
|
||||||
|
_min?: Prisma.NestedStringFilter<$PrismaModel>
|
||||||
|
_max?: Prisma.NestedStringFilter<$PrismaModel>
|
||||||
|
}
|
||||||
|
|
||||||
|
export type NestedIntFilter<$PrismaModel = never> = {
|
||||||
|
equals?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
||||||
|
in?: number[] | Prisma.ListIntFieldRefInput<$PrismaModel>
|
||||||
|
notIn?: number[] | Prisma.ListIntFieldRefInput<$PrismaModel>
|
||||||
|
lt?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
||||||
|
lte?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
||||||
|
gt?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
||||||
|
gte?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
||||||
|
not?: Prisma.NestedIntFilter<$PrismaModel> | number
|
||||||
|
}
|
||||||
|
|
||||||
|
export type NestedDateTimeWithAggregatesFilter<$PrismaModel = never> = {
|
||||||
|
equals?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||||
|
in?: Date[] | string[] | Prisma.ListDateTimeFieldRefInput<$PrismaModel>
|
||||||
|
notIn?: Date[] | string[] | Prisma.ListDateTimeFieldRefInput<$PrismaModel>
|
||||||
|
lt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||||
|
lte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||||
|
gt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||||
|
gte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||||
|
not?: Prisma.NestedDateTimeWithAggregatesFilter<$PrismaModel> | Date | string
|
||||||
|
_count?: Prisma.NestedIntFilter<$PrismaModel>
|
||||||
|
_min?: Prisma.NestedDateTimeFilter<$PrismaModel>
|
||||||
|
_max?: Prisma.NestedDateTimeFilter<$PrismaModel>
|
||||||
|
}
|
||||||
|
|
||||||
|
export type NestedBoolWithAggregatesFilter<$PrismaModel = never> = {
|
||||||
|
equals?: boolean | Prisma.BooleanFieldRefInput<$PrismaModel>
|
||||||
|
not?: Prisma.NestedBoolWithAggregatesFilter<$PrismaModel> | boolean
|
||||||
|
_count?: Prisma.NestedIntFilter<$PrismaModel>
|
||||||
|
_min?: Prisma.NestedBoolFilter<$PrismaModel>
|
||||||
|
_max?: Prisma.NestedBoolFilter<$PrismaModel>
|
||||||
|
}
|
||||||
|
|
||||||
|
export type NestedDateTimeNullableWithAggregatesFilter<$PrismaModel = never> = {
|
||||||
|
equals?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel> | null
|
||||||
|
in?: Date[] | string[] | Prisma.ListDateTimeFieldRefInput<$PrismaModel> | null
|
||||||
|
notIn?: Date[] | string[] | Prisma.ListDateTimeFieldRefInput<$PrismaModel> | null
|
||||||
|
lt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||||
|
lte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||||
|
gt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||||
|
gte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel>
|
||||||
|
not?: Prisma.NestedDateTimeNullableWithAggregatesFilter<$PrismaModel> | Date | string | null
|
||||||
|
_count?: Prisma.NestedIntNullableFilter<$PrismaModel>
|
||||||
|
_min?: Prisma.NestedDateTimeNullableFilter<$PrismaModel>
|
||||||
|
_max?: Prisma.NestedDateTimeNullableFilter<$PrismaModel>
|
||||||
|
}
|
||||||
|
|
||||||
|
export type NestedIntNullableFilter<$PrismaModel = never> = {
|
||||||
|
equals?: number | Prisma.IntFieldRefInput<$PrismaModel> | null
|
||||||
|
in?: number[] | Prisma.ListIntFieldRefInput<$PrismaModel> | null
|
||||||
|
notIn?: number[] | Prisma.ListIntFieldRefInput<$PrismaModel> | null
|
||||||
|
lt?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
||||||
|
lte?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
||||||
|
gt?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
||||||
|
gte?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
||||||
|
not?: Prisma.NestedIntNullableFilter<$PrismaModel> | number | null
|
||||||
|
}
|
||||||
|
|
||||||
|
export type NestedStringNullableFilter<$PrismaModel = never> = {
|
||||||
|
equals?: string | Prisma.StringFieldRefInput<$PrismaModel> | null
|
||||||
|
in?: string[] | Prisma.ListStringFieldRefInput<$PrismaModel> | null
|
||||||
|
notIn?: string[] | Prisma.ListStringFieldRefInput<$PrismaModel> | null
|
||||||
|
lt?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
lte?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
gt?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
gte?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
contains?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
startsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
endsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
not?: Prisma.NestedStringNullableFilter<$PrismaModel> | string | null
|
||||||
|
}
|
||||||
|
|
||||||
|
export type NestedStringNullableWithAggregatesFilter<$PrismaModel = never> = {
|
||||||
|
equals?: string | Prisma.StringFieldRefInput<$PrismaModel> | null
|
||||||
|
in?: string[] | Prisma.ListStringFieldRefInput<$PrismaModel> | null
|
||||||
|
notIn?: string[] | Prisma.ListStringFieldRefInput<$PrismaModel> | null
|
||||||
|
lt?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
lte?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
gt?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
gte?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
contains?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
startsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
endsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
not?: Prisma.NestedStringNullableWithAggregatesFilter<$PrismaModel> | string | null
|
||||||
|
_count?: Prisma.NestedIntNullableFilter<$PrismaModel>
|
||||||
|
_min?: Prisma.NestedStringNullableFilter<$PrismaModel>
|
||||||
|
_max?: Prisma.NestedStringNullableFilter<$PrismaModel>
|
||||||
|
}
|
||||||
|
|
||||||
|
export type NestedIntWithAggregatesFilter<$PrismaModel = never> = {
|
||||||
|
equals?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
||||||
|
in?: number[] | Prisma.ListIntFieldRefInput<$PrismaModel>
|
||||||
|
notIn?: number[] | Prisma.ListIntFieldRefInput<$PrismaModel>
|
||||||
|
lt?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
||||||
|
lte?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
||||||
|
gt?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
||||||
|
gte?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
||||||
|
not?: Prisma.NestedIntWithAggregatesFilter<$PrismaModel> | number
|
||||||
|
_count?: Prisma.NestedIntFilter<$PrismaModel>
|
||||||
|
_avg?: Prisma.NestedFloatFilter<$PrismaModel>
|
||||||
|
_sum?: Prisma.NestedIntFilter<$PrismaModel>
|
||||||
|
_min?: Prisma.NestedIntFilter<$PrismaModel>
|
||||||
|
_max?: Prisma.NestedIntFilter<$PrismaModel>
|
||||||
|
}
|
||||||
|
|
||||||
|
export type NestedFloatFilter<$PrismaModel = never> = {
|
||||||
|
equals?: number | Prisma.FloatFieldRefInput<$PrismaModel>
|
||||||
|
in?: number[] | Prisma.ListFloatFieldRefInput<$PrismaModel>
|
||||||
|
notIn?: number[] | Prisma.ListFloatFieldRefInput<$PrismaModel>
|
||||||
|
lt?: number | Prisma.FloatFieldRefInput<$PrismaModel>
|
||||||
|
lte?: number | Prisma.FloatFieldRefInput<$PrismaModel>
|
||||||
|
gt?: number | Prisma.FloatFieldRefInput<$PrismaModel>
|
||||||
|
gte?: number | Prisma.FloatFieldRefInput<$PrismaModel>
|
||||||
|
not?: Prisma.NestedFloatFilter<$PrismaModel> | number
|
||||||
|
}
|
||||||
|
|
||||||
|
export type NestedIntNullableWithAggregatesFilter<$PrismaModel = never> = {
|
||||||
|
equals?: number | Prisma.IntFieldRefInput<$PrismaModel> | null
|
||||||
|
in?: number[] | Prisma.ListIntFieldRefInput<$PrismaModel> | null
|
||||||
|
notIn?: number[] | Prisma.ListIntFieldRefInput<$PrismaModel> | null
|
||||||
|
lt?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
||||||
|
lte?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
||||||
|
gt?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
||||||
|
gte?: number | Prisma.IntFieldRefInput<$PrismaModel>
|
||||||
|
not?: Prisma.NestedIntNullableWithAggregatesFilter<$PrismaModel> | number | null
|
||||||
|
_count?: Prisma.NestedIntNullableFilter<$PrismaModel>
|
||||||
|
_avg?: Prisma.NestedFloatNullableFilter<$PrismaModel>
|
||||||
|
_sum?: Prisma.NestedIntNullableFilter<$PrismaModel>
|
||||||
|
_min?: Prisma.NestedIntNullableFilter<$PrismaModel>
|
||||||
|
_max?: Prisma.NestedIntNullableFilter<$PrismaModel>
|
||||||
|
}
|
||||||
|
|
||||||
|
export type NestedFloatNullableFilter<$PrismaModel = never> = {
|
||||||
|
equals?: number | Prisma.FloatFieldRefInput<$PrismaModel> | null
|
||||||
|
in?: number[] | Prisma.ListFloatFieldRefInput<$PrismaModel> | null
|
||||||
|
notIn?: number[] | Prisma.ListFloatFieldRefInput<$PrismaModel> | null
|
||||||
|
lt?: number | Prisma.FloatFieldRefInput<$PrismaModel>
|
||||||
|
lte?: number | Prisma.FloatFieldRefInput<$PrismaModel>
|
||||||
|
gt?: number | Prisma.FloatFieldRefInput<$PrismaModel>
|
||||||
|
gte?: number | Prisma.FloatFieldRefInput<$PrismaModel>
|
||||||
|
not?: Prisma.NestedFloatNullableFilter<$PrismaModel> | number | null
|
||||||
|
}
|
||||||
|
|
||||||
|
export type NestedFloatWithAggregatesFilter<$PrismaModel = never> = {
|
||||||
|
equals?: number | Prisma.FloatFieldRefInput<$PrismaModel>
|
||||||
|
in?: number[] | Prisma.ListFloatFieldRefInput<$PrismaModel>
|
||||||
|
notIn?: number[] | Prisma.ListFloatFieldRefInput<$PrismaModel>
|
||||||
|
lt?: number | Prisma.FloatFieldRefInput<$PrismaModel>
|
||||||
|
lte?: number | Prisma.FloatFieldRefInput<$PrismaModel>
|
||||||
|
gt?: number | Prisma.FloatFieldRefInput<$PrismaModel>
|
||||||
|
gte?: number | Prisma.FloatFieldRefInput<$PrismaModel>
|
||||||
|
not?: Prisma.NestedFloatWithAggregatesFilter<$PrismaModel> | number
|
||||||
|
_count?: Prisma.NestedIntFilter<$PrismaModel>
|
||||||
|
_avg?: Prisma.NestedFloatFilter<$PrismaModel>
|
||||||
|
_sum?: Prisma.NestedFloatFilter<$PrismaModel>
|
||||||
|
_min?: Prisma.NestedFloatFilter<$PrismaModel>
|
||||||
|
_max?: Prisma.NestedFloatFilter<$PrismaModel>
|
||||||
|
}
|
||||||
|
|
||||||
|
export type NestedJsonFilter<$PrismaModel = never> =
|
||||||
|
| Prisma.PatchUndefined<
|
||||||
|
Prisma.Either<Required<NestedJsonFilterBase<$PrismaModel>>, Exclude<keyof Required<NestedJsonFilterBase<$PrismaModel>>, 'path'>>,
|
||||||
|
Required<NestedJsonFilterBase<$PrismaModel>>
|
||||||
|
>
|
||||||
|
| Prisma.OptionalFlat<Omit<Required<NestedJsonFilterBase<$PrismaModel>>, 'path'>>
|
||||||
|
|
||||||
|
export type NestedJsonFilterBase<$PrismaModel = never> = {
|
||||||
|
equals?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel> | Prisma.JsonNullValueFilter
|
||||||
|
path?: string[]
|
||||||
|
mode?: Prisma.QueryMode | Prisma.EnumQueryModeFieldRefInput<$PrismaModel>
|
||||||
|
string_contains?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
string_starts_with?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
string_ends_with?: string | Prisma.StringFieldRefInput<$PrismaModel>
|
||||||
|
array_starts_with?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel> | null
|
||||||
|
array_ends_with?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel> | null
|
||||||
|
array_contains?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel> | null
|
||||||
|
lt?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel>
|
||||||
|
lte?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel>
|
||||||
|
gt?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel>
|
||||||
|
gte?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel>
|
||||||
|
not?: runtime.InputJsonValue | Prisma.JsonFieldRefInput<$PrismaModel> | Prisma.JsonNullValueFilter
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
|
||||||
|
/* !!! This is code generated by Prisma. Do not edit directly. !!! */
|
||||||
|
/* eslint-disable */
|
||||||
|
// biome-ignore-all lint: generated file
|
||||||
|
// @ts-nocheck
|
||||||
|
/*
|
||||||
|
* This file exports all enum related types from the schema.
|
||||||
|
*
|
||||||
|
* 🟢 You can import this file directly.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// This file is empty because there are no enums in the schema.
|
||||||
|
export {}
|
||||||
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,252 @@
|
|||||||
|
|
||||||
|
/* !!! This is code generated by Prisma. Do not edit directly. !!! */
|
||||||
|
/* eslint-disable */
|
||||||
|
// biome-ignore-all lint: generated file
|
||||||
|
// @ts-nocheck
|
||||||
|
/*
|
||||||
|
* WARNING: This is an internal file that is subject to change!
|
||||||
|
*
|
||||||
|
* 🛑 Under no circumstances should you import this file directly! 🛑
|
||||||
|
*
|
||||||
|
* All exports from this file are wrapped under a `Prisma` namespace object in the browser.ts file.
|
||||||
|
* While this enables partial backward compatibility, it is not part of the stable public API.
|
||||||
|
*
|
||||||
|
* If you are looking for your Models, Enums, and Input Types, please import them from the respective
|
||||||
|
* model files in the `model` directory!
|
||||||
|
*/
|
||||||
|
|
||||||
|
import * as runtime from "@prisma/client/runtime/index-browser"
|
||||||
|
|
||||||
|
export type * from '../models.ts'
|
||||||
|
export type * from './prismaNamespace.ts'
|
||||||
|
|
||||||
|
export const Decimal = runtime.Decimal
|
||||||
|
|
||||||
|
|
||||||
|
export const NullTypes = {
|
||||||
|
DbNull: runtime.NullTypes.DbNull as (new (secret: never) => typeof runtime.DbNull),
|
||||||
|
JsonNull: runtime.NullTypes.JsonNull as (new (secret: never) => typeof runtime.JsonNull),
|
||||||
|
AnyNull: runtime.NullTypes.AnyNull as (new (secret: never) => typeof runtime.AnyNull),
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Helper for filtering JSON entries that have `null` on the database (empty on the db)
|
||||||
|
*
|
||||||
|
* @see https://www.prisma.io/docs/concepts/components/prisma-client/working-with-fields/working-with-json-fields#filtering-on-a-json-field
|
||||||
|
*/
|
||||||
|
export const DbNull = runtime.DbNull
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper for filtering JSON entries that have JSON `null` values (not empty on the db)
|
||||||
|
*
|
||||||
|
* @see https://www.prisma.io/docs/concepts/components/prisma-client/working-with-fields/working-with-json-fields#filtering-on-a-json-field
|
||||||
|
*/
|
||||||
|
export const JsonNull = runtime.JsonNull
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper for filtering JSON entries that are `Prisma.DbNull` or `Prisma.JsonNull`
|
||||||
|
*
|
||||||
|
* @see https://www.prisma.io/docs/concepts/components/prisma-client/working-with-fields/working-with-json-fields#filtering-on-a-json-field
|
||||||
|
*/
|
||||||
|
export const AnyNull = runtime.AnyNull
|
||||||
|
|
||||||
|
|
||||||
|
export const ModelName = {
|
||||||
|
Session: 'Session',
|
||||||
|
User: 'User',
|
||||||
|
Role: 'Role',
|
||||||
|
UnifiSite: 'UnifiSite',
|
||||||
|
Company: 'Company',
|
||||||
|
CatalogItem: 'CatalogItem',
|
||||||
|
CredentialType: 'CredentialType',
|
||||||
|
SecureValue: 'SecureValue',
|
||||||
|
Credential: 'Credential'
|
||||||
|
} as const
|
||||||
|
|
||||||
|
export type ModelName = (typeof ModelName)[keyof typeof ModelName]
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Enums
|
||||||
|
*/
|
||||||
|
|
||||||
|
export const TransactionIsolationLevel = runtime.makeStrictEnum({
|
||||||
|
ReadUncommitted: 'ReadUncommitted',
|
||||||
|
ReadCommitted: 'ReadCommitted',
|
||||||
|
RepeatableRead: 'RepeatableRead',
|
||||||
|
Serializable: 'Serializable'
|
||||||
|
} as const)
|
||||||
|
|
||||||
|
export type TransactionIsolationLevel = (typeof TransactionIsolationLevel)[keyof typeof TransactionIsolationLevel]
|
||||||
|
|
||||||
|
|
||||||
|
export const SessionScalarFieldEnum = {
|
||||||
|
id: 'id',
|
||||||
|
sessionKey: 'sessionKey',
|
||||||
|
userId: 'userId',
|
||||||
|
expires: 'expires',
|
||||||
|
refreshTokenGenerated: 'refreshTokenGenerated',
|
||||||
|
refreshedAt: 'refreshedAt',
|
||||||
|
invalidatedAt: 'invalidatedAt'
|
||||||
|
} as const
|
||||||
|
|
||||||
|
export type SessionScalarFieldEnum = (typeof SessionScalarFieldEnum)[keyof typeof SessionScalarFieldEnum]
|
||||||
|
|
||||||
|
|
||||||
|
export const UserScalarFieldEnum = {
|
||||||
|
id: 'id',
|
||||||
|
permissions: 'permissions',
|
||||||
|
login: 'login',
|
||||||
|
name: 'name',
|
||||||
|
email: 'email',
|
||||||
|
emailVerified: 'emailVerified',
|
||||||
|
image: 'image',
|
||||||
|
userId: 'userId',
|
||||||
|
token: 'token',
|
||||||
|
createdAt: 'createdAt',
|
||||||
|
updatedAt: 'updatedAt'
|
||||||
|
} as const
|
||||||
|
|
||||||
|
export type UserScalarFieldEnum = (typeof UserScalarFieldEnum)[keyof typeof UserScalarFieldEnum]
|
||||||
|
|
||||||
|
|
||||||
|
export const RoleScalarFieldEnum = {
|
||||||
|
id: 'id',
|
||||||
|
title: 'title',
|
||||||
|
moniker: 'moniker',
|
||||||
|
permissions: 'permissions',
|
||||||
|
createdAt: 'createdAt',
|
||||||
|
updatedAt: 'updatedAt'
|
||||||
|
} as const
|
||||||
|
|
||||||
|
export type RoleScalarFieldEnum = (typeof RoleScalarFieldEnum)[keyof typeof RoleScalarFieldEnum]
|
||||||
|
|
||||||
|
|
||||||
|
export const UnifiSiteScalarFieldEnum = {
|
||||||
|
id: 'id',
|
||||||
|
name: 'name',
|
||||||
|
siteId: 'siteId',
|
||||||
|
companyId: 'companyId',
|
||||||
|
createdAt: 'createdAt',
|
||||||
|
updatedAt: 'updatedAt'
|
||||||
|
} as const
|
||||||
|
|
||||||
|
export type UnifiSiteScalarFieldEnum = (typeof UnifiSiteScalarFieldEnum)[keyof typeof UnifiSiteScalarFieldEnum]
|
||||||
|
|
||||||
|
|
||||||
|
export const CompanyScalarFieldEnum = {
|
||||||
|
id: 'id',
|
||||||
|
name: 'name',
|
||||||
|
cw_CompanyId: 'cw_CompanyId',
|
||||||
|
cw_Identifier: 'cw_Identifier',
|
||||||
|
createdAt: 'createdAt',
|
||||||
|
updatedAt: 'updatedAt'
|
||||||
|
} as const
|
||||||
|
|
||||||
|
export type CompanyScalarFieldEnum = (typeof CompanyScalarFieldEnum)[keyof typeof CompanyScalarFieldEnum]
|
||||||
|
|
||||||
|
|
||||||
|
export const CatalogItemScalarFieldEnum = {
|
||||||
|
id: 'id',
|
||||||
|
cwCatalogId: 'cwCatalogId',
|
||||||
|
name: 'name',
|
||||||
|
description: 'description',
|
||||||
|
customerDescription: 'customerDescription',
|
||||||
|
internalNotes: 'internalNotes',
|
||||||
|
manufacturer: 'manufacturer',
|
||||||
|
manufactureCwId: 'manufactureCwId',
|
||||||
|
partNumber: 'partNumber',
|
||||||
|
vendorName: 'vendorName',
|
||||||
|
vendorSku: 'vendorSku',
|
||||||
|
vendorCwId: 'vendorCwId',
|
||||||
|
price: 'price',
|
||||||
|
cost: 'cost',
|
||||||
|
inactive: 'inactive',
|
||||||
|
salesTaxable: 'salesTaxable',
|
||||||
|
onHand: 'onHand',
|
||||||
|
cwLastUpdated: 'cwLastUpdated',
|
||||||
|
createdAt: 'createdAt',
|
||||||
|
updatedAt: 'updatedAt'
|
||||||
|
} as const
|
||||||
|
|
||||||
|
export type CatalogItemScalarFieldEnum = (typeof CatalogItemScalarFieldEnum)[keyof typeof CatalogItemScalarFieldEnum]
|
||||||
|
|
||||||
|
|
||||||
|
export const CredentialTypeScalarFieldEnum = {
|
||||||
|
id: 'id',
|
||||||
|
name: 'name',
|
||||||
|
permissionScope: 'permissionScope',
|
||||||
|
icon: 'icon',
|
||||||
|
fields: 'fields',
|
||||||
|
createdAt: 'createdAt',
|
||||||
|
updatedAt: 'updatedAt'
|
||||||
|
} as const
|
||||||
|
|
||||||
|
export type CredentialTypeScalarFieldEnum = (typeof CredentialTypeScalarFieldEnum)[keyof typeof CredentialTypeScalarFieldEnum]
|
||||||
|
|
||||||
|
|
||||||
|
export const SecureValueScalarFieldEnum = {
|
||||||
|
id: 'id',
|
||||||
|
name: 'name',
|
||||||
|
content: 'content',
|
||||||
|
hash: 'hash',
|
||||||
|
credentialId: 'credentialId',
|
||||||
|
createdAt: 'createdAt',
|
||||||
|
updatedAt: 'updatedAt'
|
||||||
|
} as const
|
||||||
|
|
||||||
|
export type SecureValueScalarFieldEnum = (typeof SecureValueScalarFieldEnum)[keyof typeof SecureValueScalarFieldEnum]
|
||||||
|
|
||||||
|
|
||||||
|
export const CredentialScalarFieldEnum = {
|
||||||
|
id: 'id',
|
||||||
|
name: 'name',
|
||||||
|
notes: 'notes',
|
||||||
|
subCredentialOfId: 'subCredentialOfId',
|
||||||
|
typeId: 'typeId',
|
||||||
|
fields: 'fields',
|
||||||
|
companyId: 'companyId',
|
||||||
|
createdAt: 'createdAt',
|
||||||
|
updatedAt: 'updatedAt'
|
||||||
|
} as const
|
||||||
|
|
||||||
|
export type CredentialScalarFieldEnum = (typeof CredentialScalarFieldEnum)[keyof typeof CredentialScalarFieldEnum]
|
||||||
|
|
||||||
|
|
||||||
|
export const SortOrder = {
|
||||||
|
asc: 'asc',
|
||||||
|
desc: 'desc'
|
||||||
|
} as const
|
||||||
|
|
||||||
|
export type SortOrder = (typeof SortOrder)[keyof typeof SortOrder]
|
||||||
|
|
||||||
|
|
||||||
|
export const JsonNullValueInput = {
|
||||||
|
JsonNull: JsonNull
|
||||||
|
} as const
|
||||||
|
|
||||||
|
export type JsonNullValueInput = (typeof JsonNullValueInput)[keyof typeof JsonNullValueInput]
|
||||||
|
|
||||||
|
|
||||||
|
export const QueryMode = {
|
||||||
|
default: 'default',
|
||||||
|
insensitive: 'insensitive'
|
||||||
|
} as const
|
||||||
|
|
||||||
|
export type QueryMode = (typeof QueryMode)[keyof typeof QueryMode]
|
||||||
|
|
||||||
|
|
||||||
|
export const NullsOrder = {
|
||||||
|
first: 'first',
|
||||||
|
last: 'last'
|
||||||
|
} as const
|
||||||
|
|
||||||
|
export type NullsOrder = (typeof NullsOrder)[keyof typeof NullsOrder]
|
||||||
|
|
||||||
|
|
||||||
|
export const JsonNullValueFilter = {
|
||||||
|
DbNull: DbNull,
|
||||||
|
JsonNull: JsonNull,
|
||||||
|
AnyNull: AnyNull
|
||||||
|
} as const
|
||||||
|
|
||||||
|
export type JsonNullValueFilter = (typeof JsonNullValueFilter)[keyof typeof JsonNullValueFilter]
|
||||||
|
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
|
||||||
|
/* !!! This is code generated by Prisma. Do not edit directly. !!! */
|
||||||
|
/* eslint-disable */
|
||||||
|
// biome-ignore-all lint: generated file
|
||||||
|
// @ts-nocheck
|
||||||
|
/*
|
||||||
|
* This is a barrel export file for all models and their related types.
|
||||||
|
*
|
||||||
|
* 🟢 You can import this file directly.
|
||||||
|
*/
|
||||||
|
export type * from './models/Session.ts'
|
||||||
|
export type * from './models/User.ts'
|
||||||
|
export type * from './models/Role.ts'
|
||||||
|
export type * from './models/UnifiSite.ts'
|
||||||
|
export type * from './models/Company.ts'
|
||||||
|
export type * from './models/CatalogItem.ts'
|
||||||
|
export type * from './models/CredentialType.ts'
|
||||||
|
export type * from './models/SecureValue.ts'
|
||||||
|
export type * from './models/Credential.ts'
|
||||||
|
export type * from './commonInputTypes.ts'
|
||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
+10
-12
@@ -1,29 +1,27 @@
|
|||||||
apiVersion: apps/v1
|
apiVersion: apps/v1
|
||||||
kind: Deployment
|
kind: Deployment
|
||||||
metadata:
|
metadata:
|
||||||
name: optima-ui
|
name: optima-api
|
||||||
namespace: optima
|
namespace: optima
|
||||||
spec:
|
spec:
|
||||||
selector:
|
selector:
|
||||||
matchLabels:
|
matchLabels:
|
||||||
app: optima-ui
|
app: optima-api
|
||||||
replicas: 1
|
replicas: 1
|
||||||
template:
|
template:
|
||||||
metadata:
|
metadata:
|
||||||
labels:
|
labels:
|
||||||
app: optima-ui
|
app: optima-api
|
||||||
spec:
|
spec:
|
||||||
containers:
|
containers:
|
||||||
- name: optima-ui
|
- name: optima-api
|
||||||
image: ghcr.io/project-optima/ttscm-ui:latest
|
image: ghcr.io/project-optima/ttscm-api:latest
|
||||||
imagePullPolicy: Always
|
imagePullPolicy: Always
|
||||||
env:
|
envFrom:
|
||||||
- name: PUBLIC_API_URL
|
- secretRef:
|
||||||
value: "https://opt-api.osdci.net"
|
name: api-env-secret
|
||||||
- name: ORIGIN
|
- secretRef:
|
||||||
value: "https://optima.osdci.net"
|
name: optima-keys-secret
|
||||||
- name: PORT
|
|
||||||
value: "3000"
|
|
||||||
ports:
|
ports:
|
||||||
- containerPort: 3000
|
- containerPort: 3000
|
||||||
imagePullSecrets:
|
imagePullSecrets:
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
apiVersion: networking.k8s.io/v1
|
apiVersion: networking.k8s.io/v1
|
||||||
kind: Ingress
|
kind: Ingress
|
||||||
metadata:
|
metadata:
|
||||||
name: optima-ui-ingress
|
name: optima-api-ingress
|
||||||
namespace: optima
|
namespace: optima
|
||||||
annotations:
|
annotations:
|
||||||
traefik.ingress.kubernetes.io/router.entrypoints: websecure
|
traefik.ingress.kubernetes.io/router.entrypoints: websecure
|
||||||
@@ -9,31 +9,31 @@ metadata:
|
|||||||
spec:
|
spec:
|
||||||
tls:
|
tls:
|
||||||
- hosts:
|
- hosts:
|
||||||
- optima.osdci.net
|
- opt-api.osdci.net
|
||||||
secretName: osdci-net-cert
|
secretName: osdci-net-cert
|
||||||
rules:
|
rules:
|
||||||
- host: optima.osdci.net
|
- host: opt-api.osdci.net
|
||||||
http:
|
http:
|
||||||
paths:
|
paths:
|
||||||
- path: /
|
- path: /
|
||||||
pathType: Prefix
|
pathType: Prefix
|
||||||
backend:
|
backend:
|
||||||
service:
|
service:
|
||||||
name: optima-ui
|
name: optima-api
|
||||||
port:
|
port:
|
||||||
number: 3000
|
number: 3000
|
||||||
---
|
---
|
||||||
apiVersion: v1
|
apiVersion: v1
|
||||||
kind: Service
|
kind: Service
|
||||||
metadata:
|
metadata:
|
||||||
name: optima-ui
|
name: optima-api
|
||||||
namespace: optima
|
namespace: optima
|
||||||
labels:
|
labels:
|
||||||
app: optima-ui
|
app: optima-api
|
||||||
spec:
|
spec:
|
||||||
type: ClusterIP
|
type: ClusterIP
|
||||||
ports:
|
ports:
|
||||||
- port: 3000
|
- port: 3000
|
||||||
protocol: TCP
|
protocol: TCP
|
||||||
selector:
|
selector:
|
||||||
app: optima-ui
|
app: optima-api
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
apiVersion: batch/v1
|
||||||
|
kind: Job
|
||||||
|
metadata:
|
||||||
|
name: prisma-migrate-RELEASE_TAG
|
||||||
|
namespace: optima
|
||||||
|
labels:
|
||||||
|
app: prisma-migrate
|
||||||
|
spec:
|
||||||
|
backoffLimit: 3
|
||||||
|
ttlSecondsAfterFinished: 300
|
||||||
|
template:
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: migrate
|
||||||
|
image: ghcr.io/project-optima/ttscm-api-migrate:RELEASE_TAG
|
||||||
|
envFrom:
|
||||||
|
- secretRef:
|
||||||
|
name: api-env-secret
|
||||||
|
- secretRef:
|
||||||
|
name: optima-keys-secret
|
||||||
|
restartPolicy: Never
|
||||||
|
imagePullSecrets:
|
||||||
|
- name: github-container-registry
|
||||||
+40
-64
@@ -1,73 +1,49 @@
|
|||||||
{
|
{
|
||||||
"name": "electron-svelte",
|
"name": "tts-optima-api",
|
||||||
"productName": "electron-svelte",
|
"homepage": "https://totaltech.net",
|
||||||
"description": "Electron Svelte",
|
"version": "v0.1.0",
|
||||||
"private": true,
|
|
||||||
"version": "0.0.1",
|
|
||||||
"type": "module",
|
|
||||||
"main": ".vite/build/main.js",
|
|
||||||
"author": {
|
"author": {
|
||||||
"name": "Pandoks_",
|
"name": "Jackson Roberts",
|
||||||
"email": "35944715+Pandoks@users.noreply.github.com"
|
"email": "jackson.roberts@totaltech.net",
|
||||||
|
"url": "https://totaltech.net"
|
||||||
|
},
|
||||||
|
"module": "src/index.ts",
|
||||||
|
"type": "module",
|
||||||
|
"private": true,
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/bun": "latest",
|
||||||
|
"@types/jsonwebtoken": "^9.0.10"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"typescript": "^5"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"prepare": "svelte-kit sync || echo ''",
|
"dev": "NODE_ENV=development bun --watch src/index.ts",
|
||||||
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
|
"db:gen": "prisma generate",
|
||||||
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
|
"db:push": "prisma migrate dev --skip-generate",
|
||||||
"test:unit": "vitest",
|
"db:deploy": "prisma migrate deploy",
|
||||||
"test": "bun run test:unit -- --run && bun run test:e2e",
|
"utils:dev": "docker compose -f .docker/docker-compose.yml up --build",
|
||||||
"test:e2e": "playwright test",
|
"utils:gen_private_keys": "bun ./utils/genPrivateKeys",
|
||||||
"start": "electron-forge start",
|
"utils:create_admin_role": "bun ./utils/createAdminRole",
|
||||||
"package": "electron-forge package",
|
"utils:assign_user_role": "bun ./utils/assignUserRole"
|
||||||
"make": "electron-forge make",
|
|
||||||
"make:macos": "electron-forge make --platform darwin",
|
|
||||||
"build:server": "vite build",
|
|
||||||
"publish": "electron-forge publish"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"@electron-forge/cli": "^7.11.1",
|
|
||||||
"@electron-forge/maker-deb": "^7.11.1",
|
|
||||||
"@electron-forge/maker-dmg": "^7.11.1",
|
|
||||||
"@electron-forge/maker-rpm": "^7.11.1",
|
|
||||||
"@electron-forge/maker-squirrel": "^7.11.1",
|
|
||||||
"@electron-forge/maker-zip": "^7.11.1",
|
|
||||||
"@electron-forge/plugin-auto-unpack-natives": "^7.11.1",
|
|
||||||
"@electron-forge/plugin-fuses": "^7.11.1",
|
|
||||||
"@electron-forge/plugin-vite": "^7.11.1",
|
|
||||||
"@electron/fuses": "^1.8.0",
|
|
||||||
"@playwright/test": "^1.58.0",
|
|
||||||
"@sveltejs/adapter-node": "^5.5.3",
|
|
||||||
"@sveltejs/adapter-static": "^3.0.10",
|
|
||||||
"@sveltejs/kit": "^2.50.1",
|
|
||||||
"@sveltejs/vite-plugin-svelte": "^5.1.1",
|
|
||||||
"@tailwindcss/forms": "^0.5.11",
|
|
||||||
"@tailwindcss/typography": "^0.5.19",
|
|
||||||
"@tailwindcss/vite": "^4.1.18",
|
|
||||||
"@testing-library/jest-dom": "^6.9.1",
|
|
||||||
"@testing-library/svelte": "^5.3.1",
|
|
||||||
"@types/electron-squirrel-startup": "^1.0.2",
|
|
||||||
"@types/node": "^22.19.7",
|
|
||||||
"electron": "^36.9.5",
|
|
||||||
"jsdom": "^26.1.0",
|
|
||||||
"svelte": "^5.48.2",
|
|
||||||
"svelte-check": "^4.3.5",
|
|
||||||
"tailwindcss": "^4.1.18",
|
|
||||||
"typescript": "^5.9.3",
|
|
||||||
"vite": "^6.4.1",
|
|
||||||
"vitest": "^3.2.4"
|
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@azure/msal-node": "^5.0.2",
|
||||||
|
"@discordjs/collection": "^2.1.1",
|
||||||
|
"@duxcore/eventra": "^1.1.0",
|
||||||
|
"@prisma/adapter-pg": "^7.3.0",
|
||||||
|
"@prisma/client": "^7.3.0",
|
||||||
|
"@socket.io/bun-engine": "^0.1.0",
|
||||||
"axios": "^1.13.3",
|
"axios": "^1.13.3",
|
||||||
"dotenv": "^17.2.3",
|
"blakets": "^0.1.12",
|
||||||
"electron-squirrel-startup": "^1.0.1",
|
"cors": "^2.8.6",
|
||||||
"socket.io-client": "^4.8.3"
|
"cuid": "^3.0.0",
|
||||||
},
|
"hono": "^4.11.5",
|
||||||
"trustedDependencies": [
|
"jsonwebtoken": "^9.0.3",
|
||||||
"esbuild",
|
"keypair": "^1.0.4",
|
||||||
"electron",
|
"prisma": "^7.3.0",
|
||||||
"electron-winstaller"
|
"socket.io": "^4.8.3",
|
||||||
],
|
"zod": "^4.3.6",
|
||||||
"patchedDependencies": {
|
"zon": "^1.0.3"
|
||||||
"@sveltejs/kit": "patches/@sveltejs__kit.patch"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,26 +0,0 @@
|
|||||||
diff --git a/src/exports/vite/index.js b/src/exports/vite/index.js
|
|
||||||
index e23cf2b833fc0a04287d34328b97bf64c7916f0d..81b6f30f3a5f2c8d3d0337777e141e549c4a5f98 100644
|
|
||||||
--- a/src/exports/vite/index.js
|
|
||||||
+++ b/src/exports/vite/index.js
|
|
||||||
@@ -473,7 +473,7 @@ Tips:
|
|
||||||
// for internal use only. it's published as $app/paths externally
|
|
||||||
// we use this alias so that we won't collide with user aliases
|
|
||||||
case sveltekit_paths: {
|
|
||||||
- const { assets, base } = svelte_config.kit.paths;
|
|
||||||
+ const { assets, base, relative } = svelte_config.kit.paths;
|
|
||||||
|
|
||||||
// use the values defined in `global`, but fall back to hard-coded values
|
|
||||||
// for the sake of things like Vitest which may import this module
|
|
||||||
@@ -488,10 +488,10 @@ Tips:
|
|
||||||
|
|
||||||
return dedent`
|
|
||||||
export let base = ${s(base)};
|
|
||||||
- export let assets = ${assets ? s(assets) : 'base'};
|
|
||||||
+ export let assets = ${relative ? "'.' + " : ''}${assets ? s(assets) : 'base'};
|
|
||||||
export const app_dir = ${s(kit.appDir)};
|
|
||||||
|
|
||||||
- export const relative = ${svelte_config.kit.paths.relative};
|
|
||||||
+ export const relative = ${relative};
|
|
||||||
|
|
||||||
const initial = { base, assets };
|
|
||||||
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
import { defineConfig } from '@playwright/test';
|
|
||||||
|
|
||||||
export default defineConfig({
|
|
||||||
webServer: {
|
|
||||||
command: 'npm run build && npm run preview',
|
|
||||||
port: 4173
|
|
||||||
},
|
|
||||||
testDir: 'e2e'
|
|
||||||
});
|
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
import 'dotenv/config'
|
||||||
|
import { defineConfig, env } from 'prisma/config'
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
schema: 'prisma/schema.prisma',
|
||||||
|
migrations: {
|
||||||
|
path: 'prisma/migrations',
|
||||||
|
},
|
||||||
|
datasource: {
|
||||||
|
url: env('DATABASE_URL'),
|
||||||
|
},
|
||||||
|
})
|
||||||
@@ -0,0 +1,76 @@
|
|||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "Session" (
|
||||||
|
"id" TEXT NOT NULL,
|
||||||
|
"sessionKey" TEXT NOT NULL,
|
||||||
|
"userId" TEXT NOT NULL,
|
||||||
|
"expires" TIMESTAMP(3) NOT NULL,
|
||||||
|
"refreshTokenGenerated" BOOLEAN NOT NULL DEFAULT false,
|
||||||
|
"refreshedAt" TIMESTAMP(3),
|
||||||
|
"invalidatedAt" TIMESTAMP(3),
|
||||||
|
|
||||||
|
CONSTRAINT "Session_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "User" (
|
||||||
|
"id" TEXT NOT NULL,
|
||||||
|
"permissions" TEXT,
|
||||||
|
"login" TEXT NOT NULL,
|
||||||
|
"name" TEXT,
|
||||||
|
"email" TEXT NOT NULL,
|
||||||
|
"emailVerified" TIMESTAMP(3),
|
||||||
|
"image" TEXT,
|
||||||
|
"userId" TEXT NOT NULL,
|
||||||
|
"token" TEXT,
|
||||||
|
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||||
|
|
||||||
|
CONSTRAINT "User_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "Role" (
|
||||||
|
"id" TEXT NOT NULL,
|
||||||
|
"title" TEXT NOT NULL,
|
||||||
|
"moniker" TEXT NOT NULL,
|
||||||
|
"permissions" TEXT NOT NULL,
|
||||||
|
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||||
|
|
||||||
|
CONSTRAINT "Role_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "_RoleToUser" (
|
||||||
|
"A" TEXT NOT NULL,
|
||||||
|
"B" TEXT NOT NULL,
|
||||||
|
|
||||||
|
CONSTRAINT "_RoleToUser_AB_pkey" PRIMARY KEY ("A","B")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE UNIQUE INDEX "Session_sessionKey_key" ON "Session"("sessionKey");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE UNIQUE INDEX "User_login_key" ON "User"("login");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE UNIQUE INDEX "User_email_key" ON "User"("email");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE UNIQUE INDEX "User_userId_key" ON "User"("userId");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE UNIQUE INDEX "Role_moniker_key" ON "Role"("moniker");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE INDEX "_RoleToUser_B_index" ON "_RoleToUser"("B");
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "Session" ADD CONSTRAINT "Session_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "_RoleToUser" ADD CONSTRAINT "_RoleToUser_A_fkey" FOREIGN KEY ("A") REFERENCES "Role"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "_RoleToUser" ADD CONSTRAINT "_RoleToUser_B_fkey" FOREIGN KEY ("B") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "Company" (
|
||||||
|
"id" TEXT NOT NULL,
|
||||||
|
"name" TEXT NOT NULL,
|
||||||
|
"cw_CompanyId" INTEGER NOT NULL,
|
||||||
|
"cw_Identifier" TEXT NOT NULL,
|
||||||
|
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||||
|
|
||||||
|
CONSTRAINT "Company_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "CredentialType" (
|
||||||
|
"id" TEXT NOT NULL,
|
||||||
|
"name" TEXT NOT NULL,
|
||||||
|
"permissionScope" TEXT NOT NULL,
|
||||||
|
"icon" TEXT,
|
||||||
|
"fields" JSONB NOT NULL,
|
||||||
|
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||||
|
|
||||||
|
CONSTRAINT "CredentialType_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "SecureValue" (
|
||||||
|
"id" TEXT NOT NULL,
|
||||||
|
"name" TEXT NOT NULL,
|
||||||
|
"content" TEXT NOT NULL,
|
||||||
|
"hash" TEXT NOT NULL,
|
||||||
|
"credentialId" TEXT NOT NULL,
|
||||||
|
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||||
|
|
||||||
|
CONSTRAINT "SecureValue_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "Credential" (
|
||||||
|
"id" TEXT NOT NULL,
|
||||||
|
"name" TEXT NOT NULL,
|
||||||
|
"notes" TEXT,
|
||||||
|
"subCredentialOfId" TEXT,
|
||||||
|
"typeId" TEXT NOT NULL,
|
||||||
|
"fields" JSONB NOT NULL,
|
||||||
|
"companyId" TEXT NOT NULL,
|
||||||
|
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||||
|
|
||||||
|
CONSTRAINT "Credential_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE UNIQUE INDEX "Company_cw_CompanyId_key" ON "Company"("cw_CompanyId");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE UNIQUE INDEX "Company_cw_Identifier_key" ON "Company"("cw_Identifier");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE UNIQUE INDEX "CredentialType_name_key" ON "CredentialType"("name");
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "SecureValue" ADD CONSTRAINT "SecureValue_credentialId_fkey" FOREIGN KEY ("credentialId") REFERENCES "Credential"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "Credential" ADD CONSTRAINT "Credential_subCredentialOfId_fkey" FOREIGN KEY ("subCredentialOfId") REFERENCES "Credential"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "Credential" ADD CONSTRAINT "Credential_typeId_fkey" FOREIGN KEY ("typeId") REFERENCES "CredentialType"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "Credential" ADD CONSTRAINT "Credential_companyId_fkey" FOREIGN KEY ("companyId") REFERENCES "Company"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
# Please do not edit this file manually
|
||||||
|
# It should be added in your version-control system (e.g., Git)
|
||||||
|
provider = "postgresql"
|
||||||
@@ -0,0 +1,166 @@
|
|||||||
|
// This is your Prisma schema file,
|
||||||
|
// learn more about it in the docs: https://pris.ly/d/prisma-schema
|
||||||
|
|
||||||
|
// Looking for ways to speed up your queries, or scale easily with your serverless or edge functions?
|
||||||
|
// Try Prisma Accelerate: https://pris.ly/cli/accelerate-init
|
||||||
|
|
||||||
|
generator client {
|
||||||
|
provider = "prisma-client"
|
||||||
|
output = "../generated/prisma"
|
||||||
|
}
|
||||||
|
|
||||||
|
datasource db {
|
||||||
|
provider = "postgresql"
|
||||||
|
}
|
||||||
|
|
||||||
|
model Session {
|
||||||
|
id String @id @default(uuid())
|
||||||
|
sessionKey String @unique @default(cuid())
|
||||||
|
userId String
|
||||||
|
expires DateTime
|
||||||
|
refreshTokenGenerated Boolean @default(false)
|
||||||
|
refreshedAt DateTime?
|
||||||
|
invalidatedAt DateTime?
|
||||||
|
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||||
|
}
|
||||||
|
|
||||||
|
model User {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
roles Role[]
|
||||||
|
permissions String?
|
||||||
|
login String @unique
|
||||||
|
name String?
|
||||||
|
email String @unique
|
||||||
|
emailVerified DateTime?
|
||||||
|
image String?
|
||||||
|
|
||||||
|
userId String @unique
|
||||||
|
token String?
|
||||||
|
|
||||||
|
sessions Session[]
|
||||||
|
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime @updatedAt
|
||||||
|
}
|
||||||
|
|
||||||
|
model Role {
|
||||||
|
id String @id @default(uuid())
|
||||||
|
title String
|
||||||
|
moniker String @unique // e.g. admin, super_admin, moderator
|
||||||
|
|
||||||
|
permissions String
|
||||||
|
users User[]
|
||||||
|
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime @updatedAt
|
||||||
|
}
|
||||||
|
|
||||||
|
model UnifiSite {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
name String
|
||||||
|
|
||||||
|
siteId String @unique
|
||||||
|
|
||||||
|
companyId String?
|
||||||
|
company Company? @relation(fields: [companyId], references: [id])
|
||||||
|
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime @updatedAt
|
||||||
|
}
|
||||||
|
|
||||||
|
model Company {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
name String
|
||||||
|
|
||||||
|
cw_CompanyId Int @unique
|
||||||
|
cw_Identifier String @unique
|
||||||
|
|
||||||
|
credentials Credential[]
|
||||||
|
unifiSites UnifiSite[]
|
||||||
|
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime @updatedAt
|
||||||
|
}
|
||||||
|
|
||||||
|
model CatalogItem {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
cwCatalogId Int @unique
|
||||||
|
name String
|
||||||
|
description String?
|
||||||
|
customerDescription String?
|
||||||
|
internalNotes String?
|
||||||
|
|
||||||
|
linkedItems CatalogItem[] @relation("LinkedItems")
|
||||||
|
linkedTo CatalogItem[] @relation("LinkedItems")
|
||||||
|
|
||||||
|
manufacturer String?
|
||||||
|
manufactureCwId Int?
|
||||||
|
|
||||||
|
partNumber String?
|
||||||
|
|
||||||
|
vendorName String?
|
||||||
|
vendorSku String?
|
||||||
|
vendorCwId Int?
|
||||||
|
|
||||||
|
price Float
|
||||||
|
cost Float
|
||||||
|
|
||||||
|
inactive Boolean @default(false)
|
||||||
|
salesTaxable Boolean @default(true)
|
||||||
|
|
||||||
|
onHand Int @default(0)
|
||||||
|
cwLastUpdated DateTime?
|
||||||
|
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime @updatedAt
|
||||||
|
}
|
||||||
|
|
||||||
|
model CredentialType {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
name String @unique
|
||||||
|
|
||||||
|
permissionScope String
|
||||||
|
icon String?
|
||||||
|
fields Json
|
||||||
|
|
||||||
|
credentials Credential[]
|
||||||
|
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime @updatedAt
|
||||||
|
}
|
||||||
|
|
||||||
|
model SecureValue {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
name String
|
||||||
|
|
||||||
|
content String // Encrypted content
|
||||||
|
hash String // Hash of the original content for integrity verification and Search
|
||||||
|
|
||||||
|
credentialId String
|
||||||
|
credential Credential @relation(fields: [credentialId], references: [id], onDelete: Cascade)
|
||||||
|
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime @updatedAt
|
||||||
|
}
|
||||||
|
|
||||||
|
model Credential {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
name String
|
||||||
|
notes String?
|
||||||
|
subCredentialOfId String?
|
||||||
|
subCredentialOf Credential? @relation("SubCredentials", fields: [subCredentialOfId], references: [id], onDelete: Cascade)
|
||||||
|
subCredentials Credential[] @relation("SubCredentials")
|
||||||
|
|
||||||
|
typeId String
|
||||||
|
type CredentialType @relation(fields: [typeId], references: [id], onDelete: Cascade)
|
||||||
|
|
||||||
|
fields Json
|
||||||
|
|
||||||
|
companyId String
|
||||||
|
company Company @relation(fields: [companyId], references: [id], onDelete: Cascade)
|
||||||
|
|
||||||
|
securevalues SecureValue[]
|
||||||
|
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
updatedAt DateTime @updatedAt
|
||||||
|
}
|
||||||
Vendored
BIN
Binary file not shown.
@@ -0,0 +1,8 @@
|
|||||||
|
export default class AuthenticationError extends Error {
|
||||||
|
constructor(message: string, cause?: string) {
|
||||||
|
super();
|
||||||
|
this.name = "AuthenticationError";
|
||||||
|
this.message = message;
|
||||||
|
this.cause = cause;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
export default class AuthorizationError extends Error {
|
||||||
|
public status: number;
|
||||||
|
|
||||||
|
constructor(message: string, cause?: string, status?: number) {
|
||||||
|
super();
|
||||||
|
this.name = "AuthorizationError";
|
||||||
|
this.status = status ?? 401;
|
||||||
|
this.message = message;
|
||||||
|
this.cause = cause;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
export default class BodyError extends Error {
|
||||||
|
constructor(message: string, cause?: string) {
|
||||||
|
super();
|
||||||
|
this.name = "BodyError";
|
||||||
|
this.message = message;
|
||||||
|
this.cause = cause;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
export default class ExpiredAccessTokenError extends Error {
|
||||||
|
constructor(cause?: string) {
|
||||||
|
super();
|
||||||
|
this.name = "ExpiredAccessTokenError";
|
||||||
|
this.message = "The provided access token has expired.";
|
||||||
|
this.cause = cause;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
export default class ExpiredRefreshTokenError extends Error {
|
||||||
|
constructor(cause?: string) {
|
||||||
|
super();
|
||||||
|
this.name = "ExpiredRefreshTokenError";
|
||||||
|
this.message = "The provided refresh token has expired.";
|
||||||
|
this.cause = cause;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
export default class GenericError extends Error {
|
||||||
|
public status: number;
|
||||||
|
|
||||||
|
constructor(info: {
|
||||||
|
name: string;
|
||||||
|
message: string;
|
||||||
|
cause?: string;
|
||||||
|
status?: number;
|
||||||
|
}) {
|
||||||
|
super();
|
||||||
|
this.name = info.name;
|
||||||
|
this.status = info.status ?? 400;
|
||||||
|
this.message = info.message;
|
||||||
|
this.cause = info.cause;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
export default class InsufficientPermission extends Error {
|
||||||
|
public status: number;
|
||||||
|
|
||||||
|
constructor(message: string, cause?: string) {
|
||||||
|
super();
|
||||||
|
this.name = "InsufficientPermission";
|
||||||
|
this.status = 403;
|
||||||
|
this.message = message;
|
||||||
|
this.cause = cause;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
export default class MissingBodyValue extends Error {
|
||||||
|
constructor(valueName: string) {
|
||||||
|
super();
|
||||||
|
this.name = "MissingBodyValue";
|
||||||
|
this.message = `Value '${valueName}' is missing from the body.`;
|
||||||
|
this.cause =
|
||||||
|
"A value that was required by the body of this request is missing.";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
export default class PermissionsVerificationError extends Error {
|
||||||
|
constructor(message: string, cause?: string) {
|
||||||
|
super();
|
||||||
|
this.name = "PermissionsVerificationError";
|
||||||
|
this.message = message;
|
||||||
|
this.cause = cause;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
export default class RoleError extends Error {
|
||||||
|
constructor(message: string, cause?: string) {
|
||||||
|
super();
|
||||||
|
this.name = "RoleError";
|
||||||
|
this.message = message;
|
||||||
|
this.cause = cause;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
export default class SessionError extends Error {
|
||||||
|
constructor(message: string, cause?: string) {
|
||||||
|
super();
|
||||||
|
this.name = "SessionError";
|
||||||
|
this.message = message;
|
||||||
|
this.cause = cause;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
export default class SessionTokenError extends Error {
|
||||||
|
constructor(message: string, cause?: string) {
|
||||||
|
super();
|
||||||
|
this.name = "SessionTokenError";
|
||||||
|
this.message = message;
|
||||||
|
this.cause = cause;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
export default class UserError extends Error {
|
||||||
|
constructor(message: string, cause?: string) {
|
||||||
|
super();
|
||||||
|
this.name = "UserError";
|
||||||
|
this.message = message;
|
||||||
|
this.cause = cause;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
export { default as redirect } from "./redirect";
|
||||||
|
export { default as refresh } from "./refresh";
|
||||||
|
export { default as uri } from "./uri";
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
import { Hono } from "hono/tiny";
|
||||||
|
import { createRoute } from "../../modules/api-utils/createRoute";
|
||||||
|
import * as msal from "@azure/msal-node";
|
||||||
|
import { io, msalClient } from "../../constants";
|
||||||
|
import { users } from "../../managers/users";
|
||||||
|
|
||||||
|
/* /v1/auth/redirect */
|
||||||
|
export default createRoute("get", ["/redirect"], async (c) => {
|
||||||
|
c.status(200);
|
||||||
|
|
||||||
|
const tokenRequest: msal.AuthorizationCodeRequest = {
|
||||||
|
code: c.req.query().code as string,
|
||||||
|
scopes: ["user.read"],
|
||||||
|
redirectUri: "http://localhost:3000/v1/auth/redirect",
|
||||||
|
};
|
||||||
|
|
||||||
|
const authResult = await msalClient.acquireTokenByCode(tokenRequest);
|
||||||
|
const callbackKey = c.req.query().state as string;
|
||||||
|
const tokens = await users.authenticate(authResult);
|
||||||
|
|
||||||
|
io.of(`/auth_callback`).emit(`auth:login:callback:${callbackKey}`, {
|
||||||
|
accessToken: tokens.accessToken,
|
||||||
|
refreshToken: tokens.refreshToken,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Close the window because duh
|
||||||
|
return c.html(
|
||||||
|
`<script>
|
||||||
|
window.close();
|
||||||
|
</script>`,
|
||||||
|
);
|
||||||
|
|
||||||
|
return c.json({
|
||||||
|
status: 200,
|
||||||
|
message: "Auth Redirect Endpoint",
|
||||||
|
data: authResult,
|
||||||
|
successful: true,
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
import { Hono } from "hono/tiny";
|
||||||
|
import { createRoute } from "../../modules/api-utils/createRoute";
|
||||||
|
import { sessions } from "../../managers/sessions";
|
||||||
|
|
||||||
|
/* /v1/auth/refresh */
|
||||||
|
export default createRoute("post", ["/refresh"], async (c) => {
|
||||||
|
c.status(201);
|
||||||
|
|
||||||
|
const refreshToken = c.req.header("x-refresh-token") || "";
|
||||||
|
|
||||||
|
const session = await sessions.fetch({
|
||||||
|
refreshToken: refreshToken,
|
||||||
|
});
|
||||||
|
|
||||||
|
const newAccessToken = await session.refresh(refreshToken);
|
||||||
|
|
||||||
|
return c.json({
|
||||||
|
status: 201,
|
||||||
|
message: "Token refreshed successfully!",
|
||||||
|
data: {
|
||||||
|
accessToken: newAccessToken,
|
||||||
|
refreshToken,
|
||||||
|
},
|
||||||
|
successful: true,
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
import { Hono } from "hono/tiny";
|
||||||
|
import { createRoute } from "../../modules/api-utils/createRoute";
|
||||||
|
import cuid from "cuid";
|
||||||
|
|
||||||
|
/* /v1/auth/uri */
|
||||||
|
export default createRoute("get", ["/uri"], (c) => {
|
||||||
|
c.status(200);
|
||||||
|
|
||||||
|
const callbackKey = cuid();
|
||||||
|
const msUri = `https://login.microsoftonline.com/${process.env.MICROSOFT_TENANT_ID}/oauth2/v2.0/authorize?client_id=${process.env.MICROSOFT_CLIENT_ID}&response_type=code&redirect_uri=http%3A%2F%2Flocalhost%3A3000%2Fv1%2Fauth%2Fredirect&scope=openid+User.Read&state=${callbackKey}&prompt=login`;
|
||||||
|
|
||||||
|
return c.json({
|
||||||
|
status: 200,
|
||||||
|
message: "Successfully fetch Auth URI",
|
||||||
|
data: {
|
||||||
|
uri: msUri,
|
||||||
|
callbackKey: callbackKey,
|
||||||
|
},
|
||||||
|
successful: true,
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
import { Hono } from "hono/tiny";
|
||||||
|
import { createRoute } from "../../../modules/api-utils/createRoute";
|
||||||
|
import { companies } from "../../../managers/companies";
|
||||||
|
import { apiResponse } from "../../../modules/api-utils/apiResponse";
|
||||||
|
import { ContentfulStatusCode } from "hono/utils/http-status";
|
||||||
|
import { authMiddleware } from "../../middleware/authorization";
|
||||||
|
|
||||||
|
/* /v1/company/companies/[id]/configurations */
|
||||||
|
export default createRoute(
|
||||||
|
"get",
|
||||||
|
["/companies/:identifier/configurations"],
|
||||||
|
|
||||||
|
async (c) => {
|
||||||
|
const company = await companies.fetch(c.req.param("identifier"));
|
||||||
|
const configurations = await company.fetchConfigurations();
|
||||||
|
|
||||||
|
const response = apiResponse.successful(
|
||||||
|
"Company Configurations Fetched Successfully!",
|
||||||
|
configurations,
|
||||||
|
);
|
||||||
|
return c.json(response, response.status as ContentfulStatusCode);
|
||||||
|
},
|
||||||
|
authMiddleware({
|
||||||
|
permissions: ["company.fetch", "company.fetch.configurations"],
|
||||||
|
}),
|
||||||
|
);
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
import { Hono } from "hono/tiny";
|
||||||
|
import { createRoute } from "../../../modules/api-utils/createRoute";
|
||||||
|
import { companies } from "../../../managers/companies";
|
||||||
|
import { apiResponse } from "../../../modules/api-utils/apiResponse";
|
||||||
|
import { ContentfulStatusCode } from "hono/utils/http-status";
|
||||||
|
import { authMiddleware } from "../../middleware/authorization";
|
||||||
|
import GenericError from "../../../Errors/GenericError";
|
||||||
|
|
||||||
|
/* /v1/company/companies/[id] */
|
||||||
|
export default createRoute(
|
||||||
|
"get",
|
||||||
|
["/companies/:identifier"],
|
||||||
|
|
||||||
|
async (c) => {
|
||||||
|
const company = await companies.fetch(c.req.param("identifier"));
|
||||||
|
const includeAddress = c.req.query("includeAddress") === "true";
|
||||||
|
const includePrimaryContact =
|
||||||
|
c.req.query("includePrimaryContact") === "true";
|
||||||
|
const includeAllContacts = c.req.query("includeAllContacts") === "true";
|
||||||
|
|
||||||
|
// Check for address-specific permission if includeAddress is requested
|
||||||
|
if (includeAddress) {
|
||||||
|
const user = c.get("user");
|
||||||
|
if (!user || !(await user.hasPermission("company.fetch.address"))) {
|
||||||
|
throw new GenericError({
|
||||||
|
name: "InsufficientPermission",
|
||||||
|
message: "You do not have permission to view company addresses.",
|
||||||
|
status: 403,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for contacts permission if includeAllContacts is requested
|
||||||
|
if (includeAllContacts) {
|
||||||
|
const user = c.get("user");
|
||||||
|
if (!user || !(await user.hasPermission("company.fetch.contacts"))) {
|
||||||
|
throw new GenericError({
|
||||||
|
name: "InsufficientPermission",
|
||||||
|
message: "You do not have permission to view company contacts.",
|
||||||
|
status: 403,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = apiResponse.successful(
|
||||||
|
"Company Fetched Successfully!",
|
||||||
|
company.toJson({
|
||||||
|
includeAddress,
|
||||||
|
includePrimaryContact,
|
||||||
|
includeAllContacts,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
return c.json(response, response.status as ContentfulStatusCode);
|
||||||
|
},
|
||||||
|
authMiddleware({ permissions: ["company.fetch"] }),
|
||||||
|
);
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
import { createRoute } from "../../../modules/api-utils/createRoute";
|
||||||
|
import { unifiSites } from "../../../managers/unifiSites";
|
||||||
|
import { companies } from "../../../managers/companies";
|
||||||
|
import { apiResponse } from "../../../modules/api-utils/apiResponse";
|
||||||
|
import { ContentfulStatusCode } from "hono/utils/http-status";
|
||||||
|
import { authMiddleware } from "../../middleware/authorization";
|
||||||
|
|
||||||
|
/* GET /v1/company/companies/:identifier/unifi/sites */
|
||||||
|
export default createRoute(
|
||||||
|
"get",
|
||||||
|
["/companies/:identifier/unifi/sites"],
|
||||||
|
async (c) => {
|
||||||
|
const company = await companies.fetch(c.req.param("identifier"));
|
||||||
|
const sites = await unifiSites.fetchByCompany(company.id);
|
||||||
|
const response = apiResponse.successful(
|
||||||
|
"Company UniFi Sites Fetched Successfully!",
|
||||||
|
sites,
|
||||||
|
);
|
||||||
|
return c.json(response, response.status as ContentfulStatusCode);
|
||||||
|
},
|
||||||
|
authMiddleware({
|
||||||
|
permissions: ["unifi.access", "company.fetch"],
|
||||||
|
}),
|
||||||
|
);
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
import { Hono } from "hono/tiny";
|
||||||
|
import { createRoute } from "../../modules/api-utils/createRoute";
|
||||||
|
import { companies } from "../../managers/companies";
|
||||||
|
import { apiResponse } from "../../modules/api-utils/apiResponse";
|
||||||
|
import { ContentfulStatusCode } from "hono/utils/http-status";
|
||||||
|
import { authMiddleware } from "../middleware/authorization";
|
||||||
|
|
||||||
|
/* /v1/company/count */
|
||||||
|
export default createRoute(
|
||||||
|
"get",
|
||||||
|
["/count"],
|
||||||
|
async (c) => {
|
||||||
|
const count = await companies.count();
|
||||||
|
|
||||||
|
const response = apiResponse.successful(
|
||||||
|
"Company count fetched successfully!",
|
||||||
|
{
|
||||||
|
count,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
return c.json(response, response.status as ContentfulStatusCode);
|
||||||
|
},
|
||||||
|
authMiddleware({ permissions: ["company.fetch.many"] }),
|
||||||
|
);
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
import { Hono } from "hono/tiny";
|
||||||
|
import { createRoute } from "../../modules/api-utils/createRoute";
|
||||||
|
import { companies } from "../../managers/companies";
|
||||||
|
import { apiResponse } from "../../modules/api-utils/apiResponse";
|
||||||
|
import { ContentfulStatusCode } from "hono/utils/http-status";
|
||||||
|
import { authMiddleware } from "../middleware/authorization";
|
||||||
|
|
||||||
|
/* /v1/company/companies */
|
||||||
|
export default createRoute(
|
||||||
|
"get",
|
||||||
|
["/companies"],
|
||||||
|
async (c) => {
|
||||||
|
const page = new Number(c.req.query("page") ?? 1) as number;
|
||||||
|
const rpp = new Number(c.req.query("rpp") ?? 30) as number; // Records Per Page
|
||||||
|
const search = c.req.query("search") as string;
|
||||||
|
|
||||||
|
const data = search
|
||||||
|
? await companies.search(search, page, rpp)
|
||||||
|
: await companies.fetchPages(page, rpp);
|
||||||
|
|
||||||
|
const companyQty = search
|
||||||
|
? (await companies.search(search, 1, 999999)).length
|
||||||
|
: await companies.count();
|
||||||
|
|
||||||
|
let response = apiResponse.successful(
|
||||||
|
"Companies Fetched Successfully!",
|
||||||
|
data,
|
||||||
|
{
|
||||||
|
pagination: {
|
||||||
|
previousPage: page == 1 ? null : page - 1, // Previous Page
|
||||||
|
currentPage: page, // Current Page
|
||||||
|
nextPage: page >= companyQty / rpp ? null : page + 1, // Next Page
|
||||||
|
totalPages: Math.ceil(companyQty / rpp), // Total Number of Pages
|
||||||
|
totalRecords: companyQty, // Total Number of Records
|
||||||
|
listedRecords: rpp, // Total Number of Records being recieved at time of request.
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
return c.json(response, response.status as ContentfulStatusCode);
|
||||||
|
},
|
||||||
|
authMiddleware({ permissions: ["company.fetch.many"] }),
|
||||||
|
);
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
import { default as fetchAll } from "./fetchAll";
|
||||||
|
import { default as fetch } from "./[id]/fetch";
|
||||||
|
import { default as configurations } from "./[id]/configurations";
|
||||||
|
import { default as unifiSites } from "./[id]/unifiSites";
|
||||||
|
import { default as count } from "./count";
|
||||||
|
|
||||||
|
export { configurations, count, fetch, fetchAll, unifiSites };
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
import { Hono } from "hono/tiny";
|
||||||
|
import { createRoute } from "../../modules/api-utils/createRoute";
|
||||||
|
import { credentialTypes } from "../../managers/credentialTypes";
|
||||||
|
import { apiResponse } from "../../modules/api-utils/apiResponse";
|
||||||
|
import { ContentfulStatusCode } from "hono/utils/http-status";
|
||||||
|
import { authMiddleware } from "../middleware/authorization";
|
||||||
|
import { z } from "zod";
|
||||||
|
import { ValueType } from "../../modules/credentials/credentialTypeDefs";
|
||||||
|
|
||||||
|
/* /v1/credential-type */
|
||||||
|
export default createRoute(
|
||||||
|
"post",
|
||||||
|
["/"],
|
||||||
|
|
||||||
|
async (c) => {
|
||||||
|
const body = await c.req.json();
|
||||||
|
|
||||||
|
const fieldSchema: z.ZodType<any> = z.lazy(() =>
|
||||||
|
z.object({
|
||||||
|
id: z.string(),
|
||||||
|
name: z.string(),
|
||||||
|
required: z.boolean(),
|
||||||
|
secure: z.boolean(),
|
||||||
|
valueType: z.enum(Object.values(ValueType)),
|
||||||
|
subFields: z.array(fieldSchema).optional(),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
const schema = z.object({
|
||||||
|
name: z.string().min(1, "Name is required"),
|
||||||
|
permissionScope: z.string().min(1, "Permission scope is required"),
|
||||||
|
icon: z.string().optional(),
|
||||||
|
fields: z.array(fieldSchema),
|
||||||
|
});
|
||||||
|
|
||||||
|
const data = schema.parse(body);
|
||||||
|
|
||||||
|
console.log("Creating Credential Type with data:", data);
|
||||||
|
|
||||||
|
const credentialType = await credentialTypes.create(data as any);
|
||||||
|
|
||||||
|
const response = apiResponse.created(
|
||||||
|
"Credential Type Created Successfully!",
|
||||||
|
credentialType.toJson(),
|
||||||
|
);
|
||||||
|
return c.json(response, response.status as ContentfulStatusCode);
|
||||||
|
},
|
||||||
|
authMiddleware({ permissions: ["credential_type.create"] }),
|
||||||
|
);
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
import { Hono } from "hono/tiny";
|
||||||
|
import { createRoute } from "../../modules/api-utils/createRoute";
|
||||||
|
import { credentialTypes } from "../../managers/credentialTypes";
|
||||||
|
import { apiResponse } from "../../modules/api-utils/apiResponse";
|
||||||
|
import { ContentfulStatusCode } from "hono/utils/http-status";
|
||||||
|
import { authMiddleware } from "../middleware/authorization";
|
||||||
|
|
||||||
|
/* /v1/credential-type/:id */
|
||||||
|
export default createRoute(
|
||||||
|
"delete",
|
||||||
|
["/:id"],
|
||||||
|
|
||||||
|
async (c) => {
|
||||||
|
await credentialTypes.delete(c.req.param("id"));
|
||||||
|
|
||||||
|
const response = apiResponse.successful(
|
||||||
|
"Credential Type Deleted Successfully!",
|
||||||
|
null,
|
||||||
|
);
|
||||||
|
return c.json(response, response.status as ContentfulStatusCode);
|
||||||
|
},
|
||||||
|
authMiddleware({ permissions: ["credential_type.delete"] }),
|
||||||
|
);
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
import { Hono } from "hono/tiny";
|
||||||
|
import { createRoute } from "../../modules/api-utils/createRoute";
|
||||||
|
import { credentialTypes } from "../../managers/credentialTypes";
|
||||||
|
import { apiResponse } from "../../modules/api-utils/apiResponse";
|
||||||
|
import { ContentfulStatusCode } from "hono/utils/http-status";
|
||||||
|
import { authMiddleware } from "../middleware/authorization";
|
||||||
|
|
||||||
|
/* /v1/credential-type/:identifier */
|
||||||
|
export default createRoute(
|
||||||
|
"get",
|
||||||
|
["/:identifier"],
|
||||||
|
|
||||||
|
async (c) => {
|
||||||
|
const credentialType = await credentialTypes.fetch(
|
||||||
|
c.req.param("identifier"),
|
||||||
|
);
|
||||||
|
|
||||||
|
const response = apiResponse.successful(
|
||||||
|
"Credential Type Fetched Successfully!",
|
||||||
|
credentialType.toJson({ includeCredentialCount: true }),
|
||||||
|
);
|
||||||
|
return c.json(response, response.status as ContentfulStatusCode);
|
||||||
|
},
|
||||||
|
authMiddleware({ permissions: ["credential_type.fetch"] }),
|
||||||
|
);
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
import { Hono } from "hono/tiny";
|
||||||
|
import { createRoute } from "../../modules/api-utils/createRoute";
|
||||||
|
import { credentialTypes } from "../../managers/credentialTypes";
|
||||||
|
import { apiResponse } from "../../modules/api-utils/apiResponse";
|
||||||
|
import { ContentfulStatusCode } from "hono/utils/http-status";
|
||||||
|
import { authMiddleware } from "../middleware/authorization";
|
||||||
|
|
||||||
|
/* /v1/credential-type */
|
||||||
|
export default createRoute(
|
||||||
|
"get",
|
||||||
|
["/"],
|
||||||
|
|
||||||
|
async (c) => {
|
||||||
|
const allCredentialTypes = await credentialTypes.fetchAll();
|
||||||
|
|
||||||
|
const response = apiResponse.successful(
|
||||||
|
"Credential Types Fetched Successfully!",
|
||||||
|
allCredentialTypes.map((ct) =>
|
||||||
|
ct.toJson({ includeCredentialCount: true }),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
return c.json(response, response.status as ContentfulStatusCode);
|
||||||
|
},
|
||||||
|
authMiddleware({ permissions: ["credential_type.fetch.many"] }),
|
||||||
|
);
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
import { Hono } from "hono/tiny";
|
||||||
|
import { createRoute } from "../../modules/api-utils/createRoute";
|
||||||
|
import { credentialTypes } from "../../managers/credentialTypes";
|
||||||
|
import { apiResponse } from "../../modules/api-utils/apiResponse";
|
||||||
|
import { ContentfulStatusCode } from "hono/utils/http-status";
|
||||||
|
import { authMiddleware } from "../middleware/authorization";
|
||||||
|
|
||||||
|
/* /v1/credential-type/:id/credentials */
|
||||||
|
export default createRoute(
|
||||||
|
"get",
|
||||||
|
["/:id/credentials"],
|
||||||
|
|
||||||
|
async (c) => {
|
||||||
|
const credentialType = await credentialTypes.fetch(c.req.param("id"));
|
||||||
|
const credentials = await credentialType.fetchCredentials();
|
||||||
|
|
||||||
|
const response = apiResponse.successful(
|
||||||
|
"Credentials Fetched Successfully!",
|
||||||
|
credentials.map((cred) => cred.toJson()),
|
||||||
|
);
|
||||||
|
return c.json(response, response.status as ContentfulStatusCode);
|
||||||
|
},
|
||||||
|
authMiddleware({
|
||||||
|
permissions: ["credential_type.fetch", "credential.fetch.many"],
|
||||||
|
}),
|
||||||
|
);
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
import { default as fetch } from "./fetch";
|
||||||
|
import { default as fetchAll } from "./fetchAll";
|
||||||
|
import { default as create } from "./create";
|
||||||
|
import { default as update } from "./update";
|
||||||
|
import { default as deleteCredentialType } from "./delete";
|
||||||
|
import { default as fetchCredentials } from "./fetchCredentials";
|
||||||
|
|
||||||
|
export {
|
||||||
|
fetch,
|
||||||
|
fetchAll,
|
||||||
|
create,
|
||||||
|
update,
|
||||||
|
deleteCredentialType as delete,
|
||||||
|
fetchCredentials,
|
||||||
|
};
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
import { Hono } from "hono/tiny";
|
||||||
|
import { createRoute } from "../../modules/api-utils/createRoute";
|
||||||
|
import { credentialTypes } from "../../managers/credentialTypes";
|
||||||
|
import { apiResponse } from "../../modules/api-utils/apiResponse";
|
||||||
|
import { ContentfulStatusCode } from "hono/utils/http-status";
|
||||||
|
import { authMiddleware } from "../middleware/authorization";
|
||||||
|
import { z } from "zod";
|
||||||
|
import { ValueType } from "../../modules/credentials/credentialTypeDefs";
|
||||||
|
|
||||||
|
/* /v1/credential-type/:id */
|
||||||
|
export default createRoute(
|
||||||
|
"patch",
|
||||||
|
["/:id"],
|
||||||
|
|
||||||
|
async (c) => {
|
||||||
|
const body = await c.req.json();
|
||||||
|
const credentialType = await credentialTypes.fetch(c.req.param("id"));
|
||||||
|
|
||||||
|
const fieldSchema: z.ZodType<any> = z.lazy(() =>
|
||||||
|
z.object({
|
||||||
|
id: z.string(),
|
||||||
|
name: z.string(),
|
||||||
|
required: z.boolean(),
|
||||||
|
secure: z.boolean(),
|
||||||
|
valueType: z.enum(Object.values(ValueType)),
|
||||||
|
subFields: z.array(fieldSchema).optional(),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
const schema = z.object({
|
||||||
|
name: z.string().optional(),
|
||||||
|
permissionScope: z.string().optional(),
|
||||||
|
icon: z.string().optional(),
|
||||||
|
fields: z.array(fieldSchema).optional(),
|
||||||
|
});
|
||||||
|
|
||||||
|
const data = schema.parse(body);
|
||||||
|
|
||||||
|
await credentialType.update(data as any);
|
||||||
|
|
||||||
|
const response = apiResponse.successful(
|
||||||
|
"Credential Type Updated Successfully!",
|
||||||
|
credentialType.toJson(),
|
||||||
|
);
|
||||||
|
return c.json(response, response.status as ContentfulStatusCode);
|
||||||
|
},
|
||||||
|
authMiddleware({ permissions: ["credential_type.update"] }),
|
||||||
|
);
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
import { createRoute } from "../../modules/api-utils/createRoute";
|
||||||
|
import { credentials } from "../../managers/credentials";
|
||||||
|
import { apiResponse } from "../../modules/api-utils/apiResponse";
|
||||||
|
import { ContentfulStatusCode } from "hono/utils/http-status";
|
||||||
|
import { authMiddleware } from "../middleware/authorization";
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
/* POST /v1/credential/credentials/:id/sub-credentials */
|
||||||
|
export default createRoute(
|
||||||
|
"post",
|
||||||
|
["/credentials/:id/sub-credentials"],
|
||||||
|
|
||||||
|
async (c) => {
|
||||||
|
const parentId = c.req.param("id");
|
||||||
|
const body = await c.req.json();
|
||||||
|
|
||||||
|
const schema = z.object({
|
||||||
|
fieldId: z.string().min(1, "Field ID is required"),
|
||||||
|
name: z.string().min(1, "Name is required"),
|
||||||
|
fields: z.array(
|
||||||
|
z.object({
|
||||||
|
fieldId: z.string(),
|
||||||
|
value: z.string(),
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
});
|
||||||
|
|
||||||
|
const data = schema.parse(body);
|
||||||
|
|
||||||
|
const subCredential = await credentials.addSubCredential(
|
||||||
|
parentId,
|
||||||
|
data.fieldId,
|
||||||
|
{
|
||||||
|
name: data.name,
|
||||||
|
fields: data.fields,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
const response = apiResponse.created(
|
||||||
|
"Sub-Credential Created Successfully!",
|
||||||
|
subCredential.toJson(),
|
||||||
|
);
|
||||||
|
return c.json(response, response.status as ContentfulStatusCode);
|
||||||
|
},
|
||||||
|
authMiddleware({
|
||||||
|
permissions: ["credential.fetch", "credential.sub_credentials.create"],
|
||||||
|
}),
|
||||||
|
);
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
import { Hono } from "hono/tiny";
|
||||||
|
import { createRoute } from "../../modules/api-utils/createRoute";
|
||||||
|
import { credentials } from "../../managers/credentials";
|
||||||
|
import { apiResponse } from "../../modules/api-utils/apiResponse";
|
||||||
|
import { ContentfulStatusCode } from "hono/utils/http-status";
|
||||||
|
import { authMiddleware } from "../middleware/authorization";
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
/* /v1/credential */
|
||||||
|
export default createRoute(
|
||||||
|
"post",
|
||||||
|
["/credentials"],
|
||||||
|
|
||||||
|
async (c) => {
|
||||||
|
const body = await c.req.json();
|
||||||
|
|
||||||
|
const schema = z.object({
|
||||||
|
name: z.string().min(1, "Name is required"),
|
||||||
|
notes: z.string().optional(),
|
||||||
|
typeId: z.string().min(1, "Type ID is required"),
|
||||||
|
companyId: z.string().min(1, "Company ID is required"),
|
||||||
|
fields: z.array(
|
||||||
|
z.object({
|
||||||
|
fieldId: z.string(),
|
||||||
|
value: z.string(),
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
subCredentials: z
|
||||||
|
.record(
|
||||||
|
z.string(),
|
||||||
|
z.array(
|
||||||
|
z.object({
|
||||||
|
name: z.string().min(1, "Sub-credential name is required"),
|
||||||
|
fields: z.array(
|
||||||
|
z.object({
|
||||||
|
fieldId: z.string(),
|
||||||
|
value: z.string(),
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.optional(),
|
||||||
|
});
|
||||||
|
|
||||||
|
const data = schema.parse(body);
|
||||||
|
|
||||||
|
const credential = await credentials.create(data);
|
||||||
|
|
||||||
|
const response = apiResponse.created(
|
||||||
|
"Credential Created Successfully!",
|
||||||
|
credential.toJson(),
|
||||||
|
);
|
||||||
|
return c.json(response, response.status as ContentfulStatusCode);
|
||||||
|
},
|
||||||
|
authMiddleware({ permissions: ["credential.create"] }),
|
||||||
|
);
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
import { Hono } from "hono/tiny";
|
||||||
|
import { createRoute } from "../../modules/api-utils/createRoute";
|
||||||
|
import { credentials } from "../../managers/credentials";
|
||||||
|
import { apiResponse } from "../../modules/api-utils/apiResponse";
|
||||||
|
import { ContentfulStatusCode } from "hono/utils/http-status";
|
||||||
|
import { authMiddleware } from "../middleware/authorization";
|
||||||
|
|
||||||
|
/* /v1/credential/:id */
|
||||||
|
export default createRoute(
|
||||||
|
"delete",
|
||||||
|
["/credentials/:id"],
|
||||||
|
|
||||||
|
async (c) => {
|
||||||
|
await credentials.delete(c.req.param("id"));
|
||||||
|
|
||||||
|
const response = apiResponse.successful(
|
||||||
|
"Credential Deleted Successfully!",
|
||||||
|
null,
|
||||||
|
);
|
||||||
|
return c.json(response, response.status as ContentfulStatusCode);
|
||||||
|
},
|
||||||
|
authMiddleware({ permissions: ["credential.delete"] }),
|
||||||
|
);
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
import { Hono } from "hono/tiny";
|
||||||
|
import { createRoute } from "../../modules/api-utils/createRoute";
|
||||||
|
import { credentials } from "../../managers/credentials";
|
||||||
|
import { apiResponse } from "../../modules/api-utils/apiResponse";
|
||||||
|
import { ContentfulStatusCode } from "hono/utils/http-status";
|
||||||
|
import { authMiddleware } from "../middleware/authorization";
|
||||||
|
|
||||||
|
/* /v1/credential/:id */
|
||||||
|
export default createRoute(
|
||||||
|
"get",
|
||||||
|
["/credentials/:id"],
|
||||||
|
|
||||||
|
async (c) => {
|
||||||
|
const credential = await credentials.fetch(c.req.param("id"));
|
||||||
|
|
||||||
|
const response = apiResponse.successful(
|
||||||
|
"Credential Fetched Successfully!",
|
||||||
|
credential.toJson(),
|
||||||
|
);
|
||||||
|
return c.json(response, response.status as ContentfulStatusCode);
|
||||||
|
},
|
||||||
|
authMiddleware({ permissions: ["credential.fetch"] }),
|
||||||
|
);
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
import { Hono } from "hono/tiny";
|
||||||
|
import { createRoute } from "../../modules/api-utils/createRoute";
|
||||||
|
import { credentials } from "../../managers/credentials";
|
||||||
|
import { apiResponse } from "../../modules/api-utils/apiResponse";
|
||||||
|
import { ContentfulStatusCode } from "hono/utils/http-status";
|
||||||
|
import { authMiddleware } from "../middleware/authorization";
|
||||||
|
|
||||||
|
/* /v1/credential/company/:companyId */
|
||||||
|
export default createRoute(
|
||||||
|
"get",
|
||||||
|
["/credentials/company/:companyId"],
|
||||||
|
|
||||||
|
async (c) => {
|
||||||
|
const companyCredentials = await credentials.fetchByCompany(
|
||||||
|
c.req.param("companyId"),
|
||||||
|
);
|
||||||
|
|
||||||
|
const response = apiResponse.successful(
|
||||||
|
"Company Credentials Fetched Successfully!",
|
||||||
|
companyCredentials.map((cred) => cred.toJson()),
|
||||||
|
);
|
||||||
|
return c.json(response, response.status as ContentfulStatusCode);
|
||||||
|
},
|
||||||
|
authMiddleware({ permissions: ["credential.fetch.many"] }),
|
||||||
|
);
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
import { Hono } from "hono/tiny";
|
||||||
|
import { createRoute } from "../../modules/api-utils/createRoute";
|
||||||
|
import { credentials } from "../../managers/credentials";
|
||||||
|
import { apiResponse } from "../../modules/api-utils/apiResponse";
|
||||||
|
import { ContentfulStatusCode } from "hono/utils/http-status";
|
||||||
|
import { authMiddleware } from "../middleware/authorization";
|
||||||
|
|
||||||
|
/* /v1/credential/:id/fields */
|
||||||
|
export default createRoute(
|
||||||
|
"get",
|
||||||
|
["/credentials/:id/fields"],
|
||||||
|
|
||||||
|
async (c) => {
|
||||||
|
const credential = await credentials.fetch(c.req.param("id"));
|
||||||
|
const fields = await credential.fetchAllFieldValues();
|
||||||
|
|
||||||
|
const response = apiResponse.successful(
|
||||||
|
"Credential Fields Fetched Successfully!",
|
||||||
|
fields,
|
||||||
|
);
|
||||||
|
return c.json(response, response.status as ContentfulStatusCode);
|
||||||
|
},
|
||||||
|
authMiddleware({
|
||||||
|
permissions: ["credential.fetch", "credential.fields.fetch"],
|
||||||
|
}),
|
||||||
|
);
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
import { createRoute } from "../../modules/api-utils/createRoute";
|
||||||
|
import { credentials } from "../../managers/credentials";
|
||||||
|
import { apiResponse } from "../../modules/api-utils/apiResponse";
|
||||||
|
import { ContentfulStatusCode } from "hono/utils/http-status";
|
||||||
|
import { authMiddleware } from "../middleware/authorization";
|
||||||
|
|
||||||
|
/* GET /v1/credential/credentials/:id/sub-credentials */
|
||||||
|
export default createRoute(
|
||||||
|
"get",
|
||||||
|
["/credentials/:id/sub-credentials"],
|
||||||
|
|
||||||
|
async (c) => {
|
||||||
|
const parentId = c.req.param("id");
|
||||||
|
|
||||||
|
// Verify the parent credential exists
|
||||||
|
await credentials.fetch(parentId);
|
||||||
|
|
||||||
|
const subCredentials = await credentials.fetchSubCredentials(parentId);
|
||||||
|
|
||||||
|
const response = apiResponse.successful(
|
||||||
|
"Sub-Credentials Fetched Successfully!",
|
||||||
|
subCredentials.map((sc) => sc.toJson()),
|
||||||
|
);
|
||||||
|
return c.json(response, response.status as ContentfulStatusCode);
|
||||||
|
},
|
||||||
|
authMiddleware({
|
||||||
|
permissions: ["credential.fetch", "credential.sub_credentials.fetch"],
|
||||||
|
}),
|
||||||
|
);
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
import { default as fetch } from "./fetch";
|
||||||
|
import { default as fetchByCompany } from "./fetchByCompany";
|
||||||
|
import { default as create } from "./create";
|
||||||
|
import { default as update } from "./update";
|
||||||
|
import { default as updateFields } from "./updateFields";
|
||||||
|
import { default as fetchFields } from "./fetchFields";
|
||||||
|
import { default as readSecureValues } from "./readSecureValues";
|
||||||
|
import { default as readSecureValue } from "./readSecureValue";
|
||||||
|
import { default as deleteCredential } from "./delete";
|
||||||
|
import { default as valueTypes } from "./valueTypes";
|
||||||
|
import { default as fetchSubCredentials } from "./fetchSubCredentials";
|
||||||
|
import { default as addSubCredential } from "./addSubCredential";
|
||||||
|
import { default as removeSubCredential } from "./removeSubCredential";
|
||||||
|
|
||||||
|
export {
|
||||||
|
valueTypes,
|
||||||
|
fetch,
|
||||||
|
fetchByCompany,
|
||||||
|
create,
|
||||||
|
update,
|
||||||
|
updateFields,
|
||||||
|
fetchFields,
|
||||||
|
readSecureValues,
|
||||||
|
readSecureValue,
|
||||||
|
deleteCredential as delete,
|
||||||
|
fetchSubCredentials,
|
||||||
|
addSubCredential,
|
||||||
|
removeSubCredential,
|
||||||
|
};
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
import { Hono } from "hono/tiny";
|
||||||
|
import { createRoute } from "../../modules/api-utils/createRoute";
|
||||||
|
import { credentials } from "../../managers/credentials";
|
||||||
|
import { apiResponse } from "../../modules/api-utils/apiResponse";
|
||||||
|
import { ContentfulStatusCode } from "hono/utils/http-status";
|
||||||
|
import { authMiddleware } from "../middleware/authorization";
|
||||||
|
|
||||||
|
/* GET /v1/credential/credentials/:id/secure-values/:fieldId */
|
||||||
|
export default createRoute(
|
||||||
|
"get",
|
||||||
|
["/credentials/:id/secure-values/:fieldId"],
|
||||||
|
|
||||||
|
async (c) => {
|
||||||
|
const credential = await credentials.fetch(c.req.param("id"));
|
||||||
|
const fieldId = c.req.param("fieldId");
|
||||||
|
const value = await credential.readSecureFieldValue(fieldId);
|
||||||
|
|
||||||
|
const response = apiResponse.successful(
|
||||||
|
"Secure Value Fetched Successfully!",
|
||||||
|
{ fieldId, value },
|
||||||
|
);
|
||||||
|
return c.json(response, response.status as ContentfulStatusCode);
|
||||||
|
},
|
||||||
|
authMiddleware({
|
||||||
|
permissions: ["credential.fetch", "credential.secure_values.read"],
|
||||||
|
}),
|
||||||
|
);
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
import { Hono } from "hono/tiny";
|
||||||
|
import { createRoute } from "../../modules/api-utils/createRoute";
|
||||||
|
import { credentials } from "../../managers/credentials";
|
||||||
|
import { apiResponse } from "../../modules/api-utils/apiResponse";
|
||||||
|
import { ContentfulStatusCode } from "hono/utils/http-status";
|
||||||
|
import { authMiddleware } from "../middleware/authorization";
|
||||||
|
|
||||||
|
/* /v1/credential/:id/secure-values */
|
||||||
|
export default createRoute(
|
||||||
|
"get",
|
||||||
|
["/credentials/:id/secure-values"],
|
||||||
|
|
||||||
|
async (c) => {
|
||||||
|
const credential = await credentials.fetch(c.req.param("id"));
|
||||||
|
const secureValues = await credential.readAllSecureValues();
|
||||||
|
|
||||||
|
const response = apiResponse.successful(
|
||||||
|
"Secure Values Fetched Successfully!",
|
||||||
|
secureValues,
|
||||||
|
);
|
||||||
|
return c.json(response, response.status as ContentfulStatusCode);
|
||||||
|
},
|
||||||
|
authMiddleware({
|
||||||
|
permissions: ["credential.fetch", "credential.secure_values.read"],
|
||||||
|
}),
|
||||||
|
);
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
import { createRoute } from "../../modules/api-utils/createRoute";
|
||||||
|
import { credentials } from "../../managers/credentials";
|
||||||
|
import { apiResponse } from "../../modules/api-utils/apiResponse";
|
||||||
|
import { ContentfulStatusCode } from "hono/utils/http-status";
|
||||||
|
import { authMiddleware } from "../middleware/authorization";
|
||||||
|
|
||||||
|
/* DELETE /v1/credential/credentials/:id/sub-credentials/:subId */
|
||||||
|
export default createRoute(
|
||||||
|
"delete",
|
||||||
|
["/credentials/:id/sub-credentials/:subId"],
|
||||||
|
|
||||||
|
async (c) => {
|
||||||
|
const parentId = c.req.param("id");
|
||||||
|
const subId = c.req.param("subId");
|
||||||
|
|
||||||
|
await credentials.removeSubCredential(parentId, subId);
|
||||||
|
|
||||||
|
const response = apiResponse.successful(
|
||||||
|
"Sub-Credential Removed Successfully!",
|
||||||
|
null,
|
||||||
|
);
|
||||||
|
return c.json(response, response.status as ContentfulStatusCode);
|
||||||
|
},
|
||||||
|
authMiddleware({
|
||||||
|
permissions: ["credential.fetch", "credential.sub_credentials.delete"],
|
||||||
|
}),
|
||||||
|
);
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
import { Hono } from "hono/tiny";
|
||||||
|
import { createRoute } from "../../modules/api-utils/createRoute";
|
||||||
|
import { credentials } from "../../managers/credentials";
|
||||||
|
import { apiResponse } from "../../modules/api-utils/apiResponse";
|
||||||
|
import { ContentfulStatusCode } from "hono/utils/http-status";
|
||||||
|
import { authMiddleware } from "../middleware/authorization";
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
/* /v1/credential/:id */
|
||||||
|
export default createRoute(
|
||||||
|
"patch",
|
||||||
|
["/credentials/:id"],
|
||||||
|
|
||||||
|
async (c) => {
|
||||||
|
const body = await c.req.json();
|
||||||
|
const credential = await credentials.fetch(c.req.param("id"));
|
||||||
|
|
||||||
|
const schema = z.object({
|
||||||
|
name: z.string().optional(),
|
||||||
|
notes: z.string().nullable().optional(),
|
||||||
|
fields: z
|
||||||
|
.array(
|
||||||
|
z.object({
|
||||||
|
fieldId: z.string(),
|
||||||
|
value: z.string(),
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.optional(),
|
||||||
|
});
|
||||||
|
|
||||||
|
const data = schema.parse(body);
|
||||||
|
|
||||||
|
if (data.fields) {
|
||||||
|
await credential.validateAndUpdateFields(data.fields);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.name !== undefined || data.notes !== undefined) {
|
||||||
|
await credential.update({
|
||||||
|
...(data.name !== undefined && { name: data.name }),
|
||||||
|
...(data.notes !== undefined && { notes: data.notes }),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = apiResponse.successful(
|
||||||
|
"Credential Updated Successfully!",
|
||||||
|
credential.toJson(),
|
||||||
|
);
|
||||||
|
return c.json(response, response.status as ContentfulStatusCode);
|
||||||
|
},
|
||||||
|
authMiddleware({ permissions: ["credential.update"] }),
|
||||||
|
);
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
import { Hono } from "hono/tiny";
|
||||||
|
import { createRoute } from "../../modules/api-utils/createRoute";
|
||||||
|
import { credentials } from "../../managers/credentials";
|
||||||
|
import { apiResponse } from "../../modules/api-utils/apiResponse";
|
||||||
|
import { ContentfulStatusCode } from "hono/utils/http-status";
|
||||||
|
import { authMiddleware } from "../middleware/authorization";
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
/* /v1/credential/:id/fields */
|
||||||
|
export default createRoute(
|
||||||
|
"put",
|
||||||
|
["/credentials/:id/fields"],
|
||||||
|
|
||||||
|
async (c) => {
|
||||||
|
const body = await c.req.json();
|
||||||
|
const credential = await credentials.fetch(c.req.param("id"));
|
||||||
|
|
||||||
|
const schema = z.object({
|
||||||
|
fields: z.array(
|
||||||
|
z.object({
|
||||||
|
fieldId: z.string(),
|
||||||
|
value: z.string(),
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
});
|
||||||
|
|
||||||
|
const data = schema.parse(body);
|
||||||
|
|
||||||
|
await credential.validateAndUpdateFields(data.fields);
|
||||||
|
|
||||||
|
const response = apiResponse.successful(
|
||||||
|
"Credential Fields Updated Successfully!",
|
||||||
|
credential.toJson(),
|
||||||
|
);
|
||||||
|
return c.json(response, response.status as ContentfulStatusCode);
|
||||||
|
},
|
||||||
|
authMiddleware({
|
||||||
|
permissions: ["credential.update", "credential.fields.update"],
|
||||||
|
}),
|
||||||
|
);
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
import { createRoute } from "../../modules/api-utils/createRoute";
|
||||||
|
import { apiResponse } from "../../modules/api-utils/apiResponse";
|
||||||
|
import { ContentfulStatusCode } from "hono/utils/http-status";
|
||||||
|
import { ValueType } from "../../modules/credentials/credentialTypeDefs";
|
||||||
|
import { authMiddleware } from "../middleware/authorization";
|
||||||
|
|
||||||
|
/* GET /v1/credential/valuetypes */
|
||||||
|
export default createRoute(
|
||||||
|
"get",
|
||||||
|
["/valuetypes"],
|
||||||
|
|
||||||
|
async (c) => {
|
||||||
|
const valueTypes = Object.values(ValueType);
|
||||||
|
|
||||||
|
const response = apiResponse.successful(
|
||||||
|
"Value Types Fetched Successfully!",
|
||||||
|
valueTypes,
|
||||||
|
);
|
||||||
|
return c.json(response, response.status as ContentfulStatusCode);
|
||||||
|
},
|
||||||
|
authMiddleware(),
|
||||||
|
);
|
||||||
@@ -0,0 +1,80 @@
|
|||||||
|
import { Context, Env, MiddlewareHandler } from "hono";
|
||||||
|
import AuthorizationError from "../../Errors/AuthorizationError";
|
||||||
|
import { sessions } from "../../managers/sessions";
|
||||||
|
import { Variables } from "../../types/HonoTypes";
|
||||||
|
import GenericError from "../../Errors/GenericError";
|
||||||
|
import { events } from "../../modules/globalEvents";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Authorization Middleware
|
||||||
|
*
|
||||||
|
* This middleware will do all of the authorization for all the routes that may need authorization.
|
||||||
|
* It will check which auth type is being used and pull the correct credentials from said auth type and
|
||||||
|
* supply them as a variable to the route. If there is an error thrown at any point in this middleware, it
|
||||||
|
* will hault and will not proceed to the route handler.
|
||||||
|
*
|
||||||
|
* Eventually this method will analyze roles and permissions and supply those as objects to the route handler.
|
||||||
|
*
|
||||||
|
* ## Auth Types
|
||||||
|
* - Bearer: Access Token for user authentication
|
||||||
|
* - Key: API Key
|
||||||
|
*
|
||||||
|
* @param ctx - Hono Context Object
|
||||||
|
* @param next - Move onto the handler
|
||||||
|
*/
|
||||||
|
export const authMiddleware = (permParams?: {
|
||||||
|
permissions?: string[];
|
||||||
|
scopes?: string[];
|
||||||
|
forbiddenAuthTypes?: string[];
|
||||||
|
}): MiddlewareHandler<{
|
||||||
|
Variables: Variables;
|
||||||
|
}> => {
|
||||||
|
return async (ctx, next) => {
|
||||||
|
const authorization = ctx.req.header()["authorization"];
|
||||||
|
if (!authorization)
|
||||||
|
throw new AuthorizationError("Missing 'authorization' header.");
|
||||||
|
|
||||||
|
const components = authorization.match(
|
||||||
|
/^(Bearer|Key)\s([a-zA-Z0-9-_]+\.[a-zA-Z0-9-_]+\.[a-zA-Z0-9-_]+)$/,
|
||||||
|
);
|
||||||
|
if (!components)
|
||||||
|
throw new AuthorizationError(
|
||||||
|
"Invalid or malformed authorization header...",
|
||||||
|
);
|
||||||
|
|
||||||
|
const authType: string = components[1] ?? "";
|
||||||
|
const authValue: string = components[2] ?? "";
|
||||||
|
|
||||||
|
if (permParams?.forbiddenAuthTypes?.includes(authType))
|
||||||
|
throw new GenericError({
|
||||||
|
name: "NonpermittedAuthType",
|
||||||
|
message:
|
||||||
|
"The authorization method you are using is not permitted for this API request.",
|
||||||
|
cause: `Type '${authType}' is not permitted.`,
|
||||||
|
status: 403,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (authType) {
|
||||||
|
const session = await sessions.fetch({ accessToken: authValue });
|
||||||
|
const user = await session.fetchUser();
|
||||||
|
ctx.set("user", user);
|
||||||
|
|
||||||
|
if (
|
||||||
|
permParams?.permissions &&
|
||||||
|
permParams?.permissions.length > 0 &&
|
||||||
|
(
|
||||||
|
await Promise.all(
|
||||||
|
permParams?.permissions.map((p) => user.hasPermission(p)),
|
||||||
|
)
|
||||||
|
).includes(false)
|
||||||
|
)
|
||||||
|
throw new GenericError({
|
||||||
|
name: "InsufficentPermission",
|
||||||
|
message: "You do not have sufficent permissions to do this.",
|
||||||
|
status: 403,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
await next();
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
import { createRoute } from "../../modules/api-utils/createRoute";
|
||||||
|
import { apiResponse } from "../../modules/api-utils/apiResponse";
|
||||||
|
import { ContentfulStatusCode } from "hono/utils/http-status";
|
||||||
|
import { authMiddleware } from "../middleware/authorization";
|
||||||
|
import { PERMISSION_NODES } from "../../types/PermissionNodes";
|
||||||
|
|
||||||
|
/* /v1/permissions */
|
||||||
|
export default createRoute(
|
||||||
|
"get",
|
||||||
|
["/"],
|
||||||
|
|
||||||
|
async (c) => {
|
||||||
|
const response = apiResponse.successful(
|
||||||
|
"Permission Nodes Fetched Successfully!",
|
||||||
|
PERMISSION_NODES,
|
||||||
|
);
|
||||||
|
return c.json(response, response.status as ContentfulStatusCode);
|
||||||
|
},
|
||||||
|
authMiddleware({ permissions: ["role.read"] }),
|
||||||
|
);
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
import { createRoute } from "../../modules/api-utils/createRoute";
|
||||||
|
import { apiResponse } from "../../modules/api-utils/apiResponse";
|
||||||
|
import { ContentfulStatusCode } from "hono/utils/http-status";
|
||||||
|
import { authMiddleware } from "../middleware/authorization";
|
||||||
|
import { PERMISSION_NODES } from "../../types/PermissionNodes";
|
||||||
|
import GenericError from "../../Errors/GenericError";
|
||||||
|
|
||||||
|
/* /v1/permissions/:category */
|
||||||
|
export default createRoute(
|
||||||
|
"get",
|
||||||
|
["/:category"],
|
||||||
|
|
||||||
|
async (c) => {
|
||||||
|
const categoryKey = c.req.param(
|
||||||
|
"category",
|
||||||
|
) as keyof typeof PERMISSION_NODES;
|
||||||
|
|
||||||
|
if (!(categoryKey in PERMISSION_NODES)) {
|
||||||
|
throw new GenericError({
|
||||||
|
name: "NotFound",
|
||||||
|
message: `Permission category "${categoryKey}" not found`,
|
||||||
|
status: 404,
|
||||||
|
cause: `Valid categories: ${Object.keys(PERMISSION_NODES).join(", ")}`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = apiResponse.successful(
|
||||||
|
"Permission Category Fetched Successfully!",
|
||||||
|
PERMISSION_NODES[categoryKey],
|
||||||
|
);
|
||||||
|
return c.json(response, response.status as ContentfulStatusCode);
|
||||||
|
},
|
||||||
|
authMiddleware({ permissions: ["role.read"] }),
|
||||||
|
);
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
import { createRoute } from "../../modules/api-utils/createRoute";
|
||||||
|
import { apiResponse } from "../../modules/api-utils/apiResponse";
|
||||||
|
import { ContentfulStatusCode } from "hono/utils/http-status";
|
||||||
|
import { authMiddleware } from "../middleware/authorization";
|
||||||
|
import { getAllPermissionNodes } from "../../types/PermissionNodes";
|
||||||
|
|
||||||
|
/* /v1/permissions/nodes */
|
||||||
|
export default createRoute(
|
||||||
|
"get",
|
||||||
|
["/nodes"],
|
||||||
|
|
||||||
|
async (c) => {
|
||||||
|
const allNodes = getAllPermissionNodes();
|
||||||
|
|
||||||
|
const response = apiResponse.successful(
|
||||||
|
"All Permission Nodes Fetched Successfully!",
|
||||||
|
allNodes,
|
||||||
|
);
|
||||||
|
return c.json(response, response.status as ContentfulStatusCode);
|
||||||
|
},
|
||||||
|
authMiddleware({ permissions: ["role.read"] }),
|
||||||
|
);
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
import { default as fetchAll } from "./fetchAll";
|
||||||
|
import { default as fetchByCategory } from "./fetchByCategory";
|
||||||
|
import { default as fetchNodes } from "./fetchNodes";
|
||||||
|
|
||||||
|
export { fetchAll, fetchByCategory, fetchNodes };
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
import { Hono } from "hono/tiny";
|
||||||
|
import { createRoute } from "../../modules/api-utils/createRoute";
|
||||||
|
import { roles } from "../../managers/roles";
|
||||||
|
import { apiResponse } from "../../modules/api-utils/apiResponse";
|
||||||
|
import { ContentfulStatusCode } from "hono/utils/http-status";
|
||||||
|
import { authMiddleware } from "../middleware/authorization";
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
/* POST /v1/role/:identifier/permissions */
|
||||||
|
export default createRoute(
|
||||||
|
"post",
|
||||||
|
["/:identifier/permissions"],
|
||||||
|
|
||||||
|
async (c) => {
|
||||||
|
const identifier = c.req.param("identifier");
|
||||||
|
const body = await c.req.json();
|
||||||
|
|
||||||
|
const schema = z.object({
|
||||||
|
permissions: z
|
||||||
|
.array(z.string().min(1, "Permission node cannot be empty"))
|
||||||
|
.min(1, "At least one permission is required"),
|
||||||
|
});
|
||||||
|
|
||||||
|
const data = schema.parse(body);
|
||||||
|
|
||||||
|
const role = await roles.fetch(identifier);
|
||||||
|
await role.addPermissions(...data.permissions);
|
||||||
|
|
||||||
|
const response = apiResponse.successful(
|
||||||
|
"Permissions Added Successfully!",
|
||||||
|
role.toJson({ viewPermissions: true }),
|
||||||
|
);
|
||||||
|
return c.json(response, response.status as ContentfulStatusCode);
|
||||||
|
},
|
||||||
|
authMiddleware({ permissions: ["role.update"] }),
|
||||||
|
);
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user