Commit Graph

1896 Commits

Author SHA1 Message Date
45c89a46d6 FieldMetadata ENUM CREATE UPDATE server validation and integration tests (#12121)
# Introduction

Big diff a lot of tests and snapshots ( real diff < 500+ )

close https://github.com/twentyhq/twenty/issues/12117
close https://github.com/twentyhq/twenty/issues/12133

## What has been done here
Implemented a strong integration coverage on both fieldmetadata`SELECT`
`UPDATE` and `CREATE`.
Implemented server side validation for the options `value` `label` `id`
and collision issue with also `position`

We could improve:
- Position validation
- DefaultValue validation

## Update
```ts
 PASS  test/integration/metadata/suites/field-metadata/update-one-field-metadata-select.integration-spec.ts (41.054 s)
  Field metadata select update tests group
    ✓ Update should succeed with provided option id (2565 ms)
    ✓ Update should succeed with valid default value (1469 ms)
    ✓ Update should succeed with various options id (1257 ms)
    ✓ Update should succeed without option id (1286 ms)
    ✓ Update should trim option values (1366 ms)
    ✓ Update should succeed with default value and no options (1122 ms)
    ✓ Update should fail with unknown default value and no options (1075 ms)
    ✓ Update should fail with only white spaces id (1195 ms)
    ✓ Update should fail with empty string id (1058 ms)
    ✓ Update should fail with null id (1066 ms)
    ✓ Update should fail with not a string id (1098 ms)
    ✓ Update should fail with too long id (1373 ms)
    ✓ Update should fail with only white spaces label (1034 ms)
    ✓ Update should fail with empty string label (1057 ms)
    ✓ Update should fail with null label (1100 ms)
    ✓ Update should fail with not a string label (1144 ms)
    ✓ Update should fail with too long label (1273 ms)
    ✓ Update should fail with only white spaces value (1385 ms)
    ✓ Update should fail with empty string value (1035 ms)
    ✓ Update should fail with null value (1068 ms)
    ✓ Update should fail with not a string value (1021 ms)
    ✓ Update should fail with too long value (1134 ms)
    ✓ Update should fail with invalid option id (1137 ms)
    ✓ Update should fail with empty options (1238 ms)
    ✓ Update should fail with invalid option value format (1104 ms)
    ✓ Update should fail with comma in option label (1004 ms)
    ✓ Update should fail with duplicated option values (1015 ms)
    ✓ Update should fail with duplicated option ids (1079 ms)
    ✓ Update should fail with duplicated option positions (1266 ms)
    ✓ Update should fail with duplicated trimmed option values (1220 ms)
    ✓ Update should fail with undefined option label (1029 ms)
    ✓ Update should fail with an invalid default value (1142 ms)
    ✓ Update should fail with an unknown default value (1081 ms)
    ✓ Update should fail with undefined option value (1086 ms)

Test Suites: 1 passed, 1 total
Tests:       34 passed, 34 total
Snapshots:   28 passed, 28 total
Time:        41.079 s
```


## Create
```ts
 PASS  test/integration/metadata/suites/field-metadata/create-one-field-metadata-select.integration-spec.ts (38.292 s)
  Field metadata select creation tests group
    ✓ Create should succeed with provided option id (2096 ms)
    ✓ Create should succeed with valid default value (1316 ms)
    ✓ Create should succeed with various options id (1113 ms)
    ✓ Create should succeed without option id (1378 ms)
    ✓ Create should trim option values (1296 ms)
    ✓ Create should fail with only white spaces id (1000 ms)
    ✓ Create should fail with empty string id (1325 ms)
    ✓ Create should fail with null id (1060 ms)
    ✓ Create should fail with not a string id (1142 ms)
    ✓ Create should fail with too long id (1321 ms)
    ✓ Create should fail with only white spaces label (999 ms)
    ✓ Create should fail with empty string label (1163 ms)
    ✓ Create should fail with null label (1198 ms)
    ✓ Create should fail with not a string label (1678 ms)
    ✓ Create should fail with too long label (1527 ms)
    ✓ Create should fail with only white spaces value (1200 ms)
    ✓ Create should fail with empty string value (1102 ms)
    ✓ Create should fail with null value (1037 ms)
    ✓ Create should fail with not a string value (1462 ms)
    ✓ Create should fail with too long value (896 ms)
    ✓ Create should fail with invalid option id (997 ms)
    ✓ Create should fail with empty options (1058 ms)
    ✓ Create should fail with invalid option value format (1190 ms)
    ✓ Create should fail with comma in option label (1142 ms)
    ✓ Create should fail with duplicated option values (872 ms)
    ✓ Create should fail with duplicated option ids (860 ms)
    ✓ Create should fail with duplicated option positions (1002 ms)
    ✓ Create should fail with duplicated trimmed option values (1336 ms)
    ✓ Create should fail with undefined option label (754 ms)
    ✓ Create should fail with an invalid default value (696 ms)
    ✓ Create should fail with an unknown default value (678 ms)
    ✓ Create should fail with undefined option value (699 ms)
    ✓ Create should fail with null options (720 ms)
    ✓ Create should fail with undefined options (686 ms)

Test Suites: 1 passed, 1 total
Tests:       34 passed, 34 total
Snapshots:   29 passed, 29 total
Time:        38.314 s
```

## Conclusion
As always any suggestions are welcomed ! Please let me know


## Discussion about validation governance
### Front
Front side will be dealing with zod validations schema that he will
handle and maintain by himself

### Back validation instances
- Validation hold through DTO declarations ( run by yoga through the
resolvers )
- Server programmatic validation and exceptions handling ( run through
the services )

For this refactor/fix we decided to stick to the current implementation
only touching the `Server programmatic validation and exceptions
handling` we will handle validation centralization when we will onboard
the `nestjs-query` deprecation/integration refactor.

### Vision
In the best of the world we could think of an intermediary model that
will handle and take responsibility of the validation decorators that
would be run programmatically through the service, Yoga would still
consume it ? then we would need to have enough grain in the service to
know the input has already validated

## Notes
Introduced zod back side in order to handle very atomic and primitive
validation
2025-05-22 17:58:59 +02:00
7cc0a7ae72 add checkFileExists method in file storage service (#12229) 2025-05-22 15:09:21 +00:00
aeee04415d add more logs to cleaning command (#12219) 2025-05-22 13:25:19 +00:00
4ac47c2a1b Improve logs in workflow trigger (#12215)
- distinguish logs coming from webhook and job triggers
- add workspace and workflow ids to help debugging

Hard to debug sentry issue:

https://twenty-v7.sentry.io/issues/6605607134/?project=4507072499810304&query=&referrer=issue-stream&stream_index=7
2025-05-22 14:56:30 +02:00
0ac4cc6899 captcha - keep consistency with token send to validation (#12195)
[Sentry
error](https://twenty-v7.sentry.io/issues/6494360796/events/322590f8af5c4475beebb66e5f22a2f7/?environment=prod&project=4507072499810304&query=is%3Aunresolved%20issue.priority%3A%5Bhigh%2C%20medium%5D&referrer=previous-event&seerDrawer=true&sort=date&stream_index=6)
raised because of invalid argument type (undefined in some case when
string is expected)

closes https://github.com/twentyhq/core-team-issues/issues/869 and
closes https://github.com/twentyhq/twenty/issues/12208
2025-05-22 13:49:23 +02:00
9753637693 Fix upgrade command (#12210) 2025-05-22 12:22:21 +02:00
1365c202d9 Increase auto subdomain generation cardinality (#12206)
Fixes https://github.com/twentyhq/twenty/issues/12204
2025-05-22 11:55:20 +02:00
786ad088b3 clean not found file - add logs (#12198) 2025-05-21 23:56:02 +02:00
8d105417ba Add forgotten 054 commands (#12193)
Wrongly named branch
2025-05-21 17:59:17 +02:00
d38f448d05 Move fieldMetadata migration from metadata to core 2025-05-21 16:36:04 +02:00
dc2d7f7c94 Fix upgrade command for 0.54 2025-05-21 16:25:39 +02:00
6c9d17eee7 i18n - translations (#12189)
Created by Github action

Co-authored-by: github-actions <github-actions@twenty.com>
2025-05-21 16:20:48 +02:00
578f07374b 922 remove todos from 11809 after release (#12183)
- remove todos
- remove workflow-event-listener.workspace-entity.ts
2025-05-21 16:15:48 +02:00
85a17a54b3 Do not upgrade if no migrate (#12187) 2025-05-21 16:01:03 +02:00
a5b212369b Set findOneServerlessFunction result nullable (#12179)
solves https://github.com/twentyhq/twenty/issues/11819
remove fetch query when deleting code step
2025-05-21 13:59:20 +00:00
8c6569be3b Add relation exceptions (#12185)
Introducing a class of RelationException extending CustomException to
help grouping those exception in sentries by ExceptionCode.
I did not introduce a filter as these are thrown in utils that can be
used in multiple places now or in the future, and filters are to be
added at resolver-level.
2025-05-21 15:53:25 +02:00
7f1d6f5c7f Continue migration schema from metadata to core (#12168)
2nd part of the migration - this time we're migrating all the tables

---------

Co-authored-by: prastoin <paul@twenty.com>
2025-05-21 15:44:13 +02:00
5c16b2752d Fix workflow sub objects updates validation (#12169)
Fixes https://github.com/twentyhq/core-team-issues/issues/983

- Enabling workflow version name update, even if not in draft status
- Assert draft when updating steps
- Enabling workflow run name update
2025-05-21 13:11:32 +00:00
23ebd69541 Add Approved access domain exception filter (#12180)
We have approvedAccessDomain custom exceptions, but they were never
filtered while some of them reflects 4xx errors which we don't want to
be captured as 5xx errors
2025-05-21 14:36:14 +02:00
fdde7651a4 Fix email verification exception are not caught (#12174)
cf
[sentry](https://twenty-v7.sentry.io/issues/6606375182/?project=4507072499810304&query=is%3Aunresolved%20%21issue.type%3A%5Bperformance_consecutive_db_queries%2Cperformance_consecutive_http%2Cperformance_file_io_main_thread%2Cperformance_db_main_thread%2Cperformance_n_plus_one_db_queries%2Cperformance_n_plus_one_api_calls%2Cperformance_p95_endpoint_regression%2Cperformance_slow_db_query%2Cperformance_render_blocking_asset_span%2Cperformance_uncompressed_assets%2Cperformance_http_overhead%2Cperformance_large_http_payload%5D&referrer=issue-stream&sort=date&stream_index=4)

Email verification exception were not caught when thrown outside of
email-verification resolver, which be the case at authentication step.
2025-05-21 14:09:11 +02:00
3702fefc89 Move defaultAvatarUrl on userWorkspace + migration command (#12100)
closes https://github.com/twentyhq/core-team-issues/issues/883
2025-05-21 12:07:02 +02:00
8e2d0139ed Emit proper event on createOrUpdate csv import operation (#12163)
- use proper event emitter when upserting records with csv import
- After:


https://github.com/user-attachments/assets/8303da38-2e35-4f4c-bb13-8a7a222971b7
2025-05-21 11:59:50 +02:00
7461b7ac58 lowercase user and invitation emails (#12130)
### Solution

> After discussion with charles & weiko, we chose the long term
solution.
> 
> Fix FE to request checkUserExists resolver with lowercased emails
> Add a decorator on User (and AppToken for invitation), to lowercase
email at user (appToken) creation. ⚠️ It works for TypeOrm .save method
only (there is no user email update in codebase, but in future it
could..)
> Add email lowercasing logic in external auth controller
> Fix FE to request sendInvitations resolver with lowercased emails
> Add migration command to lowercase all existing user emails and
invitation emails

> For other BE resolvers, we let them permissive. For example, if you
made a request on CheckUserExists resolver with uppercased email, you
will not found any user. We will not transform input before checking for
existence.

[link to comment
](https://github.com/twentyhq/twenty/pull/12130#discussion_r2098062093)

### Test 🚧 
- sign-in and up from main subdomain and workspace sub domain > Google
Auth (lowercased email) ✔️ | Microsoft Auth (uppercased email ✔️ &
lowercased email) | LoginPassword (uppercased email ✔️& lowercased
email✔️)
- invite flow with uppercased and lowercased ✔️
- migration command + sign-in ( former uppercased microsoft email ✔️) /
sign-up ( former uppercased invited email ✔️)

closes https://github.com/twentyhq/private-issues/issues/278, closes
https://github.com/twentyhq/private-issues/issues/275, closes
https://github.com/twentyhq/private-issues/issues/279
2025-05-21 11:06:29 +02:00
6ff5a5bafa fixing a microsoft graph api error (#12154)
The issue is "Microsoft Graph API unknown error: TypeError: Cannot read
properties of undefined (reading 'emailAddress')"

Sentry :
https://twenty-v7.sentry.io/issues/6605750776/events/7feb4f5949a64ab78bb429cffb7f0fea/?project=4507072499810304

Fixes https://github.com/twentyhq/core-team-issues/issues/981
2025-05-21 11:04:43 +02:00
91e487dd63 Catching "no licence" microsoft account (#12143)
Catching "no licence - removed" microsoft message channels. 

Current behabiour 
> ` MessageImportException [Error]: The mailbox is either inactive,
soft-deleted, or is hosted on-premise.`

Goal:
better track errors VS user mistakes

Context: 
A similar logic was already implemented for the calendar channels. I
just replicated it to message channels
2025-05-21 11:04:11 +02:00
ad136b5246 better logging errors in messages (#12136) 2025-05-20 13:37:24 +02:00
98e199c01d Support Full Name as Record Text Identifier (#11610)
closes #11296 


[recording.webm](https://github.com/user-attachments/assets/da0f2587-a435-4bee-a802-81eb9ca92733)

---------

Co-authored-by: Charles Bochet <charles@twenty.com>
2025-05-20 00:03:30 +02:00
efc43208d3 add command to clean not found files (#12094)
closes https://github.com/twentyhq/core-team-issues/issues/883

tested on person, workspaceMember, workspace and attachments files -
dry/normal
2025-05-19 14:45:15 +02:00
52ad789e7a fix billingCustomer stripeId fetching (#12116)
### Context
Several 'Customer not found' errors arrived in Sentry, all coming from
webhook-entitlement.service, at subscription creation (coinciding with
customer creation 99% of the time).
Stripe sends many events to update/create customer, subscription,
entitlement, ...
All these events are handle in parallel but customer.created stripe
event arrived first and few seconds after subscription.created and
entitlements.active_entitlement_summary.updated

Issue happens at entitlements.active_entitlement_summary.updated
handling. It checks for customer existence through subscription. But
subscription can be not created yet at this moment.

### Solution
Check directly for customer existence in billingCustomer table. Not sure
it will fix the error because of the parallel handling of Stripe event,
but should still be better.

### Tested
- Workspace creation
- Subscription upgrade (check for entitlement update)

closes https://github.com/twentyhq/twenty/issues/11960
2025-05-19 13:37:18 +02:00
b52ef76376 971 rest api bug sentry on filter parameters (#12088)
- fix missing createBy injection in api createOne and createMany
endpoints
- add a command to fix null default value for createdBySource in
production entities
- tested on `1747159401197/` dump extract of production db without issue
2025-05-19 12:46:03 +02:00
64d988cdec Fix pg pool implementation (#12106)
Fix the following error: 
Cannot use a pool after calling end on a pool

<img width="917" alt="Screenshot 2025-05-17 at 14 56 18"
src="https://github.com/user-attachments/assets/63081831-9a7e-4633-8274-de9f8a48dbae"
/>

The problem was that the datasource manager was destroying the
connections when a datasource cache expired.
2025-05-17 15:22:10 +02:00
d93024fd02 Refactoring the reconnect service (#12089)
following qrqc #3 : refactoring the reconnect service

Fixes https://github.com/twentyhq/twenty/issues/12064
2025-05-17 13:47:01 +02:00
e83baa5438 Patch pg pool (#12081)
This PR implements a global PostgreSQL connection pool sharing
mechanism.

- Patches pg.Pool to reuse connection pools across the application when
connection parameters match, reducing resource overhead.
- New environment variables allow enabling/disabling sharing and
configuring pool size, idle timeout, and client exit behavior.

WorkspaceDatasourceFactory will now use shared pools if enabled, this
will avoid recreating 10 connections for each pods for each workspace.

---------

Co-authored-by: Charles Bochet <charlesBochet@users.noreply.github.com>
2025-05-16 21:46:37 +02:00
806bb611e8 Fix yoga scalar validations being captured (#12085)
Yoga graphql error were not correctly interpreted by the exception
handler. Mostly validations on the scalars such as bad enum options,
wrong format for uuid and such.
This PR adds a new convertGraphQLErrorToBaseGraphQLError utility
function in graphql-errors.util.ts that converts those errors to our
custom BaseGraphQLError by using the extension.http.code from the error
when possible so they can be handled the same way we treat the graphql
errors we throw ourselves.

Before
<img width="799" alt="Screenshot 2025-05-16 at 11 04 08"
src="https://github.com/user-attachments/assets/08b0a908-34d8-45a6-b315-8e211d1104ce"
/>

After
<img width="797" alt="Screenshot 2025-05-16 at 11 16 37"
src="https://github.com/user-attachments/assets/3fff0a70-6c3f-413a-b458-56030377fec9"
/>
2025-05-16 18:11:52 +02:00
160eb23454 QRQC #2 - Fix (#12054)
- [x] findOne/findManyCalendarEvents / findOne/findManyMessages - add
integration tests
- [ ] ~~fix typing~~ - coming in new PR >
[Issue](https://github.com/twentyhq/core-team-issues/issues/976)
2025-05-16 11:38:34 +02:00
dc4bcc3049 Improve sentry filtering and grouping (#12071)
Follow-up on https://github.com/twentyhq/twenty/pull/12007

In this PR

- adding a filter on HttpExceptionHandlerService to filter out 4xx
errors from driver handling (as we do for graphQL errors: see
useGraphQLErrorHandler hook - only filteredIssues are sent to`
exceptionHandlerService.captureExceptions()`.)
- grouping together more missing metadata issues
- attempting to use error codes as issues names in sentry to improve UI;
for now it says "Error" all the time
2025-05-16 11:35:48 +02:00
4d303a61d1 Change idle connection time (#12073)
As discussed with @Weiko

Even though we cache the datasource, the connection expire after
10minutes in TypeORM, that might be the reason why our app is spamming
the proxy asking for connections. Also lowering the pool size.
2025-05-15 19:23:51 +00:00
1570ad955e fix(domain-manager): correct domain URL reference and filter logic (#12074)
Updated URL reference from getFrontUrl to getBaseUrl to ensure correct
hostname handling. Adjusted record filtering logic to exclude successful
records, preventing unnecessary rendering in the UI.
2025-05-15 19:35:55 +02:00
442f8dbe3c [QRQC_2] No implicitAny in twenty-server (#12075)
# Introduction
Following https://github.com/twentyhq/twenty/pull/12068
Related with https://github.com/twentyhq/core-team-issues/issues/975

We're enabling `noImplicitAny` handled few use case manually, added a
`ts-expect-error` to the others, we should plan to handle them in the
future
2025-05-15 18:23:22 +02:00
08ce2f831e Fix missing exception catch (#12069)
add a check about uuid in rest api findOne handler
2025-05-15 15:01:57 +00:00
a8423e8503 [QRQC_2] No explicit any in twenty-server (#12068)
# Introduction

Added a no-explicit-any rule to the twenty-server, not applicable to
tests and integration tests folder

Related to https://github.com/twentyhq/core-team-issues/issues/975
Discussed with Charles

## In case of conflicts
Until this is approved I won't rebased and handle conflict, just need to
drop two latest commits and re run the scripts etc

## Legacy
We decided not to handle the existing lint error occurrences and
programmatically ignored them through a disable next line rule comment

## Open question
We might wanna activate the
[no-explicit-any](https://typescript-eslint.io/rules/no-explicit-any/)
`ignoreRestArgs` for our use case ?
```
    ignoreRestArgs?: boolean;
```

---------

Co-authored-by: etiennejouan <jouan.etienne@gmail.com>
2025-05-15 16:26:38 +02:00
c95c4383b4 Gmail temporary error (#12058)
# Handling Google error

This bug is hard to reproduce so the resolution is based only on the
Sentry logs. There is not definite error code
for this error message. in the documentation.
https://developers.google.com/workspace/gmail/api/guides

This is why i added more 5xx code types in order to be sure we catch
this. In order to refine this later, i added the error code to the
message.

Fixes https://github.com/twentyhq/twenty/issues/12025
2025-05-15 16:12:10 +02:00
f782f4dcd8 Fix CSV import upsert (#12048)
Fixes https://github.com/twentyhq/twenty/issues/11864 and
https://github.com/twentyhq/core-team-issues/issues/908

We should not send `createManyXXX` mutations with FE-forged ids in the
payload if we want to do an upsert, because that 1) prevents records
from being merged 2) triggers optimistic rendering while we can't know
before-hand which records will actually be created and which records
will only be updated

Also noticed createdBy was being overriden even for records we are
updating and not creating, which did not seem right, so fixed that too
2025-05-15 14:44:31 +02:00
411cb33137 [reconnect account] Reseting calendar status and stage on reconnect (#12061)
## Description
When a calendar channel fails, its status is not reset during the
reconnect step.

In some cases, resetting is necessary—especially when we’ve deployed a
fix and users try to reconnect their accounts after the patch.

## Why reseting to initial state
Our processes are idempotent, so we can safely set the status to null to
restart the flow. This approach covers all cases and avoids edge
conditions caused by inconsistent statuses.


Fixes : https://github.com/twentyhq/twenty/issues/12026
2025-05-15 14:12:32 +02:00
6a40ec604c Validate existing fields on creation (#12057)
Fixes https://github.com/twentyhq/twenty/issues/12040

When fields are deleted but still used in workflows we do not update
create record action settings.
It breaks all following workflow execution and the user cannot update
the settings anymore.

This PR fixes the bug by filtering on existing fields.

Next step will be to clean settings on field deletion. Adding it to fast
follows.

Also lowering throttle limit because some infinite loops are not
catched.
2025-05-15 13:24:57 +02:00
81cc5da982 [Rest Api] Fix find duplicates endpoint (#12044)
- fix endpoint
- migrate to new rest api v2 service
- add integration test
2025-05-14 20:03:59 +00:00
fdc7d6c93c Messaging issues (#12041)
messageImportException
for messaging and calendar
add more context to the Sentry Exceptions

Fixes https://github.com/twentyhq/twenty/issues/11994
2025-05-14 18:37:42 +02:00
929f7876de fix calendar events and messages post hooks (#12034)
Context
workspaceMemberId is not always present in AuthContext. Solution
implemented here is to fetch workspaceMemberId via userId.

QRQC coming ... 

Tested
- FindManyCalendarEvents / FindOneCalendarEvent
- FindManyMessages / FindOneMessage

closes https://github.com/twentyhq/twenty/issues/12027
2025-05-14 15:52:52 +02:00
0c60fa9c23 Fix cacheData not found after recomputes (#12032)
Closes https://github.com/twentyhq/core-team-issues/issues/861

sentries: [User workspace role map not found after
recompute](https://twenty-v7.sentry.io/issues/6575092700/events/f9825338a30b470eb2345fe78c1e3479/?project=4507072499810304&query=is%3Aunresolved%20issue.priority%3A%5Bhigh%2C%20medium%5D%20not%20found%20after%20recompute&referrer=next-event&stream_index=0)
(64 events in 90d), [Feature flag map not found after
recompute](https://twenty-v7.sentry.io/issues/6547696076/?project=4507072499810304&query=is%3Aunresolved%20issue.priority%3A%5Bhigh%2C%20medium%5D%20not%20found%20after%20recompute&referrer=issue-stream&stream_index=1)
(23 events in 90d)

We have a structural issue with cached data and our locking mechanism:
if a data is missing in cache and queried at the same time by two
different entities, the first one will recompute the data and indicate a
lock during the operation, while the second one will seek to recompute
the data too but be stopped because of the ongoing lock, and be left
with no data to use, condemned to throw an error. In this PR I
considered that it was more important to avoid that error and chose to
ignoreLock instead when the data is being queried (through
getFromCacheWithRecompute), but this is maybe questionnable.
An important note is that I can't figure out how users that regularly
use twenty (as it has been the case since this error occured on our
internal workspace) can encounter that error as once computed, **the key
should always be present in the workspace**: the corresponding value it
is always updated, never deleted (until this PR) and recreated. I was
not able understand how this happened

For our data cached without a version to refer to in the database, I
also chose to ignore the lock when the recompute is triggered by a data
change (eg feature flag enabling or assigning user to a role or adding
an objectPermission on a role, etc.), as we never want to imped the
recompute in that case to avoid potential stale data.
2025-05-14 15:41:24 +02:00
a4c56bd7fb 960 api rest batch create not working (#12028)
- fix batch endpoint
- migrate batch endpoint to the rest api v2
- add new integration test for batch endpoints
2025-05-14 14:44:04 +02:00