22 KiB
SYSTEM Fullstack Architecture Document
1. Template and Framework Selection
- Framework: The project is built on Nuxt 4, as specified in the provided
repomix-output.xml. - UI Library: The core component library is PrimeVue, with the Aura theme preset. This choice is confirmed in
nuxt.config.ts. - Styling: A hybrid approach using Tailwind CSS for utility-first styling and SCSS for global styles, variables, and complex component styling is adopted.
- Starter Template: The project is based on the custom template provided by the user. No external starter templates are used. This gives us full control but requires us to define the structure meticulously.
Change Log
| Date | Version | Description | Author |
|---|---|---|---|
| 2025-08-31 | 1.0 | Initial Frontend Architecture draft | Architect (Winston) |
2. Frontend Tech Stack
This table reflects the choices made in the PRD and the user's template, solidifying the frontend technology stack.
| Category | Technology | Version | Purpose & Rationale |
|---|---|---|---|
| Framework | Nuxt | ^4.0.3 |
The core application framework, providing SSR, file-based routing, and a robust module ecosystem. |
| UI Library | PrimeVue | ^4.3.7 |
The primary component library. Chosen for its rich set of components and powerful theming capabilities. |
| Theme | PrimeUI Aura | ^1.2.3 |
The default theme preset for PrimeVue, providing a modern and clean aesthetic out of the box. |
| Styling | Tailwind CSS | ^0.6.1 (primeui) |
For utility-first styling, rapid prototyping, and responsive design. |
| Styling | SCSS (Sass) | (via sass dev dep) |
For structured styling, variables, mixins, and managing global styles and theme overrides. |
| i18n | @nuxtjs/i18n | 10.0.6 |
For future-proof internationalization, as required by the PRD. |
3. Project Structure
Based on the Nuxt 4 documentation and the need to manage SCSS files effectively, the following directory structure will be adopted within the app/ directory.
app/
├── assets/
│ └── scss/
│ ├── base/
│ │ ├── _reset.scss # Optional: for CSS resets
│ │ └── _typography.scss # Base typography styles
│ ├── components/
│ │ └── _buttons.scss # Example: Custom button styles
│ ├── layout/
│ │ └── _main.scss # Main layout styles
│ ├── themes/
│ │ └── _aura-overrides.scss # Specific overrides for the Aura theme
│ ├── utils/
│ │ ├── _variables.scss # Global SCSS variables (colors, spacing)
│ │ └── _mixins.scss # Global SCSS mixins
│ └── main.scss # Main entry file that imports all other SCSS partials
│
├── components/
│ ├── common/
│ │ └── AppHeader.vue # Example of a globally used component
│ └── specific/
│ └── CellDetailView.vue # Example of a view-specific component
│
├── composables/
│ ├── useCitizen.ts # Composable for managing Citizen identity
│ └── useLedger.ts # Composable for interacting with The Main Ledger
│
├── layouts/
│ └── default.vue # The default layout for the application
│
├── pages/
│ ├── index.vue # Dashboard / Main View
│ └── cell/
│ └── [id].vue # Cell Detail View (Dynamic Route)
│
├── plugins/
│ └── primevue.ts # Plugin to register PrimeVue components/services if needed
│
└── app.vue # Main application entry point
To make this SCSS structure work seamlessly, we will update nuxt.config.ts to automatically import our main SCSS file globally.
nuxt.config.ts modification:
export default defineNuxtConfig({
// ... other configs
css: ['~/assets/scss/main.scss'],
// ... other configs
})
app/assets/scss/main.scss content:
// 1. Utilities (Variables, Mixins)
@use 'utils/variables';
@use 'utils/mixins';
// 2. Base Styles
@use 'base/reset';
@use 'base/typography';
// 3. Layout Styles
@use 'layout/main';
// 4. Component Styles
@use 'components/buttons';
// 5. Theme Overrides
@use 'themes/aura-overrides';
4. Component Standards
To ensure consistency and maintainability, all Vue components created in this project must adhere to the following standards.
Component Template (.vue files)
All components should follow the standard Vue 3 <script setup> syntax. This is the recommended modern approach by both Vue and Nuxt documentation.
Here is a minimal template for a new component:
<script setup lang="ts">
// 1. IMPORTS
// e.g., import { ref } from 'vue';
// e.g., import Button from 'primevue/button';
// 2. PROPS & EMITS
// e.g., const props = defineProps<{
// citizenId: string;
// }>();
// e.g., const emit = defineEmits(['updated']);
// 3. STATE & COMPOSABLES
// e.g., const count = ref(0);
// e.g., const { citizen } = useCitizen();
// 4. COMPUTED PROPERTIES & WATCHERS
// ...
// 5. LIFECYCLE HOOKS
// ...
// 6. METHODS
// e.g., function signTransaction() { ... }
</script>
<template>
<!--
Component template. Use PrimeVue components and Tailwind CSS classes here.
e.g., <div class="p-4 border-round-md"> ... </div>
-->
</template>
<style scoped lang="scss">
/*
Component-specific SCSS goes here.
Global variables/mixins are available thanks to nuxt.config.ts.
e.g., .my-component { color: var(--primary-color); }
*/
</style>
Naming Conventions
- Components:
PascalCase. Files should match the component name (e.g.,CellDetailView.vue). - Composables:
camelCasewith auseprefix (e.g.,useLedger.ts). - SCSS Variables:
$kebab-case(e.g.,$primary-accent-color).
5. State Management
- Approach: For the MVP, we will rely primarily on Nuxt's built-in
useStatecomposable for simple, shared, server-side-renderable state. This is the most lightweight and idiomatic approach for Nuxt 4. We will avoid installing a heavy external library like Pinia or Vuex until the application's complexity demonstrably requires it. - Structure: Shared state composables will be located in the
app/composables/directory.
State Management Template (app/composables/useSomeState.ts)
import { useState } from '#app';
// Example: Managing the current Citizen's session info
interface CitizenSession {
name: string;
publicKey: string;
// NOTE: The private key should NOT be stored in a composable.
// It should be managed in a more secure, non-persistent way.
}
export const useCitizenStore = () => {
const citizen = useState<CitizenSession | null>('citizen', () => null);
const setCitizen = (newCitizen: CitizenSession) => {
citizen.value = newCitizen;
};
const clearCitizen = () => {
citizen.value = null;
};
return {
citizen,
setCitizen,
clearCitizen,
};
};
6. API Integration
Since the project is a monolith where the frontend and backend are served by the same Nuxt instance, we will use the built-in $fetch utility for all API calls. This is the recommended, isomorphic way to handle data fetching in Nuxt 4.
Service Layer Pattern
To keep components clean and organize API logic, we will use a "service layer" pattern. All API calls will be abstracted into functions within files in the app/services directory.
app/services/cellService.ts (Example Template)
// Using Nuxt's built-in $fetch
import { $fetch } from 'ofetch';
// Define types for API payloads and responses, ideally imported from a shared location
interface Cell {
id: string;
// ... other properties
}
interface CreateCellPayload {
commonGood: string;
// ... other properties
}
const BASE_URL = '/api/cells'; // Internal API endpoint
export const cellService = {
/**
* Fetches the details for a single Cell.
* @param id The ID of the Cell.
*/
async getCellById(id: string): Promise<Cell> {
return await $fetch<Cell>(`${BASE_URL}/${id}`);
},
/**
* Creates a new Cell.
* @param payload The data for the new Cell.
*/
async createCell(payload: CreateCellPayload): Promise<Cell> {
return await $fetch<Cell>(BASE_URL, {
method: 'POST',
body: payload,
});
},
// ... other methods like joinAsConsumer, confirmPayment, etc.
};
7. Routing
Routing will be handled by Nuxt's file-based router. All pages will be created as .vue files in the app/pages directory.
Route Configuration Example (app/pages/cell/[id].vue)
<script setup lang="ts">
import { useRoute } from 'vue-router';
const route = useRoute();
const cellId = route.params.id as string;
// Fetch cell data using the service
const { data: cell, pending, error } = await useAsyncData(
`cell-${cellId}`,
() => cellService.getCellById(cellId)
);
</script>
<template>
<div v-if="pending">Loading...</div>
<div v-else-if="error">Error loading Cell: {{ error.message }}</div>
<div v-else-if="cell">
<h1>{{ cell.commonGood }}</h1>
<!-- Display cell details here -->
</div>
</template>
8. Testing Requirements
Testing is critical for ensuring the integrity of the system.
- Unit Tests: We will use Vitest for unit testing. Every composable and service function must have unit tests covering its logic.
- Component Tests: We will use
@vue/test-utilsto test individual Vue components. Tests should verify that components render correctly based on props and that they emit events when interacted with. - Test Location: All test files will be located alongside the file they are testing, with a
.spec.tsextension (e.g.,useLedger.spec.ts).
1. Introduction
This document outlines the complete fullstack architecture for Project SYSTEM, including the backend systems, frontend implementation, and their integration. It serves as the single source of truth for AI-driven development, ensuring consistency across the entire technology stack. This unified approach combines backend and frontend concerns to streamline development.
Starter Template or Existing Project
- Decision: The project is based on the custom Nuxt 4 starter template provided by the user. No external templates are used. The architecture will be built upon this existing foundation.
Change Log
| Date | Version | Description | Author |
|---|---|---|---|
| 2025-08-31 | 1.0 | Initial Fullstack Architecture draft | Architect (Winston) |
2. High Level Architecture
Technical Summary
Project SYSTEM is a monolithic, self-hosted web application built on the Nuxt 4 framework. The architecture is designed for efficiency and transparency, running on a Raspberry Pi. It features a Vue-based frontend interacting with a PostgreSQL database via Nuxt's server-side API routes. The core of the system is an immutable, event-sourced "Main Ledger" where every action is a cryptographically signed transaction, ensuring data integrity and auditability. Identity management is decentralized, based on public/private key pairs.
Platform and Infrastructure Choice
- Platform: Self-hosted on a user-provided Raspberry Pi (Model 4+).
- Key Services:
- Application Server: Nuxt 4 running in Node.js mode.
- Database: PostgreSQL.
- Containerization: The entire stack will be managed and deployed via Docker Compose. This ensures a consistent and reproducible environment.
- Deployment Host and Regions: Single host, located within the "Община's" private network.
Repository Structure
- Structure: Monorepo.
- Monorepo Tool: Not required for this level of simplicity. A single
package.jsonat the root will manage all dependencies. - Package Organization: A standard Nuxt 4 project structure will be used, as detailed in the Frontend Architecture. Server-side logic will reside within the
server/directory.
High Level Architecture Diagram
graph TD
subgraph "Browser"
A[Citizen's Browser]
end
subgraph "Raspberry Pi (Docker Compose)"
B[Nuxt 4 Server]
C[PostgreSQL Database]
B -- "Serves Frontend" --> A
A -- "API Calls ($fetch)" --> B
B -- "Reads/Writes" --> C
end
style B fill:#2D3748,stroke:#fff,stroke-width:2px,color:#fff
style C fill:#4A5568,stroke:#fff,stroke-width:2px,color:#fff
3. Tech Stack (Revised)
| Category | Technology | Version | Purpose & Rationale |
|---|---|---|---|
| Fullstack Framework | Nuxt | ^4.0.3 |
Core framework for both frontend and backend (server routes). |
| Database ORM | Prisma | latest |
Next-generation ORM for type-safe database access and automated migrations. |
| Database | PostgreSQL | latest |
Robust, reliable SQL database managed by Prisma. |
| Deployment | Docker Compose | latest |
For containerizing and managing the application stack. |
| ... | (rest of the stack remains the same) | ... | ... |
4. Database Architecture (Revised with Prisma)
The database schema will be defined declaratively using the Prisma schema language in a single source of truth file: prisma/schema.prisma. Prisma will be responsible for generating SQL migrations and providing a type-safe client for all database interactions.
Prisma Schema (prisma/schema.prisma)
// This is your Prisma schema file,
// learn more about it in the docs: https://pris.ly/d/prisma-schema
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
// User-friendly metadata associated with a public key.
model Registry {
publicKey String @id
citizenName String
avatarUrl String?
isActive Boolean @default(true)
createdAt DateTime @default(now())
}
// The core, immutable data model.
// Once a row is written, it is never updated or deleted.
model LedgerEntry {
id BigInt @id @default(autoincrement())
previousHash String?
entryHash String @unique
citizenPublicKey String
signature String
actionType String
payload Json? // The actual data for the action, e.g., cell details
createdAt DateTime @default(now())
@@index([citizenPublicKey])
@@index([payload], name: "idx_ledger_payload_entity") // Example for indexing specific payload keys
}
Migrations Workflow
Database migrations will be managed exclusively by Prisma Migrate.
- Development: After any change to
schema.prisma, a new migration will be created and applied using the command:npx prisma migrate dev --name <descriptive_migration_name> - Production: Migrations will be applied during the deployment process using:
npx prisma migrate deploy
This ensures that the database schema is always in sync with the Prisma schema definition.
5. API Specification & Server Architecture
The application will not expose a public REST или GraphQL API. All communication between the frontend and backend will occur internally via Nuxt 4 Server Routes. This approach is secure, performant, and perfectly suited for a monolithic fullstack application.
Server Directory Structure (server/)
The server-side logic will be organized as follows:
server/
├── api/
│ ├── cells/
│ │ ├── index.post.ts # Create a new Cell
│ │ └── [id]/
│ │ ├── index.get.ts # Get Cell details
│ │ └── join.post.ts # Join a Cell (as consumer/executor)
│ ├── citizens/
│ │ ├── index.post.ts # Create a new Citizen (onboarding)
│ │ └── invite.get.ts # Generate an invitation link
│ └── vault/
│ ├── index.post.ts # Create a Vault account
│ ├── login.post.ts # Login via Vault
│ └── recover.post.ts # Initiate/approve social recovery
├── middleware/
│ └── auth.ts # Middleware to check Citizen authentication
└── utils/
├── prisma.ts # Exports a singleton Prisma Client instance
└── crypto.ts # Cryptographic helper functions
Core API Endpoints (MVP)
| Endpoint | Method | Description | Request Body | Response |
|---|---|---|---|---|
/api/citizens |
POST |
Creates the first Citizen or a new one via invite. | { name, publicKey } |
Citizen |
/api/citizens/invite |
GET |
Generates a single-use invitation link. | (none) | { inviteUrl } |
/api/cells |
POST |
Creates a new "Cell". | { commonGood, executors } |
LedgerEntry |
/api/cells/[id] |
GET |
Retrieves the full state of a "Cell". | (none) | CellState |
/api/cells/[id]/join |
POST |
Joins a "Cell" as Executor or Consumer. | { role, item? } |
LedgerEntry |
/api/cells/[id]/confirm |
POST |
Confirms payment from a Consumer. | { consumerKey } |
LedgerEntry |
Core Workflow: The Cryptographic Signing Process
This sequence diagram illustrates the fundamental process for every action in the system.
sequenceDiagram
participant Browser as Citizen's Browser
participant NuxtServer as Nuxt Server / API
participant Prisma
participant Ledger as The Main Ledger (DB)
Browser->>Browser: 1. User performs action (e.g., clicks "Create Cell")
Browser->>Browser: 2. Prepare `payload` (e.g., { commonGood: "Суп" })
Browser->>Browser: 3. Get `previousHash` from last Ledger entry
Browser->>Browser: 4. Create `entryHash` from payload + previousHash
Browser->>Browser: 5. Sign `entryHash` with private key to get `signature`
Browser->>NuxtServer: 6. POST /api/cells (sends payload, publicKey, signature)
NuxtServer->>NuxtServer: 7. Receive request
NuxtServer->>NuxtServer: 8. Validate signature using sender's `publicKey`
alt Signature is Invalid
NuxtServer-->>Browser: 9a. Return 401 Unauthorized
else Signature is Valid
NuxtServer->>Prisma: 9b. Get latest `previousHash` from DB
NuxtServer->>NuxtServer: 10. Verify client's `previousHash` matches server's
NuxtServer->>Prisma: 11. `prisma.ledgerEntry.create(...)`
Prisma->>Ledger: 12. INSERT INTO "LedgerEntry"
Ledger-->>Prisma: 13. Return new entry
Prisma-->>NuxtServer: 14. Return new entry
NuxtServer-->>Browser: 15. Return new LedgerEntry (success)
end
6. Unified Project Structure
This is the final monorepo structure that integrates the frontend (app/), backend (server/), and database (prisma/) components.
system/
├── app/ # Nuxt frontend application
│ ├── assets/
│ │ └── scss/
│ ├── components/
│ ├── composables/
│ ├── layouts/
│ ├── pages/
│ └── ...
├── prisma/ # Prisma ORM configuration
│ ├── migrations/
│ │ └── .../migration.sql
│ └── schema.prisma # The single source of truth for the database
├── server/ # Nuxt server-side logic (API)
│ ├── api/
│ │ └── ...
│ ├── middleware/
│ └── utils/
├── types/ # Shared TypeScript types
│ ├── citizen.ts
│ ├── ledger.ts
│ └── index.ts
├── docker-compose.yml # Defines and runs the multi-container stack
├── Dockerfile # For building the Nuxt application image
├── nuxt.config.ts # Nuxt configuration
├── package.json
└── tsconfig.json
7. Development Workflow
- Initial Setup:
- Clone the repository.
- Create a
.envfile with theDATABASE_URL. - Run
docker-compose up -d --buildto start the PostgreSQL container and the Nuxt development server.
- Schema Changes:
- Modify
prisma/schema.prisma. - Run
npx prisma migrate dev --name <migration-name>to apply changes to the running database. Prisma Client will be auto-generated.
- Modify
- Development:
- Develop Vue components in the
app/directory. - Develop API endpoints in the
server/api/directory. - The Nuxt dev server will provide hot-reloading for both frontend and backend changes.
- Develop Vue components in the
8. Deployment Architecture
- Strategy: The application will be deployed as a set of Docker containers orchestrated by
docker-compose. This simplifies deployment on the target Raspberry Pi. - Process:
- A
Dockerfilewill build the Nuxt application into a production-ready Node.js server. docker-compose.ymlwill define two services:app: The Nuxt application built from theDockerfile.db: The official PostgreSQL image, with a persistent volume mounted to store the database data.
- Deployment consists of pulling the latest code, running
docker-compose up -d --buildon the Raspberry Pi.
- A
- Migrations: Production migrations will be applied by running
npx prisma migrate deployinside the runningappcontainer or as part of the container's startup command.
9. Security & Performance
- Security:
- Authentication: Managed via cryptographic signatures. The server is stateless regarding authentication.
- Authorization: All API endpoints inside
server/middleware/auth.tswill verify the incoming signature against thepublicKeyto ensure the action is performed by the legitimate owner of the key. - Secrets: The
DATABASE_URLwill be managed via the.envfile, which is excluded from version control.
- Performance:
- The primary performance consideration is the Raspberry Pi environment. The monolithic Nuxt/PostgreSQL stack is chosen for its low resource overhead compared to more complex microservice architectures.
- Database queries will be optimized using indices as defined in the Prisma schema.
- Frontend assets will be optimized by Nuxt's build process.