Files
2026-09-18 12:51:16 -04:00

288 lines
14 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# manager_employees_rust — Backend (Rust)
> **Assignment 4: Implementing Control Structures — Rust Backend API**
> University of the Cumberlands — Master’s Program, Semester 4 / Week 4
[![Rust](https://img.shields.io/badge/backend-Rust-orange?logo=rust)](https://www.rust-lang.org/)
[![Axum](https://img.shields.io/badge/framework-Axum_0.8-blue)](https://github.com/tokio-rs/axum)
[![Postgres](https://img.shields.io/badge/database-Postgres_/_SQLx-336791?logo=postgresql)](https://github.com/launchbadge/sqlx)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](./LICENSE)
**Author:** Carlos Gutierrez — [cgutierrez44833@ucumberlands.edu](mailto:cgutierrez44833@ucumberlands.edu)
**Repository:** [`https://git.cargdev.io/CarGDev/manager_employees_rust`](https://git.cargdev.io/CarGDev/manager_employees_rust) — **this repo is the Rust backend**
**Related Repositories (Assignment 4 — 3 implementations):**
| Implementation | Language / Role | Repository |
|---|---|---|
| **This repo** | **Rust — Backend API (Axum/Postgres)** | [`manager_employees_rust`](https://git.cargdev.io/CarGDev/manager_employees_rust) |
| Companion | Python — Backend/CLI | [`manager_employees_python`](https://git.cargdev.io/CarGDev/manager_employees_python) |
| Companion | GUI — Graphical Interface | [`manager_employees_gui`](https://git.cargdev.io/CarGDev/manager_employees_gui) |
---
## 📌 This Repository — Rust Backend Only
**This repo contains ONLY the Rust backend** for Assignment 4. The assignment requires implementing the same scheduling problem in **two contrasting languages** (e.g., Rust + Python) to demonstrate control flow across paradigms — with an optional GUI.
* **Scope of this repo:** REST API built with `Axum` (`src/router.rs:159`), `Tokio` async runtime, `SQLx` + Postgres persistence (`src/database/connection.rs:5`), and a preference-aware weekly scheduling engine (`src/router.rs:561`, `src/manager/`).
* **Companion repos** are **not** tracked here — see table above: Python implementation and GUI implementation live at `git.cargdev.io/CarGDev`.
* API docs are served via `utoipa` + Swagger UI at `/swagger-ui` (`src/router.rs:187`).
---
## 📖 Story: Assignment 4 — Implementing Control Structures
### 1. Overview
> *You will implement, without logical errors, various control structures such as conditionals, loops, and branching, in multiple programming languages. This assignment will demonstrate your mastery of syntax and control flow in different paradigms.*
> — Assignment 4 Prompt
For me, Carlos Gutierrez, this assignment was about proving that the same business rules can be expressed cleanly in two very different paradigms. I chose **Rust** for this backend because its `match`, `if let`, ownership model, and `Result` error handling force explicit, safe control flow — a sharp contrast to a dynamic language like Python or JavaScript. Every scheduling rule below is therefore not just business logic, but a deliberate exercise in conditionals, loops, and branching done idiomatically in Rust.
### 2. Application Prompt — Employee Schedule Manager
The company operates **7 days/week** with **3 shifts/day** (`morning`, `afternoon`, `evening` — `src/database/types.rs:18`). The app must:
1. **Input and Storage:** Collect employee names and their preferred shift for each day, storing in appropriate structures. Here: `users` + `employee_weekly_preferences` + `company_users` tables (`src/database/schema.rs:36`, `src/database/entities/`), with requests like `CreateEmployeeRequest` and `WeeklyPreferenceRequest` (`src/router.rs:32`, `src/router.rs:46`).
2. **Scheduling Logic:** Auto-assign shifts ensuring:
* No employee works more than **one shift per day** (`src/manager/validator.rs:5`, `src/router.rs:585`)
* Max **5 days per week** per employee (`src/manager/validator.rs:11`, `src/router.rs:586`)
* At least **2 employees per shift per day**; if fewer are available, randomly/round-robin assign additional valid employees (`src/main.rs:37`, `src/manager/scheduler.rs:25`, `src/router.rs:597`)
3. **Shift Conflicts:** Detect when a preferred shift is full and resolve by assigning to another available shift on the **same or next day** (`src/manager/conflict.rs:4`, `src/manager/conflict.rs:10`, `src/manager/conflict.rs:16`, `src/router.rs:240`).
4. **Output:** Final weekly schedule in readable format indicating employee → day → shift. Here: `GET /schedules/weekly` and `print_final_schedule()` grouping by day/shift (`src/manager/scheduler.rs:44`, `src/router.rs:542`).
5. **Bonus — Priority Ranking:** Employees rank shifts `first > second > third` (`src/database/types.rs:18`, `src/database/schema.rs:70`). The engine ranks candidates with `preference_rank()` (`src/router.rs:240`) and accommodates preferences while still meeting coverage.
### 3. Programming Language Choice
> Suggested: Python, Java, C++, JavaScript, Go, Rust — *must be contrasting paradigms*.
This repo = **Rust** (systems, compiled, ownership + exhaustive `match`) at [`manager_employees_rust`](https://git.cargdev.io/CarGDev/manager_employees_rust).
My contrasting implementations are:
* **Python:** [`manager_employees_python`](https://git.cargdev.io/CarGDev/manager_employees_python) — dynamic, interpreted, idiomatic loops/comprehensions
* **GUI:** [`manager_employees_gui`](https://git.cargdev.io/CarGDev/manager_employees_gui) — graphical input/output for employee names, preferences, and final schedule (bonus requirement)
The Rust side emphasizes `match` over `if-else` chains, iterator loops (`for day in all_days()` `src/router.rs:581`), and `Result`-based branching for DB errors (`src/app.rs:29`) — a direct contrast to Python’s approach in the companion repo.
---
## ✨ Features
* 7-day × 3-shift auto-generation via `POST /schedules/weekly/generate` (`src/router.rs:561`)
* Preference-aware ranking (`first=0, second=1, third=2, fallback=3`) (`src/router.rs:240`)
* Validation: one-shift/day, ≤5 days/week, ≥2/shift (`src/manager/validator.rs`)
* Conflict resolution: same-day fallback → next-day (`src/manager/conflict.rs`)
* Clock-in/out (`POST /employees/{id}/clock-in` `src/router.rs:627`, `PUT /employees/{id}/clock-out` `src/router.rs:652`)
* Company & employee CRUD, weekly preferences (`PUT /employees/{id}/weekly-preferences` `src/router.rs:465`)
* Health check `GET /health` (`src/router.rs:262`), CORS permissive (`src/router.rs:188`), OpenAPI docs
---
## 🛠 Tech Stack
| Layer | Choice | Version / File |
|-------|--------|----------------|
| Language | Rust | `edition = "2024"` (`Cargo.toml:4`) |
| Web | Axum | `0.8.4` (`Cargo.toml:7`) |
| Async | Tokio | `1.53.1` full (`Cargo.toml:13`) |
| DB | Postgres + SQLx | `0.9.0` chrono/postgres/runtime-tokio (`Cargo.toml:12`) |
| Validation/Types | Chrono, Serde | `chrono 0.4.42`, `serde 1.0.228` (`Cargo.toml:8`, `Cargo.toml:10`) |
| Docs | utoipa + utoipa-swagger-ui | `5` / `9` (`Cargo.toml:15`, `Cargo.toml:16`) |
| Middleware | tower-http CORS | `0.6.6` (`Cargo.toml:14`) |
| Env | dotenvy | `0.15.7` (`Cargo.toml:9`) |
---
## 📁 Project Structure
```
src/
main.rs # entry, env loading, pool init, serve (src/main.rs:10)
app.rs # AppState { pool }, ApiError → 404/500 (src/app.rs:10)
router.rs # 16 routes, OpenAPI, scheduling core (src/router.rs:159)
database/
connection.rs # connect() + init_database() (src/database/connection.rs:5)
schema.rs # 9 DDL queries: enums + 5 tables + alters (src/database/schema.rs:94)
types.rs # WorkTime, DayOfWeek enums (src/database/types.rs:18)
entities/ # Company, User, CompanyUser, Schedule, WeeklyPreference
queries/ # company, employee, preference, schedule, user
manager/ # business-rule stubs extracted from main.rs assignment_rules()
scheduler.rs # generate_weekly_schedule, fill_missing, clock-in/out
validator.rs # same-day, 5-day, 2-employee validations
conflict.rs # detect + resolve same/next day
models/
company/ # CreateCompanyRequest + service
```
---
## 🗄 Database Schema
Enums: `work_time('morning','afternoon','evening')` (`src/database/schema.rs:1`), `day_of_week('sunday'..'saturday')` (`src/database/schema.rs:10`)
| Table | Key Columns |
|-------|-------------|
| `companies` | `id SERIAL PK, name TEXT UNIQUE` (`src/database/schema.rs:27`) |
| `users` | `id, name, last_name, preference work_time` (`src/database/schema.rs:36`) |
| `company_users` | `user_id FK→users, company_id FK→companies, UNIQUE(user_id,company_id)` (`src/database/schema.rs:47`) |
| `schedules` | `user_id FK, enter_time, day_of_week, out_time, shift work_time` (`src/database/schema.rs:58`) |
| `employee_weekly_preferences` | `user_id, day_of_week, first/second/third work_time, UNIQUE(user_id,day_of_week)` (`src/database/schema.rs:70`) |
Init via `CREATE_SCHEMA_QUERIES` loop (`src/database/connection.rs:13`).
---
## 📐 Business Rules in Code
* `assignment_rules()` summary (`src/main.rs:29-39`)
* `all_days()` / `all_shifts()` helpers (`src/router.rs:224`, `src/router.rs:236`)
* Candidate filtering + `sort_by_key(preference_rank)` (`src/router.rs:589-595`)
* `assigned_days: HashMap<(user_id, DayOfWeek), bool>` + `day_count_by_user: HashMap<user_id, i32>` (`src/router.rs:576`)
---
## 🔌 API Reference
Base: `http://127.0.0.1:5005` (configurable via `APP_ADDRESS`)
| Method | Path | Tag | Description |
|--------|------|-----|-------------|
| `GET` | `/health` | health | `{"status":"ok"}` (`src/router.rs:254`) |
| `POST` | `/companies` | companies | create (`src/router.rs:276`) |
| `GET` | `/companies` | companies | list (`src/router.rs:284`) |
| `GET` | `/companies/{id}` | companies | get (`src/router.rs:310`) |
| `DELETE` | `/companies/{id}` | companies | delete (`src/router.rs:319`) |
| `POST` | `/employees` | employees | create + assign to company (`src/router.rs:350`) |
| `GET` | `/employees` | employees | list (`src/router.rs:375`) |
| `POST` | `/companies/{company_id}/employees/{employee_id}` | employees | assign (`src/router.rs:394`) |
| `DELETE` | `/employees/{id}` | employees | delete (`src/router.rs:418`) |
| `PUT` | `/employees/{id}/preference` | employees | single pref (`src/router.rs:441`) |
| `PUT` | `/employees/{id}/weekly-preferences` | employees | 7-day prefs (`src/router.rs:465`) |
| `GET` | `/companies/{company_name}/employees/names` | employees | names by company (`src/router.rs:499`) |
| `POST` | `/schedules` | schedules | create (`src/router.rs:518`) |
| `GET` | `/schedules/weekly` | schedules | list all (`src/router.rs:542`) |
| `POST` | `/schedules/weekly/generate` | schedules | auto-generate (`src/router.rs:561`) |
| `POST` | `/employees/{id}/clock-in` | schedules | clock-in now (`src/router.rs:627`) |
| `PUT` | `/employees/{id}/clock-out` | schedules | clock-out (`src/router.rs:652`) |
Swagger: `GET /swagger-ui` + `GET /api-docs/openapi.json` (`src/router.rs:187`).
---
## 🚀 Getting Started
### Prerequisites
* Rust `stable` (with `cargo`)
* Postgres ≥14 running locally
* `sqlx-cli` optional (schema is auto-created on boot)
### 1. Clone & Configure
```bash
git clone https://git.cargdev.io/CarGDev/manager_employees_rust
cd manager_employees_rust
cp .env.example .env # or create .env
```
> See also companions: [`manager_employees_python`](https://git.cargdev.io/CarGDev/manager_employees_python) and [`manager_employees_gui`](https://git.cargdev.io/CarGDev/manager_employees_gui).
`.env` (`src/main.rs:11` loads via `dotenvy`):
```env
DATABASE_URL=postgres://carlos:password@localhost/rust_manager_employees
APP_ADDRESS=127.0.0.1:5005
```
> `.env` is gitignored (` .gitignore:7`). Example URL from `.env:1`.
### 2. Create Database
```bash
createdb rust_manager_employees
# or: psql -c "CREATE DATABASE rust_manager_employees;"
```
Tables/enums are created automatically on first run via `init_database()` (`src/database/connection.rs:12`).
### 3. Run
```bash
cargo run
# → listening on 127.0.0.1:5005
curl http://127.0.0.1:5005/health
# {"status":"ok"}
```
### 4. Try the Flow
```bash
# 1. Create company
curl -X POST http://127.0.0.1:5005/companies \
-H "Content-Type: application/json" \
-d '{"name":"Acme"}'
# 2. Create employees (company_id from above)
curl -X POST http://127.0.0.1:5005/employees \
-H "Content-Type: application/json" \
-d '{"company_id":1,"name":"Ana","last_name":"Lopez","preference":"morning"}'
# 3. Set weekly preferences
curl -X PUT http://127.0.0.1:5005/employees/1/weekly-preferences \
-H "Content-Type: application/json" \
-d '{"preferences":[{"day_of_week":"monday","first":"morning","second":"afternoon","third":"evening"}]}'
# 4. Generate weekly schedule
curl -X POST http://127.0.0.1:5005/schedules/weekly/generate \
-H "Content-Type: application/json" \
-d '{"company_id":1}'
# 5. View schedule
curl http://127.0.0.1:5005/schedules/weekly
# Swagger UI: http://127.0.0.1:5005/swagger-ui
```
---
## ⚙️ Configuration
| Var | Required | Default | Description |
|-----|----------|---------|-------------|
| `DATABASE_URL` | yes | — | Postgres URL (`src/main.rs:13`) |
| `APP_ADDRESS` | no | `127.0.0.1:5005` | Bind addr (`src/main.rs:19`) |
---
## 👨‍💻 Development
```bash
cargo fmt
cargo clippy
cargo check
cargo test
```
Error handling: `ApiError(sqlx::Error)` → `RowNotFound→404`, else `500` with JSON `{"error": "..."}` (`src/app.rs:29`).
---
## 📄 License
MIT — Copyright (c) 2026 Carlos Gutierrez. See [LICENSE](./LICENSE).
---
## 📬 Contact
**Carlos Gutierrez** — University of the Cumberlands
Email: [cgutierrez44833@ucumberlands.edu](mailto:cgutierrez44833@ucumberlands.edu)
Project: `manager_employees_rust` — Rust backend for Assignment 4: Implementing Control Structures
Repositories:
* Rust Backend (this repo): <https://git.cargdev.io/CarGDev/manager_employees_rust>
* Python Backend: <https://git.cargdev.io/CarGDev/manager_employees_python>
* GUI: <https://git.cargdev.io/CarGDev/manager_employees_gui>