Rename @accord/types -> @acrd/types.

This commit is contained in:
ADAMJR 2022-12-16 00:38:45 +00:00
parent 09f751d997
commit 99cda810f8
110 changed files with 429 additions and 429 deletions

View File

@ -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",

View File

@ -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",

View File

@ -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';

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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';

View File

@ -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,

View File

@ -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;

View File

@ -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));

View File

@ -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));

View File

@ -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));

View File

@ -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;

View File

@ -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));

View File

@ -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));

View File

@ -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));

View File

@ -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';

View File

@ -1,4 +1,4 @@
import { Entity } from '@accord/types';
import { Entity } from '@acrd/types';
import { SelfUserDocument } from './models/user';
export default class Pings {

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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}`);
}
}
}
}

View File

@ -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;
}

View File

@ -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 { }
}
}

View File

@ -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',

View File

@ -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}`);
};

View File

@ -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();

View File

@ -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,

View File

@ -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);
});
});

View File

@ -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 } }),

View File

@ -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)

View File

@ -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);
}

View File

@ -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 {

View File

@ -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!');
}

View File

@ -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) };
}
}

View File

@ -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,
};
}

View File

@ -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 });

View File

@ -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: {} },

View File

@ -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),

View File

@ -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,

View File

@ -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],

View File

@ -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(

View File

@ -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)

View File

@ -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) {

View File

@ -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],

View File

@ -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 [{

View File

@ -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;

View File

@ -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);

View File

@ -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);

View File

@ -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';

View File

@ -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';

View File

@ -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) : '',

View File

@ -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';

View File

@ -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();

View File

@ -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();

View File

@ -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],

View File

@ -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;

View File

@ -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';

View File

@ -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';

View File

@ -1,4 +1,4 @@
import { WS } from '@accord/types';
import { WS } from '@acrd/types';
import { Socket } from 'socket.io';
import { WebSocket } from '../websocket';

View File

@ -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';

View File

@ -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';

View File

@ -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';

View File

@ -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);

View File

@ -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);

View File

@ -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, () => {

View File

@ -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",

View File

@ -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",

View File

@ -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>
);

View File

@ -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;

View File

@ -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)' }}

View File

@ -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;

View File

@ -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));

View File

@ -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;

View File

@ -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 (

View File

@ -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]);

View File

@ -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;

View File

@ -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;

View File

@ -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

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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>

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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 />

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -1,4 +1,4 @@
import { patterns as defaultPatterns } from '@accord/types';
import { patterns as defaultPatterns } from '@acrd/types';
export class FormatService {
private readonly patterns = {

View File

@ -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;
});

View File

@ -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)

View File

@ -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, {

View File

@ -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());
},
}));
}));
}

View File

@ -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',

View File

@ -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',

View File

@ -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