## Context
Fixes#5403
Transliteration is now integrated to form validation through the schema.
While it does not impede inputting an invalid value, it impedes
submitting a form that will fail as the transliteration is not possible.
Until then we were only performing the transliteration at save time in
the front-end, but it's best to provide the information as soon as
possible. Later we will add helpers to guide the user (eg "This name is
not valid": https://github.com/twentyhq/twenty/issues/5428).
---------
Co-authored-by: Charles Bochet <charles@twenty.com>
We do not update the comment on the local table when a foreign table key
is deleted.
This was not breaking, which is why we did not see it. But comments
should be kept up to date.
---------
Co-authored-by: Thomas Trompette <thomast@twenty.com>
This PR is updating all object metadata entities with the new
decorators, and deleting the old ones.
This way we can use the new TwentyORM with all the standard objects.
---------
Co-authored-by: Weiko <corentin@twenty.com>
In this PR
1. Enable deletion of relation fields in the product and via the api
(migration part was missing in the api)
3. Change wording, only use "deactivate" and "delete" everywhere (and
not a mix of the two + "disable", "erase")
When user is deleting its account on a specific workspace, we remove it
as if it was a workspaceMember, and if no workspaceMember remains, we
delete the workspace and the associated stripe subscription
A user has reported an issue with REST API.
We have recently migrated the graphql IDs from UUID to ID type. As Rest
API is leveraging the graphql API under the hood, the Rest API query
builder should be updated accordingly
## Context
Since pg_graphql does not return specific error/exception, we have to
map the error message and throw validation errors when needed
This PR adds a check on unicity constraint error returned by pg_graphql
when we are trying to insert duplicate records and returns a 400 instead
of being handled by the exceptionHandler as a 500.
## Context
Yoga can catch its own errors and we don't want to convert them again.
Moreover those errors don't have an "originalError" property and should
be schema related only (400 validation) so we only want to send them
back to the API caller without going through the exception handler.
Also fixed an issue in the createMany which was throwing a 500 when id
was missing from the creation payload. It seems the FE is always sending
an ID but it should actually be optional since the DB can generate one.
This is a regression from the new UUID validation introduced a few weeks
ago.
Fixes#5276.
Updates were not triggering a cache version incrementation because they
do not trigger migrations while that is where the caching version logic
was.
We have decided to move the cache incrementation logic to the services.
## Context
JobsModule is hard to maintain because we provide all the jobs there,
including their dependencies. This PR aims to split jobs in dedicated
modules.
In this PR, I'm refactoring the way we associate messageParticipant post
person/company creation. Instead of looking a all person without
participant, we are passing the one that were just created.
Also, I'm making sure the message and messageParticipant creation
transaction is commited before creating person/company creation (and
then messageParticipant association)
This PR fixes several issues:
- enum naming should be: {tableName}_{fieldName}_enum and respecting the
case
- defaultValue format handled in the FE should respect the one in the BE
In my opinion we should refactor the defaultValue:
- we should respect backend format: "'myDefault'" for constant default
and "0" for float, "now" for expressions, "true" for booleans. we can
rename it to defaultValueExpression if it is more clear but we should
not maintain a parallel system
- we should deprecate option: isDefaultValue which is confusing
- we should re-work backend to have a more unified approach between
fields and avoid having if everywhere about select, multiselect, and
currency cases. one unified "computeDefaultValue" function should do the
job
What is still broken:
- currency default Value on creation. I think we should do the refactor
first
- select default value edition.
These cases do not break the schema but are ignored currently
# This PR
- Fix#5021
- Migrates `passwordResetToken` and `passwordResetTokenExpiresAt` fields
from `core.users` to `core.appToken`
- Marks those fields as `deprecated` so we can remove them later if we
are happy with the transition -- I took this decision on my own,
@FellipeMTX let me know what you think about it, we can also remove them
straight away if you think it's better
- Fixed the `database:migration` script from the `twenty-server` to:
```json
"database:migrate": {
"executor": "nx:run-commands",
"dependsOn": ["build"], // added this line
"options": {
"cwd": "packages/twenty-server",
"commands": [
"nx typeorm -- migration:run -d src/database/typeorm/metadata/metadata.datasource",
"nx typeorm -- migration:run -d src/database/typeorm/core/core.datasource"
],
"parallel": false
}
},
```
The migration script wasn't running because the builds were not executed
- [x] Added unit tests for the token.service file's changes
Looking forward to hearing feedback from you
cc: @charlesBochet
---------
Co-authored-by: Weiko <corentin@twenty.com>
This PR is a follow up of PR #5153.
This one introduce some changes on how we're querying composite fields.
We can do:
```typescript
export class CompanyService {
constructor(
@InjectWorkspaceRepository(CompanyObjectMetadata)
private readonly companyObjectMetadataRepository: WorkspaceRepository<CompanyObjectMetadata>,
) {}
async companies(): Promise<CompanyObjectMetadata[]> {
// Old way
// const companiesFilteredByLinkLabel = await this.companyObjectMetadataRepository.find({
// where: { xLinkLabel: 'MyLabel' },
// });
// Result will return xLinkLabel property
// New way
const companiesFilteredByLinkLabel = await this.companyObjectMetadataRepository.find({
where: { xLink: { label: 'MyLabel' } },
});
// Result will return { xLink: { label: 'MyLabel' } } property instead of { xLinkLabel: 'MyLabel' }
return companiesFilteredByLinkLabel;
}
}
```
Also we can now inject `TwentyORMManage` class to manually create a
repository based on a given `workspaceId` using
`getRepositoryForWorkspace` function that way:
```typescript
export class CompanyService {
constructor(
// TwentyORMModule should be initialized
private readonly twentyORMManager,
) {}
async companies(): Promise<CompanyObjectMetadata[]> {
const repository = await this.twentyORMManager.getRepositoryForWorkspace(
'8bb6e872-a71f-4341-82b5-6b56fa81cd77',
CompanyObjectMetadata,
);
const companies = await repository.find();
return companies;
}
}
```
## Context
Because creating an object in metadata also generates a graphql type and
because graphql does not allow 2 types with the same name, we have to
manage a list of reserved keywords that can't be used as object names.
Currently we were maintaining a list of the core objects but we also
have to introduce composite fields that are also generated as gql types.
## Context
Positions are used within a view to display and sort the different
records of standard/custom object.
When we add a new record and want to put it before the existing first
record, we have to use float values to insert them in the DB and respect
the desired order. We are adding a new command that can be executed to
flatten those positions.
---------
Co-authored-by: bosiraphael <raphael.bosi@gmail.com>
While using middleware (executed pre-graphql) for graphql endpoint, we
need to swallow exception and return errors with a 200. Otherwise it's
not a valid graphql response
Various fixes
- Remote objects are read-only for now, we already hide and block most
of the write actions but the button that allows you to add a new record
in an empty collection was still visible.
- CreatedAt is not mandatory on remote objects (at least for now) so it
was breaking the show page, it now checks if createdAt exists and is not
null before trying to display the human readable format `Added x days
ago`
- The filters are overwritten in query-runner-args.factory.ts to handle
NUMBER field type, this was only working with filters like
```
{
"id": {
"in": [
1
]
}
```
but not with more depth such as
```
"and": [
{},
{
"id": {
"in": [
1
]
}
}
]
```
- Fixes CREATE FOREIGN TABLE raw query which was missing ",".
## Context
We recently introduced a change that now throws a 401 if the token is
invalid or expired.
The first implementation is using an allow list and 'IntrospectionQuery'
was missing so the playground was broken.
The check has been updated and we now only check the excludedOperations
list if a token is not present. This is because some operations can be
both used as loggedIn and loggedOut so we want to validate the token for
those sometimes (and set the workspace, user, cache version, etc). Still
not a very clean solution imho.
## Context
We have a non-nullable constraint on authorId in attachments and
documents, until we have soft-deletion we need to handle deletion of
workspace-members and their attachments/documents.
This PR introduces pre-hooks to deleteOne/deleteMany
This is called when a user deletes a workspace-member from the members
page
Next: needs to be done on user level as well. This is called when users
try to delete their own accounts. I've seen other issues such as
re-creating a user with a previously used email failing.
Adding stripe integration by making the server logic independent of the
input fields:
- query factories (remote server, foreign data wrapper, foreign table)
to loop on fields and values without hardcoding the names of the fields
- adding stripe input and type
- add the logic to handle static schema. Simply creating a big object to
store into the server
Additional work:
- rename username field to user. This is the input intended for postgres
user mapping and we now need a matching by name
---------
Co-authored-by: Thomas Trompette <thomast@twenty.com>
## Context
Currently, this middleware validates the token and stores the user,
workspace and cacheversion in the request object.
It only does so when a token is provided and ignores the middleware
logic if not. If the token is invalid or expired, the exception is
swallowed.
This PR removes the try/catch and adds an allowlist to skip the token
validation for operations executed while not signed-in.
I don't know a better way to do that with Nestjs. We can't easily add
the middleware per resolver without refactoring the flexible schema
engine so I'm doing it the other way around.
Fixes https://github.com/twentyhq/twenty/issues/5224
## Context
#4774
## How was it tested
Locally
## In further PRs
- Update connection status upon page change
- Adapt Info banner to dark mode
- placeholders for form
## Context
Currently we have an unicity constraint in the DB but we don't return a
clear error to the frontend before reaching the DB (which then throws a
500). This PR adds a validation check similar to what we have with field
creation
Closes#5097
- Uses "nx affected" to detect what projects need to be checked in the
current PR (for now, `ci-front` and `ci-server` workflows only).
- Caches results of certain tasks (`lint`, `typecheck`, `test`,
`storybook:build`) when a PR pipeline runs. The next runs of the same
PR's pipeline will then be able to reuse the PR's task cache to execute
tasks faster.
- Caches Yarn's cache folder to install dependencies faster in CI jobs.
- Rewrites the node modules cache/install steps as a custom, reusable
Github action.
- Distributes `ci-front` jobs with a "matrix" strategy.
- Sets common tasks config at the root `nx.json`. For instance, to
activate the `typecheck` task in a project, add `typecheck: {}` to its
`project.json` and it'll use the default config set in `nx.json` for the
`typecheck` task. Options can be overridden in each individual
`project.json` if needed.
- Adds "scope" tags to some projects: `scope:frontend`, `scope:backend`,
`scope:shared`. An eslint rule ensures that `scope:frontend` only
depends on `scope:frontent` or `scope:shared` projects, same for
`scope:backend`. These tags are used by `nx affected` to filter projects
by scope and generates different task cache keys according to the
requested scope.
- Enables checks for twenty-emails in the `ci-server` workflow.
Now that we have persistent cache for schemas, we want to be able to
reset its state when users run the database:reset db otherwise schemas
won't be synced with the new DB state.
Note: In an upcoming PR, we want to be able to invalidate the cache on a
workspace level when we change the metadata schema through twenty
version upgrade
## Context
Messaging and calendar cron jobs are only working for workspace that
have sub status different than incomplete, this is because currently
this is the simplest way to know if a user is onboarded. This should not
be the source of truth and this will be updated in a later version. In
the meantime, to make self-hosting easier, we are adding an extra check
on IS_BILLING_ENABLED env var since sub status is not relevant for
people not using billing.
We should not depend on the foreign data wrapper type to manage distant
table. The remote server should be enough to handle the table creation.
Here is the new flow to fetch available tables:
- check if the remote server have available tables already stored
- if not, import full schema in a temporary schema
- copy the tables into the available tables field
- delete the schema
Left todo:
- update remote server input for postgres so we receive the schema
---------
Co-authored-by: Thomas Trompette <thomast@twenty.com>
## Introduction
This PR introduces "TwentyORM," a custom ORM module designed to
streamline database interactions within our workspace schema, reducing
the need for raw SQL queries. The API mirrors TypeORM's to provide a
familiar interface while integrating enhancements specific to our
project's needs.
To facilitate this integration, new decorators prefixed with `Workspace`
have been implemented. These decorators are used to define entity
metadata more explicitly and are critical in constructing our schema
dynamically.
## New Features
- **Custom ORM System**: Named "TwentyORM," which aligns closely with
TypeORM for ease of use but is tailored to our application's specific
requirements.
- **Decorator-Driven Configuration**: Entities are now configured with
`Workspace`-prefixed decorators that clearly define schema mappings and
relationships directly within the entity classes.
- **Injectable Repositories**: Repositories can be injected similarly to
TypeORM, allowing for flexible and straightforward data management.
## Example Implementations
### Decorated Entity Definitions
Entities are defined with new decorators that outline table and field
metadata, relationships, and constraints. Here are examples of these
implementations:
#### Company Metadata Object
```typescript
@WorkspaceObject({
standardId: STANDARD_OBJECT_IDS.company,
namePlural: 'companies',
labelSingular: 'Company',
labelPlural: 'Companies',
description: 'A company',
icon: 'IconBuildingSkyscraper',
})
export class CompanyObjectMetadata extends BaseObjectMetadata {
@WorkspaceField({
standardId: COMPANY_STANDARD_FIELD_IDS.name,
type: FieldMetadataType.TEXT,
label: 'Name',
description: 'The company name',
icon: 'IconBuildingSkyscraper',
})
name: string;
@WorkspaceField({
standardId: COMPANY_STANDARD_FIELD_IDS.xLink,
type: FieldMetadataType.LINK,
label: 'X',
description: 'The company Twitter/X account',
icon: 'IconBrandX',
})
@WorkspaceIsNullable()
xLink: LinkMetadata;
@WorkspaceField({
standardId: COMPANY_STANDARD_FIELD_IDS.position,
type: FieldMetadataType.POSITION,
label: 'Position',
description: 'Company record position',
icon: 'IconHierarchy2',
})
@WorkspaceIsSystem()
@WorkspaceIsNullable()
position: number;
@WorkspaceRelation({
standardId: COMPANY_STANDARD_FIELD_IDS.accountOwner,
label: 'Account Owner',
description: 'Your team member responsible for managing the company account',
type: RelationMetadataType.MANY_TO_ONE,
inverseSideTarget: () => WorkspaceMemberObjectMetadata,
inverseSideFieldKey: 'accountOwnerForCompanies',
onDelete: RelationOnDeleteAction.SET_NULL,
})
@WorkspaceIsNullable()
accountOwner: WorkspaceMemberObjectMetadata;
}
```
#### Workspace Member Metadata Object
```typescript
@WorkspaceObject({
standardId: STANDARD_OBJECT_IDS.workspaceMember,
namePlural: 'workspaceMembers',
labelSingular: 'Workspace Member',
labelPlural: 'Workspace Members',
description: 'A workspace member',
icon: 'IconUserCircle',
})
@WorkspaceIsSystem()
@WorkspaceIsNotAuditLogged()
export class WorkspaceMemberObjectMetadata extends BaseObjectMetadata {
@WorkspaceField({
standardId: WORKSPACE_MEMBER_STANDARD_FIELD_IDS.name,
type: FieldMetadataType.FULL_NAME,
label: 'Name',
description: 'Workspace member name',
icon: 'IconCircleUser',
})
name: FullNameMetadata;
@WorkspaceRelation({
standardId: WORKSPACE_MEMBER_STANDARD_FIELD_IDS.accountOwnerForCompanies,
label: 'Account Owner For Companies',
description: 'Account owner for companies',
icon: 'IconBriefcase',
type: RelationMetadataType.ONE_TO_MANY,
inverseSideTarget: () => CompanyObjectMetadata,
inverseSideFieldKey: 'accountOwner',
onDelete: RelationOnDeleteAction.SET_NULL,
})
accountOwnerForCompanies: Relation
<CompanyObjectMetadata[]>;
}
```
### Injectable Repository Usage
Repositories can be directly injected into services, allowing for
streamlined query operations:
```typescript
export class CompanyService {
constructor(
@InjectWorkspaceRepository(CompanyObjectMetadata)
private readonly companyObjectMetadataRepository: WorkspaceRepository<CompanyObjectMetadata>,
) {}
async companies(): Promise<CompanyObjectMetadata[]> {
// Example queries demonstrating simple and relation-loaded operations
const simpleCompanies = await this.companyObjectMetadataRepository.find({});
const companiesWithOwners = await this.companyObjectMetadataRepository.find({
relations: ['accountOwner'],
});
const companiesFilteredByLinkLabel = await this.companyObjectMetadataRepository.find({
where: { xLinkLabel: 'MyLabel' },
});
return companiesFilteredByLinkLabel;
}
}
```
## Conclusions
This PR sets the foundation for a decorator-driven ORM layer that
simplifies data interactions and supports complex entity relationships
while maintaining clean and manageable code architecture. This is not
finished yet, and should be extended.
All the standard objects needs to be migrated and all the module using
the old decorators too.
---------
Co-authored-by: Weiko <corentin@twenty.com>
## Context
We recently enabled the option to bypass SSL certificate authority
validation when establishing a connection to PostgreSQL. Previously, if
this validation failed, the server would revert to unencrypted traffic.
Now, it maintains encryption even if the SSL certificate check fails. In
the process, we overlooked a few DataSource setups, prompting a review
of DataSource creation within our code.
## Current State
Our DataSource initialization is distributed as follows:
- **Database folder**: Contains 'core', 'metadata', and 'raw'
DataSources. The 'core' and 'metadata' DataSources manage migrations and
static resolver calls to the database. The 'raw' DataSource is utilized
in scripts and commands that require handling both aspects.
- **typeorm.service.ts script**: These DataSources facilitate
multi-schema connections.
## Vision for Discussion
- **SystemSchema (formerly core) DataSource**: Manages system schema
migrations and system resolvers/repos. The 'core' schema will be renamed
to 'system' as the Core API will include parts of the system and
workspace schemas.
- **MetadataSchema DataSource**: Handles metadata schema migrations and
metadata API resolvers/repos.
- **(Dynamic) WorkspaceSchema DataSource**: Will be used in the Twenty
ORM to access a specific workspace schema.
We currently do not support cross-schema joins, so maintaining these
DataSources separately should be feasible. Core API resolvers will
select the appropriate DataSource based on the field context.
- **To be discussed**: The potential need for an AdminDataSource (akin
to 'Raw'), which would be used in commands, setup scripts, and the admin
panel to connect to any database schema without loading any model. This
DataSource should be reserved for cases where utilizing metadata,
system, or workspace entities is impractical.
## In This PR
- Ensuring all existing DataSources are compliant with the SSL update.
- Introducing RawDataSource to eliminate the need for declaring new
DataSource() instances in commands.
## Context
@lucasbordeau introduced a new Yoga plugin that allows us to cache our
requests (👏), see https://github.com/twentyhq/twenty/pull/5189
I'm simply updating the implementation to allow us to use different
cache storage types such as redis
Also adding a check so it does not use cache for other operations than
ObjectMetadataItems
## Test
locally, first call takes 340ms, 2nd takes 30ms with 'redis' and 13ms
with 'memory'
In this PR I'm introducing a simple custom graphql-yoga plugin to create
a caching mechanism specific to our metadata.
The cache key is made of : workspace id + workspace cache version, with
this the cache is automatically invalidated each time a change is made
on the workspace metadata.
New strategy:
- add settings field on FieldMetadata. Contains a boolean isIdField and
for numbers, a precision
- if idField, the graphql scalar returned will be a GraphQL id. This
will allow the app to work even for ids that are not uuid
- remove globals dateScalar and numberScalar modes. These were not used
- set limit as Integer
- check manually in query runner mutations that we send a valid id
Todo left:
- remove WorkspaceBuildSchemaOptions since this is not used anymore.
Will do in another PR
---------
Co-authored-by: Thomas Trompette <thomast@twenty.com>
Co-authored-by: Weiko <corentin@twenty.com>
## Description
This PR adds recaptcha on login form. One can add any one of three
recaptcha vendor -
1. Google Recaptcha -
https://developers.google.com/recaptcha/docs/v3#programmatically_invoke_the_challenge
2. HCaptcha -
https://docs.hcaptcha.com/invisible#programmatically-invoke-the-challenge
3. Turnstile -
https://developers.cloudflare.com/turnstile/get-started/client-side-rendering/#execution-modes
### Issue
- #3546
### Environment variables -
1. `CAPTCHA_DRIVER` - `google-recaptcha` | `hcaptcha` | `turnstile`
2. `CAPTCHA_SITE_KEY` - site key
3. `CAPTCHA_SECRET_KEY` - secret key
### Engineering choices
1. If some of the above env variable provided, then, backend generates
an error -
<img width="990" alt="image"
src="https://github.com/twentyhq/twenty/assets/60139930/9fb00fab-9261-4ff3-b23e-2c2e06f1bf89">
Please note that login/signup form will keep working as expected.
2. I'm using a Captcha guard that intercepts the request. If
"captchaToken" is present in the body and all env is set, then, the
captcha token is verified by backend through the service.
3. One can use this guard on any resolver to protect it by the captcha.
4. On frontend, two hooks `useGenerateCaptchaToken` and
`useInsertCaptchaScript` is created. `useInsertCaptchaScript` adds the
respective captcha JS script on frontend. `useGenerateCaptchaToken`
returns a function that one can use to trigger captcha token generation
programatically. This allows one to generate token keeping recaptcha
invisible.
### Note
This PR contains some changes in unrelated files like indentation,
spacing, inverted comma etc. I ran "yarn nx fmt:fix twenty-front" and
"yarn nx lint twenty-front -- --fix".
### Screenshots
<img width="869" alt="image"
src="https://github.com/twentyhq/twenty/assets/60139930/a75f5677-9b66-47f7-9730-4ec916073f8c">
---------
Co-authored-by: Félix Malfait <felix.malfait@gmail.com>
Co-authored-by: Charles Bochet <charles@twenty.com>
In this PR:
- Follow up on #5170 as we did not take into account not logged in users
- only apply throttler on root fields to avoid performance overhead
In this PR I'm introducing a new patch on @graphql-yoga/nestjs package.
This patch overrides a previous patch that was made to compute the
conditionnal schema on each request,
Here we use a cache map to compute only once per schema workspace cache
version.
This allows us to have sub 100ms query time.
An error has been recently introduced in the sync of fieldMetadata. This
PR fixes it
Additionnally, we are enabling email for trialing and past_due
workspaces. There is an ongoing work to introduce a more robust
activationStatus on workspace.
While trying to migrate a workspace from 0.3.3 to 0.10.0, we've faced an
issue with the script to migrate default-values format.
This PR fixes it.
We really need to add tests on this part ;)
Previously we had to create a separate API key to give access to chrome
extension so we can make calls to the DB. This PR includes logic to
initiate a oauth flow with PKCE method which redirects to the
`Authorise` screen to give access to server tokens.
Implemented in this PR-
1. make `redirectUrl` a non-nullable parameter
2. Add `NODE_ENV` to environment variable service
3. new env variable `CHROME_EXTENSION_REDIRECT_URL` on server side
4. strict checks for redirectUrl
5. try catch blocks on utils db query methods
6. refactor Apollo Client to handle `unauthorized` condition
7. input field to enter server url (for self-hosting)
8. state to show user if its already connected
9. show error if oauth flow is cancelled by user
Follow up PR -
Renew token logic
---------
Co-authored-by: Félix Malfait <felix@twenty.com>
Refactored the code to introduce two different concepts:
- AuditLogs (immutable, raw data)
- TimelineActivities (user-friendly, transformed data)
Still some work needed:
- Add message, files, calendar events to timeline (~2 hours if done
naively)
- Refactor repository to try to abstract concept when we can (tbd, wait
for Twenty ORM)
- Introduce ability to display child timelines on parent timeline with
filtering (~2 days)
- Improve UI: add links to open note/task, improve diff display, etc
(half a day)
- Decide the path forward for Task vs Notes: either introduce a new
field type "Record Type" and start going into that direction ; or split
in two objects?
- Trigger updates when a field is changed (will be solved by real-time /
websockets: 2 weeks)
- Integrate behavioral events (1 day for POC, 1 week for
clean/documented)
<img width="1248" alt="Screenshot 2024-04-12 at 09 24 49"
src="https://github.com/twentyhq/twenty/assets/6399865/9428db1a-ab2b-492c-8b0b-d4d9a36e81fa">
When distant table does not have an id column, syncing does not work.
Today the check is only made after creating the foreign table locally.
We should do it first, so we avoid having a foreign table created and
failing right after.
Co-authored-by: Thomas Trompette <thomast@twenty.com>
## Context
We have recently added an event listener to create audit logs on objects
update. However, we have only created the structure (relations on event
standard objects) for Company, Person, Opportunity and custom objects.
There is a larger effort in #4936 to refactor this.
For now, we are disabling log auditing on all other objects
## How
Add @IsNotAuditLogged() annotation on all standard objects except
Company, Person, Opportunity
## Context
We recently introduced this verification but we didn't take into account
self-hosting that might not use billing.
## Test
tested locally with
- new workspace and new account
- existing workspace with new account and billing not enabled and status
incomplete => OK
- existing workspace with new account and billing enabled and status
incomplete => NOK
- existing workspace with new account and billing enabled and status
active => OK
We will require remote table entity to map distant table name and local
foreign table name.
Introducing the entity:
- new source of truth to know if a table is sync or not
- created synchronously at the same time as metadata and foreign table
Adding a few more changes:
- exception rather than errors so the user can see these
- `pluralize` library that will allow to stop adding `Remote` suffix on
names
---------
Co-authored-by: Thomas Trompette <thomast@twenty.com>
## Context
- Rename remaining V2 services.
- Delete messages in DB when gmail history tells us they've been
deleted. I removed the logic where we store those in a cache since it's
a bit overkill because we don't need to query gmail and can use those
ids directly. The strategy is to delete the message channel message
association of the current channel, not the message or the thread since
they can still be linked to other channels. However, we will need to
call the threadCleaner service on the workspace to remove orphan
threads/non-associated messages.
Note: deletion for full-sync is a bit tricky because we need the full
list of message ids to compare with the DB and make sure we don't
over-delete. Currently, to keep memory, we don't have a variable that
holds all ids as we flush it after each page. Easier solution would be
to wipe everything before each full sync but it's probably not great for
the user experience if they are currently manipulating messages since
full-sync can happen without a user intervention (if a partial sync
fails due to historyId being invalidated by google for some reason)
## Context
The full-sync job was enqueued within a transaction, which means it
could be executed before the transaction was commit and
connected-account was not created yet.
This PR re-arrange the code a bit to avoid this
cc @bosiraphael thx for flagging this!
## Context
Recent PR introduced a verifyTransientToken inside the
GoogleAPIsProviderEnabledGuard guard. This is used to extract the
workspaceId from the token. This is working fine for the first call sent
to google however the callback is calling the same guard which is
causing an issue because the transientToken is missing from the
callback.
Imho, the same guard shouldn't be used by the callback but for the time
being I'm adding a check to prevent using feature flag when
transientToken is absent. In fact, it is present in the request but not
in the same key. Because the scope is only relevant for the first call,
I'm simply adding a check there.
## Context
Currently the calendar scope is bound to an env variable. We want to
rollout this feature to some users so this PR adds a check on the
existing IS_CALENDAR_ENABLED flag
## Context
With the addition of cronjobs, the app is building a lot of jobs and
stores them indefinitely. There is no real point to keep all of them in
the queue once they have been processed (completed or failed) so we are
adding a new default option to the bull-mq driver
## Implementation
See bull-mq JobsOption doc
```typescript
/**
* If true, removes the job when it successfully completes
* When given a number, it specifies the maximum amount of
* jobs to keep, or you can provide an object specifying max
* age and/or count to keep. It overrides whatever setting is used in the worker.
* Default behavior is to keep the job in the completed set.
*/
removeOnComplete?: boolean | number | KeepJobs;
/**
* If true, removes the job when it fails after all attempts.
* When given a number, it specifies the maximum amount of
* jobs to keep, or you can provide an object specifying max
* age and/or count to keep. It overrides whatever setting is used in the worker.
* Default behavior is to keep the job in the failed set.
*/
removeOnFail?: boolean | number | KeepJobs;
```
removeOnFail should be a bit higher since they are the ones we are most
likely looking at when needed.
1/ When the user inputs wrong connection informations, we do not inform
him. He will only see that no tables are available.
We will display a connection failed status if an error is raised testing
the connection
2/ If the connection fails, it should still be possible to delete the
server. Today, since we try first to delete the tables, the connection
failure throws an error that will prevent server deletion. Using the
foreign tables instead of calling the distant DB.
3/ Redirect to connection show page instead of connection list after
creation
4/ Today, foreign tables are fetched without the server name. This is a
mistake because we need to know which foreign table is linked with which
server. Updating the associated query.
<img width="632" alt="Capture d’écran 2024-04-12 à 10 52 49"
src="https://github.com/twentyhq/twenty/assets/22936103/9e8406b8-75d0-494c-ac1f-5e9fa7100f5c">
---------
Co-authored-by: Thomas Trompette <thomast@twenty.com>
The authorization token has an expiry of 5 minutes, we already have
checks in place to verify this and throw a Forbidden exception. We need
to revoke the token once it's used otherwise it could be used multiple
times to gain access to tokens till it expires.
Experiment using swc instead of tsc (as we did the switch on
twenty-front)
It's **much** faster (at least 5x) but has stricter requirements.
I fixed the build but there's still an error while starting the server,
opening this PR for discussion.
Checkout the branch and try `nx build:swc twenty-server`
Read: https://docs.nestjs.com/recipes/swc#common-pitfalls
close#4925
Before, for google-auth, if the user exists, we would simply returns a
login token, without checking the InvitationLink
Now, we just call the `authService.signUp` function that handle all
use-cases for us (user exists or not, invitationLink exists or not)
Added isAuditLogged column to object-metadata-entity.ts
This is my first open source pull request. Please do let me know if made
any mistake. I will be greatfull. Thank u
---------
Co-authored-by: Félix Malfait <felix@twenty.com>
Co-authored-by: Félix Malfait <felix.malfait@gmail.com>
This PR introduces a new folder structure for business modules.
Cron commands and jobs are now stored within the same module/folder at
the root of the business module
e.g: /modules/messaging/crons/commands instead of
/modules/messaging/commands/crons
Patterns are now inside their own cron-command files since they don't
need to be exported
Ideally cronJobs and cronCommands should have their logic within the
same class but it's a bit harder than expected due to how commanderjs
and our worker need both some class heritage check, hence the first
approach is to move them in the same folder
Also Messaging fullsync/partialsync V2 has been dropped since this is
the only used version => Breaking change for ongoing jobs and crons.
Jobs can be dropped but we will need to re-run our crons (only
cron:messaging:gmail-fetch-messages-from-cache)
In the previous PR #4912 it seems that I forgot to pass the environment
on the backend.
Here is a quick fix!
I also added some "doc" in the the .env.example
- Set `readOnly` boolean in table row context. Preventing updates and
deletion
- Show page is null for remote objects. No need for complicated design
since this is temporary?
- Relation creations are now behind a feature flag for remote objects
- Refetch objects and views after syncing objects
---------
Co-authored-by: Thomas Trompette <thomast@twenty.com>
- AuthFailedAt is set when a refreshToken is not valid and an
accessToken can't be generated, meaning it will need a manual action
from the user to provide a new refresh token.
- Calendar/messaging jobs should not be executed if authFailedAt is not
null.
Add support for a new SENTRY_RELEASE and SENTRY_ENVIRONMENT env.
It is optional and allows to init sentry with a Release version and an
env (used internally at Twenty).
Docker image have been updated do intergrate the new env as an Argument
- Fix default value sent to backend, using single quotes by default
- Use default value in field definition and column definition so that
field inputs can access it
- Used currency default value in CurrencyFieldInput
---------
Co-authored-by: Charles Bochet <charles@twenty.com>
This PR:
- separates the existing updateSyncStatus endpoint into 2 endpoints
- creates mutations and hooks that will call those endpoints
- trigger the hook on toggle
- removes form logic and add a separated component for toggling
---------
Co-authored-by: Thomas Trompette <thomast@twenty.com>
We have recently discovered that we were using ID type in place of UUID
type in many place in the code.
We have merged #4895 but this introduced bugs as we forgot to replace it
everywhere
- Implemented dataloader package on metadata graphql server
- Implemented a dataloader for relation metadata module
---------
Co-authored-by: Jérémy M <jeremy.magrin@gmail.com>
We have discovered that GraphQL inputs for fields of type ids in create
/ update input where using a more permissive ID type than the type used
in FilterInput in queries.
This PRs fixes it and make sure that all Input are using UUID graphql
scalar types
## Context
Calendar scope was too broad, this PR updates it to events only.
Also changing "Cannot connect Google account to demo workspace" error to
a 404 to avoid having a 500 for something expected
We've introduced in PR #4373 standard ids to be able to rename standard
fields and objects.
Fields part was working properly, but objects part was not yet
implemented.
This PR is adding the missing parts to make it work.
Foreign tables should be created using migrations, as we do for standard
tables.
Since those are not really generated from the object metadata but from
the remote table, those migrations won't live in the object metadata
service.
This PR:
- creates new types of migration : create_foreign_table and
drop_foreign_table
- triggers those migrations rather than raw queries directly
- moves the logic to fetch current foreign tables into the remote table
service since this is not directly linked to postgres data wrapper
- adds logic to unsync all tables before deleting
---------
Co-authored-by: Thomas Trompette <thomast@twenty.com>
This PR is dropping the column `targetColumnMap` of fieldMetadata
entities.
The goal of this column was to properly map field to their respecting
column in the table.
We decide to drop it and instead compute the column name on the fly when
we need it, as it's more easier to support.
Some parts of the code has been refactored to try making implementation
of composite type more easier to understand and maintain.
Fix#3760
---------
Co-authored-by: Charles Bochet <charles@twenty.com>
Endpoint is broken since we now use `Remote` as a suffix for remote
table names.
This PR:
- creates a common function to calculate the name of the remote table
- use it in the `findAvailableRemotePostgresTables` to know if a table
has been synced or not
Co-authored-by: Thomas Trompette <thomast@twenty.com>
## Context
We are now removing Messaging V2 feature flag to use it everywhere.
## Implementation
- renaming FetchWorkspaceMessagesCommandsModule to
MessagingCommandModule to make it more generic since it it hosts all
commands related to the messaging module
- creating a crons folder inside commands and jobs crons should be named
with xxx.cron.command.ts instead of xxx.command.ts. Same for jobs, jobs
should be named with xxx.cron.job.ts. In a future PR we should make sure
those CronJobs implement a CronJob interface since it's a bit different
(a CronJob does not contain a payload compared to a Job)
- Cron commands have been renamed to "cron:$module:command" so
`fetch-all-workspaces-messages-from-cache:cron:start` has been renamed
to `cron:messaging:gmail-fetch-messages-from-cache`. Also having to
create a command to stop the cron is a bit painful to maintain so I
removed them for now, this can be easily done manually with pg-boss or
bull-mq
- Removing full-sync and partial-sync commands as they were there for
testing only, we might put them back at some point but we will have to
adapt the code anyway.
- Feature flag has been removed from the MessageChannel standard object
to make sure those new columns are created during the next sync-metadata
## Context
Calendar tables are behind a featureFlag, they do not exist if the
feature flag is off which means we should not use them for the same
reason. I'm adding a check on the featureFlag before calling the
repository.
```
Error executing raw query for workspace 20202020-1c25-4d02-bf25-6aeccf7ea419: relation "workspace_1wgvd1injqtife6y4rvfbu3h5.calendarEventParticipant" does not exist
```
## Test
locally with and without featureflag
## Context
When running a command, the process should end normally however it stays
hanging due to the open connection with redis client (when
CACHE_STORAGE_TYPE=redis)
This PR adds the necessary logic to gracefully close the connection once
the module is destroyed. Thanks to that, the command process now
properly ends once executed.
Backend: Adding a new util function that throw an error if the
objectMetadata is remote
Frontend: hiding the save button when remote
Also renaming `useObjectMetadataItemForSettings` since this hook is used
in other places than settings and is not in the settings repo. Name can
definitely be challenged!
---------
Co-authored-by: Thomas Trompette <thomast@twenty.com>
Split from https://github.com/twentyhq/twenty/pull/4518
- Setup `@ui/*` as an internal alias to reference `twenty-ui/src`.
- Configures twenty-front to understand the `@ui/*` alias on development
mode, so twenty-ui can be hot reloaded.
- When building on production mode, twenty-front needs twenty-ui to be
built beforehand (which is automatic with the `dependsOn` option).
- Configures twenty-front to understand the `@ui/*` alias when launching
tests, so there is no need to re-build twenty-ui for tests.
---------
Co-authored-by: Lucas Bordeau <bordeau.lucas@gmail.com>
Several fixes for remote objects:
- labels are now displayed in title case. Added an util for this.
- Ids are often integers but the foreign keys on the relations were
uuid. Sending the id type to the object metadata service so it can
creates the foreign key accordingly
- Graphql comments are override when several remote objects are
imported. Building a function that fetch the existing comment and update
it
---------
Co-authored-by: Thomas Trompette <thomast@twenty.com>
We've seen a few cascading errors (e.g. comment.activityId would be non
nullable but cascade behavior is set to "set null"). I think it's safer
if we have to explicitly chose the deletion behavior it every time.
Especially since Postgres default to "No action" while we defaulted to
"Set Null", which is confusing.
In the future we will most likely introduce a second param
`onSoftDelete` in the decorator
**Context**
cf. feature request
[#4597](https://github.com/twentyhq/twenty/issues/4597)
Enables deletion of custom fields that aren't active nor of type
relation
Also
1. renamed a misnamed file
2. deleted redundant hook BeforeDeleteOneField as it seemed best to move
the logic to the resolver instead
**How was it tested?**
Did not write unit tests as code is to be migrated (discussed with
@Weiko).
Locally tested.
---------
Co-authored-by: Marie Stoppa <mariestoppa@MacBook-Pro-de-Marie.local>
Foreign table id cannot be a foreign key of a base table. But the
current code use foreign keys to link object metadata with activities,
events... So we will:
- create a column without creating a foreign key
- add a comment on the table schema so pg_graphql sees it as a foreign
key
This PR:
- refactor a bit object metadata service so the mutation creation is
separated into an util
- adds the mutation creation for remote object relations
- add a new type of mutation to create a comment
---------
Co-authored-by: Thomas Trompette <thomast@twenty.com>
## Context
SyncExternalId should be renamed because this won't always represent an
id. For example, microsoft API does not use ids but dates for their
sync. Also we think external is a bit redundant so we are removing it.
Note: this field is not used by the front-end (and will probably never
be)
We were missing `JsDom` dependencies in the package.json generated by nx
while running `twenty-server`: `yarn nx build:packageJson`
Detailed explanation:
- we are currently using nx paradigm which is to put dependencies of all
projets at root, which enables global package migrations for the whole
monorepo
- for production containers, we only want specific project dependency to
be added. This is done by running `yarn nx build:packageJson` on
`twenty-server`. Nx is statically analyzing twenty-server dependencies
and generating a tailored package.json that production containers can
later use.
- However, `nx` static analysis is not flawless and is missing some
packages. We are going to stop using it as the value is not there yet
but the burden for developers is high. The guideline is to put back
project dependencies into specific package `package.json`
- Therefore, I'm adding `jsdom` to twenty-server `package.json`
- If sync fails we set authFailedAt
- This information is displayed in the frontend in accounts with a `Sync
Failed` pill
- The user can reconnect his account in the dropdown menu
- A new OAuth flow is triggered
- The account is synced
## Context
A new ADDRESS field type has been introduced and the company object has
been updated to use this new type however this introduced a few
regressions.
The good strategy would be to introduce a new field and rename the old
one.
This PR revert that change to fix the issue.
Split from https://github.com/twentyhq/twenty/pull/4518
- Upgrades dependencies and applies automatic config migrations with the
command: `npx nx migrate nx` (see
https://nx.dev/nx-api/nx/documents/migrate)
- Fixes lint errors after upgrading `@typescript-eslint`
Note: it was not possible (for now) to migrate Nx to the latest stable
version (v18.2.1) because it upgrades Typescript to v5.4.3, which seems
to cause a bug on install when Yarn tries to apply its native patches.
Might be a bug on the Yarn side.
When writing to the normalized cache (record), it's crucial to use _refs
for relationships to avoid many problems. Essentially, we only deal with
level 0 and generate all fields to be comfortable with their defaults.
When writing in queries (which should be very rare, the only cases are
prefetch and the case of activities due to the nested query; I've
reduced this to a single file for activities
usePrepareFindManyActivitiesQuery 🙂), it's important to use queryFields
to avoid bugs. I've implemented them on the side of query generation and
record generation.
When doing an updateOne / createOne, etc., it's necessary to distinguish
between optimistic writing (which we actually want to do with _refs) and
the server response without refs. This allows for a clean write in the
optimistic cache without worrying about nesting (as the first point).
To simplify the whole activities part, write to the normalized cache
first. Then, base queries on it in an idempotent manner. This way,
there's no need to worry about the current page or action. The
normalized cache is up-to-date, so I update the queries. Same idea as
for optimisticEffects, actually.
Finally, I've triggered optimisticEffects rather than the manual update
of many queries.
---------
Co-authored-by: Lucas Bordeau <bordeau.lucas@gmail.com>
* default value boolean fixed
* fixed creation, fixed updating a value to false
* fixed default value for default value if boolean
* fixed tests
---------
Co-authored-by: Félix Malfait <felix.malfait@gmail.com>
* fixes
* saving workspaceMemberId and personId when saving attendees
* add typing
* use Map
* improve saveMessageParticipants
* fix role type
* move logic in a service
* create new service
* use new service in calendar-event-attendee.service
* modify service to include more common logic
* add defaumt value to isOrganizer in calendar-event-attendee.object-metadata
* rename folder
* renaming
* update calendarEvent labels and description to match Figma
* modify conferenceUri to conferenceLink with LINK type
* update format-google-calendar-event.util to match new conferenceLink
* update CalendarEventDetails since overriding the fields is no longer needed
* fix mock metadata
* generate new uuid for field conferenceLink
* feat: wip refactor default-value
* feat: health check to migrate default value
* fix: tests
* fix: refactor defaultValue to make it more clean
* fix: unit tests
* fix: front-end default value
* fix: sever not throwing when enum contains two identical values
* fix: enum column name cannot be change
* fix: put field create/update inside transactions
* fix: check for options duplicate values front-end
* fix: missing commit transaction
* Build remote server
* Add getters
* Migrate to json inputs
* Use extendable type
* Use regex validation
* Remove acronymes
---------
Co-authored-by: Thomas Trompette <thomast@twenty.com>
* add lodash differenceWith
* add awaits
* update sync cursor is working
* add logs
* use isSyncEnabled information to enqueue jobs
* add decorator InjectObjectMetadataRepository
* fix gmail-full-sync
* Being implementing events on the frontend
* Rename JSON to RAW JSON
* Fix handling of json field on frontend
* Log user id
* Add frontend tests
* Update packages/twenty-server/src/engine/api/graphql/workspace-query-runner/jobs/save-event-to-db.job.ts
Co-authored-by: Weiko <corentin@twenty.com>
* Move db calls to a dedicated repository
* Add server-side tests
---------
Co-authored-by: Weiko <corentin@twenty.com>
* feat: merge front and server dockerfiles and optimize build
* fix: update image label
* fix: bring back support for REACT_APP_SERVER_BASE_URL injection at runtime
* fix: remove old entries & add nx cache in dockerignore
* feat: generate frontend config at runtime using Nest
* fix: format and filename
* feat: use the EnvironmentService and leave default blank
* feat: add support for DB migrations