🧹 Propose to remove channels, notifications, knowledge-graph and phpnode (#55)

* Propose to remove channels and notifications

* Propose to remove knowledge graph too

* Put back tags test

* FIxing tests

* FIx query builder test
This commit is contained in:
Romaric Mourgues
2023-05-16 14:36:39 +02:00
committed by GitHub
parent 65dc733855
commit a378cd13e1
102 changed files with 327 additions and 9224 deletions
@@ -1,4 +1,3 @@
import { Channel } from "../../../../services/channels/entities/channel";
import Company from "../../../../services/user/entities/company";
import Workspace from "../../../../services/workspaces/entities/workspace";
import User from "../../../../services/user/entities/user";
@@ -7,7 +6,6 @@ export type EmailBuilderDataPayload = {
user: User;
company: Company;
notifications: {
channel: Channel;
workspace: Workspace;
}[];
};
@@ -1,151 +0,0 @@
import axios, { AxiosInstance } from "axios";
import { KnowledgeGraphCreateBodyRequest, KnowledgeGraphCreateMessageObjectData } from "./types";
import { md5 } from "../../../../core/crypto";
import { Channel } from "../../../../services/channels/entities";
import gr from "../../../../services/global-resolver";
import Company from "../../../../services/user/entities/company";
import User from "../../../../services/user/entities/user";
import Workspace from "../../../../services/workspaces/entities/workspace";
import { getLogger, TdriveLogger } from "../../framework";
export default class KnowledgeGraphAPIClient {
protected readonly version = "1.0.0";
protected readonly axiosInstance: AxiosInstance = axios.create();
readonly apiUrl: string;
readonly logger: TdriveLogger = getLogger("knowledge-graph-api-client");
constructor(apiUrl: string) {
this.apiUrl = apiUrl;
}
private async getUserKGId(id: string, provider_id?: string) {
provider_id = provider_id || (await gr.services.users.get({ id }))?.identity_provider_id;
return provider_id;
}
private async getUserKGMailId(id: string, email?: string) {
email = email || (await gr.services.users.get({ id }))?.email_canonical;
return md5(email.trim().toLocaleLowerCase());
}
private async getCompanyKGId(id: string, identity_provider_id?: string) {
identity_provider_id =
identity_provider_id ||
(await gr.services.companies.getCompany({ id }))?.identity_provider_id ||
id;
return identity_provider_id;
}
public async onCompanyCreated(company: Partial<Company>): Promise<void> {
this.send({
records: [
{
key: "null",
value: {
id: "Company",
properties: {
_kg_company_id: await this.getCompanyKGId(company.id, company.identity_provider_id),
company_id: company.id,
company_name: company.displayName || company.name,
},
},
},
],
});
}
public async onWorkspaceCreated(workspace: Partial<Workspace>): Promise<void> {
const response = await this.send({
records: [
{
key: "null",
value: {
id: "Workspace",
properties: {
_kg_company_id: await this.getCompanyKGId(workspace.company_id),
company_id: workspace.company_id,
workspace_name: workspace.name,
workspace_id: workspace.id,
},
},
},
],
});
if (response.statusText === "OK") {
this.logger.info("onWorkspaceCreated %o", response.config.data);
}
}
public async onUserCreated(companyId: string, user: Partial<User>): Promise<void> {
const response = await this.send({
records: [
{
key: "null",
value: {
id: "User",
properties: {
_kg_user_id: await this.getUserKGId(user.id, user.identity_provider_id),
_kg_email_id: await this.getUserKGMailId(user.id, user.email_canonical),
_kg_company_all_id: await Promise.all(
user.cache.companies.map(async c => await this.getCompanyKGId(c)),
),
user_id: user.id,
email: user.email_canonical,
username: user.username_canonical,
user_last_activity: user.last_activity,
first_name: user.first_name,
user_created_at: user.creation_date,
last_name: user.last_name,
company_id: companyId,
},
},
},
],
});
if (response.statusText === "OK") {
this.logger.info("onUserCreated %o", response.config.data);
}
}
public async onChannelCreated(channel: Partial<Channel>): Promise<void> {
const response = await this.send({
records: [
{
key: "null",
value: {
id: "Channel",
properties: {
_kg_user_id: await this.getUserKGId(channel.owner),
_kg_email_id: await this.getUserKGMailId(channel.owner),
_kg_company_id: await this.getCompanyKGId(channel.company_id),
channel_id: channel.id,
channel_name: channel.name,
channel_owner: channel.owner,
workspace_id: channel.workspace_id,
company_id: channel.company_id,
},
},
},
],
});
if (response.statusText === "OK") {
this.logger.info("onChannelCreated %o", response.config.data);
}
}
private async send(data: any) {
return await this.axiosInstance.post<
KnowledgeGraphCreateBodyRequest<KnowledgeGraphCreateMessageObjectData[]>
>(`${this.apiUrl}/topics/tdrive`, data, {
headers: {
"Content-Type": "application/vnd.kafka.json.v2+json",
Accept: "application/vnd.kafka.v2+json",
},
});
}
}
@@ -1,144 +0,0 @@
import { Configuration, Consumes, getLogger, TdriveLogger, TdriveService } from "../../framework";
import { localEventBus } from "../../framework/event-bus";
import KnowledgeGraphAPI from "./provider";
import Workspace from "../../../../services/workspaces/entities/workspace";
import Company from "../../../../services/user/entities/company";
import User from "../../../../services/user/entities/user";
import { Channel } from "../../../../services/channels/entities";
import {
KnowledgeGraphGenericEventPayload,
KnowledgeGraphEvents,
KnowledgeGraphCallbackEvent,
} from "./types";
import KnowledgeGraphAPIClient from "./api-client";
import gr from "../../../../services/global-resolver";
@Consumes([])
export default class KnowledgeGraphService
extends TdriveService<KnowledgeGraphAPI>
implements KnowledgeGraphAPI
{
readonly name = "knowledge-graph";
readonly version = "1.0.0";
protected kgAPIClient: KnowledgeGraphAPIClient = this.getKnowledgeGraphApiClient();
logger: TdriveLogger = getLogger("knowledge-graph-service");
async doInit(): Promise<this> {
const use = this.getConfigurationEntry<boolean>("use");
if (!use) {
this.logger.warn("Knowledge graph is not used");
return this;
}
localEventBus.subscribe<KnowledgeGraphGenericEventPayload<Company>>(
KnowledgeGraphEvents.COMPANY_UPSERT,
this.onCompanyCreated.bind(this),
);
localEventBus.subscribe<KnowledgeGraphGenericEventPayload<Workspace>>(
KnowledgeGraphEvents.WORKSPACE_UPSERT,
this.onWorkspaceCreated.bind(this),
);
localEventBus.subscribe<KnowledgeGraphGenericEventPayload<Channel>>(
KnowledgeGraphEvents.CHANNEL_UPSERT,
this.onChannelCreated.bind(this),
);
localEventBus.subscribe<KnowledgeGraphGenericEventPayload<User>>(
KnowledgeGraphEvents.USER_UPSERT,
this.onUserCreated.bind(this),
);
return this;
}
/** When the KG service send us new events */
async onCallbackEvent(token: string, data: KnowledgeGraphCallbackEvent): Promise<void> {
if (token === this.getConfigurationEntry<string>("callback_token")) {
this.logger.info("Unimplemented: KnowledgeGraph - Callback event", data);
} else {
throw new Error("Invalid token");
}
}
async onCompanyCreated(data: KnowledgeGraphGenericEventPayload<Company>): Promise<void> {
this.logger.info(`${KnowledgeGraphEvents.COMPANY_UPSERT} %o`, data);
if (this.kgAPIClient && (await this.shouldForwardEvent([data.resource.id]))) {
this.kgAPIClient.onCompanyCreated(data.resource);
}
}
async onWorkspaceCreated(data: KnowledgeGraphGenericEventPayload<Workspace>): Promise<void> {
this.logger.info(`${KnowledgeGraphEvents.WORKSPACE_UPSERT} %o`, data);
if (this.kgAPIClient && (await this.shouldForwardEvent([data.resource.company_id]))) {
this.kgAPIClient.onWorkspaceCreated(data.resource);
}
}
async onChannelCreated(data: KnowledgeGraphGenericEventPayload<Channel>): Promise<void> {
this.logger.info(`${KnowledgeGraphEvents.CHANNEL_UPSERT} %o`, data);
if (this.kgAPIClient && (await this.shouldForwardEvent([data.resource.company_id]))) {
this.kgAPIClient.onChannelCreated(data.resource);
}
}
async onUserCreated(data: KnowledgeGraphGenericEventPayload<User>): Promise<void> {
this.logger.info(`${KnowledgeGraphEvents.USER_UPSERT} %o`, data);
if (
this.kgAPIClient &&
(await this.shouldForwardEvent(data.resource.cache?.companies || [], data.resource.id))
) {
for (const companyId of data.resource.cache?.companies || []) {
this.kgAPIClient.onUserCreated(companyId, data.resource);
}
}
}
private getConfigurationEntry<T>(key: string): T {
const configuration = new Configuration("knowledge-graph");
return configuration.get(key);
}
private getKnowledgeGraphApiClient(): KnowledgeGraphAPIClient {
const endpoint = this.getConfigurationEntry<string>("endpoint");
if (endpoint && endpoint.length) {
this.kgAPIClient = new KnowledgeGraphAPIClient(endpoint);
} else {
this.logger.info("KnowledgeGraph - No endpoint defined in default.json");
}
return this.kgAPIClient;
}
async shouldForwardEvent(
companyIds: string[] | null,
userId?: string,
): Promise<false | "all" | "metadata"> {
const user = userId ? await gr.services.users.get({ id: userId }) : null;
const forwardedCompanies = this.getConfigurationEntry<string[]>("forwarded_companies");
const isCompanyForwarded = !!(companyIds || []).find(v => forwardedCompanies.includes(v));
if (user?.preferences && !user.preferences.knowledge_graph)
user.preferences.knowledge_graph = "metadata";
return (!userId || (user && user.preferences?.knowledge_graph !== "nothing")) &&
(!companyIds ||
companyIds.length === 0 ||
isCompanyForwarded ||
forwardedCompanies.length === 0)
? user
? (user.preferences.knowledge_graph as "all" | "metadata")
: "all"
: false;
}
api(): KnowledgeGraphAPI {
return this;
}
}
@@ -1,13 +0,0 @@
import Company from "../../../../services/user/entities/company";
import { Channel } from "../../../../services/channels/entities";
import { TdriveServiceProvider } from "../../framework";
import { KnowledgeGraphGenericEventPayload } from "./types";
import Workspace from "../../../../services/workspaces/entities/workspace";
import User from "../../../../services/user/entities/user";
export default interface KnowledgeGraphAPI extends TdriveServiceProvider {
onCompanyCreated(data: KnowledgeGraphGenericEventPayload<Company>): void;
onWorkspaceCreated(data: KnowledgeGraphGenericEventPayload<Workspace>): void;
onChannelCreated(data: KnowledgeGraphGenericEventPayload<Channel>): void;
onUserCreated(data: KnowledgeGraphGenericEventPayload<User>): void;
}
@@ -1,109 +0,0 @@
export type KnowledgeGraphCreateCompanyObjectData = {
key: string;
value: {
id: string;
properties: {
company_id: string;
company_name: string;
};
};
};
export type KnowledgeGraphCreateWorkspaceObjectData = {
key: string;
value: {
id: string;
properties: {
company_id: string;
workspace_name: string;
workspace_id: string;
};
};
};
export type KnowledgeGraphCreateUserObjectData = {
key: string;
value: {
id: string;
properties: {
user_id: string;
email: string;
username: string;
user_last_activity: string;
first_name: string;
user_created_at: string;
last_name: string;
company_id: string;
};
};
};
export type KnowledgeGraphCreateChannelObjectData = {
key: string;
value: {
id: string;
properties: {
channel_id: string;
channel_name: string;
channel_owner: string;
workspace_id: string;
};
};
};
export type KnowledgeGraphCreateMessageObjectData = {
key: string;
value: {
id: string;
properties: {
message_thread_id: string;
message_created_at: string;
message_content: string;
type_message: string;
message_updated_at: string;
user_id: string;
channel_id: string;
};
};
};
export type KnowledgeGraphCreateBodyRequest<T> = {
records: T;
};
export type KnowledgeGraphRelationLinkObject = {
relation: "mention" | "sender" | "parent" | "children" | "owner";
type: "user" | "channel" | "workspace" | "company" | "message";
id: string;
};
export type KnowledgeGraphGenericEventPayload<T> = {
id: string;
resource: Partial<T>;
links: KnowledgeGraphRelationLinkObject[];
};
export enum KnowledgeGraphEvents {
COMPANY_UPSERT = "kg:company:upsert",
WORKSPACE_UPSERT = "kg:workspace:upsert",
CHANNEL_UPSERT = "kg:channel:upsert",
MESSAGE_UPSERT = "kg:message:upsert",
USER_UPSERT = "kg:user:upsert",
}
export type KnowledgeGraphCallbackEvent = {
recipients: {
type: "user";
id: string; // KG user id which is a md5 of the email
}[];
event: {
type: "user_tags"; //More events will be added later
data: {
//For user_tags event only
tags?: {
value: string;
weight: number;
}[];
};
};
};
@@ -1,189 +0,0 @@
import { FastifyRequest, RouteHandlerMethod } from "fastify";
import { FastifyInstance } from "fastify/types/instance";
import { IncomingMessage, Server, ServerResponse } from "http";
import {
ChannelCrudController,
ChannelMemberCrudController,
} from "../../../../services/channels/web/controllers";
import {
ChannelMemberParameters,
ChannelParameters,
CreateChannelBody,
} from "../../../../services/channels/web/types";
import { Consumes, TdriveService } from "../../framework";
import WebServerAPI from "../webserver/provider";
import WebSocketAPI from "../websocket/provider";
import PhpNodeAPI from "./provider";
import { RealtimeServiceAPI } from "../realtime/api";
import gr from "../../../../services/global-resolver";
@Consumes(["webserver", "websocket", "user", "channels"])
export default class PhpNodeService extends TdriveService<PhpNodeAPI> implements PhpNodeAPI {
name = "phpnode";
version = "1";
private server: FastifyInstance<Server, IncomingMessage, ServerResponse>;
private ws: WebSocketAPI;
private realtime: RealtimeServiceAPI;
api(): PhpNodeAPI {
return this;
}
async accessControl(
request: FastifyRequest,
server: FastifyInstance<Server, IncomingMessage, ServerResponse>,
) {
const token = (request.headers.authorization || "").trim().split("Token ").pop();
const secret = this.configuration.get<string>("secret", "");
let authorized = false;
if (secret && token === secret) {
authorized = true;
}
if (!authorized) {
throw server.httpErrors.badRequest("Internal Access Refused");
}
}
register(paremeters: {
method: "DELETE" | "GET" | "POST" | "PUT";
url: string;
handler: RouteHandlerMethod;
}) {
this.server.register((instance, _opts, next) => {
instance.register(
(internalServer, _, next) => {
internalServer.route({
method: paremeters.method,
url: paremeters.url,
preValidation: [request => this.accessControl(request, internalServer)],
handler: paremeters.handler,
});
next();
},
{ prefix: "/private" },
);
next();
});
}
async doStart(): Promise<this> {
return this;
}
async doInit(): Promise<this> {
this.server = this.context.getProvider<WebServerAPI>("webserver").getServer();
this.ws = this.context.getProvider<WebSocketAPI>("websocket");
this.realtime = this.context.getProvider<RealtimeServiceAPI>("realtime");
/**
* Register private calls from php for websockets
*/
this.register({
method: "POST",
url: "/pusher",
handler: (request, reply) => {
const body = request.body as { room: string; data: any };
const room = body.room;
const data = body.data;
this.ws.getIo().to(room).emit("realtime:event", { name: room, data: data });
reply.send({});
},
});
/**
* Register private calls from php channels
*/
this.register({
method: "GET",
url: "/companies/:company_id/workspaces/:workspace_id/channels/:id/members/:member_id/exists",
handler: (request: FastifyRequest<{ Params: ChannelMemberParameters }>, reply) => {
if (!gr.services) {
reply.code(500).send(); //Server is not ready
return;
}
const membersController = new ChannelMemberCrudController();
membersController.exists(request, reply);
},
});
/**
* Register private calls from php channels
*/
this.register({
method: "GET",
url: "/companies/:company_id/workspaces/:workspace_id/channels/:id",
handler: (request: FastifyRequest<{ Params: ChannelParameters }>, reply) => {
if (!gr.services) {
reply.code(500).send(); //Server is not ready
return;
}
const channelsController = new ChannelCrudController();
channelsController.getForPHP(request, reply);
},
});
/**
* Register private calls from php channels
*/
this.register({
method: "GET",
url: "/companies/:company_id/channels/:id",
handler: async (
request: FastifyRequest<{ Params: { company_id: string; id: string } }>,
reply,
) => {
if (!gr.services) {
reply.code(500).send(); //Server is not ready
return;
}
const workspaces = await gr.services.workspaces.getAllForCompany(request.params.company_id);
for (const w of workspaces) {
const channel = await gr.services.channels.channels.get(
{
company_id: request.params.company_id,
workspace_id: w.id,
id: request.params.id,
},
undefined,
);
if (channel) {
reply.send(channel);
return;
}
}
reply.code(404).send();
},
});
/**
* Register private calls from php channels
*/
this.register({
method: "POST",
url: "/companies/:company_id/workspaces/:workspace_id/channels/defaultchannel",
handler: (
request: FastifyRequest<{
Body: CreateChannelBody;
Params: ChannelParameters;
Querystring: { include_users: boolean };
}>,
reply,
) => {
if (!gr.services) {
reply.code(500).send(); //Server is not ready
return;
}
const channelsController = new ChannelCrudController();
request.currentUser = {
id: (request.body as any).user_id,
};
channelsController.save(request, reply);
},
});
return this;
}
}
@@ -1,11 +0,0 @@
import { FastifyInstance, FastifyRequest, RouteHandlerMethod } from "fastify";
import { IncomingMessage, ServerResponse, Server } from "http";
import { TdriveServiceProvider } from "../../framework";
export default interface PhpNodeAPI extends TdriveServiceProvider {
accessControl(
request: FastifyRequest,
server: FastifyInstance<Server, IncomingMessage, ServerResponse>,
): Promise<void>;
register(parameters: { method: string; url: string; handler: RouteHandlerMethod }): void;
}
@@ -43,49 +43,9 @@ export default class Tracker extends TdriveService<TrackerAPI> implements Tracke
);
});
const channelCreatedEvent = "channel:created";
localEventBus.subscribe<ResourceEventsPayload>(channelCreatedEvent, data => {
logger.debug(`Tracker - New ${channelCreatedEvent} event`);
this.track(
{
user: data.user,
event: channelCreatedEvent,
properties: this.getVisibilityObject(data.channel.visibility),
},
(err: Error) =>
err
? logger.error({ err }, "Tracker - Error while tracking", channelCreatedEvent)
: false,
);
});
const channelMemberCreatedEvent = "channel:member:created";
localEventBus.subscribe<ResourceEventsPayload>(channelMemberCreatedEvent, data => {
logger.debug(`Tracker - New ${channelMemberCreatedEvent} event`);
this.track(
{
user: data.user,
event: data.user.id !== data.member.user_id ? "channel:invite" : "channel:join",
properties: this.getVisibilityObject(data.channel.visibility),
},
(err: Error) =>
err
? logger.error({ err }, "Tracker - Error while tracking", channelMemberCreatedEvent)
: false,
);
});
return this;
}
private getVisibilityObject(visibility: string) {
return {
is_direct: visibility === "direct" ? true : false,
is_private: visibility === "private" ? true : false,
is_public: visibility === "public" ? true : false,
};
}
public async identify(
identity: IdentifyObjectType,
callback?: (err: Error) => void,