[Apps] App misc - fixes + settings permissions for apps + uploadFile (#17167)

In this PR

- handle settings permission check for applications. Until then this was
unhandled and applications could not perform actions requiring settings
permissions, even if they were granted them

- fix ties to attachment, noteTarget etc: 
When an object had their fields synchronized in the app, the system
fields created as a side-effect of the object creation (relations to
noteTarget, attachment, taskTarget, favorites, timelineActivities -
created with `isCustom: true`, not sure that is correct btw) were then
deleted because they are not declared in the app, and identified as
deletable because of `isCustom: true`.
Updating the logic to exclude system fields from the logic that detects
fields to delete.
I think this outline the confusion we have around isCustom, isSystem
etc.

- introduce uploadFile util in generated twenty client as it cannot be
handled by the client's query / mutation. I had to use this for my
invoicing app
This commit is contained in:
Marie
2026-01-18 19:41:48 +01:00
committed by GitHub
parent 6070dbf16a
commit 2c596e7b1e
9 changed files with 124 additions and 37 deletions
@@ -1,12 +1,12 @@
import { generate } from '@genql/cli';
import chalk from 'chalk';
import { join, resolve } from 'path';
import { ApiService } from '@/cli/services/api.service';
import { ConfigService } from '@/cli/services/config.service';
import { generate } from '@genql/cli';
import chalk from 'chalk';
import * as fs from 'fs-extra';
import { join, resolve } from 'path';
import {
DEFAULT_API_URL_NAME,
DEFAULT_API_KEY_NAME,
DEFAULT_API_URL_NAME,
} from 'twenty-shared/application';
export const GENERATED_FOLDER_NAME = 'generated';
@@ -107,6 +107,41 @@ export default class Twenty {
mutation<R extends MutationGenqlSelection>(request: R & { __name?: string }) {
return this.client.mutation(request);
}
async uploadFile(
fileBuffer: Buffer,
filename: string,
contentType: string = 'application/octet-stream',
fileFolder: string = 'Attachment',
): Promise<{ path: string; token: string }> {
const form = new FormData();
form.append('operations', JSON.stringify({
query: \`mutation UploadFile($file: Upload!, $fileFolder: FileFolder) {
uploadFile(file: $file, fileFolder: $fileFolder) { path token }
}\`,
variables: { file: null, fileFolder },
}));
form.append('map', JSON.stringify({ '0': ['variables.file'] }));
form.append('0', new Blob([fileBuffer], { type: contentType }), filename);
const response = await fetch(\`\${process.env.TWENTY_API_URL}/graphql\`, {
method: 'POST',
headers: {
Authorization: \`Bearer \${process.env.TWENTY_API_KEY}\`,
},
body: form,
});
const result = await response.json();
if (result.errors) {
throw new GenqlError(result.errors, result.data);
}
return result.data.uploadFile;
}
}
`;