├── .docker ├── nginx │ └── conf.d │ │ └── api.conf └── php │ ├── Dockerfile │ └── conf │ ├── xdebug.ini │ └── zz-docker.conf ├── .editorconfig ├── .env.example ├── .gitattributes ├── .github └── PULL_REQUEST_TEMPLATE.md ├── .gitignore ├── .php_cs.dist ├── .travis.yml ├── CONTRIBUTING.md ├── LICENSE.md ├── README.md ├── SECURITY.md ├── app ├── Console │ ├── Commands │ │ ├── ApiResponseCacheBustCommand.php │ │ ├── IpmaSurfaceObservationFetchCommand.php │ │ ├── IpmaWarningFetchCommand.php │ │ ├── ProCivOccurrenceCloseCommand.php │ │ └── ProCivOccurrenceFetchCommand.php │ └── Kernel.php ├── Exceptions │ └── Handler.php ├── Filters │ ├── AcronymFilter.php │ ├── Concerns │ │ └── GeoLocator.php │ ├── Contracts │ │ ├── AcronymFilter.php │ │ ├── CountyFilter.php │ │ ├── DistrictFilter.php │ │ ├── EventFilter.php │ │ ├── Filter.php │ │ ├── GeoLocator.php │ │ ├── OccurrenceFamilyFilter.php │ │ ├── OccurrenceFilter.php │ │ ├── OccurrenceSpeciesFilter.php │ │ ├── OccurrenceStatusFilter.php │ │ ├── OccurrenceTypeFilter.php │ │ ├── ParishFilter.php │ │ ├── UserFilter.php │ │ └── WeatherObservationFilter.php │ ├── CountyFilter.php │ ├── DistrictFilter.php │ ├── EventFilter.php │ ├── Filter.php │ ├── OccurrenceFamilyFilter.php │ ├── OccurrenceFilter.php │ ├── OccurrenceSpeciesFilter.php │ ├── OccurrenceStatusFilter.php │ ├── OccurrenceTypeFilter.php │ ├── ParishFilter.php │ ├── UserFilter.php │ └── WeatherObservationFilter.php ├── Http │ ├── Controllers │ │ ├── AcronymController.php │ │ ├── AuthController.php │ │ ├── Controller.php │ │ ├── CountyController.php │ │ ├── DistrictController.php │ │ ├── EventController.php │ │ ├── FallbackController.php │ │ ├── IpmaWarningController.php │ │ ├── OccurrenceController.php │ │ ├── OccurrenceFamilyController.php │ │ ├── OccurrenceReportController.php │ │ ├── OccurrenceSpeciesController.php │ │ ├── OccurrenceStatusController.php │ │ ├── OccurrenceTypeController.php │ │ ├── ParishController.php │ │ ├── UserController.php │ │ ├── UserProfileController.php │ │ ├── UserRoleController.php │ │ └── WeatherObservationController.php │ ├── Kernel.php │ ├── Middleware │ │ ├── CheckForMaintenanceMode.php │ │ ├── TrimStrings.php │ │ ├── TrustProxies.php │ │ └── ValidateJsonApiRequest.php │ ├── Requests │ │ ├── Acronym │ │ │ ├── Create.php │ │ │ ├── Delete.php │ │ │ ├── Index.php │ │ │ ├── Update.php │ │ │ └── View.php │ │ ├── Auth │ │ │ ├── Authenticate.php │ │ │ ├── Refresh.php │ │ │ └── Verify.php │ │ ├── County │ │ │ ├── Index.php │ │ │ └── View.php │ │ ├── District │ │ │ ├── Index.php │ │ │ └── View.php │ │ ├── Event │ │ │ ├── Create.php │ │ │ ├── Delete.php │ │ │ ├── Index.php │ │ │ ├── Update.php │ │ │ └── View.php │ │ ├── Occurrence │ │ │ ├── GenerateReport.php │ │ │ ├── Index.php │ │ │ ├── Update.php │ │ │ └── View.php │ │ ├── OccurrenceFamily │ │ │ └── Index.php │ │ ├── OccurrenceSpecies │ │ │ └── Index.php │ │ ├── OccurrenceStatus │ │ │ └── Index.php │ │ ├── OccurrenceType │ │ │ └── Index.php │ │ ├── Parish │ │ │ ├── Index.php │ │ │ └── View.php │ │ ├── Request.php │ │ ├── User │ │ │ ├── Create.php │ │ │ ├── Index.php │ │ │ ├── RetrieveRoles.php │ │ │ ├── Update.php │ │ │ ├── UpdateProfile.php │ │ │ ├── View.php │ │ │ └── ViewProfile.php │ │ ├── Warning │ │ │ └── Index.php │ │ └── WeatherObservation │ │ │ ├── Index.php │ │ │ └── View.php │ └── Serializers │ │ ├── AcronymSerializer.php │ │ ├── CountySerializer.php │ │ ├── DistrictSerializer.php │ │ ├── EventSerializer.php │ │ ├── EventTypeSerializer.php │ │ ├── OccurrenceFamilySerializer.php │ │ ├── OccurrenceSerializer.php │ │ ├── OccurrenceSpeciesSerializer.php │ │ ├── OccurrenceStatusSerializer.php │ │ ├── OccurrenceTypeSerializer.php │ │ ├── ParishSerializer.php │ │ ├── ProCivOccurrenceSerializer.php │ │ ├── RoleSerializer.php │ │ ├── UserSerializer.php │ │ ├── WarningSerializer.php │ │ ├── WeatherObservationSerializer.php │ │ └── WeatherStationSerializer.php ├── Jobs │ ├── Api │ │ └── ResponseCacheBuster.php │ ├── Ipma │ │ ├── SurfaceObservationFetcher.php │ │ └── WarningFetcher.php │ ├── ProCiv │ │ ├── OccurrenceCloser.php │ │ └── OccurrenceFetcher.php │ └── Report │ │ └── ReportGenerator.php ├── Models │ ├── Acronym.php │ ├── Concerns │ │ └── Cacheable.php │ ├── County.php │ ├── District.php │ ├── Event.php │ ├── EventType.php │ ├── Observers │ │ └── CacheableObserver.php │ ├── Occurrence.php │ ├── OccurrenceFamily.php │ ├── OccurrenceSpecies.php │ ├── OccurrenceStatus.php │ ├── OccurrenceType.php │ ├── Parish.php │ ├── ProCivOccurrence.php │ ├── ProCivOccurrenceLog.php │ ├── Role.php │ ├── User.php │ ├── WeatherObservation.php │ ├── WeatherSensor.php │ └── WeatherStation.php ├── Policies │ ├── AcronymPolicy.php │ ├── EventPolicy.php │ ├── OccurrencePolicy.php │ └── UserPolicy.php ├── Providers │ ├── AppServiceProvider.php │ ├── AuthServiceProvider.php │ ├── EventServiceProvider.php │ ├── FilterServiceProvider.php │ ├── HttpClientServiceProvider.php │ ├── RepositoryServiceProvider.php │ ├── ResponseServiceProvider.php │ ├── RouteServiceProvider.php │ └── ServiceClientServiceProvider.php ├── Reports │ ├── Contracts │ │ └── Report.php │ ├── OccurrenceReport.php │ └── Report.php ├── Repositories │ ├── AcronymRepository.php │ ├── AcronymRepositoryDecorator.php │ ├── Concerns │ │ └── Paginator.php │ ├── Contracts │ │ ├── AcronymRepository.php │ │ ├── CountyRepository.php │ │ ├── DistrictRepository.php │ │ ├── EventRepository.php │ │ ├── OccurrenceFamilyRepository.php │ │ ├── OccurrenceRepository.php │ │ ├── OccurrenceSpeciesRepository.php │ │ ├── OccurrenceStatusRepository.php │ │ ├── OccurrenceTypeRepository.php │ │ ├── Paginator.php │ │ ├── ParishRepository.php │ │ ├── ProCivOccurrenceRepository.php │ │ ├── Repository.php │ │ ├── UserRepository.php │ │ ├── WeatherObservationRepository.php │ │ └── WeatherStationRepository.php │ ├── CountyRepository.php │ ├── CountyRepositoryDecorator.php │ ├── DistrictRepository.php │ ├── DistrictRepositoryDecorator.php │ ├── EventRepository.php │ ├── EventRepositoryDecorator.php │ ├── OccurrenceFamilyRepository.php │ ├── OccurrenceFamilyRepositoryDecorator.php │ ├── OccurrenceRepository.php │ ├── OccurrenceRepositoryDecorator.php │ ├── OccurrenceSpeciesRepository.php │ ├── OccurrenceSpeciesRepositoryDecorator.php │ ├── OccurrenceStatusRepository.php │ ├── OccurrenceStatusRepositoryDecorator.php │ ├── OccurrenceTypeRepository.php │ ├── OccurrenceTypeRepositoryDecorator.php │ ├── ParishRepository.php │ ├── ParishRepositoryDecorator.php │ ├── ProCivOccurrenceRepository.php │ ├── Repository.php │ ├── UserRepository.php │ ├── UserRepositoryDecorator.php │ ├── WeatherObservationRepository.php │ ├── WeatherObservationRepositoryDecorator.php │ └── WeatherStationRepository.php ├── Rules │ └── ValidRole.php ├── ServiceClients │ ├── Contracts │ │ ├── IpmaApiServiceClient.php │ │ ├── ProCivWebsiteServiceClient.php │ │ └── ServiceClient.php │ ├── IpmaApiServiceClient.php │ ├── ProCivWebsiteServiceClient.php │ └── ServiceClient.php └── Support │ ├── ResponseFactory.php │ └── helpers.php ├── artisan ├── bootstrap ├── app.php └── cache │ └── .gitignore ├── composer.json ├── composer.lock ├── config ├── app.php ├── auth.php ├── broadcasting.php ├── cache.php ├── database.php ├── filesystems.php ├── hashing.php ├── jwt.php ├── logging.php ├── mail.php ├── queue.php ├── services.php ├── session.php └── view.php ├── database ├── .gitignore ├── factories │ ├── AcronymFactory.php │ ├── CountyFactory.php │ ├── DistrictFactory.php │ ├── EventFactory.php │ ├── EventTypeFactory.php │ ├── OccurrenceFactory.php │ ├── OccurrenceFamilyFactory.php │ ├── OccurrenceSpeciesFactory.php │ ├── OccurrenceStatusFactory.php │ ├── OccurrenceTypeFactory.php │ ├── ParishFactory.php │ ├── ProCivOccurrenceFactory.php │ ├── ProCivOccurrenceLogFactory.php │ ├── UserFactory.php │ ├── WeatherObservationFactory.php │ ├── WeatherSensorFactory.php │ └── WeatherStationFactory.php ├── migrations │ ├── 2019_04_08_185908_create_users_table.php │ ├── 2019_04_09_095403_create_bouncer_tables.php │ ├── 2019_04_09_095426_create_districts_table.php │ ├── 2019_04_11_145226_create_counties_table.php │ ├── 2019_04_12_074212_create_parishes_table.php │ ├── 2019_04_12_224617_create_acronyms_table.php │ ├── 2019_04_16_222648_create_event_types_table.php │ ├── 2019_04_16_222657_create_events_table.php │ ├── 2019_04_17_203846_create_occurrence_families_table.php │ ├── 2019_04_17_203847_create_occurrence_species_table.php │ ├── 2019_04_17_203847_create_occurrence_statuses_table.php │ ├── 2019_04_17_203848_create_occurrence_types_table.php │ ├── 2019_04_17_203849_create_occurrences_table.php │ ├── 2019_04_17_203850_create_prociv_occurrences_table.php │ ├── 2019_04_17_203851_create_prociv_occurrence_logs_table.php │ ├── 2019_08_13_084800_create_failed_jobs_table.php │ ├── 2019_09_04_133750_create_weather_stations_table.php │ ├── 2019_09_04_133751_create_weather_sensors_table.php │ └── 2019_09_04_133752_create_weather_observations_table.php └── seeds │ ├── AcronymSeeder.php │ ├── CountySeeder.php │ ├── DatabaseSeeder.php │ ├── DistrictSeeder.php │ ├── EventTypeSeeder.php │ ├── IpmaStationAndSensorSeeder.php │ ├── OccurrenceFamilySeeder.php │ ├── OccurrenceSpeciesSeeder.php │ ├── OccurrenceStatusSeeder.php │ ├── OccurrenceTypeSeeder.php │ ├── ParishSeeder.php │ ├── RoleSeeder.php │ └── data │ ├── acronyms.php │ ├── counties.php │ ├── districts.php │ ├── event_types.php │ ├── ipma_sensors.php │ ├── occurrence_families.php │ ├── occurrence_species.php │ ├── occurrence_statuses.php │ ├── occurrence_types.php │ └── parishes.php ├── docker-compose.yml ├── phpunit.xml.dist ├── pre-commit.sh ├── public ├── .htaccess ├── documentation │ ├── acronyms │ │ ├── endpoints.yaml │ │ └── schemas.yaml │ ├── auth │ │ ├── endpoints.yaml │ │ └── schemas.yaml │ ├── common │ │ ├── parameters │ │ │ ├── accept.yaml │ │ │ ├── authorization.yaml │ │ │ ├── content_type.yaml │ │ │ ├── exact.yaml │ │ │ ├── ids.yaml │ │ │ ├── latitude.yaml │ │ │ ├── longitude.yaml │ │ │ ├── order.yaml │ │ │ ├── page_number.yaml │ │ │ ├── page_size.yaml │ │ │ ├── radius.yaml │ │ │ └── search.yaml │ │ └── responses │ │ │ ├── 400.yaml │ │ │ ├── 401.yaml │ │ │ ├── 403.yaml │ │ │ ├── 404.yaml │ │ │ ├── 406.yaml │ │ │ ├── 415.yaml │ │ │ ├── 422.yaml │ │ │ └── 429.yaml │ ├── counties │ │ ├── endpoints.yaml │ │ └── schemas.yaml │ ├── districts │ │ ├── endpoints.yaml │ │ └── schemas.yaml │ ├── events │ │ ├── endpoints.yaml │ │ └── schemas.yaml │ ├── index.php │ ├── ipma │ │ ├── endpoints.yaml │ │ └── schemas.yaml │ ├── logo.png │ ├── occurrences │ │ ├── endpoints.yaml │ │ └── schemas.yaml │ ├── openapi.yaml │ ├── parishes │ │ ├── endpoints.yaml │ │ └── schemas.yaml │ ├── roles │ │ └── schemas.yaml │ ├── users │ │ ├── endpoints.yaml │ │ └── schemas.yaml │ └── weather │ │ ├── endpoints.yaml │ │ └── schemas.yaml ├── favicon.ico ├── index.php ├── robots.txt └── web.config ├── resources └── lang │ └── en │ ├── auth.php │ ├── pagination.php │ ├── passwords.php │ └── validation.php ├── routes ├── api │ ├── acronyms.php │ ├── auth.php │ ├── counties.php │ ├── districts.php │ ├── events.php │ ├── ipma.php │ ├── occurrences.php │ ├── parishes.php │ ├── users.php │ └── weather.php ├── console.php └── web │ └── fallback.php ├── server.php ├── storage ├── app │ ├── .gitignore │ └── public │ │ └── .gitignore ├── framework │ ├── .gitignore │ ├── cache │ │ ├── .gitignore │ │ └── data │ │ │ └── .gitignore │ ├── sessions │ │ └── .gitignore │ ├── testing │ │ └── .gitignore │ └── views │ │ └── .gitignore └── logs │ └── .gitignore └── tests ├── Integration ├── Commands │ ├── Api │ │ └── ResponseCacheBustCommandTest.php │ ├── Ipma │ │ ├── SurfaceObservationFetchCommandTest.php │ │ └── WarningFetchCommandTest.php │ └── ProCiv │ │ ├── OccurrenceCloseCommandTest.php │ │ └── OccurrenceFetchCommandTest.php ├── Controllers │ ├── AcronymController │ │ ├── CreateEndpointTest.php │ │ ├── DeleteEndpointTest.php │ │ ├── IndexEndpointTest.php │ │ ├── UpdateEndpointTest.php │ │ └── ViewEndpointTest.php │ ├── AuthController │ │ ├── AuthenticateEndpointTest.php │ │ ├── RefreshEndpointTest.php │ │ └── VerifyEndpointTest.php │ ├── CountyController │ │ ├── IndexEndpointTest.php │ │ └── ViewEndpointTest.php │ ├── DistrictController │ │ ├── IndexEndpointTest.php │ │ └── ViewEndpointTest.php │ ├── EventController │ │ ├── CreateEndpointTest.php │ │ ├── DeleteEndpointTest.php │ │ ├── IndexEndpointTest.php │ │ ├── UpdateEndpointTest.php │ │ └── ViewEndpointTest.php │ ├── FallbackController │ │ └── DocumentationEndpointTest.php │ ├── IpmaWarningController │ │ └── IndexEndpointTest.php │ ├── OccurrenceController │ │ ├── IndexEndpointTest.php │ │ ├── UpdateEndpointTest.php │ │ └── ViewEndpointTest.php │ ├── OccurrenceFamilyController │ │ └── IndexEndpointTest.php │ ├── OccurrenceReportController │ │ ├── DownloadReportEndpointTest.php │ │ └── GenerateReportEndpointTest.php │ ├── OccurrenceSpeciesController │ │ └── IndexEndpointTest.php │ ├── OccurrenceStatusController │ │ └── IndexEndpointTest.php │ ├── OccurrenceTypeController │ │ └── IndexEndpointTest.php │ ├── ParishController │ │ ├── IndexEndpointTest.php │ │ └── ViewEndpointTest.php │ ├── UserController │ │ ├── CreateEndpointTest.php │ │ ├── IndexEndpointTest.php │ │ ├── UpdateEndpointTest.php │ │ └── ViewEndpointTest.php │ ├── UserProfileController │ │ ├── UpdateEndpointTest.php │ │ └── ViewEndpointTest.php │ ├── UserRoleController │ │ └── IndexEndpointTest.php │ └── WeatherObservationController │ │ ├── IndexEndpointTest.php │ │ └── ViewEndpointTest.php ├── CreatesApplication.php ├── HttpClientMocker.php ├── RefreshDatabaseWithRoles.php └── TestCase.php ├── Unit └── TestCase.php └── data ├── Ipma ├── SurfaceObservations.json └── Warnings.json └── ProCiv ├── MainOccurrences.json └── OccurrencesByLocation.json /.docker/nginx/conf.d/api.conf: -------------------------------------------------------------------------------- 1 | server { 2 | server_name api.vost.test; 3 | listen 80; 4 | index index.php index.html; 5 | 6 | error_log /var/log/nginx/error.log; 7 | access_log /var/log/nginx/access.log; 8 | 9 | root /var/www/api.vost.test/public; 10 | 11 | location / { 12 | try_files $uri $uri/ /index.php?$query_string; 13 | gzip_static on; 14 | } 15 | 16 | location ~ \.php$ { 17 | try_files $uri =404; 18 | fastcgi_split_path_info ^(.+\.php)(/.+)$; 19 | fastcgi_pass vost_api:9000; 20 | fastcgi_index index.php; 21 | include fastcgi_params; 22 | fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; 23 | fastcgi_param PATH_INFO $fastcgi_path_info; 24 | 25 | # 26 | # CORS 27 | # 28 | add_header 'Access-Control-Allow-Origin' '*' always; 29 | add_header 'Access-Control-Allow-Credentials' 'true'; 30 | add_header 'Access-Control-Allow-Methods' 'GET, POST, DELETE, PUT'; 31 | add_header 'Access-Control-Allow-Headers' 'Version,Accept,Accept-Encoding,Accept-Language,Connection,Cookie,Authorization,DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type'; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /.docker/php/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM php:7.3.11-fpm 2 | 3 | RUN apt-get update && apt-get install -y \ 4 | build-essential \ 5 | curl \ 6 | git \ 7 | libmcrypt-dev \ 8 | locales \ 9 | unzip \ 10 | vim \ 11 | libzip-dev \ 12 | zip \ 13 | --no-install-recommends && \ 14 | apt-get clean && rm -rf /var/lib/apt/lists/* 15 | 16 | RUN docker-php-ext-install \ 17 | pcntl \ 18 | pdo_mysql \ 19 | zip 20 | 21 | RUN pecl channel-update pecl.php.net \ 22 | && printf "\n" | pecl install mcrypt-1.0.3 \ 23 | && printf "\n" | pecl install xdebug-2.9.0 \ 24 | && printf "\n" | pecl install redis-5.0.2 \ 25 | && docker-php-ext-enable mcrypt xdebug redis 26 | 27 | COPY conf/xdebug.ini /usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini 28 | COPY conf/zz-docker.conf /usr/local/etc/php-fpm.d/zz-docker.conf 29 | 30 | COPY --from=composer /usr/bin/composer /usr/bin/composer 31 | 32 | WORKDIR /var/www/api.vost.test 33 | -------------------------------------------------------------------------------- /.docker/php/conf/xdebug.ini: -------------------------------------------------------------------------------- 1 | zend_extension=/usr/local/lib/php/extensions/no-debug-non-zts-20180731/xdebug.so 2 | xdebug.remote_connect_back=1 3 | xdebug.remote_enable=1 4 | xdebug.remote_port=9000 5 | -------------------------------------------------------------------------------- /.docker/php/conf/zz-docker.conf: -------------------------------------------------------------------------------- 1 | [global] 2 | daemonize = no 3 | 4 | [www] 5 | listen = 9000 6 | 7 | pm = dynamic 8 | pm.max_children = 64 9 | pm.process_idle_timeout = 2s 10 | pm.max_requests = 1000 11 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | end_of_line = lf 6 | insert_final_newline = true 7 | indent_style = space 8 | indent_size = 4 9 | trim_trailing_whitespace = true 10 | 11 | [*.md] 12 | trim_trailing_whitespace = false 13 | 14 | [*.yml] 15 | indent_size = 2 16 | -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | APP_NAME="VOST Portugal API" 2 | APP_ENV=local 3 | APP_KEY= 4 | APP_DEBUG=true 5 | APP_URL=http://api.vost.test 6 | 7 | #LOCAL_STORAGE_PATH=/path 8 | 9 | # 10 | # JWT secret 11 | # 12 | JWT_SECRET= 13 | 14 | LOG_CHANNEL=stack 15 | 16 | DB_CONNECTION=mariadb 17 | DB_HOST=vost_mariadb 18 | DB_PORT=3306 19 | DB_DATABASE=vost_api 20 | DB_USERNAME=root 21 | DB_PASSWORD=root 22 | 23 | BROADCAST_DRIVER=log 24 | CACHE_DRIVER=redis 25 | QUEUE_CONNECTION=redis 26 | SESSION_DRIVER=redis 27 | SESSION_LIFETIME=120 28 | 29 | REDIS_HOST=vost_redis 30 | REDIS_PASSWORD=null 31 | REDIS_PORT=6379 32 | 33 | MAIL_DRIVER=smtp 34 | MAIL_HOST=smtp.mailtrap.io 35 | MAIL_PORT=2525 36 | MAIL_USERNAME=null 37 | MAIL_PASSWORD=null 38 | MAIL_ENCRYPTION=null 39 | 40 | # 41 | # Service Clients 42 | # 43 | 44 | IPMA_API_HOSTNAME=https://api.ipma.pt 45 | 46 | PROCIV_WEBSITE_HOSTNAME=http://www.prociv.pt 47 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | *.css linguist-vendored 3 | *.scss linguist-vendored 4 | *.js linguist-vendored 5 | CHANGELOG.md export-ignore 6 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 4 | 5 | ## Description 6 | This pull request addresses issue [API-XYZ](https://github.com/vostpt/api/issues/XYZ). 7 | 8 | ## Task items: 9 | - [ ] Fixed issue X; 10 | - [ ] Added feature Y; 11 | - [ ] Updated the documentation for Z; 12 | - [ ] Increased test coverage for W; 13 | - [ ] Other task related item; 14 | - [ ] ... 15 | 16 | ## Requirements (optional) 17 | Requirements when deploying the current work to an environment (`dev`, `staging` or `production`): 18 | 19 | - Migrations need to be executed; 20 | - An `ENV` variable needs to be added; 21 | - Permissions need to be set for `X`, `Y` and `Z`; 22 | - ... 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /coverage 2 | /node_modules 3 | /public/hot 4 | /public/storage 5 | /storage/*.key 6 | /vendor 7 | /.idea 8 | /.vscode 9 | /.php_cs.cache 10 | .DS_Store 11 | .env 12 | .phpunit.result.cache 13 | Homestead.json 14 | Homestead.yaml 15 | npm-debug.log 16 | yarn-error.log 17 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | 3 | dist: bionic 4 | 5 | env: 6 | - DB_HOST=localhost DB_USERNAME=travis 7 | 8 | addons: 9 | mariadb: '10.4' 10 | 11 | php: 12 | - '7.3' 13 | 14 | matrix: 15 | fast_finish: true 16 | 17 | sudo: true 18 | 19 | before_install: 20 | - echo "extension = redis.so" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini 21 | 22 | before_script: 23 | # Database setup 24 | - sudo mysql -e 'create database vost_api;' 25 | - sudo mysql -e 'CREATE USER IF NOT EXISTS travis@localhost; GRANT ALL ON *.* TO travis@localhost;' 26 | 27 | # Install project dependencies 28 | - composer install --no-interaction 29 | 30 | # Clear everything 31 | - php artisan optimize:clear 32 | 33 | # Create optimised classmap 34 | - composer dump-autoload -o 35 | 36 | # Check coding standards 37 | - composer cs-check app 38 | - composer cs-check config 39 | - composer cs-check database 40 | - composer cs-check tests 41 | 42 | script: 43 | - mkdir -p build/logs 44 | - vendor/bin/phpunit --dump-xdebug-filter build/xdebug-filter.php 45 | - vendor/bin/phpunit --testdox --prepend build/xdebug-filter.php --coverage-text --coverage-clover build/logs/clover.xml 46 | 47 | after_success: 48 | - vendor/bin/php-coveralls -v 49 | 50 | cache: 51 | directories: 52 | - vendor 53 | - $HOME/.composer/cache 54 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | ### The MIT License (MIT) 2 | 3 | Copyright (C) 2019 VOST Portugal. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Supported Versions 4 | 5 | Use this section to tell people about which versions of your project are 6 | currently being supported with security updates. 7 | 8 | | Version | Supported | 9 | | ------- | ------------------ | 10 | | 5.1.x | :white_check_mark: | 11 | | 5.0.x | :x: | 12 | | 4.0.x | :white_check_mark: | 13 | | < 4.0 | :x: | 14 | 15 | ## Reporting a Vulnerability 16 | 17 | Use this section to tell people how to report a vulnerability. 18 | 19 | Tell them where to go, how often they can expect to get an update on a 20 | reported vulnerability, what to expect if the vulnerability is accepted or 21 | declined, etc. 22 | -------------------------------------------------------------------------------- /app/Console/Commands/ApiResponseCacheBustCommand.php: -------------------------------------------------------------------------------- 1 | dispatchNow(new ResponseCacheBuster()); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /app/Console/Commands/IpmaSurfaceObservationFetchCommand.php: -------------------------------------------------------------------------------- 1 | dispatchNow(new SurfaceObservationFetcher()); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /app/Console/Commands/IpmaWarningFetchCommand.php: -------------------------------------------------------------------------------- 1 | dispatchNow(new WarningFetcher()); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /app/Console/Commands/ProCivOccurrenceCloseCommand.php: -------------------------------------------------------------------------------- 1 | dispatchNow(new OccurrenceCloser()); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /app/Console/Commands/ProCivOccurrenceFetchCommand.php: -------------------------------------------------------------------------------- 1 | dispatchNow(new OccurrenceFetcher()); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /app/Console/Kernel.php: -------------------------------------------------------------------------------- 1 | job(new OccurrenceFetcher())->everyFiveMinutes(); 29 | $schedule->job(new OccurrenceCloser())->everyThirtyMinutes(); 30 | 31 | // Fetch IPMA warnings and surface observations 32 | $schedule->job(new WarningFetcher())->everyThirtyMinutes(); 33 | $schedule->job(new SurfaceObservationFetcher())->everyThirtyMinutes(); 34 | 35 | // Bust the API response cache 36 | $schedule->job(new ResponseCacheBuster())->everyFiveMinutes(); 37 | } 38 | 39 | /** 40 | * {@inheritDoc} 41 | */ 42 | protected function commands(): void 43 | { 44 | $this->load(__DIR__.'/Commands'); 45 | 46 | require base_path('routes/console.php'); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /app/Exceptions/Handler.php: -------------------------------------------------------------------------------- 1 | getMessage(), $exception); 23 | } 24 | 25 | if ($exception instanceof HttpException) { 26 | return response()->error($exception); 27 | } 28 | 29 | if ($exception instanceof ValidationException) { 30 | return response()->validation($exception); 31 | } 32 | 33 | return response()->json([ 34 | 'errors' => [ 35 | [ 36 | 'id' => $exception->getCode(), 37 | 'detail' => $exception->getMessage(), 38 | ], 39 | ], 40 | ], 500); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /app/Filters/AcronymFilter.php: -------------------------------------------------------------------------------- 1 | collection(new Collection($cache->get('ipma_warnings', [])), new WarningSerializer(), [ 27 | 'county', 28 | 'county.district', 29 | ]); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /app/Http/Controllers/UserRoleController.php: -------------------------------------------------------------------------------- 1 | collection(Role::all(), new RoleSerializer()); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /app/Http/Middleware/CheckForMaintenanceMode.php: -------------------------------------------------------------------------------- 1 | header('Content-Type'); 30 | 31 | if ($contentTypeHeader && $contentTypeHeader !== self::MEDIA_TYPE && \mb_stripos($contentTypeHeader, self::MEDIA_TYPE) !== false) { 32 | throw new UnsupportedMediaTypeHttpException('Unsupported media type'); 33 | } 34 | 35 | $acceptHeader = $request->header('Accept'); 36 | 37 | if ($acceptHeader && $acceptHeader !== self::MEDIA_TYPE && \mb_stripos($acceptHeader, self::MEDIA_TYPE) !== false) { 38 | throw new NotAcceptableHttpException('Not acceptable'); 39 | } 40 | 41 | return $next($request); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /app/Http/Requests/Acronym/Create.php: -------------------------------------------------------------------------------- 1 | user()->can('create', Acronym::class); 19 | } 20 | 21 | /** 22 | * {@inheritDoc} 23 | */ 24 | public function rules(): array 25 | { 26 | return [ 27 | 'initials' => [ 28 | 'required', 29 | 'string', 30 | 'max:16', 31 | Rule::unique('acronyms', 'initials'), 32 | ], 33 | 'meaning' => [ 34 | 'required', 35 | 'string', 36 | 'max:255', 37 | ], 38 | ]; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /app/Http/Requests/Acronym/Delete.php: -------------------------------------------------------------------------------- 1 | route('Acronym'); 17 | 18 | return $this->user()->can('delete', $acronym); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /app/Http/Requests/Acronym/Index.php: -------------------------------------------------------------------------------- 1 | [ 20 | 'integer', 21 | ], 22 | 'page.size' => [ 23 | 'integer', 24 | ], 25 | 'ids' => [ 26 | 'array', 27 | ], 28 | 'ids.*' => [ 29 | Rule::exists('acronyms', 'id'), 30 | ], 31 | 'search' => [ 32 | 'string', 33 | ], 34 | 'exact' => [ 35 | 'boolean', 36 | ], 37 | 'sort' => [ 38 | Rule::in(AcronymFilter::getSortableColumns()), 39 | ], 40 | 'order' => [ 41 | Rule::in(AcronymFilter::getOrderValues()), 42 | ], 43 | ]; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /app/Http/Requests/Acronym/Update.php: -------------------------------------------------------------------------------- 1 | route('Acronym'); 18 | 19 | return $this->user()->can('update', $acronym); 20 | } 21 | 22 | /** 23 | * {@inheritDoc} 24 | */ 25 | public function rules(): array 26 | { 27 | return [ 28 | 'initials' => [ 29 | 'string', 30 | 'max:16', 31 | Rule::unique('acronyms', 'initials') 32 | ->ignoreModel($this->route('Acronym')), 33 | ], 34 | 'meaning' => [ 35 | 'string', 36 | 'max:255', 37 | ], 38 | ]; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /app/Http/Requests/Acronym/View.php: -------------------------------------------------------------------------------- 1 | [ 18 | 'required', 19 | 'email', 20 | ], 21 | 'password' => [ 22 | 'required', 23 | ], 24 | ]; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/Http/Requests/Auth/Refresh.php: -------------------------------------------------------------------------------- 1 | user()->can('refresh', User::class); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /app/Http/Requests/Auth/Verify.php: -------------------------------------------------------------------------------- 1 | user()->can('verify', User::class); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /app/Http/Requests/County/Index.php: -------------------------------------------------------------------------------- 1 | [ 20 | 'integer', 21 | ], 22 | 'page.size' => [ 23 | 'integer', 24 | ], 25 | 'ids' => [ 26 | 'array', 27 | ], 28 | 'ids.*' => [ 29 | Rule::exists('counties', 'id'), 30 | ], 31 | 'search' => [ 32 | 'string', 33 | ], 34 | 'exact' => [ 35 | 'boolean', 36 | ], 37 | 'districts' => [ 38 | 'array', 39 | ], 40 | 'districts.*' => [ 41 | Rule::exists('districts', 'id'), 42 | ], 43 | 'sort' => [ 44 | Rule::in(CountyFilter::getSortableColumns()), 45 | ], 46 | 'order' => [ 47 | Rule::in(CountyFilter::getOrderValues()), 48 | ], 49 | ]; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /app/Http/Requests/County/View.php: -------------------------------------------------------------------------------- 1 | [ 20 | 'integer', 21 | ], 22 | 'page.size' => [ 23 | 'integer', 24 | ], 25 | 'ids' => [ 26 | 'array', 27 | ], 28 | 'ids.*' => [ 29 | Rule::exists('districts', 'id'), 30 | ], 31 | 'search' => [ 32 | 'string', 33 | ], 34 | 'exact' => [ 35 | 'boolean', 36 | ], 37 | 'sort' => [ 38 | Rule::in(DistrictFilter::getSortableColumns()), 39 | ], 40 | 'order' => [ 41 | Rule::in(DistrictFilter::getOrderValues()), 42 | ], 43 | ]; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /app/Http/Requests/District/View.php: -------------------------------------------------------------------------------- 1 | route('Event'); 17 | 18 | return $this->user()->can('delete', $event); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /app/Http/Requests/Event/View.php: -------------------------------------------------------------------------------- 1 | route('Occurrence'); 18 | 19 | return $this->user()->can('update', $occurrence); 20 | } 21 | 22 | /** 23 | * {@inheritDoc} 24 | */ 25 | public function rules(): array 26 | { 27 | return [ 28 | 'event' => [ 29 | 'nullable', 30 | Rule::exists('events', 'id'), 31 | ], 32 | ]; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /app/Http/Requests/Occurrence/View.php: -------------------------------------------------------------------------------- 1 | [ 20 | 'integer', 21 | ], 22 | 'page.size' => [ 23 | 'integer', 24 | ], 25 | 'ids' => [ 26 | 'array', 27 | ], 28 | 'ids.*' => [ 29 | Rule::exists('occurrence_families', 'id'), 30 | ], 31 | 'search' => [ 32 | 'string', 33 | ], 34 | 'exact' => [ 35 | 'boolean', 36 | ], 37 | 'sort' => [ 38 | Rule::in(OccurrenceFamilyFilter::getSortableColumns()), 39 | ], 40 | 'order' => [ 41 | Rule::in(OccurrenceFamilyFilter::getOrderValues()), 42 | ], 43 | ]; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /app/Http/Requests/OccurrenceSpecies/Index.php: -------------------------------------------------------------------------------- 1 | [ 20 | 'integer', 21 | ], 22 | 'page.size' => [ 23 | 'integer', 24 | ], 25 | 'ids' => [ 26 | 'array', 27 | ], 28 | 'ids.*' => [ 29 | Rule::exists('occurrence_species', 'id'), 30 | ], 31 | 'search' => [ 32 | 'string', 33 | ], 34 | 'exact' => [ 35 | 'boolean', 36 | ], 37 | 'families' => [ 38 | 'array', 39 | ], 40 | 'families.*' => [ 41 | Rule::exists('occurrence_families', 'id'), 42 | ], 43 | 'sort' => [ 44 | Rule::in(OccurrenceSpeciesFilter::getSortableColumns()), 45 | ], 46 | 'order' => [ 47 | Rule::in(OccurrenceSpeciesFilter::getOrderValues()), 48 | ], 49 | ]; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /app/Http/Requests/OccurrenceStatus/Index.php: -------------------------------------------------------------------------------- 1 | [ 20 | 'integer', 21 | ], 22 | 'page.size' => [ 23 | 'integer', 24 | ], 25 | 'ids' => [ 26 | 'array', 27 | ], 28 | 'ids.*' => [ 29 | Rule::exists('occurrence_statuses', 'id'), 30 | ], 31 | 'search' => [ 32 | 'string', 33 | ], 34 | 'exact' => [ 35 | 'boolean', 36 | ], 37 | 'sort' => [ 38 | Rule::in(OccurrenceStatusFilter::getSortableColumns()), 39 | ], 40 | 'order' => [ 41 | Rule::in(OccurrenceStatusFilter::getOrderValues()), 42 | ], 43 | ]; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /app/Http/Requests/OccurrenceType/Index.php: -------------------------------------------------------------------------------- 1 | [ 20 | 'integer', 21 | ], 22 | 'page.size' => [ 23 | 'integer', 24 | ], 25 | 'ids' => [ 26 | 'array', 27 | ], 28 | 'ids.*' => [ 29 | Rule::exists('occurrence_types', 'id'), 30 | ], 31 | 'search' => [ 32 | 'string', 33 | ], 34 | 'exact' => [ 35 | 'boolean', 36 | ], 37 | 'species' => [ 38 | 'array', 39 | ], 40 | 'species.*' => [ 41 | Rule::exists('occurrence_species', 'id'), 42 | ], 43 | 'sort' => [ 44 | Rule::in(OccurrenceTypeFilter::getSortableColumns()), 45 | ], 46 | 'order' => [ 47 | Rule::in(OccurrenceTypeFilter::getOrderValues()), 48 | ], 49 | ]; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /app/Http/Requests/Parish/Index.php: -------------------------------------------------------------------------------- 1 | [ 20 | 'integer', 21 | ], 22 | 'page.size' => [ 23 | 'integer', 24 | ], 25 | 'ids' => [ 26 | 'array', 27 | ], 28 | 'ids.*' => [ 29 | Rule::exists('parishes', 'id'), 30 | ], 31 | 'search' => [ 32 | 'string', 33 | ], 34 | 'exact' => [ 35 | 'boolean', 36 | ], 37 | 'counties' => [ 38 | 'array', 39 | ], 40 | 'counties.*' => [ 41 | Rule::exists('counties', 'id'), 42 | ], 43 | 'sort' => [ 44 | Rule::in(ParishFilter::getSortableColumns()), 45 | ], 46 | 'order' => [ 47 | Rule::in(ParishFilter::getOrderValues()), 48 | ], 49 | ]; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /app/Http/Requests/Parish/View.php: -------------------------------------------------------------------------------- 1 | [ 19 | 'required', 20 | 'string', 21 | 'max:255', 22 | ], 23 | 'surname' => [ 24 | 'required', 25 | 'string', 26 | 'max:255', 27 | ], 28 | 'email' => [ 29 | 'required', 30 | 'email', 31 | 'max:255', 32 | Rule::unique('users', 'email'), 33 | ], 34 | 'password' => [ 35 | 'required', 36 | 'confirmed', 37 | ], 38 | 'password_confirmation' => [ 39 | 'required', 40 | ], 41 | ]; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /app/Http/Requests/User/Index.php: -------------------------------------------------------------------------------- 1 | user()->can('index', User::class); 21 | } 22 | 23 | /** 24 | * {@inheritDoc} 25 | */ 26 | public function rules(): array 27 | { 28 | return [ 29 | 'page.number' => [ 30 | 'integer', 31 | ], 32 | 'page.size' => [ 33 | 'integer', 34 | ], 35 | 'ids' => [ 36 | 'array', 37 | ], 38 | 'ids.*' => [ 39 | Rule::exists('users', 'id'), 40 | ], 41 | 'search' => [ 42 | 'string', 43 | ], 44 | 'exact' => [ 45 | 'boolean', 46 | ], 47 | 'sort' => [ 48 | Rule::in(UserFilter::getSortableColumns()), 49 | ], 50 | 'order' => [ 51 | Rule::in(UserFilter::getOrderValues()), 52 | ], 53 | 'roles' => [ 54 | 'array', 55 | ], 56 | 'roles.*' => [ 57 | new ValidRole(), 58 | ], 59 | ]; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /app/Http/Requests/User/RetrieveRoles.php: -------------------------------------------------------------------------------- 1 | user()->can('retrieveRoles', User::class); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /app/Http/Requests/User/Update.php: -------------------------------------------------------------------------------- 1 | route('User'); 18 | 19 | return $this->user()->can('update', $userToUpdate); 20 | } 21 | 22 | /** 23 | * {@inheritDoc} 24 | */ 25 | public function rules(): array 26 | { 27 | return [ 28 | 'name' => [ 29 | 'string', 30 | 'max:255', 31 | ], 32 | 'surname' => [ 33 | 'string', 34 | 'max:255', 35 | ], 36 | 'email' => [ 37 | 'email', 38 | 'max:255', 39 | Rule::unique('users', 'email') 40 | ->ignoreModel($this->route('User')), 41 | ], 42 | 'password' => [ 43 | 'required_with:password_confirmation', 44 | 'confirmed', 45 | ], 46 | 'password_confirmation' => [ 47 | 'required_with:password', 48 | ], 49 | 'roles' => [ 50 | 'array', 51 | ], 52 | 'roles.*' => [ 53 | Rule::exists('roles', 'name'), 54 | ], 55 | ]; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /app/Http/Requests/User/UpdateProfile.php: -------------------------------------------------------------------------------- 1 | user()->can('updateProfile', User::class); 19 | } 20 | 21 | /** 22 | * {@inheritDoc} 23 | */ 24 | public function rules(): array 25 | { 26 | return [ 27 | 'name' => [ 28 | 'string', 29 | 'max:255', 30 | ], 31 | 'surname' => [ 32 | 'string', 33 | 'max:255', 34 | ], 35 | 'email' => [ 36 | 'email', 37 | 'max:255', 38 | Rule::unique('users', 'email') 39 | ->ignoreModel($this->user()), 40 | ], 41 | 'password' => [ 42 | 'required_with:password_confirmation', 43 | 'confirmed', 44 | ], 45 | 'password_confirmation' => [ 46 | 'required_with:password', 47 | ], 48 | ]; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /app/Http/Requests/User/View.php: -------------------------------------------------------------------------------- 1 | user()->can('view', User::class); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /app/Http/Requests/User/ViewProfile.php: -------------------------------------------------------------------------------- 1 | user()->can('viewProfile', User::class); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /app/Http/Requests/Warning/Index.php: -------------------------------------------------------------------------------- 1 | $model->initials, 23 | 'meaning' => $model->meaning, 24 | 'created_at' => $model->created_at->toDateTimeString(), 25 | 'updated_at' => $model->updated_at->toDateTimeString(), 26 | ]; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /app/Http/Serializers/CountySerializer.php: -------------------------------------------------------------------------------- 1 | route('counties::view', [ 26 | 'County' => $model->getKey(), 27 | ]), 28 | ]; 29 | } 30 | 31 | /** 32 | * {@inheritDoc} 33 | */ 34 | public function getAttributes($model, array $fields = null): array 35 | { 36 | return [ 37 | 'code' => $model->code, 38 | 'name' => $model->name, 39 | 'created_at' => $model->created_at->toDateTimeString(), 40 | 'updated_at' => $model->updated_at->toDateTimeString(), 41 | ]; 42 | } 43 | 44 | /** 45 | * Associated District. 46 | * 47 | * @param County $county 48 | * 49 | * @return \Tobscure\JsonApi\Relationship 50 | */ 51 | public function district(County $county): Relationship 52 | { 53 | return new Relationship(new Resource($county->district()->first(), new DistrictSerializer())); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /app/Http/Serializers/DistrictSerializer.php: -------------------------------------------------------------------------------- 1 | route('districts::view', [ 23 | 'District' => $model->getKey(), 24 | ]), 25 | ]; 26 | } 27 | 28 | /** 29 | * {@inheritDoc} 30 | */ 31 | public function getAttributes($model, array $fields = null): array 32 | { 33 | return [ 34 | 'code' => $model->code, 35 | 'name' => $model->name, 36 | 'created_at' => $model->created_at->toDateTimeString(), 37 | 'updated_at' => $model->updated_at->toDateTimeString(), 38 | ]; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /app/Http/Serializers/EventTypeSerializer.php: -------------------------------------------------------------------------------- 1 | $model->name, 23 | 'created_at' => $model->created_at->toDateTimeString(), 24 | 'updated_at' => $model->updated_at->toDateTimeString(), 25 | ]; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /app/Http/Serializers/OccurrenceFamilySerializer.php: -------------------------------------------------------------------------------- 1 | $model->code, 23 | 'name' => $model->name, 24 | 'created_at' => $model->created_at->toDateTimeString(), 25 | 'updated_at' => $model->updated_at->toDateTimeString(), 26 | ]; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /app/Http/Serializers/OccurrenceSpeciesSerializer.php: -------------------------------------------------------------------------------- 1 | $model->code, 26 | 'name' => $model->name, 27 | 'created_at' => $model->created_at->toDateTimeString(), 28 | 'updated_at' => $model->updated_at->toDateTimeString(), 29 | ]; 30 | } 31 | 32 | /** 33 | * Associated Family. 34 | * 35 | * @param OccurrenceSpecies $occurrenceSpecies 36 | * 37 | * @return \Tobscure\JsonApi\Relationship 38 | */ 39 | public function family(OccurrenceSpecies $occurrenceSpecies): Relationship 40 | { 41 | return new Relationship(new Resource($occurrenceSpecies->family()->first(), new OccurrenceFamilySerializer())); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /app/Http/Serializers/OccurrenceStatusSerializer.php: -------------------------------------------------------------------------------- 1 | $model->code, 23 | 'name' => $model->name, 24 | 'created_at' => $model->created_at->toDateTimeString(), 25 | 'updated_at' => $model->updated_at->toDateTimeString(), 26 | ]; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /app/Http/Serializers/OccurrenceTypeSerializer.php: -------------------------------------------------------------------------------- 1 | $model->code, 26 | 'name' => $model->name, 27 | 'created_at' => $model->created_at->toDateTimeString(), 28 | 'updated_at' => $model->updated_at->toDateTimeString(), 29 | ]; 30 | } 31 | 32 | /** 33 | * Associated Species. 34 | * 35 | * @param OccurrenceType $occurrenceType 36 | * 37 | * @return \Tobscure\JsonApi\Relationship 38 | */ 39 | public function species(OccurrenceType $occurrenceType): Relationship 40 | { 41 | return new Relationship(new Resource($occurrenceType->species()->first(), new OccurrenceSpeciesSerializer())); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /app/Http/Serializers/ParishSerializer.php: -------------------------------------------------------------------------------- 1 | route('parishes::view', [ 26 | 'Parish' => $model->getKey(), 27 | ]), 28 | ]; 29 | } 30 | 31 | /** 32 | * {@inheritDoc} 33 | */ 34 | public function getAttributes($model, array $fields = null): array 35 | { 36 | return [ 37 | 'code' => $model->code, 38 | 'name' => $model->name, 39 | 'created_at' => $model->created_at->toDateTimeString(), 40 | 'updated_at' => $model->updated_at->toDateTimeString(), 41 | ]; 42 | } 43 | 44 | /** 45 | * Associated County. 46 | * 47 | * @param Parish $parish 48 | * 49 | * @return \Tobscure\JsonApi\Relationship 50 | */ 51 | public function county(Parish $parish): Relationship 52 | { 53 | return new Relationship(new Resource($parish->county()->first(), new CountySerializer())); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /app/Http/Serializers/ProCivOccurrenceSerializer.php: -------------------------------------------------------------------------------- 1 | $model->remote_id, 23 | 'ground_assets' => (int) $model->ground_assets, 24 | 'ground_operatives' => (int) $model->ground_operatives, 25 | 'aerial_assets' => (int) $model->aerial_assets, 26 | 'aerial_operatives' => (int) $model->aerial_operatives, 27 | 'created_at' => $model->created_at->toDateTimeString(), 28 | 'updated_at' => $model->updated_at->toDateTimeString(), 29 | ]; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /app/Http/Serializers/RoleSerializer.php: -------------------------------------------------------------------------------- 1 | $model->name, 23 | 'title' => $model->title, 24 | 'created_at' => $model->created_at->toDateTimeString(), 25 | 'updated_at' => $model->updated_at->toDateTimeString(), 26 | ]; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /app/Http/Serializers/UserSerializer.php: -------------------------------------------------------------------------------- 1 | route('users::view', [ 26 | 'User' => $model->getKey(), 27 | ]), 28 | ]; 29 | } 30 | 31 | /** 32 | * {@inheritDoc} 33 | */ 34 | public function getAttributes($model, array $fields = null): array 35 | { 36 | return [ 37 | 'email' => $model->email, 38 | 'name' => $model->name, 39 | 'surname' => $model->surname, 40 | 'created_at' => $model->created_at->toDateTimeString(), 41 | 'updated_at' => $model->updated_at->toDateTimeString(), 42 | ]; 43 | } 44 | 45 | /** 46 | * Associated Roles. 47 | * 48 | * @param User $user 49 | * 50 | * @return \Tobscure\JsonApi\Relationship 51 | */ 52 | public function roles(User $user): Relationship 53 | { 54 | return new Relationship(new Collection($user->roles()->get(), new RoleSerializer())); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /app/Http/Serializers/WarningSerializer.php: -------------------------------------------------------------------------------- 1 | $warning['text'], 33 | 'awareness_type_name' => $warning['awarenessTypeName'], 34 | 'awareness_level' => $warning['awarenessLevelID'], 35 | 'started_at' => $warning['started_at']->toDateTimeString(), 36 | 'ended_at' => $warning['ended_at']->toDateTimeString(), 37 | ]; 38 | } 39 | 40 | /** 41 | * Associated County. 42 | * 43 | * @param array $warning 44 | * 45 | * @return \Tobscure\JsonApi\Relationship 46 | */ 47 | public function county(array $warning): Relationship 48 | { 49 | return new Relationship(new Resource($warning['county'], new CountySerializer())); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /app/Http/Serializers/WeatherStationSerializer.php: -------------------------------------------------------------------------------- 1 | $model->entity, 26 | 'name' => $model->name, 27 | 'serial' => $model->serial, 28 | 'created_at' => $model->created_at->toDateTimeString(), 29 | 'updated_at' => $model->updated_at->toDateTimeString(), 30 | ]; 31 | } 32 | 33 | /** 34 | * Associated County. 35 | * 36 | * @param WeatherStation $weatherStation 37 | * 38 | * @return \Tobscure\JsonApi\Relationship 39 | */ 40 | public function county(WeatherStation $weatherStation): Relationship 41 | { 42 | return new Relationship(new Resource($weatherStation->county()->first(), new CountySerializer())); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /app/Jobs/Api/ResponseCacheBuster.php: -------------------------------------------------------------------------------- 1 | info(\sprintf('Busting API response cache for: %s', \implode(', ', $tags))); 34 | 35 | if ($cache->tags($tags)->flush()) { 36 | $logger->info(\sprintf('Clearing bust tags for: %s', \implode(', ', $tags))); 37 | 38 | return clear_cache_bust_tags(); 39 | } 40 | } 41 | 42 | return false; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /app/Jobs/Report/ReportGenerator.php: -------------------------------------------------------------------------------- 1 | report = $report; 35 | } 36 | 37 | /** 38 | * Execute the job. 39 | * 40 | * @param \Psr\Log\LoggerInterface $logger 41 | * 42 | * @return bool 43 | */ 44 | public function handle(LoggerInterface $logger): bool 45 | { 46 | return $this->report->isReadyForDownload() || $this->report->generate($logger); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /app/Models/Acronym.php: -------------------------------------------------------------------------------- 1 | 16) { 31 | throw new LengthException('The initials cannot have more than 16 characters'); 32 | } 33 | 34 | $this->attributes['initials'] = \mb_strtoupper(\trim($initials)); 35 | } 36 | 37 | /** 38 | * Set the meaning. 39 | * 40 | * @param string $meaning 41 | * 42 | * @throws \LengthException 43 | * 44 | * @return void 45 | */ 46 | public function setMeaningAttribute(string $meaning): void 47 | { 48 | if (\mb_strlen($meaning) > 255) { 49 | throw new LengthException('The meaning cannot have more than 255 characters'); 50 | } 51 | 52 | $this->attributes['meaning'] = $meaning; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /app/Models/Concerns/Cacheable.php: -------------------------------------------------------------------------------- 1 | hasMany(Event::class); 26 | } 27 | 28 | /** 29 | * Set the name. 30 | * 31 | * @param string $name 32 | * 33 | * @throws \LengthException 34 | * 35 | * @return void 36 | */ 37 | public function setNameAttribute(string $name): void 38 | { 39 | if (\mb_strlen($name) > 255) { 40 | throw new LengthException('The name cannot exceed 255 characters'); 41 | } 42 | 43 | $this->attributes['name'] = $name; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /app/Models/Observers/CacheableObserver.php: -------------------------------------------------------------------------------- 1 | markForCacheBust($model); 23 | } 24 | 25 | /** 26 | * Handle the updated event. 27 | * 28 | * @param Model $model 29 | * 30 | * @throws \Exception 31 | * 32 | * @return void 33 | */ 34 | public function updated(Model $model): void 35 | { 36 | $this->markForCacheBust($model); 37 | } 38 | 39 | /** 40 | * Handle the deleted event. 41 | * 42 | * @param Model $model 43 | * 44 | * @throws \Exception 45 | * 46 | * @return void 47 | */ 48 | public function deleted(Model $model): void 49 | { 50 | $this->markForCacheBust($model); 51 | } 52 | 53 | /** 54 | * Mark cache for bust. 55 | * 56 | * @param Model $model 57 | * 58 | * @throws \Exception 59 | * 60 | * @return void 61 | */ 62 | private function markForCacheBust(Model $model): void 63 | { 64 | add_cache_bust_tag(\get_class($model)); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /app/Models/OccurrenceFamily.php: -------------------------------------------------------------------------------- 1 | hasMany(OccurrenceSpecies::class); 28 | } 29 | 30 | /** 31 | * Set the name. 32 | * 33 | * @param string $name 34 | * 35 | * @throws \LengthException 36 | * 37 | * @return void 38 | */ 39 | public function setNameAttribute(string $name): void 40 | { 41 | if (\mb_strlen($name) > 255) { 42 | throw new LengthException('The name cannot exceed 255 characters'); 43 | } 44 | 45 | $this->attributes['name'] = $name; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /app/Models/OccurrenceSpecies.php: -------------------------------------------------------------------------------- 1 | belongsTo(OccurrenceFamily::class, 'family_id'); 29 | } 30 | 31 | /** 32 | * Associated Types. 33 | * 34 | * @return \Illuminate\Database\Eloquent\Relations\HasMany 35 | */ 36 | public function types(): HasMany 37 | { 38 | return $this->hasMany(OccurrenceType::class); 39 | } 40 | 41 | /** 42 | * Set the name. 43 | * 44 | * @param string $name 45 | * 46 | * @throws \LengthException 47 | * 48 | * @return void 49 | */ 50 | public function setNameAttribute(string $name): void 51 | { 52 | if (\mb_strlen($name) > 255) { 53 | throw new LengthException('The name cannot exceed 255 characters'); 54 | } 55 | 56 | $this->attributes['name'] = $name; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /app/Models/Parish.php: -------------------------------------------------------------------------------- 1 | belongsTo(County::class); 28 | } 29 | 30 | /** 31 | * Set the code. 32 | * 33 | * @param string $code 34 | * 35 | * @throws \LengthException 36 | * 37 | * @return void 38 | */ 39 | public function setCodeAttribute(string $code): void 40 | { 41 | if (\mb_strlen($code) !== 6) { 42 | throw new LengthException('The code must have 6 characters'); 43 | } 44 | 45 | $this->attributes['code'] = $code; 46 | } 47 | 48 | /** 49 | * Set the name. 50 | * 51 | * @param string $name 52 | * 53 | * @throws \LengthException 54 | * 55 | * @return void 56 | */ 57 | public function setNameAttribute(string $name): void 58 | { 59 | if (\mb_strlen($name) > 255) { 60 | throw new LengthException('The name cannot exceed 255 characters'); 61 | } 62 | 63 | $this->attributes['name'] = $name; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /app/Models/ProCivOccurrence.php: -------------------------------------------------------------------------------- 1 | 'int', 23 | 'aerial_operatives_involved' => 'int', 24 | 'ground_assets_involved' => 'int', 25 | 'ground_operatives_involved' => 'int', 26 | ]; 27 | 28 | /** 29 | * Parent occurrence. 30 | * 31 | * @return \Illuminate\Database\Eloquent\Relations\MorphOne 32 | */ 33 | public function parent(): MorphOne 34 | { 35 | return $this->morphOne(Occurrence::class, 'source'); 36 | } 37 | 38 | /** 39 | * Associated logs. 40 | * 41 | * @return \Illuminate\Database\Eloquent\Relations\HasMany 42 | */ 43 | public function logs(): HasMany 44 | { 45 | return $this->hasMany(ProCivOccurrenceLog::class, 'occurrence_id'); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /app/Models/ProCivOccurrenceLog.php: -------------------------------------------------------------------------------- 1 | belongsTo(ProCivOccurrence::class, 'occurrence_id'); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/Models/Role.php: -------------------------------------------------------------------------------- 1 | belongsTo(County::class); 26 | } 27 | 28 | /** 29 | * Associated WeatherObservations. 30 | * 31 | * @return \Illuminate\Database\Eloquent\Relations\HasMany 32 | */ 33 | public function observations(): HasMany 34 | { 35 | return $this->hasMany(WeatherObservation::class, 'station_id'); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /app/Policies/AcronymPolicy.php: -------------------------------------------------------------------------------- 1 | isAn(Role::ADMINISTRATOR); 23 | } 24 | 25 | /** 26 | * Determine whether the User can update Acronyms. 27 | * 28 | * @param User $user 29 | * @param Acronym $acronym 30 | * 31 | * @return bool 32 | */ 33 | public function update(User $user, Acronym $acronym): bool 34 | { 35 | return $user->isAn(Role::ADMINISTRATOR); 36 | } 37 | 38 | /** 39 | * Determine whether the User can delete Acronyms. 40 | * 41 | * @param User $user 42 | * @param Acronym $acronym 43 | * 44 | * @return bool 45 | */ 46 | public function delete(User $user, Acronym $acronym): bool 47 | { 48 | return $user->isAn(Role::ADMINISTRATOR); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /app/Policies/OccurrencePolicy.php: -------------------------------------------------------------------------------- 1 | isAn(Role::ADMINISTRATOR, Role::MODERATOR); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /app/Providers/AppServiceProvider.php: -------------------------------------------------------------------------------- 1 | \VOSTPT\Models\ProCivOccurrence::class, 29 | ]); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /app/Providers/AuthServiceProvider.php: -------------------------------------------------------------------------------- 1 | \VOSTPT\Policies\AcronymPolicy::class, 16 | \VOSTPT\Models\Event::class => \VOSTPT\Policies\EventPolicy::class, 17 | \VOSTPT\Models\Occurrence::class => \VOSTPT\Policies\OccurrencePolicy::class, 18 | \VOSTPT\Models\User::class => \VOSTPT\Policies\UserPolicy::class, 19 | ]; 20 | 21 | /** 22 | * Register any authentication / authorization services. 23 | * 24 | * @return void 25 | */ 26 | public function boot(): void 27 | { 28 | $this->registerPolicies(); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /app/Providers/EventServiceProvider.php: -------------------------------------------------------------------------------- 1 | app->bind(ClientInterface::class, static function () { 22 | $client = new GuzzleClient([ 23 | // Do not throw exceptions on HTTP 4xx/5xx status 24 | RequestOptions::HTTP_ERRORS => false, 25 | ]); 26 | 27 | return new GuzzleAdapter($client); 28 | }); 29 | } 30 | 31 | /** 32 | * {@inheritDoc} 33 | */ 34 | public function provides(): array 35 | { 36 | return [ 37 | ClientInterface::class, 38 | ]; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /app/Providers/ResponseServiceProvider.php: -------------------------------------------------------------------------------- 1 | app->singleton(FactoryContract::class, static function ($app) { 20 | return new ResponseFactory($app[ViewFactory::class], $app['redirect']); 21 | }); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /app/Reports/Contracts/Report.php: -------------------------------------------------------------------------------- 1 | next = $next; 29 | } 30 | 31 | /** 32 | * {@inheritdoc} 33 | */ 34 | public function findById(int $id): ?Model 35 | { 36 | return $this->next->findById($id); 37 | } 38 | 39 | /** 40 | * {@inheritDoc} 41 | */ 42 | public function createQueryBuilder(): Builder 43 | { 44 | return $this->next->createQueryBuilder(); 45 | } 46 | 47 | /** 48 | * {@inheritdoc} 49 | */ 50 | public function getPaginator(Filter $filter): LengthAwarePaginator 51 | { 52 | return Cache::tags(Acronym::class)->rememberForever($filter->getSignature(), function () use ($filter) { 53 | return $this->next->getPaginator($filter); 54 | }); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /app/Repositories/Concerns/Paginator.php: -------------------------------------------------------------------------------- 1 | createQueryBuilder(); 18 | 19 | $filter->apply($builder); 20 | 21 | return $builder->paginate( 22 | $filter->getPageSize(), 23 | $filter->getColumns(), 24 | 'page[number]', 25 | $filter->getPageNumber() 26 | )->appends($filter->getUrlParameters()); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /app/Repositories/Contracts/AcronymRepository.php: -------------------------------------------------------------------------------- 1 | createQueryBuilder() 28 | ->where('code', $code) 29 | ->first(); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /app/Repositories/DistrictRepository.php: -------------------------------------------------------------------------------- 1 | next = $next; 29 | } 30 | 31 | /** 32 | * {@inheritdoc} 33 | */ 34 | public function findById(int $id): ?Model 35 | { 36 | return $this->next->findById($id); 37 | } 38 | 39 | /** 40 | * {@inheritDoc} 41 | */ 42 | public function createQueryBuilder(): Builder 43 | { 44 | return $this->next->createQueryBuilder(); 45 | } 46 | 47 | /** 48 | * {@inheritdoc} 49 | */ 50 | public function getPaginator(Filter $filter): LengthAwarePaginator 51 | { 52 | return Cache::tags(District::class)->rememberForever($filter->getSignature(), function () use ($filter) { 53 | return $this->next->getPaginator($filter); 54 | }); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /app/Repositories/EventRepository.php: -------------------------------------------------------------------------------- 1 | next = $next; 29 | } 30 | 31 | /** 32 | * {@inheritdoc} 33 | */ 34 | public function findById(int $id): ?Model 35 | { 36 | return $this->next->findById($id); 37 | } 38 | 39 | /** 40 | * {@inheritDoc} 41 | */ 42 | public function createQueryBuilder(): Builder 43 | { 44 | return $this->next->createQueryBuilder(); 45 | } 46 | 47 | /** 48 | * {@inheritdoc} 49 | */ 50 | public function getPaginator(Filter $filter): LengthAwarePaginator 51 | { 52 | return Cache::tags(Event::class)->rememberForever($filter->getSignature(), function () use ($filter) { 53 | return $this->next->getPaginator($filter); 54 | }); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /app/Repositories/OccurrenceFamilyRepository.php: -------------------------------------------------------------------------------- 1 | createQueryBuilder() 29 | ->stalled() 30 | ->get(); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /app/Repositories/OccurrenceSpeciesRepository.php: -------------------------------------------------------------------------------- 1 | createQueryBuilder() 28 | ->where('code', $code) 29 | ->first(); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /app/Repositories/OccurrenceTypeRepository.php: -------------------------------------------------------------------------------- 1 | createQueryBuilder() 28 | ->where('code', $code) 29 | ->first(); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /app/Repositories/ParishRepository.php: -------------------------------------------------------------------------------- 1 | createQueryBuilder() 28 | ->where('code', $code) 29 | ->first(); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /app/Repositories/ProCivOccurrenceRepository.php: -------------------------------------------------------------------------------- 1 | createQueryBuilder() 26 | ->where('remote_id', $id) 27 | ->first(); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /app/Repositories/Repository.php: -------------------------------------------------------------------------------- 1 | createQueryBuilder() 17 | ->where('id', $id) 18 | ->first(); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /app/Repositories/UserRepository.php: -------------------------------------------------------------------------------- 1 | next = $next; 29 | } 30 | 31 | /** 32 | * {@inheritdoc} 33 | */ 34 | public function findById(int $id): ?Model 35 | { 36 | return $this->next->findById($id); 37 | } 38 | 39 | /** 40 | * {@inheritDoc} 41 | */ 42 | public function createQueryBuilder(): Builder 43 | { 44 | return $this->next->createQueryBuilder(); 45 | } 46 | 47 | /** 48 | * {@inheritdoc} 49 | */ 50 | public function getPaginator(Filter $filter): LengthAwarePaginator 51 | { 52 | return Cache::tags(User::class)->rememberForever($filter->getSignature(), function () use ($filter) { 53 | return $this->next->getPaginator($filter); 54 | }); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /app/Repositories/WeatherObservationRepository.php: -------------------------------------------------------------------------------- 1 | createQueryBuilder() 30 | ->where('station_id', $station->getKey()) 31 | ->where('timestamp', $timestamp) 32 | ->first(); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /app/Repositories/WeatherStationRepository.php: -------------------------------------------------------------------------------- 1 | createQueryBuilder() 26 | ->where('entity', $entity) 27 | ->where('serial', $serial) 28 | ->first(); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /app/Rules/ValidRole.php: -------------------------------------------------------------------------------- 1 | first() !== null; 18 | } 19 | 20 | /** 21 | * {@inheritDoc} 22 | */ 23 | public function message(): string 24 | { 25 | return \sprintf('The :attribute value must be one of: %s', \implode(', ', Role::pluck('name')->all())); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /app/ServiceClients/Contracts/IpmaApiServiceClient.php: -------------------------------------------------------------------------------- 1 | get('json/warnings_www.json'); 15 | } 16 | 17 | /** 18 | * {@inheritDoc} 19 | */ 20 | public function getSurfaceObservations(): array 21 | { 22 | $results = $this->get('open-data/observation/meteorology/stations/observations.json'); 23 | 24 | // Results come out of order, so we sort them out before returning them 25 | \ksort($results); 26 | 27 | return $results; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /app/ServiceClients/ProCivWebsiteServiceClient.php: -------------------------------------------------------------------------------- 1 | 'application/json;charset=utf-8', 16 | 'User-Agent' => 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:71.0) Gecko/20100101 Firefox/71.0', 17 | ]; 18 | } 19 | 20 | /** 21 | * {@inheritDoc} 22 | */ 23 | public function getMainOccurrences(): array 24 | { 25 | $response = $this->post('_vti_bin/ARM.ANPC.UI/ANPC_SituacaoOperacional.svc/GetMainOccurrences', [ 26 | 'allData' => true, 27 | ]); 28 | 29 | return $response['GetMainOccurrencesResult']['ArrayInfo'][0] ?? [ 30 | 'Data' => [], 31 | 'Total' => 0, 32 | ]; 33 | } 34 | 35 | /** 36 | * {@inheritDoc} 37 | */ 38 | public function getOccurrenceHistory(): array 39 | { 40 | $response = $this->post('_vti_bin/ARM.ANPC.UI/ANPC_SituacaoOperacional.svc/GetHistoryOccurrencesByLocation', [ 41 | 'allData' => true, 42 | ]); 43 | 44 | return $response['GetHistoryOccurrencesByLocationResult']['ArrayInfo'][0] ?? [ 45 | 'Data' => [], 46 | 'Total' => 0, 47 | ]; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /bootstrap/cache/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /config/services.php: -------------------------------------------------------------------------------- 1 | [ 8 | 'api' => [ 9 | 'hostname' => env('IPMA_API_HOSTNAME'), 10 | ], 11 | ], 12 | 13 | 'prociv' => [ 14 | 'website' => [ 15 | 'hostname' => env('PROCIV_WEBSITE_HOSTNAME'), 16 | ], 17 | ], 18 | 19 | ]; 20 | -------------------------------------------------------------------------------- /config/view.php: -------------------------------------------------------------------------------- 1 | [ 19 | resource_path('views'), 20 | ], 21 | 22 | /* 23 | |-------------------------------------------------------------------------- 24 | | Compiled View Path 25 | |-------------------------------------------------------------------------- 26 | | 27 | | This option determines where all the compiled Blade templates will be 28 | | stored for your application. Typically, this is within the storage 29 | | directory. However, as usual, you are free to change this value. 30 | | 31 | */ 32 | 33 | 'compiled' => env( 34 | 'VIEW_COMPILED_PATH', 35 | \realpath(storage_path('framework/views')) 36 | ), 37 | 38 | ]; 39 | -------------------------------------------------------------------------------- /database/.gitignore: -------------------------------------------------------------------------------- 1 | *.sqlite 2 | -------------------------------------------------------------------------------- /database/factories/AcronymFactory.php: -------------------------------------------------------------------------------- 1 | define(Acronym::class, static function (Faker $faker) { 16 | return [ 17 | 'initials' => $faker->unique()->lexify(), 18 | 'meaning' => $faker->unique()->sentence(), 19 | ]; 20 | }); 21 | -------------------------------------------------------------------------------- /database/factories/CountyFactory.php: -------------------------------------------------------------------------------- 1 | define(County::class, static function (Faker $faker) { 17 | return [ 18 | 'district_id' => static function () { 19 | return factory(District::class)->create()->getKey(); 20 | }, 21 | 'code' => $faker->unique()->numerify('######'), 22 | 'name' => \sprintf('%s County', $faker->unique()->name), 23 | ]; 24 | }); 25 | -------------------------------------------------------------------------------- /database/factories/DistrictFactory.php: -------------------------------------------------------------------------------- 1 | define(District::class, static function (Faker $faker) { 16 | return [ 17 | 'code' => $faker->unique()->numerify('######'), 18 | 'name' => \sprintf('%s District', $faker->unique()->name), 19 | ]; 20 | }); 21 | -------------------------------------------------------------------------------- /database/factories/EventFactory.php: -------------------------------------------------------------------------------- 1 | define(Event::class, static function (Faker $faker) { 18 | return [ 19 | 'type_id' => static function () { 20 | return factory(EventType::class)->create()->getKey(); 21 | }, 22 | 'parish_id' => static function () { 23 | return factory(Parish::class)->create()->getKey(); 24 | }, 25 | 'name' => $faker->sentence(), 26 | 'description' => $faker->paragraph, 27 | 'latitude' => $faker->latitude, 28 | 'longitude' => $faker->longitude, 29 | 'started_at' => $faker->dateTime, 30 | 'ended_at' => $faker->dateTime, 31 | ]; 32 | }); 33 | -------------------------------------------------------------------------------- /database/factories/EventTypeFactory.php: -------------------------------------------------------------------------------- 1 | define(EventType::class, static function (Faker $faker) { 16 | return [ 17 | 'name' => $faker->unique()->sentence(), 18 | ]; 19 | }); 20 | -------------------------------------------------------------------------------- /database/factories/OccurrenceFactory.php: -------------------------------------------------------------------------------- 1 | define(Occurrence::class, static function (Faker $faker) { 20 | return [ 21 | 'event_id' => static function () { 22 | return factory(Event::class)->create()->getKey(); 23 | }, 24 | 'type_id' => static function () { 25 | return factory(OccurrenceType::class)->create()->getKey(); 26 | }, 27 | 'status_id' => static function () { 28 | return factory(OccurrenceStatus::class)->create()->getKey(); 29 | }, 30 | 'parish_id' => static function () { 31 | return factory(Parish::class)->create()->getKey(); 32 | }, 33 | 'locality' => $faker->sentence(), 34 | 'latitude' => $faker->latitude, 35 | 'longitude' => $faker->longitude, 36 | 'started_at' => $faker->dateTime, 37 | 'ended_at' => $faker->dateTime, 38 | ]; 39 | }); 40 | -------------------------------------------------------------------------------- /database/factories/OccurrenceFamilyFactory.php: -------------------------------------------------------------------------------- 1 | define(OccurrenceFamily::class, static function (Faker $faker) { 16 | return [ 17 | 'code' => $faker->unique()->numberBetween(1000, 9999), 18 | 'name' => $faker->unique()->sentence(), 19 | ]; 20 | }); 21 | -------------------------------------------------------------------------------- /database/factories/OccurrenceSpeciesFactory.php: -------------------------------------------------------------------------------- 1 | define(OccurrenceSpecies::class, static function (Faker $faker) { 17 | return [ 18 | 'family_id' => static function () { 19 | return factory(OccurrenceFamily::class)->create()->getKey(); 20 | }, 21 | 'code' => $faker->unique()->numberBetween(1000, 9999), 22 | 'name' => $faker->unique()->sentence(), 23 | ]; 24 | }); 25 | -------------------------------------------------------------------------------- /database/factories/OccurrenceStatusFactory.php: -------------------------------------------------------------------------------- 1 | define(OccurrenceStatus::class, static function (Faker $faker) { 16 | return [ 17 | 'code' => $faker->unique()->numberBetween(1, 255), 18 | 'name' => $faker->unique()->sentence(), 19 | ]; 20 | }); 21 | -------------------------------------------------------------------------------- /database/factories/OccurrenceTypeFactory.php: -------------------------------------------------------------------------------- 1 | define(OccurrenceType::class, static function (Faker $faker) { 17 | return [ 18 | 'species_id' => static function () { 19 | return factory(OccurrenceSpecies::class)->create()->getKey(); 20 | }, 21 | 'code' => $faker->unique()->numberBetween(1000, 9999), 22 | 'name' => $faker->unique()->sentence(), 23 | ]; 24 | }); 25 | -------------------------------------------------------------------------------- /database/factories/ParishFactory.php: -------------------------------------------------------------------------------- 1 | define(Parish::class, static function (Faker $faker) { 17 | return [ 18 | 'county_id' => static function () { 19 | return factory(County::class)->create()->getKey(); 20 | }, 21 | 'code' => $faker->unique()->numerify('######'), 22 | 'name' => \sprintf('%s Parish', $faker->unique()->name), 23 | ]; 24 | }); 25 | -------------------------------------------------------------------------------- /database/factories/ProCivOccurrenceFactory.php: -------------------------------------------------------------------------------- 1 | define(ProCivOccurrence::class, static function (Faker $faker) { 16 | return [ 17 | 'remote_id' => $faker->unique()->numerify('#############'), 18 | 'ground_assets' => $faker->numberBetween(1, 8), 19 | 'ground_operatives' => $faker->numberBetween(2, 8), 20 | 'aerial_assets' => $faker->numberBetween(0, 8), 21 | 'aerial_operatives' => $faker->numberBetween(0, 8), 22 | ]; 23 | }); 24 | -------------------------------------------------------------------------------- /database/factories/UserFactory.php: -------------------------------------------------------------------------------- 1 | define(User::class, static function (Faker $faker) { 16 | return [ 17 | 'name' => $faker->firstName, 18 | 'surname' => $faker->lastName, 19 | 'email' => $faker->unique()->safeEmail, 20 | 'password' => 'secret', 21 | ]; 22 | }); 23 | -------------------------------------------------------------------------------- /database/factories/WeatherObservationFactory.php: -------------------------------------------------------------------------------- 1 | define(WeatherObservation::class, static function (Faker $faker) { 17 | return [ 18 | 'station_id' => static function () { 19 | return factory(WeatherStation::class)->create()->getKey(); 20 | }, 21 | 'temperature' => $faker->randomFloat(1, 1, 50), 22 | 'humidity' => $faker->randomFloat(1, 2, 100), 23 | 'wind_speed' => $faker->randomFloat(1, 0, 120), 24 | 'wind_direction' => $faker->randomElement(WeatherObservation::WIND_DIRECTIONS), 25 | 'precipitation' => $faker->randomFloat(1, 0, 2000), 26 | 'atmospheric_pressure' => $faker->randomFloat(1, 1010, 1030), 27 | 'radiation' => $faker->randomFloat(1, 0, 3600), 28 | 'timestamp' => $faker->dateTime(), 29 | ]; 30 | }); 31 | -------------------------------------------------------------------------------- /database/factories/WeatherSensorFactory.php: -------------------------------------------------------------------------------- 1 | define(WeatherSensor::class, static function (Faker $faker) { 17 | return [ 18 | 'station_id' => static function () { 19 | return factory(WeatherStation::class)->create()->getKey(); 20 | }, 21 | 'type' => $faker->name, 22 | 'latitude' => $faker->latitude, 23 | 'longitude' => $faker->longitude, 24 | 'altitude' => $faker->numberBetween(1, 1800), 25 | 'started_at' => $faker->date(), 26 | ]; 27 | }); 28 | -------------------------------------------------------------------------------- /database/factories/WeatherStationFactory.php: -------------------------------------------------------------------------------- 1 | define(WeatherStation::class, static function (Faker $faker) { 17 | return [ 18 | 'county_id' => static function () { 19 | return factory(County::class)->create()->getKey(); 20 | }, 21 | 'entity' => $faker->unique()->company, 22 | 'name' => \sprintf('%s Station', $faker->unique()->name), 23 | 'serial' => $faker->unique()->numerify('###'), 24 | ]; 25 | }); 26 | -------------------------------------------------------------------------------- /database/migrations/2019_04_08_185908_create_users_table.php: -------------------------------------------------------------------------------- 1 | increments('id'); 20 | 21 | $table->string('name'); 22 | $table->string('surname'); 23 | $table->string('email')->unique(); 24 | $table->string('password'); 25 | 26 | $table->timestamps(); 27 | }); 28 | } 29 | 30 | /** 31 | * Reverse the migrations. 32 | * 33 | * @return void 34 | */ 35 | public function down(): void 36 | { 37 | Schema::drop('users'); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /database/migrations/2019_04_09_095426_create_districts_table.php: -------------------------------------------------------------------------------- 1 | tinyIncrements('id'); 20 | 21 | $table->string('code')->unique(); 22 | $table->string('name')->unique(); 23 | 24 | $table->timestamps(); 25 | }); 26 | } 27 | 28 | /** 29 | * Reverse the migrations. 30 | * 31 | * @return void 32 | */ 33 | public function down(): void 34 | { 35 | Schema::drop('districts'); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /database/migrations/2019_04_11_145226_create_counties_table.php: -------------------------------------------------------------------------------- 1 | smallIncrements('id'); 20 | $table->unsignedTinyInteger('district_id'); 21 | 22 | $table->string('code')->unique(); 23 | $table->string('name'); 24 | 25 | $table->timestamps(); 26 | 27 | $table->unique([ 28 | 'district_id', 29 | 'name', 30 | ]); 31 | 32 | $table->foreign('district_id') 33 | ->references('id') 34 | ->on('districts') 35 | ->onUpdate('cascade'); 36 | }); 37 | } 38 | 39 | /** 40 | * Reverse the migrations. 41 | * 42 | * @return void 43 | */ 44 | public function down(): void 45 | { 46 | Schema::drop('counties'); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /database/migrations/2019_04_12_074212_create_parishes_table.php: -------------------------------------------------------------------------------- 1 | smallIncrements('id'); 20 | $table->unsignedSmallInteger('county_id'); 21 | 22 | $table->string('code')->unique(); 23 | $table->string('name'); 24 | 25 | $table->timestamps(); 26 | 27 | $table->unique([ 28 | 'county_id', 29 | 'name', 30 | ]); 31 | 32 | $table->foreign('county_id') 33 | ->references('id') 34 | ->on('counties') 35 | ->onUpdate('cascade'); 36 | }); 37 | } 38 | 39 | /** 40 | * Reverse the migrations. 41 | * 42 | * @return void 43 | */ 44 | public function down(): void 45 | { 46 | Schema::drop('parishes'); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /database/migrations/2019_04_12_224617_create_acronyms_table.php: -------------------------------------------------------------------------------- 1 | smallIncrements('id'); 20 | 21 | $table->string('initials')->unique(); 22 | $table->string('meaning'); 23 | 24 | $table->timestamps(); 25 | }); 26 | } 27 | 28 | /** 29 | * Reverse the migrations. 30 | * 31 | * @return void 32 | */ 33 | public function down(): void 34 | { 35 | Schema::drop('acronyms'); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /database/migrations/2019_04_16_222648_create_event_types_table.php: -------------------------------------------------------------------------------- 1 | tinyIncrements('id'); 20 | 21 | $table->string('name')->unique(); 22 | 23 | $table->timestamps(); 24 | }); 25 | } 26 | 27 | /** 28 | * Reverse the migrations. 29 | * 30 | * @return void 31 | */ 32 | public function down(): void 33 | { 34 | Schema::drop('event_types'); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /database/migrations/2019_04_17_203846_create_occurrence_families_table.php: -------------------------------------------------------------------------------- 1 | tinyIncrements('id'); 20 | 21 | $table->unsignedSmallInteger('code')->unique(); 22 | $table->string('name')->unique(); 23 | 24 | $table->timestamps(); 25 | }); 26 | } 27 | 28 | /** 29 | * Reverse the migrations. 30 | * 31 | * @return void 32 | */ 33 | public function down(): void 34 | { 35 | Schema::drop('occurrence_families'); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /database/migrations/2019_04_17_203847_create_occurrence_species_table.php: -------------------------------------------------------------------------------- 1 | tinyIncrements('id'); 20 | $table->unsignedTinyInteger('family_id'); 21 | 22 | $table->unsignedSmallInteger('code')->unique(); 23 | $table->string('name'); 24 | 25 | $table->timestamps(); 26 | 27 | $table->unique([ 28 | 'family_id', 29 | 'name', 30 | ]); 31 | 32 | $table->foreign('family_id') 33 | ->references('id') 34 | ->on('occurrence_families') 35 | ->onUpdate('cascade'); 36 | }); 37 | } 38 | 39 | /** 40 | * Reverse the migrations. 41 | * 42 | * @return void 43 | */ 44 | public function down(): void 45 | { 46 | Schema::drop('occurrence_species'); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /database/migrations/2019_04_17_203847_create_occurrence_statuses_table.php: -------------------------------------------------------------------------------- 1 | tinyIncrements('id'); 20 | 21 | $table->unsignedTinyInteger('code')->unique(); 22 | $table->string('name')->unique(); 23 | 24 | $table->timestamps(); 25 | }); 26 | } 27 | 28 | /** 29 | * Reverse the migrations. 30 | * 31 | * @return void 32 | */ 33 | public function down(): void 34 | { 35 | Schema::drop('occurrence_statuses'); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /database/migrations/2019_04_17_203848_create_occurrence_types_table.php: -------------------------------------------------------------------------------- 1 | tinyIncrements('id'); 20 | $table->unsignedTinyInteger('species_id'); 21 | 22 | $table->unsignedSmallInteger('code')->unique(); 23 | $table->string('name'); 24 | 25 | $table->timestamps(); 26 | 27 | $table->unique([ 28 | 'species_id', 29 | 'name', 30 | ]); 31 | 32 | $table->foreign('species_id') 33 | ->references('id') 34 | ->on('occurrence_species') 35 | ->onUpdate('cascade'); 36 | }); 37 | } 38 | 39 | /** 40 | * Reverse the migrations. 41 | * 42 | * @return void 43 | */ 44 | public function down(): void 45 | { 46 | Schema::drop('occurrence_types'); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /database/migrations/2019_04_17_203850_create_prociv_occurrences_table.php: -------------------------------------------------------------------------------- 1 | increments('id'); 20 | $table->string('remote_id')->unique(); 21 | 22 | $table->unsignedSmallInteger('ground_assets'); 23 | $table->unsignedSmallInteger('ground_operatives'); 24 | 25 | $table->unsignedSmallInteger('aerial_assets'); 26 | $table->unsignedSmallInteger('aerial_operatives'); 27 | 28 | $table->timestamps(); 29 | }); 30 | } 31 | 32 | /** 33 | * Reverse the migrations. 34 | * 35 | * @return void 36 | */ 37 | public function down(): void 38 | { 39 | Schema::drop('prociv_occurrences'); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /database/migrations/2019_08_13_084800_create_failed_jobs_table.php: -------------------------------------------------------------------------------- 1 | increments('id'); 20 | $table->text('connection'); 21 | $table->text('queue'); 22 | $table->longText('payload'); 23 | $table->longText('exception'); 24 | $table->timestamp('failed_at')->useCurrent(); 25 | }); 26 | } 27 | 28 | /** 29 | * Reverse the migrations. 30 | * 31 | * @return void 32 | */ 33 | public function down(): void 34 | { 35 | Schema::drop('failed_jobs'); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /database/migrations/2019_09_04_133750_create_weather_stations_table.php: -------------------------------------------------------------------------------- 1 | smallIncrements('id'); 20 | $table->unsignedSmallInteger('county_id'); 21 | 22 | $table->string('entity'); 23 | $table->string('name'); 24 | $table->string('serial'); 25 | 26 | $table->timestamps(); 27 | 28 | $table->unique([ 29 | 'entity', 30 | 'name', 31 | ]); 32 | 33 | $table->unique([ 34 | 'entity', 35 | 'serial', 36 | ]); 37 | 38 | $table->foreign('county_id') 39 | ->references('id') 40 | ->on('counties') 41 | ->onUpdate('cascade'); 42 | }); 43 | } 44 | 45 | /** 46 | * Reverse the migrations. 47 | * 48 | * @return void 49 | */ 50 | public function down(): void 51 | { 52 | Schema::drop('weather_stations'); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /database/migrations/2019_09_04_133751_create_weather_sensors_table.php: -------------------------------------------------------------------------------- 1 | smallIncrements('id'); 20 | $table->unsignedSmallInteger('station_id'); 21 | 22 | $table->string('type'); 23 | 24 | $table->decimal('latitude', 10, 8); 25 | $table->decimal('longitude', 11, 8); 26 | 27 | $table->unsignedSmallInteger('altitude'); 28 | 29 | $table->date('started_at')->nullable(); 30 | 31 | $table->timestamps(); 32 | 33 | $table->unique([ 34 | 'station_id', 35 | 'type', 36 | ]); 37 | 38 | $table->foreign('station_id') 39 | ->references('id') 40 | ->on('weather_stations') 41 | ->onUpdate('cascade'); 42 | }); 43 | } 44 | 45 | /** 46 | * Reverse the migrations. 47 | * 48 | * @return void 49 | */ 50 | public function down(): void 51 | { 52 | Schema::drop('weather_sensors'); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /database/seeds/AcronymSeeder.php: -------------------------------------------------------------------------------- 1 | create($acronym); 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /database/seeds/CountySeeder.php: -------------------------------------------------------------------------------- 1 | $counties) { 21 | $district = District::where('code', $districtCode)->first(); 22 | 23 | foreach ($counties as $county) { 24 | factory(County::class)->create(\array_merge($county, [ 25 | 'district_id' => $district->getKey(), 26 | ])); 27 | } 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /database/seeds/DatabaseSeeder.php: -------------------------------------------------------------------------------- 1 | call(RoleSeeder::class); 17 | $this->call(DistrictSeeder::class); 18 | $this->call(CountySeeder::class); 19 | $this->call(ParishSeeder::class); 20 | $this->call(AcronymSeeder::class); 21 | $this->call(EventTypeSeeder::class); 22 | $this->call(OccurrenceStatusSeeder::class); 23 | $this->call(OccurrenceFamilySeeder::class); 24 | $this->call(OccurrenceSpeciesSeeder::class); 25 | $this->call(OccurrenceTypeSeeder::class); 26 | $this->call(IpmaStationAndSensorSeeder::class); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /database/seeds/DistrictSeeder.php: -------------------------------------------------------------------------------- 1 | create($district); 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /database/seeds/EventTypeSeeder.php: -------------------------------------------------------------------------------- 1 | create($attributes); 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /database/seeds/OccurrenceFamilySeeder.php: -------------------------------------------------------------------------------- 1 | create($attributes); 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /database/seeds/OccurrenceSpeciesSeeder.php: -------------------------------------------------------------------------------- 1 | $species) { 21 | $parent = OccurrenceFamily::where('code', $code)->first(); 22 | 23 | foreach ($species as $attributes) { 24 | factory(OccurrenceSpecies::class)->create(\array_merge($attributes, [ 25 | 'family_id' => $parent->getKey(), 26 | ])); 27 | } 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /database/seeds/OccurrenceStatusSeeder.php: -------------------------------------------------------------------------------- 1 | create($occurrenceStatus); 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /database/seeds/OccurrenceTypeSeeder.php: -------------------------------------------------------------------------------- 1 | $types) { 21 | $parent = OccurrenceSpecies::where('code', $code)->first(); 22 | 23 | foreach ($types as $attributes) { 24 | factory(OccurrenceType::class)->create(\array_merge($attributes, [ 25 | 'species_id' => $parent->getKey(), 26 | ])); 27 | } 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /database/seeds/ParishSeeder.php: -------------------------------------------------------------------------------- 1 | $parishes) { 21 | $county = County::where('code', $countyCode)->first(); 22 | 23 | foreach ($parishes as $parish) { 24 | factory(Parish::class)->create(\array_merge($parish, [ 25 | 'county_id' => $county->getKey(), 26 | ])); 27 | } 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /database/seeds/RoleSeeder.php: -------------------------------------------------------------------------------- 1 | Role::ADMINISTRATOR, 19 | 'title' => 'Administrator', 20 | ]); 21 | 22 | Role::create([ 23 | 'name' => Role::MODERATOR, 24 | 'title' => 'Data Moderator', 25 | ]); 26 | 27 | Role::create([ 28 | 'name' => Role::CONTRIBUTOR, 29 | 'title' => 'Data Contributor', 30 | ]); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /database/seeds/data/event_types.php: -------------------------------------------------------------------------------- 1 | 'Incêndio', 8 | ], 9 | [ 10 | 'name' => 'Fenómeno Meteorológico Adverso', 11 | ], 12 | ]; 13 | -------------------------------------------------------------------------------- /database/seeds/data/occurrence_families.php: -------------------------------------------------------------------------------- 1 | 1000, 8 | 'name' => 'Riscos Naturais', 9 | ], 10 | [ 11 | 'code' => 2000, 12 | 'name' => 'Riscos Tecnológicos', 13 | ], 14 | [ 15 | 'code' => 3000, 16 | 'name' => 'Riscos Mistos', 17 | ], 18 | [ 19 | 'code' => 4000, 20 | 'name' => 'Protecção e Assistência a Pessoas e Bens', 21 | ], 22 | [ 23 | 'code' => 9000, 24 | 'name' => 'Operações e Estados de Alerta', 25 | ], 26 | ]; 27 | -------------------------------------------------------------------------------- /database/seeds/data/occurrence_statuses.php: -------------------------------------------------------------------------------- 1 | OccurrenceStatus::FALSE_ALERT, 10 | 'name' => 'Falso Alerta', 11 | ], 12 | [ 13 | 'code' => OccurrenceStatus::SURVEILLANCE, 14 | 'name' => 'Vigilância', 15 | ], 16 | [ 17 | 'code' => OccurrenceStatus::DISPATCH, 18 | 'name' => 'Despacho', 19 | ], 20 | [ 21 | 'code' => OccurrenceStatus::FIRST_ALERT_DISPATCH, 22 | 'name' => 'Despacho de 1º Alerta', 23 | ], 24 | [ 25 | 'code' => OccurrenceStatus::ONGOING, 26 | 'name' => 'Em Curso', 27 | ], 28 | [ 29 | 'code' => OccurrenceStatus::ARRIVAL_AT_TO, 30 | 'name' => 'Chegada ao TO', 31 | ], 32 | [ 33 | 'code' => OccurrenceStatus::RESOLVING, 34 | 'name' => 'Em Resolução', 35 | ], 36 | [ 37 | 'code' => OccurrenceStatus::CONCLUSION, 38 | 'name' => 'Conclusão', 39 | ], 40 | [ 41 | 'code' => OccurrenceStatus::CLOSED, 42 | 'name' => 'Encerrada', 43 | ], 44 | [ 45 | 'code' => OccurrenceStatus::CLOSED_BY_VOST, 46 | 'name' => 'Encerrada pela VOST', 47 | ], 48 | ]; 49 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.7' 2 | 3 | services: 4 | nginx: 5 | container_name: vost_nginx 6 | image: nginx:1.14.0-alpine 7 | ports: 8 | - 80:80 9 | volumes: 10 | - ./.docker/nginx/conf.d:/etc/nginx/conf.d:ro 11 | - .:/var/www/api.vost.test 12 | restart: unless-stopped 13 | networks: 14 | vost_network: 15 | aliases: 16 | - api.vost.test 17 | 18 | api: 19 | container_name: vost_api 20 | build: .docker/php 21 | expose: 22 | - 9000 23 | volumes: 24 | - .:/var/www/api.vost.test 25 | restart: unless-stopped 26 | networks: 27 | - vost_network 28 | 29 | mariadb: 30 | container_name: vost_mariadb 31 | image: mariadb:10.4.6 32 | ports: 33 | - 3306:3306 34 | environment: 35 | MYSQL_DATABASE: vost_api 36 | MYSQL_ROOT_PASSWORD: root 37 | restart: unless-stopped 38 | networks: 39 | - vost_network 40 | 41 | test_database: 42 | container_name: vost_test_database 43 | image: mariadb:10.4.6 44 | environment: 45 | MYSQL_DATABASE: vost_api 46 | MYSQL_ALLOW_EMPTY_PASSWORD: "yes" 47 | MYSQL_ROOT_HOST: "%" 48 | restart: unless-stopped 49 | networks: 50 | - vost_network 51 | 52 | redis: 53 | container_name: vost_redis 54 | image: redis:4.0.9-alpine 55 | ports: 56 | - 6379:6379 57 | environment: 58 | - ALLOW_EMPTY_PASSWORD=yes 59 | restart: unless-stopped 60 | networks: 61 | - vost_network 62 | 63 | networks: 64 | vost_network: 65 | -------------------------------------------------------------------------------- /pre-commit.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | EXIT_STATUS=0 4 | REGEX=".*php$" 5 | 6 | # 7 | # Coding style check per PHP file 8 | # 9 | for file in $(git diff --cached --name-only --diff-filter=ACM); do 10 | if [[ $file =~ $REGEX ]]; then 11 | composer cs-check $file 12 | 13 | EXIT_STATUS=$? 14 | 15 | if [ $EXIT_STATUS -ne 0 ]; then 16 | echo "Issues detected! To fix, execute: composer cs-fix $file" 17 | 18 | exit $EXIT_STATUS 19 | fi 20 | fi 21 | done 22 | 23 | if [ $EXIT_STATUS -eq 0 ]; then 24 | echo "All good! No coding style issues found :)" 25 | fi 26 | 27 | exit $EXIT_STATUS 28 | -------------------------------------------------------------------------------- /public/.htaccess: -------------------------------------------------------------------------------- 1 | 2 | 3 | Options -MultiViews -Indexes 4 | 5 | 6 | RewriteEngine On 7 | 8 | # Handle Authorization Header 9 | RewriteCond %{HTTP:Authorization} . 10 | RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}] 11 | 12 | # Redirect Trailing Slashes If Not A Folder... 13 | RewriteCond %{REQUEST_FILENAME} !-d 14 | RewriteCond %{REQUEST_URI} (.+)/$ 15 | RewriteRule ^ %1 [L,R=301] 16 | 17 | # Handle Front Controller... 18 | RewriteCond %{REQUEST_FILENAME} !-d 19 | RewriteCond %{REQUEST_FILENAME} !-f 20 | RewriteRule ^ index.php [L] 21 | 22 | -------------------------------------------------------------------------------- /public/documentation/acronyms/schemas.yaml: -------------------------------------------------------------------------------- 1 | Acronym: 2 | type: object 3 | properties: 4 | type: 5 | type: string 6 | default: acronyms 7 | id: 8 | type: string 9 | example: 1 10 | attributes: 11 | type: object 12 | properties: 13 | initials: 14 | type: string 15 | example: ATI 16 | meaning: 17 | type: string 18 | example: Ataque Inicial 19 | created_at: 20 | type: string 21 | format: date-time 22 | updated_at: 23 | type: string 24 | format: date-time 25 | -------------------------------------------------------------------------------- /public/documentation/auth/schemas.yaml: -------------------------------------------------------------------------------- 1 | AccessToken: 2 | type: object 3 | properties: 4 | meta: 5 | type: object 6 | properties: 7 | access_token: 8 | type: string 9 | example: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzUxMiJ9.eyJpc3MiOiJodHRwOlwvXC9sb2NhbGhvc3RcL3YxXC9hdXRoXC9yZWZyZXNoIiwiaWF0IjoxNTU0OTcwOTUzLCJleHAiOjE1NTQ5NzQ1NTMsIm5iZiI6MTU1NDk3MDk1MywianRpIjoiYTV1QmhSUXZXd3dYM2xWUiIsInN1YiI6MSwicHJ2IjoiN2IyOWQwZDhkMGU3ZDQ3ZjllM2FhNmQ3N2Q2NTJiNDZiMWEwNzUwOCIsImlkIjoxLCJuYW1lIjoiRGVzaGF3biIsInN1cm5hbWUiOiJCYXVjaCIsImVtYWlsIjoibW96ZWxsZS5zdHJvc2luQGV4YW1wbGUub3JnIiwicm9sZXMiOlsiYWRtaW5pc3RyYXRvciJdfQ.QmYfktTlX1sheCpZlb28KGnCClDv3Oe_FCz3rhPPXLOiwMxa2UYzkuqmXmUI9iwjXbPbSYUx8JckKWITi3kYww 10 | token_type: 11 | type: string 12 | default: bearer 13 | expires_in: 14 | type: integer 15 | example: 3600 16 | -------------------------------------------------------------------------------- /public/documentation/common/parameters/accept.yaml: -------------------------------------------------------------------------------- 1 | name: Accept 2 | in: header 3 | schema: 4 | type: string 5 | default: application/vnd.api+json 6 | -------------------------------------------------------------------------------- /public/documentation/common/parameters/authorization.yaml: -------------------------------------------------------------------------------- 1 | name: Authorization 2 | in: header 3 | required: true 4 | schema: 5 | type: string 6 | example: Bearer 7 | -------------------------------------------------------------------------------- /public/documentation/common/parameters/content_type.yaml: -------------------------------------------------------------------------------- 1 | name: Content-Type 2 | in: header 3 | required: true 4 | schema: 5 | type: string 6 | default: application/vnd.api+json 7 | -------------------------------------------------------------------------------- /public/documentation/common/parameters/exact.yaml: -------------------------------------------------------------------------------- 1 | name: exact 2 | in: query 3 | description: Enable exact match when searching 4 | schema: 5 | type: boolean 6 | default: 0 7 | -------------------------------------------------------------------------------- /public/documentation/common/parameters/ids.yaml: -------------------------------------------------------------------------------- 1 | name: ids[] 2 | in: query 3 | description: Ids to filter by 4 | schema: 5 | type: array 6 | items: 7 | type: string 8 | example: 1 9 | -------------------------------------------------------------------------------- /public/documentation/common/parameters/latitude.yaml: -------------------------------------------------------------------------------- 1 | name: latitude 2 | in: query 3 | description: Geolocation latitude 4 | schema: 5 | type: number 6 | format: double 7 | example: 38.166749 8 | -------------------------------------------------------------------------------- /public/documentation/common/parameters/longitude.yaml: -------------------------------------------------------------------------------- 1 | name: longitude 2 | in: query 3 | description: Geolocation longitude 4 | schema: 5 | type: number 6 | format: double 7 | example: -7.891448 8 | -------------------------------------------------------------------------------- /public/documentation/common/parameters/order.yaml: -------------------------------------------------------------------------------- 1 | name: order 2 | in: query 3 | description: Sorting order of the results 4 | schema: 5 | type: string 6 | enum: 7 | - asc 8 | - desc 9 | default: desc 10 | -------------------------------------------------------------------------------- /public/documentation/common/parameters/page_number.yaml: -------------------------------------------------------------------------------- 1 | name: page[number] 2 | in: query 3 | description: Result page number 4 | schema: 5 | type: integer 6 | minimum: 1 7 | default: 1 8 | -------------------------------------------------------------------------------- /public/documentation/common/parameters/page_size.yaml: -------------------------------------------------------------------------------- 1 | name: page[size] 2 | in: query 3 | description: Items per result page 4 | schema: 5 | type: integer 6 | minimum: 1 7 | default: 50 8 | -------------------------------------------------------------------------------- /public/documentation/common/parameters/radius.yaml: -------------------------------------------------------------------------------- 1 | name: radius 2 | in: query 3 | description: Geolocation radius 4 | schema: 5 | type: integer 6 | minimum: 1 7 | maximum: 200 8 | default: 10 9 | -------------------------------------------------------------------------------- /public/documentation/common/parameters/search.yaml: -------------------------------------------------------------------------------- 1 | name: search 2 | in: query 3 | description: Subject to search for 4 | schema: 5 | type: string 6 | -------------------------------------------------------------------------------- /public/documentation/common/responses/400.yaml: -------------------------------------------------------------------------------- 1 | description: Bad Request 2 | content: 3 | application/vnd.api+json: 4 | schema: 5 | type: object 6 | properties: 7 | errors: 8 | type: array 9 | items: 10 | type: object 11 | properties: 12 | status: 13 | type: integer 14 | default: 400 15 | detail: 16 | type: string 17 | -------------------------------------------------------------------------------- /public/documentation/common/responses/401.yaml: -------------------------------------------------------------------------------- 1 | description: Unauthorised 2 | content: 3 | application/vnd.api+json: 4 | schema: 5 | type: object 6 | properties: 7 | errors: 8 | type: array 9 | items: 10 | type: object 11 | properties: 12 | status: 13 | type: integer 14 | default: 401 15 | detail: 16 | type: string 17 | -------------------------------------------------------------------------------- /public/documentation/common/responses/403.yaml: -------------------------------------------------------------------------------- 1 | description: Forbidden 2 | content: 3 | application/vnd.api+json: 4 | schema: 5 | type: object 6 | properties: 7 | errors: 8 | type: array 9 | items: 10 | type: object 11 | properties: 12 | status: 13 | type: integer 14 | default: 403 15 | detail: 16 | type: string 17 | -------------------------------------------------------------------------------- /public/documentation/common/responses/404.yaml: -------------------------------------------------------------------------------- 1 | description: Not Found 2 | content: 3 | application/vnd.api+json: 4 | schema: 5 | type: object 6 | properties: 7 | errors: 8 | type: array 9 | items: 10 | type: object 11 | properties: 12 | status: 13 | type: integer 14 | default: 404 15 | detail: 16 | type: string 17 | -------------------------------------------------------------------------------- /public/documentation/common/responses/406.yaml: -------------------------------------------------------------------------------- 1 | description: Not Acceptable 2 | content: 3 | application/vnd.api+json: 4 | schema: 5 | type: object 6 | properties: 7 | errors: 8 | type: array 9 | items: 10 | type: object 11 | properties: 12 | status: 13 | type: integer 14 | default: 406 15 | detail: 16 | type: string 17 | -------------------------------------------------------------------------------- /public/documentation/common/responses/415.yaml: -------------------------------------------------------------------------------- 1 | description: Unsupported Media Type 2 | content: 3 | application/vnd.api+json: 4 | schema: 5 | type: object 6 | properties: 7 | errors: 8 | type: array 9 | items: 10 | type: object 11 | properties: 12 | status: 13 | type: integer 14 | default: 415 15 | detail: 16 | type: string 17 | default: Wrong media type 18 | -------------------------------------------------------------------------------- /public/documentation/common/responses/422.yaml: -------------------------------------------------------------------------------- 1 | description: Unprocessable Entity 2 | content: 3 | application/vnd.api+json: 4 | schema: 5 | type: object 6 | properties: 7 | errors: 8 | type: array 9 | items: 10 | type: object 11 | properties: 12 | detail: 13 | type: string 14 | meta: 15 | type: object 16 | properties: 17 | field: 18 | type: string 19 | -------------------------------------------------------------------------------- /public/documentation/common/responses/429.yaml: -------------------------------------------------------------------------------- 1 | description: Too Many Requests 2 | content: 3 | application/vnd.api+json: 4 | schema: 5 | type: object 6 | properties: 7 | errors: 8 | type: array 9 | items: 10 | type: object 11 | properties: 12 | detail: 13 | type: string 14 | meta: 15 | type: object 16 | properties: 17 | field: 18 | type: string 19 | -------------------------------------------------------------------------------- /public/documentation/districts/schemas.yaml: -------------------------------------------------------------------------------- 1 | District: 2 | type: object 3 | properties: 4 | type: 5 | type: string 6 | default: districts 7 | id: 8 | type: string 9 | example: 1 10 | attributes: 11 | type: object 12 | properties: 13 | code: 14 | type: integer 15 | example: 080000 16 | name: 17 | type: string 18 | example: Faro 19 | created_at: 20 | type: string 21 | format: date-time 22 | updated_at: 23 | type: string 24 | format: date-time 25 | links: 26 | type: object 27 | properties: 28 | self: 29 | type: string 30 | description: Link to this page 31 | -------------------------------------------------------------------------------- /public/documentation/index.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | VOST Portugal API 5 | 6 | 7 | 8 | 9 | 10 | 11 | 14 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /public/documentation/ipma/endpoints.yaml: -------------------------------------------------------------------------------- 1 | index: 2 | get: 3 | tags: 4 | - Warnings 5 | summary: Index warnings 6 | description: Index the available warnings. 7 | operationId: warnings::index 8 | parameters: 9 | - $ref: '../common/parameters/content_type.yaml' 10 | - $ref: '../common/parameters/accept.yaml' 11 | 12 | responses: 13 | 200: 14 | description: OK 15 | content: 16 | application/vnd.api+json: 17 | schema: 18 | type: object 19 | properties: 20 | data: 21 | type: array 22 | items: 23 | $ref: 'schemas.yaml#/WarningWithRelations' 24 | included: 25 | type: array 26 | items: 27 | - $ref: '../counties/schemas.yaml#/CountyWithRelations' 28 | - $ref: '../districts/schemas.yaml#/District' 29 | 406: 30 | $ref: '../common/responses/406.yaml' 31 | 415: 32 | $ref: '../common/responses/415.yaml' 33 | -------------------------------------------------------------------------------- /public/documentation/ipma/schemas.yaml: -------------------------------------------------------------------------------- 1 | WarningWithRelations: 2 | type: object 3 | properties: 4 | type: 5 | type: string 6 | default: warnings 7 | id: 8 | type: string 9 | format: uuid 10 | example: 8b51f8c2-31bd-49fc-9beb-93a2e2fcb894 11 | attributes: 12 | type: object 13 | properties: 14 | description: 15 | type: string 16 | example: Rajadas até 75 km/h, em especial no litoral e nas terras altas. 17 | awareness_name: 18 | type: string 19 | example: Vento 20 | awareness_level: 21 | type: string 22 | example: yellow 23 | started_at: 24 | type: string 25 | format: date-time 26 | ended_at: 27 | type: string 28 | format: date-time 29 | relationships: 30 | type: object 31 | properties: 32 | county: 33 | type: object 34 | properties: 35 | data: 36 | type: object 37 | properties: 38 | type: 39 | type: string 40 | default: counties 41 | id: 42 | type: string 43 | example: 1 44 | -------------------------------------------------------------------------------- /public/documentation/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vostpt/api/7acb11a53c58174fb007094a04b32dfaf2b26e3a/public/documentation/logo.png -------------------------------------------------------------------------------- /public/documentation/roles/schemas.yaml: -------------------------------------------------------------------------------- 1 | Role: 2 | type: object 3 | properties: 4 | type: 5 | type: string 6 | default: roles 7 | id: 8 | type: string 9 | example: 1 10 | attributes: 11 | type: object 12 | properties: 13 | name: 14 | type: string 15 | example: contributor 16 | title: 17 | type: string 18 | example: Data Contributor 19 | created_at: 20 | type: string 21 | format: date-time 22 | updated_at: 23 | type: string 24 | format: date-time 25 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vostpt/api/7acb11a53c58174fb007094a04b32dfaf2b26e3a/public/favicon.ico -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Disallow: 3 | -------------------------------------------------------------------------------- /public/web.config: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /resources/lang/en/auth.php: -------------------------------------------------------------------------------- 1 | 'These credentials do not match our records.', 18 | 'throttle' => 'Too many login attempts. Please try again in :seconds seconds.', 19 | 20 | ]; 21 | -------------------------------------------------------------------------------- /resources/lang/en/pagination.php: -------------------------------------------------------------------------------- 1 | '« Previous', 18 | 'next' => 'Next »', 19 | 20 | ]; 21 | -------------------------------------------------------------------------------- /resources/lang/en/passwords.php: -------------------------------------------------------------------------------- 1 | 'Passwords must be at least eight characters and match the confirmation.', 18 | 'reset' => 'Your password has been reset!', 19 | 'sent' => 'We have e-mailed your password reset link!', 20 | 'token' => 'This password reset token is invalid.', 21 | 'user' => "We can't find a user with that e-mail address.", 22 | 23 | ]; 24 | -------------------------------------------------------------------------------- /routes/api/acronyms.php: -------------------------------------------------------------------------------- 1 | name('acronyms::')->group(static function (): void { 16 | Route::get('/', [ 17 | AcronymController::class, 18 | 'index', 19 | ])->name('index'); 20 | 21 | Route::post('/', [ 22 | AcronymController::class, 23 | 'create', 24 | ])->name('create')->middleware('jwt-auth'); 25 | 26 | Route::get('/{Acronym}', [ 27 | AcronymController::class, 28 | 'view', 29 | ])->name('view'); 30 | 31 | Route::patch('/{Acronym}', [ 32 | AcronymController::class, 33 | 'update', 34 | ])->name('update')->middleware('jwt-auth'); 35 | 36 | Route::delete('/{Acronym}', [ 37 | AcronymController::class, 38 | 'delete', 39 | ])->name('delete')->middleware('jwt-auth'); 40 | }); 41 | -------------------------------------------------------------------------------- /routes/api/auth.php: -------------------------------------------------------------------------------- 1 | name('auth::')->group(static function (): void { 16 | Route::post('/', [ 17 | AuthController::class, 18 | 'authenticate', 19 | ])->name('authenticate'); 20 | 21 | Route::get('/verify', [ 22 | AuthController::class, 23 | 'verify', 24 | ])->name('verify')->middleware('jwt-auth'); 25 | 26 | Route::get('/refresh', [ 27 | AuthController::class, 28 | 'refresh', 29 | ])->name('refresh')->middleware('jwt-auth'); 30 | }); 31 | -------------------------------------------------------------------------------- /routes/api/counties.php: -------------------------------------------------------------------------------- 1 | name('counties::')->group(static function (): void { 16 | Route::get('/', [ 17 | CountyController::class, 18 | 'index', 19 | ])->name('index'); 20 | 21 | Route::get('/{County}', [ 22 | CountyController::class, 23 | 'view', 24 | ])->name('view'); 25 | }); 26 | -------------------------------------------------------------------------------- /routes/api/districts.php: -------------------------------------------------------------------------------- 1 | name('districts::')->group(static function (): void { 16 | Route::get('/', [ 17 | DistrictController::class, 18 | 'index', 19 | ])->name('index'); 20 | 21 | Route::get('/{District}', [ 22 | DistrictController::class, 23 | 'view', 24 | ])->name('view'); 25 | }); 26 | -------------------------------------------------------------------------------- /routes/api/events.php: -------------------------------------------------------------------------------- 1 | name('events::')->group(static function (): void { 16 | Route::get('/', [ 17 | EventController::class, 18 | 'index', 19 | ])->name('index'); 20 | 21 | Route::post('/', [ 22 | EventController::class, 23 | 'create', 24 | ])->name('create')->middleware('jwt-auth'); 25 | 26 | Route::get('/{Event}', [ 27 | EventController::class, 28 | 'view', 29 | ])->name('view'); 30 | 31 | Route::patch('/{Event}', [ 32 | EventController::class, 33 | 'update', 34 | ])->name('update')->middleware('jwt-auth'); 35 | 36 | Route::delete('/{Event}', [ 37 | EventController::class, 38 | 'delete', 39 | ])->name('delete')->middleware('jwt-auth'); 40 | }); 41 | -------------------------------------------------------------------------------- /routes/api/ipma.php: -------------------------------------------------------------------------------- 1 | name('ipma::')->group(static function (): void { 16 | Route::get('/warnings', [ 17 | IpmaWarningController::class, 18 | 'index', 19 | ])->name('warnings::index'); 20 | }); 21 | -------------------------------------------------------------------------------- /routes/api/parishes.php: -------------------------------------------------------------------------------- 1 | name('parishes::')->group(static function (): void { 16 | Route::get('/', [ 17 | ParishController::class, 18 | 'index', 19 | ])->name('index'); 20 | 21 | Route::get('/{Parish}', [ 22 | ParishController::class, 23 | 'view', 24 | ])->name('view'); 25 | }); 26 | -------------------------------------------------------------------------------- /routes/api/weather.php: -------------------------------------------------------------------------------- 1 | name('weather::')->group(static function (): void { 16 | Route::prefix('observations')->name('observations::')->group(static function (): void { 17 | Route::get('/', [ 18 | WeatherObservationController::class, 19 | 'index', 20 | ])->name('index'); 21 | 22 | Route::get('/{WeatherObservation}', [ 23 | WeatherObservationController::class, 24 | 'view', 25 | ])->name('view'); 26 | }); 27 | }); 28 | -------------------------------------------------------------------------------- /routes/console.php: -------------------------------------------------------------------------------- 1 | 8 | */ 9 | 10 | $uri = urldecode( 11 | parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH) 12 | ); 13 | 14 | // This file allows us to emulate Apache's "mod_rewrite" functionality from the 15 | // built-in PHP web server. This provides a convenient way to test a Laravel 16 | // application without having installed a "real" web server software here. 17 | if ($uri !== '/' && file_exists(__DIR__.'/public'.$uri)) { 18 | return false; 19 | } 20 | 21 | require_once __DIR__.'/public/index.php'; 22 | -------------------------------------------------------------------------------- /storage/app/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !public/ 3 | !.gitignore 4 | -------------------------------------------------------------------------------- /storage/app/public/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /storage/framework/.gitignore: -------------------------------------------------------------------------------- 1 | config.php 2 | routes.php 3 | schedule-* 4 | compiled.php 5 | services.json 6 | events.scanned.php 7 | routes.scanned.php 8 | down 9 | -------------------------------------------------------------------------------- /storage/framework/cache/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !data/ 3 | !.gitignore 4 | -------------------------------------------------------------------------------- /storage/framework/cache/data/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /storage/framework/sessions/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /storage/framework/testing/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /storage/framework/views/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /storage/logs/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /tests/Integration/Commands/Api/ResponseCacheBustCommandTest.php: -------------------------------------------------------------------------------- 1 | assertFalse(Cache::has('tags_for_cache_busting')); 23 | 24 | $this->artisan('api:bust:response-cache'); 25 | 26 | $this->assertFalse(Cache::has('tags_for_cache_busting')); 27 | } 28 | 29 | /** 30 | * @test 31 | */ 32 | public function itSuccessfullyBustsResponseCache(): void 33 | { 34 | $this->assertFalse(Cache::has('tags_for_cache_busting')); 35 | 36 | add_cache_bust_tag('foo'); 37 | 38 | $this->assertTrue(Cache::has('tags_for_cache_busting')); 39 | 40 | $this->artisan('api:bust:response-cache'); 41 | 42 | $this->assertFalse(Cache::has('tags_for_cache_busting')); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /tests/Integration/Controllers/FallbackController/DocumentationEndpointTest.php: -------------------------------------------------------------------------------- 1 | json('GET', Str::random()); 18 | 19 | $response->assertStatus(302); 20 | $response->assertRedirect('/documentation'); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /tests/Integration/CreatesApplication.php: -------------------------------------------------------------------------------- 1 | make(Kernel::class)->bootstrap(); 21 | 22 | // Clear the cache 23 | $app['cache.store']->flush(); 24 | 25 | return $app; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /tests/Integration/HttpClientMocker.php: -------------------------------------------------------------------------------- 1 | addResponse($response); 25 | } 26 | 27 | return $httpClient; 28 | } 29 | 30 | /** 31 | * @param string $path 32 | * @param int $status 33 | * @param array $headers 34 | * 35 | * @return ResponseInterface 36 | */ 37 | protected function createHttpResponse(string $path = null, int $status = 200, array $headers = []): ResponseInterface 38 | { 39 | return new Response($status, $headers, $path ? \file_get_contents(base_path($path)) : null); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /tests/Integration/RefreshDatabaseWithRoles.php: -------------------------------------------------------------------------------- 1 | artisan('db:seed', [ 19 | '--class' => 'RoleSeeder', 20 | ]); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /tests/Integration/TestCase.php: -------------------------------------------------------------------------------- 1 | 'application/vnd.api+json;charset=utf-8', 13 | ]; 14 | 15 | protected const INVALID_CONTENT_TYPE_HEADER = [ 16 | 'Content-Type' => 'application/vnd.api+json;charset=utf-8', 17 | ]; 18 | 19 | protected const VALID_CONTENT_TYPE_HEADER = [ 20 | 'Content-Type' => 'application/vnd.api+json', 21 | ]; 22 | 23 | /** 24 | * {@inheritDoc} 25 | */ 26 | protected function setUp(): void 27 | { 28 | parent::setUp(); 29 | 30 | // Make sure tests don't get throttled when hitting the endpoints 31 | $this->withoutMiddleware(\Illuminate\Routing\Middleware\ThrottleRequests::class); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /tests/Unit/TestCase.php: -------------------------------------------------------------------------------- 1 |