Rename @accord/types -> @acrd/types.
This commit is contained in:
parent
09f751d997
commit
99cda810f8
10
backend/package-lock.json
generated
10
backend/package-lock.json
generated
@ -8,8 +8,8 @@
|
||||
"name": "@acrd/backend",
|
||||
"version": "0.0.0",
|
||||
"dependencies": {
|
||||
"@accord/types": "file:../types",
|
||||
"@acrd/ion": "github:acrdapp/ion",
|
||||
"@acrd/types": "file:../types",
|
||||
"body-parser": "^1.19.0",
|
||||
"chai-things": "^0.2.0",
|
||||
"colors": "^1.4.0",
|
||||
@ -86,9 +86,6 @@
|
||||
"../src": {
|
||||
"extraneous": true
|
||||
},
|
||||
"node_modules/@accord/types": {
|
||||
"resolved": "file:../types"
|
||||
},
|
||||
"node_modules/@acrd/ion": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "git+ssh://git@github.com/acrdapp/ion.git#d2c75cd65154727ecd527e78a3e0ba66ca441f33",
|
||||
@ -110,6 +107,9 @@
|
||||
"resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-10.0.1.tgz",
|
||||
"integrity": "sha512-/fvYntiO1GeICvqbQ3doGDIP97vWmvFt83GKguJ6prmQM2iXZfFcq6YE8KteFyRtX2/h5Hf91BYvPodJKFYv5Q=="
|
||||
},
|
||||
"node_modules/@acrd/types": {
|
||||
"resolved": "file:../types"
|
||||
},
|
||||
"node_modules/@babel/helper-validator-identifier": {
|
||||
"version": "7.12.11",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.12.11.tgz",
|
||||
@ -6242,7 +6242,6 @@
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@accord/types": {},
|
||||
"@acrd/ion": {
|
||||
"version": "git+ssh://git@github.com/acrdapp/ion.git#d2c75cd65154727ecd527e78a3e0ba66ca441f33",
|
||||
"from": "@acrd/ion@github:acrdapp/ion",
|
||||
@ -6265,6 +6264,7 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"@acrd/types": {},
|
||||
"@babel/helper-validator-identifier": {
|
||||
"version": "7.12.11",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.12.11.tgz",
|
||||
|
@ -14,7 +14,7 @@
|
||||
"author": "github.com/theadamjr",
|
||||
"dependencies": {
|
||||
"@acrd/ion": "github:acrdapp/ion",
|
||||
"@accord/types": "file:../types",
|
||||
"@acrd/types": "file:../types",
|
||||
"body-parser": "^1.19.0",
|
||||
"chai-things": "^0.2.0",
|
||||
"colors": "^1.4.0",
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { Entity } from '@accord/types';
|
||||
import { Entity } from '@acrd/types';
|
||||
import DBWrapper from './db-wrapper';
|
||||
import { Channel, ChannelDocument, TextChannelDocument, VoiceChannelDocument } from './models/channel';
|
||||
import { generateSnowflake } from './snowflake-entity';
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { Entity } from '@accord/types';
|
||||
import { Entity } from '@acrd/types';
|
||||
import { UpdateQuery } from 'mongoose';
|
||||
import DBWrapper from './db-wrapper';
|
||||
import { GuildMember, GuildMemberDocument } from './models/guild-member';
|
||||
@ -21,12 +21,12 @@ export default class GuildMembers extends DBWrapper<string, GuildMemberDocument>
|
||||
return member;
|
||||
}
|
||||
|
||||
public async create(options: Partial<Entity.GuildMember>) {
|
||||
public async create(options: Partial<Entity.GuildMember>) {
|
||||
const member = await GuildMember.create({
|
||||
_id: options.id ?? generateSnowflake(),
|
||||
roleIds: [await this.getEveryoneRoleId(options.guildId!)],
|
||||
...options,
|
||||
});
|
||||
});
|
||||
await this.addGuildToUser(options.userId!, options.guildId!);
|
||||
return member;
|
||||
}
|
||||
@ -36,7 +36,7 @@ export default class GuildMembers extends DBWrapper<string, GuildMemberDocument>
|
||||
}
|
||||
|
||||
private async getEveryoneRoleId(guildId: string) {
|
||||
const role = await Role.findOne({ guildId, name: '@everyone' });
|
||||
const role = await Role.findOne({ guildId, name: '@everyone' });
|
||||
return role!.id;
|
||||
}
|
||||
|
||||
|
@ -7,7 +7,7 @@ import { APIError } from '../rest/modules/api-error';
|
||||
import { Channel } from './models/channel';
|
||||
import { Role } from './models/role';
|
||||
import { GuildMember } from './models/guild-member';
|
||||
import { Entity } from '@accord/types';
|
||||
import { Entity } from '@acrd/types';
|
||||
|
||||
export default class Guilds extends DBWrapper<string, GuildDocument> {
|
||||
public async get(id: string | undefined) {
|
||||
@ -21,7 +21,7 @@ export default class Guilds extends DBWrapper<string, GuildDocument> {
|
||||
return await Guild.findOne({ channels: { $in: id } as any });
|
||||
}
|
||||
|
||||
public async create(options: Partial<Entity.Guild>): Promise<GuildDocument> {
|
||||
public async create(options: Partial<Entity.Guild>): Promise<GuildDocument> {
|
||||
const guildId = options.id ?? generateSnowflake();
|
||||
|
||||
const [_, systemChannel, __] = await Promise.all([
|
||||
@ -39,7 +39,7 @@ export default class Guilds extends DBWrapper<string, GuildDocument> {
|
||||
}),
|
||||
deps.guildMembers.create({ guildId, userId: options.ownerId }),
|
||||
]);
|
||||
|
||||
|
||||
return guild;
|
||||
}
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { WS } from '@accord/types';
|
||||
import { WS } from '@acrd/types';
|
||||
import { APIError } from '../rest/modules/api-error';
|
||||
import DBWrapper from './db-wrapper';
|
||||
import { Invite, InviteDocument } from './models/invite';
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { Entity, MessageTypes } from '@accord/types';
|
||||
import { Entity, MessageTypes } from '@acrd/types';
|
||||
import DBWrapper from './db-wrapper';
|
||||
import { Channel } from './models/channel';
|
||||
import { Message, MessageDocument } from './models/message';
|
||||
@ -16,7 +16,7 @@ export default class Messages extends DBWrapper<string, MessageDocument> {
|
||||
// TODO: TESTME
|
||||
if (!content && !attachmentURLs?.length)
|
||||
throw new TypeError('Empty messages are not valid');
|
||||
|
||||
|
||||
return await Message.create({
|
||||
_id: generateSnowflake(),
|
||||
attachmentURLs,
|
||||
@ -26,11 +26,11 @@ export default class Messages extends DBWrapper<string, MessageDocument> {
|
||||
});
|
||||
}
|
||||
|
||||
public async createSystem(guildId: string, content: string, type?: MessageTypes.Type) {
|
||||
public async createSystem(guildId: string, content: string, type?: MessageTypes.Type) {
|
||||
const { systemChannelId: channelId } = await deps.guilds.get(guildId);
|
||||
if (!channelId)
|
||||
throw new TypeError('No system channel configured');
|
||||
|
||||
|
||||
return await Message.create({
|
||||
_id: generateSnowflake(),
|
||||
channelId,
|
||||
|
@ -2,7 +2,7 @@ import { Document, model, Schema } from 'mongoose';
|
||||
import { generateSnowflake } from '../snowflake-entity';
|
||||
|
||||
import { createdAtToDate, generateUsername, useId } from '../../utils/utils';
|
||||
import { patterns } from '@accord/types';
|
||||
import { patterns } from '@acrd/types';
|
||||
|
||||
export interface ApplicationDocument extends Document, Entity.App {
|
||||
_id: string | never;
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { ChannelTypes } from '@accord/types';
|
||||
import { ChannelTypes } from '@acrd/types';
|
||||
import { Document, model, Schema } from 'mongoose';
|
||||
import { createdAtToDate, useId } from '../../utils/utils';
|
||||
import validators from '../../utils/validators';
|
||||
@ -66,4 +66,4 @@ export const Channel = model<ChannelDocument>('channel', new Schema({
|
||||
default: [],
|
||||
},
|
||||
}, { toJSON: { getters: true } })
|
||||
.method('toClient', useId));
|
||||
.method('toClient', useId));
|
||||
|
@ -1,9 +1,9 @@
|
||||
import { Document, model, Schema } from 'mongoose';
|
||||
import { patterns } from '@accord/types';
|
||||
import { patterns } from '@acrd/types';
|
||||
import { useId } from '../../utils/utils';
|
||||
import validators from '../../utils/validators';
|
||||
import { generateSnowflake } from '../snowflake-entity';
|
||||
import { Entity } from '@accord/types';
|
||||
import { Entity } from '@acrd/types';
|
||||
|
||||
export interface GuildMemberDocument extends Document, Entity.GuildMember {
|
||||
_id: string | never;
|
||||
@ -33,4 +33,4 @@ export const GuildMember = model<GuildMemberDocument>('guildMember', new Schema(
|
||||
validate: [validators.minLength(1), 'At least 1 role is required'],
|
||||
},
|
||||
}, { toJSON: { getters: true } })
|
||||
.method('toClient', useId));
|
||||
.method('toClient', useId));
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { Entity } from '@accord/types';
|
||||
import { Entity } from '@acrd/types';
|
||||
import { Document, model, Schema } from 'mongoose';
|
||||
import { patterns } from '@accord/types';
|
||||
import { patterns } from '@acrd/types';
|
||||
import { createdAtToDate, useId } from '../../utils/utils';
|
||||
import validators from '../../utils/validators';
|
||||
import { generateSnowflake } from '../snowflake-entity';
|
||||
@ -29,5 +29,5 @@ export const Guild = model<GuildDocument>('guild', new Schema({
|
||||
validate: [validators.optionalSnowflake, 'Invalid Snowflake ID'],
|
||||
},
|
||||
},
|
||||
{ toJSON: { getters: true } })
|
||||
.method('toClient', useId));
|
||||
{ toJSON: { getters: true } })
|
||||
.method('toClient', useId));
|
@ -1,8 +1,8 @@
|
||||
import { Document, model, Schema } from 'mongoose';
|
||||
import { patterns } from '@accord/types';
|
||||
import { patterns } from '@acrd/types';
|
||||
import { useId } from '../../utils/utils';
|
||||
import generateInvite from '../utils/generate-invite';
|
||||
import { Entity, InviteTypes } from '@accord/types';
|
||||
import { Entity, InviteTypes } from '@acrd/types';
|
||||
|
||||
export interface InviteDocument extends Document, Entity.Invite {
|
||||
_id: string | never;
|
||||
|
@ -1,8 +1,8 @@
|
||||
import { Document, model, Schema } from 'mongoose';
|
||||
import { patterns } from '@accord/types';
|
||||
import { patterns } from '@acrd/types';
|
||||
import { createdAtToDate, useId } from '../../utils/utils';
|
||||
import { generateSnowflake } from '../snowflake-entity';
|
||||
import { Entity, MessageTypes } from '@accord/types';
|
||||
import { Entity, MessageTypes } from '@acrd/types';
|
||||
|
||||
export interface MessageDocument extends Document, Entity.Message {
|
||||
_id: string | never;
|
||||
@ -23,7 +23,7 @@ export const Message = model<MessageDocument>('message', new Schema({
|
||||
channelId: {
|
||||
type: String,
|
||||
required: [true, 'Channel ID is required'],
|
||||
validate: [patterns.snowflake, 'Invalid Snowflake ID'],
|
||||
validate: [patterns.snowflake, 'Invalid Snowflake ID'],
|
||||
},
|
||||
content: {
|
||||
type: String,
|
||||
@ -43,4 +43,4 @@ export const Message = model<MessageDocument>('message', new Schema({
|
||||
type: String,
|
||||
updatedAt: Date,
|
||||
}, { toJSON: { getters: true } })
|
||||
.method('toClient', useId));
|
||||
.method('toClient', useId));
|
||||
|
@ -1,12 +1,12 @@
|
||||
import { Document, model, Schema } from 'mongoose';
|
||||
import { patterns } from '@accord/types';
|
||||
import { PermissionTypes } from '@accord/types';
|
||||
import { patterns } from '@acrd/types';
|
||||
import { PermissionTypes } from '@acrd/types';
|
||||
import { createdAtToDate, useId } from '../../utils/utils';
|
||||
import validators from '../../utils/validators';
|
||||
import { generateSnowflake } from '../snowflake-entity';
|
||||
import { Entity } from '@accord/types';
|
||||
import { Entity } from '@acrd/types';
|
||||
|
||||
export function hasPermission(current: number, required: number) {
|
||||
export function hasPermission(current: number, required: number) {
|
||||
return Boolean(current & required)
|
||||
|| Boolean(current & PermissionTypes.General.ADMINISTRATOR);
|
||||
}
|
||||
@ -67,4 +67,4 @@ export const Role = model<RoleDocument>('role', new Schema({
|
||||
validate: [validators.isInteger, 'Invalid permissions integer'],
|
||||
},
|
||||
}, { toJSON: { getters: true } })
|
||||
.method('toClient', useId));
|
||||
.method('toClient', useId));
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { Entity } from '@accord/types';
|
||||
import { patterns } from '@accord/types';
|
||||
import { Entity } from '@acrd/types';
|
||||
import { patterns } from '@acrd/types';
|
||||
import { Document, model, Schema } from 'mongoose';
|
||||
import { useId } from '../../utils/utils';
|
||||
import { generateSnowflake } from '../snowflake-entity';
|
||||
@ -40,4 +40,4 @@ export const Theme = model<ThemeDocument>('theme', new Schema({
|
||||
iconURL: String,
|
||||
updatedAt: Date,
|
||||
}, { toJSON: { getters: true } })
|
||||
.method('toClient', useId));
|
||||
.method('toClient', useId));
|
@ -1,5 +1,5 @@
|
||||
import { patterns } from '@accord/types';
|
||||
import { Entity, UserTypes } from '@accord/types';
|
||||
import { patterns } from '@acrd/types';
|
||||
import { Entity, UserTypes } from '@acrd/types';
|
||||
import { Document, model, Schema } from 'mongoose';
|
||||
import passportLocalMongoose from 'passport-local-mongoose';
|
||||
import { createdAtToDate, useId } from '../../utils/utils';
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { Entity } from '@accord/types';
|
||||
import { Entity } from '@acrd/types';
|
||||
import { SelfUserDocument } from './models/user';
|
||||
|
||||
export default class Pings {
|
||||
|
@ -1,9 +1,9 @@
|
||||
import { UpdateQuery } from 'mongoose';
|
||||
import { PermissionTypes } from '@accord/types';
|
||||
import { PermissionTypes } from '@acrd/types';
|
||||
import DBWrapper from './db-wrapper';
|
||||
import { hasPermission, Role, RoleDocument } from './models/role';
|
||||
import { generateSnowflake } from './snowflake-entity';
|
||||
import { Entity } from '@accord/types';
|
||||
import { Entity } from '@acrd/types';
|
||||
|
||||
export default class Roles extends DBWrapper<string, RoleDocument> {
|
||||
public async get(id: string | undefined) {
|
||||
@ -27,7 +27,7 @@ export default class Roles extends DBWrapper<string, RoleDocument> {
|
||||
const theirHighestRole: Entity.Role = theirRoles.reduce(max('position'));
|
||||
|
||||
const selfIsOwner = selfMember.userId === guild.ownerId;
|
||||
const selfHasHigherRole = myHighestRole.position > theirHighestRole.position;
|
||||
const selfHasHigherRole = myHighestRole.position > theirHighestRole.position;
|
||||
|
||||
return selfIsOwner || selfHasHigherRole;
|
||||
}
|
||||
@ -36,11 +36,11 @@ export default class Roles extends DBWrapper<string, RoleDocument> {
|
||||
const guildRoles = await Role.find({ guildId: guild.id });
|
||||
const totalPerms = guildRoles
|
||||
.filter(r => member.roleIds.includes(r.id))
|
||||
.reduce((acc, value) => value.permissions | acc, 0);
|
||||
.reduce((acc, value) => value.permissions | acc, 0);
|
||||
|
||||
const permNumber = (typeof permission === 'string')
|
||||
? PermissionTypes.All[PermissionTypes.All[permission as string]]
|
||||
: permission;
|
||||
: permission;
|
||||
return hasPermission(totalPerms, +permNumber);
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
import cluster from 'cluster';
|
||||
import { patterns } from '@accord/types';
|
||||
import { patterns } from '@acrd/types';
|
||||
|
||||
let inc = 0;
|
||||
let lastSnowflake: string;
|
||||
@ -14,13 +14,13 @@ export function generateSnowflake() {
|
||||
const pid = pad(process.pid, 5).slice(0, 5);
|
||||
const wid = pad(cluster.worker?.id ?? 0, 5);
|
||||
const getInc = (add: number) => pad(inc + add, 12);
|
||||
|
||||
|
||||
let snowflake = `0b${msSince}${wid}${pid}${getInc(inc)}`;
|
||||
(snowflake === lastSnowflake)
|
||||
? snowflake = `0b${msSince}${wid}${pid}${getInc(++inc)}`
|
||||
: inc = 0;
|
||||
: inc = 0;
|
||||
|
||||
lastSnowflake = snowflake;
|
||||
lastSnowflake = snowflake;
|
||||
return BigInt(snowflake).toString();
|
||||
}
|
||||
|
||||
@ -30,7 +30,7 @@ function binary64(val: string) {
|
||||
.toString(2)
|
||||
.padStart(64, '0')}`;
|
||||
} catch (e) {
|
||||
return '';
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
@ -42,6 +42,6 @@ export function snowflakeToDate(snowflake: string) {
|
||||
|
||||
const sinceEpochMs = Number(
|
||||
binary64(snowflake).slice(0, 42 + 2)
|
||||
);
|
||||
);
|
||||
return new Date(sinceEpochMs + accordEpoch);
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { Entity } from '@accord/types';
|
||||
import { Entity } from '@acrd/types';
|
||||
import DBWrapper from './db-wrapper';
|
||||
import { Theme, ThemeDocument } from './models/theme';
|
||||
import parseCSS from 'css-parse';
|
||||
@ -43,6 +43,6 @@ export default class Themes extends DBWrapper<string, ThemeDocument> {
|
||||
try { parseCSS(styles) }
|
||||
catch (error: any) {
|
||||
throw new TypeError(`CSS Error: ${error.message}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -8,7 +8,7 @@ import { Guild, GuildDocument } from './models/guild';
|
||||
import { UpdateQuery, connection } from 'mongoose';
|
||||
import { promisify } from 'util';
|
||||
import { readFile } from 'fs';
|
||||
import { UserTypes, Auth } from '@accord/types';
|
||||
import { UserTypes, Auth } from '@acrd/types';
|
||||
|
||||
const readFileAsync = promisify(readFile);
|
||||
|
||||
@ -43,7 +43,7 @@ export default class Users extends DBWrapper<string, UserDocument> {
|
||||
const user = await User.findById(id);
|
||||
if (!user)
|
||||
throw new APIError(404, 'User Not Found');
|
||||
return user as any as SelfUserDocument;
|
||||
return user as any as SelfUserDocument;
|
||||
}
|
||||
public async getByEmail(email: string): Promise<SelfUserDocument> {
|
||||
const user = await User.findOne({ email }) as any as SelfUserDocument;
|
||||
@ -55,7 +55,7 @@ export default class Users extends DBWrapper<string, UserDocument> {
|
||||
public async getKnown(userId: string) {
|
||||
const user = await this.getSelf(userId);
|
||||
return await User.find({ _id: { $in: await this.getKnownIds(user) } });
|
||||
}
|
||||
}
|
||||
public async getKnownIds(user: UserTypes.Self) {
|
||||
const members = await GuildMember.find({ guildId: { $in: user.guildIds } });
|
||||
const userIds = members.map(m => m.userId);
|
||||
@ -82,8 +82,8 @@ export default class Users extends DBWrapper<string, UserDocument> {
|
||||
}
|
||||
public async verifyToken(token: string | undefined): Promise<string> {
|
||||
// too insecure to keep in memory
|
||||
const key = await readFileAsync('./keys/jwt', { encoding: 'utf-8' });
|
||||
const decoded = jwt.verify(token as string, key, { algorithms: ['RS512'] }) as UserToken;
|
||||
const key = await readFileAsync('./keys/jwt', { encoding: 'utf-8' });
|
||||
const decoded = jwt.verify(token as string, key, { algorithms: ['RS512'] }) as UserToken;
|
||||
return decoded?.id;
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { UserTypes } from '@accord/types';
|
||||
import { UserTypes } from '@acrd/types';
|
||||
|
||||
export class EmailFunctions {
|
||||
export class EmailFunctions {
|
||||
public async verifyCode(user: UserTypes.Self) {
|
||||
try {
|
||||
const expiresIn = 5 * 60 * 1000;
|
||||
@ -9,7 +9,7 @@ export class EmailFunctions {
|
||||
user,
|
||||
code: deps.verification.create(user.email, 'LOGIN', { expiresIn, codeLength: 6 }),
|
||||
}, user.email as string);
|
||||
} catch {}
|
||||
} catch { }
|
||||
}
|
||||
public async verifyEmail(emailAddress: string, user: UserTypes.Self) {
|
||||
try {
|
||||
@ -19,7 +19,7 @@ export class EmailFunctions {
|
||||
user,
|
||||
code: deps.verification.create(emailAddress, 'VERIFY_EMAIL', { expiresIn }),
|
||||
}, emailAddress);
|
||||
} catch {}
|
||||
} catch { }
|
||||
}
|
||||
public async forgotPassword(emailAddress: string, user: UserTypes.Self) {
|
||||
try {
|
||||
@ -29,6 +29,6 @@ export class EmailFunctions {
|
||||
user,
|
||||
code: deps.verification.create(emailAddress, 'FORGOT_PASSWORD', { expiresIn }),
|
||||
}, emailAddress);
|
||||
} catch {}
|
||||
} catch { }
|
||||
}
|
||||
}
|
@ -1,12 +1,12 @@
|
||||
import { createTransport } from 'nodemailer';
|
||||
import Mail from 'nodemailer/lib/mailer';
|
||||
import { pugEngine } from 'nodemailer-pug-engine';
|
||||
import { UserTypes } from '@accord/types';
|
||||
import { UserTypes } from '@acrd/types';
|
||||
|
||||
export class Email {
|
||||
private email: Mail;
|
||||
|
||||
private readonly templateDir = __dirname + '/templates';
|
||||
private readonly templateDir = __dirname + '/templates';
|
||||
|
||||
constructor() {
|
||||
this.email = createTransport({
|
||||
@ -20,7 +20,7 @@ export class Email {
|
||||
this.email.verify((error) => (error)
|
||||
? log.error(error)
|
||||
: log.info('Logged in to email service'));
|
||||
|
||||
|
||||
this.email.use('compile', pugEngine({
|
||||
templateDir: this.templateDir,
|
||||
pretty: true,
|
||||
@ -46,7 +46,7 @@ export interface EmailTemplate {
|
||||
};
|
||||
'verify-email': this['verify'];
|
||||
'forgot-password': this['verify'];
|
||||
}
|
||||
}
|
||||
|
||||
const subjects: { [k in keyof EmailTemplate]: string } = {
|
||||
'forgot-password': 'Accord - Forgot Password',
|
||||
|
@ -1,19 +1,19 @@
|
||||
import { NextFunction, Request, Response } from 'express';
|
||||
import { GuildDocument } from '../../data/models/guild';
|
||||
import { PermissionTypes } from '@accord/types';
|
||||
import { PermissionTypes } from '@acrd/types';
|
||||
import { APIError } from '../modules/api-error';
|
||||
|
||||
export default (permission: PermissionTypes.Permission) =>
|
||||
async (req: Request, res: Response, next: NextFunction) => {
|
||||
const guild: GuildDocument = res.locals.guild;
|
||||
const members = await deps.guilds.getMembers(guild.id);
|
||||
const member = members.find(m => m.userId === res.locals.user.id);
|
||||
if (!member)
|
||||
throw new APIError(401, 'You are not a guild member');
|
||||
async (req: Request, res: Response, next: NextFunction) => {
|
||||
const guild: GuildDocument = res.locals.guild;
|
||||
const members = await deps.guilds.getMembers(guild.id);
|
||||
const member = members.find(m => m.userId === res.locals.user.id);
|
||||
if (!member)
|
||||
throw new APIError(401, 'You are not a guild member');
|
||||
|
||||
const isOwner = guild.ownerId === res.locals.user.id;
|
||||
const hasPerm = await deps.roles.hasPermission(guild, member, permission);
|
||||
if (hasPerm || isOwner) return next();
|
||||
const isOwner = guild.ownerId === res.locals.user.id;
|
||||
const hasPerm = await deps.roles.hasPermission(guild, member, permission);
|
||||
if (hasPerm || isOwner) return next();
|
||||
|
||||
throw new APIError(401, `Missing Permissions: ${permission}`);
|
||||
};
|
||||
throw new APIError(401, `Missing Permissions: ${permission}`);
|
||||
};
|
@ -2,9 +2,9 @@ import { Router } from 'express';
|
||||
import { SelfUserDocument, User } from '../../data/models/user';
|
||||
import passport from 'passport';
|
||||
import { APIError } from '../modules/api-error';
|
||||
import { patterns } from '@accord/types';
|
||||
import { patterns } from '@acrd/types';
|
||||
import { extraRateLimit } from '../modules/rate-limiter';
|
||||
import { REST } from '@accord/types';
|
||||
import { REST } from '@acrd/types';
|
||||
|
||||
export const router = Router();
|
||||
|
||||
@ -44,7 +44,7 @@ router.post('/register', extraRateLimit(10), async (req, res) => {
|
||||
|
||||
router.get('/verify', extraRateLimit(25), async (req, res) => {
|
||||
const email = deps.verification.getEmailFromCode(req.query.code as string);
|
||||
const user = await User.findOne({ email }) as any;
|
||||
const user = await User.findOne({ email }) as any;
|
||||
if (!email || !user)
|
||||
throw new APIError(400, 'Invalid code');
|
||||
|
||||
@ -90,7 +90,7 @@ router.post('/change-password', extraRateLimit(10), async (req, res) => {
|
||||
throw new APIError(400, 'User not found');
|
||||
if (!user.verified)
|
||||
throw new APIError(400, 'Please verify your account');
|
||||
|
||||
|
||||
await user.changePassword(oldPassword, newPassword);
|
||||
await user.save();
|
||||
|
||||
|
@ -3,7 +3,7 @@ import { SelfUserDocument } from '../../data/models/user';
|
||||
import { APIError } from '../modules/api-error';
|
||||
import updateUser from '../middleware/update-user';
|
||||
import validateUser from '../middleware/validate-user';
|
||||
import { WS, REST } from '@accord/types';
|
||||
import { WS, REST } from '@acrd/types';
|
||||
|
||||
export const router = Router();
|
||||
|
||||
@ -18,11 +18,11 @@ router.get('/:channelId/messages', updateUser, validateUser, async (req, res) =>
|
||||
const channelMsgs = (
|
||||
await deps.messages.getChannelMessages(channelId)
|
||||
?? await deps.messages.getDMChannelMessages(channelId, res.locals.user.id)
|
||||
);
|
||||
);
|
||||
|
||||
const batchSize = 25;
|
||||
const back = Math.max(channelMsgs.length - +(req.query.back || batchSize), 0);
|
||||
|
||||
|
||||
const slicedMsgs = channelMsgs
|
||||
.slice(back)
|
||||
.filter(m => !user.ignored?.userIds.includes(m.authorId));
|
||||
@ -38,7 +38,7 @@ router.get('/:channelId/messages', updateUser, validateUser, async (req, res) =>
|
||||
partialUser: { lastReadMessageIds: user.lastReadMessageIds },
|
||||
} as WS.Args.UserUpdate);
|
||||
}
|
||||
|
||||
|
||||
res.json({
|
||||
channelId,
|
||||
total: channelMsgs.length,
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { Router } from 'express';
|
||||
import { PermissionTypes } from '@accord/types';
|
||||
import { PermissionTypes } from '@acrd/types';
|
||||
import { Guild } from '../../data/models/guild';
|
||||
import updateUser from '../middleware/update-user';
|
||||
import validateUser from '../middleware/validate-user';
|
||||
@ -24,7 +24,7 @@ router.get('/:id/channels',
|
||||
async (req, res) => {
|
||||
const channels = await deps.guilds.getChannels(req.params.id);
|
||||
res.json(channels);
|
||||
});
|
||||
});
|
||||
|
||||
router.get('/:id/invites',
|
||||
updateUser, validateUser, updateGuild,
|
||||
@ -32,16 +32,16 @@ router.get('/:id/invites',
|
||||
async (req, res) => {
|
||||
const invites = await deps.guilds.getInvites(req.params.id);
|
||||
res.json(invites);
|
||||
});
|
||||
});
|
||||
|
||||
router.get('/:id/members', updateUser, validateUser, updateGuild,
|
||||
async (req, res) => {
|
||||
const members = await deps.guilds.getMembers(req.params.id);
|
||||
res.json(members);
|
||||
});
|
||||
});
|
||||
|
||||
router.get('/:id/roles', updateUser, validateUser, updateGuild,
|
||||
async (req, res) => {
|
||||
const roles = await deps.guilds.getRoles(req.params.id);
|
||||
res.json(roles);
|
||||
});
|
||||
});
|
@ -1,4 +1,4 @@
|
||||
import { Entity, REST, UserTypes } from '@accord/types';
|
||||
import { Entity, REST, UserTypes } from '@acrd/types';
|
||||
import { Router } from 'express';
|
||||
import { User } from '../../data/models/user';
|
||||
import generateInvite from '../../data/utils/generate-invite';
|
||||
@ -58,7 +58,7 @@ router.get('/entities', updateUser, validateUser, async (req, res) => {
|
||||
const $in = (guildIds)
|
||||
? user.guildIds.concat(guildIds)
|
||||
: user.guildIds;
|
||||
|
||||
|
||||
const [channels, guilds, members, roles, themes, unsecureUsers] = await Promise.all([
|
||||
Channel.find({ guildId: { $in } }),
|
||||
Guild.find({ _id: { $in } }),
|
||||
|
@ -1,19 +1,19 @@
|
||||
import { ChannelDocument } from '../data/models/channel';
|
||||
import { patterns } from '@accord/types';
|
||||
import { patterns } from '@acrd/types';
|
||||
|
||||
export default {
|
||||
cannotChangeIfProp: (prop: string, value: any, def?: any) => (val: string) =>
|
||||
cannotChangeIfProp: (prop: string, value: any, def?: any) => (val: string) =>
|
||||
!val
|
||||
|| (this as any)[prop] !== value
|
||||
|| val === def,
|
||||
min: (min: number) => (val: number) => val >= min,
|
||||
max: (max: number) => (val: number) => val <= max,
|
||||
isInteger: (val: number ) => Number.isInteger(val) && val >= 0,
|
||||
isInteger: (val: number) => Number.isInteger(val) && val >= 0,
|
||||
minLength: (min: number) => (val: string | any[]) => val.length >= min,
|
||||
maxLength: (max: number) => (val: string | any[]) => val.length <= max,
|
||||
optionalSnowflake: (val: string) => !val || patterns.snowflake.test(val),
|
||||
optionalPattern: (type: keyof typeof patterns) => (val: string) => !val || patterns[type].test(val),
|
||||
textChannelName: function(this: ChannelDocument, val: string) {
|
||||
textChannelName: function (this: ChannelDocument, val: string) {
|
||||
const pattern = /^[a-z\-\d]+$/;
|
||||
return this.type === 'TEXT'
|
||||
&& pattern.test(val)
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { ChannelTypes } from '@accord/types';
|
||||
import { ChannelTypes } from '@acrd/types';
|
||||
|
||||
export class VoiceService {
|
||||
private connections = new Map<string, ChannelTypes.VoiceConnection[]>();
|
||||
@ -37,7 +37,7 @@ export class VoiceService {
|
||||
|
||||
cons[index] = data;
|
||||
this.connections.set(channelId, cons);
|
||||
|
||||
|
||||
return this.getForUser(channelId, data.userId);
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { Socket } from 'socket.io';
|
||||
import { patterns } from '@accord/types';
|
||||
import { patterns } from '@acrd/types';
|
||||
|
||||
export class SessionManager extends Map<string, string> {
|
||||
public get(clientId: string): string {
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { WS } from '@accord/types';
|
||||
import { WS } from '@acrd/types';
|
||||
|
||||
export class WSCooldowns {
|
||||
public readonly active = new Map<string, EventLog[]>();
|
||||
@ -10,7 +10,7 @@ export class WSCooldowns {
|
||||
this.add(userId, eventName);
|
||||
|
||||
const clientEvents = this.get(userId).length;
|
||||
const maxEvents = 60;
|
||||
const maxEvents = 60;
|
||||
if (clientEvents > maxEvents)
|
||||
throw new TypeError('You are doing too many things at once!');
|
||||
}
|
||||
|
@ -1,18 +1,18 @@
|
||||
import { Guild } from '../../data/models/guild';
|
||||
import { Socket } from 'socket.io';
|
||||
import { PermissionTypes, getPermString } from '@accord/types';
|
||||
import { PermissionTypes, getPermString } from '@acrd/types';
|
||||
|
||||
export class WSGuard {
|
||||
public userId(client: Socket) {
|
||||
return deps.webSocket.sessions.get(client.id) ?? '';
|
||||
}
|
||||
|
||||
public validateIsUser(client: Socket, userId: string) {
|
||||
public validateIsUser(client: Socket, userId: string) {
|
||||
if (this.userId(client) !== userId)
|
||||
throw new TypeError('Unauthorized');
|
||||
}
|
||||
|
||||
public async validateIsOwner(client: Socket, guildId: string) {
|
||||
public async validateIsOwner(client: Socket, guildId: string) {
|
||||
const ownerId = this.userId(client);
|
||||
const isOwner = await Guild.exists({ _id: guildId, ownerId });
|
||||
if (!isOwner)
|
||||
@ -32,15 +32,15 @@ export class WSGuard {
|
||||
}
|
||||
|
||||
private async can(permission: PermissionTypes.PermissionString, guildId: string, userId: string) {
|
||||
const guild = await deps.guilds.get(guildId);
|
||||
const member = await deps.guildMembers.getInGuild(guildId, userId);
|
||||
const guild = await deps.guilds.get(guildId);
|
||||
const member = await deps.guildMembers.getInGuild(guildId, userId);
|
||||
|
||||
return (guild.ownerId === member.userId)
|
||||
|| deps.roles.hasPermission(guild, member, PermissionTypes.All[permission]);
|
||||
|| deps.roles.hasPermission(guild, member, PermissionTypes.All[permission]);
|
||||
}
|
||||
|
||||
public async canInChannel(permission: PermissionTypes.PermissionString, channelId: string, userId: string) {
|
||||
const channel = await deps.channels.get(channelId);
|
||||
const channel = await deps.channels.get(channelId);
|
||||
const member = await deps.guildMembers.getInGuild(channel.guildId, userId);
|
||||
|
||||
const overrides = channel.overrides?.filter(o => member.roleIds.includes(o.roleId)) ?? [];
|
||||
@ -60,6 +60,6 @@ export class WSGuard {
|
||||
}
|
||||
|
||||
public async decodeKey(token: string) {
|
||||
return { id: await deps.users.verifyToken(token) };
|
||||
return { id: await deps.users.verifyToken(token) };
|
||||
}
|
||||
}
|
||||
|
@ -4,12 +4,12 @@ import { WSAction, WSEvent } from './ws-events/ws-event';
|
||||
import { resolve } from 'path';
|
||||
import { readdirSync } from 'fs';
|
||||
import { SessionManager } from './modules/session-manager';
|
||||
import { WS } from '@accord/types';
|
||||
import { WS } from '@acrd/types';
|
||||
|
||||
export class WebSocket {
|
||||
public events = new Map<keyof WS.To, WSEvent<keyof WS.To>>();
|
||||
public io: SocketServer;
|
||||
public sessions = new SessionManager();
|
||||
public sessions = new SessionManager();
|
||||
|
||||
public get connectedUserIds() {
|
||||
return Array.from(this.sessions.values());
|
||||
@ -35,7 +35,7 @@ export class WebSocket {
|
||||
try {
|
||||
const event = new Event();
|
||||
this.events.set(event.on, (event));
|
||||
} catch {}
|
||||
} catch { }
|
||||
}
|
||||
|
||||
log.verbose(`Loaded ${this.events.size} handlers`, 'ws');
|
||||
@ -53,7 +53,7 @@ export class WebSocket {
|
||||
try {
|
||||
const userId = this.sessions.userId(client);
|
||||
deps.wsCooldowns.handle(userId, event.on);
|
||||
} catch {}
|
||||
} catch { }
|
||||
}
|
||||
});
|
||||
});
|
||||
@ -68,7 +68,7 @@ export class WebSocket {
|
||||
}
|
||||
|
||||
public to(...rooms: string[]) {
|
||||
return this.io.to(rooms) as {
|
||||
return this.io.to(rooms) as {
|
||||
emit: <K extends keyof WS.From>(name: K, args: WS.From[K]) => any,
|
||||
};
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { WS } from '@accord/types';
|
||||
import { WS } from '@acrd/types';
|
||||
import { Socket } from 'socket.io';
|
||||
import { WebSocket } from '../websocket';
|
||||
import { WSEvent } from './ws-event';
|
||||
@ -9,7 +9,7 @@ export default class implements WSEvent<'CHANNEL_CREATE'> {
|
||||
public async invoke(ws: WebSocket, client: Socket, { name, guildId, type }: WS.Params.ChannelCreate) {
|
||||
if (!name || !guildId || !type)
|
||||
throw new TypeError('Not enough options were provided');
|
||||
|
||||
|
||||
await deps.wsGuard.validateCan(client, guildId, 'MANAGE_CHANNELS');
|
||||
const channel = await deps.channels.create({ name, guildId, type });
|
||||
|
||||
|
@ -3,7 +3,7 @@ import { WebSocket } from '../websocket';
|
||||
import { WSEvent, } from './ws-event';
|
||||
import { User } from '../../data/models/user';
|
||||
import { Channel, ChannelDocument } from '../../data/models/channel';
|
||||
import { WS } from '@accord/types';
|
||||
import { WS } from '@acrd/types';
|
||||
|
||||
export default class implements WSEvent<'CHANNEL_DELETE'> {
|
||||
on = 'CHANNEL_DELETE' as const;
|
||||
@ -14,7 +14,7 @@ export default class implements WSEvent<'CHANNEL_DELETE'> {
|
||||
|
||||
const channel = await deps.channels.getText(channelId);
|
||||
await deps.wsGuard.validateCan(client, channel.guildId, 'MANAGE_CHANNELS');
|
||||
|
||||
|
||||
await User.updateMany(
|
||||
{ voice: { channelId } },
|
||||
{ voice: {} },
|
||||
|
@ -2,7 +2,7 @@ import { WSEvent } from './ws-event';
|
||||
import { WebSocket } from '../websocket';
|
||||
import { Socket } from 'socket.io';
|
||||
import { SelfUserDocument } from '../../data/models/user';
|
||||
import { WS } from '@accord/types';
|
||||
import { WS } from '@acrd/types';
|
||||
|
||||
export default class implements WSEvent<'CHANNEL_JOIN'> {
|
||||
on = 'CHANNEL_JOIN' as const;
|
||||
@ -15,17 +15,17 @@ export default class implements WSEvent<'CHANNEL_JOIN'> {
|
||||
const userId = ws.sessions.get(client.id);
|
||||
const user = await deps.users.getSelf(userId);
|
||||
const movedChannel = user.voice.channelId !== channelId;
|
||||
|
||||
|
||||
if (user.voice.channelId && movedChannel)
|
||||
await deps.channelLeave.invoke(ws, client);
|
||||
|
||||
const doesExist = channel.userIds.includes(userId);
|
||||
|
||||
const doesExist = channel.userIds.includes(userId);
|
||||
if (doesExist)
|
||||
throw new TypeError('User already connected to voice');
|
||||
|
||||
// TODO: perms - validate can join
|
||||
deps.voiceService.add(channelId, { userId });
|
||||
|
||||
|
||||
await Promise.all([
|
||||
client.join(channelId),
|
||||
deps.channels.joinVC(channel, userId),
|
||||
|
@ -2,7 +2,7 @@ import { Socket } from 'socket.io';
|
||||
import { WebSocket } from '../websocket';
|
||||
import { WSEvent, } from './ws-event';
|
||||
import { Channel, ChannelDocument } from '../../data/models/channel';
|
||||
import { Entity, WS } from '@accord/types';
|
||||
import { Entity, WS } from '@acrd/types';
|
||||
|
||||
export default class implements WSEvent<'CHANNEL_UPDATE'> {
|
||||
public on = 'CHANNEL_UPDATE' as const;
|
||||
@ -10,7 +10,7 @@ export default class implements WSEvent<'CHANNEL_UPDATE'> {
|
||||
public async invoke(ws: WebSocket, client: Socket, { position, name, summary, overrides, channelId }: WS.Params.ChannelUpdate) {
|
||||
const channel = await deps.channels.get(channelId);
|
||||
await deps.wsGuard.validateCan(client, channel.guildId, 'MANAGE_CHANNELS');
|
||||
|
||||
|
||||
const partialChannel: Partial<Entity.Channel> = {};
|
||||
if (name) partialChannel.name = name;
|
||||
if (overrides) partialChannel.overrides = overrides;
|
||||
@ -29,7 +29,7 @@ export default class implements WSEvent<'CHANNEL_UPDATE'> {
|
||||
send: { channelId: channel.id, partialChannel },
|
||||
}];
|
||||
}
|
||||
|
||||
|
||||
private async raiseHigherChannels(position: number, channel: ChannelDocument) {
|
||||
await Channel.updateMany({
|
||||
guildId: channel.guildId,
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { WS } from '@accord/types';
|
||||
import { WS } from '@acrd/types';
|
||||
import { Socket } from 'socket.io';
|
||||
import { WebSocket } from '../websocket';
|
||||
import { WSEvent } from './ws-event';
|
||||
@ -9,14 +9,14 @@ export default class implements WSEvent<'GUILD_CREATE'> {
|
||||
public async invoke(ws: WebSocket, client: Socket, { name }: WS.Params.GuildCreate) {
|
||||
if (!name)
|
||||
throw new TypeError('Not enough options were provided');
|
||||
|
||||
|
||||
const userId = ws.sessions.userId(client);
|
||||
const user = await deps.users.getSelf(userId);
|
||||
const guild = await deps.guilds.create({ name, ownerId: user.id });
|
||||
const user = await deps.users.getSelf(userId);
|
||||
const guild = await deps.guilds.create({ name, ownerId: user.id });
|
||||
const entities = await deps.guilds.getEntities(guild.id);
|
||||
|
||||
await deps.wsRooms.joinGuildRooms(user, client);
|
||||
|
||||
|
||||
return [{
|
||||
emit: this.on,
|
||||
to: [client.id],
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { WS } from '@accord/types';
|
||||
import { WS } from '@acrd/types';
|
||||
import { Socket } from 'socket.io';
|
||||
import { Channel } from '../../data/models/channel';
|
||||
import { Guild } from '../../data/models/guild';
|
||||
@ -16,7 +16,7 @@ export default class implements WSEvent<'GUILD_DELETE'> {
|
||||
public async invoke(ws: WebSocket, client: Socket, { guildId }: WS.Params.GuildDelete) {
|
||||
if (!guildId)
|
||||
throw new TypeError('Not enough options were provided');
|
||||
|
||||
|
||||
await deps.wsGuard.validateIsOwner(client, guildId);
|
||||
|
||||
await User.updateMany(
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { WS } from '@accord/types';
|
||||
import { WS } from '@acrd/types';
|
||||
import { Socket } from 'socket.io';
|
||||
import { GuildDocument } from '../../data/models/guild';
|
||||
import { InviteDocument } from '../../data/models/invite';
|
||||
@ -12,16 +12,16 @@ export default class implements WSEvent<'GUILD_MEMBER_ADD'> {
|
||||
public async invoke(ws: WebSocket, client: Socket, { inviteCode }: WS.Params.GuildMemberAdd) {
|
||||
if (!inviteCode)
|
||||
throw new TypeError('Not enough options were provided');
|
||||
|
||||
|
||||
const invite = await deps.invites.get(inviteCode);
|
||||
const guild = await deps.guilds.get(invite.guildId);
|
||||
const userId = ws.sessions.userId(client);
|
||||
|
||||
|
||||
const members = await deps.guilds.getMembers(guild.id);
|
||||
const inGuild = members.some(m => m.userId === userId);
|
||||
if (inGuild)
|
||||
throw new TypeError('User already in guild');
|
||||
|
||||
|
||||
const selfUser = await deps.users.getSelf(userId);
|
||||
if (inviteCode && selfUser.bot)
|
||||
throw new TypeError('Bot users cannot accept invites');
|
||||
@ -46,24 +46,24 @@ export default class implements WSEvent<'GUILD_MEMBER_ADD'> {
|
||||
},
|
||||
}];
|
||||
}
|
||||
|
||||
|
||||
private async joinGuildMessage(guild: GuildDocument, selfUser: SelfUserDocument, ws: WebSocket) {
|
||||
try {
|
||||
const sysMessage = await deps.messages.createSystem(guild.id, `<@${selfUser.id}> joined the guild.`, 'GUILD_MEMBER_JOIN');
|
||||
|
||||
|
||||
return {
|
||||
emit: 'MESSAGE_CREATE' as const,
|
||||
to: [guild.systemChannelId!],
|
||||
send: { message: sysMessage },
|
||||
};
|
||||
} catch {}
|
||||
} catch { }
|
||||
}
|
||||
|
||||
private async handleInvite(invite: InviteDocument) {
|
||||
const inviteExpired = Number(invite.options?.expiresAt?.getTime()) < new Date().getTime();
|
||||
if (inviteExpired)
|
||||
throw new TypeError('Invite expired');
|
||||
|
||||
|
||||
invite.uses++;
|
||||
|
||||
(invite.options?.maxUses && invite.uses >= invite.options.maxUses)
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { WS } from '@accord/types';
|
||||
import { WS } from '@acrd/types';
|
||||
import { Socket } from 'socket.io';
|
||||
import { GuildDocument } from '../../data/models/guild';
|
||||
import { GuildMember } from '../../data/models/guild-member';
|
||||
@ -12,19 +12,19 @@ export default class implements WSEvent<'GUILD_MEMBER_REMOVE'> {
|
||||
public async invoke(ws: WebSocket, client: Socket, { guildId, userId }: WS.Params.GuildMemberRemove) {
|
||||
const guild = await deps.guilds.get(guildId);
|
||||
const members = await deps.guilds.getMembers(guildId);
|
||||
const member = members.find(m => m.userId === userId);
|
||||
const member = members.find(m => m.userId === userId);
|
||||
if (!member)
|
||||
throw new TypeError('Member does not exist');
|
||||
|
||||
const selfUserId = ws.sessions.get(client.id);
|
||||
if (guild.ownerId === member.userId)
|
||||
throw new TypeError('You cannot leave a guild you own');
|
||||
|
||||
|
||||
else if (selfUserId !== member.userId)
|
||||
await deps.wsGuard.validateCan(client, guildId, 'KICK_MEMBERS');
|
||||
|
||||
// TODO: validate user is higher before kicking them
|
||||
|
||||
|
||||
const user = await deps.users.getSelf(member.userId);
|
||||
const index = user.guildIds.indexOf(guildId);
|
||||
user.guildIds.splice(index, 1);
|
||||
@ -38,7 +38,7 @@ export default class implements WSEvent<'GUILD_MEMBER_REMOVE'> {
|
||||
memberClient?.emit('GUILD_DELETE', { guildId } as WS.Args.GuildDelete);
|
||||
await client.leave(guildId);
|
||||
}
|
||||
|
||||
|
||||
await this.leaveGuildRooms(client, guild);
|
||||
|
||||
return [await this.leaveGuildMessage(guild, user), {
|
||||
@ -51,17 +51,17 @@ export default class implements WSEvent<'GUILD_MEMBER_REMOVE'> {
|
||||
send: { guildId },
|
||||
}];
|
||||
}
|
||||
|
||||
|
||||
private async leaveGuildMessage(guild: GuildDocument, user: SelfUserDocument) {
|
||||
try {
|
||||
const sysMessage = await deps.messages.createSystem(guild.id, `<@${user.id}> left the guild.`, 'GUILD_MEMBER_LEAVE');
|
||||
|
||||
|
||||
return {
|
||||
emit: 'MESSAGE_CREATE' as const,
|
||||
to: [guild.systemChannelId!],
|
||||
send: { message: sysMessage },
|
||||
};
|
||||
} catch {}
|
||||
} catch { }
|
||||
}
|
||||
|
||||
private async leaveGuildRooms(client: Socket, guild: GuildDocument) {
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { WS } from '@accord/types';
|
||||
import { WS } from '@acrd/types';
|
||||
import { Socket } from 'socket.io';
|
||||
import { WebSocket } from '../websocket';
|
||||
import { WSEvent } from './ws-event';
|
||||
@ -20,14 +20,14 @@ export default class implements WSEvent<'GUILD_MEMBER_UPDATE'> {
|
||||
const isSelf = selfMember.id === memberId;
|
||||
const selfIsOwner = selfMember.userId === guild.ownerId;
|
||||
if (!isSelf && !selfHasHigherRoles && !selfIsOwner)
|
||||
throw new TypeError('Member has higher roles');
|
||||
|
||||
throw new TypeError('Member has higher roles');
|
||||
|
||||
const everyoneRole = await deps.roles.getEveryone(guild.id);
|
||||
const partialMember = {
|
||||
roleIds: [everyoneRole.id].concat(roleIds ?? []),
|
||||
};
|
||||
await deps.guildMembers.update(managedMember.id, partialMember);
|
||||
|
||||
|
||||
return [{
|
||||
emit: this.on,
|
||||
to: [managedMember.guildId],
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { WS } from '@accord/types';
|
||||
import { WS } from '@acrd/types';
|
||||
import { Socket } from 'socket.io';
|
||||
import { WebSocket } from '../websocket';
|
||||
import { WSEvent, } from './ws-event';
|
||||
@ -8,7 +8,7 @@ export default class implements WSEvent<'GUILD_ROLE_CREATE'> {
|
||||
|
||||
public async invoke(ws: WebSocket, client: Socket, { guildId }: WS.Params.GuildRoleCreate) {
|
||||
await deps.wsGuard.validateCan(client, guildId, 'MANAGE_ROLES');
|
||||
|
||||
|
||||
const role = await deps.roles.create(guildId, { name: 'New Role' });
|
||||
|
||||
return [{
|
||||
|
@ -2,7 +2,7 @@ import { Socket } from 'socket.io';
|
||||
import { WebSocket } from '../websocket';
|
||||
import { WSEvent, } from './ws-event';
|
||||
import { GuildMember } from '../../data/models/guild-member';
|
||||
import { WS } from '@accord/types';
|
||||
import { WS } from '@acrd/types';
|
||||
|
||||
export default class implements WSEvent<'GUILD_ROLE_DELETE'> {
|
||||
public on = 'GUILD_ROLE_DELETE' as const;
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { WS } from '@accord/types';
|
||||
import { WS } from '@acrd/types';
|
||||
import { Socket } from 'socket.io';
|
||||
import { WebSocket } from '../websocket';
|
||||
import { WSEvent, } from './ws-event';
|
||||
@ -8,7 +8,7 @@ export default class implements WSEvent<'GUILD_ROLE_UPDATE'> {
|
||||
|
||||
public async invoke(ws: WebSocket, client: Socket, { roleId, guildId, name, color, permissions, hoisted }: WS.Params.GuildRoleUpdate) {
|
||||
await deps.wsGuard.validateCan(client, guildId, 'MANAGE_ROLES');
|
||||
|
||||
|
||||
const userId = ws.sessions.get(client.id);
|
||||
const guild = await deps.guilds.get(guildId);
|
||||
const selfMember = await deps.guildMembers.getInGuild(guildId, userId);
|
||||
@ -23,7 +23,7 @@ export default class implements WSEvent<'GUILD_ROLE_UPDATE'> {
|
||||
throw new TypeError('You cannot change @everyone role color');
|
||||
if (everyoneRole.id === roleId && hoisted)
|
||||
throw new TypeError('You cannot hoist @everyone role');
|
||||
|
||||
|
||||
// TODO: implement position
|
||||
const partialRole = { name, color, permissions, hoisted };
|
||||
await deps.roles.update(roleId, partialRole);
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { WS, Entity } from '@accord/types';
|
||||
import { WS, Entity } from '@acrd/types';
|
||||
import { Socket } from 'socket.io';
|
||||
import { WebSocket } from '../websocket';
|
||||
import { WSEvent } from './ws-event';
|
||||
@ -6,7 +6,7 @@ import { WSEvent } from './ws-event';
|
||||
export default class implements WSEvent<'GUILD_UPDATE'> {
|
||||
public on = 'GUILD_UPDATE' as const;
|
||||
|
||||
public async invoke(ws: WebSocket, client: Socket, { guildId, name, iconURL, systemChannelId }: WS.Params.GuildUpdate) {
|
||||
public async invoke(ws: WebSocket, client: Socket, { guildId, name, iconURL, systemChannelId }: WS.Params.GuildUpdate) {
|
||||
await deps.wsGuard.validateCan(client, guildId, 'MANAGE_GUILD');
|
||||
|
||||
const guild = await deps.guilds.get(guildId);
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { WS } from '@accord/types';
|
||||
import { WS } from '@acrd/types';
|
||||
import { Socket } from 'socket.io';
|
||||
import { WebSocket } from '../websocket';
|
||||
import { WSEvent, } from './ws-event';
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { WS } from '@accord/types';
|
||||
import { WS } from '@acrd/types';
|
||||
import { Socket } from 'socket.io';
|
||||
import { WebSocket } from '../websocket';
|
||||
import { WSEvent, } from './ws-event';
|
||||
|
@ -3,16 +3,16 @@ import { WebSocket } from '../websocket';
|
||||
import { WSEvent, } from './ws-event';
|
||||
import { Channel } from '../../data/models/channel';
|
||||
import striptags from 'striptags';
|
||||
import { WS } from '@accord/types';
|
||||
import { WS } from '@acrd/types';
|
||||
|
||||
export default class implements WSEvent<'MESSAGE_CREATE'> {
|
||||
on = 'MESSAGE_CREATE' as const;
|
||||
|
||||
public async invoke(ws: WebSocket, client: Socket, { attachmentURLs, channelId, content, embed }: WS.Params.MessageCreate) {
|
||||
const authorId = ws.sessions.userId(client);
|
||||
|
||||
|
||||
const [_, message, author] = await Promise.all([
|
||||
deps.wsGuard.validateCanInChannel(client, channelId, 'SEND_MESSAGES'),
|
||||
deps.wsGuard.validateCanInChannel(client, channelId, 'SEND_MESSAGES'),
|
||||
deps.messages.create(authorId, channelId, {
|
||||
attachmentURLs,
|
||||
content: (content) ? striptags(content) : '',
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { WS } from '@accord/types';
|
||||
import { WS } from '@acrd/types';
|
||||
import { Socket } from 'socket.io';
|
||||
import { Message } from '../../data/models/message';
|
||||
import { WebSocket } from '../websocket';
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { Entity, WS } from '@accord/types';
|
||||
import { Entity, WS } from '@acrd/types';
|
||||
import { Socket } from 'socket.io';
|
||||
import { WebSocket } from '../websocket';
|
||||
import { WSEvent, } from './ws-event';
|
||||
@ -9,7 +9,7 @@ export default class implements WSEvent<'MESSAGE_UPDATE'> {
|
||||
public async invoke(ws: WebSocket, client: Socket, { messageId, content, embed }: WS.Params.MessageUpdate) {
|
||||
const message = await deps.messages.get(messageId);
|
||||
deps.wsGuard.validateIsUser(client, message.authorId);
|
||||
|
||||
|
||||
const partial: Partial<Entity.Message> = {};
|
||||
if (content) partial.content = content;
|
||||
partial.updatedAt = new Date();
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { WS } from '@accord/types';
|
||||
import { WS } from '@acrd/types';
|
||||
import { Socket } from 'socket.io';
|
||||
import { WebSocket } from '../websocket';
|
||||
import { WSEvent, } from './ws-event';
|
||||
@ -19,7 +19,7 @@ export default class implements WSEvent<'READY'> {
|
||||
try {
|
||||
if (user.voice.channelId)
|
||||
await deps.channelJoin.invoke(ws, client, { channelId: user.voice.channelId });
|
||||
} catch {}
|
||||
} catch { }
|
||||
|
||||
user.status = 'ONLINE';
|
||||
await user.save();
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { WS } from '@accord/types';
|
||||
import { WS } from '@acrd/types';
|
||||
import { Socket } from 'socket.io';
|
||||
import { WebSocket } from '../websocket';
|
||||
import { WSEvent, } from './ws-event';
|
||||
@ -6,10 +6,10 @@ import { WSEvent, } from './ws-event';
|
||||
export default class implements WSEvent<'TYPING_START'> {
|
||||
public on = 'TYPING_START' as const;
|
||||
|
||||
public async invoke(ws: WebSocket, client: Socket, { channelId }: WS.Params.TypingStart) {
|
||||
public async invoke(ws: WebSocket, client: Socket, { channelId }: WS.Params.TypingStart) {
|
||||
if (!client.rooms.has(channelId))
|
||||
await client.join(channelId);
|
||||
|
||||
|
||||
return [{
|
||||
emit: this.on,
|
||||
to: [channelId],
|
||||
|
@ -2,7 +2,7 @@ import { WSEvent } from './ws-event';
|
||||
import { Socket } from 'socket.io';
|
||||
import { WebSocket } from '../websocket';
|
||||
import generateInvite from '../../data/utils/generate-invite';
|
||||
import { WS } from '@accord/types';
|
||||
import { WS } from '@acrd/types';
|
||||
|
||||
export default class implements WSEvent<'USER_DELETE'> {
|
||||
public on = 'USER_DELETE' as const;
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { Entity, WS } from '@accord/types';
|
||||
import { Entity, WS } from '@acrd/types';
|
||||
import { Socket } from 'socket.io';
|
||||
import { WebSocket } from '../websocket';
|
||||
import { WSEvent, } from './ws-event';
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { WS } from '@accord/types';
|
||||
import { WS } from '@acrd/types';
|
||||
import { Socket } from 'socket.io';
|
||||
import { WebSocket } from '../websocket';
|
||||
import { WSEvent, } from './ws-event';
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { WS } from '@accord/types';
|
||||
import { WS } from '@acrd/types';
|
||||
import { Socket } from 'socket.io';
|
||||
import { WebSocket } from '../websocket';
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
import '@accord/types';
|
||||
import '@acrd/types';
|
||||
import { expect } from 'chai';
|
||||
import { given, test } from '@acrd/ion';
|
||||
import { SelfUserDocument } from '@accord/backend/data/models/user';
|
||||
|
@ -1,4 +1,4 @@
|
||||
import '@accord/types';
|
||||
import '@acrd/types';
|
||||
import { given, test } from '@acrd/ion';
|
||||
import { SelfUserDocument } from '@accord/backend/data/models/user';
|
||||
import { generateSnowflake } from '@accord/backend/data/snowflake-entity';
|
||||
|
@ -1,4 +1,4 @@
|
||||
import '@accord/types';
|
||||
import '@acrd/types';
|
||||
import { expect } from 'chai';
|
||||
import { given, test } from '@acrd/ion';
|
||||
import { SelfUserDocument } from '@accord/backend/data/models/user';
|
||||
|
@ -2,7 +2,7 @@ import { Channel } from '../../../src/data/models/channel';
|
||||
import { generateSnowflake } from '../../../src/data/snowflake-entity';
|
||||
import { test, given } from '@acrd/ion';
|
||||
import { longString, mongooseError } from '../../test-utils';
|
||||
import { Entity } from '@accord/types';
|
||||
import { Entity } from '@acrd/types';
|
||||
|
||||
test(createChannel, () => {
|
||||
given().expect(true);
|
||||
|
@ -2,7 +2,7 @@ import { generateSnowflake } from '../../../src/data/snowflake-entity';
|
||||
import { test, given } from '@acrd/ion';
|
||||
import { longString, mongooseError } from '../../test-utils';
|
||||
import { Role } from '../../../src/data/models/role';
|
||||
import { PermissionTypes } from '@accord/types';
|
||||
import { PermissionTypes } from '@acrd/types';
|
||||
|
||||
test(createRole, () => {
|
||||
given().expect(true);
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { test, given } from '@acrd/ion';
|
||||
import { generateSnowflake, snowflakeToDate } from '../../../src/data/snowflake-entity';
|
||||
import { patterns } from '@accord/types';
|
||||
import { patterns } from '@acrd/types';
|
||||
|
||||
describe('snowflake-entity', () => {
|
||||
test(generateSnowflake, () => {
|
||||
|
6
frontend/package-lock.json
generated
6
frontend/package-lock.json
generated
@ -8,7 +8,7 @@
|
||||
"name": "@acrd/frontend",
|
||||
"version": "0.0.0",
|
||||
"dependencies": {
|
||||
"@accord/types": "file:../types",
|
||||
"@acrd/types": "file:../types",
|
||||
"@craco/craco": "^5.9.0",
|
||||
"@dvhb/craco-extend-scope": "^1.0.1",
|
||||
"@fortawesome/fontawesome-svg-core": "^1.2.35",
|
||||
@ -83,7 +83,7 @@
|
||||
"fsevents": "^2.3.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@accord/types": {
|
||||
"node_modules/@acrd/types": {
|
||||
"resolved": "file:../types"
|
||||
},
|
||||
"node_modules/@babel/code-frame": {
|
||||
@ -21785,7 +21785,7 @@
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@accord/types": {},
|
||||
"@acrd/types": {},
|
||||
"@babel/code-frame": {
|
||||
"version": "7.18.6",
|
||||
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz",
|
||||
|
@ -3,7 +3,7 @@
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@accord/types": "file:../types",
|
||||
"@acrd/types": "file:../types",
|
||||
"@craco/craco": "^5.9.0",
|
||||
"@dvhb/craco-extend-scope": "^1.0.1",
|
||||
"@fortawesome/fontawesome-svg-core": "^1.2.35",
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { Util } from '@accord/types';
|
||||
import { Util } from '@acrd/types';
|
||||
import React from 'react';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
@ -14,7 +14,7 @@ export interface MessageBoxProps {
|
||||
cachedContent?: Util.Dictionary;
|
||||
setCachedContent?: React.Dispatch<React.SetStateAction<Util.Dictionary>>;
|
||||
}
|
||||
|
||||
|
||||
const MessageBox: React.FunctionComponent<MessageBoxProps> = (props) => {
|
||||
const dispatch = useDispatch();
|
||||
const [content, setContent] = useState(props.content ?? '');
|
||||
@ -38,28 +38,28 @@ const MessageBox: React.FunctionComponent<MessageBoxProps> = (props) => {
|
||||
(editingMessageId)
|
||||
? dispatch(updateMessage(editingMessageId, { content }))
|
||||
: dispatch(createMessage(channel.id, { content }));
|
||||
|
||||
|
||||
setContent('');
|
||||
stopEditing();
|
||||
}
|
||||
|
||||
|
||||
return (
|
||||
<div className={(editingMessageId) ? 'mt-2' : 'px-4'}>
|
||||
<div className="rounded-lg bg-bg-secondary flex items-center">
|
||||
<MessageBoxLeftSide
|
||||
content={content}
|
||||
editingMessageId={editingMessageId} />
|
||||
editingMessageId={editingMessageId} />
|
||||
<MessageBoxInput
|
||||
contentState={[content, setContent]}
|
||||
saveEdit={saveEdit} />
|
||||
</div>
|
||||
<div className="text-sm w-full h-6">
|
||||
{(editingMessageId)
|
||||
? <span className="text-xs py-2">
|
||||
escape to <Link to="#" onClick={stopEditing}>cancel</Link> •
|
||||
{(editingMessageId)
|
||||
? <span className="text-xs py-2">
|
||||
escape to <Link to="#" onClick={stopEditing}>cancel</Link> •
|
||||
enter to <Link to="#" onClick={saveEdit}> save</Link>
|
||||
</span>
|
||||
: <TypingUsers />}
|
||||
: <TypingUsers />}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { Entity } from '@accord/types';
|
||||
import { Entity } from '@acrd/types';
|
||||
import MessageBox from '../message-box/message-box';
|
||||
import { FunctionComponent } from 'react';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
@ -41,20 +41,20 @@ const MessageContent: FunctionComponent<MessageContentProps> = ({ message }) =>
|
||||
}} />)}
|
||||
</>
|
||||
);
|
||||
|
||||
|
||||
return (editingMessageId === message.id)
|
||||
? <MessageBox content={message.content} />
|
||||
: <div className="relative">
|
||||
<div
|
||||
style={{ maxWidth: '963px' }}
|
||||
className="normal whitespace-pre-wrap">
|
||||
<div
|
||||
style={{ maxWidth: '963px' }}
|
||||
className="normal whitespace-pre-wrap">
|
||||
<div
|
||||
dangerouslySetInnerHTML={{ __html: messageHTML }}
|
||||
className="overflow-auto"
|
||||
style={{ maxWidth: '100%' }} />
|
||||
<Attachments />
|
||||
</div>
|
||||
</div>;
|
||||
dangerouslySetInnerHTML={{ __html: messageHTML }}
|
||||
className="overflow-auto"
|
||||
style={{ maxWidth: '100%' }} />
|
||||
<Attachments />
|
||||
</div>
|
||||
</div>;
|
||||
}
|
||||
|
||||
export default MessageContent;
|
@ -1,11 +1,11 @@
|
||||
import { MessageTypes } from '@accord/types';
|
||||
import { MessageTypes } from '@acrd/types';
|
||||
import { FunctionComponent } from 'react';
|
||||
import Image from '../../utils/image';
|
||||
|
||||
interface MessageEmbedProps {
|
||||
embed: MessageTypes.Embed;
|
||||
}
|
||||
|
||||
|
||||
const MessageEmbed: FunctionComponent<MessageEmbedProps> = ({ embed }) => {
|
||||
return (embed) ? (
|
||||
<div style={{ borderLeft: '5px solid var(--muted)' }}
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { Entity } from '@accord/types';
|
||||
import { Entity } from '@acrd/types';
|
||||
import { FunctionComponent } from 'react';
|
||||
import { ContextMenuTrigger } from 'react-contextmenu';
|
||||
import { useSelector } from 'react-redux';
|
||||
@ -11,7 +11,7 @@ interface MessageHeaderProps {
|
||||
author?: Entity.User;
|
||||
isExtra?: boolean;
|
||||
}
|
||||
|
||||
|
||||
const MessageHeader: FunctionComponent<MessageHeaderProps> = ({ author, message, isExtra = false }) => {
|
||||
const guild = useSelector((s: Store.AppState) => s.ui.activeGuild)!;
|
||||
const member = useSelector(getMember(guild.id, message.authorId));
|
||||
@ -32,8 +32,8 @@ const MessageHeader: FunctionComponent<MessageHeaderProps> = ({ author, message,
|
||||
) : (
|
||||
<span className="text-xs muted">
|
||||
<MessageTimestamp message={message} />
|
||||
</span>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
export default MessageHeader;
|
@ -1,13 +1,13 @@
|
||||
import { Entity } from '@accord/types';
|
||||
import { Entity } from '@acrd/types';
|
||||
import moment from 'moment';
|
||||
import { FunctionComponent } from 'react';
|
||||
|
||||
interface MessageTimestampProps {
|
||||
message: Entity.Message;
|
||||
}
|
||||
|
||||
|
||||
const MessageTimestamp: FunctionComponent<MessageTimestampProps> = ({ message }) => {
|
||||
const toDays = (date: Date) => date.getTime() / 1000 / 60 / 60 / 24;
|
||||
const toDays = (date: Date) => date.getTime() / 1000 / 60 / 60 / 24;
|
||||
|
||||
const createdAt = new Date(message.createdAt);
|
||||
const midnight = new Date(new Date().setHours(0, 0, 0, 0));
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { Entity } from '@accord/types';
|
||||
import { Entity } from '@acrd/types';
|
||||
import { faPencilAlt, faTimes } from '@fortawesome/free-solid-svg-icons';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
@ -9,7 +9,7 @@ import { actions as ui } from '../../../store/ui';
|
||||
export interface MessageToolbarProps {
|
||||
message: Entity.Message;
|
||||
}
|
||||
|
||||
|
||||
const MessageToolbar: React.FunctionComponent<MessageToolbarProps> = ({ message }) => {
|
||||
const dispatch = useDispatch();
|
||||
const selfUser = useSelector((s: Store.AppState) => s.auth.user)!;
|
||||
@ -21,7 +21,7 @@ const MessageToolbar: React.FunctionComponent<MessageToolbarProps> = ({ message
|
||||
const canManage = perms.canInChannel('MANAGE_MESSAGES', guild.id, message.channelId)
|
||||
|| guild?.ownerId === selfUser.id
|
||||
|| isAuthor;
|
||||
|
||||
|
||||
return (!openModal) ? (
|
||||
<div className="float-right shadow bg-bg-secondary px-2 rounded cursor-pointer">
|
||||
{isAuthor && (
|
||||
@ -35,13 +35,13 @@ const MessageToolbar: React.FunctionComponent<MessageToolbarProps> = ({ message
|
||||
{canManage && (
|
||||
<div className="inline">
|
||||
<FontAwesomeIcon
|
||||
onClick={() => dispatch(deleteMessage(message.id))}
|
||||
className="danger"
|
||||
icon={faTimes} />
|
||||
onClick={() => dispatch(deleteMessage(message.id))}
|
||||
className="danger"
|
||||
icon={faTimes} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
) : null;
|
||||
}
|
||||
|
||||
|
||||
export default MessageToolbar;
|
@ -16,7 +16,7 @@ import { openUserProfile } from '../../../store/ui';
|
||||
import React from 'react';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import { faArrowLeft, faArrowRight, faInfoCircle } from '@fortawesome/free-solid-svg-icons';
|
||||
import { Entity } from '@accord/types';
|
||||
import { Entity } from '@acrd/types';
|
||||
|
||||
export interface MessageProps {
|
||||
message: Entity.Message;
|
||||
@ -32,11 +32,11 @@ const Message: React.FunctionComponent<MessageProps> = ({ message }: MessageProp
|
||||
const prev = messages[i - 1];
|
||||
if (!prev) return false;
|
||||
|
||||
const minsSince = moment(createdAt).diff(prev.createdAt, 'minutes');
|
||||
const minsSince = moment(createdAt).diff(prev.createdAt, 'minutes');
|
||||
const minsToSeparate = 5;
|
||||
|
||||
return minsSince <= minsToSeparate
|
||||
&& prev.authorId === message.authorId;
|
||||
&& prev.authorId === message.authorId;
|
||||
}
|
||||
const isActuallyExtra = isExtra();
|
||||
|
||||
@ -48,30 +48,30 @@ const Message: React.FunctionComponent<MessageProps> = ({ message }: MessageProp
|
||||
className="ml-8 mt-1.5"
|
||||
color="var(--success)"
|
||||
icon={faArrowRight} />
|
||||
);
|
||||
);
|
||||
if (message.system && message.type === 'GUILD_MEMBER_LEAVE') return (
|
||||
<FontAwesomeIcon
|
||||
className="ml-8 mt-1.5"
|
||||
color="var(--danger)"
|
||||
icon={faArrowLeft} />
|
||||
);
|
||||
);
|
||||
if (message.system) return (
|
||||
<FontAwesomeIcon
|
||||
className="ml-8 mt-2"
|
||||
color="var(--tertiary)"
|
||||
icon={faInfoCircle} />
|
||||
);
|
||||
);
|
||||
|
||||
return (isActuallyExtra)
|
||||
? <span className="timestamp text-xs select-none">
|
||||
{moment(createdAt).format('HH:mm')}
|
||||
</span>
|
||||
{moment(createdAt).format('HH:mm')}
|
||||
</span>
|
||||
: <Image
|
||||
className="rounded-full cursor-pointer w-10 h-10"
|
||||
src={`${process.env.REACT_APP_CDN_URL}${author.avatarURL}`}
|
||||
onError={e => e.currentTarget.src = `${process.env.REACT_APP_CDN_URL}/avatars/unknown.png`}
|
||||
onClick={() => dispatch(openUserProfile(author))}
|
||||
alt={author.username} />;
|
||||
className="rounded-full cursor-pointer w-10 h-10"
|
||||
src={`${process.env.REACT_APP_CDN_URL}${author.avatarURL}`}
|
||||
onError={e => e.currentTarget.src = `${process.env.REACT_APP_CDN_URL}/avatars/unknown.png`}
|
||||
onClick={() => dispatch(openUserProfile(author))}
|
||||
alt={author.username} />;
|
||||
}
|
||||
|
||||
return (
|
||||
|
@ -6,8 +6,8 @@ import { useEffect, useRef, useState } from 'react';
|
||||
import TextChannelHeader from './text-channel-header';
|
||||
import usePerms from '../../hooks/use-perms';
|
||||
import SkeletonMessage from '../skeleton/skeleton-message';
|
||||
import { Util } from '@accord/types';
|
||||
|
||||
import { Util } from '@acrd/types';
|
||||
|
||||
const TextBasedChannel: React.FunctionComponent = () => {
|
||||
const dispatch = useDispatch();
|
||||
const channel = useSelector((s: Store.AppState) => s.ui.activeChannel)!;
|
||||
@ -17,7 +17,7 @@ const TextBasedChannel: React.FunctionComponent = () => {
|
||||
const [cachedContent, setCachedContent] = useState<Util.Dictionary>({});
|
||||
const messagesRef = useRef<HTMLDivElement>(null);
|
||||
const msgCount = useSelector((s: Store.AppState) => s.entities.messages.total[channel.id]);
|
||||
|
||||
|
||||
const batchSize = 25;
|
||||
const loadedAllMessages = msgCount === messages.length;
|
||||
|
||||
@ -30,7 +30,7 @@ const TextBasedChannel: React.FunctionComponent = () => {
|
||||
useEffect(() => {
|
||||
const messageBox = document.querySelector('#messageBox') as HTMLDivElement;
|
||||
messageBox.focus();
|
||||
|
||||
|
||||
dispatch(fetchMessages(channel.id, batchSize));
|
||||
}, [channel.id]);
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { Entity } from '@accord/types';
|
||||
import { Entity } from '@acrd/types';
|
||||
import { faCross, faTimes } from '@fortawesome/free-solid-svg-icons';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import { ContextMenu, MenuItem } from 'react-contextmenu';
|
||||
@ -11,7 +11,7 @@ import DevModeMenuSection from './dev-mode-menu-section';
|
||||
export interface ChannelMenusProps {
|
||||
channel: Entity.Channel;
|
||||
}
|
||||
|
||||
|
||||
const ChannelMenu: React.FunctionComponent<ChannelMenusProps> = ({ channel }) => {
|
||||
const dispatch = useDispatch();
|
||||
const { guildId }: any = useParams();
|
||||
@ -37,5 +37,5 @@ const ChannelMenu: React.FunctionComponent<ChannelMenusProps> = ({ channel }) =>
|
||||
</ContextMenu>
|
||||
) : null;
|
||||
}
|
||||
|
||||
|
||||
export default ChannelMenu;
|
@ -1,4 +1,4 @@
|
||||
import { Entity } from '@accord/types';
|
||||
import { Entity } from '@acrd/types';
|
||||
import { faBan, faUser } from '@fortawesome/free-solid-svg-icons';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import { ContextMenu, MenuItem } from 'react-contextmenu';
|
||||
@ -23,7 +23,7 @@ const GuildMemberMenu: React.FunctionComponent<GuildMemberMenuProps> = ({ user }
|
||||
const member = useSelector(getMember(guild.id, user.id))!;
|
||||
const devMode = useSelector((s: Store.AppState) => s.config.devMode);
|
||||
|
||||
const isSelf = user.id === selfUser.id;
|
||||
const isSelf = user.id === selfUser.id;
|
||||
const userIsBlocked = selfUser.ignored?.userIds.includes(member.userId);
|
||||
|
||||
const canKick = perms.can('KICK_MEMBERS', guild.id);
|
||||
@ -46,7 +46,7 @@ const GuildMemberMenu: React.FunctionComponent<GuildMemberMenuProps> = ({ user }
|
||||
<span>View Profile</span>
|
||||
<FontAwesomeIcon icon={faUser} />
|
||||
</MenuItem>
|
||||
|
||||
|
||||
{user.id !== selfUser.id && (<>
|
||||
<hr className="my-2 border-bg-primary" />
|
||||
<MenuItem className="flex items-center justify-between cursor-pointer danger">
|
||||
@ -82,5 +82,5 @@ const GuildMemberMenu: React.FunctionComponent<GuildMemberMenuProps> = ({ user }
|
||||
</ContextMenu>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
export default GuildMemberMenu;
|
@ -4,7 +4,7 @@ import { getGuildRoles } from '../../../store/guilds';
|
||||
import { useState } from 'react';
|
||||
import { updateMember } from '../../../store/members';
|
||||
import usePerms from '../../../hooks/use-perms';
|
||||
import { Entity } from '@accord/types';
|
||||
import { Entity } from '@acrd/types';
|
||||
|
||||
export interface RoleManagerProps {
|
||||
member: Entity.GuildMember;
|
||||
@ -50,7 +50,7 @@ const RoleManager: React.FunctionComponent<RoleManagerProps> = ({ member }) => {
|
||||
backgroundColor: 'var(--bg-tertiary)',
|
||||
}),
|
||||
};
|
||||
|
||||
|
||||
const rolesHaveChanged = JSON.stringify(roleIds) !== JSON.stringify(slicedRoleIds);
|
||||
const roleOption = (role: Entity.Role) => ({
|
||||
label: role.name,
|
||||
@ -58,7 +58,7 @@ const RoleManager: React.FunctionComponent<RoleManagerProps> = ({ member }) => {
|
||||
color: role.color,
|
||||
disabled: !perms.memberIsHigher(guild.id, [role.id]),
|
||||
});
|
||||
|
||||
|
||||
return (
|
||||
<div onClick={e => e.preventDefault()}>
|
||||
<Select
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { Entity } from '@accord/types';
|
||||
import { Entity } from '@acrd/types';
|
||||
import { faDoorOpen } from '@fortawesome/free-solid-svg-icons';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import { ContextMenu, MenuItem } from 'react-contextmenu';
|
||||
@ -31,5 +31,5 @@ const GuildMenu: React.FunctionComponent<GuildMenuProps> = ({ guild }) => {
|
||||
</ContextMenu>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
export default GuildMenu;
|
@ -1,4 +1,4 @@
|
||||
import { Entity } from '@accord/types';
|
||||
import { Entity } from '@acrd/types';
|
||||
import { ContextMenu } from 'react-contextmenu';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { useParams } from 'react-router-dom';
|
||||
@ -7,7 +7,7 @@ import DevModeMenuSection from './dev-mode-menu-section';
|
||||
export interface MessageMenuProps {
|
||||
message: Entity.Message;
|
||||
}
|
||||
|
||||
|
||||
const MessageMenu: React.FunctionComponent<MessageMenuProps> = ({ message }) => {
|
||||
const { guildId }: any = useParams();
|
||||
const devMode = useSelector((s: Store.AppState) => s.config.devMode);
|
||||
@ -26,5 +26,5 @@ const MessageMenu: React.FunctionComponent<MessageMenuProps> = ({ message }) =>
|
||||
</ContextMenu>
|
||||
) : null;
|
||||
}
|
||||
|
||||
|
||||
export default MessageMenu;
|
@ -1,4 +1,4 @@
|
||||
import { Entity } from '@accord/types';
|
||||
import { Entity } from '@acrd/types';
|
||||
import { ContextMenu } from 'react-contextmenu';
|
||||
import { useSelector } from 'react-redux';
|
||||
import DevModeMenuSection from './dev-mode-menu-section';
|
||||
@ -22,5 +22,5 @@ const RoleMenu: React.FunctionComponent<RoleMenuProps> = ({ role }) => {
|
||||
</ContextMenu>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
export default RoleMenu;
|
@ -1,16 +1,16 @@
|
||||
import { Entity } from '@accord/types';
|
||||
import { Entity } from '@acrd/types';
|
||||
import { FunctionComponent } from 'react';
|
||||
import { ContextMenu } from 'react-contextmenu';
|
||||
|
||||
interface UserMenuProps {
|
||||
user: Entity.User;
|
||||
}
|
||||
|
||||
|
||||
const UserMenu: FunctionComponent<UserMenuProps> = ({ user }) => {
|
||||
return (
|
||||
<ContextMenu id={user.id}>
|
||||
</ContextMenu>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
export default UserMenu;
|
@ -1,4 +1,4 @@
|
||||
import { Entity } from '@accord/types';
|
||||
import { Entity } from '@acrd/types';
|
||||
import { UseFormRegister, FieldValues } from 'react-hook-form';
|
||||
import Select from 'react-select';
|
||||
|
||||
@ -10,7 +10,7 @@ interface ChannelSelectProps {
|
||||
register: UseFormRegister<FieldValues>;
|
||||
options?: any;
|
||||
};
|
||||
|
||||
|
||||
const ChannelSelect: React.FunctionComponent<ChannelSelectProps> = (props) => {
|
||||
const channelOptions: any[] = props.channels
|
||||
.filter(c => c.type === 'TEXT')
|
||||
@ -20,7 +20,7 @@ const ChannelSelect: React.FunctionComponent<ChannelSelectProps> = (props) => {
|
||||
value: '',
|
||||
color: 'var(--muted)',
|
||||
}].concat(channelOptions);
|
||||
|
||||
|
||||
const styles = {
|
||||
singleValue: () => ({ color: 'var(--font)' }),
|
||||
control: () => ({
|
||||
@ -45,7 +45,7 @@ const ChannelSelect: React.FunctionComponent<ChannelSelectProps> = (props) => {
|
||||
...styles,
|
||||
float: 'right',
|
||||
marginTop: '-38px',
|
||||
}),
|
||||
}),
|
||||
};
|
||||
|
||||
const id = props.name + 'Input';
|
||||
@ -66,5 +66,5 @@ const ChannelSelect: React.FunctionComponent<ChannelSelectProps> = (props) => {
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
export default ChannelSelect;
|
@ -14,12 +14,12 @@ import PermOverrides from './perm-overrides';
|
||||
import ScarceSelect from './scarce-select';
|
||||
import clone from 'clone';
|
||||
import { uniqueBy } from '../../../store/utils/filter';
|
||||
import { ChannelTypes } from '@accord/types';
|
||||
|
||||
const ChannelSettingsPerms: React.FunctionComponent = () => {
|
||||
const { guildId }: any = useParams();
|
||||
import { ChannelTypes } from '@acrd/types';
|
||||
|
||||
const ChannelSettingsPerms: React.FunctionComponent = () => {
|
||||
const { guildId }: any = useParams();
|
||||
const channel = useSelector((s: Store.AppState) => s.ui.activeChannel)!;
|
||||
const roles = useSelector(getGuildRoles(guildId));
|
||||
const roles = useSelector(getGuildRoles(guildId));
|
||||
const dispatch = useDispatch();
|
||||
const defaultOverride = clone(channel.overrides?.[0]) ?? {
|
||||
allow: 0,
|
||||
@ -43,12 +43,12 @@ const ChannelSettingsPerms: React.FunctionComponent = () => {
|
||||
const filtered = cloned
|
||||
.filter(c => c.allow + c.deny > 0)
|
||||
.filter(uniqueBy('roleId'));
|
||||
|
||||
|
||||
const index = filtered.findIndex(o => o.roleId === roleId);
|
||||
(index < 0)
|
||||
? filtered.push(override)
|
||||
: filtered[index] = override!;
|
||||
|
||||
|
||||
dispatch(updateChannel(channel.id, { overrides: filtered }));
|
||||
}
|
||||
const onReset = () => setOverride(defaultOverride);
|
||||
@ -63,7 +63,7 @@ const ChannelSettingsPerms: React.FunctionComponent = () => {
|
||||
<SaveChanges
|
||||
onSave={onSave}
|
||||
onReset={onReset}
|
||||
obj={override} />
|
||||
obj={override} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
@ -105,5 +105,5 @@ const ChannelSettingsPerms: React.FunctionComponent = () => {
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
export default ChannelSettingsPerms;
|
@ -1,5 +1,5 @@
|
||||
import { ChannelTypes } from '@accord/types';
|
||||
import { PermissionTypes } from '@accord/types';
|
||||
import { ChannelTypes } from '@acrd/types';
|
||||
import { PermissionTypes } from '@acrd/types';
|
||||
import React from 'react';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import usePerms from '../../../hooks/use-perms';
|
||||
@ -11,14 +11,14 @@ import PermToggle from './perm-toggle';
|
||||
export interface PermOverrides {
|
||||
overrideState: [ChannelTypes.Override, React.Dispatch<React.SetStateAction<ChannelTypes.Override>>];
|
||||
}
|
||||
|
||||
|
||||
const PermOverrides: React.FunctionComponent<PermOverrides> = ({ overrideState }) => {
|
||||
const dispatch = useDispatch();
|
||||
const { description } = usePerms();
|
||||
const channel = useSelector((s: Store.AppState) => s.ui.activeChannel)!;
|
||||
const [override, setOverride] = overrideState;
|
||||
|
||||
const category = channel.type.toLowerCase();
|
||||
const category = channel.type.toLowerCase();
|
||||
if (channel.type === 'VOICE') return null;
|
||||
|
||||
const clearOverrides = () => {
|
||||
@ -32,9 +32,9 @@ const PermOverrides: React.FunctionComponent<PermOverrides> = ({ overrideState }
|
||||
<Category
|
||||
className="muted pb-1.5 mt-5"
|
||||
title={category} />
|
||||
{Object
|
||||
.keys(description[category])
|
||||
.map(permName => (
|
||||
{Object
|
||||
.keys(description[category])
|
||||
.map(permName => (
|
||||
<div key={permName}>
|
||||
<strong
|
||||
title={PermissionTypes.All[permName]}
|
||||
@ -52,5 +52,5 @@ const PermOverrides: React.FunctionComponent<PermOverrides> = ({ overrideState }
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
export default PermOverrides;
|
@ -1,5 +1,5 @@
|
||||
import { ChannelTypes } from '@accord/types';
|
||||
import { PermissionTypes } from '@accord/types';
|
||||
import { ChannelTypes } from '@acrd/types';
|
||||
import { PermissionTypes } from '@acrd/types';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import usePerms from '../../../hooks/use-perms';
|
||||
import { openSaveChanges } from '../../../store/ui';
|
||||
@ -18,28 +18,28 @@ const PermToggle: React.FunctionComponent<PermToggleProps> = ({ overrideState, p
|
||||
|
||||
const isAllowed = (name: string) => Boolean(override.allow & PermissionTypes.All[name]);
|
||||
const isDenied = (name: string) => Boolean(override.deny & PermissionTypes.All[name]);
|
||||
|
||||
|
||||
const getDefaultValue = () => {
|
||||
if (isAllowed(permName)) return 'on';
|
||||
else if (isDenied(permName)) return 'off';
|
||||
return 'n/a';
|
||||
};
|
||||
|
||||
const togglePerm = (name: string, state: string) => {
|
||||
|
||||
const togglePerm = (name: string, state: string) => {
|
||||
if (state === 'off') {
|
||||
override.allow &= ~PermissionTypes.All[name];
|
||||
override.deny &= ~PermissionTypes.All[name];
|
||||
} else if (state === 'n/a') {
|
||||
override.allow |= PermissionTypes.All[name];
|
||||
override.deny &= ~PermissionTypes.All[name];
|
||||
} else if(state === 'on') {
|
||||
} else if (state === 'on') {
|
||||
override.allow &= ~PermissionTypes.All[name];
|
||||
override.deny |= PermissionTypes.All[name];
|
||||
}
|
||||
setOverride(override);
|
||||
dispatch(openSaveChanges(true));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<span>{description[category][permName]}</span>
|
||||
|
@ -4,7 +4,7 @@ import { createChannel } from '../../store/channels';
|
||||
import NormalButton from '../utils/buttons/normal-button';
|
||||
import Input from '../inputs/input';
|
||||
import Modal from './modal';
|
||||
import { ChannelTypes } from '@accord/types';
|
||||
import { ChannelTypes } from '@acrd/types';
|
||||
|
||||
const CreateChannel: React.FunctionComponent = () => {
|
||||
const dispatch = useDispatch();
|
||||
@ -17,7 +17,7 @@ const CreateChannel: React.FunctionComponent = () => {
|
||||
};
|
||||
|
||||
const types: ChannelTypes.Type[] = ['TEXT', 'VOICE'];
|
||||
|
||||
|
||||
return (
|
||||
<Modal typeName={'CreateChannel'} size="sm">
|
||||
<form
|
||||
@ -26,26 +26,26 @@ const CreateChannel: React.FunctionComponent = () => {
|
||||
<header className="text-center mb-5 p-5">
|
||||
<h1 className="text-2xl font-bold inline">Create Channel</h1>
|
||||
</header>
|
||||
|
||||
|
||||
<div className="flex-grow p-5">
|
||||
<Input
|
||||
label="Channel Name"
|
||||
name="name"
|
||||
register={register} />
|
||||
</div>
|
||||
|
||||
<div className="flex-grow pt-0 p-5">
|
||||
<label
|
||||
htmlFor="channelType"
|
||||
className="uppercase text-xs font-semibold">Channel Type</label>
|
||||
<select
|
||||
id="channelType"
|
||||
className="block bg-bg-secondary rounded focus:outline-none w-full h-10 p-2 mt-2"
|
||||
defaultValue={types[0]}
|
||||
{...register('type')}>
|
||||
{types.map(type => <option key={type} value={type}>{type}</option>)}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div className="flex-grow pt-0 p-5">
|
||||
<label
|
||||
htmlFor="channelType"
|
||||
className="uppercase text-xs font-semibold">Channel Type</label>
|
||||
<select
|
||||
id="channelType"
|
||||
className="block bg-bg-secondary rounded focus:outline-none w-full h-10 p-2 mt-2"
|
||||
defaultValue={types[0]}
|
||||
{...register('type')}>
|
||||
{types.map(type => <option key={type} value={type}>{type}</option>)}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<footer className="bg-bg-secondary">
|
||||
<NormalButton
|
||||
@ -55,5 +55,5 @@ const CreateChannel: React.FunctionComponent = () => {
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
export default CreateChannel;
|
@ -5,18 +5,18 @@ import { openSaveChanges } from '../../../store/ui';
|
||||
import NormalButton from '../../utils/buttons/normal-button';
|
||||
import Category from '../../utils/category';
|
||||
import Toggle from '../../inputs/toggle';
|
||||
import { PermissionTypes } from '@accord/types';
|
||||
import { PermissionTypes } from '@acrd/types';
|
||||
|
||||
export interface RolePermissionsProps {
|
||||
setRoleValue: UseFormSetValue<FieldValues>;
|
||||
setPerms: React.Dispatch<React.SetStateAction<number>>;
|
||||
perms: number;
|
||||
}
|
||||
|
||||
|
||||
const RolePermissions: React.FunctionComponent<RolePermissionsProps> = ({ perms, setPerms, setRoleValue: setValue }) => {
|
||||
const dispatch = useDispatch();
|
||||
const { description } = usePerms();
|
||||
|
||||
|
||||
const fullySetPerms = (perms: number) => {
|
||||
setPerms(perms);
|
||||
setValue('permissions', perms);
|
||||
@ -43,17 +43,17 @@ const RolePermissions: React.FunctionComponent<RolePermissionsProps> = ({ perms,
|
||||
{Object.keys(description).map(category => (
|
||||
<div key={category} className="mb-5">
|
||||
<Category className="muted pb-1.5 mt-5" title={category} />
|
||||
{Object.keys(description[category]).map(permName => (
|
||||
<>
|
||||
<strong
|
||||
title={PermissionTypes.All[permName]}
|
||||
className="secondary">{permName}</strong>
|
||||
<PermToggle
|
||||
key={permName}
|
||||
category={category}
|
||||
permName={permName} />
|
||||
</>
|
||||
))}
|
||||
{Object.keys(description[category]).map(permName => (
|
||||
<>
|
||||
<strong
|
||||
title={PermissionTypes.All[permName]}
|
||||
className="secondary">{permName}</strong>
|
||||
<PermToggle
|
||||
key={permName}
|
||||
category={category}
|
||||
permName={permName} />
|
||||
</>
|
||||
))}
|
||||
</div>
|
||||
))}
|
||||
<NormalButton
|
||||
@ -67,5 +67,5 @@ const RolePermissions: React.FunctionComponent<RolePermissionsProps> = ({ perms,
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
export default RolePermissions;
|
@ -1,4 +1,4 @@
|
||||
import { UserTypes } from '@accord/types';
|
||||
import { UserTypes } from '@acrd/types';
|
||||
import { faBug, faGavel, faRocket, faSun, faVideo } from '@fortawesome/free-solid-svg-icons';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import classNames from 'classnames';
|
||||
@ -94,11 +94,11 @@ const UserProfile: FunctionComponent = () => {
|
||||
const store = useStore();
|
||||
|
||||
if (!user) return null;
|
||||
|
||||
|
||||
const mutualGuilds = selfUser.guildIds
|
||||
.filter(id => user.guildIds.includes(id))
|
||||
.map(id => getGuild(id)(store.getState()));
|
||||
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Category
|
||||
@ -107,14 +107,14 @@ const UserProfile: FunctionComponent = () => {
|
||||
<div className="mx-2">
|
||||
{mutualGuilds.map(guild => (guild)
|
||||
? <div
|
||||
className="w-12 -ml-2 float-left scale-200"
|
||||
key={guild.id}>
|
||||
<SidebarIcon
|
||||
childClasses="bg-bg-tertiary"
|
||||
imageURL={guild.iconURL}
|
||||
name={guild.name}
|
||||
disableHoverEffect />
|
||||
</div>
|
||||
className="w-12 -ml-2 float-left scale-200"
|
||||
key={guild.id}>
|
||||
<SidebarIcon
|
||||
childClasses="bg-bg-tertiary"
|
||||
imageURL={guild.iconURL}
|
||||
name={guild.name}
|
||||
disableHoverEffect />
|
||||
</div>
|
||||
: null
|
||||
)}
|
||||
</div>
|
||||
@ -157,5 +157,5 @@ const UserProfile: FunctionComponent = () => {
|
||||
</Modal>
|
||||
) : null;
|
||||
}
|
||||
|
||||
|
||||
export default UserProfile;
|
@ -12,7 +12,7 @@ import { actions as ui } from '../../../store/ui';
|
||||
import ChannelMenu from '../../ctx-menus/channel-menu';
|
||||
import Username from '../../user/username';
|
||||
import React from 'react';
|
||||
import { Entity } from '@accord/types';
|
||||
import { Entity } from '@acrd/types';
|
||||
|
||||
const ChannelTabs: React.FunctionComponent = () => {
|
||||
const dispatch = useDispatch();
|
||||
@ -30,7 +30,7 @@ const ChannelTabs: React.FunctionComponent = () => {
|
||||
|
||||
const onClick = () => {
|
||||
if (channel.type !== 'VOICE') return;
|
||||
|
||||
|
||||
dispatch(joinVoiceChannel(channel.id));
|
||||
};
|
||||
|
||||
@ -54,7 +54,7 @@ const ChannelTabs: React.FunctionComponent = () => {
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
|
||||
dispatch(ui.pageSwitched({ channel, guild: activeGuild }));
|
||||
dispatch(ui.openedModal('ChannelSettings'));
|
||||
}}
|
||||
@ -69,23 +69,23 @@ const ChannelTabs: React.FunctionComponent = () => {
|
||||
<>
|
||||
<ContextMenuTrigger key={channel.id} id={channel.id}>
|
||||
{/* <Draggable> */}
|
||||
<Link
|
||||
onClick={onClick}
|
||||
to={link}
|
||||
<Link
|
||||
onClick={onClick}
|
||||
to={link}
|
||||
className={classNames(
|
||||
`cursor-pointer flex items-center rounded h-8 p-2 pl-3`,
|
||||
{ active: channel.id === activeChannel?.id },
|
||||
)}>
|
||||
<FontAwesomeIcon
|
||||
size="xs"
|
||||
className={classNames(
|
||||
`cursor-pointer flex items-center rounded h-8 p-2 pl-3`,
|
||||
{ active: channel.id === activeChannel?.id },
|
||||
)}>
|
||||
<FontAwesomeIcon
|
||||
size="xs"
|
||||
className={classNames(
|
||||
`float-left scale-150 muted fill-current z-0`,
|
||||
(channel.type === 'VOICE') ? 'mr-2' : 'mr-3',
|
||||
)}
|
||||
icon={icon} />
|
||||
<ChannelTabContent />
|
||||
<ChannelMenu channel={channel} />
|
||||
</Link>
|
||||
`float-left scale-150 muted fill-current z-0`,
|
||||
(channel.type === 'VOICE') ? 'mr-2' : 'mr-3',
|
||||
)}
|
||||
icon={icon} />
|
||||
<ChannelTabContent />
|
||||
<ChannelMenu channel={channel} />
|
||||
</Link>
|
||||
{/* </Draggable> */}
|
||||
</ContextMenuTrigger>
|
||||
<VCMembers />
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { Entity } from '@accord/types';
|
||||
import { Entity } from '@acrd/types';
|
||||
import { faSearch, faSearchDollar, faSearchLocation } from '@fortawesome/free-solid-svg-icons';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import { useEffect } from 'react';
|
||||
@ -14,14 +14,14 @@ import SidebarIcon from '../navigation/sidebar/sidebar-icon';
|
||||
import NormalButton from '../utils/buttons/normal-button';
|
||||
import PageWrapper from './page-wrapper';
|
||||
|
||||
interface InvitePageProps {}
|
||||
|
||||
interface InvitePageProps { }
|
||||
|
||||
const InvitePage: React.FunctionComponent<InvitePageProps> = () => {
|
||||
const history = useHistory();
|
||||
const dispatch = useDispatch();
|
||||
const { inviteId }: any = useParams();
|
||||
const invite: Entity.Invite = useSelector(getInvite(inviteId));
|
||||
const guild: Entity.Guild | undefined = useSelector(getGuild(invite?.guildId));
|
||||
const guild: Entity.Guild | undefined = useSelector(getGuild(invite?.guildId));
|
||||
|
||||
const members: Entity.GuildMember[] = useSelector(getGuildMembers(invite?.guildId));
|
||||
const ownerUser: Entity.User = useSelector(getUser(guild!?.ownerId));
|
||||
@ -89,5 +89,5 @@ const InvitePage: React.FunctionComponent<InvitePageProps> = () => {
|
||||
</Wrapper>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
export default InvitePage;
|
@ -6,7 +6,7 @@ import GuildMemberMenu from '../ctx-menus/guild-member/guild-member-menu';
|
||||
import { getGuildMembers, getGuildUsers } from '../../store/guilds';
|
||||
import { filterHoistedRoles } from '../../store/roles';
|
||||
import usePerms from '../../hooks/use-perms';
|
||||
import { Entity } from '@accord/types';
|
||||
import { Entity } from '@acrd/types';
|
||||
|
||||
const MemberList: React.FunctionComponent = () => {
|
||||
const perms = usePerms();
|
||||
@ -20,7 +20,7 @@ const MemberList: React.FunctionComponent = () => {
|
||||
const member = members.find(m => m.userId === u.id)!;
|
||||
return perms.canMember('VIEW_CHANNELS', guild, member);
|
||||
});
|
||||
|
||||
|
||||
type UserListFilter = (s: Entity.User, i: number, a: Entity.User[]) => boolean;
|
||||
const UserList = ({ category, filter: by }: { category: string, filter: UserListFilter }) => {
|
||||
const filtered = users
|
||||
@ -63,8 +63,8 @@ const MemberList: React.FunctionComponent = () => {
|
||||
category={r.name}
|
||||
filter={u => (
|
||||
getRoleIds(u.id).includes(r.id)
|
||||
&& hoistedRoleIds(u)[0] === r.id
|
||||
&& u.status === 'ONLINE')} />
|
||||
&& hoistedRoleIds(u)[0] === r.id
|
||||
&& u.status === 'ONLINE')} />
|
||||
)}
|
||||
<UserList
|
||||
category="Online"
|
||||
@ -75,5 +75,5 @@ const MemberList: React.FunctionComponent = () => {
|
||||
</div>
|
||||
) : null;
|
||||
}
|
||||
|
||||
|
||||
export default MemberList;
|
@ -4,14 +4,14 @@ import { getMemberHighestRole } from '../../store/roles';
|
||||
import { useSelector } from 'react-redux';
|
||||
import classNames from 'classnames';
|
||||
import Image from '../utils/image';
|
||||
import { Entity } from '@accord/types';
|
||||
import { Entity } from '@acrd/types';
|
||||
|
||||
export interface UsernameProps {
|
||||
user: Entity.User;
|
||||
guild?: Entity.Guild;
|
||||
size?: 'sm' | 'md' | 'lg';
|
||||
}
|
||||
|
||||
|
||||
const Username: React.FunctionComponent<UsernameProps> = ({ guild, user, size = 'md' }) => {
|
||||
const highestRole = useSelector(getMemberHighestRole(guild?.id, user.id));
|
||||
|
||||
@ -78,5 +78,5 @@ const Username: React.FunctionComponent<UsernameProps> = ({ guild, user, size =
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
export default Username;
|
@ -1,4 +1,4 @@
|
||||
import { patterns as defaultPatterns } from '@accord/types';
|
||||
import { patterns as defaultPatterns } from '@acrd/types';
|
||||
|
||||
export class FormatService {
|
||||
private readonly patterns = {
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { Entity } from '@accord/types';
|
||||
import { Entity } from '@acrd/types';
|
||||
import { getChannel, getChannelByName } from '../store/channels';
|
||||
import { getTag, getUser, getUserByTag } from '../store/users';
|
||||
|
||||
@ -16,17 +16,17 @@ export class MentionService {
|
||||
},
|
||||
};
|
||||
|
||||
constructor(private state: Store.AppState) {}
|
||||
constructor(private state: Store.AppState) { }
|
||||
|
||||
// messageBox.onInput -> formatted mentions appear fancy in message box
|
||||
public formatOriginal(content: string) {
|
||||
public formatOriginal(content: string) {
|
||||
const guildId = this.state.ui.activeGuild!.id;
|
||||
return content
|
||||
.replace(this.patterns.original.channel, (og, name) => {
|
||||
.replace(this.patterns.original.channel, (og, name) => {
|
||||
const channel = getChannelByName(guildId, name)(this.state);
|
||||
return (channel) ? `<#${channel?.id}>` : og;
|
||||
})
|
||||
.replace(this.patterns.original.user, (og, tag) => {
|
||||
.replace(this.patterns.original.user, (og, tag) => {
|
||||
const user = getUserByTag(tag)(this.state);
|
||||
return (user) ? `<@${user.id}>` : og;
|
||||
});
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { Entity } from '@accord/types';
|
||||
import { PermissionTypes } from '@accord/types';
|
||||
import { Entity } from '@acrd/types';
|
||||
import { PermissionTypes } from '@acrd/types';
|
||||
import { getChannel } from '../store/channels';
|
||||
import { getGuild, getGuildRoles } from '../store/guilds';
|
||||
import { getMember, getSelfMember } from '../store/members';
|
||||
@ -24,17 +24,17 @@ export class PermService {
|
||||
},
|
||||
};
|
||||
|
||||
constructor(private state: Store.AppState) {}
|
||||
constructor(private state: Store.AppState) { }
|
||||
|
||||
public canMember(permission: PermissionTypes.PermissionString, guild: Entity.Guild, member: Entity.GuildMember) {
|
||||
return guild.ownerId === member.userId
|
||||
|| this.hasPerm(
|
||||
this.getTotalPerms(member, guild.id),
|
||||
PermissionTypes.All[permission] as number,
|
||||
);
|
||||
this.getTotalPerms(member, guild.id),
|
||||
PermissionTypes.All[permission] as number,
|
||||
);
|
||||
}
|
||||
public canInChannel(permission: PermissionTypes.PermissionString, guildId: string, channelId: string) {
|
||||
const channel = this.getChannel(channelId);
|
||||
const channel = this.getChannel(channelId);
|
||||
const member = this.getSelfMember(guildId);
|
||||
|
||||
const overrides = channel.overrides
|
||||
@ -52,14 +52,14 @@ export class PermService {
|
||||
}
|
||||
|
||||
public can(permission: PermissionTypes.PermissionString, guildId: string) {
|
||||
const guild = this.getGuild(guildId);
|
||||
const guild = this.getGuild(guildId);
|
||||
const member = this.getSelfMember(guildId);
|
||||
|
||||
return (guild.ownerId === member.userId)
|
||||
|| this.hasPerm(
|
||||
this.getTotalPerms(member, guildId),
|
||||
PermissionTypes.All[permission] as number,
|
||||
);
|
||||
this.getTotalPerms(member, guildId),
|
||||
PermissionTypes.All[permission] as number,
|
||||
);
|
||||
}
|
||||
private getTotalPerms(member: Entity.GuildMember, guildId: string) {
|
||||
return getGuildRoles(guildId)(this.state)
|
||||
@ -68,7 +68,7 @@ export class PermService {
|
||||
}
|
||||
private hasPerm(totalPerms: number, permission: number) {
|
||||
return Boolean(totalPerms & permission)
|
||||
|| Boolean(totalPerms & PermissionTypes.General.ADMINISTRATOR);
|
||||
|| Boolean(totalPerms & PermissionTypes.General.ADMINISTRATOR);
|
||||
}
|
||||
|
||||
public canManage(prereq: PermissionTypes.PermissionString, guildId: string, managedUserId: string) {
|
||||
@ -77,7 +77,7 @@ export class PermService {
|
||||
|
||||
return this.can(prereq, guildId)
|
||||
&& (this.state.auth.user!.id === userMember.userId
|
||||
|| (this.memberIsHigher(guildId, userMember.roleIds)));
|
||||
|| (this.memberIsHigher(guildId, userMember.roleIds)));
|
||||
}
|
||||
|
||||
public canPunish(prereq: PermissionTypes.PermissionString, guildId: string, managedUserId: string) {
|
||||
@ -89,7 +89,7 @@ export class PermService {
|
||||
// TODO: test
|
||||
public memberIsHigher(guildId: string, roleIds: string[]) {
|
||||
const guild = this.getGuild(guildId);
|
||||
const member = this.getSelfMember(guildId);
|
||||
const member = this.getSelfMember(guildId);
|
||||
const myRoles = getRoles(member.roleIds)(this.state);
|
||||
const theirRoles = getRoles(roleIds)(this.state);
|
||||
|
||||
@ -98,7 +98,7 @@ export class PermService {
|
||||
const theirHighestRole: Entity.Role = theirRoles.reduce(max('position'));
|
||||
|
||||
const selfIsOwner = member.userId === guild.ownerId;
|
||||
const selfHasHigherRole = myHighestRole.position > theirHighestRole.position;
|
||||
const selfHasHigherRole = myHighestRole.position > theirHighestRole.position;
|
||||
|
||||
return selfIsOwner || selfHasHigherRole;
|
||||
}
|
||||
@ -119,7 +119,7 @@ export class PermService {
|
||||
if (!guild)
|
||||
throw new TypeError('Guild not found');
|
||||
return guild;
|
||||
}
|
||||
}
|
||||
private getSelfMember(guildId: string) {
|
||||
const member = getSelfMember(guildId)(this.state);
|
||||
if (!member)
|
||||
|
@ -1,5 +1,5 @@
|
||||
import '@accord/types';
|
||||
import { WS } from '@accord/types';
|
||||
import '@acrd/types';
|
||||
import { WS } from '@acrd/types';
|
||||
import io from 'socket.io-client';
|
||||
|
||||
const ws = (io as any).connect(process.env.REACT_APP_ROOT_API_URL, {
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { REST } from '@accord/types';
|
||||
import { REST } from '@acrd/types';
|
||||
import { actions as api } from '../api';
|
||||
import { actions as channelActions } from '../channels';
|
||||
import { actions as guildActions } from '../guilds';
|
||||
@ -23,5 +23,5 @@ export default (guildIds?: string[]) => (dispatch) => {
|
||||
dispatch(userActions.fetched(data.users));
|
||||
dispatch(meta.fetchedEntities());
|
||||
},
|
||||
}));
|
||||
}));
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
import { REST, WS } from '@accord/types';
|
||||
import { REST, WS } from '@acrd/types';
|
||||
import { createAction } from '@reduxjs/toolkit';
|
||||
import { getHeaders } from './utils/rest-headers';
|
||||
|
||||
@ -28,7 +28,7 @@ export interface WSArgs {
|
||||
export const uploadFile = (file: File, callback?: (args: REST.From.Post['/upload']) => any) => (dispatch) => {
|
||||
const formData = new FormData();
|
||||
formData.append('file', file);
|
||||
|
||||
|
||||
dispatch(actions.restCallBegan({
|
||||
method: 'post',
|
||||
url: '/upload',
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { REST, WS } from '@accord/types';
|
||||
import { REST, WS } from '@acrd/types';
|
||||
import { createSlice } from '@reduxjs/toolkit';
|
||||
import { actions as api } from './api';
|
||||
import { openDialog } from './ui';
|
||||
@ -62,7 +62,7 @@ export const loginUser = (data: REST.To.Post['/auth/login']) => (dispatch) => {
|
||||
|
||||
export const forgotPasswordEmail = (email: string) => (dispatch) => {
|
||||
if (!email) return;
|
||||
|
||||
|
||||
dispatch(api.restCallBegan({
|
||||
callback: () => dispatch(openDialog({
|
||||
variant: 'info',
|
||||
@ -105,7 +105,7 @@ export const sendVerifyCode = (code: string) => (dispatch) => {
|
||||
|
||||
export const changePassword = (oldPassword: string, newPassword: string) => (dispatch, getState: () => Store.AppState) => {
|
||||
const user = getState().auth.user!;
|
||||
|
||||
|
||||
dispatch(api.restCallBegan({
|
||||
onSuccess: [],
|
||||
method: 'post',
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { Entity, WS, ChannelTypes } from '@accord/types';
|
||||
import { Entity, WS, ChannelTypes } from '@acrd/types';
|
||||
import { createSelector, createSlice } from '@reduxjs/toolkit';
|
||||
import { actions as api } from './api';
|
||||
import { notInArray } from './utils/filter';
|
||||
@ -7,7 +7,7 @@ const slice = createSlice({
|
||||
name: 'channels',
|
||||
initialState: [] as Store.AppState['entities']['channels'],
|
||||
reducers: {
|
||||
fetched: (channels, { payload }: Store.Action<Entity.Channel[]>) => {
|
||||
fetched: (channels, { payload }: Store.Action<Entity.Channel[]>) => {
|
||||
channels.push(...payload.filter(notInArray(channels)));
|
||||
},
|
||||
created: (channels, { payload }: Store.Action<WS.Args.ChannelCreate>) => {
|
||||
@ -52,7 +52,7 @@ export const joinVoiceChannel = (channelId: string) => (dispatch, getState: () =
|
||||
const channel = getChannel(channelId)(getState()) as ChannelTypes.Voice;
|
||||
const selfUser = getState().auth.user!;
|
||||
if (channel.userIds.includes(selfUser.id)) return;
|
||||
|
||||
|
||||
dispatch(api.wsCallBegan({
|
||||
event: 'CHANNEL_JOIN',
|
||||
data: { channelId } as WS.Params.ChannelJoin,
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user