Skip to content

Commit

Permalink
feat(ElasticSearch): Postgres synchronization
Browse files Browse the repository at this point in the history
  • Loading branch information
Betree committed Dec 6, 2024
1 parent 7e99cc8 commit 7b88581
Show file tree
Hide file tree
Showing 20 changed files with 467 additions and 54 deletions.
4 changes: 4 additions & 0 deletions config/default.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
{
"port": "3060",
"services": {
"server": true,
"searchSync": true
},
"mailpit": {
"client": false
},
Expand Down
4 changes: 4 additions & 0 deletions config/test.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
{
"port": "3061",
"services": {
"server": true,
"searchSync": false
},
"database": {
"url": "postgres://opencollective@127.0.0.1:5432/opencollective_test"
},
Expand Down
21 changes: 21 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@
"pg": "8.13.1",
"pg-connection-string": "2.7.0",
"pg-format": "1.0.4",
"pg-listen": "1.7.0",
"plaid": "29.0.0",
"prepend-http": "3.0.1",
"redis": "4.6.6",
Expand Down
25 changes: 17 additions & 8 deletions server/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,16 @@ import config from 'config';
import express from 'express';
import throng from 'throng';

import { startElasticSearchPostgresSync } from './lib/elastic-search/sync-postgres';
import expressLib from './lib/express';
import logger from './lib/logger';
import { updateCachedFidoMetadata } from './lib/two-factor-authentication/fido-metadata';
import { parseToBoolean } from './lib/utils';
import routes from './routes';

const workers = process.env.WEB_CONCURRENCY || 1;

async function start(i) {
async function startExpressServer(workerId) {
const expressApp = express();

await updateCachedFidoMetadata();
Expand All @@ -35,7 +37,7 @@ async function start(i) {
host,
server.address().port,
config.env,
i,
workerId,
);
});

Expand All @@ -45,15 +47,22 @@ async function start(i) {
return expressApp;
}

// Start the express server
let app;
if (parseToBoolean(config.services.server)) {
if (['production', 'staging'].includes(config.env) && workers > 1) {
throng({ worker: startExpressServer, count: workers });
} else {
app = startExpressServer(1);
}
}

if (['production', 'staging'].includes(config.env) && workers > 1) {
throng({ worker: start, count: workers });
} else {
app = start(1);
// Start the search sync job
if (parseToBoolean(config.services.searchSync)) {
startElasticSearchPostgresSync();
}

// This is used by tests
export default async function () {
return app ? app : start(1);
export default async function startServerForTest() {
return app ? app : parseToBoolean(config.services.server) ? startExpressServer(1) : null;
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import models, { Op } from '../../../models';
import { Op } from '../../../models';
import Collective from '../../../models/Collective';
import { stripHTMLOrEmpty } from '../../sanitize-html';
import { ElasticSearchIndexName } from '../constants';

import { ElasticSearchModelAdapter } from './ElasticSearchModelAdapter';

export class ElasticSearchCollectivesAdapter implements ElasticSearchModelAdapter {
public readonly model = models.Collective;
public readonly model = Collective;
public readonly index = ElasticSearchIndexName.COLLECTIVES;
public readonly mappings = {
properties: {
Expand Down Expand Up @@ -38,8 +39,8 @@ export class ElasticSearchCollectivesAdapter implements ElasticSearchModelAdapte
maxId?: number;
ids?: number[];
} = {},
): Promise<Array<InstanceType<typeof models.Collective>>> {
return models.Collective.findAll({
): Promise<Array<InstanceType<typeof Collective>>> {
return Collective.findAll({
attributes: Object.keys(this.mappings.properties),
order: [['id', 'DESC']],
limit: options.limit,
Expand All @@ -54,7 +55,7 @@ export class ElasticSearchCollectivesAdapter implements ElasticSearchModelAdapte
}

public mapModelInstanceToDocument(
instance: InstanceType<typeof models.Collective>,
instance: InstanceType<typeof Collective>,
): Record<keyof (typeof this.mappings)['properties'], unknown> {
return {
id: instance.id,
Expand All @@ -72,7 +73,7 @@ export class ElasticSearchCollectivesAdapter implements ElasticSearchModelAdapte
isActive: instance.isActive,
isHostAccount: instance.isHostAccount,
deactivatedAt: instance.deactivatedAt,
HostCollectiveId: instance.HostCollectiveId,
HostCollectiveId: !instance.isActive ? null : instance.HostCollectiveId,
ParentCollectiveId: instance.ParentCollectiveId,
};
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,14 @@ import { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/types';
import { omit } from 'lodash';
import { Op } from 'sequelize';

import models from '../../../models';
import { CommentType } from '../../../models/Comment';
import Comment, { CommentType } from '../../../models/Comment';
import { stripHTMLOrEmpty } from '../../sanitize-html';
import { ElasticSearchIndexName } from '../constants';

import { ElasticSearchModelAdapter } from './ElasticSearchModelAdapter';

export class ElasticSearchCommentsAdapter implements ElasticSearchModelAdapter {
public readonly model = models.Comment;
public readonly model = Comment;
public readonly index = ElasticSearchIndexName.COMMENTS;
public readonly mappings = {
properties: {
Expand Down Expand Up @@ -38,7 +37,7 @@ export class ElasticSearchCommentsAdapter implements ElasticSearchModelAdapter {
ids?: number[];
} = {},
) {
return models.Comment.findAll({
return Comment.findAll({
attributes: omit(Object.keys(this.mappings.properties), ['HostCollectiveId', 'ParentCollectiveId']),
order: [['id', 'DESC']],
limit: options.limit,
Expand All @@ -52,7 +51,7 @@ export class ElasticSearchCommentsAdapter implements ElasticSearchModelAdapter {
{
association: 'collective',
required: true,
attributes: ['HostCollectiveId', 'ParentCollectiveId'],
attributes: ['isActive', 'HostCollectiveId', 'ParentCollectiveId'],
},
{
association: 'expense',
Expand All @@ -69,7 +68,7 @@ export class ElasticSearchCommentsAdapter implements ElasticSearchModelAdapter {
}

public mapModelInstanceToDocument(
instance: InstanceType<typeof models.Comment>,
instance: InstanceType<typeof Comment>,
): Record<keyof (typeof this.mappings)['properties'], unknown> {
return {
id: instance.id,
Expand All @@ -84,7 +83,7 @@ export class ElasticSearchCommentsAdapter implements ElasticSearchModelAdapter {
HostCollectiveId:
instance.expense?.HostCollectiveId ??
instance.hostApplication?.HostCollectiveId ??
instance.collective.HostCollectiveId,
(!instance.collective.isActive ? null : instance.collective.HostCollectiveId),
};
}

Expand Down
20 changes: 14 additions & 6 deletions server/lib/elastic-search/adapters/ElasticSearchExpensesAdapter.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import { omit } from 'lodash';
import { Op } from 'sequelize';

import models from '../../../models';
import Expense from '../../../models/Expense';
import { stripHTMLOrEmpty } from '../../sanitize-html';
import { ElasticSearchIndexName } from '../constants';

import { ElasticSearchModelAdapter } from './ElasticSearchModelAdapter';

export class ElasticSearchExpensesAdapter implements ElasticSearchModelAdapter {
public readonly model = models.Expense;
public readonly model = Expense;
public readonly index = ElasticSearchIndexName.EXPENSES;
public readonly mappings = {
properties: {
Expand All @@ -23,6 +23,7 @@ export class ElasticSearchExpensesAdapter implements ElasticSearchModelAdapter {
amount: { type: 'integer' },
currency: { type: 'keyword' },
status: { type: 'keyword' },
items: { type: 'text' },
// Relationships
UserId: { type: 'keyword' },
CollectiveId: { type: 'keyword' },
Expand All @@ -42,7 +43,7 @@ export class ElasticSearchExpensesAdapter implements ElasticSearchModelAdapter {
ids?: number[];
} = {},
) {
return models.Expense.findAll({
return Expense.findAll({
attributes: omit(Object.keys(this.mappings.properties), ['ParentCollectiveId']),
order: [['id', 'DESC']],
limit: options.limit,
Expand All @@ -56,14 +57,19 @@ export class ElasticSearchExpensesAdapter implements ElasticSearchModelAdapter {
{
association: 'collective',
required: true,
attributes: ['HostCollectiveId', 'ParentCollectiveId'],
attributes: ['isActive', 'HostCollectiveId', 'ParentCollectiveId'],
},
{
association: 'items',
required: true,
attributes: ['description'],
},
],
});
}

public mapModelInstanceToDocument(
instance: InstanceType<typeof models.Expense>,
instance: InstanceType<typeof Expense>,
): Record<keyof (typeof this.mappings)['properties'], unknown> {
return {
id: instance.id,
Expand All @@ -74,14 +80,16 @@ export class ElasticSearchExpensesAdapter implements ElasticSearchModelAdapter {
privateMessage: stripHTMLOrEmpty(instance.privateMessage),
invoiceInfo: instance.invoiceInfo,
reference: instance.reference,
items: instance.items.map(item => item.description).join(' '),
amount: instance.amount,
currency: instance.currency,
status: instance.status,
CollectiveId: instance.CollectiveId,
ParentCollectiveId: instance.collective.ParentCollectiveId,
FromCollectiveId: instance.FromCollectiveId,
UserId: instance.UserId,
HostCollectiveId: instance.HostCollectiveId || instance.collective.HostCollectiveId,
HostCollectiveId:
instance.HostCollectiveId || (!instance.collective.isActive ? null : instance.collective.HostCollectiveId),
};
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import { omit } from 'lodash';
import { Op } from 'sequelize';

import models from '../../../models';
import HostApplication from '../../../models/HostApplication';
import { ElasticSearchIndexName } from '../constants';

import { ElasticSearchModelAdapter } from './ElasticSearchModelAdapter';

export class ElasticSearchHostApplicationsAdapter implements ElasticSearchModelAdapter {
public readonly model = models.HostApplication;
public readonly model = HostApplication;
public readonly index = ElasticSearchIndexName.HOST_APPLICATIONS;
public readonly mappings = {
properties: {
Expand All @@ -33,7 +33,7 @@ export class ElasticSearchHostApplicationsAdapter implements ElasticSearchModelA
ids?: number[];
} = {},
) {
return models.HostApplication.findAll({
return HostApplication.findAll({
attributes: omit(Object.keys(this.mappings.properties), ['ParentCollectiveId']),
order: [['id', 'DESC']],
limit: options.limit,
Expand All @@ -54,7 +54,7 @@ export class ElasticSearchHostApplicationsAdapter implements ElasticSearchModelA
}

public mapModelInstanceToDocument(
instance: InstanceType<typeof models.HostApplication>,
instance: InstanceType<typeof HostApplication>,
): Record<keyof (typeof this.mappings)['properties'], unknown> {
return {
id: instance.id,
Expand Down
12 changes: 6 additions & 6 deletions server/lib/elastic-search/adapters/ElasticSearchOrdersAdapter.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import { omit } from 'lodash';
import { Op } from 'sequelize';

import models from '../../../models';
import Order from '../../../models/Order';
import { ElasticSearchIndexName } from '../constants';

import { ElasticSearchModelAdapter } from './ElasticSearchModelAdapter';

export class ElasticSearchOrdersAdapter implements ElasticSearchModelAdapter {
public readonly model = models.Order;
public readonly model = Order;
public readonly index = ElasticSearchIndexName.ORDERS;
public readonly mappings = {
properties: {
Expand All @@ -33,7 +33,7 @@ export class ElasticSearchOrdersAdapter implements ElasticSearchModelAdapter {
ids?: number[];
} = {},
) {
return models.Order.findAll({
return Order.findAll({
attributes: omit(Object.keys(this.mappings.properties), ['HostCollectiveId', 'ParentCollectiveId']),
order: [['id', 'DESC']],
limit: options.limit,
Expand All @@ -47,14 +47,14 @@ export class ElasticSearchOrdersAdapter implements ElasticSearchModelAdapter {
{
association: 'collective',
required: true,
attributes: ['HostCollectiveId', 'ParentCollectiveId'],
attributes: ['isActive', 'HostCollectiveId', 'ParentCollectiveId'],
},
],
});
}

public mapModelInstanceToDocument(
instance: InstanceType<typeof models.Order>,
instance: InstanceType<typeof Order>,
): Record<keyof (typeof this.mappings)['properties'], unknown> {
return {
id: instance.id,
Expand All @@ -63,7 +63,7 @@ export class ElasticSearchOrdersAdapter implements ElasticSearchModelAdapter {
description: instance.description,
CollectiveId: instance.CollectiveId,
FromCollectiveId: instance.FromCollectiveId,
HostCollectiveId: instance.collective.HostCollectiveId,
HostCollectiveId: !instance.collective.isActive ? null : instance.collective.HostCollectiveId,
ParentCollectiveId: instance.collective.ParentCollectiveId,
};
}
Expand Down
Loading

0 comments on commit 7b88581

Please sign in to comment.