Commit Graph
633 Commits
Author SHA1 Message Date
martmullandGitHub 445b76fa26 Add uninstall button to application setting (#15988)
As title

<img width="878" height="668" alt="image"
src="https://github.com/user-attachments/assets/b0c9ae1e-036f-4bdd-9bd2-a2a37c2e3b99"
/>
2025-11-21 14:45:07 +00:00
Félix MalfaitandGitHub a281f2a773 feat: add configurable response format for AI agents (text/JSON) (#15953)
## Summary
This PR adds configurable response format support for AI agents,
allowing them to return either plain text or structured JSON data based
on a defined schema.

## Key Features

### 1. Agent Response Format Configuration
- Added `AgentResponseFormat` type supporting:
  - `text`: Returns plain text responses (default)
  - `json`: Returns structured JSON based on defined schema
- New `AgentResponseSchema` type moved to `twenty-shared/ai` for sharing
between frontend/backend

### 2. Settings UI
- New `SettingsAgentResponseFormat` component for configuring response
format
- Visual schema builder for defining JSON output structure
- Real-time validation and preview
- Integrated into agent settings tab

### 3. Workflow Integration
- AI Agent workflow action automatically uses agent's configured
response format
- Output schema dynamically generated from agent's response format
- Workflow variable picker shows structured fields for JSON responses
- Backward compatible with existing text-only agents

### 4. Backend Implementation
- Added `convertAgentSchemaToZod` utility to validate JSON responses
- Agent executor service handles both text and JSON generation
- Automatic agent creation/cloning when adding AI agent steps to
workflows
- Unique agent naming with conflict resolution

### 5. Database Migration
- Migration `1763622159656-update-agent-response-format.ts` 
- Sets default `responseFormat` to `{"type":"text"}` for existing agents
- Updated all standard agents with proper response format

## Changes by Module

### Frontend (`twenty-front`)
- 🆕 `AgentResponseFormat` type
- 🆕 `SettingsAgentResponseFormat` component
- ✏️ Updated `WorkflowEditActionAiAgent` to support response format
configuration
- 🗑️ Removed deprecated `useAiAgentOutputSchema` hook and
`AiAgentOutputSchema` type

### Backend (`twenty-server`)
- 🆕 `AgentResponseFormat` type in agent entity
- 🆕 `convertAgentSchemaToZod` utility for schema validation
- ✏️ Updated `AiAgentExecutorService` to handle both text and JSON
generation
- ✏️ Updated `WorkflowSchemaWorkspaceService` to generate output schema
from agent config
- ✏️ Enhanced `WorkflowVersionStepOperationsWorkspaceService` with agent
creation/cloning
- 🆕 Agent naming constants for conflict resolution

### Shared (`twenty-shared`)
- 🆕 `AgentResponseSchema` type
- 🆕 `ModelConfiguration` type moved to shared package
- Updated exports in `ai/index.ts`

## Code Quality
- Removed useless comments following code style guidelines
- All linter checks passed
- Type-safe implementation with proper TypeScript types

## Testing
-  Database migration tested
-  Agent creation/cloning in workflows verified
-  Response format switching (text ↔ JSON) validated
-  Backward compatibility with existing agents confirmed

## Migration Notes
- Existing agents will have `responseFormat: {type: 'text'}` set
automatically
- No breaking changes - all existing functionality preserved
- Agents can be updated to use JSON format through settings UI
2025-11-20 18:32:44 +01:00
martmullandGitHub 3190ca5b9e 1858 extensibility create relation metadata decorator in thwenty sdkapplications (#15907)
as title

First PR 
I will update the twenty-cli in another PR
2025-11-20 10:22:42 +01:00
Paul RastoinandGitHub a39efeb1ab [BREAKING_CHANGE/GRAPHQL/OBJECT_METADATA_CREATE_ONE] Remove object/fields/view-fields v1 implementation (#15823)
# Introduction
Remove the v2 feature flag for view-field field-metadata and
object-metadata metadata entities

## Some details
- Disabled nestjs-query for object metadata creation and explicitly
calling it
- removed all v1 integration tests files

## Remarks
Not remove v2 referencing in both filenaming right now will handle that
globally later

## Breaking change
Due to object metadata resolver createOne standardization had to rename
the input from `CreateObjectInput` to `CreateOneObjectInput`
2025-11-17 10:55:19 +01:00
Félix MalfaitandGitHub 5dfb66917c Upgrade NestJS from 10.x to 11.x (#15836)
## Overview
This PR upgrades all NestJS dependencies from version 10.x to 11.x,
following the [official migration
guide](https://docs.nestjs.com/migration-guide). This builds on top of
the v9 to v10 upgrade completed in PR #15835.

## Changes

### Dependencies Updated
**Core packages (10.x → 11.x):**
- `@nestjs/common`: 10.4.16 → 11.0.8
- `@nestjs/core`: 10.4.16 → 11.0.8
- `@nestjs/platform-express`: 10.4.16 → 11.0.8
- `@nestjs/config`: 3.2.3 → 3.3.0
- `@nestjs/passport`: 10.0.3 → 11.0.0
- `@nestjs/axios`: 3.0.2 → 3.1.2
- `@nestjs/schedule`: ^3.0.0 → ^4.1.1
- `@nestjs/serve-static`: 4.0.2 → 5.0.1
- `@nestjs/cache-manager`: ^2.2.1 → ^2.3.0
- `@nestjs/jwt`: 10.2.0 → 11.0.0
- `@nestjs/typeorm`: 10.0.2 → 11.0.0
- `@nestjs/terminus`: 11.0.0 (already on v11)
- `@nestjs/event-emitter`: 2.1.0 (compatible)

**DevDependencies:**
- `@nestjs/testing`: ^10.4.16 → ^11.0.8
- `@nestjs/schematics`: ^10.1.0 → ^11.0.2
- `@nestjs/cli`: 10.3.0 → 11.0.0

### Code Changes
**Fixed: TwentyConfigModule conditional imports**
- Updated `TwentyConfigModule.forRoot()` to use spread operator for
conditional imports
- Fixes TypeScript error with NestJS 11's stricter DynamicModule type
checking

**Cleanup: Removed unused package**
- Removed `@revertdotdev/revert-react` (not being used anywhere in the
codebase)

## Breaking Changes Addressed

### 1.  Reflector Type Inference
- **Impact**: None - codebase only uses `reflector.get()` method
- **Analysis**: Does not use `getAllAndMerge()` or `getAllAndOverride()`
(the methods with breaking changes)
- **Files reviewed**: feature-flag.guard.ts,
message-queue-metadata.accessor.ts,
workspace-query-hook-metadata.accessor.ts

### 2.  Lifecycle Hooks Execution Order
- **Change**: Termination hooks (`OnModuleDestroy`,
`BeforeApplicationShutdown`, `OnApplicationShutdown`) now execute in
REVERSE order
- **Analysis**: Reviewed all lifecycle hook implementations
  - Redis client cleanup
  - Database connection cleanup (GlobalWorkspaceDataSource)
  - BullMQ queue/worker cleanup
  - Cache storage cleanup
- **Result**: Dependency order is safe - services using connections
clean up before the connections themselves

### 3.  Middleware Registration Order
- **Change**: Global middleware now executes first regardless of import
order
- **Analysis**: Middleware is not registered as global, so execution
order remains consistent
- **Files reviewed**: app.module.ts, middleware.module.ts

## Testing

All tests passing and build successful:

**Unit Tests (283+ tests):**
-  Health module: 38 tests passed
-  Auth module: 115 tests passed (passport v11 integration)
-  REST API: 90 tests passed (middleware and express platform)
-  Feature flags: 17 tests passed (Reflector usage)
-  Workspace: 23 tests passed

**Build & Quality:**
-  Type checking: Passed
-  Linting: Passed
-  Build: 3,683 files compiled successfully

## Verification

Tested critical NestJS functionality:
-  Authentication & Security (JWT, OAuth, guards)
-  HTTP Platform (Express integration, REST endpoints)
-  Dependency Injection (Services, factories, providers)
-  Cache Management (Redis with @nestjs/cache-manager)
-  GraphQL (Query runners, resolvers)
-  Configuration (Environment config)
-  Scheduling (Cron jobs with @nestjs/schedule v4)
-  Lifecycle Hooks (Module initialization and cleanup)
-  Reflector (Metadata reflection in guards)

## Related PRs
- #15835 - Upgrade NestJS from 9.x to 10.x (completed)

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> Upgrades NestJS to v11 and updates routing patterns, auth strategies,
GraphQL schema options, and build/dist paths (scripts, Docker, Nx,
migrations, assets), plus enables Devtools in development.
> 
> - **Backend (NestJS 11 upgrade)**:
> - Bump `@nestjs/*` packages (core, platform-express, jwt, passport,
typeorm, serve-static, schedule, cli/testing/schematics) to v11.
> - Update REST/route-trigger/file controllers to new wildcard syntax
(`*path`).
> - Refactor OAuth (Google/Microsoft) and SAML strategies (abstract base
+ explicit `validate`); minor typings.
>   - Enable `DevtoolsModule` in development.
> - **GraphQL**:
> - Add `buildSchemaOptions.orphanedTypes` for client-config types; keep
Yoga/Sentry setup.
> - **Build/Runtime & Config**:
> - Standardize dist layout (remove `src` in paths): update scripts,
Docker `CMD`, Nx `project.json`, render scripts, TypeORM migration
paths, asset resolution.
> - Adjust `nest-cli.json` (watchOptions, asset globs, migrations
outDir, monorepo/root).
> - Improve config module imports (spread conditional); tsconfig
excludes `node_modules`.
>   - Minor Nx default: `start` target caching disabled.
> 
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
1139fd85a9. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
2025-11-16 18:20:06 +01:00
martmullandGitHub df58f4102e Fix typo in command (#15818)
as title
2025-11-14 10:42:26 +00:00
0cab2b49fc (breaking change) Allow users with a single workspace to update their email. (#15736)
- Users with a single workspace are allowed to update their email across
`core.user` and `workspace_xyz.workspaceMember`.
- The latter happens asynchronously (built it like this for non-blocking
with multiple workspaces), but since we restrict the email update
functionality to a single user, we can also update the email in
workspaceMember synchronously - I left asynchronous there to receive
feedback on whether we should move to synchronous or not.
- Merged main and resolved conflicts to ensure we use the
`SettingsPermissionGuard` and the updated `workspace.service.ts` code.

One edge-case that I was trying to communicate on Discord: 

Say that an admin is a member of multiple workspaces. Therefore, they
can allow roles with PROFILE_INFORMATION permission to update their
email.

<p align="center">
<img width="553" height="115" alt="image"
src="https://github.com/user-attachments/assets/80382b1f-a9e3-4dac-b606-c2defeb2c330"
/>
</p>

However, since the admin is part of multiple workspaces, he/she cannot
even update own email - the field stays disabled, leading to some
confusion.

<p align="center">
<img width="545" height="255" alt="image"
src="https://github.com/user-attachments/assets/5e6d27db-c9a8-4d5e-9ab6-65c77beae5b4"
/>
</p>

However, the workspace can have another member with admin role or some
other role that has PROFILE_INFORMATION permission flag. That user will
be and should be allowed to update email, so we cannot hide `email` from
dropdown options.

<p align="center">
<img width="585" height="283" alt="image"
src="https://github.com/user-attachments/assets/a670d3ac-cf48-4865-a425-b909093d8420"
/>
</p>

The behavior is fine imo, just a little confusing for members with more
than one workspace.

I have also tested the flow by signing up to YC workspace with my org
google account (twenty.com), then changing email to my personal address.
- After changing, I need to login using Google with my personal account
to access YC workspace again.
- If I login using Google with org google account (twenty.com), a new
user account is created.

This behavior is consistent with Notion and Linear. 

Finally, as for the verification of email, the user is asked to verify
email while they're logged in, but just in case they logout without
verifying, the next login would force them to verify their email in the
email/password flow.

However, for Social/SSO, they must verify before they logout or else
they'd have to contact support for assistance. I have not looked into
how to show verification screen while logging in via Social/SSO yet, but
if that's something critical for completeness here, I shall revisit it.

---------

Co-authored-by: Félix Malfait <felix@twenty.com>
2025-11-13 15:00:02 +01:00
de978960d0 [FIx] shouldBypassPermissionChecks for workspaceMember repository (#15706)
<!-- CURSOR_SUMMARY -->
> [!NOTE]
> Pass `shouldBypassPermissionChecks: true` to
`getRepositoryForWorkspace('workspaceMember')` in
`1-11-clean-orphaned-user-workspaces.command.ts` to ensure member lookup
during orphan cleanup ignores permissions.
> 
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
543fd9fc01. 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-11-11 17:17:42 +01:00
martmullandGitHub 9880f192a5 Move composite types to twenty-shared (#15741) 2025-11-10 22:11:52 +01:00
Paul RastoinandGitHub 7a1e699fc8 Twenty standard and workspace custom applications 1/3 (#15625)
# Introduction
related to https://github.com/twentyhq/core-team-issues/issues/1833
In this PR we're starting the sync-metadata and standardIds deprecation
by introducing `twenty-standard` application that will regroup every
standard object such as company and opportunities. But also the
`custom-workspace-application` which is an app created at the same time
as a workspace and that will regroup everything configure within the
workspace ( custom objects fields etc )

## What's done
On both new workspace and seeded workspace creation:
- Creating a custom workspace app
- Creating a twenty standard app
- Refactored the seed core schema and workspace creation to be run
within a transaction in order to handle circular dependency foreignkey
requirements ( which is deferred for app toward workspace )
- Updated workspace entity to have a custom workspace relation (
nullable for the moment until we implem an upgrade command to handle
retro comp )
- Integration testing on user, workspace creation deletion and expected
default apps creation
- ~~Soft deleted user on `deleteUser`~~ Done by marie and rebased on it

## What's next
- Update seeder to propagate the `twenty-standard` workspace
`applicationId` to every standard synchronized entities ( cheap and fast
iteration through the about to be deprecated sync-metadata as an easy
way to synchronize standards metadata entities ).
- Update seeder to propagate the `custom-workspace-application`
workspace `applicationId` to anything custom ( `pets` and `rockets` )
- Prepend `custom-workspace-application` `applicationId` to every
metadata API operations ( create a specific cache etc )
- Upgrade command on all existing workspace to create a custom app and
associate its applicationId to any existing custom entities
- Make `universalIdentifier` and `applicationId` required for any
syncable entity
2025-11-10 16:13:12 +01:00
Félix MalfaitandGitHub 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
MarieandGitHub 0992d8031b [Fix] fix command dry run (#15697) 2025-11-07 13:32:23 +01:00
MarieandGitHub 4ce93aee52 Fix user deletion flows (#15614)
**Before**
- any user with workpace_members permission was able to remove a user
from their workspace. This triggered the deletion of workspaceMember +
of userWorkspace, but did not delete the user (even if they had no
workspace left) nor the roleTarget (acts as junction between role and
userWorkspace) which was left with a userWorkspaceId pointing to
nothing. This is because roleTarget points to userWorkspaceId but the
foreign key constraint was not implemented
- any user could delete their own account. This triggered the deletion
of all their workspaceMembers, but not of their userWorkspace nor their
user nor the roleTarget --> we have orphaned userWorkspace, not
technically but product wise - a userWorkspace without a workspaceMember
does not make sense

So the problems are
- we have some roleTargets pointing to non-existing userWorkspaceId
(which caused https://github.com/twentyhq/twenty/issues/14608 )
- we have userWorkspaces that should not exist and that have no
workspaceMember counterpart
- it is not possible for a user to leave a workspace by themselves, they
can only leave all workspaces at once, except if they are being removed
from the workspace by another user

**Now**
- if a user has multiple workspaces, they are given the possibility to
leave one workspace while remaining in the others (we show two buttons:
Leave workspace and Delete account buttons). if a user has just one
workspace, they only see Delete account
- when a user leaves a workspace, we delete their workspaceMember,
userWorkspace and roleTarget. If they don't belong to any other
workspace we also soft-delete their user
- soft-deleted users get hard deleted after 30 days thanks to a cron
- we have two commands to clean the orphans roleTarget and userWorkspace
(TODO: query db to see how many must be run)

**Next**
- once the commands have been run, we can implement and introduce the
foreign key constraint on roleTarget


Fixes https://github.com/twentyhq/twenty/issues/14608
2025-11-06 18:29:12 +00:00
neo773andGitHub dc57f00e26 register relaunch channels cron (#15662) 2025-11-06 16:14:06 +01:00
GuillimandGitHub e64603e61a release 1.10 flush cache command (#15610) 2025-11-04 17:29:06 +01:00
Charles BochetandGitHub 7ff91a61c6 Add dashboard rollout commands (#15567)
## Tests

### makeSureDashboardNamingAvailableCommand

Case 1: no dashboard custom object
Case 2: with dashboard custom object

### SeedDashboardViewCommand

Case 1: no existing view
Case 2: with existing view
2025-11-03 18:35:57 +01:00
Paul RastoinandGitHub 17acfe1d2a [REQUIRED_FOR_1_10] Fix kanban foreign key migration (#15557)
# Introduction
We introduced a foreign key addition that will fail in production due to
orphan views targetting non existing fields

## Migration
The migration will be run for any new workspace successfully or any
twenty instance without corrupted data

## Upgrade command
The upgrade command will at some point allow the migration to be run
manually after removing any corrupted data

## Release note
We should remove the migration we've manually set as being run in
production
2025-11-03 15:08:35 +00:00
GuillimandGitHub 120cec9885 fix migration command - workflow runs (#15540)
fix migration command to enable the id addition in the fieldmetadata
options of workflow runs

Isues was on the workfluw rin (xurrently in produciton) if we filter by
status: clicking "Stopped" also selects "Stoppping" automatically.

<img width="735" height="436" alt="Screenshot 2025-11-03 at 12 26 40"
src="https://github.com/user-attachments/assets/20fd8b71-f7be-4115-acae-9b36f53e6d5f"
/>
2025-11-03 12:38:14 +01:00
Charles BochetandGitHub 50eb8c5558 Add command to make sure v1.8 workspaces are not using FULL or PARTIAL sync stages (that should be already deprecated) (#15545)
In v1.8, we have already run a command to deprecate FULL or PARTIAL sync
stages.

However the code was fully deprecated in v1.10 and some workspaces might
still have this status used. This is to double check
2025-11-03 12:36:46 +01:00
5b2950c43a Introduce SSO bypass permission. (#15417)
Closes [Core Issue
#1772](https://github.com/twentyhq/core-team-issues/issues/1772).

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> Introduces SSO bypass with a new permission flag and workspace-level
provider toggles, enabling permitted users to log in via
Google/Microsoft/Password when SSO-only, with backend enforcement and
frontend UI/hooks/queries.
> 
> - **Backend**:
> - **Permission & Enforcement**: Add `PermissionFlagType.SSO_BYPASS`;
update `AuthService` to allow login via non-SSO providers when workspace
bypass is enabled and user has `SSO_BYPASS`.
> - **Workspace Model**: Add `isGoogleAuthBypassEnabled`,
`isMicrosoftAuthBypassEnabled`, `isPasswordAuthBypassEnabled`
(migration, entity, update input, service validation).
> - **Public API**: Extend `PublicWorkspaceDataOutput` with
`authBypassProviders`; resolver computes it; permissions defaults
include `SSO_BYPASS`.
> - **Frontend**:
> - **GraphQL/State**: Generate new types/fields; add
`authBypassProviders` to `GetPublicWorkspaceDataByDomain`; new states
`workspaceAuthBypassProvidersState`, `workspaceBypassModeState`.
> - **Auth UI/Logic**: Add `useWorkspaceBypass`; update sign-in form and
footer to offer "Bypass SSO" and use merged providers when enabled;
remove auto-redirect when single SSO.
> - **Settings**: Add Security section to toggle bypass methods per
provider; conditionally show Change Password via `useCanChangePassword`.
> - **Tests/Mocks**: Update mocks and tests to include bypass
flags/providers.
> 
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
8c393b2bad. 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-11-03 11:40:09 +01:00
Baptiste DevessierandGitHub 5f0d24798a Support workflows in record page layouts (#15471)
https://github.com/user-attachments/assets/275a02a3-7054-45d1-9423-75bb5223a8ba
2025-10-31 18:45:58 +01:00
Paul RastoinandGitHub f6f52d676f Explicitly set workspaceId column as uuid type to ease pg LEFT JOIN (#15430)
Pg scan was redundant because historically the workspaceId was
`varchar`, even though below migration won't change that we had a look
to workspaceId col declaration across the codebase
2025-10-29 19:08:44 +01:00
martmullandGitHub a6cc80eedd 1751 extensibility twenty sdk v2 use twenty sdk to define a serverless function trigger (#15347)
This PR adds 2 columns handlerPath and handlerName in serverlessFunction
to locate the entrypoint of a serverless in a codebase

It adds the following decorators in twenty-sdk:
- ServerlessFunction
- DatabaseEventTrigger
- RouteTrigger
- CronTrigger
- ApplicationVariable

It still supports deprecated entity.manifest.jsonc 

Overall code needs to be cleaned a little bit, but it should work
properly so you can try to test if the DEVX fits your needs

See updates in hello-world application

```typescript
import axios from 'axios';
import {
  DatabaseEventTrigger,
  ServerlessFunction,
  RouteTrigger,
  CronTrigger,
  ApplicationVariable,
} from 'twenty-sdk';

@ApplicationVariable({
  universalIdentifier: 'dedc53eb-9c12-4fe2-ba86-4a2add19d305',
  key: 'TWENTY_API_KEY',
  description: 'Twenty API Key',
  isSecret: true,
})
@DatabaseEventTrigger({
  universalIdentifier: '203f1df3-4a82-4d06-a001-b8cf22a31156',
  eventName: 'person.created',
})
@RouteTrigger({
  universalIdentifier: 'c9f84c8d-b26d-40d1-95dd-4f834ae5a2c6',
  path: '/post-card/create',
  httpMethod: 'GET',
  isAuthRequired: false,
})
@CronTrigger({
  universalIdentifier: 'dd802808-0695-49e1-98c9-d5c9e2704ce2',
  pattern: '0 0 1 1 *', // Every year 1st of January
})
@ServerlessFunction({
  universalIdentifier: 'e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf',
})
class CreateNewPostCard {
  main = async (params: { recipient: string }): Promise<string> => {
    const { recipient } = params;

    const options = {
      method: 'POST',
      url: 'http://localhost:3000/rest/postCards',
      headers: {
        'Content-Type': 'application/json',
        Authorization: `Bearer ${process.env.TWENTY_API_KEY}`,
      },
      data: { name: recipient ?? 'Unknown' },
    };

    try {
      const { data } = await axios.request(options);

      console.log(`New post card to "${recipient}" created`);

      return data;
    } catch (error) {
      console.error(error);
      throw error;
    }
  };
}

export const createNewPostCardHandler = new CreateNewPostCard().main;

```


### [edit] V2 

After the v1 proposal, I see that using a class method to define the
serverless function handler is pretty confusing. Lets leave
serverlessFunction configuration decorators on the class, but move the
handler like before. Here is the v2 hello-world serverless function:

```typescript
import axios from 'axios';
import {
  DatabaseEventTrigger,
  ServerlessFunction,
  RouteTrigger,
  CronTrigger,
  ApplicationVariable,
} from 'twenty-sdk';

@ApplicationVariable({
  universalIdentifier: 'dedc53eb-9c12-4fe2-ba86-4a2add19d305',
  key: 'TWENTY_API_KEY',
  description: 'Twenty API Key',
  isSecret: true,
})
@DatabaseEventTrigger({
  universalIdentifier: '203f1df3-4a82-4d06-a001-b8cf22a31156',
  eventName: 'person.created',
})
@RouteTrigger({
  universalIdentifier: 'c9f84c8d-b26d-40d1-95dd-4f834ae5a2c6',
  path: '/post-card/create',
  httpMethod: 'GET',
  isAuthRequired: false,
})
@CronTrigger({
  universalIdentifier: 'dd802808-0695-49e1-98c9-d5c9e2704ce2',
  pattern: '0 0 1 1 *', // Every year 1st of January
})
@ServerlessFunction({
  universalIdentifier: 'e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf',
})
export class ServerlessFunctionDefinition {}

export const main = async (params: { recipient: string }): Promise<string> => {
  const { recipient } = params;

  const options = {
    method: 'POST',
    url: 'http://localhost:3000/rest/postCards',
    headers: {
      'Content-Type': 'application/json',
      Authorization: `Bearer ${process.env.TWENTY_API_KEY}`,
    },
    data: { name: recipient ?? 'Unknown' },
  };

  try {
    const { data } = await axios.request(options);

    console.log(`New post card to "${recipient}" created`);

    return data;
  } catch (error) {
    console.error(error);
    throw error;
  }
};

```


### [edit] V3

After the v2 proposal, we don't really like decorators on empty classes.
We decided to go with a Vercel approach with a config constant

```typescript
import axios from 'axios';
import { ServerlessFunctionConfig } from 'twenty-sdk';

export const main = async (params: { recipient: string }): Promise<string> => {
  const { recipient } = params;

  const options = {
    method: 'POST',
    url: 'http://localhost:3000/rest/postCards',
    headers: {
      'Content-Type': 'application/json',
      Authorization: `Bearer ${process.env.TWENTY_API_KEY}`,
    },
    data: { name: recipient ?? 'Unknown' },
  };

  try {
    const { data } = await axios.request(options);

    console.log(`New post card to "${recipient}" created`);

    return data;
  } catch (error) {
    console.error(error);
    throw error;
  }
};

export const config: ServerlessFunctionConfig = {
  universalIdentifier: 'e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf',
  routeTriggers: [
  {
    universalIdentifier: 'c9f84c8d-b26d-40d1-95dd-4f834ae5a2c6',
    path: '/post-card/create',
    httpMethod: 'GET',
    isAuthRequired: false,
  }
  ],
  cronTriggers: [
    {
      universalIdentifier: 'dd802808-0695-49e1-98c9-d5c9e2704ce2',
      pattern: '0 0 1 1 *', // Every year 1st of January
    }
  ],
  databaseEventTriggers: [
  {
    universalIdentifier: '203f1df3-4a82-4d06-a001-b8cf22a31156',
    eventName: 'person.created',
   }
  ]
}

```
2025-10-29 16:51:43 +00:00
Paul RastoinandGitHub 4dbc78285d Remove deprecated view and related workspace entities (#15393)
# Introduction
A while ago we migrated view from workspace to metadata
Their standard objects workspace entities declaration remained we can
now remove them

## Deprecating commands before 1.5
The view migration command from workspace to metadata was introduced in
`1.5.0`. Removing the `baseWorkspaceEntity` make this command obsolete.
If tomorrow twenty handles auto upgrade in latest and a user having an
instance in `1.3.0` starts auto-upgrading he won't be able to migrate
his views ( that's why we should not support upgrade before 1.5 anymore
here )
We will have the same use case with FavoritesFolders
2025-10-28 11:03:49 +00:00
Baptiste DevessierandGitHub 9b2a73d50a Support more standard objects in Record Page Layout (#15288)
Used the `PageLayoutRenderer` for people, opportunities, tasks and
notes.

## Demo


https://github.com/user-attachments/assets/35e61702-f338-47b3-91b7-fd3951a0c967
2025-10-24 17:38:49 +02:00
WeikoandGitHub 822b4d75a4 CleanOrphanedKanbanAggregateOperationFieldMetadataIdCommand (#15344)
## Context

We want to introduce a FK between view and fieldMetadata through
KanbanAggregateOperationFieldMetadataId so we need to clean up orphan
ones
2025-10-24 17:18:02 +02:00
Thomas TrompetteandGitHub f65c4e4be9 Missing workspace id in command (#15337)
As title
2025-10-24 15:01:24 +02:00
WeikoandGitHub a5708c7289 fix migrations (#15336) 2025-10-24 14:47:47 +02:00
c7c671f3e1 Introduce a command to regenerate search vectors for standard and custom objects to fix accent issue. (#15175)
Legacy workspaces still hold the old stored expression, which omits
public.unaccent_immutable, so their tsvectors remain accented and can’t
match the new, unaccented queries. Metadata sync doesn’t touch
asExpression, so only a targeted drop/recreate fixes the underlying
column.

In simpler words, the search vector should contain `mader` instead of
`mäder` for the search to work properly. Therefore, this command
regenerates the search vector across every object that uses
`SEARCH_FIELDS_FOR_*`.

Note that dashboard has a searchVector, but breaks the pattern of using
`SEARCH_FIELDS_FOR_DASHBOARD`. If you look at
packages/twenty-server/src/modules/dashboard/standard-objects/dashboard.workspace-entity.ts:116,
the searchVector field is hard-coded as

```
asExpression: `to_tsvector('english', title)`
```

Therefore, the following code snippet.

```
    const storedExpression = hasAsExpressionSetting(
      searchVectorFieldMetadata.settings,
    )
      ? searchVectorFieldMetadata.settings.asExpression
      : undefined;

    if (storedExpression) {
      return storedExpression;
    }

    return undefined;
```

It checks whether the searchVector field already carries its own
asExpression value in metadata. If the settings object includes that
string, it returns it so the upgrade can reuse the existing expression
for objects that aren’t in our predefined lists. If not, it returns
undefined, signaling there’s no stored expression to fall back on.

---------

Co-authored-by: Félix Malfait <felix.malfait@gmail.com>
Co-authored-by: Félix Malfait <felix@twenty.com>
2025-10-24 10:16:23 +00:00
Thomas TrompetteandGitHub 4effa5351c Allow to stop running workflow (#15270)
https://github.com/user-attachments/assets/599154e4-8743-471b-b05a-721b635bcf4e

On stoppage:
- if no running steps, mark pending as failed and end the workflow
- if running steps, set as stopping and exit. Going to the next step, as
the workflow is not running anymore, it will naturally stop
2025-10-23 13:01:15 +00:00
martmullandGitHub c395fed26a Set serverlessFunctionLayerId not nullable (#15272)
As title

Existing Null serverlessFunctionLayerIds have been filled with
`upgrade:1-8:fill-null-serverless-function-layer-id` command

See 0 such records in production

<img width="651" height="415" alt="image"
src="https://github.com/user-attachments/assets/9a5868fe-aa8e-4de0-b656-2e732560fd47"
/>
2025-10-23 09:58:52 +02:00
32558673c6 feat: Implement AI Router for Dynamic Agent Selection (#15227)
Adds intelligent routing system that automatically selects the best
agent for user queries based on conversation context.

### Changes:
- Added `routerModel` column to workspace table for configurable router
LLM selection
- Implemented `RouterService` with conversation history analysis and
agent matching logic
- Created router settings UI in AI Settings page with model dropdown
- Removed agent-specific thread associations - threads are now
agent-agnostic
- Added real-time routing status notification in chat UI with shimmer
effect
- Removed automatic default assistant agent creation
- Renamed GraphQL operations from agent-specific to generic (e.g.,
`agentChatThreads` → `chatThreads`)

---------

Co-authored-by: Félix Malfait <felix.malfait@gmail.com>
Co-authored-by: Félix Malfait <felix@twenty.com>
2025-10-22 15:02:41 +02:00
c5564d9bd0 [BREAKING CHANGE] refactor: Add Entity suffix to TypeORM entity classes (#15239)
## Summary

This PR refactors all TypeORM entity classes in the Twenty codebase to
include an 'Entity' suffix (e.g., User → UserEntity, Workspace →
WorkspaceEntity) to improve code clarity and follow TypeORM naming
conventions.

## Changes

### Entity Renaming
-  Renamed **57 core TypeORM entities** with 'Entity' suffix
-  Updated all related imports, decorators, and type references
-  Fixed Repository<T>, @InjectRepository(), and
TypeOrmModule.forFeature() patterns
-  Fixed @ManyToOne/@OneToMany/@OneToOne decorator references

### Backward Compatibility
-  Preserved GraphQL schema names using @ObjectType('OriginalName')
decorators
-  **No breaking changes** to GraphQL API
-  **No database migrations** required
-  File names unchanged (user.entity.ts remains as-is)

### Code Quality
-  Fixed **497 TypeScript errors** (82% reduction from 606 to 109)
-  **All linter checks passing**
-  Improved type safety across the codebase

## Entities Renamed

```
User → UserEntity
Workspace → WorkspaceEntity
ApiKey → ApiKeyEntity
AppToken → AppTokenEntity
UserWorkspace → UserWorkspaceEntity
Webhook → WebhookEntity
FeatureFlag → FeatureFlagEntity
ApprovedAccessDomain → ApprovedAccessDomainEntity
TwoFactorAuthenticationMethod → TwoFactorAuthenticationMethodEntity
WorkspaceSSOIdentityProvider → WorkspaceSSOIdentityProviderEntity
EmailingDomain → EmailingDomainEntity
KeyValuePair → KeyValuePairEntity
PublicDomain → PublicDomainEntity
PostgresCredentials → PostgresCredentialsEntity
...and 43 more entities
```

## Impact

### Files Changed
- **400 files** modified
- **2,575 insertions**, **2,191 deletions**

### Progress
-  **82% complete** (497/606 errors fixed)
- ⚠️ **109 TypeScript errors** remain (18% of original)

## Remaining Work

The 109 remaining TypeScript errors are primarily:

1. **Function signature mismatches** (~15 errors) - Test mocks with
incorrect parameter counts
2. **Entity type mismatches** (~25 errors) - UserEntity vs
UserWorkspaceEntity confusion
3. **Pre-existing issues** (~50 errors) - Null safety and DTO
compatibility (unrelated to refactoring)
4. **Import type issues** (~10 errors) - Entities imported with 'import
type' but used as values
5. **Minor decorator issues** (~9 errors) - onDelete property
configurations

These can be addressed in follow-up PRs without blocking this
refactoring.

## Testing Checklist

- [x] Linter passing
- [ ] Unit tests should be run (CI will verify)
- [ ] Integration tests should be run (CI will verify)
- [ ] Manual testing recommended for critical user flows

## Breaking Changes

**None** - This is a pure refactoring with full backward compatibility:
- GraphQL API unchanged (uses original entity names)
- Database schema unchanged
- External APIs unchanged

## Notes

- Created comprehensive `REFACTORING_STATUS.md` documenting the entire
process
- All temporary scripts have been cleaned up
- Branch: `refactor/add-entity-suffix-to-typeorm-entities`

## Reviewers

Please review especially:
- Entity renaming patterns
- GraphQL backward compatibility
- Any areas where entity types are confused (UserEntity vs
UserWorkspaceEntity)

---------

Co-authored-by: Charles Bochet <charles@twenty.com>
2025-10-22 09:55:20 +02:00
Paul RastoinandGitHub 45473218d3 Field deactivation side effect views calendar kanban viewFields (#15180)
# Introduction
Handling both:
- field deactivation side effect on view fields, view filters and views
- field deactivation side effect on view that targets it as
`kanbanAggregateFieldMetadataId`
- field deactivation side effect on view that targets it as
`calendarFieldMetadataId`

## Coverage
added coverage
```ts
 PASS  test/integration/metadata/suites/field-metadata/kanban-aggregate-field-deactivation-deletes-views.integration-spec.ts (13.132 s)
  kanban-aggregate-field-deactivation-nullifies-kanban-properties
    ✓ should nullify kanban properties when field used as kanbanAggregateOperationFieldMetadataId is deactivated (3923 ms)
    ✓ should not modify views when field not used as kanbanAggregateOperationFieldMetadataId is deactivated (2958 ms)
    ✓ should nullify kanban properties on multiple views when they all use the same field as kanbanAggregateOperationFieldMetadataId (2542 ms)
    ✓ should nullify kanban properties when views have different aggregate operations on same field (3380 ms)

Test Suites: 1 passed, 1 total
Tests:       4 passed, 4 total
Snapshots:   0 total
Time:        13.154 s
```

```ts
 PASS  test/integration/metadata/suites/field-metadata/view-group-field-deactivation-deletes-views.integration-spec.ts (12.639 s)
  view-group-field-deactivation-deletes-views
    ✓ should delete view when field used in view group is deactivated (3469 ms)
    ✓ should not delete view when field not used in view group is deactivated (3109 ms)
    ✓ should delete multiple views when they all use the same field in view groups (2741 ms)
    ✓ should handle deactivation when view has multiple view groups with different fields (3008 ms)

Test Suites: 1 passed, 1 total
Tests:       4 passed, 4 total
Snapshots:   0 total
Time:        12.664 s
```

```ts
 PASS  test/integration/metadata/suites/field-metadata/calendar-field-deactivation-deletes-views.integration-spec.ts (14.579 s)
  calendar-field-deactivation-deletes-views
    ✓ should delete view when field used as calendarFieldMetadataId is deactivated (3388 ms)
    ✓ should not delete view when field not used as calendarFieldMetadataId is deactivated (2438 ms)
    ✓ should delete multiple views when they all use the same field as calendarFieldMetadataId (2635 ms)
    ✓ should handle deactivation when views have different calendar layouts on same field (3195 ms)
    ✓ should delete calendar view but not other view types when calendar field is deactivated (2682 ms)

Test Suites: 1 passed, 1 total
Tests:       5 passed, 5 total
Snapshots:   0 total
Time:        14.601 s, estimated 15 s
```

## View soft deletion
We decided to remove the soft deletion grain on all the views, in this
PR context we've only removed soft deleted validation requirement on any
view entities

## Conclusion

close https://github.com/twentyhq/core-team-issues/issues/1754
2025-10-21 16:12:03 +02:00
Félix MalfaitandGitHub f7421c5fc0 Add queue management dashboard (#15202)
Adds a comprehensive queue management interface to the admin panel for
viewing and managing background jobs.

**Features:**
- Queue detail pages showing paginated job lists (50 per page)
- Filter jobs by state: completed, failed, active, waiting, delayed,
paused
- Checkbox selection with bulk actions (delete jobs, retry failed jobs)
- Per-job dropdown menu for individual retry/delete
- Expandable rows showing error messages, stack traces, and job data
- Relative timestamps with hover tooltips
- Display attempt counts on failed jobs
- Dynamic retention policy info from backend

**Changes:**
- Backend: New AdminPanelQueueService with GraphQL endpoints for job
listing, retry, and delete
- Frontend: Queue detail page with QueueJobsTable component
- Updated retention policy: completed jobs kept 4 hours, failed jobs
kept 7 days (max 1000 each)
- Added JobState enum for type safety

<img width="634" height="696" alt="Screenshot_2025-10-20_at_11 45 25"
src="https://github.com/user-attachments/assets/c67bcd27-26cf-47f5-9575-3cd5684d006b"
/>
<img width="484" height="680" alt="Screenshot_2025-10-20_at_11 45 14"
src="https://github.com/user-attachments/assets/68725cc6-b3ec-4098-99ca-f9a717d6f8f1"
/>
<img width="490" height="643" alt="Screenshot_2025-10-20_at_11 45 05"
src="https://github.com/user-attachments/assets/b68a5809-33ff-4452-b48b-741aff7f1dd6"
/>



<img width="685" height="662" alt="Screenshot 2025-10-20 at 13 15 01"
src="https://github.com/user-attachments/assets/eeb5207b-de5c-4b18-bdde-392892053dab"
/>
2025-10-21 16:02:50 +02:00
187cf400aa Fix author attachment field (#15065)
# Migrate Attachment Author to CreatedBy Field

**Twill Task**: https://twill.ai/twentyhq/ENG/tasks/7

## Summary

This PR implements a migration to transition the `Attachment` object
from using an `author` relation field to using the standard `createdBy`
field, addressing issue
https://github.com/twentyhq/core-team-issues/issues/1594.

## Changes

- **Added migration command**
(`1-8-migrate-attachment-author-to-created-by.command.ts`):
- Migrates existing attachment data to use `createdBy` instead of
`author`
- Ensures data integrity during the transition to the standard field
pattern

- **Updated Attachment workspace entity**:
  - Added `createdBy` relation field to the `Attachment` standard object
  - Registered new field ID in `standard-field-ids.ts` constants

- **Integrated migration into upgrade pipeline**:
  - Added migration module for version 1.8
  - Registered in the main upgrade version command module

This change aligns the `Attachment` object with Twenty's standard field
conventions by using the built-in `createdBy` field instead of a custom
`author` field.

---

Fixes https://github.com/twentyhq/core-team-issues/issues/1594

---------

Co-authored-by: Twill <agent@twill.ai>
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Félix Malfait <felix.malfait@gmail.com>
Co-authored-by: Félix Malfait <felix@twenty.com>
2025-10-21 09:56:28 +02:00
3bec43696f feat: multi role permission intersection (#15150)
Implements permission intersection (AND logic) to prevent permission
escalation when agents act on behalf of users.

### Changes:
- **Permission Intersection**: Operations requiring both user AND agent
permissions
- **RoleContext Type**: Unified type supporting single `roleId` or
multiple `roleIds` for intersection
- **CRUD Services**: Updated to accept `roleContext` for granular
permission control
- **Agent Integration**: Chat agents now use user + agent role
intersection for all operations
- **ORM Layer**: Enhanced `getRepository` to support multi-role
permission checks

### Related:
- Part 2 of ["Acting on behalf of user" concept
PR](https://github.com/twentyhq/twenty/pull/15103)

[Closes #1661](https://github.com/twentyhq/core-team-issues/issues/1661)

---------

Co-authored-by: Félix Malfait <felix.malfait@gmail.com>
2025-10-19 09:30:05 +02:00
f6d133f285 Scaffold all company cards as widgets (#15149)
This PR doesn't support mobile and side-panel modes. The fields tab will
be displayed in these cases.

## Demo


https://github.com/user-attachments/assets/0b2e699c-2b8e-4212-8c75-00d7d2e25237

---------

Co-authored-by: Félix Malfait <felix.malfait@gmail.com>
2025-10-18 22:26:27 +02:00
WeikoandGitHub cceeb6ed4d Add applicationId to syncableEntity and fix syncApp deletion (#15170)
## Context
- All flatEntity should extend SyncableEntity
- SyncableEntity should now have applicationId and application relation
- Fix syncApp deletion, should now properly use migration v2 to delete
syncable entities
2025-10-17 17:23:00 +00:00
Félix MalfaitandGitHub 84e7fabaab Squash migration files up to v1.5.5 (#15183)
## Summary

Squash 178 migration files (up to v1.5.5) into 2 consolidated migrations
to significantly improve migration performance.

## Changes

- **Consolidate 169 common migrations** into a single squashed migration
- **Consolidate 11 billing migrations** into a single squashed migration
- **Remove 178 files** (86% reduction: 207 → 29 migration files)
- Make `FK_47a6c57e1652b6475f8248cff78` constraint `DEFERRABLE INITIALLY
DEFERRED` to fix self-referencing foreign key issues
- Preserve all 27 post-v1.5.5 migrations (25 common + 2 billing)

## Impact

-  **2-3x faster** migration execution for new installations
-  **Faster dev environment** setup (database resets)
-  Both `IS_BILLING_ENABLED=false` and `true` modes tested and verified
-  All critical tables and constraints verified

## Testing

- Tested migrations from scratch with billing disabled (26 migrations)
- Tested migrations from scratch with billing enabled (29 migrations)
- Verified all critical tables exist (workspace, user, fieldMetadata,
etc.)
- Verified all 7 billing tables created correctly when enabled
- Confirmed `DEFERRABLE` constraint is properly applied

## Files Changed

- 180 files changed: +443 insertions, -6,462 deletions
- Net reduction: -6,019 lines of code
2025-10-17 18:17:37 +02:00
Félix MalfaitandGitHub 4f1623ff76 Seeding Attachments, Disable ORM Logs, Seeding Parallelization (#15174)
Improvements to database seeding performance and developer experience.

**Changes:**

1. **Attachment seeding**: Add sample files (PDF, XLSX, PPTX, PNG, ZIP)
to dev seeds with proper file storage
2. **Seeding parallelization**: Process entities within batches in
parallel while respecting dependencies
3. **ORM query logging**: Replace manual logger toggling with
`ORM_QUERY_LOGGING` env var
- Values: `disabled` (default), `server-only` (for local dev), `always`
- Configured once in `core.datasource.ts`, removed from all seeder
services

**For .env:**
```bash
ORM_QUERY_LOGGING=server-only
```

Net result: Faster seeding, cleaner code (-68 lines), better local dev
experience.
2025-10-17 16:50:19 +02:00
EtienneandGitHub 023c330698 Release - Fix migration message command (#15148) 2025-10-17 13:27:05 +02:00
martmullandGitHub bc336a9fdc Fix migration file (#15168)
as title
2025-10-17 13:16:23 +02:00
martmullandGitHub d2e7f2a910 1635 extensibilitytwenty cli app vars (#15143)
- Update twenty-cli to support application env variable definition
- Update twenty-server to create a new `core.applicationVariable` entity
to store env variables and provide env var when executing serverless
function
- Update twenty-front to support application environment variable value
setting

<img width="1044" height="660" alt="image"
src="https://github.com/user-attachments/assets/24c3d323-5370-4a80-8174-fc4653cc3c22"
/>

<img width="1178" height="662" alt="image"
src="https://github.com/user-attachments/assets/c124f423-8ed8-4246-ae5b-a9bd6672c7dc"
/>

<img width="1163" height="823" alt="image"
src="https://github.com/user-attachments/assets/fb7425a3-facc-4895-a5eb-8a8e278e0951"
/>

<img width="1087" height="696" alt="image"
src="https://github.com/user-attachments/assets/113da8a2-5590-433c-b1b3-5ed3137f24ca"
/>

<img width="1512" height="715" alt="image"
src="https://github.com/user-attachments/assets/1d2110b7-301d-4f21-a45c-ddd54d6e3391"
/>

<img width="1287" height="581" alt="image"
src="https://github.com/user-attachments/assets/353b16c6-0527-444c-87d6-51447a96cbc7"
/>
2025-10-17 10:54:38 +02:00
MarieandGitHub 3dd70aad03 [command fix] Limit migration to workflow versions (#15145)
This migration updates the filter operand values of the workflowVersion
and workflowRuns in order to capitalize them as they should be (the
product works with both deprecated camel case and capitalized). But that
may involve thousands of workflowRuns!
Let's update the command to only update workflowVersion, and update the
cleanWorkflowRuns job to remove workflow runs that are more than 14 days
old.

This way after the command is run, all new workflow runs will have the
new value for the filter operand, and after fourteen days there will be
no trace of the workflow runs with the deprecated filter operand.
2025-10-16 18:03:22 +02:00
EtienneandGitHub b2247a7a3d Release - Fix migration channel sync stage command (#15141) 2025-10-16 16:06:14 +02:00
EtienneandGitHub 5cbaa9f4d7 Release - add command in upgrade command (#15134) 2025-10-16 15:17:49 +02:00
EtienneandGitHub a5790e3967 Release - switch 1.10 to 1.8 (#15124)
Before
<img width="500" height="500" alt="Screenshot 2025-10-16 at 11 38 07"
src="https://github.com/user-attachments/assets/7fb99487-8e18-41e4-b7e1-0951782480c2"
/>
After
<img width="500" height="500" alt="Screenshot 2025-10-16 at 11 37 58"
src="https://github.com/user-attachments/assets/602c188b-0208-4e74-a3b6-5631df1bb968"
/>
2025-10-16 09:48:30 +00:00
d750df7fff Automatically clean up soft-deleted records after X days. (#14862)
Closes #14726

### Added
- `trashRetentionDays` field to workspace entity (default: 14 days)  
- Automated trash cleanup using BullMQ jobs  
- Daily cron (00:10 UTC) that enqueues cleanup jobs for all active
workspaces
- Per-workspace limit: 100k records deleted per day  
- Calendar-based retention: records deleted on day X are cleaned up X+14
days later (at midnight UTC boundaries)

### Architecture
- **Cron (WorkspaceTrashCleanupCronJob):** Runs daily, enqueues jobs in
parallel for all workspaces
- **Job (WorkspaceTrashCleanupJob):** Processes individual workspace
cleanup
- **Service (WorkspaceTrashCleanupService):** Discovers tables with
`deletedAt`, deletes old records with quota enforcement
- **Command:** `npx nx run twenty-server:command
cron:workspace:cleanup-trash` to register the cron

### Testing
- Unit tests for service with 100% coverage of public API  
- Tested quota enforcement, error handling, and edge cases

---------

Co-authored-by: Félix Malfait <felix.malfait@gmail.com>
2025-10-14 18:49:40 +02:00
d6526af302 Move search vector command from 1-7 to 1-10 and make it less verbose. (#14892)
Made the command more explicit for idempotency.

- Drop index explicitly (even though PostgreSQL does it automatically
when a column is dropped, but it might be good practice to account for
any unforeseen failures).
- Drop column.
- Create column.
- Create index (if we reach this point, column has already been created
and no error was thrown).
 
Figured we do not need extra queries to check ObjectMetadata for the
existence of person table on a workspace - we can use the IF EXISTS
syntax instead.

---------

Co-authored-by: Félix Malfait <felix.malfait@gmail.com>
2025-10-12 18:25:45 +02:00