Technology
Every technology used in Saha Doküman
Below are the technologies used in every layer of the application, with the versions that actually run and their role in Saha Doküman. Nothing was added to make the list longer.
Versions come from the project’s package records. They are the exact versions in use, and Docker images are pinned to an exact version too.
Backend
The API is a single deployable on .NET 10, written as Minimal APIs without controllers.
| Technology | Version | Its job in Saha Doküman |
|---|---|---|
| .NET / C# | 10 (SDK 10.0.300) | All of the server side is written in C# on .NET 10, built with nullable reference checks and warnings treated as errors. |
| ASP.NET Core Minimal APIs | 10.0 | Every endpoint is a Minimal API declared in a single file per feature folder; no controllers are used. |
| Microsoft.AspNetCore.OpenApi | 10.0.13 | Generates the API contract as an OpenAPI 3.1 document; the web and mobile models are written against it. |
| Scalar.AspNetCore | 2.18.0 | Provides an interactive reference UI for trying the API in development (switched off in production). |
| JWT Bearer | 10.0.13 | Validates access tokens; refresh tokens rotate on every use. Roles are never put in the token, they are read from the permission engine on every request. |
| PasswordHasher | 10.0 | Passwords are stored as hashes made by ASP.NET Core’s PasswordHasher; no plain password is kept. |
| Rate limiting | 10.0 | Per-IP and per-user request limits on sign-in and other sensitive routes; brute-force attempts lock the account. |
| Entity Framework Core | 10.0.13 | The object-relational mapping layer for data access and schema migrations. |
| Npgsql EF Core provider | 10.0.3 | The PostgreSQL provider for EF Core; it uses PostgreSQL-specific types and indexes. |
| Markdig | 1.4.1 | Page content is stored as restricted Markdown; Markdig validates it on the syntax tree and turns it into plain text for search. |
| SkiaSharp | 4.153.1 | Every uploaded image is decoded and re-encoded: location and EXIF metadata are dropped, the size is limited and a thumbnail is made. |
| DocumentFormat.OpenXml | 3.5.1 | Imports Word files, extracts search text from Word, Excel and PowerPoint files, and reads the Excel user-import template. |
| PdfPig | 0.1.16 | Extracts search text from PDF files and keeps page numbers for citations. It runs in a separate child process to isolate broken PDFs. |
| Serilog | 4.3.1 | Writes structured log files; tokens, passwords and query-string values are masked before they reach the log. |
| MailKit | 4.18.1 | Sends the platform’s e-mail outbox over SMTP: the new company admin’s set-password link and subscription renewal reminders. |
| TimeProvider | .NET 10 | All clock reads go through TimeProvider; tests control time with a fake clock and the database’s microsecond precision is respected. |
Data and search
All content, ledgers and the search index live in PostgreSQL; no separate search server is needed.
| Technology | Version | Its job in Saha Doküman |
|---|---|---|
| PostgreSQL | 18.6 | The single data store for pages, versions, ledgers, the search index, knowledge chunks and the audit log; the cluster is created with Turkish (ICU tr-TR) collation. |
| tsvector + GIN | Full-text search uses GIN-indexed tsvector columns; the vector is built in C# from the same word rules as the query. | |
| TurkishNormalizer | Normalises Turkish and Arabic letters with an explicit, length-preserving table; search normalisation happens only on the server. | |
| ArabicWords | Resolves Arabic word forms (such as the definite article) so a search without the “ال” prefix still finds the word. | |
| Advisory locks | Transaction-level application locks serialise concurrent work such as publishing and indexing. | |
| xmin concurrency | Concurrent edits are detected with PostgreSQL’s xmin column (optimistic concurrency) instead of silently overwriting. | |
| Append-only ledgers | Publishing events and contribution-check records are append-only; the runtime role’s database rights exclude update and delete. |
AI and RAG
Semantic search runs locally; answer generation is optional and only uses permitted excerpts.
| Technology | Version | Its job in Saha Doküman |
|---|---|---|
| Ollama | 0.35.1 | Runs the embedding model locally on the server; it sits on the internal network only and has no published port. |
| bge-m3 | 1024 dim. | A multilingual embedding model (Turkish included) that produces a 1024-dimension vector for every knowledge chunk. |
| Microsoft.Extensions.AI | 10.10.0 | An abstraction for the embedding generator: Ollama in production, a deterministic fake in tests. |
| OllamaSharp | 5.5.0 | The client that connects the Microsoft.Extensions.AI abstraction to the local Ollama server. |
| System.Numerics.Tensors | 10.0.13 | SIMD-accelerated dot products and norms for the in-memory vector index; the permission filter is applied before scoring. |
| Hybrid retrieval + RRF | The top 40 results from vector and full-text search are merged with Reciprocal Rank Fusion (RRF); rule chunks get a small boost. | |
| Anthropic C# SDK | 12.58.0 | The official C# SDK used to produce the assistant’s answers through the Claude API as a stream; error types and retries are typed. |
| claude-sonnet-5-5 | default model | The default model is claude-sonnet-5-5; model and effort level are set in configuration, and the API key is read only from configuration or the secret store. |
| Citations + prompt caching | Excerpts are sent as document blocks with citations enabled, so each statement links to its source block; the system prompt is cached. | |
| ModelContextProtocol.AspNetCore | 2.2.0 | A read-only MCP server at /mcp built with the official MCP C# SDK, with the tools search_documentation, get_page and list_rules. |
| Retrieval API | POST /api/rag/retrieve for external systems: returns chunks with text, citation URL, block id, version and full provenance. | |
| Personal access tokens | Personal access tokens with a scope, an expiry and revocation; only a hash is stored and every call acts with its owner’s permissions. |
Web
The web interface of the application, built with types generated from the API contract.
| Technology | Version | Its job in Saha Doküman |
|---|---|---|
| React | 19.3.1 | The web interface is written with React 19 function components and hooks. |
| TypeScript | 5.9.3 | The web project is written in strict TypeScript; the API types are generated from the contract. |
| Vite | 8.3.4 | The build tool and dev server of the web app; it proxies the API locally. |
| Tailwind CSS | 4.3.3 | The interface is styled with Tailwind 4; logical utility classes keep the layout right-to-left ready for Arabic. |
| React Router | 7.18.4 | Page routing in the web app. |
| TanStack Query | 5.104.3 | Caches server state (lists, pages, permissions) and handles refetching and cancellation. |
| openapi-fetch | 0.17.0 | A small, type-safe client that calls the API with the OpenAPI types. |
| openapi-typescript | 7.13.0 | Generates the TypeScript types from the docs/openapi.json contract; the build fails when the contract changes. |
| TipTap + @tiptap/markdown | 3.32.0 | The rich-text engine of the block editor; it reads and writes content as Markdown. |
| react-markdown + remark-gfm | 10.1.0 / 4.0.1 | Displays content safely (raw HTML is never accepted); GFM tables and task lists are supported. |
| dnd-kit | 6.3.1 / 10.0.0 | Reorders blocks with drag and drop. |
| lucide-react | 1.59.0 | The icon set of the interface. |
| sonner | 2.0.8 | Shows feedback such as “saved” or “submitted” as toast notifications. |
| diff | 9.0.0 | Computes the difference between two versions in the version history. |
Mobile
One code base for Android and iOS; it also runs on the web, for smoke tests.
| Technology | Version | Its job in Saha Doküman |
|---|---|---|
| Flutter / Dart | 3.41.9 / 3.11 | Builds the Android and iOS app from one code base (Flutter 3.41, Dart 3.11). |
| flutter_riverpod | 3.3.2 | State management with Notifier, AsyncNotifier and Provider; no code generation. |
| go_router | 17.5.0 | Declarative page routing of the mobile app. |
| dio | 5.11.1 | The single HTTP client, with an auth interceptor that refreshes the token in a single flight. |
| flutter_secure_storage | 11.2.0 | Keeps the session tokens in the operating system’s secure store (Keychain / Keystore). |
| flutter_markdown_plus | 1.0.12 | Shows page content as Markdown behind a single AppMarkdown widget. |
| image_picker / file_picker | 1.2.4 / 13.1.0 | Photo and file selection; uploads are sent as bytes (MultipartFile.fromBytes). |
| url_launcher | 6.3.3 | Opens link blocks and external addresses in the browser. |
| flutter_localizations + intl | 0.20.2 | Localisation and date/number formats for the Turkish, Arabic (right-to-left) and English interface. |
| shared_preferences | 2.5.6 | Keeps non-secret user preferences. |
Infrastructure and operations
sahadoc.com runs on a single Linux server with Docker; companies get their own database and service container.
| Technology | Version | Its job in Saha Doküman |
|---|---|---|
| Docker + Compose | The production stack is defined with Docker Compose: PostgreSQL, Ollama and the API are separate containers; the database and Ollama sit on the internal network only. | |
| nginx | 1.24 | Terminates TLS and redirects www to the apex domain; it writes the X-Forwarded headers itself, and its access log leaves out the query string and the Authorization header. |
| Let's Encrypt | Free TLS certificates; a wildcard certificate for the company subdomains is issued with DNS validation. | |
| Multi-tenant hosting | In the SaaS each company runs with its own database, file folder and dependency container; keys derived per company make one company’s token useless at another. | |
| Backup + restore | The database and the files are backed up together, and the restore script keeps them together. | |
| Verification jobs | Jobs that verify the ledger chain and file integrity; in the SaaS they loop over every company database. | |
| Structured logs | Structured, daily-rolling log files with secrets removed; container logs are size-limited. |
Quality
Permissions and privacy are verified both by tests and by an adversarial audit.
| Technology | Version | Its job in Saha Doküman |
|---|---|---|
| xUnit v2 | 2.9.3 | The backend test framework (v2). |
| Shouldly | 4.3.0 | Readable assertions; FluentAssertions and Moq are deliberately not used. |
| NSubstitute | 6.2.0 | Substitutes dependencies in unit tests. |
| Respawn | 7.0.0 | Quickly resets the database between integration tests; every test collection has its own database. |
| ASP.NET Core test host | 10.0.13 | Tests the API end to end, in memory against a real PostgreSQL. |
| FakeTimeProvider | 10.11.0 | Tests time-dependent behaviour (reminders, session lifetimes) deterministically. |
| Persona x endpoint matrix | A persona × endpoint table for every endpoint: an unauthorised person must get 404, and every new endpoint is added to the table. | |
| OpenAPI snapshot + JSON samples | The OpenAPI document is a snapshot test; JSON samples feed the client tests; the contract is changed deliberately. | |
| Vitest | 5.0.5 | The web test runner. |
| Testing Library (React) | 16.3.3 | Tests components the way a user sees them. |
| MSW | 3.0.3 | Mocks the API at the network level in web tests. |
| flutter_test + mocktail | 1.0.5 | Mobile unit and widget tests; models are tested against the docs/samples JSON files. |
| Red-team audit | Every leak channel (search, files, notifications, assistant, API, MCP) is probed with unauthorised personas, and revocation scenarios are tested. |
The journey of a request
When a user searches or asks the assistant a question, this happens, in order.
Client
The web or mobile app (or an external tool over MCP) sends an HTTPS request.
nginx
TLS ends here; the client address and scheme headers are written reliably.
Identity and limits
The token is validated, the rate limit is applied and the company is determined (from the subdomain in the SaaS).
Permission
The permission engine says what the user may see; everything not visible is a 404.
Data and search
Full-text and vector candidates are fetched from PostgreSQL and filtered by permission.
AI (optional)
The selected permitted excerpts are checked again; if enabled, Claude writes the answer with sources.
Response
The result returns to the client with its source information, never cached by an intermediate layer.
- Client
- Service
- Data store
- External
- main path
- data flow
- in the background
- permission-filtered
Clients
- Web app — React 19
- Mobile app — Flutter · Android / iOS
- External AI — MCP · access token
One Saha Doküman application
- Saha Doküman API — .NET 10 · permission engine
- Assistant (RAG) — hybrid search + re-check
- Embedding worker — in the background
Data and AI
- PostgreSQL 18 — content · versions · ledger · knowledge chunks + vectors
- Ollama · bge-m3 — local embeddings
- Claude API — Anthropic · optional
The journey of a piece of information
From the moment someone writes it to the moment someone finds it: every step has an owner, and four checkpoints stand on the way.
- Client
- Checkpoint
- Service
- Data store
- External
- main path
- data flow
- in the background
- permission-filtered
Add
Draft
Written in the web or mobile app, from a template, or imported from Word.
Contribution check
Language review and similar-information check. If the information already exists, you see it first.
Before saving
Approve
Change request
The contribution goes to the department admin with its note.
Approval
The admin reviews it, corrects it if needed, and approves or rejects it.
Before publishing
Publish
One publishing service
The new version is written together with who added it and who published it.
Ledger
Append-only and linked by a chain of hashes: the record cannot be rewritten.
Index
Search index
Updated at once, ready for search in Turkish, Arabic and English.
Knowledge chunks
Sections, rules, tables and file pages become short excerpts that keep their source.
Local embeddings
Vectors are computed on the server with Ollama; the text does not leave.
Find and answer
Search
Only pages you may see; hidden ones do not show, not even as a count.
Permission filter
Assistant
Semantic and full-text results are merged; if the company enabled it, only permitted excerpts go to Claude and the answer cites them.
Checked again
Other AI tools
Through MCP with a personal access token, under the same permissions.
Checked again
Four checkpoints on the way
- Before savingEvery addition and change passes a contribution check.
- Before publishingA contributor’s change is published only after an admin approves it.
- Permission filterEvery result and every excerpt is filtered by the user’s permissions.
- Checked againEach excerpt is checked once more before it leaves for AI.
A technical question?
Let us go through the architecture, the installation and the security audit together.