# Projects Home Section API

## Problem

The website frontend needs a clean API for current projects without consuming carousel content or relying on the generic home section endpoint. Carousel and projects content are stored in the same `home_sections` table, so the API must filter by `section`.

## Data Source

- Table: `home_sections`
- Schema reference: `mbs_website_portal (2).sql`
- Relevant columns: `id`, `section`, `heading`, `url_link`, `content`, `image`, `order`
- Filtering rule: return only rows where `section = 'projects'`

## Endpoint

`GET /api/home_sections/projects`

The endpoint returns projects ordered by `order ASC`.

Example response:

```json
[
  {
    "id": 15,
    "heading": "Implementation of Management Information System (MIS)",
    "content": "The Malawi Bureau of Standards (MBS) is implementing...",
    "image": "http://192.168.0.5:7006/home_sections/1763976628_mis-acronym-management-information-system-260nw-2413343361.webp",
    "url_link": null,
    "order": 4
  }
]
```

`GET /api/home_sections/{section}/{id}`

The endpoint returns one item from the shared `home_sections` table. `{section}` must be either `projects` or `carousel`, and `{id}` must be the content ID.

Example response:

```json
{
  "id": 15,
  "section": "projects",
  "heading": "Implementation of Management Information System (MIS)",
  "content": "The Malawi Bureau of Standards (MBS) is implementing...",
  "image": "http://192.168.0.5:7006/home_sections/1763976628_mis-acronym-management-information-system-260nw-2413343361.webp",
  "url_link": null,
  "order": 4,
  "created_at": "2025-10-13 11:57:45",
  "updated_at": "2025-11-24 09:30:28"
}
```

If no matching item exists, the endpoint returns:

```json
{
  "message": "Home section content not found."
}
```

## Image URL Behavior

Image paths may be stored with or without a leading `public/` prefix, such as `public/home_sections/example.png` or `home_sections/example.png`. API responses must normalize each non-empty image path through the shared image URL helper before returning JSON.

The helper is controlled by the lowercase environment variable:

```text
production=false
```

- When `production=false`, the helper ensures the browser-ready URL includes `/public/`, for example `http://192.168.0.5:7006/public/home_sections/example.png`.
- When `production=true`, the helper removes the leading `public/` segment from the browser-ready URL, for example `https://admin.mbsmw.org/home_sections/example.png`.
- Existing full `http://` or `https://` image URLs are returned unchanged.
- If a project has no image, `image` is returned as `null`.

## Acceptance Criteria

- The response includes only `home_sections` rows where `section = 'projects'`.
- Carousel rows are not returned.
- Projects are sorted by `order ASC`.
- Image values are absolute URLs when an image path exists.
- Image values include `/public/` when `production=false` and omit `/public/` when `production=true`.
- Missing images are returned as `null`.
- An empty projects result returns `[]`.
- The detail endpoint returns only matching `projects` or `carousel` content by ID.
- The detail endpoint returns `404` when no matching content exists.
- The existing carousel endpoint remains unchanged.

---

# Public Notices CRUD and Public API

## Problem

The website needs a controlled way for admin users to manage public notices and a safe public API for the frontend to display only notices that are meant for public viewing. Public notices already exist in the database, but the app needs model/controller support, dashboard CRUD screens, protected admin JSON APIs, and one public read endpoint.

## Goals

- Allow admin users to create, view, update, and delete public notices from the existing Blade dashboard.
- Provide protected JSON CRUD APIs for admin integrations.
- Provide one public website API that returns only published, active notices.
- Reuse the existing `public_notices` table and current dashboard views.
- Avoid changing existing home sections, projects, carousel, and memo APIs.

## Data Source

- Table: `public_notices`
- Schema reference: `mbs_website_portal (2).sql`
- Model: `App\Models\PublicNotice`
- Relevant columns:
  - `id`
  - `title`
  - `content`
  - `notice_type`
  - `status`
  - `image_url`
  - `published_date`
  - `expiry_date`
  - `created_at`
  - `updated_at`

## Public Endpoint

`GET /api/public-notices`

Returns public notices where:

- `status = 'published'`
- `expiry_date` is `NULL` or `expiry_date` is today or later

The endpoint orders results by:

1. `published_date DESC`
2. `created_at DESC`

Example response:

```json
[
  {
    "id": 1,
    "title": "Public Notice Title",
    "content": "Notice details for the public website.",
    "notice_type": "General",
    "status": "published",
    "image_url": "http://192.168.0.5:7006/public_notices/1776740000_notice-image.webp",
    "published_date": "2026-04-21",
    "expiry_date": "2026-05-21",
    "created_at": "2026-04-21T08:00:00.000000Z",
    "updated_at": "2026-04-21T08:00:00.000000Z"
  }
]
```

If no published active notices exist, the endpoint returns:

```json
[]
```

## Public Notice Image URL Behavior

Notice image paths may be stored with or without a leading `public/` prefix, such as `public/public_notices/example.png` or `public_notices/example.png`. Public and admin JSON APIs must normalize `image_url` through the shared image URL helper.

- When `production=false`, `image_url` includes `/public/`, for example `http://192.168.0.5:7006/public/public_notices/example.png`.
- When `production=true`, `image_url` omits `/public/`, for example `https://admin.mbsmw.org/public_notices/example.png`.
- Existing full `http://` or `https://` image URLs are returned unchanged.
- Missing notice images are returned as `null`.

## Admin Dashboard Routes

The existing Blade CRUD route remains:

`Route::resource('/public_notices', 'PublicNoticeController')`

Dashboard pages:

- `GET /public_notices`
- `GET /public_notices/create`
- `POST /public_notices`
- `GET /public_notices/{id}/edit`
- `PUT /public_notices/{id}`
- `DELETE /public_notices/{id}`

Views:

- `resources/views/dashboard/public-notices/index.blade.php`
- `resources/views/dashboard/public-notices/form.blade.php`

## Admin JSON API

The admin JSON APIs are protected inside the existing `get.menu` and `role:user|admin` middleware flow.

- `GET /admin/api/public-notices`
- `POST /admin/api/public-notices`
- `GET /admin/api/public-notices/{id}`
- `PUT /admin/api/public-notices/{id}`
- `DELETE /admin/api/public-notices/{id}`

Admin JSON responses include:

- `id`
- `title`
- `content`
- `notice_type`
- `image_url`
- `status`
- `published_date`
- `expiry_date`
- `created_at`
- `updated_at`

Create/update requests support multipart form data for notice image uploads:

```text
title: Public Notice Title
content: Notice details for the public website.
notice_type: General
status: published
published_date: 2026-04-21
expiry_date: 2026-05-21
image: optional jpg, jpeg, png, or webp file
```

Example JSON response:

```json
{
  "id": 1,
  "title": "Public Notice Title",
  "content": "Notice details for the public website.",
  "notice_type": "General",
  "status": "published",
  "image_url": "http://192.168.0.5:7006/public_notices/1776740000_notice-image.webp",
  "published_date": "2026-04-21",
  "expiry_date": "2026-05-21",
  "created_at": "2026-04-21T08:00:00.000000Z",
  "updated_at": "2026-04-21T08:00:00.000000Z"
}
```

Validation error response:

```json
{
  "message": "The given data was invalid.",
  "errors": {
    "status": [
      "The selected status is invalid."
    ]
  }
}
```

## Validation Rules

Required fields:

- `title`
- `content`
- `status`

Optional fields:

- `notice_type`
- `image`
- `published_date`
- `expiry_date`

Rules:

- `title`: string, max 191
- `content`: string
- `notice_type`: nullable string, max 191
- `status`: one of `draft`, `published`, `archived`
- `image`: nullable image, jpg/jpeg/png/webp, max 10MB
- `published_date`: nullable date
- `expiry_date`: nullable date, same day or after `published_date` when both dates are supplied

## View Requirements

- The public notices index page shows notice thumbnails and `expiry_date`.
- The create/edit form includes an `expiry_date` date field and image upload with preview.
- The status field is a select with:
  - `draft`
  - `published`
  - `archived`
- The dashboard keeps delete confirmation.
- The dashboard uses a minimal, modern, uncluttered table and two-column desktop form.
- The dashboard shows success/error feedback through normal Laravel session validation behavior.

## Acceptance Criteria

- Admin users can create, edit, and delete public notices from `/public_notices`.
- Admin users can list, create, read, update, and delete notices through `/admin/api/public-notices`.
- Admin users with either `user` or `admin` role can access public notice pages and admin notice APIs.
- Admin JSON validation failures return `422` with an `errors` object.
- Admin users can upload JPG, JPEG, PNG, or WebP images up to 10MB.
- Replacing or deleting a notice deletes the old local notice image.
- `GET /api/public-notices` returns only notices where `status = 'published'`.
- `GET /api/public-notices` excludes notices with `expiry_date` before today.
- `GET /api/public-notices` includes published notices where `expiry_date` is empty.
- `GET /api/public-notices` returns `[]` when no active published notices exist.
- Public API responses expose only the approved public notice fields.
- Public and admin API responses return `image_url` as an absolute URL when an image exists.
- Public and admin API responses include `/public/` in `image_url` when `production=false` and omit `/public/` when `production=true`.
- Existing home sections, projects, carousel, and memo endpoints remain unchanged.

## Out of Scope

- Creating a new migration for `public_notices`.
- Adding file attachments to notices.
- Adding public notice categories beyond the existing `notice_type` text field.
- Adding frontend display components outside this Laravel backend.
