mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-18 02:21:13 +00:00
refactor: Format nodes-base package (A-F) (#3800)
* 🔨 prettier formated nodes - A * 🔨 prettier formated nodes - B * ⚡ prettier formated nodes - C * ⚡ prettier formated nodes - D * ⚡ prettier formated nodes - E-F * 🎨 Adjust nodes-base formatting command (#3805) * Format additional files in nodes A-F (#3811) * ⚡ fixes * 🎨 Add Mindee to ignored dirs Co-authored-by: Iván Ovejero <ivov.src@gmail.com>
This commit is contained in:
@@ -1,6 +1,4 @@
|
||||
import {
|
||||
INodeProperties,
|
||||
} from 'n8n-workflow';
|
||||
import { INodeProperties } from 'n8n-workflow';
|
||||
|
||||
export const campaignOperations: INodeProperties[] = [
|
||||
{
|
||||
@@ -48,9 +46,7 @@ export const campaignOperations: INodeProperties[] = [
|
||||
],
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'campaign',
|
||||
],
|
||||
resource: ['campaign'],
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -69,15 +65,12 @@ export const campaignFields: INodeProperties[] = [
|
||||
},
|
||||
default: [],
|
||||
required: true,
|
||||
description: 'The ID of the campaign to add the contact to. Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code-examples/expressions/">expression</a>.',
|
||||
description:
|
||||
'The ID of the campaign to add the contact to. Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code-examples/expressions/">expression</a>.',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'campaign',
|
||||
],
|
||||
operation: [
|
||||
'addContact',
|
||||
],
|
||||
resource: ['campaign'],
|
||||
operation: ['addContact'],
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -90,12 +83,8 @@ export const campaignFields: INodeProperties[] = [
|
||||
description: 'The email of the contact to add to the campaign',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'campaign',
|
||||
],
|
||||
operation: [
|
||||
'addContact',
|
||||
],
|
||||
resource: ['campaign'],
|
||||
operation: ['addContact'],
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -107,12 +96,8 @@ export const campaignFields: INodeProperties[] = [
|
||||
default: {},
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'campaign',
|
||||
],
|
||||
operation: [
|
||||
'addContact',
|
||||
],
|
||||
resource: ['campaign'],
|
||||
operation: ['addContact'],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
@@ -213,12 +198,8 @@ export const campaignFields: INodeProperties[] = [
|
||||
description: 'The name of the campaign to create',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'campaign',
|
||||
],
|
||||
operation: [
|
||||
'create',
|
||||
],
|
||||
resource: ['campaign'],
|
||||
operation: ['create'],
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -235,12 +216,8 @@ export const campaignFields: INodeProperties[] = [
|
||||
description: 'The ID of the campaign to retrieve',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'campaign',
|
||||
],
|
||||
operation: [
|
||||
'get',
|
||||
],
|
||||
resource: ['campaign'],
|
||||
operation: ['get'],
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -256,12 +233,8 @@ export const campaignFields: INodeProperties[] = [
|
||||
description: 'Whether to return all results or only up to a given limit',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'campaign',
|
||||
],
|
||||
operation: [
|
||||
'getAll',
|
||||
],
|
||||
resource: ['campaign'],
|
||||
operation: ['getAll'],
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -277,15 +250,9 @@ export const campaignFields: INodeProperties[] = [
|
||||
},
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'campaign',
|
||||
],
|
||||
operation: [
|
||||
'getAll',
|
||||
],
|
||||
returnAll: [
|
||||
false,
|
||||
],
|
||||
resource: ['campaign'],
|
||||
operation: ['getAll'],
|
||||
returnAll: [false],
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -302,12 +269,8 @@ export const campaignFields: INodeProperties[] = [
|
||||
description: 'The ID of the campaign to pause. The campaign must be in RUNNING mode.',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'campaign',
|
||||
],
|
||||
operation: [
|
||||
'pause',
|
||||
],
|
||||
resource: ['campaign'],
|
||||
operation: ['pause'],
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -324,12 +287,8 @@ export const campaignFields: INodeProperties[] = [
|
||||
description: 'The ID of the campaign to start. Email provider and contacts must be set.',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'campaign',
|
||||
],
|
||||
operation: [
|
||||
'start',
|
||||
],
|
||||
resource: ['campaign'],
|
||||
operation: ['start'],
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -343,18 +302,15 @@ export const campaignFields: INodeProperties[] = [
|
||||
type: 'options',
|
||||
default: '',
|
||||
required: true,
|
||||
description: 'The ID of the campaign to duplicate. Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code-examples/expressions/">expression</a>.',
|
||||
description:
|
||||
'The ID of the campaign to duplicate. Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code-examples/expressions/">expression</a>.',
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getCampaigns',
|
||||
},
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'campaign',
|
||||
],
|
||||
operation: [
|
||||
'duplicate',
|
||||
],
|
||||
resource: ['campaign'],
|
||||
operation: ['duplicate'],
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -367,12 +323,8 @@ export const campaignFields: INodeProperties[] = [
|
||||
description: 'The name of the new campaign to create',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'campaign',
|
||||
],
|
||||
operation: [
|
||||
'duplicate',
|
||||
],
|
||||
resource: ['campaign'],
|
||||
operation: ['duplicate'],
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -384,12 +336,8 @@ export const campaignFields: INodeProperties[] = [
|
||||
placeholder: 'Add Field',
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: [
|
||||
'duplicate',
|
||||
],
|
||||
resource: [
|
||||
'campaign',
|
||||
],
|
||||
operation: ['duplicate'],
|
||||
resource: ['campaign'],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
@@ -412,7 +360,8 @@ export const campaignFields: INodeProperties[] = [
|
||||
name: 'copyMails',
|
||||
type: 'boolean',
|
||||
default: true,
|
||||
description: 'Whether to copy all the steps of the email sequence from the original campaign',
|
||||
description:
|
||||
'Whether to copy all the steps of the email sequence from the original campaign',
|
||||
},
|
||||
{
|
||||
displayName: 'Copy Global Settings',
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
import {
|
||||
INodeProperties,
|
||||
} from 'n8n-workflow';
|
||||
import { INodeProperties } from 'n8n-workflow';
|
||||
|
||||
export const contactListOperations: INodeProperties[] = [
|
||||
{
|
||||
@@ -23,9 +21,7 @@ export const contactListOperations: INodeProperties[] = [
|
||||
],
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'contactList',
|
||||
],
|
||||
resource: ['contactList'],
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -44,15 +40,12 @@ export const contactListFields: INodeProperties[] = [
|
||||
},
|
||||
default: [],
|
||||
required: true,
|
||||
description: 'The ID of the contact list to add the contact to. Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code-examples/expressions/">expression</a>.',
|
||||
description:
|
||||
'The ID of the contact list to add the contact to. Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code-examples/expressions/">expression</a>.',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'contactList',
|
||||
],
|
||||
operation: [
|
||||
'add',
|
||||
],
|
||||
resource: ['contactList'],
|
||||
operation: ['add'],
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -65,12 +58,8 @@ export const contactListFields: INodeProperties[] = [
|
||||
description: 'The email of the contact to add to the contact list',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'contactList',
|
||||
],
|
||||
operation: [
|
||||
'add',
|
||||
],
|
||||
resource: ['contactList'],
|
||||
operation: ['add'],
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -82,12 +71,8 @@ export const contactListFields: INodeProperties[] = [
|
||||
default: {},
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'contactList',
|
||||
],
|
||||
operation: [
|
||||
'add',
|
||||
],
|
||||
resource: ['contactList'],
|
||||
operation: ['add'],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
@@ -187,12 +172,8 @@ export const contactListFields: INodeProperties[] = [
|
||||
description: 'Whether to return all results or only up to a given limit',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'contactList',
|
||||
],
|
||||
operation: [
|
||||
'getAll',
|
||||
],
|
||||
resource: ['contactList'],
|
||||
operation: ['getAll'],
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -208,15 +189,9 @@ export const contactListFields: INodeProperties[] = [
|
||||
},
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: [
|
||||
'contactList',
|
||||
],
|
||||
operation: [
|
||||
'getAll',
|
||||
],
|
||||
returnAll: [
|
||||
false,
|
||||
],
|
||||
resource: ['contactList'],
|
||||
operation: ['getAll'],
|
||||
returnAll: [false],
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
import {
|
||||
IExecuteFunctions
|
||||
} from 'n8n-core';
|
||||
import { IExecuteFunctions } from 'n8n-core';
|
||||
|
||||
import {
|
||||
IDataObject,
|
||||
@@ -8,28 +6,16 @@ import {
|
||||
INodeExecutionData,
|
||||
INodeType,
|
||||
INodeTypeDescription,
|
||||
JsonObject
|
||||
JsonObject,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import {
|
||||
emeliaApiTest,
|
||||
emeliaGraphqlRequest,
|
||||
loadResource,
|
||||
} from './GenericFunctions';
|
||||
import { emeliaApiTest, emeliaGraphqlRequest, loadResource } from './GenericFunctions';
|
||||
|
||||
import {
|
||||
campaignFields,
|
||||
campaignOperations,
|
||||
} from './CampaignDescription';
|
||||
import { campaignFields, campaignOperations } from './CampaignDescription';
|
||||
|
||||
import {
|
||||
contactListFields,
|
||||
contactListOperations,
|
||||
} from './ContactListDescription';
|
||||
import { contactListFields, contactListOperations } from './ContactListDescription';
|
||||
|
||||
import {
|
||||
isEmpty,
|
||||
} from 'lodash';
|
||||
import { isEmpty } from 'lodash';
|
||||
|
||||
export class Emelia implements INodeType {
|
||||
description: INodeTypeDescription = {
|
||||
@@ -102,17 +88,13 @@ export class Emelia implements INodeType {
|
||||
const operation = this.getNodeParameter('operation', 0);
|
||||
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
|
||||
try {
|
||||
|
||||
if (resource === 'campaign') {
|
||||
|
||||
// **********************************
|
||||
// campaign
|
||||
// **********************************
|
||||
|
||||
if (operation === 'addContact') {
|
||||
|
||||
// ----------------------------------
|
||||
// campaign: addContact
|
||||
// ----------------------------------
|
||||
@@ -128,8 +110,13 @@ export class Emelia implements INodeType {
|
||||
}
|
||||
|
||||
if (additionalFields.customFieldsUi) {
|
||||
const customFields = (additionalFields.customFieldsUi as IDataObject || {}).customFieldsValues as IDataObject[] || [];
|
||||
const data = customFields.reduce((obj, value) => Object.assign(obj, { [`${value.fieldName}`]: value.value }), {});
|
||||
const customFields =
|
||||
(((additionalFields.customFieldsUi as IDataObject) || {})
|
||||
.customFieldsValues as IDataObject[]) || [];
|
||||
const data = customFields.reduce(
|
||||
(obj, value) => Object.assign(obj, { [`${value.fieldName}`]: value.value }),
|
||||
{},
|
||||
);
|
||||
Object.assign(contact, data);
|
||||
//@ts-ignore
|
||||
delete contact.customFieldsUi;
|
||||
@@ -148,9 +135,7 @@ export class Emelia implements INodeType {
|
||||
});
|
||||
|
||||
returnData.push({ contactId: responseData.data.addContactToCampaignHook });
|
||||
|
||||
} else if (operation === 'create') {
|
||||
|
||||
// ----------------------------------
|
||||
// campaign: create
|
||||
// ----------------------------------
|
||||
@@ -175,9 +160,7 @@ export class Emelia implements INodeType {
|
||||
});
|
||||
|
||||
returnData.push(responseData.data.createCampaign);
|
||||
|
||||
} else if (operation === 'get') {
|
||||
|
||||
// ----------------------------------
|
||||
// campaign: get
|
||||
// ----------------------------------
|
||||
@@ -218,9 +201,7 @@ export class Emelia implements INodeType {
|
||||
});
|
||||
|
||||
returnData.push(responseData.data.campaign);
|
||||
|
||||
} else if (operation === 'getAll') {
|
||||
|
||||
// ----------------------------------
|
||||
// campaign: getAll
|
||||
// ----------------------------------
|
||||
@@ -258,9 +239,7 @@ export class Emelia implements INodeType {
|
||||
}
|
||||
|
||||
returnData.push(...campaigns);
|
||||
|
||||
} else if (operation === 'pause') {
|
||||
|
||||
// ----------------------------------
|
||||
// campaign: pause
|
||||
// ----------------------------------
|
||||
@@ -277,9 +256,7 @@ export class Emelia implements INodeType {
|
||||
});
|
||||
|
||||
returnData.push({ success: true });
|
||||
|
||||
} else if (operation === 'start') {
|
||||
|
||||
// ----------------------------------
|
||||
// campaign: start
|
||||
// ----------------------------------
|
||||
@@ -296,9 +273,7 @@ export class Emelia implements INodeType {
|
||||
});
|
||||
|
||||
returnData.push({ success: true });
|
||||
|
||||
} else if (operation === 'duplicate') {
|
||||
|
||||
// ----------------------------------
|
||||
// campaign: duplicate
|
||||
// ----------------------------------
|
||||
@@ -313,7 +288,9 @@ export class Emelia implements INodeType {
|
||||
copyProvider: true,
|
||||
...options,
|
||||
};
|
||||
const { data: { duplicateCampaign } } = await emeliaGraphqlRequest.call(this, {
|
||||
const {
|
||||
data: { duplicateCampaign },
|
||||
} = await emeliaGraphqlRequest.call(this, {
|
||||
query: `
|
||||
mutation duplicateCampaign(
|
||||
$fromId: ID!
|
||||
@@ -338,15 +315,12 @@ export class Emelia implements INodeType {
|
||||
|
||||
returnData.push({ _id: duplicateCampaign });
|
||||
}
|
||||
|
||||
} else if (resource === 'contactList') {
|
||||
|
||||
// **********************************
|
||||
// ContactList
|
||||
// **********************************
|
||||
|
||||
if (operation === 'add') {
|
||||
|
||||
// ----------------------------------
|
||||
// contactList: add
|
||||
// ----------------------------------
|
||||
@@ -362,8 +336,13 @@ export class Emelia implements INodeType {
|
||||
}
|
||||
|
||||
if (additionalFields.customFieldsUi) {
|
||||
const customFields = (additionalFields.customFieldsUi as IDataObject || {}).customFieldsValues as IDataObject[] || [];
|
||||
const data = customFields.reduce((obj, value) => Object.assign(obj, { [`${value.fieldName}`]: value.value }), {});
|
||||
const customFields =
|
||||
(((additionalFields.customFieldsUi as IDataObject) || {})
|
||||
.customFieldsValues as IDataObject[]) || [];
|
||||
const data = customFields.reduce(
|
||||
(obj, value) => Object.assign(obj, { [`${value.fieldName}`]: value.value }),
|
||||
{},
|
||||
);
|
||||
Object.assign(contact, data);
|
||||
//@ts-ignore
|
||||
delete contact.customFieldsUi;
|
||||
@@ -382,9 +361,7 @@ export class Emelia implements INodeType {
|
||||
});
|
||||
|
||||
returnData.push({ contactId: responseData.data.addContactsToListHook });
|
||||
|
||||
} else if (operation === 'getAll') {
|
||||
|
||||
// ----------------------------------
|
||||
// contactList: getAll
|
||||
// ----------------------------------
|
||||
@@ -414,18 +391,14 @@ export class Emelia implements INodeType {
|
||||
|
||||
returnData.push(...contactLists);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
|
||||
if (this.continueOnFail()) {
|
||||
returnData.push({ error: (error as JsonObject).message });
|
||||
continue;
|
||||
}
|
||||
|
||||
throw error;
|
||||
|
||||
}
|
||||
}
|
||||
return [this.helpers.returnJsonArray(returnData)];
|
||||
|
||||
@@ -8,11 +8,7 @@ import {
|
||||
IWebhookResponseData,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import {
|
||||
emeliaApiRequest,
|
||||
emeliaApiTest,
|
||||
emeliaGraphqlRequest,
|
||||
} from './GenericFunctions';
|
||||
import { emeliaApiRequest, emeliaApiTest, emeliaGraphqlRequest } from './GenericFunctions';
|
||||
|
||||
interface Campaign {
|
||||
_id: string;
|
||||
@@ -53,7 +49,8 @@ export class EmeliaTrigger implements INodeType {
|
||||
displayName: 'Campaign Name or ID',
|
||||
name: 'campaignId',
|
||||
type: 'options',
|
||||
description: 'Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code-examples/expressions/">expression</a>',
|
||||
description:
|
||||
'Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code-examples/expressions/">expression</a>',
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getCampaigns',
|
||||
},
|
||||
@@ -115,12 +112,10 @@ export class EmeliaTrigger implements INodeType {
|
||||
variables: '{}',
|
||||
});
|
||||
|
||||
return responseData.data.campaigns.map(
|
||||
(campaign: Campaign) => ({
|
||||
name: campaign.name,
|
||||
value: campaign._id,
|
||||
}),
|
||||
);
|
||||
return responseData.data.campaigns.map((campaign: Campaign) => ({
|
||||
name: campaign.name,
|
||||
value: campaign._id,
|
||||
}));
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -147,7 +142,7 @@ export class EmeliaTrigger implements INodeType {
|
||||
const campaignId = this.getNodeParameter('campaignId') as string;
|
||||
const body = {
|
||||
hookUrl: webhookUrl,
|
||||
events: events.map(e => e.toUpperCase()),
|
||||
events: events.map((e) => e.toUpperCase()),
|
||||
campaignId,
|
||||
};
|
||||
|
||||
@@ -179,9 +174,7 @@ export class EmeliaTrigger implements INodeType {
|
||||
async webhook(this: IWebhookFunctions): Promise<IWebhookResponseData> {
|
||||
const req = this.getRequestObject();
|
||||
return {
|
||||
workflowData: [
|
||||
this.helpers.returnJsonArray(req.body),
|
||||
],
|
||||
workflowData: [this.helpers.returnJsonArray(req.body)],
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
import {
|
||||
IExecuteFunctions,
|
||||
ILoadOptionsFunctions,
|
||||
} from 'n8n-core';
|
||||
import { IExecuteFunctions, ILoadOptionsFunctions } from 'n8n-core';
|
||||
|
||||
import {
|
||||
ICredentialsDecrypted,
|
||||
@@ -39,7 +36,7 @@ export async function emeliaApiRequest(
|
||||
body: object = {},
|
||||
qs: object = {},
|
||||
) {
|
||||
const { apiKey } = await this.getCredentials('emeliaApi') as { apiKey: string };
|
||||
const { apiKey } = (await this.getCredentials('emeliaApi')) as { apiKey: string };
|
||||
|
||||
const options = {
|
||||
headers: {
|
||||
@@ -55,7 +52,7 @@ export async function emeliaApiRequest(
|
||||
try {
|
||||
return await this.helpers.request!.call(this, options);
|
||||
} catch (error) {
|
||||
throw new NodeApiError(this.getNode(), (error as JsonObject));
|
||||
throw new NodeApiError(this.getNode(), error as JsonObject);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,7 +63,7 @@ export async function loadResource(
|
||||
this: ILoadOptionsFunctions,
|
||||
resource: 'campaign' | 'contactList',
|
||||
): Promise<INodePropertyOptions[]> {
|
||||
const mapping: { [key in 'campaign' | 'contactList']: { query: string, key: string } } = {
|
||||
const mapping: { [key in 'campaign' | 'contactList']: { query: string; key: string } } = {
|
||||
campaign: {
|
||||
query: `
|
||||
query GetCampaigns {
|
||||
@@ -91,13 +88,18 @@ export async function loadResource(
|
||||
|
||||
const responseData = await emeliaGraphqlRequest.call(this, { query: mapping[resource].query });
|
||||
|
||||
return responseData.data[mapping[resource].key].map((campaign: { name: string, _id: string }) => ({
|
||||
name: campaign.name,
|
||||
value: campaign._id,
|
||||
}));
|
||||
return responseData.data[mapping[resource].key].map(
|
||||
(campaign: { name: string; _id: string }) => ({
|
||||
name: campaign.name,
|
||||
value: campaign._id,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
export async function emeliaApiTest(this: ICredentialTestFunctions, credential: ICredentialsDecrypted): Promise<INodeCredentialTestResult> {
|
||||
export async function emeliaApiTest(
|
||||
this: ICredentialTestFunctions,
|
||||
credential: ICredentialsDecrypted,
|
||||
): Promise<INodeCredentialTestResult> {
|
||||
const credentials = credential.data;
|
||||
|
||||
const body = {
|
||||
|
||||
Reference in New Issue
Block a user