working group controller
This commit is contained in:
parent
bac7b6f275
commit
22340d8a96
19 changed files with 253 additions and 12 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -146,3 +146,4 @@ dist
|
||||||
# misc
|
# misc
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
|
||||||
|
.idea
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
# chatbridge
|
# chatbridge
|
||||||
|
|
||||||
|
Testing running
|
||||||
|
|
||||||
Invite people to a bridged nextcloud chat with a token
|
Invite people to a bridged nextcloud chat with a token
|
||||||
|
|
||||||
Using matterbridge - https://github.com/42wim/matterbridge
|
Using matterbridge - https://github.com/42wim/matterbridge
|
||||||
|
|
11
config/custom-environment-variables.ts
Executable file
11
config/custom-environment-variables.ts
Executable file
|
@ -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'
|
||||||
|
}
|
7
config/defaults.ts
Normal file → Executable file
7
config/defaults.ts
Normal file → Executable file
|
@ -1,10 +1,3 @@
|
||||||
export default {
|
export default {
|
||||||
port: '8999',
|
port: '8999',
|
||||||
postgresConfig: {
|
|
||||||
host: '127.0.0.1',
|
|
||||||
port: '6500',
|
|
||||||
username: 'POSTGRES_USER',
|
|
||||||
password: 'POSTGRES_PASSWORD',
|
|
||||||
database: 'POSTGRES_DB',
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,3 +6,5 @@ POSTGRES_PORT=6500
|
||||||
POSTGRES_USER=
|
POSTGRES_USER=
|
||||||
POSTGRES_PASSWORD=
|
POSTGRES_PASSWORD=
|
||||||
POSTGRES_DB=
|
POSTGRES_DB=
|
||||||
|
|
||||||
|
ADMIN_TOKEN=
|
|
@ -7,7 +7,7 @@
|
||||||
"author": "sneakers-the-rat <JLSaunders987@gmail.com>",
|
"author": "sneakers-the-rat <JLSaunders987@gmail.com>",
|
||||||
"license": "AGPL-3.0",
|
"license": "AGPL-3.0",
|
||||||
"scripts": {
|
"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 .",
|
"build": "tsc -p .",
|
||||||
"typeorm": "typeorm-ts-node-commonjs",
|
"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",
|
"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-dom": "^18.2.0",
|
||||||
"react-scripts": "^5.0.1",
|
"react-scripts": "^5.0.1",
|
||||||
"redis": "^4.6.7",
|
"redis": "^4.6.7",
|
||||||
|
"reflect-metadata": "^0.1.13",
|
||||||
"typeorm": "^0.3.17",
|
"typeorm": "^0.3.17",
|
||||||
"typescript": "^5.1.6",
|
"typescript": "^5.1.6",
|
||||||
"zod": "^3.21.4"
|
"zod": "^3.21.4"
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
|
||||||
|
|
||||||
require('dotenv').config();
|
require('dotenv').config();
|
||||||
import express, { NextFunction, Request, Response } from 'express';
|
import express, { NextFunction, Request, Response } from 'express';
|
||||||
import config from 'config';
|
import config from 'config';
|
||||||
|
@ -5,6 +7,7 @@ import cors from 'cors';
|
||||||
import { AppDataSource } from './db/data-source';
|
import { AppDataSource } from './db/data-source';
|
||||||
import AppError from './errors/appError';
|
import AppError from './errors/appError';
|
||||||
|
|
||||||
|
import groupRoutes from "./routes/group.routes";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -12,9 +15,23 @@ AppDataSource.initialize()
|
||||||
.then(async () => {
|
.then(async () => {
|
||||||
|
|
||||||
const app = express();
|
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) => {
|
app.all('*', (req: Request, res: Response, next: NextFunction) => {
|
||||||
next(new AppError(404, `Route ${req.originalUrl} not found`));
|
next(new AppError(404, `Route ${req.originalUrl} not found`));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const port = config.get<number>('port');
|
||||||
|
app.listen(port);
|
||||||
|
|
||||||
|
console.log(`Server started on port: ${port}`)
|
||||||
|
|
||||||
})
|
})
|
||||||
|
|
0
server/controllers/bridge.controller.ts
Normal file
0
server/controllers/bridge.controller.ts
Normal file
0
server/controllers/channel.controller.ts
Normal file
0
server/controllers/channel.controller.ts
Normal file
52
server/controllers/group.controller.ts
Normal file
52
server/controllers/group.controller.ts
Normal file
|
@ -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<string>('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<GetGroupInput>,
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
};
|
|
@ -14,7 +14,7 @@ const postgresConfig = config.get<{
|
||||||
export const AppDataSource = new DataSource({
|
export const AppDataSource = new DataSource({
|
||||||
...postgresConfig,
|
...postgresConfig,
|
||||||
type: 'postgres',
|
type: 'postgres',
|
||||||
synchronize: false,
|
synchronize: true,
|
||||||
logging: false,
|
logging: false,
|
||||||
entities: ['server/entities/**/*.entity{.ts,.js}'],
|
entities: ['server/entities/**/*.entity{.ts,.js}'],
|
||||||
migrations: ['server/migrations/**/*{.ts,.js}'],
|
migrations: ['server/migrations/**/*{.ts,.js}'],
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
18
server/entities/channel.entity.ts
Normal file
18
server/entities/channel.entity.ts
Normal file
|
@ -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
|
||||||
|
|
||||||
|
}
|
||||||
|
|
|
@ -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[]
|
||||||
|
|
||||||
|
}
|
|
@ -14,4 +14,6 @@ export default abstract class Model extends BaseEntity {
|
||||||
|
|
||||||
@UpdateDateColumn()
|
@UpdateDateColumn()
|
||||||
updated_at: Date;
|
updated_at: Date;
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
24
server/middleware/validate.ts
Normal file
24
server/middleware/validate.ts
Normal file
|
@ -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);
|
||||||
|
}
|
||||||
|
};
|
21
server/routes/group.routes.ts
Normal file
21
server/routes/group.routes.ts
Normal file
|
@ -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
|
21
server/schemas/group.schema.ts
Normal file
21
server/schemas/group.schema.ts
Normal file
|
@ -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<typeof createGroupSchema>['body'];
|
||||||
|
export type GetGroupInput = TypeOf<typeof getGroupSchema>;
|
|
@ -11,7 +11,8 @@
|
||||||
"skipLibCheck": true,
|
"skipLibCheck": true,
|
||||||
"outDir": "./build",
|
"outDir": "./build",
|
||||||
"rootDir": ".",
|
"rootDir": ".",
|
||||||
|
|
||||||
|
},
|
||||||
"include": ["server/**/*"],
|
"include": ["server/**/*"],
|
||||||
"exclude": ["node_modules", "roles"]
|
"exclude": ["node_modules", "roles"]
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
Loading…
Reference in a new issue