mirror of
https://github.com/turt2live/matrix-dimension.git
synced 2024-10-01 01:05:53 -04:00
Add the start of an admin API and re-add widgets
The frontend is still broken and doesn't use these endpoints at all. A migration tool still needs to be written to pull in existing widget configurations.
This commit is contained in:
parent
826364e803
commit
599fb80112
4
.gitignore
vendored
4
.gitignore
vendored
@ -10,6 +10,10 @@ config/integrations/*_development.yaml
|
|||||||
config/integrations/*_production.yaml
|
config/integrations/*_production.yaml
|
||||||
build/
|
build/
|
||||||
dimension.db
|
dimension.db
|
||||||
|
src-ts/**/*.js
|
||||||
|
src-ts/**/*.js.map
|
||||||
|
web/**/*.js
|
||||||
|
web/**/*.js.map
|
||||||
|
|
||||||
# Logs
|
# Logs
|
||||||
logs
|
logs
|
||||||
|
15
package-lock.json
generated
15
package-lock.json
generated
@ -5478,8 +5478,7 @@
|
|||||||
"path-parse": {
|
"path-parse": {
|
||||||
"version": "1.0.5",
|
"version": "1.0.5",
|
||||||
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.5.tgz",
|
||||||
"integrity": "sha1-PBrfhx6pzWyUMbbqK9dKD/BVxME=",
|
"integrity": "sha1-PBrfhx6pzWyUMbbqK9dKD/BVxME="
|
||||||
"dev": true
|
|
||||||
},
|
},
|
||||||
"path-to-regexp": {
|
"path-to-regexp": {
|
||||||
"version": "0.1.7",
|
"version": "0.1.7",
|
||||||
@ -7524,7 +7523,6 @@
|
|||||||
"version": "1.4.0",
|
"version": "1.4.0",
|
||||||
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.4.0.tgz",
|
||||||
"integrity": "sha512-aW7sVKPufyHqOmyyLzg/J+8606v5nevBgaliIlV7nUpVMsDnoBGV/cbSLNjZAg9q0Cfd/+easKVKQ8vOu8fn1Q==",
|
"integrity": "sha512-aW7sVKPufyHqOmyyLzg/J+8606v5nevBgaliIlV7nUpVMsDnoBGV/cbSLNjZAg9q0Cfd/+easKVKQ8vOu8fn1Q==",
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"path-parse": "1.0.5"
|
"path-parse": "1.0.5"
|
||||||
}
|
}
|
||||||
@ -9583,6 +9581,17 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"umzug": {
|
||||||
|
"version": "2.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/umzug/-/umzug-2.1.0.tgz",
|
||||||
|
"integrity": "sha512-BgT+ekpItEWaG+3JjLLj6yVTxw2wIH8Cr6JyKYIzukWAx9nzGhC6BGHb/IRMjpobMM1qtIrReATwLUjKpU2iOQ==",
|
||||||
|
"requires": {
|
||||||
|
"babel-runtime": "6.26.0",
|
||||||
|
"bluebird": "3.5.1",
|
||||||
|
"lodash": "4.17.4",
|
||||||
|
"resolve": "1.4.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"union-value": {
|
"union-value": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.0.tgz",
|
||||||
|
@ -40,11 +40,10 @@
|
|||||||
"sequelize-typescript": "^0.6.1",
|
"sequelize-typescript": "^0.6.1",
|
||||||
"sqlite3": "^3.1.13",
|
"sqlite3": "^3.1.13",
|
||||||
"typescript-rest": "^1.2.0",
|
"typescript-rest": "^1.2.0",
|
||||||
|
"umzug": "^2.1.0",
|
||||||
"url": "^0.11.0"
|
"url": "^0.11.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"embed-video": "^2.0.0",
|
|
||||||
"screenfull": "^3.3.2",
|
|
||||||
"@angular/animations": "^5.0.0",
|
"@angular/animations": "^5.0.0",
|
||||||
"@angular/common": "^5.0.0",
|
"@angular/common": "^5.0.0",
|
||||||
"@angular/compiler": "^5.0.0",
|
"@angular/compiler": "^5.0.0",
|
||||||
@ -67,6 +66,7 @@
|
|||||||
"core-js": "^2.5.2",
|
"core-js": "^2.5.2",
|
||||||
"css-loader": "^0.28.7",
|
"css-loader": "^0.28.7",
|
||||||
"cssnano": "^3.10.0",
|
"cssnano": "^3.10.0",
|
||||||
|
"embed-video": "^2.0.0",
|
||||||
"extract-text-webpack-plugin": "^3.0.2",
|
"extract-text-webpack-plugin": "^3.0.2",
|
||||||
"file-loader": "^1.1.5",
|
"file-loader": "^1.1.5",
|
||||||
"goby": "^1.1.2",
|
"goby": "^1.1.2",
|
||||||
@ -86,13 +86,13 @@
|
|||||||
"rimraf": "^2.6.2",
|
"rimraf": "^2.6.2",
|
||||||
"rxjs": "^5.5.5",
|
"rxjs": "^5.5.5",
|
||||||
"sass-loader": "^6.0.3",
|
"sass-loader": "^6.0.3",
|
||||||
|
"screenfull": "^3.3.2",
|
||||||
"shelljs": "^0.7.8",
|
"shelljs": "^0.7.8",
|
||||||
"spinkit": "^1.2.5",
|
"spinkit": "^1.2.5",
|
||||||
"style-loader": "^0.19.1",
|
"style-loader": "^0.19.1",
|
||||||
"ts-helpers": "^1.1.2",
|
"ts-helpers": "^1.1.2",
|
||||||
"tslint": "^5.8.0",
|
"tslint": "^5.8.0",
|
||||||
"tslint-loader": "^3.4.3",
|
"tslint-loader": "^3.4.3",
|
||||||
"typescript": "^2.6.2",
|
|
||||||
"url-loader": "^0.6.2",
|
"url-loader": "^0.6.2",
|
||||||
"webpack": "^3.10.0",
|
"webpack": "^3.10.0",
|
||||||
"webpack-dev-server": "^2.9.7",
|
"webpack-dev-server": "^2.9.7",
|
||||||
|
@ -22,7 +22,7 @@ export default class Webserver {
|
|||||||
private loadRoutes() {
|
private loadRoutes() {
|
||||||
const apis = ["scalar", "dimension"].map(a => path.join(__dirname, a, "*.js"));
|
const apis = ["scalar", "dimension"].map(a => path.join(__dirname, a, "*.js"));
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
Server.loadServices(router, apis);
|
apis.forEach(a => Server.loadServices(router, [a]));
|
||||||
const routes = _.uniq(router.stack.map(r => r.route.path));
|
const routes = _.uniq(router.stack.map(r => r.route.path));
|
||||||
for (const route of routes) {
|
for (const route of routes) {
|
||||||
this.app.options(route, (_req, res) => res.sendStatus(200));
|
this.app.options(route, (_req, res) => res.sendStatus(200));
|
||||||
|
35
src-ts/api/dimension/DimensionAdminService.ts
Normal file
35
src-ts/api/dimension/DimensionAdminService.ts
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
import { GET, Path, QueryParam } from "typescript-rest";
|
||||||
|
import * as Promise from "bluebird";
|
||||||
|
import { ScalarService } from "../scalar/ScalarService";
|
||||||
|
import config from "../../config";
|
||||||
|
import { ApiError } from "../ApiError";
|
||||||
|
|
||||||
|
interface DimensionInfoResponse {
|
||||||
|
admins: string[],
|
||||||
|
}
|
||||||
|
|
||||||
|
@Path("/api/v1/dimension/admin")
|
||||||
|
export class DimensionAdminService {
|
||||||
|
|
||||||
|
public static isAdmin(userId: string) {
|
||||||
|
return config.admins.indexOf(userId) >= 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static validateAndGetAdminTokenOwner(scalarToken: string): Promise<string> {
|
||||||
|
return ScalarService.getTokenOwner(scalarToken).then(userId => {
|
||||||
|
if (!DimensionAdminService.isAdmin(userId))
|
||||||
|
throw new ApiError(401, {message: "You must be an administrator to use this API"});
|
||||||
|
else return userId;
|
||||||
|
}, ScalarService.invalidTokenErrorHandler);
|
||||||
|
}
|
||||||
|
|
||||||
|
@GET
|
||||||
|
@Path("info")
|
||||||
|
public getInfo(@QueryParam("scalar_token") scalarToken: string): Promise<DimensionInfoResponse> {
|
||||||
|
return DimensionAdminService.validateAndGetAdminTokenOwner(scalarToken).then(_userId => {
|
||||||
|
// Only let admins see other admins
|
||||||
|
// A 200 OK essentially means "you're an admin".
|
||||||
|
return {admins: config.admins};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
54
src-ts/api/dimension/DimensionIntegrationsService.ts
Normal file
54
src-ts/api/dimension/DimensionIntegrationsService.ts
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
import { GET, Path, QueryParam } from "typescript-rest";
|
||||||
|
import * as Promise from "bluebird";
|
||||||
|
import { ScalarService } from "../scalar/ScalarService";
|
||||||
|
import { DimensionStore } from "../../db/DimensionStore";
|
||||||
|
import { DimensionAdminService } from "./DimensionAdminService";
|
||||||
|
import { Widget } from "../../integrations/Widget";
|
||||||
|
import { MemoryCache } from "../../MemoryCache";
|
||||||
|
|
||||||
|
interface IntegrationsResponse {
|
||||||
|
widgets: Widget[],
|
||||||
|
}
|
||||||
|
|
||||||
|
@Path("/api/v1/dimension/integrations")
|
||||||
|
export class DimensionIntegrationsService {
|
||||||
|
|
||||||
|
private static integrationCache = new MemoryCache();
|
||||||
|
|
||||||
|
public static clearIntegrationCache() {
|
||||||
|
DimensionIntegrationsService.integrationCache.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
@GET
|
||||||
|
@Path("enabled")
|
||||||
|
public getEnabledIntegrations(@QueryParam("scalar_token") scalarToken: string): Promise<IntegrationsResponse> {
|
||||||
|
return ScalarService.getTokenOwner(scalarToken).then(_userId => {
|
||||||
|
return this.getIntegrations(true);
|
||||||
|
}, ScalarService.invalidTokenErrorHandler);
|
||||||
|
}
|
||||||
|
|
||||||
|
@GET
|
||||||
|
@Path("all")
|
||||||
|
public getAllIntegrations(@QueryParam("scalar_token") scalarToken: string): Promise<IntegrationsResponse> {
|
||||||
|
return DimensionAdminService.validateAndGetAdminTokenOwner(scalarToken).then(_userId => {
|
||||||
|
return this.getIntegrations(null);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private getIntegrations(isEnabledCheck?: boolean): Promise<IntegrationsResponse> {
|
||||||
|
const cachedResponse = DimensionIntegrationsService.integrationCache.get("integrations_" + isEnabledCheck);
|
||||||
|
if (cachedResponse) {
|
||||||
|
return cachedResponse;
|
||||||
|
}
|
||||||
|
const response = <IntegrationsResponse>{
|
||||||
|
widgets: [],
|
||||||
|
};
|
||||||
|
return Promise.resolve()
|
||||||
|
.then(() => DimensionStore.getWidgets(isEnabledCheck))
|
||||||
|
.then(widgets => response.widgets = widgets)
|
||||||
|
|
||||||
|
// Cache and return response
|
||||||
|
.then(() => DimensionIntegrationsService.integrationCache.put("integrations_" + isEnabledCheck, response))
|
||||||
|
.then(() => response);
|
||||||
|
}
|
||||||
|
}
|
@ -40,6 +40,14 @@ export class ScalarService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static invalidTokenErrorHandler(error: any): any {
|
||||||
|
if (error !== "Invalid token") {
|
||||||
|
LogService.error("ScalarWidgetService", "Error processing request");
|
||||||
|
LogService.error("ScalarWidgetService", error);
|
||||||
|
}
|
||||||
|
throw new ApiError(401, {message: "Invalid token"});
|
||||||
|
}
|
||||||
|
|
||||||
@POST
|
@POST
|
||||||
@Path("register")
|
@Path("register")
|
||||||
public register(request: RegisterRequest): Promise<ScalarRegisterResponse> {
|
public register(request: RegisterRequest): Promise<ScalarRegisterResponse> {
|
||||||
@ -94,13 +102,7 @@ export class ScalarService {
|
|||||||
public getAccount(@QueryParam("scalar_token") scalarToken: string): Promise<ScalarAccountResponse> {
|
public getAccount(@QueryParam("scalar_token") scalarToken: string): Promise<ScalarAccountResponse> {
|
||||||
return ScalarService.getTokenOwner(scalarToken).then(userId => {
|
return ScalarService.getTokenOwner(scalarToken).then(userId => {
|
||||||
return {user_id: userId};
|
return {user_id: userId};
|
||||||
}, err => {
|
}, ScalarService.invalidTokenErrorHandler);
|
||||||
if (err !== "Invalid token") {
|
|
||||||
LogService.error("ScalarService", "Error getting account information for user");
|
|
||||||
LogService.error("ScalarService", err);
|
|
||||||
}
|
|
||||||
throw new ApiError(401, {message: "Invalid token"});
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -1,7 +1,6 @@
|
|||||||
import { GET, Path, QueryParam } from "typescript-rest";
|
import { GET, Path, QueryParam } from "typescript-rest";
|
||||||
import * as Promise from "bluebird";
|
import * as Promise from "bluebird";
|
||||||
import { LogService } from "matrix-js-snippets";
|
import { LogService } from "matrix-js-snippets";
|
||||||
import { ApiError } from "../ApiError";
|
|
||||||
import { MemoryCache } from "../../MemoryCache";
|
import { MemoryCache } from "../../MemoryCache";
|
||||||
import { MatrixLiteClient } from "../../matrix/MatrixLiteClient";
|
import { MatrixLiteClient } from "../../matrix/MatrixLiteClient";
|
||||||
import config from "../../config";
|
import config from "../../config";
|
||||||
@ -67,15 +66,9 @@ export class ScalarWidgetService {
|
|||||||
|
|
||||||
@GET
|
@GET
|
||||||
@Path("title_lookup")
|
@Path("title_lookup")
|
||||||
public register(@QueryParam("scalar_token") scalarToken: string, @QueryParam("curl") url: string): Promise<UrlPreviewResponse> {
|
public titleLookup(@QueryParam("scalar_token") scalarToken: string, @QueryParam("curl") url: string): Promise<UrlPreviewResponse> {
|
||||||
return ScalarService.getTokenOwner(scalarToken).then(_userId => {
|
return ScalarService.getTokenOwner(scalarToken).then(_userId => {
|
||||||
return ScalarWidgetService.getUrlTitle(url);
|
return ScalarWidgetService.getUrlTitle(url);
|
||||||
}, err => {
|
}, ScalarService.invalidTokenErrorHandler);
|
||||||
if (err !== "Invalid token") {
|
|
||||||
LogService.error("ScalarWidgetService", "Error getting account information for user");
|
|
||||||
LogService.error("ScalarWidgetService", err);
|
|
||||||
}
|
|
||||||
throw new ApiError(401, {message: "Invalid token"});
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -5,6 +5,10 @@ import User from "./models/User";
|
|||||||
import UserScalarToken from "./models/UserScalarToken";
|
import UserScalarToken from "./models/UserScalarToken";
|
||||||
import Upstream from "./models/Upstream";
|
import Upstream from "./models/Upstream";
|
||||||
import * as Promise from "bluebird";
|
import * as Promise from "bluebird";
|
||||||
|
import WidgetRecord from "./models/WidgetRecord";
|
||||||
|
import * as path from "path";
|
||||||
|
import * as Umzug from "umzug";
|
||||||
|
import { Widget } from "../integrations/Widget";
|
||||||
|
|
||||||
class _DimensionStore {
|
class _DimensionStore {
|
||||||
private sequelize: Sequelize;
|
private sequelize: Sequelize;
|
||||||
@ -22,12 +26,23 @@ class _DimensionStore {
|
|||||||
User,
|
User,
|
||||||
UserScalarToken,
|
UserScalarToken,
|
||||||
Upstream,
|
Upstream,
|
||||||
|
WidgetRecord,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public updateSchema(): Promise<any> {
|
public updateSchema(): Promise<any> {
|
||||||
LogService.info("DimensionStore", "Updating schema...");
|
LogService.info("DimensionStore", "Updating schema...");
|
||||||
return this.sequelize.sync();
|
|
||||||
|
const migrator = new Umzug({
|
||||||
|
storage: "sequelize",
|
||||||
|
storageOptions: {sequelize: this.sequelize},
|
||||||
|
migrations: {
|
||||||
|
params: [this.sequelize.getQueryInterface()],
|
||||||
|
path: path.join(__dirname, "migrations"),
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return migrator.up();
|
||||||
}
|
}
|
||||||
|
|
||||||
public doesUserHaveTokensForAllUpstreams(userId: string): Promise<boolean> {
|
public doesUserHaveTokensForAllUpstreams(userId: string): Promise<boolean> {
|
||||||
@ -56,7 +71,10 @@ class _DimensionStore {
|
|||||||
|
|
||||||
public getTokenOwner(scalarToken: string): Promise<User> {
|
public getTokenOwner(scalarToken: string): Promise<User> {
|
||||||
let user: User = null;
|
let user: User = null;
|
||||||
return UserScalarToken.findAll({where: {isDimensionToken: true, scalarToken: scalarToken}, include: [User]}).then(tokens => {
|
return UserScalarToken.findAll({
|
||||||
|
where: {isDimensionToken: true, scalarToken: scalarToken},
|
||||||
|
include: [User]
|
||||||
|
}).then(tokens => {
|
||||||
if (!tokens || tokens.length === 0) {
|
if (!tokens || tokens.length === 0) {
|
||||||
return Promise.reject("Invalid token");
|
return Promise.reject("Invalid token");
|
||||||
}
|
}
|
||||||
@ -70,6 +88,12 @@ class _DimensionStore {
|
|||||||
return Promise.resolve(user);
|
return Promise.resolve(user);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public getWidgets(isEnabled?: boolean): Promise<Widget[]> {
|
||||||
|
let conditions = {};
|
||||||
|
if (isEnabled === true || isEnabled === false) conditions = {where: {isEnabled: isEnabled}};
|
||||||
|
return WidgetRecord.findAll(conditions).then(widgets => widgets.map(w => new Widget(w)));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const DimensionStore = new _DimensionStore();
|
export const DimensionStore = new _DimensionStore();
|
42
src-ts/db/migrations/20171218203245-AddTables.ts
Normal file
42
src-ts/db/migrations/20171218203245-AddTables.ts
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
import { QueryInterface } from "sequelize";
|
||||||
|
import { DataType } from "sequelize-typescript";
|
||||||
|
import * as Promise from "bluebird";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
up: (queryInterface: QueryInterface) => {
|
||||||
|
return Promise.resolve()
|
||||||
|
.then(() => queryInterface.createTable("dimension_users", {
|
||||||
|
"userId": {type: DataType.STRING, primaryKey: true, allowNull: false},
|
||||||
|
}))
|
||||||
|
.then(() => queryInterface.createTable("dimension_upstreams", {
|
||||||
|
"id": {type: DataType.INTEGER, primaryKey: true, autoIncrement: true, allowNull: false},
|
||||||
|
"name": {type: DataType.STRING, allowNull: false},
|
||||||
|
"type": {type: DataType.STRING, allowNull: false},
|
||||||
|
"scalarUrl": {type: DataType.STRING, allowNull: false},
|
||||||
|
"apiUrl": {type: DataType.STRING, allowNull: false},
|
||||||
|
}))
|
||||||
|
.then(() => queryInterface.createTable("dimension_scalar_tokens", {
|
||||||
|
"id": {type: DataType.INTEGER, primaryKey: true, autoIncrement: true, allowNull: false},
|
||||||
|
"userId": {
|
||||||
|
type: DataType.STRING,
|
||||||
|
allowNull: false,
|
||||||
|
references: {model: "dimension_users", key: "userId"},
|
||||||
|
onUpdate: "cascade", onDelete: "cascade",
|
||||||
|
},
|
||||||
|
"scalarToken": {type: DataType.STRING, allowNull: false},
|
||||||
|
"isDimensionToken": {type: DataType.BOOLEAN, allowNull: false},
|
||||||
|
"upstreamId": {
|
||||||
|
type: DataType.INTEGER,
|
||||||
|
allowNull: true,
|
||||||
|
references: {model: "dimension_upstreams", key: "id"},
|
||||||
|
onUpdate: "cascade", onDelete: "cascade",
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
},
|
||||||
|
down: (queryInterface: QueryInterface) => {
|
||||||
|
return Promise.resolve()
|
||||||
|
.then(() => queryInterface.dropTable("dimension_users"))
|
||||||
|
.then(() => queryInterface.dropTable("dimension_upstreams"))
|
||||||
|
.then(() => queryInterface.dropTable("dimension_scalar_tokens"));
|
||||||
|
}
|
||||||
|
}
|
72
src-ts/db/migrations/20171218203245-AddWidgets.ts
Normal file
72
src-ts/db/migrations/20171218203245-AddWidgets.ts
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
import { QueryInterface } from "sequelize";
|
||||||
|
import { DataType } from "sequelize-typescript";
|
||||||
|
import * as Promise from "bluebird";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
up: (queryInterface: QueryInterface) => {
|
||||||
|
return Promise.resolve()
|
||||||
|
.then(() => queryInterface.createTable("dimension_widgets", {
|
||||||
|
"id": {type: DataType.INTEGER, primaryKey: true, autoIncrement: true, allowNull: false},
|
||||||
|
"type": {type: DataType.STRING, allowNull: false},
|
||||||
|
"name": {type: DataType.STRING, allowNull: false},
|
||||||
|
"avatarUrl": {type: DataType.STRING, allowNull: false},
|
||||||
|
"description": {type: DataType.STRING, allowNull: false},
|
||||||
|
"isEnabled": {type: DataType.BOOLEAN, allowNull: false},
|
||||||
|
"optionsJson": {type: DataType.STRING, allowNull: true},
|
||||||
|
}))
|
||||||
|
.then(() => queryInterface.bulkInsert("dimension_widgets", [
|
||||||
|
{
|
||||||
|
type: "custom",
|
||||||
|
name: "Custom Widget",
|
||||||
|
avatarUrl: "/img/avatars/customwidget.png",
|
||||||
|
isEnabled: true,
|
||||||
|
isPublic: true,
|
||||||
|
description: "A webpage embedded in the room.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "etherpad",
|
||||||
|
name: "Etherpad",
|
||||||
|
avatarUrl: "/img/avatars/etherpad.png",
|
||||||
|
isEnabled: true,
|
||||||
|
isPublic: true,
|
||||||
|
description: "Collaborate on documents with members of your room.",
|
||||||
|
optionsJson: '{"defaultUrl":"https://demo.riot.im/etherpad/p/$roomId_$padName"}',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "googlecalendar",
|
||||||
|
name: "Google Calendar",
|
||||||
|
isEnabled: true,
|
||||||
|
isPublic: true,
|
||||||
|
avatarUrl: "/img/avatars/googlecalendar.png",
|
||||||
|
description: "Share upcoming events in your room with a Google Calendar.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "googledocs",
|
||||||
|
name: "Google Docs",
|
||||||
|
isEnabled: true,
|
||||||
|
isPublic: true,
|
||||||
|
avatarUrl: "/img/avatars/googledocs.png",
|
||||||
|
description: "Collaborate on and share documents using Google Docs.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "youtube",
|
||||||
|
name: "YouTube Video",
|
||||||
|
isEnabled: true,
|
||||||
|
isPublic: true,
|
||||||
|
avatarUrl: "/img/avatars/youtube.png",
|
||||||
|
description: "Embed a YouTube, Vimeo, or DailyMotion video in your room.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "twitch",
|
||||||
|
name: "Twitch Livestream",
|
||||||
|
isEnabled: true,
|
||||||
|
isPublic: true,
|
||||||
|
avatarUrl: "/img/avatars/twitch.png",
|
||||||
|
description: "Embed a Twitch livestream into your room.",
|
||||||
|
},
|
||||||
|
]));
|
||||||
|
},
|
||||||
|
down: (queryInterface: QueryInterface) => {
|
||||||
|
return queryInterface.dropTable("dimension_widgets");
|
||||||
|
}
|
||||||
|
}
|
8
src-ts/db/models/IntegrationRecord.ts
Normal file
8
src-ts/db/models/IntegrationRecord.ts
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
export interface IntegrationRecord {
|
||||||
|
type: string;
|
||||||
|
name: string;
|
||||||
|
avatarUrl: string;
|
||||||
|
description: string;
|
||||||
|
isEnabled: boolean;
|
||||||
|
isPublic: boolean;
|
||||||
|
}
|
35
src-ts/db/models/WidgetRecord.ts
Normal file
35
src-ts/db/models/WidgetRecord.ts
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
import { AutoIncrement, Column, Model, PrimaryKey, Table } from "sequelize-typescript";
|
||||||
|
import { IntegrationRecord } from "./IntegrationRecord";
|
||||||
|
|
||||||
|
@Table({
|
||||||
|
tableName: "dimension_widgets",
|
||||||
|
underscoredAll: false,
|
||||||
|
timestamps: false,
|
||||||
|
})
|
||||||
|
export default class WidgetRecord extends Model<WidgetRecord> implements IntegrationRecord {
|
||||||
|
@PrimaryKey
|
||||||
|
@AutoIncrement
|
||||||
|
@Column
|
||||||
|
id: number;
|
||||||
|
|
||||||
|
@Column
|
||||||
|
type: string;
|
||||||
|
|
||||||
|
@Column
|
||||||
|
name: string;
|
||||||
|
|
||||||
|
@Column
|
||||||
|
avatarUrl: string;
|
||||||
|
|
||||||
|
@Column
|
||||||
|
description: string;
|
||||||
|
|
||||||
|
@Column
|
||||||
|
isEnabled: boolean;
|
||||||
|
|
||||||
|
@Column
|
||||||
|
isPublic: boolean;
|
||||||
|
|
||||||
|
@Column
|
||||||
|
optionsJson: string;
|
||||||
|
}
|
30
src-ts/integrations/Integration.ts
Normal file
30
src-ts/integrations/Integration.ts
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
import { IntegrationRecord } from "../db/models/IntegrationRecord";
|
||||||
|
|
||||||
|
export class Integration {
|
||||||
|
// These are meant to be set by the underlying integration
|
||||||
|
public category: "bot" | "complex-bot" | "bridge" | "widget";
|
||||||
|
public type: string;
|
||||||
|
public requirements: IntegrationRequirement[];
|
||||||
|
|
||||||
|
// These are meant to be set by us
|
||||||
|
public displayName: string;
|
||||||
|
public avatarUrl: string;
|
||||||
|
public description: string;
|
||||||
|
public isEnabled: boolean;
|
||||||
|
public isPublic: boolean;
|
||||||
|
|
||||||
|
constructor(record: IntegrationRecord) {
|
||||||
|
this.type = record.type;
|
||||||
|
this.displayName = record.name;
|
||||||
|
this.avatarUrl = record.avatarUrl;
|
||||||
|
this.description = record.description;
|
||||||
|
this.isEnabled = record.isEnabled;
|
||||||
|
this.isPublic = record.isPublic;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IntegrationRequirement {
|
||||||
|
condition: "publicRoom" | "canSendEventTypes";
|
||||||
|
argument: any;
|
||||||
|
expectedValue: any;
|
||||||
|
}
|
21
src-ts/integrations/Widget.ts
Normal file
21
src-ts/integrations/Widget.ts
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
import { Integration } from "./Integration";
|
||||||
|
import WidgetRecord from "../db/models/WidgetRecord";
|
||||||
|
|
||||||
|
export interface EtherpadWidgetOptions {
|
||||||
|
defaultUrl: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Widget extends Integration {
|
||||||
|
public options: any;
|
||||||
|
|
||||||
|
constructor(widgetRecord: WidgetRecord) {
|
||||||
|
super(widgetRecord);
|
||||||
|
this.category = "widget";
|
||||||
|
this.options = widgetRecord.optionsJson ? JSON.parse(widgetRecord.optionsJson) : {};
|
||||||
|
this.requirements = [{
|
||||||
|
condition: "canSendEventTypes",
|
||||||
|
argument: ["im.vector.widget"],
|
||||||
|
expectedValue: true,
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user