Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 43 additions & 36 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,47 +1,54 @@
# APP AMOR
# SECCURITY

APP AMOR em stack premium com React, Vite, TypeScript, Framer Motion e Capacitor, mantendo pipeline de APK.
SECCURITY e uma shell desktop para operacoes locais de seguranca em Windows. O app combina uma interface React + Electron com persistencia em SQLite para organizar inventario de ferramentas, workspaces operacionais, sessoes, eventos, alertas e um console PowerShell integrado.

## Stack

- Electron + Vite + React + TypeScript
- `better-sqlite3` para persistencia local
- IPC entre renderer e main process para launchers, terminal e leitura do runtime
- `electron-builder` para empacotamento Windows

## Estrutura

- `src/app/`: bootstrap da aplicacao e navegacao por capitulos
- `src/components/`: blocos visuais reutilizaveis
- `src/sections/`: capitulos da experiencia
- `src/data/content.ts`: hero, quiz, galeria, timeline, memorias, promessas e textos finais
- `src/services/`: acesso isolado a storage, audio, countdown e browser APIs
- `src/styles/`: tokens, globals, layout e componentes base
- `public/assets/`: fotos finais tratadas por categoria (`hero`, `highlights`, `timeline`, `gallery`, `memories`)
- `scripts/prepare-photos.mjs`: prepara as fotos do ZIP em WebP
- `android/`: projeto Android do Capacitor
- `ios/`: projeto iOS do Capacitor
- `www/`: build web pronto para sync do Capacitor

## Comandos

- `npm run dev`: ambiente local com Vite
- `npm run lint`: verificacao com ESLint
- `npm run typecheck`: verificacao TypeScript strict
- `npm run build`: gera a pasta `www`
- `npm run photos:prepare`: converte e organiza as fotos em WebP
- `npm run cap:sync`: build web + sync do Capacitor
- `npm run android:open`: abre o projeto Android
- `npm run ios:open`: abre o projeto iOS
- `npm run apk:debug`: gera APK debug em `android/app/build/outputs/apk/debug/`
- `npm run apk:release`: gera APK release em `android/app/build/outputs/apk/release/`
- `src/app/`: shell do renderer, navegacao lateral e bootstrap da UI
- `src/features/`: telas de overview, apps, workspaces, sessions, alerts, events, console e logs
- `src/hooks/`: estado do desktop e sincronizacao do renderer com a API local
- `src/services/`: cliente IPC/browser preview usado pelo renderer
- `electron/`: main process, IPC, terminal, scans, launchers e servicos de runtime
- `backend/`: schema SQLite, paths e repositorios locais
- `shared/`: contratos de tipos e catalogo de ferramentas
- `build/`: assets de packaging

## Scripts

- `npm run dev`: sobe o renderer Vite, compila o processo Electron e abre o app desktop
- `npm run build`: gera `dist/` e `dist-electron/`
- `npm run dist`: empacota a aplicacao com `electron-builder`
- `npm run dist:win`: gera artefatos Windows
- `npm run lint`: executa ESLint
- `npm run typecheck`: executa o TypeScript no renderer
- `npm run typecheck:electron`: executa o TypeScript do main process
- `npm run rebuild:native`: recompila modulos nativos para a versao local do Electron
- `npm run sign:local`: aplica assinatura local para builds Windows

## Fluxo recomendado

1. `npm install`
2. `npm run photos:prepare`
3. `npm run lint`
4. `npm run typecheck`
5. `npm run build`
6. `npm run cap:sync`
7. `npm run apk:debug`
2. `npm run lint`
3. `npm run typecheck`
4. `npm run build`
5. `npm run dist:win`

## Estado atual

- A shell principal do SECCURITY esta em producao no renderer atual.
- O bootstrap oferece modo `browser-preview` quando o preload do Electron nao esta disponivel.
- O backend local persiste logs, eventos, sessoes, workspaces e alertas em SQLite.
- O modulo de updates ainda usa resposta mockada.

## Observacoes

- Execute tudo a partir de `apps/nossa-historia`.
- O countdown atual considera a data alvo em `src/data/content.ts`.
- O app valida memorias, datas e imagens antes de persistir no storage.
- Como o projeto esta no OneDrive, builds Android ainda podem sofrer lock de arquivos. Se isso acontecer, limpe `android/app/build` e `android/build` antes de rodar de novo.
- No PowerShell do Windows, se `npm` estiver bloqueado pela execution policy, use `npm.cmd`.
- O banco local e criado em `app.getPath("userData")/seccurity.db`.
- O repositorio ainda pode conter alguns artefatos historicos fora da shell principal; a aplicacao ativa descrita aqui e a desktop shell do SECCURITY.
12 changes: 11 additions & 1 deletion backend/db/repositories/toolsRepo.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { DetectedTool, ToolId, ToolPathSource } from "../../../shared/types";
import { getDatabase } from "../db";
import { toolCatalog } from "../../../shared/toolCatalog";

export interface ToolRow {
tool_id: ToolId;
Expand Down Expand Up @@ -39,6 +40,7 @@ function mapRow(row: ToolRow): ToolRow {

export function listToolRows(): ToolRow[] {
const database = getDatabase();
const activeToolIds = new Set(toolCatalog.map((tool) => tool.id));
const rows = database
.prepare(
`
Expand All @@ -63,7 +65,7 @@ export function listToolRows(): ToolRow[] {
)
.all() as ToolRow[];

return rows.map(mapRow);
return rows.map(mapRow).filter((row) => activeToolIds.has(row.tool_id));
}

export function getToolRowMap(): Map<ToolId, ToolRow> {
Expand Down Expand Up @@ -170,6 +172,14 @@ export function upsertTool(tool: DetectedTool): void {
export function persistTools(tools: DetectedTool[]): void {
const database = getDatabase();
const transaction = database.transaction((entries: DetectedTool[]) => {
const activeToolIds = entries.map((entry) => entry.id);
if (activeToolIds.length > 0) {
const placeholders = activeToolIds.map(() => "?").join(", ");
database
.prepare(`DELETE FROM tools WHERE tool_id NOT IN (${placeholders})`)
.run(...activeToolIds);
}

entries.forEach(upsertTool);
});

Expand Down
12 changes: 11 additions & 1 deletion backend/db/repositories/workspacesRepo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ interface WorkspaceAssignmentRow {
}

const defaultWorkspaceId = "primary-workspace";
const activeToolIds = new Set(toolCatalog.map((tool) => tool.id));

function mapAssignmentRow(row: WorkspaceAssignmentRow): WorkspaceAppAssignment {
return {
Expand All @@ -54,7 +55,7 @@ export function getAssignmentsForWorkspace(workspaceId: string): WorkspaceAppAss
)
.all(workspaceId) as WorkspaceAssignmentRow[];

return rows.map(mapAssignmentRow);
return rows.map(mapAssignmentRow).filter((assignment) => activeToolIds.has(assignment.toolId));
}

export function ensureDefaultWorkspace(): void {
Expand Down Expand Up @@ -98,6 +99,15 @@ export function ensureDefaultWorkspace(): void {
`);

const transaction = database.transaction(() => {
database
.prepare(
`
DELETE FROM workspace_apps
WHERE workspace_id = ? AND tool_id NOT IN (${toolCatalog.map(() => "?").join(", ")})
`
)
.run(defaultWorkspaceId, ...toolCatalog.map((tool) => tool.id));

toolCatalog.forEach((tool, index) => {
upsertAssignment.run(
defaultWorkspaceId,
Expand Down
12 changes: 0 additions & 12 deletions capacitor.config.ts

This file was deleted.

1 change: 1 addition & 0 deletions electron/preload.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { terminalChannels } from "./services/ipcChannels";
const desktopApi: DesktopApi = {
bootstrap: () => ipcRenderer.invoke("seccurity:bootstrap"),
listTools: () => ipcRenderer.invoke("tools:list"),
getToolIcon: (executablePath: string | null) => ipcRenderer.invoke("tools:get-icon", executablePath),
saveTool: (input: ToolSaveInput) => ipcRenderer.invoke("tools:save", input),
browseToolExecutablePath: (toolId: ToolId) => ipcRenderer.invoke("tools:browse-executable", toolId),
listWorkspaces: () => ipcRenderer.invoke("workspaces:list"),
Expand Down
2 changes: 2 additions & 0 deletions electron/services/ipc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { listProviders } from "./providerService";
import { listSessions } from "./sessionService";
import {
getCachedTools,
getToolIcon,
scanInstalledTools,
setManualToolExecutablePath,
launchTool,
Expand Down Expand Up @@ -89,6 +90,7 @@ function buildBootstrap(): DesktopBootstrap {

export function registerIpcHandlers(): void {
ipcMain.handle("tools:list", () => getCachedTools());
ipcMain.handle("tools:get-icon", (_event, executablePath: string | null) => getToolIcon(executablePath));

ipcMain.handle("tools:save", (_event, input: ToolSaveInput) =>
setManualToolExecutablePath(input.toolId, input.executablePath)
Expand Down
74 changes: 72 additions & 2 deletions electron/services/toolService.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import fs from "node:fs";
import path from "node:path";
import { spawnSync } from "node:child_process";
import { shell } from "electron";
import { app, shell } from "electron";
import { clearRegistryCache, findRegistryInstallPaths } from "../../backend/registry";
import {
getToolRowMap,
Expand Down Expand Up @@ -56,6 +56,61 @@ function expandTemplate(template: string): string {
return template.replace(/\{([^}]+)\}/g, (_, token: keyof typeof pathTokens) => pathTokens[token] ?? "");
}

function wildcardSegmentToRegex(segment: string): RegExp {
const escaped = segment.replace(/[.+?^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*");
return new RegExp(`^${escaped}$`, "i");
}

function expandWildcardPath(templatePath: string): string[] {
const normalized = path.normalize(templatePath);
if (!normalized.includes("*")) {
return [normalized];
}

const parsed = path.parse(normalized);
const root = parsed.root || path.sep;
const remainder = normalized.slice(root.length);
const segments = remainder.split(path.sep).filter(Boolean);

let currentRoots = [root];

for (const segment of segments) {
const hasWildcard = segment.includes("*");
const nextRoots: string[] = [];

for (const currentRoot of currentRoots) {
if (!hasWildcard) {
nextRoots.push(path.join(currentRoot, segment));
continue;
}

try {
const entries = fs.readdirSync(currentRoot, { withFileTypes: true });
const matcher = wildcardSegmentToRegex(segment);
for (const entry of entries) {
if (!entry.isDirectory() && segment !== segments.at(-1)) {
continue;
}

if (matcher.test(entry.name)) {
nextRoots.push(path.join(currentRoot, entry.name));
}
}
} catch {
continue;
}
}

currentRoots = unique(nextRoots);

if (currentRoots.length === 0) {
break;
}
}

return currentRoots;
}

function findOnPath(executableNames: string[]): string[] {
const matches: string[] = [];

Expand Down Expand Up @@ -191,7 +246,9 @@ function resolveAutomaticPath(definition: ToolDefinition): AutomaticDetectionRes
};
}

const commonPath = definition.commonPathTemplates.map(expandTemplate).find(fileExists);
const commonPath = definition.commonPathTemplates
.flatMap((template) => expandWildcardPath(expandTemplate(template)))
.find(fileExists);
if (commonPath) {
return {
source: "common",
Expand Down Expand Up @@ -305,6 +362,19 @@ export function setManualToolExecutablePath(
};
}

export async function getToolIcon(executablePath: string | null): Promise<string | null> {
if (!executablePath || !fileExists(executablePath)) {
return null;
}

try {
const icon = await app.getFileIcon(executablePath, { size: "small" });
return icon.isEmpty() ? null : icon.toDataURL();
} catch {
return null;
}
}

export async function launchTool(
toolId: ToolId,
launchSource = "launcher",
Expand Down
Loading