diff --git a/.gitignore b/.gitignore index b594126..dacf2a9 100644 --- a/.gitignore +++ b/.gitignore @@ -146,3 +146,4 @@ dist # misc .DS_Store +.idea diff --git a/README.md b/README.md index dcee563..3e14de9 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ # chatbridge +Testing running + Invite people to a bridged nextcloud chat with a token Using matterbridge - https://github.com/42wim/matterbridge diff --git a/config/custom-environment-variables.ts b/config/custom-environment-variables.ts new file mode 100755 index 0000000..53abdd3 --- /dev/null +++ b/config/custom-environment-variables.ts @@ -0,0 +1,11 @@ +export default { + port: 'PORT', + postgresConfig: { + host: 'POSTGRES_HOST', + port: 'POSTGRES_PORT', + username: 'POSTGRES_USER', + password: 'POSTGRES_PASSWORD', + database: 'POSTGRES_DB', + }, + admin_token: 'ADMIN_TOKEN' +} \ No newline at end of file diff --git a/config/defaults.ts b/config/defaults.ts old mode 100644 new mode 100755 index b17fa97..b6ebad5 --- a/config/defaults.ts +++ b/config/defaults.ts @@ -1,10 +1,3 @@ export default { port: '8999', - postgresConfig: { - host: '127.0.0.1', - port: '6500', - username: 'POSTGRES_USER', - password: 'POSTGRES_PASSWORD', - database: 'POSTGRES_DB', - }, } diff --git a/example.env b/example.env index 68a6c98..1382ac0 100644 --- a/example.env +++ b/example.env @@ -6,3 +6,5 @@ POSTGRES_PORT=6500 POSTGRES_USER= POSTGRES_PASSWORD= POSTGRES_DB= + +ADMIN_TOKEN= \ No newline at end of file diff --git a/package.json b/package.json index 238a745..048b318 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,7 @@ "author": "sneakers-the-rat ", "license": "AGPL-3.0", "scripts": { - "start": "ts-node-dev --respawn --transpile-only --exit-child src/app.ts", + "start": "ts-node-dev --respawn --transpile-only --exit-child server/app.ts", "build": "tsc -p .", "typeorm": "typeorm-ts-node-commonjs", "migrate": "rm -rf build && yarn build && yarn typeorm migration:generate ./src/migrations/added-user-entity -d ./src/utils/data-source.ts", @@ -32,6 +32,7 @@ "react-dom": "^18.2.0", "react-scripts": "^5.0.1", "redis": "^4.6.7", + "reflect-metadata": "^0.1.13", "typeorm": "^0.3.17", "typescript": "^5.1.6", "zod": "^3.21.4" diff --git a/server/app.ts b/server/app.ts index d2a5dd2..063d811 100644 --- a/server/app.ts +++ b/server/app.ts @@ -1,3 +1,5 @@ + + require('dotenv').config(); import express, { NextFunction, Request, Response } from 'express'; import config from 'config'; @@ -5,6 +7,7 @@ import cors from 'cors'; import { AppDataSource } from './db/data-source'; import AppError from './errors/appError'; +import groupRoutes from "./routes/group.routes"; @@ -12,9 +15,23 @@ AppDataSource.initialize() .then(async () => { const app = express(); + app.use(express.json({limit: "10kb"})); + + app.get('/healthcheck', async (_, res: Response) => { + res.status(200).json({ + status: "online!!!!" + }) + }) + + app.use('/groups', groupRoutes); app.all('*', (req: Request, res: Response, next: NextFunction) => { next(new AppError(404, `Route ${req.originalUrl} not found`)); }); + const port = config.get('port'); + app.listen(port); + + console.log(`Server started on port: ${port}`) + }) diff --git a/server/controllers/bridge.controller.ts b/server/controllers/bridge.controller.ts new file mode 100644 index 0000000..e69de29 diff --git a/server/controllers/channel.controller.ts b/server/controllers/channel.controller.ts new file mode 100644 index 0000000..e69de29 diff --git a/server/controllers/group.controller.ts b/server/controllers/group.controller.ts new file mode 100644 index 0000000..5bc30fe --- /dev/null +++ b/server/controllers/group.controller.ts @@ -0,0 +1,52 @@ +import {NextFunction, Request, Response} from 'express'; +import {CreateGroupInput, GetGroupInput, getGroupSchema} from "../schemas/group.schema"; +import config from 'config'; +import {Group} from "../entities/group.entity"; +import AppError from "../errors/appError"; + +import {AppDataSource} from "../db/data-source"; + +const groupRepository = AppDataSource.getRepository(Group) + +export const createGroupHandler = async( + req: Request<{}, {}, CreateGroupInput>, + res: Response +) => { + console.log(req.body); + const admin_token = config.get('admin_token'); + if (req.body.token !== admin_token){ + return res.status(403).json({ + status: 'fail', + message: 'Not authorized to create group without correct admin token' + }) + } + + let group = await groupRepository.create({...req.body}) + let result = await groupRepository.save(group) + return res.send(result) + +} + +export const getGroupHandler = async( + req: Request, + res: Response, + next: NextFunction +) => { + try { + let group = await groupRepository.findOneBy({name: req.params.name}) + + if (!group) { + return next(new AppError(404, "No group with matching name exists")) + } + + return res.status(200).json({ + status: 'success', + data: { + group + } + }) + + } catch (err: any) { + next(err); + } +}; \ No newline at end of file diff --git a/server/db/data-source.ts b/server/db/data-source.ts index bfa7e67..3d56e21 100644 --- a/server/db/data-source.ts +++ b/server/db/data-source.ts @@ -14,7 +14,7 @@ const postgresConfig = config.get<{ export const AppDataSource = new DataSource({ ...postgresConfig, type: 'postgres', - synchronize: false, + synchronize: true, logging: false, entities: ['server/entities/**/*.entity{.ts,.js}'], migrations: ['server/migrations/**/*{.ts,.js}'], diff --git a/server/entities/bridge.entity.ts b/server/entities/bridge.entity.ts index e69de29..8ce1abf 100644 --- a/server/entities/bridge.entity.ts +++ b/server/entities/bridge.entity.ts @@ -0,0 +1,55 @@ +import { Entity, Column, Index, OneToMany, TableInheritance, ChildEntity } from 'typeorm'; +import Model from './model.entity'; +import { Channel } from "./channel.entity"; + +export enum BridgeEnumType { + SLACK = 'slack', + DISCORD = 'discord', + MATRIX = 'matrix' +} + +@Entity('bridges') +@TableInheritance({column: { type: "varchar", name: "type" }}) +export class Bridge extends Model { + @Column({ + type: "enum", + enum: BridgeEnumType + }) + Protocol: string; + + @Column() + Label: string; + + @Column({ + default: true + }) + PrefixMessagesWithNick: boolean; + + // See https://github.com/42wim/matterbridge/wiki/Settings#prefixmessageswithnick + @Column({ + default: "[{LABEL}] <{NICK}> " + }) + RemoteNickFormat: string; + + @Column({ + unique: true + }) + Token: string; + + @OneToMany(() => Channel, (channel) => channel.bridge) + channels: Channel[] + +} + +@ChildEntity() +export class MatrixBridge extends Bridge { + // Matrix-specific + @Column() + Server: string; + + @Column() + Login: string; + + @Column() + Password: string; +} \ No newline at end of file diff --git a/server/entities/channel.entity.ts b/server/entities/channel.entity.ts new file mode 100644 index 0000000..67209e1 --- /dev/null +++ b/server/entities/channel.entity.ts @@ -0,0 +1,18 @@ +import { Entity, Column, Index, ManyToOne, OneToOne } from 'typeorm'; +import Model from './model.entity'; +import { Bridge } from "./bridge.entity"; +import { Group } from "./group.entity"; + +@Entity('channels') +export class Channel extends Model { + @Column() + name: string; + + @ManyToOne(() => Bridge, (bridge) => bridge.channels) + bridge: Bridge + + @ManyToOne(() => Group, (group) => group.channels) + group: Group + +} + diff --git a/server/entities/group.entity.ts b/server/entities/group.entity.ts index e69de29..efaf032 100644 --- a/server/entities/group.entity.ts +++ b/server/entities/group.entity.ts @@ -0,0 +1,20 @@ +import { Entity, Column, Index, OneToMany } from 'typeorm'; +import Model from './model.entity'; +import { Channel } from "./channel.entity"; + +@Entity('groups') +export class Group extends Model { + @Column({ + unique: true + }) + name: string; + + @Column({ + default: true + }) + enable: boolean; + + @OneToMany(() => Channel, (channel) => channel.bridge) + channels: Channel[] + +} \ No newline at end of file diff --git a/server/entities/model.entity.ts b/server/entities/model.entity.ts index fefc56d..fa8ade5 100644 --- a/server/entities/model.entity.ts +++ b/server/entities/model.entity.ts @@ -14,4 +14,6 @@ export default abstract class Model extends BaseEntity { @UpdateDateColumn() updated_at: Date; + + } diff --git a/server/middleware/validate.ts b/server/middleware/validate.ts new file mode 100644 index 0000000..57818ab --- /dev/null +++ b/server/middleware/validate.ts @@ -0,0 +1,24 @@ +import { Request, Response, NextFunction } from 'express'; +import { AnyZodObject, ZodError } from 'zod'; + +export const validate = + (schema: AnyZodObject) => + (req: Request, res: Response, next: NextFunction) => { + try { + schema.parse({ + params: req.params, + query: req.query, + body: req.body, + }); + + next(); + } catch (error) { + if (error instanceof ZodError) { + return res.status(400).json({ + status: 'fail', + errors: error.errors, + }); + } + next(error); + } + }; \ No newline at end of file diff --git a/server/routes/group.routes.ts b/server/routes/group.routes.ts new file mode 100644 index 0000000..9c77e33 --- /dev/null +++ b/server/routes/group.routes.ts @@ -0,0 +1,21 @@ +import express from 'express'; + +import { + createGroupHandler, + getGroupHandler +} from "../controllers/group.controller"; + +import { + createGroupSchema +} from "../schemas/group.schema"; + +import { validate } from "../middleware/validate"; + +const router = express.Router(); + +router + .route('/') + .post(createGroupHandler) + .get(getGroupHandler) + +export default router diff --git a/server/schemas/group.schema.ts b/server/schemas/group.schema.ts new file mode 100644 index 0000000..5f59b1b --- /dev/null +++ b/server/schemas/group.schema.ts @@ -0,0 +1,21 @@ +import { object, string, TypeOf } from 'zod'; + +export const createGroupSchema = object({ + body: object({ + name: string({ + required_error: "Name for group required" + }), + token: string({ + required_error: "Administration token required for creating groups" + }) + }) +}) + +export const getGroupSchema = object({ + name: string({ + required_error: "Name of group required to get group" + }) +}) + +export type CreateGroupInput = TypeOf['body']; +export type GetGroupInput = TypeOf; \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json index bffc247..fa395be 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -11,7 +11,8 @@ "skipLibCheck": true, "outDir": "./build", "rootDir": ".", - "include": ["server/**/*"], - "exclude": ["node_modules", "roles"] - } + + }, + "include": ["server/**/*"], + "exclude": ["node_modules", "roles"] }