REST API and persistence layer for the ZK attendance platform — a biometric time-and-attendance system built around ZKTeco fingerprint terminals.
ZKAPI owns the database. It exposes CRUD endpoints over employees, departments and attendance records, and it is the write target for the device collector that polls the physical terminals.
The platform is three repositories. This one is the middle layer.
┌──────────────────────┐
│ 8 × ZKTeco terminal │ 192.168.8.35 → .42, TCP 4370
│ (fingerprint/face) │
└──────────┬───────────┘
│ zkemkeeper COM SDK (pull)
▼
┌──────────────────────┐
│ ZUtility │ Windows service. Polls terminals, pairs raw
│ (device collector) │ punches into check-in/check-out sessions.
└──────────┬───────────┘
│ HTTP POST /api/Attendance
▼
┌──────────────────────┐
│ ZKAPI ← this repo│ ASP.NET Core + EF Core. Owns the MySQL schema.
│ (REST + database) │
└──────────┬───────────┘
│ HTTP GET (SWR)
▼
┌──────────────────────┐
│ FrontZK │ Next.js dashboard. Daily register, per-employee
│ (dashboard) │ history, monthly summaries.
└──────────────────────┘
| Repository | Role | Link |
|---|---|---|
| ZKAPI | REST API, MySQL schema | this repository |
| ZUtility | Device collector (Windows) | Heritina-sys/ZUtility |
| FrontZK | Web dashboard | Heritina-sys/FrontZK |
Full data flow, including the enroll-number translation that happens on write:
docs/ARCHITECTURE.md.
| Runtime | .NET 10 (net10.0), ASP.NET Core |
| ORM | Entity Framework Core 8.0.11 |
| Database | MySQL 8.0 via Pomelo.EntityFrameworkCore.MySql 8.0.2 |
| API docs | Swashbuckle / Swagger UI |
| Nullable | enabled |
- .NET 10 SDK
- MySQL 8.0 reachable from the host
CREATE DATABASE att CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;appsettings.json ships with an empty connection string on purpose — real
credentials must never be committed. Provide yours by environment variable:
export ConnectionStrings__DefaultConnection="server=localhost;port=3306;database=att;user=zkapi;password=<your-password>"or, for local development, with the .NET secret store:
cd ZKAPI
dotnet user-secrets init
dotnet user-secrets set "ConnectionStrings:DefaultConnection" "server=localhost;port=3306;database=att;user=zkapi;password=<your-password>"Both are read automatically — no code change needed. See
docs/CONFIGURATION.md for precedence rules and the
full setting list.
This project has no EF migrations committed (see Known limitations). Until migrations are added, generate the schema from the model:
cd ZKAPI
dotnet ef migrations add InitialCreate
dotnet ef database updatedotnet run --project ZKAPI| HTTP | http://localhost:5159 |
| HTTPS | https://localhost:7171 |
| Swagger UI | http://localhost:5159/swagger |
Three resources, conventional REST, no authentication (see Known
limitations). Full request/response schemas and examples:
docs/API.md.
| Method | Route | Purpose |
|---|---|---|
GET |
/api/Users |
List employees, department name joined |
GET |
/api/Users/{id} |
One employee |
POST |
/api/Users |
Create employee |
PUT |
/api/Users/{id} |
Update employee |
DELETE |
/api/Users/{id} |
Delete employee |
GET |
/api/Departments |
List departments |
GET |
/api/Departments/{id} |
One department |
POST |
/api/Departments |
Create department |
PUT |
/api/Departments/{id} |
Update department |
DELETE |
/api/Departments/{id} |
Delete department |
GET |
/api/Attendance |
List attendance, employee name joined |
GET |
/api/Attendance/{id} |
One attendance record |
POST |
/api/Attendance |
Record a punch — resolves by EnrollNumber |
PUT |
/api/Attendance/{id} |
Update a record |
DELETE |
/api/Attendance/{id} |
Delete a record |
POST /api/Attendanceis asymmetric with the rest of the API. The incominguserIdfield is interpreted as the terminal's enroll number, not asUser.Id. The controller looks up the matching employee and stores the real foreign key. This exists because ZUtility only knows enroll numbers — the terminals have no concept of database identity.GETresponses return the realUser.Id. Details indocs/API.md.
Department 1 ──── ∞ User 1 ──── ∞ Attendance
| Entity | Key fields |
|---|---|
Department |
Id, Name (required, ≤100) |
User |
Id, EnrollNumber (terminal ID), Name (≤100), DepartmentId → FK |
Attendance |
Id, UserId → FK, Date, CheckIn, CheckOut, CheckInMode, CheckOutMode |
CheckInMode / CheckOutMode carry the terminal's verification method. The
numeric mapping is defined by the ZKTeco SDK and mirrored in FrontZK
(lib/types.ts):
| Value | Method |
|---|---|
| 0 | Fingerprint |
| 1 | Card |
| 2 | Password |
| 3 | Face |
| 4 | Manual |
Every field on Attendance except Id is nullable, which is how an open
session is represented: a record with CheckIn set and CheckOut still null.
These are real, present in the code today, and tracked here rather than left for the next reader to discover.
| # | Issue | Impact |
|---|---|---|
| 1 | No authentication or authorisation. Program.cs calls UseAuthorization() but registers no authentication scheme, and no controller carries [Authorize]. |
Anyone who can reach the port has full read/write/delete access to employee attendance records. Do not expose this outside a trusted network. See SECURITY.md. |
| 2 | No EF migrations committed. AppDbContext defines the model but no Migrations/ folder exists. |
The schema cannot be reproduced or versioned from the repository. Onboarding requires generating a migration by hand. |
| 3 | CORS origin hardcoded to http://localhost:3000 in Program.cs. |
Any non-local FrontZK deployment needs a recompile. |
| 4 | Swagger is served unconditionally, outside an IsDevelopment() guard. |
The full API shape is published in every environment, including production. |
| 5 | Query parameters in docs/API.md are not implemented. FrontZK calls /api/Attendance?date=…, ?userId=…, ?startDate=…&endDate=…; AttendanceController.GetAll() ignores all of them and returns the entire table. |
The dashboard filters client-side. Works at 41 employees, will not work at scale. |
| 6 | Duplicated [HttpPost] attribute on AttendanceController.Create. |
Harmless today, but it is a copy-paste artefact that should go. |
| 7 | WeatherForecast scaffolding (WeatherForecast.cs, WeatherForecastController.cs) is still present from the project template. |
Dead code and a live unauthenticated endpoint. |
| 8 | user=root;password=root was committed in appsettings.json from May 2026 until September 2026. |
Removed from HEAD, still present in git history. See SECURITY.md. |
Contributions that close any of these are welcome — see CONTRIBUTING.md.
ZKAPI.slnx Solution (SLNX format)
ZKAPI/
├── Program.cs Host, DI, EF, CORS, Swagger
├── Controllers/
│ ├── UsersController.cs
│ ├── DepartmentsController.cs
│ ├── AttendanceController.cs
│ └── WeatherForecastController.cs ← template leftover
├── Models/
│ ├── AppDbContext.cs DbSets and relationship config
│ ├── User.cs / UserDto.cs
│ ├── Department.cs / DepartmentDto.cs
│ └── Attendance.cs / AttendanceDto.cs
├── appsettings.json No secrets — empty connection string
└── ZKAPI.csproj
docs/
├── ARCHITECTURE.md Three-repo data flow, design decisions
├── API.md Endpoint reference
└── CONFIGURATION.md Settings and precedence
| Document | Contents |
|---|---|
docs/ARCHITECTURE.md |
System data flow, why the enroll-number indirection exists, state ownership |
docs/API.md |
Every endpoint, payloads, status codes, curl examples |
docs/CONFIGURATION.md |
Every setting, environment variables, precedence |
CONTRIBUTING.md |
Branching, commit format, review expectations |
SECURITY.md |
Reporting process and the credential exposure history |
CHANGELOG.md |
Release history |
MIT.