summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/activitypub/util/transforms/index.ts2
-rw-r--r--src/api/routes/channels/#channel_id/messages/index.ts32
-rw-r--r--src/api/routes/guilds/#guild_id/members/#member_id/index.ts5
-rw-r--r--src/api/util/utility/EmbedHandlers.ts262
-rw-r--r--src/gateway/events/Message.ts55
-rw-r--r--src/gateway/opcodes/Identify.ts65
-rw-r--r--src/util/entities/Channel.ts7
-rw-r--r--src/util/entities/Emoji.ts2
-rw-r--r--src/util/entities/Invite.ts2
-rw-r--r--src/util/entities/Member.ts1
-rw-r--r--src/util/entities/Role.ts4
-rw-r--r--src/util/entities/Sticker.ts4
-rw-r--r--src/util/entities/VoiceState.ts4
-rw-r--r--src/util/util/Database.ts12
-rw-r--r--src/util/util/Sentry.ts35
15 files changed, 300 insertions, 192 deletions
diff --git a/src/activitypub/util/transforms/index.ts b/src/activitypub/util/transforms/index.ts

index e8107ca0..7333233e 100644 --- a/src/activitypub/util/transforms/index.ts +++ b/src/activitypub/util/transforms/index.ts
@@ -126,7 +126,7 @@ export const messageFromAP = async (data: APNote): Promise<Message> => { const member = channel instanceof Channel ? await Member.findOneOrFail({ - where: { id: user.id, guild_id: channel.guild.id }, + where: { id: user.id, guild_id: channel.guild!.id }, }) : undefined; diff --git a/src/api/routes/channels/#channel_id/messages/index.ts b/src/api/routes/channels/#channel_id/messages/index.ts
index c384a05b..a5bfcfd7 100644 --- a/src/api/routes/channels/#channel_id/messages/index.ts +++ b/src/api/routes/channels/#channel_id/messages/index.ts
@@ -40,7 +40,13 @@ import { import { Request, Response, Router } from "express"; import { HTTPError } from "lambert-server"; import multer from "multer"; -import { FindManyOptions, FindOperator, LessThan, MoreThan } from "typeorm"; +import { + FindManyOptions, + FindOperator, + LessThan, + MoreThan, + MoreThanOrEqual, +} from "typeorm"; import { URL } from "url"; const router: Router = Router(); @@ -122,12 +128,24 @@ router.get( if (around) { query.take = Math.floor(limit / 2); - const [right, left] = await Promise.all([ - Message.find({ ...query, where: { id: LessThan(around) } }), - Message.find({ ...query, where: { id: MoreThan(around) } }), - ]); - right.push(...left); - messages = right; + if (query.take != 0) { + const [right, left] = await Promise.all([ + Message.find({ ...query, where: { id: LessThan(around) } }), + Message.find({ + ...query, + where: { id: MoreThanOrEqual(around) }, + }), + ]); + left.push(...right); + messages = left; + } else { + query.take = 1; + const message = await Message.findOne({ + ...query, + where: { id: around }, + }); + messages = message ? [message] : []; + } } else { if (after) { if (BigInt(after) > BigInt(Snowflake.generate())) diff --git a/src/api/routes/guilds/#guild_id/members/#member_id/index.ts b/src/api/routes/guilds/#guild_id/members/#member_id/index.ts
index cafb922e..c168f2dc 100644 --- a/src/api/routes/guilds/#guild_id/members/#member_id/index.ts +++ b/src/api/routes/guilds/#guild_id/members/#member_id/index.ts
@@ -18,6 +18,7 @@ import { route } from "@spacebar/api"; import { + DiscordApiErrors, emitEvent, Emoji, getPermission, @@ -198,7 +199,9 @@ router.put( member_id = req.user_id; rights.hasThrow("JOIN_GUILDS"); } else { - // TODO: join others by controller + // TODO: check oauth2 scope + + throw DiscordApiErrors.MISSING_REQUIRED_OAUTH2_SCOPE; } const guild = await Guild.findOneOrFail({ diff --git a/src/api/util/utility/EmbedHandlers.ts b/src/api/util/utility/EmbedHandlers.ts
index 15e3f67f..0f1e88a5 100644 --- a/src/api/util/utility/EmbedHandlers.ts +++ b/src/api/util/utility/EmbedHandlers.ts
@@ -16,12 +16,12 @@ along with this program. If not, see <https://www.gnu.org/licenses/>. */ -import { Config, Embed, EmbedType } from "@spacebar/util"; -import fetch, { RequestInit } from "node-fetch"; +import { Config, Embed, EmbedImage, EmbedType } from "@spacebar/util"; import * as cheerio from "cheerio"; -import probe from "probe-image-size"; import crypto from "crypto"; +import fetch, { RequestInit } from "node-fetch"; import { yellow } from "picocolors"; +import probe from "probe-image-size"; export const DEFAULT_FETCH_OPTIONS: RequestInit = { redirect: "follow", @@ -35,6 +35,20 @@ export const DEFAULT_FETCH_OPTIONS: RequestInit = { method: "GET", }; +const makeEmbedImage = ( + url: string | undefined, + width: number | undefined, + height: number | undefined, +): Required<EmbedImage> | undefined => { + if (!url || !width || !height) return undefined; + return { + url, + width, + height, + proxy_url: getProxyUrl(new URL(url), width, height), + }; +}; + let hasWarnedAboutImagor = false; export const getProxyUrl = ( @@ -78,13 +92,24 @@ export const getProxyUrl = ( const getMeta = ($: cheerio.CheerioAPI, name: string): string | undefined => { let elem = $(`meta[property="${name}"]`); if (!elem.length) elem = $(`meta[name="${name}"]`); - return elem.attr("content") || elem.text(); + const ret = elem.attr("content") || elem.text(); + return ret.trim().length == 0 ? undefined : ret; +}; + +const tryParseInt = (str: string | undefined) => { + if (!str) return undefined; + try { + return parseInt(str); + } catch (e) { + return undefined; + } }; export const getMetaDescriptions = (text: string) => { const $ = cheerio.load(text); return { + type: getMeta($, "og:type"), title: getMeta($, "og:title") || $("title").first().text(), provider_name: getMeta($, "og:site_name"), author: getMeta($, "article:author"), @@ -92,10 +117,13 @@ export const getMetaDescriptions = (text: string) => { image: getMeta($, "og:image") || getMeta($, "twitter:image"), image_fallback: $(`image`).attr("src"), video_fallback: $(`video`).attr("src"), - width: parseInt(getMeta($, "og:image:width") || "0"), - height: parseInt(getMeta($, "og:image:height") || "0"), + width: tryParseInt(getMeta($, "og:image:width")), + height: tryParseInt(getMeta($, "og:image:height")), url: getMeta($, "og:url"), youtube_embed: getMeta($, "og:video:secure_url"), + site_name: getMeta($, "og:site_name"), + + $, }; }; @@ -116,13 +144,11 @@ const genericImageHandler = async (url: URL): Promise<Embed | null> => { method: "HEAD", }); - let width, height, image; + let image; if (type.headers.get("content-type")?.indexOf("image") !== -1) { const result = await probe(url.href); - width = result.width; - height = result.height; - image = url.href; + image = makeEmbedImage(url.href, result.width, result.height); } else if (type.headers.get("content-type")?.indexOf("video") !== -1) { // TODO return null; @@ -131,22 +157,19 @@ const genericImageHandler = async (url: URL): Promise<Embed | null> => { const response = await doFetch(url); if (!response) return null; const metas = getMetaDescriptions(await response.text()); - width = metas.width; - height = metas.height; - image = metas.image || metas.image_fallback; + image = makeEmbedImage( + metas.image || metas.image_fallback, + metas.width, + metas.height, + ); } - if (!width || !height || !image) return null; + if (!image) return null; return { url: url.href, type: EmbedType.image, - thumbnail: { - width: width, - height: height, - url: url.href, - proxy_url: getProxyUrl(new URL(image), width, height), - }, + thumbnail: image, }; }; @@ -165,7 +188,8 @@ export const EmbedHandlers: { const response = await doFetch(url); if (!response) return null; - const metas = getMetaDescriptions(await response.text()); + const text = await response.text(); + const metas = getMetaDescriptions(text); // TODO: handle video @@ -178,26 +202,27 @@ export const EmbedHandlers: { } if (!metas.image && (!metas.title || !metas.description)) { + // we don't have any content to display return null; } + let embedType = EmbedType.link; + if (metas.type == "article") embedType = EmbedType.article; + if (metas.type == "object") embedType = EmbedType.article; // github + if (metas.type == "rich") embedType = EmbedType.rich; + return { url: url.href, - type: EmbedType.link, + type: embedType, title: metas.title, - thumbnail: { - width: metas.width, - height: metas.height, - url: metas.image, - proxy_url: metas.image - ? getProxyUrl( - new URL(metas.image), - metas.width, - metas.height, - ) - : undefined, - }, + thumbnail: makeEmbedImage(metas.image, metas.width, metas.height), description: metas.description, + provider: metas.site_name + ? { + name: metas.site_name, + url: url.origin, + } + : undefined, }; }, @@ -207,12 +232,23 @@ export const EmbedHandlers: { "c.tenor.com": genericImageHandler, "media.tenor.com": genericImageHandler, - // TODO: facebook - // have to use their APIs or something because they don't send the metas in initial html + "facebook.com": (url) => EmbedHandlers["www.facebook.com"](url), + "www.facebook.com": async (url: URL) => { + const response = await doFetch(url); + if (!response) return null; + const metas = getMetaDescriptions(await response.text()); - "twitter.com": (url: URL) => { - return EmbedHandlers["www.twitter.com"](url); + return { + url: url.href, + type: EmbedType.link, + title: metas.title, + description: metas.description, + thumbnail: makeEmbedImage(metas.image, 640, 640), + color: 16777215, + }; }, + + "twitter.com": (url) => EmbedHandlers["www.twitter.com"](url), "www.twitter.com": async (url: URL) => { const token = Config.get().external.twitter; if (!token) return null; @@ -330,14 +366,7 @@ export const EmbedHandlers: { type: EmbedType.link, title: metas.title, description: metas.description, - thumbnail: { - width: 640, - height: 640, - proxy_url: metas.image - ? getProxyUrl(new URL(metas.image), 640, 640) - : undefined, - url: metas.image, - }, + thumbnail: makeEmbedImage(metas.image, 640, 640), provider: { url: "https://spotify.com", name: "Spotify", @@ -345,32 +374,25 @@ export const EmbedHandlers: { }; }, - "pixiv.net": (url: URL) => { - return EmbedHandlers["www.pixiv.net"](url); - }, + // TODO: docs: Pixiv won't work without Imagor + "pixiv.net": (url) => EmbedHandlers["www.pixiv.net"](url), "www.pixiv.net": async (url: URL) => { const response = await doFetch(url); if (!response) return null; const metas = getMetaDescriptions(await response.text()); - // TODO: doesn't show images. think it's a bug in the cdn + if (!metas.image) return null; + return { url: url.href, type: EmbedType.image, title: metas.title, description: metas.description, - image: { - width: metas.width, - height: metas.height, - url: url.href, - proxy_url: metas.image - ? getProxyUrl( - new URL(metas.image), - metas.width, - metas.height, - ) - : undefined, - }, + image: makeEmbedImage( + metas.image || metas.image_fallback, + metas.width, + metas.height, + ), provider: { url: "https://pixiv.net", name: "Pixiv", @@ -382,6 +404,42 @@ export const EmbedHandlers: { const response = await doFetch(url); if (!response) return null; const metas = getMetaDescriptions(await response.text()); + const numReviews = metas.$("#review_summary_num_reviews").val() as + | string + | undefined; + const price = metas + .$(".game_purchase_price.price") + .data("price-final") as number | undefined; + const releaseDate = metas + .$(".release_date") + .find("div.date") + .text() + .trim(); + const isReleased = new Date(releaseDate) < new Date(); + + const fields: Embed["fields"] = []; + + if (numReviews) + fields.push({ + name: "Reviews", + value: numReviews, + inline: true, + }); + + if (price) + fields.push({ + name: "Price", + value: `$${price / 100}`, + inline: true, + }); + + // if the release date is in the past, it's already out + if (releaseDate && !isReleased) + fields.push({ + name: "Release Date", + value: releaseDate, + inline: true, + }); return { url: url.href, @@ -402,14 +460,12 @@ export const EmbedHandlers: { url: "https://store.steampowered.com", name: "Steam", }, - // TODO: fields for release date + fields, // TODO: Video }; }, - "reddit.com": (url: URL) => { - return EmbedHandlers["www.reddit.com"](url); - }, + "reddit.com": (url) => EmbedHandlers["www.reddit.com"](url), "www.reddit.com": async (url: URL) => { const res = await EmbedHandlers["default"](url); return { @@ -420,49 +476,65 @@ export const EmbedHandlers: { }, }; }, - "youtu.be": (url: URL) => { - return EmbedHandlers["www.youtube.com"](url); - }, - "youtube.com": (url: URL) => { - return EmbedHandlers["www.youtube.com"](url); - }, + + "youtu.be": (url) => EmbedHandlers["www.youtube.com"](url), + "youtube.com": (url) => EmbedHandlers["www.youtube.com"](url), "www.youtube.com": async (url: URL): Promise<Embed | null> => { const response = await doFetch(url); if (!response) return null; const metas = getMetaDescriptions(await response.text()); return { - video: { - // TODO: does this adjust with aspect ratio? - width: metas.width, - height: metas.height, - url: metas.youtube_embed, - }, + video: makeEmbedImage( + metas.youtube_embed, + metas.width, + metas.height, + ), url: url.href, - type: EmbedType.video, + type: metas.youtube_embed ? EmbedType.video : EmbedType.link, title: metas.title, - thumbnail: { - width: metas.width, - height: metas.height, - url: metas.image, - proxy_url: metas.image - ? getProxyUrl( - new URL(metas.image), - metas.width, - metas.height, - ) - : undefined, - }, + thumbnail: makeEmbedImage( + metas.image || metas.image_fallback, + metas.width, + metas.height, + ), provider: { url: "https://www.youtube.com", name: "YouTube", }, description: metas.description, color: 16711680, - author: { - name: metas.author, - // TODO: author channel url - }, + author: metas.author + ? { + name: metas.author, + // TODO: author channel url + } + : undefined, + }; + }, + + "www.xkcd.com": (url) => EmbedHandlers["xkcd.com"](url), + "xkcd.com": async (url) => { + const response = await doFetch(url); + if (!response) return null; + + const metas = getMetaDescriptions(await response.text()); + const hoverText = metas.$("#comic img").attr("title"); + + if (!metas.image) return null; + + const { width, height } = await probe(metas.image); + + return { + url: url.href, + type: EmbedType.rich, + title: `xkcd: ${metas.title}`, + image: makeEmbedImage(metas.image, width, height), + footer: hoverText + ? { + text: hoverText, + } + : undefined, }; }, diff --git a/src/gateway/events/Message.ts b/src/gateway/events/Message.ts
index 45790146..52d9edd8 100644 --- a/src/gateway/events/Message.ts +++ b/src/gateway/events/Message.ts
@@ -16,15 +16,15 @@ along with this program. If not, see <https://www.gnu.org/licenses/>. */ -import { WebSocket, Payload, CLOSECODES, OPCODES } from "@spacebar/gateway"; -import OPCodeHandlers from "../opcodes"; -import { check } from "../opcodes/instanceOf"; -import WS from "ws"; -import { PayloadSchema, ErlpackType } from "@spacebar/util"; import * as Sentry from "@sentry/node"; +import { CLOSECODES, OPCODES, Payload, WebSocket } from "@spacebar/gateway"; +import { ErlpackType, PayloadSchema } from "@spacebar/util"; +import fs from "fs/promises"; import BigIntJson from "json-bigint"; import path from "path"; -import fs from "fs/promises"; +import WS from "ws"; +import OPCodeHandlers from "../opcodes"; +import { check } from "../opcodes/instanceOf"; const bigIntJson = BigIntJson({ storeAsString: true }); let erlpack: ErlpackType | null = null; @@ -88,33 +88,28 @@ export async function Message(this: WebSocket, buffer: WS.Data) { return; } - const transaction = - data.op != 1 - ? Sentry.startTransaction({ - op: OPCODES[data.op], - name: `GATEWAY ${OPCODES[data.op]}`, - data: { - ...data.d, - token: data?.d?.token ? "[Redacted]" : undefined, - }, - }) - : undefined; - try { - const ret = await OPCodeHandler.call(this, data); - Sentry.withScope((scope) => { - scope.setSpan(transaction); - scope.setUser({ id: this.user_id }); - transaction?.finish(); - }); - return ret; + return await Sentry.startActiveSpan( + { + op: "websocket.server", + name: `GATEWAY ${OPCODES[data.op]}`, + data: { + ...data.d, + token: data?.d?.token ? "[Redacted]" : undefined, + }, + }, + async () => { + const ret = await OPCodeHandler.call(this, data); + Sentry.setUser({ id: this.user_id }); + return ret; + }, + ); } catch (error) { - Sentry.withScope((scope) => { - scope.setSpan(transaction); - if (this.user_id) scope.setUser({ id: this.user_id }); - Sentry.captureException(error); + Sentry.captureException(error, { + user: { + id: this.user_id, + }, }); - transaction?.finish(); console.error(`Error: Op ${data.op}`, error); // if (!this.CLOSED && this.CLOSING) return this.close(CLOSECODES.Unknown_error); diff --git a/src/gateway/opcodes/Identify.ts b/src/gateway/opcodes/Identify.ts
index 7610901a..9a3128d9 100644 --- a/src/gateway/opcodes/Identify.ts +++ b/src/gateway/opcodes/Identify.ts
@@ -17,49 +17,57 @@ */ import { - WebSocket, - Payload, - setupListener, - Capabilities, CLOSECODES, + Capabilities, OPCODES, + Payload, Send, + WebSocket, + setupListener, } from "@spacebar/gateway"; import { - checkToken, + Application, + Config, + DMChannel, + DefaultUserGuildSettings, + EVENTEnum, + Guild, + GuildOrUnavailable, + IdentifySchema, Intents, Member, - ReadyEventData, - Session, - EVENTEnum, - Config, - PublicUser, - PrivateUserProjection, - ReadState, - Application, - emitEvent, - SessionsReplace, - PrivateSessionProjection, MemberPrivateProjection, + OPCodes, + Permissions, PresenceUpdateEvent, - IdentifySchema, - DefaultUserGuildSettings, - ReadyGuildDTO, - Guild, + PrivateSessionProjection, + PrivateUserProjection, + PublicUser, PublicUserProjection, + ReadState, + ReadyEventData, + ReadyGuildDTO, ReadyUserGuildSettingsEntries, - UserSettings, - Permissions, - DMChannel, - GuildOrUnavailable, Recipient, - OPCodes, + Session, + SessionsReplace, + UserSettings, + checkToken, + emitEvent, } from "@spacebar/util"; import { check } from "./instanceOf"; // TODO: user sharding // TODO: check privileged intents, if defined in the config +const tryGetUserFromToken = async (...args: Parameters<typeof checkToken>) => { + try { + return (await checkToken(...args)).user; + } catch (e) { + return null; + } +}; + export async function onIdentify(this: WebSocket, data: Payload) { if (this.user_id) { // we've already identified @@ -74,7 +82,7 @@ export async function onIdentify(this: WebSocket, data: Payload) { this.capabilities = new Capabilities(identify.capabilities || 0); - const { user } = await checkToken(identify.token, { + const user = await tryGetUserFromToken(identify.token, { relations: ["relationships", "relationships.to", "settings"], select: [...PrivateUserProjection, "relationships"], }); @@ -332,10 +340,9 @@ export async function onIdentify(this: WebSocket, data: Payload) { // TODO how is active determined? // in our lazy request impl, we just pick the 'most relevant' session active: x.session_id == session.session_id, - activities: x.activities, + activities: x.activities ?? [], client_info: x.client_info, - // TODO: what does all mean? - session_id: x.session_id == session.session_id ? "all" : x.session_id, + session_id: x.session_id, // TODO: discord.com sends 'all', what is that??? status: x.status, })); diff --git a/src/util/entities/Channel.ts b/src/util/entities/Channel.ts
index 8b692ac7..0ccabd62 100644 --- a/src/util/entities/Channel.ts +++ b/src/util/entities/Channel.ts
@@ -102,10 +102,11 @@ export class Channel extends BaseClass { guild_id?: string; @JoinColumn({ name: "guild_id" }) - @ManyToOne(() => Guild, { + @ManyToOne(() => Guild, (guild) => guild.channels, { onDelete: "CASCADE", + nullable: true, }) - guild: Guild; + guild?: Guild; @Column({ nullable: true }) @RelationId((channel: Channel) => channel.parent) @@ -571,7 +572,6 @@ export interface DMChannel extends Omit<Channel, "type" | "recipients"> { export function isTextChannel(type: ChannelType): boolean { switch (type) { case ChannelType.GUILD_STORE: - case ChannelType.GUILD_VOICE: case ChannelType.GUILD_STAGE_VOICE: case ChannelType.GUILD_CATEGORY: case ChannelType.GUILD_FORUM: @@ -580,6 +580,7 @@ export function isTextChannel(type: ChannelType): boolean { case ChannelType.DM: case ChannelType.GROUP_DM: case ChannelType.GUILD_NEWS: + case ChannelType.GUILD_VOICE: case ChannelType.GUILD_NEWS_THREAD: case ChannelType.GUILD_PUBLIC_THREAD: case ChannelType.GUILD_PRIVATE_THREAD: diff --git a/src/util/entities/Emoji.ts b/src/util/entities/Emoji.ts
index 0bc2f423..4d851698 100644 --- a/src/util/entities/Emoji.ts +++ b/src/util/entities/Emoji.ts
@@ -33,7 +33,7 @@ export class Emoji extends BaseClass { guild_id: string; @JoinColumn({ name: "guild_id" }) - @ManyToOne(() => Guild, { + @ManyToOne(() => Guild, (guild) => guild.emojis, { onDelete: "CASCADE", }) guild: Guild; diff --git a/src/util/entities/Invite.ts b/src/util/entities/Invite.ts
index 7970c4f0..f7e54fbe 100644 --- a/src/util/entities/Invite.ts +++ b/src/util/entities/Invite.ts
@@ -53,7 +53,7 @@ export class Invite extends BaseClassWithoutId { guild_id: string; @JoinColumn({ name: "guild_id" }) - @ManyToOne(() => Guild, { + @ManyToOne(() => Guild, (guild) => guild.invites, { onDelete: "CASCADE", }) guild: Guild; diff --git a/src/util/entities/Member.ts b/src/util/entities/Member.ts
index e7b89976..16b18ab1 100644 --- a/src/util/entities/Member.ts +++ b/src/util/entities/Member.ts
@@ -327,6 +327,7 @@ export class Member extends BaseClassWithoutId { id: guild_id, }, relations: PublicGuildRelations, + relationLoadStrategy: "query", }); const memberCount = await Member.count({ where: { guild_id } }); diff --git a/src/util/entities/Role.ts b/src/util/entities/Role.ts
index 9a601f31..e8e5feda 100644 --- a/src/util/entities/Role.ts +++ b/src/util/entities/Role.ts
@@ -23,12 +23,12 @@ import { Guild } from "./Guild"; @Entity("roles") export class Role extends BaseClass { - @Column({ nullable: true }) + @Column() @RelationId((role: Role) => role.guild) guild_id: string; @JoinColumn({ name: "guild_id" }) - @ManyToOne(() => Guild, { + @ManyToOne(() => Guild, (guild) => guild.roles, { onDelete: "CASCADE", }) guild: Guild; diff --git a/src/util/entities/Sticker.ts b/src/util/entities/Sticker.ts
index cd07e65a..e9294f92 100644 --- a/src/util/entities/Sticker.ts +++ b/src/util/entities/Sticker.ts
@@ -17,9 +17,9 @@ */ import { Column, Entity, JoinColumn, ManyToOne, RelationId } from "typeorm"; -import { User } from "./User"; import { BaseClass } from "./BaseClass"; import { Guild } from "./Guild"; +import { User } from "./User"; export enum StickerType { STANDARD = 1, @@ -62,7 +62,7 @@ export class Sticker extends BaseClass { guild_id?: string; @JoinColumn({ name: "guild_id" }) - @ManyToOne(() => Guild, { + @ManyToOne(() => Guild, (guild) => guild.stickers, { onDelete: "CASCADE", }) guild?: Guild; diff --git a/src/util/entities/VoiceState.ts b/src/util/entities/VoiceState.ts
index b291c4d3..84b0ca71 100644 --- a/src/util/entities/VoiceState.ts +++ b/src/util/entities/VoiceState.ts
@@ -20,8 +20,8 @@ import { Column, Entity, JoinColumn, ManyToOne, RelationId } from "typeorm"; import { BaseClass } from "./BaseClass"; import { Channel } from "./Channel"; import { Guild } from "./Guild"; -import { User } from "./User"; import { Member } from "./Member"; +import { User } from "./User"; //https://gist.github.com/vassjozsef/e482c65df6ee1facaace8b3c9ff66145#file-voice_state-ex @Entity("voice_states") @@ -31,7 +31,7 @@ export class VoiceState extends BaseClass { guild_id: string; @JoinColumn({ name: "guild_id" }) - @ManyToOne(() => Guild, { + @ManyToOne(() => Guild, (guild) => guild.voice_states, { onDelete: "CASCADE", }) guild?: Guild; diff --git a/src/util/util/Database.ts b/src/util/util/Database.ts
index a6b24b3e..3a45eea0 100644 --- a/src/util/util/Database.ts +++ b/src/util/util/Database.ts
@@ -16,12 +16,12 @@ along with this program. If not, see <https://www.gnu.org/licenses/>. */ -import { DataSource } from "typeorm"; -import { yellow, green, red } from "picocolors"; -import { Migration } from "../entities/Migration"; -import { ConfigEntity } from "../entities/Config"; import { config } from "dotenv"; import path from "path"; +import { green, red, yellow } from "picocolors"; +import { DataSource } from "typeorm"; +import { ConfigEntity } from "../entities/Config"; +import { Migration } from "../entities/Migration"; // UUID extension option is only supported with postgres // We want to generate all id's with Snowflakes that's why we have our own BaseEntity class @@ -50,7 +50,7 @@ const DataSourceOptions = new DataSource({ database: isSqlite ? dbConnectionString : undefined, entities: [path.join(__dirname, "..", "entities", "*.js")], synchronize: !!process.env.DB_SYNC, - logging: false, + logging: !!process.env.DB_LOGGING, bigNumberStrings: false, supportBigNumbers: true, name: "default", @@ -129,7 +129,7 @@ export async function initDatabase(): Promise<DataSource> { return dbConnection; } -export { dbConnection, DataSourceOptions, DatabaseType }; +export { DataSourceOptions, DatabaseType, dbConnection }; export async function closeDatabase() { await dbConnection?.destroy(); diff --git a/src/util/util/Sentry.ts b/src/util/util/Sentry.ts
index e302da0c..74a23a1e 100644 --- a/src/util/util/Sentry.ts +++ b/src/util/util/Sentry.ts
@@ -16,13 +16,12 @@ along with this program. If not, see <https://www.gnu.org/licenses/>. */ -import { Config } from "./Config"; import { yellow } from "picocolors"; +import { Config } from "./Config"; -import express from "express"; -import * as SentryNode from "@sentry/node"; -import * as Tracing from "@sentry/tracing"; import * as Integrations from "@sentry/integrations"; +import * as SentryNode from "@sentry/node"; +import express from "express"; // Work around for when bundle calls api/etc let errorHandlersUsed = false; @@ -46,16 +45,28 @@ export const Sentry = { ); } + const integrations = [ + new SentryNode.Integrations.Http({ tracing: true }), + new Integrations.RewriteFrames({ + root: __dirname, + }), + new SentryNode.Integrations.Http({ + tracing: true, + breadcrumbs: true, + }), + ...SentryNode.autoDiscoverNodePerformanceMonitoringIntegrations(), + ]; + + if (app) + integrations.push( + new SentryNode.Integrations.Express({ + app, + }), + ); + SentryNode.init({ dsn: endpoint, - integrations: [ - new SentryNode.Integrations.Http({ tracing: true }), - new Tracing.Integrations.Express({ app }), - new Tracing.Integrations.Mysql(), - new Integrations.RewriteFrames({ - root: __dirname, - }), - ], + integrations, tracesSampleRate: traceSampleRate, // naming? environment, });