# News Module — Architecture Map

> **Last updated:** 2026-06-10
> **Status:** Living document — incrementally updated as new information is discovered

---

## 1. Purpose & Responsibilities

The News module is a **simple CMS** for publishing internal club news and announcements. It manages:
- Article creation (title, body, category, cover image)
- Article editing and updating
- Publish/unpublish toggle (draft vs published lifecycle)
- Categorized article listing with search and filters
- Cover image upload handling (JPG/PNG, 5MB max)

It does **NOT** directly manage:
- Push notifications (no event integration)
- Member-facing mobile news feed (no API endpoints)
- Comments or reactions
- Scheduled publishing (manual toggle only)

---

## 2. Directory & File Structure

```
app/Modules/News/
├── bootstrap.php                              # Permission registration (2 permissions)
├── Routes.php                                 # 7 routes
├── Controllers/
│   └── NewsController.php                    # All CRUD + togglePublish + image upload
├── Models/
│   └── NewsArticle.php                       # Article model (no soft delete)
└── Views/
    ├── index.php                             # Article list with filters
    ├── create.php                            # New article form
    ├── edit.php                              # Edit article form
    └── show.php                              # Single article view
```

---

## 3. Database Schema (Production — Source of Truth)

### 3.1 `news_articles` Table (0 rows as of 2026-06-10)

| Column | Type | Nullable | Key | Notes |
|--------|------|----------|-----|-------|
| id | bigint unsigned | NO | PRI | auto_increment |
| title_ar | varchar(300) | NO | | Arabic article title |
| body_ar | text | NO | | Arabic article body (HTML/text) |
| category | enum('match','facility','announcement','schedule','general') | NO | | Default: 'general' |
| cover_image_path | varchar(500) | YES | | Relative path to uploaded image |
| related_match_id | bigint unsigned | YES | | FK to matches (optional link) |
| related_facility_id | bigint unsigned | YES | | FK to facilities (optional link) |
| is_published | tinyint(1) | NO | MUL | Default: 0 (draft) |
| published_at | datetime | YES | | Set when published |
| created_by | bigint unsigned | YES | | FK to employees |
| created_at | timestamp | YES | | |
| updated_at | timestamp | YES | | On update cascade |

### 3.2 Related Tables (owned by other modules)

| Table | Owner Module | Relationship |
|-------|-------------|--------------|
| employees | HR | created_by FK (author) |
| matches | (Matches/Sports) | related_match_id FK (optional) |
| facilities | Facilities | related_facility_id FK (optional) |

---

## 4. Routes

| Method | Path | Action | Middleware | Permission |
|--------|------|--------|------------|------------|
| GET | /news | index | auth | news.view |
| GET | /news/create | create | auth | news.manage |
| POST | /news | store | auth, csrf | news.manage |
| GET | /news/{id} | show | auth | news.view |
| GET | /news/{id}/edit | edit | auth | news.manage |
| POST | /news/{id} | update | auth, csrf | news.manage |
| POST | /news/{id}/toggle-publish | togglePublish | auth, csrf | news.manage |

---

## 5. Core Business Flows

### 5.1 Article Creation
1. Admin fills form: title_ar (required), body_ar (required), category, cover image (optional)
2. Validation: title non-empty, body non-empty, valid category, image type JPG/PNG, max 5MB
3. Image upload: saved to `{UPLOAD_PATH}/news/` with timestamped filename
4. Article created with `is_published=0` (draft by default)
5. Redirects to article show page

### 5.2 Publish/Unpublish Toggle
1. POST to `/news/{id}/toggle-publish`
2. If currently published: sets `is_published=0`, `published_at=null`
3. If currently draft: sets `is_published=1`, `published_at=now()`
4. Redirects back to article show page

### 5.3 Article Listing
- Paginated (20 per page)
- Filterable by: search text (title LIKE), category, published status
- Ordered by `created_at DESC`

---

## 6. Events Dispatched

None. This module does not dispatch any events.

---

## 7. Events Consumed

None. This module does not listen to any events.

---

## 8. Cross-Module Dependencies

### 8.1 News IMPORTS FROM:

None. The News module is fully self-contained with no external module imports.

### 8.2 Other modules that IMPORT FROM News:

| Module | What it imports |
|--------|----------------|
| Tutorials | References news tutorial keys (news.create-article, news.publish-article, news.manage-categories) |

---

## 9. Permissions

| Key | Description |
|-----|-------------|
| news.view | View article list and individual articles |
| news.manage | Create, edit, publish/unpublish articles |

---

## 10. Background Processes (Cron)

None.

---

## 11. Configuration Dependencies

| Config | Source | Purpose |
|--------|--------|---------|
| UPLOAD_PATH | .env | Base directory for file uploads (defaults to 'public/uploads') |

---

## 12. High-Risk Areas

### 12.1 Image Upload Security
- File type validation uses `$_FILES['cover_image']['type']` (MIME type from client — can be spoofed)
- No server-side MIME verification (e.g., `finfo_file`)
- No filename sanitization beyond timestamp + random hex
- Upload directory created with `mkdir(..., 0755, true)` if not exists

### 12.2 No Soft Delete
- Articles have no `is_archived` column — no soft delete support
- No delete route exists, but if added later, deletion would be permanent
- No archive/restore workflow

### 12.3 No Event Integration
- Publishing an article does not notify anyone (no EventBus dispatch)
- No mobile push notification, no email digest
- Cannot trigger automated workflows on publish

### 12.4 Module State: Empty
- 0 articles in production — module is deployed but unused
- No seed data

---

## 13. Known Patterns & Gotchas

1. **No menu registration**: bootstrap.php only registers permissions — no sidebar menu entry visible
2. **No soft delete**: Model has `$softDelete = false` — deleted records are gone permanently
3. **Category is ENUM**: Adding new categories requires a migration (ALTER TABLE)
4. **Related match/facility IDs**: Fillable fields exist but no UI in create/edit forms to set them
5. **Image path format**: Always `/uploads/news/news_{timestamp}_{hex}.{ext}`
6. **Draft by default**: New articles start unpublished — must explicitly toggle to publish
7. **No rich text editor visible**: body_ar stored as text — view layer may render HTML
8. **Category colors**: Hardcoded in model — match=red, facility=blue, announcement=amber, schedule=purple, general=gray
9. **No pagination on search**: Search uses the same paginate() method as listing
10. **No API**: No REST API or mobile-facing endpoints — admin panel only
