# A Cidade — Arquitetura

> Versão analisada: `v6` (Laravel 5.5.50 / PHP 7.2.34 / PostgreSQL 10).
> Slug do módulo: `a-cidade` — `modulos.id = 13` (nome `A Cidade`, `status = false`).
> Categoria âncora: `category_contents.id = 4`, `slug = 'a-cidade'`.

---

## 1. Natureza do módulo

A Cidade **não é um módulo de domínio**. É um *rótulo* aplicado sobre a categoria
de conteúdo `a-cidade` dentro do motor genérico de conteúdos. Consequências
arquiteturais:

| Aspecto | Realidade na v6 |
|---|---|
| Tabela própria | Não existe. Tudo mora em `contents`, `category_contents`, `archives`, `links`. |
| Model / Repository / Service próprios | Não existem. Reaproveitados de `contents`. |
| Controller de CMS | Nenhum. O `Prefecture\ContentController` trata o módulo por `switch ($slug)`. |
| Controller de portal | Um: `Portal\AboutController` (45 linhas, 1 método). |
| Listagem do CMS | Uma **view SQL** (`a_cidade`) consumida pelo `DatatableController`. |
| Domínio | Editorial/institucional: texto rico, imagem de destaque, galeria de fotos e um link de vídeo. |

Ou seja: a v6 adds **zero** tabelas e **zero** colunas para o módulo. O que
existe de "A Cidade" é a tripla *(categoria + view SQL + prefixo de rota)*.

## 2. Camadas

O módulo não introduz camadas novas; ele percorre as camadas herdadas do
monólito:

```
Portal (público)                       CMS (admin)
───────────────                        ───────────
Route (web.php)                        Route (basepro.php)
  └─ Portal\AboutController              └─ Prefecture\ContentController
       └─ GetInformationsPrefectureTrait      └─ App\Prefecture\Content (Eloquent)
            └─ App\Prefecture\Content              └─ tabela `contents`
       └─ view Blade                            └─ tabela `a_cidade` (view SQL)
                                                 └─ Yajra DataTables (JSON)
```

| Camada | Classe / artefato | Caminho |
|---|---|---|
| Rota (portal) | `Route::prefix('a-cidade')` | `v6/routes/web.php:85-87` |
| Rota (CMS) | `Route::resource('conteudo', ...)` + `datatable/a_cidade` | `v6/routes/basepro.php:174`, `:414` |
| Controller (portal) | `Portal\AboutController` | `v6/app/Http/Controllers/Portal/AboutController.php` |
| Controller (CMS) | `Prefecture\ContentController`, `Prefecture\DatatableController` | `v6/app/Http/Controllers/Prefecture/` |
| Trait | `GetInformationsPrefectureTrait` | `v6/app/Http/Traits/GetInformationsPrefectureTrait.php` |
| Model | `App\Prefecture\Content`, `CategoryContent`, `Archive`, `Link` | `v6/app/Http/Model/Prefecture/` |
| Repositório | `IContentRepository` (contrato vazio) | `v6/app/Repositories/Contracts/IContentRepository.php` |
| Helper global | `embedVideo()`, `normalizeVideoLink()`, `menuClicks()`, `getLargFile()`, `getMinFile()` | `v6/app/Helpers/functions.php` |
| Helper de permissão | `has_access()` | `v6/app/Library/Helper/appHelpers.php:22` |
| View (portal) | `informacoes-instituicao/acidade/show.blade.php` | `v6/resources/views/portal/layouts/modelo{1..6}/` |
| View (CMS) | `prefecture/contents/a-cidade/*` | `v6/resources/views/prefecture/contents/a-cidade/` |
| JS | `datatable.js` da grade | `v6/public/assets/js/custom/contents/acidade/datatable.js` |

## 3. Fluxo da requisição — portal público

```
GET /a-cidade/{slug}
  │
  ├─ Middleware `check.site.manutention`  (v6/routes/web.php:7)
  │     └─ aborta/redi em caso de manutenção global do portal
  │
  ├─ Route::any('/{slug}', 'Portal\AboutController@getIndex')   [nome: a-cidade.index]
  │
  ├─ Portal\AboutController@getIndex($slug)
  │   ├─ $contentNew = $this->getContentDataWith($slug)
  │   │     Content::with('archiveHighlight','archiveGallery','categoryContents:id,name,slug')
  │   │            ->where('slug', $param)->first()
  │   │     ⚠ NÃO filtra category_contents nem status
  │   │
  │   ├─ if (!$contentNew) return view("admin.errors.conteudo-nao-encontrado")  → HTTP 200
  │   │
  │   ├─ $contentNew->load('linksUnique')
  │   ├─ $linkVideo = isset($contentNew->linksUnique)
  │   │                  ? embedVideo($contentNew->linksUnique->link) : null
  │   ├─ $title = $contentNew->title
  │   ├─ $this->updateClicks($contentNew->id)   → contents.clicks = clicks + 1
  │   │
  │   └─ return view('portal.layouts.' . $this->getTemplateActived()
  │                 . '.informacoes-instituicao.acidade.show',
  │                 compact('contentNew','title','linkVideo'))
  │
  └─ Blade: modeloN/informacoes-instituicao/acidade/show.blade.php
        └─ @component('...components-acidade.index')
             ├─ slot breadcrumb → Breadcrumbs::view('...layout.breadcrumb',
             │                        $contentNew->categoryContents->slug, $contentNew)
             ├─ slot imageHighlight (archiveHighlight->path, se o arquivo existir)
             ├─ slot title / slot text
             ├─ slot gallery → @include("...informacoes-instituicao.galeria.index")
             └─ slot video   → @include("portal.statics.video")  (iframe com $linkVideo)
```

### 3.1 Escolha do layout em runtime

`getTemplateActived()` é o ponto mais sensível do fluxo:

```php
// v6/app/Http/Traits/GetInformationsPrefectureTrait.php:92-102
public function getTemplateActived()
{
    $templateActived = Configuration::where('key', 'layout')->first()->value;

    if (!View::exists('portal.layouts.' . $templateActived . '.home.index')) {
        DB::table('configurations')->where('key', 'pagina_em_manutencao')->update(['value' => 1]);
        abort(404, 'view_inexistente');
    }

    return $templateActived;
}
```

Observações verificadas:

- O método existe **duas vezes**: no trait (acima, `public`) e em
  `v6/app/Http/Controllers/Admin/Controller.php:39` (`protected`, corpo idêntico).
  Como o trait é aplicado na classe, **o método do trait vence** o método herdado.
- O teste de existência é sobre `home.index`, **não** sobre a view de A Cidade.
  Consequência: se `configurations.layout` for `default` ou `newsbit`, o
  `abort(404)` dispara **antes** de qualquer resolução de view do módulo, e o
  portal inteiro é colocado em manutenção (`pagina_em_manutencao = 1`).
- `modelo1`..`modelo6` têm `home/index.blade.php`; `default` e `newsbit` **não**
  têm (verificado por filesystem).

## 4. Fluxo da requisição — CMS

```
GET /cms/conteudos/a-cidade                         [listar-conteudo]
  └─ Prefecture\ContentController@genericIndex('a-cidade')
       ├─ if ($slug === 'a-cidade') return $this->returnIndex('A Cidade', $slug)
       ├─ generateLog('A Cidade')              → activity_log
       ├─ menuClicks('a-cidade')                → menu_orders.clicks++
       └─ view("prefecture.contents.a-cidade.index")
            └─ <table id="tabela_a_cidade">
                 └─ JS: runDatatables('a_cidade', collums, true, 'ASC', null, true)
                      └─ AJAX GET /cms/datatable/a_cidade
                           └─ DatatableController@get_a_cidade()
                                ├─ DB::table('a_cidade')->get()      ← view SQL
                                └─ DataTables::of($this->struct_views($data, 'a-cidade'))->make(true)

POST /cms/conteudo        [conteudo.store]  → ContentController@store(ContentRequest)
PUT  /cms/conteudo/{id}   [conteudo.update] → ContentController@update(ContentRequest, $id)
DELETE /cms/conteudo/delete/{id} [generic.conteudo.delete] → genericDestroy($id)  (soft delete)
POST /cms/ajax/generic/content/deleteAll → genericDeleteAll(Request) → repository->destroyAll()
```

`struct_views()` injeta as flags de permissão por linha:

```php
// v6/app/Http/Controllers/Prefecture/DatatableController.php:504-520
public function struct_views($data, $permission)
{
    for ($i = 0; $i < count($data); $i++) {
        $data[$i]->edit = has_access("$permission.edit");

        if ($permission === 'diario-oficial') {
            $data[$i]->delete = Sentinel::inRole('super-admin');
        } else {
            $data[$i]->delete = has_access("$permission.delete");
        }

        $data[$i]->active = has_access("$permission.restore");
        $data[$i]->inactivate = has_access("$permission.inactivate");
    }

    return $data;
}
```

O módulo usa a chave `'a-cidade'` — logo `a-cidade.edit`, `a-cidade.delete`,
`a-cidade.restore` e `a-cidade.inactivate`. O caso especial `super-admin` do
`delete` vale apenas para `diario-oficial`.

## 5. Diagrama do fluxo (resumo)

```
                    ┌──────────────────────────────┐
  Visitante ──────►│ GET /a-cidade/{slug}         │
                    │ check.site.manutention       │
                    └───────────────┬──────────────┘
                                    ▼
                    ┌──────────────────────────────┐
                    │ Portal\AboutController       │
                    │  getContentDataWith(slug)    │◄── Content (Eloquent)
                    │  embedVideo(linksUnique.link)│◄── helper global
                    │  updateClicks(id)            │──► UPDATE contents.clicks
                    │  getTemplateActived()         │──► configurations.layout
                    └───────────────┬──────────────┘
                                    ▼
       portal.layouts.{layout}.informacoes-instituicao.acidade.show
                    ┌──────────────────────────────┐
                    │ Operador ─► /cms/conteudos/  │
                    │            a-cidade          │
                    │  DataTable ← view SQL a_cidade
                    │  CRUD     ← contents         │
                    └──────────────────────────────┘
```

## 6. Dependências externas (composer)

Nenhuma dependência nova é introduzida pelo módulo. As Used:

| Pacote | Versão | Uso no módulo |
|---|---|---|
| `laravel/framework` | `5.5.*` | Roteamento, Eloquent, View, Validation, FormRequest |
| `yajra/laravel-datatables` | `^1.5` | JSON do DataTable da listagem do CMS |
| `cartalyst/sentinel` | `2.0.*` | Sessão, papéis e permissões (`has_access`) |
| `intervention/image` | `^2.7` | Compressão/derivados das imagens de destaque e galeria |
| `barryvdh/laravel-dompdf` | `^0.8.7` | Não usado diretamente por A Cidade |

Dependências do framework não-PHP usadas na camada de apresentação:
AdminLTE 1.22 (CMS), Bootstrap 4 (portal), Summernote (editor de texto),
lightGallery (galeria), `addtoany` (redes sociais, v6) / `addthis` (v5).

## 7. Mapa de classes do módulo

| Classe | Papel |
|---|---|
| `App\Http\Controllers\Portal\AboutController` | Renderiza a página pública. Herda `Admin\Controller` e usa `GetInformationsPrefectureTrait`. |
| `App\Http\Controllers\Prefecture\ContentController` | CRUD genérico de `contents`; resolve o módulo pelo `switch ($slug)` (`:94`, `:166`). |
| `App\Http\Controllers\Prefecture\DatatableController` | Serve a listagem JSON da view SQL `a_cidade` (`:246-250`). |
| `App\Http\Controllers\Admin\Controller` | Base do controller do portal; traz `getTemplateActived()` **protegido** (`:39`), sobrescrito pelo trait. |
| `App\Http\Traits\GetInformationsPrefectureTrait` | `getContentDataWith()`, `getTemplateActived()`, `updateClicks()` e consultas correlatas. |
| `App\Prefecture\Content` | Eloquent de `contents`; relações `categoryContents()`, `linksUnique()`, `archiveHighlight()`, `archiveGallery()`, `archives()`. |
| `App\Prefecture\CategoryContent` | Eloquent de `category_contents`; é a âncora `a-cidade`. |
| `App\Prefecture\Archive` | Eloquent de `archives`; destaque (`highlight = 1`) e galeria (`highlight IS NULL`). |
| `App\Prefecture\Link` | Eloquent de `links`; o vídeo é o `linksUnique` do conteúdo. |
| `App\Http\Presenters\ContentPresenter` | Presenter do `Content` (usado por `PresentableTrait`). |

## 8. Referências cruzadas

- O módulo **A Instituição** (`category_contents.id = 6`, slug `a-instituicao`)
  compartilha a mesma espinha dorsal e vive sob o prefixo público `/a-instituicao`
  — ver `../a-instituicao/`.
- O motor de CRUD de conteúdos é comum a ~30 módulos — ver `../noticias/`,
  `../secretarias/`, `../orgaos/`.
- O painel de autenticação, o menu do CMS e o `modulos` são transversais —
  ver `../_00_panorama/`.
