Our help centre is currently under construction. Some articles might be missing, and some languages might not be available yet. 

INT: Corporate Portal Technical

Prev Next
This content is currently unavailable in Dutch. You are viewing the default (English) version.

Please note: This article is OTYS only, and can only be read by users that have logged in.

Reminder for developers

Before working on this project in any way or form, make sure to fully understand what it is, how it’s setup and what roles there are. This information can be found in other articles.


Tech-stack used

The Corporate portal consists of 2 main projects, the Front-End and Back-end. The Front-end is an Angular v21 applications and the Back-end an Symfony 7.4 application.

Below the 3th party packages used that are not directly bound to either Angular or Symfony.

Project

Package

Description

Front-end

Angular CDK

Used for drag&drop and more

Front-end

Transloco

Used for translations

Front-end

Splide JS

Used for sliding animations

Front-end

Angular Calendar

Used for calendar features

Front-end

AngularX QR Code

Used for QR code generation

Front-end

Date FNS

Date formatting

Front-end

NGX Editor

WYSIWYG Editor

Front-end

Vanilla Colorful

Color picker

Back-end

API Platform

REST API

Back-end

AWS SDK

Cloudflare integration

Back-end

Firebase PHP JWT

JWT token generation

Back-end

Gedmo

Translations

Back-end

Nelmio Cors

CORS Policy


OWS Usage

This application directly connects to OWS. Most requests are made on behalf of the user, and their session id. However some requests are done with our Your!T api key. Below a list per type:

User Requests

  • Otys.Services.VacancyService.getDetail > Get vacancy detail

  • Otys.Services.VacancyService.getListEx > List vacancies

  • Otys.Services.VacancyService.createFromProfile > Create vacancy from template

  • Otys.Services.VacancyService.update > Update vacancy

  • Otys.Services.VacancyService.Update > Update vacancy fields (batch)

  • Otys.Services.VacancyService.updateEx > Update vacancy (extended)

  • Otys.Services.VacancyService.delete > Delete vacancy

  • Otys.Services.VacancyService.getExtraFieldsList > Get vacancy extra fields

  • Otys.Services.VacancyService.getExtraFieldOptionsList > Get options for extra field

  • Otys.Services.VacancyService.getVancancyProfileList > List vacancy templates

  • Otys.Services.VacancyService.getVacancyProfile > Get vacancy profile

  • Otys.Services.VacancyService.getOptionLists > Get option lists (HM/VM/CM)

  • Otys.Services.VacancyService.getSearchFacilityInfo > Get Actonomy search info

  • Otys.Services.VacancyStatusService.getList > List vacancy statuses

  • Otys.Services.VacancyTypeService.getList > List vacancy types

  • Otys.Services.VacancyQuestionSetService.getVacancyQuestionSet > Get vacancy question set

  • Otys.Services.VacancyQuestionSetService.getDetail > Get question set detail

  • Otys.Services.ProcedureService.getDetail > Get procedure detail

  • Otys.Services.ProcedureService.getListEx > List procedures

  • Otys.Services.ProcedureService.update > Update procedure

  • Otys.Services.ProcedureStatus1Service.getList > List procedure statuses

  • Otys.Services.ProcedureRejectionReasonsSettingsService.getList > List rejection reasons

  • Otys.Services.CandidateService.getDetail > Get candidate detail

  • Otys.Services.CandidateService.getListEx > List candidates

  • Otys.Services.CandidateService.getOptionLists > Get candidate option lists (translations)

  • Otys.Services.UserService.getDetail > Get user profile detail

  • Otys.Services.UserService.getListEx > List users

  • Otys.Services.UserService.update > Update user profile

  • Otys.Services.SettingsService.get > Get settings (locale, appointment planner)

  • Otys.Services.MatchCriteriaSettingsService.getList > List match criteria settings

  • Otys.Services.FileService.getTempKey > Get temp download key for document

  • Otys.Services.FileService.bind > Bind uploaded file to entity

  • Otys.Services.AttachedDocumentsService.getList > List attached documents

  • Otys.Services.NoticeService.getTypesForEntity > Get notice types for entity

  • Otys.Services.NoticeService.add > Add a notice/note

  • Otys.Services.NoticeService.getDetail > Get notice detail

  • Otys.Services.DocumentTypeService.add > Add document type

  • Otys.Services.DossierService.getList > List dossier entries

  • Otys.Services.EmailService.send > Send email

  • Otys.Services.CalendarRemoteService.getAppointmentsTypes > Get appointment types

  • Otys.Services.CalendarRemoteService.getColleagues > Get colleagues

  • Otys.Services.CalendarRemoteService.getAppointments > Get calendar appointments

  • Otys.Services.CalendarRemoteService.putAppointment > Create appointment

  • Otys.Services.CaldavService.getOutlookRefreshToken > Get Outlook refresh token

  • updatePassword > Change password (authenticated)

  • logout > Logout current session

  • userFileUploadRequest > Upload file to OWS

App Requests (your!t)

  • Otys.Services.CsmService.getValue > Get CSM config value (roles, TOTP enabled)

  • Otys.Services.UserService.getClientUsers > Find user by username for password reset

  • Otys.Services.CsmService.setValue > Set temporary password via CSM

  • Otys.Services.UserService.getTotpSettings > Get TOTP setup (secret + QR)

  • Otys.Services.UserService.verifyTotpCode > Verify TOTP code during setup

  • Otys.Services.WebsiteService.getList > Sync client brands/websites

Session-less Requests (based around temp user session)

  • login > Login (obtain OWS session)

  • check > Validate existing session

  • Otys.Services.UserService.getDetail > Get user detail during auth flow

  • logout > Logout temp session after password reset

  • logOutDevice > Logout a specific device session

  • checkTotpcode > Verify TOTP code (pre-auth)

  • updatePassword > Update password with temp session

File Requests (user based)

  • VacancyPhoto > Get vacancy photo

  • UserPhoto > Get user photo

  • CandidatePhoto > Get candidate photo


Back-end Caching setup

The back-end cache is setup for OWS caching

The central caching mechanism for OWS data is OwsCacheService. This service wraps Symfony's tag-aware cache and adds multi-tenancy awareness. It exposes the following key method:

getOrCompute(string $key, callable $callback, int $ttl, array $tags, ?int $clientId): Attempts to retrieve a cached value by key. If the key is not found, it executes the callback, caches the result with the given TTL and tags, and returns it.

Cache keys are automatically scoped to the current tenant. The service prepends ows_{clientId}_ to every key, so a key like vacancy_detail_abc123 for client 42 becomes ows_42_vacancy_detail_abc123 (before Redis-level prefixing). When no user is authenticated (e.g. during background sync), the client ID can be passed explicitly.

Automatic Tagging

Every cached item receives three automatic tags:

client_{clientId} — groups all cache entries for a specific tenant.

The "category" tag — derived from the first segment of the key (e.g. vacancy from vacancy_detail_123, procedure from procedure_statuses).

client_{clientId}_{category} — combines both for client-scoped category invalidation.

Additional tags can be passed via the $tags parameter for fine-grained invalidation (e.g. vacancy_123 to tag a specific vacancy's cache entries).

TTLs in Practice

Different data types use different TTLs based on how frequently they change:

10 seconds: Vacancy detail (changes frequently during editing)

600 seconds (10 min): Client brand websites

3600 seconds (1 hour): Default for most data

86400 seconds (24 hours): Relatively static data like procedure statuses, vacancy question sets, and rejection reasons

Cache Invalidation

Cache invalidation is tag-based, with three strategies:

By entity type (invalidateByEntityType): Invalidates the client_{clientId}_{entityType} tag, clearing all cached data of that type for the current tenant. Used after bulk operations or when multiple entries of a type may be stale (e.g. invalidating all procedure cache after a status change).

By specific entity (invalidateEntity): Invalidates a specific tag like vacancy_{vacancyUid}, clearing only the cache entries tagged with that specific entity ID. Used after updates to individual resources.

Full client cache clear (clearClientCache): Invalidates the client_{clientId} tag, clearing all OWS cache for the current tenant. Exposed via DELETE /api/cache for Portal Admins.


Front-end caching setup

The application uses a custom HTTP caching layer built on an Angular HttpInterceptorFn. It caches GET request responses in localStorage, scoped per user, with configurable TTLs and multiple caching strategies. Only GET requests are cached; mutations (POST, PUT, PATCH, DELETE) bypass caching and automatically invalidate related cache entries.

Storage

Cached responses are stored in localStorage. Each entry is keyed with a prefix that includes an environment-specific cache prefix and the current user's ID (e.g., cp_user_42_/api/vacancies?page=1). This ensures cached data is isolated per user and per environment. Entries are stored as JSON objects containing the response data, a timestamp, and the TTL.

For content-language-aware endpoints, the Accept-Language header value is appended to the cache key (e.g., /api/vacancies|lang=nl), so responses in different languages are cached separately.

Cache Strategies

Each HTTP GET request can specify one of four strategies via an Angular HttpContextToken:

cache-first:  Checks localStorage first. If a valid (non-expired) entry exists, it is returned immediately without making a network request. If not, the request is made, and the response is cached for future use.

network-first: Always makes a network request. On success, the response is cached. On failure (network error), falls back to the cached entry if one exists. Useful for data where freshness is important but offline resilience is desired.

cache-then-network (default for most entity reads): Returns the cached entry immediately if available, then fires a network request in the background. The subscriber receives two emissions: first the cached data, then the fresh network data. If the network request fails after cache was served, a stale-data warning toast is shown to the user (unless the error is a 404). This gives the user instant perceived performance while still keeping data fresh.

network-only: Bypasses caching entirely. The request always goes to the network, and the response is not stored. Used for mutation-adjacent reads (e.g., refresh()) and endpoints where caching is inappropriate.

Request Deduplication

An in-memory Map tracks in-flight requests by cache key. If a second request is made for the same resource while the first is still pending, the second subscriber shares the same Observable (via RxJS share()). This prevents duplicate simultaneous HTTP calls to the same endpoint.

TTLs

The BaseEntityService accepts a TTL in its constructor. If not specified, it defaults to 5 minutes (300,000 ms). The global fallback TTL (used when no strategy token is provided on a request) is also 5 minutes, configured via the HTTP_CACHE_CONFIG injection token.

Most entity services use a 24-hour TTL (86,400,000 ms). This applies to client, client brand, procedure, procedure status, vacancy, vacancy flow, vacancy flow step, vacancy status, vacancy template, and vacancy type services. The activity log service is the exception, using a 1-minute TTL due to its frequently changing nature.

Cache Exclusions

Auth-related endpoints (URLs matching /auth/) are excluded from caching entirely, regardless of strategy. This is configured via the excludePatterns array on the global cache config.

Cache Invalidation

On mutation: when a create, update, patch, or delete operation is performed through a BaseEntityService, all cache entries whose key contains that service's base URL are invalidated. For example, creating a vacancy removes all cached entries matching /vacancies.

On logout: the clearByUserPrefix() method removes all localStorage entries for the logged-out user's ID prefix, ensuring no cached data leaks between user sessions.

Each entity service also exposes a refresh() method that first invalidates the cache for that entity, then fetches fresh data with network-only.

Automatic Cleanup

A background timer runs every 5 minutes and scans all cache entries for the current user. Expired entries (where the elapsed time exceeds the TTL) are removed from localStorage. If localStorage runs out of space when writing a new entry, expired entries are cleared and the write is retried.


Devops setup

Unclear right now