Commit Graph

89 Commits

Author SHA1 Message Date
Paul Rastoin 0c545bcdeb [BREAKING-CHANGE] Centralize system View viewField side effect (#23081)
# Introduction

Closes https://github.com/twentyhq/core-team-issues/issues/2669

Part of the `isSystemSideEffect` engine-ownership effort. Until now, a
custom object's default **INDEX** table view (`All {objectLabelPlural}`)
and its view fields were built imperatively in `ObjectMetadataService`
with random `v4()` identifiers, while `twenty-standard` authored its own
copies with hardcoded literals. The two never converged, an object
rename could drift the view, and nothing marked these rows as
engine-owned.

This PR makes the metadata side-effect engine the **single owner** of
the INDEX view and its view fields, on name-free deterministic
identifiers, for custom and standard objects alike.

## Core design

- **Name-free deterministic identity.** The INDEX view identifier
derives from `object identifier + ViewKey.INDEX`
(`getSystemViewUniversalIdentifier`); each view-field identifier derives
from `view identifier + field identifier`
(`getViewFieldUniversalIdentifier`). An object rename (with a pinned
object identifier) keeps the same view, losslessly.
- **`isSystemSideEffect: true` is provenance.** Every INDEX view / view
field the engine emits is flagged system-owned, so manifest deletion
inference never drops it. The flag follows the view: a view field
inherits its parent view's flag.
- **The engine is the sole owner of the INDEX view.** It always emits
it; a caller providing one with the same derived identifier is a genuine
conflict surfaced by the engine's reserved-identifier collision, not
silently deferred.

## Changes

### Shared (`twenty-shared`)

- `getIndexViewUniversalIdentifier` →
`getSystemViewUniversalIdentifier`, now taking a `viewKey` (generalizes
to any singleton engine-owned view).
- Standard field identifiers extracted into a new
`STANDARD_OBJECT_FIELDS` constant, so both an object's `fields` and its
INDEX view read the same field identifiers.
- `buildStandardObjectIndexView` derives the standard INDEX view +
view-field identifiers from `STANDARD_OBJECT_FIELDS`, replacing the
hardcoded literals in `standard-object.constant.ts`.

### Metadata side-effect engine (custom objects)

- **`objectSystemFieldsAndIndexViewOnCreate`** (replaces
`objectSystemFieldsOnCreate`): on object creation, provisions the 7
reserved system fields **and** the INDEX view with one view field per
displayable system field, all `isSystemSideEffect: true`.
- **`fieldIndexViewFieldOnCreate`** (new): on field creation, provisions
the field's INDEX view field. Object created in the same batch →
visible, positioned before the system view fields; pre-existing object →
hidden, appended (preserving the historical `createOneField` behavior).
Both branches resolve the INDEX view by its derived identifier (single
map access, never a scan).
- **`fieldSystemViewFieldsOnDelete`** (new): on field deletion,
cascade-deletes every engine-owned view field displaying it.
- **`objectSystemSideEffectsOnDelete`** (extended): now also
cascade-deletes the object's engine-owned views and their view fields
(in addition to system fields, indexes, searchFieldMetadata). Every
lookup walks a foreign-key aggregator down from the deleted object, so
the work is proportional to what the object owns, never to workspace
size.
- Object-create and field-create positions are derived from the same
caller-input field list, so the INDEX view layout is contiguous with no
handler-ordering dependency.
- `view` / `viewField` added to the side-effect companion metadata names
for `fieldMetadata` and `objectMetadata`.

### Reserved-identifier invariant

A caller can never define an entity whose identifier collides with one a
system side effect produces: caller inputs are forced
`isSystemSideEffect: false` at every entry point (API and app-manifest
transpilers), and the engine raises
`RESERVED_SYSTEM_UNIVERSAL_IDENTIFIER`, aborting the operation, when a
system emission lands on a caller-claimed identifier. Covered by a new
engine-level test.

### Caller-side provisioning removed

The imperative INDEX view + view-field provisioning is removed from
`ObjectMetadataService.createOneObject`. The record-page `FIELDS_WIDGET`
view is intentionally left caller-side and deferred to the follow-up
(see below).

### `twenty-standard` convergence

Standard INDEX views and their view fields converge on the same
derived-identifier + `isSystemSideEffect: true` scheme as the engine.
`twenty-standard` syncs through the from/to migration path (which never
runs the side-effect engine), so it authors this INDEX surface itself,
matching what the engine produces for custom objects.

## Rollout

Two `2.26.0` workspace commands, running after the `2.25`
messageCampaign commands:

- `upgrade:2-26:reconcile-index-view-universal-identifier` re-owns the
INDEX views of the **twenty-standard and workspace-custom applications**
and all their view fields to the derived identifiers with
`isSystemSideEffect: true`, in a single per-workspace transaction. Each
view field identifier is keyed on the application of the **displayed
field** (an app or user column on a standard INDEX view converges too).
Soft-deleted views and view fields are skipped: one can coexist with an
active successor on the same derivation inputs and both would derive the
same identifier. Children reference the view by primary key, so the
re-own is lossless.
- `upgrade:2-26:demote-and-backfill-application-index-view` handles
**manifest-installed applications**, which never had their INDEX view
auto-provisioned: every caller-authored INDEX view of another
application is demoted to `key: null` (a plain additional view under its
manifest identifier), then every application object gets the
engine-owned INDEX view and its full view-field layout backfilled
through the migration pipeline's legacy path (no side-effect expansion),
views committed before view fields across applications since a view
field belongs to the application owning its field. Idempotent and
retry-safe: engine-owned INDEX views are neither demoted nor
re-backfilled, and view creation and view-field creation are gated
independently, so a retry after a partial failure still backfills the
missing view fields of an already-committed view.

Both support `--dry-run` and invalidate the full flat-maps closure
(parents aggregate the re-owned identifiers, children resolve them as
universal foreign keys, and page-layout widget universal configurations
resolve view PKs at cache-build time).

The `2.25` `upgrade:2-25:add-message-campaign-name-field` command is
adapted to resolve the campaign INDEX view by its INDEX key on the
object instead of by universal identifier: it now runs before the
reconcile, on workspaces still holding legacy identifiers.

## ⚠️ Breaking change

This PR **mutates 187 previously hardcoded universal identifiers** — the
standard objects' INDEX views and their view fields (the literals
removed from `standard-object.constant.ts`), now derived.

- **Handled by the `2.26` commands above** for all existing workspaces.
- **The INDEX key is now engine-reserved.** The flat view validator
rejects caller-created INDEX views (API and manifest inputs are forced
`isSystemSideEffect: false`) and enforces a single non-deleted INDEX
view per object; `view.key` is no longer a comparable/updatable
property, so no writer can promote or demote a view after creation.
`ViewManifest.key` is deprecated and ignored (manifest views are always
additional views, so old apps keep syncing and demoted views are not
promoted back); the REST/GraphQL create path now rejects `key: INDEX`.
In-repo example apps (`hello-world`, `document-generator`) no longer
declare it.
- **12 declared-but-never-seeded standard INDEX view field identifiers
deleted** (the former `preservedViewFields` on `timelineActivity`,
`workflowRun` and `workspaceMember`): after the reconcile, no workspace
row references them.
- **`computeFlatViewFieldsToCreate` now derives view field identifiers**
instead of drawing `v4()` ones, which also changes what the committed
`1-23` record-page backfill produces going forward (deliberate,
documented in-code).
- **Record-page views and view fields are not affected** (identifiers
unchanged).
- **In-repo apps: `twenty-last-contact` updated.** It was the only app
declaring explicit INDEX view fields (10 columns across `allPeople` /
`allCompanies` / `allOpportunities`) through manifest `viewFields`.
Those target identifiers are now engine-owned and derived, so the
manifest inputs no longer resolve and install failed with `View not
found`. The app now declares only its fields; the engine's
`fieldIndexViewFieldOnCreate` provisions the matching INDEX view field
automatically. No other app under `packages/twenty-apps` references any
of the 187 mutated identifiers, and apps that target standard views
point at record-page views (e.g. `real-estate` →
`opportunityRecordPageFields`) or their own objects (`twenty-partners`),
all unchanged.

### Loss of granularity for app maintainers

The engine now owns the INDEX view field of every field a caller adds to
an object, so app maintainers lose direct control over those columns.
Previously an app could target the engine-owned INDEX view with an
explicit manifest `viewField` and set its `position` and `isVisible`.
Now `fieldIndexViewFieldOnCreate` appends a **hidden** view field in
caller-input order on field creation, so:

- Columns an app previously showed at a **dedicated position** and
**visible** (e.g. `twenty-last-contact`'s last-contact columns) become
**hidden** and **appended in input order** after install.
- There is currently **no manifest way to override** the
engine-provisioned INDEX view field's position, visibility, or size.

This is a deliberate regression accepted for the sake of
single-ownership, and app maintainers should expect their INDEX columns
to move/hide after upgrading. A follow-up override API will let
maintainers reclaim per-field control over the engine-provisioned INDEX
view field.

## Testing

- Unit specs for each handler: object create (system fields + INDEX
view/view fields, override, position offset), field create (same-batch
vs existing-object, non-displayable noop, no-INDEX-view noop), field
delete, object delete (fields/indexes/searchFieldMetadata/views/view
fields cascade, reverse-relation view field on another object).
- Engine-level test for the reserved-identifier collision.
- `twenty-standard` guard test that its INDEX views/view fields stay on
the derived scheme and stay system-owned.
- Integration test: full engine provisioning of the INDEX view/view
fields on object creation, same view id preserved across an object
rename, and cascade delete on object deletion.

## Follow-up

The full record-page stack (record-page view, its view fields, view
field groups, page layout / tab / widget) is still built imperatively
and moves into the engine in
https://github.com/twentyhq/core-team-issues/issues/2721.
2026-07-29 13:32:21 +00:00
Félix Malfait 13f380b80d perf(front-component): fingerprint built-JS URLs by path for CDN caching (#22530)
**Stacked on #22523** — base is that branch, so the diff shows only this
commit. GitHub will retarget it to `main` automatically once #22523
merges.

Follows up on @FelixMalfait's question on #22523: move the
BuiltFrontComponent cache key from a query string into the path so it
plays well with Cloudflare cache rules.

## What

- URL: `/rest/front-components/:id?checksum=<c>` →
`/rest/front-components/:id/<c>.js` (`getFrontComponentUrl`).
- Route: the controller now accepts `[':frontComponentId',
':frontComponentId/:cacheKey']`. `:cacheKey` is a pure cache-buster the
server **ignores** — it still resolves by `:frontComponentId`, exactly
as the query param did.

## Why a path segment (not `:id-<checksum>.js`)

A path-based, extension-bearing URL is matched by Cloudflare's
**default** static-asset caching and by trivial `*.js` path cache rules,
and it's immune to any "ignore query string" cache setting that would
otherwise collapse `?checksum=` to one entry and serve stale JS.

I used a path **segment** (`/:id/:checksum.js`) rather than the literal
`:id-<checksum>.js` you sketched because the id is a **UUID — which
itself contains hyphens** — so a `-` separator is ambiguous to parse. A
segment is unambiguous and equally CDN-friendly (still ends in `.js`).

## Backward compatibility

The bare `:frontComponentId` route is kept, so URLs minted before this
deploys (query-string form, or in-flight pages) still resolve. It can be
dropped in a later release once no client mints the old form. No data
migration — the URL is computed at render time from `frontComponentId` +
`builtComponentChecksum`.

## ⚠️ Decision for you: this alone does not edge-cache — `private` vs
`public`

BFC is served behind `WorkspaceAuthGuard` and #22523 set its header to
**`private`**, max-age, immutable. `private` means shared caches
(Cloudflare) **won't** store it — so today this is browser-cache only,
and the path change just makes it *ready* for edge caching + clean cache
rules.

To actually get **edge** caching you'd additionally either flip BFC to
`public` or add a Cloudflare rule that overrides cache-control — which
means **accepting that the `id`+`checksum` URL becomes the access
capability** (a cache hit is served without re-checking origin auth).
The cache key is unique per component+build so there's no
cross-workspace mixup, but the built JS effectively becomes
public-by-URL (same posture PublicAsset already has). I've **left it
`private`** here; flipping to `public` is your call and can be a
one-line follow-up.

## Tests

- `getFrontComponentUrl` unit test: fingerprinted path when a checksum
is present, bare fallback otherwise.
- Integration test: the `/front-components/:id/:checksum.js` path serves
the built JS with `Content-Type: application/javascript` and
`Cache-Control: private, max-age=86400, immutable`. Existing bare-route
tests remain and still pass.

https://claude.ai/code/session_01AKwhTxYFDhWhCZ4b7sf35W

---
_Generated by [Claude
Code](https://claude.ai/code/session_01AKwhTxYFDhWhCZ4b7sf35W)_

<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/22530?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>
<!-- End of auto-generated description by cubic. -->
2026-07-03 17:17:14 +02:00
Raphaël Bosi 41d5d80a65 Migrate Company and Person standard fields in preparation for the enrichment app (#21171)
# Migrate Company and Person standard fields in preparation for the
enrichment app

## Why

Our standard `Person`/`Company` objects accumulated fields that aren't
generic to every
business, while missing a more universal revenue field that essentially
every CRM ships.
This PR makes the **Standard application** hold a tighter, more
universal set of fields,
and sets the stage for a follow-up PR that introduces a **People Data
Labs enrichment app**
to populate them.

## What changes

### Standard fields

**Demoted (Standard → Workspace Custom application)** — not generic
enough to ship as standard:

| Object  | Field                          | Type     |
| ------- | ------------------------------ | -------- |
| Company | annualRecurringRevenue (ARR)   | CURRENCY |
| Company | employees                      | NUMBER   |
| Company | idealCustomerProfile (ICP)     | BOOLEAN  |
| Company | xLink (X/Twitter)              | LINKS    |
| Person  | xLink (X/Twitter)              | LINKS    |
| Person  | city                           | TEXT     |

**Added (new generic Standard field)** — present in
Salesforce/HubSpot/Zoho, PDL-populatable:

| Object | Field | Type |
| ------- | ------------- |
-------------------------------------------------------- |
| Company | annualRevenue | CURRENCY (generic total revenue; replaces
the niche ARR) |

### Behavior by workspace

* **New workspaces:** demoted fields are gone; `annualRevenue` is
**active**.
* **Existing workspaces:** demoted fields are **preserved as active
custom fields, data intact**;
`annualRevenue` is created **inactive (opt-in)** with its column ready,
so a later activation
  is a metadata-only toggle.

### Upgrade commands (v2.9)

Three idempotent, per-workspace commands, run in timestamp order:

1. **`upgrade:2-9:move-demoted-standard-fields-to-custom-application`**
(1799000040000) —
re-owns the 6 demoted fields to the workspace custom application
(`isCustom = true`,
new `applicationId` + fresh `universalIdentifier`), keeping their data
and active state.
2. **`upgrade:2-9:rename-conflicting-custom-fields`** (1799000045000) —
if a workspace already
has a *custom* field named `annualRevenue`, renames it to
`annualRevenueCustom`
(data preserved via column rename) so the standard field can be added.
Skips non-custom matches.
3. **`upgrade:2-9:add-inactive-generic-standard-fields`**
(1799000050000) — creates
`Company.annualRevenue` on existing workspaces as inactive, guarded to
skip workspaces
   missing the target object or where the name is still taken.

**Failure model:** the workspace iterator isolates failures per
workspace (one workspace failing
never affects others); within a workspace the runner records per-command
status and resumes on the
next run, and every command is idempotent, so partial runs self-heal.

### Supporting changes

* **Field-option color palette:** widened the `TagColor` union
(`twenty-shared` `FieldMetadataOptions`
+ the field-metadata `options.input` DTO) from 10 colors to the full
theme palette, benefiting any
  future SELECT/MULTI_SELECT field.
* **Dev seeder:**
* The default "Annual Recurring Revenue" dashboard widget now points at
the generic
    `annualRevenue` field (renamed to "Annual Revenue").
* Removed the "Companies by Size (Stacked by City)" widget (relied on
the demoted `employees`).
* `employees` is dropped from company data seeds and re-added as a
**custom** field seed, so dev
workspaces still get an `employees` column matching the demoted
behavior.

### Cleanup

Front-end record types (`Company.ts`/`Person.ts`), the
`getDisplayNameFromParticipant` test mock,
metadata integration specs, the Zapier `crud_record` test, and the
regenerated
`get-standard-object-metadata-related-entity-ids` snapshot.

## ⚠️ Breaking change (intentional)

Removes standard fields `Company.annualRecurringRevenue`,
`Company.employees`,
`Company.idealCustomerProfile`, `Company.xLink`, `Person.xLink`, and
`Person.city` from the core
GraphQL schema (replaced by `Company.annualRevenue`).

This is why the breaking-changes check reports a large number of
removals — `graphql-inspector`
flags any removed object field plus its derived
aggregate/order-by/filter/update types.

**Mitigation:** the
`upgrade:2-9:move-demoted-standard-fields-to-custom-application` command
re-owns these fields as custom fields per workspace, preserving their
name and data, so existing
tenants keep working. New workspaces won't have them.
2026-06-04 15:54:04 +00:00
Paul Rastoin 6a908b7876 Front component s3 redirect (#21116)
# Introduction
Unload the server of the file stream when possible
Also fix inconsistent pipeline exception management

Needs to highly be QA, not sure how the cors will behave here
2026-06-02 09:35:58 +00:00
martmull fbae66de8a Fix error when token invalid (#20972)
## Before

<img width="914" height="519" alt="image"
src="https://github.com/user-attachments/assets/8933bf9e-d8db-4670-ad08-69e900083fc1"
/>

## After

<img width="1105" height="523" alt="image"
src="https://github.com/user-attachments/assets/d8638dbb-adee-4b7d-9d29-1a2a0185ab4f"
/>
2026-05-27 16:30:14 +00:00
nitin 068d365731 feat(sdk): error on incompatible view filter operand at sync time (#20763)
view filters with mismatched operand + field type now error at sync --
was silently failing before
2026-05-22 09:01:40 +00:00
Weiko 27fd124c2e Dedicated REST controllers for object & field metadata (#20364)
## Summary
- Replace the dynamic `RestApiMetadataController` (which parsed
`/rest/metadata/*path` and proxied to internal GraphQL) with two
dedicated controllers: `ObjectMetadataController` and
  `FieldMetadataController`.
- Drop the GraphQL hop: reads hit Postgres directly via TypeORM
repositories; writes call the existing
`{create,update,delete}One{Object,Field}` service methods.
- Introduce a new clean response shape behind a workspace feature flag
(`IS_REST_METADATA_API_NEW_FORMAT_DIRECT`) — see grace period below.
- Update the OpenAPI spec so the REST playground reflects the (default)
legacy shape during the grace period.

  ## Why
The legacy metadata controller was over-complex: it routed every method
through a path parser, a set of GraphQL query-builder factories, an
internal GraphQL call, and a
`cleanGraphQLResponse` post-processor. Operation names from GraphQL
(`createOneObject`, `updateOneField`, …) leaked straight into REST
responses. The internal-GraphQL hop also gave us
nothing on metadata reads — pagination, filtering, and serialization all
happen against the same Postgres tables either way.

  ## Feature flag & grace period
  `IS_REST_METADATA_API_NEW_FORMAT_DIRECT` (workspace-scoped):
- **Existing workspaces:** flag absent → resolves to `false` → **legacy
response shape** (no behavior change).
- **Newly created workspaces:** flag seeded to `true` via
`DEFAULT_FEATURE_FLAGS` → **new response shape** from day one.
- **Toggle:** support-assisted (no frontend); customers contact us to
opt into the new shape early.
- **Removal:** the flag, the legacy adapter utils
(`to-legacy-{object,field}-metadata-response.util.ts`), and the
parametrized test wrapper get deleted after the grace window. New shape
becomes the only shape; OpenAPI flips to new shape; POST loses the
conditional and reverts to a declarative response.

  ## Response shapes

| Operation | Legacy (flag OFF, default for existing) | New (flag ON) |
|-----------|-----------------------------------------|---------------|
| `GET /rest/metadata/objects` | `{ data: { objects: [...] }, pageInfo,
totalCount }` | `{ data: [...], pageInfo, totalCount }` |
| `GET /rest/metadata/objects/:id` | `{ data: { object: {...} } }` | `{
... }` |
| `POST /rest/metadata/objects` | `201 { data: { createOneObject: {...}
} }` | `201 { ... }` |
| `PATCH/PUT /rest/metadata/objects/:id` | `{ data: { updateOneObject:
{...} } }` | `{ ... }` |
| `DELETE /rest/metadata/objects/:id` | `{ data: { deleteOneObject: {
... } } }` | `{ ... }` |

Same matrix for `/rest/metadata/fields`. Cursor params
(`starting_after`, `ending_before`, `limit`) and `totalCount` are
preserved across both shapes. POST returns `201` in both (old
  controller already did — the doc on main saying `200` was wrong).

  ## Implementation notes
- Reads go straight to Postgres with TypeORM cursor pagination
(`paginateByIdCursor` util, mutually-exclusive `starting_after` /
`ending_before`). No cache on this path — caching +
  filterable pagination didn't combine cleanly.
- Object endpoints inline `fields[]` via a single follow-up `WHERE
objectMetadataId IN (...)` query.
- Controllers read the flag via `FeatureFlagService.isFeatureEnabled`
and conditionally pass the result through a legacy-shape adapter util
before returning.
- Per-domain REST exception filters
(`{Object,Field}MetadataRestApiExceptionFilter`); the `exceptionCode →
httpStatus` switch is extracted to a util so it can be merged with the
  existing GraphQL handler later.
- New controllers live inside the metadata domain modules
(`metadata-modules/{object,field}-metadata/controllers/`) to match
existing precedent (view-field, view, page-layout, …).
- Removes: `RestApiMetadataController`, `RestApiMetadataService`,
`metadata/query-builder/`, `clean-graphql-response.utils.ts`.
- Integration tests are parametrized over both flag values via
`describe.each` — both shapes are asserted in CI.
- OpenAPI fixes inherited from the migration (kept as-is): documents
flat `fields: [...]` rather than the obsolete `{edges:{node:[...]}}`
wrapping; always emits `totalCount`; POST
status `201`. These match what customers actually receive on both
shapes.

Note: Next goal is to implement something similar for graphql and remove
nestjs-query dependency for those 2 entities, then generalise it.
Note2: We have the same issue with Core Rest API such as
```json
{
  "data": {
    "createCompany": {
      "id": "123e4567-e89b-12d3-a456-426614174000",
      "createdAt": "2026-05-07T12:14:52.769Z",
      "updatedAt": "2026-05-07T12:14:52.769Z",
      "deletedAt": "2026-05-07T12:14:52.769Z",
   ...
```
with "createCompany" here which is odd compared to REST standards (FYI
@etiennejouan @charlesBochet)

## Before (Without feature flag)
<img width="1346" height="712" alt="Screenshot 2026-05-12 at 20 50 38"
src="https://github.com/user-attachments/assets/316ce225-1045-4aac-97a9-60fd537eb1ec"
/>
<img width="1378" height="729" alt="Screenshot 2026-05-12 at 20 52 24"
src="https://github.com/user-attachments/assets/a621ab6f-e4f8-44d5-817c-1efd25d33c30"
/>

## After (With feature flag)
<img width="1376" height="728" alt="Screenshot 2026-05-12 at 20 50 46"
src="https://github.com/user-attachments/assets/2424d9c5-e4ed-497c-8e5c-6b54d78675e4"
/>
<img width="1375" height="727" alt="Screenshot 2026-05-12 at 20 51 47"
src="https://github.com/user-attachments/assets/101d957f-38ed-45d9-ab7b-f4f4eb983397"
/>

---------

Co-authored-by: prastoin <paul@twenty.com>
2026-05-13 12:31:16 +00:00
Abdul Rahman e0563377b5 Fix unclear metadata validation errors (#20234)
https://github.com/user-attachments/assets/8f8f1122-3de1-4a9b-8bb4-a3c8d31e47ae

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Charles Bochet <charles@twenty.com>
2026-05-05 18:18:05 +00:00
Ratish jain 3290bf3ab1 fix(rest-api): prevent silent pagination failures and include valid options in enum validation errors (#20092)
# Summary 
(fixes #20044)

This PR implements two fixes for the REST API to enforce stricter
validation and provide better error messages.

Issue 1: Cursor parameter silently ignored
Problem: When users provided common cursor aliases (e.g., cursor, after,
before) instead of the correct parameter names (starting_after,
ending_before), the API silently ignored them and returned page 1 on
every request.
Solution: Added strict validation to detect common cursor aliases and
throw a clear error directing users to use the correct parameter names.
Files modified:
-
packages/twenty-server/src/engine/api/rest/input-request-parsers/rest-input-request-parser.exception.ts
-
packages/twenty-server/src/engine/api/rest/input-request-parsers/starting-after-parser-utils/parse-starting-after-rest-request.util.ts
-
packages/twenty-server/src/engine/api/rest/input-request-parsers/ending-before-parser-utils/parse-ending-before-rest-request.util.ts
- Test files for both parsers
Example error:
Invalid cursor parameter 'cursor'. Use 'starting_after' for pagination.
---
Issue 2: OpportunityStageEnum not validated on REST
Problem: When creating or updating opportunities via REST with an
invalid stage value, the API either silently dropped the value or
returned a generic error without listing valid options.
Solution: Updated the SELECT field validation to include valid options
in the error message.
Files modified:
-
packages/twenty-server/src/engine/api/common/common-args-processors/data-arg-processor/validator-utils/validate-rating-and-select-field-or-throw.util.ts
- Test file
Example error:
Invalid value "BAD_VALUE" for field "stage". Valid values are: NEW,
SCREENING, MEETING, PROPOSAL, CUSTOMER
---
### Testing
- Added 5 new test cases for `parse-starting-after-rest-request.util.ts`
- Added 6 new test cases for `parse-ending-before-rest-request.util.ts`
- Added 1 new test case for
`validate-rating-and-select-field-or-throw.util.ts`
- All 21 tests passing
---
Breaking Changes
None - correct usage is unaffected.

---------

Co-authored-by: Etienne <45695613+etiennejouan@users.noreply.github.com>
2026-04-28 14:31:18 +00:00
Charles Bochet 10c49a49c4 feat(sdk): support viewSorts in app manifests (#19881)
## Summary

Today the SDK lets apps declare `filters` on a view but not `sorts`, so
any view installed via an app manifest can never have a default
ordering. This PR adds declarative view sorts end-to-end: SDK manifest
type, `defineView` validation, CLI scaffold, and the application
install/sync pipeline that converts the manifest into the universal flat
entity used by workspace migrations. The persistence layer
(`ViewSortEntity`, resolvers, action handlers, builders…) already
existed server-side; the missing piece was the manifest → universal-flat
converter and the relation wiring on `view`.

## Changes

**`twenty-shared`**
- Add `ViewSortDirection` enum (`ASC` | `DESC`) and re-export it from
`twenty-shared/types`.
- Add `ViewSortManifest` type and an optional `sorts?:
ViewSortManifest[]` on `ViewManifest`, exported from
`twenty-shared/application`.

**`twenty-sdk`**
- Validate `sorts` entries in `defineView` (`universalIdentifier`,
`fieldMetadataUniversalIdentifier`, `direction` ∈ `ASC`/`DESC`).
- Add a commented `// sorts: [ ... ]` example to the CLI view scaffold
template + matching snapshot assertion.

**`twenty-server`**
- Re-export `ViewSortDirection` from `twenty-shared/types` in
`view-sort/enums/view-sort-direction.ts` (single source of truth,
backward compatible for existing imports).
- New converter `fromViewSortManifestToUniversalFlatViewSort` (+ unit
tests for `ASC` and `DESC`).
- Wire the converter into
`computeApplicationManifestAllUniversalFlatEntityMaps` so
`viewManifest.sorts` are added to `flatViewSortMaps`, mirroring how
filters are processed.
- Replace the `// @ts-expect-error TODO migrate viewSort to v2 /
viewSorts: null` placeholder in `ALL_ONE_TO_MANY_METADATA_RELATIONS`
with the proper relation (`viewSortIds` /
`viewSortUniversalIdentifiers`).
- Update affected snapshots (`get-metadata-related-metadata-names`,
`all-universal-flat-entity-foreign-key-aggregator-properties`).

## Example usage

\`\`\`ts
defineView({
  name: 'All issues',
  objectUniversalIdentifier: 'issue',
  sorts: [
    {
      universalIdentifier: 'all-issues__sort-created-at',
      fieldMetadataUniversalIdentifier: 'createdAt',
      direction: 'DESC',
    },
  ],
});
\`\`\`
2026-04-20 14:31:06 +02:00
Charles Bochet b284c8323c Remove Favorite and FavoriteFolder from workspace schema (#19536)
## Summary

- Removes all workspace schema definitions for `Favorite` and
`FavoriteFolder` entities, which have been fully migrated to
`NavigationMenuItems`
- Deletes 26 standalone files including workspace entities, NestJS
modules, services, listeners, jobs, standard application builders (field
metadata, views, view fields, view field groups, indexes, page layouts),
mocks, and integration tests
- Cleans up ~40 modified files: removes `favorites` relation from 10
workspace entities and their field metadata utils, removes entries from
all builder maps, shared constants (`STANDARD_OBJECTS`,
`CoreObjectNameSingular`, `DEFAULT_RELATIONS_OBJECTS_STANDARD_IDS`), SDK
default relations, AI tool filtering, and standard object icons
2026-04-10 12:47:06 +02:00
Weiko 6f3a86c4a9 Fix insert conflict between field permission and RLS (#19244)
Insert operations blocked by field-level update permissions on
non-editable fields

The insert code path in permissions.utils.ts fell through to the update
case (no break), causing validateUpdateFieldPermissionOrThrow to reject
inserts when any field had "Edit disabled" which could conflict with RLS
predicates (used for insertion of new records)

Fixes https://github.com/twentyhq/twenty/issues/19201

We will keep checking update permissions for insertion (until we decide
to have a separate permission flag for insertion) but to fix the issue
we will skip this part if it conflicts with an RLS predicate
2026-04-02 14:01:51 +00:00
Abdul Rahman 5efe69f8d3 Migrate field permission to syncable entity (#18751)
Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>
Co-authored-by: Charles Bochet <charles@twenty.com>
2026-03-27 14:47:27 +01:00
Charles Bochet 5c745059ad refactor: remove "core" naming from views and eliminate converter layer (#18667)
## Summary

- **Remove all "core" prefixes** from the views system — the
metadata-based storage migration is complete, so `CoreView`,
`coreViewsSelector`, `getCoreViews`, etc. are now just `View`,
`viewsSelector`, `getViews`
- **Eliminate the entire converter layer** (15 files, ~850 lines
deleted) — `convertCoreViewToView` and all sub-converters were either
no-ops or trivially adding `__typename` / mapping identical enum values.
Local enums now re-export from generated GraphQL types directly (single
source of truth)
- **Unify `View` and `ViewWithRelations`** into one type —
`ViewWithRelations` is now a type alias for `View`, selectors return
data directly without conversion

### Backend
- Rename `@ObjectType('CoreView')` → `@ObjectType('View')` (and all
sub-entities)
- Rename resolver methods: `getCoreViews` → `getViews`, `createCoreView`
→ `createView`, etc.
- Rename `FIND_ALL_CORE_VIEWS_GRAPHQL_OPERATION` →
`FIND_ALL_VIEWS_GRAPHQL_OPERATION`

### Frontend
- Delete 15 converter files (`convertGqlView*ToView*`,
`convertView*ToGql`, `convertViewWithRelationsToView`)
- Re-export `ViewType`, `ViewKey`, `ViewFilterGroupLogicalOperator` from
generated enums (no more duplicate enum definitions with different
casing)
- Replace `ViewOpenRecordInType` with `ViewOpenRecordIn` from generated
- Remove `__typename` from all local view sub-types
- Remove unused `variant` from `ViewFilter`, make `displayValue` and
`definition` optional
- Rename ~45 GraphQL query/mutation files and all selectors to drop
"core" prefix
- Delete unused `viewsWithRelationsSelector`
2026-03-16 09:57:18 +01:00
martmull 403db7ad3f Add default viewField when creating object (#18441)
as title
2026-03-06 14:59:31 +01:00
Thomas Trompette b4e924b671 Sync views and navigation items (#18003)
Both objects are necessary to fully enjoy objects within applications

<img width="770" height="311" alt="Capture d’écran 2026-02-17 à 15 19
43"
src="https://github.com/user-attachments/assets/48c51fa4-63f4-45b2-a40a-df73f3aa79be"
/>
2026-02-17 16:32:41 +01:00
Paul Rastoin 8c951d3623 Migrate Views-xxx Index Field Object Skill to be fully universal ( all actions and metadata runner and builder ) + all metadata update actions runner (#17687)
# What this PR does
Overall naming `universal` versus `flat` is not always the most updated
and so on
Will make a big cleaning tour after I've finished the whole migration
Migrating all `view` and ( filter fields etc ) `field` `object` `index`
to the universal pattern on all `services`, `builder` and `runner`
levels

## Universal and flat optimistic tooling

`addUniversalFlatEntityToUniversalFlatEntityAndRelatedEntityMapsThroughMutationOrThrow`
and its delete counterpart maintain the consistency of
`UniversalFlatEntityMaps` when an entity is created or removed. Beyond
inserting/removing the entity from its own maps, they walk through
`ALL_UNIVERSAL_METADATA_RELATIONS` to update the **aggregator arrays**
on related parent entities — the add appends the new entity's
`universalIdentifier` to the parent's aggregator (e.g. a new viewField's
identifier gets appended to its parent view's
`viewFieldUniversalIdentifiers`), and the delete filters it out. This
keeps the maps in sync so that diff computations and relation lookups
remain accurate throughout the migration building process.

## ALL_UNIVERSAL_METADATA_RELATIONS
`ALL_UNIVERSAL_METADATA_RELATIONS` is the universal counterpart of
`ALL_METADATA_RELATIONS`. It maps each metadata entity to its
many-to-one and one-to-many relations using universal foreign keys
(`*UniversalIdentifier`) instead of database IDs (`*Id`). This allows
migration actions to reference related entities in a workspace-agnostic
way. Relations that are workspace-specific (e.g. `workspace`,
`dataSource`, `userWorkspace`) are set to `null` and skipped during
resolution.

## `workspaceMigrationCreateIdEnrichment`
Reserved to API metadata ( will be able to validate at app installation
lvl )
- Workspace migration `create` actions now carry an optional `id` (and
`fieldIdByUniversalIdentifier` for object/field actions) so that
caller-provided IDs flow through the entire build-validate-run pipeline.
- New `enrichCreateWorkspaceMigrationActionsWithIds` utility resolves
`universalIdentifier → id` mappings after the builder runs and injects
them into the migration actions before the runner persists entities.
- Runner action handlers use the provided IDs instead of generating new
UUIDs, enabling deterministic entity creation for synchronization
workflows.


## `resolveUniversalUpdateRelationIdentifiersToIds`
`resolveUniversalUpdateRelationIdentifiersToIds` converts universal
identifiers (workspace-agnostic, stable keys) in a migration update
payload into concrete database UUIDs, so the update can be applied to a
specific workspace.

It iterates over the many-to-one relations defined in
`ALL_UNIVERSAL_METADATA_RELATIONS` for the given entity type, replaces
each `*UniversalIdentifier` property with its corresponding `*Id` by
looking up the target entity in `allFlatEntityMaps`, and throws if a
non-null identifier can't be resolved.

Used by all `update` action handlers in
`transpileUniversalActionToFlatAction`, avoiding duplicated resolution
logic across handlers.

## What this PR does not
- Migrating twenty-standard declaration to universal
- Migrating all the inputs transpilers to universal
- Migrating all metadata to be fully universal ( we still need to
de-scope the type of all of them and refactor their validator very close
)

---------

Co-authored-by: Charles Bochet <charles@twenty.com>
2026-02-09 19:02:38 +01:00
Weiko 96bc3594a3 Serve frontend components (#17798)
## Context
Ability to serve frontend component

See:
```typescript
curl -i 'http://localhost:3000/rest/front-components/35063b3f-bc4c-4358-8966-7762677802a3' \
--header 'Authorization: Bearer eyJhb...'
HTTP/1.1 200 OK
X-Powered-By: Express
Access-Control-Allow-Origin: *
Content-Type: application/javascript
Date: Mon, 09 Feb 2026 10:28:17 GMT
Connection: keep-alive
Keep-Alive: timeout=5
Transfer-Encoding: chunked

// react-globals:react/jsx-runtime
var jsx = globalThis.jsx;
var jsxs = globalThis.jsxs;
var Fragment = globalThis.React.Fragment;

// src/front-components/test.tsx
var RemoteComponents = globalThis.RemoteComponents;
var Component = () => {
  return /* @__PURE__ */ jsxs(RemoteComponents.HtmlDiv, { style: { padding: "20px", fontFamily: "sans-serif" }, children: [
    /* @__PURE__ */ jsx(RemoteComponents.HtmlH1, { children: "My new component!" }),
    /* @__PURE__ */ jsx(RemoteComponents.HtmlP, { children: "This is your front component: test" })
  ] });
};
var test_default = globalThis.jsx(Component, {});
export {
  test_default as default
};
//# sourceMappingURL=test.mjs.map
```

readFile_v2 returns a Node.js Stream object (Readable). Here we are
using stream pipeline which connects the readable stream (file) to the
writable stream (HTTP response) which efficiently streams the file
content directly to the HTTP response without loading the entire file
into memory. (in chunks, handling backpressure and closing the
connection when the file is fully sent)
2026-02-09 14:52:14 +00:00
Abdullah. 2c8d3f02e1 feat: upgrade to Storybook version 10 (#17110)
Upgraded to Storybook 10. We still use `@storybook/test-runner` for
testing since it appears it'd require more work to move from Jest to
Vitest than I initially anticipated, but I completed this PR to fix
`storybook:serve:dev` - it takes time to load, but it works the way it
used to with Storybook 8.


https://github.com/user-attachments/assets/7afc32c6-4bcf-4b37-b83b-8d00d28dda15
2026-01-13 08:18:07 +00:00
Paul Rastoin a6415db775 Refactor workspace migration and validation error types and centralize runner optimistic rendering (#16920)
# Introduction

In this PR we're:
- Refactoring the workspace migration action type introducing grain over
metadata and operation type ( for example operation `create` and
metadata `field` )
- Thanks to above point we can now factorize the runner optimistic
rendering out of each runner actions-handler file using the existing
into the generic one ( -3200 lines of code here )
- Still thanks to action type refactor we're able to dynamically compose
the response error type only send data when there's here. No more static
counter and static summary error message. This way we won't have to re
run snapshot every time we add a new entity to the engine ( huge
snapshot diff here )

## Noticeable points:
- We introduce an index update action to avoid any complex typing for
not having one or a tuple of actions instead. Now the drop and insert
logic is directly inferred from the update action handler instead of
being two action ( delete index and create index )

## TODO
- [x] Define base actions types
- [x] Migrate all actions to action type and metadata name pattern (
base actions )
- [x] Refactor flat entity validation type to embed metadata name
- [x] Refactor optimistic rendering within runner
- [x] Refactor legacy cache invalidation switch
- [x] Refactor response error format ( dynamic counter again + no empty
entries )
- [x] Try factorizing and removing redundant nor unused type declaration
in metadata actions type intermediary files
- [x] Adapt front to new response error format

## Remarks
- ~~Should create an issue for generic replace flat entity in related
flat entity maps~~ overkill
- Should create an issue for oneToMany foreignKey being nullable not
always cascade delete optimistic rendering edge case to either docs or
fix it in delete flat entity and related entity ( re-code the pg
cascading behavior )
- We could also factorize the builder to only implement validators and
not the intermediary file
2026-01-05 10:17:16 +01:00
Félix Malfait 21ff42074d feat: implement skills system for AI agents (#16865)
## Summary
This PR introduces a Skills system for AI agents, inspired by the [Agent
Skills specification](https://agentskills.io/specification).

## Changes

### Backend
- **SkillEntity**: New database entity with migration for storing skills
- **V2 Sync Mechanism**: Implemented FlatSkill, builders, validators,
and action handlers following the v2 flat entity pattern
- **Standard Skills**: Pre-defined skills (workflow-building,
data-manipulation, dashboard-building, metadata-building, research,
code-interpreter, xlsx, pdf, docx, pptx)
- **GraphQL API**: CRUD operations for skills with proper guards and
permissions
- **Workspace Cache**: Integrated skills into the workspace cache system

### Frontend  
- **Skills Table**: Searchable table in AI settings showing all skills
- **Skill Form**: Create/edit page with Label (primary), Description,
and Content (markdown editor)
- **API Name**: Following existing patterns, name is derived from label
with advanced settings toggle for custom API names
- **Standard vs Custom**: Standard skills are read-only, custom skills
can be edited/deleted

## Key Design Decisions
- Skills are stored in the database (Salesforce-like approach) rather
than files
- Name is derived from Label by default (isLabelSyncedWithName pattern)
- Skills reference functions/files via @ mentions in markdown content
rather than explicit relations
- Standard skills are synced from code, custom skills are created via UI

## Screenshots
Skills table and form UI follow existing settings patterns.

## Testing
- [x] Lint passes
- [x] Typecheck passes
- [ ] CI tests
2026-01-02 14:22:01 +00:00
Paul Rastoin 98a9ae2a0e Migrate view filter group to v2 (#16876)
## Introduction
On the side hanlded PR with a // agents on another repo
Made several iterations to fix behavior and direction
Find below auto-generated PR description

closes https://github.com/twentyhq/core-team-issues/issues/2037

Created generic tooling for entity circular dep checking, will be useful
for permissions validation too @Weiko


## Migrate `viewFilterGroup` entity to v2 flat architecture

### Summary
Migrates the `viewFilterGroup` entity from v1 to the v2 flat entity
architecture, following the established patterns for other v2 entities
like `viewFilter`, `view`, and `viewField`.

### Changes

**Types & Constants**
- Added `FlatViewFilterGroup` and `FlatViewFilterGroupMaps` types
- Added editable properties constant for `viewFilterGroup`
- Registered `viewFilterGroup` in `ALL_METADATA_NAME`,
`ALL_METADATA_RELATION_PROPERTIES`,
`ALL_METADATA_MANY_TO_ONE_RELATIONS`, and related constants

**Cache Service**
- Created `WorkspaceFlatViewFilterGroupMapCacheService` with proper
relation loading for `viewFilters` and `childViewFilterGroups`
- Updated `WorkspaceFlatViewMapCacheService` to load `viewFilterGroups`
relation

**Builder & Validator**
- Created `WorkspaceMigrationV2ViewFilterGroupActionsBuilderService`
- Created `FlatViewFilterGroupValidatorService` with creation, update,
and deletion validation
- Integrated validation into the orchestrator service (runs before
`viewFilter` validation)

**Action Handlers**
- Created create, update, and delete action handlers for
`viewFilterGroup`

**Service Migration**
- Rewrote `ViewFilterGroupService` to use v2 migration pattern with
`WorkspaceMigrationValidateBuildAndRunService`
- Created utility functions for transforming DTOs to flat entities

**Database Migration**
- Added migration to make `parentViewFilterGroupId` foreign key
deferrable (handles self-referential parent/child insertions)

**ViewFilter Integration**
- Added `viewFilterGroupId` validation in
`FlatViewFilterValidatorService`
- Updated `viewFilter` many-to-one relations to include
`viewFilterGroup`

**Tests**
- Added integration tests for successful creation, update, deletion, and
destruction
- Added failing test cases for non-existent entities and invalid
references
- Added failing test for `viewFilter` creation with non-existent
`viewFilterGroupId`

### Breaking Changes
None - existing API contracts are preserved.
2026-01-02 14:16:01 +01:00
Weiko 15b21570ae Row level permissions - POC 1 (#16599)
## Context
This PR adds the core structure for RLS implementation:
- RLS data model
- RLS service layer
- RLS WorkspaceMigration and Syncable Entity + cache + Validations
- RLS resolver layer
- ORM layer with RLS Predicate to ORM WHERE clause conversion with
workspaceMember record transposition

Tests are missing though

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> Establishes core row-level permissions infrastructure and enforcement
across the stack.
> 
> - Backend: new `rowLevelPermissionPredicate` and
`rowLevelPermissionPredicateGroup` entities, TypeORM migration, feature
flag `IS_ROW_LEVEL_PERMISSION_PREDICATES_ENABLED`, flat-entity
maps/cache wiring, services and GraphQL resolvers for CRUD, and
inclusion of `workspaceMember` in auth context
> - ORM: applies row-level permission predicates to SELECT, DELETE, and
SOFT DELETE query builders; propagates context through
GlobalWorkspaceOrmManager/EntityManager
> - GraphQL: generated schema/types/queries/mutations for
creating/updating/deleting/fetching predicates and groups
> - Frontend: settings page adds a gated "Record-level" section
(placeholder) and metadata error handler labels for new entities
> 
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
fe955cc4588a92157afa6795fb574189a4be1e93. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->

---------

Co-authored-by: Félix Malfait <felix.malfait@gmail.com>
2025-12-31 16:15:17 +01:00
Raphaël Bosi f48adb5f07 Migrate page layout services (#16443)
Last step of the page layout migration:
- Migrate services
- Write integration tests
2025-12-10 13:49:44 +00:00
Raphaël Bosi 5fb7e76005 Migrate page layout to v2 (#16364) 2025-12-08 14:11:24 +00:00
Marie 7ce22d5c7e breaking (soft) - Migrate viewGroup.fieldMetadataId -> view.mainGroupByFieldMetadataId (2/3) (#16277)
Should be merged once https://github.com/twentyhq/twenty/pull/16206 has
been released + command run to prod

In this PR
- Remove usage of viewGroup.fieldMetadataId, both in BE and FE states. 
- But we still need to properly populate it until we fully remove
viewGroup.fieldMetadataId from db and ORM entity (upcoming 3rd PR out of
3). fieldMetadataId was removed from CoreViewGroup type and
CreateViewGroupInput and is determined BE-side based on the associated
view's mainGroupByFieldMetadataId. **I expect this means a downtime on
viewGroup creation, until both FE and BE are deployed and cache is
flushed.** This seems acceptable to me as it only regards viewGroup
creation.
    - this information is replaced by view.mainGroupByFieldMetadataID
- Handle view group creation, update and deletion in the BE as a
side-effect of a view creation, update or deletion. Optimistic effects
are still used
- Add validation at view creation or update regarding
mainGroupByFieldMetadata

Left to do in 3rd PR
- Remove viewGroup.fieldMetadataId from db and ORM entity
- Restore feature allowing to update an existing grouped view's group by
field (already OK on BE side but need to rebuild FE optimistic)
2025-12-05 14:00:03 +00:00
Raphaël Bosi 077be7644c Migrate page layout widget to v2 of the API (#16323) 2025-12-05 13:04:06 +00:00
Paul Rastoin b2d785de4b Page layout tab v2 (#16319)
# Introduction
Migrating `pageLayoutTab` to the v2 engine
- Types and constants
- Builder and validate
- Runner

Introduced a new `StrictSyncableEntity` that enforces that
`universalIdentifier` and `applicationId` are defined
As these entities are brand new we could enforce this rule already
This still requires a migration command to associate the existing
entities to custom workspace application instance and define
universalIdentifier

Handled retro-comp of the migration through a migration as upgrade
command fallback

---------

Co-authored-by: bosiraphael <raphael.bosi@gmail.com>
2025-12-04 17:37:16 +01:00
Etienne d1befa7e35 Query complexity validation (#16274)
Validations : 
- relations count (in common api)
- oneToMany relation nested count (in common)
- requested fields count (in gql)
- root resolver count (in gql)
- root resolver duplicates (in gql)
- specific complexity for metadata / nesting count (in gql)
2025-12-04 16:33:08 +01:00
Paul Rastoin 59672e3e34 Migrate agent v2 (#16214)
# Introduction
Closes https://github.com/twentyhq/core-team-issues/issues/1980

In this PR we migrate the agent from v1 to v2.

## New FlatRoleTargetByAgentIdMaps
Derivated the `flatRoleTargetMaps` to be building a
`flatRoleTargetByAgentIdMaps` to ease retrieving a roleId to associate
to an agent

## Coverage
Added strong coverage on both failing and successful CRU agents
operations

---------

Co-authored-by: Weiko <corentin@twenty.com>
2025-12-03 11:51:19 +01:00
Paul Rastoin ea3c5d2d45 Migrate role and role target to v2 (#16009)
# Introduction
close https://github.com/twentyhq/core-team-issues/issues/1930
close https://github.com/twentyhq/core-team-issues/issues/1929
Migrating role and roleTarget entities to the v2 core engine, allowing
v2 caching leverage and allow migrating agent to v2 that needs role
target in prior
After agent we should be able to pass twenty standard app totally though
workspace migration

## Role target assignation
Please note that role target have 3 creation entrypoints:
- Agent
- User workspace
- ApiKey

Refactored all 3 of them to pass through a new role-target.service.ts
that consumes the v2 under the hood.

---------

Co-authored-by: Weiko <corentin@twenty.com>
2025-11-28 17:06:11 +00:00
Etienne 76cc07a5f7 Revert x2 - Common - Field validation (#15491) (#15879)
Revert https://github.com/twentyhq/twenty/pull/15821

Should not have been reverted. Conflicting with this
[PR](https://github.com/twentyhq/twenty/pull/15742),
[reverted](https://github.com/twentyhq/twenty/pull/15875)

Co-authored-by: Weiko <corentin@twenty.com>
2025-11-18 12:04:00 +01:00
martmull 867422627c Revert "Common - Field validation (#15491)" (#15821)
This reverts commit 261cba40ef.
2025-11-14 13:47:22 +01:00
Raphaël Bosi 56b3a0e8a5 Fix REST API Delete and Destroy (#15806)
Fixes https://github.com/twentyhq/twenty/issues/15801
2025-11-13 18:14:32 +01:00
Etienne 261cba40ef Common - Field validation (#15491)
Closes : https://github.com/twentyhq/core-team-issues/issues/1622

To do in other PR : 
- Add migration command for non nullable text, raw_json & array fields
- Add null transformation

---------

Co-authored-by: Weiko <corentin@twenty.com>
2025-11-13 15:45:35 +01:00
martmull 9880f192a5 Move composite types to twenty-shared (#15741) 2025-11-10 22:11:52 +01:00
Félix Malfait cff17db6cb Enhance role-check system with stricter checks (#15392)
## Overview

This PR strengthens our permission system by introducing more granular
role-based access control across the platform.

## Changes

### New Permissions Added
- **Applications** - Control who can install and manage applications
- **Layouts** - Control who can customize page layouts and UI structure
- **AI** - Control access to AI features and agents
- **Upload File** - Separate permission for file uploads
- **Download File** - Separate permission for file downloads (frontend
visibility)

### Security Enhancements
- Implemented whitelist-based validation for workspace field updates
- Added explicit permission guards to core entity resolvers
- Enhanced ESLint rule to enforce permission checks on all mutations
- Created `CustomPermissionGuard` and `NoPermissionGuard` for better
code documentation

### Affected Components
- Core entity resolvers: webhooks, files, domains, applications,
layouts, postgres credentials
- Workspace update mutations now use whitelist validation
- Settings UI updated with new permission controls

### Developer Experience
- ESLint now catches missing permission guards during development
- Explicit guard markers make permission requirements clear in code
review
- Comprehensive test coverage for new permission logic

## Testing
-  All TypeScript type checks pass
-  ESLint validation passes
-  New permission guards properly enforced
-  Frontend UI displays new permissions correctly

## Migration Notes
Existing workspaces will need to assign the new permissions to roles as
needed. By default, all new permissions are set to `false` for non-admin
roles.
2025-11-07 15:37:17 +01:00
Paul Rastoin 8dd43be9a2 Create many view groups (#15591)
# Introduction
Same as https://github.com/twentyhq/twenty/pull/15576 but for view
groups creation

When creating a kanban view with v2 flag activated in production result
in race condition due to request being slow and //.
That's why we're introducing a batch create on view group here
closing https://github.com/twentyhq/core-team-issues/issues/1847

## In v2
- batch create view group endpoint is available
- frontend will target the new endpoint

## In v1
- batch create view group endpoint is not available
- frontend will stick to old fake batch view group creation loop


## Gallery
### v2
<img width="1796" height="486" alt="image"
src="https://github.com/user-attachments/assets/932cfe9f-85f1-41cc-a1c4-72a4b5d5a256"
/>

### v1
<img width="1852" height="966" alt="image"
src="https://github.com/user-attachments/assets/8aa9df11-cdea-4b12-ae60-118cdf5e257b"
/>
2025-11-04 15:17:55 +01:00
Paul Rastoin c19a799973 Fix view rest api in v2 (#15398)
# Introduction
Initially wanted to reactive the test introduced in
https://github.com/twentyhq/twenty/pull/15393, that was failing because
of direct data source access removing all views ( even seeded one )
which was making the test fail

While doing so discovered a lot of issue with the rest API:
- Rest api wasn't consuming the v2 at all
- Rest api wasn't prepared to handle v2 exceptions
- Rest api did not handled unknown exceptions ( timeout )

Refactored the cleanup of each test to follow black box pattern and
avoid test leakage
2025-10-29 15:07:08 +01:00
Marie 473efb5dc5 [groupBy] Order by within records (#15404)
Closes https://github.com/twentyhq/core-team-issues/issues/1727

<img width="1415" height="625" alt="image"
src="https://github.com/user-attachments/assets/cd132582-4a95-463c-8946-87b77670e023"
/>
2025-10-29 14:49:10 +01:00
Etienne 0849483d2e Common - Remove feature flag (#15371)
To be done in an other PR, move graphql-query-runner handlers in
common-api-query-runner folder (+ renaming + typing improvments)


Fixes : 
- totalCount on findMany (Rest)
- getAllSelectedFields did not handle composite field (Rest)
- issue with endCursor (Rest)
- args processing for updateOne and createOne (Rest + Gql)
- fix findDuplicates (Gql)
2025-10-29 10:01:52 +00:00
Etienne ee6b86b1a8 Common - many fixes (#15395) 2025-10-28 13:33:41 +01:00
Paul Rastoin 0780380fe5 Fix composite field update v2 (#15290)
# Introduction
Fixing composite field update by computing field column type for each of
its properties instead of globally

## Coverage
Added integration tests for each composite field on both successful
`create` and `update`
```ts
Test Suites: 17 passed, 17 total
Tests:       104 passed, 104 total
Snapshots:   14 passed, 14 total
Time:        135.431 s, estimated 143 s
```

## Conlusion
Related to https://github.com/twentyhq/core-team-issues/issues/1753
2025-10-23 16:26:46 +02:00
Etienne 245b0a014a Fix integration test (#15232) 2025-10-21 17:44:25 +02:00
Paul Rastoin 7d747c9876 [REQUIRES_CACHE_FLUSH][GQL_VIEW_GROUP_API_BREAKING_CHANGE] ViewGroup in v2 (#15052)
# Introduction
Adding view-group to core engine v2
Following https://github.com/twentyhq/twenty/pull/15010 ( same pattern )

## What's done
- Created flat-view-group
- flat view group runner
- flat view group builder
- create view group service v2 and input transpilers
- refactor the existing view group resolver to fix standard (
BREAKING_CHANGE on graphql api update especially ) REST stays the same
- refactored the front to consume the mutations autogenerated

close https://github.com/twentyhq/core-team-issues/issues/1665
2025-10-14 15:55:50 +02:00
nitin 44f97058ac Tidy up validation for configuration (#14939)
closes https://github.com/twentyhq/core-team-issues/issues/1605

TODO:  
~~- add stories~~
~~- add base graph on companies when creating a new graph widget~~
2025-10-09 20:34:51 +00:00
Paul Rastoin 59fbe35a8c Move view in metadata-modules/ and create atomic folder + module for each view entity (#14990)
# Introduction
Preparing view-filter and view-group introduction in v2 core engine
Moving view from `core-modules` to `metadata-modules`

## What happened

### Created dedicated modules for each view entity:
- ViewFieldModule
- ViewFilterModule
- ViewFilterGroupModule
- ViewGroupModule
- ViewSortModule

### Each module is now completely independent with its own:
- Controller
- Resolver
- Service
- Entity

### Created dedicated abstraction metadata module folder for:
- flat-view-field
- flat-view

### Dependencies 
- Eleminated circular dep on ViewModule to all others ones
- Granular import not importing the whole viewModule anymore everywhere


close https://github.com/twentyhq/core-team-issues/issues/1703
2025-10-09 15:18:15 +02:00
Etienne c693c4d9cf Common API - create common find one query (#14720)
closes https://github.com/twentyhq/core-team-issues/issues/1418

Tested : 
- findOne on Rest and Gql

### Vision
#### Common
- Common is kind of renamed Gql base resolver
- Common handles args (filter, values, ...) validation
- Common accepts depth or raw gql selected fields to compute
selectedFields
- Common is directly called by each CommonQueries (findOne, ...)
service, which extend CommonBaseQuery service
- Common sequence : 
| - Parse & Validate args (args-handlers, to create)
| - Build query (query-parsers : currently in gql-query-parsers, to
move)
| - Execute query
| - Fetch relation + format
#### Rest
- Simple parsing (without metadata validation)
- Calling Common API
- Simple rest response formatting
#### Gql
- Calling Common API
2025-10-08 10:18:42 +00:00
Marie 3927b36406 [View migration] Clean ViewFilterOperand (#14785)
In the BE we have stored filter operand as IS_EMPTY, IS_NOT_EMPTY, etc. 
For some reason in the FE we were manipulating IsEmpty, IsNotEmpty, etc.
(maybe because they were used before in Views before they were moved to
core)

So we were converting the operands in the FE from IS to Is
(convertViewFilterOperandFromCore) to read and manipulate viewFilters,
and then back to BE version to send mutations etc., from Is to IS
(convertViewFilterOperandToCore).
The migration is now over, so we can remove and simplify that code. 
(In the 1-5:migrate-views-to-core command we still do the migration from
Is format to IS format.)
2025-09-30 17:12:03 +02:00
nitin ea144c434b Add configuration types for widgets (#14717)
closes https://github.com/twentyhq/core-team-issues/issues/1532
2025-09-29 11:41:26 +00:00