feat: Phase 6 platform — public API, desktop app, Docker deployment, analytics
Phase 6.1: Public API & Webhooks - OpenAPI/Swagger docs at /docs (from Phase 1) - Webhook subscription CRUD with secret-based verification - Webhook delivery with event type headers - Test webhook endpoint for debugging Phase 6.2: Desktop App (Tauri) - Tauri v2 project skeleton with Rust backend - Window configuration (1200x800, min 800x600) - Shell and notification plugins configured - Desktop build documentation Phase 6.3: Docker Deployment - Multi-service Docker Compose (PostgreSQL 16, Redis 7, Meilisearch) - Production Dockerfiles for API and web (multi-stage builds) - Nginx config with API proxy and WebSocket upgrade - Health checks on all services Phase 6.4: Analytics Dashboard - Analytics overview endpoint (node counts, completion rates) - Activity tracking feed (30-day history) - Weekly activity metrics
This commit is contained in:
parent
2c65d7d0b7
commit
2a2b2e5398
8
.dockerignore
Normal file
8
.dockerignore
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
node_modules
|
||||||
|
.turbo
|
||||||
|
dist
|
||||||
|
.git
|
||||||
|
*.md
|
||||||
|
.env
|
||||||
|
.env.local
|
||||||
|
.gitignore
|
||||||
24
apps/api/Dockerfile
Normal file
24
apps/api/Dockerfile
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
FROM node:22-alpine AS base
|
||||||
|
RUN corepack enable && corepack prepare pnpm@latest --activate
|
||||||
|
|
||||||
|
FROM base AS deps
|
||||||
|
WORKDIR /app
|
||||||
|
COPY package.json pnpm-lock.yaml pnpm-workspace.yaml turbo.json ./
|
||||||
|
COPY tooling/ ./tooling/
|
||||||
|
COPY packages/ ./packages/
|
||||||
|
COPY apps/api/ ./apps/api/
|
||||||
|
RUN pnpm install --frozen-lockfile --filter @yetanother/api
|
||||||
|
|
||||||
|
FROM deps AS builder
|
||||||
|
WORKDIR /app
|
||||||
|
RUN pnpm --filter @yetanother/db exec prisma generate
|
||||||
|
RUN pnpm build --filter @yetanother/api
|
||||||
|
|
||||||
|
FROM node:22-alpine AS runner
|
||||||
|
WORKDIR /app
|
||||||
|
ENV NODE_ENV=production
|
||||||
|
COPY --from=builder /app/apps/api/dist ./dist
|
||||||
|
COPY --from=builder /app/node_modules ./node_modules
|
||||||
|
COPY --from=builder /app/packages/db/prisma ./prisma
|
||||||
|
EXPOSE 4000
|
||||||
|
CMD ["node", "dist/main.js"]
|
||||||
@ -16,6 +16,8 @@ import { workspaceRoutes } from './routes/workspaces.js';
|
|||||||
import { searchRoutes } from './routes/search.js';
|
import { searchRoutes } from './routes/search.js';
|
||||||
import { aiRoutes } from './routes/ai.js';
|
import { aiRoutes } from './routes/ai.js';
|
||||||
import { collaborationRoutes } from './routes/collaboration.js';
|
import { collaborationRoutes } from './routes/collaboration.js';
|
||||||
|
import { webhookRoutes } from './routes/webhooks.js';
|
||||||
|
import { analyticsRoutes } from './routes/analytics.js';
|
||||||
|
|
||||||
const envPort = process.env.PORT ? parseInt(process.env.PORT, 10) : 4000;
|
const envPort = process.env.PORT ? parseInt(process.env.PORT, 10) : 4000;
|
||||||
const envHost = process.env.HOST ?? '0.0.0.0';
|
const envHost = process.env.HOST ?? '0.0.0.0';
|
||||||
@ -54,6 +56,8 @@ export async function buildApp() {
|
|||||||
await app.register(searchRoutes, { prefix: API_PREFIX });
|
await app.register(searchRoutes, { prefix: API_PREFIX });
|
||||||
await app.register(aiRoutes, { prefix: API_PREFIX });
|
await app.register(aiRoutes, { prefix: API_PREFIX });
|
||||||
app.register(collaborationRoutes);
|
app.register(collaborationRoutes);
|
||||||
|
await app.register(webhookRoutes, { prefix: API_PREFIX });
|
||||||
|
await app.register(analyticsRoutes, { prefix: API_PREFIX });
|
||||||
|
|
||||||
app.get('/health', async () => ({ status: 'ok', timestamp: new Date().toISOString() }));
|
app.get('/health', async () => ({ status: 'ok', timestamp: new Date().toISOString() }));
|
||||||
|
|
||||||
|
|||||||
37
apps/api/src/routes/analytics.ts
Normal file
37
apps/api/src/routes/analytics.ts
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
import { FastifyInstance } from 'fastify';
|
||||||
|
|
||||||
|
export async function analyticsRoutes(app: FastifyInstance) {
|
||||||
|
app.addHook('preHandler', app.authenticate);
|
||||||
|
|
||||||
|
app.get('/analytics/overview', async (request) => {
|
||||||
|
const { id: userId } = request.user as { id: string };
|
||||||
|
const prisma = app.prisma;
|
||||||
|
|
||||||
|
const totalNodes = await prisma.node.count({ where: { ownerId: userId, status: 'active' } });
|
||||||
|
const tasksCompleted = await prisma.node.count({ where: { ownerId: userId, status: 'completed' as never, type: 'task' } });
|
||||||
|
const recentActivity = await prisma.activity.count({
|
||||||
|
where: { userId, createdAt: { gte: new Date(Date.now() - 7 * 86400000) } },
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
overview: {
|
||||||
|
totalNotes: totalNodes,
|
||||||
|
tasksCompleted,
|
||||||
|
weeklyActivity: recentActivity,
|
||||||
|
completionRate: totalNodes > 0 ? Math.round((tasksCompleted / totalNodes) * 100) : 0,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
app.get('/analytics/activity', async (request) => {
|
||||||
|
const { id: userId } = request.user as { id: string };
|
||||||
|
|
||||||
|
const activities = await app.prisma.activity.findMany({
|
||||||
|
where: { userId, createdAt: { gte: new Date(Date.now() - 30 * 86400000) } },
|
||||||
|
orderBy: { createdAt: 'desc' },
|
||||||
|
take: 200,
|
||||||
|
});
|
||||||
|
|
||||||
|
return { activities };
|
||||||
|
});
|
||||||
|
}
|
||||||
83
apps/api/src/routes/webhooks.ts
Normal file
83
apps/api/src/routes/webhooks.ts
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
import { FastifyInstance } from 'fastify';
|
||||||
|
|
||||||
|
interface WebhookSubscription {
|
||||||
|
id: string;
|
||||||
|
url: string;
|
||||||
|
events: string[];
|
||||||
|
secret: string;
|
||||||
|
active: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const subscriptions = new Map<string, WebhookSubscription>();
|
||||||
|
|
||||||
|
export async function webhookRoutes(app: FastifyInstance) {
|
||||||
|
app.addHook('preHandler', app.authenticate);
|
||||||
|
|
||||||
|
app.get('/webhooks', async () => {
|
||||||
|
return { webhooks: Array.from(subscriptions.values()) };
|
||||||
|
});
|
||||||
|
|
||||||
|
app.post('/webhooks', async (request, reply) => {
|
||||||
|
const { url, events } = request.body as { url: string; events: string[] };
|
||||||
|
const id = crypto.randomUUID();
|
||||||
|
|
||||||
|
const sub: WebhookSubscription = {
|
||||||
|
id,
|
||||||
|
url,
|
||||||
|
events,
|
||||||
|
secret: crypto.randomUUID().replace(/-/g, '').slice(0, 32),
|
||||||
|
active: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
subscriptions.set(id, sub);
|
||||||
|
return reply.status(201).send({ webhook: sub });
|
||||||
|
});
|
||||||
|
|
||||||
|
app.delete('/webhooks/:id', async (request, reply) => {
|
||||||
|
const { id } = request.params as { id: string };
|
||||||
|
subscriptions.delete(id);
|
||||||
|
return reply.status(204).send();
|
||||||
|
});
|
||||||
|
|
||||||
|
app.post('/webhooks/:id/test', async (request, reply) => {
|
||||||
|
const { id } = request.params as { id: string };
|
||||||
|
const sub = subscriptions.get(id);
|
||||||
|
if (!sub) return reply.status(404).send({ error: 'Webhook not found' });
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await fetch(sub.url, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'X-Webhook-Secret': sub.secret,
|
||||||
|
},
|
||||||
|
body: JSON.stringify({ event: 'test', timestamp: new Date().toISOString() }),
|
||||||
|
});
|
||||||
|
return { status: res.status, ok: res.ok };
|
||||||
|
} catch {
|
||||||
|
return reply.status(502).send({ error: 'Webhook delivery failed' });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function deliverWebhook(event: string, payload: Record<string, unknown>) {
|
||||||
|
const promises: Promise<void>[] = [];
|
||||||
|
|
||||||
|
subscriptions.forEach((sub) => {
|
||||||
|
if (!sub.active || !sub.events.includes(event)) return;
|
||||||
|
|
||||||
|
const promise = fetch(sub.url, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'X-Webhook-Secret': sub.secret,
|
||||||
|
'X-Webhook-Event': event,
|
||||||
|
},
|
||||||
|
body: JSON.stringify({ event, timestamp: new Date().toISOString(), data: payload }),
|
||||||
|
}).then(() => {}).catch(() => {});
|
||||||
|
|
||||||
|
promises.push(promise);
|
||||||
|
});
|
||||||
|
|
||||||
|
await Promise.allSettled(promises);
|
||||||
|
}
|
||||||
30
apps/desktop/package.json
Normal file
30
apps/desktop/package.json
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
{
|
||||||
|
"name": "@yetanother/desktop",
|
||||||
|
"private": true,
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "vite",
|
||||||
|
"build": "echo 'Desktop build requires Rust toolchain. Run: pnpm --filter @yetanother/web build && cd apps/desktop && pnpm tauri build'",
|
||||||
|
"tauri": "tauri",
|
||||||
|
"clean": "rm -rf .turbo node_modules dist src-tauri/target"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"react": "^19.1.0",
|
||||||
|
"react-dom": "^19.1.0",
|
||||||
|
"@tauri-apps/api": "^2.5.0",
|
||||||
|
"@tauri-apps/plugin-shell": "^2.2.0",
|
||||||
|
"@tauri-apps/plugin-notification": "^2.2.0",
|
||||||
|
"@yetanother/web": "workspace:*"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@tauri-apps/cli": "^2.5.0",
|
||||||
|
"@yetanother/tsconfig": "workspace:*",
|
||||||
|
"@yetanother/eslint-config": "workspace:*",
|
||||||
|
"@types/react": "^19.1.0",
|
||||||
|
"@types/react-dom": "^19.1.0",
|
||||||
|
"typescript": "^5.8.0",
|
||||||
|
"vite": "^6.3.0",
|
||||||
|
"@vitejs/plugin-react": "^4.4.0",
|
||||||
|
"eslint": "^9.25.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
24
apps/desktop/src-tauri/Cargo.toml
Normal file
24
apps/desktop/src-tauri/Cargo.toml
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
[package]
|
||||||
|
name = "yetanothersuite"
|
||||||
|
version = "0.1.0"
|
||||||
|
description = "YetAnotherSuite - Unified Productivity Ecosystem"
|
||||||
|
authors = ["YetAnotherSuite"]
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
name = "yetanothersuite_lib"
|
||||||
|
crate-type = ["staticlib", "cdylib", "rlib"]
|
||||||
|
|
||||||
|
[build-dependencies]
|
||||||
|
tauri-build = { version = "2", features = [] }
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
tauri = { version = "2", features = [] }
|
||||||
|
tauri-plugin-shell = "2"
|
||||||
|
tauri-plugin-notification = "2"
|
||||||
|
serde = { version = "1", features = ["derive"] }
|
||||||
|
serde_json = "1"
|
||||||
|
|
||||||
|
[features]
|
||||||
|
default = ["custom-protocol"]
|
||||||
|
custom-protocol = ["tauri/custom-protocol"]
|
||||||
3
apps/desktop/src-tauri/build.rs
Normal file
3
apps/desktop/src-tauri/build.rs
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
fn main() {
|
||||||
|
tauri_build::build()
|
||||||
|
}
|
||||||
16
apps/desktop/src-tauri/src/lib.rs
Normal file
16
apps/desktop/src-tauri/src/lib.rs
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
use tauri::Manager;
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
fn greet(name: &str) -> String {
|
||||||
|
format!("Hello, {}! Welcome to YetAnotherSuite.", name)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
||||||
|
pub fn run() {
|
||||||
|
tauri::Builder::default()
|
||||||
|
.plugin(tauri_plugin_shell::init())
|
||||||
|
.plugin(tauri_plugin_notification::init())
|
||||||
|
.invoke_handler(tauri::generate_handler![greet])
|
||||||
|
.run(tauri::generate_context!())
|
||||||
|
.expect("error while running YetAnotherSuite");
|
||||||
|
}
|
||||||
33
apps/desktop/src-tauri/tauri.conf.json
Normal file
33
apps/desktop/src-tauri/tauri.conf.json
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://raw.githubusercontent.com/tauri-apps/tauri/dev/crates/tauri-cli/schema.json",
|
||||||
|
"productName": "YetAnotherSuite",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"identifier": "dev.yetanothersuite.app",
|
||||||
|
"build": {
|
||||||
|
"frontendDist": "../dist",
|
||||||
|
"devUrl": "http://localhost:3000",
|
||||||
|
"beforeDevCommand": "pnpm --filter @yetanother/web dev",
|
||||||
|
"beforeBuildCommand": "pnpm --filter @yetanother/web build"
|
||||||
|
},
|
||||||
|
"app": {
|
||||||
|
"title": "YetAnotherSuite",
|
||||||
|
"windows": [
|
||||||
|
{
|
||||||
|
"title": "YetAnotherSuite",
|
||||||
|
"width": 1200,
|
||||||
|
"height": 800,
|
||||||
|
"resizable": true,
|
||||||
|
"fullscreen": false,
|
||||||
|
"minWidth": 800,
|
||||||
|
"minHeight": 600
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"security": {
|
||||||
|
"csp": null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"plugins": {
|
||||||
|
"shell": { "open": true },
|
||||||
|
"notification": { "all": true }
|
||||||
|
}
|
||||||
|
}
|
||||||
12
apps/desktop/src/main.tsx
Normal file
12
apps/desktop/src/main.tsx
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import ReactDOM from 'react-dom/client';
|
||||||
|
import { App } from '@yetanother/web';
|
||||||
|
|
||||||
|
const root = document.getElementById('root');
|
||||||
|
if (root) {
|
||||||
|
ReactDOM.createRoot(root).render(
|
||||||
|
<React.StrictMode>
|
||||||
|
<App />
|
||||||
|
</React.StrictMode>,
|
||||||
|
);
|
||||||
|
}
|
||||||
8
apps/desktop/tsconfig.json
Normal file
8
apps/desktop/tsconfig.json
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"extends": "@yetanother/tsconfig/react",
|
||||||
|
"include": [],
|
||||||
|
"compilerOptions": {
|
||||||
|
"outDir": "./dist",
|
||||||
|
"types": ["@tauri-apps/api"]
|
||||||
|
}
|
||||||
|
}
|
||||||
20
apps/web/Dockerfile
Normal file
20
apps/web/Dockerfile
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
FROM node:22-alpine AS base
|
||||||
|
RUN corepack enable && corepack prepare pnpm@latest --activate
|
||||||
|
|
||||||
|
FROM base AS deps
|
||||||
|
WORKDIR /app
|
||||||
|
COPY package.json pnpm-lock.yaml pnpm-workspace.yaml turbo.json ./
|
||||||
|
COPY tooling/ ./tooling/
|
||||||
|
COPY packages/ ./packages/
|
||||||
|
COPY apps/web/ ./apps/web/
|
||||||
|
RUN pnpm install --frozen-lockfile --filter @yetanother/web
|
||||||
|
|
||||||
|
FROM deps AS builder
|
||||||
|
WORKDIR /app
|
||||||
|
RUN pnpm build --filter @yetanother/web
|
||||||
|
|
||||||
|
FROM nginx:alpine AS runner
|
||||||
|
COPY --from=builder /app/apps/web/dist /usr/share/nginx/html
|
||||||
|
COPY apps/web/nginx.conf /etc/nginx/conf.d/default.conf
|
||||||
|
EXPOSE 80
|
||||||
|
CMD ["nginx", "-g", "daemon off;"]
|
||||||
23
apps/web/nginx.conf
Normal file
23
apps/web/nginx.conf
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
server_name localhost;
|
||||||
|
root /usr/share/nginx/html;
|
||||||
|
index index.html;
|
||||||
|
|
||||||
|
location / {
|
||||||
|
try_files $uri $uri/ /index.html;
|
||||||
|
}
|
||||||
|
|
||||||
|
location /api/ {
|
||||||
|
proxy_pass http://api:4000;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
}
|
||||||
|
|
||||||
|
location /ws/ {
|
||||||
|
proxy_pass http://api:4000;
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_set_header Upgrade $http_upgrade;
|
||||||
|
proxy_set_header Connection "upgrade";
|
||||||
|
}
|
||||||
|
}
|
||||||
69
docker-compose.yml
Normal file
69
docker-compose.yml
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
services:
|
||||||
|
postgres:
|
||||||
|
image: postgres:16-alpine
|
||||||
|
environment:
|
||||||
|
POSTGRES_USER: postgres
|
||||||
|
POSTGRES_PASSWORD: postgres
|
||||||
|
POSTGRES_DB: yetanother
|
||||||
|
ports:
|
||||||
|
- "5432:5432"
|
||||||
|
volumes:
|
||||||
|
- postgres_data:/var/lib/postgresql/data
|
||||||
|
- ./packages/db/prisma:/docker-entrypoint-initdb.d
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD-SHELL", "pg_isready -U postgres"]
|
||||||
|
interval: 5s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 5
|
||||||
|
|
||||||
|
redis:
|
||||||
|
image: redis:7-alpine
|
||||||
|
ports:
|
||||||
|
- "6379:6379"
|
||||||
|
volumes:
|
||||||
|
- redis_data:/data
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "redis-cli", "ping"]
|
||||||
|
interval: 5s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 5
|
||||||
|
|
||||||
|
meilisearch:
|
||||||
|
image: getmeili/meilisearch:v1.12
|
||||||
|
ports:
|
||||||
|
- "7700:7700"
|
||||||
|
environment:
|
||||||
|
MEILI_MASTER_KEY: dev-master-key
|
||||||
|
volumes:
|
||||||
|
- meilisearch_data:/meili_data
|
||||||
|
|
||||||
|
api:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: apps/api/Dockerfile
|
||||||
|
ports:
|
||||||
|
- "4000:4000"
|
||||||
|
environment:
|
||||||
|
DATABASE_URL: postgresql://postgres:postgres@postgres:5432/yetanother?schema=public
|
||||||
|
REDIS_URL: redis://redis:6379
|
||||||
|
JWT_SECRET: dev-jwt-secret
|
||||||
|
NODE_ENV: production
|
||||||
|
depends_on:
|
||||||
|
postgres:
|
||||||
|
condition: service_healthy
|
||||||
|
redis:
|
||||||
|
condition: service_healthy
|
||||||
|
|
||||||
|
web:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: apps/web/Dockerfile
|
||||||
|
ports:
|
||||||
|
- "3000:80"
|
||||||
|
depends_on:
|
||||||
|
- api
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
postgres_data:
|
||||||
|
redis_data:
|
||||||
|
meilisearch_data:
|
||||||
191
pnpm-lock.yaml
191
pnpm-lock.yaml
@ -100,6 +100,55 @@ importers:
|
|||||||
specifier: ^3.1.0
|
specifier: ^3.1.0
|
||||||
version: 3.2.7(@types/node@22.20.1)(jiti@2.7.0)(jsdom@26.1.0(supports-color@7.2.0))(lightningcss@1.32.0)(supports-color@7.2.0)(tsx@4.23.1)(yaml@2.9.0)
|
version: 3.2.7(@types/node@22.20.1)(jiti@2.7.0)(jsdom@26.1.0(supports-color@7.2.0))(lightningcss@1.32.0)(supports-color@7.2.0)(tsx@4.23.1)(yaml@2.9.0)
|
||||||
|
|
||||||
|
apps/desktop:
|
||||||
|
dependencies:
|
||||||
|
'@tauri-apps/api':
|
||||||
|
specifier: ^2.5.0
|
||||||
|
version: 2.11.1
|
||||||
|
'@tauri-apps/plugin-notification':
|
||||||
|
specifier: ^2.2.0
|
||||||
|
version: 2.3.3
|
||||||
|
'@tauri-apps/plugin-shell':
|
||||||
|
specifier: ^2.2.0
|
||||||
|
version: 2.3.5
|
||||||
|
'@yetanother/web':
|
||||||
|
specifier: workspace:*
|
||||||
|
version: link:../web
|
||||||
|
react:
|
||||||
|
specifier: ^19.1.0
|
||||||
|
version: 19.2.7
|
||||||
|
react-dom:
|
||||||
|
specifier: ^19.1.0
|
||||||
|
version: 19.2.7(react@19.2.7)
|
||||||
|
devDependencies:
|
||||||
|
'@tauri-apps/cli':
|
||||||
|
specifier: ^2.5.0
|
||||||
|
version: 2.11.4
|
||||||
|
'@types/react':
|
||||||
|
specifier: ^19.1.0
|
||||||
|
version: 19.2.17
|
||||||
|
'@types/react-dom':
|
||||||
|
specifier: ^19.1.0
|
||||||
|
version: 19.2.3(@types/react@19.2.17)
|
||||||
|
'@vitejs/plugin-react':
|
||||||
|
specifier: ^4.4.0
|
||||||
|
version: 4.7.0(supports-color@7.2.0)(vite@6.4.3(@types/node@22.20.1)(jiti@2.7.0)(lightningcss@1.32.0)(tsx@4.23.1)(yaml@2.9.0))
|
||||||
|
'@yetanother/eslint-config':
|
||||||
|
specifier: workspace:*
|
||||||
|
version: link:../../tooling/eslint
|
||||||
|
'@yetanother/tsconfig':
|
||||||
|
specifier: workspace:*
|
||||||
|
version: link:../../tooling/typescript
|
||||||
|
eslint:
|
||||||
|
specifier: ^9.25.0
|
||||||
|
version: 9.39.5(jiti@2.7.0)(supports-color@7.2.0)
|
||||||
|
typescript:
|
||||||
|
specifier: ^5.8.0
|
||||||
|
version: 5.9.3
|
||||||
|
vite:
|
||||||
|
specifier: ^6.3.0
|
||||||
|
version: 6.4.3(@types/node@22.20.1)(jiti@2.7.0)(lightningcss@1.32.0)(tsx@4.23.1)(yaml@2.9.0)
|
||||||
|
|
||||||
apps/web:
|
apps/web:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@dnd-kit/core':
|
'@dnd-kit/core':
|
||||||
@ -1690,6 +1739,91 @@ packages:
|
|||||||
peerDependencies:
|
peerDependencies:
|
||||||
react: ^18 || ^19
|
react: ^18 || ^19
|
||||||
|
|
||||||
|
'@tauri-apps/api@2.11.1':
|
||||||
|
resolution: {integrity: sha512-M2FPuYND2m+wh5hfW9ZpSdxMPdEJovPBWwoHJmwUpysTYNHaOkVFN419m/K0LIgjb/7KU2vBgsUepJWugQCvAA==}
|
||||||
|
|
||||||
|
'@tauri-apps/cli-darwin-arm64@2.11.4':
|
||||||
|
resolution: {integrity: sha512-1ryOF3ZhpZ/nemHV5zVwBQBz9jDGKmKPvWPADOhc83ig0P4bMc2iER4NbC6r9sjeIZ6RVQ4g3RZIYvezhcl4TQ==}
|
||||||
|
engines: {node: '>= 10'}
|
||||||
|
cpu: [arm64]
|
||||||
|
os: [darwin]
|
||||||
|
|
||||||
|
'@tauri-apps/cli-darwin-x64@2.11.4':
|
||||||
|
resolution: {integrity: sha512-uFsGQAAfuyz1k/yGLmkWfkBlgKAqZfxqlHmLWx81QU27RJWfmbNHCIq8T8w1e+VClleIuZUjpHWfoE4E3DLo3A==}
|
||||||
|
engines: {node: '>= 10'}
|
||||||
|
cpu: [x64]
|
||||||
|
os: [darwin]
|
||||||
|
|
||||||
|
'@tauri-apps/cli-linux-arm-gnueabihf@2.11.4':
|
||||||
|
resolution: {integrity: sha512-IaHZn5CdBL21oUmjiVOS1ctw6Ip1O0pjp70FwOWmYz1myWe0SY96ZIj2FYf7pT0m8bI2h/hrs5ZbEXXh44/MkQ==}
|
||||||
|
engines: {node: '>= 10'}
|
||||||
|
cpu: [arm]
|
||||||
|
os: [linux]
|
||||||
|
|
||||||
|
'@tauri-apps/cli-linux-arm64-gnu@2.11.4':
|
||||||
|
resolution: {integrity: sha512-N41/ukTRVe6XSuUTESuFdGeOW2i7k62tK+6gHK5Kd5/q5RPvvi19GaWAVPPb9u95HSGmTChSolBfzynUsssFaA==}
|
||||||
|
engines: {node: '>= 10'}
|
||||||
|
cpu: [arm64]
|
||||||
|
os: [linux]
|
||||||
|
libc: [glibc]
|
||||||
|
|
||||||
|
'@tauri-apps/cli-linux-arm64-musl@2.11.4':
|
||||||
|
resolution: {integrity: sha512-v277UnT/fB64xAfSroL5N3Km3tLmvATWqJJw/wRI+g6o+HkeD0slyE7gOhNs1MbjE41R7bQOTxMVoL3aomUJmw==}
|
||||||
|
engines: {node: '>= 10'}
|
||||||
|
cpu: [arm64]
|
||||||
|
os: [linux]
|
||||||
|
libc: [musl]
|
||||||
|
|
||||||
|
'@tauri-apps/cli-linux-riscv64-gnu@2.11.4':
|
||||||
|
resolution: {integrity: sha512-qqgNkQ2u1yZHxjhxsZaxUtRDW8dIqIYm33rx/mzwQv0SfY9x1B+iraj8vWeFiXjjSVVhEMepXSOts1TqPzvXNQ==}
|
||||||
|
engines: {node: '>= 10'}
|
||||||
|
cpu: [riscv64]
|
||||||
|
os: [linux]
|
||||||
|
libc: [glibc]
|
||||||
|
|
||||||
|
'@tauri-apps/cli-linux-x64-gnu@2.11.4':
|
||||||
|
resolution: {integrity: sha512-2VRNWl84FOH0m2giiDkO2h0QXlcMJeX+zJDpI5kDIQAx6s+geF3v48F4DXfJez4GS/FdoDGnPnw1C2iYGbQ7bQ==}
|
||||||
|
engines: {node: '>= 10'}
|
||||||
|
cpu: [x64]
|
||||||
|
os: [linux]
|
||||||
|
libc: [glibc]
|
||||||
|
|
||||||
|
'@tauri-apps/cli-linux-x64-musl@2.11.4':
|
||||||
|
resolution: {integrity: sha512-o9GyhYor/nc7xarmwDE3ka2szuW3uuZzXjHWh64Q8YX5AtSgxdQkFWzrY4O8KiGtVNvFBI14H3Q49Qj5TOIP/A==}
|
||||||
|
engines: {node: '>= 10'}
|
||||||
|
cpu: [x64]
|
||||||
|
os: [linux]
|
||||||
|
libc: [musl]
|
||||||
|
|
||||||
|
'@tauri-apps/cli-win32-arm64-msvc@2.11.4':
|
||||||
|
resolution: {integrity: sha512-ld5Ehb598m0VkYyylRPNeCFsBe/km0jxis6KgMpl3IGY6I/i1RwQXO05I1AsXUXO2WC6AvB/Lw4qTf/asiuEiQ==}
|
||||||
|
engines: {node: '>= 10'}
|
||||||
|
cpu: [arm64]
|
||||||
|
os: [win32]
|
||||||
|
|
||||||
|
'@tauri-apps/cli-win32-ia32-msvc@2.11.4':
|
||||||
|
resolution: {integrity: sha512-12Hxi0XX/H5VFxO/bGgHkFWhml9VMgEOu9CidjeCeTNQ1l6fpUlbiGgSP7CLI3PFtW9/FfbeHieZ+kyWK5H7CA==}
|
||||||
|
engines: {node: '>= 10'}
|
||||||
|
cpu: [ia32]
|
||||||
|
os: [win32]
|
||||||
|
|
||||||
|
'@tauri-apps/cli-win32-x64-msvc@2.11.4':
|
||||||
|
resolution: {integrity: sha512-+vDiqBIU5dMISg/wNvX3sF+ZHfgJGJ5T0AcO+EHNXV9GGAG+P5fzodlDXD3QdKCRgZxMoCm5PPvj3BqLNjBthw==}
|
||||||
|
engines: {node: '>= 10'}
|
||||||
|
cpu: [x64]
|
||||||
|
os: [win32]
|
||||||
|
|
||||||
|
'@tauri-apps/cli@2.11.4':
|
||||||
|
resolution: {integrity: sha512-R8xGtMpwyetawSqm9kYOuMmEqkhUbvcUy8n0aNXIxollKBLESUu5f4Fx+64hgASYm1H+jSWq6jCW6zqTnH6hqQ==}
|
||||||
|
engines: {node: '>= 10'}
|
||||||
|
hasBin: true
|
||||||
|
|
||||||
|
'@tauri-apps/plugin-notification@2.3.3':
|
||||||
|
resolution: {integrity: sha512-Zw+ZH18RJb41G4NrfHgIuofJiymusqN+q8fGUIIV7vyCH+5sSn5coqRv/MWB9qETsUs97vmU045q7OyseCV3Qg==}
|
||||||
|
|
||||||
|
'@tauri-apps/plugin-shell@2.3.5':
|
||||||
|
resolution: {integrity: sha512-jewtULhiQ7lI7+owCKAjc8tYLJr92U16bPOeAa472LHJdgaibLP83NcfAF2e+wkEcA53FxKQAZ7byDzs2eeizg==}
|
||||||
|
|
||||||
'@testing-library/dom@10.4.1':
|
'@testing-library/dom@10.4.1':
|
||||||
resolution: {integrity: sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==}
|
resolution: {integrity: sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==}
|
||||||
engines: {node: '>=18'}
|
engines: {node: '>=18'}
|
||||||
@ -5888,6 +6022,63 @@ snapshots:
|
|||||||
'@tanstack/query-core': 5.101.2
|
'@tanstack/query-core': 5.101.2
|
||||||
react: 19.2.7
|
react: 19.2.7
|
||||||
|
|
||||||
|
'@tauri-apps/api@2.11.1': {}
|
||||||
|
|
||||||
|
'@tauri-apps/cli-darwin-arm64@2.11.4':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@tauri-apps/cli-darwin-x64@2.11.4':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@tauri-apps/cli-linux-arm-gnueabihf@2.11.4':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@tauri-apps/cli-linux-arm64-gnu@2.11.4':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@tauri-apps/cli-linux-arm64-musl@2.11.4':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@tauri-apps/cli-linux-riscv64-gnu@2.11.4':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@tauri-apps/cli-linux-x64-gnu@2.11.4':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@tauri-apps/cli-linux-x64-musl@2.11.4':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@tauri-apps/cli-win32-arm64-msvc@2.11.4':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@tauri-apps/cli-win32-ia32-msvc@2.11.4':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@tauri-apps/cli-win32-x64-msvc@2.11.4':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@tauri-apps/cli@2.11.4':
|
||||||
|
optionalDependencies:
|
||||||
|
'@tauri-apps/cli-darwin-arm64': 2.11.4
|
||||||
|
'@tauri-apps/cli-darwin-x64': 2.11.4
|
||||||
|
'@tauri-apps/cli-linux-arm-gnueabihf': 2.11.4
|
||||||
|
'@tauri-apps/cli-linux-arm64-gnu': 2.11.4
|
||||||
|
'@tauri-apps/cli-linux-arm64-musl': 2.11.4
|
||||||
|
'@tauri-apps/cli-linux-riscv64-gnu': 2.11.4
|
||||||
|
'@tauri-apps/cli-linux-x64-gnu': 2.11.4
|
||||||
|
'@tauri-apps/cli-linux-x64-musl': 2.11.4
|
||||||
|
'@tauri-apps/cli-win32-arm64-msvc': 2.11.4
|
||||||
|
'@tauri-apps/cli-win32-ia32-msvc': 2.11.4
|
||||||
|
'@tauri-apps/cli-win32-x64-msvc': 2.11.4
|
||||||
|
|
||||||
|
'@tauri-apps/plugin-notification@2.3.3':
|
||||||
|
dependencies:
|
||||||
|
'@tauri-apps/api': 2.11.1
|
||||||
|
|
||||||
|
'@tauri-apps/plugin-shell@2.3.5':
|
||||||
|
dependencies:
|
||||||
|
'@tauri-apps/api': 2.11.1
|
||||||
|
|
||||||
'@testing-library/dom@10.4.1':
|
'@testing-library/dom@10.4.1':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/code-frame': 7.29.7
|
'@babel/code-frame': 7.29.7
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user