matrix-dimension/src/db/NebStore.ts
Tony Stipanic 4954de2a96
Upgrade everything to Angular 12 and more + build changes
This is a very big commit that does an initial job of upgrading everything to the latest version. TSLint gets replaced by ESLint. Instead of plain node, now ts-node is being used. Old modules also get replaced with new ones (mostly ng2 to ngx). Also obsolete configs have been replaced with how it's used today with Angular.

This includes:

* Upgrade to:
** Angular 12
** Typescript 4
** ESLint 7 and replace TSLint
** Bootstrap 5
** Eerything connected to these
* Run with ts-node
* Convert wepack config to angular config
* Remove typescript-ioc
* Update tsconfigs
* Run a git command instead of using a library for sshort hash
* Move assets to a new location align with default Angular settings
* Database migration for new avatarUrl locations
* Simplify Model extension align with newest sequelize version
* Remove breadcrumb hack
* Fix homeserver typo
* A few general fixes that are necessary with newest Typescript rules
* Define Express.User interface
2021-08-29 19:39:43 +02:00

385 lines
17 KiB
TypeScript

import { NebConfig } from "../models/neb";
import NebConfiguration from "./models/NebConfiguration";
import NebIntegration from "./models/NebIntegration";
import Upstream from "./models/Upstream";
import AppService from "./models/AppService";
import { LogService } from "matrix-js-snippets";
import { NebClient } from "../neb/NebClient";
import NebBotUser from "./models/NebBotUser";
import NebNotificationUser from "./models/NebNotificationUser";
import { AppserviceStore } from "./AppserviceStore";
import config from "../config";
import { SimpleBot } from "../integrations/SimpleBot";
import { NebProxy } from "../neb/NebProxy";
import { ComplexBot } from "../integrations/ComplexBot";
export interface SupportedIntegration {
type: string;
name: string;
avatarUrl: string;
description: string;
}
export class NebStore {
private static INTEGRATIONS_MODULAR_SUPPORTED = ["giphy", "guggy", "github", "google", "imgur", "rss", "travisci", "wikipedia"];
private static INTEGRATIONS = {
// TODO: Support Circle CI
// "circleci": {
// name: "Circle CI",
// avatarUrl: "/assets/img/avatars/circleci.png",
// description: "Announces build results from Circle CI to the room.",
// simple: false,
// },
"echo": {
name: "Echo",
avatarUrl: "/assets/img/avatars/echo.png", // TODO: Make this image
description: "Repeats text given to it from !echo",
simple: true,
},
"giphy": {
name: "Giphy",
avatarUrl: "/assets/img/avatars/giphy.png",
description: "Posts a GIF from Giphy using !giphy <query>",
simple: true,
},
"guggy": {
name: "Guggy",
avatarUrl: "/assets/img/avatars/guggy.png",
description: "Send a reaction GIF using !guggy <query>",
simple: true,
},
// TODO: Support Github
// "github": {
// name: "Github",
// avatarUrl: "/assets/img/avatars/github.png",
// description: "Github issue management and announcements for a repository",
// simple: false,
// },
"google": {
name: "Google",
avatarUrl: "/assets/img/avatars/google.png",
description: "Searches Google Images using !google image <query>",
simple: true,
},
"imgur": {
name: "Imgur",
avatarUrl: "/assets/img/avatars/imgur.png",
description: "Searches and posts images from Imgur using !imgur <query>",
simple: true,
},
// TODO: Support JIRA
// "jira": {
// name: "Jira",
// avatarUrl: "/assets/img/avatars/jira.png",
// description: "Jira issue management and announcements for a project",
// simple: false,
// },
"rss": {
name: "RSS",
avatarUrl: "/assets/img/avatars/rssbot.png",
description: "Announces changes to RSS feeds in the room",
simple: false,
},
"travisci": {
name: "Travis CI",
avatarUrl: "/assets/img/avatars/travisci.png",
description: "Announces build results from Travis CI to the room",
simple: false,
},
"wikipedia": {
name: "Wikipedia",
avatarUrl: "/assets/img/avatars/wikipedia.png",
description: "Searches wikipedia using !wikipedia <query>",
simple: true,
},
};
private static async listEnabledNebBots(simple: boolean): Promise<{ neb: NebConfig, integration: NebIntegration }[]> {
const nebConfigs = await NebStore.getAllConfigs();
const integrations: { neb: NebConfig, integration: NebIntegration }[] = [];
const hasTypes: string[] = [];
for (const neb of nebConfigs) {
for (const integration of neb.dbIntegrations) {
if (!integration.isEnabled) continue;
const metadata = NebStore.INTEGRATIONS[integration.type];
if (!metadata || metadata.simple !== simple) continue;
if (hasTypes.indexOf(integration.type) !== -1) continue;
integrations.push({neb, integration});
hasTypes.push(integration.type);
}
}
return integrations;
}
public static async listEnabledNebSimpleBots(): Promise<{ neb: NebConfig, integration: NebIntegration }[]> {
return NebStore.listEnabledNebBots(true);
}
public static async listEnabledNebComplexBots(): Promise<{ neb: NebConfig, integration: NebIntegration }[]> {
return NebStore.listEnabledNebBots(false);
}
public static async listSimpleBots(requestingUserId: string): Promise<SimpleBot[]> {
const rawIntegrations = await NebStore.listEnabledNebSimpleBots();
return Promise.all(rawIntegrations.map(async i => {
const proxy = new NebProxy(i.neb, requestingUserId);
let userId = null;
try {
userId = await proxy.getBotUserId(i.integration);
} catch (e) {
LogService.error("NebStore", e);
}
const bot = SimpleBot.fromNeb(i.integration, userId);
bot.isOnline = !!userId;
return bot;
}));
}
public static async listComplexBots(requestingUserId: string, roomId: string): Promise<ComplexBot[]> {
const rawIntegrations = await NebStore.listEnabledNebComplexBots();
return Promise.all(rawIntegrations.map(async i => {
const proxy = new NebProxy(i.neb, requestingUserId);
try {
const notifUserId = await proxy.getNotificationUserId(i.integration, roomId);
const botUserId = null; // TODO: For github
const botConfig = await proxy.getServiceConfiguration(i.integration, roomId);
const bot = new ComplexBot(i.integration, notifUserId, botUserId, botConfig);
bot.isOnline = !!bot.notificationUserId;
return bot;
} catch (e) {
LogService.error("NebStore", e);
const bot = new ComplexBot(i.integration, null, null, null);
bot.isOnline = false;
return bot;
}
}));
}
public static async setComplexBotConfig(requestingUserId: string, type: string, roomId: string, newConfig: any): Promise<any> {
const rawIntegrations = await NebStore.listEnabledNebComplexBots();
const integration = rawIntegrations.find(i => i.integration.type === type);
if (!integration) throw new Error("Integration not found");
const proxy = new NebProxy(integration.neb, requestingUserId);
return proxy.setServiceConfiguration(integration.integration, roomId, newConfig);
}
public static async removeSimpleBot(type: string, roomId: string, requestingUserId: string): Promise<any> {
const rawIntegrations = await NebStore.listEnabledNebSimpleBots();
const integration = rawIntegrations.find(i => i.integration.type === type);
if (!integration) throw new Error("Integration not found");
const proxy = new NebProxy(integration.neb, requestingUserId);
return proxy.removeBotFromRoom(integration.integration, roomId);
}
public static async getAllConfigs(): Promise<NebConfig[]> {
const configs = await NebConfiguration.findAll();
return Promise.all((configs || []).map(c => NebStore.getConfig(c.id)));
}
public static async getConfig(id: number): Promise<NebConfig> {
const nebConfig = await NebConfiguration.findByPk(id);
if (!nebConfig) throw new Error("Configuration not found");
const integrations = await NebIntegration.findAll({where: {nebId: id}});
const fullIntegrations = await NebStore.getCompleteIntegrations(nebConfig, integrations);
return new NebConfig(nebConfig, fullIntegrations);
}
public static async createForUpstream(upstreamId: number): Promise<NebConfig> {
const upstream = await Upstream.findByPk(upstreamId);
if (!upstream) throw new Error("Upstream not found");
const nebConfig = await NebConfiguration.create({
upstreamId: upstream.id,
});
return NebStore.getConfig(nebConfig.id);
}
public static async createForAppservice(appserviceId: string, adminUrl: string): Promise<NebConfig> {
const appservice = await AppService.findByPk(appserviceId);
if (!appservice) throw new Error("Appservice not found");
const nebConfig = await NebConfiguration.create({
appserviceId: appservice.id,
adminUrl: adminUrl,
});
return NebStore.getConfig(nebConfig.id);
}
public static async getOrCreateIntegration(configurationId: number, integrationType: string): Promise<NebIntegration> {
if (!NebStore.INTEGRATIONS[integrationType]) throw new Error("Integration not supported");
const nebConfig = await NebConfiguration.findByPk(configurationId);
if (!nebConfig) throw new Error("Configuration not found");
let integration = await NebIntegration.findOne({where: {nebId: nebConfig.id, type: integrationType}});
if (!integration) {
LogService.info("NebStore", "Creating integration " + integrationType + " for NEB " + configurationId);
integration = await NebIntegration.create({
type: integrationType,
name: NebStore.INTEGRATIONS[integrationType].name,
avatarUrl: NebStore.INTEGRATIONS[integrationType].avatarUrl,
description: NebStore.INTEGRATIONS[integrationType].description,
isEnabled: false,
isPublic: true,
nebId: configurationId,
});
}
return integration;
}
public static async getCompleteIntegrations(nebConfig: NebConfiguration, knownIntegrations: NebIntegration[]): Promise<NebIntegration[]> {
const supported = NebStore.getSupportedIntegrations(nebConfig);
const notSupported: SupportedIntegration[] = [];
for (const supportedIntegration of supported) {
let isSupported = false;
for (const integration of knownIntegrations) {
if (integration.type === supportedIntegration.type) {
isSupported = true;
break;
}
}
if (!isSupported) notSupported.push(supportedIntegration);
}
const promises = [];
for (const missingIntegration of notSupported) {
promises.push(NebStore.getOrCreateIntegration(nebConfig.id, missingIntegration.type));
}
return Promise.all(promises).then(addedIntegrations => (addedIntegrations || []).concat(knownIntegrations));
}
public static getSupportedIntegrations(nebConfig: NebConfiguration): SupportedIntegration[] {
const result = [];
for (const type of Object.keys(NebStore.INTEGRATIONS)) {
if (nebConfig.upstreamId && NebStore.INTEGRATIONS_MODULAR_SUPPORTED.indexOf(type) === -1) continue;
const integrationConfig = JSON.parse(JSON.stringify(NebStore.INTEGRATIONS[type]));
integrationConfig["type"] = type;
result.push(integrationConfig);
}
return result;
}
public static async setIntegrationEnabled(configurationId: number, integrationType: string, isEnabled: boolean): Promise<any> {
const integration = await this.getOrCreateIntegration(configurationId, integrationType);
integration.isEnabled = isEnabled;
await integration.save();
const neb = await this.getConfig(configurationId);
if (!neb.appserviceId) return; // Done - nothing to do from here
const client = new NebClient(neb);
const botUsers = await NebBotUser.findAll({where: {integrationId: integration.id}});
for (const user of botUsers) {
await client.updateUser(user.appserviceUserId, isEnabled, true);
}
const notificationUsers = await NebNotificationUser.findAll({where: {integrationId: integration.id}});
for (const user of notificationUsers) {
await client.updateUser(user.appserviceUserId, isEnabled, false);
}
}
public static async getOrCreateBotUser(configurationId: number, integrationType: string): Promise<NebBotUser> {
const neb = await NebStore.getConfig(configurationId);
if (!neb.appserviceId) throw new Error("Instance not bound to an appservice");
const integration = await this.getOrCreateIntegration(configurationId, integrationType);
const users = await NebBotUser.findAll({where: {integrationId: integration.id}});
if (!users || users.length === 0) {
const appservice = await AppserviceStore.getAppservice(neb.appserviceId);
const userId = "@" + appservice.userPrefix + "_" + integrationType + ":" + config.homeserver.name;
const appserviceUser = await AppserviceStore.getOrCreateUser(neb.appserviceId, userId);
const client = new NebClient(neb);
await client.updateUser(userId, integration.isEnabled, true); // creates the user in go-neb
client.updateUserProfile(userId, integration.name, config.goneb.avatars[integration.type]).then(() => {
LogService.info("NebStore", "Updated profile for " + userId);
}).catch(err => {
LogService.error("NebStore", "Error updating profile for " + userId);
LogService.error("NebStore", err);
});
const serviceId = appservice.id + "_integration_" + integrationType;
return NebBotUser.create({
serviceId: serviceId,
appserviceUserId: appserviceUser.id,
integrationId: integration.id,
});
}
return users[0];
}
public static async getOrCreateNotificationUser(configurationId: number, integrationType: string, forUserId: string): Promise<NebNotificationUser> {
const neb = await NebStore.getConfig(configurationId);
if (!neb.appserviceId) throw new Error("Instance not bound to an appservice");
const integration = await this.getOrCreateIntegration(configurationId, integrationType);
const users = await NebNotificationUser.findAll({where: {integrationId: integration.id, ownerId: forUserId}});
if (!users || users.length === 0) {
const safeUserId = AppserviceStore.getSafeUserId(forUserId);
const appservice = await AppserviceStore.getAppservice(neb.appserviceId);
const userId = "@" + appservice.userPrefix + "_" + integrationType + "_notifications_" + safeUserId + ":" + config.homeserver.name;
const appserviceUser = await AppserviceStore.getOrCreateUser(neb.appserviceId, userId);
const client = new NebClient(neb);
await client.updateUser(userId, integration.isEnabled, false); // creates the user in go-neb
client.updateUserProfile(userId, integration.name + " Notifications [" + forUserId + "]", config.goneb.avatars[integration.type]).then(() => {
LogService.info("NebStore", "Updated profile for " + userId);
}).catch(err => {
LogService.error("NebStore", "Error updating profile for " + userId);
LogService.error("NebStore", err);
});
const serviceId = appservice.id + "_integration_" + integrationType + "_notifications_" + safeUserId;
return NebNotificationUser.create({
serviceId: serviceId,
appserviceUserId: appserviceUser.id,
integrationId: integration.id,
ownerId: forUserId,
});
}
return users[0];
}
public static async setIntegrationConfig(configurationId: number, integrationType: string, newConfig: any): Promise<any> {
const botUser = await NebStore.getOrCreateBotUser(configurationId, integrationType);
const neb = await NebStore.getConfig(configurationId);
const client = new NebClient(neb);
return client.setServiceConfig(botUser.serviceId, botUser.appserviceUserId, integrationType, newConfig);
}
public static async getIntegrationConfig(configurationId: number, integrationType: string): Promise<any> {
const botUser = await NebStore.getOrCreateBotUser(configurationId, integrationType);
const neb = await NebStore.getConfig(configurationId);
const client = new NebClient(neb);
return client.getServiceConfig(botUser.serviceId);
}
private constructor() {
}
}