summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/Server.ts (renamed from bundle/src/Server.ts)62
-rw-r--r--src/api/Server.ts (renamed from api/src/Server.ts)31
-rw-r--r--src/api/global.d.ts (renamed from api/src/global.d.ts)0
-rw-r--r--src/api/index.ts (renamed from api/src/index.ts)2
-rw-r--r--src/api/middlewares/Authentication.ts (renamed from api/src/middlewares/Authentication.ts)6
-rw-r--r--src/api/middlewares/BodyParser.ts (renamed from api/src/middlewares/BodyParser.ts)2
-rw-r--r--src/api/middlewares/CORS.ts (renamed from api/src/middlewares/CORS.ts)0
-rw-r--r--src/api/middlewares/ErrorHandler.ts (renamed from api/src/middlewares/ErrorHandler.ts)3
-rw-r--r--src/api/middlewares/RateLimit.ts (renamed from api/src/middlewares/RateLimit.ts)19
-rw-r--r--src/api/middlewares/TestClient.ts156
-rw-r--r--src/api/middlewares/Translation.ts (renamed from api/src/middlewares/Translation.ts)10
-rw-r--r--src/api/middlewares/index.ts (renamed from api/src/middlewares/index.ts)0
-rw-r--r--src/api/routes/-/healthz.ts (renamed from api/src/routes/-/healthz.ts)2
-rw-r--r--src/api/routes/-/readyz.ts (renamed from api/src/routes/-/readyz.ts)2
-rw-r--r--src/api/routes/applications/#id/bot/index.ts83
-rw-r--r--src/api/routes/applications/#id/entitlements.ts (renamed from api/src/routes/applications/#id/entitlements.ts)2
-rw-r--r--src/api/routes/applications/#id/index.ts29
-rw-r--r--src/api/routes/applications/#id/skus.ts (renamed from api/src/routes/applications/index.ts)5
-rw-r--r--src/api/routes/applications/detectable.ts (renamed from api/src/routes/applications/detectable.ts)2
-rw-r--r--src/api/routes/applications/index.ts34
-rw-r--r--src/api/routes/auth/location-metadata.ts12
-rw-r--r--src/api/routes/auth/login.ts (renamed from api/src/routes/auth/login.ts)53
-rw-r--r--src/api/routes/auth/mfa/totp.ts36
-rw-r--r--src/api/routes/auth/register.ts (renamed from api/src/routes/auth/register.ts)65
-rw-r--r--src/api/routes/channels/#channel_id/followers.ts (renamed from api/src/routes/channels/#channel_id/followers.ts)2
-rw-r--r--src/api/routes/channels/#channel_id/index.ts (renamed from api/src/routes/channels/#channel_id/index.ts)44
-rw-r--r--src/api/routes/channels/#channel_id/invites.ts58
-rw-r--r--src/api/routes/channels/#channel_id/messages/#message_id/ack.ts (renamed from api/src/routes/channels/#channel_id/messages/#message_id/ack.ts)17
-rw-r--r--src/api/routes/channels/#channel_id/messages/#message_id/crosspost.ts (renamed from api/src/routes/channels/#channel_id/messages/#message_id/crosspost.ts)2
-rw-r--r--src/api/routes/channels/#channel_id/messages/#message_id/index.ts (renamed from api/src/routes/channels/#channel_id/messages/#message_id/index.ts)120
-rw-r--r--src/api/routes/channels/#channel_id/messages/#message_id/reactions.ts (renamed from api/src/routes/channels/#channel_id/messages/#message_id/reactions.ts)94
-rw-r--r--src/api/routes/channels/#channel_id/messages/bulk-delete.ts (renamed from api/src/routes/channels/#channel_id/messages/bulk-delete.ts)17
-rw-r--r--src/api/routes/channels/#channel_id/messages/index.ts (renamed from api/src/routes/channels/#channel_id/messages/index.ts)103
-rw-r--r--src/api/routes/channels/#channel_id/permissions.ts (renamed from api/src/routes/channels/#channel_id/permissions.ts)23
-rw-r--r--src/api/routes/channels/#channel_id/pins.ts (renamed from api/src/routes/channels/#channel_id/pins.ts)24
-rw-r--r--src/api/routes/channels/#channel_id/purge.ts77
-rw-r--r--src/api/routes/channels/#channel_id/recipients.ts (renamed from api/src/routes/channels/#channel_id/recipients.ts)7
-rw-r--r--src/api/routes/channels/#channel_id/typing.ts (renamed from api/src/routes/channels/#channel_id/typing.ts)6
-rw-r--r--src/api/routes/channels/#channel_id/webhooks.ts (renamed from api/src/routes/channels/#channel_id/webhooks.ts)21
-rw-r--r--src/api/routes/discoverable-guilds.ts39
-rw-r--r--src/api/routes/discovery.ts (renamed from api/src/routes/discovery.ts)6
-rw-r--r--src/api/routes/downloads.ts20
-rw-r--r--src/api/routes/experiments.ts (renamed from api/src/routes/experiments.ts)6
-rw-r--r--src/api/routes/gateway/bot.ts (renamed from api/src/routes/gateway/bot.ts)4
-rw-r--r--src/api/routes/gateway/index.ts (renamed from api/src/routes/gateway/index.ts)4
-rw-r--r--src/api/routes/gifs/search.ts (renamed from api/src/routes/gifs/search.ts)10
-rw-r--r--src/api/routes/gifs/trending-gifs.ts (renamed from api/src/routes/gifs/trending-gifs.ts)10
-rw-r--r--src/api/routes/gifs/trending.ts (renamed from api/src/routes/gifs/trending.ts)15
-rw-r--r--src/api/routes/guild-recommendations.ts (renamed from api/src/routes/guild-recommendations.ts)15
-rw-r--r--src/api/routes/guilds/#guild_id/audit-logs.ts (renamed from api/src/routes/guilds/#guild_id/audit-logs.ts)5
-rw-r--r--src/api/routes/guilds/#guild_id/bans.ts (renamed from api/src/routes/guilds/#guild_id/bans.ts)75
-rw-r--r--src/api/routes/guilds/#guild_id/channels.ts (renamed from api/src/routes/guilds/#guild_id/channels.ts)12
-rw-r--r--src/api/routes/guilds/#guild_id/delete.ts (renamed from api/src/routes/guilds/#guild_id/delete.ts)7
-rw-r--r--src/api/routes/guilds/#guild_id/discovery-requirements.ts37
-rw-r--r--src/api/routes/guilds/#guild_id/emojis.ts (renamed from api/src/routes/guilds/#guild_id/emojis.ts)43
-rw-r--r--src/api/routes/guilds/#guild_id/index.ts (renamed from api/src/routes/guilds/#guild_id/index.ts)56
-rw-r--r--src/api/routes/guilds/#guild_id/integrations.ts9
-rw-r--r--src/api/routes/guilds/#guild_id/invites.ts (renamed from api/src/routes/guilds/#guild_id/invites.ts)2
-rw-r--r--src/api/routes/guilds/#guild_id/members/#member_id/index.ts (renamed from api/src/routes/guilds/#guild_id/members/#member_id/index.ts)38
-rw-r--r--src/api/routes/guilds/#guild_id/members/#member_id/nick.ts (renamed from api/src/routes/guilds/#guild_id/members/#member_id/nick.ts)10
-rw-r--r--src/api/routes/guilds/#guild_id/members/#member_id/roles/#role_id/index.ts (renamed from api/src/routes/guilds/#guild_id/members/#member_id/roles/#role_id/index.ts)2
-rw-r--r--src/api/routes/guilds/#guild_id/members/index.ts (renamed from api/src/routes/guilds/#guild_id/members/index.ts)5
-rw-r--r--src/api/routes/guilds/#guild_id/premium.ts (renamed from api/src/routes/guilds/#guild_id/premium.ts)2
-rw-r--r--src/api/routes/guilds/#guild_id/prune.ts (renamed from api/src/routes/guilds/#guild_id/prune.ts)25
-rw-r--r--src/api/routes/guilds/#guild_id/regions.ts (renamed from api/src/routes/guilds/#guild_id/regions.ts)7
-rw-r--r--src/api/routes/guilds/#guild_id/roles/#role_id/index.ts (renamed from api/src/routes/guilds/#guild_id/roles/#role_id/index.ts)20
-rw-r--r--src/api/routes/guilds/#guild_id/roles/index.ts (renamed from api/src/routes/guilds/#guild_id/roles/index.ts)42
-rw-r--r--src/api/routes/guilds/#guild_id/stickers.ts (renamed from api/src/routes/guilds/#guild_id/stickers.ts)35
-rw-r--r--src/api/routes/guilds/#guild_id/templates.ts (renamed from api/src/routes/guilds/#guild_id/templates.ts)31
-rw-r--r--src/api/routes/guilds/#guild_id/vanity-url.ts (renamed from api/src/routes/guilds/#guild_id/vanity-url.ts)25
-rw-r--r--src/api/routes/guilds/#guild_id/voice-states/#user_id/index.ts (renamed from api/src/routes/guilds/#guild_id/voice-states/#user_id/index.ts)40
-rw-r--r--src/api/routes/guilds/#guild_id/webhooks.ts9
-rw-r--r--src/api/routes/guilds/#guild_id/welcome_screen.ts (renamed from api/src/routes/guilds/#guild_id/welcome_screen.ts)20
-rw-r--r--src/api/routes/guilds/#guild_id/widget.json.ts (renamed from api/src/routes/guilds/#guild_id/widget.json.ts)13
-rw-r--r--src/api/routes/guilds/#guild_id/widget.png.ts (renamed from api/src/routes/guilds/#guild_id/widget.png.ts)25
-rw-r--r--src/api/routes/guilds/#guild_id/widget.ts (renamed from api/src/routes/guilds/#guild_id/widget.ts)11
-rw-r--r--src/api/routes/guilds/index.ts (renamed from api/src/routes/guilds/index.ts)22
-rw-r--r--src/api/routes/guilds/templates/index.ts (renamed from api/src/routes/guilds/templates/index.ts)51
-rw-r--r--src/api/routes/invites/index.ts (renamed from api/src/routes/invites/index.ts)24
-rw-r--r--src/api/routes/oauth2/tokens.ts (renamed from api/src/routes/oauth2/tokens.ts)2
-rw-r--r--src/api/routes/outbound-promotions.ts (renamed from api/src/routes/outbound-promotions.ts)2
-rw-r--r--src/api/routes/partners/#guild_id/requirements.ts37
-rw-r--r--src/api/routes/ping.ts (renamed from api/src/routes/ping.ts)6
-rw-r--r--src/api/routes/policies/instance/domains.ts17
-rw-r--r--src/api/routes/policies/instance/index.ts (renamed from api/src/routes/policies/instance/index.ts)5
-rw-r--r--src/api/routes/policies/instance/limits.ts (renamed from api/src/routes/policies/instance/limits.ts)4
-rw-r--r--src/api/routes/scheduled-maintenances/upcoming_json.ts12
-rw-r--r--src/api/routes/science.ts (renamed from api/src/routes/science.ts)2
-rw-r--r--src/api/routes/stage-instances.ts (renamed from api/src/routes/stage-instances.ts)2
-rw-r--r--src/api/routes/sticker-packs/index.ts (renamed from api/src/routes/sticker-packs/index.ts)2
-rw-r--r--src/api/routes/stickers/#sticker_id/index.ts (renamed from api/src/routes/stickers/#sticker_id/index.ts)6
-rw-r--r--src/api/routes/stop.ts (renamed from api/src/routes/stop.ts)13
-rw-r--r--src/api/routes/store/published-listings/applications.ts (renamed from api/src/routes/store/published-listings/applications.ts)2
-rw-r--r--src/api/routes/store/published-listings/applications/#id/subscription-plans.ts (renamed from api/src/routes/store/published-listings/applications/#id/subscription-plans.ts)2
-rw-r--r--src/api/routes/store/published-listings/skus.ts (renamed from api/src/routes/store/published-listings/skus.ts)2
-rw-r--r--src/api/routes/store/published-listings/skus/#sku_id/subscription-plans.ts313
-rw-r--r--src/api/routes/teams.ts (renamed from api/src/routes/teams.ts)2
-rw-r--r--src/api/routes/template.ts.disabled (renamed from api/src/routes/template.ts.disabled)0
-rw-r--r--src/api/routes/track.ts (renamed from api/src/routes/track.ts)2
-rw-r--r--src/api/routes/updates.ts20
-rw-r--r--src/api/routes/users/#id/index.ts (renamed from api/src/routes/users/#id/index.ts)4
-rw-r--r--src/api/routes/users/#id/profile.ts94
-rw-r--r--src/api/routes/users/#id/relationships.ts46
-rw-r--r--src/api/routes/users/@me/activities/statistics/applications.ts (renamed from api/src/routes/users/@me/activities/statistics/applications.ts)2
-rw-r--r--src/api/routes/users/@me/affinities/guilds.ts (renamed from api/src/routes/users/@me/affinities/guilds.ts)2
-rw-r--r--src/api/routes/users/@me/affinities/users.ts (renamed from api/src/routes/users/@me/affinities/users.ts)2
-rw-r--r--src/api/routes/users/@me/applications/#app_id/entitlements.ts (renamed from api/src/routes/users/@me/applications/#app_id/entitlements.ts)2
-rw-r--r--src/api/routes/users/@me/billing/country-code.ts (renamed from api/src/routes/users/@me/billing/country-code.ts)2
-rw-r--r--src/api/routes/users/@me/billing/payment-sources.ts (renamed from api/src/routes/users/@me/billing/payment-sources.ts)2
-rw-r--r--src/api/routes/users/@me/billing/subscriptions.ts (renamed from api/src/routes/users/@me/billing/subscriptions.ts)2
-rw-r--r--src/api/routes/users/@me/channels.ts (renamed from api/src/routes/users/@me/channels.ts)9
-rw-r--r--src/api/routes/users/@me/connections.ts (renamed from api/src/routes/users/@me/connections.ts)2
-rw-r--r--src/api/routes/users/@me/delete.ts (renamed from api/src/routes/users/@me/delete.ts)14
-rw-r--r--src/api/routes/users/@me/devices.ts (renamed from api/src/routes/users/@me/devices.ts)2
-rw-r--r--src/api/routes/users/@me/disable.ts (renamed from api/src/routes/users/@me/disable.ts)13
-rw-r--r--src/api/routes/users/@me/email-settings.ts (renamed from api/src/routes/users/@me/email-settings.ts)2
-rw-r--r--src/api/routes/users/@me/entitlements.ts (renamed from api/src/routes/users/@me/entitlements.ts)2
-rw-r--r--src/api/routes/users/@me/guilds.ts (renamed from api/src/routes/users/@me/guilds.ts)5
-rw-r--r--src/api/routes/users/@me/guilds/premium/subscription-slots.ts (renamed from api/src/routes/users/@me/guilds/premium/subscription-slots.ts)2
-rw-r--r--src/api/routes/users/@me/index.ts (renamed from api/src/routes/users/@me/index.ts)79
-rw-r--r--src/api/routes/users/@me/library.ts (renamed from api/src/routes/users/@me/library.ts)2
-rw-r--r--src/api/routes/users/@me/mfa/codes.ts48
-rw-r--r--src/api/routes/users/@me/mfa/totp/disable.ts40
-rw-r--r--src/api/routes/users/@me/mfa/totp/enable.ts49
-rw-r--r--src/api/routes/users/@me/notes.ts53
-rw-r--r--src/api/routes/users/@me/relationships.ts (renamed from api/src/routes/users/@me/relationships.ts)68
-rw-r--r--src/api/routes/users/@me/settings.ts (renamed from api/src/routes/users/@me/settings.ts)10
-rw-r--r--src/api/routes/voice/regions.ts (renamed from api/src/routes/voice/regions.ts)5
-rw-r--r--src/api/start.ts (renamed from api/src/start.ts)13
-rw-r--r--src/api/util/entities/AssetCacheItem.ts3
-rw-r--r--src/api/util/entities/blockedEmailDomains.txt (renamed from api/src/util/entities/blockedEmailDomains.txt)0
-rw-r--r--src/api/util/entities/trustedEmailDomains.txt (renamed from api/src/util/entities/trustedEmailDomains.txt)0
-rw-r--r--src/api/util/handlers/Instance.ts (renamed from api/src/util/handlers/Instance.ts)2
-rw-r--r--src/api/util/handlers/Message.ts (renamed from api/src/util/handlers/Message.ts)83
-rw-r--r--src/api/util/handlers/Voice.ts (renamed from api/src/util/handlers/Voice.ts)0
-rw-r--r--src/api/util/handlers/route.ts (renamed from api/src/util/handlers/route.ts)17
-rw-r--r--src/api/util/index.ts (renamed from api/src/util/index.ts)8
-rw-r--r--src/api/util/utility/Base64.ts (renamed from api/src/util/utility/Base64.ts)0
-rw-r--r--src/api/util/utility/RandomInviteID.ts (renamed from api/src/util/utility/RandomInviteID.ts)7
-rw-r--r--src/api/util/utility/String.ts (renamed from api/src/util/utility/String.ts)2
-rw-r--r--src/api/util/utility/captcha.ts46
-rw-r--r--src/api/util/utility/ipAddress.ts (renamed from api/src/util/utility/ipAddress.ts)8
-rw-r--r--src/api/util/utility/passwordStrength.ts (renamed from api/src/util/utility/passwordStrength.ts)13
-rw-r--r--src/cdn/Server.ts (renamed from cdn/src/Server.ts)16
-rw-r--r--src/cdn/index.ts (renamed from cdn/src/index.ts)2
-rw-r--r--src/cdn/routes/attachments.ts77
-rw-r--r--src/cdn/routes/avatars.ts (renamed from cdn/src/routes/avatars.ts)82
-rw-r--r--src/cdn/routes/external.ts (renamed from cdn/src/routes/external.ts)16
-rw-r--r--src/cdn/routes/ping.ts (renamed from cdn/src/routes/ping.ts)2
-rw-r--r--src/cdn/routes/role-icons.ts (renamed from cdn/src/routes/role-icons.ts)80
-rw-r--r--src/cdn/start.ts (renamed from cdn/src/start.ts)0
-rw-r--r--src/cdn/util/FileStorage.ts (renamed from cdn/src/util/FileStorage.ts)17
-rw-r--r--src/cdn/util/S3Storage.ts (renamed from cdn/src/util/S3Storage.ts)12
-rw-r--r--src/cdn/util/Storage.ts (renamed from cdn/src/util/Storage.ts)22
-rw-r--r--src/cdn/util/index.ts (renamed from cdn/src/util/index.ts)0
-rw-r--r--src/cdn/util/multer.ts (renamed from cdn/src/util/multer.ts)4
-rw-r--r--src/gateway/Server.ts (renamed from gateway/src/Server.ts)21
-rw-r--r--src/gateway/events/Close.ts (renamed from gateway/src/events/Close.ts)19
-rw-r--r--src/gateway/events/Connection.ts (renamed from gateway/src/events/Connection.ts)47
-rw-r--r--src/gateway/events/Message.ts57
-rw-r--r--src/gateway/index.ts (renamed from gateway/src/index.ts)4
-rw-r--r--src/gateway/listener/listener.ts (renamed from gateway/src/listener/listener.ts)82
-rw-r--r--src/gateway/opcodes/Heartbeat.ts (renamed from gateway/src/opcodes/Heartbeat.ts)2
-rw-r--r--src/gateway/opcodes/Identify.ts (renamed from gateway/src/opcodes/Identify.ts)158
-rw-r--r--src/gateway/opcodes/LazyRequest.ts (renamed from gateway/src/opcodes/LazyRequest.ts)82
-rw-r--r--src/gateway/opcodes/PresenceUpdate.ts (renamed from gateway/src/opcodes/PresenceUpdate.ts)14
-rw-r--r--src/gateway/opcodes/RequestGuildMembers.ts (renamed from gateway/src/opcodes/RequestGuildMembers.ts)0
-rw-r--r--src/gateway/opcodes/Resume.ts (renamed from gateway/src/opcodes/Resume.ts)4
-rw-r--r--src/gateway/opcodes/VoiceStateUpdate.ts (renamed from gateway/src/opcodes/VoiceStateUpdate.ts)55
-rw-r--r--src/gateway/opcodes/experiments.json (renamed from gateway/src/opcodes/experiments.json)0
-rw-r--r--src/gateway/opcodes/index.ts (renamed from gateway/src/opcodes/index.ts)4
-rw-r--r--src/gateway/opcodes/instanceOf.ts (renamed from gateway/src/opcodes/instanceOf.ts)2
-rw-r--r--src/gateway/start.ts (renamed from gateway/src/start.ts)6
-rw-r--r--src/gateway/util/Constants.ts (renamed from gateway/src/util/Constants.ts)4
-rw-r--r--src/gateway/util/Heartbeat.ts (renamed from gateway/src/util/Heartbeat.ts)0
-rw-r--r--src/gateway/util/Send.ts (renamed from gateway/src/util/Send.ts)3
-rw-r--r--src/gateway/util/SessionUtils.ts (renamed from gateway/src/util/SessionUtils.ts)4
-rw-r--r--src/gateway/util/WebSocket.ts (renamed from gateway/src/util/WebSocket.ts)4
-rw-r--r--src/gateway/util/index.ts (renamed from gateway/src/util/index.ts)2
-rw-r--r--src/start.ts (renamed from bundle/src/start.ts)32
-rw-r--r--src/stats.ts21
-rw-r--r--src/util/config/Config.ts46
-rw-r--r--src/util/config/index.ts2
-rw-r--r--src/util/config/types/ApiConfiguration.ts5
-rw-r--r--src/util/config/types/ClientConfiguration.ts8
-rw-r--r--src/util/config/types/DefaultsConfiguration.ts6
-rw-r--r--src/util/config/types/EndpointConfiguration.ts4
-rw-r--r--src/util/config/types/GeneralConfiguration.ts12
-rw-r--r--src/util/config/types/GifConfiguration.ts5
-rw-r--r--src/util/config/types/GuildConfiguration.ts6
-rw-r--r--src/util/config/types/KafkaConfiguration.ts5
-rw-r--r--src/util/config/types/LimitConfigurations.ts9
-rw-r--r--src/util/config/types/LoginConfiguration.ts3
-rw-r--r--src/util/config/types/MetricsConfiguration.ts3
-rw-r--r--src/util/config/types/RabbitMQConfiguration.ts3
-rw-r--r--src/util/config/types/RegionConfiguration.ts16
-rw-r--r--src/util/config/types/RegisterConfiguration.ts19
-rw-r--r--src/util/config/types/SecurityConfiguration.ts19
-rw-r--r--src/util/config/types/SentryConfiguration.ts8
-rw-r--r--src/util/config/types/TemplateConfiguration.ts6
-rw-r--r--src/util/config/types/index.ts18
-rw-r--r--src/util/config/types/subconfigurations/client/ClientReleaseConfiguration.ts4
-rw-r--r--src/util/config/types/subconfigurations/client/index.ts1
-rw-r--r--src/util/config/types/subconfigurations/defaults/GuildDefaults.ts8
-rw-r--r--src/util/config/types/subconfigurations/defaults/UserDefaults.ts5
-rw-r--r--src/util/config/types/subconfigurations/defaults/index.ts2
-rw-r--r--src/util/config/types/subconfigurations/guild/AutoJoin.ts5
-rw-r--r--src/util/config/types/subconfigurations/guild/Discovery.ts6
-rw-r--r--src/util/config/types/subconfigurations/guild/index.ts2
-rw-r--r--src/util/config/types/subconfigurations/index.ts8
-rw-r--r--src/util/config/types/subconfigurations/kafka/KafkaBroker.ts4
-rw-r--r--src/util/config/types/subconfigurations/kafka/index.ts1
-rw-r--r--src/util/config/types/subconfigurations/limits/ChannelLimits.ts5
-rw-r--r--src/util/config/types/subconfigurations/limits/GuildLimits.ts8
-rw-r--r--src/util/config/types/subconfigurations/limits/MessageLimits.ts8
-rw-r--r--src/util/config/types/subconfigurations/limits/RateLimits.ts18
-rw-r--r--src/util/config/types/subconfigurations/limits/UserLimits.ts5
-rw-r--r--src/util/config/types/subconfigurations/limits/index.ts6
-rw-r--r--src/util/config/types/subconfigurations/limits/ratelimits/Auth.ts12
-rw-r--r--src/util/config/types/subconfigurations/limits/ratelimits/RateLimitOptions.ts6
-rw-r--r--src/util/config/types/subconfigurations/limits/ratelimits/Route.ts19
-rw-r--r--src/util/config/types/subconfigurations/limits/ratelimits/index.ts3
-rw-r--r--src/util/config/types/subconfigurations/region/Region.ts12
-rw-r--r--src/util/config/types/subconfigurations/region/index.ts1
-rw-r--r--src/util/config/types/subconfigurations/register/DateOfBirth.ts4
-rw-r--r--src/util/config/types/subconfigurations/register/Email.ts7
-rw-r--r--src/util/config/types/subconfigurations/register/Password.ts7
-rw-r--r--src/util/config/types/subconfigurations/register/index.ts3
-rw-r--r--src/util/config/types/subconfigurations/security/Captcha.ts6
-rw-r--r--src/util/config/types/subconfigurations/security/TwoFactor.ts3
-rw-r--r--src/util/config/types/subconfigurations/security/index.ts2
-rw-r--r--src/util/dtos/DmChannelDTO.ts (renamed from util/src/dtos/DmChannelDTO.ts)4
-rw-r--r--src/util/dtos/UserDTO.ts (renamed from util/src/dtos/UserDTO.ts)0
-rw-r--r--src/util/dtos/index.ts (renamed from util/src/dtos/index.ts)0
-rw-r--r--src/util/entities/Application.ts (renamed from util/src/entities/Application.ts)101
-rw-r--r--src/util/entities/Attachment.ts (renamed from util/src/entities/Attachment.ts)2
-rw-r--r--src/util/entities/AuditLog.ts (renamed from util/src/entities/AuditLog.ts)18
-rw-r--r--src/util/entities/BackupCodes.ts19
-rw-r--r--src/util/entities/Ban.ts (renamed from util/src/entities/Ban.ts)4
-rw-r--r--src/util/entities/BaseClass.ts26
-rw-r--r--src/util/entities/Categories.ts (renamed from util/src/entities/Categories.ts)24
-rw-r--r--src/util/entities/Channel.ts (renamed from util/src/entities/Channel.ts)769
-rw-r--r--src/util/entities/ClientRelease.ts (renamed from util/src/entities/ClientRelease.ts)2
-rw-r--r--src/util/entities/Config.ts11
-rw-r--r--src/util/entities/ConnectedAccount.ts (renamed from util/src/entities/ConnectedAccount.ts)2
-rw-r--r--src/util/entities/Emoji.ts (renamed from util/src/entities/Emoji.ts)5
-rw-r--r--src/util/entities/Encryption.ts25
-rw-r--r--src/util/entities/Group.ts (renamed from util/src/entities/Group.ts)8
-rw-r--r--src/util/entities/Guild.ts (renamed from util/src/entities/Guild.ts)80
-rw-r--r--src/util/entities/Invite.ts (renamed from util/src/entities/Invite.ts)25
-rw-r--r--src/util/entities/Member.ts (renamed from util/src/entities/Member.ts)152
-rw-r--r--src/util/entities/Message.ts (renamed from util/src/entities/Message.ts)42
-rw-r--r--src/util/entities/Migration.ts (renamed from util/src/entities/Migration.ts)4
-rw-r--r--src/util/entities/Note.ts18
-rw-r--r--src/util/entities/RateLimit.ts (renamed from util/src/entities/RateLimit.ts)0
-rw-r--r--src/util/entities/ReadState.ts (renamed from util/src/entities/ReadState.ts)9
-rw-r--r--src/util/entities/Recipient.ts (renamed from util/src/entities/Recipient.ts)4
-rw-r--r--src/util/entities/Relationship.ts (renamed from util/src/entities/Relationship.ts)8
-rw-r--r--src/util/entities/Role.ts (renamed from util/src/entities/Role.ts)2
-rw-r--r--src/util/entities/Session.ts (renamed from util/src/entities/Session.ts)16
-rw-r--r--src/util/entities/Sticker.ts (renamed from util/src/entities/Sticker.ts)12
-rw-r--r--src/util/entities/StickerPack.ts (renamed from util/src/entities/StickerPack.ts)4
-rw-r--r--src/util/entities/Team.ts (renamed from util/src/entities/Team.ts)4
-rw-r--r--src/util/entities/TeamMember.ts (renamed from util/src/entities/TeamMember.ts)6
-rw-r--r--src/util/entities/Template.ts (renamed from util/src/entities/Template.ts)0
-rw-r--r--src/util/entities/User.ts (renamed from util/src/entities/User.ts)224
-rw-r--r--src/util/entities/UserGroup.ts (renamed from util/src/entities/UserGroup.ts)14
-rw-r--r--src/util/entities/UserSettings.ts119
-rw-r--r--src/util/entities/VoiceState.ts (renamed from util/src/entities/VoiceState.ts)8
-rw-r--r--src/util/entities/Webhook.ts (renamed from util/src/entities/Webhook.ts)12
-rw-r--r--src/util/entities/index.ts (renamed from util/src/entities/index.ts)5
-rw-r--r--src/util/index.ts (renamed from util/src/index.ts)9
-rw-r--r--src/util/interfaces/Activity.ts (renamed from util/src/interfaces/Activity.ts)2
-rw-r--r--src/util/interfaces/Event.ts (renamed from util/src/interfaces/Event.ts)22
-rw-r--r--src/util/interfaces/Interaction.ts (renamed from util/src/interfaces/Interaction.ts)4
-rw-r--r--src/util/interfaces/Presence.ts (renamed from util/src/interfaces/Presence.ts)4
-rw-r--r--src/util/interfaces/Status.ts (renamed from util/src/interfaces/Status.ts)0
-rw-r--r--src/util/interfaces/index.ts (renamed from util/src/interfaces/index.ts)4
-rw-r--r--src/util/migrations/mariadb/1659901151025-initial.ts1218
-rw-r--r--src/util/migrations/mariadb/1659921859145-premium_since_as_date.ts25
-rw-r--r--src/util/migrations/mariadb/1660130586602-updated-applications.ts184
-rw-r--r--src/util/migrations/mariadb/1661273147273-test.ts149
-rw-r--r--src/util/migrations/mariadb/1661273179287-test2.ts18
-rw-r--r--src/util/migrations/postgres/1659899687168-initial.ts1244
-rw-r--r--src/util/migrations/postgres/1659921826567-premium_since_as_date.ts25
-rw-r--r--src/util/migrations/postgres/1660130561959-updated-applications.ts181
-rw-r--r--src/util/migrations/postgres/1660257815436-CodeCleanup2.ts58
-rw-r--r--src/util/migrations/postgres/1660258372154-CodeCleanup3.ts18
-rw-r--r--src/util/migrations/postgres/1660260565996-CodeCleanup4.ts32
-rw-r--r--src/util/migrations/postgres/1660265907544-CodeCleanup5.ts25
-rw-r--r--src/util/migrations/postgres/1660416055566-InvitersAreDeletable.ts25
-rw-r--r--src/util/migrations/postgres/1660549242936-fix_nullables.ts29
-rw-r--r--src/util/migrations/sqlite/1659899662635-initial.ts3528
-rw-r--r--src/util/migrations/sqlite/1659921722863-premium_since_as_date.ts251
-rw-r--r--src/util/migrations/sqlite/1660130536131-updated-applications.ts828
-rw-r--r--src/util/migrations/sqlite/1660257576211-CodeCleanup1.ts325
-rw-r--r--src/util/migrations/sqlite/1660257795259-CodeCleanup2.ts571
-rw-r--r--src/util/migrations/sqlite/1660258351379-CodeCleanup3.ts230
-rw-r--r--src/util/migrations/sqlite/1660260672914-CodeCleanup4.ts458
-rw-r--r--src/util/migrations/sqlite/1660416010862-InvitersAreDeletable.ts245
-rw-r--r--src/util/migrations/sqlite/1660538628956-sync_migrations.ts171
-rw-r--r--src/util/migrations/sqlite/1660549233583-fix_nullables.ts239
-rw-r--r--src/util/schemas/ActivitySchema.ts (renamed from gateway/src/schema/Activity.ts)16
-rw-r--r--src/util/schemas/BanCreateSchema.ts4
-rw-r--r--src/util/schemas/BanModeratorSchema.ts7
-rw-r--r--src/util/schemas/BanRegistrySchema.ts8
-rw-r--r--src/util/schemas/BulkDeleteSchema.ts3
-rw-r--r--src/util/schemas/ChannelModifySchema.ts28
-rw-r--r--src/util/schemas/ChannelPermissionOverwriteSchema.ts5
-rw-r--r--src/util/schemas/ChannelReorderSchema.ts1
-rw-r--r--src/util/schemas/DmChannelCreateSchema.ts4
-rw-r--r--src/util/schemas/EmojiCreateSchema.ts6
-rw-r--r--src/util/schemas/EmojiModifySchema.ts4
-rw-r--r--src/util/schemas/GuildCreateSchema.ts14
-rw-r--r--src/util/schemas/GuildTemplateCreateSchema.ts4
-rw-r--r--src/util/schemas/GuildUpdateSchema.ts18
-rw-r--r--src/util/schemas/GuildUpdateWelcomeScreenSchema.ts10
-rw-r--r--src/util/schemas/IdentifySchema.ts (renamed from gateway/src/schema/Identify.ts)14
-rw-r--r--src/util/schemas/InviteCreateSchema.ts11
-rw-r--r--src/util/schemas/LazyRequestSchema.ts (renamed from gateway/src/schema/LazyRequest.ts)2
-rw-r--r--src/util/schemas/LoginSchema.ts8
-rw-r--r--src/util/schemas/MemberChangeSchema.ts3
-rw-r--r--src/util/schemas/MemberNickChangeSchema.ts3
-rw-r--r--src/util/schemas/MessageAcknowledgeSchema.ts8
-rw-r--r--src/util/schemas/MessageCreateSchema.ts33
-rw-r--r--src/util/schemas/MfaCodesSchema.ts4
-rw-r--r--src/util/schemas/ModifyGuildStickerSchema.ts15
-rw-r--r--src/util/schemas/PruneSchema.ts6
-rw-r--r--src/util/schemas/PurgeSchema.ts4
-rw-r--r--src/util/schemas/RegisterSchema.ts26
-rw-r--r--src/util/schemas/RelationshipPostSchema.ts4
-rw-r--r--src/util/schemas/RelationshipPutSchema.ts5
-rw-r--r--src/util/schemas/RoleModifySchema.ts10
-rw-r--r--src/util/schemas/RolePositionUpdateSchema.ts4
-rw-r--r--src/util/schemas/TemplateCreateSchema.ts4
-rw-r--r--src/util/schemas/TemplateModifySchema.ts4
-rw-r--r--src/util/schemas/TotpDisableSchema.ts3
-rw-r--r--src/util/schemas/TotpEnableSchema.ts5
-rw-r--r--src/util/schemas/TotpSchema.ts6
-rw-r--r--src/util/schemas/UserModifySchema.ts19
-rw-r--r--src/util/schemas/UserSettingsSchema.ts3
-rw-r--r--src/util/schemas/VanityUrlSchema.ts7
-rw-r--r--src/util/schemas/VoiceStateUpdateSchema.ts18
-rw-r--r--src/util/schemas/WebhookCreateSchema.ts8
-rw-r--r--src/util/schemas/WidgetModifySchema.ts4
-rw-r--r--src/util/schemas/index.ts43
-rw-r--r--src/util/util/ApiError.ts (renamed from util/src/util/ApiError.ts)3
-rw-r--r--src/util/util/Array.ts (renamed from util/src/util/Array.ts)0
-rw-r--r--src/util/util/AutoUpdate.ts (renamed from util/src/util/AutoUpdate.ts)13
-rw-r--r--src/util/util/BitField.ts (renamed from util/src/util/BitField.ts)3
-rw-r--r--src/util/util/Categories.ts (renamed from util/src/util/Categories.ts)2
-rw-r--r--src/util/util/Config.ts105
-rw-r--r--src/util/util/Constants.ts (renamed from util/src/util/Constants.ts)136
-rw-r--r--src/util/util/Database.ts107
-rw-r--r--src/util/util/Email.ts (renamed from util/src/util/Email.ts)2
-rw-r--r--src/util/util/Event.ts (renamed from util/src/util/Event.ts)17
-rw-r--r--src/util/util/FieldError.ts (renamed from util/src/util/FieldError.ts)8
-rw-r--r--src/util/util/Intents.ts (renamed from util/src/util/Intents.ts)5
-rw-r--r--src/util/util/InvisibleCharacters.ts57
-rw-r--r--src/util/util/MFA.ts18
-rw-r--r--src/util/util/MessageFlags.ts (renamed from util/src/util/MessageFlags.ts)2
-rw-r--r--src/util/util/Permissions.ts (renamed from util/src/util/Permissions.ts)48
-rw-r--r--src/util/util/RabbitMQ.ts (renamed from util/src/util/RabbitMQ.ts)4
-rw-r--r--src/util/util/Regex.ts (renamed from util/src/util/Regex.ts)0
-rw-r--r--src/util/util/Rights.ts (renamed from util/src/util/Rights.ts)24
-rw-r--r--src/util/util/Snowflake.ts (renamed from util/src/util/Snowflake.ts)17
-rw-r--r--src/util/util/String.ts (renamed from util/src/util/String.ts)0
-rw-r--r--src/util/util/Token.ts (renamed from util/src/util/Token.ts)17
-rw-r--r--src/util/util/TraverseDirectory.ts (renamed from util/src/util/TraverseDirectory.ts)9
-rw-r--r--src/util/util/cdn.ts (renamed from util/src/util/cdn.ts)13
-rw-r--r--src/util/util/imports/Checks.ts121
-rw-r--r--src/util/util/imports/HTTPError.ts5
-rw-r--r--src/util/util/imports/OrmUtils.ts96
-rw-r--r--src/util/util/imports/index.ts3
-rw-r--r--src/util/util/index.ts (renamed from util/src/util/index.ts)9
375 files changed, 15223 insertions, 2569 deletions
diff --git a/bundle/src/Server.ts b/src/Server.ts

index 74a36114..89a3cea2 100644 --- a/bundle/src/Server.ts +++ b/src/Server.ts
@@ -1,15 +1,16 @@ process.on("unhandledRejection", console.error); process.on("uncaughtException", console.error); -import http from "http"; import * as Api from "@fosscord/api"; -import * as Gateway from "@fosscord/gateway"; import { CDNServer } from "@fosscord/cdn"; -import express from "express"; -import { green, bold, yellow } from "picocolors"; -import { Config, initDatabase } from "@fosscord/util"; +import * as Gateway from "@fosscord/gateway"; +import { Config, getOrInitialiseDatabase } from "@fosscord/util"; import * as Sentry from "@sentry/node"; import * as Tracing from "@sentry/tracing"; +import express from "express"; +import http from "http"; +import { bold, green, yellow } from "picocolors"; +// import { PluginLoader } from "@fosscord/util"; const app = express(); const server = http.createServer(); @@ -25,57 +26,24 @@ const cdn = new CDNServer({ server, port, production, app }); const gateway = new Gateway.Server({ server, port, production }); //this is what has been added for the /stop API route -process.on('SIGTERM', () => { +process.on("SIGTERM", () => { + setTimeout(() => process.exit(0), 3000); server.close(() => { - console.log("Stop API has been successfully POSTed, SIGTERM sent") - }) -}) + console.log("Stop API has been successfully POSTed, SIGTERM sent"); + }); +}); //this is what has been added for the /stop API route async function main() { - await initDatabase(); + await getOrInitialiseDatabase(); await Config.init(); - // only set endpointPublic, if not already set - await Config.set({ - cdn: { - endpointClient: "${location.host}", - endpointPrivate: `http://localhost:${port}`, - }, - gateway: { - endpointClient: - '${location.protocol === "https:" ? "wss://" : "ws://"}${location.host}', - endpointPrivate: `ws://localhost:${port}`, - ...(!Config.get().gateway.endpointPublic && { - endpointPublic: `ws://localhost:${port}`, - }), - }, - // regions: { - // default: "fosscord", - // useDefaultAsOptimal: true, - // available: [ - // { - // id: "fosscord", - // name: "Fosscord", - // endpoint: "127.0.0.1:3001", - // vip: false, - // custom: false, - // deprecated: false, - // }, - // ], - // }, - } as any); //Sentry if (Config.get().sentry.enabled) { - console.log( - `[Bundle] ${yellow("You are using Sentry! This may slightly impact performance on large loads!")}` - ); + console.log(`[Bundle] ${yellow("You are using Sentry! This may slightly impact performance on large loads!")}`); Sentry.init({ dsn: Config.get().sentry.endpoint, - integrations: [ - new Sentry.Integrations.Http({ tracing: true }), - new Tracing.Integrations.Express({ app }), - ], + integrations: [new Sentry.Integrations.Http({ tracing: true }), new Tracing.Integrations.Express({ app })], tracesSampleRate: Config.get().sentry.traceSampleRate, environment: Config.get().sentry.environment }); @@ -91,8 +59,10 @@ async function main() { }); } + server.listen(port); await Promise.all([api.start(), cdn.start(), gateway.start()]); console.log(`[Server] ${green(`listening on port ${bold(port)}`)}`); + // PluginLoader.loadPlugins(); } main().catch(console.error); diff --git a/api/src/Server.ts b/src/api/Server.ts
index 4cf0917d..e92335a5 100644 --- a/api/src/Server.ts +++ b/src/api/Server.ts
@@ -1,18 +1,16 @@ -import "missing-native-js-functions"; +import { Config, getOrInitialiseDatabase, initEvent, registerRoutes } from "@fosscord/util"; +import { NextFunction, Request, Response, Router } from "express"; import { Server, ServerOptions } from "lambert-server"; +import morgan from "morgan"; +import path from "path"; +import { red } from "picocolors"; import { Authentication, CORS } from "./middlewares/"; -import { Config, initDatabase, initEvent } from "@fosscord/util"; -import { ErrorHandler } from "./middlewares/ErrorHandler"; import { BodyParser } from "./middlewares/BodyParser"; -import { Router, Request, Response, NextFunction } from "express"; -import path from "path"; +import { ErrorHandler } from "./middlewares/ErrorHandler"; import { initRateLimits } from "./middlewares/RateLimit"; import TestClient from "./middlewares/TestClient"; import { initTranslation } from "./middlewares/Translation"; -import morgan from "morgan"; import { initInstance } from "./util/handlers/Instance"; -import { registerRoutes } from "@fosscord/util"; -import { red } from "picocolors" export interface FosscordServerOptions extends ServerOptions {} @@ -34,7 +32,7 @@ export class FosscordServer extends Server { } async start() { - await initDatabase(); + await getOrInitialiseDatabase(); await Config.init(); await initEvent(); await initInstance(); @@ -44,13 +42,13 @@ export class FosscordServer extends Server { this.app.use( morgan("combined", { skip: (req, res) => { - var skip = !(process.env["LOG_REQUESTS"]?.includes(res.statusCode.toString()) ?? false); + let skip = !(process.env["LOG_REQUESTS"]?.includes(res.statusCode.toString()) ?? false); if (process.env["LOG_REQUESTS"]?.charAt(0) == "-") skip = !skip; return skip; } }) ); - }; + } this.app.use(CORS); this.app.use(BodyParser({ inflate: true, limit: "10mb" })); @@ -87,8 +85,13 @@ export class FosscordServer extends Server { this.app.use(ErrorHandler); TestClient(this.app); - if (logRequests) console.log(red(`Warning: Request logging is enabled! This will spam your console!\nTo disable this, unset the 'LOG_REQUESTS' environment variable!`)); - + if (logRequests) + console.log( + red( + `Warning: Request logging is enabled! This will spam your console!\nTo disable this, unset the 'LOG_REQUESTS' environment variable!` + ) + ); + return super.start(); } -}; \ No newline at end of file +} diff --git a/api/src/global.d.ts b/src/api/global.d.ts
index 7751af8f..7751af8f 100644 --- a/api/src/global.d.ts +++ b/src/api/global.d.ts
diff --git a/api/src/index.ts b/src/api/index.ts
index adc7649c..5f97a463 100644 --- a/api/src/index.ts +++ b/src/api/index.ts
@@ -1,3 +1,3 @@ -export * from "./Server"; export * from "./middlewares/"; +export * from "./Server"; export * from "./util/"; diff --git a/api/src/middlewares/Authentication.ts b/src/api/middlewares/Authentication.ts
index 5a08caf3..6d063953 100644 --- a/api/src/middlewares/Authentication.ts +++ b/src/api/middlewares/Authentication.ts
@@ -1,15 +1,15 @@ +import { checkToken, Config, HTTPError, Rights } from "@fosscord/util"; import { NextFunction, Request, Response } from "express"; -import { HTTPError } from "lambert-server"; -import { checkToken, Config, Rights } from "@fosscord/util"; export const NO_AUTHORIZATION_ROUTES = [ // Authentication routes "/auth/login", "/auth/register", "/auth/location-metadata", + "/auth/mfa/totp", // Routes with a seperate auth system "/webhooks/", - // Public information endpoints + // Public information endpoints "/ping", "/gateway", "/experiments", diff --git a/api/src/middlewares/BodyParser.ts b/src/api/middlewares/BodyParser.ts
index 4cb376bc..36d89da7 100644 --- a/api/src/middlewares/BodyParser.ts +++ b/src/api/middlewares/BodyParser.ts
@@ -1,6 +1,6 @@ +import { HTTPError } from "@fosscord/util"; import bodyParser, { OptionsJson } from "body-parser"; import { NextFunction, Request, Response } from "express"; -import { HTTPError } from "lambert-server"; export function BodyParser(opts?: OptionsJson) { const jsonParser = bodyParser.json(opts); diff --git a/api/src/middlewares/CORS.ts b/src/api/middlewares/CORS.ts
index 20260cf9..20260cf9 100644 --- a/api/src/middlewares/CORS.ts +++ b/src/api/middlewares/CORS.ts
diff --git a/api/src/middlewares/ErrorHandler.ts b/src/api/middlewares/ErrorHandler.ts
index 2012b91c..813adc18 100644 --- a/api/src/middlewares/ErrorHandler.ts +++ b/src/api/middlewares/ErrorHandler.ts
@@ -1,6 +1,5 @@ +import { ApiError, FieldError, HTTPError } from "@fosscord/util"; import { NextFunction, Request, Response } from "express"; -import { HTTPError } from "lambert-server"; -import { ApiError, FieldError } from "@fosscord/util"; const EntityNotFoundErrorRegex = /"(\w+)"/; export function ErrorHandler(error: Error, req: Request, res: Response, next: NextFunction) { diff --git a/api/src/middlewares/RateLimit.ts b/src/api/middlewares/RateLimit.ts
index 13f1602c..dc93dcef 100644 --- a/api/src/middlewares/RateLimit.ts +++ b/src/api/middlewares/RateLimit.ts
@@ -1,6 +1,6 @@ -import { Config, getRights, listenEvent, Rights } from "@fosscord/util"; -import { NextFunction, Request, Response, Router } from "express"; import { getIpAdress } from "@fosscord/api"; +import { Config, getRights, listenEvent } from "@fosscord/util"; +import { NextFunction, Request, Response, Router } from "express"; import { API_PREFIX_TRAILING_SLASH } from "./Authentication"; // Docs: https://discord.com/developers/docs/topics/rate-limits @@ -28,7 +28,7 @@ type RateLimit = { expires_at: Date; }; -var Cache = new Map<string, RateLimit>(); +let Cache = new Map<string, RateLimit>(); const EventRateLimit = "RATELIMIT"; export default function rateLimit(opts: { @@ -48,14 +48,14 @@ export default function rateLimit(opts: { // exempt user? if so, immediately short circuit if (req.user_id) { const rights = await getRights(req.user_id); - if (rights.has("BYPASS_RATE_LIMITS")) return; + if (rights.has("BYPASS_RATE_LIMITS")) return next(); } const bucket_id = opts.bucket || req.originalUrl.replace(API_PREFIX_TRAILING_SLASH, ""); - var executor_id = getIpAdress(req); + let executor_id = getIpAdress(req); if (!opts.onlyIp && req.user_id) executor_id = req.user_id; - var max_hits = opts.count; + let max_hits = opts.count; if (opts.bot && req.user_bot) max_hits = opts.bot; if (opts.GET && ["GET", "OPTIONS", "HEAD"].includes(req.method)) max_hits = opts.GET; else if (opts.MODIFY && ["POST", "DELETE", "PATCH", "PUT"].includes(req.method)) max_hits = opts.MODIFY; @@ -121,6 +121,7 @@ export default function rateLimit(opts: { export async function initRateLimits(app: Router) { const { routes, global, ip, error, disabled } = Config.get().limits.rate; if (disabled) return; + console.log("Enabling rate limits..."); await listenEvent(EventRateLimit, (event) => { Cache.set(event.channel_id as string, event.data); event.acknowledge?.(); @@ -163,9 +164,9 @@ export async function initRateLimits(app: Router) { app.use("/auth/register", rateLimit({ onlyIp: true, success: true, ...routes.auth.register })); } -async function hitRoute(opts: { executor_id: string; bucket_id: string; max_hits: number; window: number; }) { +async function hitRoute(opts: { executor_id: string; bucket_id: string; max_hits: number; window: number }) { const id = opts.executor_id + opts.bucket_id; - var limit = Cache.get(id); + let limit = Cache.get(id); if (!limit) { limit = { id: opts.bucket_id, @@ -183,7 +184,7 @@ async function hitRoute(opts: { executor_id: string; bucket_id: string; max_hits } /* - var ratelimit = await RateLimit.findOne({ id: opts.bucket_id, executor_id: opts.executor_id }); + let ratelimit = await RateLimit.findOne({ where: { id: opts.bucket_id, executor_id: opts.executor_id } }); if (!ratelimit) { ratelimit = new RateLimit({ id: opts.bucket_id, diff --git a/src/api/middlewares/TestClient.ts b/src/api/middlewares/TestClient.ts new file mode 100644
index 00000000..2c195994 --- /dev/null +++ b/src/api/middlewares/TestClient.ts
@@ -0,0 +1,156 @@ +import { Config } from "@fosscord/util"; +import express, { Application, Request, Response } from "express"; +import fs from "fs"; +import fetch, { Headers, Response as FetchResponse } from "node-fetch"; +import path from "path"; +import { green } from "picocolors"; +import ProxyAgent from "proxy-agent"; +import { AssetCacheItem } from "../util/entities/AssetCacheItem"; + +const AssetsPath = path.join(__dirname, "..", "..", "..", "assets"); + +export default function TestClient(app: Application) { + const agent = new ProxyAgent(); + + //build client page + let html = fs.readFileSync(path.join(AssetsPath, "index.html"), { encoding: "utf8" }); + html = applyEnv(html); + html = applyInlinePlugins(html); + html = applyPlugins(html); + html = applyPreloadPlugins(html); + + //load asset cache + let newAssetCache: Map<string, AssetCacheItem> = new Map<string, AssetCacheItem>(); + let assetCacheDir = path.join(AssetsPath, "cache"); + if (process.env.ASSET_CACHE_DIR) assetCacheDir = process.env.ASSET_CACHE_DIR; + + console.log(`[TestClient] ${green(`Using asset cache path: ${assetCacheDir}`)}`); + if (!fs.existsSync(assetCacheDir)) { + fs.mkdirSync(assetCacheDir); + } + if (fs.existsSync(path.join(assetCacheDir, "index.json"))) { + let rawdata = fs.readFileSync(path.join(assetCacheDir, "index.json")); + newAssetCache = new Map<string, AssetCacheItem>(Object.entries(JSON.parse(rawdata.toString()))); + } + + app.use("/assets", express.static(path.join(AssetsPath))); + app.get("/assets/:file", async (req: Request, res: Response) => { + delete req.headers.host; + let response: FetchResponse; + let buffer: Buffer; + let assetCacheItem: AssetCacheItem = new AssetCacheItem(req.params.file); + if (newAssetCache.has(req.params.file)) { + assetCacheItem = newAssetCache.get(req.params.file)!; + assetCacheItem.Headers.forEach((value: any, name: any) => { + res.set(name, value); + }); + } else { + if(req.params.file.endsWith(".map")) { + return res.status(404).send("Not found"); + } + console.log(`[TestClient] Downloading file not yet cached! Asset file: ${req.params.file}`); + response = await fetch(`https://discord.com/assets/${req.params.file}`, { + agent, + // @ts-ignore + headers: { + ...req.headers + } + }); + + //set cache info + assetCacheItem.Headers = Object.fromEntries(stripHeaders(response.headers)); + assetCacheItem.FilePath = path.join(assetCacheDir, req.params.file); + assetCacheItem.Key = req.params.file; + //add to cache and save + newAssetCache.set(req.params.file, assetCacheItem); + fs.writeFileSync(path.join(assetCacheDir, "index.json"), JSON.stringify(Object.fromEntries(newAssetCache), null, 4)); + //download file + fs.writeFileSync(assetCacheItem.FilePath, await response.buffer()); + } + + assetCacheItem.Headers.forEach((value: string, name: string) => { + res.set(name, value); + }); + return res.send(fs.readFileSync(assetCacheItem.FilePath)); + }); + app.get("/developers*", (_req: Request, res: Response) => { + const { useTestClient } = Config.get().client; + res.set("Cache-Control", "public, max-age=" + 60 * 60 * 24); + res.set("content-type", "text/html"); + + if (!useTestClient) return res.send("Test client is disabled on this instance. Use a stand-alone client to connect this instance."); + + res.send(fs.readFileSync(path.join(__dirname, "..", "..", "..", "assets", "developers.html"), { encoding: "utf8" })); + }); + app.get("*", (req: Request, res: Response) => { + const { useTestClient } = Config.get().client; + res.set("Cache-Control", "public, max-age=" + 60 * 60 * 24); + res.set("content-type", "text/html"); + + if (req.url.startsWith("/api") || req.url.startsWith("/__development")) return; + + if (!useTestClient) return res.send("Test client is disabled on this instance. Use a stand-alone client to connect this instance."); + if (req.url.startsWith("/invite")) return res.send(html.replace("9b2b7f0632acd0c5e781", "9f24f709a3de09b67c49")); + + res.send(html); + }); +} + +function applyEnv(html: string): string { + const CDN_ENDPOINT = (Config.get()?.cdn.endpointPublic || process.env.CDN || "").replace(/(https?)?(:\/\/?)/g, ""); + const GATEWAY_ENDPOINT = Config.get()?.gateway.endpointPublic || process.env.GATEWAY || ""; + + if (CDN_ENDPOINT) { + html = html.replace(/CDN_HOST: .+/, `CDN_HOST: \`${CDN_ENDPOINT}\`,`); + } + if (GATEWAY_ENDPOINT) { + html = html.replace(/GATEWAY_ENDPOINT: .+/, `GATEWAY_ENDPOINT: \`${GATEWAY_ENDPOINT}\`,`); + } + return html; +} + +function applyPlugins(html: string): string { + // plugins + let files = fs.readdirSync(path.join(AssetsPath, "plugins")); + let plugins = ""; + files.forEach((x) => { + if (x.endsWith(".js")) plugins += `<script src='/assets/plugins/${x}'></script>\n`; + }); + return html.replaceAll("<!-- plugin marker -->", plugins); +} + +function applyInlinePlugins(html: string): string { + // inline plugins + let files = fs.readdirSync(path.join(AssetsPath, "inline-plugins")); + let plugins = ""; + files.forEach((x) => { + if (x.endsWith(".js")) plugins += `<script src='/assets/inline-plugins/${x}'></script>\n\n`; + }); + return html.replaceAll("<!-- inline plugin marker -->", plugins); +} + +function applyPreloadPlugins(html: string): string { + //preload plugins + let files = fs.readdirSync(path.join(AssetsPath, "preload-plugins")); + let plugins = ""; + files.forEach((x) => { + if (x.endsWith(".js")) plugins += `<script>${fs.readFileSync(path.join(AssetsPath, "preload-plugins", x))}</script>\n`; + }); + return html.replaceAll("<!-- preload plugin marker -->", plugins); +} + +function stripHeaders(headers: Headers): Headers { + [ + "content-length", + "content-security-policy", + "strict-transport-security", + "set-cookie", + "transfer-encoding", + "expect-ct", + "access-control-allow-origin", + "content-encoding" + ].forEach((headerName) => { + headers.delete(headerName); + }); + return headers; +} diff --git a/api/src/middlewares/Translation.ts b/src/api/middlewares/Translation.ts
index baabf221..8e5e67e6 100644 --- a/api/src/middlewares/Translation.ts +++ b/src/api/middlewares/Translation.ts
@@ -1,13 +1,13 @@ +import { Router } from "express"; import fs from "fs"; -import path from "path"; import i18next from "i18next"; import i18nextMiddleware from "i18next-http-middleware"; import i18nextBackend from "i18next-node-fs-backend"; -import { Router } from "express"; +import path from "path"; export async function initTranslation(router: Router) { - const languages = fs.readdirSync(path.join(__dirname, "..", "..", "locales")); - const namespaces = fs.readdirSync(path.join(__dirname, "..", "..", "locales", "en")); + const languages = fs.readdirSync(path.join(__dirname, "..", "..", "..", "assets", "locales")); + const namespaces = fs.readdirSync(path.join(__dirname, "..", "..", "..", "assets", "locales", "en")); const ns = namespaces.filter((x) => x.endsWith(".json")).map((x) => x.slice(0, x.length - 5)); await i18next @@ -19,7 +19,7 @@ export async function initTranslation(router: Router) { fallbackLng: "en", ns, backend: { - loadPath: __dirname + "/../../locales/{{lng}}/{{ns}}.json" + loadPath: __dirname + "/../../../assets/locales/{{lng}}/{{ns}}.json" }, load: "all" }); diff --git a/api/src/middlewares/index.ts b/src/api/middlewares/index.ts
index f0c50dbe..f0c50dbe 100644 --- a/api/src/middlewares/index.ts +++ b/src/api/middlewares/index.ts
diff --git a/api/src/routes/-/healthz.ts b/src/api/routes/-/healthz.ts
index f7bcfebf..5dee9e86 100644 --- a/api/src/routes/-/healthz.ts +++ b/src/api/routes/-/healthz.ts
@@ -1,5 +1,5 @@ -import { Router, Response, Request } from "express"; import { route } from "@fosscord/api"; +import { Request, Response, Router } from "express"; import { getConnection } from "typeorm"; const router = Router(); diff --git a/api/src/routes/-/readyz.ts b/src/api/routes/-/readyz.ts
index f7bcfebf..5dee9e86 100644 --- a/api/src/routes/-/readyz.ts +++ b/src/api/routes/-/readyz.ts
@@ -1,5 +1,5 @@ -import { Router, Response, Request } from "express"; import { route } from "@fosscord/api"; +import { Request, Response, Router } from "express"; import { getConnection } from "typeorm"; const router = Router(); diff --git a/src/api/routes/applications/#id/bot/index.ts b/src/api/routes/applications/#id/bot/index.ts new file mode 100644
index 00000000..e663059e --- /dev/null +++ b/src/api/routes/applications/#id/bot/index.ts
@@ -0,0 +1,83 @@ +import { route } from "@fosscord/api"; +import { Application, Config, FieldErrors, generateToken, handleFile, OrmUtils, trimSpecial, User } from "@fosscord/util"; +import { Request, Response, Router } from "express"; +import { HTTPError } from "lambert-server"; +import { verifyToken } from "node-2fa"; + +const router: Router = Router(); + +router.post("/", route({}), async (req: Request, res: Response) => { + const app = await Application.findOne({ where: { id: req.params.id } }); + if (!app) return res.status(404); + const username = trimSpecial(app.name); + const discriminator = await User.generateDiscriminator(username); + if (!discriminator) { + // We've failed to generate a valid and unused discriminator + throw FieldErrors({ + username: { + code: "USERNAME_TOO_MANY_USERS", + message: req?.t("auth:register.USERNAME_TOO_MANY_USERS") + } + }); + } + + const user = OrmUtils.mergeDeep(new User(), { + created_at: new Date(), + username: username, + discriminator, + id: app.id, + bot: true, + system: false, + premium_since: null, + desktop: false, + mobile: false, + premium: false, + premium_type: 0, + bio: app.description, + mfa_enabled: true, + totp_secret: "", + totp_backup_codes: [], + verified: true, + disabled: false, + deleted: false, + email: null, + rights: Config.get().register.defaultRights, + nsfw_allowed: true, + public_flags: "0", + flags: "0", + data: { + hash: null, + valid_tokens_since: new Date() + }, + settings: {}, + extended_settings: {}, + fingerprints: [], + notes: {} + }); + await user.save(); + app.bot = user; + await app.save(); + res.send().status(204); +}); + +router.post("/reset", route({}), async (req: Request, res: Response) => { + let bot = await User.findOne({ where: { id: req.params.id } }); + let owner = await User.findOne({ where: { id: req.user_id } }); + if (!bot) return res.status(404); + if (owner?.totp_secret && (!req.body.code || verifyToken(owner.totp_secret, req.body.code))) { + throw new HTTPError(req.t("auth:login.INVALID_TOTP_CODE"), 60008); + } + bot.data = { hash: undefined, valid_tokens_since: new Date() }; + await bot.save(); + let token = await generateToken(bot.id); + res.json({ token }).status(200); +}); + +router.patch("/", route({}), async (req: Request, res: Response) => { + if (req.body.avatar) req.body.avatar = await handleFile(`/avatars/${req.params.id}`, req.body.avatar as string); + let app = OrmUtils.mergeDeep(await User.findOne({ where: { id: req.params.id } }), req.body); + await app.save(); + res.json(app).status(200); +}); + +export default router; diff --git a/api/src/routes/applications/#id/entitlements.ts b/src/api/routes/applications/#id/entitlements.ts
index cfcfe40f..26054eb0 100644 --- a/api/src/routes/applications/#id/entitlements.ts +++ b/src/api/routes/applications/#id/entitlements.ts
@@ -1,5 +1,5 @@ -import { Router, Response, Request } from "express"; import { route } from "@fosscord/api"; +import { Request, Response, Router } from "express"; const router = Router(); diff --git a/src/api/routes/applications/#id/index.ts b/src/api/routes/applications/#id/index.ts new file mode 100644
index 00000000..398227fd --- /dev/null +++ b/src/api/routes/applications/#id/index.ts
@@ -0,0 +1,29 @@ +import { route } from "@fosscord/api"; +import { Application, OrmUtils } from "@fosscord/util"; +import { Request, Response, Router } from "express"; + +const router: Router = Router(); + +router.get("/", route({}), async (req: Request, res: Response) => { + let results = await Application.findOne({ where: { id: req.params.id }, relations: ["owner", "bot"] }); + res.json(results).status(200); +}); + +router.patch("/", route({}), async (req: Request, res: Response) => { + delete req.body.icon; + let app = OrmUtils.mergeDeep(await Application.findOne({ where: { id: req.params.id }, relations: ["owner", "bot"] }), req.body); + if (app.bot) { + app.bot.bio = req.body.description; + app.bot?.save(); + } + if (req.body.tags) app.tags = req.body.tags; + await app.save(); + res.json(app).status(200); +}); + +router.post("/delete", route({}), async (req: Request, res: Response) => { + await Application.delete(req.params.id); + res.send().status(200); +}); + +export default router; diff --git a/api/src/routes/applications/index.ts b/src/api/routes/applications/#id/skus.ts
index 28ce42da..df7ad4bb 100644 --- a/api/src/routes/applications/index.ts +++ b/src/api/routes/applications/#id/skus.ts
@@ -1,11 +1,10 @@ -import { Request, Response, Router } from "express"; import { route } from "@fosscord/api"; +import { Request, Response, Router } from "express"; const router: Router = Router(); router.get("/", route({}), async (req: Request, res: Response) => { - //TODO - res.send([]).status(200); + res.json([]).status(200); }); export default router; diff --git a/api/src/routes/applications/detectable.ts b/src/api/routes/applications/detectable.ts
index 28ce42da..f012a595 100644 --- a/api/src/routes/applications/detectable.ts +++ b/src/api/routes/applications/detectable.ts
@@ -1,5 +1,5 @@ -import { Request, Response, Router } from "express"; import { route } from "@fosscord/api"; +import { Request, Response, Router } from "express"; const router: Router = Router(); diff --git a/src/api/routes/applications/index.ts b/src/api/routes/applications/index.ts new file mode 100644
index 00000000..191833f2 --- /dev/null +++ b/src/api/routes/applications/index.ts
@@ -0,0 +1,34 @@ +import { route } from "@fosscord/api"; +import { Application, OrmUtils, trimSpecial, User } from "@fosscord/util"; +import { Request, Response, Router } from "express"; + +const router: Router = Router(); + +export interface ApplicationCreateSchema { + name: string; + team_id?: string | number; +} + +router.get("/", route({}), async (req: Request, res: Response) => { + //TODO + let results = await Application.find({ where: { owner: { id: req.user_id } }, relations: ["owner", "bot"] }); + res.json(results).status(200); +}); + +router.post("/", route({}), async (req: Request, res: Response) => { + const body = req.body as ApplicationCreateSchema; + const user = await User.findOne({ where: { id: req.user_id } }); + if (!user) res.status(420); + let app = OrmUtils.mergeDeep(new Application(), { + name: trimSpecial(body.name), + description: "", + bot_public: true, + owner: user, + verify_key: "IMPLEMENTME", + flags: 0 + }); + await app.save(); + res.json(app).status(200); +}); + +export default router; diff --git a/src/api/routes/auth/location-metadata.ts b/src/api/routes/auth/location-metadata.ts new file mode 100644
index 00000000..b8caf579 --- /dev/null +++ b/src/api/routes/auth/location-metadata.ts
@@ -0,0 +1,12 @@ +import { getIpAdress, IPAnalysis, route } from "@fosscord/api"; +import { Request, Response, Router } from "express"; +const router = Router(); + +router.get("/", route({}), async (req: Request, res: Response) => { + //TODO + //Note: It's most likely related to legal. At the moment Discord hasn't finished this too + const country_code = (await IPAnalysis(getIpAdress(req))).country_code; + res.json({ consent_required: false, country_code: country_code, promotional_email_opt_in: { required: true, pre_checked: false } }); +}); + +export default router; diff --git a/api/src/routes/auth/login.ts b/src/api/routes/auth/login.ts
index a89721ea..68b2656a 100644 --- a/api/src/routes/auth/login.ts +++ b/src/api/routes/auth/login.ts
@@ -1,30 +1,29 @@ import { Request, Response, Router } from "express"; -import { route } from "@fosscord/api"; -import bcrypt from "bcrypt"; -import { Config, User, generateToken, adjustEmail, FieldErrors } from "@fosscord/util"; +import { route, getIpAdress, verifyCaptcha } from "@fosscord/api"; +import { Config, User, generateToken, adjustEmail, FieldErrors, LoginSchema } from "@fosscord/util"; +import crypto from "crypto"; + +let bcrypt: any; +try { + bcrypt = require("bcrypt"); +} catch { + bcrypt = require("bcryptjs"); + console.log("Warning: using bcryptjs because bcrypt is not installed! Performance will be affected."); +} const router: Router = Router(); export default router; -export interface LoginSchema { - login: string; - password: string; - undelete?: boolean; - captcha_key?: string; - login_source?: string; - gift_code_sku_id?: string; -} - router.post("/", route({ body: "LoginSchema" }), async (req: Request, res: Response) => { const { login, password, captcha_key, undelete } = req.body as LoginSchema; const email = adjustEmail(login); - console.log("login", email); + const ip = getIpAdress(req); const config = Config.get(); if (config.login.requireCaptcha && config.security.captcha.enabled) { + const { sitekey, service } = config.security.captcha; if (!captcha_key) { - const { sitekey, service } = config.security.captcha; return res.status(400).json({ captcha_key: ["captcha-required"], captcha_sitekey: sitekey, @@ -32,12 +31,20 @@ router.post("/", route({ body: "LoginSchema" }), async (req: Request, res: Respo }); } - // TODO: check captcha + const ip = getIpAdress(req); + const verify = await verifyCaptcha(captcha_key, ip); + if (!verify.success) { + return res.status(400).json({ + captcha_key: verify["error-codes"], + captcha_sitekey: sitekey, + captcha_service: service + }) + } } const user = await User.findOneOrFail({ where: [{ phone: login }, { email: login }], - select: ["data", "id", "disabled", "deleted", "settings"] + select: ["data", "id", "disabled", "deleted", "settings", "totp_secret", "mfa_enabled"] }).catch((e) => { throw FieldErrors({ login: { message: req.t("auth:login.INVALID_LOGIN"), code: "INVALID_LOGIN" } }); }); @@ -57,6 +64,20 @@ router.post("/", route({ body: "LoginSchema" }), async (req: Request, res: Respo throw FieldErrors({ password: { message: req.t("auth:login.INVALID_PASSWORD"), code: "INVALID_PASSWORD" } }); } + if (user.mfa_enabled) { + // TODO: This is not a discord.com ticket. I'm not sure what it is but I'm lazy + const ticket = crypto.randomBytes(40).toString("hex"); + + await User.update({ id: user.id }, { totp_last_ticket: ticket }); + + return res.json({ + ticket: ticket, + mfa: true, + sms: false, // TODO + token: null + }); + } + const token = await generateToken(user.id); // Notice this will have a different token structure, than discord diff --git a/src/api/routes/auth/mfa/totp.ts b/src/api/routes/auth/mfa/totp.ts new file mode 100644
index 00000000..9938569e --- /dev/null +++ b/src/api/routes/auth/mfa/totp.ts
@@ -0,0 +1,36 @@ +import { route } from "@fosscord/api"; +import { BackupCode, generateToken, TotpSchema, User } from "@fosscord/util"; +import { Request, Response, Router } from "express"; +import { HTTPError } from "lambert-server"; +import { verifyToken } from "node-2fa"; +const router = Router(); + +router.post("/", route({ body: "TotpSchema" }), async (req: Request, res: Response) => { + const { code, ticket, gift_code_sku_id, login_source } = req.body as TotpSchema; + + const user = await User.findOneOrFail({ + where: { + totp_last_ticket: ticket + }, + select: ["id", "totp_secret", "settings"] + }); + + const backup = await BackupCode.findOne({ where: { code: code, expired: false, consumed: false, user: { id: user.id } } }); + + if (!backup) { + const ret = verifyToken(user.totp_secret!, code); + if (!ret || ret.delta != 0) throw new HTTPError(req.t("auth:login.INVALID_TOTP_CODE"), 60008); + } else { + backup.consumed = true; + await backup.save(); + } + + await User.update({ id: user.id }, { totp_last_ticket: "" }); + + return res.json({ + token: await generateToken(user.id), + user_settings: user.settings + }); +}); + +export default router; diff --git a/api/src/routes/auth/register.ts b/src/api/routes/auth/register.ts
index 94dd6502..5cc28f7a 100644 --- a/api/src/routes/auth/register.ts +++ b/src/api/routes/auth/register.ts
@@ -1,38 +1,17 @@ +import { getIpAdress, IPAnalysis, isProxy, route, verifyCaptcha } from "@fosscord/api"; +import { adjustEmail, Config, FieldErrors, generateToken, HTTPError, Invite, RegisterSchema, User } from "@fosscord/util"; import { Request, Response, Router } from "express"; -import { Config, generateToken, Invite, FieldErrors, User, adjustEmail, trimSpecial } from "@fosscord/util"; -import { route, getIpAdress, IPAnalysis, isProxy } from "@fosscord/api"; -import "missing-native-js-functions"; -import bcrypt from "bcrypt"; -import { HTTPError } from "lambert-server"; -const router: Router = Router(); - -export interface RegisterSchema { - /** - * @minLength 2 - * @maxLength 32 - */ - username: string; - /** - * @minLength 1 - * @maxLength 72 - */ - password?: string; - consent: boolean; - /** - * @TJS-format email - */ - email?: string; - fingerprint?: string; - invite?: string; - /** - * @TJS-type string - */ - date_of_birth?: Date; // "2000-04-03" - gift_code_sku_id?: string; - captcha_key?: string; +let bcrypt: any; +try { + bcrypt = require("bcrypt"); +} catch { + bcrypt = require("bcryptjs"); + console.log("Warning: using bcryptjs because bcrypt is not installed! Performance will be affected."); } +const router: Router = Router(); + router.post("/", route({ body: "RegisterSchema" }), async (req: Request, res: Response) => { const body = req.body as RegisterSchema; const { register, security } = Config.get(); @@ -64,9 +43,15 @@ router.post("/", route({ body: "RegisterSchema" }), async (req: Request, res: Re }); } + if (!register.allowGuests) { + throw FieldErrors({ + email: { code: "GUESTS_DISABLED", message: req.t("auth:register.GUESTS_DISABLED") } + }); + } + if (register.requireCaptcha && security.captcha.enabled) { + const { sitekey, service } = security.captcha; if (!body.captcha_key) { - const { sitekey, service } = security.captcha; return res?.status(400).json({ captcha_key: ["captcha-required"], captcha_sitekey: sitekey, @@ -74,7 +59,14 @@ router.post("/", route({ body: "RegisterSchema" }), async (req: Request, res: Re }); } - // TODO: check captcha + const verify = await verifyCaptcha(body.captcha_key, ip); + if (!verify.success) { + return res.status(400).json({ + captcha_key: verify["error-codes"], + captcha_sitekey: sitekey, + captcha_service: service + }); + } } if (!register.allowMultipleAccounts) { @@ -108,7 +100,7 @@ router.post("/", route({ body: "RegisterSchema" }), async (req: Request, res: Re } // check if there is already an account with this email - const exists = await User.findOne({ email: email }); + const exists = await User.findOne({ where: { email: email } }); if (exists) { throw FieldErrors({ @@ -124,7 +116,8 @@ router.post("/", route({ body: "RegisterSchema" }), async (req: Request, res: Re }); } - if (register.dateOfBirth.required && !body.date_of_birth) { + // If no password is provided, this is a guest account + if (register.dateOfBirth.required && !body.date_of_birth && body.password) { throw FieldErrors({ date_of_birth: { code: "BASE_TYPE_REQUIRED", message: req.t("common:field.BASE_TYPE_REQUIRED") } }); @@ -167,8 +160,6 @@ router.post("/", route({ body: "RegisterSchema" }), async (req: Request, res: Re await Invite.joinGuild(user.id, body.invite); } - console.log("register", body.email, body.username, ip); - return res.json({ token: await generateToken(user.id) }); }); diff --git a/api/src/routes/channels/#channel_id/followers.ts b/src/api/routes/channels/#channel_id/followers.ts
index 641af4f8..c06db61b 100644 --- a/api/src/routes/channels/#channel_id/followers.ts +++ b/src/api/routes/channels/#channel_id/followers.ts
@@ -1,4 +1,4 @@ -import { Router, Response, Request } from "express"; +import { Router } from "express"; const router: Router = Router(); // TODO: diff --git a/api/src/routes/channels/#channel_id/index.ts b/src/api/routes/channels/#channel_id/index.ts
index 2fca4fdf..a65cf451 100644 --- a/api/src/routes/channels/#channel_id/index.ts +++ b/src/api/routes/channels/#channel_id/index.ts
@@ -1,15 +1,16 @@ +import { route } from "@fosscord/api"; import { Channel, ChannelDeleteEvent, - ChannelPermissionOverwriteType, + ChannelModifySchema, ChannelType, ChannelUpdateEvent, emitEvent, - Recipient, - handleFile + handleFile, + OrmUtils, + Recipient } from "@fosscord/util"; import { Request, Response, Router } from "express"; -import { route } from "@fosscord/api"; const router: Router = Router(); // TODO: delete channel @@ -18,7 +19,7 @@ const router: Router = Router(); router.get("/", route({ permission: "VIEW_CHANNEL" }), async (req: Request, res: Response) => { const { channel_id } = req.params; - const channel = await Channel.findOneOrFail({ id: channel_id }); + const channel = await Channel.findOneOrFail({ where: { id: channel_id } }); return res.send(channel); }); @@ -29,7 +30,7 @@ router.delete("/", route({ permission: "MANAGE_CHANNELS" }), async (req: Request const channel = await Channel.findOneOrFail({ where: { id: channel_id }, relations: ["recipients"] }); if (channel.type === ChannelType.DM) { - const recipient = await Recipient.findOneOrFail({ where: { channel_id: channel_id, user_id: req.user_id } }); + const recipient = await Recipient.findOneOrFail({ where: { channel_id, user_id: req.user_id } }); recipient.closed = true; await Promise.all([ recipient.save(), @@ -47,38 +48,13 @@ router.delete("/", route({ permission: "MANAGE_CHANNELS" }), async (req: Request res.send(channel); }); -export interface ChannelModifySchema { - /** - * @maxLength 100 - */ - name?: string; - type?: ChannelType; - topic?: string; - icon?: string | null; - bitrate?: number; - user_limit?: number; - rate_limit_per_user?: number; - position?: number; - permission_overwrites?: { - id: string; - type: ChannelPermissionOverwriteType; - allow: string; - deny: string; - }[]; - parent_id?: string; - id?: string; // is not used (only for guild create) - nsfw?: boolean; - rtc_region?: string; - default_auto_archive_duration?: number; -} - router.patch("/", route({ body: "ChannelModifySchema", permission: "MANAGE_CHANNELS" }), async (req: Request, res: Response) => { - var payload = req.body as ChannelModifySchema; + let payload = req.body as ChannelModifySchema; const { channel_id } = req.params; if (payload.icon) payload.icon = await handleFile(`/channel-icons/${channel_id}`, payload.icon); - const channel = await Channel.findOneOrFail({ id: channel_id }); - channel.assign(payload); + let channel = await Channel.findOneOrFail({ where: { id: channel_id } }); + channel = OrmUtils.mergeDeep(channel, payload); await Promise.all([ channel.save(), diff --git a/src/api/routes/channels/#channel_id/invites.ts b/src/api/routes/channels/#channel_id/invites.ts new file mode 100644
index 00000000..3a1d2666 --- /dev/null +++ b/src/api/routes/channels/#channel_id/invites.ts
@@ -0,0 +1,58 @@ +import { route } from "@fosscord/api"; +import { Channel, emitEvent, Guild, HTTPError, Invite, InviteCreateEvent, OrmUtils, PublicInviteRelation, User } from "@fosscord/util"; +import { Request, Response, Router } from "express"; +import { isTextChannel } from "./messages"; + +const router: Router = Router(); + +router.post( + "/", + route({ body: "InviteCreateSchema", permission: "CREATE_INSTANT_INVITE", right: "CREATE_INVITES" }), + async (req: Request, res: Response) => { + const { user_id } = req; + const { channel_id } = req.params; + const channel = await Channel.findOneOrFail({ where: { id: channel_id }, select: ["id", "name", "type", "guild_id"] }); + isTextChannel(channel.type); + + if (!channel.guild_id) { + throw new HTTPError("This channel doesn't exist", 404); + } + const { guild_id } = channel; + + const expires_at = new Date(req.body.max_age * 1000 + Date.now()); + + const invite = await OrmUtils.mergeDeep(new Invite(), { + temporary: req.body.temporary || true, + max_uses: req.body.max_uses, + max_age: req.body.max_age, + expires_at, + guild_id, + channel_id, + inviter_id: user_id + }).save(); + //TODO: check this, removed toJSON call + const data = JSON.parse(JSON.stringify(invite)); + data.inviter = await User.getPublicUser(req.user_id); + data.guild = await Guild.findOne({ where: { id: guild_id } }); + data.channel = channel; + + await emitEvent({ event: "INVITE_CREATE", data, guild_id } as InviteCreateEvent); + res.status(201).send(data); + } +); + +router.get("/", route({ permission: "MANAGE_CHANNELS" }), async (req: Request, res: Response) => { + const { channel_id } = req.params; + const channel = await Channel.findOneOrFail({ where: { id: channel_id } }); + + if (!channel.guild_id) { + throw new HTTPError("This channel doesn't exist", 404); + } + const { guild_id } = channel; + + const invites = await Invite.find({ where: { guild_id }, relations: PublicInviteRelation }); + + res.status(200).send(invites); +}); + +export default router; diff --git a/api/src/routes/channels/#channel_id/messages/#message_id/ack.ts b/src/api/routes/channels/#channel_id/messages/#message_id/ack.ts
index 885c5eca..5ebeed49 100644 --- a/api/src/routes/channels/#channel_id/messages/#message_id/ack.ts +++ b/src/api/routes/channels/#channel_id/messages/#message_id/ack.ts
@@ -1,26 +1,17 @@ -import { emitEvent, getPermission, MessageAckEvent, ReadState, Snowflake } from "@fosscord/util"; -import { Request, Response, Router } from "express"; import { route } from "@fosscord/api"; +import { emitEvent, getPermission, MessageAckEvent, OrmUtils, ReadState } from "@fosscord/util"; +import { Request, Response, Router } from "express"; const router = Router(); -// TODO: public read receipts & privacy scoping -// TODO: send read state event to all channel members -// TODO: advance-only notification cursor - -export interface MessageAcknowledgeSchema { - manual?: boolean; - mention_count?: number; -} - router.post("/", route({ body: "MessageAcknowledgeSchema" }), async (req: Request, res: Response) => { const { channel_id, message_id } = req.params; const permission = await getPermission(req.user_id, undefined, channel_id); permission.hasThrow("VIEW_CHANNEL"); - let read_state = await ReadState.findOne({ user_id: req.user_id, channel_id }); - if (!read_state) read_state = new ReadState({ user_id: req.user_id, channel_id }); + let read_state = await ReadState.findOne({ where: { user_id: req.user_id, channel_id } }); + if (!read_state) read_state = OrmUtils.mergeDeep(new ReadState(), { user_id: req.user_id, channel_id }) as ReadState; read_state.last_message_id = message_id; await read_state.save(); diff --git a/api/src/routes/channels/#channel_id/messages/#message_id/crosspost.ts b/src/api/routes/channels/#channel_id/messages/#message_id/crosspost.ts
index b2cb6763..fbbc65f0 100644 --- a/api/src/routes/channels/#channel_id/messages/#message_id/crosspost.ts +++ b/src/api/routes/channels/#channel_id/messages/#message_id/crosspost.ts
@@ -1,5 +1,5 @@ -import { Router, Response, Request } from "express"; import { route } from "@fosscord/api"; +import { Request, Response, Router } from "express"; const router = Router(); diff --git a/api/src/routes/channels/#channel_id/messages/#message_id/index.ts b/src/api/routes/channels/#channel_id/messages/#message_id/index.ts
index 63fee9b9..b082e083 100644 --- a/api/src/routes/channels/#channel_id/messages/#message_id/index.ts +++ b/src/api/routes/channels/#channel_id/messages/#message_id/index.ts
@@ -1,25 +1,22 @@ +import { handleMessage, postHandleMessage, route } from "@fosscord/api"; import { Attachment, Channel, - Embed, - DiscordApiErrors, emitEvent, FosscordApiErrors, getPermission, getRights, - Message, + HTTPError, + Message, MessageCreateEvent, + MessageCreateSchema, MessageDeleteEvent, MessageUpdateEvent, Snowflake, - uploadFile + uploadFile } from "@fosscord/util"; -import { Router, Response, Request } from "express"; +import { Request, Response, Router } from "express"; import multer from "multer"; -import { route } from "@fosscord/api"; -import { handleMessage, postHandleMessage } from "@fosscord/api"; -import { MessageCreateSchema } from "../index"; -import { HTTPError } from "lambert-server"; const router = Router(); // TODO: message content/embed string length limit @@ -33,50 +30,53 @@ const messageUpload = multer({ storage: multer.memoryStorage() }); // max upload 50 mb -router.patch("/", route({ body: "MessageCreateSchema", permission: "SEND_MESSAGES", right: "SEND_MESSAGES" }), async (req: Request, res: Response) => { - const { message_id, channel_id } = req.params; - var body = req.body as MessageCreateSchema; +router.patch( + "/", + route({ body: "MessageCreateSchema", permission: "SEND_MESSAGES", right: "SEND_MESSAGES" }), + async (req: Request, res: Response) => { + const { message_id, channel_id } = req.params; + let body = req.body as MessageCreateSchema; - const message = await Message.findOneOrFail({ where: { id: message_id, channel_id }, relations: ["attachments"] }); + const message = await Message.findOneOrFail({ where: { id: message_id, channel_id }, relations: ["attachments"] }); - const permissions = await getPermission(req.user_id, undefined, channel_id); - - const rights = await getRights(req.user_id); + const permissions = await getPermission(req.user_id, undefined, channel_id); - if ((req.user_id !== message.author_id)) { - if (!rights.has("MANAGE_MESSAGES")) { - permissions.hasThrow("MANAGE_MESSAGES"); - body = { flags: body.flags }; -// guild admins can only suppress embeds of other messages, no such restriction imposed to instance-wide admins - } - } else rights.hasThrow("SELF_EDIT_MESSAGES"); - - const new_message = await handleMessage({ - ...message, - // TODO: should message_reference be overridable? - // @ts-ignore - message_reference: message.message_reference, - ...body, - author_id: message.author_id, - channel_id, - id: message_id, - edited_timestamp: new Date() - }); - - await Promise.all([ - new_message!.save(), - await emitEvent({ - event: "MESSAGE_UPDATE", + const rights = await getRights(req.user_id); + + if (req.user_id !== message.author_id) { + if (!rights.has("MANAGE_MESSAGES")) { + permissions.hasThrow("MANAGE_MESSAGES"); + body = { flags: body.flags }; + // guild admins can only suppress embeds of other messages, no such restriction imposed to instance-wide admins + } + } else rights.hasThrow("SELF_EDIT_MESSAGES"); + + const new_message = await handleMessage({ + ...message, + // TODO: should message_reference be overridable? + // @ts-ignore + message_reference: message.message_reference, + ...body, + author_id: message.author_id, channel_id, - data: { ...new_message, nonce: undefined } - } as MessageUpdateEvent) - ]); + id: message_id, + edited_timestamp: new Date() + }); - postHandleMessage(message); + await Promise.all([ + new_message!.save(), + await emitEvent({ + event: "MESSAGE_UPDATE", + channel_id, + data: { ...new_message, nonce: undefined } + } as MessageUpdateEvent) + ]); - return res.json(message); -}); + postHandleMessage(message); + return res.json(message); + } +); // Backfill message with specific timestamp router.put( @@ -92,9 +92,9 @@ router.put( route({ body: "MessageCreateSchema", permission: "SEND_MESSAGES", right: "SEND_BACKDATED_EVENTS" }), async (req: Request, res: Response) => { const { channel_id, message_id } = req.params; - var body = req.body as MessageCreateSchema; + let body = req.body as MessageCreateSchema; const attachments: Attachment[] = []; - + const rights = await getRights(req.user_id); rights.hasThrow("SEND_MESSAGES"); @@ -103,20 +103,20 @@ router.put( throw new HTTPError("Message IDs must be positive integers", 400); } - const snowflake = Snowflake.deconstruct(message_id) + const snowflake = Snowflake.deconstruct(message_id); if (Date.now() < snowflake.timestamp) { // message is in the future throw FosscordApiErrors.CANNOT_BACKFILL_TO_THE_FUTURE; } - const exists = await Message.findOne({ where: { id: message_id, channel_id: channel_id }}); + const exists = await Message.findOne({ where: { id: message_id, channel_id: channel_id } }); if (exists) { throw FosscordApiErrors.CANNOT_REPLACE_BY_BACKFILL; } if (req.file) { try { - const file = await uploadFile(`/attachments/${req.params.channel_id}`, req.file); + const file: any = await uploadFile(`/attachments/${req.params.channel_id}`, req.file); attachments.push({ ...file, proxy_url: file.url }); } catch (error) { return res.status(400).json(error); @@ -136,19 +136,19 @@ router.put( channel_id, attachments, edited_timestamp: undefined, - timestamp: new Date(snowflake.timestamp), + timestamp: new Date(snowflake.timestamp) }); //Fix for the client bug - delete message.member - + delete message.member; + await Promise.all([ message.save(), emitEvent({ event: "MESSAGE_CREATE", channel_id: channel_id, data: message } as MessageCreateEvent), channel.save() ]); - postHandleMessage(message).catch((e) => { }); // no await as it shouldnt block the message send function and silently catch error + postHandleMessage(message).catch((e) => {}); // no await as it shouldnt block the message send function and silently catch error return res.json(message); } @@ -160,7 +160,7 @@ router.get("/", route({ permission: "VIEW_CHANNEL" }), async (req: Request, res: const message = await Message.findOneOrFail({ where: { id: message_id, channel_id }, relations: ["attachments"] }); const permissions = await getPermission(req.user_id, undefined, channel_id); - + if (message.author_id !== req.user_id) permissions.hasThrow("READ_MESSAGE_HISTORY"); return res.json(message); @@ -169,12 +169,12 @@ router.get("/", route({ permission: "VIEW_CHANNEL" }), async (req: Request, res: router.delete("/", route({}), async (req: Request, res: Response) => { const { message_id, channel_id } = req.params; - const channel = await Channel.findOneOrFail({ id: channel_id }); - const message = await Message.findOneOrFail({ id: message_id }); - + const channel = await Channel.findOneOrFail({ where: { id: channel_id } }); + const message = await Message.findOneOrFail({ where: { id: message_id } }); + const rights = await getRights(req.user_id); - if ((message.author_id !== req.user_id)) { + if (message.author_id !== req.user_id) { if (!rights.has("MANAGE_MESSAGES")) { const permission = await getPermission(req.user_id, channel.guild_id, channel_id); permission.hasThrow("MANAGE_MESSAGES"); diff --git a/api/src/routes/channels/#channel_id/messages/#message_id/reactions.ts b/src/api/routes/channels/#channel_id/messages/#message_id/reactions.ts
index d93cf70f..44de5c45 100644 --- a/api/src/routes/channels/#channel_id/messages/#message_id/reactions.ts +++ b/src/api/routes/channels/#channel_id/messages/#message_id/reactions.ts
@@ -1,8 +1,10 @@ +import { route } from "@fosscord/api"; import { Channel, emitEvent, Emoji, getPermission, + HTTPError, Member, Message, MessageReactionAddEvent, @@ -13,9 +15,7 @@ import { PublicUserProjection, User } from "@fosscord/util"; -import { route } from "@fosscord/api"; -import { Router, Response, Request } from "express"; -import { HTTPError } from "lambert-server"; +import { Request, Response, Router } from "express"; import { In } from "typeorm"; const router = Router(); @@ -39,7 +39,7 @@ function getEmoji(emoji: string): PartialEmoji { router.delete("/", route({ permission: "MANAGE_MESSAGES" }), async (req: Request, res: Response) => { const { message_id, channel_id } = req.params; - const channel = await Channel.findOneOrFail({ id: channel_id }); + const channel = await Channel.findOneOrFail({ where: { id: channel_id } }); await Message.update({ id: message_id, channel_id }, { reactions: [] }); @@ -60,7 +60,7 @@ router.delete("/:emoji", route({ permission: "MANAGE_MESSAGES" }), async (req: R const { message_id, channel_id } = req.params; const emoji = getEmoji(req.params.emoji); - const message = await Message.findOneOrFail({ id: message_id, channel_id }); + const message = await Message.findOneOrFail({ where: { id: message_id, channel_id } }); const already_added = message.reactions.find((x) => (x.emoji.id === emoji.id && emoji.id) || x.emoji.name === emoji.name); if (!already_added) throw new HTTPError("Reaction not found", 404); @@ -87,7 +87,7 @@ router.get("/:emoji", route({ permission: "VIEW_CHANNEL" }), async (req: Request const { message_id, channel_id } = req.params; const emoji = getEmoji(req.params.emoji); - const message = await Message.findOneOrFail({ id: message_id, channel_id }); + const message = await Message.findOneOrFail({ where: { id: message_id, channel_id } }); const reaction = message.reactions.find((x) => (x.emoji.id === emoji.id && emoji.id) || x.emoji.name === emoji.name); if (!reaction) throw new HTTPError("Reaction not found", 404); @@ -101,56 +101,60 @@ router.get("/:emoji", route({ permission: "VIEW_CHANNEL" }), async (req: Request res.json(users); }); -router.put("/:emoji/:user_id", route({ permission: "READ_MESSAGE_HISTORY", right: "SELF_ADD_REACTIONS" }), async (req: Request, res: Response) => { - const { message_id, channel_id, user_id } = req.params; - if (user_id !== "@me") throw new HTTPError("Invalid user"); - const emoji = getEmoji(req.params.emoji); - - const channel = await Channel.findOneOrFail({ id: channel_id }); - const message = await Message.findOneOrFail({ id: message_id, channel_id }); - const already_added = message.reactions.find((x) => (x.emoji.id === emoji.id && emoji.id) || x.emoji.name === emoji.name); - - if (!already_added) req.permission!.hasThrow("ADD_REACTIONS"); - - if (emoji.id) { - const external_emoji = await Emoji.findOneOrFail({ id: emoji.id }); - if (!already_added) req.permission!.hasThrow("USE_EXTERNAL_EMOJIS"); - emoji.animated = external_emoji.animated; - emoji.name = external_emoji.name; - } +router.put( + "/:emoji/:user_id", + route({ permission: "READ_MESSAGE_HISTORY", right: "SELF_ADD_REACTIONS" }), + async (req: Request, res: Response) => { + const { message_id, channel_id, user_id } = req.params; + if (user_id !== "@me") throw new HTTPError("Invalid user"); + const emoji = getEmoji(req.params.emoji); + + const channel = await Channel.findOneOrFail({ where: { id: channel_id } }); + const message = await Message.findOneOrFail({ where: { id: message_id, channel_id } }); + const already_added = message.reactions.find((x) => (x.emoji.id === emoji.id && emoji.id) || x.emoji.name === emoji.name); + + if (!already_added) req.permission!.hasThrow("ADD_REACTIONS"); + + if (emoji.id) { + const external_emoji = await Emoji.findOneOrFail({ where: { id: emoji.id } }); + if (!already_added) req.permission!.hasThrow("USE_EXTERNAL_EMOJIS"); + emoji.animated = external_emoji.animated; + emoji.name = external_emoji.name; + } - if (already_added) { - if (already_added.user_ids.includes(req.user_id)) return res.sendStatus(204); // Do not throw an error ¯\_(ツ)_/¯ as discord also doesn't throw any error - already_added.count++; - } else message.reactions.push({ count: 1, emoji, user_ids: [req.user_id] }); + if (already_added) { + if (already_added.user_ids.includes(req.user_id)) return res.sendStatus(204); // Do not throw an error ¯\_(ツ)_/¯ as discord also doesn't throw any error + already_added.count++; + } else message.reactions.push({ count: 1, emoji, user_ids: [req.user_id] }); - await message.save(); + await message.save(); - const member = channel.guild_id && (await Member.findOneOrFail({ id: req.user_id })); + const member = channel.guild_id && (await Member.findOneOrFail({ where: { id: req.user_id } })); - await emitEvent({ - event: "MESSAGE_REACTION_ADD", - channel_id, - data: { - user_id: req.user_id, + await emitEvent({ + event: "MESSAGE_REACTION_ADD", channel_id, - message_id, - guild_id: channel.guild_id, - emoji, - member - } - } as MessageReactionAddEvent); + data: { + user_id: req.user_id, + channel_id, + message_id, + guild_id: channel.guild_id, + emoji, + member + } + } as MessageReactionAddEvent); - res.sendStatus(204); -}); + res.sendStatus(204); + } +); router.delete("/:emoji/:user_id", route({}), async (req: Request, res: Response) => { - var { message_id, channel_id, user_id } = req.params; + let { message_id, channel_id, user_id } = req.params; const emoji = getEmoji(req.params.emoji); - const channel = await Channel.findOneOrFail({ id: channel_id }); - const message = await Message.findOneOrFail({ id: message_id, channel_id }); + const channel = await Channel.findOneOrFail({ where: { id: channel_id } }); + const message = await Message.findOneOrFail({ where: { id: message_id, channel_id } }); if (user_id === "@me") user_id = req.user_id; else { diff --git a/api/src/routes/channels/#channel_id/messages/bulk-delete.ts b/src/api/routes/channels/#channel_id/messages/bulk-delete.ts
index 6eacf249..561a40c0 100644 --- a/api/src/routes/channels/#channel_id/messages/bulk-delete.ts +++ b/src/api/routes/channels/#channel_id/messages/bulk-delete.ts
@@ -1,31 +1,26 @@ -import { Router, Response, Request } from "express"; -import { Channel, Config, emitEvent, getPermission, getRights, MessageDeleteBulkEvent, Message } from "@fosscord/util"; -import { HTTPError } from "lambert-server"; import { route } from "@fosscord/api"; +import { Channel, Config, emitEvent, getPermission, getRights, HTTPError, Message, MessageDeleteBulkEvent } from "@fosscord/util"; +import { Request, Response, Router } from "express"; import { In } from "typeorm"; const router: Router = Router(); export default router; -export interface BulkDeleteSchema { - messages: string[]; -} - // should users be able to bulk delete messages or only bots? ANSWER: all users // should this request fail, if you provide messages older than 14 days/invalid ids? ANSWER: NO // https://discord.com/developers/docs/resources/channel#bulk-delete-messages router.post("/", route({ body: "BulkDeleteSchema" }), async (req: Request, res: Response) => { const { channel_id } = req.params; - const channel = await Channel.findOneOrFail({ id: channel_id }); + const channel = await Channel.findOneOrFail({ where: { id: channel_id } }); if (!channel.guild_id) throw new HTTPError("Can't bulk delete dm channel messages", 400); const rights = await getRights(req.user_id); rights.hasThrow("SELF_DELETE_MESSAGES"); - + let superuser = rights.has("MANAGE_MESSAGES"); const permission = await getPermission(req.user_id, channel?.guild_id, channel_id); - + const { maxBulkDelete } = Config.get().limits.message; const { messages } = req.body as { messages: string[] }; @@ -35,7 +30,7 @@ router.post("/", route({ body: "BulkDeleteSchema" }), async (req: Request, res: if (messages.length > maxBulkDelete) throw new HTTPError(`You cannot delete more than ${maxBulkDelete} messages`); } - await Message.delete(messages.map((x) => ({ id: x }))); + await Message.delete({ id: In(messages) }); await emitEvent({ event: "MESSAGE_DELETE_BULK", diff --git a/api/src/routes/channels/#channel_id/messages/index.ts b/src/api/routes/channels/#channel_id/messages/index.ts
index 54e6edcc..5fdcb6f9 100644 --- a/api/src/routes/channels/#channel_id/messages/index.ts +++ b/src/api/routes/channels/#channel_id/messages/index.ts
@@ -1,22 +1,21 @@ -import { Router, Response, Request } from "express"; +import { handleMessage, postHandleMessage, route } from "@fosscord/api"; import { Attachment, Channel, ChannelType, Config, DmChannelDTO, - Embed, emitEvent, getPermission, - getRights, + HTTPError, + Member, Message, MessageCreateEvent, + MessageCreateSchema, Snowflake, - uploadFile, - Member + uploadFile } from "@fosscord/util"; -import { HTTPError } from "lambert-server"; -import { handleMessage, postHandleMessage, route } from "@fosscord/api"; +import { Request, Response, Router } from "express"; import multer from "multer"; import { FindManyOptions, LessThan, MoreThan } from "typeorm"; import { URL } from "url"; @@ -49,43 +48,11 @@ export function isTextChannel(type: ChannelType): boolean { } } -export interface MessageCreateSchema { - type?: number; - content?: string; - nonce?: string; - channel_id?: string; - tts?: boolean; - flags?: string; - embeds?: Embed[]; - embed?: Embed; - // TODO: ^ embed is deprecated in favor of embeds (https://discord.com/developers/docs/resources/channel#message-object) - allowed_mentions?: { - parse?: string[]; - roles?: string[]; - users?: string[]; - replied_user?: boolean; - }; - message_reference?: { - message_id: string; - channel_id: string; - guild_id?: string; - fail_if_not_exists?: boolean; - }; - payload_json?: string; - file?: any; - /** - TODO: we should create an interface for attachments - TODO: OpenWAAO<-->attachment-style metadata conversion - **/ - attachments?: any[]; - sticker_ids?: string[]; -} - // https://discord.com/developers/docs/resources/channel#create-message // get messages router.get("/", async (req: Request, res: Response) => { const channel_id = req.params.channel_id; - const channel = await Channel.findOneOrFail({ id: channel_id }); + const channel = await Channel.findOneOrFail({ where: { id: channel_id } }); if (!channel) throw new HTTPError("Channel not found", 404); isTextChannel(channel.type); @@ -95,29 +62,26 @@ router.get("/", async (req: Request, res: Response) => { const limit = Number(req.query.limit) || 50; if (limit < 1 || limit > 100) throw new HTTPError("limit must be between 1 and 100", 422); - var halfLimit = Math.floor(limit / 2); + let halfLimit = Math.floor(limit / 2); const permissions = await getPermission(req.user_id, channel.guild_id, channel_id); permissions.hasThrow("VIEW_CHANNEL"); if (!permissions.has("READ_MESSAGE_HISTORY")) return res.json([]); - var query: FindManyOptions<Message> & { where: { id?: any; }; } = { + let query: FindManyOptions<Message> & { where: { id?: any } } = { order: { id: "DESC" }, take: limit, where: { channel_id }, relations: ["author", "webhook", "application", "mentions", "mention_roles", "mention_channels", "sticker_items", "attachments"] }; - if (after) { if (after > new Snowflake()) return res.status(422); query.where.id = MoreThan(after); - } - else if (before) { + } else if (before) { if (before < req.params.channel_id) return res.status(422); query.where.id = LessThan(before); - } - else if (around) { + } else if (around) { query.where.id = [ MoreThan((BigInt(around) - BigInt(halfLimit)).toString()), LessThan((BigInt(around) + BigInt(halfLimit)).toString()) @@ -142,15 +106,14 @@ router.get("/", async (req: Request, res: Response) => { const uri = y.proxy_url.startsWith("http") ? y.proxy_url : `https://example.org${y.proxy_url}`; y.proxy_url = `${endpoint == null ? "" : endpoint}${new URL(uri).pathname}`; }); - + /** Some clients ( discord.js ) only check if a property exists within the response, which causes erorrs when, say, the `application` property is `null`. **/ - - for (var curr in x) { - if (x[curr] === null) - delete x[curr]; + + for (let curr in x) { + if (x[curr] === null) delete x[curr]; } return x; @@ -162,7 +125,7 @@ router.get("/", async (req: Request, res: Response) => { const messageUpload = multer({ limits: { fileSize: 1024 * 1024 * 100, - fields: 10, + fields: 10 // files: 1 }, storage: multer.memoryStorage() @@ -189,21 +152,20 @@ router.post( route({ body: "MessageCreateSchema", permission: "SEND_MESSAGES", right: "SEND_MESSAGES" }), async (req: Request, res: Response) => { const { channel_id } = req.params; - var body = req.body as MessageCreateSchema; + let body = req.body as MessageCreateSchema; const attachments: Attachment[] = []; const channel = await Channel.findOneOrFail({ where: { id: channel_id }, relations: ["recipients", "recipients.user"] }); if (!channel.isWritable()) { - throw new HTTPError(`Cannot send messages to channel of type ${channel.type}`, 400) + throw new HTTPError(`Cannot send messages to channel of type ${channel.type}`, 400); } - const files = req.files as Express.Multer.File[] ?? []; - for (var currFile of files) { + const files = (req.files as Express.Multer.File[]) ?? []; + for (let currFile of files) { try { - const file = await uploadFile(`/attachments/${channel.id}`, currFile); + const file: any = await uploadFile(`/attachments/${channel.id}`, currFile); attachments.push({ ...file, proxy_url: file.url }); - } - catch (error) { + } catch (error) { return res.status(400).json(error); } } @@ -244,10 +206,20 @@ router.post( }) ); } - - //Fix for the client bug - delete message.member - + + //Defining member fields + var member = await Member.findOneOrFail({ where: { id: req.user_id }, relations: ["roles"] }); + // TODO: This doesn't work either + // member.roles = member.roles.filter((role) => { + // return role.id !== role.guild_id; + // }).map((role) => { + // return role.id; + // }); + message.member = member; + // TODO: Figure this out + // delete message.member.last_message_id; + // delete message.member.index; + await Promise.all([ message.save(), emitEvent({ event: "MESSAGE_CREATE", channel_id: channel_id, data: message } as MessageCreateEvent), @@ -255,9 +227,8 @@ router.post( channel.save() ]); - postHandleMessage(message).catch((e) => { }); // no await as it shouldnt block the message send function and silently catch error + postHandleMessage(message).catch((e) => {}); // no await as it shouldnt block the message send function and silently catch error return res.json(message); } ); - diff --git a/api/src/routes/channels/#channel_id/permissions.ts b/src/api/routes/channels/#channel_id/permissions.ts
index 2eded853..bd462ea6 100644 --- a/api/src/routes/channels/#channel_id/permissions.ts +++ b/src/api/routes/channels/#channel_id/permissions.ts
@@ -1,23 +1,18 @@ +import { route } from "@fosscord/api"; import { Channel, ChannelPermissionOverwrite, - ChannelPermissionOverwriteType, + ChannelPermissionOverwriteSchema, ChannelUpdateEvent, emitEvent, - getPermission, + HTTPError, Member, Role } from "@fosscord/util"; -import { Router, Response, Request } from "express"; -import { HTTPError } from "lambert-server"; +import { Request, Response, Router } from "express"; -import { route } from "@fosscord/api"; const router: Router = Router(); -// TODO: Only permissions your bot has in the guild or channel can be allowed/denied (unless your bot has a MANAGE_ROLES overwrite in the channel) - -export interface ChannelPermissionOverwriteSchema extends ChannelPermissionOverwrite {} - router.put( "/:overwrite_id", route({ body: "ChannelPermissionOverwriteSchema", permission: "MANAGE_ROLES" }), @@ -25,17 +20,17 @@ router.put( const { channel_id, overwrite_id } = req.params; const body = req.body as ChannelPermissionOverwriteSchema; - var channel = await Channel.findOneOrFail({ id: channel_id }); + let channel = await Channel.findOneOrFail({ where: { id: channel_id } }); if (!channel.guild_id) throw new HTTPError("Channel not found", 404); if (body.type === 0) { - if (!(await Role.count({ id: overwrite_id }))) throw new HTTPError("role not found", 404); + if (!(await Role.count({ where: { id: overwrite_id } }))) throw new HTTPError("role not found", 404); } else if (body.type === 1) { - if (!(await Member.count({ id: overwrite_id }))) throw new HTTPError("user not found", 404); + if (!(await Member.count({ where: { id: overwrite_id } }))) throw new HTTPError("user not found", 404); } else throw new HTTPError("type not supported", 501); // @ts-ignore - var overwrite: ChannelPermissionOverwrite = channel.permission_overwrites.find((x) => x.id === overwrite_id); + let overwrite: ChannelPermissionOverwrite = channel.permission_overwrites.find((x) => x.id === overwrite_id); if (!overwrite) { // @ts-ignore overwrite = { @@ -64,7 +59,7 @@ router.put( router.delete("/:overwrite_id", route({ permission: "MANAGE_ROLES" }), async (req: Request, res: Response) => { const { channel_id, overwrite_id } = req.params; - const channel = await Channel.findOneOrFail({ id: channel_id }); + const channel = await Channel.findOneOrFail({ where: { id: channel_id } }); if (!channel.guild_id) throw new HTTPError("Channel not found", 404); channel.permission_overwrites = channel.permission_overwrites!.filter((x) => x.id === overwrite_id); diff --git a/api/src/routes/channels/#channel_id/pins.ts b/src/api/routes/channels/#channel_id/pins.ts
index e71e659f..5c28feac 100644 --- a/api/src/routes/channels/#channel_id/pins.ts +++ b/src/api/routes/channels/#channel_id/pins.ts
@@ -1,28 +1,18 @@ -import { - Channel, - ChannelPinsUpdateEvent, - Config, - emitEvent, - getPermission, - Message, - MessageUpdateEvent, - DiscordApiErrors -} from "@fosscord/util"; -import { Router, Request, Response } from "express"; -import { HTTPError } from "lambert-server"; import { route } from "@fosscord/api"; +import { Channel, ChannelPinsUpdateEvent, Config, DiscordApiErrors, emitEvent, Message, MessageUpdateEvent } from "@fosscord/util"; +import { Request, Response, Router } from "express"; const router: Router = Router(); router.put("/:message_id", route({ permission: "VIEW_CHANNEL" }), async (req: Request, res: Response) => { const { channel_id, message_id } = req.params; - const message = await Message.findOneOrFail({ id: message_id }); + const message = await Message.findOneOrFail({ where: { id: message_id } }); // * in dm channels anyone can pin messages -> only check for guilds if (message.guild_id) req.permission!.hasThrow("MANAGE_MESSAGES"); - const pinned_count = await Message.count({ channel: { id: channel_id }, pinned: true }); + const pinned_count = await Message.count({ where: { channel: { id: channel_id }, pinned: true } }); const { maxPins } = Config.get().limits.channel; if (pinned_count >= maxPins) throw DiscordApiErrors.MAXIMUM_PINS.withParams(maxPins); @@ -50,10 +40,10 @@ router.put("/:message_id", route({ permission: "VIEW_CHANNEL" }), async (req: Re router.delete("/:message_id", route({ permission: "VIEW_CHANNEL" }), async (req: Request, res: Response) => { const { channel_id, message_id } = req.params; - const channel = await Channel.findOneOrFail({ id: channel_id }); + const channel = await Channel.findOneOrFail({ where: { id: channel_id } }); if (channel.guild_id) req.permission!.hasThrow("MANAGE_MESSAGES"); - const message = await Message.findOneOrFail({ id: message_id }); + const message = await Message.findOneOrFail({ where: { id: message_id } }); message.pinned = false; await Promise.all([ @@ -82,7 +72,7 @@ router.delete("/:message_id", route({ permission: "VIEW_CHANNEL" }), async (req: router.get("/", route({ permission: ["READ_MESSAGE_HISTORY"] }), async (req: Request, res: Response) => { const { channel_id } = req.params; - let pins = await Message.find({ channel_id: channel_id, pinned: true }); + let pins = await Message.find({ where: { channel_id, pinned: true } }); res.send(pins); }); diff --git a/src/api/routes/channels/#channel_id/purge.ts b/src/api/routes/channels/#channel_id/purge.ts new file mode 100644
index 00000000..aebdb832 --- /dev/null +++ b/src/api/routes/channels/#channel_id/purge.ts
@@ -0,0 +1,77 @@ +import { route } from "@fosscord/api"; +import { + Channel, + Config, + emitEvent, + getPermission, + getRights, + HTTPError, + Message, + MessageDeleteBulkEvent, + PurgeSchema +} from "@fosscord/util"; +import { Request, Response, Router } from "express"; +import { Between, FindManyOptions, In, Not } from "typeorm"; +import { isTextChannel } from "./messages"; + +const router: Router = Router(); + +export default router; + +/** +TODO: apply the delete bit by bit to prevent client and database stress +**/ +router.post( + "/", + route({ + /*body: "PurgeSchema",*/ + }), + async (req: Request, res: Response) => { + const { channel_id } = req.params; + const channel = await Channel.findOneOrFail({ where: { id: channel_id } }); + + if (!channel.guild_id) throw new HTTPError("Can't purge dm channels", 400); + isTextChannel(channel.type); + + const rights = await getRights(req.user_id); + if (!rights.has("MANAGE_MESSAGES")) { + const permissions = await getPermission(req.user_id, channel.guild_id, channel_id); + permissions.hasThrow("MANAGE_MESSAGES"); + permissions.hasThrow("MANAGE_CHANNELS"); + } + + const { before, after } = req.body as PurgeSchema; + + // TODO: send the deletion event bite-by-bite to prevent client stress + + let query: FindManyOptions<Message> & { where: { id?: any } } = { + order: { id: "ASC" }, + // take: limit, + where: { + channel_id, + id: Between(after, before), // the right way around + author_id: rights.has("SELF_DELETE_MESSAGES") ? undefined : Not(req.user_id) + // if you lack the right of self-deletion, you can't delete your own messages, even in purges + }, + relations: ["author", "webhook", "application", "mentions", "mention_roles", "mention_channels", "sticker_items", "attachments"] + }; + + const messages = await Message.find(query); + const endpoint = Config.get().cdn.endpointPublic; + + if (messages.length == 0) { + res.sendStatus(304); + return; + } + + await Message.delete({ id: In(messages) }); + + await emitEvent({ + event: "MESSAGE_DELETE_BULK", + channel_id, + data: { ids: messages.map((x) => x.id), channel_id, guild_id: channel.guild_id } + } as MessageDeleteBulkEvent); + + res.sendStatus(204); + } +); diff --git a/api/src/routes/channels/#channel_id/recipients.ts b/src/api/routes/channels/#channel_id/recipients.ts
index e6466211..276a0eda 100644 --- a/api/src/routes/channels/#channel_id/recipients.ts +++ b/src/api/routes/channels/#channel_id/recipients.ts
@@ -1,4 +1,4 @@ -import { Request, Response, Router } from "express"; +import { route } from "@fosscord/api"; import { Channel, ChannelRecipientAddEvent, @@ -6,11 +6,12 @@ import { DiscordApiErrors, DmChannelDTO, emitEvent, + OrmUtils, PublicUserProjection, Recipient, User } from "@fosscord/util"; -import { route } from "@fosscord/api"; +import { Request, Response, Router } from "express"; const router: Router = Router(); @@ -28,7 +29,7 @@ router.put("/:user_id", route({}), async (req: Request, res: Response) => { throw DiscordApiErrors.INVALID_RECIPIENT; //TODO is this the right error? } - channel.recipients!.push(new Recipient({ channel_id: channel_id, user_id: user_id })); + channel.recipients!.push(OrmUtils.mergeDeep(new Recipient(), { channel_id, user_id: user_id })); await channel.save(); await emitEvent({ diff --git a/api/src/routes/channels/#channel_id/typing.ts b/src/api/routes/channels/#channel_id/typing.ts
index 56652368..26d0fcfa 100644 --- a/api/src/routes/channels/#channel_id/typing.ts +++ b/src/api/routes/channels/#channel_id/typing.ts
@@ -1,6 +1,6 @@ -import { Channel, emitEvent, Member, TypingStartEvent } from "@fosscord/util"; import { route } from "@fosscord/api"; -import { Router, Request, Response } from "express"; +import { Channel, emitEvent, Member, TypingStartEvent } from "@fosscord/util"; +import { Request, Response, Router } from "express"; const router: Router = Router(); @@ -8,7 +8,7 @@ router.post("/", route({ permission: "SEND_MESSAGES" }), async (req: Request, re const { channel_id } = req.params; const user_id = req.user_id; const timestamp = Date.now(); - const channel = await Channel.findOneOrFail({ id: channel_id }); + const channel = await Channel.findOneOrFail({ where: { id: channel_id } }); const member = await Member.findOne({ where: { id: user_id, guild_id: channel.guild_id }, relations: ["roles", "user"] }); await emitEvent({ diff --git a/api/src/routes/channels/#channel_id/webhooks.ts b/src/api/routes/channels/#channel_id/webhooks.ts
index 92895da6..38dcb869 100644 --- a/api/src/routes/channels/#channel_id/webhooks.ts +++ b/src/api/routes/channels/#channel_id/webhooks.ts
@@ -1,19 +1,9 @@ -import { Router, Response, Request } from "express"; import { route } from "@fosscord/api"; -import { Channel, Config, getPermission, trimSpecial, Webhook } from "@fosscord/util"; -import { HTTPError } from "lambert-server"; +import { Channel, Config, DiscordApiErrors, HTTPError, trimSpecial, Webhook } from "@fosscord/util"; +import { Request, Response, Router } from "express"; import { isTextChannel } from "./messages/index"; -import { DiscordApiErrors } from "@fosscord/util"; const router: Router = Router(); -// TODO: webhooks -export interface WebhookCreateSchema { - /** - * @maxLength 80 - */ - name: string; - avatar: string; -} //TODO: implement webhooks router.get("/", route({}), async (req: Request, res: Response) => { res.json([]); @@ -22,20 +12,21 @@ router.get("/", route({}), async (req: Request, res: Response) => { // TODO: use Image Data Type for avatar instead of String router.post("/", route({ body: "WebhookCreateSchema", permission: "MANAGE_WEBHOOKS" }), async (req: Request, res: Response) => { const channel_id = req.params.channel_id; - const channel = await Channel.findOneOrFail({ id: channel_id }); + const channel = await Channel.findOneOrFail({ where: { id: channel_id } }); isTextChannel(channel.type); if (!channel.guild_id) throw new HTTPError("Not a guild channel", 400); - const webhook_count = await Webhook.count({ channel_id }); + const webhook_count = await Webhook.count({ where: { channel_id } }); const { maxWebhooks } = Config.get().limits.channel; if (webhook_count > maxWebhooks) throw DiscordApiErrors.MAXIMUM_WEBHOOKS.withParams(maxWebhooks); - var { avatar, name } = req.body as { name: string; avatar?: string }; + let { avatar, name } = req.body as { name: string; avatar?: string }; name = trimSpecial(name); if (name === "clyde") throw new HTTPError("Invalid name", 400); // TODO: save webhook in database and send response + res.json(new Webhook()); }); export default router; diff --git a/src/api/routes/discoverable-guilds.ts b/src/api/routes/discoverable-guilds.ts new file mode 100644
index 00000000..2bf49287 --- /dev/null +++ b/src/api/routes/discoverable-guilds.ts
@@ -0,0 +1,39 @@ +import { Config, Guild } from "@fosscord/util"; + +import { Request, Response, Router } from "express"; +import { Like } from "typeorm"; +import { route } from ".."; + +const router = Router(); + +router.get("/", route({}), async (req: Request, res: Response) => { + const { offset, limit, categories } = req.query; + let showAllGuilds = Config.get().guild.discovery.showAllGuilds; + let configLimit = Config.get().guild.discovery.limit; + // ! this only works using SQL querys + // const guilds = await Guild.find({ where: { features: "DISCOVERABLE" } }); //, take: Math.abs(Number(limit)) }); + let guilds; + if (categories == undefined) { + guilds = showAllGuilds + ? await Guild.find({ take: Math.abs(Number(limit || configLimit)) }) + : await Guild.find({ where: { features: Like("%DISCOVERABLE%") }, take: Math.abs(Number(limit || configLimit)) }); + } else { + guilds = showAllGuilds + ? await Guild.find({ where: { primary_category_id: Number(categories) }, take: Math.abs(Number(limit || configLimit)) }) + : await Guild.find({ + where: { primary_category_id: Number(categories), features: Like("%DISCOVERABLE%") }, + take: Math.abs(Number(limit || configLimit)) + }); + } + + const total = guilds ? guilds.length : undefined; + + res.send({ + total: total, + guilds: guilds, + offset: Number(offset || Config.get().guild.discovery.offset), + limit: Number(limit || configLimit) + }); +}); + +export default router; diff --git a/api/src/routes/discovery.ts b/src/api/routes/discovery.ts
index 1991400e..7b9edd48 100644 --- a/api/src/routes/discovery.ts +++ b/src/api/routes/discovery.ts
@@ -1,6 +1,6 @@ import { Categories } from "@fosscord/util"; -import { Router, Response, Request } from "express"; -import { route } from "@fosscord/api"; +import { Request, Response, Router } from "express"; +import { route } from ".."; const router = Router(); @@ -10,7 +10,7 @@ router.get("/categories", route({}), async (req: Request, res: Response) => { const { locale, primary_only } = req.query; - const out = primary_only ? await Categories.find() : await Categories.find({ where: `"is_primary" = "true"` }); + const out = primary_only ? await Categories.find() : await Categories.find({ where: { is_primary: true } }); res.send(out); }); diff --git a/src/api/routes/downloads.ts b/src/api/routes/downloads.ts new file mode 100644
index 00000000..c86c1fb0 --- /dev/null +++ b/src/api/routes/downloads.ts
@@ -0,0 +1,20 @@ +import { Config, Release } from "@fosscord/util"; +import { Request, Response, Router } from "express"; +import { route } from ".."; + +const router = Router(); + +router.get("/:branch", route({}), async (req: Request, res: Response) => { + const { client } = Config.get(); + const { branch } = req.params; + const { platform } = req.query; + //TODO + + if (!platform || !["linux", "osx", "win"].includes(platform.toString())) return res.status(404); + + const release = await Release.findOneOrFail({ where: { name: client.releases.upstreamVersion } }); + + res.redirect(release[`win_url`]); +}); + +export default router; diff --git a/api/src/routes/experiments.ts b/src/api/routes/experiments.ts
index 7be86fb8..0355c631 100644 --- a/api/src/routes/experiments.ts +++ b/src/api/routes/experiments.ts
@@ -1,11 +1,11 @@ -import { Router, Response, Request } from "express"; -import { route } from "@fosscord/api"; +import { Request, Response, Router } from "express"; +import { route } from ".."; const router = Router(); router.get("/", route({}), (req: Request, res: Response) => { // TODO: - res.send({ fingerprint: "", assignments: [], guild_experiments:[] }); + res.send({ fingerprint: "", assignments: [], guild_experiments: [] }); }); export default router; diff --git a/api/src/routes/gateway/bot.ts b/src/api/routes/gateway/bot.ts
index f1dbb9df..0e44f6b2 100644 --- a/api/src/routes/gateway/bot.ts +++ b/src/api/routes/gateway/bot.ts
@@ -1,6 +1,6 @@ -import { Config } from "@fosscord/util"; -import { Router, Response, Request } from "express"; import { route, RouteOptions } from "@fosscord/api"; +import { Config } from "@fosscord/util"; +import { Request, Response, Router } from "express"; const router = Router(); diff --git a/api/src/routes/gateway/index.ts b/src/api/routes/gateway/index.ts
index 9bad7478..47037573 100644 --- a/api/src/routes/gateway/index.ts +++ b/src/api/routes/gateway/index.ts
@@ -1,6 +1,6 @@ -import { Config } from "@fosscord/util"; -import { Router, Response, Request } from "express"; import { route, RouteOptions } from "@fosscord/api"; +import { Config } from "@fosscord/util"; +import { Request, Response, Router } from "express"; const router = Router(); diff --git a/api/src/routes/gifs/search.ts b/src/api/routes/gifs/search.ts
index 9ad7a592..8b5e984a 100644 --- a/api/src/routes/gifs/search.ts +++ b/src/api/routes/gifs/search.ts
@@ -1,7 +1,7 @@ -import { Router, Response, Request } from "express"; -import fetch from "node-fetch"; -import ProxyAgent from 'proxy-agent'; import { route } from "@fosscord/api"; +import { Request, Response, Router } from "express"; +import fetch from "node-fetch"; +import ProxyAgent from "proxy-agent"; import { getGifApiKey, parseGifResult } from "./trending"; const router = Router(); @@ -11,7 +11,7 @@ router.get("/", route({}), async (req: Request, res: Response) => { const { q, media_format, locale } = req.query; const apiKey = getGifApiKey(); - + const agent = new ProxyAgent(); const response = await fetch(`https://g.tenor.com/v1/search?q=${q}&media_format=${media_format}&locale=${locale}&key=${apiKey}`, { @@ -20,7 +20,7 @@ router.get("/", route({}), async (req: Request, res: Response) => { headers: { "Content-Type": "application/json" } }); - const { results } = await response.json(); + const { results } = (await response.json()) as any; res.json(results.map(parseGifResult)).status(200); }); diff --git a/api/src/routes/gifs/trending-gifs.ts b/src/api/routes/gifs/trending-gifs.ts
index 6d97bf7c..65a9600e 100644 --- a/api/src/routes/gifs/trending-gifs.ts +++ b/src/api/routes/gifs/trending-gifs.ts
@@ -1,7 +1,7 @@ -import { Router, Response, Request } from "express"; -import fetch from "node-fetch"; -import ProxyAgent from 'proxy-agent'; import { route } from "@fosscord/api"; +import { Request, Response, Router } from "express"; +import fetch from "node-fetch"; +import ProxyAgent from "proxy-agent"; import { getGifApiKey, parseGifResult } from "./trending"; const router = Router(); @@ -11,7 +11,7 @@ router.get("/", route({}), async (req: Request, res: Response) => { const { media_format, locale } = req.query; const apiKey = getGifApiKey(); - + const agent = new ProxyAgent(); const response = await fetch(`https://g.tenor.com/v1/trending?media_format=${media_format}&locale=${locale}&key=${apiKey}`, { @@ -20,7 +20,7 @@ router.get("/", route({}), async (req: Request, res: Response) => { headers: { "Content-Type": "application/json" } }); - const { results } = await response.json(); + const { results } = (await response.json()) as any; res.json(results.map(parseGifResult)).status(200); }); diff --git a/api/src/routes/gifs/trending.ts b/src/api/routes/gifs/trending.ts
index c81b4c08..45396ff0 100644 --- a/api/src/routes/gifs/trending.ts +++ b/src/api/routes/gifs/trending.ts
@@ -1,9 +1,8 @@ -import { Router, Response, Request } from "express"; -import fetch from "node-fetch"; -import ProxyAgent from 'proxy-agent'; import { route } from "@fosscord/api"; -import { Config } from "@fosscord/util"; -import { HTTPError } from "lambert-server"; +import { Config, HTTPError } from "@fosscord/util"; +import { Request, Response, Router } from "express"; +import fetch from "node-fetch"; +import ProxyAgent from "proxy-agent"; const router = Router(); @@ -34,7 +33,7 @@ router.get("/", route({}), async (req: Request, res: Response) => { const { media_format, locale } = req.query; const apiKey = getGifApiKey(); - + const agent = new ProxyAgent(); const [responseSource, trendGifSource] = await Promise.all([ @@ -50,8 +49,8 @@ router.get("/", route({}), async (req: Request, res: Response) => { }) ]); - const { tags } = await responseSource.json(); - const { results } = await trendGifSource.json(); + const { tags } = (await responseSource.json()) as any; + const { results } = (await trendGifSource.json()) as any; res.json({ categories: tags.map((x: any) => ({ name: x.searchterm, src: x.image })), diff --git a/api/src/routes/guild-recommendations.ts b/src/api/routes/guild-recommendations.ts
index 1432f39c..0248a9c3 100644 --- a/api/src/routes/guild-recommendations.ts +++ b/src/api/routes/guild-recommendations.ts
@@ -1,23 +1,24 @@ -import { Guild, Config } from "@fosscord/util"; +import { Config, Guild } from "@fosscord/util"; -import { Router, Request, Response } from "express"; -import { route } from "@fosscord/api"; +import { Request, Response, Router } from "express"; +import { Like } from "typeorm"; +import { route } from ".."; const router = Router(); router.get("/", route({}), async (req: Request, res: Response) => { const { limit, personalization_disabled } = req.query; - var showAllGuilds = Config.get().guild.discovery.showAllGuilds; + let showAllGuilds = Config.get().guild.discovery.showAllGuilds; // ! this only works using SQL querys // TODO: implement this with default typeorm query // const guilds = await Guild.find({ where: { features: "DISCOVERABLE" } }); //, take: Math.abs(Number(limit)) }); - const genLoadId = (size: Number) => [...Array(size)].map(() => Math.floor(Math.random() * 16).toString(16)).join(''); + const genLoadId = (size: Number) => [...Array(size)].map(() => Math.floor(Math.random() * 16).toString(16)).join(""); const guilds = showAllGuilds ? await Guild.find({ take: Math.abs(Number(limit || 24)) }) - : await Guild.find({ where: `"features" LIKE '%DISCOVERABLE%'`, take: Math.abs(Number(limit || 24)) }); - res.send({ recommended_guilds: guilds, load_id: `server_recs/${genLoadId(32)}`}).status(200); + : await Guild.find({ where: { features: Like("%DISCOVERABLE%") }, take: Math.abs(Number(limit || 24)) }); + res.send({ recommended_guilds: guilds, load_id: `server_recs/${genLoadId(32)}` }).status(200); }); export default router; diff --git a/api/src/routes/guilds/#guild_id/audit-logs.ts b/src/api/routes/guilds/#guild_id/audit-logs.ts
index a4f2f800..05b9982e 100644 --- a/api/src/routes/guilds/#guild_id/audit-logs.ts +++ b/src/api/routes/guilds/#guild_id/audit-logs.ts
@@ -1,8 +1,5 @@ -import { Router, Response, Request } from "express"; -import { Channel, ChannelUpdateEvent, getPermission, emitEvent } from "@fosscord/util"; -import { HTTPError } from "lambert-server"; import { route } from "@fosscord/api"; -import { ChannelModifySchema } from "../../channels/#channel_id"; +import { Request, Response, Router } from "express"; const router = Router(); //TODO: implement audit logs diff --git a/api/src/routes/guilds/#guild_id/bans.ts b/src/api/routes/guilds/#guild_id/bans.ts
index 1ce41936..4600b4cb 100644 --- a/api/src/routes/guilds/#guild_id/bans.ts +++ b/src/api/routes/guilds/#guild_id/bans.ts
@@ -1,29 +1,18 @@ -import { Request, Response, Router } from "express"; -import { DiscordApiErrors, emitEvent, getPermission, GuildBanAddEvent, GuildBanRemoveEvent, Guild, Ban, User, Member } from "@fosscord/util"; -import { HTTPError } from "lambert-server"; import { getIpAdress, route } from "@fosscord/api"; - -export interface BanCreateSchema { - delete_message_days?: string; - reason?: string; -}; - -export interface BanRegistrySchema { - id: string; - user_id: string; - guild_id: string; - executor_id: string; - ip?: string; - reason?: string | undefined; -}; - -export interface BanModeratorSchema { - id: string; - user_id: string; - guild_id: string; - executor_id: string; - reason?: string | undefined; -}; +import { + Ban, + BanModeratorSchema, + BanRegistrySchema, + DiscordApiErrors, + emitEvent, + GuildBanAddEvent, + GuildBanRemoveEvent, + HTTPError, + Member, + OrmUtils, + User +} from "@fosscord/util"; +import { Request, Response, Router } from "express"; const router: Router = Router(); @@ -32,7 +21,7 @@ const router: Router = Router(); router.get("/", route({ permission: "BAN_MEMBERS" }), async (req: Request, res: Response) => { const { guild_id } = req.params; - let bans = await Ban.find({ guild_id: guild_id }); + let bans = await Ban.find({ where: { guild_id } }); let promisesToAwait: object[] = []; const bansObj: object[] = []; @@ -65,16 +54,16 @@ router.get("/:user", route({ permission: "BAN_MEMBERS" }), async (req: Request, const { guild_id } = req.params; const user_id = req.params.ban; - let ban = await Ban.findOneOrFail({ guild_id: guild_id, user_id: user_id }) as BanRegistrySchema; - + let ban = (await Ban.findOneOrFail({ where: { guild_id, user_id } })) as BanRegistrySchema; + if (ban.user_id === ban.executor_id) throw DiscordApiErrors.UNKNOWN_BAN; // pretend self-bans don't exist to prevent victim chasing - + /* Filter secret from registry. */ - + ban = ban as BanModeratorSchema; - delete ban.ip + delete ban.ip; return res.json(ban); }); @@ -83,14 +72,14 @@ router.put("/:user_id", route({ body: "BanCreateSchema", permission: "BAN_MEMBER const { guild_id } = req.params; const banned_user_id = req.params.user_id; - if ( (req.user_id === banned_user_id) && (banned_user_id === req.permission!.cache.guild?.owner_id)) + if (req.user_id === banned_user_id && banned_user_id === req.permission!.cache.guild?.owner_id) throw new HTTPError("You are the guild owner, hence can't ban yourself", 403); - + if (req.permission!.cache.guild?.owner_id === banned_user_id) throw new HTTPError("You can't ban the owner", 400); - + const banned_user = await User.getPublicUser(banned_user_id); - const ban = new Ban({ + const ban = OrmUtils.mergeDeep(new Ban(), { user_id: banned_user_id, guild_id: guild_id, ip: getIpAdress(req), @@ -114,15 +103,15 @@ router.put("/:user_id", route({ body: "BanCreateSchema", permission: "BAN_MEMBER return res.json(ban); }); -router.put("/@me", route({ body: "BanCreateSchema"}), async (req: Request, res: Response) => { +router.put("/@me", route({ body: "BanCreateSchema" }), async (req: Request, res: Response) => { const { guild_id } = req.params; const banned_user = await User.getPublicUser(req.params.user_id); - if (req.permission!.cache.guild?.owner_id === req.params.user_id) + if (req.permission!.cache.guild?.owner_id === req.params.user_id) throw new HTTPError("You are the guild owner, hence can't ban yourself", 403); - - const ban = new Ban({ + + const ban = OrmUtils.mergeDeep(new Ban(), { user_id: req.params.user_id, guild_id: guild_id, ip: getIpAdress(req), @@ -149,13 +138,13 @@ router.put("/@me", route({ body: "BanCreateSchema"}), async (req: Request, res: router.delete("/:user_id", route({ permission: "BAN_MEMBERS" }), async (req: Request, res: Response) => { const { guild_id, user_id } = req.params; - let ban = await Ban.findOneOrFail({ guild_id: guild_id, user_id: user_id }); - + let ban = await Ban.findOneOrFail({ where: { guild_id, user_id } }); + if (ban.user_id === ban.executor_id) throw DiscordApiErrors.UNKNOWN_BAN; // make self-bans irreversible and hide them from view to avoid victim chasing - + const banned_user = await User.getPublicUser(user_id); - + await Promise.all([ Ban.delete({ user_id: user_id, diff --git a/api/src/routes/guilds/#guild_id/channels.ts b/src/api/routes/guilds/#guild_id/channels.ts
index a921fa21..3563eb4c 100644 --- a/api/src/routes/guilds/#guild_id/channels.ts +++ b/src/api/routes/guilds/#guild_id/channels.ts
@@ -1,13 +1,11 @@ -import { Router, Response, Request } from "express"; -import { Channel, ChannelUpdateEvent, getPermission, emitEvent } from "@fosscord/util"; -import { HTTPError } from "lambert-server"; import { route } from "@fosscord/api"; -import { ChannelModifySchema } from "../../channels/#channel_id"; +import { Channel, ChannelModifySchema, ChannelReorderSchema, ChannelUpdateEvent, emitEvent, HTTPError } from "@fosscord/util"; +import { Request, Response, Router } from "express"; const router = Router(); router.get("/", route({}), async (req: Request, res: Response) => { const { guild_id } = req.params; - const channels = await Channel.find({ guild_id }); + const channels = await Channel.find({ where: { guild_id } }); res.json(channels); }); @@ -22,8 +20,6 @@ router.post("/", route({ body: "ChannelModifySchema", permission: "MANAGE_CHANNE res.status(201).json(channel); }); -export type ChannelReorderSchema = { id: string; position?: number; lock_permissions?: boolean; parent_id?: string }[]; - router.patch("/", route({ body: "ChannelReorderSchema", permission: "MANAGE_CHANNELS" }), async (req: Request, res: Response) => { // changes guild channel position const { guild_id } = req.params; @@ -48,7 +44,7 @@ router.patch("/", route({ body: "ChannelReorderSchema", permission: "MANAGE_CHAN } await Channel.update({ guild_id, id: x.id }, opts); - const channel = await Channel.findOneOrFail({ guild_id, id: x.id }); + const channel = await Channel.findOneOrFail({ where: { guild_id, id: x.id } }); await emitEvent({ event: "CHANNEL_UPDATE", data: channel, channel_id: x.id, guild_id } as ChannelUpdateEvent); }) diff --git a/api/src/routes/guilds/#guild_id/delete.ts b/src/api/routes/guilds/#guild_id/delete.ts
index bd158c56..e6a1a6b2 100644 --- a/api/src/routes/guilds/#guild_id/delete.ts +++ b/src/api/routes/guilds/#guild_id/delete.ts
@@ -1,14 +1,13 @@ -import { Channel, emitEvent, GuildDeleteEvent, Guild, Member, Message, Role, Invite, Emoji } from "@fosscord/util"; -import { Router, Request, Response } from "express"; -import { HTTPError } from "lambert-server"; import { route } from "@fosscord/api"; +import { emitEvent, Guild, GuildDeleteEvent, HTTPError } from "@fosscord/util"; +import { Request, Response, Router } from "express"; const router = Router(); // discord prefixes this route with /delete instead of using the delete method // docs are wrong https://discord.com/developers/docs/resources/guild#delete-guild router.post("/", route({}), async (req: Request, res: Response) => { - var { guild_id } = req.params; + let { guild_id } = req.params; const guild = await Guild.findOneOrFail({ where: { id: guild_id }, select: ["owner_id"] }); if (guild.owner_id !== req.user_id) throw new HTTPError("You are not the owner of this guild", 401); diff --git a/src/api/routes/guilds/#guild_id/discovery-requirements.ts b/src/api/routes/guilds/#guild_id/discovery-requirements.ts new file mode 100644
index 00000000..c0260fe7 --- /dev/null +++ b/src/api/routes/guilds/#guild_id/discovery-requirements.ts
@@ -0,0 +1,37 @@ +import { route } from "@fosscord/api"; +import { Request, Response, Router } from "express"; + +const router = Router(); + +router.get("/", route({}), async (req: Request, res: Response) => { + const { guild_id } = req.params; + // TODO: + // Load from database + // Admin control, but for now it allows anyone to be discoverable + + res.send({ + guild_id: guild_id, + safe_environment: true, + healthy: true, + health_score_pending: false, + size: true, + nsfw_properties: {}, + protected: true, + sufficient: true, + sufficient_without_grace_period: true, + valid_rules_channel: true, + retention_healthy: true, + engagement_healthy: true, + age: true, + minimum_age: 0, + health_score: { + avg_nonnew_participators: 0, + avg_nonnew_communicators: 0, + num_intentful_joiners: 0, + perc_ret_w1_intentful: 0 + }, + minimum_size: 0 + }); +}); + +export default router; diff --git a/api/src/routes/guilds/#guild_id/emojis.ts b/src/api/routes/guilds/#guild_id/emojis.ts
index 85d7ac05..db5ae325 100644 --- a/api/src/routes/guilds/#guild_id/emojis.ts +++ b/src/api/routes/guilds/#guild_id/emojis.ts
@@ -1,21 +1,22 @@ -import { Router, Request, Response } from "express"; -import { Config, DiscordApiErrors, emitEvent, Emoji, GuildEmojisUpdateEvent, handleFile, Member, Snowflake, User } from "@fosscord/util"; import { route } from "@fosscord/api"; +import { + Config, + DiscordApiErrors, + emitEvent, + Emoji, + EmojiCreateSchema, + EmojiModifySchema, + GuildEmojisUpdateEvent, + handleFile, + Member, + OrmUtils, + Snowflake, + User +} from "@fosscord/util"; +import { Request, Response, Router } from "express"; const router = Router(); -export interface EmojiCreateSchema { - name?: string; - image: string; - require_colons?: boolean | null; - roles?: string[]; -} - -export interface EmojiModifySchema { - name?: string; - roles?: string[]; -} - router.get("/", route({}), async (req: Request, res: Response) => { const { guild_id } = req.params; @@ -41,16 +42,16 @@ router.post("/", route({ body: "EmojiCreateSchema", permission: "MANAGE_EMOJIS_A const body = req.body as EmojiCreateSchema; const id = Snowflake.generate(); - const emoji_count = await Emoji.count({ guild_id: guild_id }); + const emoji_count = await Emoji.count({ where: { guild_id } }); const { maxEmojis } = Config.get().limits.guild; if (emoji_count >= maxEmojis) throw DiscordApiErrors.MAXIMUM_NUMBER_OF_EMOJIS_REACHED.withParams(maxEmojis); if (body.require_colons == null) body.require_colons = true; - const user = await User.findOneOrFail({ id: req.user_id }); + const user = await User.findOneOrFail({ where: { id: req.user_id } }); body.image = (await handleFile(`/emojis/${id}`, body.image)) as string; - const emoji = await new Emoji({ + const emoji = await OrmUtils.mergeDeep(new Emoji(), { id: id, guild_id: guild_id, ...body, @@ -66,7 +67,7 @@ router.post("/", route({ body: "EmojiCreateSchema", permission: "MANAGE_EMOJIS_A guild_id: guild_id, data: { guild_id: guild_id, - emojis: await Emoji.find({ guild_id: guild_id }) + emojis: await Emoji.find({ where: { guild_id } }) } } as GuildEmojisUpdateEvent); @@ -80,14 +81,14 @@ router.patch( const { emoji_id, guild_id } = req.params; const body = req.body as EmojiModifySchema; - const emoji = await new Emoji({ ...body, id: emoji_id, guild_id: guild_id }).save(); + const emoji = await OrmUtils.mergeDeep(new Emoji(), { ...body, id: emoji_id, guild_id: guild_id }).save(); await emitEvent({ event: "GUILD_EMOJIS_UPDATE", guild_id: guild_id, data: { guild_id: guild_id, - emojis: await Emoji.find({ guild_id: guild_id }) + emojis: await Emoji.find({ where: { guild_id } }) } } as GuildEmojisUpdateEvent); @@ -108,7 +109,7 @@ router.delete("/:emoji_id", route({ permission: "MANAGE_EMOJIS_AND_STICKERS" }), guild_id: guild_id, data: { guild_id: guild_id, - emojis: await Emoji.find({ guild_id: guild_id }) + emojis: await Emoji.find({ where: { guild_id } }) } } as GuildEmojisUpdateEvent); diff --git a/api/src/routes/guilds/#guild_id/index.ts b/src/api/routes/guilds/#guild_id/index.ts
index 4ec3df72..af889982 100644 --- a/api/src/routes/guilds/#guild_id/index.ts +++ b/src/api/routes/guilds/#guild_id/index.ts
@@ -1,33 +1,27 @@ -import { Request, Response, Router } from "express"; -import { DiscordApiErrors, emitEvent, getPermission, getRights, Guild, GuildUpdateEvent, handleFile, Member } from "@fosscord/util"; -import { HTTPError } from "lambert-server"; import { route } from "@fosscord/api"; -import "missing-native-js-functions"; -import { GuildCreateSchema } from "../index"; +import { + DiscordApiErrors, + emitEvent, + getPermission, + getRights, + Guild, + GuildUpdateEvent, + GuildUpdateSchema, + handleFile, + HTTPError, + Member, + OrmUtils +} from "@fosscord/util"; +import { Request, Response, Router } from "express"; const router = Router(); -export interface GuildUpdateSchema extends Omit<GuildCreateSchema, "channels"> { - banner?: string | null; - splash?: string | null; - description?: string; - features?: string[]; - verification_level?: number; - default_message_notifications?: number; - system_channel_flags?: number; - explicit_content_filter?: number; - public_updates_channel_id?: string; - afk_timeout?: number; - afk_channel_id?: string; - preferred_locale?: string; -} - router.get("/", route({}), async (req: Request, res: Response) => { const { guild_id } = req.params; const [guild, member] = await Promise.all([ - Guild.findOneOrFail({ id: guild_id }), - Member.findOne({ guild_id: guild_id, id: req.user_id }) + Guild.findOneOrFail({ where: { id: guild_id } }), + Member.findOne({ where: { guild_id, id: req.user_id } }) ]); if (!member) throw new HTTPError("You are not a member of the guild you are trying to access", 401); @@ -37,31 +31,31 @@ router.get("/", route({}), async (req: Request, res: Response) => { return res.send(guild); }); -router.patch("/", route({ body: "GuildUpdateSchema"}), async (req: Request, res: Response) => { +router.patch("/", route({ body: "GuildUpdateSchema" }), async (req: Request, res: Response) => { const body = req.body as GuildUpdateSchema; const { guild_id } = req.params; - - + const rights = await getRights(req.user_id); const permission = await getPermission(req.user_id, guild_id); - - if (!rights.has("MANAGE_GUILDS")||!permission.has("MANAGE_GUILD")) + + if (!rights.has("MANAGE_GUILDS") || !permission.has("MANAGE_GUILD")) throw DiscordApiErrors.MISSING_PERMISSIONS.withParams("MANAGE_GUILD"); - + // TODO: guild update check image if (body.icon) body.icon = await handleFile(`/icons/${guild_id}`, body.icon); if (body.banner) body.banner = await handleFile(`/banners/${guild_id}`, body.banner); if (body.splash) body.splash = await handleFile(`/splashes/${guild_id}`, body.splash); - var guild = await Guild.findOneOrFail({ + let guild = await Guild.findOneOrFail({ where: { id: guild_id }, relations: ["emojis", "roles", "stickers"] }); // TODO: check if body ids are valid - guild.assign(body); + guild = OrmUtils.mergeDeep(guild, body); - const data = guild.toJSON(); + //TODO: check this, removed toJSON call + const data = JSON.parse(JSON.stringify(guild)); // TODO: guild hashes // TODO: fix vanity_url_code, template_id delete data.vanity_url_code; diff --git a/src/api/routes/guilds/#guild_id/integrations.ts b/src/api/routes/guilds/#guild_id/integrations.ts new file mode 100644
index 00000000..6a5abec3 --- /dev/null +++ b/src/api/routes/guilds/#guild_id/integrations.ts
@@ -0,0 +1,9 @@ +import { route } from "@fosscord/api"; +import { Request, Response, Router } from "express"; +const router = Router(); + +//TODO: implement integrations list +router.get("/", route({}), async (req: Request, res: Response) => { + res.json([]); +}); +export default router; diff --git a/api/src/routes/guilds/#guild_id/invites.ts b/src/api/routes/guilds/#guild_id/invites.ts
index b7534e31..c663df72 100644 --- a/api/src/routes/guilds/#guild_id/invites.ts +++ b/src/api/routes/guilds/#guild_id/invites.ts
@@ -1,5 +1,5 @@ -import { getPermission, Invite, PublicInviteRelation } from "@fosscord/util"; import { route } from "@fosscord/api"; +import { Invite, PublicInviteRelation } from "@fosscord/util"; import { Request, Response, Router } from "express"; const router = Router(); diff --git a/api/src/routes/guilds/#guild_id/members/#member_id/index.ts b/src/api/routes/guilds/#guild_id/members/#member_id/index.ts
index c285abb3..57152f9a 100644 --- a/api/src/routes/guilds/#guild_id/members/#member_id/index.ts +++ b/src/api/routes/guilds/#guild_id/members/#member_id/index.ts
@@ -1,19 +1,26 @@ -import { Request, Response, Router } from "express"; -import { Member, getPermission, getRights, Role, GuildMemberUpdateEvent, emitEvent, Sticker, Emoji, Rights, Guild } from "@fosscord/util"; -import { HTTPError } from "lambert-server"; import { route } from "@fosscord/api"; +import { + emitEvent, + Emoji, + getPermission, + getRights, + Guild, + GuildMemberUpdateEvent, + Member, + MemberChangeSchema, + OrmUtils, + Role, + Sticker +} from "@fosscord/util"; +import { Request, Response, Router } from "express"; const router = Router(); -export interface MemberChangeSchema { - roles?: string[]; -} - router.get("/", route({}), async (req: Request, res: Response) => { const { guild_id, member_id } = req.params; await Member.IsInGuildOrFail(req.user_id, guild_id); - const member = await Member.findOneOrFail({ id: member_id, guild_id }); + const member = await Member.findOneOrFail({ where: { id: member_id, guild_id } }); return res.json(member); }); @@ -25,13 +32,13 @@ router.patch("/", route({ body: "MemberChangeSchema" }), async (req: Request, re const member = await Member.findOneOrFail({ where: { id: member_id, guild_id }, relations: ["roles", "user"] }); const permission = await getPermission(req.user_id, guild_id); - const everyone = await Role.findOneOrFail({ guild_id: guild_id, name: "@everyone", position: 0 }); + const everyone = await Role.findOneOrFail({ where: { guild_id: guild_id, name: "@everyone", position: 0 } }); if (body.roles) { permission.hasThrow("MANAGE_ROLES"); if (body.roles.indexOf(everyone.id) === -1) body.roles.push(everyone.id); - member.roles = body.roles.map((x) => new Role({ id: x })); // foreign key constraint will fail if role doesn't exist + member.roles = body.roles.map((x) => OrmUtils.mergeDeep(new Role(), { id: x })); // foreign key constraint will fail if role doesn't exist } await member.save(); @@ -49,7 +56,6 @@ router.patch("/", route({ body: "MemberChangeSchema" }), async (req: Request, re }); router.put("/", route({}), async (req: Request, res: Response) => { - // TODO: Lurker mode const rights = await getRights(req.user_id); @@ -59,22 +65,22 @@ router.put("/", route({}), async (req: Request, res: Response) => { member_id = req.user_id; rights.hasThrow("JOIN_GUILDS"); } else { - // TODO: join others by controller + // TODO: join others by controller } - var guild = await Guild.findOneOrFail({ + let guild = await Guild.findOneOrFail({ where: { id: guild_id } }); - var emoji = await Emoji.find({ + let emoji = await Emoji.find({ where: { guild_id: guild_id } }); - var roles = await Role.find({ + let roles = await Role.find({ where: { guild_id: guild_id } }); - var stickers = await Sticker.find({ + let stickers = await Sticker.find({ where: { guild_id: guild_id } }); diff --git a/api/src/routes/guilds/#guild_id/members/#member_id/nick.ts b/src/api/routes/guilds/#guild_id/members/#member_id/nick.ts
index 27f7f65d..26411f97 100644 --- a/api/src/routes/guilds/#guild_id/members/#member_id/nick.ts +++ b/src/api/routes/guilds/#guild_id/members/#member_id/nick.ts
@@ -1,16 +1,12 @@ -import { getPermission, Member, PermissionResolvable } from "@fosscord/util"; import { route } from "@fosscord/api"; +import { getPermission, Member, PermissionResolvable } from "@fosscord/util"; import { Request, Response, Router } from "express"; const router = Router(); -export interface MemberNickChangeSchema { - nick: string; -} - router.patch("/", route({ body: "MemberNickChangeSchema" }), async (req: Request, res: Response) => { - var { guild_id, member_id } = req.params; - var permissionString: PermissionResolvable = "MANAGE_NICKNAMES"; + let { guild_id, member_id } = req.params; + let permissionString: PermissionResolvable = "MANAGE_NICKNAMES"; if (member_id === "@me") { member_id = req.user_id; permissionString = "CHANGE_NICKNAME"; diff --git a/api/src/routes/guilds/#guild_id/members/#member_id/roles/#role_id/index.ts b/src/api/routes/guilds/#guild_id/members/#member_id/roles/#role_id/index.ts
index 8f5ca7ba..0aa7a4dc 100644 --- a/api/src/routes/guilds/#guild_id/members/#member_id/roles/#role_id/index.ts +++ b/src/api/routes/guilds/#guild_id/members/#member_id/roles/#role_id/index.ts
@@ -1,5 +1,5 @@ -import { getPermission, Member } from "@fosscord/util"; import { route } from "@fosscord/api"; +import { Member } from "@fosscord/util"; import { Request, Response, Router } from "express"; const router = Router(); diff --git a/api/src/routes/guilds/#guild_id/members/index.ts b/src/api/routes/guilds/#guild_id/members/index.ts
index b730a4e7..08164626 100644 --- a/api/src/routes/guilds/#guild_id/members/index.ts +++ b/src/api/routes/guilds/#guild_id/members/index.ts
@@ -1,8 +1,7 @@ -import { Request, Response, Router } from "express"; -import { Guild, Member, PublicMemberProjection } from "@fosscord/util"; import { route } from "@fosscord/api"; +import { HTTPError, Member, PublicMemberProjection } from "@fosscord/util"; +import { Request, Response, Router } from "express"; import { MoreThan } from "typeorm"; -import { HTTPError } from "lambert-server"; const router = Router(); diff --git a/api/src/routes/guilds/#guild_id/premium.ts b/src/api/routes/guilds/#guild_id/premium.ts
index 75361ac6..b7716378 100644 --- a/api/src/routes/guilds/#guild_id/premium.ts +++ b/src/api/routes/guilds/#guild_id/premium.ts
@@ -1,5 +1,5 @@ -import { Router, Request, Response } from "express"; import { route } from "@fosscord/api"; +import { Request, Response, Router } from "express"; const router = Router(); router.get("/subscriptions", route({}), async (req: Request, res: Response) => { diff --git a/api/src/routes/guilds/#guild_id/prune.ts b/src/api/routes/guilds/#guild_id/prune.ts
index 0e587d22..3645721c 100644 --- a/api/src/routes/guilds/#guild_id/prune.ts +++ b/src/api/routes/guilds/#guild_id/prune.ts
@@ -1,21 +1,21 @@ -import { Router, Request, Response } from "express"; -import { Guild, Member, Snowflake } from "@fosscord/util"; -import { LessThan, IsNull } from "typeorm"; import { route } from "@fosscord/api"; +import { Guild, Member, Snowflake } from "@fosscord/util"; +import { Request, Response, Router } from "express"; +import { IsNull, LessThan } from "typeorm"; const router = Router(); //Returns all inactive members, respecting role hierarchy export const inactiveMembers = async (guild_id: string, user_id: string, days: number, roles: string[] = []) => { - var date = new Date(); + let date = new Date(); date.setDate(date.getDate() - days); //Snowflake should have `generateFromTime` method? Or similar? - var minId = BigInt(date.valueOf() - Snowflake.EPOCH) << BigInt(22); + let minId = BigInt(date.valueOf() - Snowflake.EPOCH) << BigInt(22); /** idea: ability to customise the cutoff variable possible candidates: public read receipt, last presence, last VC leave **/ - var members = await Member.find({ + let members = await Member.find({ where: [ { guild_id, @@ -33,7 +33,7 @@ export const inactiveMembers = async (guild_id: string, user_id: string, days: n //I'm sure I can do this in the above db query ( and it would probably be better to do so ), but oh well. if (roles.length && members.length) members = members.filter((user) => user.roles?.some((role) => roles.includes(role.id))); - const me = await Member.findOneOrFail({ id: user_id, guild_id }, { relations: ["roles"] }); + const me = await Member.findOneOrFail({ where: { id: user_id, guild_id }, relations: ["roles"] }); const myHighestRole = Math.max(...(me.roles?.map((x) => x.position) || [])); const guild = await Guild.findOneOrFail({ where: { id: guild_id } }); @@ -54,7 +54,7 @@ export const inactiveMembers = async (guild_id: string, user_id: string, days: n router.get("/", route({}), async (req: Request, res: Response) => { const days = parseInt(req.query.days as string); - var roles = req.query.include_roles; + let roles = req.query.include_roles; if (typeof roles === "string") roles = [roles]; //express will return array otherwise const members = await inactiveMembers(req.params.guild_id, req.user_id, days, roles as string[]); @@ -62,17 +62,10 @@ router.get("/", route({}), async (req: Request, res: Response) => { res.send({ pruned: members.length }); }); -export interface PruneSchema { - /** - * @min 0 - */ - days: number; -} - router.post("/", route({ permission: "KICK_MEMBERS", right: "KICK_BAN_MEMBERS" }), async (req: Request, res: Response) => { const days = parseInt(req.body.days); - var roles = req.query.include_roles; + let roles = req.query.include_roles; if (typeof roles === "string") roles = [roles]; const { guild_id } = req.params; diff --git a/api/src/routes/guilds/#guild_id/regions.ts b/src/api/routes/guilds/#guild_id/regions.ts
index 75d24fd1..aa57ec65 100644 --- a/api/src/routes/guilds/#guild_id/regions.ts +++ b/src/api/routes/guilds/#guild_id/regions.ts
@@ -1,13 +1,12 @@ -import { Config, Guild, Member } from "@fosscord/util"; +import { getIpAdress, getVoiceRegions, route } from "@fosscord/api"; +import { Guild } from "@fosscord/util"; import { Request, Response, Router } from "express"; -import { getVoiceRegions, route } from "@fosscord/api"; -import { getIpAdress } from "@fosscord/api"; const router = Router(); router.get("/", route({}), async (req: Request, res: Response) => { const { guild_id } = req.params; - const guild = await Guild.findOneOrFail({ id: guild_id }); + const guild = await Guild.findOneOrFail({ where: { id: guild_id } }); //TODO we should use an enum for guild's features and not hardcoded strings return res.json(await getVoiceRegions(getIpAdress(req), guild.features.includes("VIP_REGIONS"))); }); diff --git a/api/src/routes/guilds/#guild_id/roles/#role_id/index.ts b/src/api/routes/guilds/#guild_id/roles/#role_id/index.ts
index 2ad01682..7f9dbc6f 100644 --- a/api/src/routes/guilds/#guild_id/roles/#role_id/index.ts +++ b/src/api/routes/guilds/#guild_id/roles/#role_id/index.ts
@@ -1,15 +1,23 @@ -import { Router, Request, Response } from "express"; -import { Role, Member, GuildRoleUpdateEvent, GuildRoleDeleteEvent, emitEvent, handleFile } from "@fosscord/util"; import { route } from "@fosscord/api"; -import { HTTPError } from "lambert-server"; -import { RoleModifySchema } from "../"; +import { + emitEvent, + GuildRoleDeleteEvent, + GuildRoleUpdateEvent, + handleFile, + HTTPError, + Member, + OrmUtils, + Role, + RoleModifySchema +} from "@fosscord/util"; +import { Request, Response, Router } from "express"; const router = Router(); router.get("/", route({}), async (req: Request, res: Response) => { const { guild_id, role_id } = req.params; await Member.IsInGuildOrFail(req.user_id, guild_id); - const role = await Role.findOneOrFail({ guild_id, id: role_id }); + const role = await Role.findOneOrFail({ where: { guild_id, id: role_id } }); return res.json(role); }); @@ -43,7 +51,7 @@ router.patch("/", route({ body: "RoleModifySchema", permission: "MANAGE_ROLES" } if (body.icon) body.icon = await handleFile(`/role-icons/${role_id}`, body.icon as string); - const role = new Role({ + const role = OrmUtils.mergeDeep(new Role(), { ...body, id: role_id, guild_id, diff --git a/api/src/routes/guilds/#guild_id/roles/index.ts b/src/api/routes/guilds/#guild_id/roles/index.ts
index 53465105..9791f7a9 100644 --- a/api/src/routes/guilds/#guild_id/roles/index.ts +++ b/src/api/routes/guilds/#guild_id/roles/index.ts
@@ -1,43 +1,27 @@ -import { Request, Response, Router } from "express"; +import { route } from "@fosscord/api"; import { - Role, + Config, + DiscordApiErrors, + emitEvent, getPermission, - Member, GuildRoleCreateEvent, GuildRoleUpdateEvent, - GuildRoleDeleteEvent, - emitEvent, - Config, - DiscordApiErrors, - handleFile + Member, + OrmUtils, + Role, + RoleModifySchema, + RolePositionUpdateSchema } from "@fosscord/util"; -import { HTTPError } from "lambert-server"; -import { route } from "@fosscord/api"; +import { Request, Response, Router } from "express"; const router: Router = Router(); -export interface RoleModifySchema { - name?: string; - permissions?: string; - color?: number; - hoist?: boolean; // whether the role should be displayed separately in the sidebar - mentionable?: boolean; // whether the role should be mentionable - position?: number; - icon?: string; - unicode_emoji?: string; -} - -export type RolePositionUpdateSchema = { - id: string; - position: number; -}[]; - router.get("/", route({}), async (req: Request, res: Response) => { const guild_id = req.params.guild_id; await Member.IsInGuildOrFail(req.user_id, guild_id); - const roles = await Role.find({ guild_id: guild_id }); + const roles = await Role.find({ where: { guild_id } }); return res.json(roles); }); @@ -46,12 +30,12 @@ router.post("/", route({ body: "RoleModifySchema", permission: "MANAGE_ROLES" }) const guild_id = req.params.guild_id; const body = req.body as RoleModifySchema; - const role_count = await Role.count({ guild_id }); + const role_count = await Role.count({ where: { guild_id } }); const { maxRoles } = Config.get().limits.guild; if (role_count > maxRoles) throw DiscordApiErrors.MAXIMUM_ROLES.withParams(maxRoles); - const role = new Role({ + let role: Role = OrmUtils.mergeDeep(new Role(), { // values before ...body are default and can be overriden position: 0, hoist: false, diff --git a/api/src/routes/guilds/#guild_id/stickers.ts b/src/api/routes/guilds/#guild_id/stickers.ts
index 4ea1dce1..15741780 100644 --- a/api/src/routes/guilds/#guild_id/stickers.ts +++ b/src/api/routes/guilds/#guild_id/stickers.ts
@@ -1,25 +1,26 @@ +import { route } from "@fosscord/api"; import { emitEvent, GuildStickersUpdateEvent, - handleFile, + HTTPError, Member, + ModifyGuildStickerSchema, + OrmUtils, Snowflake, Sticker, StickerFormatType, StickerType, uploadFile } from "@fosscord/util"; -import { Router, Request, Response } from "express"; -import { route } from "@fosscord/api"; +import { Request, Response, Router } from "express"; import multer from "multer"; -import { HTTPError } from "lambert-server"; const router = Router(); router.get("/", route({}), async (req: Request, res: Response) => { const { guild_id } = req.params; await Member.IsInGuildOrFail(req.user_id, guild_id); - res.json(await Sticker.find({ guild_id })); + res.json(await Sticker.find({ where: { guild_id } })); }); const bodyParser = multer({ @@ -43,7 +44,7 @@ router.post( const id = Snowflake.generate(); const [sticker] = await Promise.all([ - new Sticker({ + OrmUtils.mergeDeep(new Sticker(), { ...body, guild_id, id, @@ -79,25 +80,9 @@ router.get("/:sticker_id", route({}), async (req: Request, res: Response) => { const { guild_id, sticker_id } = req.params; await Member.IsInGuildOrFail(req.user_id, guild_id); - res.json(await Sticker.findOneOrFail({ guild_id, id: sticker_id })); + res.json(await Sticker.findOneOrFail({ where: { guild_id, id: sticker_id } })); }); -export interface ModifyGuildStickerSchema { - /** - * @minLength 2 - * @maxLength 30 - */ - name: string; - /** - * @maxLength 100 - */ - description?: string; - /** - * @maxLength 200 - */ - tags: string; -} - router.patch( "/:sticker_id", route({ body: "ModifyGuildStickerSchema", permission: "MANAGE_EMOJIS_AND_STICKERS" }), @@ -105,7 +90,7 @@ router.patch( const { guild_id, sticker_id } = req.params; const body = req.body as ModifyGuildStickerSchema; - const sticker = await new Sticker({ ...body, guild_id, id: sticker_id }).save(); + const sticker = await OrmUtils.mergeDeep(new Sticker(), { ...body, guild_id, id: sticker_id }).save(); await sendStickerUpdateEvent(guild_id); return res.json(sticker); @@ -118,7 +103,7 @@ async function sendStickerUpdateEvent(guild_id: string) { guild_id: guild_id, data: { guild_id: guild_id, - stickers: await Sticker.find({ guild_id: guild_id }) + stickers: await Sticker.find({ where: { guild_id } }) } } as GuildStickersUpdateEvent); } diff --git a/api/src/routes/guilds/#guild_id/templates.ts b/src/api/routes/guilds/#guild_id/templates.ts
index 5179e761..448ee033 100644 --- a/api/src/routes/guilds/#guild_id/templates.ts +++ b/src/api/routes/guilds/#guild_id/templates.ts
@@ -1,8 +1,6 @@ +import { generateCode, route } from "@fosscord/api"; +import { Guild, HTTPError, OrmUtils, Template } from "@fosscord/util"; import { Request, Response, Router } from "express"; -import { Guild, Template } from "@fosscord/util"; -import { HTTPError } from "lambert-server"; -import { route } from "@fosscord/api"; -import { generateCode } from "@fosscord/api"; const router: Router = Router(); @@ -23,20 +21,10 @@ const TemplateGuildProjection: (keyof Guild)[] = [ "icon" ]; -export interface TemplateCreateSchema { - name: string; - description?: string; -} - -export interface TemplateModifySchema { - name: string; - description?: string; -} - router.get("/", route({}), async (req: Request, res: Response) => { const { guild_id } = req.params; - var templates = await Template.find({ source_guild_id: guild_id }); + let templates = await Template.find({ where: { source_guild_id: guild_id } }); return res.json(templates); }); @@ -44,10 +32,10 @@ router.get("/", route({}), async (req: Request, res: Response) => { router.post("/", route({ body: "TemplateCreateSchema", permission: "MANAGE_GUILD" }), async (req: Request, res: Response) => { const { guild_id } = req.params; const guild = await Guild.findOneOrFail({ where: { id: guild_id }, select: TemplateGuildProjection }); - const exists = await Template.findOneOrFail({ id: guild_id }).catch((e) => {}); + const exists = await Template.findOneOrFail({ where: { id: guild_id } }).catch((e) => {}); if (exists) throw new HTTPError("Template already exists", 400); - const template = await new Template({ + const template = await OrmUtils.mergeDeep(new Template(), { ...req.body, code: generateCode(), creator_id: req.user_id, @@ -75,7 +63,7 @@ router.put("/:code", route({ permission: "MANAGE_GUILD" }), async (req: Request, const { code, guild_id } = req.params; const guild = await Guild.findOneOrFail({ where: { id: guild_id }, select: TemplateGuildProjection }); - const template = await new Template({ code, serialized_source_guild: guild }).save(); + const template = await OrmUtils.mergeDeep(new Template(), { code, serialized_source_guild: guild }).save(); res.json(template); }); @@ -84,7 +72,12 @@ router.patch("/:code", route({ body: "TemplateModifySchema", permission: "MANAGE const { code, guild_id } = req.params; const { name, description } = req.body; - const template = await new Template({ code, name: name, description: description, source_guild_id: guild_id }).save(); + const template = await OrmUtils.mergeDeep(new Template(), { + code, + name: name, + description: description, + source_guild_id: guild_id + }).save(); res.json(template); }); diff --git a/api/src/routes/guilds/#guild_id/vanity-url.ts b/src/api/routes/guilds/#guild_id/vanity-url.ts
index 29cd25e2..bf2db134 100644 --- a/api/src/routes/guilds/#guild_id/vanity-url.ts +++ b/src/api/routes/guilds/#guild_id/vanity-url.ts
@@ -1,7 +1,6 @@ -import { Channel, ChannelType, getPermission, Guild, Invite, trimSpecial } from "@fosscord/util"; -import { Router, Request, Response } from "express"; import { route } from "@fosscord/api"; -import { HTTPError } from "lambert-server"; +import { Channel, ChannelType, Guild, HTTPError, Invite, OrmUtils, VanityUrlSchema } from "@fosscord/util"; +import { Request, Response, Router } from "express"; const router = Router(); @@ -9,7 +8,7 @@ const InviteRegex = /\W/g; router.get("/", route({ permission: "MANAGE_GUILD" }), async (req: Request, res: Response) => { const { guild_id } = req.params; - const guild = await Guild.findOneOrFail({ id: guild_id }); + const guild = await Guild.findOneOrFail({ where: { id: guild_id } }); if (!guild.features.includes("ALIASABLE_NAMES")) { const invite = await Invite.findOne({ where: { guild_id: guild_id, vanity_url: true } }); @@ -24,30 +23,22 @@ router.get("/", route({ permission: "MANAGE_GUILD" }), async (req: Request, res: } }); -export interface VanityUrlSchema { - /** - * @minLength 1 - * @maxLength 20 - */ - code?: string; -} - router.patch("/", route({ body: "VanityUrlSchema", permission: "MANAGE_GUILD" }), async (req: Request, res: Response) => { const { guild_id } = req.params; const body = req.body as VanityUrlSchema; const code = body.code?.replace(InviteRegex, ""); - const guild = await Guild.findOneOrFail({ id: guild_id }); + const guild = await Guild.findOneOrFail({ where: { id: guild_id } }); if (!guild.features.includes("VANITY_URL")) throw new HTTPError("Your guild doesn't support vanity urls"); if (!code || code.length === 0) throw new HTTPError("Code cannot be null or empty"); - const invite = await Invite.findOne({ code }); + const invite = await Invite.findOne({ where: { code } }); if (invite) throw new HTTPError("Invite already exists"); - const { id } = await Channel.findOneOrFail({ guild_id, type: ChannelType.GUILD_TEXT }); + const { id } = await Channel.findOneOrFail({ where: { guild_id, type: ChannelType.GUILD_TEXT } }); - await new Invite({ + await OrmUtils.mergeDeep(new Invite(), { vanity_url: true, code: code, temporary: false, @@ -60,7 +51,7 @@ router.patch("/", route({ body: "VanityUrlSchema", permission: "MANAGE_GUILD" }) channel_id: id }).save(); - return res.json({ code: code }); + return res.json({ where: { code } }); }); export default router; diff --git a/api/src/routes/guilds/#guild_id/voice-states/#user_id/index.ts b/src/api/routes/guilds/#guild_id/voice-states/#user_id/index.ts
index f9fbea54..797d348e 100644 --- a/api/src/routes/guilds/#guild_id/voice-states/#user_id/index.ts +++ b/src/api/routes/guilds/#guild_id/voice-states/#user_id/index.ts
@@ -1,23 +1,21 @@ -import { Channel, ChannelType, DiscordApiErrors, emitEvent, getPermission, VoiceState, VoiceStateUpdateEvent } from "@fosscord/util"; import { route } from "@fosscord/api"; +import { + Channel, + ChannelType, + DiscordApiErrors, + emitEvent, + getPermission, + OrmUtils, + VoiceState, + VoiceStateUpdateEvent, + VoiceStateUpdateSchema +} from "@fosscord/util"; import { Request, Response, Router } from "express"; const router = Router(); -//TODO need more testing when community guild and voice stage channel are working - -export interface VoiceStateUpdateSchema { - channel_id: string; - guild_id?: string; - suppress?: boolean; - request_to_speak_timestamp?: Date; - self_mute?: boolean; - self_deaf?: boolean; - self_video?: boolean; -} - router.patch("/", route({ body: "VoiceStateUpdateSchema" }), async (req: Request, res: Response) => { const body = req.body as VoiceStateUpdateSchema; - var { guild_id, user_id } = req.params; + let { guild_id, user_id } = req.params; if (user_id === "@me") user_id = req.user_id; const perms = await getPermission(req.user_id, guild_id, body.channel_id); @@ -33,15 +31,17 @@ router.patch("/", route({ body: "VoiceStateUpdateSchema" }), async (req: Request if (!body.suppress) body.request_to_speak_timestamp = new Date(); if (body.request_to_speak_timestamp) perms.hasThrow("REQUEST_TO_SPEAK"); - const voice_state = await VoiceState.findOne({ - guild_id, - channel_id: body.channel_id, - user_id + let voice_state = await VoiceState.findOne({ + where: { + guild_id, + channel_id: body.channel_id, + user_id + } }); if (!voice_state) throw DiscordApiErrors.UNKNOWN_VOICE_STATE; - voice_state.assign(body); - const channel = await Channel.findOneOrFail({ guild_id, id: body.channel_id }); + voice_state = OrmUtils.mergeDeep(voice_state, body) as VoiceState; + const channel = await Channel.findOneOrFail({ where: { guild_id, id: body.channel_id } }); if (channel.type !== ChannelType.GUILD_STAGE_VOICE) { throw DiscordApiErrors.CANNOT_EXECUTE_ON_THIS_CHANNEL_TYPE; } diff --git a/src/api/routes/guilds/#guild_id/webhooks.ts b/src/api/routes/guilds/#guild_id/webhooks.ts new file mode 100644
index 00000000..80e6a59a --- /dev/null +++ b/src/api/routes/guilds/#guild_id/webhooks.ts
@@ -0,0 +1,9 @@ +import { route } from "@fosscord/api"; +import { Request, Response, Router } from "express"; +const router = Router(); + +//TODO: implement webhooks +router.get("/", route({}), async (req: Request, res: Response) => { + res.json([]); +}); +export default router; diff --git a/api/src/routes/guilds/#guild_id/welcome_screen.ts b/src/api/routes/guilds/#guild_id/welcome_screen.ts
index 7141f17e..85c22a19 100644 --- a/api/src/routes/guilds/#guild_id/welcome_screen.ts +++ b/src/api/routes/guilds/#guild_id/welcome_screen.ts
@@ -1,25 +1,13 @@ -import { Request, Response, Router } from "express"; -import { Guild, getPermission, Snowflake, Member } from "@fosscord/util"; -import { HTTPError } from "lambert-server"; import { route } from "@fosscord/api"; +import { Guild, GuildUpdateWelcomeScreenSchema, HTTPError, Member } from "@fosscord/util"; +import { Request, Response, Router } from "express"; const router: Router = Router(); -export interface GuildUpdateWelcomeScreenSchema { - welcome_channels?: { - channel_id: string; - description: string; - emoji_id?: string; - emoji_name: string; - }[]; - enabled?: boolean; - description?: string; -} - router.get("/", route({}), async (req: Request, res: Response) => { const guild_id = req.params.guild_id; - const guild = await Guild.findOneOrFail({ id: guild_id }); + const guild = await Guild.findOneOrFail({ where: { id: guild_id } }); await Member.IsInGuildOrFail(req.user_id, guild_id); res.json(guild.welcome_screen); @@ -29,7 +17,7 @@ router.patch("/", route({ body: "GuildUpdateWelcomeScreenSchema", permission: "M const guild_id = req.params.guild_id; const body = req.body as GuildUpdateWelcomeScreenSchema; - const guild = await Guild.findOneOrFail({ id: guild_id }); + const guild = await Guild.findOneOrFail({ where: { id: guild_id } }); if (!guild.welcome_screen.enabled) throw new HTTPError("Welcome screen disabled", 400); if (body.welcome_channels) guild.welcome_screen.welcome_channels = body.welcome_channels; // TODO: check if they exist and are valid diff --git a/api/src/routes/guilds/#guild_id/widget.json.ts b/src/api/routes/guilds/#guild_id/widget.json.ts
index c31519fa..368fe46e 100644 --- a/api/src/routes/guilds/#guild_id/widget.json.ts +++ b/src/api/routes/guilds/#guild_id/widget.json.ts
@@ -1,7 +1,6 @@ -import { Request, Response, Router } from "express"; -import { Config, Permissions, Guild, Invite, Channel, Member } from "@fosscord/util"; -import { HTTPError } from "lambert-server"; import { random, route } from "@fosscord/api"; +import { Channel, Guild, HTTPError, Invite, Member, OrmUtils, Permissions } from "@fosscord/util"; +import { Request, Response, Router } from "express"; const router: Router = Router(); @@ -17,11 +16,11 @@ const router: Router = Router(); router.get("/", route({}), async (req: Request, res: Response) => { const { guild_id } = req.params; - const guild = await Guild.findOneOrFail({ id: guild_id }); + const guild = await Guild.findOneOrFail({ where: { id: guild_id } }); if (!guild.widget_enabled) throw new HTTPError("Widget Disabled", 404); // Fetch existing widget invite for widget channel - var invite = await Invite.findOne({ channel_id: guild.widget_channel_id }); + let invite = await Invite.findOne({ where: { channel_id: guild.widget_channel_id } }); if (guild.widget_channel_id && !invite) { // Create invite for channel if none exists @@ -41,7 +40,7 @@ router.get("/", route({}), async (req: Request, res: Response) => { inviter_id: null }; - invite = await new Invite(body).save(); + invite = await OrmUtils.mergeDeep(new Invite(), body).save(); } // Fetch voice channels, and the @everyone permissions object @@ -63,7 +62,7 @@ router.get("/", route({}), async (req: Request, res: Response) => { // Fetch members // TODO: Understand how Discord's max 100 random member sample works, and apply to here (see top of this file) - let members = await Member.find({ guild_id: guild_id }); + let members = await Member.find({ where: { guild_id } }); // Construct object to respond with const data = { diff --git a/api/src/routes/guilds/#guild_id/widget.png.ts b/src/api/routes/guilds/#guild_id/widget.png.ts
index 4c82b740..1c4ef29b 100644 --- a/api/src/routes/guilds/#guild_id/widget.png.ts +++ b/src/api/routes/guilds/#guild_id/widget.png.ts
@@ -1,10 +1,19 @@ -import { Request, Response, Router } from "express"; -import { Guild } from "@fosscord/util"; -import { HTTPError } from "lambert-server"; import { route } from "@fosscord/api"; +import { Guild, HTTPError } from "@fosscord/util"; +import { Request, Response, Router } from "express"; import fs from "fs"; import path from "path"; +// Setup canvas +let createCanvas: any, loadImage: any; +try { + createCanvas = require("canvas").createCanvas; + loadImage = require("canvas").loadImage; +} catch { + console.log("Canvas not found, disabling widgets!"); +} +const sizeOf = require("image-size"); + const router: Router = Router(); // TODO: use svg templates instead of node-canvas for improved performance and to change it easily @@ -12,9 +21,10 @@ const router: Router = Router(); // https://discord.com/developers/docs/resources/guild#get-guild-widget-image // TODO: Cache the response router.get("/", route({}), async (req: Request, res: Response) => { + if (!createCanvas) return res.status(404); const { guild_id } = req.params; - const guild = await Guild.findOneOrFail({ id: guild_id }); + const guild = await Guild.findOneOrFail({ where: { id: guild_id } }); if (!guild.widget_enabled) throw new HTTPError("Unknown Guild", 404); // Fetch guild information @@ -28,13 +38,8 @@ router.get("/", route({}), async (req: Request, res: Response) => { throw new HTTPError("Value must be one of ('shield', 'banner1', 'banner2', 'banner3', 'banner4').", 400); } - // Setup canvas - const { createCanvas } = require("canvas"); - const { loadImage } = require("canvas"); - const sizeOf = require("image-size"); - // TODO: Widget style templates need Fosscord branding - const source = path.join(__dirname, "..", "..", "..", "..", "assets", "widget", `${style}.png`); + const source = path.join(__dirname, "..", "..", "..", "..", "..", "assets", "widget", `${style}.png`); if (!fs.existsSync(source)) { throw new HTTPError("Widget template does not exist.", 400); } diff --git a/api/src/routes/guilds/#guild_id/widget.ts b/src/api/routes/guilds/#guild_id/widget.ts
index 2640618d..d2369dd1 100644 --- a/api/src/routes/guilds/#guild_id/widget.ts +++ b/src/api/routes/guilds/#guild_id/widget.ts
@@ -1,11 +1,6 @@ -import { Request, Response, Router } from "express"; -import { Guild } from "@fosscord/util"; import { route } from "@fosscord/api"; - -export interface WidgetModifySchema { - enabled: boolean; // whether the widget is enabled - channel_id: string; // the widget channel id -} +import { Guild, WidgetModifySchema } from "@fosscord/util"; +import { Request, Response, Router } from "express"; const router: Router = Router(); @@ -13,7 +8,7 @@ const router: Router = Router(); router.get("/", route({}), async (req: Request, res: Response) => { const { guild_id } = req.params; - const guild = await Guild.findOneOrFail({ id: guild_id }); + const guild = await Guild.findOneOrFail({ where: { id: guild_id } }); return res.json({ enabled: guild.widget_enabled || false, channel_id: guild.widget_channel_id || null }); }); diff --git a/api/src/routes/guilds/index.ts b/src/api/routes/guilds/index.ts
index 10721413..6946e2f7 100644 --- a/api/src/routes/guilds/index.ts +++ b/src/api/routes/guilds/index.ts
@@ -1,32 +1,18 @@ -import { Router, Request, Response } from "express"; -import { Role, Guild, Snowflake, Config, getRights, Member, Channel, DiscordApiErrors, handleFile } from "@fosscord/util"; import { route } from "@fosscord/api"; -import { ChannelModifySchema } from "../channels/#channel_id"; +import { Config, DiscordApiErrors, getRights, Guild, GuildCreateSchema, Member } from "@fosscord/util"; +import { Request, Response, Router } from "express"; const router: Router = Router(); -export interface GuildCreateSchema { - /** - * @maxLength 100 - */ - name: string; - region?: string; - icon?: string | null; - channels?: ChannelModifySchema[]; - guild_template_code?: string; - system_channel_id?: string; - rules_channel_id?: string; -} - //TODO: create default channel router.post("/", route({ body: "GuildCreateSchema", right: "CREATE_GUILDS" }), async (req: Request, res: Response) => { const body = req.body as GuildCreateSchema; const { maxGuilds } = Config.get().limits.user; - const guild_count = await Member.count({ id: req.user_id }); + const guild_count = await Member.count({ where: { id: req.user_id } }); const rights = await getRights(req.user_id); - if ((guild_count >= maxGuilds)&&!rights.has("MANAGE_GUILDS")) { + if (guild_count >= maxGuilds && !rights.has("MANAGE_GUILDS")) { throw DiscordApiErrors.MAXIMUM_GUILDS.withParams(maxGuilds); } diff --git a/api/src/routes/guilds/templates/index.ts b/src/api/routes/guilds/templates/index.ts
index 3d922e85..467186a3 100644 --- a/api/src/routes/guilds/templates/index.ts +++ b/src/api/routes/guilds/templates/index.ts
@@ -1,23 +1,18 @@ -import { Request, Response, Router } from "express"; -import { Template, Guild, Role, Snowflake, Config, User, Member } from "@fosscord/util"; import { route } from "@fosscord/api"; -import { DiscordApiErrors } from "@fosscord/util"; +import { Config, DiscordApiErrors, Guild, GuildTemplateCreateSchema, Member, OrmUtils, Role, Snowflake, Template } from "@fosscord/util"; +import { Request, Response, Router } from "express"; import fetch from "node-fetch"; const router: Router = Router(); -export interface GuildTemplateCreateSchema { - name: string; - avatar?: string | null; -} - router.get("/:code", route({}), async (req: Request, res: Response) => { const { allowDiscordTemplates, allowRaws, enabled } = Config.get().templates; if (!enabled) res.json({ code: 403, message: "Template creation & usage is disabled on this instance." }).sendStatus(403); const { code } = req.params; - + if (code.startsWith("discord:")) { - if (!allowDiscordTemplates) return res.json({ code: 403, message: "Discord templates cannot be used on this instance." }).sendStatus(403); + if (!allowDiscordTemplates) + return res.json({ code: 403, message: "Discord templates cannot be used on this instance." }).sendStatus(403); const discordTemplateID = code.split("discord:", 2)[1]; const discordTemplateData = await fetch(`https://discord.com/api/v9/guilds/templates/${discordTemplateID}`, { @@ -28,12 +23,12 @@ router.get("/:code", route({}), async (req: Request, res: Response) => { } if (code.startsWith("external:")) { - if (!allowRaws) return res.json({ code: 403, message: "Importing raws is disabled on this instance." }).sendStatus(403); + if (!allowRaws) return res.json({ code: 403, message: "Importing raws is disabled on this instance." }).sendStatus(403); return res.json(code.split("external:", 2)[1]); } - const template = await Template.findOneOrFail({ code: code }); + const template = await Template.findOneOrFail({ where: { code } }); res.json(template); }); @@ -47,34 +42,36 @@ router.post("/:code", route({ body: "GuildTemplateCreateSchema" }), async (req: const { maxGuilds } = Config.get().limits.user; - const guild_count = await Member.count({ id: req.user_id }); + const guild_count = await Member.count({ where: { id: req.user_id } }); if (guild_count >= maxGuilds) { throw DiscordApiErrors.MAXIMUM_GUILDS.withParams(maxGuilds); } - const template = await Template.findOneOrFail({ code: code }); + const template = await Template.findOneOrFail({ where: { code } }); const guild_id = Snowflake.generate(); const [guild, role] = await Promise.all([ - new Guild({ + OrmUtils.mergeDeep(new Guild(), { ...body, ...template.serialized_source_guild, id: guild_id, owner_id: req.user_id }).save(), - new Role({ - id: guild_id, - guild_id: guild_id, - color: 0, - hoist: false, - managed: true, - mentionable: true, - name: "@everyone", - permissions: BigInt("2251804225"), - position: 0, - tags: null - }).save() + ( + OrmUtils.mergeDeep(new Role(), { + id: guild_id, + guild_id: guild_id, + color: 0, + hoist: false, + managed: true, + mentionable: true, + name: "@everyone", + permissions: BigInt("2251804225"), + position: 0, + tags: null + }) as Role + ).save() ]); await Member.addToGuild(req.user_id, guild_id); diff --git a/api/src/routes/invites/index.ts b/src/api/routes/invites/index.ts
index eeafb22a..73c9324c 100644 --- a/api/src/routes/invites/index.ts +++ b/src/api/routes/invites/index.ts
@@ -1,7 +1,6 @@ -import { Router, Request, Response } from "express"; -import { emitEvent, getPermission, Guild, Invite, InviteDeleteEvent, User, PublicInviteRelation } from "@fosscord/util"; import { route } from "@fosscord/api"; -import { HTTPError } from "lambert-server"; +import { emitEvent, getPermission, Guild, HTTPError, Invite, InviteDeleteEvent, PublicInviteRelation, User } from "@fosscord/util"; +import { Request, Response, Router } from "express"; const router: Router = Router(); @@ -13,15 +12,16 @@ router.get("/:code", route({}), async (req: Request, res: Response) => { res.status(200).send(invite); }); -router.post("/:code", route({right: "USE_MASS_INVITES"}), async (req: Request, res: Response) => { +router.post("/:code", route({ right: "USE_MASS_INVITES" }), async (req: Request, res: Response) => { const { code } = req.params; - const { guild_id } = await Invite.findOneOrFail({ code }) - const { features } = await Guild.findOneOrFail({ id: guild_id}); - const { public_flags } = await User.findOneOrFail({ id: req.user_id }); - - if(features.includes("INTERNAL_EMPLOYEE_ONLY") && (public_flags & 1) !== 1) throw new HTTPError("Only intended for the staff of this server.", 401); - if(features.includes("INVITES_CLOSED")) throw new HTTPError("Sorry, this guild has joins closed.", 403); - + const { guild_id } = await Invite.findOneOrFail({ where: { code } }); + const { features } = await Guild.findOneOrFail({ where: { id: guild_id } }); + const { public_flags } = await User.findOneOrFail({ where: { id: req.user_id } }); + + if (features.includes("INTERNAL_EMPLOYEE_ONLY") && (public_flags & 1) !== 1) + throw new HTTPError("Only intended for the staff of this server.", 401); + if (features.includes("INVITES_CLOSED")) throw new HTTPError("Sorry, this guild has joins closed.", 403); + const invite = await Invite.joinGuild(req.user_id, code); res.json(invite); @@ -30,7 +30,7 @@ router.post("/:code", route({right: "USE_MASS_INVITES"}), async (req: Request, r // * cant use permission of route() function because path doesn't have guild_id/channel_id router.delete("/:code", route({}), async (req: Request, res: Response) => { const { code } = req.params; - const invite = await Invite.findOneOrFail({ code }); + const invite = await Invite.findOneOrFail({ where: { code } }); const { guild_id, channel_id } = invite; const permission = await getPermission(req.user_id, guild_id, channel_id); diff --git a/api/src/routes/oauth2/tokens.ts b/src/api/routes/oauth2/tokens.ts
index bd284221..831dc7af 100644 --- a/api/src/routes/oauth2/tokens.ts +++ b/src/api/routes/oauth2/tokens.ts
@@ -1,5 +1,5 @@ -import { Router, Request, Response } from "express"; import { route } from "@fosscord/api"; +import { Request, Response, Router } from "express"; const router = Router(); router.get("/", route({}), async (req: Request, res: Response) => { diff --git a/api/src/routes/outbound-promotions.ts b/src/api/routes/outbound-promotions.ts
index 411e95bf..8e407184 100644 --- a/api/src/routes/outbound-promotions.ts +++ b/src/api/routes/outbound-promotions.ts
@@ -1,5 +1,5 @@ -import { Request, Response, Router } from "express"; import { route } from "@fosscord/api"; +import { Request, Response, Router } from "express"; const router: Router = Router(); diff --git a/src/api/routes/partners/#guild_id/requirements.ts b/src/api/routes/partners/#guild_id/requirements.ts new file mode 100644
index 00000000..c0260fe7 --- /dev/null +++ b/src/api/routes/partners/#guild_id/requirements.ts
@@ -0,0 +1,37 @@ +import { route } from "@fosscord/api"; +import { Request, Response, Router } from "express"; + +const router = Router(); + +router.get("/", route({}), async (req: Request, res: Response) => { + const { guild_id } = req.params; + // TODO: + // Load from database + // Admin control, but for now it allows anyone to be discoverable + + res.send({ + guild_id: guild_id, + safe_environment: true, + healthy: true, + health_score_pending: false, + size: true, + nsfw_properties: {}, + protected: true, + sufficient: true, + sufficient_without_grace_period: true, + valid_rules_channel: true, + retention_healthy: true, + engagement_healthy: true, + age: true, + minimum_age: 0, + health_score: { + avg_nonnew_participators: 0, + avg_nonnew_communicators: 0, + num_intentful_joiners: 0, + perc_ret_w1_intentful: 0 + }, + minimum_size: 0 + }); +}); + +export default router; diff --git a/api/src/routes/ping.ts b/src/api/routes/ping.ts
index 3c1da2c3..5f1b0174 100644 --- a/api/src/routes/ping.ts +++ b/src/api/routes/ping.ts
@@ -1,6 +1,6 @@ -import { Router, Response, Request } from "express"; import { route } from "@fosscord/api"; import { Config } from "@fosscord/util"; +import { Request, Response, Router } from "express"; const router = Router(); @@ -18,8 +18,8 @@ router.get("/", route({}), (req: Request, res: Response) => { correspondenceUserID: general.correspondenceUserID, frontPage: general.frontPage, - tosPage: general.tosPage, - }, + tosPage: general.tosPage + } }); }); diff --git a/src/api/routes/policies/instance/domains.ts b/src/api/routes/policies/instance/domains.ts new file mode 100644
index 00000000..fed0a627 --- /dev/null +++ b/src/api/routes/policies/instance/domains.ts
@@ -0,0 +1,17 @@ +import { route } from "@fosscord/api"; +import { Config } from "@fosscord/util"; +import { Request, Response, Router } from "express"; +const router = Router(); + +router.get("/", route({}), async (req: Request, res: Response) => { + const { cdn, gateway } = Config.get(); + + const IdentityForm = { + cdn: cdn.endpointPublic || process.env.CDN || "http://localhost:3001", + gateway: gateway.endpointPublic || process.env.GATEWAY || "ws://localhost:3002" + }; + + res.json(IdentityForm); +}); + +export default router; diff --git a/api/src/routes/policies/instance/index.ts b/src/api/routes/policies/instance/index.ts
index e3da014f..a8ffd285 100644 --- a/api/src/routes/policies/instance/index.ts +++ b/src/api/routes/policies/instance/index.ts
@@ -1,10 +1,9 @@ -import { Router, Request, Response } from "express"; import { route } from "@fosscord/api"; import { Config } from "@fosscord/util"; +import { Request, Response, Router } from "express"; const router = Router(); - -router.get("/",route({}), async (req: Request, res: Response) => { +router.get("/", route({}), async (req: Request, res: Response) => { const { general } = Config.get(); res.json(general); }); diff --git a/api/src/routes/policies/instance/limits.ts b/src/api/routes/policies/instance/limits.ts
index 7de1476b..0d42fc7b 100644 --- a/api/src/routes/policies/instance/limits.ts +++ b/src/api/routes/policies/instance/limits.ts
@@ -1,9 +1,9 @@ -import { Router, Request, Response } from "express"; import { route } from "@fosscord/api"; import { Config } from "@fosscord/util"; +import { Request, Response, Router } from "express"; const router = Router(); -router.get("/",route({}), async (req: Request, res: Response) => { +router.get("/", route({}), async (req: Request, res: Response) => { const { limits } = Config.get(); res.json(limits); }); diff --git a/src/api/routes/scheduled-maintenances/upcoming_json.ts b/src/api/routes/scheduled-maintenances/upcoming_json.ts new file mode 100644
index 00000000..ec4ddc7c --- /dev/null +++ b/src/api/routes/scheduled-maintenances/upcoming_json.ts
@@ -0,0 +1,12 @@ +import { route } from "@fosscord/api"; +import { Request, Response, Router } from "express"; +const router = Router(); + +router.get("/scheduled-maintenances/upcoming.json", route({}), async (req: Request, res: Response) => { + res.json({ + page: {}, + scheduled_maintenances: {} + }); +}); + +export default router; diff --git a/api/src/routes/science.ts b/src/api/routes/science.ts
index 8556a3ad..cb01e576 100644 --- a/api/src/routes/science.ts +++ b/src/api/routes/science.ts
@@ -1,5 +1,5 @@ -import { Router, Response, Request } from "express"; import { route } from "@fosscord/api"; +import { Request, Response, Router } from "express"; const router = Router(); diff --git a/api/src/routes/stage-instances.ts b/src/api/routes/stage-instances.ts
index 411e95bf..8e407184 100644 --- a/api/src/routes/stage-instances.ts +++ b/src/api/routes/stage-instances.ts
@@ -1,5 +1,5 @@ -import { Request, Response, Router } from "express"; import { route } from "@fosscord/api"; +import { Request, Response, Router } from "express"; const router: Router = Router(); diff --git a/api/src/routes/sticker-packs/index.ts b/src/api/routes/sticker-packs/index.ts
index e6560d12..dddc7f70 100644 --- a/api/src/routes/sticker-packs/index.ts +++ b/src/api/routes/sticker-packs/index.ts
@@ -1,6 +1,6 @@ -import { Request, Response, Router } from "express"; import { route } from "@fosscord/api"; import { StickerPack } from "@fosscord/util"; +import { Request, Response, Router } from "express"; const router: Router = Router(); diff --git a/api/src/routes/stickers/#sticker_id/index.ts b/src/api/routes/stickers/#sticker_id/index.ts
index 293ca089..16eb2059 100644 --- a/api/src/routes/stickers/#sticker_id/index.ts +++ b/src/api/routes/stickers/#sticker_id/index.ts
@@ -1,12 +1,12 @@ -import { Sticker } from "@fosscord/util"; -import { Router, Request, Response } from "express"; import { route } from "@fosscord/api"; +import { Sticker } from "@fosscord/util"; +import { Request, Response, Router } from "express"; const router = Router(); router.get("/", route({}), async (req: Request, res: Response) => { const { sticker_id } = req.params; - res.json(await Sticker.find({ id: sticker_id })); + res.json(await Sticker.find({ where: { id: sticker_id } })); }); export default router; diff --git a/api/src/routes/stop.ts b/src/api/routes/stop.ts
index 7f8b78ba..fb77b4f3 100644 --- a/api/src/routes/stop.ts +++ b/src/api/routes/stop.ts
@@ -1,22 +1,21 @@ -import { Router, Request, Response } from "express"; import { route } from "@fosscord/api"; import { User } from "@fosscord/util"; +import { Request, Response, Router } from "express"; const router: Router = Router(); router.post("/", route({}), async (req: Request, res: Response) => { //EXPERIMENTAL: have an "OPERATOR" platform permission implemented for this API route const user = await User.findOneOrFail({ where: { id: req.user_id }, select: ["rights"] }); - if((Number(user.rights) << Number(0))%Number(2)==Number(1)) { + if ((Number(user.rights) << Number(0)) % Number(2) == Number(1)) { console.log("user that POSTed to the API was ALLOWED"); console.log(user.rights); - res.sendStatus(200) - process.kill(process.pid, 'SIGTERM') - } - else { + res.sendStatus(200); + process.kill(process.pid, "SIGTERM"); + } else { console.log("operation failed"); console.log(user.rights); - res.sendStatus(403) + res.sendStatus(403); } }); diff --git a/api/src/routes/store/published-listings/applications.ts b/src/api/routes/store/published-listings/applications.ts
index 060a4c3d..3d0f7998 100644 --- a/api/src/routes/store/published-listings/applications.ts +++ b/src/api/routes/store/published-listings/applications.ts
@@ -1,5 +1,5 @@ -import { Request, Response, Router } from "express"; import { route } from "@fosscord/api"; +import { Request, Response, Router } from "express"; const router: Router = Router(); diff --git a/api/src/routes/store/published-listings/applications/#id/subscription-plans.ts b/src/api/routes/store/published-listings/applications/#id/subscription-plans.ts
index 54151ae5..86fce75d 100644 --- a/api/src/routes/store/published-listings/applications/#id/subscription-plans.ts +++ b/src/api/routes/store/published-listings/applications/#id/subscription-plans.ts
@@ -1,5 +1,5 @@ -import { Request, Response, Router } from "express"; import { route } from "@fosscord/api"; +import { Request, Response, Router } from "express"; const router: Router = Router(); diff --git a/api/src/routes/store/published-listings/skus.ts b/src/api/routes/store/published-listings/skus.ts
index 060a4c3d..3d0f7998 100644 --- a/api/src/routes/store/published-listings/skus.ts +++ b/src/api/routes/store/published-listings/skus.ts
@@ -1,5 +1,5 @@ -import { Request, Response, Router } from "express"; import { route } from "@fosscord/api"; +import { Request, Response, Router } from "express"; const router: Router = Router(); diff --git a/src/api/routes/store/published-listings/skus/#sku_id/subscription-plans.ts b/src/api/routes/store/published-listings/skus/#sku_id/subscription-plans.ts new file mode 100644
index 00000000..f8c78bc5 --- /dev/null +++ b/src/api/routes/store/published-listings/skus/#sku_id/subscription-plans.ts
@@ -0,0 +1,313 @@ +import { route } from "@fosscord/api"; +import { Request, Response, Router } from "express"; + +const router: Router = Router(); + +const skus = new Map([ + [ + "521842865731534868", + [ + { + id: "511651856145973248", + name: "Individual Premium Tier 3 Monthly (Legacy)", + interval: 1, + interval_count: 1, + tax_inclusive: true, + sku_id: "521842865731534868", + currency: "eur", + price: 0, + price_tier: null + }, + { + id: "511651860671627264", + name: "Individiual Premium Tier 3 Yearly (Legacy)", + interval: 2, + interval_count: 1, + tax_inclusive: true, + sku_id: "521842865731534868", + currency: "eur", + price: 0, + price_tier: null + } + ] + ], + [ + "521846918637420545", + [ + { + id: "511651871736201216", + name: "Individual Premium Tier 2 Monthly", + interval: 1, + interval_count: 1, + tax_inclusive: true, + sku_id: "521846918637420545", + currency: "eur", + price: 0, + price_tier: null + }, + { + id: "511651876987469824", + name: "Individual Premum Tier 2 Yearly", + interval: 2, + interval_count: 1, + tax_inclusive: true, + sku_id: "521846918637420545", + currency: "eur", + price: 0, + price_tier: null + }, + { + id: "978380684370378761", + name: "Individual Premum Tier 1", + interval: 2, + interval_count: 1, + tax_inclusive: true, + sku_id: "521846918637420545", + currency: "eur", + price: 0, + price_tier: null + } + ] + ], + [ + "521847234246082599", + [ + { + id: "642251038925127690", + name: "Individual Premium Tier 3 Quarterly", + interval: 1, + interval_count: 3, + tax_inclusive: true, + sku_id: "521847234246082599", + currency: "eur", + price: 0, + price_tier: null + }, + { + id: "511651880837840896", + name: "Individual Premium Tier 3 Monthly", + interval: 1, + interval_count: 1, + tax_inclusive: true, + sku_id: "521847234246082599", + currency: "eur", + price: 0, + price_tier: null + }, + { + id: "511651885459963904", + name: "Individual Premium Tier 3 Yearly", + interval: 2, + interval_count: 1, + tax_inclusive: true, + sku_id: "521847234246082599", + currency: "eur", + price: 0, + price_tier: null + } + ] + ], + [ + "590663762298667008", + [ + { + id: "590665532894740483", + name: "Crowd Premium Monthly", + interval: 1, + interval_count: 1, + tax_inclusive: true, + sku_id: "590663762298667008", + discount_price: 0, + currency: "eur", + price: 0, + price_tier: null + }, + { + id: "590665538238152709", + name: "Crowd Premium Yearly", + interval: 2, + interval_count: 1, + tax_inclusive: true, + sku_id: "590663762298667008", + discount_price: 0, + currency: "eur", + price: 0, + price_tier: null + } + ], + ], + [ + "978380684370378762", + [ + [ + { + "id": "978380692553465866", + "name": "Premium Tier 0 Monthly", + "interval": 1, + "interval_count": 1, + "tax_inclusive": true, + "sku_id": "978380684370378762", + "currency": "usd", + "price": 299, + "price_tier": null, + "prices": { + "0": { + "country_prices": { + "country_code": "US", + "prices": [ + { + "currency": "usd", + "amount": 0, + "exponent": 2 + } + ] + }, + "payment_source_prices": { + "775487223059316758": [ + { + "currency": "usd", + "amount": 0, + "exponent": 2 + } + ], + "736345864146255982": [ + { + "currency": "usd", + "amount": 0, + "exponent": 2 + } + ], + "683074999590060249": [ + { + "currency": "usd", + "amount": 0, + "exponent": 2 + } + ] + } + }, + "3": { + "country_prices": { + "country_code": "US", + "prices": [ + { + "currency": "usd", + "amount": 0, + "exponent": 2 + } + ] + }, + "payment_source_prices": { + "775487223059316758": [ + { + "currency": "usd", + "amount": 0, + "exponent": 2 + } + ], + "736345864146255982": [ + { + "currency": "usd", + "amount": 0, + "exponent": 2 + } + ], + "683074999590060249": [ + { + "currency": "usd", + "amount": 0, + "exponent": 2 + } + ] + } + }, + "4": { + "country_prices": { + "country_code": "US", + "prices": [ + { + "currency": "usd", + "amount": 0, + "exponent": 2 + } + ] + }, + "payment_source_prices": { + "775487223059316758": [ + { + "currency": "usd", + "amount": 0, + "exponent": 2 + } + ], + "736345864146255982": [ + { + "currency": "usd", + "amount": 0, + "exponent": 2 + } + ], + "683074999590060249": [ + { + "currency": "usd", + "amount": 0, + "exponent": 2 + } + ] + } + }, + "1": { + "country_prices": { + "country_code": "US", + "prices": [ + { + "currency": "usd", + "amount": 0, + "exponent": 2 + } + ] + }, + "payment_source_prices": { + "775487223059316758": [ + { + "currency": "usd", + "amount": 0, + "exponent": 2 + } + ], + "736345864146255982": [ + { + "currency": "usd", + "amount": 0, + "exponent": 2 + } + ], + "683074999590060249": [ + { + "currency": "usd", + "amount": 0, + "exponent": 2 + } + ] + } + } + } + } + ] + ] + ] +]); + +router.get("/", route({}), async (req: Request, res: Response) => { + // TODO: add the ability to add custom + const { sku_id } = req.params; + + if (!skus.has(sku_id)) { + console.log(`Request for invalid SKU ${sku_id}! Please report this!`); + res.sendStatus(404); + } else { + res.json(skus.get(sku_id)).status(200); + } +}); + +export default router; diff --git a/api/src/routes/teams.ts b/src/api/routes/teams.ts
index 7ce3abcb..9aa1c10e 100644 --- a/api/src/routes/teams.ts +++ b/src/api/routes/teams.ts
@@ -1,5 +1,5 @@ -import { Request, Response, Router } from "express"; import { route } from "@fosscord/api"; +import { Request, Response, Router } from "express"; const router: Router = Router(); diff --git a/api/src/routes/template.ts.disabled b/src/api/routes/template.ts.disabled
index fcc59ef4..fcc59ef4 100644 --- a/api/src/routes/template.ts.disabled +++ b/src/api/routes/template.ts.disabled
diff --git a/api/src/routes/track.ts b/src/api/routes/track.ts
index 8556a3ad..cb01e576 100644 --- a/api/src/routes/track.ts +++ b/src/api/routes/track.ts
@@ -1,5 +1,5 @@ -import { Router, Response, Request } from "express"; import { route } from "@fosscord/api"; +import { Request, Response, Router } from "express"; const router = Router(); diff --git a/src/api/routes/updates.ts b/src/api/routes/updates.ts new file mode 100644
index 00000000..6019371e --- /dev/null +++ b/src/api/routes/updates.ts
@@ -0,0 +1,20 @@ +import { route } from "@fosscord/api"; +import { Config, Release } from "@fosscord/util"; +import { Request, Response, Router } from "express"; + +const router = Router(); + +router.get("/", route({}), async (req: Request, res: Response) => { + const { client } = Config.get(); + + const release = await Release.findOneOrFail({ where: { name: client.releases.upstreamVersion } }); + + res.json({ + name: release.name, + pub_date: release.pub_date, + url: release.url, + notes: release.notes + }); +}); + +export default router; diff --git a/api/src/routes/users/#id/index.ts b/src/api/routes/users/#id/index.ts
index bdb1060f..e33e5695 100644 --- a/api/src/routes/users/#id/index.ts +++ b/src/api/routes/users/#id/index.ts
@@ -1,6 +1,6 @@ -import { Router, Request, Response } from "express"; -import { User } from "@fosscord/util"; import { route } from "@fosscord/api"; +import { User } from "@fosscord/util"; +import { Request, Response, Router } from "express"; const router: Router = Router(); diff --git a/src/api/routes/users/#id/profile.ts b/src/api/routes/users/#id/profile.ts new file mode 100644
index 00000000..766c9880 --- /dev/null +++ b/src/api/routes/users/#id/profile.ts
@@ -0,0 +1,94 @@ +import { route } from "@fosscord/api"; +import { Member, PublicConnectedAccount, User, UserPublic } from "@fosscord/util"; +import { Request, Response, Router } from "express"; + +const router: Router = Router(); + +export interface UserProfileResponse { + user: UserPublic; + connected_accounts: PublicConnectedAccount; + premium_guild_since?: Date; + premium_since?: Date; +} + +router.get("/", route({ test: { response: { body: "UserProfileResponse" } } }), async (req: Request, res: Response) => { + if (req.params.id === "@me") req.params.id = req.user_id; + + const { guild_id, with_mutual_guilds } = req.query; + + const user = await User.getPublicUser(req.params.id, { relations: ["connected_accounts"] }); + + var mutual_guilds: object[] = []; + var premium_guild_since; + + if (with_mutual_guilds == "true") { + const requested_member = await Member.find({ where: { id: req.params.id } }); + const self_member = await Member.find({ where: { id: req.user_id } }); + + for (const rmem of requested_member) { + if (rmem.premium_since) { + if (premium_guild_since) { + if (premium_guild_since > rmem.premium_since) { + premium_guild_since = rmem.premium_since; + } + } else { + premium_guild_since = rmem.premium_since; + } + } + for (const smem of self_member) { + if (smem.guild_id === rmem.guild_id) { + mutual_guilds.push({ id: rmem.guild_id, nick: rmem.nick }); + } + } + } + } + + const guild_member = + guild_id && typeof guild_id == "string" + ? await Member.findOneOrFail({ where: { id: req.params.id, guild_id: guild_id }, relations: ["roles"] }) + : undefined; + + // TODO: make proper DTO's in util? + + const userDto = { + username: user.username, + discriminator: user.discriminator, + id: user.id, + public_flags: user.public_flags, + avatar: user.avatar, + accent_color: user.accent_color, + banner: user.banner, + bio: req.user_bot ? null : user.bio, + bot: user.bot + }; + + const guildMemberDto = guild_member + ? { + avatar: user.avatar, // TODO + banner: user.banner, // TODO + bio: req.user_bot ? null : user.bio, // TODO + communication_disabled_until: null, // TODO + deaf: guild_member.deaf, + flags: user.flags, + is_pending: guild_member.pending, + pending: guild_member.pending, // why is this here twice, discord? + joined_at: guild_member.joined_at, + mute: guild_member.mute, + nick: guild_member.nick, + premium_since: guild_member.premium_since, + roles: guild_member.roles.map((x) => x.id).filter((id) => id != guild_id), + user: userDto + } + : undefined; + + res.json({ + connected_accounts: user.connected_accounts, + premium_guild_since: premium_guild_since, // TODO + premium_since: user.premium_since, // TODO + mutual_guilds: mutual_guilds, // TODO {id: "", nick: null} when ?with_mutual_guilds=true + user: userDto, + guild_member: guildMemberDto + }); +}); + +export default router; diff --git a/src/api/routes/users/#id/relationships.ts b/src/api/routes/users/#id/relationships.ts new file mode 100644
index 00000000..9b7e3402 --- /dev/null +++ b/src/api/routes/users/#id/relationships.ts
@@ -0,0 +1,46 @@ +import { route } from "@fosscord/api"; +import { User } from "@fosscord/util"; +import { Request, Response, Router } from "express"; + +const router: Router = Router(); + +export interface UserRelationsResponse { + object: { + id?: string; + username?: string; + avatar?: string; + discriminator?: string; + public_flags?: number; + }; +} + +router.get("/", route({ test: { response: { body: "UserRelationsResponse" } } }), async (req: Request, res: Response) => { + let mutual_relations: object[] = []; + const requested_relations = await User.findOneOrFail({ + where: { id: req.params.id }, + relations: ["relationships"] + }); + const self_relations = await User.findOneOrFail({ + where: { id: req.user_id }, + relations: ["relationships"] + }); + + for (const rmem of requested_relations.relationships) { + for (const smem of self_relations.relationships) + if (rmem.to_id === smem.to_id && rmem.type === 1 && rmem.to_id !== req.user_id) { + let relation_user = await User.getPublicUser(rmem.to_id); + + mutual_relations.push({ + id: relation_user.id, + username: relation_user.username, + avatar: relation_user.avatar, + discriminator: relation_user.discriminator, + public_flags: relation_user.public_flags + }); + } + } + + res.json(mutual_relations); +}); + +export default router; diff --git a/api/src/routes/users/@me/activities/statistics/applications.ts b/src/api/routes/users/@me/activities/statistics/applications.ts
index 014df8af..ba359b47 100644 --- a/api/src/routes/users/@me/activities/statistics/applications.ts +++ b/src/api/routes/users/@me/activities/statistics/applications.ts
@@ -1,5 +1,5 @@ -import { Router, Response, Request } from "express"; import { route } from "@fosscord/api"; +import { Request, Response, Router } from "express"; const router = Router(); diff --git a/api/src/routes/users/@me/affinities/guilds.ts b/src/api/routes/users/@me/affinities/guilds.ts
index 8d744744..e733910f 100644 --- a/api/src/routes/users/@me/affinities/guilds.ts +++ b/src/api/routes/users/@me/affinities/guilds.ts
@@ -1,5 +1,5 @@ -import { Router, Response, Request } from "express"; import { route } from "@fosscord/api"; +import { Request, Response, Router } from "express"; const router = Router(); diff --git a/api/src/routes/users/@me/affinities/users.ts b/src/api/routes/users/@me/affinities/users.ts
index 6d4e4991..758bedc3 100644 --- a/api/src/routes/users/@me/affinities/users.ts +++ b/src/api/routes/users/@me/affinities/users.ts
@@ -1,5 +1,5 @@ -import { Router, Response, Request } from "express"; import { route } from "@fosscord/api"; +import { Request, Response, Router } from "express"; const router = Router(); diff --git a/api/src/routes/users/@me/applications/#app_id/entitlements.ts b/src/api/routes/users/@me/applications/#app_id/entitlements.ts
index 411e95bf..8e407184 100644 --- a/api/src/routes/users/@me/applications/#app_id/entitlements.ts +++ b/src/api/routes/users/@me/applications/#app_id/entitlements.ts
@@ -1,5 +1,5 @@ -import { Request, Response, Router } from "express"; import { route } from "@fosscord/api"; +import { Request, Response, Router } from "express"; const router: Router = Router(); diff --git a/api/src/routes/users/@me/billing/country-code.ts b/src/api/routes/users/@me/billing/country-code.ts
index 33d40796..72601f42 100644 --- a/api/src/routes/users/@me/billing/country-code.ts +++ b/src/api/routes/users/@me/billing/country-code.ts
@@ -1,5 +1,5 @@ -import { Request, Response, Router } from "express"; import { route } from "@fosscord/api"; +import { Request, Response, Router } from "express"; const router: Router = Router(); diff --git a/api/src/routes/users/@me/billing/payment-sources.ts b/src/api/routes/users/@me/billing/payment-sources.ts
index 014df8af..ba359b47 100644 --- a/api/src/routes/users/@me/billing/payment-sources.ts +++ b/src/api/routes/users/@me/billing/payment-sources.ts
@@ -1,5 +1,5 @@ -import { Router, Response, Request } from "express"; import { route } from "@fosscord/api"; +import { Request, Response, Router } from "express"; const router = Router(); diff --git a/api/src/routes/users/@me/billing/subscriptions.ts b/src/api/routes/users/@me/billing/subscriptions.ts
index 411e95bf..8e407184 100644 --- a/api/src/routes/users/@me/billing/subscriptions.ts +++ b/src/api/routes/users/@me/billing/subscriptions.ts
@@ -1,5 +1,5 @@ -import { Request, Response, Router } from "express"; import { route } from "@fosscord/api"; +import { Request, Response, Router } from "express"; const router: Router = Router(); diff --git a/api/src/routes/users/@me/channels.ts b/src/api/routes/users/@me/channels.ts
index 78f531e1..c17275ec 100644 --- a/api/src/routes/users/@me/channels.ts +++ b/src/api/routes/users/@me/channels.ts
@@ -1,6 +1,6 @@ -import { Request, Response, Router } from "express"; -import { Recipient, DmChannelDTO, Channel } from "@fosscord/util"; import { route } from "@fosscord/api"; +import { Channel, DmChannelCreateSchema, DmChannelDTO, Recipient } from "@fosscord/util"; +import { Request, Response, Router } from "express"; const router: Router = Router(); @@ -12,11 +12,6 @@ router.get("/", route({}), async (req: Request, res: Response) => { res.json(await Promise.all(recipients.map((r) => DmChannelDTO.from(r.channel, [req.user_id])))); }); -export interface DmChannelCreateSchema { - name?: string; - recipients: string[]; -} - router.post("/", route({ body: "DmChannelCreateSchema" }), async (req: Request, res: Response) => { const body = req.body as DmChannelCreateSchema; res.json(await Channel.createDMChannel(body.recipients, req.user_id, body.name)); diff --git a/api/src/routes/users/@me/connections.ts b/src/api/routes/users/@me/connections.ts
index 411e95bf..8e407184 100644 --- a/api/src/routes/users/@me/connections.ts +++ b/src/api/routes/users/@me/connections.ts
@@ -1,5 +1,5 @@ -import { Request, Response, Router } from "express"; import { route } from "@fosscord/api"; +import { Request, Response, Router } from "express"; const router: Router = Router(); diff --git a/api/src/routes/users/@me/delete.ts b/src/api/routes/users/@me/delete.ts
index c24c3f1e..dfc6131b 100644 --- a/api/src/routes/users/@me/delete.ts +++ b/src/api/routes/users/@me/delete.ts
@@ -1,8 +1,14 @@ -import { Router, Request, Response } from "express"; -import { Guild, Member, User } from "@fosscord/util"; import { route } from "@fosscord/api"; -import bcrypt from "bcrypt"; -import { HTTPError } from "lambert-server"; +import { HTTPError, Member, User } from "@fosscord/util"; +import { Request, Response, Router } from "express"; + +let bcrypt: any; +try { + bcrypt = require("bcrypt"); +} catch { + bcrypt = require("bcryptjs"); + console.log("Warning: using bcryptjs because bcrypt is not installed! Performance will be affected."); +} const router = Router(); diff --git a/api/src/routes/users/@me/devices.ts b/src/api/routes/users/@me/devices.ts
index 8556a3ad..cb01e576 100644 --- a/api/src/routes/users/@me/devices.ts +++ b/src/api/routes/users/@me/devices.ts
@@ -1,5 +1,5 @@ -import { Router, Response, Request } from "express"; import { route } from "@fosscord/api"; +import { Request, Response, Router } from "express"; const router = Router(); diff --git a/api/src/routes/users/@me/disable.ts b/src/api/routes/users/@me/disable.ts
index 4aff3774..05976908 100644 --- a/api/src/routes/users/@me/disable.ts +++ b/src/api/routes/users/@me/disable.ts
@@ -1,7 +1,14 @@ -import { User } from "@fosscord/util"; -import { Router, Response, Request } from "express"; import { route } from "@fosscord/api"; -import bcrypt from "bcrypt"; +import { User } from "@fosscord/util"; +import { Request, Response, Router } from "express"; + +let bcrypt: any; +try { + bcrypt = require("bcrypt"); +} catch { + bcrypt = require("bcryptjs"); + console.log("Warning: using bcryptjs because bcrypt is not installed! Performance will be affected."); +} const router = Router(); diff --git a/api/src/routes/users/@me/email-settings.ts b/src/api/routes/users/@me/email-settings.ts
index 3114984e..28d0864a 100644 --- a/api/src/routes/users/@me/email-settings.ts +++ b/src/api/routes/users/@me/email-settings.ts
@@ -1,5 +1,5 @@ -import { Router, Response, Request } from "express"; import { route } from "@fosscord/api"; +import { Request, Response, Router } from "express"; const router = Router(); diff --git a/api/src/routes/users/@me/entitlements.ts b/src/api/routes/users/@me/entitlements.ts
index 341e2b4c..7aaa5d7c 100644 --- a/api/src/routes/users/@me/entitlements.ts +++ b/src/api/routes/users/@me/entitlements.ts
@@ -1,5 +1,5 @@ -import { Router, Response, Request } from "express"; import { route } from "@fosscord/api"; +import { Request, Response, Router } from "express"; const router = Router(); diff --git a/api/src/routes/users/@me/guilds.ts b/src/api/routes/users/@me/guilds.ts
index 754a240e..5141aa3d 100644 --- a/api/src/routes/users/@me/guilds.ts +++ b/src/api/routes/users/@me/guilds.ts
@@ -1,7 +1,6 @@ -import { Router, Request, Response } from "express"; -import { Guild, Member, User, GuildDeleteEvent, GuildMemberRemoveEvent, emitEvent, Config } from "@fosscord/util"; -import { HTTPError } from "lambert-server"; import { route } from "@fosscord/api"; +import { Config, emitEvent, Guild, GuildDeleteEvent, GuildMemberRemoveEvent, HTTPError, Member, User } from "@fosscord/util"; +import { Request, Response, Router } from "express"; const router: Router = Router(); diff --git a/api/src/routes/users/@me/guilds/premium/subscription-slots.ts b/src/api/routes/users/@me/guilds/premium/subscription-slots.ts
index 014df8af..ba359b47 100644 --- a/api/src/routes/users/@me/guilds/premium/subscription-slots.ts +++ b/src/api/routes/users/@me/guilds/premium/subscription-slots.ts
@@ -1,5 +1,5 @@ -import { Router, Response, Request } from "express"; import { route } from "@fosscord/api"; +import { Request, Response, Router } from "express"; const router = Router(); diff --git a/api/src/routes/users/@me/index.ts b/src/api/routes/users/@me/index.ts
index 1af413c4..563300dc 100644 --- a/api/src/routes/users/@me/index.ts +++ b/src/api/routes/users/@me/index.ts
@@ -1,39 +1,40 @@ -import { Router, Request, Response } from "express"; -import { User, PrivateUserProjection, emitEvent, UserUpdateEvent, handleFile, FieldErrors } from "@fosscord/util"; import { route } from "@fosscord/api"; -import bcrypt from "bcrypt"; +import { + adjustEmail, + Config, + emitEvent, + FieldErrors, + generateToken, + handleFile, + OrmUtils, + PrivateUserProjection, + User, + UserModifySchema, + UserUpdateEvent +} from "@fosscord/util"; +import { Request, Response, Router } from "express"; -const router: Router = Router(); - -export interface UserModifySchema { - /** - * @minLength 1 - * @maxLength 100 - */ - username?: string; - avatar?: string | null; - /** - * @maxLength 1024 - */ - bio?: string; - accent_color?: number; - banner?: string | null; - password?: string; - new_password?: string; - code?: string; +let bcrypt: any; +try { + bcrypt = require("bcrypt"); +} catch { + bcrypt = require("bcryptjs"); + console.log("Warning: using bcryptjs because bcrypt is not installed! Performance will be affected."); } +const router: Router = Router(); + router.get("/", route({}), async (req: Request, res: Response) => { res.json(await User.findOne({ select: PrivateUserProjection, where: { id: req.user_id } })); }); router.patch("/", route({ body: "UserModifySchema" }), async (req: Request, res: Response) => { + var token = null as any; const body = req.body as UserModifySchema; if (body.avatar) body.avatar = await handleFile(`/avatars/${req.user_id}`, body.avatar as string); if (body.banner) body.banner = await handleFile(`/banners/${req.user_id}`, body.banner as string); - - const user = await User.findOneOrFail({ where: { id: req.user_id }, select: [...PrivateUserProjection, "data"] }); + let user = await User.findOneOrFail({ where: { id: req.user_id }, select: [...PrivateUserProjection, "data"] }); if (body.password) { if (user.data?.hash) { @@ -46,6 +47,13 @@ router.patch("/", route({ body: "UserModifySchema" }), async (req: Request, res: } } + if (body.email) { + body.email = adjustEmail(body.email); + if (!body.email && Config.get().register.email.required) + throw FieldErrors({ email: { message: req.t("auth:register.EMAIL_INVALID"), code: "EMAIL_INVALID" } }); + if (!body.password) throw FieldErrors({ password: { message: req.t("auth:register.INVALID_PASSWORD"), code: "INVALID_PASSWORD" } }); + } + if (body.new_password) { if (!body.password && !user.email) { throw FieldErrors({ @@ -53,18 +61,20 @@ router.patch("/", route({ body: "UserModifySchema" }), async (req: Request, res: }); } user.data.hash = await bcrypt.hash(body.new_password, 12); + user.data.valid_tokens_since = new Date(); + token = (await generateToken(user.id)) as string; } - if(body.username){ - var check_username = body?.username?.replace(/\s/g, ''); - if(!check_username) { - throw FieldErrors({ - username: { code: "BASE_TYPE_REQUIRED", message: req.t("common:field.BASE_TYPE_REQUIRED") } - }); - } - } + if (body.username) { + let check_username = body?.username?.replace(/\s/g, ""); + if (!check_username) { + throw FieldErrors({ + username: { code: "BASE_TYPE_REQUIRED", message: req.t("common:field.BASE_TYPE_REQUIRED") } + }); + } + } - user.assign(body); + user = OrmUtils.mergeDeep(user, body); await user.save(); // @ts-ignore @@ -77,7 +87,10 @@ router.patch("/", route({ body: "UserModifySchema" }), async (req: Request, res: data: user } as UserUpdateEvent); - res.json(user); + res.json({ + ...user, + token + }); }); export default router; diff --git a/api/src/routes/users/@me/library.ts b/src/api/routes/users/@me/library.ts
index 7ac13bae..0aea02a0 100644 --- a/api/src/routes/users/@me/library.ts +++ b/src/api/routes/users/@me/library.ts
@@ -1,5 +1,5 @@ -import { Router, Response, Request } from "express"; import { route } from "@fosscord/api"; +import { Request, Response, Router } from "express"; const router = Router(); diff --git a/src/api/routes/users/@me/mfa/codes.ts b/src/api/routes/users/@me/mfa/codes.ts new file mode 100644
index 00000000..c62581cc --- /dev/null +++ b/src/api/routes/users/@me/mfa/codes.ts
@@ -0,0 +1,48 @@ +import { route } from "@fosscord/api"; +import { BackupCode, Config, FieldErrors, generateMfaBackupCodes, MfaCodesSchema, User } from "@fosscord/util"; +import { Request, Response, Router } from "express"; + +let bcrypt: any; +try { + bcrypt = require("bcrypt"); +} catch { + bcrypt = require("bcryptjs"); + console.log("Warning: using bcryptjs because bcrypt is not installed! Performance will be affected."); +} + +const router = Router(); + +// TODO: This route is replaced with users/@me/mfa/codes-verification in newer clients + +router.post("/", route({ body: "MfaCodesSchema" }), async (req: Request, res: Response) => { + const { password, regenerate } = req.body as MfaCodesSchema; + + const user = await User.findOneOrFail({ where: { id: req.user_id }, select: ["data"] }); + + if (!(await bcrypt.compare(password, user.data.hash || ""))) { + throw FieldErrors({ password: { message: req.t("auth:login.INVALID_PASSWORD"), code: "INVALID_PASSWORD" } }); + } + + var codes: BackupCode[]; + if (regenerate && Config.get().security.twoFactor.generateBackupCodes) { + await BackupCode.update({ user: { id: req.user_id } }, { expired: true }); + + codes = generateMfaBackupCodes(req.user_id); + await Promise.all(codes.map((x) => x.save())); + } else { + codes = await BackupCode.find({ + where: { + user: { + id: req.user_id + }, + expired: false + } + }); + } + + return res.json({ + backup_codes: codes.map((x) => ({ ...x, expired: undefined })) + }); +}); + +export default router; diff --git a/src/api/routes/users/@me/mfa/totp/disable.ts b/src/api/routes/users/@me/mfa/totp/disable.ts new file mode 100644
index 00000000..6bc9a5c7 --- /dev/null +++ b/src/api/routes/users/@me/mfa/totp/disable.ts
@@ -0,0 +1,40 @@ +import { route } from "@fosscord/api"; +import { BackupCode, generateToken, TotpDisableSchema, User } from "@fosscord/util"; +import { Request, Response, Router } from "express"; +import { HTTPError } from "lambert-server"; +import { verifyToken } from "node-2fa"; + +const router = Router(); + +router.post("/", route({ body: "TotpDisableSchema" }), async (req: Request, res: Response) => { + const body = req.body as TotpDisableSchema; + + const user = await User.findOneOrFail({ where: { id: req.user_id }, select: ["totp_secret"] }); + + const backup = await BackupCode.findOne({ where: { code: body.code } }); + if (!backup) { + const ret = verifyToken(user.totp_secret!, body.code); + if (!ret || ret.delta != 0) throw new HTTPError(req.t("auth:login.INVALID_TOTP_CODE"), 60008); + } + + await User.update( + { id: req.user_id }, + { + mfa_enabled: false, + totp_secret: "" + } + ); + + await BackupCode.update( + { user: { id: req.user_id } }, + { + expired: true + } + ); + + return res.json({ + token: await generateToken(user.id) + }); +}); + +export default router; diff --git a/src/api/routes/users/@me/mfa/totp/enable.ts b/src/api/routes/users/@me/mfa/totp/enable.ts new file mode 100644
index 00000000..f3a73c28 --- /dev/null +++ b/src/api/routes/users/@me/mfa/totp/enable.ts
@@ -0,0 +1,49 @@ +import { route } from "@fosscord/api"; +import { BackupCode, Config, generateMfaBackupCodes, generateToken, TotpEnableSchema, User } from "@fosscord/util"; +import { Request, Response, Router } from "express"; +import { HTTPError } from "lambert-server"; +import { verifyToken } from "node-2fa"; + +let bcrypt: any; +try { + bcrypt = require("bcrypt"); +} catch { + bcrypt = require("bcryptjs"); + console.log("Warning: using bcryptjs because bcrypt is not installed! Performance will be affected."); +} + +const router = Router(); + +router.post("/", route({ body: "TotpEnableSchema" }), async (req: Request, res: Response) => { + const body = req.body as TotpEnableSchema; + + const user = await User.findOneOrFail({ where: { id: req.user_id }, select: ["data"] }); + + // TODO: Are guests allowed to enable 2fa? + if (user.data.hash) { + if (!(await bcrypt.compare(body.password, user.data.hash))) { + throw new HTTPError(req.t("auth:login.INVALID_PASSWORD")); + } + } + + if (!body.secret) throw new HTTPError(req.t("auth:login.INVALID_TOTP_SECRET"), 60005); + + if (!body.code) throw new HTTPError(req.t("auth:login.INVALID_TOTP_CODE"), 60008); + + if (verifyToken(body.secret, body.code)?.delta != 0) throw new HTTPError(req.t("auth:login.INVALID_TOTP_CODE"), 60008); + + let backup_codes: BackupCode[] = []; + if (Config.get().security.twoFactor.generateBackupCodes) { + backup_codes = generateMfaBackupCodes(req.user_id); + await Promise.all(backup_codes.map((x) => x.save())); + } + + await User.update({ id: req.user_id }, { mfa_enabled: true, totp_secret: body.secret }); + + res.send({ + token: await generateToken(user.id), + backup_codes: backup_codes.map((x) => ({ ...x, expired: undefined })) + }); +}); + +export default router; diff --git a/src/api/routes/users/@me/notes.ts b/src/api/routes/users/@me/notes.ts new file mode 100644
index 00000000..fc207401 --- /dev/null +++ b/src/api/routes/users/@me/notes.ts
@@ -0,0 +1,53 @@ +import { route } from "@fosscord/api"; +import { emitEvent, Note, Snowflake, User } from "@fosscord/util"; +import { Request, Response, Router } from "express"; + +const router: Router = Router(); + +router.get("/:id", route({}), async (req: Request, res: Response) => { + const { id } = req.params; + + const note = await Note.findOneOrFail({ + where: { + owner: { id: req.user_id }, + target: { id: id } + } + }); + + return res.json({ + note: note?.content, + note_user_id: id, + user_id: req.user_id + }); +}); + +router.put("/:id", route({}), async (req: Request, res: Response) => { + const { id } = req.params; + const owner = await User.findOneOrFail({ where: { id: req.user_id } }); + const target = await User.findOneOrFail({ where: { id: id } }); //if noted user does not exist throw + const { note } = req.body; + + if (note && note.length) { + // upsert a note + if (await Note.findOne({ where: { owner: { id: owner.id }, target: { id: target.id } } })) { + Note.update({ owner: { id: owner.id }, target: { id: target.id } }, { owner, target, content: note }); + } else { + Note.insert({ id: Snowflake.generate(), owner, target, content: note }); + } + } else { + await Note.delete({ owner: { id: owner.id }, target: { id: target.id } }); + } + + await emitEvent({ + event: "USER_NOTE_UPDATE", + data: { + note: note, + id: target.id + }, + user_id: owner.id + }); + + return res.status(204); +}); + +export default router; diff --git a/api/src/routes/users/@me/relationships.ts b/src/api/routes/users/@me/relationships.ts
index 0c13cdba..8267c142 100644 --- a/api/src/routes/users/@me/relationships.ts +++ b/src/api/routes/users/@me/relationships.ts
@@ -1,17 +1,18 @@ +import { route } from "@fosscord/api"; import { - RelationshipAddEvent, - User, - PublicUserProjection, - RelationshipType, - RelationshipRemoveEvent, + Config, + DiscordApiErrors, emitEvent, + HTTPError, + OrmUtils, + PublicUserProjection, Relationship, - Config + RelationshipAddEvent, + RelationshipRemoveEvent, + RelationshipType, + User } from "@fosscord/util"; -import { Router, Response, Request } from "express"; -import { HTTPError } from "lambert-server"; -import { DiscordApiErrors } from "@fosscord/util"; -import { route } from "@fosscord/api"; +import { Request, Response, Router } from "express"; const router = Router(); @@ -37,24 +38,19 @@ router.get("/", route({}), async (req: Request, res: Response) => { return res.json(related_users); }); -export interface RelationshipPutSchema { - type?: RelationshipType; -} - router.put("/:id", route({ body: "RelationshipPutSchema" }), async (req: Request, res: Response) => { return await updateRelationship( req, res, - await User.findOneOrFail({ id: req.params.id }, { relations: ["relationships", "relationships.to"], select: userProjection }), + await User.findOneOrFail({ + where: { id: req.params.id }, + relations: ["relationships", "relationships.to"], + select: userProjection + }), req.body.type ?? RelationshipType.friends ); }); -export interface RelationshipPostSchema { - discriminator: string; - username: string; -} - router.post("/", route({ body: "RelationshipPostSchema" }), async (req: Request, res: Response) => { return await updateRelationship( req, @@ -75,8 +71,8 @@ router.delete("/:id", route({}), async (req: Request, res: Response) => { const { id } = req.params; if (id === req.user_id) throw new HTTPError("You can't remove yourself as a friend"); - const user = await User.findOneOrFail({ id: req.user_id }, { select: userProjection, relations: ["relationships"] }); - const friend = await User.findOneOrFail({ id: id }, { select: userProjection, relations: ["relationships"] }); + const user = await User.findOneOrFail({ where: { id: req.user_id }, select: userProjection, relations: ["relationships"] }); + const friend = await User.findOneOrFail({ where: { id: id }, select: userProjection, relations: ["relationships"] }); const relationship = user.relationships.find((x) => x.to_id === id); const friendRequest = friend.relationships.find((x) => x.to_id === req.user_id); @@ -124,12 +120,13 @@ async function updateRelationship(req: Request, res: Response, friend: User, typ const id = friend.id; if (id === req.user_id) throw new HTTPError("You can't add yourself as a friend"); - const user = await User.findOneOrFail( - { id: req.user_id }, - { relations: ["relationships", "relationships.to"], select: userProjection } - ); + const user = await User.findOneOrFail({ + where: { id: req.user_id }, + relations: ["relationships", "relationships.to"], + select: userProjection + }); - var relationship = user.relationships.find((x) => x.to_id === id); + let relationship = user.relationships.find((x) => x.to_id === id); const friendRequest = friend.relationships.find((x) => x.to_id === req.user_id); // TODO: you can add infinitely many blocked users (should this be prevented?) @@ -139,7 +136,9 @@ async function updateRelationship(req: Request, res: Response, friend: User, typ relationship.type = RelationshipType.blocked; await relationship.save(); } else { - relationship = await new Relationship({ to_id: id, type: RelationshipType.blocked, from_id: req.user_id }).save(); + relationship = await ( + OrmUtils.mergeDeep(new Relationship(), { to_id: id, type: RelationshipType.blocked, from_id: req.user_id }) as Relationship + ).save(); } if (friendRequest && friendRequest.type !== RelationshipType.blocked) { @@ -165,8 +164,13 @@ async function updateRelationship(req: Request, res: Response, friend: User, typ const { maxFriends } = Config.get().limits.user; if (user.relationships.length >= maxFriends) throw DiscordApiErrors.MAXIMUM_FRIENDS.withParams(maxFriends); - var incoming_relationship = new Relationship({ nickname: undefined, type: RelationshipType.incoming, to: user, from: friend }); - var outgoing_relationship = new Relationship({ + let incoming_relationship = OrmUtils.mergeDeep(new Relationship(), { + nickname: undefined, + type: RelationshipType.incoming, + to: user, + from: friend + }); + let outgoing_relationship = OrmUtils.mergeDeep(new Relationship(), { nickname: undefined, type: RelationshipType.outgoing, to: friend, @@ -177,7 +181,7 @@ async function updateRelationship(req: Request, res: Response, friend: User, typ if (friendRequest.type === RelationshipType.blocked) throw new HTTPError("The user blocked you"); if (friendRequest.type === RelationshipType.friends) throw new HTTPError("You are already friends with the user"); // accept friend request - incoming_relationship = friendRequest; + incoming_relationship = friendRequest as any; //TODO: checkme, any cast incoming_relationship.type = RelationshipType.friends; } @@ -185,7 +189,7 @@ async function updateRelationship(req: Request, res: Response, friend: User, typ if (relationship.type === RelationshipType.outgoing) throw new HTTPError("You already sent a friend request"); if (relationship.type === RelationshipType.blocked) throw new HTTPError("Unblock the user before sending a friend request"); if (relationship.type === RelationshipType.friends) throw new HTTPError("You are already friends with the user"); - outgoing_relationship = relationship; + outgoing_relationship = relationship as any; //TODO: checkme, any cast outgoing_relationship.type = RelationshipType.friends; } diff --git a/api/src/routes/users/@me/settings.ts b/src/api/routes/users/@me/settings.ts
index b22b72fb..e276a22a 100644 --- a/api/src/routes/users/@me/settings.ts +++ b/src/api/routes/users/@me/settings.ts
@@ -1,17 +1,15 @@ -import { Router, Response, Request } from "express"; -import { User, UserSettings } from "@fosscord/util"; import { route } from "@fosscord/api"; +import { User, UserSettings } from "@fosscord/util"; +import { Request, Response, Router } from "express"; const router = Router(); -export interface UserSettingsSchema extends Partial<UserSettings> {} - router.patch("/", route({ body: "UserSettingsSchema" }), async (req: Request, res: Response) => { const body = req.body as UserSettings; if (body.locale === "en") body.locale = "en-US"; // fix discord client crash on unkown locale - const user = await User.findOneOrFail({ id: req.user_id, bot: false }); - user.settings = { ...user.settings, ...body }; + const user = await User.findOneOrFail({ where: { id: req.user_id, bot: false }, relations: ["settings"] }); + user.settings = { ...user.settings, ...body } as UserSettings; await user.save(); res.sendStatus(204); diff --git a/api/src/routes/voice/regions.ts b/src/api/routes/voice/regions.ts
index 4de304ee..eacdcf11 100644 --- a/api/src/routes/voice/regions.ts +++ b/src/api/routes/voice/regions.ts
@@ -1,6 +1,5 @@ -import { Router, Request, Response } from "express"; -import { getIpAdress, route } from "@fosscord/api"; -import { getVoiceRegions } from "@fosscord/api"; +import { getIpAdress, getVoiceRegions, route } from "@fosscord/api"; +import { Request, Response, Router } from "express"; const router: Router = Router(); diff --git a/api/src/start.ts b/src/api/start.ts
index ccb4d108..c407484d 100644 --- a/api/src/start.ts +++ b/src/api/start.ts
@@ -1,17 +1,16 @@ process.on("uncaughtException", console.error); process.on("unhandledRejection", console.error); -import "missing-native-js-functions"; -import { config } from "dotenv"; -config(); -import { FosscordServer } from "./Server"; import cluster from "cluster"; +import { config } from "dotenv"; import os from "os"; -var cores = 1; +import { FosscordServer } from "./Server"; +config(); +let cores = 1; try { cores = Number(process.env.THREADS) || os.cpus().length; } catch { - console.log("[API] Failed to get thread count! Using 1...") + console.log("[API] Failed to get thread count! Using 1..."); } if (cluster.isMaster && process.env.NODE_ENV == "production") { @@ -27,7 +26,7 @@ if (cluster.isMaster && process.env.NODE_ENV == "production") { cluster.fork(); }); } else { - var port = Number(process.env.PORT) || 3001; + let port = Number(process.env.PORT) || 3001; const server = new FosscordServer({ port }); server.start().catch(console.error); diff --git a/src/api/util/entities/AssetCacheItem.ts b/src/api/util/entities/AssetCacheItem.ts new file mode 100644
index 00000000..958d5a61 --- /dev/null +++ b/src/api/util/entities/AssetCacheItem.ts
@@ -0,0 +1,3 @@ +export class AssetCacheItem { + constructor(public Key: string, public FilePath: string = "", public Headers: any = null as any) {} +} diff --git a/api/src/util/entities/blockedEmailDomains.txt b/src/api/util/entities/blockedEmailDomains.txt
index eb88305d..eb88305d 100644 --- a/api/src/util/entities/blockedEmailDomains.txt +++ b/src/api/util/entities/blockedEmailDomains.txt
diff --git a/api/src/util/entities/trustedEmailDomains.txt b/src/api/util/entities/trustedEmailDomains.txt
index 38ffa4fa..38ffa4fa 100644 --- a/api/src/util/entities/trustedEmailDomains.txt +++ b/src/api/util/entities/trustedEmailDomains.txt
diff --git a/api/src/util/handlers/Instance.ts b/src/api/util/handlers/Instance.ts
index 6bddfa98..e03c9488 100644 --- a/api/src/util/handlers/Instance.ts +++ b/src/api/util/handlers/Instance.ts
@@ -9,7 +9,7 @@ export async function initInstance() { const { autoJoin } = Config.get().guild; if (autoJoin.enabled && !autoJoin.guilds?.length) { - let guild = await Guild.findOne({}); + let guild = await Guild.findOne({ where: {}, order: { id: "ASC" } }); if (guild) { // @ts-ignore await Config.set({ guild: { autoJoin: { guilds: [guild.id] } } }); diff --git a/api/src/util/handlers/Message.ts b/src/api/util/handlers/Message.ts
index 48f87dfe..d760d27c 100644 --- a/api/src/util/handlers/Message.ts +++ b/src/api/util/handlers/Message.ts
@@ -1,31 +1,32 @@ import { + Application, + Attachment, Channel, + CHANNEL_MENTION, + Config, Embed, emitEvent, + EVERYONE_MENTION, + getPermission, + getRights, Guild, + HERE_MENTION, + HTTPError, Message, MessageCreateEvent, + MessageCreateSchema, + MessageType, MessageUpdateEvent, - getPermission, - getRights, - CHANNEL_MENTION, - Snowflake, - USER_MENTION, - ROLE_MENTION, + OrmUtils, Role, - EVERYONE_MENTION, - HERE_MENTION, - MessageType, + ROLE_MENTION, User, - Application, - Webhook, - Attachment, - Config, + USER_MENTION, + Webhook } from "@fosscord/util"; -import { HTTPError } from "lambert-server"; -import fetch from "node-fetch"; import cheerio from "cheerio"; -import { MessageCreateSchema } from "../../routes/channels/#channel_id/messages"; +import fetch from "node-fetch"; + const allow_empty = false; // TODO: check webhook, application, system author, stickers // TODO: embed gifs/videos/images @@ -47,7 +48,7 @@ export async function handleMessage(opts: MessageOptions): Promise<Message> { const channel = await Channel.findOneOrFail({ where: { id: opts.channel_id }, relations: ["recipients"] }); if (!channel || !opts.channel_id) throw new HTTPError("Channel not found", 404); - const message = new Message({ + const message = OrmUtils.mergeDeep(new Message(), { ...opts, sticker_items: opts.sticker_ids?.map((x) => ({ id: x })), guild_id: channel.guild_id, @@ -59,21 +60,21 @@ export async function handleMessage(opts: MessageOptions): Promise<Message> { }); if (message.content && message.content.length > Config.get().limits.message.maxCharacters) { - throw new HTTPError("Content length over max character limit") + throw new HTTPError("Content length over max character limit"); } if (opts.author_id) { message.author = await User.getPublicUser(opts.author_id); const rights = await getRights(opts.author_id); rights.hasThrow("SEND_MESSAGES"); - } + } if (opts.application_id) { - message.application = await Application.findOneOrFail({ id: opts.application_id }); + message.application = await Application.findOneOrFail({ where: { id: opts.application_id } }); } if (opts.webhook_id) { - message.webhook = await Webhook.findOneOrFail({ id: opts.webhook_id }); + message.webhook = await Webhook.findOneOrFail({ where: { id: opts.webhook_id } }); } - + const permission = await getPermission(opts.author_id, channel.guild_id, opts.channel_id); permission.hasThrow("SEND_MESSAGES"); if (permission.cache.member) { @@ -85,10 +86,12 @@ export async function handleMessage(opts: MessageOptions): Promise<Message> { permission.hasThrow("READ_MESSAGE_HISTORY"); // code below has to be redone when we add custom message routing if (message.guild_id !== null) { - const guild = await Guild.findOneOrFail({ id: channel.guild_id }); + const guild = await Guild.findOneOrFail({ where: { id: channel.guild_id } }); if (!guild.features.includes("CROSS_CHANNEL_REPLIES")) { - if (opts.message_reference.guild_id !== channel.guild_id) throw new HTTPError("You can only reference messages from this guild"); - if (opts.message_reference.channel_id !== opts.channel_id) throw new HTTPError("You can only reference messages from this channel"); + if (opts.message_reference.guild_id !== channel.guild_id) + throw new HTTPError("You can only reference messages from this guild"); + if (opts.message_reference.channel_id !== opts.channel_id) + throw new HTTPError("You can only reference messages from this channel"); } } /** Q: should be checked if the referenced message exists? ANSWER: NO @@ -98,17 +101,18 @@ export async function handleMessage(opts: MessageOptions): Promise<Message> { } // TODO: stickers/activity - if (!allow_empty && (!opts.content && !opts.embeds?.length && !opts.attachments?.length && !opts.sticker_ids?.length)) { + if (!allow_empty && !opts.content && !opts.embeds?.length && !opts.attachments?.length && !opts.sticker_ids?.length) { throw new HTTPError("Empty messages are not allowed", 50006); } - var content = opts.content; - var mention_channel_ids = [] as string[]; - var mention_role_ids = [] as string[]; - var mention_user_ids = [] as string[]; - var mention_everyone = false; + let content = opts.content; + let mention_channel_ids = [] as string[]; + let mention_role_ids = [] as string[]; + let mention_user_ids = [] as string[]; + let mention_everyone = false; - if (content) { // TODO: explicit-only mentions + if (content) { + // TODO: explicit-only mentions message.content = content.trim(); for (const [_, mention] of content.matchAll(CHANNEL_MENTION)) { if (!mention_channel_ids.includes(mention)) mention_channel_ids.push(mention); @@ -120,7 +124,7 @@ export async function handleMessage(opts: MessageOptions): Promise<Message> { await Promise.all( Array.from(content.matchAll(ROLE_MENTION)).map(async ([_, mention]) => { - const role = await Role.findOneOrFail({ id: mention, guild_id: channel.guild_id }); + const role = await Role.findOneOrFail({ where: { id: mention, guild_id: channel.guild_id } }); if (role.mentionable || permission.has("MANAGE_ROLES")) { mention_role_ids.push(mention); } @@ -132,9 +136,9 @@ export async function handleMessage(opts: MessageOptions): Promise<Message> { } } - message.mention_channels = mention_channel_ids.map((x) => new Channel({ id: x })); - message.mention_roles = mention_role_ids.map((x) => new Role({ id: x })); - message.mentions = mention_user_ids.map((x) => new User({ id: x })); + message.mention_channels = mention_channel_ids.map((x) => OrmUtils.mergeDeep(new Channel(), { id: x })); + message.mention_roles = mention_role_ids.map((x) => OrmUtils.mergeDeep(new Role(), { id: x })); + message.mentions = mention_user_ids.map((x) => OrmUtils.mergeDeep(new User(), { id: x })); message.mention_everyone = mention_everyone; // TODO: check and put it all in the body @@ -144,7 +148,7 @@ export async function handleMessage(opts: MessageOptions): Promise<Message> { // TODO: cache link result in db export async function postHandleMessage(message: Message) { - var links = message.content?.match(LINK_REGEX); + let links = message.content?.match(LINK_REGEX); if (!links) return; const data = { ...message }; @@ -156,7 +160,7 @@ export async function postHandleMessage(message: Message) { try { const request = await fetch(link, { ...DEFAULT_FETCH_OPTIONS, - size: Config.get().limits.message.maxEmbedDownloadSize, + size: Config.get().limits.message.maxEmbedDownloadSize }); const text = await request.text(); @@ -201,9 +205,10 @@ export async function postHandleMessage(message: Message) { export async function sendMessage(opts: MessageOptions) { const message = await handleMessage({ ...opts, timestamp: new Date() }); + //TODO: check this, removed toJSON call await Promise.all([ Message.insert(message), - emitEvent({ event: "MESSAGE_CREATE", channel_id: opts.channel_id, data: message.toJSON() } as MessageCreateEvent) + emitEvent({ event: "MESSAGE_CREATE", channel_id: opts.channel_id, data: message } as MessageCreateEvent) ]); postHandleMessage(message).catch((e) => {}); // no await as it should catch error non-blockingly diff --git a/api/src/util/handlers/Voice.ts b/src/api/util/handlers/Voice.ts
index 4d60eb91..4d60eb91 100644 --- a/api/src/util/handlers/Voice.ts +++ b/src/api/util/handlers/Voice.ts
diff --git a/api/src/util/handlers/route.ts b/src/api/util/handlers/route.ts
index 3d3bbc37..d43ae103 100644 --- a/api/src/util/handlers/route.ts +++ b/src/api/util/handlers/route.ts
@@ -1,8 +1,6 @@ import { DiscordApiErrors, EVENT, - Event, - EventData, FieldErrors, FosscordApiErrors, getPermission, @@ -12,14 +10,14 @@ import { RightResolvable, Rights } from "@fosscord/util"; +import Ajv from "ajv"; +import addFormats from "ajv-formats"; +import { AnyValidateFunction } from "ajv/dist/core"; import { NextFunction, Request, Response } from "express"; import fs from "fs"; import path from "path"; -import Ajv from "ajv"; -import { AnyValidateFunction } from "ajv/dist/core"; -import addFormats from "ajv-formats"; -const SchemaPath = path.join(__dirname, "..", "..", "..", "assets", "schemas.json"); +const SchemaPath = path.join(__dirname, "..", "..", "..", "..", "assets", "schemas.json"); const schemas = JSON.parse(fs.readFileSync(SchemaPath, { encoding: "utf8" })); export const ajv = new Ajv({ @@ -87,7 +85,7 @@ const normalizeBody = (body: any = {}) => { }; export function route(opts: RouteOptions) { - var validate: AnyValidateFunction<any> | undefined; + let validate: AnyValidateFunction<any> | undefined; if (opts.body) { validate = ajv.getSchema(opts.body); if (!validate) throw new Error(`Body schema ${opts.body} not found`); @@ -117,6 +115,11 @@ export function route(opts: RouteOptions) { const valid = validate(normalizeBody(req.body)); if (!valid) { const fields: Record<string, { code?: string; message: string }> = {}; + if (process.env.LOG_INVALID_BODY) { + console.log(`Got invalid request: ${req.method} ${req.originalUrl}`); + console.log(req.body); + validate.errors?.forEach((x) => console.log(x.params)); + } validate.errors?.forEach((x) => (fields[x.instancePath.slice(1)] = { code: x.keyword, message: x.message || "" })); throw FieldErrors(fields); } diff --git a/api/src/util/index.ts b/src/api/util/index.ts
index ffbcf24e..31e75325 100644 --- a/api/src/util/index.ts +++ b/src/api/util/index.ts
@@ -1,8 +1,10 @@ +export * from "./entities/AssetCacheItem"; +export * from "./handlers/Message"; +export * from "./handlers/route"; +export * from "./handlers/Voice"; export * from "./utility/Base64"; export * from "./utility/ipAddress"; -export * from "./handlers/Message"; export * from "./utility/passwordStrength"; export * from "./utility/RandomInviteID"; -export * from "./handlers/route"; export * from "./utility/String"; -export * from "./handlers/Voice"; +export * from "./utility/captcha"; \ No newline at end of file diff --git a/api/src/util/utility/Base64.ts b/src/api/util/utility/Base64.ts
index 46cff77a..46cff77a 100644 --- a/api/src/util/utility/Base64.ts +++ b/src/api/util/utility/Base64.ts
diff --git a/api/src/util/utility/RandomInviteID.ts b/src/api/util/utility/RandomInviteID.ts
index 7ea344e0..feebfd3d 100644 --- a/api/src/util/utility/RandomInviteID.ts +++ b/src/api/util/utility/RandomInviteID.ts
@@ -22,11 +22,10 @@ export function snowflakeBasedInvite() { // snowflakes hold ~10.75 characters worth of entropy; // safe to generate a 8-char invite out of them let str = ""; - for (let i=0; i < 10; i++) { - + for (let i = 0; i < 10; i++) { str.concat(chars.charAt(Number(snowflake % base))); snowflake = snowflake / base; } - - return str.substr(3,8).split("").reverse().join(""); + + return str.substr(3, 8).split("").reverse().join(""); } diff --git a/api/src/util/utility/String.ts b/src/api/util/utility/String.ts
index 982b7e11..a2e491e4 100644 --- a/api/src/util/utility/String.ts +++ b/src/api/util/utility/String.ts
@@ -1,6 +1,6 @@ +import { FieldErrors } from "@fosscord/util"; import { Request } from "express"; import { ntob } from "./Base64"; -import { FieldErrors } from "@fosscord/util"; export function checkLength(str: string, min: number, max: number, key: string, req: Request) { if (str.length < min || str.length > max) { diff --git a/src/api/util/utility/captcha.ts b/src/api/util/utility/captcha.ts new file mode 100644
index 00000000..739647d2 --- /dev/null +++ b/src/api/util/utility/captcha.ts
@@ -0,0 +1,46 @@ +import { Config } from "@fosscord/util"; +import fetch from "node-fetch"; + +export interface hcaptchaResponse { + success: boolean; + challenge_ts: string; + hostname: string; + credit: boolean; + "error-codes": string[]; + score: number; // enterprise only + score_reason: string[]; // enterprise only +} + +export interface recaptchaResponse { + success: boolean; + score: number; // between 0 - 1 + action: string; + challenge_ts: string; + hostname: string; + "error-codes"?: string[]; +} + +const verifyEndpoints = { + hcaptcha: "https://hcaptcha.com/siteverify", + recaptcha: "https://www.google.com/recaptcha/api/siteverify", +} + +export async function verifyCaptcha(response: string, ip?: string) { + const { security } = Config.get(); + const { service, secret, sitekey } = security.captcha; + + if (!service) throw new Error("Cannot verify captcha without service"); + + const res = await fetch(verifyEndpoints[service], { + method: "POST", + headers: { + "Content-Type": "application/x-www-form-urlencoded", + }, + body: `response=${encodeURIComponent(response)}` + + `&secret=${encodeURIComponent(secret!)}` + + `&sitekey=${encodeURIComponent(sitekey!)}` + + (ip ? `&remoteip=${encodeURIComponent(ip!)}` : ""), + }); + + return await res.json() as hcaptchaResponse | recaptchaResponse; +} \ No newline at end of file diff --git a/api/src/util/utility/ipAddress.ts b/src/api/util/utility/ipAddress.ts
index 13cc9603..c96feb9e 100644 --- a/api/src/util/utility/ipAddress.ts +++ b/src/api/util/utility/ipAddress.ts
@@ -65,7 +65,7 @@ export async function IPAnalysis(ip: string): Promise<typeof exampleData> { const { ipdataApiKey } = Config.get().security; if (!ipdataApiKey) return { ...exampleData, ip }; - return (await fetch(`https://api.ipdata.co/${ip}?api-key=${ipdataApiKey}`)).json(); + return (await fetch(`https://api.ipdata.co/${ip}?api-key=${ipdataApiKey}`)).json() as any; } export function isProxy(data: typeof exampleData) { @@ -78,7 +78,11 @@ export function isProxy(data: typeof exampleData) { export function getIpAdress(req: Request): string { // @ts-ignore - return req.headers[Config.get().security.forwadedFor] || req.socket.remoteAddress; + return ( + req.headers[Config.get().security.forwadedFor as string] || + req.headers[Config.get().security.forwadedFor?.toLowerCase() as string] || + req.socket.remoteAddress + ); } export function distanceBetweenLocations(loc1: any, loc2: any): number { diff --git a/api/src/util/utility/passwordStrength.ts b/src/api/util/utility/passwordStrength.ts
index 439700d0..ff83d3df 100644 --- a/api/src/util/utility/passwordStrength.ts +++ b/src/api/util/utility/passwordStrength.ts
@@ -1,5 +1,4 @@ import { Config } from "@fosscord/util"; -import "missing-native-js-functions"; const reNUMBER = /[0-9]/g; const reUPPERCASELETTER = /[A-Z]/g; @@ -19,7 +18,7 @@ const blocklist: string[] = []; // TODO: update ones passwordblocklist is stored */ export function checkPassword(password: string): number { const { minLength, minNumbers, minUpperCase, minSymbols } = Config.get().register.password; - var strength = 0; + let strength = 0; // checks for total password len if (password.length >= minLength - 1) { @@ -45,16 +44,16 @@ export function checkPassword(password: string): number { if (password.length == password.count(reNUMBER) || password.length === password.count(reUPPERCASELETTER)) { strength = 0; } - + let entropyMap: { [key: string]: number } = {}; for (let i = 0; i < password.length; i++) { if (entropyMap[password[i]]) entropyMap[password[i]]++; else entropyMap[password[i]] = 1; } - + let entropies = Object.values(entropyMap); - - entropies.map(x => (x / entropyMap.length)); - strength += entropies.reduceRight((a: number, x: number) => a - (x * Math.log2(x))) / Math.log2(password.length); + + entropies.map((x) => x / entropyMap.length); + strength += entropies.reduceRight((a: number, x: number) => a - x * Math.log2(x)) / Math.log2(password.length); return strength; } diff --git a/cdn/src/Server.ts b/src/cdn/Server.ts
index b8d71fa9..ec5edc68 100644 --- a/cdn/src/Server.ts +++ b/src/cdn/Server.ts
@@ -1,9 +1,9 @@ +import { Config, getOrInitialiseDatabase, registerRoutes } from "@fosscord/util"; +import bodyParser from "body-parser"; import { Server, ServerOptions } from "lambert-server"; -import { Config, initDatabase, registerRoutes } from "@fosscord/util"; import path from "path"; import avatarsRoute from "./routes/avatars"; import iconsRoute from "./routes/role-icons"; -import bodyParser from "body-parser"; export interface CDNServerOptions extends ServerOptions {} @@ -15,7 +15,7 @@ export class CDNServer extends Server { } async start() { - await initDatabase(); + await getOrInitialiseDatabase(); await Config.init(); this.app.use((req, res, next) => { res.set("Access-Control-Allow-Origin", "*"); @@ -24,14 +24,8 @@ export class CDNServer extends Server { "Content-security-policy", "default-src * data: blob: filesystem: about: ws: wss: 'unsafe-inline' 'unsafe-eval'; script-src * data: blob: 'unsafe-inline' 'unsafe-eval'; connect-src * data: blob: 'unsafe-inline'; img-src * data: blob: 'unsafe-inline'; frame-src * data: blob: ; style-src * data: blob: 'unsafe-inline'; font-src * data: blob: 'unsafe-inline';" ); - res.set( - "Access-Control-Allow-Headers", - req.header("Access-Control-Request-Headers") || "*" - ); - res.set( - "Access-Control-Allow-Methods", - req.header("Access-Control-Request-Methods") || "*" - ); + res.set("Access-Control-Allow-Headers", req.header("Access-Control-Request-Headers") || "*"); + res.set("Access-Control-Allow-Methods", req.header("Access-Control-Request-Methods") || "*"); next(); }); this.app.use(bodyParser.json({ inflate: true, limit: "10mb" })); diff --git a/cdn/src/index.ts b/src/cdn/index.ts
index a24300d6..e32fd606 100644 --- a/cdn/src/index.ts +++ b/src/cdn/index.ts
@@ -1,4 +1,4 @@ export * from "./Server"; export * from "./util/FileStorage"; -export * from "./util/Storage"; export * from "./util/multer"; +export * from "./util/Storage"; diff --git a/src/cdn/routes/attachments.ts b/src/cdn/routes/attachments.ts new file mode 100644
index 00000000..013f03d8 --- /dev/null +++ b/src/cdn/routes/attachments.ts
@@ -0,0 +1,77 @@ +import { Config, HTTPError, Snowflake } from "@fosscord/util"; +import { Request, Response, Router } from "express"; +import FileType from "file-type"; +import imageSize from "image-size"; +import { multer } from "../util/multer"; +import { storage } from "../util/Storage"; + +const router = Router(); + +const SANITIZED_CONTENT_TYPE = ["text/html", "text/mhtml", "multipart/related", "application/xhtml+xml"]; + +router.post("/:channel_id", multer.single("file"), async (req: Request, res: Response) => { + if (req.headers.signature !== Config.get().security.requestSignature) throw new HTTPError("Invalid request signature"); + if (!req.file) throw new HTTPError("file missing"); + + const { buffer, mimetype, size, originalname, fieldname } = req.file; + const { channel_id } = req.params; + const filename = originalname.replaceAll(" ", "_").replace(/[^a-zA-Z0-9._]+/g, ""); + const id = Snowflake.generate(); + const path = `attachments/${channel_id}/${id}/${filename}`; + + const endpoint = Config.get()?.cdn.endpointPublic || "http://localhost:3003"; + + await storage.set(path, buffer); + let width; + let height; + if (mimetype.includes("image")) { + const dimensions = imageSize(buffer); + if (dimensions) { + width = dimensions.width; + height = dimensions.height; + } + } + + const file = { + id, + content_type: mimetype, + filename: filename, + size, + url: `${endpoint}/${path}`, + width, + height + }; + + return res.json(file); +}); + +router.get("/:channel_id/:id/:filename", async (req: Request, res: Response) => { + const { channel_id, id, filename } = req.params; + + const file = await storage.get(`attachments/${channel_id}/${id}/${filename}`); + if (!file) throw new HTTPError("File not found"); + const type = await FileType.fromBuffer(file); + let content_type = type?.mime || "application/octet-stream"; + + if (SANITIZED_CONTENT_TYPE.includes(content_type)) { + content_type = "application/octet-stream"; + } + + res.set("Content-Type", content_type); + res.set("Cache-Control", "public, max-age=31536000"); + + return res.send(file); +}); + +router.delete("/:channel_id/:id/:filename", async (req: Request, res: Response) => { + if (req.headers.signature !== Config.get().security.requestSignature) throw new HTTPError("Invalid request signature"); + + const { channel_id, id, filename } = req.params; + const path = `attachments/${channel_id}/${id}/${filename}`; + + await storage.delete(path); + + return res.send({ success: true }); +}); + +export default router; diff --git a/cdn/src/routes/avatars.ts b/src/cdn/routes/avatars.ts
index 2a4a0ffe..fa26938f 100644 --- a/cdn/src/routes/avatars.ts +++ b/src/cdn/routes/avatars.ts
@@ -1,10 +1,9 @@ -import { Router, Response, Request } from "express"; -import { Config, Snowflake } from "@fosscord/util"; -import { storage } from "../util/Storage"; -import FileType from "file-type"; -import { HTTPError } from "lambert-server"; +import { Config, HTTPError, Snowflake } from "@fosscord/util"; import crypto from "crypto"; +import { Request, Response, Router } from "express"; +import FileType from "file-type"; import { multer } from "../util/multer"; +import { storage } from "../util/Storage"; // TODO: check premium and animated pfp are allowed in the config // TODO: generate different sizes of icon @@ -12,54 +11,38 @@ import { multer } from "../util/multer"; // TODO: delete old icons const ANIMATED_MIME_TYPES = ["image/apng", "image/gif", "image/gifv"]; -const STATIC_MIME_TYPES = [ - "image/png", - "image/jpeg", - "image/webp", - "image/svg+xml", - "image/svg", -]; +const STATIC_MIME_TYPES = ["image/png", "image/jpeg", "image/webp", "image/svg+xml", "image/svg"]; const ALLOWED_MIME_TYPES = [...ANIMATED_MIME_TYPES, ...STATIC_MIME_TYPES]; const router = Router(); -router.post( - "/:user_id", - multer.single("file"), - async (req: Request, res: Response) => { - if (req.headers.signature !== Config.get().security.requestSignature) - throw new HTTPError("Invalid request signature"); - if (!req.file) throw new HTTPError("Missing file"); - const { buffer, mimetype, size, originalname, fieldname } = req.file; - const { user_id } = req.params; - - var hash = crypto - .createHash("md5") - .update(Snowflake.generate()) - .digest("hex"); - - const type = await FileType.fromBuffer(buffer); - if (!type || !ALLOWED_MIME_TYPES.includes(type.mime)) - throw new HTTPError("Invalid file type"); - if (ANIMATED_MIME_TYPES.includes(type.mime)) hash = `a_${hash}`; // animated icons have a_ infront of the hash - - const path = `avatars/${user_id}/${hash}`; - const endpoint = - Config.get().cdn.endpointPublic || "http://localhost:3003"; - - await storage.set(path, buffer); - - return res.json({ - id: hash, - content_type: type.mime, - size, - url: `${endpoint}${req.baseUrl}/${user_id}/${hash}`, - }); - } -); +router.post("/:user_id", multer.single("file"), async (req: Request, res: Response) => { + if (req.headers.signature !== Config.get().security.requestSignature) throw new HTTPError("Invalid request signature"); + if (!req.file) throw new HTTPError("Missing file"); + const { buffer, mimetype, size, originalname, fieldname } = req.file; + const { user_id } = req.params; + + let hash = crypto.createHash("md5").update(Snowflake.generate()).digest("hex"); + + const type = await FileType.fromBuffer(buffer); + if (!type || !ALLOWED_MIME_TYPES.includes(type.mime)) throw new HTTPError("Invalid file type"); + if (ANIMATED_MIME_TYPES.includes(type.mime)) hash = `a_${hash}`; // animated icons have a_ infront of the hash + + const path = `avatars/${user_id}/${hash}`; + const endpoint = Config.get().cdn.endpointPublic || "http://localhost:3003"; + + await storage.set(path, buffer); + + return res.json({ + id: hash, + content_type: type.mime, + size, + url: `${endpoint}${req.baseUrl}/${user_id}/${hash}` + }); +}); router.get("/:user_id", async (req: Request, res: Response) => { - var { user_id } = req.params; + let { user_id } = req.params; user_id = user_id.split(".")[0]; // remove .file extension const path = `avatars/${user_id}`; @@ -74,7 +57,7 @@ router.get("/:user_id", async (req: Request, res: Response) => { }); router.get("/:user_id/:hash", async (req: Request, res: Response) => { - var { user_id, hash } = req.params; + let { user_id, hash } = req.params; hash = hash.split(".")[0]; // remove .file extension const path = `avatars/${user_id}/${hash}`; @@ -89,8 +72,7 @@ router.get("/:user_id/:hash", async (req: Request, res: Response) => { }); router.delete("/:user_id/:id", async (req: Request, res: Response) => { - if (req.headers.signature !== Config.get().security.requestSignature) - throw new HTTPError("Invalid request signature"); + if (req.headers.signature !== Config.get().security.requestSignature) throw new HTTPError("Invalid request signature"); const { user_id, id } = req.params; const path = `avatars/${user_id}/${id}`; diff --git a/cdn/src/routes/external.ts b/src/cdn/routes/external.ts
index dc90e3c8..7ccf9b8a 100644 --- a/cdn/src/routes/external.ts +++ b/src/cdn/routes/external.ts
@@ -1,10 +1,8 @@ -import { Router, Response, Request } from "express"; +import { Config, HTTPError, Snowflake } from "@fosscord/util"; +import { Request, Response, Router } from "express"; +import FileType from "file-type"; import fetch from "node-fetch"; -import { HTTPError } from "lambert-server"; -import { Snowflake } from "@fosscord/util"; import { storage } from "../util/Storage"; -import FileType from "file-type"; -import { Config } from "@fosscord/util"; // TODO: somehow handle the deletion of images posted to the /external route @@ -13,17 +11,15 @@ const DEFAULT_FETCH_OPTIONS: any = { redirect: "follow", follow: 1, headers: { - "user-agent": - "Mozilla/5.0 (compatible Fosscordbot/0.1; +https://fosscord.com)", + "user-agent": "Mozilla/5.0 (compatible Fosscordbot/0.1; +https://fosscord.com)" }, size: 1024 * 1024 * 8, compress: true, - method: "GET", + method: "GET" }; router.post("/", async (req: Request, res: Response) => { - if (req.headers.signature !== Config.get().security.requestSignature) - throw new HTTPError("Invalid request signature"); + if (req.headers.signature !== Config.get().security.requestSignature) throw new HTTPError("Invalid request signature"); if (!req.body) throw new HTTPError("Invalid Body"); diff --git a/cdn/src/routes/ping.ts b/src/cdn/routes/ping.ts
index 38daf81e..420cf567 100644 --- a/cdn/src/routes/ping.ts +++ b/src/cdn/routes/ping.ts
@@ -1,4 +1,4 @@ -import { Router, Response, Request } from "express"; +import { Request, Response, Router } from "express"; const router = Router(); diff --git a/cdn/src/routes/role-icons.ts b/src/cdn/routes/role-icons.ts
index 12aae8a4..768e194f 100644 --- a/cdn/src/routes/role-icons.ts +++ b/src/cdn/routes/role-icons.ts
@@ -1,10 +1,9 @@ -import { Router, Response, Request } from "express"; -import { Config, Snowflake } from "@fosscord/util"; -import { storage } from "../util/Storage"; -import FileType from "file-type"; -import { HTTPError } from "lambert-server"; +import { Config, HTTPError, Snowflake } from "@fosscord/util"; import crypto from "crypto"; +import { Request, Response, Router } from "express"; +import FileType from "file-type"; import { multer } from "../util/multer"; +import { storage } from "../util/Storage"; //Role icons ---> avatars.ts modified @@ -12,53 +11,37 @@ import { multer } from "../util/multer"; // TODO: generate different sizes of icon // TODO: generate different image types of icon -const STATIC_MIME_TYPES = [ - "image/png", - "image/jpeg", - "image/webp", - "image/svg+xml", - "image/svg", -]; +const STATIC_MIME_TYPES = ["image/png", "image/jpeg", "image/webp", "image/svg+xml", "image/svg"]; const ALLOWED_MIME_TYPES = [...STATIC_MIME_TYPES]; const router = Router(); -router.post( - "/:role_id", - multer.single("file"), - async (req: Request, res: Response) => { - if (req.headers.signature !== Config.get().security.requestSignature) - throw new HTTPError("Invalid request signature"); - if (!req.file) throw new HTTPError("Missing file"); - const { buffer, mimetype, size, originalname, fieldname } = req.file; - const { role_id } = req.params; - - var hash = crypto - .createHash("md5") - .update(Snowflake.generate()) - .digest("hex"); - - const type = await FileType.fromBuffer(buffer); - if (!type || !ALLOWED_MIME_TYPES.includes(type.mime)) - throw new HTTPError("Invalid file type"); - - const path = `role-icons/${role_id}/${hash}.png`; - const endpoint = - Config.get().cdn.endpointPublic || "http://localhost:3003"; - - await storage.set(path, buffer); - - return res.json({ - id: hash, - content_type: type.mime, - size, - url: `${endpoint}${req.baseUrl}/${role_id}/${hash}`, - }); - } -); +router.post("/:role_id", multer.single("file"), async (req: Request, res: Response) => { + if (req.headers.signature !== Config.get().security.requestSignature) throw new HTTPError("Invalid request signature"); + if (!req.file) throw new HTTPError("Missing file"); + const { buffer, mimetype, size, originalname, fieldname } = req.file; + const { role_id } = req.params; + + let hash = crypto.createHash("md5").update(Snowflake.generate()).digest("hex"); + + const type = await FileType.fromBuffer(buffer); + if (!type || !ALLOWED_MIME_TYPES.includes(type.mime)) throw new HTTPError("Invalid file type"); + + const path = `role-icons/${role_id}/${hash}.png`; + const endpoint = Config.get().cdn.endpointPublic || "http://localhost:3003"; + + await storage.set(path, buffer); + + return res.json({ + id: hash, + content_type: type.mime, + size, + url: `${endpoint}${req.baseUrl}/${role_id}/${hash}` + }); +}); router.get("/:role_id", async (req: Request, res: Response) => { - var { role_id } = req.params; + let { role_id } = req.params; //role_id = role_id.split(".")[0]; // remove .file extension const path = `role-icons/${role_id}`; @@ -73,7 +56,7 @@ router.get("/:role_id", async (req: Request, res: Response) => { }); router.get("/:role_id/:hash", async (req: Request, res: Response) => { - var { role_id, hash } = req.params; + let { role_id, hash } = req.params; //hash = hash.split(".")[0]; // remove .file extension const path = `role-icons/${role_id}/${hash}`; @@ -88,8 +71,7 @@ router.get("/:role_id/:hash", async (req: Request, res: Response) => { }); router.delete("/:role_id/:id", async (req: Request, res: Response) => { - if (req.headers.signature !== Config.get().security.requestSignature) - throw new HTTPError("Invalid request signature"); + if (req.headers.signature !== Config.get().security.requestSignature) throw new HTTPError("Invalid request signature"); const { role_id, id } = req.params; const path = `role-icons/${role_id}/${id}`; diff --git a/cdn/src/start.ts b/src/cdn/start.ts
index 71681b40..71681b40 100644 --- a/cdn/src/start.ts +++ b/src/cdn/start.ts
diff --git a/cdn/src/util/FileStorage.ts b/src/cdn/util/FileStorage.ts
index 84ecf556..fea013a6 100644 --- a/cdn/src/util/FileStorage.ts +++ b/src/cdn/util/FileStorage.ts
@@ -1,20 +1,18 @@ -import { Storage } from "./Storage"; import fs from "fs"; -import fse from "fs-extra"; -import { join, relative, dirname } from "path"; -import "missing-native-js-functions"; +import { dirname, join } from "path"; import { Readable } from "stream"; -import ExifTransformer = require("exif-be-gone"); +import { Storage } from "./Storage"; +//import ExifTransformer = require("exif-be-gone"); +import ExifTransformer from "exif-be-gone"; // TODO: split stored files into separate folders named after cloned route function getPath(path: string) { // STORAGE_LOCATION has a default value in start.ts const root = process.env.STORAGE_LOCATION || "../"; - var filename = join(root, path); + let filename = join(root, path); - if (path.indexOf("\0") !== -1 || !filename.startsWith(root)) - throw new Error("invalid path"); + if (path.indexOf("\0") !== -1 || !filename.startsWith(root)) throw new Error("invalid path"); return filename; } @@ -36,7 +34,8 @@ export class FileStorage implements Storage { async set(path: string, value: any) { path = getPath(path); - fse.ensureDirSync(dirname(path)); + //fse.ensureDirSync(dirname(path)); + fs.mkdirSync(dirname(path), { recursive: true }); value = Readable.from(value); const cleaned_file = fs.createWriteStream(path); diff --git a/cdn/src/util/S3Storage.ts b/src/cdn/util/S3Storage.ts
index c4066817..a7892e5e 100644 --- a/cdn/src/util/S3Storage.ts +++ b/src/cdn/util/S3Storage.ts
@@ -11,11 +11,7 @@ const readableToBuffer = (readable: Readable): Promise<Buffer> => }); export class S3Storage implements Storage { - public constructor( - private client: S3, - private bucket: string, - private basePath?: string - ) {} + public constructor(private client: S3, private bucket: string, private basePath?: string) {} /** * Always return a string, to ensure consistency. @@ -28,7 +24,7 @@ export class S3Storage implements Storage { await this.client.putObject({ Bucket: this.bucket, Key: `${this.bucketBasePath}${path}`, - Body: data, + Body: data }); } @@ -36,7 +32,7 @@ export class S3Storage implements Storage { try { const s3Object = await this.client.getObject({ Bucket: this.bucket, - Key: `${this.bucketBasePath ?? ""}${path}`, + Key: `${this.bucketBasePath ?? ""}${path}` }); if (!s3Object.Body) return null; @@ -54,7 +50,7 @@ export class S3Storage implements Storage { async delete(path: string): Promise<void> { await this.client.deleteObject({ Bucket: this.bucket, - Key: `${this.bucketBasePath}${path}`, + Key: `${this.bucketBasePath}${path}` }); } } diff --git a/cdn/src/util/Storage.ts b/src/cdn/util/Storage.ts
index 89dd5634..1ab6a1d9 100644 --- a/cdn/src/util/Storage.ts +++ b/src/cdn/util/Storage.ts
@@ -1,8 +1,9 @@ -import { FileStorage } from "./FileStorage"; import path from "path"; -import fse from "fs-extra"; -import { bgCyan, black } from "picocolors"; +import { FileStorage } from "./FileStorage"; +//import fse from "fs-extra"; import { S3 } from "@aws-sdk/client-s3"; +import fs from "fs"; +import { bgCyan, black } from "picocolors"; import { S3Storage } from "./S3Storage"; process.cwd(); @@ -22,7 +23,8 @@ if (process.env.STORAGE_PROVIDER === "file" || !process.env.STORAGE_PROVIDER) { location = path.join(process.cwd(), "files"); } console.log(`[CDN] storage location: ${bgCyan(`${black(location)}`)}`); - fse.ensureDirSync(location); + //fse.ensureDirSync(location); + fs.mkdirSync(location, { recursive: true }); process.env.STORAGE_LOCATION = location; storage = new FileStorage(); @@ -31,16 +33,12 @@ if (process.env.STORAGE_PROVIDER === "file" || !process.env.STORAGE_PROVIDER) { bucket = process.env.STORAGE_BUCKET; if (!region) { - console.error( - `[CDN] You must provide a region when using the S3 storage provider.` - ); + console.error(`[CDN] You must provide a region when using the S3 storage provider.`); process.exit(1); } if (!bucket) { - console.error( - `[CDN] You must provide a bucket when using the S3 storage provider.` - ); + console.error(`[CDN] You must provide a bucket when using the S3 storage provider.`); process.exit(1); } @@ -48,9 +46,7 @@ if (process.env.STORAGE_PROVIDER === "file" || !process.env.STORAGE_PROVIDER) { let location = process.env.STORAGE_LOCATION; if (!location) { - console.warn( - `[CDN] STORAGE_LOCATION unconfigured for S3 provider, defaulting to the bucket root...` - ); + console.warn(`[CDN] STORAGE_LOCATION unconfigured for S3 provider, defaulting to the bucket root...`); location = undefined; } diff --git a/cdn/src/util/index.ts b/src/cdn/util/index.ts
index 07a5c31a..07a5c31a 100644 --- a/cdn/src/util/index.ts +++ b/src/cdn/util/index.ts
diff --git a/cdn/src/util/multer.ts b/src/cdn/util/multer.ts
index bfdf6aff..f56b0fb5 100644 --- a/cdn/src/util/multer.ts +++ b/src/cdn/util/multer.ts
@@ -5,6 +5,6 @@ export const multer = multerConfig({ limits: { fields: 10, files: 10, - fileSize: 1024 * 1024 * 100, // 100 mb - }, + fileSize: 1024 * 1024 * 100 // 100 mb + } }); diff --git a/gateway/src/Server.ts b/src/gateway/Server.ts
index 7e1489be..97da3fa0 100644 --- a/gateway/src/Server.ts +++ b/src/gateway/Server.ts
@@ -1,10 +1,9 @@ -import "missing-native-js-functions"; +import { closeDatabase, Config, getOrInitialiseDatabase, initEvent } from "@fosscord/util"; import dotenv from "dotenv"; -dotenv.config(); -import { closeDatabase, Config, initDatabase, initEvent } from "@fosscord/util"; +import http from "http"; import ws from "ws"; import { Connection } from "./events/Connection"; -import http from "http"; +dotenv.config(); export class Server { public ws: ws.Server; @@ -12,15 +11,7 @@ export class Server { public server: http.Server; public production: boolean; - constructor({ - port, - server, - production, - }: { - port: number; - server?: http.Server; - production?: boolean; - }) { + constructor({ port, server, production }: { port: number; server?: http.Server; production?: boolean }) { this.port = port; this.production = production || false; @@ -40,14 +31,14 @@ export class Server { this.ws = new ws.Server({ maxPayload: 4096, - noServer: true, + noServer: true }); this.ws.on("connection", Connection); this.ws.on("error", console.error); } async start(): Promise<void> { - await initDatabase(); + await getOrInitialiseDatabase(); await Config.init(); await initEvent(); if (!this.server.listening) { diff --git a/gateway/src/events/Close.ts b/src/gateway/events/Close.ts
index 5b7c512c..34831eab 100644 --- a/gateway/src/events/Close.ts +++ b/src/gateway/events/Close.ts
@@ -1,12 +1,5 @@ import { WebSocket } from "@fosscord/gateway"; -import { - emitEvent, - PresenceUpdateEvent, - PrivateSessionProjection, - Session, - SessionsReplace, - User, -} from "@fosscord/util"; +import { emitEvent, PresenceUpdateEvent, PrivateSessionProjection, Session, SessionsReplace, User } from "@fosscord/util"; export async function Close(this: WebSocket, code: number, reason: string) { console.log("[WebSocket] closed", code, reason); @@ -19,17 +12,17 @@ export async function Close(this: WebSocket, code: number, reason: string) { await Session.delete({ session_id: this.session_id }); const sessions = await Session.find({ where: { user_id: this.user_id }, - select: PrivateSessionProjection, + select: PrivateSessionProjection }); await emitEvent({ event: "SESSIONS_REPLACE", user_id: this.user_id, - data: sessions, + data: sessions } as SessionsReplace); const session = sessions.first() || { activities: [], client_info: {}, - status: "offline", + status: "offline" }; await emitEvent({ @@ -39,8 +32,8 @@ export async function Close(this: WebSocket, code: number, reason: string) { user: await User.getPublicUser(this.user_id), activities: session.activities, client_status: session?.client_info, - status: session.status, - }, + status: session.status + } } as PresenceUpdateEvent); } } diff --git a/gateway/src/events/Connection.ts b/src/gateway/events/Connection.ts
index 4954cd08..5a5ce48f 100644 --- a/gateway/src/events/Connection.ts +++ b/src/gateway/events/Connection.ts
@@ -1,14 +1,14 @@ -import WS from "ws"; import { WebSocket } from "@fosscord/gateway"; -import { Send } from "../util/Send"; +import { IncomingMessage } from "http"; +import { URL } from "url"; +import WS from "ws"; +import { createDeflate } from "zlib"; import { CLOSECODES, OPCODES } from "../util/Constants"; import { setHeartbeat } from "../util/Heartbeat"; -import { IncomingMessage } from "http"; +import { Send } from "../util/Send"; import { Close } from "./Close"; import { Message } from "./Message"; -import { createDeflate } from "zlib"; -import { URL } from "url"; -var erlpack: any; +let erlpack: any; try { erlpack = require("@yukikaze-bot/erlpack"); } catch (error) {} @@ -17,16 +17,27 @@ try { // TODO: specify rate limit in config // TODO: check msg max size -export async function Connection( - this: WS.Server, - socket: WebSocket, - request: IncomingMessage -) { +export async function Connection(this: WS.Server, socket: WebSocket, request: IncomingMessage) { try { // @ts-ignore socket.on("close", Close); // @ts-ignore socket.on("message", Message); + + if (process.env.WS_LOGEVENTS) + [ + "close", + "error", + "upgrade", + //"message", + "open", + "ping", + "pong", + "unexpected-response" + ].forEach((x) => { + socket.on(x, (y) => console.log(x, y)); + }); + console.log(`[Gateway] Connections: ${this.clients.size}`); const { searchParams } = new URL(`http://localhost${request.url}`); @@ -34,23 +45,19 @@ export async function Connection( socket.encoding = searchParams.get("encoding") || "json"; if (!["json", "etf"].includes(socket.encoding)) { if (socket.encoding === "etf" && erlpack) { - throw new Error( - "Erlpack is not installed: 'npm i @yukikaze-bot/erlpack'" - ); + throw new Error("Erlpack is not installed: 'npm i @yukikaze-bot/erlpack'"); } return socket.close(CLOSECODES.Decode_error); } // @ts-ignore socket.version = Number(searchParams.get("version")) || 8; - if (socket.version != 8) - return socket.close(CLOSECODES.Invalid_API_version); + if (socket.version != 8) return socket.close(CLOSECODES.Invalid_API_version); // @ts-ignore socket.compress = searchParams.get("compress") || ""; if (socket.compress) { - if (socket.compress !== "zlib-stream") - return socket.close(CLOSECODES.Decode_error); + if (socket.compress !== "zlib-stream") return socket.close(CLOSECODES.Decode_error); socket.deflate = createDeflate({ chunkSize: 65535 }); socket.deflate.on("data", (chunk) => socket.send(chunk)); } @@ -65,8 +72,8 @@ export async function Connection( await Send(socket, { op: OPCODES.Hello, d: { - heartbeat_interval: 1000 * 30, - }, + heartbeat_interval: 1000 * 30 + } }); socket.readyTimeout = setTimeout(() => { diff --git a/src/gateway/events/Message.ts b/src/gateway/events/Message.ts new file mode 100644
index 00000000..96950a42 --- /dev/null +++ b/src/gateway/events/Message.ts
@@ -0,0 +1,57 @@ +import { Payload, WebSocket } from "@fosscord/gateway"; +import OPCodeHandlers from "../opcodes"; +import { check } from "../opcodes/instanceOf"; +import { CLOSECODES } from "../util/Constants"; +let erlpack: any; +try { + erlpack = require("@yukikaze-bot/erlpack"); +} catch (error) {} + +const PayloadSchema = { + op: Number, + $d: Object || Number, // or number for heartbeat sequence + $s: Number, + $t: String +}; + +export async function Message(this: WebSocket, buffer: Buffer) { + // TODO: compression + let data: Payload; + + if (this.encoding === "etf" && buffer instanceof Buffer) data = erlpack.unpack(buffer); + else if (this.encoding === "json") + data = JSON.parse(buffer as unknown as string); //TODO: is this even correct?? seems to work for web clients... + else if (/--debug|--inspect/.test(process.execArgv.join(" "))) { + debugger; + return; + } else { + console.log("Invalid gateway connection! Use a debugger to inspect!"); + return; + } + + if (process.env.WS_VERBOSE) console.log(`[Websocket] Incomming message: ${JSON.stringify(data)}`); + if (data.op !== 1) check.call(this, PayloadSchema, data); + else { + //custom validation for numbers, because heartbeat + if (data.s || data.t || (typeof data.d !== "number" && data.d)) { + console.log("Invalid heartbeat..."); + this.close(CLOSECODES.Decode_error); + } + } + + // @ts-ignore + const OPCodeHandler = OPCodeHandlers[data.op]; + if (!OPCodeHandler) { + console.error("[Gateway] Unkown opcode " + data.op); + // TODO: if all opcodes are implemented comment this out: + // this.close(CLOSECODES.Unknown_opcode); + return; + } + + try { + return await OPCodeHandler.call(this, data); + } catch (error) { + console.error(error); + if (!this.CLOSED && this.CLOSING) return this.close(CLOSECODES.Unknown_error); + } +} diff --git a/gateway/src/index.ts b/src/gateway/index.ts
index d77ce931..730347f9 100644 --- a/gateway/src/index.ts +++ b/src/gateway/index.ts
@@ -1,4 +1,4 @@ +export * from "./listener/listener"; +export * from "./opcodes/"; export * from "./Server"; export * from "./util/"; -export * from "./opcodes/"; -export * from "./listener/listener"; diff --git a/gateway/src/listener/listener.ts b/src/gateway/listener/listener.ts
index 060de65b..811318af 100644 --- a/gateway/src/listener/listener.ts +++ b/src/gateway/listener/listener.ts
@@ -1,21 +1,20 @@ +import { WebSocket } from "@fosscord/gateway"; import { + EVENTEnum, + EventOpts, getPermission, - Permissions, - RabbitMQ, listenEvent, - EventOpts, ListenEventOpts, Member, - EVENTEnum, + Permissions, + RabbitMQ, + Recipient, Relationship, - RelationshipType, + RelationshipType } from "@fosscord/util"; +import { Channel as AMQChannel } from "amqplib"; import { OPCODES } from "../util/Constants"; import { Send } from "../util/Send"; -import { WebSocket } from "@fosscord/gateway"; -import "missing-native-js-functions"; -import { Channel as AMQChannel } from "amqplib"; -import { Recipient } from "@fosscord/util"; // TODO: close connection on Invalidated Token // TODO: check intent @@ -24,17 +23,14 @@ import { Recipient } from "@fosscord/util"; // Sharding: calculate if the current shard id matches the formula: shard_id = (guild_id >> 22) % num_shards // https://discord.com/developers/docs/topics/gateway#sharding -export function handlePresenceUpdate( - this: WebSocket, - { event, acknowledge, data }: EventOpts -) { +export function handlePresenceUpdate(this: WebSocket, { event, acknowledge, data }: EventOpts) { acknowledge?.(); if (event === EVENTEnum.PresenceUpdate) { return Send(this, { op: OPCODES.Dispatch, t: event, d: data, - s: this.sequence++, + s: this.sequence++ }); } } @@ -44,23 +40,25 @@ export async function setupListener(this: WebSocket) { const [members, recipients, relationships] = await Promise.all([ Member.find({ where: { id: this.user_id }, - relations: ["guild", "guild.channels"], + relations: ["guild", "guild.channels"] }), Recipient.find({ where: { user_id: this.user_id, closed: false }, - relations: ["channel"], + relations: ["channel"] }), Relationship.find({ - from_id: this.user_id, - type: RelationshipType.friends, - }), + where: { + from_id: this.user_id, + type: RelationshipType.friends + } + }) ]); const guilds = members.map((x) => x.guild); const dm_channels = recipients.map((x) => x.channel); const opts: { acknowledge: boolean; channel?: AMQChannel } = { - acknowledge: true, + acknowledge: true }; this.listen_options = opts; const consumer = consume.bind(this); @@ -74,11 +72,7 @@ export async function setupListener(this: WebSocket) { this.events[this.user_id] = await listenEvent(this.user_id, consumer, opts); relationships.forEach(async (relationship) => { - this.events[relationship.to_id] = await listenEvent( - relationship.to_id, - handlePresenceUpdate.bind(this), - opts - ); + this.events[relationship.to_id] = await listenEvent(relationship.to_id, handlePresenceUpdate.bind(this), opts); }); dm_channels.forEach(async (channel) => { @@ -91,16 +85,8 @@ export async function setupListener(this: WebSocket) { this.events[guild.id] = await listenEvent(guild.id, consumer, opts); guild.channels.forEach(async (channel) => { - if ( - permission - .overwriteChannel(channel.permission_overwrites!) - .has("VIEW_CHANNEL") - ) { - this.events[channel.id] = await listenEvent( - channel.id, - consumer, - opts - ); + if (permission.overwriteChannel(channel.permission_overwrites!).has("VIEW_CHANNEL")) { + this.events[channel.id] = await listenEvent(channel.id, consumer, opts); } }); }); @@ -132,11 +118,7 @@ async function consume(this: WebSocket, opts: EventOpts) { delete this.member_events[data.user.id]; case "GUILD_MEMBER_ADD": if (this.member_events[data.user.id]) break; // already subscribed - this.member_events[data.user.id] = await listenEvent( - data.user.id, - handlePresenceUpdate.bind(this), - this.listen_options - ); + this.member_events[data.user.id] = await listenEvent(data.user.id, handlePresenceUpdate.bind(this), this.listen_options); break; case "GUILD_MEMBER_REMOVE": if (!this.member_events[data.user.id]) break; @@ -149,21 +131,13 @@ async function consume(this: WebSocket, opts: EventOpts) { opts.cancel(); break; case "CHANNEL_CREATE": - if ( - !permission - .overwriteChannel(data.permission_overwrites) - .has("VIEW_CHANNEL") - ) { + if (!permission.overwriteChannel(data.permission_overwrites).has("VIEW_CHANNEL")) { return; } this.events[id] = await listenEvent(id, consumer, listenOpts); break; case "RELATIONSHIP_ADD": - this.events[data.user.id] = await listenEvent( - data.user.id, - handlePresenceUpdate.bind(this), - this.listen_options - ); + this.events[data.user.id] = await listenEvent(data.user.id, handlePresenceUpdate.bind(this), this.listen_options); break; case "GUILD_CREATE": this.events[id] = await listenEvent(id, consumer, listenOpts); @@ -171,11 +145,7 @@ async function consume(this: WebSocket, opts: EventOpts) { case "CHANNEL_UPDATE": const exists = this.events[id]; // @ts-ignore - if ( - permission - .overwriteChannel(data.permission_overwrites) - .has("VIEW_CHANNEL") - ) { + if (permission.overwriteChannel(data.permission_overwrites).has("VIEW_CHANNEL")) { if (exists) break; this.events[id] = await listenEvent(id, consumer, listenOpts); } else { @@ -245,6 +215,6 @@ async function consume(this: WebSocket, opts: EventOpts) { op: OPCODES.Dispatch, t: event, d: data, - s: this.sequence++, + s: this.sequence++ }); } diff --git a/gateway/src/opcodes/Heartbeat.ts b/src/gateway/opcodes/Heartbeat.ts
index 50394130..42b72d4b 100644 --- a/gateway/src/opcodes/Heartbeat.ts +++ b/src/gateway/opcodes/Heartbeat.ts
@@ -2,7 +2,7 @@ import { Payload, WebSocket } from "@fosscord/gateway"; import { setHeartbeat } from "../util/Heartbeat"; import { Send } from "../util/Send"; -export async function onHeartbeat(this: WebSocket, data: Payload) { +export async function onHeartbeat(this: WebSocket, _data: Payload) { // TODO: validate payload setHeartbeat(this); diff --git a/gateway/src/opcodes/Identify.ts b/src/gateway/opcodes/Identify.ts
index 860000da..ac6955fd 100644 --- a/gateway/src/opcodes/Identify.ts +++ b/src/gateway/opcodes/Identify.ts
@@ -1,33 +1,35 @@ -import { WebSocket, Payload } from "@fosscord/gateway"; +import { Payload, WebSocket } from "@fosscord/gateway"; import { + Application, checkToken, + Config, + emitEvent, + EVENTEnum, + IdentifySchema, Intents, Member, - ReadyEventData, - User, - Session, - EVENTEnum, - Config, + MemberPrivateProjection, + OrmUtils, + PresenceUpdateEvent, + PrivateSessionProjection, + PrivateUserProjection, PublicMember, PublicUser, - PrivateUserProjection, ReadState, - Application, - emitEvent, + ReadyEventData, + Recipient, + Session, SessionsReplace, - PrivateSessionProjection, - MemberPrivateProjection, - PresenceUpdateEvent, + User, + UserSettings } from "@fosscord/util"; -import { Send } from "../util/Send"; +import { setupListener } from "../listener/listener"; import { CLOSECODES, OPCODES } from "../util/Constants"; +import { Send } from "../util/Send"; import { genSessionId } from "../util/SessionUtils"; -import { setupListener } from "../listener/listener"; -import { IdentifySchema } from "../schema/Identify"; +import { check } from "./instanceOf"; // import experiments from "./experiments.json"; const experiments: any = []; -import { check } from "./instanceOf"; -import { Recipient } from "@fosscord/util"; // TODO: user sharding // TODO: check privileged intents, if defined in the config @@ -51,57 +53,49 @@ export async function onIdentify(this: WebSocket, data: Payload) { const session_id = genSessionId(); this.session_id = session_id; //Set the session of the WebSocket object - const [user, read_states, members, recipients, session, application] = - await Promise.all([ - User.findOneOrFail({ - where: { id: this.user_id }, - relations: ["relationships", "relationships.to"], - select: [...PrivateUserProjection, "relationships"], - }), - ReadState.find({ user_id: this.user_id }), - Member.find({ - where: { id: this.user_id }, - select: MemberPrivateProjection, - relations: [ - "guild", - "guild.channels", - "guild.emojis", - "guild.emojis.user", - "guild.roles", - "guild.stickers", - "user", - "roles", - ], - }), - Recipient.find({ - where: { user_id: this.user_id, closed: false }, - relations: [ - "channel", - "channel.recipients", - "channel.recipients.user", - ], - // TODO: public user selection - }), - // save the session and delete it when the websocket is closed - new Session({ - user_id: this.user_id, - session_id: session_id, - // TODO: check if status is only one of: online, dnd, offline, idle - status: identify.presence?.status || "offline", //does the session always start as online? - client_info: { - //TODO read from identity - client: "desktop", - os: identify.properties?.os, - version: 0, - }, - activities: [], - }).save(), - Application.findOne({ id: this.user_id }), - ]); + const [user, read_states, members, recipients, session, application] = await Promise.all([ + User.findOneOrFail({ + where: { id: this.user_id }, + relations: ["relationships", "relationships.to", "settings"], + select: [...PrivateUserProjection, "relationships"] + }), + ReadState.find({ where: { user_id: this.user_id } }), + Member.find({ + where: { id: this.user_id }, + select: MemberPrivateProjection, + relations: ["guild", "guild.channels", "guild.emojis", "guild.emojis.user", "guild.roles", "guild.stickers", "user", "roles"] + }), + Recipient.find({ + where: { user_id: this.user_id, closed: false }, + relations: ["channel", "channel.recipients", "channel.recipients.user"] + // TODO: public user selection + }), + // save the session and delete it when the websocket is closed + await OrmUtils.mergeDeep(new Session(), { + user_id: this.user_id, + session_id: session_id, + // TODO: check if status is only one of: online, dnd, offline, idle + status: identify.presence?.status || "offline", //does the session always start as online? + client_info: { + //TODO read from identity + client: "desktop", + os: identify.properties?.os, + version: 0 + }, + activities: [] + }).save(), + Application.findOne({ where: { id: this.user_id } }) + ]); if (!user) return this.close(CLOSECODES.Authentication_failed); + if (!user.settings) { + //settings may not exist after updating... + user.settings = new UserSettings(); + user.settings.id = user.id; + //await (user.settings as UserSettings).save(); + } - if (!identify.intents) identify.intents = BigInt("0x6ffffffff"); + if (!identify.intents) identify.intents = "30064771071"; this.intents = new Intents(identify.intents); if (identify.shard) { this.shard_id = identify.shard[0]; @@ -117,7 +111,7 @@ export async function onIdentify(this: WebSocket, data: Payload) { return this.close(CLOSECODES.Invalid_shard); } } - var users: PublicUser[] = []; + let users: PublicUser[] = []; const merged_members = members.map((x: Member) => { return [ @@ -125,8 +119,8 @@ export async function onIdentify(this: WebSocket, data: Payload) { ...x, roles: x.roles.map((x) => x.id), settings: undefined, - guild: undefined, - }, + guild: undefined + } ]; }) as PublicMember[][]; let guilds = members.map((x) => ({ ...x.guild, joined_at: x.joined_at })); @@ -139,7 +133,7 @@ export async function onIdentify(this: WebSocket, data: Payload) { op: OPCODES.Dispatch, t: EVENTEnum.GuildCreate, s: this.sequence++, - d: guild, + d: guild }); }, 500); return { id: guild.id, unavailable: true }; @@ -156,9 +150,7 @@ export async function onIdentify(this: WebSocket, data: Payload) { //TODO is this needed? check if users in group dm that are not friends are sent in the READY event users = users.concat(x.channel.recipients as unknown as User[]); if (x.channel.isDm()) { - x.channel.recipients = x.channel.recipients!.filter( - (x) => x.id !== this.user_id - ); + x.channel.recipients = x.channel.recipients!.filter((x) => x.id !== this.user_id); } return x.channel; }); @@ -185,8 +177,8 @@ export async function onIdentify(this: WebSocket, data: Payload) { user_id: this.user_id, data: await Session.find({ where: { user_id: this.user_id }, - select: PrivateSessionProjection, - }), + select: PrivateSessionProjection + }) } as SessionsReplace); emitEvent({ event: "PRESENCE_UPDATE", @@ -195,8 +187,8 @@ export async function onIdentify(this: WebSocket, data: Payload) { user: await User.getPublicUser(this.user_id), activities: session.activities, client_status: session?.client_info, - status: session.status, - }, + status: session.status + } } as PresenceUpdateEvent); }); @@ -231,7 +223,7 @@ export async function onIdentify(this: WebSocket, data: Payload) { const d: ReadyEventData = { v: 8, - application, + application: { id: application?.id ?? "", flags: application?.flags ?? 0 }, //TODO: check this code! user: privateUser, user_settings: user.settings, // @ts-ignore @@ -248,12 +240,12 @@ export async function onIdentify(this: WebSocket, data: Payload) { read_state: { entries: read_states, partial: false, - version: 304128, + version: 304128 }, user_guild_settings: { entries: user_guild_settings_entries, partial: false, // TODO partial - version: 642, + version: 642 }, private_channels: channels, session_id: session_id, @@ -261,8 +253,8 @@ export async function onIdentify(this: WebSocket, data: Payload) { connected_accounts: [], // TODO consents: { personalization: { - consented: false, // TODO - }, + consented: false // TODO + } }, country_code: user.settings.locale, friend_suggestion_count: 0, // TODO @@ -270,7 +262,7 @@ export async function onIdentify(this: WebSocket, data: Payload) { experiments: experiments, // TODO guild_join_requests: [], // TODO what is this? users: users.filter((x) => x).unique(), - merged_members: merged_members, + merged_members: merged_members // shard // TODO: only for user sharding }; @@ -279,7 +271,7 @@ export async function onIdentify(this: WebSocket, data: Payload) { op: OPCODES.Dispatch, t: EVENTEnum.Ready, s: this.sequence++, - d, + d }); //TODO send READY_SUPPLEMENTAL diff --git a/gateway/src/opcodes/LazyRequest.ts b/src/gateway/opcodes/LazyRequest.ts
index 2156070f..ea69779e 100644 --- a/gateway/src/opcodes/LazyRequest.ts +++ b/src/gateway/opcodes/LazyRequest.ts
@@ -1,12 +1,8 @@ -import { getPermission, listenEvent, Member, Role } from "@fosscord/util"; -import { LazyRequest } from "../schema/LazyRequest"; -import { Send } from "../util/Send"; +import { handlePresenceUpdate, Payload, WebSocket } from "@fosscord/gateway"; +import { getOrInitialiseDatabase, getPermission, LazyRequest, listenEvent, Member, Role } from "@fosscord/util"; import { OPCODES } from "../util/Constants"; -import { WebSocket, Payload, handlePresenceUpdate } from "@fosscord/gateway"; +import { Send } from "../util/Send"; import { check } from "./instanceOf"; -import "missing-native-js-functions"; -import { getRepository } from "typeorm"; -import "missing-native-js-functions"; // TODO: only show roles/members that have access to this channel // TODO: config: to list all members (even those who are offline) sorted by role, or just those who are online @@ -17,17 +13,18 @@ async function getMembers(guild_id: string, range: [number, number]) { throw new Error("range is not a valid array"); } // TODO: wait for typeorm to implement ordering for .find queries https://github.com/typeorm/typeorm/issues/2620 + // TODO: rewrite this, released in 0.3.0 - let members = await getRepository(Member) + let members: Member[] = await ( + await getOrInitialiseDatabase() + ) + .getRepository(Member) .createQueryBuilder("member") .where("member.guild_id = :guild_id", { guild_id }) .leftJoinAndSelect("member.roles", "role") .leftJoinAndSelect("member.user", "user") .leftJoinAndSelect("user.sessions", "session") - .addSelect( - "CASE WHEN session.status = 'offline' THEN 0 ELSE 1 END", - "_status" - ) + .addSelect("CASE WHEN session.status = 'offline' THEN 0 ELSE 1 END", "_status") .orderBy("role.position", "DESC") .addOrderBy("_status", "DESC") .addOrderBy("user.username", "ASC") @@ -42,28 +39,26 @@ async function getMembers(guild_id: string, range: [number, number]) { .flat() .unique((r: Role) => r.id); + const offlineItems = []; + for (const role of member_roles) { // @ts-ignore - const [role_members, other_members] = partition(members, (m: Member) => - m.roles.find((r) => r.id === role.id) - ); + const [role_members, other_members] = partition(members, (m: Member) => m.roles.find((r) => r.id === role.id)); const group = { count: role_members.length, - id: role.id === guild_id ? "online" : role.id, + id: role.id === guild_id ? "online" : role.id }; items.push({ group }); groups.push(group); for (const member of role_members) { - const roles = member.roles - .filter((x: Role) => x.id !== guild_id) - .map((x: Role) => x.id); + const roles = member.roles.filter((x: Role) => x.id !== guild_id).map((x: Role) => x.id); const session = member.user.sessions.first(); // TODO: properly mock/hide offline/invisible status - items.push({ + const item = { member: { ...member, roles, @@ -71,19 +66,38 @@ async function getMembers(guild_id: string, range: [number, number]) { presence: { ...session, activities: session?.activities || [], - user: { id: member.user.id }, - }, - }, - }); + user: { id: member.user.id } + } + } + }; + + if (!member?.user?.sessions || !member.user.sessions.length) { + offlineItems.push(item); + group.count--; + continue; + } + + items.push(item); } members = other_members; } + if (offlineItems.length) { + const group = { + count: offlineItems.length, + id: "offline" + }; + items.push({ group }); + groups.push(group); + + items.push(...offlineItems); + } + return { items, groups, range, - members: items.map((x) => x.member).filter((x) => x), + members: items.map((x) => ("member" in x ? x.member : undefined)).filter((x) => !!x) }; } @@ -101,7 +115,7 @@ export async function onLazyRequest(this: WebSocket, { d }: Payload) { const ranges = channels![channel_id]; if (!Array.isArray(ranges)) throw new Error("Not a valid Array"); - const member_count = await Member.count({ guild_id }); + const member_count = await Member.count({ where: { guild_id } }); const ops = await Promise.all(ranges.map((x) => getMembers(guild_id, x))); // TODO: unsubscribe member_events that are not in op.members @@ -110,11 +124,7 @@ export async function onLazyRequest(this: WebSocket, { d }: Payload) { op.members.forEach(async (member) => { if (this.events[member.user.id]) return; // already subscribed as friend if (this.member_events[member.user.id]) return; // already subscribed in member list - this.member_events[member.user.id] = await listenEvent( - member.user.id, - handlePresenceUpdate.bind(this), - this.listen_options - ); + this.member_events[member.user.id] = await listenEvent(member.user.id, handlePresenceUpdate.bind(this), this.listen_options); }); }); @@ -126,7 +136,7 @@ export async function onLazyRequest(this: WebSocket, { d }: Payload) { ops: ops.map((x) => ({ items: x.items, op: "SYNC", - range: x.range, + range: x.range })), online_count: member_count, member_count, @@ -135,8 +145,8 @@ export async function onLazyRequest(this: WebSocket, { d }: Payload) { groups: ops .map((x) => x.groups) .flat() - .unique(), - }, + .unique() + } }); } @@ -145,9 +155,7 @@ function partition<T>(array: T[], isValid: Function) { return array.reduce( // @ts-ignore ([pass, fail], elem) => { - return isValid(elem) - ? [[...pass, elem], fail] - : [pass, [...fail, elem]]; + return isValid(elem) ? [[...pass, elem], fail] : [pass, [...fail, elem]]; }, [[], []] ); diff --git a/gateway/src/opcodes/PresenceUpdate.ts b/src/gateway/opcodes/PresenceUpdate.ts
index 415df6ee..ad712234 100644 --- a/gateway/src/opcodes/PresenceUpdate.ts +++ b/src/gateway/opcodes/PresenceUpdate.ts
@@ -1,16 +1,12 @@ -import { WebSocket, Payload } from "@fosscord/gateway"; -import { emitEvent, PresenceUpdateEvent, Session, User } from "@fosscord/util"; -import { ActivitySchema } from "../schema/Activity"; +import { Payload, WebSocket } from "@fosscord/gateway"; +import { ActivitySchema, emitEvent, PresenceUpdateEvent, Session, User } from "@fosscord/util"; import { check } from "./instanceOf"; export async function onPresenceUpdate(this: WebSocket, { d }: Payload) { check.call(this, ActivitySchema, d); const presence = d as ActivitySchema; - await Session.update( - { session_id: this.session_id }, - { status: presence.status, activities: presence.activities } - ); + await Session.update({ session_id: this.session_id }, { status: presence.status, activities: presence.activities }); await emitEvent({ event: "PRESENCE_UPDATE", @@ -19,7 +15,7 @@ export async function onPresenceUpdate(this: WebSocket, { d }: Payload) { user: await User.getPublicUser(this.user_id), activities: presence.activities, client_status: {}, // TODO: - status: presence.status, - }, + status: presence.status + } } as PresenceUpdateEvent); } diff --git a/gateway/src/opcodes/RequestGuildMembers.ts b/src/gateway/opcodes/RequestGuildMembers.ts
index b80721dc..b80721dc 100644 --- a/gateway/src/opcodes/RequestGuildMembers.ts +++ b/src/gateway/opcodes/RequestGuildMembers.ts
diff --git a/gateway/src/opcodes/Resume.ts b/src/gateway/opcodes/Resume.ts
index 42dc586d..f320864b 100644 --- a/gateway/src/opcodes/Resume.ts +++ b/src/gateway/opcodes/Resume.ts
@@ -1,11 +1,11 @@ -import { WebSocket, Payload } from "@fosscord/gateway"; +import { Payload, WebSocket } from "@fosscord/gateway"; import { Send } from "../util/Send"; export async function onResume(this: WebSocket, data: Payload) { console.log("Got Resume -> cancel not implemented"); await Send(this, { op: 9, - d: false, + d: false }); // return this.close(CLOSECODES.Invalid_session); diff --git a/gateway/src/opcodes/VoiceStateUpdate.ts b/src/gateway/opcodes/VoiceStateUpdate.ts
index 321e6b17..20502584 100644 --- a/gateway/src/opcodes/VoiceStateUpdate.ts +++ b/src/gateway/opcodes/VoiceStateUpdate.ts
@@ -1,17 +1,18 @@ -import { VoiceStateUpdateSchema } from "../schema/VoiceStateUpdateSchema"; import { Payload, WebSocket } from "@fosscord/gateway"; -import { genVoiceToken } from "../util/SessionUtils"; -import { check } from "./instanceOf"; import { Config, emitEvent, Guild, Member, + OrmUtils, Region, VoiceServerUpdateEvent, VoiceState, VoiceStateUpdateEvent, + VoiceStateUpdateSchema } from "@fosscord/util"; +import { genVoiceToken } from "../util/SessionUtils"; +import { check } from "./instanceOf"; // TODO: check if a voice server is setup // Notice: Bot users respect the voice channel's user limit, if set. When the voice channel is full, you will not receive the Voice State Update or Voice Server Update events in response to your own Voice State Update. Having MANAGE_CHANNELS permission bypasses this limit and allows you to join regardless of the channel being full or not. @@ -19,42 +20,41 @@ export async function onVoiceStateUpdate(this: WebSocket, data: Payload) { check.call(this, VoiceStateUpdateSchema, data.d); const body = data.d as VoiceStateUpdateSchema; + if (body.guild_id == null) { + console.log(`[Gateway] VoiceStateUpdate called with guild_id == null by user ${this.user_id}!`); + return; + } + let voiceState: VoiceState; try { voiceState = await VoiceState.findOneOrFail({ - where: { user_id: this.user_id }, + where: { user_id: this.user_id } }); - if ( - voiceState.session_id !== this.session_id && - body.channel_id === null - ) { + if (voiceState.session_id !== this.session_id && body.channel_id === null) { //Should we also check guild_id === null? //changing deaf or mute on a client that's not the one with the same session of the voicestate in the database should be ignored return; } //If a user change voice channel between guild we should send a left event first - if ( - voiceState.guild_id !== body.guild_id && - voiceState.session_id === this.session_id - ) { + if (voiceState.guild_id !== body.guild_id && voiceState.session_id === this.session_id) { await emitEvent({ event: "VOICE_STATE_UPDATE", data: { ...voiceState, channel_id: null }, - guild_id: voiceState.guild_id, + guild_id: voiceState.guild_id }); } //The event send by Discord's client on channel leave has both guild_id and channel_id as null if (body.guild_id === null) body.guild_id = voiceState.guild_id; - voiceState.assign(body); + voiceState = OrmUtils.mergeDeep(voiceState, body); } catch (error) { - voiceState = new VoiceState({ + voiceState = OrmUtils.mergeDeep(new VoiceState(), { ...body, user_id: this.user_id, deaf: false, mute: false, - suppress: false, + suppress: false }); } @@ -63,12 +63,11 @@ export async function onVoiceStateUpdate(this: WebSocket, data: Payload) { //TODO this may fail voiceState.member = await Member.findOneOrFail({ where: { id: voiceState.user_id, guild_id: voiceState.guild_id }, - relations: ["user", "roles"], + relations: ["user", "roles"] }); //If the session changed we generate a new token - if (voiceState.session_id !== this.session_id) - voiceState.token = genVoiceToken(); + if (voiceState.session_id !== this.session_id) voiceState.token = genVoiceToken(); voiceState.session_id = this.session_id; const { id, ...newObj } = voiceState; @@ -78,23 +77,19 @@ export async function onVoiceStateUpdate(this: WebSocket, data: Payload) { emitEvent({ event: "VOICE_STATE_UPDATE", data: newObj, - guild_id: voiceState.guild_id, - } as VoiceStateUpdateEvent), + guild_id: voiceState.guild_id + } as VoiceStateUpdateEvent) ]); //If it's null it means that we are leaving the channel and this event is not needed if (voiceState.channel_id !== null) { - const guild = await Guild.findOne({ id: voiceState.guild_id }); + const guild = await Guild.findOne({ where: { id: voiceState.guild_id } }); const regions = Config.get().regions; let guildRegion: Region; if (guild && guild.region) { - guildRegion = regions.available.filter( - (r) => r.id === guild.region - )[0]; + guildRegion = regions.available.filter((r) => r.id === guild.region)[0]; } else { - guildRegion = regions.available.filter( - (r) => r.id === regions.default - )[0]; + guildRegion = regions.available.filter((r) => r.id === regions.default)[0]; } await emitEvent({ @@ -102,9 +97,9 @@ export async function onVoiceStateUpdate(this: WebSocket, data: Payload) { data: { token: voiceState.token, guild_id: voiceState.guild_id, - endpoint: guildRegion.endpoint, + endpoint: guildRegion.endpoint }, - guild_id: voiceState.guild_id, + guild_id: voiceState.guild_id } as VoiceServerUpdateEvent); } } diff --git a/gateway/src/opcodes/experiments.json b/src/gateway/opcodes/experiments.json
index 0370b5da..0370b5da 100644 --- a/gateway/src/opcodes/experiments.json +++ b/src/gateway/opcodes/experiments.json
diff --git a/gateway/src/opcodes/index.ts b/src/gateway/opcodes/index.ts
index 027739db..d5dc7de1 100644 --- a/gateway/src/opcodes/index.ts +++ b/src/gateway/opcodes/index.ts
@@ -1,4 +1,4 @@ -import { WebSocket, Payload } from "@fosscord/gateway"; +import { Payload, WebSocket } from "@fosscord/gateway"; import { onHeartbeat } from "./Heartbeat"; import { onIdentify } from "./Identify"; import { onLazyRequest } from "./LazyRequest"; @@ -21,5 +21,5 @@ export default { // 9: Invalid Session // 10: Hello // 13: Dm_update - 14: onLazyRequest, + 14: onLazyRequest }; diff --git a/gateway/src/opcodes/instanceOf.ts b/src/gateway/opcodes/instanceOf.ts
index 6fd50852..95d74963 100644 --- a/gateway/src/opcodes/instanceOf.ts +++ b/src/gateway/opcodes/instanceOf.ts
@@ -1,5 +1,5 @@ -import { instanceOf } from "lambert-server"; import { WebSocket } from "@fosscord/gateway"; +import { instanceOf } from "@fosscord/util"; import { CLOSECODES } from "../util/Constants"; export function check(this: WebSocket, schema: any, data: any) { diff --git a/gateway/src/start.ts b/src/gateway/start.ts
index 09a54751..97420d7e 100644 --- a/gateway/src/start.ts +++ b/src/gateway/start.ts
@@ -1,14 +1,14 @@ process.on("uncaughtException", console.error); process.on("unhandledRejection", console.error); -import { Server } from "./Server"; import { config } from "dotenv"; +import { Server } from "./Server"; config(); -var port = Number(process.env.PORT); +let port = Number(process.env.PORT); if (isNaN(port)) port = 3002; const server = new Server({ - port, + port }); server.start(); diff --git a/gateway/src/util/Constants.ts b/src/gateway/util/Constants.ts
index 692f9028..78455ff8 100644 --- a/gateway/src/util/Constants.ts +++ b/src/gateway/util/Constants.ts
@@ -22,7 +22,7 @@ export enum OPCODES { Stream_Watch = 20, Stream_Ping = 21, Stream_Set_Paused = 22, - Request_Application_Commands = 24, + Request_Application_Commands = 24 } export enum CLOSECODES { Unknown_error = 4000, @@ -39,7 +39,7 @@ export enum CLOSECODES { Sharding_required, Invalid_API_version, Invalid_intent, - Disallowed_intent, + Disallowed_intent } export interface Payload { diff --git a/gateway/src/util/Heartbeat.ts b/src/gateway/util/Heartbeat.ts
index f6871cfe..f6871cfe 100644 --- a/gateway/src/util/Heartbeat.ts +++ b/src/gateway/util/Heartbeat.ts
diff --git a/gateway/src/util/Send.ts b/src/gateway/util/Send.ts
index c8627b03..7826dd40 100644 --- a/gateway/src/util/Send.ts +++ b/src/gateway/util/Send.ts
@@ -1,4 +1,4 @@ -var erlpack: any; +let erlpack: any; try { erlpack = require("@yukikaze-bot/erlpack"); } catch (error) { @@ -7,6 +7,7 @@ try { import { Payload, WebSocket } from "@fosscord/gateway"; export async function Send(socket: WebSocket, data: Payload) { + if (process.env.WS_VERBOSE) console.log(`[Websocket] Outgoing message: ${JSON.stringify(data)}`); let buffer: Buffer | string; if (socket.encoding === "etf") buffer = erlpack.pack(data); // TODO: encode circular object diff --git a/gateway/src/util/SessionUtils.ts b/src/gateway/util/SessionUtils.ts
index bf854042..c66c7e76 100644 --- a/gateway/src/util/SessionUtils.ts +++ b/src/gateway/util/SessionUtils.ts
@@ -7,7 +7,5 @@ export function genVoiceToken() { } function genRanHex(size: number) { - return [...Array(size)] - .map(() => Math.floor(Math.random() * 16).toString(16)) - .join(""); + return [...Array(size)].map(() => Math.floor(Math.random() * 16).toString(16)).join(""); } diff --git a/gateway/src/util/WebSocket.ts b/src/gateway/util/WebSocket.ts
index e3313f40..9496da85 100644 --- a/gateway/src/util/WebSocket.ts +++ b/src/gateway/util/WebSocket.ts
@@ -8,8 +8,8 @@ export interface WebSocket extends WS { session_id: string; encoding: "etf" | "json"; compress?: "zlib-stream"; - shard_count?: bigint; - shard_id?: bigint; + shard_count?: number; + shard_id?: number; deflate?: Deflate; heartbeatTimeout: NodeJS.Timeout; readyTimeout: NodeJS.Timeout; diff --git a/gateway/src/util/index.ts b/src/gateway/util/index.ts
index 0be5ecee..a5085228 100644 --- a/gateway/src/util/index.ts +++ b/src/gateway/util/index.ts
@@ -1,5 +1,5 @@ export * from "./Constants"; +export * from "./Heartbeat"; export * from "./Send"; export * from "./SessionUtils"; -export * from "./Heartbeat"; export * from "./WebSocket"; diff --git a/bundle/src/start.ts b/src/start.ts
index de3b5848..cf1a42a5 100644 --- a/bundle/src/start.ts +++ b/src/start.ts
@@ -1,19 +1,19 @@ // process.env.MONGOMS_DEBUG = "true"; -import "reflect-metadata"; +import { execSync } from "child_process"; import cluster, { Worker } from "cluster"; +import { config } from "dotenv"; import os from "os"; -import { red, bold, yellow, cyan } from "picocolors"; +import { bold, cyan, red, yellow } from "picocolors"; +import "reflect-metadata"; import { initStats } from "./stats"; -import { config } from "dotenv"; config(); -import { execSync } from "child_process"; // TODO: add socket event transmission -var cores = 1; +let cores = 1; try { cores = Number(process.env.THREADS) || os.cpus().length; } catch { - console.log("[API] Failed to get thread count! Using 1...") + console.log("[API] Failed to get thread count! Using 1..."); } if (cluster.isMaster) { @@ -35,19 +35,9 @@ if (cluster.isMaster) { ██║ ╚██████╔╝███████║███████║╚██████╗╚██████╔╝██║ ██║██████╔╝ ╚═╝ ╚═════╝ ╚══════╝╚══════╝ ╚═════╝ ╚═════╝ ╚═╝ ╚═╝╚═════╝ - fosscord-server | ${yellow( - `Pre-release (${ - commit !== null - ? commit.slice(0, 7) - : "Unknown (Git cannot be found)" - })` - )} + fosscord-server | ${yellow(`Pre-release (${commit !== null ? commit.slice(0, 7) : "Unknown (Git cannot be found)"})`)} -Commit Hash: ${ - commit !== null - ? `${cyan(commit)} (${yellow(commit.slice(0, 7))})` - : "Unknown (Git cannot be found)" - } +Commit Hash: ${commit !== null ? `${cyan(commit)} (${yellow(commit.slice(0, 7))})` : "Unknown (Git cannot be found)"} Cores: ${cyan(os.cpus().length)} (Using ${cores} thread(s).) `) ); @@ -84,11 +74,7 @@ Cores: ${cyan(os.cpus().length)} (Using ${cores} thread(s).) }); cluster.on("exit", (worker: any, code: any, signal: any) => { - console.log( - `[Worker] ${red( - `died with PID: ${worker.process.pid} , restarting ...` - )}` - ); + console.log(`[Worker] ${red(`died with PID: ${worker.process.pid} , restarting ...`)}`); cluster.fork(); }); } diff --git a/src/stats.ts b/src/stats.ts new file mode 100644
index 00000000..05f0ab77 --- /dev/null +++ b/src/stats.ts
@@ -0,0 +1,21 @@ +import os from "os"; +import { red } from "picocolors"; + +export function initStats() { + console.log(`[Path] running in ${__dirname}`); + try { + console.log(`[CPU] ${os.cpus()[0].model} Cores x${os.cpus().length}`); + } catch { + console.log("[CPU] Failed to get cpu model!"); + } + + console.log(`[System] ${os.platform()} ${os.arch()}`); + console.log(`[Process] running with PID: ${process.pid}`); + if (process.getuid && process.getuid() === 0) { + console.warn( + red( + `[Process] Warning fosscord is running as root, this highly discouraged and might expose your system vulnerable to attackers. Please run fosscord as a user without root privileges.` + ) + ); + } +} diff --git a/src/util/config/Config.ts b/src/util/config/Config.ts new file mode 100644
index 00000000..36c4509d --- /dev/null +++ b/src/util/config/Config.ts
@@ -0,0 +1,46 @@ +import { + ApiConfiguration, + ClientConfiguration, + DefaultsConfiguration, + EndpointConfiguration, + GeneralConfiguration, + GifConfiguration, + GuildConfiguration, + KafkaConfiguration, + LimitsConfiguration, + LoginConfiguration, + MetricsConfiguration, + RabbitMQConfiguration, + RegionConfiguration, + RegisterConfiguration, + SecurityConfiguration, + SentryConfiguration, + TemplateConfiguration +} from "."; + +export class ConfigValue { + gateway: EndpointConfiguration = { + endpointPublic: '${location.protocol === "https:" ? "wss://" : "ws://"}${location.host}', + endpointPrivate: `ws://localhost:3001` + }; + cdn: EndpointConfiguration = { + endpointPublic: "${location.host}", + endpointPrivate: `http://localhost:3001` + }; + api: ApiConfiguration = new ApiConfiguration(); + general: GeneralConfiguration = new GeneralConfiguration(); + limits: LimitsConfiguration = new LimitsConfiguration(); + security: SecurityConfiguration = new SecurityConfiguration(); + login: LoginConfiguration = new LoginConfiguration(); + register: RegisterConfiguration = new RegisterConfiguration(); + regions: RegionConfiguration = new RegionConfiguration(); + guild: GuildConfiguration = new GuildConfiguration(); + gif: GifConfiguration = new GifConfiguration(); + rabbitmq: RabbitMQConfiguration = new RabbitMQConfiguration(); + kafka: KafkaConfiguration = new KafkaConfiguration(); + templates: TemplateConfiguration = new TemplateConfiguration(); + client: ClientConfiguration = new ClientConfiguration(); + metrics: MetricsConfiguration = new MetricsConfiguration(); + sentry: SentryConfiguration = new SentryConfiguration(); + defaults: DefaultsConfiguration = new DefaultsConfiguration(); +} diff --git a/src/util/config/index.ts b/src/util/config/index.ts new file mode 100644
index 00000000..0a9b58ae --- /dev/null +++ b/src/util/config/index.ts
@@ -0,0 +1,2 @@ +export * from "./Config"; +export * from "./types/index"; diff --git a/src/util/config/types/ApiConfiguration.ts b/src/util/config/types/ApiConfiguration.ts new file mode 100644
index 00000000..442a5986 --- /dev/null +++ b/src/util/config/types/ApiConfiguration.ts
@@ -0,0 +1,5 @@ +export class ApiConfiguration { + defaultVersion: string = "9"; + activeVersions: string[] = ["6", "7", "8", "9"]; + useFosscordEnhancements: boolean = true; +} diff --git a/src/util/config/types/ClientConfiguration.ts b/src/util/config/types/ClientConfiguration.ts new file mode 100644
index 00000000..44704404 --- /dev/null +++ b/src/util/config/types/ClientConfiguration.ts
@@ -0,0 +1,8 @@ +import { ClientReleaseConfiguration } from "."; + +export class ClientConfiguration { + //classes + releases: ClientReleaseConfiguration = new ClientReleaseConfiguration(); + //base types + useTestClient: boolean = true; +} diff --git a/src/util/config/types/DefaultsConfiguration.ts b/src/util/config/types/DefaultsConfiguration.ts new file mode 100644
index 00000000..d5ee39e7 --- /dev/null +++ b/src/util/config/types/DefaultsConfiguration.ts
@@ -0,0 +1,6 @@ +import { GuildDefaults, UserDefaults } from "."; + +export class DefaultsConfiguration { + guild: GuildDefaults = new GuildDefaults(); + user: UserDefaults = new UserDefaults(); +} diff --git a/src/util/config/types/EndpointConfiguration.ts b/src/util/config/types/EndpointConfiguration.ts new file mode 100644
index 00000000..26f3106f --- /dev/null +++ b/src/util/config/types/EndpointConfiguration.ts
@@ -0,0 +1,4 @@ +export class EndpointConfiguration { + endpointPrivate: string | null = null; + endpointPublic: string | null = null; +} diff --git a/src/util/config/types/GeneralConfiguration.ts b/src/util/config/types/GeneralConfiguration.ts new file mode 100644
index 00000000..5cb8df89 --- /dev/null +++ b/src/util/config/types/GeneralConfiguration.ts
@@ -0,0 +1,12 @@ +import { Snowflake } from "../../util"; + +export class GeneralConfiguration { + instanceName: string = "Fosscord Instance"; + instanceDescription: string | null = "This is a Fosscord instance made in the pre-release days"; + frontPage: string | null = null; + tosPage: string | null = null; + correspondenceEmail: string | null = "noreply@localhost.local"; + correspondenceUserID: string | null = null; + image: string | null = null; + instanceId: string = Snowflake.generate(); +} diff --git a/src/util/config/types/GifConfiguration.ts b/src/util/config/types/GifConfiguration.ts new file mode 100644
index 00000000..565c2ac0 --- /dev/null +++ b/src/util/config/types/GifConfiguration.ts
@@ -0,0 +1,5 @@ +export class GifConfiguration { + enabled: boolean = true; + provider: "tenor" = "tenor"; // more coming soon + apiKey?: string = "LIVDSRZULELA"; +} diff --git a/src/util/config/types/GuildConfiguration.ts b/src/util/config/types/GuildConfiguration.ts new file mode 100644
index 00000000..ebc1b442 --- /dev/null +++ b/src/util/config/types/GuildConfiguration.ts
@@ -0,0 +1,6 @@ +import { AutoJoinConfiguration, DiscoveryConfiguration } from "."; + +export class GuildConfiguration { + discovery: DiscoveryConfiguration = new DiscoveryConfiguration(); + autoJoin: AutoJoinConfiguration = new AutoJoinConfiguration(); +} diff --git a/src/util/config/types/KafkaConfiguration.ts b/src/util/config/types/KafkaConfiguration.ts new file mode 100644
index 00000000..a3aa8058 --- /dev/null +++ b/src/util/config/types/KafkaConfiguration.ts
@@ -0,0 +1,5 @@ +import { KafkaBroker } from "."; + +export class KafkaConfiguration { + brokers: KafkaBroker[] | null = null; +} diff --git a/src/util/config/types/LimitConfigurations.ts b/src/util/config/types/LimitConfigurations.ts new file mode 100644
index 00000000..a3a52cf5 --- /dev/null +++ b/src/util/config/types/LimitConfigurations.ts
@@ -0,0 +1,9 @@ +import { ChannelLimits, GuildLimits, MessageLimits, RateLimits, UserLimits } from "."; + +export class LimitsConfiguration { + user: UserLimits = new UserLimits(); + guild: GuildLimits = new GuildLimits(); + message: MessageLimits = new MessageLimits(); + channel: ChannelLimits = new ChannelLimits(); + rate: RateLimits = new RateLimits(); +} diff --git a/src/util/config/types/LoginConfiguration.ts b/src/util/config/types/LoginConfiguration.ts new file mode 100644
index 00000000..d8b737b9 --- /dev/null +++ b/src/util/config/types/LoginConfiguration.ts
@@ -0,0 +1,3 @@ +export class LoginConfiguration { + requireCaptcha: boolean = false; +} diff --git a/src/util/config/types/MetricsConfiguration.ts b/src/util/config/types/MetricsConfiguration.ts new file mode 100644
index 00000000..f6b1d8e6 --- /dev/null +++ b/src/util/config/types/MetricsConfiguration.ts
@@ -0,0 +1,3 @@ +export class MetricsConfiguration { + timeout: number = 30000; +} diff --git a/src/util/config/types/RabbitMQConfiguration.ts b/src/util/config/types/RabbitMQConfiguration.ts new file mode 100644
index 00000000..bd4b6ca3 --- /dev/null +++ b/src/util/config/types/RabbitMQConfiguration.ts
@@ -0,0 +1,3 @@ +export class RabbitMQConfiguration { + host: string | null = null; +} diff --git a/src/util/config/types/RegionConfiguration.ts b/src/util/config/types/RegionConfiguration.ts new file mode 100644
index 00000000..b4b8c4a3 --- /dev/null +++ b/src/util/config/types/RegionConfiguration.ts
@@ -0,0 +1,16 @@ +import { Region } from "."; + +export class RegionConfiguration { + default: string = "fosscord"; + useDefaultAsOptimal: boolean = true; + available: Region[] = [ + { + id: "fosscord", + name: "Fosscord", + endpoint: "127.0.0.1:3004", + vip: false, + custom: false, + deprecated: false + } + ]; +} diff --git a/src/util/config/types/RegisterConfiguration.ts b/src/util/config/types/RegisterConfiguration.ts new file mode 100644
index 00000000..68946272 --- /dev/null +++ b/src/util/config/types/RegisterConfiguration.ts
@@ -0,0 +1,19 @@ +import { DateOfBirthConfiguration, EmailConfiguration, PasswordConfiguration } from "."; + +export class RegisterConfiguration { + //classes + email: EmailConfiguration = new EmailConfiguration(); + dateOfBirth: DateOfBirthConfiguration = new DateOfBirthConfiguration(); + password: PasswordConfiguration = new PasswordConfiguration(); + //base types + disabled: boolean = false; + requireCaptcha: boolean = true; + requireInvite: boolean = false; + allowGuests: boolean = true; + guestsRequireInvite: boolean = true; + allowNewRegistration: boolean = true; + allowMultipleAccounts: boolean = true; + blockProxies: boolean = true; + incrementingDiscriminators: boolean = false; // random otherwise + defaultRights: string = "0"; +} diff --git a/src/util/config/types/SecurityConfiguration.ts b/src/util/config/types/SecurityConfiguration.ts new file mode 100644
index 00000000..a2cebbd3 --- /dev/null +++ b/src/util/config/types/SecurityConfiguration.ts
@@ -0,0 +1,19 @@ +import crypto from "crypto"; +import { CaptchaConfiguration, TwoFactorConfiguration } from "."; + +export class SecurityConfiguration { + //classes + captcha: CaptchaConfiguration = new CaptchaConfiguration(); + twoFactor: TwoFactorConfiguration = new TwoFactorConfiguration(); + //base types + autoUpdate: boolean | number = true; + requestSignature: string = crypto.randomBytes(32).toString("base64"); + jwtSecret: string = crypto.randomBytes(256).toString("base64"); + // header to get the real user ip address + // X-Forwarded-For for nginx/reverse proxies + // CF-Connecting-IP for cloudflare + forwadedFor: string | null = null; + ipdataApiKey: string | null = "eca677b284b3bac29eb72f5e496aa9047f26543605efe99ff2ce35c9"; + mfaBackupCodeCount: number = 10; + mfaBackupCodeBytes: number = 4; +} diff --git a/src/util/config/types/SentryConfiguration.ts b/src/util/config/types/SentryConfiguration.ts new file mode 100644
index 00000000..d50f5f4c --- /dev/null +++ b/src/util/config/types/SentryConfiguration.ts
@@ -0,0 +1,8 @@ +import { hostname } from "os"; + +export class SentryConfiguration { + enabled: boolean = false; + endpoint: string = "https://05e8e3d005f34b7d97e920ae5870a5e5@sentry.thearcanebrony.net/6"; + traceSampleRate: number = 1.0; + environment: string = hostname(); +} diff --git a/src/util/config/types/TemplateConfiguration.ts b/src/util/config/types/TemplateConfiguration.ts new file mode 100644
index 00000000..aade2934 --- /dev/null +++ b/src/util/config/types/TemplateConfiguration.ts
@@ -0,0 +1,6 @@ +export class TemplateConfiguration { + enabled: boolean = true; + allowTemplateCreation: boolean = true; + allowDiscordTemplates: boolean = true; + allowRaws: boolean = true; +} diff --git a/src/util/config/types/index.ts b/src/util/config/types/index.ts new file mode 100644
index 00000000..a8cdff4c --- /dev/null +++ b/src/util/config/types/index.ts
@@ -0,0 +1,18 @@ +export * from "./ApiConfiguration"; +export * from "./ClientConfiguration"; +export * from "./DefaultsConfiguration"; +export * from "./EndpointConfiguration"; +export * from "./GeneralConfiguration"; +export * from "./GifConfiguration"; +export * from "./GuildConfiguration"; +export * from "./KafkaConfiguration"; +export * from "./LimitConfigurations"; +export * from "./LoginConfiguration"; +export * from "./MetricsConfiguration"; +export * from "./RabbitMQConfiguration"; +export * from "./RegionConfiguration"; +export * from "./RegisterConfiguration"; +export * from "./SecurityConfiguration"; +export * from "./SentryConfiguration"; +export * from "./subconfigurations/index"; +export * from "./TemplateConfiguration"; diff --git a/src/util/config/types/subconfigurations/client/ClientReleaseConfiguration.ts b/src/util/config/types/subconfigurations/client/ClientReleaseConfiguration.ts new file mode 100644
index 00000000..b082b711 --- /dev/null +++ b/src/util/config/types/subconfigurations/client/ClientReleaseConfiguration.ts
@@ -0,0 +1,4 @@ +export class ClientReleaseConfiguration { + useLocalRelease: boolean = true; //TODO + upstreamVersion: string = "0.0.264"; +} diff --git a/src/util/config/types/subconfigurations/client/index.ts b/src/util/config/types/subconfigurations/client/index.ts new file mode 100644
index 00000000..96bbb0ca --- /dev/null +++ b/src/util/config/types/subconfigurations/client/index.ts
@@ -0,0 +1 @@ +export * from "./ClientReleaseConfiguration"; diff --git a/src/util/config/types/subconfigurations/defaults/GuildDefaults.ts b/src/util/config/types/subconfigurations/defaults/GuildDefaults.ts new file mode 100644
index 00000000..435ae06c --- /dev/null +++ b/src/util/config/types/subconfigurations/defaults/GuildDefaults.ts
@@ -0,0 +1,8 @@ +export class GuildDefaults { + maxPresences: number = 250000; + maxVideoChannelUsers: number = 200; + afkTimeout: number = 300; + defaultMessageNotifications: number = 1; + explicitContentFilter: number = 0; + test: number = 123; +} diff --git a/src/util/config/types/subconfigurations/defaults/UserDefaults.ts b/src/util/config/types/subconfigurations/defaults/UserDefaults.ts new file mode 100644
index 00000000..cc56be36 --- /dev/null +++ b/src/util/config/types/subconfigurations/defaults/UserDefaults.ts
@@ -0,0 +1,5 @@ +export class UserDefaults { + premium: boolean = false; + premium_type: number = 2; + verified: boolean = true; +} diff --git a/src/util/config/types/subconfigurations/defaults/index.ts b/src/util/config/types/subconfigurations/defaults/index.ts new file mode 100644
index 00000000..50258d1c --- /dev/null +++ b/src/util/config/types/subconfigurations/defaults/index.ts
@@ -0,0 +1,2 @@ +export * from "./GuildDefaults"; +export * from "./UserDefaults"; diff --git a/src/util/config/types/subconfigurations/guild/AutoJoin.ts b/src/util/config/types/subconfigurations/guild/AutoJoin.ts new file mode 100644
index 00000000..4d7af352 --- /dev/null +++ b/src/util/config/types/subconfigurations/guild/AutoJoin.ts
@@ -0,0 +1,5 @@ +export class AutoJoinConfiguration { + enabled: boolean = true; + guilds: string[] = []; + canLeave: boolean = true; +} diff --git a/src/util/config/types/subconfigurations/guild/Discovery.ts b/src/util/config/types/subconfigurations/guild/Discovery.ts new file mode 100644
index 00000000..a7cb81db --- /dev/null +++ b/src/util/config/types/subconfigurations/guild/Discovery.ts
@@ -0,0 +1,6 @@ +export class DiscoveryConfiguration { + showAllGuilds: boolean = false; + useRecommendation: boolean = false; // TODO: Recommendation, privacy concern? + offset: number = 0; + limit: number = 24; +} diff --git a/src/util/config/types/subconfigurations/guild/index.ts b/src/util/config/types/subconfigurations/guild/index.ts new file mode 100644
index 00000000..e9614856 --- /dev/null +++ b/src/util/config/types/subconfigurations/guild/index.ts
@@ -0,0 +1,2 @@ +export * from "./AutoJoin"; +export * from "./Discovery"; diff --git a/src/util/config/types/subconfigurations/index.ts b/src/util/config/types/subconfigurations/index.ts new file mode 100644
index 00000000..bfbadc92 --- /dev/null +++ b/src/util/config/types/subconfigurations/index.ts
@@ -0,0 +1,8 @@ +export * from "./client/index"; +export * from "./defaults/index"; +export * from "./guild/index"; +export * from "./kafka/index"; +export * from "./limits/index"; +export * from "./region/index"; +export * from "./register/index"; +export * from "./security/index"; diff --git a/src/util/config/types/subconfigurations/kafka/KafkaBroker.ts b/src/util/config/types/subconfigurations/kafka/KafkaBroker.ts new file mode 100644
index 00000000..f7dc1cf7 --- /dev/null +++ b/src/util/config/types/subconfigurations/kafka/KafkaBroker.ts
@@ -0,0 +1,4 @@ +export interface KafkaBroker { + ip: string; + port: number; +} diff --git a/src/util/config/types/subconfigurations/kafka/index.ts b/src/util/config/types/subconfigurations/kafka/index.ts new file mode 100644
index 00000000..2c633950 --- /dev/null +++ b/src/util/config/types/subconfigurations/kafka/index.ts
@@ -0,0 +1 @@ +export * from "./KafkaBroker"; diff --git a/src/util/config/types/subconfigurations/limits/ChannelLimits.ts b/src/util/config/types/subconfigurations/limits/ChannelLimits.ts new file mode 100644
index 00000000..76eeeb41 --- /dev/null +++ b/src/util/config/types/subconfigurations/limits/ChannelLimits.ts
@@ -0,0 +1,5 @@ +export class ChannelLimits { + maxPins: number = 500; + maxTopic: number = 1024; + maxWebhooks: number = 100; +} diff --git a/src/util/config/types/subconfigurations/limits/GuildLimits.ts b/src/util/config/types/subconfigurations/limits/GuildLimits.ts new file mode 100644
index 00000000..015654cb --- /dev/null +++ b/src/util/config/types/subconfigurations/limits/GuildLimits.ts
@@ -0,0 +1,8 @@ +export class GuildLimits { + maxRoles: number = 1000; + maxEmojis: number = 2000; + maxMembers: number = 25000000; + maxChannels: number = 65535; + maxChannelsInCategory: number = 65535; + hideOfflineMember: number = 3; +} diff --git a/src/util/config/types/subconfigurations/limits/MessageLimits.ts b/src/util/config/types/subconfigurations/limits/MessageLimits.ts new file mode 100644
index 00000000..684a5057 --- /dev/null +++ b/src/util/config/types/subconfigurations/limits/MessageLimits.ts
@@ -0,0 +1,8 @@ +export class MessageLimits { + maxCharacters: number = 1048576; + maxTTSCharacters: number = 160; + maxReactions: number = 2048; + maxAttachmentSize: number = 1024 * 1024 * 1024; + maxBulkDelete: number = 1000; + maxEmbedDownloadSize: number = 1024 * 1024 * 5; +} diff --git a/src/util/config/types/subconfigurations/limits/RateLimits.ts b/src/util/config/types/subconfigurations/limits/RateLimits.ts new file mode 100644
index 00000000..764acdd6 --- /dev/null +++ b/src/util/config/types/subconfigurations/limits/RateLimits.ts
@@ -0,0 +1,18 @@ +import { RateLimitOptions, RouteRateLimit } from "."; + +export class RateLimits { + disabled: boolean = true; + ip: Omit<RateLimitOptions, "bot_count"> = { + count: 500, + window: 5 + }; + global: RateLimitOptions = { + count: 250, + window: 5 + }; + error: RateLimitOptions = { + count: 10, + window: 5 + }; + routes: RouteRateLimit = new RouteRateLimit(); +} diff --git a/src/util/config/types/subconfigurations/limits/UserLimits.ts b/src/util/config/types/subconfigurations/limits/UserLimits.ts new file mode 100644
index 00000000..b8bdcb2d --- /dev/null +++ b/src/util/config/types/subconfigurations/limits/UserLimits.ts
@@ -0,0 +1,5 @@ +export class UserLimits { + maxGuilds: number = 1048576; + maxUsername: number = 127; + maxFriends: number = 5000; +} diff --git a/src/util/config/types/subconfigurations/limits/index.ts b/src/util/config/types/subconfigurations/limits/index.ts new file mode 100644
index 00000000..a4911542 --- /dev/null +++ b/src/util/config/types/subconfigurations/limits/index.ts
@@ -0,0 +1,6 @@ +export * from "./ChannelLimits"; +export * from "./GuildLimits"; +export * from "./MessageLimits"; +export * from "./RateLimits"; +export * from "./ratelimits/index"; +export * from "./UserLimits"; diff --git a/src/util/config/types/subconfigurations/limits/ratelimits/Auth.ts b/src/util/config/types/subconfigurations/limits/ratelimits/Auth.ts new file mode 100644
index 00000000..0f363e7f --- /dev/null +++ b/src/util/config/types/subconfigurations/limits/ratelimits/Auth.ts
@@ -0,0 +1,12 @@ +import { RateLimitOptions } from "./RateLimitOptions"; + +export class AuthRateLimit { + login: RateLimitOptions = { + count: 5, + window: 60 + }; + register: RateLimitOptions = { + count: 2, + window: 60 * 60 * 12 + }; +} diff --git a/src/util/config/types/subconfigurations/limits/ratelimits/RateLimitOptions.ts b/src/util/config/types/subconfigurations/limits/ratelimits/RateLimitOptions.ts new file mode 100644
index 00000000..829813fb --- /dev/null +++ b/src/util/config/types/subconfigurations/limits/ratelimits/RateLimitOptions.ts
@@ -0,0 +1,6 @@ +export interface RateLimitOptions { + bot?: number; + count: number; + window: number; + onyIp?: boolean; +} diff --git a/src/util/config/types/subconfigurations/limits/ratelimits/Route.ts b/src/util/config/types/subconfigurations/limits/ratelimits/Route.ts new file mode 100644
index 00000000..6890699e --- /dev/null +++ b/src/util/config/types/subconfigurations/limits/ratelimits/Route.ts
@@ -0,0 +1,19 @@ +import { AuthRateLimit } from "./Auth"; +import { RateLimitOptions } from "./RateLimitOptions"; + +export class RouteRateLimit { + guild: RateLimitOptions = { + count: 5, + window: 5 + }; + webhook: RateLimitOptions = { + count: 10, + window: 5 + }; + channel: RateLimitOptions = { + count: 10, + window: 5 + }; + auth: AuthRateLimit = new AuthRateLimit(); + // TODO: rate limit configuration for all routes +} diff --git a/src/util/config/types/subconfigurations/limits/ratelimits/index.ts b/src/util/config/types/subconfigurations/limits/ratelimits/index.ts new file mode 100644
index 00000000..432eb601 --- /dev/null +++ b/src/util/config/types/subconfigurations/limits/ratelimits/index.ts
@@ -0,0 +1,3 @@ +export * from "./Auth"; +export * from "./RateLimitOptions"; +export * from "./Route"; diff --git a/src/util/config/types/subconfigurations/region/Region.ts b/src/util/config/types/subconfigurations/region/Region.ts new file mode 100644
index 00000000..c1bcfd01 --- /dev/null +++ b/src/util/config/types/subconfigurations/region/Region.ts
@@ -0,0 +1,12 @@ +export interface Region { + id: string; + name: string; + endpoint: string; + location?: { + latitude: number; + longitude: number; + }; + vip: boolean; + custom: boolean; + deprecated: boolean; +} diff --git a/src/util/config/types/subconfigurations/region/index.ts b/src/util/config/types/subconfigurations/region/index.ts new file mode 100644
index 00000000..2beb8de7 --- /dev/null +++ b/src/util/config/types/subconfigurations/region/index.ts
@@ -0,0 +1 @@ +export * from "./Region"; diff --git a/src/util/config/types/subconfigurations/register/DateOfBirth.ts b/src/util/config/types/subconfigurations/register/DateOfBirth.ts new file mode 100644
index 00000000..4831a4b7 --- /dev/null +++ b/src/util/config/types/subconfigurations/register/DateOfBirth.ts
@@ -0,0 +1,4 @@ +export class DateOfBirthConfiguration { + required: boolean = true; + minimum: number = 13; // in years +} diff --git a/src/util/config/types/subconfigurations/register/Email.ts b/src/util/config/types/subconfigurations/register/Email.ts new file mode 100644
index 00000000..7f54faa7 --- /dev/null +++ b/src/util/config/types/subconfigurations/register/Email.ts
@@ -0,0 +1,7 @@ +export class EmailConfiguration { + required: boolean = false; + allowlist: boolean = false; + blocklist: boolean = true; + domains: string[] = []; // TODO: efficiently save domain blocklist in database + // domains: fs.readFileSync(__dirname + "/blockedEmailDomains.txt", { encoding: "utf8" }).split("\n"), +} diff --git a/src/util/config/types/subconfigurations/register/Password.ts b/src/util/config/types/subconfigurations/register/Password.ts new file mode 100644
index 00000000..383bdcfa --- /dev/null +++ b/src/util/config/types/subconfigurations/register/Password.ts
@@ -0,0 +1,7 @@ +export class PasswordConfiguration { + required: boolean = false; + minLength: number = 8; + minNumbers: number = 2; + minUpperCase: number = 2; + minSymbols: number = 0; +} diff --git a/src/util/config/types/subconfigurations/register/index.ts b/src/util/config/types/subconfigurations/register/index.ts new file mode 100644
index 00000000..d9738120 --- /dev/null +++ b/src/util/config/types/subconfigurations/register/index.ts
@@ -0,0 +1,3 @@ +export * from "./DateOfBirth"; +export * from "./Email"; +export * from "./Password"; diff --git a/src/util/config/types/subconfigurations/security/Captcha.ts b/src/util/config/types/subconfigurations/security/Captcha.ts new file mode 100644
index 00000000..21c4ef44 --- /dev/null +++ b/src/util/config/types/subconfigurations/security/Captcha.ts
@@ -0,0 +1,6 @@ +export class CaptchaConfiguration { + enabled: boolean = false; + service: "recaptcha" | "hcaptcha" | null = null; // TODO: hcaptcha, custom + sitekey: string | null = null; + secret: string | null = null; +} diff --git a/src/util/config/types/subconfigurations/security/TwoFactor.ts b/src/util/config/types/subconfigurations/security/TwoFactor.ts new file mode 100644
index 00000000..20d2f9ab --- /dev/null +++ b/src/util/config/types/subconfigurations/security/TwoFactor.ts
@@ -0,0 +1,3 @@ +export class TwoFactorConfiguration { + generateBackupCodes: boolean = true; +} diff --git a/src/util/config/types/subconfigurations/security/index.ts b/src/util/config/types/subconfigurations/security/index.ts new file mode 100644
index 00000000..17619589 --- /dev/null +++ b/src/util/config/types/subconfigurations/security/index.ts
@@ -0,0 +1,2 @@ +export * from "./Captcha"; +export * from "./TwoFactor"; diff --git a/util/src/dtos/DmChannelDTO.ts b/src/util/dtos/DmChannelDTO.ts
index 226b2f9d..93b1adfa 100644 --- a/util/src/dtos/DmChannelDTO.ts +++ b/src/util/dtos/DmChannelDTO.ts
@@ -1,5 +1,5 @@ -import { MinimalPublicUserDTO } from "./UserDTO"; import { Channel, PublicUserProjection, User } from "../entities"; +import { MinimalPublicUserDTO } from "./UserDTO"; export class DmChannelDTO { icon: string | null; @@ -35,7 +35,7 @@ export class DmChannelDTO { excludedRecipients(excluded_recipients: string[]): DmChannelDTO { return { ...this, - recipients: this.recipients.filter((r) => !excluded_recipients.includes(r.id)), + recipients: this.recipients.filter((r) => !excluded_recipients.includes(r.id)) }; } } diff --git a/util/src/dtos/UserDTO.ts b/src/util/dtos/UserDTO.ts
index ee2752a4..ee2752a4 100644 --- a/util/src/dtos/UserDTO.ts +++ b/src/util/dtos/UserDTO.ts
diff --git a/util/src/dtos/index.ts b/src/util/dtos/index.ts
index 0e8f8459..0e8f8459 100644 --- a/util/src/dtos/index.ts +++ b/src/util/dtos/index.ts
diff --git a/util/src/entities/Application.ts b/src/util/entities/Application.ts
index fab3d93f..35fc496d 100644 --- a/util/src/entities/Application.ts +++ b/src/util/entities/Application.ts
@@ -1,6 +1,5 @@ -import { Column, Entity, JoinColumn, ManyToOne, RelationId } from "typeorm"; +import { Column, Entity, JoinColumn, ManyToOne, OneToOne } from "typeorm"; import { BaseClass } from "./BaseClass"; -import { Guild } from "./Guild"; import { Team } from "./Team"; import { User } from "./User"; @@ -12,55 +11,101 @@ export class Application extends BaseClass { @Column({ nullable: true }) icon?: string; - @Column() + @Column({ nullable: true }) description: string; - @Column({ type: "simple-array", nullable: true }) - rpc_origins?: string[]; + @Column({ nullable: true }) + summary: string = ""; + + @Column({ type: "simple-json", nullable: true }) + type?: any; @Column() - bot_public: boolean; + hook: boolean = true; @Column() - bot_require_code_grant: boolean; + bot_public?: boolean = true; - @Column({ nullable: true }) - terms_of_service_url?: string; + @Column() + bot_require_code_grant?: boolean = false; - @Column({ nullable: true }) - privacy_policy_url?: string; + @Column() + verify_key: string; @JoinColumn({ name: "owner_id" }) @ManyToOne(() => User) - owner?: User; + owner: User; + + @Column() + flags: number = 0; + + @Column({ type: "simple-array", nullable: true }) + redirect_uris: string[] = []; @Column({ nullable: true }) - summary?: string; + rpc_application_state: number = 0; - @Column() - verify_key: string; + @Column({ nullable: true }) + store_application_state: number = 1; - @JoinColumn({ name: "team_id" }) - @ManyToOne(() => Team, { - onDelete: "CASCADE", - }) - team?: Team; + @Column({ nullable: true }) + verification_state: number = 1; - @JoinColumn({ name: "guild_id" }) - @ManyToOne(() => Guild) - guild: Guild; // if this application is a game sold, this field will be the guild to which it has been linked + @Column({ nullable: true }) + interactions_endpoint_url?: string; @Column({ nullable: true }) - primary_sku_id?: string; // if this application is a game sold, this field will be the id of the "Game SKU" that is created, + integration_public: boolean = true; @Column({ nullable: true }) - slug?: string; // if this application is a game sold, this field will be the URL slug that links to the store page + integration_require_code_grant: boolean = false; + + @Column({ nullable: true }) + discoverability_state: number = 1; + + @Column({ nullable: true }) + discovery_eligibility_flags: number = 2240; + + @JoinColumn({ name: "bot_user_id" }) + @OneToOne(() => User) + bot?: User; + + @Column({ type: "simple-array", nullable: true }) + tags?: string[]; @Column({ nullable: true }) cover_image?: string; // the application's default rich presence invite cover image hash - @Column() - flags: string; // the application's public flags + @Column({ type: "simple-json", nullable: true }) + install_params?: { scopes: string[]; permissions: string }; + + @Column({ nullable: true }) + terms_of_service_url?: string; + + @Column({ nullable: true }) + privacy_policy_url?: string; + + //just for us + + //@Column({ type: "simple-array", nullable: true }) + //rpc_origins?: string[]; + + //@JoinColumn({ name: "guild_id" }) + //@ManyToOne(() => Guild) + //guild?: Guild; // if this application is a game sold, this field will be the guild to which it has been linked + + //@Column({ nullable: true }) + //primary_sku_id?: string; // if this application is a game sold, this field will be the id of the "Game SKU" that is created, + + //@Column({ nullable: true }) + //slug?: string; // if this application is a game sold, this field will be the URL slug that links to the store page + + @JoinColumn({ name: "team_id" }) + @ManyToOne(() => Team, { + onDelete: "CASCADE", + nullable: true + }) + team?: Team; } export interface ApplicationCommand { @@ -93,7 +138,7 @@ export enum ApplicationCommandOptionType { BOOLEAN = 5, USER = 6, CHANNEL = 7, - ROLE = 8, + ROLE = 8 } export interface ApplicationCommandInteractionData { diff --git a/util/src/entities/Attachment.ts b/src/util/entities/Attachment.ts
index 7b4b17eb..8392f415 100644 --- a/util/src/entities/Attachment.ts +++ b/src/util/entities/Attachment.ts
@@ -32,7 +32,7 @@ export class Attachment extends BaseClass { @JoinColumn({ name: "message_id" }) @ManyToOne(() => require("./Message").Message, (message: import("./Message").Message) => message.attachments, { - onDelete: "CASCADE", + onDelete: "CASCADE" }) message: import("./Message").Message; diff --git a/util/src/entities/AuditLog.ts b/src/util/entities/AuditLog.ts
index b003e7ba..6f394f42 100644 --- a/util/src/entities/AuditLog.ts +++ b/src/util/entities/AuditLog.ts
@@ -5,24 +5,24 @@ import { User } from "./User"; export enum AuditLogEvents { // guild level - GUILD_UPDATE = 1, + GUILD_UPDATE = 1, GUILD_IMPORT = 2, GUILD_EXPORTED = 3, GUILD_ARCHIVE = 4, GUILD_UNARCHIVE = 5, // join-leave - USER_JOIN = 6, + USER_JOIN = 6, USER_LEAVE = 7, // channels - CHANNEL_CREATE = 10, + CHANNEL_CREATE = 10, CHANNEL_UPDATE = 11, CHANNEL_DELETE = 12, // permission overrides - CHANNEL_OVERWRITE_CREATE = 13, + CHANNEL_OVERWRITE_CREATE = 13, CHANNEL_OVERWRITE_UPDATE = 14, CHANNEL_OVERWRITE_DELETE = 15, // kick and ban - MEMBER_KICK = 20, + MEMBER_KICK = 20, MEMBER_PRUNE = 21, MEMBER_BAN_ADD = 22, MEMBER_BAN_REMOVE = 23, @@ -79,18 +79,18 @@ export enum AuditLogEvents { // application commands APPLICATION_COMMAND_PERMISSION_UPDATE = 121, // automod - POLICY_CREATE = 140, + POLICY_CREATE = 140, POLICY_UPDATE = 141, POLICY_DELETE = 142, - MESSAGE_BLOCKED_BY_POLICIES = 143, // in fosscord, blocked messages are stealth-dropped + MESSAGE_BLOCKED_BY_POLICIES = 143, // in fosscord, blocked messages are stealth-dropped // instance policies affecting the guild GUILD_AFFECTED_BY_POLICIES = 216, // message moves IN_GUILD_MESSAGE_MOVE = 223, CROSS_GUILD_MESSAGE_MOVE = 224, // message routing - ROUTE_CREATE = 225, - ROUTE_UPDATE = 226, + ROUTE_CREATE = 225, + ROUTE_UPDATE = 226 } @Entity("audit_logs") diff --git a/src/util/entities/BackupCodes.ts b/src/util/entities/BackupCodes.ts new file mode 100644
index 00000000..503b1dbd --- /dev/null +++ b/src/util/entities/BackupCodes.ts
@@ -0,0 +1,19 @@ +import { Column, Entity, JoinColumn, ManyToOne } from "typeorm"; +import { BaseClass } from "./BaseClass"; +import { User } from "./User"; + +@Entity("backup_codes") +export class BackupCode extends BaseClass { + @JoinColumn({ name: "user_id" }) + @ManyToOne(() => User, { onDelete: "CASCADE" }) + user: User; + + @Column() + code: string; + + @Column() + consumed: boolean; + + @Column() + expired: boolean; +} diff --git a/util/src/entities/Ban.ts b/src/util/entities/Ban.ts
index 9504bd8e..27c75278 100644 --- a/util/src/entities/Ban.ts +++ b/src/util/entities/Ban.ts
@@ -11,7 +11,7 @@ export class Ban extends BaseClass { @JoinColumn({ name: "user_id" }) @ManyToOne(() => User, { - onDelete: "CASCADE", + onDelete: "CASCADE" }) user: User; @@ -21,7 +21,7 @@ export class Ban extends BaseClass { @JoinColumn({ name: "guild_id" }) @ManyToOne(() => Guild, { - onDelete: "CASCADE", + onDelete: "CASCADE" }) guild: Guild; diff --git a/src/util/entities/BaseClass.ts b/src/util/entities/BaseClass.ts new file mode 100644
index 00000000..aecc2465 --- /dev/null +++ b/src/util/entities/BaseClass.ts
@@ -0,0 +1,26 @@ +import "reflect-metadata"; +import { BaseEntity, ObjectIdColumn, PrimaryColumn, SaveOptions } from "typeorm"; +import { Snowflake } from "../util/Snowflake"; + +export class BaseClassWithoutId extends BaseEntity { + constructor() { + super(); + } +} + +export const PrimaryIdColumn = process.env.DATABASE?.startsWith("mongodb") ? ObjectIdColumn : PrimaryColumn; + +export class BaseClass extends BaseClassWithoutId { + @PrimaryIdColumn() + id: string; + + constructor() { + super(); + if (!this.id) this.id = Snowflake.generate(); + } + + save(options?: SaveOptions | undefined): Promise<this> { + if (!this.id) this.id = Snowflake.generate(); + return super.save(options); + } +} diff --git a/util/src/entities/Categories.ts b/src/util/entities/Categories.ts
index 81fbc303..08a79112 100644 --- a/util/src/entities/Categories.ts +++ b/src/util/entities/Categories.ts
@@ -1,4 +1,4 @@ -import { PrimaryColumn, Column, Entity} from "typeorm"; +import { Column, Entity, PrimaryColumn } from "typeorm"; import { BaseClassWithoutId } from "./BaseClass"; // TODO: categories: @@ -16,18 +16,18 @@ import { BaseClassWithoutId } from "./BaseClass"; // Also populate discord default categories @Entity("categories") -export class Categories extends BaseClassWithoutId { // Not using snowflake - - @PrimaryColumn() - id: number; +export class Categories extends BaseClassWithoutId { + // Not using snowflake - @Column({ nullable: true }) - name: string; + @PrimaryColumn() + id: number; - @Column({ type: "simple-json" }) - localizations: string; + @Column({ nullable: true }) + name: string; - @Column({ nullable: true }) - is_primary: boolean; + @Column({ type: "simple-json" }) + localizations: string; -} \ No newline at end of file + @Column({ nullable: true }) + is_primary: boolean; +} diff --git a/util/src/entities/Channel.ts b/src/util/entities/Channel.ts
index 69c08be7..b17fdba0 100644 --- a/util/src/entities/Channel.ts +++ b/src/util/entities/Channel.ts
@@ -1,380 +1,389 @@ -import { Column, Entity, JoinColumn, ManyToOne, OneToMany, RelationId } from "typeorm"; -import { BaseClass } from "./BaseClass"; -import { Guild } from "./Guild"; -import { PublicUserProjection, User } from "./User"; -import { HTTPError } from "lambert-server"; -import { containsAll, emitEvent, getPermission, Snowflake, trimSpecial, InvisibleCharacters } from "../util"; -import { ChannelCreateEvent, ChannelRecipientRemoveEvent } from "../interfaces"; -import { Recipient } from "./Recipient"; -import { Message } from "./Message"; -import { ReadState } from "./ReadState"; -import { Invite } from "./Invite"; -import { VoiceState } from "./VoiceState"; -import { Webhook } from "./Webhook"; -import { DmChannelDTO } from "../dtos"; - -export enum ChannelType { - GUILD_TEXT = 0, // a text channel within a guild - DM = 1, // a direct message between users - GUILD_VOICE = 2, // a voice channel within a guild - GROUP_DM = 3, // a direct message between multiple users - GUILD_CATEGORY = 4, // an organizational category that contains zero or more channels - GUILD_NEWS = 5, // a channel that users can follow and crosspost into a guild or route - GUILD_STORE = 6, // a channel in which game developers can sell their things - ENCRYPTED = 7, // end-to-end encrypted channel - ENCRYPTED_THREAD = 8, // end-to-end encrypted thread channel - TRANSACTIONAL = 9, // event chain style transactional channel - GUILD_NEWS_THREAD = 10, // a temporary sub-channel within a GUILD_NEWS channel - GUILD_PUBLIC_THREAD = 11, // a temporary sub-channel within a GUILD_TEXT channel - GUILD_PRIVATE_THREAD = 12, // a temporary sub-channel within a GUILD_TEXT channel that is only viewable by those invited and those with the MANAGE_THREADS permission - GUILD_STAGE_VOICE = 13, // a voice channel for hosting events with an audience - DIRECTORY = 14, // guild directory listing channel - GUILD_FORUM = 15, // forum composed of IM threads - TICKET_TRACKER = 33, // ticket tracker, individual ticket items shall have type 12 - KANBAN = 34, // confluence like kanban board - VOICELESS_WHITEBOARD = 35, // whiteboard but without voice (whiteboard + voice is the same as stage) - CUSTOM_START = 64, // start custom channel types from here - UNHANDLED = 255 // unhandled unowned pass-through channel type -} - -@Entity("channels") -export class Channel extends BaseClass { - @Column() - created_at: Date; - - @Column({ nullable: true }) - name?: string; - - @Column({ type: "text", nullable: true }) - icon?: string | null; - - @Column({ type: "int" }) - type: ChannelType; - - @OneToMany(() => Recipient, (recipient: Recipient) => recipient.channel, { - cascade: true, - orphanedRowAction: "delete", - }) - recipients?: Recipient[]; - - @Column({ nullable: true }) - last_message_id: string; - - @Column({ nullable: true }) - @RelationId((channel: Channel) => channel.guild) - guild_id?: string; - - @JoinColumn({ name: "guild_id" }) - @ManyToOne(() => Guild, { - onDelete: "CASCADE", - }) - guild: Guild; - - @Column({ nullable: true }) - @RelationId((channel: Channel) => channel.parent) - parent_id: string; - - @JoinColumn({ name: "parent_id" }) - @ManyToOne(() => Channel) - parent?: Channel; - - // for group DMs and owned custom channel types - @Column({ nullable: true }) - @RelationId((channel: Channel) => channel.owner) - owner_id: string; - - @JoinColumn({ name: "owner_id" }) - @ManyToOne(() => User) - owner: User; - - @Column({ nullable: true }) - last_pin_timestamp?: number; - - @Column({ nullable: true }) - default_auto_archive_duration?: number; - - @Column({ nullable: true }) - position?: number; - - @Column({ type: "simple-json", nullable: true }) - permission_overwrites?: ChannelPermissionOverwrite[]; - - @Column({ nullable: true }) - video_quality_mode?: number; - - @Column({ nullable: true }) - bitrate?: number; - - @Column({ nullable: true }) - user_limit?: number; - - @Column({ nullable: true }) - nsfw?: boolean; - - @Column({ nullable: true }) - rate_limit_per_user?: number; - - @Column({ nullable: true }) - topic?: string; - - @OneToMany(() => Invite, (invite: Invite) => invite.channel, { - cascade: true, - orphanedRowAction: "delete", - }) - invites?: Invite[]; - - @Column({ nullable: true }) - retention_policy_id?: string; - - @OneToMany(() => Message, (message: Message) => message.channel, { - cascade: true, - orphanedRowAction: "delete", - }) - messages?: Message[]; - - @OneToMany(() => VoiceState, (voice_state: VoiceState) => voice_state.channel, { - cascade: true, - orphanedRowAction: "delete", - }) - voice_states?: VoiceState[]; - - @OneToMany(() => ReadState, (read_state: ReadState) => read_state.channel, { - cascade: true, - orphanedRowAction: "delete", - }) - read_states?: ReadState[]; - - @OneToMany(() => Webhook, (webhook: Webhook) => webhook.channel, { - cascade: true, - orphanedRowAction: "delete", - }) - webhooks?: Webhook[]; - - // TODO: DM channel - static async createChannel( - channel: Partial<Channel>, - user_id: string = "0", - opts?: { - keepId?: boolean; - skipExistsCheck?: boolean; - skipPermissionCheck?: boolean; - skipEventEmit?: boolean; - skipNameChecks?: boolean; - } - ) { - if (!opts?.skipPermissionCheck) { - // Always check if user has permission first - const permissions = await getPermission(user_id, channel.guild_id); - permissions.hasThrow("MANAGE_CHANNELS"); - } - - if (!opts?.skipNameChecks) { - const guild = await Guild.findOneOrFail({ id: channel.guild_id }); - if (!guild.features.includes("ALLOW_INVALID_CHANNEL_NAMES") && channel.name) { - for (var character of InvisibleCharacters) - if (channel.name.includes(character)) - throw new HTTPError("Channel name cannot include invalid characters", 403); - - if (channel.name.match(/\-\-+/g)) - throw new HTTPError("Channel name cannot include multiple adjacent dashes.", 403) - - if (channel.name.charAt(0) === "-" || - channel.name.charAt(channel.name.length - 1) === "-") - throw new HTTPError("Channel name cannot start/end with dash.", 403) - } - - if (!guild.features.includes("ALLOW_UNNAMED_CHANNELS")) { - if (!channel.name) - throw new HTTPError("Channel name cannot be empty.", 403); - } - } - - switch (channel.type) { - case ChannelType.GUILD_TEXT: - case ChannelType.GUILD_NEWS: - case ChannelType.GUILD_VOICE: - if (channel.parent_id && !opts?.skipExistsCheck) { - const exists = await Channel.findOneOrFail({ id: channel.parent_id }); - if (!exists) throw new HTTPError("Parent id channel doesn't exist", 400); - if (exists.guild_id !== channel.guild_id) - throw new HTTPError("The category channel needs to be in the guild"); - } - break; - case ChannelType.GUILD_CATEGORY: - case ChannelType.UNHANDLED: - break; - case ChannelType.DM: - case ChannelType.GROUP_DM: - throw new HTTPError("You can't create a dm channel in a guild"); - case ChannelType.GUILD_STORE: - default: - throw new HTTPError("Not yet supported"); - } - - if (!channel.permission_overwrites) channel.permission_overwrites = []; - // TODO: eagerly auto generate position of all guild channels - - channel = { - ...channel, - ...(!opts?.keepId && { id: Snowflake.generate() }), - created_at: new Date(), - position: (channel.type === ChannelType.UNHANDLED ? 0 : channel.position) || 0, - }; - - await Promise.all([ - new Channel(channel).save(), - !opts?.skipEventEmit - ? emitEvent({ - event: "CHANNEL_CREATE", - data: channel, - guild_id: channel.guild_id, - } as ChannelCreateEvent) - : Promise.resolve(), - ]); - - return channel; - } - - static async createDMChannel(recipients: string[], creator_user_id: string, name?: string) { - recipients = recipients.unique().filter((x) => x !== creator_user_id); - const otherRecipientsUsers = await User.find({ where: recipients.map((x) => ({ id: x })) }); - - // TODO: check config for max number of recipients - /** if you want to disallow note to self channels, uncomment the conditional below - if (otherRecipientsUsers.length !== recipients.length) { - throw new HTTPError("Recipient/s not found"); - } - **/ - - const type = recipients.length > 1 ? ChannelType.DM : ChannelType.GROUP_DM; - - let channel = null; - - const channelRecipients = [...recipients, creator_user_id]; - - const userRecipients = await Recipient.find({ - where: { user_id: creator_user_id }, - relations: ["channel", "channel.recipients"], - }); - - for (let ur of userRecipients) { - let re = ur.channel.recipients!.map((r) => r.user_id); - if (re.length === channelRecipients.length) { - if (containsAll(re, channelRecipients)) { - if (channel == null) { - channel = ur.channel; - await ur.assign({ closed: false }).save(); - } - } - } - } - - if (channel == null) { - name = trimSpecial(name); - - channel = await new Channel({ - name, - type, - owner_id: type === ChannelType.DM ? undefined : null, // 1:1 DMs are ownerless in fosscord-server - created_at: new Date(), - last_message_id: null, - recipients: channelRecipients.map( - (x) => - new Recipient({ user_id: x, closed: !(type === ChannelType.GROUP_DM || x === creator_user_id) }) - ), - }).save(); - } - - const channel_dto = await DmChannelDTO.from(channel); - - if (type === ChannelType.GROUP_DM) { - for (let recipient of channel.recipients!) { - await emitEvent({ - event: "CHANNEL_CREATE", - data: channel_dto.excludedRecipients([recipient.user_id]), - user_id: recipient.user_id, - }); - } - } else { - await emitEvent({ event: "CHANNEL_CREATE", data: channel_dto, user_id: creator_user_id }); - } - - if (recipients.length === 1) return channel_dto; - else return channel_dto.excludedRecipients([creator_user_id]); - } - - static async removeRecipientFromChannel(channel: Channel, user_id: string) { - await Recipient.delete({ channel_id: channel.id, user_id: user_id }); - channel.recipients = channel.recipients?.filter((r) => r.user_id !== user_id); - - if (channel.recipients?.length === 0) { - await Channel.deleteChannel(channel); - await emitEvent({ - event: "CHANNEL_DELETE", - data: await DmChannelDTO.from(channel, [user_id]), - user_id: user_id, - }); - return; - } - - await emitEvent({ - event: "CHANNEL_DELETE", - data: await DmChannelDTO.from(channel, [user_id]), - user_id: user_id, - }); - - //If the owner leave the server user is the new owner - if (channel.owner_id === user_id) { - channel.owner_id = "1"; // The channel is now owned by the server user - await emitEvent({ - event: "CHANNEL_UPDATE", - data: await DmChannelDTO.from(channel, [user_id]), - channel_id: channel.id, - }); - } - - await channel.save(); - - await emitEvent({ - event: "CHANNEL_RECIPIENT_REMOVE", - data: { - channel_id: channel.id, - user: await User.findOneOrFail({ where: { id: user_id }, select: PublicUserProjection }), - }, - channel_id: channel.id, - } as ChannelRecipientRemoveEvent); - } - - static async deleteChannel(channel: Channel) { - await Message.delete({ channel_id: channel.id }); //TODO we should also delete the attachments from the cdn but to do that we need to move cdn.ts in util - //TODO before deleting the channel we should check and delete other relations - await Channel.delete({ id: channel.id }); - } - - isDm() { - return this.type === ChannelType.DM || this.type === ChannelType.GROUP_DM; - } - - // Does the channel support sending messages ( eg categories do not ) - isWritable() { - const disallowedChannelTypes = [ - ChannelType.GUILD_CATEGORY, - ChannelType.GUILD_STAGE_VOICE, - ChannelType.VOICELESS_WHITEBOARD, - ]; - return disallowedChannelTypes.indexOf(this.type) == -1; - } -} - -export interface ChannelPermissionOverwrite { - allow: string; - deny: string; - id: string; - type: ChannelPermissionOverwriteType; -} - -export enum ChannelPermissionOverwriteType { - role = 0, - member = 1, - group = 2, -} +import { Column, Entity, JoinColumn, ManyToOne, OneToMany, RelationId } from "typeorm"; +import { DmChannelDTO } from "../dtos"; +import { ChannelCreateEvent, ChannelRecipientRemoveEvent } from "../interfaces"; +import { containsAll, emitEvent, getPermission, InvisibleCharacters, Snowflake, trimSpecial } from "../util"; +import { HTTPError } from "../util/imports/HTTPError"; +import { OrmUtils } from "../util/imports/OrmUtils"; +import { BaseClass } from "./BaseClass"; +import { Guild } from "./Guild"; +import { Invite } from "./Invite"; +import { Message } from "./Message"; +import { ReadState } from "./ReadState"; +import { Recipient } from "./Recipient"; +import { PublicUserProjection, User } from "./User"; +import { VoiceState } from "./VoiceState"; +import { Webhook } from "./Webhook"; + +export enum ChannelType { + GUILD_TEXT = 0, // a text channel within a guild + DM = 1, // a direct message between users + GUILD_VOICE = 2, // a voice channel within a guild + GROUP_DM = 3, // a direct message between multiple users + GUILD_CATEGORY = 4, // an organizational category that contains zero or more channels + GUILD_NEWS = 5, // a channel that users can follow and crosspost into a guild or route + GUILD_STORE = 6, // a channel in which game developers can sell their things + ENCRYPTED = 7, // end-to-end encrypted channel + ENCRYPTED_THREAD = 8, // end-to-end encrypted thread channel + TRANSACTIONAL = 9, // event chain style transactional channel + GUILD_NEWS_THREAD = 10, // a temporary sub-channel within a GUILD_NEWS channel + GUILD_PUBLIC_THREAD = 11, // a temporary sub-channel within a GUILD_TEXT channel + GUILD_PRIVATE_THREAD = 12, // a temporary sub-channel within a GUILD_TEXT channel that is only viewable by those invited and those with the MANAGE_THREADS permission + GUILD_STAGE_VOICE = 13, // a voice channel for hosting events with an audience + DIRECTORY = 14, // guild directory listing channel + GUILD_FORUM = 15, // forum composed of IM threads + TICKET_TRACKER = 33, // ticket tracker, individual ticket items shall have type 12 + KANBAN = 34, // confluence like kanban board + VOICELESS_WHITEBOARD = 35, // whiteboard but without voice (whiteboard + voice is the same as stage) + CUSTOM_START = 64, // start custom channel types from here + UNHANDLED = 255 // unhandled unowned pass-through channel type +} + +@Entity("channels") +export class Channel extends BaseClass { + @Column() + created_at: Date; + + @Column({ nullable: true }) + name?: string; + + @Column({ type: "text", nullable: true }) + icon?: string | null; + + @Column({ type: "int" }) + type: ChannelType; + + @OneToMany(() => Recipient, (recipient: Recipient) => recipient.channel, { + cascade: true, + orphanedRowAction: "delete" + }) + recipients?: Recipient[]; + + @Column({ nullable: true }) + last_message_id: string; + + @Column({ nullable: true }) + @RelationId((channel: Channel) => channel.guild) + guild_id?: string; + + @JoinColumn({ name: "guild_id" }) + @ManyToOne(() => Guild, { + onDelete: "CASCADE" + }) + guild: Guild; + + @Column({ nullable: true }) + @RelationId((channel: Channel) => channel.parent) + parent_id: string; + + @JoinColumn({ name: "parent_id" }) + @ManyToOne(() => Channel) + parent?: Channel; + + // for group DMs and owned custom channel types + @Column({ nullable: true }) + @RelationId((channel: Channel) => channel.owner) + owner_id: string; + + @JoinColumn({ name: "owner_id" }) + @ManyToOne(() => User) + owner: User; + + @Column({ nullable: true }) + last_pin_timestamp?: number; + + @Column({ nullable: true }) + default_auto_archive_duration?: number; + + @Column({ nullable: true }) + position?: number; + + @Column({ type: "simple-json", nullable: true }) + permission_overwrites?: ChannelPermissionOverwrite[]; + + @Column({ nullable: true }) + video_quality_mode?: number; + + @Column({ nullable: true }) + bitrate?: number; + + @Column({ nullable: true }) + user_limit?: number; + + @Column({ nullable: true }) + nsfw?: boolean; + + @Column({ nullable: true }) + rate_limit_per_user?: number; + + @Column({ nullable: true }) + topic?: string; + + @OneToMany(() => Invite, (invite: Invite) => invite.channel, { + cascade: true, + orphanedRowAction: "delete" + }) + invites?: Invite[]; + + @Column({ nullable: true }) + retention_policy_id?: string; + + @OneToMany(() => Message, (message: Message) => message.channel, { + cascade: true, + orphanedRowAction: "delete" + }) + messages?: Message[]; + + @OneToMany(() => VoiceState, (voice_state: VoiceState) => voice_state.channel, { + cascade: true, + orphanedRowAction: "delete" + }) + voice_states?: VoiceState[]; + + @OneToMany(() => ReadState, (read_state: ReadState) => read_state.channel, { + cascade: true, + orphanedRowAction: "delete" + }) + read_states?: ReadState[]; + + @OneToMany(() => Webhook, (webhook: Webhook) => webhook.channel, { + cascade: true, + orphanedRowAction: "delete" + }) + webhooks?: Webhook[]; + + @Column({ nullable: true }) + flags?: number = 0; + + @Column({ nullable: true }) + default_thread_rate_limit_per_user?: number = 0; + + // TODO: DM channel + static async createChannel( + channel: Partial<Channel>, + user_id: string = "0", + opts?: { + keepId?: boolean; + skipExistsCheck?: boolean; + skipPermissionCheck?: boolean; + skipEventEmit?: boolean; + skipNameChecks?: boolean; + } + ) { + if (!opts?.skipPermissionCheck) { + // Always check if user has permission first + const permissions = await getPermission(user_id, channel.guild_id); + permissions.hasThrow("MANAGE_CHANNELS"); + } + + if (!opts?.skipNameChecks) { + const guild = await Guild.findOneOrFail({ where: { id: channel.guild_id } }); + if (!guild.features.includes("ALLOW_INVALID_CHANNEL_NAMES") && channel.name) { + for (let character of InvisibleCharacters) + if (channel.name.includes(character)) throw new HTTPError("Channel name cannot include invalid characters", 403); + + // Categories and voice skip these checks on discord.com + const skipChecksTypes = [ChannelType.GUILD_CATEGORY, ChannelType.GUILD_VOICE]; + if ((channel.type && !skipChecksTypes.includes(channel.type)) || guild.features.includes("IRC_LIKE_CHANNEL_NAMES")) { + if (channel.name.includes(" ")) throw new HTTPError("Channel name cannot include invalid characters", 403); + + if (channel.name.match(/\-\-+/g)) throw new HTTPError("Channel name cannot include multiple adjacent dashes.", 403); + + if (channel.name.charAt(0) === "-" || channel.name.charAt(channel.name.length - 1) === "-") + throw new HTTPError("Channel name cannot start/end with dash.", 403); + } else channel.name = channel.name.trim(); //category names are trimmed client side on discord.com + } + + if (!guild.features.includes("ALLOW_UNNAMED_CHANNELS")) { + if (!channel.name) throw new HTTPError("Channel name cannot be empty.", 403); + } + } + + switch (channel.type) { + case ChannelType.GUILD_TEXT: + case ChannelType.GUILD_NEWS: + case ChannelType.GUILD_VOICE: + if (channel.parent_id && !opts?.skipExistsCheck) { + const exists = await Channel.findOneOrFail({ where: { id: channel.parent_id } }); + if (!exists) throw new HTTPError("Parent id channel doesn't exist", 400); + if (exists.guild_id !== channel.guild_id) throw new HTTPError("The category channel needs to be in the guild"); + } + break; + case ChannelType.GUILD_CATEGORY: + case ChannelType.UNHANDLED: + break; + case ChannelType.DM: + case ChannelType.GROUP_DM: + throw new HTTPError("You can't create a dm channel in a guild"); + case ChannelType.GUILD_STORE: + default: + throw new HTTPError("Not yet supported"); + } + + if (!channel.permission_overwrites) channel.permission_overwrites = []; + // TODO: eagerly auto generate position of all guild channels + + channel = { + ...channel, + ...(!opts?.keepId && { id: Snowflake.generate() }), + created_at: new Date(), + position: (channel.type === ChannelType.UNHANDLED ? 0 : channel.position) || 0 + }; + + await Promise.all([ + OrmUtils.mergeDeep(new Channel(), channel).save(), + !opts?.skipEventEmit + ? emitEvent({ + event: "CHANNEL_CREATE", + data: channel, + guild_id: channel.guild_id + } as ChannelCreateEvent) + : Promise.resolve() + ]); + + return channel; + } + + static async createDMChannel(recipients: string[], creator_user_id: string, name?: string) { + recipients = recipients.unique().filter((x) => x !== creator_user_id); + const otherRecipientsUsers = await User.find({ where: recipients.map((x) => ({ id: x })) }); + + // TODO: check config for max number of recipients + /** if you want to disallow note to self channels, uncomment the conditional below + if (otherRecipientsUsers.length !== recipients.length) { + throw new HTTPError("Recipient/s not found"); + } + **/ + + const type = recipients.length > 1 ? ChannelType.GROUP_DM : ChannelType.DM; + + let channel = null; + + const channelRecipients = [...recipients, creator_user_id]; + + const userRecipients = await Recipient.find({ + where: { user_id: creator_user_id }, + relations: ["channel", "channel.recipients"] + }); + + for (let ur of userRecipients) { + let re = ur.channel.recipients!.map((r) => r.user_id); + if (re.length === channelRecipients.length) { + if (containsAll(re, channelRecipients)) { + if (channel == null) { + channel = ur.channel; + ur = OrmUtils.mergeDeep(ur, { closed: false }); + await ur.save(); + } + } + } + } + + if (channel == null) { + name = trimSpecial(name); + + channel = await ( + OrmUtils.mergeDeep(new Channel(), { + name, + type, + owner_id: type === ChannelType.DM ? undefined : null, // 1:1 DMs are ownerless in fosscord-server + created_at: new Date(), + last_message_id: null, + recipients: channelRecipients.map((x) => + OrmUtils.mergeDeep(new Recipient(), { + user_id: x, + closed: !(type === ChannelType.GROUP_DM || x === creator_user_id) + }) + ) + }) as Channel + ).save(); + } + + const channel_dto = await DmChannelDTO.from(channel); + + if (type === ChannelType.GROUP_DM) { + for (let recipient of channel.recipients!) { + await emitEvent({ + event: "CHANNEL_CREATE", + data: channel_dto.excludedRecipients([recipient.user_id]), + user_id: recipient.user_id + }); + } + } else { + await emitEvent({ event: "CHANNEL_CREATE", data: channel_dto, user_id: creator_user_id }); + } + + if (recipients.length === 1) return channel_dto; + else return channel_dto.excludedRecipients([creator_user_id]); + } + + static async removeRecipientFromChannel(channel: Channel, user_id: string) { + await Recipient.delete({ channel_id: channel.id, user_id: user_id }); + channel.recipients = channel.recipients?.filter((r) => r.user_id !== user_id); + + if (channel.recipients?.length === 0) { + await Channel.deleteChannel(channel); + await emitEvent({ + event: "CHANNEL_DELETE", + data: await DmChannelDTO.from(channel, [user_id]), + user_id: user_id + }); + return; + } + + await emitEvent({ + event: "CHANNEL_DELETE", + data: await DmChannelDTO.from(channel, [user_id]), + user_id: user_id + }); + + //If the owner leave the server user is the new owner + if (channel.owner_id === user_id) { + channel.owner_id = "1"; // The channel is now owned by the server user + await emitEvent({ + event: "CHANNEL_UPDATE", + data: await DmChannelDTO.from(channel, [user_id]), + channel_id: channel.id + }); + } + + await channel.save(); + + await emitEvent({ + event: "CHANNEL_RECIPIENT_REMOVE", + data: { + channel_id: channel.id, + user: await User.findOneOrFail({ where: { id: user_id }, select: PublicUserProjection }) + }, + channel_id: channel.id + } as ChannelRecipientRemoveEvent); + } + + static async deleteChannel(channel: Channel) { + await Message.delete({ channel_id: channel.id }); //TODO we should also delete the attachments from the cdn but to do that we need to move cdn.ts in util + //TODO before deleting the channel we should check and delete other relations + await Channel.delete({ id: channel.id }); + } + + isDm() { + return this.type === ChannelType.DM || this.type === ChannelType.GROUP_DM; + } + + // Does the channel support sending messages ( eg categories do not ) + isWritable() { + const disallowedChannelTypes = [ChannelType.GUILD_CATEGORY, ChannelType.GUILD_STAGE_VOICE, ChannelType.VOICELESS_WHITEBOARD]; + return disallowedChannelTypes.indexOf(this.type) == -1; + } +} + +export interface ChannelPermissionOverwrite { + allow: string; + deny: string; + id: string; + type: ChannelPermissionOverwriteType; +} + +export enum ChannelPermissionOverwriteType { + role = 0, + member = 1, + group = 2 +} diff --git a/util/src/entities/ClientRelease.ts b/src/util/entities/ClientRelease.ts
index c5afd307..2723ab67 100644 --- a/util/src/entities/ClientRelease.ts +++ b/src/util/entities/ClientRelease.ts
@@ -1,4 +1,4 @@ -import { Column, Entity} from "typeorm"; +import { Column, Entity } from "typeorm"; import { BaseClass } from "./BaseClass"; @Entity("client_release") diff --git a/src/util/entities/Config.ts b/src/util/entities/Config.ts new file mode 100644
index 00000000..fc33bb65 --- /dev/null +++ b/src/util/entities/Config.ts
@@ -0,0 +1,11 @@ +import { Column, Entity } from "typeorm"; +import { BaseClassWithoutId, PrimaryIdColumn } from "./BaseClass"; + +@Entity("config") +export class ConfigEntity extends BaseClassWithoutId { + @PrimaryIdColumn() + key: string; + + @Column({ type: "simple-json", nullable: true }) + value: number | boolean | null | string | undefined; +} diff --git a/util/src/entities/ConnectedAccount.ts b/src/util/entities/ConnectedAccount.ts
index 09ae30ab..018b3995 100644 --- a/util/src/entities/ConnectedAccount.ts +++ b/src/util/entities/ConnectedAccount.ts
@@ -12,7 +12,7 @@ export class ConnectedAccount extends BaseClass { @JoinColumn({ name: "user_id" }) @ManyToOne(() => User, { - onDelete: "CASCADE", + onDelete: "CASCADE" }) user: User; diff --git a/util/src/entities/Emoji.ts b/src/util/entities/Emoji.ts
index a3615b7d..a2552995 100644 --- a/util/src/entities/Emoji.ts +++ b/src/util/entities/Emoji.ts
@@ -2,7 +2,6 @@ import { Column, Entity, JoinColumn, ManyToOne, RelationId } from "typeorm"; import { User } from "."; import { BaseClass } from "./BaseClass"; import { Guild } from "./Guild"; -import { Role } from "./Role"; @Entity("emojis") export class Emoji extends BaseClass { @@ -17,7 +16,7 @@ export class Emoji extends BaseClass { @JoinColumn({ name: "guild_id" }) @ManyToOne(() => Guild, { - onDelete: "CASCADE", + onDelete: "CASCADE" }) guild: Guild; @@ -40,7 +39,7 @@ export class Emoji extends BaseClass { @Column({ type: "simple-array" }) roles: string[]; // roles this emoji is whitelisted to (new discord feature?) - + @Column({ type: "simple-array", nullable: true }) groups: string[]; // user groups this emoji is whitelisted to (Fosscord extension) } diff --git a/src/util/entities/Encryption.ts b/src/util/entities/Encryption.ts new file mode 100644
index 00000000..43f02962 --- /dev/null +++ b/src/util/entities/Encryption.ts
@@ -0,0 +1,25 @@ +import { Column, Entity } from "typeorm"; +import { Snowflake } from "../util"; +import { BitField } from "../util/BitField"; +import { BaseClass } from "./BaseClass"; + +@Entity("security_settings") +export class SecuritySettings extends BaseClass { + @Column({ nullable: true }) + guild_id: Snowflake; + + @Column({ nullable: true }) + channel_id: Snowflake; + + @Column() + encryption_permission_mask: BitField; + + @Column() + allowed_algorithms: string[]; + + @Column() + current_algorithm: string; + + @Column({ nullable: true }) + used_since_message: Snowflake; +} diff --git a/util/src/entities/Group.ts b/src/util/entities/Group.ts
index b24d38cf..23aaabf2 100644 --- a/util/src/entities/Group.ts +++ b/src/util/entities/Group.ts
@@ -1,11 +1,11 @@ -import { Column, Entity, JoinColumn, ManyToOne, RelationId } from "typeorm"; +import { Column, Entity } from "typeorm"; import { BaseClass } from "./BaseClass"; @Entity("groups") export class UserGroup extends BaseClass { - @Column({ nullable: true }) - parent?: BigInt; + @Column({ nullable: true }) + parent?: BigInt; @Column() color: number; @@ -13,7 +13,7 @@ export class UserGroup extends BaseClass { @Column() hoist: boolean; - @Column() + @Column() mentionable: boolean; @Column() diff --git a/util/src/entities/Guild.ts b/src/util/entities/Guild.ts
index 70bb41c5..015c6d04 100644 --- a/util/src/entities/Guild.ts +++ b/src/util/entities/Guild.ts
@@ -1,5 +1,6 @@ -import { Column, Entity, JoinColumn, ManyToMany, ManyToOne, OneToMany, OneToOne, RelationId } from "typeorm"; +import { Column, Entity, JoinColumn, ManyToOne, OneToMany, RelationId } from "typeorm"; import { Config, handleFile, Snowflake } from ".."; +import { OrmUtils } from "../util/imports/OrmUtils"; import { Ban } from "./Ban"; import { BaseClass } from "./BaseClass"; import { Channel } from "./Channel"; @@ -31,15 +32,7 @@ import { Webhook } from "./Webhook"; // "Gacha" // ], -export const PublicGuildRelations = [ - "channels", - "emojis", - "members", - "roles", - "stickers", - "voice_states", - "members.user", -]; +export const PublicGuildRelations = ["channels", "emojis", "members", "roles", "stickers", "voice_states", "members.user"]; @Entity("guilds") export class Guild extends BaseClass { @@ -52,7 +45,7 @@ export class Guild extends BaseClass { afk_channel?: Channel; @Column({ nullable: true }) - afk_timeout?: number; + afk_timeout?: number = Config.get().defaults.guild.afkTimeout; // * commented out -> use owner instead // application id of the guild creator if it is bot-created @@ -62,7 +55,7 @@ export class Guild extends BaseClass { @JoinColumn({ name: "ban_ids" }) @OneToMany(() => Ban, (ban: Ban) => ban.guild, { cascade: true, - orphanedRowAction: "delete", + orphanedRowAction: "delete" }) bans: Ban[]; @@ -70,7 +63,7 @@ export class Guild extends BaseClass { banner?: string; @Column({ nullable: true }) - default_message_notifications?: number; + default_message_notifications?: number = Config.get().defaults.guild.defaultMessageNotifications; @Column({ nullable: true }) description?: string; @@ -79,7 +72,7 @@ export class Guild extends BaseClass { discovery_splash?: string; @Column({ nullable: true }) - explicit_content_filter?: number; + explicit_content_filter?: number = Config.get().defaults.guild.explicitContentFilter; @Column({ type: "simple-array" }) features: string[]; //TODO use enum @@ -95,24 +88,24 @@ export class Guild extends BaseClass { large?: boolean; @Column({ nullable: true }) - max_members?: number; // e.g. default 100.000 + max_members?: number = Config.get().limits.guild.maxMembers; // e.g. default 100.000 @Column({ nullable: true }) - max_presences?: number; + max_presences?: number = Config.get().defaults.guild.maxPresences; @Column({ nullable: true }) - max_video_channel_users?: number; // ? default: 25, is this max 25 streaming or watching + max_video_channel_users?: number = Config.get().defaults.guild.maxVideoChannelUsers; // ? default: 25, is this max 25 streaming or watching @Column({ nullable: true }) - member_count?: number; + member_count?: number = 0; @Column({ nullable: true }) - presence_count?: number; // users online + presence_count?: number = 0; // users online @OneToMany(() => Member, (member: Member) => member.guild, { cascade: true, orphanedRowAction: "delete", - onDelete: "CASCADE", + onDelete: "CASCADE" }) members: Member[]; @@ -120,14 +113,14 @@ export class Guild extends BaseClass { @OneToMany(() => Role, (role: Role) => role.guild, { cascade: true, orphanedRowAction: "delete", - onDelete: "CASCADE", + onDelete: "CASCADE" }) roles: Role[]; @JoinColumn({ name: "channel_ids" }) @OneToMany(() => Channel, (channel: Channel) => channel.guild, { cascade: true, - orphanedRowAction: "delete", + orphanedRowAction: "delete" }) channels: Channel[]; @@ -143,7 +136,7 @@ export class Guild extends BaseClass { @OneToMany(() => Emoji, (emoji: Emoji) => emoji.guild, { cascade: true, orphanedRowAction: "delete", - onDelete: "CASCADE", + onDelete: "CASCADE" }) emojis: Emoji[]; @@ -151,7 +144,7 @@ export class Guild extends BaseClass { @OneToMany(() => Sticker, (sticker: Sticker) => sticker.guild, { cascade: true, orphanedRowAction: "delete", - onDelete: "CASCADE", + onDelete: "CASCADE" }) stickers: Sticker[]; @@ -159,7 +152,7 @@ export class Guild extends BaseClass { @OneToMany(() => Invite, (invite: Invite) => invite.guild, { cascade: true, orphanedRowAction: "delete", - onDelete: "CASCADE", + onDelete: "CASCADE" }) invites: Invite[]; @@ -167,7 +160,7 @@ export class Guild extends BaseClass { @OneToMany(() => VoiceState, (voicestate: VoiceState) => voicestate.guild, { cascade: true, orphanedRowAction: "delete", - onDelete: "CASCADE", + onDelete: "CASCADE" }) voice_states: VoiceState[]; @@ -175,7 +168,7 @@ export class Guild extends BaseClass { @OneToMany(() => Webhook, (webhook: Webhook) => webhook.guild, { cascade: true, orphanedRowAction: "delete", - onDelete: "CASCADE", + onDelete: "CASCADE" }) webhooks: Webhook[]; @@ -269,7 +262,7 @@ export class Guild extends BaseClass { @Column({ nullable: true }) nsfw?: boolean; - + // TODO: nested guilds @Column({ nullable: true }) parent?: string; @@ -277,15 +270,14 @@ export class Guild extends BaseClass { // only for developer portal permissions?: number; - static async createGuild(body: { - name?: string; - icon?: string | null; - owner_id?: string; - channels?: Partial<Channel>[]; - }) { + //new guild settings, 11/08/2022: + @Column({ nullable: true }) + premium_progress_bar_enabled: boolean = false; + + static async createGuild(body: { name?: string; icon?: string | null; owner_id?: string; channels?: Partial<Channel>[] }) { const guild_id = Snowflake.generate(); - const guild = await new Guild({ + const guild: Guild = OrmUtils.mergeDeep(new Guild(), { name: body.name || "Fosscord", icon: await handleFile(`/icons/${guild_id}`, body.icon as string), region: Config.get().regions.default, @@ -313,14 +305,15 @@ export class Guild extends BaseClass { welcome_screen: { enabled: false, description: "Fill in your description", - welcome_channels: [], + welcome_channels: [] }, - widget_enabled: true, // NB: don't set it as false to prevent artificial restrictions - }).save(); + widget_enabled: true // NB: don't set it as false to prevent artificial restrictions + }); + await guild.save(); // we have to create the role _after_ the guild because else we would get a "SQLITE_CONSTRAINT: FOREIGN KEY constraint failed" error // TODO: make the @everyone a pseudorole that is dynamically generated at runtime so we can save storage - await new Role({ + let role: Role = OrmUtils.mergeDeep(new Role(), { id: guild_id, guild_id: guild_id, color: 0, @@ -333,7 +326,8 @@ export class Guild extends BaseClass { position: 0, icon: null, unicode_emoji: null - }).save(); + }); + await role.save(); if (!body.channels || !body.channels.length) body.channels = [{ id: "01", type: 0, name: "general" }]; @@ -346,15 +340,15 @@ export class Guild extends BaseClass { }); for (const channel of body.channels?.sort((a, b) => (a.parent_id ? 1 : -1))) { - var id = ids.get(channel.id) || Snowflake.generate(); + let id = ids.get(channel.id) || Snowflake.generate(); - var parent_id = ids.get(channel.parent_id); + let parent_id = ids.get(channel.parent_id); await Channel.createChannel({ ...channel, guild_id, id, parent_id }, body.owner_id, { keepId: true, skipExistsCheck: true, skipPermissionCheck: true, - skipEventEmit: true, + skipEventEmit: true }); } diff --git a/util/src/entities/Invite.ts b/src/util/entities/Invite.ts
index 6ac64ddc..f6ba85d7 100644 --- a/util/src/entities/Invite.ts +++ b/src/util/entities/Invite.ts
@@ -1,8 +1,9 @@ -import { Column, Entity, JoinColumn, ManyToOne, RelationId, PrimaryColumn } from "typeorm"; -import { Member } from "./Member"; +import { random } from "@fosscord/api"; +import { Column, Entity, JoinColumn, ManyToOne, PrimaryColumn, RelationId } from "typeorm"; import { BaseClassWithoutId } from "./BaseClass"; import { Channel } from "./Channel"; import { Guild } from "./Guild"; +import { Member } from "./Member"; import { User } from "./User"; export const PublicInviteRelation = ["inviter", "guild", "channel"]; @@ -10,13 +11,13 @@ export const PublicInviteRelation = ["inviter", "guild", "channel"]; @Entity("invites") export class Invite extends BaseClassWithoutId { @PrimaryColumn() - code: string; + code: string = random(); @Column() - temporary: boolean; + temporary: boolean = true; @Column() - uses: number; + uses: number = 0; @Column() max_uses: number; @@ -25,7 +26,7 @@ export class Invite extends BaseClassWithoutId { max_age: number; @Column() - created_at: Date; + created_at: Date = new Date(); @Column() expires_at: Date; @@ -36,7 +37,7 @@ export class Invite extends BaseClassWithoutId { @JoinColumn({ name: "guild_id" }) @ManyToOne(() => Guild, { - onDelete: "CASCADE", + onDelete: "CASCADE" }) guild: Guild; @@ -46,7 +47,7 @@ export class Invite extends BaseClassWithoutId { @JoinColumn({ name: "channel_id" }) @ManyToOne(() => Channel, { - onDelete: "CASCADE", + onDelete: "CASCADE" }) channel: Channel; @@ -55,7 +56,9 @@ export class Invite extends BaseClassWithoutId { inviter_id: string; @JoinColumn({ name: "inviter_id" }) - @ManyToOne(() => User) + @ManyToOne(() => User, { + onDelete: "CASCADE" + }) inviter: User; @Column({ nullable: true }) @@ -64,7 +67,7 @@ export class Invite extends BaseClassWithoutId { @JoinColumn({ name: "target_user_id" }) @ManyToOne(() => User, { - onDelete: "CASCADE", + onDelete: "CASCADE" }) target_user?: string; // could be used for "User specific invites" https://github.com/fosscord/fosscord/issues/62 @@ -75,7 +78,7 @@ export class Invite extends BaseClassWithoutId { vanity_url?: boolean; static async joinGuild(user_id: string, code: string) { - const invite = await Invite.findOneOrFail({ code }); + const invite = await Invite.findOneOrFail({ where: { code } }); if (invite.uses++ >= invite.max_uses && invite.max_uses !== 0) await Invite.delete({ code }); else await invite.save(); diff --git a/util/src/entities/Member.ts b/src/util/entities/Member.ts
index fe2d5590..42a014d4 100644 --- a/util/src/entities/Member.ts +++ b/src/util/entities/Member.ts
@@ -1,30 +1,14 @@ -import { PublicUser, User } from "./User"; -import { BaseClass } from "./BaseClass"; -import { - Column, - Entity, - Index, - JoinColumn, - JoinTable, - ManyToMany, - ManyToOne, - PrimaryGeneratedColumn, - RelationId, -} from "typeorm"; -import { Guild } from "./Guild"; -import { Config, emitEvent } from "../util"; -import { - GuildCreateEvent, - GuildDeleteEvent, - GuildMemberAddEvent, - GuildMemberRemoveEvent, - GuildMemberUpdateEvent, -} from "../interfaces"; -import { HTTPError } from "lambert-server"; -import { Role } from "./Role"; -import { BaseClassWithoutId } from "./BaseClass"; +import { Column, Entity, Index, JoinColumn, JoinTable, ManyToMany, ManyToOne, PrimaryGeneratedColumn, RelationId } from "typeorm"; import { Ban, PublicGuildRelations } from "."; +import { GuildCreateEvent, GuildDeleteEvent, GuildMemberAddEvent, GuildMemberRemoveEvent, GuildMemberUpdateEvent } from "../interfaces"; +import { Config, emitEvent } from "../util"; import { DiscordApiErrors } from "../util/Constants"; +import { HTTPError } from "../util/imports/HTTPError"; +import { OrmUtils } from "../util/imports/OrmUtils"; +import { BaseClassWithoutId } from "./BaseClass"; +import { Guild } from "./Guild"; +import { Role } from "./Role"; +import { PublicUser, User } from "./User"; export const MemberPrivateProjection: (keyof Member)[] = [ "id", @@ -39,7 +23,7 @@ export const MemberPrivateProjection: (keyof Member)[] = [ "premium_since", "roles", "settings", - "user", + "user" ]; @Entity("members") @@ -54,7 +38,7 @@ export class Member extends BaseClassWithoutId { @JoinColumn({ name: "id" }) @ManyToOne(() => User, { - onDelete: "CASCADE", + onDelete: "CASCADE" }) user: User; @@ -64,20 +48,20 @@ export class Member extends BaseClassWithoutId { @JoinColumn({ name: "guild_id" }) @ManyToOne(() => Guild, { - onDelete: "CASCADE", + onDelete: "CASCADE" }) guild: Guild; @Column({ nullable: true }) nick?: string; - + @JoinTable({ name: "member_roles", joinColumn: { name: "index", referencedColumnName: "index" }, inverseJoinColumn: { name: "role_id", - referencedColumnName: "id", - }, + referencedColumnName: "id" + } }) @ManyToMany(() => Role, { cascade: true }) roles: Role[]; @@ -85,8 +69,8 @@ export class Member extends BaseClassWithoutId { @Column() joined_at: Date; - @Column({ type: "bigint", nullable: true }) - premium_since?: number; + @Column({ nullable: true }) + premium_since?: Date; @Column() deaf: boolean; @@ -102,14 +86,14 @@ export class Member extends BaseClassWithoutId { @Column({ nullable: true }) last_message_id?: string; - + /** @JoinColumn({ name: "id" }) @ManyToOne(() => User, { onDelete: "DO NOTHING", // do not auto-kick force-joined members just because their joiners left the server }) **/ - @Column({ nullable: true}) + @Column({ nullable: true }) joined_by?: string; // TODO: add this when we have proper read receipts @@ -117,35 +101,37 @@ export class Member extends BaseClassWithoutId { // read_state: ReadState; static async IsInGuildOrFail(user_id: string, guild_id: string) { - if (await Member.count({ id: user_id, guild: { id: guild_id } })) return true; + if (await Member.count({ where: { id: user_id, guild: { id: guild_id } } })) return true; throw new HTTPError("You are not member of this guild", 403); } static async removeFromGuild(user_id: string, guild_id: string) { - const guild = await Guild.findOneOrFail({ select: ["owner_id"], where: { id: guild_id } }); + const guild = await Guild.findOneOrFail({ select: ["owner_id", "member_count"], where: { id: guild_id } }); if (guild.owner_id === user_id) throw new Error("The owner cannot be removed of the guild"); const member = await Member.findOneOrFail({ where: { id: user_id, guild_id }, relations: ["user"] }); // use promise all to execute all promises at the same time -> save time + //TODO: check for bugs + if (guild.member_count) guild.member_count--; return Promise.all([ Member.delete({ id: user_id, - guild_id, + guild_id }), - Guild.decrement({ id: guild_id }, "member_count", -1), + //Guild.decrement({ id: guild_id }, "member_count", -1), emitEvent({ event: "GUILD_DELETE", data: { - id: guild_id, + id: guild_id }, - user_id: user_id, + user_id: user_id } as GuildDeleteEvent), emitEvent({ event: "GUILD_MEMBER_REMOVE", data: { guild_id, user: member.user }, - guild_id, - } as GuildMemberRemoveEvent), + guild_id + } as GuildMemberRemoveEvent) ]); } @@ -155,11 +141,11 @@ export class Member extends BaseClassWithoutId { Member.findOneOrFail({ where: { id: user_id, guild_id }, relations: ["user", "roles"], // we don't want to load the role objects just the ids - select: ["index", "roles.id"], + select: ["index"] }), - Role.findOneOrFail({ where: { id: role_id, guild_id }, select: ["id"] }), + Role.findOneOrFail({ where: { id: role_id, guild_id }, select: ["id"] }) ]); - member.roles.push(new Role({ id: role_id })); + member.roles.push(OrmUtils.mergeDeep(new Role(), { id: role_id })); await Promise.all([ member.save(), @@ -168,10 +154,10 @@ export class Member extends BaseClassWithoutId { data: { guild_id, user: member.user, - roles: member.roles.map((x) => x.id), + roles: member.roles.map((x) => x.id) }, - guild_id, - } as GuildMemberUpdateEvent), + guild_id + } as GuildMemberUpdateEvent) ]); } @@ -181,9 +167,9 @@ export class Member extends BaseClassWithoutId { Member.findOneOrFail({ where: { id: user_id, guild_id }, relations: ["user", "roles"], // we don't want to load the role objects just the ids - select: ["roles.id", "index"], + select: ["index"] }), - await Role.findOneOrFail({ id: role_id, guild_id }), + await Role.findOneOrFail({ where: { id: role_id, guild_id } }) ]); member.roles = member.roles.filter((x) => x.id == role_id); @@ -194,10 +180,10 @@ export class Member extends BaseClassWithoutId { data: { guild_id, user: member.user, - roles: member.roles.map((x) => x.id), + roles: member.roles.map((x) => x.id) }, - guild_id, - } as GuildMemberUpdateEvent), + guild_id + } as GuildMemberUpdateEvent) ]); } @@ -205,9 +191,9 @@ export class Member extends BaseClassWithoutId { const member = await Member.findOneOrFail({ where: { id: user_id, - guild_id, + guild_id }, - relations: ["user"], + relations: ["user"] }); member.nick = nickname; @@ -219,10 +205,10 @@ export class Member extends BaseClassWithoutId { data: { guild_id, user: member.user, - nick: nickname, + nick: nickname }, - guild_id, - } as GuildMemberUpdateEvent), + guild_id + } as GuildMemberUpdateEvent) ]); } @@ -233,19 +219,19 @@ export class Member extends BaseClassWithoutId { throw DiscordApiErrors.USER_BANNED; } const { maxGuilds } = Config.get().limits.user; - const guild_count = await Member.count({ id: user_id }); + const guild_count = await Member.count({ where: { id: user_id } }); if (guild_count >= maxGuilds) { throw new HTTPError(`You are at the ${maxGuilds} server limit.`, 403); } const guild = await Guild.findOneOrFail({ where: { - id: guild_id, + id: guild_id }, - relations: PublicGuildRelations, + relations: PublicGuildRelations }); - if (await Member.count({ id: user.id, guild: { id: guild_id } })) + if (await Member.count({ where: { id: user.id, guild: { id: guild_id } } })) throw new HTTPError("You are already a member of this guild", 400); const member = { @@ -254,16 +240,17 @@ export class Member extends BaseClassWithoutId { nick: undefined, roles: [guild_id], // @everyone role joined_at: new Date(), - premium_since: (new Date()).getTime(), + premium_since: null, deaf: false, mute: false, - pending: false, + pending: false }; - + //TODO: check for bugs + if (guild.member_count) guild.member_count++; await Promise.all([ - new Member({ + OrmUtils.mergeDeep(new Member(), { ...member, - roles: [new Role({ id: guild_id })], + roles: [OrmUtils.mergeDeep(new Role(), { id: guild_id })], // read_state: {}, settings: { channel_overrides: [], @@ -272,19 +259,19 @@ export class Member extends BaseClassWithoutId { muted: false, suppress_everyone: false, suppress_roles: false, - version: 0, - }, + version: 0 + } // Member.save is needed because else the roles relations wouldn't be updated }).save(), - Guild.increment({ id: guild_id }, "member_count", 1), + //Guild.increment({ id: guild_id }, "member_count", 1), emitEvent({ event: "GUILD_MEMBER_ADD", data: { ...member, user, - guild_id, + guild_id }, - guild_id, + guild_id } as GuildMemberAddEvent), emitEvent({ event: "GUILD_CREATE", @@ -297,10 +284,10 @@ export class Member extends BaseClassWithoutId { joined_at: member.joined_at, presences: [], stage_instances: [], - threads: [], + threads: [] }, - user_id, - } as GuildCreateEvent), + user_id + } as GuildCreateEvent) ]); } } @@ -326,16 +313,7 @@ export interface MuteConfig { selected_time_window: number; } -export type PublicMemberKeys = - | "id" - | "guild_id" - | "nick" - | "roles" - | "joined_at" - | "pending" - | "deaf" - | "mute" - | "premium_since"; +export type PublicMemberKeys = "id" | "guild_id" | "nick" | "roles" | "joined_at" | "pending" | "deaf" | "mute" | "premium_since"; export const PublicMemberProjection: PublicMemberKeys[] = [ "id", @@ -346,7 +324,7 @@ export const PublicMemberProjection: PublicMemberKeys[] = [ "pending", "deaf", "mute", - "premium_since", + "premium_since" ]; // @ts-ignore diff --git a/util/src/entities/Message.ts b/src/util/entities/Message.ts
index e18cf691..8122b532 100644 --- a/util/src/entities/Message.ts +++ b/src/util/entities/Message.ts
@@ -1,29 +1,15 @@ -import { User } from "./User"; -import { Member } from "./Member"; -import { Role } from "./Role"; -import { Channel } from "./Channel"; +import { Column, CreateDateColumn, Entity, Index, JoinColumn, JoinTable, ManyToMany, ManyToOne, OneToMany, RelationId } from "typeorm"; import { InteractionType } from "../interfaces/Interaction"; import { Application } from "./Application"; -import { - Column, - CreateDateColumn, - Entity, - FindConditions, - Index, - JoinColumn, - JoinTable, - ManyToMany, - ManyToOne, - OneToMany, - RelationId, - RemoveOptions, - UpdateDateColumn, -} from "typeorm"; +import { Attachment } from "./Attachment"; import { BaseClass } from "./BaseClass"; +import { Channel } from "./Channel"; import { Guild } from "./Guild"; -import { Webhook } from "./Webhook"; +import { Member } from "./Member"; +import { Role } from "./Role"; import { Sticker } from "./Sticker"; -import { Attachment } from "./Attachment"; +import { User } from "./User"; +import { Webhook } from "./Webhook"; export enum MessageType { DEFAULT = 0, @@ -63,7 +49,7 @@ export class Message extends BaseClass { @JoinColumn({ name: "channel_id" }) @ManyToOne(() => Channel, { - onDelete: "CASCADE", + onDelete: "CASCADE" }) channel: Channel; @@ -73,7 +59,7 @@ export class Message extends BaseClass { @JoinColumn({ name: "guild_id" }) @ManyToOne(() => Guild, { - onDelete: "CASCADE", + onDelete: "CASCADE" }) guild?: Guild; @@ -84,7 +70,7 @@ export class Message extends BaseClass { @JoinColumn({ name: "author_id", referencedColumnName: "id" }) @ManyToOne(() => User, { - onDelete: "CASCADE", + onDelete: "CASCADE" }) author?: User; @@ -94,7 +80,7 @@ export class Message extends BaseClass { @JoinColumn({ name: "member_id", referencedColumnName: "id" }) @ManyToOne(() => User, { - onDelete: "CASCADE", + onDelete: "CASCADE" }) member?: Member; @@ -148,7 +134,7 @@ export class Message extends BaseClass { @OneToMany(() => Attachment, (attachment: Attachment) => attachment.message, { cascade: true, - orphanedRowAction: "delete", + orphanedRowAction: "delete" }) attachments?: Attachment[]; @@ -213,7 +199,7 @@ export interface MessageComponent { export enum MessageComponentType { Script = 0, // self command script ActionRow = 1, - Button = 2, + Button = 2 } export interface Embed { @@ -254,7 +240,7 @@ export enum EmbedType { video = "video", gifv = "gifv", article = "article", - link = "link", + link = "link" } export interface EmbedImage { diff --git a/util/src/entities/Migration.ts b/src/util/entities/Migration.ts
index 3f39ae72..626ec429 100644 --- a/util/src/entities/Migration.ts +++ b/src/util/entities/Migration.ts
@@ -1,9 +1,7 @@ import { Column, Entity, ObjectIdColumn, PrimaryGeneratedColumn } from "typeorm"; import { BaseClassWithoutId } from "."; -export const PrimaryIdAutoGenerated = process.env.DATABASE?.startsWith("mongodb") - ? ObjectIdColumn - : PrimaryGeneratedColumn; +export const PrimaryIdAutoGenerated = process.env.DATABASE?.startsWith("mongodb") ? ObjectIdColumn : PrimaryGeneratedColumn; @Entity("migrations") export class Migration extends BaseClassWithoutId { diff --git a/src/util/entities/Note.ts b/src/util/entities/Note.ts new file mode 100644
index 00000000..b3ac45ee --- /dev/null +++ b/src/util/entities/Note.ts
@@ -0,0 +1,18 @@ +import { Column, Entity, JoinColumn, ManyToOne, Unique } from "typeorm"; +import { BaseClass } from "./BaseClass"; +import { User } from "./User"; + +@Entity("notes") +@Unique(["owner", "target"]) +export class Note extends BaseClass { + @JoinColumn({ name: "owner_id" }) + @ManyToOne(() => User, { onDelete: "CASCADE" }) + owner: User; + + @JoinColumn({ name: "target_id" }) + @ManyToOne(() => User, { onDelete: "CASCADE" }) + target: User; + + @Column() + content: string; +} diff --git a/util/src/entities/RateLimit.ts b/src/util/entities/RateLimit.ts
index f5916f6b..f5916f6b 100644 --- a/util/src/entities/RateLimit.ts +++ b/src/util/entities/RateLimit.ts
diff --git a/util/src/entities/ReadState.ts b/src/util/entities/ReadState.ts
index b915573b..77d2c08a 100644 --- a/util/src/entities/ReadState.ts +++ b/src/util/entities/ReadState.ts
@@ -1,7 +1,6 @@ import { Column, Entity, Index, JoinColumn, ManyToOne, RelationId } from "typeorm"; import { BaseClass } from "./BaseClass"; import { Channel } from "./Channel"; -import { Message } from "./Message"; import { User } from "./User"; // for read receipts @@ -17,7 +16,7 @@ export class ReadState extends BaseClass { @JoinColumn({ name: "channel_id" }) @ManyToOne(() => Channel, { - onDelete: "CASCADE", + onDelete: "CASCADE" }) channel: Channel; @@ -27,14 +26,14 @@ export class ReadState extends BaseClass { @JoinColumn({ name: "user_id" }) @ManyToOne(() => User, { - onDelete: "CASCADE", + onDelete: "CASCADE" }) user: User; // fully read marker @Column({ nullable: true }) - last_message_id: string; - + last_message_id: string; + // public read receipt @Column({ nullable: true }) public_ack: string; diff --git a/util/src/entities/Recipient.ts b/src/util/entities/Recipient.ts
index a945f938..fc9e629b 100644 --- a/util/src/entities/Recipient.ts +++ b/src/util/entities/Recipient.ts
@@ -9,7 +9,7 @@ export class Recipient extends BaseClass { @JoinColumn({ name: "channel_id" }) @ManyToOne(() => require("./Channel").Channel, { - onDelete: "CASCADE", + onDelete: "CASCADE" }) channel: import("./Channel").Channel; @@ -19,7 +19,7 @@ export class Recipient extends BaseClass { @JoinColumn({ name: "user_id" }) @ManyToOne(() => require("./User").User, { - onDelete: "CASCADE", + onDelete: "CASCADE" }) user: import("./User").User; diff --git a/util/src/entities/Relationship.ts b/src/util/entities/Relationship.ts
index c3592c76..b55d9e64 100644 --- a/util/src/entities/Relationship.ts +++ b/src/util/entities/Relationship.ts
@@ -6,7 +6,7 @@ export enum RelationshipType { outgoing = 4, incoming = 3, blocked = 2, - friends = 1, + friends = 1 } @Entity("relationships") @@ -18,7 +18,7 @@ export class Relationship extends BaseClass { @JoinColumn({ name: "from_id" }) @ManyToOne(() => User, { - onDelete: "CASCADE", + onDelete: "CASCADE" }) from: User; @@ -28,7 +28,7 @@ export class Relationship extends BaseClass { @JoinColumn({ name: "to_id" }) @ManyToOne(() => User, { - onDelete: "CASCADE", + onDelete: "CASCADE" }) to: User; @@ -43,7 +43,7 @@ export class Relationship extends BaseClass { id: this.to?.id || this.to_id, type: this.type, nickname: this.nickname, - user: this.to?.toPublicUser(), + user: this.to?.toPublicUser() }; } } diff --git a/util/src/entities/Role.ts b/src/util/entities/Role.ts
index 4b721b5b..b1fd9bb1 100644 --- a/util/src/entities/Role.ts +++ b/src/util/entities/Role.ts
@@ -11,7 +11,7 @@ export class Role extends BaseClass { @JoinColumn({ name: "guild_id" }) @ManyToOne(() => Guild, { - onDelete: "CASCADE", + onDelete: "CASCADE" }) guild: Guild; diff --git a/util/src/entities/Session.ts b/src/util/entities/Session.ts
index 969efa89..0cb4c309 100644 --- a/util/src/entities/Session.ts +++ b/src/util/entities/Session.ts
@@ -1,8 +1,8 @@ -import { User } from "./User"; -import { BaseClass } from "./BaseClass"; import { Column, Entity, JoinColumn, ManyToOne, RelationId } from "typeorm"; -import { Status } from "../interfaces/Status"; import { Activity } from "../interfaces/Activity"; +import { Status } from "../interfaces/Status"; +import { BaseClass } from "./BaseClass"; +import { User } from "./User"; //TODO we need to remove all sessions on server start because if the server crashes without closing websockets it won't delete them @@ -14,7 +14,7 @@ export class Session extends BaseClass { @JoinColumn({ name: "user_id" }) @ManyToOne(() => User, { - onDelete: "CASCADE", + onDelete: "CASCADE" }) user: User; @@ -37,10 +37,4 @@ export class Session extends BaseClass { status: Status; //TODO enum } -export const PrivateSessionProjection: (keyof Session)[] = [ - "user_id", - "session_id", - "activities", - "client_info", - "status", -]; +export const PrivateSessionProjection: (keyof Session)[] = ["user_id", "session_id", "activities", "client_info", "status"]; diff --git a/util/src/entities/Sticker.ts b/src/util/entities/Sticker.ts
index 37bc6fbe..69836e62 100644 --- a/util/src/entities/Sticker.ts +++ b/src/util/entities/Sticker.ts
@@ -1,18 +1,18 @@ 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, - GUILD = 2, + GUILD = 2 } export enum StickerFormatType { GIF = 0, // gif is a custom format type and not in discord spec PNG = 1, APNG = 2, - LOTTIE = 3, + LOTTIE = 3 } @Entity("stickers") @@ -36,7 +36,7 @@ export class Sticker extends BaseClass { @JoinColumn({ name: "pack_id" }) @ManyToOne(() => require("./StickerPack").StickerPack, { onDelete: "CASCADE", - nullable: true, + nullable: true }) pack: import("./StickerPack").StickerPack; @@ -45,7 +45,7 @@ export class Sticker extends BaseClass { @JoinColumn({ name: "guild_id" }) @ManyToOne(() => Guild, { - onDelete: "CASCADE", + onDelete: "CASCADE" }) guild?: Guild; @@ -54,7 +54,7 @@ export class Sticker extends BaseClass { @JoinColumn({ name: "user_id" }) @ManyToOne(() => User, { - onDelete: "CASCADE", + onDelete: "CASCADE" }) user?: User; diff --git a/util/src/entities/StickerPack.ts b/src/util/entities/StickerPack.ts
index ec8c69a2..4619af34 100644 --- a/util/src/entities/StickerPack.ts +++ b/src/util/entities/StickerPack.ts
@@ -1,4 +1,4 @@ -import { Column, Entity, JoinColumn, ManyToOne, OneToMany, OneToOne, RelationId } from "typeorm"; +import { Column, Entity, JoinColumn, ManyToOne, OneToMany, RelationId } from "typeorm"; import { Sticker } from "."; import { BaseClass } from "./BaseClass"; @@ -15,7 +15,7 @@ export class StickerPack extends BaseClass { @OneToMany(() => Sticker, (sticker: Sticker) => sticker.pack, { cascade: true, - orphanedRowAction: "delete", + orphanedRowAction: "delete" }) stickers: Sticker[]; diff --git a/util/src/entities/Team.ts b/src/util/entities/Team.ts
index 22140b7f..1d2d7002 100644 --- a/util/src/entities/Team.ts +++ b/src/util/entities/Team.ts
@@ -1,4 +1,4 @@ -import { Column, Entity, JoinColumn, ManyToMany, ManyToOne, OneToMany, RelationId } from "typeorm"; +import { Column, Entity, JoinColumn, ManyToOne, OneToMany, RelationId } from "typeorm"; import { BaseClass } from "./BaseClass"; import { TeamMember } from "./TeamMember"; import { User } from "./User"; @@ -10,7 +10,7 @@ export class Team extends BaseClass { @JoinColumn({ name: "member_ids" }) @OneToMany(() => TeamMember, (member: TeamMember) => member.team, { - orphanedRowAction: "delete", + orphanedRowAction: "delete" }) members: TeamMember[]; diff --git a/util/src/entities/TeamMember.ts b/src/util/entities/TeamMember.ts
index b726e1e8..d11ebf95 100644 --- a/util/src/entities/TeamMember.ts +++ b/src/util/entities/TeamMember.ts
@@ -4,7 +4,7 @@ import { User } from "./User"; export enum TeamMemberState { INVITED = 1, - ACCEPTED = 2, + ACCEPTED = 2 } @Entity("team_members") @@ -21,7 +21,7 @@ export class TeamMember extends BaseClass { @JoinColumn({ name: "team_id" }) @ManyToOne(() => require("./Team").Team, (team: import("./Team").Team) => team.members, { - onDelete: "CASCADE", + onDelete: "CASCADE" }) team: import("./Team").Team; @@ -31,7 +31,7 @@ export class TeamMember extends BaseClass { @JoinColumn({ name: "user_id" }) @ManyToOne(() => User, { - onDelete: "CASCADE", + onDelete: "CASCADE" }) user: User; } diff --git a/util/src/entities/Template.ts b/src/util/entities/Template.ts
index 1d952283..1d952283 100644 --- a/util/src/entities/Template.ts +++ b/src/util/entities/Template.ts
diff --git a/util/src/entities/User.ts b/src/util/entities/User.ts
index 9b1c494e..1237b676 100644 --- a/util/src/entities/User.ts +++ b/src/util/entities/User.ts
@@ -1,10 +1,11 @@ -import { Column, Entity, FindOneOptions, JoinColumn, ManyToMany, OneToMany, RelationId } from "typeorm"; -import { BaseClass } from "./BaseClass"; +import { Column, Entity, FindOneOptions, FindOptionsSelectByString, JoinColumn, OneToMany, OneToOne } from "typeorm"; +import { Member, Session, UserSettings } from "."; +import { Config, FieldErrors, Snowflake, trimSpecial } from ".."; import { BitField } from "../util/BitField"; -import { Relationship } from "./Relationship"; +import { OrmUtils } from "../util/imports/OrmUtils"; +import { BaseClass } from "./BaseClass"; import { ConnectedAccount } from "./ConnectedAccount"; -import { Config, FieldErrors, Snowflake, trimSpecial } from ".."; -import { Member, Session } from "."; +import { Relationship } from "./Relationship"; export enum PublicUserEnum { username, @@ -16,7 +17,7 @@ export enum PublicUserEnum { banner, bio, bot, - premium_since, + premium_since } export type PublicUserKeys = keyof typeof PublicUserEnum; @@ -30,17 +31,15 @@ export enum PrivateUserEnum { premium, premium_type, disabled, - settings, + settings // locale } export type PrivateUserKeys = keyof typeof PrivateUserEnum | PublicUserKeys; -export const PublicUserProjection = Object.values(PublicUserEnum).filter( - (x) => typeof x === "string" -) as PublicUserKeys[]; +export const PublicUserProjection = Object.values(PublicUserEnum).filter((x) => typeof x === "string") as PublicUserKeys[]; export const PrivateUserProjection = [ ...PublicUserProjection, - ...Object.values(PrivateUserEnum).filter((x) => typeof x === "string"), + ...Object.values(PrivateUserEnum).filter((x) => typeof x === "string") ] as PrivateUserKeys[]; // Private user data that should never get sent to the client @@ -82,58 +81,64 @@ export class User extends BaseClass { phone?: string; // phone number of the user @Column({ select: false }) - desktop: boolean; // if the user has desktop app installed + desktop: boolean = false; // if the user has desktop app installed @Column({ select: false }) - mobile: boolean; // if the user has mobile app installed + mobile: boolean = false; // if the user has mobile app installed @Column() - premium: boolean; // if user bought individual premium - - @Column() - premium_type: number; // individual premium level + premium: boolean = Config.get().defaults.user.premium; // if user bought individual premium @Column() - bot: boolean; // if user is bot + premium_type: number = Config.get().defaults.user.premium_type; // individual premium level @Column() + bot: boolean = false; // if user is bot + + @Column({ nullable: true }) bio: string; // short description of the user (max 190 chars -> should be configurable) @Column() - system: boolean; // shouldn't be used, the api sends this field type true, if the generated message comes from a system generated author + system: boolean = false; // shouldn't be used, the api sends this field type true, if the generated message comes from a system generated author @Column({ select: false }) - nsfw_allowed: boolean; // if the user can do age-restricted actions (NSFW channels/guilds/commands) - - @Column({ select: false }) + nsfw_allowed: boolean = true; // if the user can do age-restricted actions (NSFW channels/guilds/commands) // TODO: depending on age + + @Column({ select: false, nullable: true }) mfa_enabled: boolean; // if multi factor authentication is enabled + @Column({ select: false, nullable: true }) + totp_secret?: string; + + @Column({ nullable: true, select: false }) + totp_last_ticket?: string; + @Column() - created_at: Date; // registration date + created_at: Date = new Date(); // registration date @Column({ nullable: true }) - premium_since: Date; // premium date + premium_since: Date = new Date(); // premium date @Column({ select: false }) - verified: boolean; // if the user is offically verified + verified: boolean = Config.get().defaults.user.verified; // if the user is offically verified @Column() - disabled: boolean; // if the account is disabled + disabled: boolean = false; // if the account is disabled @Column() - deleted: boolean; // if the user was deleted + deleted: boolean = false; // if the user was deleted @Column({ nullable: true, select: false }) email?: string; // email of the user @Column() - flags: string; // UserFlags + flags: string = "0"; // UserFlags // TODO: generate @Column() - public_flags: number; + public_flags: number = 0; @Column({ type: "bigint" }) - rights: string; // Rights + rights: string = Config.get().register.defaultRights; // Rights @OneToMany(() => Session, (session: Session) => session.user) sessions: Session[]; @@ -141,14 +146,14 @@ export class User extends BaseClass { @JoinColumn({ name: "relationship_ids" }) @OneToMany(() => Relationship, (relationship: Relationship) => relationship.from, { cascade: true, - orphanedRowAction: "delete", + orphanedRowAction: "delete" }) relationships: Relationship[]; @JoinColumn({ name: "connected_account_ids" }) @OneToMany(() => ConnectedAccount, (account: ConnectedAccount) => account.user, { cascade: true, - orphanedRowAction: "delete", + orphanedRowAction: "delete" }) connected_accounts: ConnectedAccount[]; @@ -159,17 +164,29 @@ export class User extends BaseClass { }; @Column({ type: "simple-array", select: false }) - fingerprints: string[]; // array of fingerprints -> used to prevent multiple accounts + fingerprints: string[] = []; // array of fingerprints -> used to prevent multiple accounts - @Column({ type: "simple-json", select: false }) + @OneToOne(() => UserSettings, { + cascade: true, + orphanedRowAction: "delete", + eager: false + }) + @JoinColumn() settings: UserSettings; - + // workaround to prevent fossord-unaware clients from deleting settings not used by them @Column({ type: "simple-json", select: false }) - extended_settings: string; + extended_settings: string = "{}"; @Column({ type: "simple-json" }) - notes: { [key: string]: string }; //key is ID of user + notes: { [key: string]: string } = {}; //key is ID of user + + async save(): Promise<any> { + if (!this.settings) this.settings = new UserSettings(); + this.settings.id = this.id; + //await this.settings.save(); + return super.save(); + } toPublicUser() { const user: any = {}; @@ -180,19 +197,17 @@ export class User extends BaseClass { } static async getPublicUser(user_id: string, opts?: FindOneOptions<User>) { - return await User.findOneOrFail( - { id: user_id }, - { - ...opts, - select: [...PublicUserProjection, ...(opts?.select || [])], - } - ); + return await User.findOneOrFail({ + where: { id: user_id }, + select: [...PublicUserProjection, ...((opts?.select as FindOptionsSelectByString<User>) || [])], + ...opts + }); } - private static async generateDiscriminator(username: string): Promise<string | undefined> { + public static async generateDiscriminator(username: string): Promise<string | undefined> { if (Config.get().register.incrementingDiscriminators) { // discriminator will be incrementally generated - + // First we need to figure out the currently highest discrimnator for the given username and then increment it const users = await User.find({ where: { username }, select: ["discriminator"] }); const highestDiscriminator = Math.max(0, ...users.map((u) => Number(u.discriminator))); @@ -223,7 +238,7 @@ export class User extends BaseClass { username, password, date_of_birth, - req, + req }: { username: string; password?: string; @@ -240,48 +255,30 @@ export class User extends BaseClass { throw FieldErrors({ username: { code: "USERNAME_TOO_MANY_USERS", - message: req.t("auth:register.USERNAME_TOO_MANY_USERS"), - }, + message: req?.t("auth:register.USERNAME_TOO_MANY_USERS") + } }); } // TODO: save date_of_birth // appearently discord doesn't save the date of birth and just calculate if nsfw is allowed // if nsfw_allowed is null/undefined it'll require date_of_birth to set it to true/false - const language = req.language === "en" ? "en-US" : req.language || "en-US"; + const language = req?.language === "en" ? "en-US" : req?.language || "en-US"; - const user = new User({ - created_at: new Date(), + const user = OrmUtils.mergeDeep(new User(), { + //required: username: username, discriminator, id: Snowflake.generate(), - bot: false, - system: false, - premium_since: new Date(), - desktop: false, - mobile: false, - premium: true, - premium_type: 2, - bio: "", - mfa_enabled: false, - verified: true, - disabled: false, - deleted: false, email: email, - rights: "0", // TODO: grant rights correctly, as 0 actually stands for no rights at all - nsfw_allowed: true, // TODO: depending on age - public_flags: "0", - flags: "0", // TODO: generate data: { hash: password, - valid_tokens_since: new Date(), + valid_tokens_since: new Date() }, - settings: { ...defaultSettings, locale: language }, - extended_settings: {}, - fingerprints: [], - notes: {}, + settings: { ...new UserSettings(), locale: language } }); + //await (user.settings as UserSettings).save(); await user.save(); setImmediate(async () => { @@ -296,85 +293,6 @@ export class User extends BaseClass { } } -export const defaultSettings: UserSettings = { - afk_timeout: 3600, - allow_accessibility_detection: true, - animate_emoji: true, - animate_stickers: 0, - contact_sync_enabled: false, - convert_emoticons: false, - custom_status: null, - default_guilds_restricted: false, - detect_platform_accounts: false, - developer_mode: true, - disable_games_tab: true, - enable_tts_command: false, - explicit_content_filter: 0, - friend_source_flags: { all: true }, - gateway_connected: false, - gif_auto_play: true, - guild_folders: [], - guild_positions: [], - inline_attachment_media: true, - inline_embed_media: true, - locale: "en-US", - message_display_compact: true, - native_phone_integration_enabled: true, - render_embeds: true, - render_reactions: true, - restricted_guilds: [], - show_current_game: true, - status: "online", - stream_notifications_enabled: false, - theme: "dark", - timezone_offset: 0, // TODO: timezone from request -}; - -export interface UserSettings { - afk_timeout: number; - allow_accessibility_detection: boolean; - animate_emoji: boolean; - animate_stickers: number; - contact_sync_enabled: boolean; - convert_emoticons: boolean; - custom_status: { - emoji_id?: string; - emoji_name?: string; - expires_at?: number; - text?: string; - } | null; - default_guilds_restricted: boolean; - detect_platform_accounts: boolean; - developer_mode: boolean; - disable_games_tab: boolean; - enable_tts_command: boolean; - explicit_content_filter: number; - friend_source_flags: { all: boolean }; - gateway_connected: boolean; - gif_auto_play: boolean; - // every top guild is displayed as a "folder" - guild_folders: { - color: number; - guild_ids: string[]; - id: number; - name: string; - }[]; - guild_positions: string[]; // guild ids ordered by position - inline_attachment_media: boolean; - inline_embed_media: boolean; - locale: string; // en_US - message_display_compact: boolean; - native_phone_integration_enabled: boolean; - render_embeds: boolean; - render_reactions: boolean; - restricted_guilds: string[]; - show_current_game: boolean; - status: "online" | "offline" | "dnd" | "idle" | "invisible"; - stream_notifications_enabled: boolean; - theme: "dark" | "white"; // dark - timezone_offset: number; // e.g -60 -} - export const CUSTOM_USER_FLAG_OFFSET = BigInt(1) << BigInt(32); export class UserFlags extends BitField { @@ -398,6 +316,6 @@ export class UserFlags extends BitField { VERIFIED_BOT: BigInt(1) << BigInt(16), EARLY_VERIFIED_BOT_DEVELOPER: BigInt(1) << BigInt(17), CERTIFIED_MODERATOR: BigInt(1) << BigInt(18), - BOT_HTTP_INTERACTIONS: BigInt(1) << BigInt(19), + BOT_HTTP_INTERACTIONS: BigInt(1) << BigInt(19) }; } diff --git a/util/src/entities/UserGroup.ts b/src/util/entities/UserGroup.ts
index 709b9d0b..08d68a4e 100644 --- a/util/src/entities/UserGroup.ts +++ b/src/util/entities/UserGroup.ts
@@ -1,7 +1,6 @@ -import { Column, Entity, JoinColumn, ManyToOne, RelationId } from "typeorm"; +import { Column, Entity, JoinColumn, ManyToOne } from "typeorm"; import { BaseClass } from "./BaseClass"; -import { Guild } from "./Guild"; import { User } from "./User"; @Entity("groups") @@ -11,11 +10,11 @@ export class UserGroup extends BaseClass { @Column() hoist: boolean; - + @JoinColumn({ name: "controller", referencedColumnName: "id" }) @ManyToOne(() => User) controller?: User; - + @Column() mentionable_by?: string; @@ -27,11 +26,10 @@ export class UserGroup extends BaseClass { @Column({ nullable: true }) icon: string; - + @Column({ nullable: true }) parent?: string; - - @Column({ type: "simple-array", nullable: true}) - associciations: string[]; + @Column({ type: "simple-array", nullable: true }) + associciations: string[]; } diff --git a/src/util/entities/UserSettings.ts b/src/util/entities/UserSettings.ts new file mode 100644
index 00000000..9fa18a4d --- /dev/null +++ b/src/util/entities/UserSettings.ts
@@ -0,0 +1,119 @@ +import { Column, Entity } from "typeorm"; +import { BaseClassWithoutId, PrimaryIdColumn } from "."; + +@Entity("user_settings") +export class UserSettings extends BaseClassWithoutId { + @PrimaryIdColumn() + id: string; + + @Column({ nullable: true }) + afk_timeout: number = 3600; + + @Column({ nullable: true }) + allow_accessibility_detection: boolean = true; + + @Column({ nullable: true }) + animate_emoji: boolean = true; + + @Column({ nullable: true }) + animate_stickers: number = 0; + + @Column({ nullable: true }) + contact_sync_enabled: boolean = false; + + @Column({ nullable: true }) + convert_emoticons: boolean = false; + + @Column({ nullable: true, type: "simple-json" }) + custom_status: CustomStatus | null = null; + + @Column({ nullable: true }) + default_guilds_restricted: boolean = false; + + @Column({ nullable: true }) + detect_platform_accounts: boolean = false; + + @Column({ nullable: true }) + developer_mode: boolean = true; + + @Column({ nullable: true }) + disable_games_tab: boolean = true; + + @Column({ nullable: true }) + enable_tts_command: boolean = false; + + @Column({ nullable: true }) + explicit_content_filter: number = 0; + + @Column({ nullable: true, type: "simple-json" }) + friend_source_flags: FriendSourceFlags = { all: true }; + + @Column({ nullable: true }) + gateway_connected: boolean = false; + + @Column({ nullable: true }) + gif_auto_play: boolean = false; + + @Column({ nullable: true, type: "simple-json" }) + guild_folders: GuildFolder[] = []; // every top guild is displayed as a "folder" + + @Column({ nullable: true, type: "simple-json" }) + guild_positions: string[] = []; // guild ids ordered by position + + @Column({ nullable: true }) + inline_attachment_media: boolean = true; + + @Column({ nullable: true }) + inline_embed_media: boolean = true; + + @Column({ nullable: true }) + locale: string = "en-US"; // en_US + + @Column({ nullable: true }) + message_display_compact: boolean = false; + + @Column({ nullable: true }) + native_phone_integration_enabled: boolean = true; + + @Column({ nullable: true }) + render_embeds: boolean = true; + + @Column({ nullable: true }) + render_reactions: boolean = true; + + @Column({ nullable: true, type: "simple-json" }) + restricted_guilds: string[] = []; + + @Column({ nullable: true }) + show_current_game: boolean = true; + + @Column({ nullable: true }) + status: "online" | "offline" | "dnd" | "idle" | "invisible" = "online"; + + @Column({ nullable: true }) + stream_notifications_enabled: boolean = false; + + @Column({ nullable: true }) + theme: "dark" | "white" = "dark"; // dark + + @Column({ nullable: true }) + timezone_offset: number = 0; // e.g -60 +} + +interface CustomStatus { + emoji_id?: string; + emoji_name?: string; + expires_at?: number; + text?: string; +} + +interface GuildFolder { + color: number; + guild_ids: string[]; + id: number; + name: string; +} + +interface FriendSourceFlags { + all: boolean; +} diff --git a/util/src/entities/VoiceState.ts b/src/util/entities/VoiceState.ts
index 75748a01..baf2c687 100644 --- a/util/src/entities/VoiceState.ts +++ b/src/util/entities/VoiceState.ts
@@ -2,8 +2,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") @@ -14,7 +14,7 @@ export class VoiceState extends BaseClass { @JoinColumn({ name: "guild_id" }) @ManyToOne(() => Guild, { - onDelete: "CASCADE", + onDelete: "CASCADE" }) guild?: Guild; @@ -24,7 +24,7 @@ export class VoiceState extends BaseClass { @JoinColumn({ name: "channel_id" }) @ManyToOne(() => Channel, { - onDelete: "CASCADE", + onDelete: "CASCADE" }) channel: Channel; @@ -34,7 +34,7 @@ export class VoiceState extends BaseClass { @JoinColumn({ name: "user_id" }) @ManyToOne(() => User, { - onDelete: "CASCADE", + onDelete: "CASCADE" }) user: User; diff --git a/util/src/entities/Webhook.ts b/src/util/entities/Webhook.ts
index 89538417..3d94ddb6 100644 --- a/util/src/entities/Webhook.ts +++ b/src/util/entities/Webhook.ts
@@ -7,7 +7,7 @@ import { User } from "./User"; export enum WebhookType { Incoming = 1, - ChannelFollower = 2, + ChannelFollower = 2 } @Entity("webhooks") @@ -30,7 +30,7 @@ export class Webhook extends BaseClass { @JoinColumn({ name: "guild_id" }) @ManyToOne(() => Guild, { - onDelete: "CASCADE", + onDelete: "CASCADE" }) guild: Guild; @@ -40,7 +40,7 @@ export class Webhook extends BaseClass { @JoinColumn({ name: "channel_id" }) @ManyToOne(() => Channel, { - onDelete: "CASCADE", + onDelete: "CASCADE" }) channel: Channel; @@ -50,7 +50,7 @@ export class Webhook extends BaseClass { @JoinColumn({ name: "application_id" }) @ManyToOne(() => Application, { - onDelete: "CASCADE", + onDelete: "CASCADE" }) application: Application; @@ -60,7 +60,7 @@ export class Webhook extends BaseClass { @JoinColumn({ name: "user_id" }) @ManyToOne(() => User, { - onDelete: "CASCADE", + onDelete: "CASCADE" }) user: User; @@ -70,7 +70,7 @@ export class Webhook extends BaseClass { @JoinColumn({ name: "source_guild_id" }) @ManyToOne(() => Guild, { - onDelete: "CASCADE", + onDelete: "CASCADE" }) source_guild: Guild; } diff --git a/util/src/entities/index.ts b/src/util/entities/index.ts
index f023d5a6..2b91c2ba 100644 --- a/util/src/entities/index.ts +++ b/src/util/entities/index.ts
@@ -1,10 +1,12 @@ export * from "./Application"; export * from "./Attachment"; export * from "./AuditLog"; +export * from "./BackupCodes"; export * from "./Ban"; export * from "./BaseClass"; export * from "./Categories"; export * from "./Channel"; +export * from "./ClientRelease"; export * from "./Config"; export * from "./ConnectedAccount"; export * from "./Emoji"; @@ -13,6 +15,7 @@ export * from "./Invite"; export * from "./Member"; export * from "./Message"; export * from "./Migration"; +export * from "./Note"; export * from "./RateLimit"; export * from "./ReadState"; export * from "./Recipient"; @@ -25,6 +28,6 @@ export * from "./Team"; export * from "./TeamMember"; export * from "./Template"; export * from "./User"; +export * from "./UserSettings"; export * from "./VoiceState"; export * from "./Webhook"; -export * from "./ClientRelease"; \ No newline at end of file diff --git a/util/src/index.ts b/src/util/index.ts
index ae0f7e54..b26ed278 100644 --- a/util/src/index.ts +++ b/src/util/index.ts
@@ -1,6 +1,9 @@ import "reflect-metadata"; -export * from "./util/index"; -export * from "./interfaces/index"; -export * from "./entities/index"; +export * from "./config/index"; export * from "./dtos/index"; +export * from "./entities/index"; +export * from "./interfaces/index"; +export * from "./schemas"; +export * from "./util/index"; +export * from "./util/MFA"; diff --git a/util/src/interfaces/Activity.ts b/src/util/interfaces/Activity.ts
index 43984afd..3b36b4a6 100644 --- a/util/src/interfaces/Activity.ts +++ b/src/util/interfaces/Activity.ts
@@ -40,5 +40,5 @@ export enum ActivityType { STREAMING = 1, LISTENING = 2, CUSTOM = 4, - COMPETING = 5, + COMPETING = 5 } diff --git a/util/src/interfaces/Event.ts b/src/util/interfaces/Event.ts
index 416082ed..f97f4615 100644 --- a/util/src/interfaces/Event.ts +++ b/src/util/interfaces/Event.ts
@@ -1,19 +1,19 @@ -import { PublicUser, User, UserSettings } from "../entities/User"; +import { Activity, Status } from "."; +import { Sticker, UserSettings } from ".."; +import { ApplicationCommand } from "../entities/Application"; import { Channel } from "../entities/Channel"; -import { Guild } from "../entities/Guild"; -import { Member, PublicMember, UserGuildSettings } from "../entities/Member"; +import { ConnectedAccount } from "../entities/ConnectedAccount"; import { Emoji } from "../entities/Emoji"; -import { Role } from "../entities/Role"; +import { Guild } from "../entities/Guild"; import { Invite } from "../entities/Invite"; +import { PublicMember, UserGuildSettings } from "../entities/Member"; import { Message, PartialEmoji } from "../entities/Message"; +import { RelationshipType } from "../entities/Relationship"; +import { Role } from "../entities/Role"; +import { PublicUser, User } from "../entities/User"; import { VoiceState } from "../entities/VoiceState"; -import { ApplicationCommand } from "../entities/Application"; import { Interaction } from "./Interaction"; -import { ConnectedAccount } from "../entities/ConnectedAccount"; -import { Relationship, RelationshipType } from "../entities/Relationship"; import { Presence } from "./Presence"; -import { Sticker } from ".."; -import { Activity, Status } from "."; export interface Event { guild_id?: string; @@ -93,7 +93,7 @@ export interface ReadyEventData { }; application?: { id: string; - flags: string; + flags: number; }; merged_members?: PublicMember[][]; // probably all users who the user is in contact with @@ -580,7 +580,7 @@ export enum EVENTEnum { ApplicationCommandCreate = "APPLICATION_COMMAND_CREATE", ApplicationCommandUpdate = "APPLICATION_COMMAND_UPDATE", ApplicationCommandDelete = "APPLICATION_COMMAND_DELETE", - SessionsReplace = "SESSIONS_REPLACE", + SessionsReplace = "SESSIONS_REPLACE" } export type EVENT = diff --git a/util/src/interfaces/Interaction.ts b/src/util/interfaces/Interaction.ts
index 5d3aae24..c53a1ed4 100644 --- a/util/src/interfaces/Interaction.ts +++ b/src/util/interfaces/Interaction.ts
@@ -14,7 +14,7 @@ export interface Interaction { export enum InteractionType { SelfCommand = 0, Ping = 1, - ApplicationCommand = 2, + ApplicationCommand = 2 } export enum InteractionResponseType { @@ -23,7 +23,7 @@ export enum InteractionResponseType { Acknowledge = 2, ChannelMessage = 3, ChannelMessageWithSource = 4, - AcknowledgeWithSource = 5, + AcknowledgeWithSource = 5 } export interface InteractionApplicationCommandCallbackData { diff --git a/util/src/interfaces/Presence.ts b/src/util/interfaces/Presence.ts
index 7663891a..5b66139e 100644 --- a/util/src/interfaces/Presence.ts +++ b/src/util/interfaces/Presence.ts
@@ -1,6 +1,6 @@ -import { ClientStatus, Status } from "./Status"; -import { Activity } from "./Activity"; import { PublicUser } from "../entities/User"; +import { Activity } from "./Activity"; +import { ClientStatus, Status } from "./Status"; export interface Presence { user: PublicUser; diff --git a/util/src/interfaces/Status.ts b/src/util/interfaces/Status.ts
index 5d2e1bba..5d2e1bba 100644 --- a/util/src/interfaces/Status.ts +++ b/src/util/interfaces/Status.ts
diff --git a/util/src/interfaces/index.ts b/src/util/interfaces/index.ts
index ab7fa429..a074030e 100644 --- a/util/src/interfaces/index.ts +++ b/src/util/interfaces/index.ts
@@ -1,5 +1,5 @@ export * from "./Activity"; -export * from "./Presence"; -export * from "./Interaction"; export * from "./Event"; +export * from "./Interaction"; +export * from "./Presence"; export * from "./Status"; diff --git a/src/util/migrations/mariadb/1659901151025-initial.ts b/src/util/migrations/mariadb/1659901151025-initial.ts new file mode 100644
index 00000000..1e1f64db --- /dev/null +++ b/src/util/migrations/mariadb/1659901151025-initial.ts
@@ -0,0 +1,1218 @@ +import { MigrationInterface, QueryRunner } from "typeorm"; + +export class initial1659901151025 implements MigrationInterface { + name = "initial1659901151025"; + + public async up(queryRunner: QueryRunner): Promise<void> { + await queryRunner.query(` + CREATE TABLE \`config\` ( + \`key\` varchar(255) NOT NULL, + \`value\` text NULL, + PRIMARY KEY (\`key\`) + ) ENGINE = InnoDB + `); + await queryRunner.query(` + CREATE TABLE \`relationships\` ( + \`id\` varchar(255) NOT NULL, + \`from_id\` varchar(255) NOT NULL, + \`to_id\` varchar(255) NOT NULL, + \`nickname\` varchar(255) NULL, + \`type\` int NOT NULL, + UNIQUE INDEX \`IDX_a0b2ff0a598df0b0d055934a17\` (\`from_id\`, \`to_id\`), + PRIMARY KEY (\`id\`) + ) ENGINE = InnoDB + `); + await queryRunner.query(` + CREATE TABLE \`connected_accounts\` ( + \`id\` varchar(255) NOT NULL, + \`user_id\` varchar(255) NULL, + \`access_token\` varchar(255) NOT NULL, + \`friend_sync\` tinyint NOT NULL, + \`name\` varchar(255) NOT NULL, + \`revoked\` tinyint NOT NULL, + \`show_activity\` tinyint NOT NULL, + \`type\` varchar(255) NOT NULL, + \`verified\` tinyint NOT NULL, + \`visibility\` int NOT NULL, + PRIMARY KEY (\`id\`) + ) ENGINE = InnoDB + `); + await queryRunner.query(` + CREATE TABLE \`users\` ( + \`id\` varchar(255) NOT NULL, + \`username\` varchar(255) NOT NULL, + \`discriminator\` varchar(255) NOT NULL, + \`avatar\` varchar(255) NULL, + \`accent_color\` int NULL, + \`banner\` varchar(255) NULL, + \`phone\` varchar(255) NULL, + \`desktop\` tinyint NOT NULL, + \`mobile\` tinyint NOT NULL, + \`premium\` tinyint NOT NULL, + \`premium_type\` int NOT NULL, + \`bot\` tinyint NOT NULL, + \`bio\` varchar(255) NOT NULL, + \`system\` tinyint NOT NULL, + \`nsfw_allowed\` tinyint NOT NULL, + \`mfa_enabled\` tinyint NOT NULL, + \`totp_secret\` varchar(255) NULL, + \`totp_last_ticket\` varchar(255) NULL, + \`created_at\` datetime NOT NULL, + \`premium_since\` datetime NULL, + \`verified\` tinyint NOT NULL, + \`disabled\` tinyint NOT NULL, + \`deleted\` tinyint NOT NULL, + \`email\` varchar(255) NULL, + \`flags\` varchar(255) NOT NULL, + \`public_flags\` int NOT NULL, + \`rights\` bigint NOT NULL, + \`data\` text NOT NULL, + \`fingerprints\` text NOT NULL, + \`settings\` text NOT NULL, + \`extended_settings\` text NOT NULL, + \`notes\` text NOT NULL, + PRIMARY KEY (\`id\`) + ) ENGINE = InnoDB + `); + await queryRunner.query(` + CREATE TABLE \`backup_codes\` ( + \`id\` varchar(255) NOT NULL, + \`code\` varchar(255) NOT NULL, + \`consumed\` tinyint NOT NULL, + \`expired\` tinyint NOT NULL, + \`user_id\` varchar(255) NULL, + PRIMARY KEY (\`id\`) + ) ENGINE = InnoDB + `); + await queryRunner.query(` + CREATE TABLE \`bans\` ( + \`id\` varchar(255) NOT NULL, + \`user_id\` varchar(255) NULL, + \`guild_id\` varchar(255) NULL, + \`executor_id\` varchar(255) NULL, + \`ip\` varchar(255) NOT NULL, + \`reason\` varchar(255) NULL, + PRIMARY KEY (\`id\`) + ) ENGINE = InnoDB + `); + await queryRunner.query(` + CREATE TABLE \`recipients\` ( + \`id\` varchar(255) NOT NULL, + \`channel_id\` varchar(255) NOT NULL, + \`user_id\` varchar(255) NOT NULL, + \`closed\` tinyint NOT NULL DEFAULT 0, + PRIMARY KEY (\`id\`) + ) ENGINE = InnoDB + `); + await queryRunner.query(` + CREATE TABLE \`roles\` ( + \`id\` varchar(255) NOT NULL, + \`guild_id\` varchar(255) NULL, + \`color\` int NOT NULL, + \`hoist\` tinyint NOT NULL, + \`managed\` tinyint NOT NULL, + \`mentionable\` tinyint NOT NULL, + \`name\` varchar(255) NOT NULL, + \`permissions\` varchar(255) NOT NULL, + \`position\` int NOT NULL, + \`icon\` varchar(255) NULL, + \`unicode_emoji\` varchar(255) NULL, + \`tags\` text NULL, + PRIMARY KEY (\`id\`) + ) ENGINE = InnoDB + `); + await queryRunner.query(` + CREATE TABLE \`members\` ( + \`index\` int NOT NULL AUTO_INCREMENT, + \`id\` varchar(255) NOT NULL, + \`guild_id\` varchar(255) NOT NULL, + \`nick\` varchar(255) NULL, + \`joined_at\` datetime NOT NULL, + \`premium_since\` bigint NULL, + \`deaf\` tinyint NOT NULL, + \`mute\` tinyint NOT NULL, + \`pending\` tinyint NOT NULL, + \`settings\` text NOT NULL, + \`last_message_id\` varchar(255) NULL, + \`joined_by\` varchar(255) NULL, + UNIQUE INDEX \`IDX_bb2bf9386ac443afbbbf9f12d3\` (\`id\`, \`guild_id\`), + PRIMARY KEY (\`index\`) + ) ENGINE = InnoDB + `); + await queryRunner.query(` + CREATE TABLE \`webhooks\` ( + \`id\` varchar(255) NOT NULL, + \`type\` int NOT NULL, + \`name\` varchar(255) NULL, + \`avatar\` varchar(255) NULL, + \`token\` varchar(255) NULL, + \`guild_id\` varchar(255) NULL, + \`channel_id\` varchar(255) NULL, + \`application_id\` varchar(255) NULL, + \`user_id\` varchar(255) NULL, + \`source_guild_id\` varchar(255) NULL, + PRIMARY KEY (\`id\`) + ) ENGINE = InnoDB + `); + await queryRunner.query(` + CREATE TABLE \`stickers\` ( + \`id\` varchar(255) NOT NULL, + \`name\` varchar(255) NOT NULL, + \`description\` varchar(255) NULL, + \`available\` tinyint NULL, + \`tags\` varchar(255) NULL, + \`pack_id\` varchar(255) NULL, + \`guild_id\` varchar(255) NULL, + \`user_id\` varchar(255) NULL, + \`type\` int NOT NULL, + \`format_type\` int NOT NULL, + PRIMARY KEY (\`id\`) + ) ENGINE = InnoDB + `); + await queryRunner.query(` + CREATE TABLE \`attachments\` ( + \`id\` varchar(255) NOT NULL, + \`filename\` varchar(255) NOT NULL, + \`size\` int NOT NULL, + \`url\` varchar(255) NOT NULL, + \`proxy_url\` varchar(255) NOT NULL, + \`height\` int NULL, + \`width\` int NULL, + \`content_type\` varchar(255) NULL, + \`message_id\` varchar(255) NULL, + PRIMARY KEY (\`id\`) + ) ENGINE = InnoDB + `); + await queryRunner.query(` + CREATE TABLE \`messages\` ( + \`id\` varchar(255) NOT NULL, + \`channel_id\` varchar(255) NULL, + \`guild_id\` varchar(255) NULL, + \`author_id\` varchar(255) NULL, + \`member_id\` varchar(255) NULL, + \`webhook_id\` varchar(255) NULL, + \`application_id\` varchar(255) NULL, + \`content\` varchar(255) NULL, + \`timestamp\` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6), + \`edited_timestamp\` datetime NULL, + \`tts\` tinyint NULL, + \`mention_everyone\` tinyint NULL, + \`embeds\` text NOT NULL, + \`reactions\` text NOT NULL, + \`nonce\` text NULL, + \`pinned\` tinyint NULL, + \`type\` int NOT NULL, + \`activity\` text NULL, + \`flags\` varchar(255) NULL, + \`message_reference\` text NULL, + \`interaction\` text NULL, + \`components\` text NULL, + \`message_reference_id\` varchar(255) NULL, + INDEX \`IDX_86b9109b155eb70c0a2ca3b4b6\` (\`channel_id\`), + INDEX \`IDX_05535bc695e9f7ee104616459d\` (\`author_id\`), + UNIQUE INDEX \`IDX_3ed7a60fb7dbe04e1ba9332a8b\` (\`channel_id\`, \`id\`), + PRIMARY KEY (\`id\`) + ) ENGINE = InnoDB + `); + await queryRunner.query(` + CREATE TABLE \`read_states\` ( + \`id\` varchar(255) NOT NULL, + \`channel_id\` varchar(255) NOT NULL, + \`user_id\` varchar(255) NOT NULL, + \`last_message_id\` varchar(255) NULL, + \`public_ack\` varchar(255) NULL, + \`notifications_cursor\` varchar(255) NULL, + \`last_pin_timestamp\` datetime NULL, + \`mention_count\` int NULL, + UNIQUE INDEX \`IDX_0abf8b443321bd3cf7f81ee17a\` (\`channel_id\`, \`user_id\`), + PRIMARY KEY (\`id\`) + ) ENGINE = InnoDB + `); + await queryRunner.query(` + CREATE TABLE \`invites\` ( + \`code\` varchar(255) NOT NULL, + \`temporary\` tinyint NOT NULL, + \`uses\` int NOT NULL, + \`max_uses\` int NOT NULL, + \`max_age\` int NOT NULL, + \`created_at\` datetime NOT NULL, + \`expires_at\` datetime NOT NULL, + \`guild_id\` varchar(255) NULL, + \`channel_id\` varchar(255) NULL, + \`inviter_id\` varchar(255) NULL, + \`target_user_id\` varchar(255) NULL, + \`target_user_type\` int NULL, + \`vanity_url\` tinyint NULL, + PRIMARY KEY (\`code\`) + ) ENGINE = InnoDB + `); + await queryRunner.query(` + CREATE TABLE \`voice_states\` ( + \`id\` varchar(255) NOT NULL, + \`guild_id\` varchar(255) NULL, + \`channel_id\` varchar(255) NULL, + \`user_id\` varchar(255) NULL, + \`session_id\` varchar(255) NOT NULL, + \`token\` varchar(255) NULL, + \`deaf\` tinyint NOT NULL, + \`mute\` tinyint NOT NULL, + \`self_deaf\` tinyint NOT NULL, + \`self_mute\` tinyint NOT NULL, + \`self_stream\` tinyint NULL, + \`self_video\` tinyint NOT NULL, + \`suppress\` tinyint NOT NULL, + \`request_to_speak_timestamp\` datetime NULL, + PRIMARY KEY (\`id\`) + ) ENGINE = InnoDB + `); + await queryRunner.query(` + CREATE TABLE \`channels\` ( + \`id\` varchar(255) NOT NULL, + \`created_at\` datetime NOT NULL, + \`name\` varchar(255) NULL, + \`icon\` text NULL, + \`type\` int NOT NULL, + \`last_message_id\` varchar(255) NULL, + \`guild_id\` varchar(255) NULL, + \`parent_id\` varchar(255) NULL, + \`owner_id\` varchar(255) NULL, + \`last_pin_timestamp\` int NULL, + \`default_auto_archive_duration\` int NULL, + \`position\` int NULL, + \`permission_overwrites\` text NULL, + \`video_quality_mode\` int NULL, + \`bitrate\` int NULL, + \`user_limit\` int NULL, + \`nsfw\` tinyint NULL, + \`rate_limit_per_user\` int NULL, + \`topic\` varchar(255) NULL, + \`retention_policy_id\` varchar(255) NULL, + PRIMARY KEY (\`id\`) + ) ENGINE = InnoDB + `); + await queryRunner.query(` + CREATE TABLE \`emojis\` ( + \`id\` varchar(255) NOT NULL, + \`animated\` tinyint NOT NULL, + \`available\` tinyint NOT NULL, + \`guild_id\` varchar(255) NOT NULL, + \`user_id\` varchar(255) NULL, + \`managed\` tinyint NOT NULL, + \`name\` varchar(255) NOT NULL, + \`require_colons\` tinyint NOT NULL, + \`roles\` text NOT NULL, + \`groups\` text NULL, + PRIMARY KEY (\`id\`) + ) ENGINE = InnoDB + `); + await queryRunner.query(` + CREATE TABLE \`templates\` ( + \`id\` varchar(255) NOT NULL, + \`code\` varchar(255) NOT NULL, + \`name\` varchar(255) NOT NULL, + \`description\` varchar(255) NULL, + \`usage_count\` int NULL, + \`creator_id\` varchar(255) NULL, + \`created_at\` datetime NOT NULL, + \`updated_at\` datetime NOT NULL, + \`source_guild_id\` varchar(255) NULL, + \`serialized_source_guild\` text NOT NULL, + UNIQUE INDEX \`IDX_be38737bf339baf63b1daeffb5\` (\`code\`), + PRIMARY KEY (\`id\`) + ) ENGINE = InnoDB + `); + await queryRunner.query(` + CREATE TABLE \`guilds\` ( + \`id\` varchar(255) NOT NULL, + \`afk_channel_id\` varchar(255) NULL, + \`afk_timeout\` int NULL, + \`banner\` varchar(255) NULL, + \`default_message_notifications\` int NULL, + \`description\` varchar(255) NULL, + \`discovery_splash\` varchar(255) NULL, + \`explicit_content_filter\` int NULL, + \`features\` text NOT NULL, + \`primary_category_id\` int NULL, + \`icon\` varchar(255) NULL, + \`large\` tinyint NULL, + \`max_members\` int NULL, + \`max_presences\` int NULL, + \`max_video_channel_users\` int NULL, + \`member_count\` int NULL, + \`presence_count\` int NULL, + \`template_id\` varchar(255) NULL, + \`mfa_level\` int NULL, + \`name\` varchar(255) NOT NULL, + \`owner_id\` varchar(255) NULL, + \`preferred_locale\` varchar(255) NULL, + \`premium_subscription_count\` int NULL, + \`premium_tier\` int NULL, + \`public_updates_channel_id\` varchar(255) NULL, + \`rules_channel_id\` varchar(255) NULL, + \`region\` varchar(255) NULL, + \`splash\` varchar(255) NULL, + \`system_channel_id\` varchar(255) NULL, + \`system_channel_flags\` int NULL, + \`unavailable\` tinyint NULL, + \`verification_level\` int NULL, + \`welcome_screen\` text NOT NULL, + \`widget_channel_id\` varchar(255) NULL, + \`widget_enabled\` tinyint NULL, + \`nsfw_level\` int NULL, + \`nsfw\` tinyint NULL, + \`parent\` varchar(255) NULL, + PRIMARY KEY (\`id\`) + ) ENGINE = InnoDB + `); + await queryRunner.query(` + CREATE TABLE \`team_members\` ( + \`id\` varchar(255) NOT NULL, + \`membership_state\` int NOT NULL, + \`permissions\` text NOT NULL, + \`team_id\` varchar(255) NULL, + \`user_id\` varchar(255) NULL, + PRIMARY KEY (\`id\`) + ) ENGINE = InnoDB + `); + await queryRunner.query(` + CREATE TABLE \`teams\` ( + \`id\` varchar(255) NOT NULL, + \`icon\` varchar(255) NULL, + \`name\` varchar(255) NOT NULL, + \`owner_user_id\` varchar(255) NULL, + PRIMARY KEY (\`id\`) + ) ENGINE = InnoDB + `); + await queryRunner.query(` + CREATE TABLE \`applications\` ( + \`id\` varchar(255) NOT NULL, + \`name\` varchar(255) NOT NULL, + \`icon\` varchar(255) NULL, + \`description\` varchar(255) NOT NULL, + \`rpc_origins\` text NULL, + \`bot_public\` tinyint NOT NULL, + \`bot_require_code_grant\` tinyint NOT NULL, + \`terms_of_service_url\` varchar(255) NULL, + \`privacy_policy_url\` varchar(255) NULL, + \`summary\` varchar(255) NULL, + \`verify_key\` varchar(255) NOT NULL, + \`primary_sku_id\` varchar(255) NULL, + \`slug\` varchar(255) NULL, + \`cover_image\` varchar(255) NULL, + \`flags\` varchar(255) NOT NULL, + \`owner_id\` varchar(255) NULL, + \`team_id\` varchar(255) NULL, + \`guild_id\` varchar(255) NULL, + PRIMARY KEY (\`id\`) + ) ENGINE = InnoDB + `); + await queryRunner.query(` + CREATE TABLE \`audit_logs\` ( + \`id\` varchar(255) NOT NULL, + \`user_id\` varchar(255) NULL, + \`action_type\` int NOT NULL, + \`options\` text NULL, + \`changes\` text NOT NULL, + \`reason\` varchar(255) NULL, + \`target_id\` varchar(255) NULL, + PRIMARY KEY (\`id\`) + ) ENGINE = InnoDB + `); + await queryRunner.query(` + CREATE TABLE \`categories\` ( + \`id\` int NOT NULL, + \`name\` varchar(255) NULL, + \`localizations\` text NOT NULL, + \`is_primary\` tinyint NULL, + PRIMARY KEY (\`id\`) + ) ENGINE = InnoDB + `); + await queryRunner.query(` + CREATE TABLE \`rate_limits\` ( + \`id\` varchar(255) NOT NULL, + \`executor_id\` varchar(255) NOT NULL, + \`hits\` int NOT NULL, + \`blocked\` tinyint NOT NULL, + \`expires_at\` datetime NOT NULL, + PRIMARY KEY (\`id\`) + ) ENGINE = InnoDB + `); + await queryRunner.query(` + CREATE TABLE \`sessions\` ( + \`id\` varchar(255) NOT NULL, + \`user_id\` varchar(255) NULL, + \`session_id\` varchar(255) NOT NULL, + \`activities\` text NULL, + \`client_info\` text NOT NULL, + \`status\` varchar(255) NOT NULL, + PRIMARY KEY (\`id\`) + ) ENGINE = InnoDB + `); + await queryRunner.query(` + CREATE TABLE \`sticker_packs\` ( + \`id\` varchar(255) NOT NULL, + \`name\` varchar(255) NOT NULL, + \`description\` varchar(255) NULL, + \`banner_asset_id\` varchar(255) NULL, + \`cover_sticker_id\` varchar(255) NULL, + \`coverStickerId\` varchar(255) NULL, + PRIMARY KEY (\`id\`) + ) ENGINE = InnoDB + `); + await queryRunner.query(` + CREATE TABLE \`client_release\` ( + \`id\` varchar(255) NOT NULL, + \`name\` varchar(255) NOT NULL, + \`pub_date\` varchar(255) NOT NULL, + \`url\` varchar(255) NOT NULL, + \`deb_url\` varchar(255) NOT NULL, + \`osx_url\` varchar(255) NOT NULL, + \`win_url\` varchar(255) NOT NULL, + \`notes\` varchar(255) NULL, + PRIMARY KEY (\`id\`) + ) ENGINE = InnoDB + `); + await queryRunner.query(` + CREATE TABLE \`notes\` ( + \`id\` varchar(255) NOT NULL, + \`content\` varchar(255) NOT NULL, + \`owner_id\` varchar(255) NULL, + \`target_id\` varchar(255) NULL, + UNIQUE INDEX \`IDX_74e6689b9568cc965b8bfc9150\` (\`owner_id\`, \`target_id\`), + PRIMARY KEY (\`id\`) + ) ENGINE = InnoDB + `); + await queryRunner.query(` + CREATE TABLE \`member_roles\` ( + \`index\` int NOT NULL, + \`role_id\` varchar(255) NOT NULL, + INDEX \`IDX_5d7ddc8a5f9c167f548625e772\` (\`index\`), + INDEX \`IDX_e9080e7a7997a0170026d5139c\` (\`role_id\`), + PRIMARY KEY (\`index\`, \`role_id\`) + ) ENGINE = InnoDB + `); + await queryRunner.query(` + CREATE TABLE \`message_user_mentions\` ( + \`messagesId\` varchar(255) NOT NULL, + \`usersId\` varchar(255) NOT NULL, + INDEX \`IDX_a343387fc560ef378760681c23\` (\`messagesId\`), + INDEX \`IDX_b831eb18ceebd28976239b1e2f\` (\`usersId\`), + PRIMARY KEY (\`messagesId\`, \`usersId\`) + ) ENGINE = InnoDB + `); + await queryRunner.query(` + CREATE TABLE \`message_role_mentions\` ( + \`messagesId\` varchar(255) NOT NULL, + \`rolesId\` varchar(255) NOT NULL, + INDEX \`IDX_a8242cf535337a490b0feaea0b\` (\`messagesId\`), + INDEX \`IDX_29d63eb1a458200851bc37d074\` (\`rolesId\`), + PRIMARY KEY (\`messagesId\`, \`rolesId\`) + ) ENGINE = InnoDB + `); + await queryRunner.query(` + CREATE TABLE \`message_channel_mentions\` ( + \`messagesId\` varchar(255) NOT NULL, + \`channelsId\` varchar(255) NOT NULL, + INDEX \`IDX_2a27102ecd1d81b4582a436092\` (\`messagesId\`), + INDEX \`IDX_bdb8c09e1464cabf62105bf4b9\` (\`channelsId\`), + PRIMARY KEY (\`messagesId\`, \`channelsId\`) + ) ENGINE = InnoDB + `); + await queryRunner.query(` + CREATE TABLE \`message_stickers\` ( + \`messagesId\` varchar(255) NOT NULL, + \`stickersId\` varchar(255) NOT NULL, + INDEX \`IDX_40bb6f23e7cc133292e92829d2\` (\`messagesId\`), + INDEX \`IDX_e22a70819d07659c7a71c112a1\` (\`stickersId\`), + PRIMARY KEY (\`messagesId\`, \`stickersId\`) + ) ENGINE = InnoDB + `); + await queryRunner.query(` + ALTER TABLE \`relationships\` + ADD CONSTRAINT \`FK_9af4194bab1250b1c584ae4f1d7\` FOREIGN KEY (\`from_id\`) REFERENCES \`users\`(\`id\`) ON DELETE CASCADE ON UPDATE NO ACTION + `); + await queryRunner.query(` + ALTER TABLE \`relationships\` + ADD CONSTRAINT \`FK_9c7f6b98a9843b76dce1b0c878b\` FOREIGN KEY (\`to_id\`) REFERENCES \`users\`(\`id\`) ON DELETE CASCADE ON UPDATE NO ACTION + `); + await queryRunner.query(` + ALTER TABLE \`connected_accounts\` + ADD CONSTRAINT \`FK_f47244225a6a1eac04a3463dd90\` FOREIGN KEY (\`user_id\`) REFERENCES \`users\`(\`id\`) ON DELETE CASCADE ON UPDATE NO ACTION + `); + await queryRunner.query(` + ALTER TABLE \`backup_codes\` + ADD CONSTRAINT \`FK_70066ea80d2f4b871beda32633b\` FOREIGN KEY (\`user_id\`) REFERENCES \`users\`(\`id\`) ON DELETE CASCADE ON UPDATE NO ACTION + `); + await queryRunner.query(` + ALTER TABLE \`bans\` + ADD CONSTRAINT \`FK_5999e8e449f80a236ff72023559\` FOREIGN KEY (\`user_id\`) REFERENCES \`users\`(\`id\`) ON DELETE CASCADE ON UPDATE NO ACTION + `); + await queryRunner.query(` + ALTER TABLE \`bans\` + ADD CONSTRAINT \`FK_9d3ab7dd180ebdd245cdb66ecad\` FOREIGN KEY (\`guild_id\`) REFERENCES \`guilds\`(\`id\`) ON DELETE CASCADE ON UPDATE NO ACTION + `); + await queryRunner.query(` + ALTER TABLE \`bans\` + ADD CONSTRAINT \`FK_07ad88c86d1f290d46748410d58\` FOREIGN KEY (\`executor_id\`) REFERENCES \`users\`(\`id\`) ON DELETE NO ACTION ON UPDATE NO ACTION + `); + await queryRunner.query(` + ALTER TABLE \`recipients\` + ADD CONSTRAINT \`FK_2f18ee1ba667f233ae86c0ea60e\` FOREIGN KEY (\`channel_id\`) REFERENCES \`channels\`(\`id\`) ON DELETE CASCADE ON UPDATE NO ACTION + `); + await queryRunner.query(` + ALTER TABLE \`recipients\` + ADD CONSTRAINT \`FK_6157e8b6ba4e6e3089616481fe2\` FOREIGN KEY (\`user_id\`) REFERENCES \`users\`(\`id\`) ON DELETE CASCADE ON UPDATE NO ACTION + `); + await queryRunner.query(` + ALTER TABLE \`roles\` + ADD CONSTRAINT \`FK_c32c1ab1c4dc7dcb0278c4b1b8b\` FOREIGN KEY (\`guild_id\`) REFERENCES \`guilds\`(\`id\`) ON DELETE CASCADE ON UPDATE NO ACTION + `); + await queryRunner.query(` + ALTER TABLE \`members\` + ADD CONSTRAINT \`FK_28b53062261b996d9c99fa12404\` FOREIGN KEY (\`id\`) REFERENCES \`users\`(\`id\`) ON DELETE CASCADE ON UPDATE NO ACTION + `); + await queryRunner.query(` + ALTER TABLE \`members\` + ADD CONSTRAINT \`FK_16aceddd5b89825b8ed6029ad1c\` FOREIGN KEY (\`guild_id\`) REFERENCES \`guilds\`(\`id\`) ON DELETE CASCADE ON UPDATE NO ACTION + `); + await queryRunner.query(` + ALTER TABLE \`webhooks\` + ADD CONSTRAINT \`FK_487a7af59d189f744fe394368fc\` FOREIGN KEY (\`guild_id\`) REFERENCES \`guilds\`(\`id\`) ON DELETE CASCADE ON UPDATE NO ACTION + `); + await queryRunner.query(` + ALTER TABLE \`webhooks\` + ADD CONSTRAINT \`FK_df528cf77e82f8032230e7e37d8\` FOREIGN KEY (\`channel_id\`) REFERENCES \`channels\`(\`id\`) ON DELETE CASCADE ON UPDATE NO ACTION + `); + await queryRunner.query(` + ALTER TABLE \`webhooks\` + ADD CONSTRAINT \`FK_c3e5305461931763b56aa905f1c\` FOREIGN KEY (\`application_id\`) REFERENCES \`applications\`(\`id\`) ON DELETE CASCADE ON UPDATE NO ACTION + `); + await queryRunner.query(` + ALTER TABLE \`webhooks\` + ADD CONSTRAINT \`FK_0d523f6f997c86e052c49b1455f\` FOREIGN KEY (\`user_id\`) REFERENCES \`users\`(\`id\`) ON DELETE CASCADE ON UPDATE NO ACTION + `); + await queryRunner.query(` + ALTER TABLE \`webhooks\` + ADD CONSTRAINT \`FK_3a285f4f49c40e0706d3018bc9f\` FOREIGN KEY (\`source_guild_id\`) REFERENCES \`guilds\`(\`id\`) ON DELETE CASCADE ON UPDATE NO ACTION + `); + await queryRunner.query(` + ALTER TABLE \`stickers\` + ADD CONSTRAINT \`FK_e7cfa5cefa6661b3fb8fda8ce69\` FOREIGN KEY (\`pack_id\`) REFERENCES \`sticker_packs\`(\`id\`) ON DELETE CASCADE ON UPDATE NO ACTION + `); + await queryRunner.query(` + ALTER TABLE \`stickers\` + ADD CONSTRAINT \`FK_193d551d852aca5347ef5c9f205\` FOREIGN KEY (\`guild_id\`) REFERENCES \`guilds\`(\`id\`) ON DELETE CASCADE ON UPDATE NO ACTION + `); + await queryRunner.query(` + ALTER TABLE \`stickers\` + ADD CONSTRAINT \`FK_8f4ee73f2bb2325ff980502e158\` FOREIGN KEY (\`user_id\`) REFERENCES \`users\`(\`id\`) ON DELETE CASCADE ON UPDATE NO ACTION + `); + await queryRunner.query(` + ALTER TABLE \`attachments\` + ADD CONSTRAINT \`FK_623e10eec51ada466c5038979e3\` FOREIGN KEY (\`message_id\`) REFERENCES \`messages\`(\`id\`) ON DELETE CASCADE ON UPDATE NO ACTION + `); + await queryRunner.query(` + ALTER TABLE \`messages\` + ADD CONSTRAINT \`FK_86b9109b155eb70c0a2ca3b4b6d\` FOREIGN KEY (\`channel_id\`) REFERENCES \`channels\`(\`id\`) ON DELETE CASCADE ON UPDATE NO ACTION + `); + await queryRunner.query(` + ALTER TABLE \`messages\` + ADD CONSTRAINT \`FK_b193588441b085352a4c0109423\` FOREIGN KEY (\`guild_id\`) REFERENCES \`guilds\`(\`id\`) ON DELETE CASCADE ON UPDATE NO ACTION + `); + await queryRunner.query(` + ALTER TABLE \`messages\` + ADD CONSTRAINT \`FK_05535bc695e9f7ee104616459d3\` FOREIGN KEY (\`author_id\`) REFERENCES \`users\`(\`id\`) ON DELETE CASCADE ON UPDATE NO ACTION + `); + await queryRunner.query(` + ALTER TABLE \`messages\` + ADD CONSTRAINT \`FK_b0525304f2262b7014245351c76\` FOREIGN KEY (\`member_id\`) REFERENCES \`users\`(\`id\`) ON DELETE CASCADE ON UPDATE NO ACTION + `); + await queryRunner.query(` + ALTER TABLE \`messages\` + ADD CONSTRAINT \`FK_f83c04bcf1df4e5c0e7a52ed348\` FOREIGN KEY (\`webhook_id\`) REFERENCES \`webhooks\`(\`id\`) ON DELETE NO ACTION ON UPDATE NO ACTION + `); + await queryRunner.query(` + ALTER TABLE \`messages\` + ADD CONSTRAINT \`FK_5d3ec1cb962de6488637fd779d6\` FOREIGN KEY (\`application_id\`) REFERENCES \`applications\`(\`id\`) ON DELETE NO ACTION ON UPDATE NO ACTION + `); + await queryRunner.query(` + ALTER TABLE \`messages\` + ADD CONSTRAINT \`FK_61a92bb65b302a76d9c1fcd3174\` FOREIGN KEY (\`message_reference_id\`) REFERENCES \`messages\`(\`id\`) ON DELETE NO ACTION ON UPDATE NO ACTION + `); + await queryRunner.query(` + ALTER TABLE \`read_states\` + ADD CONSTRAINT \`FK_40da2fca4e0eaf7a23b5bfc5d34\` FOREIGN KEY (\`channel_id\`) REFERENCES \`channels\`(\`id\`) ON DELETE CASCADE ON UPDATE NO ACTION + `); + await queryRunner.query(` + ALTER TABLE \`read_states\` + ADD CONSTRAINT \`FK_195f92e4dd1254a4e348c043763\` FOREIGN KEY (\`user_id\`) REFERENCES \`users\`(\`id\`) ON DELETE CASCADE ON UPDATE NO ACTION + `); + await queryRunner.query(` + ALTER TABLE \`invites\` + ADD CONSTRAINT \`FK_3f4939aa1461e8af57fea3fb05d\` FOREIGN KEY (\`guild_id\`) REFERENCES \`guilds\`(\`id\`) ON DELETE CASCADE ON UPDATE NO ACTION + `); + await queryRunner.query(` + ALTER TABLE \`invites\` + ADD CONSTRAINT \`FK_6a15b051fe5050aa00a4b9ff0f6\` FOREIGN KEY (\`channel_id\`) REFERENCES \`channels\`(\`id\`) ON DELETE CASCADE ON UPDATE NO ACTION + `); + await queryRunner.query(` + ALTER TABLE \`invites\` + ADD CONSTRAINT \`FK_15c35422032e0b22b4ada95f48f\` FOREIGN KEY (\`inviter_id\`) REFERENCES \`users\`(\`id\`) ON DELETE NO ACTION ON UPDATE NO ACTION + `); + await queryRunner.query(` + ALTER TABLE \`invites\` + ADD CONSTRAINT \`FK_11a0d394f8fc649c19ce5f16b59\` FOREIGN KEY (\`target_user_id\`) REFERENCES \`users\`(\`id\`) ON DELETE CASCADE ON UPDATE NO ACTION + `); + await queryRunner.query(` + ALTER TABLE \`voice_states\` + ADD CONSTRAINT \`FK_03779ef216d4b0358470d9cb748\` FOREIGN KEY (\`guild_id\`) REFERENCES \`guilds\`(\`id\`) ON DELETE CASCADE ON UPDATE NO ACTION + `); + await queryRunner.query(` + ALTER TABLE \`voice_states\` + ADD CONSTRAINT \`FK_9f8d389866b40b6657edd026dd4\` FOREIGN KEY (\`channel_id\`) REFERENCES \`channels\`(\`id\`) ON DELETE CASCADE ON UPDATE NO ACTION + `); + await queryRunner.query(` + ALTER TABLE \`voice_states\` + ADD CONSTRAINT \`FK_5fe1d5f931a67e85039c640001b\` FOREIGN KEY (\`user_id\`) REFERENCES \`users\`(\`id\`) ON DELETE CASCADE ON UPDATE NO ACTION + `); + await queryRunner.query(` + ALTER TABLE \`channels\` + ADD CONSTRAINT \`FK_c253dafe5f3a03ec00cd8fb4581\` FOREIGN KEY (\`guild_id\`) REFERENCES \`guilds\`(\`id\`) ON DELETE CASCADE ON UPDATE NO ACTION + `); + await queryRunner.query(` + ALTER TABLE \`channels\` + ADD CONSTRAINT \`FK_3274522d14af40540b1a883fc80\` FOREIGN KEY (\`parent_id\`) REFERENCES \`channels\`(\`id\`) ON DELETE NO ACTION ON UPDATE NO ACTION + `); + await queryRunner.query(` + ALTER TABLE \`channels\` + ADD CONSTRAINT \`FK_3873ed438575cce703ecff4fc7b\` FOREIGN KEY (\`owner_id\`) REFERENCES \`users\`(\`id\`) ON DELETE NO ACTION ON UPDATE NO ACTION + `); + await queryRunner.query(` + ALTER TABLE \`emojis\` + ADD CONSTRAINT \`FK_4b988e0db89d94cebcf07f598cc\` FOREIGN KEY (\`guild_id\`) REFERENCES \`guilds\`(\`id\`) ON DELETE CASCADE ON UPDATE NO ACTION + `); + await queryRunner.query(` + ALTER TABLE \`emojis\` + ADD CONSTRAINT \`FK_fa7ddd5f9a214e28ce596548421\` FOREIGN KEY (\`user_id\`) REFERENCES \`users\`(\`id\`) ON DELETE NO ACTION ON UPDATE NO ACTION + `); + await queryRunner.query(` + ALTER TABLE \`templates\` + ADD CONSTRAINT \`FK_d7374b7f8f5fbfdececa4fb62e1\` FOREIGN KEY (\`creator_id\`) REFERENCES \`users\`(\`id\`) ON DELETE NO ACTION ON UPDATE NO ACTION + `); + await queryRunner.query(` + ALTER TABLE \`templates\` + ADD CONSTRAINT \`FK_445d00eaaea0e60a017a5ed0c11\` FOREIGN KEY (\`source_guild_id\`) REFERENCES \`guilds\`(\`id\`) ON DELETE NO ACTION ON UPDATE NO ACTION + `); + await queryRunner.query(` + ALTER TABLE \`guilds\` + ADD CONSTRAINT \`FK_f591a66b8019d87b0fe6c12dad6\` FOREIGN KEY (\`afk_channel_id\`) REFERENCES \`channels\`(\`id\`) ON DELETE NO ACTION ON UPDATE NO ACTION + `); + await queryRunner.query(` + ALTER TABLE \`guilds\` + ADD CONSTRAINT \`FK_e2a2f873a64a5cf62526de42325\` FOREIGN KEY (\`template_id\`) REFERENCES \`templates\`(\`id\`) ON DELETE NO ACTION ON UPDATE NO ACTION + `); + await queryRunner.query(` + ALTER TABLE \`guilds\` + ADD CONSTRAINT \`FK_fc1a451727e3643ca572a3bb394\` FOREIGN KEY (\`owner_id\`) REFERENCES \`users\`(\`id\`) ON DELETE NO ACTION ON UPDATE NO ACTION + `); + await queryRunner.query(` + ALTER TABLE \`guilds\` + ADD CONSTRAINT \`FK_8d450b016dc8bec35f36729e4b0\` FOREIGN KEY (\`public_updates_channel_id\`) REFERENCES \`channels\`(\`id\`) ON DELETE NO ACTION ON UPDATE NO ACTION + `); + await queryRunner.query(` + ALTER TABLE \`guilds\` + ADD CONSTRAINT \`FK_95828668aa333460582e0ca6396\` FOREIGN KEY (\`rules_channel_id\`) REFERENCES \`channels\`(\`id\`) ON DELETE NO ACTION ON UPDATE NO ACTION + `); + await queryRunner.query(` + ALTER TABLE \`guilds\` + ADD CONSTRAINT \`FK_cfc3d3ad260f8121c95b31a1fce\` FOREIGN KEY (\`system_channel_id\`) REFERENCES \`channels\`(\`id\`) ON DELETE NO ACTION ON UPDATE NO ACTION + `); + await queryRunner.query(` + ALTER TABLE \`guilds\` + ADD CONSTRAINT \`FK_9d1d665379eefde7876a17afa99\` FOREIGN KEY (\`widget_channel_id\`) REFERENCES \`channels\`(\`id\`) ON DELETE NO ACTION ON UPDATE NO ACTION + `); + await queryRunner.query(` + ALTER TABLE \`team_members\` + ADD CONSTRAINT \`FK_fdad7d5768277e60c40e01cdcea\` FOREIGN KEY (\`team_id\`) REFERENCES \`teams\`(\`id\`) ON DELETE CASCADE ON UPDATE NO ACTION + `); + await queryRunner.query(` + ALTER TABLE \`team_members\` + ADD CONSTRAINT \`FK_c2bf4967c8c2a6b845dadfbf3d4\` FOREIGN KEY (\`user_id\`) REFERENCES \`users\`(\`id\`) ON DELETE CASCADE ON UPDATE NO ACTION + `); + await queryRunner.query(` + ALTER TABLE \`teams\` + ADD CONSTRAINT \`FK_13f00abf7cb6096c43ecaf8c108\` FOREIGN KEY (\`owner_user_id\`) REFERENCES \`users\`(\`id\`) ON DELETE NO ACTION ON UPDATE NO ACTION + `); + await queryRunner.query(` + ALTER TABLE \`applications\` + ADD CONSTRAINT \`FK_e57508958bf92b9d9d25231b5e8\` FOREIGN KEY (\`owner_id\`) REFERENCES \`users\`(\`id\`) ON DELETE NO ACTION ON UPDATE NO ACTION + `); + await queryRunner.query(` + ALTER TABLE \`applications\` + ADD CONSTRAINT \`FK_a36ed02953077f408d0f3ebc424\` FOREIGN KEY (\`team_id\`) REFERENCES \`teams\`(\`id\`) ON DELETE CASCADE ON UPDATE NO ACTION + `); + await queryRunner.query(` + ALTER TABLE \`applications\` + ADD CONSTRAINT \`FK_e5bf78cdbbe9ba91062d74c5aba\` FOREIGN KEY (\`guild_id\`) REFERENCES \`guilds\`(\`id\`) ON DELETE NO ACTION ON UPDATE NO ACTION + `); + await queryRunner.query(` + ALTER TABLE \`audit_logs\` + ADD CONSTRAINT \`FK_3cd01cd3ae7aab010310d96ac8e\` FOREIGN KEY (\`target_id\`) REFERENCES \`users\`(\`id\`) ON DELETE NO ACTION ON UPDATE NO ACTION + `); + await queryRunner.query(` + ALTER TABLE \`audit_logs\` + ADD CONSTRAINT \`FK_bd2726fd31b35443f2245b93ba0\` FOREIGN KEY (\`user_id\`) REFERENCES \`users\`(\`id\`) ON DELETE NO ACTION ON UPDATE NO ACTION + `); + await queryRunner.query(` + ALTER TABLE \`sessions\` + ADD CONSTRAINT \`FK_085d540d9f418cfbdc7bd55bb19\` FOREIGN KEY (\`user_id\`) REFERENCES \`users\`(\`id\`) ON DELETE CASCADE ON UPDATE NO ACTION + `); + await queryRunner.query(` + ALTER TABLE \`sticker_packs\` + ADD CONSTRAINT \`FK_448fafba4355ee1c837bbc865f1\` FOREIGN KEY (\`coverStickerId\`) REFERENCES \`stickers\`(\`id\`) ON DELETE NO ACTION ON UPDATE NO ACTION + `); + await queryRunner.query(` + ALTER TABLE \`notes\` + ADD CONSTRAINT \`FK_f9e103f8ae67cb1787063597925\` FOREIGN KEY (\`owner_id\`) REFERENCES \`users\`(\`id\`) ON DELETE CASCADE ON UPDATE NO ACTION + `); + await queryRunner.query(` + ALTER TABLE \`notes\` + ADD CONSTRAINT \`FK_23e08e5b4481711d573e1abecdc\` FOREIGN KEY (\`target_id\`) REFERENCES \`users\`(\`id\`) ON DELETE CASCADE ON UPDATE NO ACTION + `); + await queryRunner.query(` + ALTER TABLE \`member_roles\` + ADD CONSTRAINT \`FK_5d7ddc8a5f9c167f548625e772e\` FOREIGN KEY (\`index\`) REFERENCES \`members\`(\`index\`) ON DELETE CASCADE ON UPDATE CASCADE + `); + await queryRunner.query(` + ALTER TABLE \`member_roles\` + ADD CONSTRAINT \`FK_e9080e7a7997a0170026d5139c1\` FOREIGN KEY (\`role_id\`) REFERENCES \`roles\`(\`id\`) ON DELETE CASCADE ON UPDATE CASCADE + `); + await queryRunner.query(` + ALTER TABLE \`message_user_mentions\` + ADD CONSTRAINT \`FK_a343387fc560ef378760681c236\` FOREIGN KEY (\`messagesId\`) REFERENCES \`messages\`(\`id\`) ON DELETE CASCADE ON UPDATE CASCADE + `); + await queryRunner.query(` + ALTER TABLE \`message_user_mentions\` + ADD CONSTRAINT \`FK_b831eb18ceebd28976239b1e2f8\` FOREIGN KEY (\`usersId\`) REFERENCES \`users\`(\`id\`) ON DELETE CASCADE ON UPDATE CASCADE + `); + await queryRunner.query(` + ALTER TABLE \`message_role_mentions\` + ADD CONSTRAINT \`FK_a8242cf535337a490b0feaea0b4\` FOREIGN KEY (\`messagesId\`) REFERENCES \`messages\`(\`id\`) ON DELETE CASCADE ON UPDATE CASCADE + `); + await queryRunner.query(` + ALTER TABLE \`message_role_mentions\` + ADD CONSTRAINT \`FK_29d63eb1a458200851bc37d074b\` FOREIGN KEY (\`rolesId\`) REFERENCES \`roles\`(\`id\`) ON DELETE CASCADE ON UPDATE CASCADE + `); + await queryRunner.query(` + ALTER TABLE \`message_channel_mentions\` + ADD CONSTRAINT \`FK_2a27102ecd1d81b4582a4360921\` FOREIGN KEY (\`messagesId\`) REFERENCES \`messages\`(\`id\`) ON DELETE CASCADE ON UPDATE CASCADE + `); + await queryRunner.query(` + ALTER TABLE \`message_channel_mentions\` + ADD CONSTRAINT \`FK_bdb8c09e1464cabf62105bf4b9d\` FOREIGN KEY (\`channelsId\`) REFERENCES \`channels\`(\`id\`) ON DELETE CASCADE ON UPDATE CASCADE + `); + await queryRunner.query(` + ALTER TABLE \`message_stickers\` + ADD CONSTRAINT \`FK_40bb6f23e7cc133292e92829d28\` FOREIGN KEY (\`messagesId\`) REFERENCES \`messages\`(\`id\`) ON DELETE CASCADE ON UPDATE CASCADE + `); + await queryRunner.query(` + ALTER TABLE \`message_stickers\` + ADD CONSTRAINT \`FK_e22a70819d07659c7a71c112a1f\` FOREIGN KEY (\`stickersId\`) REFERENCES \`stickers\`(\`id\`) ON DELETE CASCADE ON UPDATE CASCADE + `); + await queryRunner.query(` + CREATE TABLE \`query-result-cache\` ( + \`id\` int NOT NULL AUTO_INCREMENT, + \`identifier\` varchar(255) NULL, + \`time\` bigint NOT NULL, + \`duration\` int NOT NULL, + \`query\` text NOT NULL, + \`result\` text NOT NULL, + PRIMARY KEY (\`id\`) + ) ENGINE = InnoDB + `); + } + + public async down(queryRunner: QueryRunner): Promise<void> { + await queryRunner.query(` + DROP TABLE \`query-result-cache\` + `); + await queryRunner.query(` + ALTER TABLE \`message_stickers\` DROP FOREIGN KEY \`FK_e22a70819d07659c7a71c112a1f\` + `); + await queryRunner.query(` + ALTER TABLE \`message_stickers\` DROP FOREIGN KEY \`FK_40bb6f23e7cc133292e92829d28\` + `); + await queryRunner.query(` + ALTER TABLE \`message_channel_mentions\` DROP FOREIGN KEY \`FK_bdb8c09e1464cabf62105bf4b9d\` + `); + await queryRunner.query(` + ALTER TABLE \`message_channel_mentions\` DROP FOREIGN KEY \`FK_2a27102ecd1d81b4582a4360921\` + `); + await queryRunner.query(` + ALTER TABLE \`message_role_mentions\` DROP FOREIGN KEY \`FK_29d63eb1a458200851bc37d074b\` + `); + await queryRunner.query(` + ALTER TABLE \`message_role_mentions\` DROP FOREIGN KEY \`FK_a8242cf535337a490b0feaea0b4\` + `); + await queryRunner.query(` + ALTER TABLE \`message_user_mentions\` DROP FOREIGN KEY \`FK_b831eb18ceebd28976239b1e2f8\` + `); + await queryRunner.query(` + ALTER TABLE \`message_user_mentions\` DROP FOREIGN KEY \`FK_a343387fc560ef378760681c236\` + `); + await queryRunner.query(` + ALTER TABLE \`member_roles\` DROP FOREIGN KEY \`FK_e9080e7a7997a0170026d5139c1\` + `); + await queryRunner.query(` + ALTER TABLE \`member_roles\` DROP FOREIGN KEY \`FK_5d7ddc8a5f9c167f548625e772e\` + `); + await queryRunner.query(` + ALTER TABLE \`notes\` DROP FOREIGN KEY \`FK_23e08e5b4481711d573e1abecdc\` + `); + await queryRunner.query(` + ALTER TABLE \`notes\` DROP FOREIGN KEY \`FK_f9e103f8ae67cb1787063597925\` + `); + await queryRunner.query(` + ALTER TABLE \`sticker_packs\` DROP FOREIGN KEY \`FK_448fafba4355ee1c837bbc865f1\` + `); + await queryRunner.query(` + ALTER TABLE \`sessions\` DROP FOREIGN KEY \`FK_085d540d9f418cfbdc7bd55bb19\` + `); + await queryRunner.query(` + ALTER TABLE \`audit_logs\` DROP FOREIGN KEY \`FK_bd2726fd31b35443f2245b93ba0\` + `); + await queryRunner.query(` + ALTER TABLE \`audit_logs\` DROP FOREIGN KEY \`FK_3cd01cd3ae7aab010310d96ac8e\` + `); + await queryRunner.query(` + ALTER TABLE \`applications\` DROP FOREIGN KEY \`FK_e5bf78cdbbe9ba91062d74c5aba\` + `); + await queryRunner.query(` + ALTER TABLE \`applications\` DROP FOREIGN KEY \`FK_a36ed02953077f408d0f3ebc424\` + `); + await queryRunner.query(` + ALTER TABLE \`applications\` DROP FOREIGN KEY \`FK_e57508958bf92b9d9d25231b5e8\` + `); + await queryRunner.query(` + ALTER TABLE \`teams\` DROP FOREIGN KEY \`FK_13f00abf7cb6096c43ecaf8c108\` + `); + await queryRunner.query(` + ALTER TABLE \`team_members\` DROP FOREIGN KEY \`FK_c2bf4967c8c2a6b845dadfbf3d4\` + `); + await queryRunner.query(` + ALTER TABLE \`team_members\` DROP FOREIGN KEY \`FK_fdad7d5768277e60c40e01cdcea\` + `); + await queryRunner.query(` + ALTER TABLE \`guilds\` DROP FOREIGN KEY \`FK_9d1d665379eefde7876a17afa99\` + `); + await queryRunner.query(` + ALTER TABLE \`guilds\` DROP FOREIGN KEY \`FK_cfc3d3ad260f8121c95b31a1fce\` + `); + await queryRunner.query(` + ALTER TABLE \`guilds\` DROP FOREIGN KEY \`FK_95828668aa333460582e0ca6396\` + `); + await queryRunner.query(` + ALTER TABLE \`guilds\` DROP FOREIGN KEY \`FK_8d450b016dc8bec35f36729e4b0\` + `); + await queryRunner.query(` + ALTER TABLE \`guilds\` DROP FOREIGN KEY \`FK_fc1a451727e3643ca572a3bb394\` + `); + await queryRunner.query(` + ALTER TABLE \`guilds\` DROP FOREIGN KEY \`FK_e2a2f873a64a5cf62526de42325\` + `); + await queryRunner.query(` + ALTER TABLE \`guilds\` DROP FOREIGN KEY \`FK_f591a66b8019d87b0fe6c12dad6\` + `); + await queryRunner.query(` + ALTER TABLE \`templates\` DROP FOREIGN KEY \`FK_445d00eaaea0e60a017a5ed0c11\` + `); + await queryRunner.query(` + ALTER TABLE \`templates\` DROP FOREIGN KEY \`FK_d7374b7f8f5fbfdececa4fb62e1\` + `); + await queryRunner.query(` + ALTER TABLE \`emojis\` DROP FOREIGN KEY \`FK_fa7ddd5f9a214e28ce596548421\` + `); + await queryRunner.query(` + ALTER TABLE \`emojis\` DROP FOREIGN KEY \`FK_4b988e0db89d94cebcf07f598cc\` + `); + await queryRunner.query(` + ALTER TABLE \`channels\` DROP FOREIGN KEY \`FK_3873ed438575cce703ecff4fc7b\` + `); + await queryRunner.query(` + ALTER TABLE \`channels\` DROP FOREIGN KEY \`FK_3274522d14af40540b1a883fc80\` + `); + await queryRunner.query(` + ALTER TABLE \`channels\` DROP FOREIGN KEY \`FK_c253dafe5f3a03ec00cd8fb4581\` + `); + await queryRunner.query(` + ALTER TABLE \`voice_states\` DROP FOREIGN KEY \`FK_5fe1d5f931a67e85039c640001b\` + `); + await queryRunner.query(` + ALTER TABLE \`voice_states\` DROP FOREIGN KEY \`FK_9f8d389866b40b6657edd026dd4\` + `); + await queryRunner.query(` + ALTER TABLE \`voice_states\` DROP FOREIGN KEY \`FK_03779ef216d4b0358470d9cb748\` + `); + await queryRunner.query(` + ALTER TABLE \`invites\` DROP FOREIGN KEY \`FK_11a0d394f8fc649c19ce5f16b59\` + `); + await queryRunner.query(` + ALTER TABLE \`invites\` DROP FOREIGN KEY \`FK_15c35422032e0b22b4ada95f48f\` + `); + await queryRunner.query(` + ALTER TABLE \`invites\` DROP FOREIGN KEY \`FK_6a15b051fe5050aa00a4b9ff0f6\` + `); + await queryRunner.query(` + ALTER TABLE \`invites\` DROP FOREIGN KEY \`FK_3f4939aa1461e8af57fea3fb05d\` + `); + await queryRunner.query(` + ALTER TABLE \`read_states\` DROP FOREIGN KEY \`FK_195f92e4dd1254a4e348c043763\` + `); + await queryRunner.query(` + ALTER TABLE \`read_states\` DROP FOREIGN KEY \`FK_40da2fca4e0eaf7a23b5bfc5d34\` + `); + await queryRunner.query(` + ALTER TABLE \`messages\` DROP FOREIGN KEY \`FK_61a92bb65b302a76d9c1fcd3174\` + `); + await queryRunner.query(` + ALTER TABLE \`messages\` DROP FOREIGN KEY \`FK_5d3ec1cb962de6488637fd779d6\` + `); + await queryRunner.query(` + ALTER TABLE \`messages\` DROP FOREIGN KEY \`FK_f83c04bcf1df4e5c0e7a52ed348\` + `); + await queryRunner.query(` + ALTER TABLE \`messages\` DROP FOREIGN KEY \`FK_b0525304f2262b7014245351c76\` + `); + await queryRunner.query(` + ALTER TABLE \`messages\` DROP FOREIGN KEY \`FK_05535bc695e9f7ee104616459d3\` + `); + await queryRunner.query(` + ALTER TABLE \`messages\` DROP FOREIGN KEY \`FK_b193588441b085352a4c0109423\` + `); + await queryRunner.query(` + ALTER TABLE \`messages\` DROP FOREIGN KEY \`FK_86b9109b155eb70c0a2ca3b4b6d\` + `); + await queryRunner.query(` + ALTER TABLE \`attachments\` DROP FOREIGN KEY \`FK_623e10eec51ada466c5038979e3\` + `); + await queryRunner.query(` + ALTER TABLE \`stickers\` DROP FOREIGN KEY \`FK_8f4ee73f2bb2325ff980502e158\` + `); + await queryRunner.query(` + ALTER TABLE \`stickers\` DROP FOREIGN KEY \`FK_193d551d852aca5347ef5c9f205\` + `); + await queryRunner.query(` + ALTER TABLE \`stickers\` DROP FOREIGN KEY \`FK_e7cfa5cefa6661b3fb8fda8ce69\` + `); + await queryRunner.query(` + ALTER TABLE \`webhooks\` DROP FOREIGN KEY \`FK_3a285f4f49c40e0706d3018bc9f\` + `); + await queryRunner.query(` + ALTER TABLE \`webhooks\` DROP FOREIGN KEY \`FK_0d523f6f997c86e052c49b1455f\` + `); + await queryRunner.query(` + ALTER TABLE \`webhooks\` DROP FOREIGN KEY \`FK_c3e5305461931763b56aa905f1c\` + `); + await queryRunner.query(` + ALTER TABLE \`webhooks\` DROP FOREIGN KEY \`FK_df528cf77e82f8032230e7e37d8\` + `); + await queryRunner.query(` + ALTER TABLE \`webhooks\` DROP FOREIGN KEY \`FK_487a7af59d189f744fe394368fc\` + `); + await queryRunner.query(` + ALTER TABLE \`members\` DROP FOREIGN KEY \`FK_16aceddd5b89825b8ed6029ad1c\` + `); + await queryRunner.query(` + ALTER TABLE \`members\` DROP FOREIGN KEY \`FK_28b53062261b996d9c99fa12404\` + `); + await queryRunner.query(` + ALTER TABLE \`roles\` DROP FOREIGN KEY \`FK_c32c1ab1c4dc7dcb0278c4b1b8b\` + `); + await queryRunner.query(` + ALTER TABLE \`recipients\` DROP FOREIGN KEY \`FK_6157e8b6ba4e6e3089616481fe2\` + `); + await queryRunner.query(` + ALTER TABLE \`recipients\` DROP FOREIGN KEY \`FK_2f18ee1ba667f233ae86c0ea60e\` + `); + await queryRunner.query(` + ALTER TABLE \`bans\` DROP FOREIGN KEY \`FK_07ad88c86d1f290d46748410d58\` + `); + await queryRunner.query(` + ALTER TABLE \`bans\` DROP FOREIGN KEY \`FK_9d3ab7dd180ebdd245cdb66ecad\` + `); + await queryRunner.query(` + ALTER TABLE \`bans\` DROP FOREIGN KEY \`FK_5999e8e449f80a236ff72023559\` + `); + await queryRunner.query(` + ALTER TABLE \`backup_codes\` DROP FOREIGN KEY \`FK_70066ea80d2f4b871beda32633b\` + `); + await queryRunner.query(` + ALTER TABLE \`connected_accounts\` DROP FOREIGN KEY \`FK_f47244225a6a1eac04a3463dd90\` + `); + await queryRunner.query(` + ALTER TABLE \`relationships\` DROP FOREIGN KEY \`FK_9c7f6b98a9843b76dce1b0c878b\` + `); + await queryRunner.query(` + ALTER TABLE \`relationships\` DROP FOREIGN KEY \`FK_9af4194bab1250b1c584ae4f1d7\` + `); + await queryRunner.query(` + DROP INDEX \`IDX_e22a70819d07659c7a71c112a1\` ON \`message_stickers\` + `); + await queryRunner.query(` + DROP INDEX \`IDX_40bb6f23e7cc133292e92829d2\` ON \`message_stickers\` + `); + await queryRunner.query(` + DROP TABLE \`message_stickers\` + `); + await queryRunner.query(` + DROP INDEX \`IDX_bdb8c09e1464cabf62105bf4b9\` ON \`message_channel_mentions\` + `); + await queryRunner.query(` + DROP INDEX \`IDX_2a27102ecd1d81b4582a436092\` ON \`message_channel_mentions\` + `); + await queryRunner.query(` + DROP TABLE \`message_channel_mentions\` + `); + await queryRunner.query(` + DROP INDEX \`IDX_29d63eb1a458200851bc37d074\` ON \`message_role_mentions\` + `); + await queryRunner.query(` + DROP INDEX \`IDX_a8242cf535337a490b0feaea0b\` ON \`message_role_mentions\` + `); + await queryRunner.query(` + DROP TABLE \`message_role_mentions\` + `); + await queryRunner.query(` + DROP INDEX \`IDX_b831eb18ceebd28976239b1e2f\` ON \`message_user_mentions\` + `); + await queryRunner.query(` + DROP INDEX \`IDX_a343387fc560ef378760681c23\` ON \`message_user_mentions\` + `); + await queryRunner.query(` + DROP TABLE \`message_user_mentions\` + `); + await queryRunner.query(` + DROP INDEX \`IDX_e9080e7a7997a0170026d5139c\` ON \`member_roles\` + `); + await queryRunner.query(` + DROP INDEX \`IDX_5d7ddc8a5f9c167f548625e772\` ON \`member_roles\` + `); + await queryRunner.query(` + DROP TABLE \`member_roles\` + `); + await queryRunner.query(` + DROP INDEX \`IDX_74e6689b9568cc965b8bfc9150\` ON \`notes\` + `); + await queryRunner.query(` + DROP TABLE \`notes\` + `); + await queryRunner.query(` + DROP TABLE \`client_release\` + `); + await queryRunner.query(` + DROP TABLE \`sticker_packs\` + `); + await queryRunner.query(` + DROP TABLE \`sessions\` + `); + await queryRunner.query(` + DROP TABLE \`rate_limits\` + `); + await queryRunner.query(` + DROP TABLE \`categories\` + `); + await queryRunner.query(` + DROP TABLE \`audit_logs\` + `); + await queryRunner.query(` + DROP TABLE \`applications\` + `); + await queryRunner.query(` + DROP TABLE \`teams\` + `); + await queryRunner.query(` + DROP TABLE \`team_members\` + `); + await queryRunner.query(` + DROP TABLE \`guilds\` + `); + await queryRunner.query(` + DROP INDEX \`IDX_be38737bf339baf63b1daeffb5\` ON \`templates\` + `); + await queryRunner.query(` + DROP TABLE \`templates\` + `); + await queryRunner.query(` + DROP TABLE \`emojis\` + `); + await queryRunner.query(` + DROP TABLE \`channels\` + `); + await queryRunner.query(` + DROP TABLE \`voice_states\` + `); + await queryRunner.query(` + DROP TABLE \`invites\` + `); + await queryRunner.query(` + DROP INDEX \`IDX_0abf8b443321bd3cf7f81ee17a\` ON \`read_states\` + `); + await queryRunner.query(` + DROP TABLE \`read_states\` + `); + await queryRunner.query(` + DROP INDEX \`IDX_3ed7a60fb7dbe04e1ba9332a8b\` ON \`messages\` + `); + await queryRunner.query(` + DROP INDEX \`IDX_05535bc695e9f7ee104616459d\` ON \`messages\` + `); + await queryRunner.query(` + DROP INDEX \`IDX_86b9109b155eb70c0a2ca3b4b6\` ON \`messages\` + `); + await queryRunner.query(` + DROP TABLE \`messages\` + `); + await queryRunner.query(` + DROP TABLE \`attachments\` + `); + await queryRunner.query(` + DROP TABLE \`stickers\` + `); + await queryRunner.query(` + DROP TABLE \`webhooks\` + `); + await queryRunner.query(` + DROP INDEX \`IDX_bb2bf9386ac443afbbbf9f12d3\` ON \`members\` + `); + await queryRunner.query(` + DROP TABLE \`members\` + `); + await queryRunner.query(` + DROP TABLE \`roles\` + `); + await queryRunner.query(` + DROP TABLE \`recipients\` + `); + await queryRunner.query(` + DROP TABLE \`bans\` + `); + await queryRunner.query(` + DROP TABLE \`backup_codes\` + `); + await queryRunner.query(` + DROP TABLE \`users\` + `); + await queryRunner.query(` + DROP TABLE \`connected_accounts\` + `); + await queryRunner.query(` + DROP INDEX \`IDX_a0b2ff0a598df0b0d055934a17\` ON \`relationships\` + `); + await queryRunner.query(` + DROP TABLE \`relationships\` + `); + await queryRunner.query(` + DROP TABLE \`config\` + `); + } +} diff --git a/src/util/migrations/mariadb/1659921859145-premium_since_as_date.ts b/src/util/migrations/mariadb/1659921859145-premium_since_as_date.ts new file mode 100644
index 00000000..549d6b9f --- /dev/null +++ b/src/util/migrations/mariadb/1659921859145-premium_since_as_date.ts
@@ -0,0 +1,25 @@ +import { MigrationInterface, QueryRunner } from "typeorm"; + +export class premiumSinceAsDate1659921859145 implements MigrationInterface { + name = "premiumSinceAsDate1659921859145"; + + public async up(queryRunner: QueryRunner): Promise<void> { + await queryRunner.query(` + ALTER TABLE \`members\` DROP COLUMN \`premium_since\` + `); + await queryRunner.query(` + ALTER TABLE \`members\` + ADD \`premium_since\` datetime NULL + `); + } + + public async down(queryRunner: QueryRunner): Promise<void> { + await queryRunner.query(` + ALTER TABLE \`members\` DROP COLUMN \`premium_since\` + `); + await queryRunner.query(` + ALTER TABLE \`members\` + ADD \`premium_since\` bigint NULL + `); + } +} diff --git a/src/util/migrations/mariadb/1660130586602-updated-applications.ts b/src/util/migrations/mariadb/1660130586602-updated-applications.ts new file mode 100644
index 00000000..fea076db --- /dev/null +++ b/src/util/migrations/mariadb/1660130586602-updated-applications.ts
@@ -0,0 +1,184 @@ +import { MigrationInterface, QueryRunner } from "typeorm"; + +export class updatedApplications1660130586602 implements MigrationInterface { + name = "updatedApplications1660130586602"; + + public async up(queryRunner: QueryRunner): Promise<void> { + await queryRunner.query(` + ALTER TABLE \`applications\` DROP FOREIGN KEY \`FK_e5bf78cdbbe9ba91062d74c5aba\` + `); + await queryRunner.query(` + ALTER TABLE \`applications\` DROP COLUMN \`rpc_origins\` + `); + await queryRunner.query(` + ALTER TABLE \`applications\` DROP COLUMN \`primary_sku_id\` + `); + await queryRunner.query(` + ALTER TABLE \`applications\` DROP COLUMN \`slug\` + `); + await queryRunner.query(` + ALTER TABLE \`applications\` DROP COLUMN \`guild_id\` + `); + await queryRunner.query(` + ALTER TABLE \`applications\` + ADD \`type\` text NULL + `); + await queryRunner.query(` + ALTER TABLE \`applications\` + ADD \`hook\` tinyint NOT NULL + `); + await queryRunner.query(` + ALTER TABLE \`applications\` + ADD \`redirect_uris\` text NULL + `); + await queryRunner.query(` + ALTER TABLE \`applications\` + ADD \`rpc_application_state\` int NULL + `); + await queryRunner.query(` + ALTER TABLE \`applications\` + ADD \`store_application_state\` int NULL + `); + await queryRunner.query(` + ALTER TABLE \`applications\` + ADD \`verification_state\` int NULL + `); + await queryRunner.query(` + ALTER TABLE \`applications\` + ADD \`interactions_endpoint_url\` varchar(255) NULL + `); + await queryRunner.query(` + ALTER TABLE \`applications\` + ADD \`integration_public\` tinyint NULL + `); + await queryRunner.query(` + ALTER TABLE \`applications\` + ADD \`integration_require_code_grant\` tinyint NULL + `); + await queryRunner.query(` + ALTER TABLE \`applications\` + ADD \`discoverability_state\` int NULL + `); + await queryRunner.query(` + ALTER TABLE \`applications\` + ADD \`discovery_eligibility_flags\` int NULL + `); + await queryRunner.query(` + ALTER TABLE \`applications\` + ADD \`tags\` text NULL + `); + await queryRunner.query(` + ALTER TABLE \`applications\` + ADD \`install_params\` text NULL + `); + await queryRunner.query(` + ALTER TABLE \`applications\` + ADD \`bot_user_id\` varchar(255) NULL + `); + await queryRunner.query(` + ALTER TABLE \`applications\` + ADD UNIQUE INDEX \`IDX_2ce5a55796fe4c2f77ece57a64\` (\`bot_user_id\`) + `); + await queryRunner.query(` + ALTER TABLE \`applications\` CHANGE \`description\` \`description\` varchar(255) NULL + `); + await queryRunner.query(` + ALTER TABLE \`applications\` DROP COLUMN \`flags\` + `); + await queryRunner.query(` + ALTER TABLE \`applications\` + ADD \`flags\` int NOT NULL + `); + await queryRunner.query(` + CREATE UNIQUE INDEX \`REL_2ce5a55796fe4c2f77ece57a64\` ON \`applications\` (\`bot_user_id\`) + `); + await queryRunner.query(` + ALTER TABLE \`applications\` + ADD CONSTRAINT \`FK_2ce5a55796fe4c2f77ece57a647\` FOREIGN KEY (\`bot_user_id\`) REFERENCES \`users\`(\`id\`) ON DELETE NO ACTION ON UPDATE NO ACTION + `); + } + + public async down(queryRunner: QueryRunner): Promise<void> { + await queryRunner.query(` + ALTER TABLE \`applications\` DROP FOREIGN KEY \`FK_2ce5a55796fe4c2f77ece57a647\` + `); + await queryRunner.query(` + DROP INDEX \`REL_2ce5a55796fe4c2f77ece57a64\` ON \`applications\` + `); + await queryRunner.query(` + ALTER TABLE \`applications\` DROP COLUMN \`flags\` + `); + await queryRunner.query(` + ALTER TABLE \`applications\` + ADD \`flags\` varchar(255) NOT NULL + `); + await queryRunner.query(` + ALTER TABLE \`applications\` CHANGE \`description\` \`description\` varchar(255) NOT NULL + `); + await queryRunner.query(` + ALTER TABLE \`applications\` DROP INDEX \`IDX_2ce5a55796fe4c2f77ece57a64\` + `); + await queryRunner.query(` + ALTER TABLE \`applications\` DROP COLUMN \`bot_user_id\` + `); + await queryRunner.query(` + ALTER TABLE \`applications\` DROP COLUMN \`install_params\` + `); + await queryRunner.query(` + ALTER TABLE \`applications\` DROP COLUMN \`tags\` + `); + await queryRunner.query(` + ALTER TABLE \`applications\` DROP COLUMN \`discovery_eligibility_flags\` + `); + await queryRunner.query(` + ALTER TABLE \`applications\` DROP COLUMN \`discoverability_state\` + `); + await queryRunner.query(` + ALTER TABLE \`applications\` DROP COLUMN \`integration_require_code_grant\` + `); + await queryRunner.query(` + ALTER TABLE \`applications\` DROP COLUMN \`integration_public\` + `); + await queryRunner.query(` + ALTER TABLE \`applications\` DROP COLUMN \`interactions_endpoint_url\` + `); + await queryRunner.query(` + ALTER TABLE \`applications\` DROP COLUMN \`verification_state\` + `); + await queryRunner.query(` + ALTER TABLE \`applications\` DROP COLUMN \`store_application_state\` + `); + await queryRunner.query(` + ALTER TABLE \`applications\` DROP COLUMN \`rpc_application_state\` + `); + await queryRunner.query(` + ALTER TABLE \`applications\` DROP COLUMN \`redirect_uris\` + `); + await queryRunner.query(` + ALTER TABLE \`applications\` DROP COLUMN \`hook\` + `); + await queryRunner.query(` + ALTER TABLE \`applications\` DROP COLUMN \`type\` + `); + await queryRunner.query(` + ALTER TABLE \`applications\` + ADD \`guild_id\` varchar(255) NULL + `); + await queryRunner.query(` + ALTER TABLE \`applications\` + ADD \`slug\` varchar(255) NULL + `); + await queryRunner.query(` + ALTER TABLE \`applications\` + ADD \`primary_sku_id\` varchar(255) NULL + `); + await queryRunner.query(` + ALTER TABLE \`applications\` + ADD \`rpc_origins\` text NULL + `); + await queryRunner.query(` + ALTER TABLE \`applications\` + ADD CONSTRAINT \`FK_e5bf78cdbbe9ba91062d74c5aba\` FOREIGN KEY (\`guild_id\`) REFERENCES \`guilds\`(\`id\`) ON DELETE NO ACTION ON UPDATE NO ACTION + `); + } +} diff --git a/src/util/migrations/mariadb/1661273147273-test.ts b/src/util/migrations/mariadb/1661273147273-test.ts new file mode 100644
index 00000000..4e077a11 --- /dev/null +++ b/src/util/migrations/mariadb/1661273147273-test.ts
@@ -0,0 +1,149 @@ +import { MigrationInterface, QueryRunner } from "typeorm"; + +export class test1661273147273 implements MigrationInterface { + name = 'test1661273147273' + + public async up(queryRunner: QueryRunner): Promise<void> { + await queryRunner.query(` + ALTER TABLE \`invites\` DROP FOREIGN KEY \`FK_15c35422032e0b22b4ada95f48f\` + `); + await queryRunner.query(` + DROP INDEX \`IDX_2ce5a55796fe4c2f77ece57a64\` ON \`applications\` + `); + await queryRunner.query(` + CREATE TABLE \`plugin_config\` ( + \`key\` varchar(255) NOT NULL, + \`value\` text NULL, + PRIMARY KEY (\`key\`) + ) ENGINE = InnoDB + `); + await queryRunner.query(` + CREATE TABLE \`user_settings\` ( + \`id\` varchar(255) NOT NULL, + \`afk_timeout\` int NULL, + \`allow_accessibility_detection\` tinyint NULL, + \`animate_emoji\` tinyint NULL, + \`animate_stickers\` int NULL, + \`contact_sync_enabled\` tinyint NULL, + \`convert_emoticons\` tinyint NULL, + \`custom_status\` text NULL, + \`default_guilds_restricted\` tinyint NULL, + \`detect_platform_accounts\` tinyint NULL, + \`developer_mode\` tinyint NULL, + \`disable_games_tab\` tinyint NULL, + \`enable_tts_command\` tinyint NULL, + \`explicit_content_filter\` int NULL, + \`friend_source_flags\` text NULL, + \`gateway_connected\` tinyint NULL, + \`gif_auto_play\` tinyint NULL, + \`guild_folders\` text NULL, + \`guild_positions\` text NULL, + \`inline_attachment_media\` tinyint NULL, + \`inline_embed_media\` tinyint NULL, + \`locale\` varchar(255) NULL, + \`message_display_compact\` tinyint NULL, + \`native_phone_integration_enabled\` tinyint NULL, + \`render_embeds\` tinyint NULL, + \`render_reactions\` tinyint NULL, + \`restricted_guilds\` text NULL, + \`show_current_game\` tinyint NULL, + \`status\` varchar(255) NULL, + \`stream_notifications_enabled\` tinyint NULL, + \`theme\` varchar(255) NULL, + \`timezone_offset\` int NULL, + PRIMARY KEY (\`id\`) + ) ENGINE = InnoDB + `); + await queryRunner.query(` + ALTER TABLE \`users\` DROP COLUMN \`settings\` + `); + await queryRunner.query(` + ALTER TABLE \`users\` + ADD \`settingsId\` varchar(255) NULL + `); + await queryRunner.query(` + ALTER TABLE \`users\` + ADD UNIQUE INDEX \`IDX_76ba283779c8441fd5ff819c8c\` (\`settingsId\`) + `); + await queryRunner.query(` + ALTER TABLE \`channels\` + ADD \`flags\` int NULL + `); + await queryRunner.query(` + ALTER TABLE \`channels\` + ADD \`default_thread_rate_limit_per_user\` int NULL + `); + await queryRunner.query(` + ALTER TABLE \`guilds\` + ADD \`premium_progress_bar_enabled\` tinyint NULL + `); + await queryRunner.query(` + ALTER TABLE \`users\` CHANGE \`bio\` \`bio\` varchar(255) NULL + `); + await queryRunner.query(` + ALTER TABLE \`users\` CHANGE \`mfa_enabled\` \`mfa_enabled\` tinyint NULL + `); + await queryRunner.query(` + CREATE UNIQUE INDEX \`REL_76ba283779c8441fd5ff819c8c\` ON \`users\` (\`settingsId\`) + `); + await queryRunner.query(` + ALTER TABLE \`users\` + ADD CONSTRAINT \`FK_76ba283779c8441fd5ff819c8cf\` FOREIGN KEY (\`settingsId\`) REFERENCES \`user_settings\`(\`id\`) ON DELETE NO ACTION ON UPDATE NO ACTION + `); + await queryRunner.query(` + ALTER TABLE \`invites\` + ADD CONSTRAINT \`FK_15c35422032e0b22b4ada95f48f\` FOREIGN KEY (\`inviter_id\`) REFERENCES \`users\`(\`id\`) ON DELETE CASCADE ON UPDATE NO ACTION + `); + } + + public async down(queryRunner: QueryRunner): Promise<void> { + await queryRunner.query(` + ALTER TABLE \`invites\` DROP FOREIGN KEY \`FK_15c35422032e0b22b4ada95f48f\` + `); + await queryRunner.query(` + ALTER TABLE \`users\` DROP FOREIGN KEY \`FK_76ba283779c8441fd5ff819c8cf\` + `); + await queryRunner.query(` + DROP INDEX \`REL_76ba283779c8441fd5ff819c8c\` ON \`users\` + `); + await queryRunner.query(` + ALTER TABLE \`users\` CHANGE \`mfa_enabled\` \`mfa_enabled\` tinyint NOT NULL + `); + await queryRunner.query(` + ALTER TABLE \`users\` CHANGE \`bio\` \`bio\` varchar(255) NOT NULL + `); + await queryRunner.query(` + ALTER TABLE \`guilds\` DROP COLUMN \`premium_progress_bar_enabled\` + `); + await queryRunner.query(` + ALTER TABLE \`channels\` DROP COLUMN \`default_thread_rate_limit_per_user\` + `); + await queryRunner.query(` + ALTER TABLE \`channels\` DROP COLUMN \`flags\` + `); + await queryRunner.query(` + ALTER TABLE \`users\` DROP INDEX \`IDX_76ba283779c8441fd5ff819c8c\` + `); + await queryRunner.query(` + ALTER TABLE \`users\` DROP COLUMN \`settingsId\` + `); + await queryRunner.query(` + ALTER TABLE \`users\` + ADD \`settings\` text NOT NULL + `); + await queryRunner.query(` + DROP TABLE \`user_settings\` + `); + await queryRunner.query(` + DROP TABLE \`plugin_config\` + `); + await queryRunner.query(` + CREATE UNIQUE INDEX \`IDX_2ce5a55796fe4c2f77ece57a64\` ON \`applications\` (\`bot_user_id\`) + `); + await queryRunner.query(` + ALTER TABLE \`invites\` + ADD CONSTRAINT \`FK_15c35422032e0b22b4ada95f48f\` FOREIGN KEY (\`inviter_id\`) REFERENCES \`users\`(\`id\`) ON DELETE NO ACTION ON UPDATE NO ACTION + `); + } + +} diff --git a/src/util/migrations/mariadb/1661273179287-test2.ts b/src/util/migrations/mariadb/1661273179287-test2.ts new file mode 100644
index 00000000..0f77f284 --- /dev/null +++ b/src/util/migrations/mariadb/1661273179287-test2.ts
@@ -0,0 +1,18 @@ +import { MigrationInterface, QueryRunner } from "typeorm"; + +export class test21661273179287 implements MigrationInterface { + name = 'test21661273179287' + + public async up(queryRunner: QueryRunner): Promise<void> { + await queryRunner.query(` + DROP INDEX \`IDX_76ba283779c8441fd5ff819c8c\` ON \`users\` + `); + } + + public async down(queryRunner: QueryRunner): Promise<void> { + await queryRunner.query(` + CREATE UNIQUE INDEX \`IDX_76ba283779c8441fd5ff819c8c\` ON \`users\` (\`settingsId\`) + `); + } + +} diff --git a/src/util/migrations/postgres/1659899687168-initial.ts b/src/util/migrations/postgres/1659899687168-initial.ts new file mode 100644
index 00000000..dc89a572 --- /dev/null +++ b/src/util/migrations/postgres/1659899687168-initial.ts
@@ -0,0 +1,1244 @@ +import { MigrationInterface, QueryRunner } from "typeorm"; + +export class initial1659899687168 implements MigrationInterface { + name = "initial1659899687168"; + + public async up(queryRunner: QueryRunner): Promise<void> { + await queryRunner.query(` + CREATE TABLE "config" ( + "key" character varying NOT NULL, + "value" text, + CONSTRAINT "PK_26489c99ddbb4c91631ef5cc791" PRIMARY KEY ("key") + ) + `); + await queryRunner.query(` + CREATE TABLE "relationships" ( + "id" character varying NOT NULL, + "from_id" character varying NOT NULL, + "to_id" character varying NOT NULL, + "nickname" character varying, + "type" integer NOT NULL, + CONSTRAINT "PK_ba20e2f5cf487408e08e4dcecaf" PRIMARY KEY ("id") + ) + `); + await queryRunner.query(` + CREATE UNIQUE INDEX "IDX_a0b2ff0a598df0b0d055934a17" ON "relationships" ("from_id", "to_id") + `); + await queryRunner.query(` + CREATE TABLE "connected_accounts" ( + "id" character varying NOT NULL, + "user_id" character varying, + "access_token" character varying NOT NULL, + "friend_sync" boolean NOT NULL, + "name" character varying NOT NULL, + "revoked" boolean NOT NULL, + "show_activity" boolean NOT NULL, + "type" character varying NOT NULL, + "verified" boolean NOT NULL, + "visibility" integer NOT NULL, + CONSTRAINT "PK_70416f1da0be645bb31da01c774" PRIMARY KEY ("id") + ) + `); + await queryRunner.query(` + CREATE TABLE "users" ( + "id" character varying NOT NULL, + "username" character varying NOT NULL, + "discriminator" character varying NOT NULL, + "avatar" character varying, + "accent_color" integer, + "banner" character varying, + "phone" character varying, + "desktop" boolean NOT NULL, + "mobile" boolean NOT NULL, + "premium" boolean NOT NULL, + "premium_type" integer NOT NULL, + "bot" boolean NOT NULL, + "bio" character varying NOT NULL, + "system" boolean NOT NULL, + "nsfw_allowed" boolean NOT NULL, + "mfa_enabled" boolean NOT NULL, + "totp_secret" character varying, + "totp_last_ticket" character varying, + "created_at" TIMESTAMP NOT NULL, + "premium_since" TIMESTAMP, + "verified" boolean NOT NULL, + "disabled" boolean NOT NULL, + "deleted" boolean NOT NULL, + "email" character varying, + "flags" character varying NOT NULL, + "public_flags" integer NOT NULL, + "rights" bigint NOT NULL, + "data" text NOT NULL, + "fingerprints" text NOT NULL, + "settings" text NOT NULL, + "extended_settings" text NOT NULL, + "notes" text NOT NULL, + CONSTRAINT "PK_a3ffb1c0c8416b9fc6f907b7433" PRIMARY KEY ("id") + ) + `); + await queryRunner.query(` + CREATE TABLE "backup_codes" ( + "id" character varying NOT NULL, + "code" character varying NOT NULL, + "consumed" boolean NOT NULL, + "expired" boolean NOT NULL, + "user_id" character varying, + CONSTRAINT "PK_34ab957382dbc57e8fb53f1638f" PRIMARY KEY ("id") + ) + `); + await queryRunner.query(` + CREATE TABLE "bans" ( + "id" character varying NOT NULL, + "user_id" character varying, + "guild_id" character varying, + "executor_id" character varying, + "ip" character varying NOT NULL, + "reason" character varying, + CONSTRAINT "PK_a4d6f261bffa4615c62d756566a" PRIMARY KEY ("id") + ) + `); + await queryRunner.query(` + CREATE TABLE "recipients" ( + "id" character varying NOT NULL, + "channel_id" character varying NOT NULL, + "user_id" character varying NOT NULL, + "closed" boolean NOT NULL DEFAULT false, + CONSTRAINT "PK_de8fc5a9c364568f294798fe1e9" PRIMARY KEY ("id") + ) + `); + await queryRunner.query(` + CREATE TABLE "roles" ( + "id" character varying NOT NULL, + "guild_id" character varying, + "color" integer NOT NULL, + "hoist" boolean NOT NULL, + "managed" boolean NOT NULL, + "mentionable" boolean NOT NULL, + "name" character varying NOT NULL, + "permissions" character varying NOT NULL, + "position" integer NOT NULL, + "icon" character varying, + "unicode_emoji" character varying, + "tags" text, + CONSTRAINT "PK_c1433d71a4838793a49dcad46ab" PRIMARY KEY ("id") + ) + `); + await queryRunner.query(` + CREATE TABLE "members" ( + "index" SERIAL NOT NULL, + "id" character varying NOT NULL, + "guild_id" character varying NOT NULL, + "nick" character varying, + "joined_at" TIMESTAMP NOT NULL, + "premium_since" bigint, + "deaf" boolean NOT NULL, + "mute" boolean NOT NULL, + "pending" boolean NOT NULL, + "settings" text NOT NULL, + "last_message_id" character varying, + "joined_by" character varying, + CONSTRAINT "PK_b4a6b8c2478e5df990909c6cf6a" PRIMARY KEY ("index") + ) + `); + await queryRunner.query(` + CREATE UNIQUE INDEX "IDX_bb2bf9386ac443afbbbf9f12d3" ON "members" ("id", "guild_id") + `); + await queryRunner.query(` + CREATE TABLE "webhooks" ( + "id" character varying NOT NULL, + "type" integer NOT NULL, + "name" character varying, + "avatar" character varying, + "token" character varying, + "guild_id" character varying, + "channel_id" character varying, + "application_id" character varying, + "user_id" character varying, + "source_guild_id" character varying, + CONSTRAINT "PK_9e8795cfc899ab7bdaa831e8527" PRIMARY KEY ("id") + ) + `); + await queryRunner.query(` + CREATE TABLE "stickers" ( + "id" character varying NOT NULL, + "name" character varying NOT NULL, + "description" character varying, + "available" boolean, + "tags" character varying, + "pack_id" character varying, + "guild_id" character varying, + "user_id" character varying, + "type" integer NOT NULL, + "format_type" integer NOT NULL, + CONSTRAINT "PK_e1dafa4063a5532645cc2810374" PRIMARY KEY ("id") + ) + `); + await queryRunner.query(` + CREATE TABLE "attachments" ( + "id" character varying NOT NULL, + "filename" character varying NOT NULL, + "size" integer NOT NULL, + "url" character varying NOT NULL, + "proxy_url" character varying NOT NULL, + "height" integer, + "width" integer, + "content_type" character varying, + "message_id" character varying, + CONSTRAINT "PK_5e1f050bcff31e3084a1d662412" PRIMARY KEY ("id") + ) + `); + await queryRunner.query(` + CREATE TABLE "messages" ( + "id" character varying NOT NULL, + "channel_id" character varying, + "guild_id" character varying, + "author_id" character varying, + "member_id" character varying, + "webhook_id" character varying, + "application_id" character varying, + "content" character varying, + "timestamp" TIMESTAMP NOT NULL DEFAULT now(), + "edited_timestamp" TIMESTAMP, + "tts" boolean, + "mention_everyone" boolean, + "embeds" text NOT NULL, + "reactions" text NOT NULL, + "nonce" text, + "pinned" boolean, + "type" integer NOT NULL, + "activity" text, + "flags" character varying, + "message_reference" text, + "interaction" text, + "components" text, + "message_reference_id" character varying, + CONSTRAINT "PK_18325f38ae6de43878487eff986" PRIMARY KEY ("id") + ) + `); + await queryRunner.query(` + CREATE INDEX "IDX_86b9109b155eb70c0a2ca3b4b6" ON "messages" ("channel_id") + `); + await queryRunner.query(` + CREATE INDEX "IDX_05535bc695e9f7ee104616459d" ON "messages" ("author_id") + `); + await queryRunner.query(` + CREATE UNIQUE INDEX "IDX_3ed7a60fb7dbe04e1ba9332a8b" ON "messages" ("channel_id", "id") + `); + await queryRunner.query(` + CREATE TABLE "read_states" ( + "id" character varying NOT NULL, + "channel_id" character varying NOT NULL, + "user_id" character varying NOT NULL, + "last_message_id" character varying, + "public_ack" character varying, + "notifications_cursor" character varying, + "last_pin_timestamp" TIMESTAMP, + "mention_count" integer, + CONSTRAINT "PK_e6956a804978f01b713b1ed58e2" PRIMARY KEY ("id") + ) + `); + await queryRunner.query(` + CREATE UNIQUE INDEX "IDX_0abf8b443321bd3cf7f81ee17a" ON "read_states" ("channel_id", "user_id") + `); + await queryRunner.query(` + CREATE TABLE "invites" ( + "code" character varying NOT NULL, + "temporary" boolean NOT NULL, + "uses" integer NOT NULL, + "max_uses" integer NOT NULL, + "max_age" integer NOT NULL, + "created_at" TIMESTAMP NOT NULL, + "expires_at" TIMESTAMP NOT NULL, + "guild_id" character varying, + "channel_id" character varying, + "inviter_id" character varying, + "target_user_id" character varying, + "target_user_type" integer, + "vanity_url" boolean, + CONSTRAINT "PK_33fd8a248db1cd832baa8aa25bf" PRIMARY KEY ("code") + ) + `); + await queryRunner.query(` + CREATE TABLE "voice_states" ( + "id" character varying NOT NULL, + "guild_id" character varying, + "channel_id" character varying, + "user_id" character varying, + "session_id" character varying NOT NULL, + "token" character varying, + "deaf" boolean NOT NULL, + "mute" boolean NOT NULL, + "self_deaf" boolean NOT NULL, + "self_mute" boolean NOT NULL, + "self_stream" boolean, + "self_video" boolean NOT NULL, + "suppress" boolean NOT NULL, + "request_to_speak_timestamp" TIMESTAMP, + CONSTRAINT "PK_ada09a50c134fad1369b510e3ce" PRIMARY KEY ("id") + ) + `); + await queryRunner.query(` + CREATE TABLE "channels" ( + "id" character varying NOT NULL, + "created_at" TIMESTAMP NOT NULL, + "name" character varying, + "icon" text, + "type" integer NOT NULL, + "last_message_id" character varying, + "guild_id" character varying, + "parent_id" character varying, + "owner_id" character varying, + "last_pin_timestamp" integer, + "default_auto_archive_duration" integer, + "position" integer, + "permission_overwrites" text, + "video_quality_mode" integer, + "bitrate" integer, + "user_limit" integer, + "nsfw" boolean, + "rate_limit_per_user" integer, + "topic" character varying, + "retention_policy_id" character varying, + CONSTRAINT "PK_bc603823f3f741359c2339389f9" PRIMARY KEY ("id") + ) + `); + await queryRunner.query(` + CREATE TABLE "emojis" ( + "id" character varying NOT NULL, + "animated" boolean NOT NULL, + "available" boolean NOT NULL, + "guild_id" character varying NOT NULL, + "user_id" character varying, + "managed" boolean NOT NULL, + "name" character varying NOT NULL, + "require_colons" boolean NOT NULL, + "roles" text NOT NULL, + "groups" text, + CONSTRAINT "PK_9adb96a675f555c6169bad7ba62" PRIMARY KEY ("id") + ) + `); + await queryRunner.query(` + CREATE TABLE "templates" ( + "id" character varying NOT NULL, + "code" character varying NOT NULL, + "name" character varying NOT NULL, + "description" character varying, + "usage_count" integer, + "creator_id" character varying, + "created_at" TIMESTAMP NOT NULL, + "updated_at" TIMESTAMP NOT NULL, + "source_guild_id" character varying, + "serialized_source_guild" text NOT NULL, + CONSTRAINT "UQ_be38737bf339baf63b1daeffb55" UNIQUE ("code"), + CONSTRAINT "PK_515948649ce0bbbe391de702ae5" PRIMARY KEY ("id") + ) + `); + await queryRunner.query(` + CREATE TABLE "guilds" ( + "id" character varying NOT NULL, + "afk_channel_id" character varying, + "afk_timeout" integer, + "banner" character varying, + "default_message_notifications" integer, + "description" character varying, + "discovery_splash" character varying, + "explicit_content_filter" integer, + "features" text NOT NULL, + "primary_category_id" integer, + "icon" character varying, + "large" boolean, + "max_members" integer, + "max_presences" integer, + "max_video_channel_users" integer, + "member_count" integer, + "presence_count" integer, + "template_id" character varying, + "mfa_level" integer, + "name" character varying NOT NULL, + "owner_id" character varying, + "preferred_locale" character varying, + "premium_subscription_count" integer, + "premium_tier" integer, + "public_updates_channel_id" character varying, + "rules_channel_id" character varying, + "region" character varying, + "splash" character varying, + "system_channel_id" character varying, + "system_channel_flags" integer, + "unavailable" boolean, + "verification_level" integer, + "welcome_screen" text NOT NULL, + "widget_channel_id" character varying, + "widget_enabled" boolean, + "nsfw_level" integer, + "nsfw" boolean, + "parent" character varying, + CONSTRAINT "PK_e7e7f2a51bd6d96a9ac2aa560f9" PRIMARY KEY ("id") + ) + `); + await queryRunner.query(` + CREATE TABLE "team_members" ( + "id" character varying NOT NULL, + "membership_state" integer NOT NULL, + "permissions" text NOT NULL, + "team_id" character varying, + "user_id" character varying, + CONSTRAINT "PK_ca3eae89dcf20c9fd95bf7460aa" PRIMARY KEY ("id") + ) + `); + await queryRunner.query(` + CREATE TABLE "teams" ( + "id" character varying NOT NULL, + "icon" character varying, + "name" character varying NOT NULL, + "owner_user_id" character varying, + CONSTRAINT "PK_7e5523774a38b08a6236d322403" PRIMARY KEY ("id") + ) + `); + await queryRunner.query(` + CREATE TABLE "applications" ( + "id" character varying NOT NULL, + "name" character varying NOT NULL, + "icon" character varying, + "description" character varying NOT NULL, + "rpc_origins" text, + "bot_public" boolean NOT NULL, + "bot_require_code_grant" boolean NOT NULL, + "terms_of_service_url" character varying, + "privacy_policy_url" character varying, + "summary" character varying, + "verify_key" character varying NOT NULL, + "primary_sku_id" character varying, + "slug" character varying, + "cover_image" character varying, + "flags" character varying NOT NULL, + "owner_id" character varying, + "team_id" character varying, + "guild_id" character varying, + CONSTRAINT "PK_938c0a27255637bde919591888f" PRIMARY KEY ("id") + ) + `); + await queryRunner.query(` + CREATE TABLE "audit_logs" ( + "id" character varying NOT NULL, + "user_id" character varying, + "action_type" integer NOT NULL, + "options" text, + "changes" text NOT NULL, + "reason" character varying, + "target_id" character varying, + CONSTRAINT "PK_1bb179d048bbc581caa3b013439" PRIMARY KEY ("id") + ) + `); + await queryRunner.query(` + CREATE TABLE "categories" ( + "id" integer NOT NULL, + "name" character varying, + "localizations" text NOT NULL, + "is_primary" boolean, + CONSTRAINT "PK_24dbc6126a28ff948da33e97d3b" PRIMARY KEY ("id") + ) + `); + await queryRunner.query(` + CREATE TABLE "rate_limits" ( + "id" character varying NOT NULL, + "executor_id" character varying NOT NULL, + "hits" integer NOT NULL, + "blocked" boolean NOT NULL, + "expires_at" TIMESTAMP NOT NULL, + CONSTRAINT "PK_3b4449f1f5fc167d921ee619f65" PRIMARY KEY ("id") + ) + `); + await queryRunner.query(` + CREATE TABLE "sessions" ( + "id" character varying NOT NULL, + "user_id" character varying, + "session_id" character varying NOT NULL, + "activities" text, + "client_info" text NOT NULL, + "status" character varying NOT NULL, + CONSTRAINT "PK_3238ef96f18b355b671619111bc" PRIMARY KEY ("id") + ) + `); + await queryRunner.query(` + CREATE TABLE "sticker_packs" ( + "id" character varying NOT NULL, + "name" character varying NOT NULL, + "description" character varying, + "banner_asset_id" character varying, + "cover_sticker_id" character varying, + "coverStickerId" character varying, + CONSTRAINT "PK_a27381efea0f876f5d3233af655" PRIMARY KEY ("id") + ) + `); + await queryRunner.query(` + CREATE TABLE "client_release" ( + "id" character varying NOT NULL, + "name" character varying NOT NULL, + "pub_date" character varying NOT NULL, + "url" character varying NOT NULL, + "deb_url" character varying NOT NULL, + "osx_url" character varying NOT NULL, + "win_url" character varying NOT NULL, + "notes" character varying, + CONSTRAINT "PK_4c4ea258342d2d6ba1be0a71a43" PRIMARY KEY ("id") + ) + `); + await queryRunner.query(` + CREATE TABLE "notes" ( + "id" character varying NOT NULL, + "content" character varying NOT NULL, + "owner_id" character varying, + "target_id" character varying, + CONSTRAINT "UQ_74e6689b9568cc965b8bfc9150b" UNIQUE ("owner_id", "target_id"), + CONSTRAINT "PK_af6206538ea96c4e77e9f400c3d" PRIMARY KEY ("id") + ) + `); + await queryRunner.query(` + CREATE TABLE "member_roles" ( + "index" integer NOT NULL, + "role_id" character varying NOT NULL, + CONSTRAINT "PK_951c1d72a0fd1da8760b4a1fd66" PRIMARY KEY ("index", "role_id") + ) + `); + await queryRunner.query(` + CREATE INDEX "IDX_5d7ddc8a5f9c167f548625e772" ON "member_roles" ("index") + `); + await queryRunner.query(` + CREATE INDEX "IDX_e9080e7a7997a0170026d5139c" ON "member_roles" ("role_id") + `); + await queryRunner.query(` + CREATE TABLE "message_user_mentions" ( + "messagesId" character varying NOT NULL, + "usersId" character varying NOT NULL, + CONSTRAINT "PK_9b9b6e245ad47a48dbd7605d4fb" PRIMARY KEY ("messagesId", "usersId") + ) + `); + await queryRunner.query(` + CREATE INDEX "IDX_a343387fc560ef378760681c23" ON "message_user_mentions" ("messagesId") + `); + await queryRunner.query(` + CREATE INDEX "IDX_b831eb18ceebd28976239b1e2f" ON "message_user_mentions" ("usersId") + `); + await queryRunner.query(` + CREATE TABLE "message_role_mentions" ( + "messagesId" character varying NOT NULL, + "rolesId" character varying NOT NULL, + CONSTRAINT "PK_74dba92cc300452a6e14b83ed44" PRIMARY KEY ("messagesId", "rolesId") + ) + `); + await queryRunner.query(` + CREATE INDEX "IDX_a8242cf535337a490b0feaea0b" ON "message_role_mentions" ("messagesId") + `); + await queryRunner.query(` + CREATE INDEX "IDX_29d63eb1a458200851bc37d074" ON "message_role_mentions" ("rolesId") + `); + await queryRunner.query(` + CREATE TABLE "message_channel_mentions" ( + "messagesId" character varying NOT NULL, + "channelsId" character varying NOT NULL, + CONSTRAINT "PK_85cb45351497cd9d06a79ced65e" PRIMARY KEY ("messagesId", "channelsId") + ) + `); + await queryRunner.query(` + CREATE INDEX "IDX_2a27102ecd1d81b4582a436092" ON "message_channel_mentions" ("messagesId") + `); + await queryRunner.query(` + CREATE INDEX "IDX_bdb8c09e1464cabf62105bf4b9" ON "message_channel_mentions" ("channelsId") + `); + await queryRunner.query(` + CREATE TABLE "message_stickers" ( + "messagesId" character varying NOT NULL, + "stickersId" character varying NOT NULL, + CONSTRAINT "PK_ed820c4093d0b8cd1d2bcf66087" PRIMARY KEY ("messagesId", "stickersId") + ) + `); + await queryRunner.query(` + CREATE INDEX "IDX_40bb6f23e7cc133292e92829d2" ON "message_stickers" ("messagesId") + `); + await queryRunner.query(` + CREATE INDEX "IDX_e22a70819d07659c7a71c112a1" ON "message_stickers" ("stickersId") + `); + await queryRunner.query(` + ALTER TABLE "relationships" + ADD CONSTRAINT "FK_9af4194bab1250b1c584ae4f1d7" FOREIGN KEY ("from_id") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE NO ACTION + `); + await queryRunner.query(` + ALTER TABLE "relationships" + ADD CONSTRAINT "FK_9c7f6b98a9843b76dce1b0c878b" FOREIGN KEY ("to_id") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE NO ACTION + `); + await queryRunner.query(` + ALTER TABLE "connected_accounts" + ADD CONSTRAINT "FK_f47244225a6a1eac04a3463dd90" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE NO ACTION + `); + await queryRunner.query(` + ALTER TABLE "backup_codes" + ADD CONSTRAINT "FK_70066ea80d2f4b871beda32633b" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE NO ACTION + `); + await queryRunner.query(` + ALTER TABLE "bans" + ADD CONSTRAINT "FK_5999e8e449f80a236ff72023559" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE NO ACTION + `); + await queryRunner.query(` + ALTER TABLE "bans" + ADD CONSTRAINT "FK_9d3ab7dd180ebdd245cdb66ecad" FOREIGN KEY ("guild_id") REFERENCES "guilds"("id") ON DELETE CASCADE ON UPDATE NO ACTION + `); + await queryRunner.query(` + ALTER TABLE "bans" + ADD CONSTRAINT "FK_07ad88c86d1f290d46748410d58" FOREIGN KEY ("executor_id") REFERENCES "users"("id") ON DELETE NO ACTION ON UPDATE NO ACTION + `); + await queryRunner.query(` + ALTER TABLE "recipients" + ADD CONSTRAINT "FK_2f18ee1ba667f233ae86c0ea60e" FOREIGN KEY ("channel_id") REFERENCES "channels"("id") ON DELETE CASCADE ON UPDATE NO ACTION + `); + await queryRunner.query(` + ALTER TABLE "recipients" + ADD CONSTRAINT "FK_6157e8b6ba4e6e3089616481fe2" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE NO ACTION + `); + await queryRunner.query(` + ALTER TABLE "roles" + ADD CONSTRAINT "FK_c32c1ab1c4dc7dcb0278c4b1b8b" FOREIGN KEY ("guild_id") REFERENCES "guilds"("id") ON DELETE CASCADE ON UPDATE NO ACTION + `); + await queryRunner.query(` + ALTER TABLE "members" + ADD CONSTRAINT "FK_28b53062261b996d9c99fa12404" FOREIGN KEY ("id") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE NO ACTION + `); + await queryRunner.query(` + ALTER TABLE "members" + ADD CONSTRAINT "FK_16aceddd5b89825b8ed6029ad1c" FOREIGN KEY ("guild_id") REFERENCES "guilds"("id") ON DELETE CASCADE ON UPDATE NO ACTION + `); + await queryRunner.query(` + ALTER TABLE "webhooks" + ADD CONSTRAINT "FK_487a7af59d189f744fe394368fc" FOREIGN KEY ("guild_id") REFERENCES "guilds"("id") ON DELETE CASCADE ON UPDATE NO ACTION + `); + await queryRunner.query(` + ALTER TABLE "webhooks" + ADD CONSTRAINT "FK_df528cf77e82f8032230e7e37d8" FOREIGN KEY ("channel_id") REFERENCES "channels"("id") ON DELETE CASCADE ON UPDATE NO ACTION + `); + await queryRunner.query(` + ALTER TABLE "webhooks" + ADD CONSTRAINT "FK_c3e5305461931763b56aa905f1c" FOREIGN KEY ("application_id") REFERENCES "applications"("id") ON DELETE CASCADE ON UPDATE NO ACTION + `); + await queryRunner.query(` + ALTER TABLE "webhooks" + ADD CONSTRAINT "FK_0d523f6f997c86e052c49b1455f" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE NO ACTION + `); + await queryRunner.query(` + ALTER TABLE "webhooks" + ADD CONSTRAINT "FK_3a285f4f49c40e0706d3018bc9f" FOREIGN KEY ("source_guild_id") REFERENCES "guilds"("id") ON DELETE CASCADE ON UPDATE NO ACTION + `); + await queryRunner.query(` + ALTER TABLE "stickers" + ADD CONSTRAINT "FK_e7cfa5cefa6661b3fb8fda8ce69" FOREIGN KEY ("pack_id") REFERENCES "sticker_packs"("id") ON DELETE CASCADE ON UPDATE NO ACTION + `); + await queryRunner.query(` + ALTER TABLE "stickers" + ADD CONSTRAINT "FK_193d551d852aca5347ef5c9f205" FOREIGN KEY ("guild_id") REFERENCES "guilds"("id") ON DELETE CASCADE ON UPDATE NO ACTION + `); + await queryRunner.query(` + ALTER TABLE "stickers" + ADD CONSTRAINT "FK_8f4ee73f2bb2325ff980502e158" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE NO ACTION + `); + await queryRunner.query(` + ALTER TABLE "attachments" + ADD CONSTRAINT "FK_623e10eec51ada466c5038979e3" FOREIGN KEY ("message_id") REFERENCES "messages"("id") ON DELETE CASCADE ON UPDATE NO ACTION + `); + await queryRunner.query(` + ALTER TABLE "messages" + ADD CONSTRAINT "FK_86b9109b155eb70c0a2ca3b4b6d" FOREIGN KEY ("channel_id") REFERENCES "channels"("id") ON DELETE CASCADE ON UPDATE NO ACTION + `); + await queryRunner.query(` + ALTER TABLE "messages" + ADD CONSTRAINT "FK_b193588441b085352a4c0109423" FOREIGN KEY ("guild_id") REFERENCES "guilds"("id") ON DELETE CASCADE ON UPDATE NO ACTION + `); + await queryRunner.query(` + ALTER TABLE "messages" + ADD CONSTRAINT "FK_05535bc695e9f7ee104616459d3" FOREIGN KEY ("author_id") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE NO ACTION + `); + await queryRunner.query(` + ALTER TABLE "messages" + ADD CONSTRAINT "FK_b0525304f2262b7014245351c76" FOREIGN KEY ("member_id") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE NO ACTION + `); + await queryRunner.query(` + ALTER TABLE "messages" + ADD CONSTRAINT "FK_f83c04bcf1df4e5c0e7a52ed348" FOREIGN KEY ("webhook_id") REFERENCES "webhooks"("id") ON DELETE NO ACTION ON UPDATE NO ACTION + `); + await queryRunner.query(` + ALTER TABLE "messages" + ADD CONSTRAINT "FK_5d3ec1cb962de6488637fd779d6" FOREIGN KEY ("application_id") REFERENCES "applications"("id") ON DELETE NO ACTION ON UPDATE NO ACTION + `); + await queryRunner.query(` + ALTER TABLE "messages" + ADD CONSTRAINT "FK_61a92bb65b302a76d9c1fcd3174" FOREIGN KEY ("message_reference_id") REFERENCES "messages"("id") ON DELETE NO ACTION ON UPDATE NO ACTION + `); + await queryRunner.query(` + ALTER TABLE "read_states" + ADD CONSTRAINT "FK_40da2fca4e0eaf7a23b5bfc5d34" FOREIGN KEY ("channel_id") REFERENCES "channels"("id") ON DELETE CASCADE ON UPDATE NO ACTION + `); + await queryRunner.query(` + ALTER TABLE "read_states" + ADD CONSTRAINT "FK_195f92e4dd1254a4e348c043763" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE NO ACTION + `); + await queryRunner.query(` + ALTER TABLE "invites" + ADD CONSTRAINT "FK_3f4939aa1461e8af57fea3fb05d" FOREIGN KEY ("guild_id") REFERENCES "guilds"("id") ON DELETE CASCADE ON UPDATE NO ACTION + `); + await queryRunner.query(` + ALTER TABLE "invites" + ADD CONSTRAINT "FK_6a15b051fe5050aa00a4b9ff0f6" FOREIGN KEY ("channel_id") REFERENCES "channels"("id") ON DELETE CASCADE ON UPDATE NO ACTION + `); + await queryRunner.query(` + ALTER TABLE "invites" + ADD CONSTRAINT "FK_15c35422032e0b22b4ada95f48f" FOREIGN KEY ("inviter_id") REFERENCES "users"("id") ON DELETE NO ACTION ON UPDATE NO ACTION + `); + await queryRunner.query(` + ALTER TABLE "invites" + ADD CONSTRAINT "FK_11a0d394f8fc649c19ce5f16b59" FOREIGN KEY ("target_user_id") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE NO ACTION + `); + await queryRunner.query(` + ALTER TABLE "voice_states" + ADD CONSTRAINT "FK_03779ef216d4b0358470d9cb748" FOREIGN KEY ("guild_id") REFERENCES "guilds"("id") ON DELETE CASCADE ON UPDATE NO ACTION + `); + await queryRunner.query(` + ALTER TABLE "voice_states" + ADD CONSTRAINT "FK_9f8d389866b40b6657edd026dd4" FOREIGN KEY ("channel_id") REFERENCES "channels"("id") ON DELETE CASCADE ON UPDATE NO ACTION + `); + await queryRunner.query(` + ALTER TABLE "voice_states" + ADD CONSTRAINT "FK_5fe1d5f931a67e85039c640001b" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE NO ACTION + `); + await queryRunner.query(` + ALTER TABLE "channels" + ADD CONSTRAINT "FK_c253dafe5f3a03ec00cd8fb4581" FOREIGN KEY ("guild_id") REFERENCES "guilds"("id") ON DELETE CASCADE ON UPDATE NO ACTION + `); + await queryRunner.query(` + ALTER TABLE "channels" + ADD CONSTRAINT "FK_3274522d14af40540b1a883fc80" FOREIGN KEY ("parent_id") REFERENCES "channels"("id") ON DELETE NO ACTION ON UPDATE NO ACTION + `); + await queryRunner.query(` + ALTER TABLE "channels" + ADD CONSTRAINT "FK_3873ed438575cce703ecff4fc7b" FOREIGN KEY ("owner_id") REFERENCES "users"("id") ON DELETE NO ACTION ON UPDATE NO ACTION + `); + await queryRunner.query(` + ALTER TABLE "emojis" + ADD CONSTRAINT "FK_4b988e0db89d94cebcf07f598cc" FOREIGN KEY ("guild_id") REFERENCES "guilds"("id") ON DELETE CASCADE ON UPDATE NO ACTION + `); + await queryRunner.query(` + ALTER TABLE "emojis" + ADD CONSTRAINT "FK_fa7ddd5f9a214e28ce596548421" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE NO ACTION ON UPDATE NO ACTION + `); + await queryRunner.query(` + ALTER TABLE "templates" + ADD CONSTRAINT "FK_d7374b7f8f5fbfdececa4fb62e1" FOREIGN KEY ("creator_id") REFERENCES "users"("id") ON DELETE NO ACTION ON UPDATE NO ACTION + `); + await queryRunner.query(` + ALTER TABLE "templates" + ADD CONSTRAINT "FK_445d00eaaea0e60a017a5ed0c11" FOREIGN KEY ("source_guild_id") REFERENCES "guilds"("id") ON DELETE NO ACTION ON UPDATE NO ACTION + `); + await queryRunner.query(` + ALTER TABLE "guilds" + ADD CONSTRAINT "FK_f591a66b8019d87b0fe6c12dad6" FOREIGN KEY ("afk_channel_id") REFERENCES "channels"("id") ON DELETE NO ACTION ON UPDATE NO ACTION + `); + await queryRunner.query(` + ALTER TABLE "guilds" + ADD CONSTRAINT "FK_e2a2f873a64a5cf62526de42325" FOREIGN KEY ("template_id") REFERENCES "templates"("id") ON DELETE NO ACTION ON UPDATE NO ACTION + `); + await queryRunner.query(` + ALTER TABLE "guilds" + ADD CONSTRAINT "FK_fc1a451727e3643ca572a3bb394" FOREIGN KEY ("owner_id") REFERENCES "users"("id") ON DELETE NO ACTION ON UPDATE NO ACTION + `); + await queryRunner.query(` + ALTER TABLE "guilds" + ADD CONSTRAINT "FK_8d450b016dc8bec35f36729e4b0" FOREIGN KEY ("public_updates_channel_id") REFERENCES "channels"("id") ON DELETE NO ACTION ON UPDATE NO ACTION + `); + await queryRunner.query(` + ALTER TABLE "guilds" + ADD CONSTRAINT "FK_95828668aa333460582e0ca6396" FOREIGN KEY ("rules_channel_id") REFERENCES "channels"("id") ON DELETE NO ACTION ON UPDATE NO ACTION + `); + await queryRunner.query(` + ALTER TABLE "guilds" + ADD CONSTRAINT "FK_cfc3d3ad260f8121c95b31a1fce" FOREIGN KEY ("system_channel_id") REFERENCES "channels"("id") ON DELETE NO ACTION ON UPDATE NO ACTION + `); + await queryRunner.query(` + ALTER TABLE "guilds" + ADD CONSTRAINT "FK_9d1d665379eefde7876a17afa99" FOREIGN KEY ("widget_channel_id") REFERENCES "channels"("id") ON DELETE NO ACTION ON UPDATE NO ACTION + `); + await queryRunner.query(` + ALTER TABLE "team_members" + ADD CONSTRAINT "FK_fdad7d5768277e60c40e01cdcea" FOREIGN KEY ("team_id") REFERENCES "teams"("id") ON DELETE CASCADE ON UPDATE NO ACTION + `); + await queryRunner.query(` + ALTER TABLE "team_members" + ADD CONSTRAINT "FK_c2bf4967c8c2a6b845dadfbf3d4" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE NO ACTION + `); + await queryRunner.query(` + ALTER TABLE "teams" + ADD CONSTRAINT "FK_13f00abf7cb6096c43ecaf8c108" FOREIGN KEY ("owner_user_id") REFERENCES "users"("id") ON DELETE NO ACTION ON UPDATE NO ACTION + `); + await queryRunner.query(` + ALTER TABLE "applications" + ADD CONSTRAINT "FK_e57508958bf92b9d9d25231b5e8" FOREIGN KEY ("owner_id") REFERENCES "users"("id") ON DELETE NO ACTION ON UPDATE NO ACTION + `); + await queryRunner.query(` + ALTER TABLE "applications" + ADD CONSTRAINT "FK_a36ed02953077f408d0f3ebc424" FOREIGN KEY ("team_id") REFERENCES "teams"("id") ON DELETE CASCADE ON UPDATE NO ACTION + `); + await queryRunner.query(` + ALTER TABLE "applications" + ADD CONSTRAINT "FK_e5bf78cdbbe9ba91062d74c5aba" FOREIGN KEY ("guild_id") REFERENCES "guilds"("id") ON DELETE NO ACTION ON UPDATE NO ACTION + `); + await queryRunner.query(` + ALTER TABLE "audit_logs" + ADD CONSTRAINT "FK_3cd01cd3ae7aab010310d96ac8e" FOREIGN KEY ("target_id") REFERENCES "users"("id") ON DELETE NO ACTION ON UPDATE NO ACTION + `); + await queryRunner.query(` + ALTER TABLE "audit_logs" + ADD CONSTRAINT "FK_bd2726fd31b35443f2245b93ba0" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE NO ACTION ON UPDATE NO ACTION + `); + await queryRunner.query(` + ALTER TABLE "sessions" + ADD CONSTRAINT "FK_085d540d9f418cfbdc7bd55bb19" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE NO ACTION + `); + await queryRunner.query(` + ALTER TABLE "sticker_packs" + ADD CONSTRAINT "FK_448fafba4355ee1c837bbc865f1" FOREIGN KEY ("coverStickerId") REFERENCES "stickers"("id") ON DELETE NO ACTION ON UPDATE NO ACTION + `); + await queryRunner.query(` + ALTER TABLE "notes" + ADD CONSTRAINT "FK_f9e103f8ae67cb1787063597925" FOREIGN KEY ("owner_id") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE NO ACTION + `); + await queryRunner.query(` + ALTER TABLE "notes" + ADD CONSTRAINT "FK_23e08e5b4481711d573e1abecdc" FOREIGN KEY ("target_id") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE NO ACTION + `); + await queryRunner.query(` + ALTER TABLE "member_roles" + ADD CONSTRAINT "FK_5d7ddc8a5f9c167f548625e772e" FOREIGN KEY ("index") REFERENCES "members"("index") ON DELETE CASCADE ON UPDATE CASCADE + `); + await queryRunner.query(` + ALTER TABLE "member_roles" + ADD CONSTRAINT "FK_e9080e7a7997a0170026d5139c1" FOREIGN KEY ("role_id") REFERENCES "roles"("id") ON DELETE CASCADE ON UPDATE CASCADE + `); + await queryRunner.query(` + ALTER TABLE "message_user_mentions" + ADD CONSTRAINT "FK_a343387fc560ef378760681c236" FOREIGN KEY ("messagesId") REFERENCES "messages"("id") ON DELETE CASCADE ON UPDATE CASCADE + `); + await queryRunner.query(` + ALTER TABLE "message_user_mentions" + ADD CONSTRAINT "FK_b831eb18ceebd28976239b1e2f8" FOREIGN KEY ("usersId") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE + `); + await queryRunner.query(` + ALTER TABLE "message_role_mentions" + ADD CONSTRAINT "FK_a8242cf535337a490b0feaea0b4" FOREIGN KEY ("messagesId") REFERENCES "messages"("id") ON DELETE CASCADE ON UPDATE CASCADE + `); + await queryRunner.query(` + ALTER TABLE "message_role_mentions" + ADD CONSTRAINT "FK_29d63eb1a458200851bc37d074b" FOREIGN KEY ("rolesId") REFERENCES "roles"("id") ON DELETE CASCADE ON UPDATE CASCADE + `); + await queryRunner.query(` + ALTER TABLE "message_channel_mentions" + ADD CONSTRAINT "FK_2a27102ecd1d81b4582a4360921" FOREIGN KEY ("messagesId") REFERENCES "messages"("id") ON DELETE CASCADE ON UPDATE CASCADE + `); + await queryRunner.query(` + ALTER TABLE "message_channel_mentions" + ADD CONSTRAINT "FK_bdb8c09e1464cabf62105bf4b9d" FOREIGN KEY ("channelsId") REFERENCES "channels"("id") ON DELETE CASCADE ON UPDATE CASCADE + `); + await queryRunner.query(` + ALTER TABLE "message_stickers" + ADD CONSTRAINT "FK_40bb6f23e7cc133292e92829d28" FOREIGN KEY ("messagesId") REFERENCES "messages"("id") ON DELETE CASCADE ON UPDATE CASCADE + `); + await queryRunner.query(` + ALTER TABLE "message_stickers" + ADD CONSTRAINT "FK_e22a70819d07659c7a71c112a1f" FOREIGN KEY ("stickersId") REFERENCES "stickers"("id") ON DELETE CASCADE ON UPDATE CASCADE + `); + await queryRunner.query(` + CREATE TABLE "query-result-cache" ( + "id" SERIAL NOT NULL, + "identifier" character varying, + "time" bigint NOT NULL, + "duration" integer NOT NULL, + "query" text NOT NULL, + "result" text NOT NULL, + CONSTRAINT "PK_6a98f758d8bfd010e7e10ffd3d3" PRIMARY KEY ("id") + ) + `); + } + + public async down(queryRunner: QueryRunner): Promise<void> { + await queryRunner.query(` + DROP TABLE "query-result-cache" + `); + await queryRunner.query(` + ALTER TABLE "message_stickers" DROP CONSTRAINT "FK_e22a70819d07659c7a71c112a1f" + `); + await queryRunner.query(` + ALTER TABLE "message_stickers" DROP CONSTRAINT "FK_40bb6f23e7cc133292e92829d28" + `); + await queryRunner.query(` + ALTER TABLE "message_channel_mentions" DROP CONSTRAINT "FK_bdb8c09e1464cabf62105bf4b9d" + `); + await queryRunner.query(` + ALTER TABLE "message_channel_mentions" DROP CONSTRAINT "FK_2a27102ecd1d81b4582a4360921" + `); + await queryRunner.query(` + ALTER TABLE "message_role_mentions" DROP CONSTRAINT "FK_29d63eb1a458200851bc37d074b" + `); + await queryRunner.query(` + ALTER TABLE "message_role_mentions" DROP CONSTRAINT "FK_a8242cf535337a490b0feaea0b4" + `); + await queryRunner.query(` + ALTER TABLE "message_user_mentions" DROP CONSTRAINT "FK_b831eb18ceebd28976239b1e2f8" + `); + await queryRunner.query(` + ALTER TABLE "message_user_mentions" DROP CONSTRAINT "FK_a343387fc560ef378760681c236" + `); + await queryRunner.query(` + ALTER TABLE "member_roles" DROP CONSTRAINT "FK_e9080e7a7997a0170026d5139c1" + `); + await queryRunner.query(` + ALTER TABLE "member_roles" DROP CONSTRAINT "FK_5d7ddc8a5f9c167f548625e772e" + `); + await queryRunner.query(` + ALTER TABLE "notes" DROP CONSTRAINT "FK_23e08e5b4481711d573e1abecdc" + `); + await queryRunner.query(` + ALTER TABLE "notes" DROP CONSTRAINT "FK_f9e103f8ae67cb1787063597925" + `); + await queryRunner.query(` + ALTER TABLE "sticker_packs" DROP CONSTRAINT "FK_448fafba4355ee1c837bbc865f1" + `); + await queryRunner.query(` + ALTER TABLE "sessions" DROP CONSTRAINT "FK_085d540d9f418cfbdc7bd55bb19" + `); + await queryRunner.query(` + ALTER TABLE "audit_logs" DROP CONSTRAINT "FK_bd2726fd31b35443f2245b93ba0" + `); + await queryRunner.query(` + ALTER TABLE "audit_logs" DROP CONSTRAINT "FK_3cd01cd3ae7aab010310d96ac8e" + `); + await queryRunner.query(` + ALTER TABLE "applications" DROP CONSTRAINT "FK_e5bf78cdbbe9ba91062d74c5aba" + `); + await queryRunner.query(` + ALTER TABLE "applications" DROP CONSTRAINT "FK_a36ed02953077f408d0f3ebc424" + `); + await queryRunner.query(` + ALTER TABLE "applications" DROP CONSTRAINT "FK_e57508958bf92b9d9d25231b5e8" + `); + await queryRunner.query(` + ALTER TABLE "teams" DROP CONSTRAINT "FK_13f00abf7cb6096c43ecaf8c108" + `); + await queryRunner.query(` + ALTER TABLE "team_members" DROP CONSTRAINT "FK_c2bf4967c8c2a6b845dadfbf3d4" + `); + await queryRunner.query(` + ALTER TABLE "team_members" DROP CONSTRAINT "FK_fdad7d5768277e60c40e01cdcea" + `); + await queryRunner.query(` + ALTER TABLE "guilds" DROP CONSTRAINT "FK_9d1d665379eefde7876a17afa99" + `); + await queryRunner.query(` + ALTER TABLE "guilds" DROP CONSTRAINT "FK_cfc3d3ad260f8121c95b31a1fce" + `); + await queryRunner.query(` + ALTER TABLE "guilds" DROP CONSTRAINT "FK_95828668aa333460582e0ca6396" + `); + await queryRunner.query(` + ALTER TABLE "guilds" DROP CONSTRAINT "FK_8d450b016dc8bec35f36729e4b0" + `); + await queryRunner.query(` + ALTER TABLE "guilds" DROP CONSTRAINT "FK_fc1a451727e3643ca572a3bb394" + `); + await queryRunner.query(` + ALTER TABLE "guilds" DROP CONSTRAINT "FK_e2a2f873a64a5cf62526de42325" + `); + await queryRunner.query(` + ALTER TABLE "guilds" DROP CONSTRAINT "FK_f591a66b8019d87b0fe6c12dad6" + `); + await queryRunner.query(` + ALTER TABLE "templates" DROP CONSTRAINT "FK_445d00eaaea0e60a017a5ed0c11" + `); + await queryRunner.query(` + ALTER TABLE "templates" DROP CONSTRAINT "FK_d7374b7f8f5fbfdececa4fb62e1" + `); + await queryRunner.query(` + ALTER TABLE "emojis" DROP CONSTRAINT "FK_fa7ddd5f9a214e28ce596548421" + `); + await queryRunner.query(` + ALTER TABLE "emojis" DROP CONSTRAINT "FK_4b988e0db89d94cebcf07f598cc" + `); + await queryRunner.query(` + ALTER TABLE "channels" DROP CONSTRAINT "FK_3873ed438575cce703ecff4fc7b" + `); + await queryRunner.query(` + ALTER TABLE "channels" DROP CONSTRAINT "FK_3274522d14af40540b1a883fc80" + `); + await queryRunner.query(` + ALTER TABLE "channels" DROP CONSTRAINT "FK_c253dafe5f3a03ec00cd8fb4581" + `); + await queryRunner.query(` + ALTER TABLE "voice_states" DROP CONSTRAINT "FK_5fe1d5f931a67e85039c640001b" + `); + await queryRunner.query(` + ALTER TABLE "voice_states" DROP CONSTRAINT "FK_9f8d389866b40b6657edd026dd4" + `); + await queryRunner.query(` + ALTER TABLE "voice_states" DROP CONSTRAINT "FK_03779ef216d4b0358470d9cb748" + `); + await queryRunner.query(` + ALTER TABLE "invites" DROP CONSTRAINT "FK_11a0d394f8fc649c19ce5f16b59" + `); + await queryRunner.query(` + ALTER TABLE "invites" DROP CONSTRAINT "FK_15c35422032e0b22b4ada95f48f" + `); + await queryRunner.query(` + ALTER TABLE "invites" DROP CONSTRAINT "FK_6a15b051fe5050aa00a4b9ff0f6" + `); + await queryRunner.query(` + ALTER TABLE "invites" DROP CONSTRAINT "FK_3f4939aa1461e8af57fea3fb05d" + `); + await queryRunner.query(` + ALTER TABLE "read_states" DROP CONSTRAINT "FK_195f92e4dd1254a4e348c043763" + `); + await queryRunner.query(` + ALTER TABLE "read_states" DROP CONSTRAINT "FK_40da2fca4e0eaf7a23b5bfc5d34" + `); + await queryRunner.query(` + ALTER TABLE "messages" DROP CONSTRAINT "FK_61a92bb65b302a76d9c1fcd3174" + `); + await queryRunner.query(` + ALTER TABLE "messages" DROP CONSTRAINT "FK_5d3ec1cb962de6488637fd779d6" + `); + await queryRunner.query(` + ALTER TABLE "messages" DROP CONSTRAINT "FK_f83c04bcf1df4e5c0e7a52ed348" + `); + await queryRunner.query(` + ALTER TABLE "messages" DROP CONSTRAINT "FK_b0525304f2262b7014245351c76" + `); + await queryRunner.query(` + ALTER TABLE "messages" DROP CONSTRAINT "FK_05535bc695e9f7ee104616459d3" + `); + await queryRunner.query(` + ALTER TABLE "messages" DROP CONSTRAINT "FK_b193588441b085352a4c0109423" + `); + await queryRunner.query(` + ALTER TABLE "messages" DROP CONSTRAINT "FK_86b9109b155eb70c0a2ca3b4b6d" + `); + await queryRunner.query(` + ALTER TABLE "attachments" DROP CONSTRAINT "FK_623e10eec51ada466c5038979e3" + `); + await queryRunner.query(` + ALTER TABLE "stickers" DROP CONSTRAINT "FK_8f4ee73f2bb2325ff980502e158" + `); + await queryRunner.query(` + ALTER TABLE "stickers" DROP CONSTRAINT "FK_193d551d852aca5347ef5c9f205" + `); + await queryRunner.query(` + ALTER TABLE "stickers" DROP CONSTRAINT "FK_e7cfa5cefa6661b3fb8fda8ce69" + `); + await queryRunner.query(` + ALTER TABLE "webhooks" DROP CONSTRAINT "FK_3a285f4f49c40e0706d3018bc9f" + `); + await queryRunner.query(` + ALTER TABLE "webhooks" DROP CONSTRAINT "FK_0d523f6f997c86e052c49b1455f" + `); + await queryRunner.query(` + ALTER TABLE "webhooks" DROP CONSTRAINT "FK_c3e5305461931763b56aa905f1c" + `); + await queryRunner.query(` + ALTER TABLE "webhooks" DROP CONSTRAINT "FK_df528cf77e82f8032230e7e37d8" + `); + await queryRunner.query(` + ALTER TABLE "webhooks" DROP CONSTRAINT "FK_487a7af59d189f744fe394368fc" + `); + await queryRunner.query(` + ALTER TABLE "members" DROP CONSTRAINT "FK_16aceddd5b89825b8ed6029ad1c" + `); + await queryRunner.query(` + ALTER TABLE "members" DROP CONSTRAINT "FK_28b53062261b996d9c99fa12404" + `); + await queryRunner.query(` + ALTER TABLE "roles" DROP CONSTRAINT "FK_c32c1ab1c4dc7dcb0278c4b1b8b" + `); + await queryRunner.query(` + ALTER TABLE "recipients" DROP CONSTRAINT "FK_6157e8b6ba4e6e3089616481fe2" + `); + await queryRunner.query(` + ALTER TABLE "recipients" DROP CONSTRAINT "FK_2f18ee1ba667f233ae86c0ea60e" + `); + await queryRunner.query(` + ALTER TABLE "bans" DROP CONSTRAINT "FK_07ad88c86d1f290d46748410d58" + `); + await queryRunner.query(` + ALTER TABLE "bans" DROP CONSTRAINT "FK_9d3ab7dd180ebdd245cdb66ecad" + `); + await queryRunner.query(` + ALTER TABLE "bans" DROP CONSTRAINT "FK_5999e8e449f80a236ff72023559" + `); + await queryRunner.query(` + ALTER TABLE "backup_codes" DROP CONSTRAINT "FK_70066ea80d2f4b871beda32633b" + `); + await queryRunner.query(` + ALTER TABLE "connected_accounts" DROP CONSTRAINT "FK_f47244225a6a1eac04a3463dd90" + `); + await queryRunner.query(` + ALTER TABLE "relationships" DROP CONSTRAINT "FK_9c7f6b98a9843b76dce1b0c878b" + `); + await queryRunner.query(` + ALTER TABLE "relationships" DROP CONSTRAINT "FK_9af4194bab1250b1c584ae4f1d7" + `); + await queryRunner.query(` + DROP INDEX "public"."IDX_e22a70819d07659c7a71c112a1" + `); + await queryRunner.query(` + DROP INDEX "public"."IDX_40bb6f23e7cc133292e92829d2" + `); + await queryRunner.query(` + DROP TABLE "message_stickers" + `); + await queryRunner.query(` + DROP INDEX "public"."IDX_bdb8c09e1464cabf62105bf4b9" + `); + await queryRunner.query(` + DROP INDEX "public"."IDX_2a27102ecd1d81b4582a436092" + `); + await queryRunner.query(` + DROP TABLE "message_channel_mentions" + `); + await queryRunner.query(` + DROP INDEX "public"."IDX_29d63eb1a458200851bc37d074" + `); + await queryRunner.query(` + DROP INDEX "public"."IDX_a8242cf535337a490b0feaea0b" + `); + await queryRunner.query(` + DROP TABLE "message_role_mentions" + `); + await queryRunner.query(` + DROP INDEX "public"."IDX_b831eb18ceebd28976239b1e2f" + `); + await queryRunner.query(` + DROP INDEX "public"."IDX_a343387fc560ef378760681c23" + `); + await queryRunner.query(` + DROP TABLE "message_user_mentions" + `); + await queryRunner.query(` + DROP INDEX "public"."IDX_e9080e7a7997a0170026d5139c" + `); + await queryRunner.query(` + DROP INDEX "public"."IDX_5d7ddc8a5f9c167f548625e772" + `); + await queryRunner.query(` + DROP TABLE "member_roles" + `); + await queryRunner.query(` + DROP TABLE "notes" + `); + await queryRunner.query(` + DROP TABLE "client_release" + `); + await queryRunner.query(` + DROP TABLE "sticker_packs" + `); + await queryRunner.query(` + DROP TABLE "sessions" + `); + await queryRunner.query(` + DROP TABLE "rate_limits" + `); + await queryRunner.query(` + DROP TABLE "categories" + `); + await queryRunner.query(` + DROP TABLE "audit_logs" + `); + await queryRunner.query(` + DROP TABLE "applications" + `); + await queryRunner.query(` + DROP TABLE "teams" + `); + await queryRunner.query(` + DROP TABLE "team_members" + `); + await queryRunner.query(` + DROP TABLE "guilds" + `); + await queryRunner.query(` + DROP TABLE "templates" + `); + await queryRunner.query(` + DROP TABLE "emojis" + `); + await queryRunner.query(` + DROP TABLE "channels" + `); + await queryRunner.query(` + DROP TABLE "voice_states" + `); + await queryRunner.query(` + DROP TABLE "invites" + `); + await queryRunner.query(` + DROP INDEX "public"."IDX_0abf8b443321bd3cf7f81ee17a" + `); + await queryRunner.query(` + DROP TABLE "read_states" + `); + await queryRunner.query(` + DROP INDEX "public"."IDX_3ed7a60fb7dbe04e1ba9332a8b" + `); + await queryRunner.query(` + DROP INDEX "public"."IDX_05535bc695e9f7ee104616459d" + `); + await queryRunner.query(` + DROP INDEX "public"."IDX_86b9109b155eb70c0a2ca3b4b6" + `); + await queryRunner.query(` + DROP TABLE "messages" + `); + await queryRunner.query(` + DROP TABLE "attachments" + `); + await queryRunner.query(` + DROP TABLE "stickers" + `); + await queryRunner.query(` + DROP TABLE "webhooks" + `); + await queryRunner.query(` + DROP INDEX "public"."IDX_bb2bf9386ac443afbbbf9f12d3" + `); + await queryRunner.query(` + DROP TABLE "members" + `); + await queryRunner.query(` + DROP TABLE "roles" + `); + await queryRunner.query(` + DROP TABLE "recipients" + `); + await queryRunner.query(` + DROP TABLE "bans" + `); + await queryRunner.query(` + DROP TABLE "backup_codes" + `); + await queryRunner.query(` + DROP TABLE "users" + `); + await queryRunner.query(` + DROP TABLE "connected_accounts" + `); + await queryRunner.query(` + DROP INDEX "public"."IDX_a0b2ff0a598df0b0d055934a17" + `); + await queryRunner.query(` + DROP TABLE "relationships" + `); + await queryRunner.query(` + DROP TABLE "config" + `); + } +} diff --git a/src/util/migrations/postgres/1659921826567-premium_since_as_date.ts b/src/util/migrations/postgres/1659921826567-premium_since_as_date.ts new file mode 100644
index 00000000..7543ce85 --- /dev/null +++ b/src/util/migrations/postgres/1659921826567-premium_since_as_date.ts
@@ -0,0 +1,25 @@ +import { MigrationInterface, QueryRunner } from "typeorm"; + +export class premiumSinceAsDate1659921826567 implements MigrationInterface { + name = "premiumSinceAsDate1659921826567"; + + public async up(queryRunner: QueryRunner): Promise<void> { + await queryRunner.query(` + ALTER TABLE "members" DROP COLUMN "premium_since" + `); + await queryRunner.query(` + ALTER TABLE "members" + ADD "premium_since" TIMESTAMP + `); + } + + public async down(queryRunner: QueryRunner): Promise<void> { + await queryRunner.query(` + ALTER TABLE "members" DROP COLUMN "premium_since" + `); + await queryRunner.query(` + ALTER TABLE "members" + ADD "premium_since" bigint + `); + } +} diff --git a/src/util/migrations/postgres/1660130561959-updated-applications.ts b/src/util/migrations/postgres/1660130561959-updated-applications.ts new file mode 100644
index 00000000..3d47d6d3 --- /dev/null +++ b/src/util/migrations/postgres/1660130561959-updated-applications.ts
@@ -0,0 +1,181 @@ +import { MigrationInterface, QueryRunner } from "typeorm"; + +export class updatedApplications1660130561959 implements MigrationInterface { + name = "updatedApplications1660130561959"; + + public async up(queryRunner: QueryRunner): Promise<void> { + await queryRunner.query(` + ALTER TABLE "applications" DROP CONSTRAINT "FK_e5bf78cdbbe9ba91062d74c5aba" + `); + await queryRunner.query(` + ALTER TABLE "applications" DROP COLUMN "rpc_origins" + `); + await queryRunner.query(` + ALTER TABLE "applications" DROP COLUMN "primary_sku_id" + `); + await queryRunner.query(` + ALTER TABLE "applications" DROP COLUMN "slug" + `); + await queryRunner.query(` + ALTER TABLE "applications" DROP COLUMN "guild_id" + `); + await queryRunner.query(` + ALTER TABLE "applications" + ADD "type" text + `); + await queryRunner.query(` + ALTER TABLE "applications" + ADD "hook" boolean NOT NULL + `); + await queryRunner.query(` + ALTER TABLE "applications" + ADD "redirect_uris" text + `); + await queryRunner.query(` + ALTER TABLE "applications" + ADD "rpc_application_state" integer + `); + await queryRunner.query(` + ALTER TABLE "applications" + ADD "store_application_state" integer + `); + await queryRunner.query(` + ALTER TABLE "applications" + ADD "verification_state" integer + `); + await queryRunner.query(` + ALTER TABLE "applications" + ADD "interactions_endpoint_url" character varying + `); + await queryRunner.query(` + ALTER TABLE "applications" + ADD "integration_public" boolean + `); + await queryRunner.query(` + ALTER TABLE "applications" + ADD "integration_require_code_grant" boolean + `); + await queryRunner.query(` + ALTER TABLE "applications" + ADD "discoverability_state" integer + `); + await queryRunner.query(` + ALTER TABLE "applications" + ADD "discovery_eligibility_flags" integer + `); + await queryRunner.query(` + ALTER TABLE "applications" + ADD "tags" text + `); + await queryRunner.query(` + ALTER TABLE "applications" + ADD "install_params" text + `); + await queryRunner.query(` + ALTER TABLE "applications" + ADD "bot_user_id" character varying + `); + await queryRunner.query(` + ALTER TABLE "applications" + ADD CONSTRAINT "UQ_2ce5a55796fe4c2f77ece57a647" UNIQUE ("bot_user_id") + `); + await queryRunner.query(` + ALTER TABLE "applications" + ALTER COLUMN "description" DROP NOT NULL + `); + await queryRunner.query(` + ALTER TABLE "applications" DROP COLUMN "flags" + `); + await queryRunner.query(` + ALTER TABLE "applications" + ADD "flags" integer NOT NULL + `); + await queryRunner.query(` + ALTER TABLE "applications" + ADD CONSTRAINT "FK_2ce5a55796fe4c2f77ece57a647" FOREIGN KEY ("bot_user_id") REFERENCES "users"("id") ON DELETE NO ACTION ON UPDATE NO ACTION + `); + } + + public async down(queryRunner: QueryRunner): Promise<void> { + await queryRunner.query(` + ALTER TABLE "applications" DROP CONSTRAINT "FK_2ce5a55796fe4c2f77ece57a647" + `); + await queryRunner.query(` + ALTER TABLE "applications" DROP COLUMN "flags" + `); + await queryRunner.query(` + ALTER TABLE "applications" + ADD "flags" character varying NOT NULL + `); + await queryRunner.query(` + ALTER TABLE "applications" + ALTER COLUMN "description" + SET NOT NULL + `); + await queryRunner.query(` + ALTER TABLE "applications" DROP CONSTRAINT "UQ_2ce5a55796fe4c2f77ece57a647" + `); + await queryRunner.query(` + ALTER TABLE "applications" DROP COLUMN "bot_user_id" + `); + await queryRunner.query(` + ALTER TABLE "applications" DROP COLUMN "install_params" + `); + await queryRunner.query(` + ALTER TABLE "applications" DROP COLUMN "tags" + `); + await queryRunner.query(` + ALTER TABLE "applications" DROP COLUMN "discovery_eligibility_flags" + `); + await queryRunner.query(` + ALTER TABLE "applications" DROP COLUMN "discoverability_state" + `); + await queryRunner.query(` + ALTER TABLE "applications" DROP COLUMN "integration_require_code_grant" + `); + await queryRunner.query(` + ALTER TABLE "applications" DROP COLUMN "integration_public" + `); + await queryRunner.query(` + ALTER TABLE "applications" DROP COLUMN "interactions_endpoint_url" + `); + await queryRunner.query(` + ALTER TABLE "applications" DROP COLUMN "verification_state" + `); + await queryRunner.query(` + ALTER TABLE "applications" DROP COLUMN "store_application_state" + `); + await queryRunner.query(` + ALTER TABLE "applications" DROP COLUMN "rpc_application_state" + `); + await queryRunner.query(` + ALTER TABLE "applications" DROP COLUMN "redirect_uris" + `); + await queryRunner.query(` + ALTER TABLE "applications" DROP COLUMN "hook" + `); + await queryRunner.query(` + ALTER TABLE "applications" DROP COLUMN "type" + `); + await queryRunner.query(` + ALTER TABLE "applications" + ADD "guild_id" character varying + `); + await queryRunner.query(` + ALTER TABLE "applications" + ADD "slug" character varying + `); + await queryRunner.query(` + ALTER TABLE "applications" + ADD "primary_sku_id" character varying + `); + await queryRunner.query(` + ALTER TABLE "applications" + ADD "rpc_origins" text + `); + await queryRunner.query(` + ALTER TABLE "applications" + ADD CONSTRAINT "FK_e5bf78cdbbe9ba91062d74c5aba" FOREIGN KEY ("guild_id") REFERENCES "guilds"("id") ON DELETE NO ACTION ON UPDATE NO ACTION + `); + } +} diff --git a/src/util/migrations/postgres/1660257815436-CodeCleanup2.ts b/src/util/migrations/postgres/1660257815436-CodeCleanup2.ts new file mode 100644
index 00000000..3e4167e9 --- /dev/null +++ b/src/util/migrations/postgres/1660257815436-CodeCleanup2.ts
@@ -0,0 +1,58 @@ +import { MigrationInterface, QueryRunner } from "typeorm"; + +export class CodeCleanup21660257815436 implements MigrationInterface { + name = "CodeCleanup21660257815436"; + + public async up(queryRunner: QueryRunner): Promise<void> { + await queryRunner.query(` + CREATE TABLE "user_settings" ( + "id" character varying NOT NULL, + "afk_timeout" integer, + "allow_accessibility_detection" boolean, + "animate_emoji" boolean, + "animate_stickers" integer, + "contact_sync_enabled" boolean, + "convert_emoticons" boolean, + "custom_status" text, + "default_guilds_restricted" boolean, + "detect_platform_accounts" boolean, + "developer_mode" boolean, + "disable_games_tab" boolean, + "enable_tts_command" boolean, + "explicit_content_filter" integer, + "friend_source_flags" text, + "gateway_connected" boolean, + "gif_auto_play" boolean, + "guild_folders" text, + "guild_positions" text, + "inline_attachment_media" boolean, + "inline_embed_media" boolean, + "locale" character varying, + "message_display_compact" boolean, + "native_phone_integration_enabled" boolean, + "render_embeds" boolean, + "render_reactions" boolean, + "restricted_guilds" text, + "show_current_game" boolean, + "status" character varying, + "stream_notifications_enabled" boolean, + "theme" character varying, + "timezone_offset" integer, + CONSTRAINT "PK_00f004f5922a0744d174530d639" PRIMARY KEY ("id") + ) + `); + await queryRunner.query(` + ALTER TABLE "guilds" + ADD "premium_progress_bar_enabled" boolean + `); + } + + public async down(queryRunner: QueryRunner): Promise<void> { + await queryRunner.query(` + ALTER TABLE "guilds" DROP COLUMN "premium_progress_bar_enabled" + `); + await queryRunner.query(` + DROP TABLE "user_settings" + `); + } +} diff --git a/src/util/migrations/postgres/1660258372154-CodeCleanup3.ts b/src/util/migrations/postgres/1660258372154-CodeCleanup3.ts new file mode 100644
index 00000000..3071b59f --- /dev/null +++ b/src/util/migrations/postgres/1660258372154-CodeCleanup3.ts
@@ -0,0 +1,18 @@ +import { MigrationInterface, QueryRunner } from "typeorm"; + +export class CodeCleanup31660258372154 implements MigrationInterface { + name = "CodeCleanup31660258372154"; + + public async up(queryRunner: QueryRunner): Promise<void> { + await queryRunner.query(` + ALTER TABLE "users" DROP COLUMN "settings" + `); + } + + public async down(queryRunner: QueryRunner): Promise<void> { + await queryRunner.query(` + ALTER TABLE "users" + ADD "settings" text NOT NULL + `); + } +} diff --git a/src/util/migrations/postgres/1660260565996-CodeCleanup4.ts b/src/util/migrations/postgres/1660260565996-CodeCleanup4.ts new file mode 100644
index 00000000..c2e9aa85 --- /dev/null +++ b/src/util/migrations/postgres/1660260565996-CodeCleanup4.ts
@@ -0,0 +1,32 @@ +import { MigrationInterface, QueryRunner } from "typeorm"; + +export class CodeCleanup41660260565996 implements MigrationInterface { + name = "CodeCleanup41660260565996"; + + public async up(queryRunner: QueryRunner): Promise<void> { + await queryRunner.query(` + ALTER TABLE "users" + ADD "settingsId" character varying + `); + await queryRunner.query(` + ALTER TABLE "users" + ADD CONSTRAINT "UQ_76ba283779c8441fd5ff819c8cf" UNIQUE ("settingsId") + `); + await queryRunner.query(` + ALTER TABLE "users" + ADD CONSTRAINT "FK_76ba283779c8441fd5ff819c8cf" FOREIGN KEY ("settingsId") REFERENCES "user_settings"("id") ON DELETE NO ACTION ON UPDATE NO ACTION + `); + } + + public async down(queryRunner: QueryRunner): Promise<void> { + await queryRunner.query(` + ALTER TABLE "users" DROP CONSTRAINT "FK_76ba283779c8441fd5ff819c8cf" + `); + await queryRunner.query(` + ALTER TABLE "users" DROP CONSTRAINT "UQ_76ba283779c8441fd5ff819c8cf" + `); + await queryRunner.query(` + ALTER TABLE "users" DROP COLUMN "settingsId" + `); + } +} diff --git a/src/util/migrations/postgres/1660265907544-CodeCleanup5.ts b/src/util/migrations/postgres/1660265907544-CodeCleanup5.ts new file mode 100644
index 00000000..0f098e1f --- /dev/null +++ b/src/util/migrations/postgres/1660265907544-CodeCleanup5.ts
@@ -0,0 +1,25 @@ +import { MigrationInterface, QueryRunner } from "typeorm"; + +export class CodeCleanup51660265907544 implements MigrationInterface { + name = "CodeCleanup51660265907544"; + + public async up(queryRunner: QueryRunner): Promise<void> { + await queryRunner.query(` + ALTER TABLE "channels" + ADD "flags" integer + `); + await queryRunner.query(` + ALTER TABLE "channels" + ADD "default_thread_rate_limit_per_user" integer + `); + } + + public async down(queryRunner: QueryRunner): Promise<void> { + await queryRunner.query(` + ALTER TABLE "channels" DROP COLUMN "default_thread_rate_limit_per_user" + `); + await queryRunner.query(` + ALTER TABLE "channels" DROP COLUMN "flags" + `); + } +} diff --git a/src/util/migrations/postgres/1660416055566-InvitersAreDeletable.ts b/src/util/migrations/postgres/1660416055566-InvitersAreDeletable.ts new file mode 100644
index 00000000..e8321ede --- /dev/null +++ b/src/util/migrations/postgres/1660416055566-InvitersAreDeletable.ts
@@ -0,0 +1,25 @@ +import { MigrationInterface, QueryRunner } from "typeorm"; + +export class InvitersAreDeletable1660416055566 implements MigrationInterface { + name = "InvitersAreDeletable1660416055566"; + + public async up(queryRunner: QueryRunner): Promise<void> { + await queryRunner.query(` + ALTER TABLE "invites" DROP CONSTRAINT "FK_15c35422032e0b22b4ada95f48f" + `); + await queryRunner.query(` + ALTER TABLE "invites" + ADD CONSTRAINT "FK_15c35422032e0b22b4ada95f48f" FOREIGN KEY ("inviter_id") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE NO ACTION + `); + } + + public async down(queryRunner: QueryRunner): Promise<void> { + await queryRunner.query(` + ALTER TABLE "invites" DROP CONSTRAINT "FK_15c35422032e0b22b4ada95f48f" + `); + await queryRunner.query(` + ALTER TABLE "invites" + ADD CONSTRAINT "FK_15c35422032e0b22b4ada95f48f" FOREIGN KEY ("inviter_id") REFERENCES "users"("id") ON DELETE NO ACTION ON UPDATE NO ACTION + `); + } +} diff --git a/src/util/migrations/postgres/1660549242936-fix_nullables.ts b/src/util/migrations/postgres/1660549242936-fix_nullables.ts new file mode 100644
index 00000000..07fd52f7 --- /dev/null +++ b/src/util/migrations/postgres/1660549242936-fix_nullables.ts
@@ -0,0 +1,29 @@ +import { MigrationInterface, QueryRunner } from "typeorm"; + +export class fixNullables1660549242936 implements MigrationInterface { + name = "fixNullables1660549242936"; + + public async up(queryRunner: QueryRunner): Promise<void> { + await queryRunner.query(` + ALTER TABLE "users" + ALTER COLUMN "bio" DROP NOT NULL + `); + await queryRunner.query(` + ALTER TABLE "users" + ALTER COLUMN "mfa_enabled" DROP NOT NULL + `); + } + + public async down(queryRunner: QueryRunner): Promise<void> { + await queryRunner.query(` + ALTER TABLE "users" + ALTER COLUMN "mfa_enabled" + SET NOT NULL + `); + await queryRunner.query(` + ALTER TABLE "users" + ALTER COLUMN "bio" + SET NOT NULL + `); + } +} diff --git a/src/util/migrations/sqlite/1659899662635-initial.ts b/src/util/migrations/sqlite/1659899662635-initial.ts new file mode 100644
index 00000000..dffaa51d --- /dev/null +++ b/src/util/migrations/sqlite/1659899662635-initial.ts
@@ -0,0 +1,3528 @@ +import { MigrationInterface, QueryRunner } from "typeorm"; + +export class initial1659899662635 implements MigrationInterface { + name = "initial1659899662635"; + + public async up(queryRunner: QueryRunner): Promise<void> { + await queryRunner.query(` + CREATE TABLE "config" ("key" varchar PRIMARY KEY NOT NULL, "value" text) + `); + await queryRunner.query(` + CREATE TABLE "relationships" ( + "id" varchar PRIMARY KEY NOT NULL, + "from_id" varchar NOT NULL, + "to_id" varchar NOT NULL, + "nickname" varchar, + "type" integer NOT NULL + ) + `); + await queryRunner.query(` + CREATE UNIQUE INDEX "IDX_a0b2ff0a598df0b0d055934a17" ON "relationships" ("from_id", "to_id") + `); + await queryRunner.query(` + CREATE TABLE "connected_accounts" ( + "id" varchar PRIMARY KEY NOT NULL, + "user_id" varchar, + "access_token" varchar NOT NULL, + "friend_sync" boolean NOT NULL, + "name" varchar NOT NULL, + "revoked" boolean NOT NULL, + "show_activity" boolean NOT NULL, + "type" varchar NOT NULL, + "verified" boolean NOT NULL, + "visibility" integer NOT NULL + ) + `); + await queryRunner.query(` + CREATE TABLE "users" ( + "id" varchar PRIMARY KEY NOT NULL, + "username" varchar NOT NULL, + "discriminator" varchar NOT NULL, + "avatar" varchar, + "accent_color" integer, + "banner" varchar, + "phone" varchar, + "desktop" boolean NOT NULL, + "mobile" boolean NOT NULL, + "premium" boolean NOT NULL, + "premium_type" integer NOT NULL, + "bot" boolean NOT NULL, + "bio" varchar NOT NULL, + "system" boolean NOT NULL, + "nsfw_allowed" boolean NOT NULL, + "mfa_enabled" boolean NOT NULL, + "totp_secret" varchar, + "totp_last_ticket" varchar, + "created_at" datetime NOT NULL, + "premium_since" datetime, + "verified" boolean NOT NULL, + "disabled" boolean NOT NULL, + "deleted" boolean NOT NULL, + "email" varchar, + "flags" varchar NOT NULL, + "public_flags" integer NOT NULL, + "rights" bigint NOT NULL, + "data" text NOT NULL, + "fingerprints" text NOT NULL, + "settings" text NOT NULL, + "extended_settings" text NOT NULL, + "notes" text NOT NULL + ) + `); + await queryRunner.query(` + CREATE TABLE "backup_codes" ( + "id" varchar PRIMARY KEY NOT NULL, + "code" varchar NOT NULL, + "consumed" boolean NOT NULL, + "expired" boolean NOT NULL, + "user_id" varchar + ) + `); + await queryRunner.query(` + CREATE TABLE "bans" ( + "id" varchar PRIMARY KEY NOT NULL, + "user_id" varchar, + "guild_id" varchar, + "executor_id" varchar, + "ip" varchar NOT NULL, + "reason" varchar + ) + `); + await queryRunner.query(` + CREATE TABLE "recipients" ( + "id" varchar PRIMARY KEY NOT NULL, + "channel_id" varchar NOT NULL, + "user_id" varchar NOT NULL, + "closed" boolean NOT NULL DEFAULT (0) + ) + `); + await queryRunner.query(` + CREATE TABLE "roles" ( + "id" varchar PRIMARY KEY NOT NULL, + "guild_id" varchar, + "color" integer NOT NULL, + "hoist" boolean NOT NULL, + "managed" boolean NOT NULL, + "mentionable" boolean NOT NULL, + "name" varchar NOT NULL, + "permissions" varchar NOT NULL, + "position" integer NOT NULL, + "icon" varchar, + "unicode_emoji" varchar, + "tags" text + ) + `); + await queryRunner.query(` + CREATE TABLE "members" ( + "index" integer PRIMARY KEY AUTOINCREMENT NOT NULL, + "id" varchar NOT NULL, + "guild_id" varchar NOT NULL, + "nick" varchar, + "joined_at" datetime NOT NULL, + "premium_since" bigint, + "deaf" boolean NOT NULL, + "mute" boolean NOT NULL, + "pending" boolean NOT NULL, + "settings" text NOT NULL, + "last_message_id" varchar, + "joined_by" varchar + ) + `); + await queryRunner.query(` + CREATE UNIQUE INDEX "IDX_bb2bf9386ac443afbbbf9f12d3" ON "members" ("id", "guild_id") + `); + await queryRunner.query(` + CREATE TABLE "webhooks" ( + "id" varchar PRIMARY KEY NOT NULL, + "type" integer NOT NULL, + "name" varchar, + "avatar" varchar, + "token" varchar, + "guild_id" varchar, + "channel_id" varchar, + "application_id" varchar, + "user_id" varchar, + "source_guild_id" varchar + ) + `); + await queryRunner.query(` + CREATE TABLE "stickers" ( + "id" varchar PRIMARY KEY NOT NULL, + "name" varchar NOT NULL, + "description" varchar, + "available" boolean, + "tags" varchar, + "pack_id" varchar, + "guild_id" varchar, + "user_id" varchar, + "type" integer NOT NULL, + "format_type" integer NOT NULL + ) + `); + await queryRunner.query(` + CREATE TABLE "attachments" ( + "id" varchar PRIMARY KEY NOT NULL, + "filename" varchar NOT NULL, + "size" integer NOT NULL, + "url" varchar NOT NULL, + "proxy_url" varchar NOT NULL, + "height" integer, + "width" integer, + "content_type" varchar, + "message_id" varchar + ) + `); + await queryRunner.query(` + CREATE TABLE "messages" ( + "id" varchar PRIMARY KEY NOT NULL, + "channel_id" varchar, + "guild_id" varchar, + "author_id" varchar, + "member_id" varchar, + "webhook_id" varchar, + "application_id" varchar, + "content" varchar, + "timestamp" datetime NOT NULL DEFAULT (datetime('now')), + "edited_timestamp" datetime, + "tts" boolean, + "mention_everyone" boolean, + "embeds" text NOT NULL, + "reactions" text NOT NULL, + "nonce" text, + "pinned" boolean, + "type" integer NOT NULL, + "activity" text, + "flags" varchar, + "message_reference" text, + "interaction" text, + "components" text, + "message_reference_id" varchar + ) + `); + await queryRunner.query(` + CREATE INDEX "IDX_86b9109b155eb70c0a2ca3b4b6" ON "messages" ("channel_id") + `); + await queryRunner.query(` + CREATE INDEX "IDX_05535bc695e9f7ee104616459d" ON "messages" ("author_id") + `); + await queryRunner.query(` + CREATE UNIQUE INDEX "IDX_3ed7a60fb7dbe04e1ba9332a8b" ON "messages" ("channel_id", "id") + `); + await queryRunner.query(` + CREATE TABLE "read_states" ( + "id" varchar PRIMARY KEY NOT NULL, + "channel_id" varchar NOT NULL, + "user_id" varchar NOT NULL, + "last_message_id" varchar, + "public_ack" varchar, + "notifications_cursor" varchar, + "last_pin_timestamp" datetime, + "mention_count" integer + ) + `); + await queryRunner.query(` + CREATE UNIQUE INDEX "IDX_0abf8b443321bd3cf7f81ee17a" ON "read_states" ("channel_id", "user_id") + `); + await queryRunner.query(` + CREATE TABLE "invites" ( + "code" varchar PRIMARY KEY NOT NULL, + "temporary" boolean NOT NULL, + "uses" integer NOT NULL, + "max_uses" integer NOT NULL, + "max_age" integer NOT NULL, + "created_at" datetime NOT NULL, + "expires_at" datetime NOT NULL, + "guild_id" varchar, + "channel_id" varchar, + "inviter_id" varchar, + "target_user_id" varchar, + "target_user_type" integer, + "vanity_url" boolean + ) + `); + await queryRunner.query(` + CREATE TABLE "voice_states" ( + "id" varchar PRIMARY KEY NOT NULL, + "guild_id" varchar, + "channel_id" varchar, + "user_id" varchar, + "session_id" varchar NOT NULL, + "token" varchar, + "deaf" boolean NOT NULL, + "mute" boolean NOT NULL, + "self_deaf" boolean NOT NULL, + "self_mute" boolean NOT NULL, + "self_stream" boolean, + "self_video" boolean NOT NULL, + "suppress" boolean NOT NULL, + "request_to_speak_timestamp" datetime + ) + `); + await queryRunner.query(` + CREATE TABLE "channels" ( + "id" varchar PRIMARY KEY NOT NULL, + "created_at" datetime NOT NULL, + "name" varchar, + "icon" text, + "type" integer NOT NULL, + "last_message_id" varchar, + "guild_id" varchar, + "parent_id" varchar, + "owner_id" varchar, + "last_pin_timestamp" integer, + "default_auto_archive_duration" integer, + "position" integer, + "permission_overwrites" text, + "video_quality_mode" integer, + "bitrate" integer, + "user_limit" integer, + "nsfw" boolean, + "rate_limit_per_user" integer, + "topic" varchar, + "retention_policy_id" varchar + ) + `); + await queryRunner.query(` + CREATE TABLE "emojis" ( + "id" varchar PRIMARY KEY NOT NULL, + "animated" boolean NOT NULL, + "available" boolean NOT NULL, + "guild_id" varchar NOT NULL, + "user_id" varchar, + "managed" boolean NOT NULL, + "name" varchar NOT NULL, + "require_colons" boolean NOT NULL, + "roles" text NOT NULL, + "groups" text + ) + `); + await queryRunner.query(` + CREATE TABLE "templates" ( + "id" varchar PRIMARY KEY NOT NULL, + "code" varchar NOT NULL, + "name" varchar NOT NULL, + "description" varchar, + "usage_count" integer, + "creator_id" varchar, + "created_at" datetime NOT NULL, + "updated_at" datetime NOT NULL, + "source_guild_id" varchar, + "serialized_source_guild" text NOT NULL, + CONSTRAINT "UQ_be38737bf339baf63b1daeffb55" UNIQUE ("code") + ) + `); + await queryRunner.query(` + CREATE TABLE "guilds" ( + "id" varchar PRIMARY KEY NOT NULL, + "afk_channel_id" varchar, + "afk_timeout" integer, + "banner" varchar, + "default_message_notifications" integer, + "description" varchar, + "discovery_splash" varchar, + "explicit_content_filter" integer, + "features" text NOT NULL, + "primary_category_id" integer, + "icon" varchar, + "large" boolean, + "max_members" integer, + "max_presences" integer, + "max_video_channel_users" integer, + "member_count" integer, + "presence_count" integer, + "template_id" varchar, + "mfa_level" integer, + "name" varchar NOT NULL, + "owner_id" varchar, + "preferred_locale" varchar, + "premium_subscription_count" integer, + "premium_tier" integer, + "public_updates_channel_id" varchar, + "rules_channel_id" varchar, + "region" varchar, + "splash" varchar, + "system_channel_id" varchar, + "system_channel_flags" integer, + "unavailable" boolean, + "verification_level" integer, + "welcome_screen" text NOT NULL, + "widget_channel_id" varchar, + "widget_enabled" boolean, + "nsfw_level" integer, + "nsfw" boolean, + "parent" varchar + ) + `); + await queryRunner.query(` + CREATE TABLE "team_members" ( + "id" varchar PRIMARY KEY NOT NULL, + "membership_state" integer NOT NULL, + "permissions" text NOT NULL, + "team_id" varchar, + "user_id" varchar + ) + `); + await queryRunner.query(` + CREATE TABLE "teams" ( + "id" varchar PRIMARY KEY NOT NULL, + "icon" varchar, + "name" varchar NOT NULL, + "owner_user_id" varchar + ) + `); + await queryRunner.query(` + CREATE TABLE "applications" ( + "id" varchar PRIMARY KEY NOT NULL, + "name" varchar NOT NULL, + "icon" varchar, + "description" varchar NOT NULL, + "rpc_origins" text, + "bot_public" boolean NOT NULL, + "bot_require_code_grant" boolean NOT NULL, + "terms_of_service_url" varchar, + "privacy_policy_url" varchar, + "summary" varchar, + "verify_key" varchar NOT NULL, + "primary_sku_id" varchar, + "slug" varchar, + "cover_image" varchar, + "flags" varchar NOT NULL, + "owner_id" varchar, + "team_id" varchar, + "guild_id" varchar + ) + `); + await queryRunner.query(` + CREATE TABLE "audit_logs" ( + "id" varchar PRIMARY KEY NOT NULL, + "user_id" varchar, + "action_type" integer NOT NULL, + "options" text, + "changes" text NOT NULL, + "reason" varchar, + "target_id" varchar + ) + `); + await queryRunner.query(` + CREATE TABLE "categories" ( + "id" integer PRIMARY KEY NOT NULL, + "name" varchar, + "localizations" text NOT NULL, + "is_primary" boolean + ) + `); + await queryRunner.query(` + CREATE TABLE "rate_limits" ( + "id" varchar PRIMARY KEY NOT NULL, + "executor_id" varchar NOT NULL, + "hits" integer NOT NULL, + "blocked" boolean NOT NULL, + "expires_at" datetime NOT NULL + ) + `); + await queryRunner.query(` + CREATE TABLE "sessions" ( + "id" varchar PRIMARY KEY NOT NULL, + "user_id" varchar, + "session_id" varchar NOT NULL, + "activities" text, + "client_info" text NOT NULL, + "status" varchar NOT NULL + ) + `); + await queryRunner.query(` + CREATE TABLE "sticker_packs" ( + "id" varchar PRIMARY KEY NOT NULL, + "name" varchar NOT NULL, + "description" varchar, + "banner_asset_id" varchar, + "cover_sticker_id" varchar, + "coverStickerId" varchar + ) + `); + await queryRunner.query(` + CREATE TABLE "client_release" ( + "id" varchar PRIMARY KEY NOT NULL, + "name" varchar NOT NULL, + "pub_date" varchar NOT NULL, + "url" varchar NOT NULL, + "deb_url" varchar NOT NULL, + "osx_url" varchar NOT NULL, + "win_url" varchar NOT NULL, + "notes" varchar + ) + `); + await queryRunner.query(` + CREATE TABLE "notes" ( + "id" varchar PRIMARY KEY NOT NULL, + "content" varchar NOT NULL, + "owner_id" varchar, + "target_id" varchar, + CONSTRAINT "UQ_74e6689b9568cc965b8bfc9150b" UNIQUE ("owner_id", "target_id") + ) + `); + await queryRunner.query(` + CREATE TABLE "member_roles" ( + "index" integer NOT NULL, + "role_id" varchar NOT NULL, + PRIMARY KEY ("index", "role_id") + ) + `); + await queryRunner.query(` + CREATE INDEX "IDX_5d7ddc8a5f9c167f548625e772" ON "member_roles" ("index") + `); + await queryRunner.query(` + CREATE INDEX "IDX_e9080e7a7997a0170026d5139c" ON "member_roles" ("role_id") + `); + await queryRunner.query(` + CREATE TABLE "message_user_mentions" ( + "messagesId" varchar NOT NULL, + "usersId" varchar NOT NULL, + PRIMARY KEY ("messagesId", "usersId") + ) + `); + await queryRunner.query(` + CREATE INDEX "IDX_a343387fc560ef378760681c23" ON "message_user_mentions" ("messagesId") + `); + await queryRunner.query(` + CREATE INDEX "IDX_b831eb18ceebd28976239b1e2f" ON "message_user_mentions" ("usersId") + `); + await queryRunner.query(` + CREATE TABLE "message_role_mentions" ( + "messagesId" varchar NOT NULL, + "rolesId" varchar NOT NULL, + PRIMARY KEY ("messagesId", "rolesId") + ) + `); + await queryRunner.query(` + CREATE INDEX "IDX_a8242cf535337a490b0feaea0b" ON "message_role_mentions" ("messagesId") + `); + await queryRunner.query(` + CREATE INDEX "IDX_29d63eb1a458200851bc37d074" ON "message_role_mentions" ("rolesId") + `); + await queryRunner.query(` + CREATE TABLE "message_channel_mentions" ( + "messagesId" varchar NOT NULL, + "channelsId" varchar NOT NULL, + PRIMARY KEY ("messagesId", "channelsId") + ) + `); + await queryRunner.query(` + CREATE INDEX "IDX_2a27102ecd1d81b4582a436092" ON "message_channel_mentions" ("messagesId") + `); + await queryRunner.query(` + CREATE INDEX "IDX_bdb8c09e1464cabf62105bf4b9" ON "message_channel_mentions" ("channelsId") + `); + await queryRunner.query(` + CREATE TABLE "message_stickers" ( + "messagesId" varchar NOT NULL, + "stickersId" varchar NOT NULL, + PRIMARY KEY ("messagesId", "stickersId") + ) + `); + await queryRunner.query(` + CREATE INDEX "IDX_40bb6f23e7cc133292e92829d2" ON "message_stickers" ("messagesId") + `); + await queryRunner.query(` + CREATE INDEX "IDX_e22a70819d07659c7a71c112a1" ON "message_stickers" ("stickersId") + `); + await queryRunner.query(` + DROP INDEX "IDX_a0b2ff0a598df0b0d055934a17" + `); + await queryRunner.query(` + CREATE TABLE "temporary_relationships" ( + "id" varchar PRIMARY KEY NOT NULL, + "from_id" varchar NOT NULL, + "to_id" varchar NOT NULL, + "nickname" varchar, + "type" integer NOT NULL, + CONSTRAINT "FK_9af4194bab1250b1c584ae4f1d7" FOREIGN KEY ("from_id") REFERENCES "users" ("id") ON DELETE CASCADE ON UPDATE NO ACTION, + CONSTRAINT "FK_9c7f6b98a9843b76dce1b0c878b" FOREIGN KEY ("to_id") REFERENCES "users" ("id") ON DELETE CASCADE ON UPDATE NO ACTION + ) + `); + await queryRunner.query(` + INSERT INTO "temporary_relationships"("id", "from_id", "to_id", "nickname", "type") + SELECT "id", + "from_id", + "to_id", + "nickname", + "type" + FROM "relationships" + `); + await queryRunner.query(` + DROP TABLE "relationships" + `); + await queryRunner.query(` + ALTER TABLE "temporary_relationships" + RENAME TO "relationships" + `); + await queryRunner.query(` + CREATE UNIQUE INDEX "IDX_a0b2ff0a598df0b0d055934a17" ON "relationships" ("from_id", "to_id") + `); + await queryRunner.query(` + CREATE TABLE "temporary_connected_accounts" ( + "id" varchar PRIMARY KEY NOT NULL, + "user_id" varchar, + "access_token" varchar NOT NULL, + "friend_sync" boolean NOT NULL, + "name" varchar NOT NULL, + "revoked" boolean NOT NULL, + "show_activity" boolean NOT NULL, + "type" varchar NOT NULL, + "verified" boolean NOT NULL, + "visibility" integer NOT NULL, + CONSTRAINT "FK_f47244225a6a1eac04a3463dd90" FOREIGN KEY ("user_id") REFERENCES "users" ("id") ON DELETE CASCADE ON UPDATE NO ACTION + ) + `); + await queryRunner.query(` + INSERT INTO "temporary_connected_accounts"( + "id", + "user_id", + "access_token", + "friend_sync", + "name", + "revoked", + "show_activity", + "type", + "verified", + "visibility" + ) + SELECT "id", + "user_id", + "access_token", + "friend_sync", + "name", + "revoked", + "show_activity", + "type", + "verified", + "visibility" + FROM "connected_accounts" + `); + await queryRunner.query(` + DROP TABLE "connected_accounts" + `); + await queryRunner.query(` + ALTER TABLE "temporary_connected_accounts" + RENAME TO "connected_accounts" + `); + await queryRunner.query(` + CREATE TABLE "temporary_backup_codes" ( + "id" varchar PRIMARY KEY NOT NULL, + "code" varchar NOT NULL, + "consumed" boolean NOT NULL, + "expired" boolean NOT NULL, + "user_id" varchar, + CONSTRAINT "FK_70066ea80d2f4b871beda32633b" FOREIGN KEY ("user_id") REFERENCES "users" ("id") ON DELETE CASCADE ON UPDATE NO ACTION + ) + `); + await queryRunner.query(` + INSERT INTO "temporary_backup_codes"("id", "code", "consumed", "expired", "user_id") + SELECT "id", + "code", + "consumed", + "expired", + "user_id" + FROM "backup_codes" + `); + await queryRunner.query(` + DROP TABLE "backup_codes" + `); + await queryRunner.query(` + ALTER TABLE "temporary_backup_codes" + RENAME TO "backup_codes" + `); + await queryRunner.query(` + CREATE TABLE "temporary_bans" ( + "id" varchar PRIMARY KEY NOT NULL, + "user_id" varchar, + "guild_id" varchar, + "executor_id" varchar, + "ip" varchar NOT NULL, + "reason" varchar, + CONSTRAINT "FK_5999e8e449f80a236ff72023559" FOREIGN KEY ("user_id") REFERENCES "users" ("id") ON DELETE CASCADE ON UPDATE NO ACTION, + CONSTRAINT "FK_9d3ab7dd180ebdd245cdb66ecad" FOREIGN KEY ("guild_id") REFERENCES "guilds" ("id") ON DELETE CASCADE ON UPDATE NO ACTION, + CONSTRAINT "FK_07ad88c86d1f290d46748410d58" FOREIGN KEY ("executor_id") REFERENCES "users" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION + ) + `); + await queryRunner.query(` + INSERT INTO "temporary_bans"( + "id", + "user_id", + "guild_id", + "executor_id", + "ip", + "reason" + ) + SELECT "id", + "user_id", + "guild_id", + "executor_id", + "ip", + "reason" + FROM "bans" + `); + await queryRunner.query(` + DROP TABLE "bans" + `); + await queryRunner.query(` + ALTER TABLE "temporary_bans" + RENAME TO "bans" + `); + await queryRunner.query(` + CREATE TABLE "temporary_recipients" ( + "id" varchar PRIMARY KEY NOT NULL, + "channel_id" varchar NOT NULL, + "user_id" varchar NOT NULL, + "closed" boolean NOT NULL DEFAULT (0), + CONSTRAINT "FK_2f18ee1ba667f233ae86c0ea60e" FOREIGN KEY ("channel_id") REFERENCES "channels" ("id") ON DELETE CASCADE ON UPDATE NO ACTION, + CONSTRAINT "FK_6157e8b6ba4e6e3089616481fe2" FOREIGN KEY ("user_id") REFERENCES "users" ("id") ON DELETE CASCADE ON UPDATE NO ACTION + ) + `); + await queryRunner.query(` + INSERT INTO "temporary_recipients"("id", "channel_id", "user_id", "closed") + SELECT "id", + "channel_id", + "user_id", + "closed" + FROM "recipients" + `); + await queryRunner.query(` + DROP TABLE "recipients" + `); + await queryRunner.query(` + ALTER TABLE "temporary_recipients" + RENAME TO "recipients" + `); + await queryRunner.query(` + CREATE TABLE "temporary_roles" ( + "id" varchar PRIMARY KEY NOT NULL, + "guild_id" varchar, + "color" integer NOT NULL, + "hoist" boolean NOT NULL, + "managed" boolean NOT NULL, + "mentionable" boolean NOT NULL, + "name" varchar NOT NULL, + "permissions" varchar NOT NULL, + "position" integer NOT NULL, + "icon" varchar, + "unicode_emoji" varchar, + "tags" text, + CONSTRAINT "FK_c32c1ab1c4dc7dcb0278c4b1b8b" FOREIGN KEY ("guild_id") REFERENCES "guilds" ("id") ON DELETE CASCADE ON UPDATE NO ACTION + ) + `); + await queryRunner.query(` + INSERT INTO "temporary_roles"( + "id", + "guild_id", + "color", + "hoist", + "managed", + "mentionable", + "name", + "permissions", + "position", + "icon", + "unicode_emoji", + "tags" + ) + SELECT "id", + "guild_id", + "color", + "hoist", + "managed", + "mentionable", + "name", + "permissions", + "position", + "icon", + "unicode_emoji", + "tags" + FROM "roles" + `); + await queryRunner.query(` + DROP TABLE "roles" + `); + await queryRunner.query(` + ALTER TABLE "temporary_roles" + RENAME TO "roles" + `); + await queryRunner.query(` + DROP INDEX "IDX_bb2bf9386ac443afbbbf9f12d3" + `); + await queryRunner.query(` + CREATE TABLE "temporary_members" ( + "index" integer PRIMARY KEY AUTOINCREMENT NOT NULL, + "id" varchar NOT NULL, + "guild_id" varchar NOT NULL, + "nick" varchar, + "joined_at" datetime NOT NULL, + "premium_since" bigint, + "deaf" boolean NOT NULL, + "mute" boolean NOT NULL, + "pending" boolean NOT NULL, + "settings" text NOT NULL, + "last_message_id" varchar, + "joined_by" varchar, + CONSTRAINT "FK_28b53062261b996d9c99fa12404" FOREIGN KEY ("id") REFERENCES "users" ("id") ON DELETE CASCADE ON UPDATE NO ACTION, + CONSTRAINT "FK_16aceddd5b89825b8ed6029ad1c" FOREIGN KEY ("guild_id") REFERENCES "guilds" ("id") ON DELETE CASCADE ON UPDATE NO ACTION + ) + `); + await queryRunner.query(` + INSERT INTO "temporary_members"( + "index", + "id", + "guild_id", + "nick", + "joined_at", + "premium_since", + "deaf", + "mute", + "pending", + "settings", + "last_message_id", + "joined_by" + ) + SELECT "index", + "id", + "guild_id", + "nick", + "joined_at", + "premium_since", + "deaf", + "mute", + "pending", + "settings", + "last_message_id", + "joined_by" + FROM "members" + `); + await queryRunner.query(` + DROP TABLE "members" + `); + await queryRunner.query(` + ALTER TABLE "temporary_members" + RENAME TO "members" + `); + await queryRunner.query(` + CREATE UNIQUE INDEX "IDX_bb2bf9386ac443afbbbf9f12d3" ON "members" ("id", "guild_id") + `); + await queryRunner.query(` + CREATE TABLE "temporary_webhooks" ( + "id" varchar PRIMARY KEY NOT NULL, + "type" integer NOT NULL, + "name" varchar, + "avatar" varchar, + "token" varchar, + "guild_id" varchar, + "channel_id" varchar, + "application_id" varchar, + "user_id" varchar, + "source_guild_id" varchar, + CONSTRAINT "FK_487a7af59d189f744fe394368fc" FOREIGN KEY ("guild_id") REFERENCES "guilds" ("id") ON DELETE CASCADE ON UPDATE NO ACTION, + CONSTRAINT "FK_df528cf77e82f8032230e7e37d8" FOREIGN KEY ("channel_id") REFERENCES "channels" ("id") ON DELETE CASCADE ON UPDATE NO ACTION, + CONSTRAINT "FK_c3e5305461931763b56aa905f1c" FOREIGN KEY ("application_id") REFERENCES "applications" ("id") ON DELETE CASCADE ON UPDATE NO ACTION, + CONSTRAINT "FK_0d523f6f997c86e052c49b1455f" FOREIGN KEY ("user_id") REFERENCES "users" ("id") ON DELETE CASCADE ON UPDATE NO ACTION, + CONSTRAINT "FK_3a285f4f49c40e0706d3018bc9f" FOREIGN KEY ("source_guild_id") REFERENCES "guilds" ("id") ON DELETE CASCADE ON UPDATE NO ACTION + ) + `); + await queryRunner.query(` + INSERT INTO "temporary_webhooks"( + "id", + "type", + "name", + "avatar", + "token", + "guild_id", + "channel_id", + "application_id", + "user_id", + "source_guild_id" + ) + SELECT "id", + "type", + "name", + "avatar", + "token", + "guild_id", + "channel_id", + "application_id", + "user_id", + "source_guild_id" + FROM "webhooks" + `); + await queryRunner.query(` + DROP TABLE "webhooks" + `); + await queryRunner.query(` + ALTER TABLE "temporary_webhooks" + RENAME TO "webhooks" + `); + await queryRunner.query(` + CREATE TABLE "temporary_stickers" ( + "id" varchar PRIMARY KEY NOT NULL, + "name" varchar NOT NULL, + "description" varchar, + "available" boolean, + "tags" varchar, + "pack_id" varchar, + "guild_id" varchar, + "user_id" varchar, + "type" integer NOT NULL, + "format_type" integer NOT NULL, + CONSTRAINT "FK_e7cfa5cefa6661b3fb8fda8ce69" FOREIGN KEY ("pack_id") REFERENCES "sticker_packs" ("id") ON DELETE CASCADE ON UPDATE NO ACTION, + CONSTRAINT "FK_193d551d852aca5347ef5c9f205" FOREIGN KEY ("guild_id") REFERENCES "guilds" ("id") ON DELETE CASCADE ON UPDATE NO ACTION, + CONSTRAINT "FK_8f4ee73f2bb2325ff980502e158" FOREIGN KEY ("user_id") REFERENCES "users" ("id") ON DELETE CASCADE ON UPDATE NO ACTION + ) + `); + await queryRunner.query(` + INSERT INTO "temporary_stickers"( + "id", + "name", + "description", + "available", + "tags", + "pack_id", + "guild_id", + "user_id", + "type", + "format_type" + ) + SELECT "id", + "name", + "description", + "available", + "tags", + "pack_id", + "guild_id", + "user_id", + "type", + "format_type" + FROM "stickers" + `); + await queryRunner.query(` + DROP TABLE "stickers" + `); + await queryRunner.query(` + ALTER TABLE "temporary_stickers" + RENAME TO "stickers" + `); + await queryRunner.query(` + CREATE TABLE "temporary_attachments" ( + "id" varchar PRIMARY KEY NOT NULL, + "filename" varchar NOT NULL, + "size" integer NOT NULL, + "url" varchar NOT NULL, + "proxy_url" varchar NOT NULL, + "height" integer, + "width" integer, + "content_type" varchar, + "message_id" varchar, + CONSTRAINT "FK_623e10eec51ada466c5038979e3" FOREIGN KEY ("message_id") REFERENCES "messages" ("id") ON DELETE CASCADE ON UPDATE NO ACTION + ) + `); + await queryRunner.query(` + INSERT INTO "temporary_attachments"( + "id", + "filename", + "size", + "url", + "proxy_url", + "height", + "width", + "content_type", + "message_id" + ) + SELECT "id", + "filename", + "size", + "url", + "proxy_url", + "height", + "width", + "content_type", + "message_id" + FROM "attachments" + `); + await queryRunner.query(` + DROP TABLE "attachments" + `); + await queryRunner.query(` + ALTER TABLE "temporary_attachments" + RENAME TO "attachments" + `); + await queryRunner.query(` + DROP INDEX "IDX_86b9109b155eb70c0a2ca3b4b6" + `); + await queryRunner.query(` + DROP INDEX "IDX_05535bc695e9f7ee104616459d" + `); + await queryRunner.query(` + DROP INDEX "IDX_3ed7a60fb7dbe04e1ba9332a8b" + `); + await queryRunner.query(` + CREATE TABLE "temporary_messages" ( + "id" varchar PRIMARY KEY NOT NULL, + "channel_id" varchar, + "guild_id" varchar, + "author_id" varchar, + "member_id" varchar, + "webhook_id" varchar, + "application_id" varchar, + "content" varchar, + "timestamp" datetime NOT NULL DEFAULT (datetime('now')), + "edited_timestamp" datetime, + "tts" boolean, + "mention_everyone" boolean, + "embeds" text NOT NULL, + "reactions" text NOT NULL, + "nonce" text, + "pinned" boolean, + "type" integer NOT NULL, + "activity" text, + "flags" varchar, + "message_reference" text, + "interaction" text, + "components" text, + "message_reference_id" varchar, + CONSTRAINT "FK_86b9109b155eb70c0a2ca3b4b6d" FOREIGN KEY ("channel_id") REFERENCES "channels" ("id") ON DELETE CASCADE ON UPDATE NO ACTION, + CONSTRAINT "FK_b193588441b085352a4c0109423" FOREIGN KEY ("guild_id") REFERENCES "guilds" ("id") ON DELETE CASCADE ON UPDATE NO ACTION, + CONSTRAINT "FK_05535bc695e9f7ee104616459d3" FOREIGN KEY ("author_id") REFERENCES "users" ("id") ON DELETE CASCADE ON UPDATE NO ACTION, + CONSTRAINT "FK_b0525304f2262b7014245351c76" FOREIGN KEY ("member_id") REFERENCES "users" ("id") ON DELETE CASCADE ON UPDATE NO ACTION, + CONSTRAINT "FK_f83c04bcf1df4e5c0e7a52ed348" FOREIGN KEY ("webhook_id") REFERENCES "webhooks" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION, + CONSTRAINT "FK_5d3ec1cb962de6488637fd779d6" FOREIGN KEY ("application_id") REFERENCES "applications" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION, + CONSTRAINT "FK_61a92bb65b302a76d9c1fcd3174" FOREIGN KEY ("message_reference_id") REFERENCES "messages" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION + ) + `); + await queryRunner.query(` + INSERT INTO "temporary_messages"( + "id", + "channel_id", + "guild_id", + "author_id", + "member_id", + "webhook_id", + "application_id", + "content", + "timestamp", + "edited_timestamp", + "tts", + "mention_everyone", + "embeds", + "reactions", + "nonce", + "pinned", + "type", + "activity", + "flags", + "message_reference", + "interaction", + "components", + "message_reference_id" + ) + SELECT "id", + "channel_id", + "guild_id", + "author_id", + "member_id", + "webhook_id", + "application_id", + "content", + "timestamp", + "edited_timestamp", + "tts", + "mention_everyone", + "embeds", + "reactions", + "nonce", + "pinned", + "type", + "activity", + "flags", + "message_reference", + "interaction", + "components", + "message_reference_id" + FROM "messages" + `); + await queryRunner.query(` + DROP TABLE "messages" + `); + await queryRunner.query(` + ALTER TABLE "temporary_messages" + RENAME TO "messages" + `); + await queryRunner.query(` + CREATE INDEX "IDX_86b9109b155eb70c0a2ca3b4b6" ON "messages" ("channel_id") + `); + await queryRunner.query(` + CREATE INDEX "IDX_05535bc695e9f7ee104616459d" ON "messages" ("author_id") + `); + await queryRunner.query(` + CREATE UNIQUE INDEX "IDX_3ed7a60fb7dbe04e1ba9332a8b" ON "messages" ("channel_id", "id") + `); + await queryRunner.query(` + DROP INDEX "IDX_0abf8b443321bd3cf7f81ee17a" + `); + await queryRunner.query(` + CREATE TABLE "temporary_read_states" ( + "id" varchar PRIMARY KEY NOT NULL, + "channel_id" varchar NOT NULL, + "user_id" varchar NOT NULL, + "last_message_id" varchar, + "public_ack" varchar, + "notifications_cursor" varchar, + "last_pin_timestamp" datetime, + "mention_count" integer, + CONSTRAINT "FK_40da2fca4e0eaf7a23b5bfc5d34" FOREIGN KEY ("channel_id") REFERENCES "channels" ("id") ON DELETE CASCADE ON UPDATE NO ACTION, + CONSTRAINT "FK_195f92e4dd1254a4e348c043763" FOREIGN KEY ("user_id") REFERENCES "users" ("id") ON DELETE CASCADE ON UPDATE NO ACTION + ) + `); + await queryRunner.query(` + INSERT INTO "temporary_read_states"( + "id", + "channel_id", + "user_id", + "last_message_id", + "public_ack", + "notifications_cursor", + "last_pin_timestamp", + "mention_count" + ) + SELECT "id", + "channel_id", + "user_id", + "last_message_id", + "public_ack", + "notifications_cursor", + "last_pin_timestamp", + "mention_count" + FROM "read_states" + `); + await queryRunner.query(` + DROP TABLE "read_states" + `); + await queryRunner.query(` + ALTER TABLE "temporary_read_states" + RENAME TO "read_states" + `); + await queryRunner.query(` + CREATE UNIQUE INDEX "IDX_0abf8b443321bd3cf7f81ee17a" ON "read_states" ("channel_id", "user_id") + `); + await queryRunner.query(` + CREATE TABLE "temporary_invites" ( + "code" varchar PRIMARY KEY NOT NULL, + "temporary" boolean NOT NULL, + "uses" integer NOT NULL, + "max_uses" integer NOT NULL, + "max_age" integer NOT NULL, + "created_at" datetime NOT NULL, + "expires_at" datetime NOT NULL, + "guild_id" varchar, + "channel_id" varchar, + "inviter_id" varchar, + "target_user_id" varchar, + "target_user_type" integer, + "vanity_url" boolean, + CONSTRAINT "FK_3f4939aa1461e8af57fea3fb05d" FOREIGN KEY ("guild_id") REFERENCES "guilds" ("id") ON DELETE CASCADE ON UPDATE NO ACTION, + CONSTRAINT "FK_6a15b051fe5050aa00a4b9ff0f6" FOREIGN KEY ("channel_id") REFERENCES "channels" ("id") ON DELETE CASCADE ON UPDATE NO ACTION, + CONSTRAINT "FK_15c35422032e0b22b4ada95f48f" FOREIGN KEY ("inviter_id") REFERENCES "users" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION, + CONSTRAINT "FK_11a0d394f8fc649c19ce5f16b59" FOREIGN KEY ("target_user_id") REFERENCES "users" ("id") ON DELETE CASCADE ON UPDATE NO ACTION + ) + `); + await queryRunner.query(` + INSERT INTO "temporary_invites"( + "code", + "temporary", + "uses", + "max_uses", + "max_age", + "created_at", + "expires_at", + "guild_id", + "channel_id", + "inviter_id", + "target_user_id", + "target_user_type", + "vanity_url" + ) + SELECT "code", + "temporary", + "uses", + "max_uses", + "max_age", + "created_at", + "expires_at", + "guild_id", + "channel_id", + "inviter_id", + "target_user_id", + "target_user_type", + "vanity_url" + FROM "invites" + `); + await queryRunner.query(` + DROP TABLE "invites" + `); + await queryRunner.query(` + ALTER TABLE "temporary_invites" + RENAME TO "invites" + `); + await queryRunner.query(` + CREATE TABLE "temporary_voice_states" ( + "id" varchar PRIMARY KEY NOT NULL, + "guild_id" varchar, + "channel_id" varchar, + "user_id" varchar, + "session_id" varchar NOT NULL, + "token" varchar, + "deaf" boolean NOT NULL, + "mute" boolean NOT NULL, + "self_deaf" boolean NOT NULL, + "self_mute" boolean NOT NULL, + "self_stream" boolean, + "self_video" boolean NOT NULL, + "suppress" boolean NOT NULL, + "request_to_speak_timestamp" datetime, + CONSTRAINT "FK_03779ef216d4b0358470d9cb748" FOREIGN KEY ("guild_id") REFERENCES "guilds" ("id") ON DELETE CASCADE ON UPDATE NO ACTION, + CONSTRAINT "FK_9f8d389866b40b6657edd026dd4" FOREIGN KEY ("channel_id") REFERENCES "channels" ("id") ON DELETE CASCADE ON UPDATE NO ACTION, + CONSTRAINT "FK_5fe1d5f931a67e85039c640001b" FOREIGN KEY ("user_id") REFERENCES "users" ("id") ON DELETE CASCADE ON UPDATE NO ACTION + ) + `); + await queryRunner.query(` + INSERT INTO "temporary_voice_states"( + "id", + "guild_id", + "channel_id", + "user_id", + "session_id", + "token", + "deaf", + "mute", + "self_deaf", + "self_mute", + "self_stream", + "self_video", + "suppress", + "request_to_speak_timestamp" + ) + SELECT "id", + "guild_id", + "channel_id", + "user_id", + "session_id", + "token", + "deaf", + "mute", + "self_deaf", + "self_mute", + "self_stream", + "self_video", + "suppress", + "request_to_speak_timestamp" + FROM "voice_states" + `); + await queryRunner.query(` + DROP TABLE "voice_states" + `); + await queryRunner.query(` + ALTER TABLE "temporary_voice_states" + RENAME TO "voice_states" + `); + await queryRunner.query(` + CREATE TABLE "temporary_channels" ( + "id" varchar PRIMARY KEY NOT NULL, + "created_at" datetime NOT NULL, + "name" varchar, + "icon" text, + "type" integer NOT NULL, + "last_message_id" varchar, + "guild_id" varchar, + "parent_id" varchar, + "owner_id" varchar, + "last_pin_timestamp" integer, + "default_auto_archive_duration" integer, + "position" integer, + "permission_overwrites" text, + "video_quality_mode" integer, + "bitrate" integer, + "user_limit" integer, + "nsfw" boolean, + "rate_limit_per_user" integer, + "topic" varchar, + "retention_policy_id" varchar, + CONSTRAINT "FK_c253dafe5f3a03ec00cd8fb4581" FOREIGN KEY ("guild_id") REFERENCES "guilds" ("id") ON DELETE CASCADE ON UPDATE NO ACTION, + CONSTRAINT "FK_3274522d14af40540b1a883fc80" FOREIGN KEY ("parent_id") REFERENCES "channels" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION, + CONSTRAINT "FK_3873ed438575cce703ecff4fc7b" FOREIGN KEY ("owner_id") REFERENCES "users" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION + ) + `); + await queryRunner.query(` + INSERT INTO "temporary_channels"( + "id", + "created_at", + "name", + "icon", + "type", + "last_message_id", + "guild_id", + "parent_id", + "owner_id", + "last_pin_timestamp", + "default_auto_archive_duration", + "position", + "permission_overwrites", + "video_quality_mode", + "bitrate", + "user_limit", + "nsfw", + "rate_limit_per_user", + "topic", + "retention_policy_id" + ) + SELECT "id", + "created_at", + "name", + "icon", + "type", + "last_message_id", + "guild_id", + "parent_id", + "owner_id", + "last_pin_timestamp", + "default_auto_archive_duration", + "position", + "permission_overwrites", + "video_quality_mode", + "bitrate", + "user_limit", + "nsfw", + "rate_limit_per_user", + "topic", + "retention_policy_id" + FROM "channels" + `); + await queryRunner.query(` + DROP TABLE "channels" + `); + await queryRunner.query(` + ALTER TABLE "temporary_channels" + RENAME TO "channels" + `); + await queryRunner.query(` + CREATE TABLE "temporary_emojis" ( + "id" varchar PRIMARY KEY NOT NULL, + "animated" boolean NOT NULL, + "available" boolean NOT NULL, + "guild_id" varchar NOT NULL, + "user_id" varchar, + "managed" boolean NOT NULL, + "name" varchar NOT NULL, + "require_colons" boolean NOT NULL, + "roles" text NOT NULL, + "groups" text, + CONSTRAINT "FK_4b988e0db89d94cebcf07f598cc" FOREIGN KEY ("guild_id") REFERENCES "guilds" ("id") ON DELETE CASCADE ON UPDATE NO ACTION, + CONSTRAINT "FK_fa7ddd5f9a214e28ce596548421" FOREIGN KEY ("user_id") REFERENCES "users" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION + ) + `); + await queryRunner.query(` + INSERT INTO "temporary_emojis"( + "id", + "animated", + "available", + "guild_id", + "user_id", + "managed", + "name", + "require_colons", + "roles", + "groups" + ) + SELECT "id", + "animated", + "available", + "guild_id", + "user_id", + "managed", + "name", + "require_colons", + "roles", + "groups" + FROM "emojis" + `); + await queryRunner.query(` + DROP TABLE "emojis" + `); + await queryRunner.query(` + ALTER TABLE "temporary_emojis" + RENAME TO "emojis" + `); + await queryRunner.query(` + CREATE TABLE "temporary_templates" ( + "id" varchar PRIMARY KEY NOT NULL, + "code" varchar NOT NULL, + "name" varchar NOT NULL, + "description" varchar, + "usage_count" integer, + "creator_id" varchar, + "created_at" datetime NOT NULL, + "updated_at" datetime NOT NULL, + "source_guild_id" varchar, + "serialized_source_guild" text NOT NULL, + CONSTRAINT "UQ_be38737bf339baf63b1daeffb55" UNIQUE ("code"), + CONSTRAINT "FK_d7374b7f8f5fbfdececa4fb62e1" FOREIGN KEY ("creator_id") REFERENCES "users" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION, + CONSTRAINT "FK_445d00eaaea0e60a017a5ed0c11" FOREIGN KEY ("source_guild_id") REFERENCES "guilds" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION + ) + `); + await queryRunner.query(` + INSERT INTO "temporary_templates"( + "id", + "code", + "name", + "description", + "usage_count", + "creator_id", + "created_at", + "updated_at", + "source_guild_id", + "serialized_source_guild" + ) + SELECT "id", + "code", + "name", + "description", + "usage_count", + "creator_id", + "created_at", + "updated_at", + "source_guild_id", + "serialized_source_guild" + FROM "templates" + `); + await queryRunner.query(` + DROP TABLE "templates" + `); + await queryRunner.query(` + ALTER TABLE "temporary_templates" + RENAME TO "templates" + `); + await queryRunner.query(` + CREATE TABLE "temporary_guilds" ( + "id" varchar PRIMARY KEY NOT NULL, + "afk_channel_id" varchar, + "afk_timeout" integer, + "banner" varchar, + "default_message_notifications" integer, + "description" varchar, + "discovery_splash" varchar, + "explicit_content_filter" integer, + "features" text NOT NULL, + "primary_category_id" integer, + "icon" varchar, + "large" boolean, + "max_members" integer, + "max_presences" integer, + "max_video_channel_users" integer, + "member_count" integer, + "presence_count" integer, + "template_id" varchar, + "mfa_level" integer, + "name" varchar NOT NULL, + "owner_id" varchar, + "preferred_locale" varchar, + "premium_subscription_count" integer, + "premium_tier" integer, + "public_updates_channel_id" varchar, + "rules_channel_id" varchar, + "region" varchar, + "splash" varchar, + "system_channel_id" varchar, + "system_channel_flags" integer, + "unavailable" boolean, + "verification_level" integer, + "welcome_screen" text NOT NULL, + "widget_channel_id" varchar, + "widget_enabled" boolean, + "nsfw_level" integer, + "nsfw" boolean, + "parent" varchar, + CONSTRAINT "FK_f591a66b8019d87b0fe6c12dad6" FOREIGN KEY ("afk_channel_id") REFERENCES "channels" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION, + CONSTRAINT "FK_e2a2f873a64a5cf62526de42325" FOREIGN KEY ("template_id") REFERENCES "templates" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION, + CONSTRAINT "FK_fc1a451727e3643ca572a3bb394" FOREIGN KEY ("owner_id") REFERENCES "users" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION, + CONSTRAINT "FK_8d450b016dc8bec35f36729e4b0" FOREIGN KEY ("public_updates_channel_id") REFERENCES "channels" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION, + CONSTRAINT "FK_95828668aa333460582e0ca6396" FOREIGN KEY ("rules_channel_id") REFERENCES "channels" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION, + CONSTRAINT "FK_cfc3d3ad260f8121c95b31a1fce" FOREIGN KEY ("system_channel_id") REFERENCES "channels" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION, + CONSTRAINT "FK_9d1d665379eefde7876a17afa99" FOREIGN KEY ("widget_channel_id") REFERENCES "channels" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION + ) + `); + await queryRunner.query(` + INSERT INTO "temporary_guilds"( + "id", + "afk_channel_id", + "afk_timeout", + "banner", + "default_message_notifications", + "description", + "discovery_splash", + "explicit_content_filter", + "features", + "primary_category_id", + "icon", + "large", + "max_members", + "max_presences", + "max_video_channel_users", + "member_count", + "presence_count", + "template_id", + "mfa_level", + "name", + "owner_id", + "preferred_locale", + "premium_subscription_count", + "premium_tier", + "public_updates_channel_id", + "rules_channel_id", + "region", + "splash", + "system_channel_id", + "system_channel_flags", + "unavailable", + "verification_level", + "welcome_screen", + "widget_channel_id", + "widget_enabled", + "nsfw_level", + "nsfw", + "parent" + ) + SELECT "id", + "afk_channel_id", + "afk_timeout", + "banner", + "default_message_notifications", + "description", + "discovery_splash", + "explicit_content_filter", + "features", + "primary_category_id", + "icon", + "large", + "max_members", + "max_presences", + "max_video_channel_users", + "member_count", + "presence_count", + "template_id", + "mfa_level", + "name", + "owner_id", + "preferred_locale", + "premium_subscription_count", + "premium_tier", + "public_updates_channel_id", + "rules_channel_id", + "region", + "splash", + "system_channel_id", + "system_channel_flags", + "unavailable", + "verification_level", + "welcome_screen", + "widget_channel_id", + "widget_enabled", + "nsfw_level", + "nsfw", + "parent" + FROM "guilds" + `); + await queryRunner.query(` + DROP TABLE "guilds" + `); + await queryRunner.query(` + ALTER TABLE "temporary_guilds" + RENAME TO "guilds" + `); + await queryRunner.query(` + CREATE TABLE "temporary_team_members" ( + "id" varchar PRIMARY KEY NOT NULL, + "membership_state" integer NOT NULL, + "permissions" text NOT NULL, + "team_id" varchar, + "user_id" varchar, + CONSTRAINT "FK_fdad7d5768277e60c40e01cdcea" FOREIGN KEY ("team_id") REFERENCES "teams" ("id") ON DELETE CASCADE ON UPDATE NO ACTION, + CONSTRAINT "FK_c2bf4967c8c2a6b845dadfbf3d4" FOREIGN KEY ("user_id") REFERENCES "users" ("id") ON DELETE CASCADE ON UPDATE NO ACTION + ) + `); + await queryRunner.query(` + INSERT INTO "temporary_team_members"( + "id", + "membership_state", + "permissions", + "team_id", + "user_id" + ) + SELECT "id", + "membership_state", + "permissions", + "team_id", + "user_id" + FROM "team_members" + `); + await queryRunner.query(` + DROP TABLE "team_members" + `); + await queryRunner.query(` + ALTER TABLE "temporary_team_members" + RENAME TO "team_members" + `); + await queryRunner.query(` + CREATE TABLE "temporary_teams" ( + "id" varchar PRIMARY KEY NOT NULL, + "icon" varchar, + "name" varchar NOT NULL, + "owner_user_id" varchar, + CONSTRAINT "FK_13f00abf7cb6096c43ecaf8c108" FOREIGN KEY ("owner_user_id") REFERENCES "users" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION + ) + `); + await queryRunner.query(` + INSERT INTO "temporary_teams"("id", "icon", "name", "owner_user_id") + SELECT "id", + "icon", + "name", + "owner_user_id" + FROM "teams" + `); + await queryRunner.query(` + DROP TABLE "teams" + `); + await queryRunner.query(` + ALTER TABLE "temporary_teams" + RENAME TO "teams" + `); + await queryRunner.query(` + CREATE TABLE "temporary_applications" ( + "id" varchar PRIMARY KEY NOT NULL, + "name" varchar NOT NULL, + "icon" varchar, + "description" varchar NOT NULL, + "rpc_origins" text, + "bot_public" boolean NOT NULL, + "bot_require_code_grant" boolean NOT NULL, + "terms_of_service_url" varchar, + "privacy_policy_url" varchar, + "summary" varchar, + "verify_key" varchar NOT NULL, + "primary_sku_id" varchar, + "slug" varchar, + "cover_image" varchar, + "flags" varchar NOT NULL, + "owner_id" varchar, + "team_id" varchar, + "guild_id" varchar, + CONSTRAINT "FK_e57508958bf92b9d9d25231b5e8" FOREIGN KEY ("owner_id") REFERENCES "users" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION, + CONSTRAINT "FK_a36ed02953077f408d0f3ebc424" FOREIGN KEY ("team_id") REFERENCES "teams" ("id") ON DELETE CASCADE ON UPDATE NO ACTION, + CONSTRAINT "FK_e5bf78cdbbe9ba91062d74c5aba" FOREIGN KEY ("guild_id") REFERENCES "guilds" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION + ) + `); + await queryRunner.query(` + INSERT INTO "temporary_applications"( + "id", + "name", + "icon", + "description", + "rpc_origins", + "bot_public", + "bot_require_code_grant", + "terms_of_service_url", + "privacy_policy_url", + "summary", + "verify_key", + "primary_sku_id", + "slug", + "cover_image", + "flags", + "owner_id", + "team_id", + "guild_id" + ) + SELECT "id", + "name", + "icon", + "description", + "rpc_origins", + "bot_public", + "bot_require_code_grant", + "terms_of_service_url", + "privacy_policy_url", + "summary", + "verify_key", + "primary_sku_id", + "slug", + "cover_image", + "flags", + "owner_id", + "team_id", + "guild_id" + FROM "applications" + `); + await queryRunner.query(` + DROP TABLE "applications" + `); + await queryRunner.query(` + ALTER TABLE "temporary_applications" + RENAME TO "applications" + `); + await queryRunner.query(` + CREATE TABLE "temporary_audit_logs" ( + "id" varchar PRIMARY KEY NOT NULL, + "user_id" varchar, + "action_type" integer NOT NULL, + "options" text, + "changes" text NOT NULL, + "reason" varchar, + "target_id" varchar, + CONSTRAINT "FK_3cd01cd3ae7aab010310d96ac8e" FOREIGN KEY ("target_id") REFERENCES "users" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION, + CONSTRAINT "FK_bd2726fd31b35443f2245b93ba0" FOREIGN KEY ("user_id") REFERENCES "users" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION + ) + `); + await queryRunner.query(` + INSERT INTO "temporary_audit_logs"( + "id", + "user_id", + "action_type", + "options", + "changes", + "reason", + "target_id" + ) + SELECT "id", + "user_id", + "action_type", + "options", + "changes", + "reason", + "target_id" + FROM "audit_logs" + `); + await queryRunner.query(` + DROP TABLE "audit_logs" + `); + await queryRunner.query(` + ALTER TABLE "temporary_audit_logs" + RENAME TO "audit_logs" + `); + await queryRunner.query(` + CREATE TABLE "temporary_sessions" ( + "id" varchar PRIMARY KEY NOT NULL, + "user_id" varchar, + "session_id" varchar NOT NULL, + "activities" text, + "client_info" text NOT NULL, + "status" varchar NOT NULL, + CONSTRAINT "FK_085d540d9f418cfbdc7bd55bb19" FOREIGN KEY ("user_id") REFERENCES "users" ("id") ON DELETE CASCADE ON UPDATE NO ACTION + ) + `); + await queryRunner.query(` + INSERT INTO "temporary_sessions"( + "id", + "user_id", + "session_id", + "activities", + "client_info", + "status" + ) + SELECT "id", + "user_id", + "session_id", + "activities", + "client_info", + "status" + FROM "sessions" + `); + await queryRunner.query(` + DROP TABLE "sessions" + `); + await queryRunner.query(` + ALTER TABLE "temporary_sessions" + RENAME TO "sessions" + `); + await queryRunner.query(` + CREATE TABLE "temporary_sticker_packs" ( + "id" varchar PRIMARY KEY NOT NULL, + "name" varchar NOT NULL, + "description" varchar, + "banner_asset_id" varchar, + "cover_sticker_id" varchar, + "coverStickerId" varchar, + CONSTRAINT "FK_448fafba4355ee1c837bbc865f1" FOREIGN KEY ("coverStickerId") REFERENCES "stickers" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION + ) + `); + await queryRunner.query(` + INSERT INTO "temporary_sticker_packs"( + "id", + "name", + "description", + "banner_asset_id", + "cover_sticker_id", + "coverStickerId" + ) + SELECT "id", + "name", + "description", + "banner_asset_id", + "cover_sticker_id", + "coverStickerId" + FROM "sticker_packs" + `); + await queryRunner.query(` + DROP TABLE "sticker_packs" + `); + await queryRunner.query(` + ALTER TABLE "temporary_sticker_packs" + RENAME TO "sticker_packs" + `); + await queryRunner.query(` + CREATE TABLE "temporary_notes" ( + "id" varchar PRIMARY KEY NOT NULL, + "content" varchar NOT NULL, + "owner_id" varchar, + "target_id" varchar, + CONSTRAINT "UQ_74e6689b9568cc965b8bfc9150b" UNIQUE ("owner_id", "target_id"), + CONSTRAINT "FK_f9e103f8ae67cb1787063597925" FOREIGN KEY ("owner_id") REFERENCES "users" ("id") ON DELETE CASCADE ON UPDATE NO ACTION, + CONSTRAINT "FK_23e08e5b4481711d573e1abecdc" FOREIGN KEY ("target_id") REFERENCES "users" ("id") ON DELETE CASCADE ON UPDATE NO ACTION + ) + `); + await queryRunner.query(` + INSERT INTO "temporary_notes"("id", "content", "owner_id", "target_id") + SELECT "id", + "content", + "owner_id", + "target_id" + FROM "notes" + `); + await queryRunner.query(` + DROP TABLE "notes" + `); + await queryRunner.query(` + ALTER TABLE "temporary_notes" + RENAME TO "notes" + `); + await queryRunner.query(` + DROP INDEX "IDX_5d7ddc8a5f9c167f548625e772" + `); + await queryRunner.query(` + DROP INDEX "IDX_e9080e7a7997a0170026d5139c" + `); + await queryRunner.query(` + CREATE TABLE "temporary_member_roles" ( + "index" integer NOT NULL, + "role_id" varchar NOT NULL, + CONSTRAINT "FK_5d7ddc8a5f9c167f548625e772e" FOREIGN KEY ("index") REFERENCES "members" ("index") ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT "FK_e9080e7a7997a0170026d5139c1" FOREIGN KEY ("role_id") REFERENCES "roles" ("id") ON DELETE CASCADE ON UPDATE CASCADE, + PRIMARY KEY ("index", "role_id") + ) + `); + await queryRunner.query(` + INSERT INTO "temporary_member_roles"("index", "role_id") + SELECT "index", + "role_id" + FROM "member_roles" + `); + await queryRunner.query(` + DROP TABLE "member_roles" + `); + await queryRunner.query(` + ALTER TABLE "temporary_member_roles" + RENAME TO "member_roles" + `); + await queryRunner.query(` + CREATE INDEX "IDX_5d7ddc8a5f9c167f548625e772" ON "member_roles" ("index") + `); + await queryRunner.query(` + CREATE INDEX "IDX_e9080e7a7997a0170026d5139c" ON "member_roles" ("role_id") + `); + await queryRunner.query(` + DROP INDEX "IDX_a343387fc560ef378760681c23" + `); + await queryRunner.query(` + DROP INDEX "IDX_b831eb18ceebd28976239b1e2f" + `); + await queryRunner.query(` + CREATE TABLE "temporary_message_user_mentions" ( + "messagesId" varchar NOT NULL, + "usersId" varchar NOT NULL, + CONSTRAINT "FK_a343387fc560ef378760681c236" FOREIGN KEY ("messagesId") REFERENCES "messages" ("id") ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT "FK_b831eb18ceebd28976239b1e2f8" FOREIGN KEY ("usersId") REFERENCES "users" ("id") ON DELETE CASCADE ON UPDATE CASCADE, + PRIMARY KEY ("messagesId", "usersId") + ) + `); + await queryRunner.query(` + INSERT INTO "temporary_message_user_mentions"("messagesId", "usersId") + SELECT "messagesId", + "usersId" + FROM "message_user_mentions" + `); + await queryRunner.query(` + DROP TABLE "message_user_mentions" + `); + await queryRunner.query(` + ALTER TABLE "temporary_message_user_mentions" + RENAME TO "message_user_mentions" + `); + await queryRunner.query(` + CREATE INDEX "IDX_a343387fc560ef378760681c23" ON "message_user_mentions" ("messagesId") + `); + await queryRunner.query(` + CREATE INDEX "IDX_b831eb18ceebd28976239b1e2f" ON "message_user_mentions" ("usersId") + `); + await queryRunner.query(` + DROP INDEX "IDX_a8242cf535337a490b0feaea0b" + `); + await queryRunner.query(` + DROP INDEX "IDX_29d63eb1a458200851bc37d074" + `); + await queryRunner.query(` + CREATE TABLE "temporary_message_role_mentions" ( + "messagesId" varchar NOT NULL, + "rolesId" varchar NOT NULL, + CONSTRAINT "FK_a8242cf535337a490b0feaea0b4" FOREIGN KEY ("messagesId") REFERENCES "messages" ("id") ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT "FK_29d63eb1a458200851bc37d074b" FOREIGN KEY ("rolesId") REFERENCES "roles" ("id") ON DELETE CASCADE ON UPDATE CASCADE, + PRIMARY KEY ("messagesId", "rolesId") + ) + `); + await queryRunner.query(` + INSERT INTO "temporary_message_role_mentions"("messagesId", "rolesId") + SELECT "messagesId", + "rolesId" + FROM "message_role_mentions" + `); + await queryRunner.query(` + DROP TABLE "message_role_mentions" + `); + await queryRunner.query(` + ALTER TABLE "temporary_message_role_mentions" + RENAME TO "message_role_mentions" + `); + await queryRunner.query(` + CREATE INDEX "IDX_a8242cf535337a490b0feaea0b" ON "message_role_mentions" ("messagesId") + `); + await queryRunner.query(` + CREATE INDEX "IDX_29d63eb1a458200851bc37d074" ON "message_role_mentions" ("rolesId") + `); + await queryRunner.query(` + DROP INDEX "IDX_2a27102ecd1d81b4582a436092" + `); + await queryRunner.query(` + DROP INDEX "IDX_bdb8c09e1464cabf62105bf4b9" + `); + await queryRunner.query(` + CREATE TABLE "temporary_message_channel_mentions" ( + "messagesId" varchar NOT NULL, + "channelsId" varchar NOT NULL, + CONSTRAINT "FK_2a27102ecd1d81b4582a4360921" FOREIGN KEY ("messagesId") REFERENCES "messages" ("id") ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT "FK_bdb8c09e1464cabf62105bf4b9d" FOREIGN KEY ("channelsId") REFERENCES "channels" ("id") ON DELETE CASCADE ON UPDATE CASCADE, + PRIMARY KEY ("messagesId", "channelsId") + ) + `); + await queryRunner.query(` + INSERT INTO "temporary_message_channel_mentions"("messagesId", "channelsId") + SELECT "messagesId", + "channelsId" + FROM "message_channel_mentions" + `); + await queryRunner.query(` + DROP TABLE "message_channel_mentions" + `); + await queryRunner.query(` + ALTER TABLE "temporary_message_channel_mentions" + RENAME TO "message_channel_mentions" + `); + await queryRunner.query(` + CREATE INDEX "IDX_2a27102ecd1d81b4582a436092" ON "message_channel_mentions" ("messagesId") + `); + await queryRunner.query(` + CREATE INDEX "IDX_bdb8c09e1464cabf62105bf4b9" ON "message_channel_mentions" ("channelsId") + `); + await queryRunner.query(` + DROP INDEX "IDX_40bb6f23e7cc133292e92829d2" + `); + await queryRunner.query(` + DROP INDEX "IDX_e22a70819d07659c7a71c112a1" + `); + await queryRunner.query(` + CREATE TABLE "temporary_message_stickers" ( + "messagesId" varchar NOT NULL, + "stickersId" varchar NOT NULL, + CONSTRAINT "FK_40bb6f23e7cc133292e92829d28" FOREIGN KEY ("messagesId") REFERENCES "messages" ("id") ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT "FK_e22a70819d07659c7a71c112a1f" FOREIGN KEY ("stickersId") REFERENCES "stickers" ("id") ON DELETE CASCADE ON UPDATE CASCADE, + PRIMARY KEY ("messagesId", "stickersId") + ) + `); + await queryRunner.query(` + INSERT INTO "temporary_message_stickers"("messagesId", "stickersId") + SELECT "messagesId", + "stickersId" + FROM "message_stickers" + `); + await queryRunner.query(` + DROP TABLE "message_stickers" + `); + await queryRunner.query(` + ALTER TABLE "temporary_message_stickers" + RENAME TO "message_stickers" + `); + await queryRunner.query(` + CREATE INDEX "IDX_40bb6f23e7cc133292e92829d2" ON "message_stickers" ("messagesId") + `); + await queryRunner.query(` + CREATE INDEX "IDX_e22a70819d07659c7a71c112a1" ON "message_stickers" ("stickersId") + `); + await queryRunner.query(` + CREATE TABLE "query-result-cache" ( + "id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, + "identifier" varchar, + "time" bigint NOT NULL, + "duration" integer NOT NULL, + "query" text NOT NULL, + "result" text NOT NULL + ) + `); + } + + public async down(queryRunner: QueryRunner): Promise<void> { + await queryRunner.query(` + DROP TABLE "query-result-cache" + `); + await queryRunner.query(` + DROP INDEX "IDX_e22a70819d07659c7a71c112a1" + `); + await queryRunner.query(` + DROP INDEX "IDX_40bb6f23e7cc133292e92829d2" + `); + await queryRunner.query(` + ALTER TABLE "message_stickers" + RENAME TO "temporary_message_stickers" + `); + await queryRunner.query(` + CREATE TABLE "message_stickers" ( + "messagesId" varchar NOT NULL, + "stickersId" varchar NOT NULL, + PRIMARY KEY ("messagesId", "stickersId") + ) + `); + await queryRunner.query(` + INSERT INTO "message_stickers"("messagesId", "stickersId") + SELECT "messagesId", + "stickersId" + FROM "temporary_message_stickers" + `); + await queryRunner.query(` + DROP TABLE "temporary_message_stickers" + `); + await queryRunner.query(` + CREATE INDEX "IDX_e22a70819d07659c7a71c112a1" ON "message_stickers" ("stickersId") + `); + await queryRunner.query(` + CREATE INDEX "IDX_40bb6f23e7cc133292e92829d2" ON "message_stickers" ("messagesId") + `); + await queryRunner.query(` + DROP INDEX "IDX_bdb8c09e1464cabf62105bf4b9" + `); + await queryRunner.query(` + DROP INDEX "IDX_2a27102ecd1d81b4582a436092" + `); + await queryRunner.query(` + ALTER TABLE "message_channel_mentions" + RENAME TO "temporary_message_channel_mentions" + `); + await queryRunner.query(` + CREATE TABLE "message_channel_mentions" ( + "messagesId" varchar NOT NULL, + "channelsId" varchar NOT NULL, + PRIMARY KEY ("messagesId", "channelsId") + ) + `); + await queryRunner.query(` + INSERT INTO "message_channel_mentions"("messagesId", "channelsId") + SELECT "messagesId", + "channelsId" + FROM "temporary_message_channel_mentions" + `); + await queryRunner.query(` + DROP TABLE "temporary_message_channel_mentions" + `); + await queryRunner.query(` + CREATE INDEX "IDX_bdb8c09e1464cabf62105bf4b9" ON "message_channel_mentions" ("channelsId") + `); + await queryRunner.query(` + CREATE INDEX "IDX_2a27102ecd1d81b4582a436092" ON "message_channel_mentions" ("messagesId") + `); + await queryRunner.query(` + DROP INDEX "IDX_29d63eb1a458200851bc37d074" + `); + await queryRunner.query(` + DROP INDEX "IDX_a8242cf535337a490b0feaea0b" + `); + await queryRunner.query(` + ALTER TABLE "message_role_mentions" + RENAME TO "temporary_message_role_mentions" + `); + await queryRunner.query(` + CREATE TABLE "message_role_mentions" ( + "messagesId" varchar NOT NULL, + "rolesId" varchar NOT NULL, + PRIMARY KEY ("messagesId", "rolesId") + ) + `); + await queryRunner.query(` + INSERT INTO "message_role_mentions"("messagesId", "rolesId") + SELECT "messagesId", + "rolesId" + FROM "temporary_message_role_mentions" + `); + await queryRunner.query(` + DROP TABLE "temporary_message_role_mentions" + `); + await queryRunner.query(` + CREATE INDEX "IDX_29d63eb1a458200851bc37d074" ON "message_role_mentions" ("rolesId") + `); + await queryRunner.query(` + CREATE INDEX "IDX_a8242cf535337a490b0feaea0b" ON "message_role_mentions" ("messagesId") + `); + await queryRunner.query(` + DROP INDEX "IDX_b831eb18ceebd28976239b1e2f" + `); + await queryRunner.query(` + DROP INDEX "IDX_a343387fc560ef378760681c23" + `); + await queryRunner.query(` + ALTER TABLE "message_user_mentions" + RENAME TO "temporary_message_user_mentions" + `); + await queryRunner.query(` + CREATE TABLE "message_user_mentions" ( + "messagesId" varchar NOT NULL, + "usersId" varchar NOT NULL, + PRIMARY KEY ("messagesId", "usersId") + ) + `); + await queryRunner.query(` + INSERT INTO "message_user_mentions"("messagesId", "usersId") + SELECT "messagesId", + "usersId" + FROM "temporary_message_user_mentions" + `); + await queryRunner.query(` + DROP TABLE "temporary_message_user_mentions" + `); + await queryRunner.query(` + CREATE INDEX "IDX_b831eb18ceebd28976239b1e2f" ON "message_user_mentions" ("usersId") + `); + await queryRunner.query(` + CREATE INDEX "IDX_a343387fc560ef378760681c23" ON "message_user_mentions" ("messagesId") + `); + await queryRunner.query(` + DROP INDEX "IDX_e9080e7a7997a0170026d5139c" + `); + await queryRunner.query(` + DROP INDEX "IDX_5d7ddc8a5f9c167f548625e772" + `); + await queryRunner.query(` + ALTER TABLE "member_roles" + RENAME TO "temporary_member_roles" + `); + await queryRunner.query(` + CREATE TABLE "member_roles" ( + "index" integer NOT NULL, + "role_id" varchar NOT NULL, + PRIMARY KEY ("index", "role_id") + ) + `); + await queryRunner.query(` + INSERT INTO "member_roles"("index", "role_id") + SELECT "index", + "role_id" + FROM "temporary_member_roles" + `); + await queryRunner.query(` + DROP TABLE "temporary_member_roles" + `); + await queryRunner.query(` + CREATE INDEX "IDX_e9080e7a7997a0170026d5139c" ON "member_roles" ("role_id") + `); + await queryRunner.query(` + CREATE INDEX "IDX_5d7ddc8a5f9c167f548625e772" ON "member_roles" ("index") + `); + await queryRunner.query(` + ALTER TABLE "notes" + RENAME TO "temporary_notes" + `); + await queryRunner.query(` + CREATE TABLE "notes" ( + "id" varchar PRIMARY KEY NOT NULL, + "content" varchar NOT NULL, + "owner_id" varchar, + "target_id" varchar, + CONSTRAINT "UQ_74e6689b9568cc965b8bfc9150b" UNIQUE ("owner_id", "target_id") + ) + `); + await queryRunner.query(` + INSERT INTO "notes"("id", "content", "owner_id", "target_id") + SELECT "id", + "content", + "owner_id", + "target_id" + FROM "temporary_notes" + `); + await queryRunner.query(` + DROP TABLE "temporary_notes" + `); + await queryRunner.query(` + ALTER TABLE "sticker_packs" + RENAME TO "temporary_sticker_packs" + `); + await queryRunner.query(` + CREATE TABLE "sticker_packs" ( + "id" varchar PRIMARY KEY NOT NULL, + "name" varchar NOT NULL, + "description" varchar, + "banner_asset_id" varchar, + "cover_sticker_id" varchar, + "coverStickerId" varchar + ) + `); + await queryRunner.query(` + INSERT INTO "sticker_packs"( + "id", + "name", + "description", + "banner_asset_id", + "cover_sticker_id", + "coverStickerId" + ) + SELECT "id", + "name", + "description", + "banner_asset_id", + "cover_sticker_id", + "coverStickerId" + FROM "temporary_sticker_packs" + `); + await queryRunner.query(` + DROP TABLE "temporary_sticker_packs" + `); + await queryRunner.query(` + ALTER TABLE "sessions" + RENAME TO "temporary_sessions" + `); + await queryRunner.query(` + CREATE TABLE "sessions" ( + "id" varchar PRIMARY KEY NOT NULL, + "user_id" varchar, + "session_id" varchar NOT NULL, + "activities" text, + "client_info" text NOT NULL, + "status" varchar NOT NULL + ) + `); + await queryRunner.query(` + INSERT INTO "sessions"( + "id", + "user_id", + "session_id", + "activities", + "client_info", + "status" + ) + SELECT "id", + "user_id", + "session_id", + "activities", + "client_info", + "status" + FROM "temporary_sessions" + `); + await queryRunner.query(` + DROP TABLE "temporary_sessions" + `); + await queryRunner.query(` + ALTER TABLE "audit_logs" + RENAME TO "temporary_audit_logs" + `); + await queryRunner.query(` + CREATE TABLE "audit_logs" ( + "id" varchar PRIMARY KEY NOT NULL, + "user_id" varchar, + "action_type" integer NOT NULL, + "options" text, + "changes" text NOT NULL, + "reason" varchar, + "target_id" varchar + ) + `); + await queryRunner.query(` + INSERT INTO "audit_logs"( + "id", + "user_id", + "action_type", + "options", + "changes", + "reason", + "target_id" + ) + SELECT "id", + "user_id", + "action_type", + "options", + "changes", + "reason", + "target_id" + FROM "temporary_audit_logs" + `); + await queryRunner.query(` + DROP TABLE "temporary_audit_logs" + `); + await queryRunner.query(` + ALTER TABLE "applications" + RENAME TO "temporary_applications" + `); + await queryRunner.query(` + CREATE TABLE "applications" ( + "id" varchar PRIMARY KEY NOT NULL, + "name" varchar NOT NULL, + "icon" varchar, + "description" varchar NOT NULL, + "rpc_origins" text, + "bot_public" boolean NOT NULL, + "bot_require_code_grant" boolean NOT NULL, + "terms_of_service_url" varchar, + "privacy_policy_url" varchar, + "summary" varchar, + "verify_key" varchar NOT NULL, + "primary_sku_id" varchar, + "slug" varchar, + "cover_image" varchar, + "flags" varchar NOT NULL, + "owner_id" varchar, + "team_id" varchar, + "guild_id" varchar + ) + `); + await queryRunner.query(` + INSERT INTO "applications"( + "id", + "name", + "icon", + "description", + "rpc_origins", + "bot_public", + "bot_require_code_grant", + "terms_of_service_url", + "privacy_policy_url", + "summary", + "verify_key", + "primary_sku_id", + "slug", + "cover_image", + "flags", + "owner_id", + "team_id", + "guild_id" + ) + SELECT "id", + "name", + "icon", + "description", + "rpc_origins", + "bot_public", + "bot_require_code_grant", + "terms_of_service_url", + "privacy_policy_url", + "summary", + "verify_key", + "primary_sku_id", + "slug", + "cover_image", + "flags", + "owner_id", + "team_id", + "guild_id" + FROM "temporary_applications" + `); + await queryRunner.query(` + DROP TABLE "temporary_applications" + `); + await queryRunner.query(` + ALTER TABLE "teams" + RENAME TO "temporary_teams" + `); + await queryRunner.query(` + CREATE TABLE "teams" ( + "id" varchar PRIMARY KEY NOT NULL, + "icon" varchar, + "name" varchar NOT NULL, + "owner_user_id" varchar + ) + `); + await queryRunner.query(` + INSERT INTO "teams"("id", "icon", "name", "owner_user_id") + SELECT "id", + "icon", + "name", + "owner_user_id" + FROM "temporary_teams" + `); + await queryRunner.query(` + DROP TABLE "temporary_teams" + `); + await queryRunner.query(` + ALTER TABLE "team_members" + RENAME TO "temporary_team_members" + `); + await queryRunner.query(` + CREATE TABLE "team_members" ( + "id" varchar PRIMARY KEY NOT NULL, + "membership_state" integer NOT NULL, + "permissions" text NOT NULL, + "team_id" varchar, + "user_id" varchar + ) + `); + await queryRunner.query(` + INSERT INTO "team_members"( + "id", + "membership_state", + "permissions", + "team_id", + "user_id" + ) + SELECT "id", + "membership_state", + "permissions", + "team_id", + "user_id" + FROM "temporary_team_members" + `); + await queryRunner.query(` + DROP TABLE "temporary_team_members" + `); + await queryRunner.query(` + ALTER TABLE "guilds" + RENAME TO "temporary_guilds" + `); + await queryRunner.query(` + CREATE TABLE "guilds" ( + "id" varchar PRIMARY KEY NOT NULL, + "afk_channel_id" varchar, + "afk_timeout" integer, + "banner" varchar, + "default_message_notifications" integer, + "description" varchar, + "discovery_splash" varchar, + "explicit_content_filter" integer, + "features" text NOT NULL, + "primary_category_id" integer, + "icon" varchar, + "large" boolean, + "max_members" integer, + "max_presences" integer, + "max_video_channel_users" integer, + "member_count" integer, + "presence_count" integer, + "template_id" varchar, + "mfa_level" integer, + "name" varchar NOT NULL, + "owner_id" varchar, + "preferred_locale" varchar, + "premium_subscription_count" integer, + "premium_tier" integer, + "public_updates_channel_id" varchar, + "rules_channel_id" varchar, + "region" varchar, + "splash" varchar, + "system_channel_id" varchar, + "system_channel_flags" integer, + "unavailable" boolean, + "verification_level" integer, + "welcome_screen" text NOT NULL, + "widget_channel_id" varchar, + "widget_enabled" boolean, + "nsfw_level" integer, + "nsfw" boolean, + "parent" varchar + ) + `); + await queryRunner.query(` + INSERT INTO "guilds"( + "id", + "afk_channel_id", + "afk_timeout", + "banner", + "default_message_notifications", + "description", + "discovery_splash", + "explicit_content_filter", + "features", + "primary_category_id", + "icon", + "large", + "max_members", + "max_presences", + "max_video_channel_users", + "member_count", + "presence_count", + "template_id", + "mfa_level", + "name", + "owner_id", + "preferred_locale", + "premium_subscription_count", + "premium_tier", + "public_updates_channel_id", + "rules_channel_id", + "region", + "splash", + "system_channel_id", + "system_channel_flags", + "unavailable", + "verification_level", + "welcome_screen", + "widget_channel_id", + "widget_enabled", + "nsfw_level", + "nsfw", + "parent" + ) + SELECT "id", + "afk_channel_id", + "afk_timeout", + "banner", + "default_message_notifications", + "description", + "discovery_splash", + "explicit_content_filter", + "features", + "primary_category_id", + "icon", + "large", + "max_members", + "max_presences", + "max_video_channel_users", + "member_count", + "presence_count", + "template_id", + "mfa_level", + "name", + "owner_id", + "preferred_locale", + "premium_subscription_count", + "premium_tier", + "public_updates_channel_id", + "rules_channel_id", + "region", + "splash", + "system_channel_id", + "system_channel_flags", + "unavailable", + "verification_level", + "welcome_screen", + "widget_channel_id", + "widget_enabled", + "nsfw_level", + "nsfw", + "parent" + FROM "temporary_guilds" + `); + await queryRunner.query(` + DROP TABLE "temporary_guilds" + `); + await queryRunner.query(` + ALTER TABLE "templates" + RENAME TO "temporary_templates" + `); + await queryRunner.query(` + CREATE TABLE "templates" ( + "id" varchar PRIMARY KEY NOT NULL, + "code" varchar NOT NULL, + "name" varchar NOT NULL, + "description" varchar, + "usage_count" integer, + "creator_id" varchar, + "created_at" datetime NOT NULL, + "updated_at" datetime NOT NULL, + "source_guild_id" varchar, + "serialized_source_guild" text NOT NULL, + CONSTRAINT "UQ_be38737bf339baf63b1daeffb55" UNIQUE ("code") + ) + `); + await queryRunner.query(` + INSERT INTO "templates"( + "id", + "code", + "name", + "description", + "usage_count", + "creator_id", + "created_at", + "updated_at", + "source_guild_id", + "serialized_source_guild" + ) + SELECT "id", + "code", + "name", + "description", + "usage_count", + "creator_id", + "created_at", + "updated_at", + "source_guild_id", + "serialized_source_guild" + FROM "temporary_templates" + `); + await queryRunner.query(` + DROP TABLE "temporary_templates" + `); + await queryRunner.query(` + ALTER TABLE "emojis" + RENAME TO "temporary_emojis" + `); + await queryRunner.query(` + CREATE TABLE "emojis" ( + "id" varchar PRIMARY KEY NOT NULL, + "animated" boolean NOT NULL, + "available" boolean NOT NULL, + "guild_id" varchar NOT NULL, + "user_id" varchar, + "managed" boolean NOT NULL, + "name" varchar NOT NULL, + "require_colons" boolean NOT NULL, + "roles" text NOT NULL, + "groups" text + ) + `); + await queryRunner.query(` + INSERT INTO "emojis"( + "id", + "animated", + "available", + "guild_id", + "user_id", + "managed", + "name", + "require_colons", + "roles", + "groups" + ) + SELECT "id", + "animated", + "available", + "guild_id", + "user_id", + "managed", + "name", + "require_colons", + "roles", + "groups" + FROM "temporary_emojis" + `); + await queryRunner.query(` + DROP TABLE "temporary_emojis" + `); + await queryRunner.query(` + ALTER TABLE "channels" + RENAME TO "temporary_channels" + `); + await queryRunner.query(` + CREATE TABLE "channels" ( + "id" varchar PRIMARY KEY NOT NULL, + "created_at" datetime NOT NULL, + "name" varchar, + "icon" text, + "type" integer NOT NULL, + "last_message_id" varchar, + "guild_id" varchar, + "parent_id" varchar, + "owner_id" varchar, + "last_pin_timestamp" integer, + "default_auto_archive_duration" integer, + "position" integer, + "permission_overwrites" text, + "video_quality_mode" integer, + "bitrate" integer, + "user_limit" integer, + "nsfw" boolean, + "rate_limit_per_user" integer, + "topic" varchar, + "retention_policy_id" varchar + ) + `); + await queryRunner.query(` + INSERT INTO "channels"( + "id", + "created_at", + "name", + "icon", + "type", + "last_message_id", + "guild_id", + "parent_id", + "owner_id", + "last_pin_timestamp", + "default_auto_archive_duration", + "position", + "permission_overwrites", + "video_quality_mode", + "bitrate", + "user_limit", + "nsfw", + "rate_limit_per_user", + "topic", + "retention_policy_id" + ) + SELECT "id", + "created_at", + "name", + "icon", + "type", + "last_message_id", + "guild_id", + "parent_id", + "owner_id", + "last_pin_timestamp", + "default_auto_archive_duration", + "position", + "permission_overwrites", + "video_quality_mode", + "bitrate", + "user_limit", + "nsfw", + "rate_limit_per_user", + "topic", + "retention_policy_id" + FROM "temporary_channels" + `); + await queryRunner.query(` + DROP TABLE "temporary_channels" + `); + await queryRunner.query(` + ALTER TABLE "voice_states" + RENAME TO "temporary_voice_states" + `); + await queryRunner.query(` + CREATE TABLE "voice_states" ( + "id" varchar PRIMARY KEY NOT NULL, + "guild_id" varchar, + "channel_id" varchar, + "user_id" varchar, + "session_id" varchar NOT NULL, + "token" varchar, + "deaf" boolean NOT NULL, + "mute" boolean NOT NULL, + "self_deaf" boolean NOT NULL, + "self_mute" boolean NOT NULL, + "self_stream" boolean, + "self_video" boolean NOT NULL, + "suppress" boolean NOT NULL, + "request_to_speak_timestamp" datetime + ) + `); + await queryRunner.query(` + INSERT INTO "voice_states"( + "id", + "guild_id", + "channel_id", + "user_id", + "session_id", + "token", + "deaf", + "mute", + "self_deaf", + "self_mute", + "self_stream", + "self_video", + "suppress", + "request_to_speak_timestamp" + ) + SELECT "id", + "guild_id", + "channel_id", + "user_id", + "session_id", + "token", + "deaf", + "mute", + "self_deaf", + "self_mute", + "self_stream", + "self_video", + "suppress", + "request_to_speak_timestamp" + FROM "temporary_voice_states" + `); + await queryRunner.query(` + DROP TABLE "temporary_voice_states" + `); + await queryRunner.query(` + ALTER TABLE "invites" + RENAME TO "temporary_invites" + `); + await queryRunner.query(` + CREATE TABLE "invites" ( + "code" varchar PRIMARY KEY NOT NULL, + "temporary" boolean NOT NULL, + "uses" integer NOT NULL, + "max_uses" integer NOT NULL, + "max_age" integer NOT NULL, + "created_at" datetime NOT NULL, + "expires_at" datetime NOT NULL, + "guild_id" varchar, + "channel_id" varchar, + "inviter_id" varchar, + "target_user_id" varchar, + "target_user_type" integer, + "vanity_url" boolean + ) + `); + await queryRunner.query(` + INSERT INTO "invites"( + "code", + "temporary", + "uses", + "max_uses", + "max_age", + "created_at", + "expires_at", + "guild_id", + "channel_id", + "inviter_id", + "target_user_id", + "target_user_type", + "vanity_url" + ) + SELECT "code", + "temporary", + "uses", + "max_uses", + "max_age", + "created_at", + "expires_at", + "guild_id", + "channel_id", + "inviter_id", + "target_user_id", + "target_user_type", + "vanity_url" + FROM "temporary_invites" + `); + await queryRunner.query(` + DROP TABLE "temporary_invites" + `); + await queryRunner.query(` + DROP INDEX "IDX_0abf8b443321bd3cf7f81ee17a" + `); + await queryRunner.query(` + ALTER TABLE "read_states" + RENAME TO "temporary_read_states" + `); + await queryRunner.query(` + CREATE TABLE "read_states" ( + "id" varchar PRIMARY KEY NOT NULL, + "channel_id" varchar NOT NULL, + "user_id" varchar NOT NULL, + "last_message_id" varchar, + "public_ack" varchar, + "notifications_cursor" varchar, + "last_pin_timestamp" datetime, + "mention_count" integer + ) + `); + await queryRunner.query(` + INSERT INTO "read_states"( + "id", + "channel_id", + "user_id", + "last_message_id", + "public_ack", + "notifications_cursor", + "last_pin_timestamp", + "mention_count" + ) + SELECT "id", + "channel_id", + "user_id", + "last_message_id", + "public_ack", + "notifications_cursor", + "last_pin_timestamp", + "mention_count" + FROM "temporary_read_states" + `); + await queryRunner.query(` + DROP TABLE "temporary_read_states" + `); + await queryRunner.query(` + CREATE UNIQUE INDEX "IDX_0abf8b443321bd3cf7f81ee17a" ON "read_states" ("channel_id", "user_id") + `); + await queryRunner.query(` + DROP INDEX "IDX_3ed7a60fb7dbe04e1ba9332a8b" + `); + await queryRunner.query(` + DROP INDEX "IDX_05535bc695e9f7ee104616459d" + `); + await queryRunner.query(` + DROP INDEX "IDX_86b9109b155eb70c0a2ca3b4b6" + `); + await queryRunner.query(` + ALTER TABLE "messages" + RENAME TO "temporary_messages" + `); + await queryRunner.query(` + CREATE TABLE "messages" ( + "id" varchar PRIMARY KEY NOT NULL, + "channel_id" varchar, + "guild_id" varchar, + "author_id" varchar, + "member_id" varchar, + "webhook_id" varchar, + "application_id" varchar, + "content" varchar, + "timestamp" datetime NOT NULL DEFAULT (datetime('now')), + "edited_timestamp" datetime, + "tts" boolean, + "mention_everyone" boolean, + "embeds" text NOT NULL, + "reactions" text NOT NULL, + "nonce" text, + "pinned" boolean, + "type" integer NOT NULL, + "activity" text, + "flags" varchar, + "message_reference" text, + "interaction" text, + "components" text, + "message_reference_id" varchar + ) + `); + await queryRunner.query(` + INSERT INTO "messages"( + "id", + "channel_id", + "guild_id", + "author_id", + "member_id", + "webhook_id", + "application_id", + "content", + "timestamp", + "edited_timestamp", + "tts", + "mention_everyone", + "embeds", + "reactions", + "nonce", + "pinned", + "type", + "activity", + "flags", + "message_reference", + "interaction", + "components", + "message_reference_id" + ) + SELECT "id", + "channel_id", + "guild_id", + "author_id", + "member_id", + "webhook_id", + "application_id", + "content", + "timestamp", + "edited_timestamp", + "tts", + "mention_everyone", + "embeds", + "reactions", + "nonce", + "pinned", + "type", + "activity", + "flags", + "message_reference", + "interaction", + "components", + "message_reference_id" + FROM "temporary_messages" + `); + await queryRunner.query(` + DROP TABLE "temporary_messages" + `); + await queryRunner.query(` + CREATE UNIQUE INDEX "IDX_3ed7a60fb7dbe04e1ba9332a8b" ON "messages" ("channel_id", "id") + `); + await queryRunner.query(` + CREATE INDEX "IDX_05535bc695e9f7ee104616459d" ON "messages" ("author_id") + `); + await queryRunner.query(` + CREATE INDEX "IDX_86b9109b155eb70c0a2ca3b4b6" ON "messages" ("channel_id") + `); + await queryRunner.query(` + ALTER TABLE "attachments" + RENAME TO "temporary_attachments" + `); + await queryRunner.query(` + CREATE TABLE "attachments" ( + "id" varchar PRIMARY KEY NOT NULL, + "filename" varchar NOT NULL, + "size" integer NOT NULL, + "url" varchar NOT NULL, + "proxy_url" varchar NOT NULL, + "height" integer, + "width" integer, + "content_type" varchar, + "message_id" varchar + ) + `); + await queryRunner.query(` + INSERT INTO "attachments"( + "id", + "filename", + "size", + "url", + "proxy_url", + "height", + "width", + "content_type", + "message_id" + ) + SELECT "id", + "filename", + "size", + "url", + "proxy_url", + "height", + "width", + "content_type", + "message_id" + FROM "temporary_attachments" + `); + await queryRunner.query(` + DROP TABLE "temporary_attachments" + `); + await queryRunner.query(` + ALTER TABLE "stickers" + RENAME TO "temporary_stickers" + `); + await queryRunner.query(` + CREATE TABLE "stickers" ( + "id" varchar PRIMARY KEY NOT NULL, + "name" varchar NOT NULL, + "description" varchar, + "available" boolean, + "tags" varchar, + "pack_id" varchar, + "guild_id" varchar, + "user_id" varchar, + "type" integer NOT NULL, + "format_type" integer NOT NULL + ) + `); + await queryRunner.query(` + INSERT INTO "stickers"( + "id", + "name", + "description", + "available", + "tags", + "pack_id", + "guild_id", + "user_id", + "type", + "format_type" + ) + SELECT "id", + "name", + "description", + "available", + "tags", + "pack_id", + "guild_id", + "user_id", + "type", + "format_type" + FROM "temporary_stickers" + `); + await queryRunner.query(` + DROP TABLE "temporary_stickers" + `); + await queryRunner.query(` + ALTER TABLE "webhooks" + RENAME TO "temporary_webhooks" + `); + await queryRunner.query(` + CREATE TABLE "webhooks" ( + "id" varchar PRIMARY KEY NOT NULL, + "type" integer NOT NULL, + "name" varchar, + "avatar" varchar, + "token" varchar, + "guild_id" varchar, + "channel_id" varchar, + "application_id" varchar, + "user_id" varchar, + "source_guild_id" varchar + ) + `); + await queryRunner.query(` + INSERT INTO "webhooks"( + "id", + "type", + "name", + "avatar", + "token", + "guild_id", + "channel_id", + "application_id", + "user_id", + "source_guild_id" + ) + SELECT "id", + "type", + "name", + "avatar", + "token", + "guild_id", + "channel_id", + "application_id", + "user_id", + "source_guild_id" + FROM "temporary_webhooks" + `); + await queryRunner.query(` + DROP TABLE "temporary_webhooks" + `); + await queryRunner.query(` + DROP INDEX "IDX_bb2bf9386ac443afbbbf9f12d3" + `); + await queryRunner.query(` + ALTER TABLE "members" + RENAME TO "temporary_members" + `); + await queryRunner.query(` + CREATE TABLE "members" ( + "index" integer PRIMARY KEY AUTOINCREMENT NOT NULL, + "id" varchar NOT NULL, + "guild_id" varchar NOT NULL, + "nick" varchar, + "joined_at" datetime NOT NULL, + "premium_since" bigint, + "deaf" boolean NOT NULL, + "mute" boolean NOT NULL, + "pending" boolean NOT NULL, + "settings" text NOT NULL, + "last_message_id" varchar, + "joined_by" varchar + ) + `); + await queryRunner.query(` + INSERT INTO "members"( + "index", + "id", + "guild_id", + "nick", + "joined_at", + "premium_since", + "deaf", + "mute", + "pending", + "settings", + "last_message_id", + "joined_by" + ) + SELECT "index", + "id", + "guild_id", + "nick", + "joined_at", + "premium_since", + "deaf", + "mute", + "pending", + "settings", + "last_message_id", + "joined_by" + FROM "temporary_members" + `); + await queryRunner.query(` + DROP TABLE "temporary_members" + `); + await queryRunner.query(` + CREATE UNIQUE INDEX "IDX_bb2bf9386ac443afbbbf9f12d3" ON "members" ("id", "guild_id") + `); + await queryRunner.query(` + ALTER TABLE "roles" + RENAME TO "temporary_roles" + `); + await queryRunner.query(` + CREATE TABLE "roles" ( + "id" varchar PRIMARY KEY NOT NULL, + "guild_id" varchar, + "color" integer NOT NULL, + "hoist" boolean NOT NULL, + "managed" boolean NOT NULL, + "mentionable" boolean NOT NULL, + "name" varchar NOT NULL, + "permissions" varchar NOT NULL, + "position" integer NOT NULL, + "icon" varchar, + "unicode_emoji" varchar, + "tags" text + ) + `); + await queryRunner.query(` + INSERT INTO "roles"( + "id", + "guild_id", + "color", + "hoist", + "managed", + "mentionable", + "name", + "permissions", + "position", + "icon", + "unicode_emoji", + "tags" + ) + SELECT "id", + "guild_id", + "color", + "hoist", + "managed", + "mentionable", + "name", + "permissions", + "position", + "icon", + "unicode_emoji", + "tags" + FROM "temporary_roles" + `); + await queryRunner.query(` + DROP TABLE "temporary_roles" + `); + await queryRunner.query(` + ALTER TABLE "recipients" + RENAME TO "temporary_recipients" + `); + await queryRunner.query(` + CREATE TABLE "recipients" ( + "id" varchar PRIMARY KEY NOT NULL, + "channel_id" varchar NOT NULL, + "user_id" varchar NOT NULL, + "closed" boolean NOT NULL DEFAULT (0) + ) + `); + await queryRunner.query(` + INSERT INTO "recipients"("id", "channel_id", "user_id", "closed") + SELECT "id", + "channel_id", + "user_id", + "closed" + FROM "temporary_recipients" + `); + await queryRunner.query(` + DROP TABLE "temporary_recipients" + `); + await queryRunner.query(` + ALTER TABLE "bans" + RENAME TO "temporary_bans" + `); + await queryRunner.query(` + CREATE TABLE "bans" ( + "id" varchar PRIMARY KEY NOT NULL, + "user_id" varchar, + "guild_id" varchar, + "executor_id" varchar, + "ip" varchar NOT NULL, + "reason" varchar + ) + `); + await queryRunner.query(` + INSERT INTO "bans"( + "id", + "user_id", + "guild_id", + "executor_id", + "ip", + "reason" + ) + SELECT "id", + "user_id", + "guild_id", + "executor_id", + "ip", + "reason" + FROM "temporary_bans" + `); + await queryRunner.query(` + DROP TABLE "temporary_bans" + `); + await queryRunner.query(` + ALTER TABLE "backup_codes" + RENAME TO "temporary_backup_codes" + `); + await queryRunner.query(` + CREATE TABLE "backup_codes" ( + "id" varchar PRIMARY KEY NOT NULL, + "code" varchar NOT NULL, + "consumed" boolean NOT NULL, + "expired" boolean NOT NULL, + "user_id" varchar + ) + `); + await queryRunner.query(` + INSERT INTO "backup_codes"("id", "code", "consumed", "expired", "user_id") + SELECT "id", + "code", + "consumed", + "expired", + "user_id" + FROM "temporary_backup_codes" + `); + await queryRunner.query(` + DROP TABLE "temporary_backup_codes" + `); + await queryRunner.query(` + ALTER TABLE "connected_accounts" + RENAME TO "temporary_connected_accounts" + `); + await queryRunner.query(` + CREATE TABLE "connected_accounts" ( + "id" varchar PRIMARY KEY NOT NULL, + "user_id" varchar, + "access_token" varchar NOT NULL, + "friend_sync" boolean NOT NULL, + "name" varchar NOT NULL, + "revoked" boolean NOT NULL, + "show_activity" boolean NOT NULL, + "type" varchar NOT NULL, + "verified" boolean NOT NULL, + "visibility" integer NOT NULL + ) + `); + await queryRunner.query(` + INSERT INTO "connected_accounts"( + "id", + "user_id", + "access_token", + "friend_sync", + "name", + "revoked", + "show_activity", + "type", + "verified", + "visibility" + ) + SELECT "id", + "user_id", + "access_token", + "friend_sync", + "name", + "revoked", + "show_activity", + "type", + "verified", + "visibility" + FROM "temporary_connected_accounts" + `); + await queryRunner.query(` + DROP TABLE "temporary_connected_accounts" + `); + await queryRunner.query(` + DROP INDEX "IDX_a0b2ff0a598df0b0d055934a17" + `); + await queryRunner.query(` + ALTER TABLE "relationships" + RENAME TO "temporary_relationships" + `); + await queryRunner.query(` + CREATE TABLE "relationships" ( + "id" varchar PRIMARY KEY NOT NULL, + "from_id" varchar NOT NULL, + "to_id" varchar NOT NULL, + "nickname" varchar, + "type" integer NOT NULL + ) + `); + await queryRunner.query(` + INSERT INTO "relationships"("id", "from_id", "to_id", "nickname", "type") + SELECT "id", + "from_id", + "to_id", + "nickname", + "type" + FROM "temporary_relationships" + `); + await queryRunner.query(` + DROP TABLE "temporary_relationships" + `); + await queryRunner.query(` + CREATE UNIQUE INDEX "IDX_a0b2ff0a598df0b0d055934a17" ON "relationships" ("from_id", "to_id") + `); + await queryRunner.query(` + DROP INDEX "IDX_e22a70819d07659c7a71c112a1" + `); + await queryRunner.query(` + DROP INDEX "IDX_40bb6f23e7cc133292e92829d2" + `); + await queryRunner.query(` + DROP TABLE "message_stickers" + `); + await queryRunner.query(` + DROP INDEX "IDX_bdb8c09e1464cabf62105bf4b9" + `); + await queryRunner.query(` + DROP INDEX "IDX_2a27102ecd1d81b4582a436092" + `); + await queryRunner.query(` + DROP TABLE "message_channel_mentions" + `); + await queryRunner.query(` + DROP INDEX "IDX_29d63eb1a458200851bc37d074" + `); + await queryRunner.query(` + DROP INDEX "IDX_a8242cf535337a490b0feaea0b" + `); + await queryRunner.query(` + DROP TABLE "message_role_mentions" + `); + await queryRunner.query(` + DROP INDEX "IDX_b831eb18ceebd28976239b1e2f" + `); + await queryRunner.query(` + DROP INDEX "IDX_a343387fc560ef378760681c23" + `); + await queryRunner.query(` + DROP TABLE "message_user_mentions" + `); + await queryRunner.query(` + DROP INDEX "IDX_e9080e7a7997a0170026d5139c" + `); + await queryRunner.query(` + DROP INDEX "IDX_5d7ddc8a5f9c167f548625e772" + `); + await queryRunner.query(` + DROP TABLE "member_roles" + `); + await queryRunner.query(` + DROP TABLE "notes" + `); + await queryRunner.query(` + DROP TABLE "client_release" + `); + await queryRunner.query(` + DROP TABLE "sticker_packs" + `); + await queryRunner.query(` + DROP TABLE "sessions" + `); + await queryRunner.query(` + DROP TABLE "rate_limits" + `); + await queryRunner.query(` + DROP TABLE "categories" + `); + await queryRunner.query(` + DROP TABLE "audit_logs" + `); + await queryRunner.query(` + DROP TABLE "applications" + `); + await queryRunner.query(` + DROP TABLE "teams" + `); + await queryRunner.query(` + DROP TABLE "team_members" + `); + await queryRunner.query(` + DROP TABLE "guilds" + `); + await queryRunner.query(` + DROP TABLE "templates" + `); + await queryRunner.query(` + DROP TABLE "emojis" + `); + await queryRunner.query(` + DROP TABLE "channels" + `); + await queryRunner.query(` + DROP TABLE "voice_states" + `); + await queryRunner.query(` + DROP TABLE "invites" + `); + await queryRunner.query(` + DROP INDEX "IDX_0abf8b443321bd3cf7f81ee17a" + `); + await queryRunner.query(` + DROP TABLE "read_states" + `); + await queryRunner.query(` + DROP INDEX "IDX_3ed7a60fb7dbe04e1ba9332a8b" + `); + await queryRunner.query(` + DROP INDEX "IDX_05535bc695e9f7ee104616459d" + `); + await queryRunner.query(` + DROP INDEX "IDX_86b9109b155eb70c0a2ca3b4b6" + `); + await queryRunner.query(` + DROP TABLE "messages" + `); + await queryRunner.query(` + DROP TABLE "attachments" + `); + await queryRunner.query(` + DROP TABLE "stickers" + `); + await queryRunner.query(` + DROP TABLE "webhooks" + `); + await queryRunner.query(` + DROP INDEX "IDX_bb2bf9386ac443afbbbf9f12d3" + `); + await queryRunner.query(` + DROP TABLE "members" + `); + await queryRunner.query(` + DROP TABLE "roles" + `); + await queryRunner.query(` + DROP TABLE "recipients" + `); + await queryRunner.query(` + DROP TABLE "bans" + `); + await queryRunner.query(` + DROP TABLE "backup_codes" + `); + await queryRunner.query(` + DROP TABLE "users" + `); + await queryRunner.query(` + DROP TABLE "connected_accounts" + `); + await queryRunner.query(` + DROP INDEX "IDX_a0b2ff0a598df0b0d055934a17" + `); + await queryRunner.query(` + DROP TABLE "relationships" + `); + await queryRunner.query(` + DROP TABLE "config" + `); + } +} diff --git a/src/util/migrations/sqlite/1659921722863-premium_since_as_date.ts b/src/util/migrations/sqlite/1659921722863-premium_since_as_date.ts new file mode 100644
index 00000000..b6307ee9 --- /dev/null +++ b/src/util/migrations/sqlite/1659921722863-premium_since_as_date.ts
@@ -0,0 +1,251 @@ +import { MigrationInterface, QueryRunner } from "typeorm"; + +export class premiumSinceAsDate1659921722863 implements MigrationInterface { + name = "premiumSinceAsDate1659921722863"; + + public async up(queryRunner: QueryRunner): Promise<void> { + await queryRunner.query(` + DROP INDEX "IDX_bb2bf9386ac443afbbbf9f12d3" + `); + await queryRunner.query(` + CREATE TABLE "temporary_members" ( + "index" integer PRIMARY KEY AUTOINCREMENT NOT NULL, + "id" varchar NOT NULL, + "guild_id" varchar NOT NULL, + "nick" varchar, + "joined_at" datetime NOT NULL, + "premium_since" bigint, + "deaf" boolean NOT NULL, + "mute" boolean NOT NULL, + "pending" boolean NOT NULL, + "settings" text NOT NULL, + "last_message_id" varchar, + "joined_by" varchar, + CONSTRAINT "FK_16aceddd5b89825b8ed6029ad1c" FOREIGN KEY ("guild_id") REFERENCES "guilds" ("id") ON DELETE CASCADE ON UPDATE NO ACTION, + CONSTRAINT "FK_28b53062261b996d9c99fa12404" FOREIGN KEY ("id") REFERENCES "users" ("id") ON DELETE CASCADE ON UPDATE NO ACTION + ) + `); + await queryRunner.query(` + INSERT INTO "temporary_members"( + "index", + "id", + "guild_id", + "nick", + "joined_at", + "premium_since", + "deaf", + "mute", + "pending", + "settings", + "last_message_id", + "joined_by" + ) + SELECT "index", + "id", + "guild_id", + "nick", + "joined_at", + "premium_since", + "deaf", + "mute", + "pending", + "settings", + "last_message_id", + "joined_by" + FROM "members" + `); + await queryRunner.query(` + DROP TABLE "members" + `); + await queryRunner.query(` + ALTER TABLE "temporary_members" + RENAME TO "members" + `); + await queryRunner.query(` + CREATE UNIQUE INDEX "IDX_bb2bf9386ac443afbbbf9f12d3" ON "members" ("id", "guild_id") + `); + await queryRunner.query(` + DROP INDEX "IDX_bb2bf9386ac443afbbbf9f12d3" + `); + await queryRunner.query(` + CREATE TABLE "temporary_members" ( + "index" integer PRIMARY KEY AUTOINCREMENT NOT NULL, + "id" varchar NOT NULL, + "guild_id" varchar NOT NULL, + "nick" varchar, + "joined_at" datetime NOT NULL, + "premium_since" datetime, + "deaf" boolean NOT NULL, + "mute" boolean NOT NULL, + "pending" boolean NOT NULL, + "settings" text NOT NULL, + "last_message_id" varchar, + "joined_by" varchar, + CONSTRAINT "FK_16aceddd5b89825b8ed6029ad1c" FOREIGN KEY ("guild_id") REFERENCES "guilds" ("id") ON DELETE CASCADE ON UPDATE NO ACTION, + CONSTRAINT "FK_28b53062261b996d9c99fa12404" FOREIGN KEY ("id") REFERENCES "users" ("id") ON DELETE CASCADE ON UPDATE NO ACTION + ) + `); + await queryRunner.query(` + INSERT INTO "temporary_members"( + "index", + "id", + "guild_id", + "nick", + "joined_at", + "premium_since", + "deaf", + "mute", + "pending", + "settings", + "last_message_id", + "joined_by" + ) + SELECT "index", + "id", + "guild_id", + "nick", + "joined_at", + "premium_since", + "deaf", + "mute", + "pending", + "settings", + "last_message_id", + "joined_by" + FROM "members" + `); + await queryRunner.query(` + DROP TABLE "members" + `); + await queryRunner.query(` + ALTER TABLE "temporary_members" + RENAME TO "members" + `); + await queryRunner.query(` + CREATE UNIQUE INDEX "IDX_bb2bf9386ac443afbbbf9f12d3" ON "members" ("id", "guild_id") + `); + } + + public async down(queryRunner: QueryRunner): Promise<void> { + await queryRunner.query(` + DROP INDEX "IDX_bb2bf9386ac443afbbbf9f12d3" + `); + await queryRunner.query(` + ALTER TABLE "members" + RENAME TO "temporary_members" + `); + await queryRunner.query(` + CREATE TABLE "members" ( + "index" integer PRIMARY KEY AUTOINCREMENT NOT NULL, + "id" varchar NOT NULL, + "guild_id" varchar NOT NULL, + "nick" varchar, + "joined_at" datetime NOT NULL, + "premium_since" bigint, + "deaf" boolean NOT NULL, + "mute" boolean NOT NULL, + "pending" boolean NOT NULL, + "settings" text NOT NULL, + "last_message_id" varchar, + "joined_by" varchar, + CONSTRAINT "FK_16aceddd5b89825b8ed6029ad1c" FOREIGN KEY ("guild_id") REFERENCES "guilds" ("id") ON DELETE CASCADE ON UPDATE NO ACTION, + CONSTRAINT "FK_28b53062261b996d9c99fa12404" FOREIGN KEY ("id") REFERENCES "users" ("id") ON DELETE CASCADE ON UPDATE NO ACTION + ) + `); + await queryRunner.query(` + INSERT INTO "members"( + "index", + "id", + "guild_id", + "nick", + "joined_at", + "premium_since", + "deaf", + "mute", + "pending", + "settings", + "last_message_id", + "joined_by" + ) + SELECT "index", + "id", + "guild_id", + "nick", + "joined_at", + "premium_since", + "deaf", + "mute", + "pending", + "settings", + "last_message_id", + "joined_by" + FROM "temporary_members" + `); + await queryRunner.query(` + DROP TABLE "temporary_members" + `); + await queryRunner.query(` + CREATE UNIQUE INDEX "IDX_bb2bf9386ac443afbbbf9f12d3" ON "members" ("id", "guild_id") + `); + await queryRunner.query(` + DROP INDEX "IDX_bb2bf9386ac443afbbbf9f12d3" + `); + await queryRunner.query(` + ALTER TABLE "members" + RENAME TO "temporary_members" + `); + await queryRunner.query(` + CREATE TABLE "members" ( + "index" integer PRIMARY KEY AUTOINCREMENT NOT NULL, + "id" varchar NOT NULL, + "guild_id" varchar NOT NULL, + "nick" varchar, + "joined_at" datetime NOT NULL, + "premium_since" bigint, + "deaf" boolean NOT NULL, + "mute" boolean NOT NULL, + "pending" boolean NOT NULL, + "settings" text NOT NULL, + "last_message_id" varchar, + "joined_by" varchar, + CONSTRAINT "FK_16aceddd5b89825b8ed6029ad1c" FOREIGN KEY ("guild_id") REFERENCES "guilds" ("id") ON DELETE CASCADE ON UPDATE NO ACTION, + CONSTRAINT "FK_28b53062261b996d9c99fa12404" FOREIGN KEY ("id") REFERENCES "users" ("id") ON DELETE CASCADE ON UPDATE NO ACTION + ) + `); + await queryRunner.query(` + INSERT INTO "members"( + "index", + "id", + "guild_id", + "nick", + "joined_at", + "premium_since", + "deaf", + "mute", + "pending", + "settings", + "last_message_id", + "joined_by" + ) + SELECT "index", + "id", + "guild_id", + "nick", + "joined_at", + "premium_since", + "deaf", + "mute", + "pending", + "settings", + "last_message_id", + "joined_by" + FROM "temporary_members" + `); + await queryRunner.query(` + DROP TABLE "temporary_members" + `); + await queryRunner.query(` + CREATE UNIQUE INDEX "IDX_bb2bf9386ac443afbbbf9f12d3" ON "members" ("id", "guild_id") + `); + } +} diff --git a/src/util/migrations/sqlite/1660130536131-updated-applications.ts b/src/util/migrations/sqlite/1660130536131-updated-applications.ts new file mode 100644
index 00000000..bcb1c929 --- /dev/null +++ b/src/util/migrations/sqlite/1660130536131-updated-applications.ts
@@ -0,0 +1,828 @@ +import { MigrationInterface, QueryRunner } from "typeorm"; + +export class updatedApplications1660130536131 implements MigrationInterface { + name = "updatedApplications1660130536131"; + + public async up(queryRunner: QueryRunner): Promise<void> { + await queryRunner.query(` + CREATE TABLE "temporary_applications" ( + "id" varchar PRIMARY KEY NOT NULL, + "name" varchar NOT NULL, + "icon" varchar, + "description" varchar NOT NULL, + "rpc_origins" text, + "bot_public" boolean NOT NULL, + "bot_require_code_grant" boolean NOT NULL, + "terms_of_service_url" varchar, + "privacy_policy_url" varchar, + "summary" varchar, + "verify_key" varchar NOT NULL, + "primary_sku_id" varchar, + "slug" varchar, + "cover_image" varchar, + "flags" varchar NOT NULL, + "owner_id" varchar, + "team_id" varchar, + "guild_id" varchar, + CONSTRAINT "FK_a36ed02953077f408d0f3ebc424" FOREIGN KEY ("team_id") REFERENCES "teams" ("id") ON DELETE CASCADE ON UPDATE NO ACTION, + CONSTRAINT "FK_e57508958bf92b9d9d25231b5e8" FOREIGN KEY ("owner_id") REFERENCES "users" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION + ) + `); + await queryRunner.query(` + INSERT INTO "temporary_applications"( + "id", + "name", + "icon", + "description", + "rpc_origins", + "bot_public", + "bot_require_code_grant", + "terms_of_service_url", + "privacy_policy_url", + "summary", + "verify_key", + "primary_sku_id", + "slug", + "cover_image", + "flags", + "owner_id", + "team_id", + "guild_id" + ) + SELECT "id", + "name", + "icon", + "description", + "rpc_origins", + "bot_public", + "bot_require_code_grant", + "terms_of_service_url", + "privacy_policy_url", + "summary", + "verify_key", + "primary_sku_id", + "slug", + "cover_image", + "flags", + "owner_id", + "team_id", + "guild_id" + FROM "applications" + `); + await queryRunner.query(` + DROP TABLE "applications" + `); + await queryRunner.query(` + ALTER TABLE "temporary_applications" + RENAME TO "applications" + `); + await queryRunner.query(` + CREATE TABLE "temporary_applications" ( + "id" varchar PRIMARY KEY NOT NULL, + "name" varchar NOT NULL, + "icon" varchar, + "description" varchar NOT NULL, + "bot_public" boolean NOT NULL, + "bot_require_code_grant" boolean NOT NULL, + "terms_of_service_url" varchar, + "privacy_policy_url" varchar, + "summary" varchar, + "verify_key" varchar NOT NULL, + "cover_image" varchar, + "flags" varchar NOT NULL, + "owner_id" varchar, + "team_id" varchar, + CONSTRAINT "FK_a36ed02953077f408d0f3ebc424" FOREIGN KEY ("team_id") REFERENCES "teams" ("id") ON DELETE CASCADE ON UPDATE NO ACTION, + CONSTRAINT "FK_e57508958bf92b9d9d25231b5e8" FOREIGN KEY ("owner_id") REFERENCES "users" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION + ) + `); + await queryRunner.query(` + INSERT INTO "temporary_applications"( + "id", + "name", + "icon", + "description", + "bot_public", + "bot_require_code_grant", + "terms_of_service_url", + "privacy_policy_url", + "summary", + "verify_key", + "cover_image", + "flags", + "owner_id", + "team_id" + ) + SELECT "id", + "name", + "icon", + "description", + "bot_public", + "bot_require_code_grant", + "terms_of_service_url", + "privacy_policy_url", + "summary", + "verify_key", + "cover_image", + "flags", + "owner_id", + "team_id" + FROM "applications" + `); + await queryRunner.query(` + DROP TABLE "applications" + `); + await queryRunner.query(` + ALTER TABLE "temporary_applications" + RENAME TO "applications" + `); + await queryRunner.query(` + CREATE TABLE "temporary_applications" ( + "id" varchar PRIMARY KEY NOT NULL, + "name" varchar NOT NULL, + "icon" varchar, + "description" varchar NOT NULL, + "bot_public" boolean NOT NULL, + "bot_require_code_grant" boolean NOT NULL, + "terms_of_service_url" varchar, + "privacy_policy_url" varchar, + "summary" varchar, + "verify_key" varchar NOT NULL, + "cover_image" varchar, + "flags" varchar NOT NULL, + "owner_id" varchar, + "team_id" varchar, + "type" text, + "hook" boolean NOT NULL, + "redirect_uris" text, + "rpc_application_state" integer, + "store_application_state" integer, + "verification_state" integer, + "interactions_endpoint_url" varchar, + "integration_public" boolean, + "integration_require_code_grant" boolean, + "discoverability_state" integer, + "discovery_eligibility_flags" integer, + "tags" text, + "install_params" text, + "bot_user_id" varchar, + CONSTRAINT "UQ_b7f6e13565e920916d902e1f431" UNIQUE ("bot_user_id"), + CONSTRAINT "FK_a36ed02953077f408d0f3ebc424" FOREIGN KEY ("team_id") REFERENCES "teams" ("id") ON DELETE CASCADE ON UPDATE NO ACTION, + CONSTRAINT "FK_e57508958bf92b9d9d25231b5e8" FOREIGN KEY ("owner_id") REFERENCES "users" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION + ) + `); + await queryRunner.query(` + INSERT INTO "temporary_applications"( + "id", + "name", + "icon", + "description", + "bot_public", + "bot_require_code_grant", + "terms_of_service_url", + "privacy_policy_url", + "summary", + "verify_key", + "cover_image", + "flags", + "owner_id", + "team_id" + ) + SELECT "id", + "name", + "icon", + "description", + "bot_public", + "bot_require_code_grant", + "terms_of_service_url", + "privacy_policy_url", + "summary", + "verify_key", + "cover_image", + "flags", + "owner_id", + "team_id" + FROM "applications" + `); + await queryRunner.query(` + DROP TABLE "applications" + `); + await queryRunner.query(` + ALTER TABLE "temporary_applications" + RENAME TO "applications" + `); + await queryRunner.query(` + CREATE TABLE "temporary_applications" ( + "id" varchar PRIMARY KEY NOT NULL, + "name" varchar NOT NULL, + "icon" varchar, + "description" varchar, + "bot_public" boolean NOT NULL, + "bot_require_code_grant" boolean NOT NULL, + "terms_of_service_url" varchar, + "privacy_policy_url" varchar, + "summary" varchar, + "verify_key" varchar NOT NULL, + "cover_image" varchar, + "flags" integer NOT NULL, + "owner_id" varchar, + "team_id" varchar, + "type" text, + "hook" boolean NOT NULL, + "redirect_uris" text, + "rpc_application_state" integer, + "store_application_state" integer, + "verification_state" integer, + "interactions_endpoint_url" varchar, + "integration_public" boolean, + "integration_require_code_grant" boolean, + "discoverability_state" integer, + "discovery_eligibility_flags" integer, + "tags" text, + "install_params" text, + "bot_user_id" varchar, + CONSTRAINT "UQ_b7f6e13565e920916d902e1f431" UNIQUE ("bot_user_id"), + CONSTRAINT "FK_a36ed02953077f408d0f3ebc424" FOREIGN KEY ("team_id") REFERENCES "teams" ("id") ON DELETE CASCADE ON UPDATE NO ACTION, + CONSTRAINT "FK_e57508958bf92b9d9d25231b5e8" FOREIGN KEY ("owner_id") REFERENCES "users" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION + ) + `); + await queryRunner.query(` + INSERT INTO "temporary_applications"( + "id", + "name", + "icon", + "description", + "bot_public", + "bot_require_code_grant", + "terms_of_service_url", + "privacy_policy_url", + "summary", + "verify_key", + "cover_image", + "flags", + "owner_id", + "team_id", + "type", + "hook", + "redirect_uris", + "rpc_application_state", + "store_application_state", + "verification_state", + "interactions_endpoint_url", + "integration_public", + "integration_require_code_grant", + "discoverability_state", + "discovery_eligibility_flags", + "tags", + "install_params", + "bot_user_id" + ) + SELECT "id", + "name", + "icon", + "description", + "bot_public", + "bot_require_code_grant", + "terms_of_service_url", + "privacy_policy_url", + "summary", + "verify_key", + "cover_image", + "flags", + "owner_id", + "team_id", + "type", + "hook", + "redirect_uris", + "rpc_application_state", + "store_application_state", + "verification_state", + "interactions_endpoint_url", + "integration_public", + "integration_require_code_grant", + "discoverability_state", + "discovery_eligibility_flags", + "tags", + "install_params", + "bot_user_id" + FROM "applications" + `); + await queryRunner.query(` + DROP TABLE "applications" + `); + await queryRunner.query(` + ALTER TABLE "temporary_applications" + RENAME TO "applications" + `); + await queryRunner.query(` + CREATE TABLE "temporary_applications" ( + "id" varchar PRIMARY KEY NOT NULL, + "name" varchar NOT NULL, + "icon" varchar, + "description" varchar, + "bot_public" boolean NOT NULL, + "bot_require_code_grant" boolean NOT NULL, + "terms_of_service_url" varchar, + "privacy_policy_url" varchar, + "summary" varchar, + "verify_key" varchar NOT NULL, + "cover_image" varchar, + "flags" integer NOT NULL, + "owner_id" varchar, + "team_id" varchar, + "type" text, + "hook" boolean NOT NULL, + "redirect_uris" text, + "rpc_application_state" integer, + "store_application_state" integer, + "verification_state" integer, + "interactions_endpoint_url" varchar, + "integration_public" boolean, + "integration_require_code_grant" boolean, + "discoverability_state" integer, + "discovery_eligibility_flags" integer, + "tags" text, + "install_params" text, + "bot_user_id" varchar, + CONSTRAINT "UQ_b7f6e13565e920916d902e1f431" UNIQUE ("bot_user_id"), + CONSTRAINT "FK_a36ed02953077f408d0f3ebc424" FOREIGN KEY ("team_id") REFERENCES "teams" ("id") ON DELETE CASCADE ON UPDATE NO ACTION, + CONSTRAINT "FK_e57508958bf92b9d9d25231b5e8" FOREIGN KEY ("owner_id") REFERENCES "users" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION, + CONSTRAINT "FK_2ce5a55796fe4c2f77ece57a647" FOREIGN KEY ("bot_user_id") REFERENCES "users" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION + ) + `); + await queryRunner.query(` + INSERT INTO "temporary_applications"( + "id", + "name", + "icon", + "description", + "bot_public", + "bot_require_code_grant", + "terms_of_service_url", + "privacy_policy_url", + "summary", + "verify_key", + "cover_image", + "flags", + "owner_id", + "team_id", + "type", + "hook", + "redirect_uris", + "rpc_application_state", + "store_application_state", + "verification_state", + "interactions_endpoint_url", + "integration_public", + "integration_require_code_grant", + "discoverability_state", + "discovery_eligibility_flags", + "tags", + "install_params", + "bot_user_id" + ) + SELECT "id", + "name", + "icon", + "description", + "bot_public", + "bot_require_code_grant", + "terms_of_service_url", + "privacy_policy_url", + "summary", + "verify_key", + "cover_image", + "flags", + "owner_id", + "team_id", + "type", + "hook", + "redirect_uris", + "rpc_application_state", + "store_application_state", + "verification_state", + "interactions_endpoint_url", + "integration_public", + "integration_require_code_grant", + "discoverability_state", + "discovery_eligibility_flags", + "tags", + "install_params", + "bot_user_id" + FROM "applications" + `); + await queryRunner.query(` + DROP TABLE "applications" + `); + await queryRunner.query(` + ALTER TABLE "temporary_applications" + RENAME TO "applications" + `); + } + + public async down(queryRunner: QueryRunner): Promise<void> { + await queryRunner.query(` + ALTER TABLE "applications" + RENAME TO "temporary_applications" + `); + await queryRunner.query(` + CREATE TABLE "applications" ( + "id" varchar PRIMARY KEY NOT NULL, + "name" varchar NOT NULL, + "icon" varchar, + "description" varchar, + "bot_public" boolean NOT NULL, + "bot_require_code_grant" boolean NOT NULL, + "terms_of_service_url" varchar, + "privacy_policy_url" varchar, + "summary" varchar, + "verify_key" varchar NOT NULL, + "cover_image" varchar, + "flags" integer NOT NULL, + "owner_id" varchar, + "team_id" varchar, + "type" text, + "hook" boolean NOT NULL, + "redirect_uris" text, + "rpc_application_state" integer, + "store_application_state" integer, + "verification_state" integer, + "interactions_endpoint_url" varchar, + "integration_public" boolean, + "integration_require_code_grant" boolean, + "discoverability_state" integer, + "discovery_eligibility_flags" integer, + "tags" text, + "install_params" text, + "bot_user_id" varchar, + CONSTRAINT "UQ_b7f6e13565e920916d902e1f431" UNIQUE ("bot_user_id"), + CONSTRAINT "FK_a36ed02953077f408d0f3ebc424" FOREIGN KEY ("team_id") REFERENCES "teams" ("id") ON DELETE CASCADE ON UPDATE NO ACTION, + CONSTRAINT "FK_e57508958bf92b9d9d25231b5e8" FOREIGN KEY ("owner_id") REFERENCES "users" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION + ) + `); + await queryRunner.query(` + INSERT INTO "applications"( + "id", + "name", + "icon", + "description", + "bot_public", + "bot_require_code_grant", + "terms_of_service_url", + "privacy_policy_url", + "summary", + "verify_key", + "cover_image", + "flags", + "owner_id", + "team_id", + "type", + "hook", + "redirect_uris", + "rpc_application_state", + "store_application_state", + "verification_state", + "interactions_endpoint_url", + "integration_public", + "integration_require_code_grant", + "discoverability_state", + "discovery_eligibility_flags", + "tags", + "install_params", + "bot_user_id" + ) + SELECT "id", + "name", + "icon", + "description", + "bot_public", + "bot_require_code_grant", + "terms_of_service_url", + "privacy_policy_url", + "summary", + "verify_key", + "cover_image", + "flags", + "owner_id", + "team_id", + "type", + "hook", + "redirect_uris", + "rpc_application_state", + "store_application_state", + "verification_state", + "interactions_endpoint_url", + "integration_public", + "integration_require_code_grant", + "discoverability_state", + "discovery_eligibility_flags", + "tags", + "install_params", + "bot_user_id" + FROM "temporary_applications" + `); + await queryRunner.query(` + DROP TABLE "temporary_applications" + `); + await queryRunner.query(` + ALTER TABLE "applications" + RENAME TO "temporary_applications" + `); + await queryRunner.query(` + CREATE TABLE "applications" ( + "id" varchar PRIMARY KEY NOT NULL, + "name" varchar NOT NULL, + "icon" varchar, + "description" varchar NOT NULL, + "bot_public" boolean NOT NULL, + "bot_require_code_grant" boolean NOT NULL, + "terms_of_service_url" varchar, + "privacy_policy_url" varchar, + "summary" varchar, + "verify_key" varchar NOT NULL, + "cover_image" varchar, + "flags" varchar NOT NULL, + "owner_id" varchar, + "team_id" varchar, + "type" text, + "hook" boolean NOT NULL, + "redirect_uris" text, + "rpc_application_state" integer, + "store_application_state" integer, + "verification_state" integer, + "interactions_endpoint_url" varchar, + "integration_public" boolean, + "integration_require_code_grant" boolean, + "discoverability_state" integer, + "discovery_eligibility_flags" integer, + "tags" text, + "install_params" text, + "bot_user_id" varchar, + CONSTRAINT "UQ_b7f6e13565e920916d902e1f431" UNIQUE ("bot_user_id"), + CONSTRAINT "FK_a36ed02953077f408d0f3ebc424" FOREIGN KEY ("team_id") REFERENCES "teams" ("id") ON DELETE CASCADE ON UPDATE NO ACTION, + CONSTRAINT "FK_e57508958bf92b9d9d25231b5e8" FOREIGN KEY ("owner_id") REFERENCES "users" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION + ) + `); + await queryRunner.query(` + INSERT INTO "applications"( + "id", + "name", + "icon", + "description", + "bot_public", + "bot_require_code_grant", + "terms_of_service_url", + "privacy_policy_url", + "summary", + "verify_key", + "cover_image", + "flags", + "owner_id", + "team_id", + "type", + "hook", + "redirect_uris", + "rpc_application_state", + "store_application_state", + "verification_state", + "interactions_endpoint_url", + "integration_public", + "integration_require_code_grant", + "discoverability_state", + "discovery_eligibility_flags", + "tags", + "install_params", + "bot_user_id" + ) + SELECT "id", + "name", + "icon", + "description", + "bot_public", + "bot_require_code_grant", + "terms_of_service_url", + "privacy_policy_url", + "summary", + "verify_key", + "cover_image", + "flags", + "owner_id", + "team_id", + "type", + "hook", + "redirect_uris", + "rpc_application_state", + "store_application_state", + "verification_state", + "interactions_endpoint_url", + "integration_public", + "integration_require_code_grant", + "discoverability_state", + "discovery_eligibility_flags", + "tags", + "install_params", + "bot_user_id" + FROM "temporary_applications" + `); + await queryRunner.query(` + DROP TABLE "temporary_applications" + `); + await queryRunner.query(` + ALTER TABLE "applications" + RENAME TO "temporary_applications" + `); + await queryRunner.query(` + CREATE TABLE "applications" ( + "id" varchar PRIMARY KEY NOT NULL, + "name" varchar NOT NULL, + "icon" varchar, + "description" varchar NOT NULL, + "bot_public" boolean NOT NULL, + "bot_require_code_grant" boolean NOT NULL, + "terms_of_service_url" varchar, + "privacy_policy_url" varchar, + "summary" varchar, + "verify_key" varchar NOT NULL, + "cover_image" varchar, + "flags" varchar NOT NULL, + "owner_id" varchar, + "team_id" varchar, + CONSTRAINT "FK_a36ed02953077f408d0f3ebc424" FOREIGN KEY ("team_id") REFERENCES "teams" ("id") ON DELETE CASCADE ON UPDATE NO ACTION, + CONSTRAINT "FK_e57508958bf92b9d9d25231b5e8" FOREIGN KEY ("owner_id") REFERENCES "users" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION + ) + `); + await queryRunner.query(` + INSERT INTO "applications"( + "id", + "name", + "icon", + "description", + "bot_public", + "bot_require_code_grant", + "terms_of_service_url", + "privacy_policy_url", + "summary", + "verify_key", + "cover_image", + "flags", + "owner_id", + "team_id" + ) + SELECT "id", + "name", + "icon", + "description", + "bot_public", + "bot_require_code_grant", + "terms_of_service_url", + "privacy_policy_url", + "summary", + "verify_key", + "cover_image", + "flags", + "owner_id", + "team_id" + FROM "temporary_applications" + `); + await queryRunner.query(` + DROP TABLE "temporary_applications" + `); + await queryRunner.query(` + ALTER TABLE "applications" + RENAME TO "temporary_applications" + `); + await queryRunner.query(` + CREATE TABLE "applications" ( + "id" varchar PRIMARY KEY NOT NULL, + "name" varchar NOT NULL, + "icon" varchar, + "description" varchar NOT NULL, + "rpc_origins" text, + "bot_public" boolean NOT NULL, + "bot_require_code_grant" boolean NOT NULL, + "terms_of_service_url" varchar, + "privacy_policy_url" varchar, + "summary" varchar, + "verify_key" varchar NOT NULL, + "primary_sku_id" varchar, + "slug" varchar, + "cover_image" varchar, + "flags" varchar NOT NULL, + "owner_id" varchar, + "team_id" varchar, + "guild_id" varchar, + CONSTRAINT "FK_a36ed02953077f408d0f3ebc424" FOREIGN KEY ("team_id") REFERENCES "teams" ("id") ON DELETE CASCADE ON UPDATE NO ACTION, + CONSTRAINT "FK_e57508958bf92b9d9d25231b5e8" FOREIGN KEY ("owner_id") REFERENCES "users" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION + ) + `); + await queryRunner.query(` + INSERT INTO "applications"( + "id", + "name", + "icon", + "description", + "bot_public", + "bot_require_code_grant", + "terms_of_service_url", + "privacy_policy_url", + "summary", + "verify_key", + "cover_image", + "flags", + "owner_id", + "team_id" + ) + SELECT "id", + "name", + "icon", + "description", + "bot_public", + "bot_require_code_grant", + "terms_of_service_url", + "privacy_policy_url", + "summary", + "verify_key", + "cover_image", + "flags", + "owner_id", + "team_id" + FROM "temporary_applications" + `); + await queryRunner.query(` + DROP TABLE "temporary_applications" + `); + await queryRunner.query(` + ALTER TABLE "applications" + RENAME TO "temporary_applications" + `); + await queryRunner.query(` + CREATE TABLE "applications" ( + "id" varchar PRIMARY KEY NOT NULL, + "name" varchar NOT NULL, + "icon" varchar, + "description" varchar NOT NULL, + "rpc_origins" text, + "bot_public" boolean NOT NULL, + "bot_require_code_grant" boolean NOT NULL, + "terms_of_service_url" varchar, + "privacy_policy_url" varchar, + "summary" varchar, + "verify_key" varchar NOT NULL, + "primary_sku_id" varchar, + "slug" varchar, + "cover_image" varchar, + "flags" varchar NOT NULL, + "owner_id" varchar, + "team_id" varchar, + "guild_id" varchar, + CONSTRAINT "FK_e5bf78cdbbe9ba91062d74c5aba" FOREIGN KEY ("guild_id") REFERENCES "guilds" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION, + CONSTRAINT "FK_a36ed02953077f408d0f3ebc424" FOREIGN KEY ("team_id") REFERENCES "teams" ("id") ON DELETE CASCADE ON UPDATE NO ACTION, + CONSTRAINT "FK_e57508958bf92b9d9d25231b5e8" FOREIGN KEY ("owner_id") REFERENCES "users" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION + ) + `); + await queryRunner.query(` + INSERT INTO "applications"( + "id", + "name", + "icon", + "description", + "rpc_origins", + "bot_public", + "bot_require_code_grant", + "terms_of_service_url", + "privacy_policy_url", + "summary", + "verify_key", + "primary_sku_id", + "slug", + "cover_image", + "flags", + "owner_id", + "team_id", + "guild_id" + ) + SELECT "id", + "name", + "icon", + "description", + "rpc_origins", + "bot_public", + "bot_require_code_grant", + "terms_of_service_url", + "privacy_policy_url", + "summary", + "verify_key", + "primary_sku_id", + "slug", + "cover_image", + "flags", + "owner_id", + "team_id", + "guild_id" + FROM "temporary_applications" + `); + await queryRunner.query(` + DROP TABLE "temporary_applications" + `); + } +} diff --git a/src/util/migrations/sqlite/1660257576211-CodeCleanup1.ts b/src/util/migrations/sqlite/1660257576211-CodeCleanup1.ts new file mode 100644
index 00000000..95410544 --- /dev/null +++ b/src/util/migrations/sqlite/1660257576211-CodeCleanup1.ts
@@ -0,0 +1,325 @@ +import { MigrationInterface, QueryRunner } from "typeorm"; + +export class CodeCleanup11660257576211 implements MigrationInterface { + name = "CodeCleanup11660257576211"; + + public async up(queryRunner: QueryRunner): Promise<void> { + await queryRunner.query(` + CREATE TABLE "user_settings" ( + "id" varchar PRIMARY KEY NOT NULL, + "afk_timeout" integer, + "allow_accessibility_detection" boolean, + "animate_emoji" boolean, + "animate_stickers" integer, + "contact_sync_enabled" boolean, + "convert_emoticons" boolean, + "custom_status" text, + "default_guilds_restricted" boolean, + "detect_platform_accounts" boolean, + "developer_mode" boolean, + "disable_games_tab" boolean, + "enable_tts_command" boolean, + "explicit_content_filter" integer, + "friend_source_flags" text, + "gateway_connected" boolean, + "gif_auto_play" boolean, + "guild_folders" text, + "guild_positions" text, + "inline_attachment_media" boolean, + "inline_embed_media" boolean, + "locale" varchar, + "message_display_compact" boolean, + "native_phone_integration_enabled" boolean, + "render_embeds" boolean, + "render_reactions" boolean, + "restricted_guilds" text, + "show_current_game" boolean, + "status" varchar, + "stream_notifications_enabled" boolean, + "theme" varchar, + "timezone_offset" integer + ) + `); + await queryRunner.query(` + CREATE TABLE "temporary_guilds" ( + "id" varchar PRIMARY KEY NOT NULL, + "afk_channel_id" varchar, + "afk_timeout" integer, + "banner" varchar, + "default_message_notifications" integer, + "description" varchar, + "discovery_splash" varchar, + "explicit_content_filter" integer, + "features" text NOT NULL, + "primary_category_id" integer, + "icon" varchar, + "large" boolean, + "max_members" integer, + "max_presences" integer, + "max_video_channel_users" integer, + "member_count" integer, + "presence_count" integer, + "template_id" varchar, + "mfa_level" integer, + "name" varchar NOT NULL, + "owner_id" varchar, + "preferred_locale" varchar, + "premium_subscription_count" integer, + "premium_tier" integer, + "public_updates_channel_id" varchar, + "rules_channel_id" varchar, + "region" varchar, + "splash" varchar, + "system_channel_id" varchar, + "system_channel_flags" integer, + "unavailable" boolean, + "verification_level" integer, + "welcome_screen" text NOT NULL, + "widget_channel_id" varchar, + "widget_enabled" boolean, + "nsfw_level" integer, + "nsfw" boolean, + "parent" varchar, + "premium_progress_bar_enabled" boolean NOT NULL, + CONSTRAINT "FK_9d1d665379eefde7876a17afa99" FOREIGN KEY ("widget_channel_id") REFERENCES "channels" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION, + CONSTRAINT "FK_cfc3d3ad260f8121c95b31a1fce" FOREIGN KEY ("system_channel_id") REFERENCES "channels" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION, + CONSTRAINT "FK_95828668aa333460582e0ca6396" FOREIGN KEY ("rules_channel_id") REFERENCES "channels" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION, + CONSTRAINT "FK_8d450b016dc8bec35f36729e4b0" FOREIGN KEY ("public_updates_channel_id") REFERENCES "channels" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION, + CONSTRAINT "FK_fc1a451727e3643ca572a3bb394" FOREIGN KEY ("owner_id") REFERENCES "users" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION, + CONSTRAINT "FK_e2a2f873a64a5cf62526de42325" FOREIGN KEY ("template_id") REFERENCES "templates" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION, + CONSTRAINT "FK_f591a66b8019d87b0fe6c12dad6" FOREIGN KEY ("afk_channel_id") REFERENCES "channels" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION + ) + `); + await queryRunner.query(` + INSERT INTO "temporary_guilds"( + "id", + "afk_channel_id", + "afk_timeout", + "banner", + "default_message_notifications", + "description", + "discovery_splash", + "explicit_content_filter", + "features", + "primary_category_id", + "icon", + "large", + "max_members", + "max_presences", + "max_video_channel_users", + "member_count", + "presence_count", + "template_id", + "mfa_level", + "name", + "owner_id", + "preferred_locale", + "premium_subscription_count", + "premium_tier", + "public_updates_channel_id", + "rules_channel_id", + "region", + "splash", + "system_channel_id", + "system_channel_flags", + "unavailable", + "verification_level", + "welcome_screen", + "widget_channel_id", + "widget_enabled", + "nsfw_level", + "nsfw", + "parent" + ) + SELECT "id", + "afk_channel_id", + "afk_timeout", + "banner", + "default_message_notifications", + "description", + "discovery_splash", + "explicit_content_filter", + "features", + "primary_category_id", + "icon", + "large", + "max_members", + "max_presences", + "max_video_channel_users", + "member_count", + "presence_count", + "template_id", + "mfa_level", + "name", + "owner_id", + "preferred_locale", + "premium_subscription_count", + "premium_tier", + "public_updates_channel_id", + "rules_channel_id", + "region", + "splash", + "system_channel_id", + "system_channel_flags", + "unavailable", + "verification_level", + "welcome_screen", + "widget_channel_id", + "widget_enabled", + "nsfw_level", + "nsfw", + "parent" + FROM "guilds" + `); + await queryRunner.query(` + DROP TABLE "guilds" + `); + await queryRunner.query(` + ALTER TABLE "temporary_guilds" + RENAME TO "guilds" + `); + } + + public async down(queryRunner: QueryRunner): Promise<void> { + await queryRunner.query(` + ALTER TABLE "guilds" + RENAME TO "temporary_guilds" + `); + await queryRunner.query(` + CREATE TABLE "guilds" ( + "id" varchar PRIMARY KEY NOT NULL, + "afk_channel_id" varchar, + "afk_timeout" integer, + "banner" varchar, + "default_message_notifications" integer, + "description" varchar, + "discovery_splash" varchar, + "explicit_content_filter" integer, + "features" text NOT NULL, + "primary_category_id" integer, + "icon" varchar, + "large" boolean, + "max_members" integer, + "max_presences" integer, + "max_video_channel_users" integer, + "member_count" integer, + "presence_count" integer, + "template_id" varchar, + "mfa_level" integer, + "name" varchar NOT NULL, + "owner_id" varchar, + "preferred_locale" varchar, + "premium_subscription_count" integer, + "premium_tier" integer, + "public_updates_channel_id" varchar, + "rules_channel_id" varchar, + "region" varchar, + "splash" varchar, + "system_channel_id" varchar, + "system_channel_flags" integer, + "unavailable" boolean, + "verification_level" integer, + "welcome_screen" text NOT NULL, + "widget_channel_id" varchar, + "widget_enabled" boolean, + "nsfw_level" integer, + "nsfw" boolean, + "parent" varchar, + CONSTRAINT "FK_9d1d665379eefde7876a17afa99" FOREIGN KEY ("widget_channel_id") REFERENCES "channels" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION, + CONSTRAINT "FK_cfc3d3ad260f8121c95b31a1fce" FOREIGN KEY ("system_channel_id") REFERENCES "channels" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION, + CONSTRAINT "FK_95828668aa333460582e0ca6396" FOREIGN KEY ("rules_channel_id") REFERENCES "channels" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION, + CONSTRAINT "FK_8d450b016dc8bec35f36729e4b0" FOREIGN KEY ("public_updates_channel_id") REFERENCES "channels" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION, + CONSTRAINT "FK_fc1a451727e3643ca572a3bb394" FOREIGN KEY ("owner_id") REFERENCES "users" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION, + CONSTRAINT "FK_e2a2f873a64a5cf62526de42325" FOREIGN KEY ("template_id") REFERENCES "templates" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION, + CONSTRAINT "FK_f591a66b8019d87b0fe6c12dad6" FOREIGN KEY ("afk_channel_id") REFERENCES "channels" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION + ) + `); + await queryRunner.query(` + INSERT INTO "guilds"( + "id", + "afk_channel_id", + "afk_timeout", + "banner", + "default_message_notifications", + "description", + "discovery_splash", + "explicit_content_filter", + "features", + "primary_category_id", + "icon", + "large", + "max_members", + "max_presences", + "max_video_channel_users", + "member_count", + "presence_count", + "template_id", + "mfa_level", + "name", + "owner_id", + "preferred_locale", + "premium_subscription_count", + "premium_tier", + "public_updates_channel_id", + "rules_channel_id", + "region", + "splash", + "system_channel_id", + "system_channel_flags", + "unavailable", + "verification_level", + "welcome_screen", + "widget_channel_id", + "widget_enabled", + "nsfw_level", + "nsfw", + "parent" + ) + SELECT "id", + "afk_channel_id", + "afk_timeout", + "banner", + "default_message_notifications", + "description", + "discovery_splash", + "explicit_content_filter", + "features", + "primary_category_id", + "icon", + "large", + "max_members", + "max_presences", + "max_video_channel_users", + "member_count", + "presence_count", + "template_id", + "mfa_level", + "name", + "owner_id", + "preferred_locale", + "premium_subscription_count", + "premium_tier", + "public_updates_channel_id", + "rules_channel_id", + "region", + "splash", + "system_channel_id", + "system_channel_flags", + "unavailable", + "verification_level", + "welcome_screen", + "widget_channel_id", + "widget_enabled", + "nsfw_level", + "nsfw", + "parent" + FROM "temporary_guilds" + `); + await queryRunner.query(` + DROP TABLE "temporary_guilds" + `); + await queryRunner.query(` + DROP TABLE "user_settings" + `); + } +} diff --git a/src/util/migrations/sqlite/1660257795259-CodeCleanup2.ts b/src/util/migrations/sqlite/1660257795259-CodeCleanup2.ts new file mode 100644
index 00000000..b21e190c --- /dev/null +++ b/src/util/migrations/sqlite/1660257795259-CodeCleanup2.ts
@@ -0,0 +1,571 @@ +import { MigrationInterface, QueryRunner } from "typeorm"; + +export class CodeCleanup21660257795259 implements MigrationInterface { + name = "CodeCleanup21660257795259"; + + public async up(queryRunner: QueryRunner): Promise<void> { + await queryRunner.query(` + CREATE TABLE "temporary_guilds" ( + "id" varchar PRIMARY KEY NOT NULL, + "afk_channel_id" varchar, + "afk_timeout" integer, + "banner" varchar, + "default_message_notifications" integer, + "description" varchar, + "discovery_splash" varchar, + "explicit_content_filter" integer, + "features" text NOT NULL, + "primary_category_id" integer, + "icon" varchar, + "large" boolean, + "max_members" integer, + "max_presences" integer, + "max_video_channel_users" integer, + "member_count" integer, + "presence_count" integer, + "template_id" varchar, + "mfa_level" integer, + "name" varchar NOT NULL, + "owner_id" varchar, + "preferred_locale" varchar, + "premium_subscription_count" integer, + "premium_tier" integer, + "public_updates_channel_id" varchar, + "rules_channel_id" varchar, + "region" varchar, + "splash" varchar, + "system_channel_id" varchar, + "system_channel_flags" integer, + "unavailable" boolean, + "verification_level" integer, + "welcome_screen" text NOT NULL, + "widget_channel_id" varchar, + "widget_enabled" boolean, + "nsfw_level" integer, + "nsfw" boolean, + "parent" varchar, + "premium_progress_bar_enabled" boolean NOT NULL, + CONSTRAINT "FK_f591a66b8019d87b0fe6c12dad6" FOREIGN KEY ("afk_channel_id") REFERENCES "channels" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION, + CONSTRAINT "FK_e2a2f873a64a5cf62526de42325" FOREIGN KEY ("template_id") REFERENCES "templates" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION, + CONSTRAINT "FK_fc1a451727e3643ca572a3bb394" FOREIGN KEY ("owner_id") REFERENCES "users" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION, + CONSTRAINT "FK_8d450b016dc8bec35f36729e4b0" FOREIGN KEY ("public_updates_channel_id") REFERENCES "channels" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION, + CONSTRAINT "FK_95828668aa333460582e0ca6396" FOREIGN KEY ("rules_channel_id") REFERENCES "channels" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION, + CONSTRAINT "FK_cfc3d3ad260f8121c95b31a1fce" FOREIGN KEY ("system_channel_id") REFERENCES "channels" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION, + CONSTRAINT "FK_9d1d665379eefde7876a17afa99" FOREIGN KEY ("widget_channel_id") REFERENCES "channels" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION + ) + `); + await queryRunner.query(` + INSERT INTO "temporary_guilds"( + "id", + "afk_channel_id", + "afk_timeout", + "banner", + "default_message_notifications", + "description", + "discovery_splash", + "explicit_content_filter", + "features", + "primary_category_id", + "icon", + "large", + "max_members", + "max_presences", + "max_video_channel_users", + "member_count", + "presence_count", + "template_id", + "mfa_level", + "name", + "owner_id", + "preferred_locale", + "premium_subscription_count", + "premium_tier", + "public_updates_channel_id", + "rules_channel_id", + "region", + "splash", + "system_channel_id", + "system_channel_flags", + "unavailable", + "verification_level", + "welcome_screen", + "widget_channel_id", + "widget_enabled", + "nsfw_level", + "nsfw", + "parent", + "premium_progress_bar_enabled" + ) + SELECT "id", + "afk_channel_id", + "afk_timeout", + "banner", + "default_message_notifications", + "description", + "discovery_splash", + "explicit_content_filter", + "features", + "primary_category_id", + "icon", + "large", + "max_members", + "max_presences", + "max_video_channel_users", + "member_count", + "presence_count", + "template_id", + "mfa_level", + "name", + "owner_id", + "preferred_locale", + "premium_subscription_count", + "premium_tier", + "public_updates_channel_id", + "rules_channel_id", + "region", + "splash", + "system_channel_id", + "system_channel_flags", + "unavailable", + "verification_level", + "welcome_screen", + "widget_channel_id", + "widget_enabled", + "nsfw_level", + "nsfw", + "parent", + "premium_progress_bar_enabled" + FROM "guilds" + `); + await queryRunner.query(` + DROP TABLE "guilds" + `); + await queryRunner.query(` + ALTER TABLE "temporary_guilds" + RENAME TO "guilds" + `); + await queryRunner.query(` + CREATE TABLE "temporary_guilds" ( + "id" varchar PRIMARY KEY NOT NULL, + "afk_channel_id" varchar, + "afk_timeout" integer, + "banner" varchar, + "default_message_notifications" integer, + "description" varchar, + "discovery_splash" varchar, + "explicit_content_filter" integer, + "features" text NOT NULL, + "primary_category_id" integer, + "icon" varchar, + "large" boolean, + "max_members" integer, + "max_presences" integer, + "max_video_channel_users" integer, + "member_count" integer, + "presence_count" integer, + "template_id" varchar, + "mfa_level" integer, + "name" varchar NOT NULL, + "owner_id" varchar, + "preferred_locale" varchar, + "premium_subscription_count" integer, + "premium_tier" integer, + "public_updates_channel_id" varchar, + "rules_channel_id" varchar, + "region" varchar, + "splash" varchar, + "system_channel_id" varchar, + "system_channel_flags" integer, + "unavailable" boolean, + "verification_level" integer, + "welcome_screen" text NOT NULL, + "widget_channel_id" varchar, + "widget_enabled" boolean, + "nsfw_level" integer, + "nsfw" boolean, + "parent" varchar, + "premium_progress_bar_enabled" boolean, + CONSTRAINT "FK_f591a66b8019d87b0fe6c12dad6" FOREIGN KEY ("afk_channel_id") REFERENCES "channels" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION, + CONSTRAINT "FK_e2a2f873a64a5cf62526de42325" FOREIGN KEY ("template_id") REFERENCES "templates" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION, + CONSTRAINT "FK_fc1a451727e3643ca572a3bb394" FOREIGN KEY ("owner_id") REFERENCES "users" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION, + CONSTRAINT "FK_8d450b016dc8bec35f36729e4b0" FOREIGN KEY ("public_updates_channel_id") REFERENCES "channels" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION, + CONSTRAINT "FK_95828668aa333460582e0ca6396" FOREIGN KEY ("rules_channel_id") REFERENCES "channels" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION, + CONSTRAINT "FK_cfc3d3ad260f8121c95b31a1fce" FOREIGN KEY ("system_channel_id") REFERENCES "channels" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION, + CONSTRAINT "FK_9d1d665379eefde7876a17afa99" FOREIGN KEY ("widget_channel_id") REFERENCES "channels" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION + ) + `); + await queryRunner.query(` + INSERT INTO "temporary_guilds"( + "id", + "afk_channel_id", + "afk_timeout", + "banner", + "default_message_notifications", + "description", + "discovery_splash", + "explicit_content_filter", + "features", + "primary_category_id", + "icon", + "large", + "max_members", + "max_presences", + "max_video_channel_users", + "member_count", + "presence_count", + "template_id", + "mfa_level", + "name", + "owner_id", + "preferred_locale", + "premium_subscription_count", + "premium_tier", + "public_updates_channel_id", + "rules_channel_id", + "region", + "splash", + "system_channel_id", + "system_channel_flags", + "unavailable", + "verification_level", + "welcome_screen", + "widget_channel_id", + "widget_enabled", + "nsfw_level", + "nsfw", + "parent", + "premium_progress_bar_enabled" + ) + SELECT "id", + "afk_channel_id", + "afk_timeout", + "banner", + "default_message_notifications", + "description", + "discovery_splash", + "explicit_content_filter", + "features", + "primary_category_id", + "icon", + "large", + "max_members", + "max_presences", + "max_video_channel_users", + "member_count", + "presence_count", + "template_id", + "mfa_level", + "name", + "owner_id", + "preferred_locale", + "premium_subscription_count", + "premium_tier", + "public_updates_channel_id", + "rules_channel_id", + "region", + "splash", + "system_channel_id", + "system_channel_flags", + "unavailable", + "verification_level", + "welcome_screen", + "widget_channel_id", + "widget_enabled", + "nsfw_level", + "nsfw", + "parent", + "premium_progress_bar_enabled" + FROM "guilds" + `); + await queryRunner.query(` + DROP TABLE "guilds" + `); + await queryRunner.query(` + ALTER TABLE "temporary_guilds" + RENAME TO "guilds" + `); + } + + public async down(queryRunner: QueryRunner): Promise<void> { + await queryRunner.query(` + ALTER TABLE "guilds" + RENAME TO "temporary_guilds" + `); + await queryRunner.query(` + CREATE TABLE "guilds" ( + "id" varchar PRIMARY KEY NOT NULL, + "afk_channel_id" varchar, + "afk_timeout" integer, + "banner" varchar, + "default_message_notifications" integer, + "description" varchar, + "discovery_splash" varchar, + "explicit_content_filter" integer, + "features" text NOT NULL, + "primary_category_id" integer, + "icon" varchar, + "large" boolean, + "max_members" integer, + "max_presences" integer, + "max_video_channel_users" integer, + "member_count" integer, + "presence_count" integer, + "template_id" varchar, + "mfa_level" integer, + "name" varchar NOT NULL, + "owner_id" varchar, + "preferred_locale" varchar, + "premium_subscription_count" integer, + "premium_tier" integer, + "public_updates_channel_id" varchar, + "rules_channel_id" varchar, + "region" varchar, + "splash" varchar, + "system_channel_id" varchar, + "system_channel_flags" integer, + "unavailable" boolean, + "verification_level" integer, + "welcome_screen" text NOT NULL, + "widget_channel_id" varchar, + "widget_enabled" boolean, + "nsfw_level" integer, + "nsfw" boolean, + "parent" varchar, + "premium_progress_bar_enabled" boolean NOT NULL, + CONSTRAINT "FK_f591a66b8019d87b0fe6c12dad6" FOREIGN KEY ("afk_channel_id") REFERENCES "channels" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION, + CONSTRAINT "FK_e2a2f873a64a5cf62526de42325" FOREIGN KEY ("template_id") REFERENCES "templates" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION, + CONSTRAINT "FK_fc1a451727e3643ca572a3bb394" FOREIGN KEY ("owner_id") REFERENCES "users" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION, + CONSTRAINT "FK_8d450b016dc8bec35f36729e4b0" FOREIGN KEY ("public_updates_channel_id") REFERENCES "channels" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION, + CONSTRAINT "FK_95828668aa333460582e0ca6396" FOREIGN KEY ("rules_channel_id") REFERENCES "channels" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION, + CONSTRAINT "FK_cfc3d3ad260f8121c95b31a1fce" FOREIGN KEY ("system_channel_id") REFERENCES "channels" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION, + CONSTRAINT "FK_9d1d665379eefde7876a17afa99" FOREIGN KEY ("widget_channel_id") REFERENCES "channels" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION + ) + `); + await queryRunner.query(` + INSERT INTO "guilds"( + "id", + "afk_channel_id", + "afk_timeout", + "banner", + "default_message_notifications", + "description", + "discovery_splash", + "explicit_content_filter", + "features", + "primary_category_id", + "icon", + "large", + "max_members", + "max_presences", + "max_video_channel_users", + "member_count", + "presence_count", + "template_id", + "mfa_level", + "name", + "owner_id", + "preferred_locale", + "premium_subscription_count", + "premium_tier", + "public_updates_channel_id", + "rules_channel_id", + "region", + "splash", + "system_channel_id", + "system_channel_flags", + "unavailable", + "verification_level", + "welcome_screen", + "widget_channel_id", + "widget_enabled", + "nsfw_level", + "nsfw", + "parent", + "premium_progress_bar_enabled" + ) + SELECT "id", + "afk_channel_id", + "afk_timeout", + "banner", + "default_message_notifications", + "description", + "discovery_splash", + "explicit_content_filter", + "features", + "primary_category_id", + "icon", + "large", + "max_members", + "max_presences", + "max_video_channel_users", + "member_count", + "presence_count", + "template_id", + "mfa_level", + "name", + "owner_id", + "preferred_locale", + "premium_subscription_count", + "premium_tier", + "public_updates_channel_id", + "rules_channel_id", + "region", + "splash", + "system_channel_id", + "system_channel_flags", + "unavailable", + "verification_level", + "welcome_screen", + "widget_channel_id", + "widget_enabled", + "nsfw_level", + "nsfw", + "parent", + "premium_progress_bar_enabled" + FROM "temporary_guilds" + `); + await queryRunner.query(` + DROP TABLE "temporary_guilds" + `); + await queryRunner.query(` + ALTER TABLE "guilds" + RENAME TO "temporary_guilds" + `); + await queryRunner.query(` + CREATE TABLE "guilds" ( + "id" varchar PRIMARY KEY NOT NULL, + "afk_channel_id" varchar, + "afk_timeout" integer, + "banner" varchar, + "default_message_notifications" integer, + "description" varchar, + "discovery_splash" varchar, + "explicit_content_filter" integer, + "features" text NOT NULL, + "primary_category_id" integer, + "icon" varchar, + "large" boolean, + "max_members" integer, + "max_presences" integer, + "max_video_channel_users" integer, + "member_count" integer, + "presence_count" integer, + "template_id" varchar, + "mfa_level" integer, + "name" varchar NOT NULL, + "owner_id" varchar, + "preferred_locale" varchar, + "premium_subscription_count" integer, + "premium_tier" integer, + "public_updates_channel_id" varchar, + "rules_channel_id" varchar, + "region" varchar, + "splash" varchar, + "system_channel_id" varchar, + "system_channel_flags" integer, + "unavailable" boolean, + "verification_level" integer, + "welcome_screen" text NOT NULL, + "widget_channel_id" varchar, + "widget_enabled" boolean, + "nsfw_level" integer, + "nsfw" boolean, + "parent" varchar, + "premium_progress_bar_enabled" boolean NOT NULL, + CONSTRAINT "FK_f591a66b8019d87b0fe6c12dad6" FOREIGN KEY ("afk_channel_id") REFERENCES "channels" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION, + CONSTRAINT "FK_e2a2f873a64a5cf62526de42325" FOREIGN KEY ("template_id") REFERENCES "templates" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION, + CONSTRAINT "FK_fc1a451727e3643ca572a3bb394" FOREIGN KEY ("owner_id") REFERENCES "users" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION, + CONSTRAINT "FK_8d450b016dc8bec35f36729e4b0" FOREIGN KEY ("public_updates_channel_id") REFERENCES "channels" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION, + CONSTRAINT "FK_95828668aa333460582e0ca6396" FOREIGN KEY ("rules_channel_id") REFERENCES "channels" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION, + CONSTRAINT "FK_cfc3d3ad260f8121c95b31a1fce" FOREIGN KEY ("system_channel_id") REFERENCES "channels" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION, + CONSTRAINT "FK_9d1d665379eefde7876a17afa99" FOREIGN KEY ("widget_channel_id") REFERENCES "channels" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION + ) + `); + await queryRunner.query(` + INSERT INTO "guilds"( + "id", + "afk_channel_id", + "afk_timeout", + "banner", + "default_message_notifications", + "description", + "discovery_splash", + "explicit_content_filter", + "features", + "primary_category_id", + "icon", + "large", + "max_members", + "max_presences", + "max_video_channel_users", + "member_count", + "presence_count", + "template_id", + "mfa_level", + "name", + "owner_id", + "preferred_locale", + "premium_subscription_count", + "premium_tier", + "public_updates_channel_id", + "rules_channel_id", + "region", + "splash", + "system_channel_id", + "system_channel_flags", + "unavailable", + "verification_level", + "welcome_screen", + "widget_channel_id", + "widget_enabled", + "nsfw_level", + "nsfw", + "parent", + "premium_progress_bar_enabled" + ) + SELECT "id", + "afk_channel_id", + "afk_timeout", + "banner", + "default_message_notifications", + "description", + "discovery_splash", + "explicit_content_filter", + "features", + "primary_category_id", + "icon", + "large", + "max_members", + "max_presences", + "max_video_channel_users", + "member_count", + "presence_count", + "template_id", + "mfa_level", + "name", + "owner_id", + "preferred_locale", + "premium_subscription_count", + "premium_tier", + "public_updates_channel_id", + "rules_channel_id", + "region", + "splash", + "system_channel_id", + "system_channel_flags", + "unavailable", + "verification_level", + "welcome_screen", + "widget_channel_id", + "widget_enabled", + "nsfw_level", + "nsfw", + "parent", + "premium_progress_bar_enabled" + FROM "temporary_guilds" + `); + await queryRunner.query(` + DROP TABLE "temporary_guilds" + `); + } +} diff --git a/src/util/migrations/sqlite/1660258351379-CodeCleanup3.ts b/src/util/migrations/sqlite/1660258351379-CodeCleanup3.ts new file mode 100644
index 00000000..b19864fa --- /dev/null +++ b/src/util/migrations/sqlite/1660258351379-CodeCleanup3.ts
@@ -0,0 +1,230 @@ +import { MigrationInterface, QueryRunner } from "typeorm"; + +export class CodeCleanup31660258351379 implements MigrationInterface { + name = "CodeCleanup31660258351379"; + + public async up(queryRunner: QueryRunner): Promise<void> { + await queryRunner.query(` + CREATE TABLE "temporary_users" ( + "id" varchar PRIMARY KEY NOT NULL, + "username" varchar NOT NULL, + "discriminator" varchar NOT NULL, + "avatar" varchar, + "accent_color" integer, + "banner" varchar, + "phone" varchar, + "desktop" boolean NOT NULL, + "mobile" boolean NOT NULL, + "premium" boolean NOT NULL, + "premium_type" integer NOT NULL, + "bot" boolean NOT NULL, + "bio" varchar NOT NULL, + "system" boolean NOT NULL, + "nsfw_allowed" boolean NOT NULL, + "mfa_enabled" boolean NOT NULL, + "totp_secret" varchar, + "totp_last_ticket" varchar, + "created_at" datetime NOT NULL, + "premium_since" datetime, + "verified" boolean NOT NULL, + "disabled" boolean NOT NULL, + "deleted" boolean NOT NULL, + "email" varchar, + "flags" varchar NOT NULL, + "public_flags" integer NOT NULL, + "rights" bigint NOT NULL, + "data" text NOT NULL, + "fingerprints" text NOT NULL, + "extended_settings" text NOT NULL, + "notes" text NOT NULL + ) + `); + await queryRunner.query(` + INSERT INTO "temporary_users"( + "id", + "username", + "discriminator", + "avatar", + "accent_color", + "banner", + "phone", + "desktop", + "mobile", + "premium", + "premium_type", + "bot", + "bio", + "system", + "nsfw_allowed", + "mfa_enabled", + "totp_secret", + "totp_last_ticket", + "created_at", + "premium_since", + "verified", + "disabled", + "deleted", + "email", + "flags", + "public_flags", + "rights", + "data", + "fingerprints", + "extended_settings", + "notes" + ) + SELECT "id", + "username", + "discriminator", + "avatar", + "accent_color", + "banner", + "phone", + "desktop", + "mobile", + "premium", + "premium_type", + "bot", + "bio", + "system", + "nsfw_allowed", + "mfa_enabled", + "totp_secret", + "totp_last_ticket", + "created_at", + "premium_since", + "verified", + "disabled", + "deleted", + "email", + "flags", + "public_flags", + "rights", + "data", + "fingerprints", + "extended_settings", + "notes" + FROM "users" + `); + await queryRunner.query(` + DROP TABLE "users" + `); + await queryRunner.query(` + ALTER TABLE "temporary_users" + RENAME TO "users" + `); + } + + public async down(queryRunner: QueryRunner): Promise<void> { + await queryRunner.query(` + ALTER TABLE "users" + RENAME TO "temporary_users" + `); + await queryRunner.query(` + CREATE TABLE "users" ( + "id" varchar PRIMARY KEY NOT NULL, + "username" varchar NOT NULL, + "discriminator" varchar NOT NULL, + "avatar" varchar, + "accent_color" integer, + "banner" varchar, + "phone" varchar, + "desktop" boolean NOT NULL, + "mobile" boolean NOT NULL, + "premium" boolean NOT NULL, + "premium_type" integer NOT NULL, + "bot" boolean NOT NULL, + "bio" varchar NOT NULL, + "system" boolean NOT NULL, + "nsfw_allowed" boolean NOT NULL, + "mfa_enabled" boolean NOT NULL, + "totp_secret" varchar, + "totp_last_ticket" varchar, + "created_at" datetime NOT NULL, + "premium_since" datetime, + "verified" boolean NOT NULL, + "disabled" boolean NOT NULL, + "deleted" boolean NOT NULL, + "email" varchar, + "flags" varchar NOT NULL, + "public_flags" integer NOT NULL, + "rights" bigint NOT NULL, + "data" text NOT NULL, + "fingerprints" text NOT NULL, + "settings" text NOT NULL, + "extended_settings" text NOT NULL, + "notes" text NOT NULL + ) + `); + await queryRunner.query(` + INSERT INTO "users"( + "id", + "username", + "discriminator", + "avatar", + "accent_color", + "banner", + "phone", + "desktop", + "mobile", + "premium", + "premium_type", + "bot", + "bio", + "system", + "nsfw_allowed", + "mfa_enabled", + "totp_secret", + "totp_last_ticket", + "created_at", + "premium_since", + "verified", + "disabled", + "deleted", + "email", + "flags", + "public_flags", + "rights", + "data", + "fingerprints", + "extended_settings", + "notes" + ) + SELECT "id", + "username", + "discriminator", + "avatar", + "accent_color", + "banner", + "phone", + "desktop", + "mobile", + "premium", + "premium_type", + "bot", + "bio", + "system", + "nsfw_allowed", + "mfa_enabled", + "totp_secret", + "totp_last_ticket", + "created_at", + "premium_since", + "verified", + "disabled", + "deleted", + "email", + "flags", + "public_flags", + "rights", + "data", + "fingerprints", + "extended_settings", + "notes" + FROM "temporary_users" + `); + await queryRunner.query(` + DROP TABLE "temporary_users" + `); + } +} diff --git a/src/util/migrations/sqlite/1660260672914-CodeCleanup4.ts b/src/util/migrations/sqlite/1660260672914-CodeCleanup4.ts new file mode 100644
index 00000000..ed284bc8 --- /dev/null +++ b/src/util/migrations/sqlite/1660260672914-CodeCleanup4.ts
@@ -0,0 +1,458 @@ +import { MigrationInterface, QueryRunner } from "typeorm"; + +export class CodeCleanup41660260672914 implements MigrationInterface { + name = "CodeCleanup41660260672914"; + + public async up(queryRunner: QueryRunner): Promise<void> { + await queryRunner.query(` + CREATE TABLE "temporary_users" ( + "id" varchar PRIMARY KEY NOT NULL, + "username" varchar NOT NULL, + "discriminator" varchar NOT NULL, + "avatar" varchar, + "accent_color" integer, + "banner" varchar, + "phone" varchar, + "desktop" boolean NOT NULL, + "mobile" boolean NOT NULL, + "premium" boolean NOT NULL, + "premium_type" integer NOT NULL, + "bot" boolean NOT NULL, + "bio" varchar NOT NULL, + "system" boolean NOT NULL, + "nsfw_allowed" boolean NOT NULL, + "mfa_enabled" boolean NOT NULL, + "totp_secret" varchar, + "totp_last_ticket" varchar, + "created_at" datetime NOT NULL, + "premium_since" datetime, + "verified" boolean NOT NULL, + "disabled" boolean NOT NULL, + "deleted" boolean NOT NULL, + "email" varchar, + "flags" varchar NOT NULL, + "public_flags" integer NOT NULL, + "rights" bigint NOT NULL, + "data" text NOT NULL, + "fingerprints" text NOT NULL, + "extended_settings" text NOT NULL, + "notes" text NOT NULL, + "settingsId" varchar, + CONSTRAINT "UQ_b1dd13b6ed980004a795ca184a6" UNIQUE ("settingsId") + ) + `); + await queryRunner.query(` + INSERT INTO "temporary_users"( + "id", + "username", + "discriminator", + "avatar", + "accent_color", + "banner", + "phone", + "desktop", + "mobile", + "premium", + "premium_type", + "bot", + "bio", + "system", + "nsfw_allowed", + "mfa_enabled", + "totp_secret", + "totp_last_ticket", + "created_at", + "premium_since", + "verified", + "disabled", + "deleted", + "email", + "flags", + "public_flags", + "rights", + "data", + "fingerprints", + "extended_settings", + "notes" + ) + SELECT "id", + "username", + "discriminator", + "avatar", + "accent_color", + "banner", + "phone", + "desktop", + "mobile", + "premium", + "premium_type", + "bot", + "bio", + "system", + "nsfw_allowed", + "mfa_enabled", + "totp_secret", + "totp_last_ticket", + "created_at", + "premium_since", + "verified", + "disabled", + "deleted", + "email", + "flags", + "public_flags", + "rights", + "data", + "fingerprints", + "extended_settings", + "notes" + FROM "users" + `); + await queryRunner.query(` + DROP TABLE "users" + `); + await queryRunner.query(` + ALTER TABLE "temporary_users" + RENAME TO "users" + `); + await queryRunner.query(` + CREATE TABLE "temporary_users" ( + "id" varchar PRIMARY KEY NOT NULL, + "username" varchar NOT NULL, + "discriminator" varchar NOT NULL, + "avatar" varchar, + "accent_color" integer, + "banner" varchar, + "phone" varchar, + "desktop" boolean NOT NULL, + "mobile" boolean NOT NULL, + "premium" boolean NOT NULL, + "premium_type" integer NOT NULL, + "bot" boolean NOT NULL, + "bio" varchar NOT NULL, + "system" boolean NOT NULL, + "nsfw_allowed" boolean NOT NULL, + "mfa_enabled" boolean NOT NULL, + "totp_secret" varchar, + "totp_last_ticket" varchar, + "created_at" datetime NOT NULL, + "premium_since" datetime, + "verified" boolean NOT NULL, + "disabled" boolean NOT NULL, + "deleted" boolean NOT NULL, + "email" varchar, + "flags" varchar NOT NULL, + "public_flags" integer NOT NULL, + "rights" bigint NOT NULL, + "data" text NOT NULL, + "fingerprints" text NOT NULL, + "extended_settings" text NOT NULL, + "notes" text NOT NULL, + "settingsId" varchar, + CONSTRAINT "UQ_b1dd13b6ed980004a795ca184a6" UNIQUE ("settingsId"), + CONSTRAINT "FK_76ba283779c8441fd5ff819c8cf" FOREIGN KEY ("settingsId") REFERENCES "user_settings" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION + ) + `); + await queryRunner.query(` + INSERT INTO "temporary_users"( + "id", + "username", + "discriminator", + "avatar", + "accent_color", + "banner", + "phone", + "desktop", + "mobile", + "premium", + "premium_type", + "bot", + "bio", + "system", + "nsfw_allowed", + "mfa_enabled", + "totp_secret", + "totp_last_ticket", + "created_at", + "premium_since", + "verified", + "disabled", + "deleted", + "email", + "flags", + "public_flags", + "rights", + "data", + "fingerprints", + "extended_settings", + "notes", + "settingsId" + ) + SELECT "id", + "username", + "discriminator", + "avatar", + "accent_color", + "banner", + "phone", + "desktop", + "mobile", + "premium", + "premium_type", + "bot", + "bio", + "system", + "nsfw_allowed", + "mfa_enabled", + "totp_secret", + "totp_last_ticket", + "created_at", + "premium_since", + "verified", + "disabled", + "deleted", + "email", + "flags", + "public_flags", + "rights", + "data", + "fingerprints", + "extended_settings", + "notes", + "settingsId" + FROM "users" + `); + await queryRunner.query(` + DROP TABLE "users" + `); + await queryRunner.query(` + ALTER TABLE "temporary_users" + RENAME TO "users" + `); + } + + public async down(queryRunner: QueryRunner): Promise<void> { + await queryRunner.query(` + ALTER TABLE "users" + RENAME TO "temporary_users" + `); + await queryRunner.query(` + CREATE TABLE "users" ( + "id" varchar PRIMARY KEY NOT NULL, + "username" varchar NOT NULL, + "discriminator" varchar NOT NULL, + "avatar" varchar, + "accent_color" integer, + "banner" varchar, + "phone" varchar, + "desktop" boolean NOT NULL, + "mobile" boolean NOT NULL, + "premium" boolean NOT NULL, + "premium_type" integer NOT NULL, + "bot" boolean NOT NULL, + "bio" varchar NOT NULL, + "system" boolean NOT NULL, + "nsfw_allowed" boolean NOT NULL, + "mfa_enabled" boolean NOT NULL, + "totp_secret" varchar, + "totp_last_ticket" varchar, + "created_at" datetime NOT NULL, + "premium_since" datetime, + "verified" boolean NOT NULL, + "disabled" boolean NOT NULL, + "deleted" boolean NOT NULL, + "email" varchar, + "flags" varchar NOT NULL, + "public_flags" integer NOT NULL, + "rights" bigint NOT NULL, + "data" text NOT NULL, + "fingerprints" text NOT NULL, + "extended_settings" text NOT NULL, + "notes" text NOT NULL, + "settingsId" varchar, + CONSTRAINT "UQ_b1dd13b6ed980004a795ca184a6" UNIQUE ("settingsId") + ) + `); + await queryRunner.query(` + INSERT INTO "users"( + "id", + "username", + "discriminator", + "avatar", + "accent_color", + "banner", + "phone", + "desktop", + "mobile", + "premium", + "premium_type", + "bot", + "bio", + "system", + "nsfw_allowed", + "mfa_enabled", + "totp_secret", + "totp_last_ticket", + "created_at", + "premium_since", + "verified", + "disabled", + "deleted", + "email", + "flags", + "public_flags", + "rights", + "data", + "fingerprints", + "extended_settings", + "notes", + "settingsId" + ) + SELECT "id", + "username", + "discriminator", + "avatar", + "accent_color", + "banner", + "phone", + "desktop", + "mobile", + "premium", + "premium_type", + "bot", + "bio", + "system", + "nsfw_allowed", + "mfa_enabled", + "totp_secret", + "totp_last_ticket", + "created_at", + "premium_since", + "verified", + "disabled", + "deleted", + "email", + "flags", + "public_flags", + "rights", + "data", + "fingerprints", + "extended_settings", + "notes", + "settingsId" + FROM "temporary_users" + `); + await queryRunner.query(` + DROP TABLE "temporary_users" + `); + await queryRunner.query(` + ALTER TABLE "users" + RENAME TO "temporary_users" + `); + await queryRunner.query(` + CREATE TABLE "users" ( + "id" varchar PRIMARY KEY NOT NULL, + "username" varchar NOT NULL, + "discriminator" varchar NOT NULL, + "avatar" varchar, + "accent_color" integer, + "banner" varchar, + "phone" varchar, + "desktop" boolean NOT NULL, + "mobile" boolean NOT NULL, + "premium" boolean NOT NULL, + "premium_type" integer NOT NULL, + "bot" boolean NOT NULL, + "bio" varchar NOT NULL, + "system" boolean NOT NULL, + "nsfw_allowed" boolean NOT NULL, + "mfa_enabled" boolean NOT NULL, + "totp_secret" varchar, + "totp_last_ticket" varchar, + "created_at" datetime NOT NULL, + "premium_since" datetime, + "verified" boolean NOT NULL, + "disabled" boolean NOT NULL, + "deleted" boolean NOT NULL, + "email" varchar, + "flags" varchar NOT NULL, + "public_flags" integer NOT NULL, + "rights" bigint NOT NULL, + "data" text NOT NULL, + "fingerprints" text NOT NULL, + "extended_settings" text NOT NULL, + "notes" text NOT NULL + ) + `); + await queryRunner.query(` + INSERT INTO "users"( + "id", + "username", + "discriminator", + "avatar", + "accent_color", + "banner", + "phone", + "desktop", + "mobile", + "premium", + "premium_type", + "bot", + "bio", + "system", + "nsfw_allowed", + "mfa_enabled", + "totp_secret", + "totp_last_ticket", + "created_at", + "premium_since", + "verified", + "disabled", + "deleted", + "email", + "flags", + "public_flags", + "rights", + "data", + "fingerprints", + "extended_settings", + "notes" + ) + SELECT "id", + "username", + "discriminator", + "avatar", + "accent_color", + "banner", + "phone", + "desktop", + "mobile", + "premium", + "premium_type", + "bot", + "bio", + "system", + "nsfw_allowed", + "mfa_enabled", + "totp_secret", + "totp_last_ticket", + "created_at", + "premium_since", + "verified", + "disabled", + "deleted", + "email", + "flags", + "public_flags", + "rights", + "data", + "fingerprints", + "extended_settings", + "notes" + FROM "temporary_users" + `); + await queryRunner.query(` + DROP TABLE "temporary_users" + `); + } +} diff --git a/src/util/migrations/sqlite/1660416010862-InvitersAreDeletable.ts b/src/util/migrations/sqlite/1660416010862-InvitersAreDeletable.ts new file mode 100644
index 00000000..a05082c6 --- /dev/null +++ b/src/util/migrations/sqlite/1660416010862-InvitersAreDeletable.ts
@@ -0,0 +1,245 @@ +import { MigrationInterface, QueryRunner } from "typeorm"; + +export class InvitersAreDeletable1660416010862 implements MigrationInterface { + name = "InvitersAreDeletable1660416010862"; + + public async up(queryRunner: QueryRunner): Promise<void> { + await queryRunner.query(` + CREATE TABLE "temporary_invites" ( + "code" varchar PRIMARY KEY NOT NULL, + "temporary" boolean NOT NULL, + "uses" integer NOT NULL, + "max_uses" integer NOT NULL, + "max_age" integer NOT NULL, + "created_at" datetime NOT NULL, + "expires_at" datetime NOT NULL, + "guild_id" varchar, + "channel_id" varchar, + "inviter_id" varchar, + "target_user_id" varchar, + "target_user_type" integer, + "vanity_url" boolean, + CONSTRAINT "FK_11a0d394f8fc649c19ce5f16b59" FOREIGN KEY ("target_user_id") REFERENCES "users" ("id") ON DELETE CASCADE ON UPDATE NO ACTION, + CONSTRAINT "FK_6a15b051fe5050aa00a4b9ff0f6" FOREIGN KEY ("channel_id") REFERENCES "channels" ("id") ON DELETE CASCADE ON UPDATE NO ACTION, + CONSTRAINT "FK_3f4939aa1461e8af57fea3fb05d" FOREIGN KEY ("guild_id") REFERENCES "guilds" ("id") ON DELETE CASCADE ON UPDATE NO ACTION + ) + `); + await queryRunner.query(` + INSERT INTO "temporary_invites"( + "code", + "temporary", + "uses", + "max_uses", + "max_age", + "created_at", + "expires_at", + "guild_id", + "channel_id", + "inviter_id", + "target_user_id", + "target_user_type", + "vanity_url" + ) + SELECT "code", + "temporary", + "uses", + "max_uses", + "max_age", + "created_at", + "expires_at", + "guild_id", + "channel_id", + "inviter_id", + "target_user_id", + "target_user_type", + "vanity_url" + FROM "invites" + `); + await queryRunner.query(` + DROP TABLE "invites" + `); + await queryRunner.query(` + ALTER TABLE "temporary_invites" + RENAME TO "invites" + `); + await queryRunner.query(` + CREATE TABLE "temporary_invites" ( + "code" varchar PRIMARY KEY NOT NULL, + "temporary" boolean NOT NULL, + "uses" integer NOT NULL, + "max_uses" integer NOT NULL, + "max_age" integer NOT NULL, + "created_at" datetime NOT NULL, + "expires_at" datetime NOT NULL, + "guild_id" varchar, + "channel_id" varchar, + "inviter_id" varchar, + "target_user_id" varchar, + "target_user_type" integer, + "vanity_url" boolean, + CONSTRAINT "FK_11a0d394f8fc649c19ce5f16b59" FOREIGN KEY ("target_user_id") REFERENCES "users" ("id") ON DELETE CASCADE ON UPDATE NO ACTION, + CONSTRAINT "FK_6a15b051fe5050aa00a4b9ff0f6" FOREIGN KEY ("channel_id") REFERENCES "channels" ("id") ON DELETE CASCADE ON UPDATE NO ACTION, + CONSTRAINT "FK_3f4939aa1461e8af57fea3fb05d" FOREIGN KEY ("guild_id") REFERENCES "guilds" ("id") ON DELETE CASCADE ON UPDATE NO ACTION, + CONSTRAINT "FK_15c35422032e0b22b4ada95f48f" FOREIGN KEY ("inviter_id") REFERENCES "users" ("id") ON DELETE CASCADE ON UPDATE NO ACTION + ) + `); + await queryRunner.query(` + INSERT INTO "temporary_invites"( + "code", + "temporary", + "uses", + "max_uses", + "max_age", + "created_at", + "expires_at", + "guild_id", + "channel_id", + "inviter_id", + "target_user_id", + "target_user_type", + "vanity_url" + ) + SELECT "code", + "temporary", + "uses", + "max_uses", + "max_age", + "created_at", + "expires_at", + "guild_id", + "channel_id", + "inviter_id", + "target_user_id", + "target_user_type", + "vanity_url" + FROM "invites" + `); + await queryRunner.query(` + DROP TABLE "invites" + `); + await queryRunner.query(` + ALTER TABLE "temporary_invites" + RENAME TO "invites" + `); + } + + public async down(queryRunner: QueryRunner): Promise<void> { + await queryRunner.query(` + ALTER TABLE "invites" + RENAME TO "temporary_invites" + `); + await queryRunner.query(` + CREATE TABLE "invites" ( + "code" varchar PRIMARY KEY NOT NULL, + "temporary" boolean NOT NULL, + "uses" integer NOT NULL, + "max_uses" integer NOT NULL, + "max_age" integer NOT NULL, + "created_at" datetime NOT NULL, + "expires_at" datetime NOT NULL, + "guild_id" varchar, + "channel_id" varchar, + "inviter_id" varchar, + "target_user_id" varchar, + "target_user_type" integer, + "vanity_url" boolean, + CONSTRAINT "FK_11a0d394f8fc649c19ce5f16b59" FOREIGN KEY ("target_user_id") REFERENCES "users" ("id") ON DELETE CASCADE ON UPDATE NO ACTION, + CONSTRAINT "FK_6a15b051fe5050aa00a4b9ff0f6" FOREIGN KEY ("channel_id") REFERENCES "channels" ("id") ON DELETE CASCADE ON UPDATE NO ACTION, + CONSTRAINT "FK_3f4939aa1461e8af57fea3fb05d" FOREIGN KEY ("guild_id") REFERENCES "guilds" ("id") ON DELETE CASCADE ON UPDATE NO ACTION + ) + `); + await queryRunner.query(` + INSERT INTO "invites"( + "code", + "temporary", + "uses", + "max_uses", + "max_age", + "created_at", + "expires_at", + "guild_id", + "channel_id", + "inviter_id", + "target_user_id", + "target_user_type", + "vanity_url" + ) + SELECT "code", + "temporary", + "uses", + "max_uses", + "max_age", + "created_at", + "expires_at", + "guild_id", + "channel_id", + "inviter_id", + "target_user_id", + "target_user_type", + "vanity_url" + FROM "temporary_invites" + `); + await queryRunner.query(` + DROP TABLE "temporary_invites" + `); + await queryRunner.query(` + ALTER TABLE "invites" + RENAME TO "temporary_invites" + `); + await queryRunner.query(` + CREATE TABLE "invites" ( + "code" varchar PRIMARY KEY NOT NULL, + "temporary" boolean NOT NULL, + "uses" integer NOT NULL, + "max_uses" integer NOT NULL, + "max_age" integer NOT NULL, + "created_at" datetime NOT NULL, + "expires_at" datetime NOT NULL, + "guild_id" varchar, + "channel_id" varchar, + "inviter_id" varchar, + "target_user_id" varchar, + "target_user_type" integer, + "vanity_url" boolean, + CONSTRAINT "FK_11a0d394f8fc649c19ce5f16b59" FOREIGN KEY ("target_user_id") REFERENCES "users" ("id") ON DELETE CASCADE ON UPDATE NO ACTION, + CONSTRAINT "FK_15c35422032e0b22b4ada95f48f" FOREIGN KEY ("inviter_id") REFERENCES "users" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION, + CONSTRAINT "FK_6a15b051fe5050aa00a4b9ff0f6" FOREIGN KEY ("channel_id") REFERENCES "channels" ("id") ON DELETE CASCADE ON UPDATE NO ACTION, + CONSTRAINT "FK_3f4939aa1461e8af57fea3fb05d" FOREIGN KEY ("guild_id") REFERENCES "guilds" ("id") ON DELETE CASCADE ON UPDATE NO ACTION + ) + `); + await queryRunner.query(` + INSERT INTO "invites"( + "code", + "temporary", + "uses", + "max_uses", + "max_age", + "created_at", + "expires_at", + "guild_id", + "channel_id", + "inviter_id", + "target_user_id", + "target_user_type", + "vanity_url" + ) + SELECT "code", + "temporary", + "uses", + "max_uses", + "max_age", + "created_at", + "expires_at", + "guild_id", + "channel_id", + "inviter_id", + "target_user_id", + "target_user_type", + "vanity_url" + FROM "temporary_invites" + `); + await queryRunner.query(` + DROP TABLE "temporary_invites" + `); + } +} diff --git a/src/util/migrations/sqlite/1660538628956-sync_migrations.ts b/src/util/migrations/sqlite/1660538628956-sync_migrations.ts new file mode 100644
index 00000000..95c59d28 --- /dev/null +++ b/src/util/migrations/sqlite/1660538628956-sync_migrations.ts
@@ -0,0 +1,171 @@ +import { MigrationInterface, QueryRunner } from "typeorm"; + +export class syncMigrations1660538628956 implements MigrationInterface { + name = "syncMigrations1660538628956"; + + public async up(queryRunner: QueryRunner): Promise<void> { + await queryRunner.query(` + CREATE TABLE "temporary_channels" ( + "id" varchar PRIMARY KEY NOT NULL, + "created_at" datetime NOT NULL, + "name" varchar, + "icon" text, + "type" integer NOT NULL, + "last_message_id" varchar, + "guild_id" varchar, + "parent_id" varchar, + "owner_id" varchar, + "last_pin_timestamp" integer, + "default_auto_archive_duration" integer, + "position" integer, + "permission_overwrites" text, + "video_quality_mode" integer, + "bitrate" integer, + "user_limit" integer, + "nsfw" boolean, + "rate_limit_per_user" integer, + "topic" varchar, + "retention_policy_id" varchar, + "flags" integer, + "default_thread_rate_limit_per_user" integer, + CONSTRAINT "FK_3873ed438575cce703ecff4fc7b" FOREIGN KEY ("owner_id") REFERENCES "users" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION, + CONSTRAINT "FK_3274522d14af40540b1a883fc80" FOREIGN KEY ("parent_id") REFERENCES "channels" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION, + CONSTRAINT "FK_c253dafe5f3a03ec00cd8fb4581" FOREIGN KEY ("guild_id") REFERENCES "guilds" ("id") ON DELETE CASCADE ON UPDATE NO ACTION + ) + `); + await queryRunner.query(` + INSERT INTO "temporary_channels"( + "id", + "created_at", + "name", + "icon", + "type", + "last_message_id", + "guild_id", + "parent_id", + "owner_id", + "last_pin_timestamp", + "default_auto_archive_duration", + "position", + "permission_overwrites", + "video_quality_mode", + "bitrate", + "user_limit", + "nsfw", + "rate_limit_per_user", + "topic", + "retention_policy_id" + ) + SELECT "id", + "created_at", + "name", + "icon", + "type", + "last_message_id", + "guild_id", + "parent_id", + "owner_id", + "last_pin_timestamp", + "default_auto_archive_duration", + "position", + "permission_overwrites", + "video_quality_mode", + "bitrate", + "user_limit", + "nsfw", + "rate_limit_per_user", + "topic", + "retention_policy_id" + FROM "channels" + `); + await queryRunner.query(` + DROP TABLE "channels" + `); + await queryRunner.query(` + ALTER TABLE "temporary_channels" + RENAME TO "channels" + `); + } + + public async down(queryRunner: QueryRunner): Promise<void> { + await queryRunner.query(` + ALTER TABLE "channels" + RENAME TO "temporary_channels" + `); + await queryRunner.query(` + CREATE TABLE "channels" ( + "id" varchar PRIMARY KEY NOT NULL, + "created_at" datetime NOT NULL, + "name" varchar, + "icon" text, + "type" integer NOT NULL, + "last_message_id" varchar, + "guild_id" varchar, + "parent_id" varchar, + "owner_id" varchar, + "last_pin_timestamp" integer, + "default_auto_archive_duration" integer, + "position" integer, + "permission_overwrites" text, + "video_quality_mode" integer, + "bitrate" integer, + "user_limit" integer, + "nsfw" boolean, + "rate_limit_per_user" integer, + "topic" varchar, + "retention_policy_id" varchar, + CONSTRAINT "FK_3873ed438575cce703ecff4fc7b" FOREIGN KEY ("owner_id") REFERENCES "users" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION, + CONSTRAINT "FK_3274522d14af40540b1a883fc80" FOREIGN KEY ("parent_id") REFERENCES "channels" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION, + CONSTRAINT "FK_c253dafe5f3a03ec00cd8fb4581" FOREIGN KEY ("guild_id") REFERENCES "guilds" ("id") ON DELETE CASCADE ON UPDATE NO ACTION + ) + `); + await queryRunner.query(` + INSERT INTO "channels"( + "id", + "created_at", + "name", + "icon", + "type", + "last_message_id", + "guild_id", + "parent_id", + "owner_id", + "last_pin_timestamp", + "default_auto_archive_duration", + "position", + "permission_overwrites", + "video_quality_mode", + "bitrate", + "user_limit", + "nsfw", + "rate_limit_per_user", + "topic", + "retention_policy_id" + ) + SELECT "id", + "created_at", + "name", + "icon", + "type", + "last_message_id", + "guild_id", + "parent_id", + "owner_id", + "last_pin_timestamp", + "default_auto_archive_duration", + "position", + "permission_overwrites", + "video_quality_mode", + "bitrate", + "user_limit", + "nsfw", + "rate_limit_per_user", + "topic", + "retention_policy_id" + FROM "temporary_channels" + `); + await queryRunner.query(` + DROP TABLE "temporary_channels" + `); + } +} diff --git a/src/util/migrations/sqlite/1660549233583-fix_nullables.ts b/src/util/migrations/sqlite/1660549233583-fix_nullables.ts new file mode 100644
index 00000000..fa60bdc1 --- /dev/null +++ b/src/util/migrations/sqlite/1660549233583-fix_nullables.ts
@@ -0,0 +1,239 @@ +import { MigrationInterface, QueryRunner } from "typeorm"; + +export class fixNullables1660549233583 implements MigrationInterface { + name = "fixNullables1660549233583"; + + public async up(queryRunner: QueryRunner): Promise<void> { + await queryRunner.query(` + CREATE TABLE "temporary_users" ( + "id" varchar PRIMARY KEY NOT NULL, + "username" varchar NOT NULL, + "discriminator" varchar NOT NULL, + "avatar" varchar, + "accent_color" integer, + "banner" varchar, + "phone" varchar, + "desktop" boolean NOT NULL, + "mobile" boolean NOT NULL, + "premium" boolean NOT NULL, + "premium_type" integer NOT NULL, + "bot" boolean NOT NULL, + "bio" varchar, + "system" boolean NOT NULL, + "nsfw_allowed" boolean NOT NULL, + "mfa_enabled" boolean, + "totp_secret" varchar, + "totp_last_ticket" varchar, + "created_at" datetime NOT NULL, + "premium_since" datetime, + "verified" boolean NOT NULL, + "disabled" boolean NOT NULL, + "deleted" boolean NOT NULL, + "email" varchar, + "flags" varchar NOT NULL, + "public_flags" integer NOT NULL, + "rights" bigint NOT NULL, + "data" text NOT NULL, + "fingerprints" text NOT NULL, + "extended_settings" text NOT NULL, + "notes" text NOT NULL, + "settingsId" varchar, + CONSTRAINT "UQ_b1dd13b6ed980004a795ca184a6" UNIQUE ("settingsId"), + CONSTRAINT "FK_76ba283779c8441fd5ff819c8cf" FOREIGN KEY ("settingsId") REFERENCES "user_settings" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION + ) + `); + await queryRunner.query(` + INSERT INTO "temporary_users"( + "id", + "username", + "discriminator", + "avatar", + "accent_color", + "banner", + "phone", + "desktop", + "mobile", + "premium", + "premium_type", + "bot", + "bio", + "system", + "nsfw_allowed", + "mfa_enabled", + "totp_secret", + "totp_last_ticket", + "created_at", + "premium_since", + "verified", + "disabled", + "deleted", + "email", + "flags", + "public_flags", + "rights", + "data", + "fingerprints", + "extended_settings", + "notes", + "settingsId" + ) + SELECT "id", + "username", + "discriminator", + "avatar", + "accent_color", + "banner", + "phone", + "desktop", + "mobile", + "premium", + "premium_type", + "bot", + "bio", + "system", + "nsfw_allowed", + "mfa_enabled", + "totp_secret", + "totp_last_ticket", + "created_at", + "premium_since", + "verified", + "disabled", + "deleted", + "email", + "flags", + "public_flags", + "rights", + "data", + "fingerprints", + "extended_settings", + "notes", + "settingsId" + FROM "users" + `); + await queryRunner.query(` + DROP TABLE "users" + `); + await queryRunner.query(` + ALTER TABLE "temporary_users" + RENAME TO "users" + `); + } + + public async down(queryRunner: QueryRunner): Promise<void> { + await queryRunner.query(` + ALTER TABLE "users" + RENAME TO "temporary_users" + `); + await queryRunner.query(` + CREATE TABLE "users" ( + "id" varchar PRIMARY KEY NOT NULL, + "username" varchar NOT NULL, + "discriminator" varchar NOT NULL, + "avatar" varchar, + "accent_color" integer, + "banner" varchar, + "phone" varchar, + "desktop" boolean NOT NULL, + "mobile" boolean NOT NULL, + "premium" boolean NOT NULL, + "premium_type" integer NOT NULL, + "bot" boolean NOT NULL, + "bio" varchar NOT NULL, + "system" boolean NOT NULL, + "nsfw_allowed" boolean NOT NULL, + "mfa_enabled" boolean NOT NULL, + "totp_secret" varchar, + "totp_last_ticket" varchar, + "created_at" datetime NOT NULL, + "premium_since" datetime, + "verified" boolean NOT NULL, + "disabled" boolean NOT NULL, + "deleted" boolean NOT NULL, + "email" varchar, + "flags" varchar NOT NULL, + "public_flags" integer NOT NULL, + "rights" bigint NOT NULL, + "data" text NOT NULL, + "fingerprints" text NOT NULL, + "extended_settings" text NOT NULL, + "notes" text NOT NULL, + "settingsId" varchar, + CONSTRAINT "UQ_b1dd13b6ed980004a795ca184a6" UNIQUE ("settingsId"), + CONSTRAINT "FK_76ba283779c8441fd5ff819c8cf" FOREIGN KEY ("settingsId") REFERENCES "user_settings" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION + ) + `); + await queryRunner.query(` + INSERT INTO "users"( + "id", + "username", + "discriminator", + "avatar", + "accent_color", + "banner", + "phone", + "desktop", + "mobile", + "premium", + "premium_type", + "bot", + "bio", + "system", + "nsfw_allowed", + "mfa_enabled", + "totp_secret", + "totp_last_ticket", + "created_at", + "premium_since", + "verified", + "disabled", + "deleted", + "email", + "flags", + "public_flags", + "rights", + "data", + "fingerprints", + "extended_settings", + "notes", + "settingsId" + ) + SELECT "id", + "username", + "discriminator", + "avatar", + "accent_color", + "banner", + "phone", + "desktop", + "mobile", + "premium", + "premium_type", + "bot", + "bio", + "system", + "nsfw_allowed", + "mfa_enabled", + "totp_secret", + "totp_last_ticket", + "created_at", + "premium_since", + "verified", + "disabled", + "deleted", + "email", + "flags", + "public_flags", + "rights", + "data", + "fingerprints", + "extended_settings", + "notes", + "settingsId" + FROM "temporary_users" + `); + await queryRunner.query(` + DROP TABLE "temporary_users" + `); + } +} diff --git a/gateway/src/schema/Activity.ts b/src/util/schemas/ActivitySchema.ts
index e18f66c8..d94557ea 100644 --- a/gateway/src/schema/Activity.ts +++ b/src/util/schemas/ActivitySchema.ts
@@ -11,7 +11,7 @@ export const ActivitySchema = { $created_at: Date, $timestamps: { $start: Number, - $end: Number, + $end: Number }, $application_id: String, $details: String, @@ -19,28 +19,28 @@ export const ActivitySchema = { $emoji: { $name: String, $id: String, - $animated: Boolean, + $animated: Boolean }, $party: { $id: String, - $size: [Number, Number], + $size: [Number, Number] }, $assets: { $large_image: String, $large_text: String, $small_image: String, - $small_text: String, + $small_text: String }, $secrets: { $join: String, $spectate: String, - $match: String, + $match: String }, $instance: Boolean, - $flags: String, - }, + $flags: String + } ], - $since: Number, // unix time (in milliseconds) of when the client went idle, or null if the client is not idle + $since: Number // unix time (in milliseconds) of when the client went idle, or null if the client is not idle }; export interface ActivitySchema { diff --git a/src/util/schemas/BanCreateSchema.ts b/src/util/schemas/BanCreateSchema.ts new file mode 100644
index 00000000..834577dc --- /dev/null +++ b/src/util/schemas/BanCreateSchema.ts
@@ -0,0 +1,4 @@ +export interface BanCreateSchema { + delete_message_days?: string; + reason?: string; +} diff --git a/src/util/schemas/BanModeratorSchema.ts b/src/util/schemas/BanModeratorSchema.ts new file mode 100644
index 00000000..afb76433 --- /dev/null +++ b/src/util/schemas/BanModeratorSchema.ts
@@ -0,0 +1,7 @@ +export interface BanModeratorSchema { + id: string; + user_id: string; + guild_id: string; + executor_id: string; + reason?: string | undefined; +} diff --git a/src/util/schemas/BanRegistrySchema.ts b/src/util/schemas/BanRegistrySchema.ts new file mode 100644
index 00000000..501f94dc --- /dev/null +++ b/src/util/schemas/BanRegistrySchema.ts
@@ -0,0 +1,8 @@ +export interface BanRegistrySchema { + id: string; + user_id: string; + guild_id: string; + executor_id: string; + ip?: string; + reason?: string | undefined; +} diff --git a/src/util/schemas/BulkDeleteSchema.ts b/src/util/schemas/BulkDeleteSchema.ts new file mode 100644
index 00000000..bfc4df65 --- /dev/null +++ b/src/util/schemas/BulkDeleteSchema.ts
@@ -0,0 +1,3 @@ +export interface BulkDeleteSchema { + messages: string[]; +} diff --git a/src/util/schemas/ChannelModifySchema.ts b/src/util/schemas/ChannelModifySchema.ts new file mode 100644
index 00000000..f5babef0 --- /dev/null +++ b/src/util/schemas/ChannelModifySchema.ts
@@ -0,0 +1,28 @@ +import { ChannelPermissionOverwriteType, ChannelType } from ".."; + +export interface ChannelModifySchema { + /** + * @maxLength 100 + */ + name?: string; + type?: ChannelType; + topic?: string; + icon?: string | null; + bitrate?: number; + user_limit?: number; + rate_limit_per_user?: number; + position?: number; + permission_overwrites?: { + id: string; + type: ChannelPermissionOverwriteType; + allow: string; + deny: string; + }[]; + parent_id?: string; + id?: string; // is not used (only for guild create) + nsfw?: boolean; + rtc_region?: string; + default_auto_archive_duration?: number; + flags?: number; + default_thread_rate_limit_per_user?: number; +} diff --git a/src/util/schemas/ChannelPermissionOverwriteSchema.ts b/src/util/schemas/ChannelPermissionOverwriteSchema.ts new file mode 100644
index 00000000..e8bc13bb --- /dev/null +++ b/src/util/schemas/ChannelPermissionOverwriteSchema.ts
@@ -0,0 +1,5 @@ +import { ChannelPermissionOverwrite } from "@fosscord/util"; + +// TODO: Only permissions your bot has in the guild or channel can be allowed/denied (unless your bot has a MANAGE_ROLES overwrite in the channel) + +export interface ChannelPermissionOverwriteSchema extends ChannelPermissionOverwrite {} diff --git a/src/util/schemas/ChannelReorderSchema.ts b/src/util/schemas/ChannelReorderSchema.ts new file mode 100644
index 00000000..95c2eba9 --- /dev/null +++ b/src/util/schemas/ChannelReorderSchema.ts
@@ -0,0 +1 @@ +export type ChannelReorderSchema = { id: string; position?: number; lock_permissions?: boolean; parent_id?: string }[]; diff --git a/src/util/schemas/DmChannelCreateSchema.ts b/src/util/schemas/DmChannelCreateSchema.ts new file mode 100644
index 00000000..1b0fe86d --- /dev/null +++ b/src/util/schemas/DmChannelCreateSchema.ts
@@ -0,0 +1,4 @@ +export interface DmChannelCreateSchema { + name?: string; + recipients: string[]; +} diff --git a/src/util/schemas/EmojiCreateSchema.ts b/src/util/schemas/EmojiCreateSchema.ts new file mode 100644
index 00000000..34084713 --- /dev/null +++ b/src/util/schemas/EmojiCreateSchema.ts
@@ -0,0 +1,6 @@ +export interface EmojiCreateSchema { + name?: string; + image: string; + require_colons?: boolean | null; + roles?: string[]; +} diff --git a/src/util/schemas/EmojiModifySchema.ts b/src/util/schemas/EmojiModifySchema.ts new file mode 100644
index 00000000..05d2d395 --- /dev/null +++ b/src/util/schemas/EmojiModifySchema.ts
@@ -0,0 +1,4 @@ +export interface EmojiModifySchema { + name?: string; + roles?: string[]; +} diff --git a/src/util/schemas/GuildCreateSchema.ts b/src/util/schemas/GuildCreateSchema.ts new file mode 100644
index 00000000..e4855119 --- /dev/null +++ b/src/util/schemas/GuildCreateSchema.ts
@@ -0,0 +1,14 @@ +import { ChannelModifySchema } from "."; + +export interface GuildCreateSchema { + /** + * @maxLength 100 + */ + name: string; + region?: string; + icon?: string | null; + channels?: ChannelModifySchema[]; + guild_template_code?: string; + system_channel_id?: string; + rules_channel_id?: string; +} diff --git a/src/util/schemas/GuildTemplateCreateSchema.ts b/src/util/schemas/GuildTemplateCreateSchema.ts new file mode 100644
index 00000000..59db8428 --- /dev/null +++ b/src/util/schemas/GuildTemplateCreateSchema.ts
@@ -0,0 +1,4 @@ +export interface GuildTemplateCreateSchema { + name: string; + avatar?: string | null; +} diff --git a/src/util/schemas/GuildUpdateSchema.ts b/src/util/schemas/GuildUpdateSchema.ts new file mode 100644
index 00000000..86527cf1 --- /dev/null +++ b/src/util/schemas/GuildUpdateSchema.ts
@@ -0,0 +1,18 @@ +import { GuildCreateSchema } from "."; + +export interface GuildUpdateSchema extends Omit<GuildCreateSchema, "channels" | "name"> { + name?: string; + banner?: string | null; + splash?: string | null; + description?: string; + features?: string[]; + verification_level?: number; + default_message_notifications?: number; + system_channel_flags?: number; + explicit_content_filter?: number; + public_updates_channel_id?: string; + afk_timeout?: number; + afk_channel_id?: string; + preferred_locale?: string; + premium_progress_bar_enabled?: boolean; +} diff --git a/src/util/schemas/GuildUpdateWelcomeScreenSchema.ts b/src/util/schemas/GuildUpdateWelcomeScreenSchema.ts new file mode 100644
index 00000000..c8b8ba4e --- /dev/null +++ b/src/util/schemas/GuildUpdateWelcomeScreenSchema.ts
@@ -0,0 +1,10 @@ +export interface GuildUpdateWelcomeScreenSchema { + welcome_channels?: { + channel_id: string; + description: string; + emoji_id?: string; + emoji_name: string; + }[]; + enabled?: boolean; + description?: string; +} diff --git a/gateway/src/schema/Identify.ts b/src/util/schemas/IdentifySchema.ts
index 21141321..bb5ae0c8 100644 --- a/gateway/src/schema/Identify.ts +++ b/src/util/schemas/IdentifySchema.ts
@@ -1,8 +1,8 @@ -import { ActivitySchema } from "./Activity"; +import { ActivitySchema } from "./ActivitySchema"; export const IdentifySchema = { token: String, - $intents: BigInt, // discord uses a Integer for bitfields we use bigints tho. | instanceOf will automatically convert the Number to a BigInt + $intents: String, // discord uses a Integer for bitfields we use bigints tho. | instanceOf will automatically convert the Number to a BigInt $properties: Object, // { // // discord uses $ in the property key for bots, so we need to double prefix it, because instanceOf treats $ (prefix) as a optional key @@ -33,7 +33,7 @@ export const IdentifySchema = { $presence: ActivitySchema, $compress: Boolean, $large_threshold: Number, - $shard: [BigInt, BigInt], + $shard: [Number, Number], $guild_subscriptions: Boolean, $capabilities: Number, $client_state: { @@ -41,10 +41,10 @@ export const IdentifySchema = { $highest_last_message_id: String, $read_state_version: Number, $user_guild_settings_version: Number, - $user_settings_version: undefined, + $user_settings_version: undefined }, $v: Number, - $version: Number, + $version: Number }; export interface IdentifySchema { @@ -71,11 +71,11 @@ export interface IdentifySchema { client_version?: string; system_locale?: string; }; - intents?: bigint; // discord uses a Integer for bitfields we use bigints tho. | instanceOf will automatically convert the Number to a BigInt + intents?: string; // discord uses a Integer for bitfields we use bigints tho. | instanceOf will automatically convert the Number to a BigInt presence?: ActivitySchema; compress?: boolean; large_threshold?: number; - shard?: [bigint, bigint]; + shard?: [number, number]; guild_subscriptions?: boolean; capabilities?: number; client_state?: { diff --git a/src/util/schemas/InviteCreateSchema.ts b/src/util/schemas/InviteCreateSchema.ts new file mode 100644
index 00000000..cac11147 --- /dev/null +++ b/src/util/schemas/InviteCreateSchema.ts
@@ -0,0 +1,11 @@ +export interface InviteCreateSchema { + target_user_id?: string; + target_type?: string; + validate?: string; // ? what is this + max_age?: number; + max_uses?: number; + temporary?: boolean; + unique?: boolean; + target_user?: string; + target_user_type?: number; +} diff --git a/gateway/src/schema/LazyRequest.ts b/src/util/schemas/LazyRequestSchema.ts
index 1fe658bb..fbed5c5b 100644 --- a/gateway/src/schema/LazyRequest.ts +++ b/src/util/schemas/LazyRequestSchema.ts
@@ -15,5 +15,5 @@ export const LazyRequest = { $typing: Boolean, $threads: Boolean, $members: [] as any[], - $thread_member_lists: [] as any[], + $thread_member_lists: [] as any[] }; diff --git a/src/util/schemas/LoginSchema.ts b/src/util/schemas/LoginSchema.ts new file mode 100644
index 00000000..dc889d94 --- /dev/null +++ b/src/util/schemas/LoginSchema.ts
@@ -0,0 +1,8 @@ +export interface LoginSchema { + login: string; + password: string; + undelete?: boolean; + captcha_key?: string; + login_source?: string; + gift_code_sku_id?: string; +} diff --git a/src/util/schemas/MemberChangeSchema.ts b/src/util/schemas/MemberChangeSchema.ts new file mode 100644
index 00000000..db434538 --- /dev/null +++ b/src/util/schemas/MemberChangeSchema.ts
@@ -0,0 +1,3 @@ +export interface MemberChangeSchema { + roles?: string[]; +} diff --git a/src/util/schemas/MemberNickChangeSchema.ts b/src/util/schemas/MemberNickChangeSchema.ts new file mode 100644
index 00000000..d863038c --- /dev/null +++ b/src/util/schemas/MemberNickChangeSchema.ts
@@ -0,0 +1,3 @@ +export interface MemberNickChangeSchema { + nick: string; +} diff --git a/src/util/schemas/MessageAcknowledgeSchema.ts b/src/util/schemas/MessageAcknowledgeSchema.ts new file mode 100644
index 00000000..3f4eb2b6 --- /dev/null +++ b/src/util/schemas/MessageAcknowledgeSchema.ts
@@ -0,0 +1,8 @@ +// TODO: public read receipts & privacy scoping +// TODO: send read state event to all channel members +// TODO: advance-only notification cursor + +export interface MessageAcknowledgeSchema { + manual?: boolean; + mention_count?: number; +} diff --git a/src/util/schemas/MessageCreateSchema.ts b/src/util/schemas/MessageCreateSchema.ts new file mode 100644
index 00000000..bf3470bb --- /dev/null +++ b/src/util/schemas/MessageCreateSchema.ts
@@ -0,0 +1,33 @@ +import { Embed } from "@fosscord/util"; + +export interface MessageCreateSchema { + type?: number; + content?: string; + nonce?: string; + channel_id?: string; + tts?: boolean; + flags?: string; + embeds?: Embed[]; + embed?: Embed; + // TODO: ^ embed is deprecated in favor of embeds (https://discord.com/developers/docs/resources/channel#message-object) + allowed_mentions?: { + parse?: string[]; + roles?: string[]; + users?: string[]; + replied_user?: boolean; + }; + message_reference?: { + message_id: string; + channel_id: string; + guild_id?: string; + fail_if_not_exists?: boolean; + }; + payload_json?: string; + file?: any; + /** + TODO: we should create an interface for attachments + TODO: OpenWAAO<-->attachment-style metadata conversion + **/ + attachments?: any[]; + sticker_ids?: string[]; +} diff --git a/src/util/schemas/MfaCodesSchema.ts b/src/util/schemas/MfaCodesSchema.ts new file mode 100644
index 00000000..ac05b9a4 --- /dev/null +++ b/src/util/schemas/MfaCodesSchema.ts
@@ -0,0 +1,4 @@ +export interface MfaCodesSchema { + password: string; + regenerate?: boolean; +} diff --git a/src/util/schemas/ModifyGuildStickerSchema.ts b/src/util/schemas/ModifyGuildStickerSchema.ts new file mode 100644
index 00000000..159cc44f --- /dev/null +++ b/src/util/schemas/ModifyGuildStickerSchema.ts
@@ -0,0 +1,15 @@ +export interface ModifyGuildStickerSchema { + /** + * @minLength 2 + * @maxLength 30 + */ + name: string; + /** + * @maxLength 100 + */ + description?: string; + /** + * @maxLength 200 + */ + tags: string; +} diff --git a/src/util/schemas/PruneSchema.ts b/src/util/schemas/PruneSchema.ts new file mode 100644
index 00000000..bea5e2b4 --- /dev/null +++ b/src/util/schemas/PruneSchema.ts
@@ -0,0 +1,6 @@ +export interface PruneSchema { + /** + * @min 0 + */ + days: number; +} diff --git a/src/util/schemas/PurgeSchema.ts b/src/util/schemas/PurgeSchema.ts new file mode 100644
index 00000000..f5ab0a20 --- /dev/null +++ b/src/util/schemas/PurgeSchema.ts
@@ -0,0 +1,4 @@ +export interface PurgeSchema { + before: string; + after: string; +} diff --git a/src/util/schemas/RegisterSchema.ts b/src/util/schemas/RegisterSchema.ts new file mode 100644
index 00000000..9bbd9db5 --- /dev/null +++ b/src/util/schemas/RegisterSchema.ts
@@ -0,0 +1,26 @@ +export interface RegisterSchema { + /** + * @minLength 2 + * @maxLength 32 + */ + username: string; + /** + * @minLength 1 + * @maxLength 72 + */ + password?: string; + consent: boolean; + /** + * @TJS-format email + */ + email?: string; + fingerprint?: string; + invite?: string; + /** + * @TJS-type string + */ + date_of_birth?: Date; // "2000-04-03" + gift_code_sku_id?: string; + captcha_key?: string; + promotional_email_opt_in?: boolean; +} diff --git a/src/util/schemas/RelationshipPostSchema.ts b/src/util/schemas/RelationshipPostSchema.ts new file mode 100644
index 00000000..774c67f6 --- /dev/null +++ b/src/util/schemas/RelationshipPostSchema.ts
@@ -0,0 +1,4 @@ +export interface RelationshipPostSchema { + discriminator: string; + username: string; +} diff --git a/src/util/schemas/RelationshipPutSchema.ts b/src/util/schemas/RelationshipPutSchema.ts new file mode 100644
index 00000000..0a7f9720 --- /dev/null +++ b/src/util/schemas/RelationshipPutSchema.ts
@@ -0,0 +1,5 @@ +import { RelationshipType } from "@fosscord/util"; + +export interface RelationshipPutSchema { + type?: RelationshipType; +} diff --git a/src/util/schemas/RoleModifySchema.ts b/src/util/schemas/RoleModifySchema.ts new file mode 100644
index 00000000..f3f4a20e --- /dev/null +++ b/src/util/schemas/RoleModifySchema.ts
@@ -0,0 +1,10 @@ +export interface RoleModifySchema { + name?: string; + permissions?: string; + color?: number; + hoist?: boolean; // whether the role should be displayed separately in the sidebar + mentionable?: boolean; // whether the role should be mentionable + position?: number; + icon?: string; + unicode_emoji?: string; +} diff --git a/src/util/schemas/RolePositionUpdateSchema.ts b/src/util/schemas/RolePositionUpdateSchema.ts new file mode 100644
index 00000000..993d1ae0 --- /dev/null +++ b/src/util/schemas/RolePositionUpdateSchema.ts
@@ -0,0 +1,4 @@ +export type RolePositionUpdateSchema = { + id: string; + position: number; +}[]; diff --git a/src/util/schemas/TemplateCreateSchema.ts b/src/util/schemas/TemplateCreateSchema.ts new file mode 100644
index 00000000..160934f5 --- /dev/null +++ b/src/util/schemas/TemplateCreateSchema.ts
@@ -0,0 +1,4 @@ +export interface TemplateCreateSchema { + name: string; + description?: string; +} diff --git a/src/util/schemas/TemplateModifySchema.ts b/src/util/schemas/TemplateModifySchema.ts new file mode 100644
index 00000000..f9c9d14b --- /dev/null +++ b/src/util/schemas/TemplateModifySchema.ts
@@ -0,0 +1,4 @@ +export interface TemplateModifySchema { + name: string; + description?: string; +} diff --git a/src/util/schemas/TotpDisableSchema.ts b/src/util/schemas/TotpDisableSchema.ts new file mode 100644
index 00000000..51446e1c --- /dev/null +++ b/src/util/schemas/TotpDisableSchema.ts
@@ -0,0 +1,3 @@ +export interface TotpDisableSchema { + code: string; +} diff --git a/src/util/schemas/TotpEnableSchema.ts b/src/util/schemas/TotpEnableSchema.ts new file mode 100644
index 00000000..4e3551d9 --- /dev/null +++ b/src/util/schemas/TotpEnableSchema.ts
@@ -0,0 +1,5 @@ +export interface TotpEnableSchema { + password: string; + code?: string; + secret?: string; +} diff --git a/src/util/schemas/TotpSchema.ts b/src/util/schemas/TotpSchema.ts new file mode 100644
index 00000000..941a92ec --- /dev/null +++ b/src/util/schemas/TotpSchema.ts
@@ -0,0 +1,6 @@ +export interface TotpSchema { + code: string; + ticket: string; + gift_code_sku_id?: string | null; + login_source?: string | null; +} diff --git a/src/util/schemas/UserModifySchema.ts b/src/util/schemas/UserModifySchema.ts new file mode 100644
index 00000000..622497d9 --- /dev/null +++ b/src/util/schemas/UserModifySchema.ts
@@ -0,0 +1,19 @@ +export interface UserModifySchema { + /** + * @minLength 1 + * @maxLength 100 + */ + username?: string; + discriminator?: string; + avatar?: string | null; + /** + * @maxLength 1024 + */ + bio?: string; + accent_color?: number; + banner?: string | null; + password?: string; + new_password?: string; + code?: string; + email?: string; +} diff --git a/src/util/schemas/UserSettingsSchema.ts b/src/util/schemas/UserSettingsSchema.ts new file mode 100644
index 00000000..eb9b316d --- /dev/null +++ b/src/util/schemas/UserSettingsSchema.ts
@@ -0,0 +1,3 @@ +import { UserSettings } from "@fosscord/util"; + +export interface UserSettingsSchema extends Partial<UserSettings> {} diff --git a/src/util/schemas/VanityUrlSchema.ts b/src/util/schemas/VanityUrlSchema.ts new file mode 100644
index 00000000..4dd9b9da --- /dev/null +++ b/src/util/schemas/VanityUrlSchema.ts
@@ -0,0 +1,7 @@ +export interface VanityUrlSchema { + /** + * @minLength 1 + * @maxLength 20 + */ + code?: string; +} diff --git a/src/util/schemas/VoiceStateUpdateSchema.ts b/src/util/schemas/VoiceStateUpdateSchema.ts new file mode 100644
index 00000000..ea286b1a --- /dev/null +++ b/src/util/schemas/VoiceStateUpdateSchema.ts
@@ -0,0 +1,18 @@ +export const VoiceStateUpdateSchema = { + $guild_id: String, + $channel_id: String, + self_mute: Boolean, + self_deaf: Boolean, + self_video: Boolean +}; + +//TODO need more testing when community guild and voice stage channel are working +export interface VoiceStateUpdateSchema { + channel_id: string; + guild_id?: string; + suppress?: boolean; + request_to_speak_timestamp?: Date; + self_mute?: boolean; + self_deaf?: boolean; + self_video?: boolean; +} diff --git a/src/util/schemas/WebhookCreateSchema.ts b/src/util/schemas/WebhookCreateSchema.ts new file mode 100644
index 00000000..12ab1869 --- /dev/null +++ b/src/util/schemas/WebhookCreateSchema.ts
@@ -0,0 +1,8 @@ +// TODO: webhooks +export interface WebhookCreateSchema { + /** + * @maxLength 80 + */ + name: string; + avatar?: string; +} diff --git a/src/util/schemas/WidgetModifySchema.ts b/src/util/schemas/WidgetModifySchema.ts new file mode 100644
index 00000000..26d4504f --- /dev/null +++ b/src/util/schemas/WidgetModifySchema.ts
@@ -0,0 +1,4 @@ +export interface WidgetModifySchema { + enabled: boolean; // whether the widget is enabled + channel_id: string; // the widget channel id +} diff --git a/src/util/schemas/index.ts b/src/util/schemas/index.ts new file mode 100644
index 00000000..a15ab4b0 --- /dev/null +++ b/src/util/schemas/index.ts
@@ -0,0 +1,43 @@ +export * from "./ActivitySchema"; +export * from "./BanCreateSchema"; +export * from "./BanModeratorSchema"; +export * from "./BanRegistrySchema"; +export * from "./BulkDeleteSchema"; +export * from "./ChannelModifySchema"; +export * from "./ChannelPermissionOverwriteSchema"; +export * from "./ChannelReorderSchema"; +export * from "./DmChannelCreateSchema"; +export * from "./EmojiCreateSchema"; +export * from "./EmojiModifySchema"; +export * from "./GuildCreateSchema"; +export * from "./GuildTemplateCreateSchema"; +export * from "./GuildUpdateSchema"; +export * from "./GuildUpdateWelcomeScreenSchema"; +export * from "./IdentifySchema"; +export * from "./InviteCreateSchema"; +export * from "./LazyRequestSchema"; +export * from "./LoginSchema"; +export * from "./MemberChangeSchema"; +export * from "./MemberNickChangeSchema"; +export * from "./MessageAcknowledgeSchema"; +export * from "./MessageCreateSchema"; +export * from "./MfaCodesSchema"; +export * from "./ModifyGuildStickerSchema"; +export * from "./PruneSchema"; +export * from "./PurgeSchema"; +export * from "./RegisterSchema"; +export * from "./RelationshipPostSchema"; +export * from "./RelationshipPutSchema"; +export * from "./RoleModifySchema"; +export * from "./RolePositionUpdateSchema"; +export * from "./TemplateCreateSchema"; +export * from "./TemplateModifySchema"; +export * from "./TotpDisableSchema"; +export * from "./TotpEnableSchema"; +export * from "./TotpSchema"; +export * from "./UserModifySchema"; +export * from "./UserSettingsSchema"; +export * from "./VanityUrlSchema"; +export * from "./VoiceStateUpdateSchema"; +export * from "./WebhookCreateSchema"; +export * from "./WidgetModifySchema"; diff --git a/util/src/util/ApiError.ts b/src/util/util/ApiError.ts
index f1a9b4f6..c133e6e7 100644 --- a/util/src/util/ApiError.ts +++ b/src/util/util/ApiError.ts
@@ -9,8 +9,7 @@ export class ApiError extends Error { } withDefaultParams(): ApiError { - if (this.defaultParams) - return new ApiError(applyParamsToString(this.message, this.defaultParams), this.code, this.httpStatus); + if (this.defaultParams) return new ApiError(applyParamsToString(this.message, this.defaultParams), this.code, this.httpStatus); return this; } diff --git a/util/src/util/Array.ts b/src/util/util/Array.ts
index 5a45d1b5..5a45d1b5 100644 --- a/util/src/util/Array.ts +++ b/src/util/util/Array.ts
diff --git a/util/src/util/AutoUpdate.ts b/src/util/util/AutoUpdate.ts
index 531bd8b7..08418040 100644 --- a/util/src/util/AutoUpdate.ts +++ b/src/util/util/AutoUpdate.ts
@@ -1,13 +1,12 @@ -import "missing-native-js-functions"; -import fetch from "node-fetch"; -import ProxyAgent from 'proxy-agent'; -import readline from "readline"; import fs from "fs/promises"; +import fetch from "node-fetch"; import path from "path"; +import ProxyAgent from "proxy-agent"; +import readline from "readline"; const rl = readline.createInterface({ input: process.stdin, - output: process.stdout, + output: process.stdout }); export function enableAutoUpdate(opts: { @@ -18,7 +17,7 @@ export function enableAutoUpdate(opts: { downloadType?: "zip"; }) { if (!opts.checkInterval) return; - var interval = 1000 * 60 * 60 * 24; + let interval = 1000 * 60 * 60 * 24; if (typeof opts.checkInterval === "number") opts.checkInterval = 1000 * interval; const i = setInterval(async () => { @@ -76,7 +75,7 @@ async function getLatestVersion(url: string) { try { const agent = new ProxyAgent(); const response = await fetch(url, { agent }); - const content = await response.json(); + const content: any = await response.json(); return content.version; } catch (error) { throw new Error("[Auto update] check failed for " + url); diff --git a/util/src/util/BitField.ts b/src/util/util/BitField.ts
index fb887e05..306bfb32 100644 --- a/util/src/util/BitField.ts +++ b/src/util/util/BitField.ts
@@ -138,6 +138,9 @@ export class BitField { return bit.map((p) => resolve.call(this, p)).reduce((prev, p) => BigInt(prev) | BigInt(p), BigInt(0)); } if (typeof bit === "string" && typeof FLAGS[bit] !== "undefined") return FLAGS[bit]; + if (bit === "0") return BigInt(0); //special case: 0 + if (typeof bit === "string") return BigInt(bit); //last ditch effort... + if (/--debug|--inspect/.test(process.execArgv.join(" "))) debugger; //if you're here, we have an invalid bitfield... if bit is 0, thats fine, I guess... throw new RangeError("BITFIELD_INVALID: " + bit); } } diff --git a/util/src/util/Categories.ts b/src/util/util/Categories.ts
index a3c69da7..cd706a8a 100644 --- a/util/src/util/Categories.ts +++ b/src/util/util/Categories.ts
@@ -1 +1 @@ -//TODO: populate default discord categories + init, get and set methods \ No newline at end of file +//TODO: populate default discord categories + init, get and set methods diff --git a/src/util/util/Config.ts b/src/util/util/Config.ts new file mode 100644
index 00000000..cc7090a6 --- /dev/null +++ b/src/util/util/Config.ts
@@ -0,0 +1,105 @@ +import fs from "fs"; +import path from "path"; +import { OrmUtils } from "."; +import { ConfigValue } from "../config"; +import { ConfigEntity } from "../entities/Config"; + +// TODO: yaml instead of json +const overridePath = process.env.CONFIG_PATH ?? ""; + +let config: ConfigValue; +let pairs: ConfigEntity[]; + +// TODO: use events to inform about config updates +// Config keys are separated with _ + +export const Config = { + init: async function init() { + if (config) return config; + console.log("[Config] Loading configuration..."); + pairs = await ConfigEntity.find(); + config = pairsToConfig(pairs); + //config = (config || {}).merge(new ConfigValue()); + config = OrmUtils.mergeDeep(new ConfigValue(), config); + + if (process.env.CONFIG_PATH) + try { + const overrideConfig = JSON.parse(fs.readFileSync(overridePath, { encoding: "utf8" })); + config = OrmUtils.mergeDeep(config, overrideConfig); + } catch (error) { + fs.writeFileSync(overridePath, JSON.stringify(config, null, 4)); + } + + if (fs.existsSync(path.join(process.cwd(), "initial.json"))) + try { + console.log("[Config] Found initial configuration, merging..."); + const overrideConfig = JSON.parse(fs.readFileSync(path.join(process.cwd(), "initial.json"), { encoding: "utf8" })); + config = OrmUtils.mergeDeep(config, overrideConfig); + fs.rmSync(path.join(process.cwd(), "initial.json")); + } catch (error) { + fs.writeFileSync(path.join(process.cwd(), "failed.conf"), JSON.stringify(config, null, 4)); + } + + return this.set(config); + }, + get: function get() { + if (!config) { + if (/--debug|--inspect/.test(process.execArgv.join(" "))) + console.log( + "Oops.. trying to get config without config existing... Returning defaults... (Is the database still initialising?)" + ); + return new ConfigValue(); + } + return config; + }, + set: function set(val: Partial<ConfigValue>) { + if (!config || !val) return; + config = val.merge(config); + + return applyConfig(config); + } +}; + +function applyConfig(val: ConfigValue) { + async function apply(obj: any, key = ""): Promise<any> { + if (typeof obj === "object" && obj !== null) + return Promise.all(Object.keys(obj).map((k) => apply(obj[k], key ? `${key}_${k}` : k))); + + let pair = pairs.find((x) => x.key === key); + if (!pair) pair = new ConfigEntity(); + + pair.key = key; + pair.value = obj; + return pair.save(); + } + if (process.env.CONFIG_PATH) { + if (/--debug|--inspect/.test(process.execArgv.join(" "))) console.log(`Writing config: ${process.env.CONFIG_PATH}`); + fs.writeFileSync(overridePath, JSON.stringify(val, null, 4)); + } + + return apply(val); +} + +function pairsToConfig(pairs: ConfigEntity[]) { + let value: any = {}; + + pairs.forEach((p) => { + const keys = p.key.split("_"); + let obj = value; + let prev = ""; + let prevObj = obj; + let i = 0; + + for (const key of keys) { + if (!isNaN(Number(key)) && !prevObj[prev]?.length) prevObj[prev] = obj = []; + if (i++ === keys.length - 1) obj[key] = p.value; + else if (!obj[key]) obj[key] = {}; + + prev = key; + prevObj = obj; + obj = obj[key]; + } + }); + + return value as ConfigValue; +} diff --git a/util/src/util/Constants.ts b/src/util/util/Constants.ts
index a5d3fcd2..f7aff26a 100644 --- a/util/src/util/Constants.ts +++ b/src/util/util/Constants.ts
@@ -6,7 +6,7 @@ export const WSCodes = { 4010: "SHARDING_INVALID", 4011: "SHARDING_REQUIRED", 4013: "INVALID_INTENTS", - 4014: "DISALLOWED_INTENTS", + 4014: "DISALLOWED_INTENTS" }; /** @@ -31,7 +31,7 @@ export const WsStatus = { DISCONNECTED: 5, WAITING_FOR_GUILDS: 6, IDENTIFYING: 7, - RESUMING: 8, + RESUMING: 8 }; /** @@ -48,7 +48,7 @@ export const VoiceStatus = { CONNECTING: 1, AUTHENTICATING: 2, RECONNECTING: 3, - DISCONNECTED: 4, + DISCONNECTED: 4 }; export const OPCodes = { @@ -63,7 +63,7 @@ export const OPCodes = { REQUEST_GUILD_MEMBERS: 8, INVALID_SESSION: 9, HELLO: 10, - HEARTBEAT_ACK: 11, + HEARTBEAT_ACK: 11 }; export const VoiceOPCodes = { @@ -75,7 +75,7 @@ export const VoiceOPCodes = { SPEAKING: 5, HELLO: 8, CLIENT_CONNECT: 12, - CLIENT_DISCONNECT: 13, + CLIENT_DISCONNECT: 13 }; export const Events = { @@ -133,7 +133,7 @@ export const Events = { SHARD_READY: "shardReady", SHARD_RESUME: "shardResume", INVALIDATED: "invalidated", - RAW: "raw", + RAW: "raw" }; export const ShardEvents = { @@ -142,7 +142,7 @@ export const ShardEvents = { INVALID_SESSION: "invalidSession", READY: "ready", RESUMED: "resumed", - ALL_READY: "allReady", + ALL_READY: "allReady" }; /** @@ -234,7 +234,7 @@ export const WSEvents = keyMirror([ "TYPING_START", "VOICE_STATE_UPDATE", "VOICE_SERVER_UPDATE", - "WEBHOOKS_UPDATE", + "WEBHOOKS_UPDATE" ]); /** @@ -277,7 +277,7 @@ export const MessageTypes = [ null, null, null, - "REPLY", + "REPLY" ]; /** @@ -286,9 +286,7 @@ export const MessageTypes = [ * * REPLY * @typedef {string} SystemMessageType */ -export const SystemMessageTypes = MessageTypes.filter( - (type: string | null) => type && type !== "DEFAULT" && type !== "REPLY" -); +export const SystemMessageTypes = MessageTypes.filter((type: string | null) => type && type !== "DEFAULT" && type !== "REPLY"); /** * <info>Bots cannot set a `CUSTOM_STATUS`, it is only for custom statuses received from users</info> @@ -310,12 +308,12 @@ export const ChannelTypes = { GROUP: 3, CATEGORY: 4, NEWS: 5, - STORE: 6, + STORE: 6 }; export const ClientApplicationAssetTypes = { SMALL: 1, - BIG: 2, + BIG: 2 }; export const Colors = { @@ -347,7 +345,7 @@ export const Colors = { BLURPLE: 0x7289da, GREYPLE: 0x99aab5, DARK_BUT_NOT_BLACK: 0x2c2f33, - NOT_QUITE_BLACK: 0x23272a, + NOT_QUITE_BLACK: 0x23272a }; /** @@ -556,14 +554,8 @@ export const DiscordApiErrors = { UNKNOWN_GUILD_SCHEDULED_EVENT_USER: new ApiError("Unknown Guild Scheduled Event User", 10071), BOT_PROHIBITED_ENDPOINT: new ApiError("Bots cannot use this endpoint", 20001), BOT_ONLY_ENDPOINT: new ApiError("Only bots can use this endpoint", 20002), - EXPLICIT_CONTENT_CANNOT_BE_SENT_TO_RECIPIENT: new ApiError( - "Explicit content cannot be sent to the desired recipient(s)", - 20009 - ), - ACTION_NOT_AUTHORIZED_ON_APPLICATION: new ApiError( - "You are not authorized to perform this action on this application", - 20012 - ), + EXPLICIT_CONTENT_CANNOT_BE_SENT_TO_RECIPIENT: new ApiError("Explicit content cannot be sent to the desired recipient(s)", 20009), + ACTION_NOT_AUTHORIZED_ON_APPLICATION: new ApiError("You are not authorized to perform this action on this application", 20012), SLOWMODE_RATE_LIMIT: new ApiError("This action cannot be performed due to slowmode rate limit", 20016), ONLY_OWNER: new ApiError("Only the owner of this account can perform this action", 20018), ANNOUNCEMENT_RATE_LIMITS: new ApiError("This message cannot be edited due to announcement rate limits", 20022), @@ -576,40 +568,25 @@ export const DiscordApiErrors = { MAXIMUM_GUILDS: new ApiError("Maximum number of guilds reached ({})", 30001, undefined, ["100"]), MAXIMUM_FRIENDS: new ApiError("Maximum number of friends reached ({})", 30002, undefined, ["1000"]), MAXIMUM_PINS: new ApiError("Maximum number of pins reached for the channel ({})", 30003, undefined, ["50"]), - MAXIMUM_NUMBER_OF_RECIPIENTS_REACHED: new ApiError("Maximum number of recipients reached ({})", 30004, undefined, [ - "10", - ]), + MAXIMUM_NUMBER_OF_RECIPIENTS_REACHED: new ApiError("Maximum number of recipients reached ({})", 30004, undefined, ["10"]), MAXIMUM_ROLES: new ApiError("Maximum number of guild roles reached ({})", 30005, undefined, ["250"]), MAXIMUM_WEBHOOKS: new ApiError("Maximum number of webhooks reached ({})", 30007, undefined, ["10"]), MAXIMUM_NUMBER_OF_EMOJIS_REACHED: new ApiError("Maximum number of emojis reached", 30008), MAXIMUM_REACTIONS: new ApiError("Maximum number of reactions reached ({})", 30010, undefined, ["20"]), MAXIMUM_CHANNELS: new ApiError("Maximum number of guild channels reached ({})", 30013, undefined, ["500"]), - MAXIMUM_ATTACHMENTS: new ApiError("Maximum number of attachments in a message reached ({})", 30015, undefined, [ - "10", - ]), + MAXIMUM_ATTACHMENTS: new ApiError("Maximum number of attachments in a message reached ({})", 30015, undefined, ["10"]), MAXIMUM_INVITES: new ApiError("Maximum number of invites reached ({})", 30016, undefined, ["1000"]), MAXIMUM_ANIMATED_EMOJIS: new ApiError("Maximum number of animated emojis reached", 30018), MAXIMUM_SERVER_MEMBERS: new ApiError("Maximum number of server members reached", 30019), - MAXIMUM_SERVER_CATEGORIES: new ApiError( - "Maximum number of server categories has been reached ({})", - 30030, - undefined, - ["5"] - ), + MAXIMUM_SERVER_CATEGORIES: new ApiError("Maximum number of server categories has been reached ({})", 30030, undefined, ["5"]), GUILD_ALREADY_HAS_TEMPLATE: new ApiError("Guild already has a template", 30031), MAXIMUM_THREAD_PARTICIPANTS: new ApiError("Max number of thread participants has been reached", 30033), - MAXIMUM_BANS_FOR_NON_GUILD_MEMBERS: new ApiError( - "Maximum number of bans for non-guild members have been exceeded", - 30035 - ), + MAXIMUM_BANS_FOR_NON_GUILD_MEMBERS: new ApiError("Maximum number of bans for non-guild members have been exceeded", 30035), MAXIMUM_BANS_FETCHES: new ApiError("Maximum number of bans fetches has been reached", 30037), MAXIMUM_STICKERS: new ApiError("Maximum number of stickers reached", 30039), MAXIMUM_PRUNE_REQUESTS: new ApiError("Maximum number of prune requests has been reached. Try again later", 30040), UNAUTHORIZED: new ApiError("Unauthorized. Provide a valid token and try again", 40001), - ACCOUNT_VERIFICATION_REQUIRED: new ApiError( - "You need to verify your account in order to perform this action", - 40002 - ), + ACCOUNT_VERIFICATION_REQUIRED: new ApiError("You need to verify your account in order to perform this action", 40002), OPENING_DIRECT_MESSAGES_TOO_FAST: new ApiError("You are opening direct messages too fast", 40003), REQUEST_ENTITY_TOO_LARGE: new ApiError("Request entity too large. Try sending something smaller in size", 40005), FEATURE_TEMPORARILY_DISABLED: new ApiError("This feature has been temporarily disabled server-side", 40006), @@ -625,10 +602,7 @@ export const DiscordApiErrors = { CANNOT_SEND_EMPTY_MESSAGE: new ApiError("Cannot send an empty message", 50006), CANNOT_MESSAGE_USER: new ApiError("Cannot send messages to this user", 50007), CANNOT_SEND_MESSAGES_IN_VOICE_CHANNEL: new ApiError("Cannot send messages in a voice channel", 50008), - CHANNEL_VERIFICATION_LEVEL_TOO_HIGH: new ApiError( - "Channel verification level is too high for you to gain access", - 50009 - ), + CHANNEL_VERIFICATION_LEVEL_TOO_HIGH: new ApiError("Channel verification level is too high for you to gain access", 50009), OAUTH2_APPLICATION_BOT_ABSENT: new ApiError("OAuth2 application does not have a bot", 50010), MAXIMUM_OAUTH2_APPLICATIONS: new ApiError("OAuth2 application limit reached", 50011), INVALID_OAUTH_STATE: new ApiError("Invalid OAuth2 state", 50012), @@ -641,10 +615,7 @@ export const DiscordApiErrors = { undefined, ["2", "100"] ), - CANNOT_PIN_MESSAGE_IN_OTHER_CHANNEL: new ApiError( - "A message can only be pinned to the channel it was sent in", - 50019 - ), + CANNOT_PIN_MESSAGE_IN_OTHER_CHANNEL: new ApiError("A message can only be pinned to the channel it was sent in", 50019), INVALID_OR_TAKEN_INVITE_CODE: new ApiError("Invite code was either invalid or taken", 50020), CANNOT_EXECUTE_ON_SYSTEM_MESSAGE: new ApiError("Cannot execute action on a system message", 50021), CANNOT_EXECUTE_ON_THIS_CHANNEL_TYPE: new ApiError("Cannot execute action on this channel type", 50024), @@ -658,34 +629,22 @@ export const DiscordApiErrors = { "Invalid form body (returned for both application/json and multipart/form-data bodies), or invalid Content-Type provided", 50035 ), - INVITE_ACCEPTED_TO_GUILD_NOT_CONTAINING_BOT: new ApiError( - "An invite was accepted to a guild the application's bot is not in", - 50036 - ), + INVITE_ACCEPTED_TO_GUILD_NOT_CONTAINING_BOT: new ApiError("An invite was accepted to a guild the application's bot is not in", 50036), INVALID_API_VERSION: new ApiError("Invalid API version provided", 50041), FILE_EXCEEDS_MAXIMUM_SIZE: new ApiError("File uploaded exceeds the maximum size", 50045), INVALID_FILE_UPLOADED: new ApiError("Invalid file uploaded", 50046), CANNOT_SELF_REDEEM_GIFT: new ApiError("Cannot self-redeem this gift", 50054), PAYMENT_SOURCE_REQUIRED: new ApiError("Payment source required to redeem gift", 50070), - CANNOT_DELETE_COMMUNITY_REQUIRED_CHANNEL: new ApiError( - "Cannot delete a channel required for Community guilds", - 50074 - ), + CANNOT_DELETE_COMMUNITY_REQUIRED_CHANNEL: new ApiError("Cannot delete a channel required for Community guilds", 50074), INVALID_STICKER_SENT: new ApiError("Invalid sticker sent", 50081), CANNOT_EDIT_ARCHIVED_THREAD: new ApiError( "Tried to perform an operation on an archived thread, such as editing a message or adding a user to the thread", 50083 ), INVALID_THREAD_NOTIFICATION_SETTINGS: new ApiError("Invalid thread notification settings", 50084), - BEFORE_EARLIER_THAN_THREAD_CREATION_DATE: new ApiError( - "before value is earlier than the thread creation date", - 50085 - ), + BEFORE_EARLIER_THAN_THREAD_CREATION_DATE: new ApiError("before value is earlier than the thread creation date", 50085), SERVER_NOT_AVAILABLE_IN_YOUR_LOCATION: new ApiError("This server is not available in your location", 50095), - SERVER_NEEDS_MONETIZATION_ENABLED: new ApiError( - "This server needs monetization enabled in order to perform this action", - 50097 - ), + SERVER_NEEDS_MONETIZATION_ENABLED: new ApiError("This server needs monetization enabled in order to perform this action", 50097), TWO_FACTOR_REQUIRED: new ApiError("Two factor is required for this operation", 60003), NO_USERS_WITH_DISCORDTAG_EXIST: new ApiError("No users with DiscordTag exist", 80004), REACTION_BLOCKED: new ApiError("Reaction was blocked", 90001), @@ -694,33 +653,17 @@ export const DiscordApiErrors = { THREAD_ALREADY_CREATED_FOR_THIS_MESSAGE: new ApiError("A thread has already been created for this message", 160004), THREAD_IS_LOCKED: new ApiError("Thread is locked", 160005), MAXIMUM_NUMBER_OF_ACTIVE_THREADS: new ApiError("Maximum number of active threads reached", 160006), - MAXIMUM_NUMBER_OF_ACTIVE_ANNOUNCEMENT_THREADS: new ApiError( - "Maximum number of active announcement threads reached", - 160007 - ), + MAXIMUM_NUMBER_OF_ACTIVE_ANNOUNCEMENT_THREADS: new ApiError("Maximum number of active announcement threads reached", 160007), INVALID_JSON_FOR_UPLOADED_LOTTIE_FILE: new ApiError("Invalid JSON for uploaded Lottie file", 170001), - LOTTIES_CANNOT_CONTAIN_RASTERIZED_IMAGES: new ApiError( - "Uploaded Lotties cannot contain rasterized images such as PNG or JPEG", - 170002 - ), + LOTTIES_CANNOT_CONTAIN_RASTERIZED_IMAGES: new ApiError("Uploaded Lotties cannot contain rasterized images such as PNG or JPEG", 170002), STICKER_MAXIMUM_FRAMERATE: new ApiError("Sticker maximum framerate exceeded", 170003), - STICKER_MAXIMUM_FRAME_COUNT: new ApiError("Sticker frame count exceeds maximum of {} frames", 170004, undefined, [ - "1000", - ]), + STICKER_MAXIMUM_FRAME_COUNT: new ApiError("Sticker frame count exceeds maximum of {} frames", 170004, undefined, ["1000"]), LOTTIE_ANIMATION_MAXIMUM_DIMENSIONS: new ApiError("Lottie animation maximum dimensions exceeded", 170005), - STICKER_FRAME_RATE_TOO_SMALL_OR_TOO_LARGE: new ApiError( - "Sticker frame rate is either too small or too large", - 170006 - ), - STICKER_ANIMATION_DURATION_MAXIMUM: new ApiError( - "Sticker animation duration exceeds maximum of {} seconds", - 170007, - undefined, - ["5"] - ), + STICKER_FRAME_RATE_TOO_SMALL_OR_TOO_LARGE: new ApiError("Sticker frame rate is either too small or too large", 170006), + STICKER_ANIMATION_DURATION_MAXIMUM: new ApiError("Sticker animation duration exceeds maximum of {} seconds", 170007, undefined, ["5"]), //Other errors - UNKNOWN_VOICE_STATE: new ApiError("Unknown Voice State", 10065, 404), + UNKNOWN_VOICE_STATE: new ApiError("Unknown Voice State", 10065, 404) }; /** @@ -746,7 +689,7 @@ export const FosscordApiErrors = { CANNOT_BACKFILL_TO_THE_FUTURE: new ApiError("You cannot backfill messages in the future", 55003), CANNOT_GRANT_PERMISSIONS_EXCEEDING_RIGHTS: new ApiError("You cannot grant permissions exceeding your own rights", 50050), ROUTES_LOOPING: new ApiError("Loops in the route definition ({})", 50060, undefined, [""]), - CANNOT_REMOVE_ROUTE: new ApiError("Cannot remove message route while it is in effect and being used", 50061), + CANNOT_REMOVE_ROUTE: new ApiError("Cannot remove message route while it is in effect and being used", 50061) }; /** @@ -765,11 +708,7 @@ export const DefaultMessageNotifications = ["ALL", "MENTIONS", "MUTED"]; * * INSERTED (Fosscord extension) * @typedef {string} MembershipStates */ -export const MembershipStates = [ - "INSERTED", - "INVITED", - "ACCEPTED", -]; +export const MembershipStates = ["INSERTED", "INVITED", "ACCEPTED"]; /** * The value set for a webhook's type: @@ -778,15 +717,10 @@ export const MembershipStates = [ * * Custom (Fosscord extension) * @typedef {string} WebhookTypes */ -export const WebhookTypes = [ - "Custom", - "Incoming", - "Channel Follower", -]; +export const WebhookTypes = ["Custom", "Incoming", "Channel Follower"]; function keyMirror(arr: string[]) { let tmp = Object.create(null); for (const value of arr) tmp[value] = value; return tmp; } - diff --git a/src/util/util/Database.ts b/src/util/util/Database.ts new file mode 100644
index 00000000..b9f8365e --- /dev/null +++ b/src/util/util/Database.ts
@@ -0,0 +1,107 @@ +import { config } from "dotenv"; +import fs from "fs"; +import path from "path"; +import { green, red, yellow } from "picocolors"; +import { exit } from "process"; +import "reflect-metadata"; +import { DataSource, DataSourceOptions, PrimaryColumn, PrimaryGeneratedColumn } from "typeorm"; +import * as Models from "../entities"; +import { BaseClass, BaseClassWithoutId } from "../entities"; + +// 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 + +let promise: Promise<any>; +let dataSource: DataSource; + +export async function getOrInitialiseDatabase(): Promise<DataSource> { + //if (dataSource) return dataSource; // prevent initalizing multiple times + + if (dataSource.isInitialized) return dataSource; + + await dataSource.initialize(); + console.log(`[Database] ${green("Connected!")}`); + await dataSource.runMigrations(); + console.log(`[Database] ${green("Up to date!")}`); + + if ("DB_MIGRATE" in process.env) { + console.log("DB_MIGRATE specified, exiting!"); + exit(0); + } + return dataSource; +} + +export function closeDatabase() { + dataSource?.destroy(); +} + +function getDataSourceOptions(): DataSourceOptions { + config(); + //get connection string and check for migrations + const dbConnectionString = process.env.DATABASE || path.join(process.cwd(), "database.db"); + const type = dbConnectionString.includes("://") ? dbConnectionString.split(":")[0]?.replace("+srv", "") : ("sqlite" as any); + const isSqlite = type.includes("sqlite"); + const migrationsExist = fs.existsSync(path.join(__dirname, "..", "migrations", type)); + //read env vars + const synchronizeInsteadOfMigrations = "DB_UNSAFE" in process.env; + const verboseDb = "DB_VERBOSE" in process.env; + + if (isSqlite) + console.log(`[Database] ${red(`You are running sqlite! Please keep in mind that we recommend setting up a dedicated database!`)}`); + if (verboseDb) + console.log( + `[Database] ${red(`Verbose database logging is enabled, this might impact performance! Unset DB_VERBOSE to disable.`)}` + ); + + if (synchronizeInsteadOfMigrations) { + console.log( + `[Database] ${red( + `Unsafe database upgrades are enabled! We are not responsible for broken databases! Unset DB_UNSAFE to disable.` + )}` + ); + } else if (!migrationsExist) { + console.log(`[Database] ${red(`Database engine not supported! Set UNSAFE_DB to bypass.`)}`); + console.log(`[Database] ${red(`Please mention this to Fosscord developers, and provide this info:`)}`); + console.log( + `[Database]\n${red( + JSON.stringify( + { + db_type: type, + migrations_exist: migrationsExist + }, + null, + 4 + ) + )}` + ); + + if (!("DB_MIGRATE" in process.env)) exit(1); + } + console.log(`[Database] ${yellow(`Configuring data source to use ${type} database...`)}`); + return { + type, + charset: "utf8mb4", + url: isSqlite ? undefined : dbConnectionString, + database: isSqlite ? dbConnectionString : undefined, + // @ts-ignore + //entities: Object.values(Models).filter((x) => x.constructor.name !== "Object" && x.constructor.name !== "Array" && x.constructor.name !== "BigInt" && x).map(x=>x.name), + entities: Object.values(Models).filter((x) => x.constructor.name == "Function" && shouldIncludeEntity(x.name)), + synchronize: synchronizeInsteadOfMigrations, + logging: verboseDb ? "all" : false, + cache: { + duration: 1000 * 3 // cache all find queries for 3 seconds + }, + bigNumberStrings: false, + supportBigNumbers: true, + name: "default", + migrations: synchronizeInsteadOfMigrations ? [] : [path.join(__dirname, "..", "migrations", type, "*.js")], + migrationsRun: !synchronizeInsteadOfMigrations, + applicationName: `Fosscord Server`, + } as DataSourceOptions; +} + +function shouldIncludeEntity(name: string): boolean { + return ![BaseClassWithoutId, PrimaryColumn, BaseClass, PrimaryGeneratedColumn].map((x) => x.name).includes(name); +} + +export default dataSource = new DataSource(getDataSourceOptions()); diff --git a/util/src/util/Email.ts b/src/util/util/Email.ts
index 6885da33..c98ccff0 100644 --- a/util/src/util/Email.ts +++ b/src/util/util/Email.ts
@@ -15,7 +15,7 @@ export function adjustEmail(email?: string): string | undefined { // replace .dots and +alternatives -> Gmail Dot Trick https://support.google.com/mail/answer/7436150 and https://generator.email/blog/gmail-generator let v = user.replace(/[.]|(\+.*)/g, "") + "@gmail.com"; } - + if (domain === "google.com") { // replace .dots and +alternatives -> Google Staff GMail Dot Trick let v = user.replace(/[.]|(\+.*)/g, "") + "@google.com"; diff --git a/util/src/util/Event.ts b/src/util/util/Event.ts
index bb624051..383e4e50 100644 --- a/util/src/util/Event.ts +++ b/src/util/util/Event.ts
@@ -1,7 +1,7 @@ import { Channel } from "amqplib"; -import { RabbitMQ } from "./RabbitMQ"; import EventEmitter from "events"; import { EVENT, Event } from "../interfaces"; +import { RabbitMQ } from "./RabbitMQ"; export const events = new EventEmitter(); export async function emitEvent(payload: Omit<Event, "created_at">) { @@ -58,8 +58,8 @@ export async function listenEvent(event: string, callback: (event: EventOpts) => process.setMaxListeners(process.getMaxListeners() - 1); }; - const listener = (msg: ProcessEvent) => { - msg.type === "event" && msg.id === event && callback({ ...msg.event, cancel }); + const listener = (message: any) => { + message.type === "event" && message.id === event && callback({ ...message.event, cancel }); }; process.addListener("message", listener); @@ -79,12 +79,7 @@ export async function listenEvent(event: string, callback: (event: EventOpts) => } } -async function rabbitListen( - channel: Channel, - id: string, - callback: (event: EventOpts) => any, - opts?: { acknowledge?: boolean } -) { +async function rabbitListen(channel: Channel, id: string, callback: (event: EventOpts) => any, opts?: { acknowledge?: boolean }) { await channel.assertExchange(id, "fanout", { durable: false }); const q = await channel.assertQueue("", { exclusive: true, autoDelete: true }); @@ -109,12 +104,12 @@ async function rabbitListen( channel.ack(opts); }, channel, - cancel, + cancel }); // rabbitCh.ack(opts); }, { - noAck: !opts?.acknowledge, + noAck: !opts?.acknowledge } ); diff --git a/util/src/util/FieldError.ts b/src/util/util/FieldError.ts
index 406b33e8..bdffd618 100644 --- a/util/src/util/FieldError.ts +++ b/src/util/util/FieldError.ts
@@ -1,5 +1,3 @@ -import "missing-native-js-functions"; - export function FieldErrors(fields: Record<string, { code?: string; message: string }>) { return new FieldError( 50035, @@ -8,9 +6,9 @@ export function FieldErrors(fields: Record<string, { code?: string; message: str _errors: [ { message, - code: code || "BASE_TYPE_INVALID", - }, - ], + code: code || "BASE_TYPE_INVALID" + } + ] })) ); } diff --git a/util/src/util/Intents.ts b/src/util/util/Intents.ts
index 1e840b76..db4c9e28 100644 --- a/util/src/util/Intents.ts +++ b/src/util/util/Intents.ts
@@ -22,13 +22,12 @@ export class Intents extends BitField { GUILD_POLICY_EXECUTION: BigInt(1) << BigInt(21), // guild policy execution LIVE_MESSAGE_COMPOSITION: BigInt(1) << BigInt(32), // allow composing messages using the gateway GUILD_ROUTES: BigInt(1) << BigInt(41), // message routes affecting the guild - DIRECT_MESSAGES_THREADS: BigInt(1) << BigInt(42), // direct message threads + DIRECT_MESSAGES_THREADS: BigInt(1) << BigInt(42), // direct message threads JUMBO_EVENTS: BigInt(1) << BigInt(43), // jumbo events (size limits to be defined later) LOBBIES: BigInt(1) << BigInt(44), // lobbies - INSTANCE_ROUTES: BigInt(1) << BigInt(60), // all message route changes + INSTANCE_ROUTES: BigInt(1) << BigInt(60), // all message route changes INSTANCE_GUILD_CHANGES: BigInt(1) << BigInt(61), // all guild create, guild object patch, split, merge and delete events INSTANCE_POLICY_UPDATES: BigInt(1) << BigInt(62), // all instance policy updates INSTANCE_USER_UPDATES: BigInt(1) << BigInt(63) // all instance user updates }; } - diff --git a/src/util/util/InvisibleCharacters.ts b/src/util/util/InvisibleCharacters.ts new file mode 100644
index 00000000..9bffec58 --- /dev/null +++ b/src/util/util/InvisibleCharacters.ts
@@ -0,0 +1,57 @@ +// List from https://invisible-characters.com/ +export const InvisibleCharacters = [ + "\u{9}", //Tab + "\u{c}", //Form feed + //'\u{20}', //Space //categories can have spaces in them + "\u{ad}", //Soft hyphen + // '\u{34f}', //Combining grapheme joiner + "\u{61c}", //Arabic letter mark + "\u{115f}", //Hangul choseong filler + "\u{1160}", //Hangul jungseong filler + "\u{17b4}", //Khmer vowel inherent AQ + "\u{17b5}", //Khmer vowel inherent AA + "\u{180e}", //Mongolian vowel separator + "\u{2000}", //En quad + "\u{2001}", //Em quad + "\u{2002}", //En space + "\u{2003}", //Em space + "\u{2004}", //Three-per-em space + "\u{2005}", //Four-per-em space + "\u{2006}", //Six-per-em space + "\u{2007}", //Figure space + "\u{2008}", //Punctuation space + "\u{2009}", //Thin space + "\u{200a}", //Hair space + "\u{200b}", //Zero width space + "\u{200c}", //Zero width non-joiner + // '\u{200d}', //Zero width joiner + "\u{200e}", //Left-to-right mark + "\u{200f}", //Right-to-left mark + "\u{202f}", //Narrow no-break space + "\u{205f}", //Medium mathematical space + // '\u{2060}', //Word joiner -- WJ is required in some languages that don't use spaces to split words + "\u{2061}", //Function application + "\u{2062}", //Invisible times + "\u{2063}", //Invisible separator + "\u{2064}", //Invisible plus + "\u{206a}", //Inhibit symmetric swapping + "\u{206b}", //Activate symmetric swapping + "\u{206c}", //Inhibit arabic form shaping + "\u{206d}", //Activate arabic form shaping + "\u{206e}", //National digit shapes + "\u{206f}", //Nominal digit shapes + "\u{3000}", //Ideographic space + "\u{2800}", //Braille pattern blank + "\u{3164}", //Hangul filler + "\u{feff}", //Zero width no-break space + "\u{ffa0}", //Haldwidth hangul filler + "\u{1d159}", //Musical symbol null notehead + "\u{1d173}", //Musical symbol begin beam + "\u{1d174}", //Musical symbol end beam + "\u{1d175}", //Musical symbol begin tie + "\u{1d176}", //Musical symbol end tie + "\u{1d177}", //Musical symbol begin slur + "\u{1d178}", //Musical symbol end slur + "\u{1d179}", //Musical symbol begin phrase + "\u{1d17a}" //Musical symbol end phrase +]; diff --git a/src/util/util/MFA.ts b/src/util/util/MFA.ts new file mode 100644
index 00000000..b9af6d23 --- /dev/null +++ b/src/util/util/MFA.ts
@@ -0,0 +1,18 @@ +import crypto from "crypto"; +import { Config } from "."; +import { BackupCode } from "../entities/BackupCodes"; + +export function generateMfaBackupCodes(user_id: string) { + let backup_codes: BackupCode[] = []; + for (let i = 0; i < Config.get().security.mfaBackupCodeCount; i++) { + const code = BackupCode.create({ + user: { id: user_id }, + code: crypto.randomBytes(Config.get().security.mfaBackupCodeBytes).toString("hex"), // 8 characters + consumed: false, + expired: false + }); + backup_codes.push(code); + } + + return backup_codes; +} diff --git a/util/src/util/MessageFlags.ts b/src/util/util/MessageFlags.ts
index b59295c4..c5d5d02a 100644 --- a/util/src/util/MessageFlags.ts +++ b/src/util/util/MessageFlags.ts
@@ -15,6 +15,6 @@ export class MessageFlags extends BitField { INTERACTION_WAIT: BigInt(1) << BigInt(7), // discord.com calls this LOADING // FAILED_TO_MENTION_SOME_ROLES_IN_THREAD: BigInt(1) << BigInt(8) SCRIPT_WAIT: BigInt(1) << BigInt(24), // waiting for the self command to complete - IMPORT_WAIT: BigInt(1) << BigInt(25), // latest message of a bulk import, waiting for the rest of the channel to be backfilled + IMPORT_WAIT: BigInt(1) << BigInt(25) // latest message of a bulk import, waiting for the rest of the channel to be backfilled }; } diff --git a/util/src/util/Permissions.ts b/src/util/util/Permissions.ts
index e5459ab5..b8d0d8ae 100644 --- a/util/src/util/Permissions.ts +++ b/src/util/util/Permissions.ts
@@ -1,17 +1,8 @@ // https://github.com/discordjs/discord.js/blob/master/src/util/Permissions.js // Apache License Version 2.0 Copyright 2015 - 2021 Amish Shah +import { HTTPError } from ".."; import { Channel, ChannelPermissionOverwrite, Guild, Member, Role } from "../entities"; -import { BitField } from "./BitField"; -import "missing-native-js-functions"; -import { BitFieldResolvable, BitFlag } from "./BitField"; - -var HTTPError: any; - -try { - HTTPError = require("lambert-server").HTTPError; -} catch (e) { - HTTPError = Error; -} +import { BitField, BitFieldResolvable, BitFlag } from "./BitField"; export type PermissionResolvable = bigint | number | Permissions | PermissionResolvable[] | PermissionString; @@ -68,7 +59,7 @@ export class Permissions extends BitField { MANAGE_THREADS: BitFlag(34), USE_PUBLIC_THREADS: BitFlag(35), USE_PRIVATE_THREADS: BitFlag(36), - USE_EXTERNAL_STICKERS: BitFlag(37), + USE_EXTERNAL_STICKERS: BitFlag(37) /** * CUSTOM PERMISSIONS ideas: @@ -132,7 +123,7 @@ export class Permissions extends BitField { static finalPermission({ user, guild, - channel, + channel }: { user: { id: string; roles: string[] }; guild: { roles: Role[] }; @@ -172,7 +163,7 @@ export class Permissions extends BitField { "USE_EXTERNAL_EMOJIS", "CONNECT", "SPEAK", - "MANAGE_CHANNELS", + "MANAGE_CHANNELS" ]); } @@ -207,9 +198,9 @@ export async function getPermission( } = {} ) { if (!user_id) throw new HTTPError("User not found"); - var channel: Channel | undefined; - var member: Member | undefined; - var guild: Guild | undefined; + let channel: Channel | undefined; + let member: Member | undefined; + let guild: Guild | undefined; if (channel_id) { channel = await Channel.findOneOrFail({ @@ -222,8 +213,8 @@ export async function getPermission( "owner_id", "guild_id", // @ts-ignore - ...(opts.channel_select || []), - ], + ...(opts.channel_select || []) + ] }); if (channel.guild_id) guild_id = channel.guild_id; // derive guild_id from the channel } @@ -235,9 +226,9 @@ export async function getPermission( "id", "owner_id", // @ts-ignore - ...(opts.guild_select || []), + ...(opts.guild_select || []) ], - relations: opts.guild_relations, + relations: opts.guild_relations }); if (guild.owner_id === user_id) return new Permissions(Permissions.FLAGS.ADMINISTRATOR); @@ -247,9 +238,10 @@ export async function getPermission( select: [ "id", "roles", + "index", // @ts-ignore - ...(opts.member_select || []), - ], + ...(opts.member_select || []) + ] }); } @@ -257,19 +249,19 @@ export async function getPermission( if (!recipient_ids?.length) recipient_ids = null; // TODO: remove guild.roles and convert recipient_ids to recipients - var permission = Permissions.finalPermission({ + let permission = Permissions.finalPermission({ user: { id: user_id, - roles: member?.roles.map((x) => x.id) || [], + roles: member?.roles.map((x) => x.id) || [] }, guild: { - roles: member?.roles || [], + roles: member?.roles || [] }, channel: { overwrites: channel?.permission_overwrites, owner_id: channel?.owner_id, - recipient_ids, - }, + recipient_ids + } }); const obj = new Permissions(permission); diff --git a/util/src/util/RabbitMQ.ts b/src/util/util/RabbitMQ.ts
index 0f5eb6aa..638b805b 100644 --- a/util/src/util/RabbitMQ.ts +++ b/src/util/util/RabbitMQ.ts
@@ -1,4 +1,4 @@ -import amqp, { Connection, Channel } from "amqplib"; +import { Channel, Connection } from "amqplib"; // import Config from "./Config"; export const RabbitMQ: { connection: Connection | null; channel: Channel | null; init: () => Promise<void> } = { @@ -15,5 +15,5 @@ export const RabbitMQ: { connection: Connection | null; channel: Channel | null; // console.log(`[RabbitMQ] connected`); // this.channel = await this.connection.createChannel(); // console.log(`[RabbitMQ] channel created`); - }, + } }; diff --git a/util/src/util/Regex.ts b/src/util/util/Regex.ts
index 83fc9fe8..83fc9fe8 100644 --- a/util/src/util/Regex.ts +++ b/src/util/util/Regex.ts
diff --git a/util/src/util/Rights.ts b/src/util/util/Rights.ts
index b28c75b7..51bb098c 100644 --- a/util/src/util/Rights.ts +++ b/src/util/util/Rights.ts
@@ -1,15 +1,6 @@ -import { BitField } from "./BitField"; -import "missing-native-js-functions"; -import { BitFieldResolvable, BitFlag } from "./BitField"; +import { HTTPError } from ".."; import { User } from "../entities"; - -var HTTPError: any; - -try { - HTTPError = require("lambert-server").HTTPError; -} catch (e) { - HTTPError = Error; -} +import { BitField, BitFieldResolvable, BitFlag } from "./BitField"; export type RightResolvable = bigint | number | Rights | RightResolvable[] | RightString; @@ -60,7 +51,7 @@ export class Rights extends BitField { CREDITABLE: BitFlag(32), // can receive money from monetisation related features KICK_BAN_MEMBERS: BitFlag(33), // can kick or ban guild or group DM members in the guilds/groups that they have KICK_MEMBERS, or BAN_MEMBERS - SELF_LEAVE_GROUPS: BitFlag(34), + SELF_LEAVE_GROUPS: BitFlag(34), // can leave the guilds or group DMs that they joined on their own (one can always leave a guild or group DMs they have been force-added) PRESENCE: BitFlag(35), // inverts the presence confidentiality default (OPERATOR's presence is not routed by default, others' are) for a given user @@ -88,15 +79,16 @@ export class Rights extends BitField { // @ts-ignore throw new HTTPError(`You are missing the following rights ${permission}`, 403); } - } const ALL_RIGHTS = Object.values(Rights.FLAGS).reduce((total, val) => total | val, BigInt(0)); -export async function getRights( user_id: string +export async function getRights( + user_id: string /**, opts: { in_behalf?: (keyof User)[]; - } = {} **/) { + } = {} **/ +) { let user = await User.findOneOrFail({ where: { id: user_id } }); return new Rights(user.rights); -} +} diff --git a/util/src/util/Snowflake.ts b/src/util/util/Snowflake.ts
index 134d526e..89f4f0c3 100644 --- a/util/src/util/Snowflake.ts +++ b/src/util/util/Snowflake.ts
@@ -83,14 +83,15 @@ export class Snowflake { return dec; } - static generateWorkerProcess() { // worker process - returns a number - var time = BigInt(Date.now() - Snowflake.EPOCH) << BigInt(22); - var worker = Snowflake.workerId << 17n; - var process = Snowflake.processId << 12n; - var increment = Snowflake.INCREMENT++; + static generateWorkerProcess() { + // worker process - returns a number + let time = BigInt(Date.now() - Snowflake.EPOCH) << BigInt(22); + let worker = Snowflake.workerId << 17n; + let process = Snowflake.processId << 12n; + let increment = Snowflake.INCREMENT++; return BigInt(time | worker | process | increment); } - + static generate() { return Snowflake.generateWorkerProcess().toString(); } @@ -117,13 +118,13 @@ export class Snowflake { workerID: parseInt(BINARY.substring(42, 47), 2), processID: parseInt(BINARY.substring(47, 52), 2), increment: parseInt(BINARY.substring(52, 64), 2), - binary: BINARY, + binary: BINARY }; Object.defineProperty(res, "date", { get: function get() { return new Date(this.timestamp); }, - enumerable: true, + enumerable: true }); return res; } diff --git a/util/src/util/String.ts b/src/util/util/String.ts
index 55f11e8d..55f11e8d 100644 --- a/util/src/util/String.ts +++ b/src/util/util/String.ts
diff --git a/util/src/util/Token.ts b/src/util/util/Token.ts
index 500ace45..d192a13a 100644 --- a/util/src/util/Token.ts +++ b/src/util/util/Token.ts
@@ -1,6 +1,6 @@ import jwt, { VerifyOptions } from "jsonwebtoken"; -import { Config } from "./Config"; import { User } from "../entities"; +import { Config } from "./Config"; export const JWTOptions: VerifyOptions = { algorithms: ["HS256"] }; @@ -11,18 +11,17 @@ export function checkToken(token: string, jwtSecret: string): Promise<any> { in fosscord, even with instances that have bot distinction; we won't enforce "Bot" prefix, as we don't really have separate pathways for bots **/ - + jwt.verify(token, jwtSecret, JWTOptions, async (err, decoded: any) => { if (err || !decoded) return rej("Invalid Token"); - const user = await User.findOne( - { id: decoded.id }, - { select: ["data", "bot", "disabled", "deleted", "rights"] } - ); + const user = await User.findOne({ + where: { id: decoded.id }, + select: ["data", "bot", "disabled", "deleted", "rights"] + }); if (!user) return rej("Invalid Token"); // we need to round it to seconds as it saved as seconds in jwt iat and valid_tokens_since is stored in milliseconds - if (decoded.iat * 1000 < new Date(user.data.valid_tokens_since).setSeconds(0, 0)) - return rej("Invalid Token"); + if (decoded.iat * 1000 < new Date(user.data.valid_tokens_since).setSeconds(0, 0)) return rej("Invalid Token"); if (user.disabled) return rej("User disabled"); if (user.deleted) return rej("User not found"); @@ -40,7 +39,7 @@ export async function generateToken(id: string) { { id: id, iat }, Config.get().security.jwtSecret, { - algorithm, + algorithm }, (err, token) => { if (err) return rej(err); diff --git a/util/src/util/TraverseDirectory.ts b/src/util/util/TraverseDirectory.ts
index 3d0d6279..6f60e73b 100644 --- a/util/src/util/TraverseDirectory.ts +++ b/src/util/util/TraverseDirectory.ts
@@ -1,13 +1,10 @@ import { Server, traverseDirectory } from "lambert-server"; //if we're using ts-node, use ts files instead of js -const extension = Symbol.for("ts-node.register.instance") in process ? "ts" : "js" +const extension = Symbol.for("ts-node.register.instance") in process ? "ts" : "js"; -const DEFAULT_FILTER = new RegExp("^([^\.].*)(?<!\.d)\.(" + extension + ")$"); +const DEFAULT_FILTER = new RegExp("^([^.].*)(?<!.d).(" + extension + ")$"); export function registerRoutes(server: Server, root: string) { - return traverseDirectory( - { dirname: root, recursive: true, filter: DEFAULT_FILTER }, - server.registerRoute.bind(server, root) - ); + return traverseDirectory({ dirname: root, recursive: true, filter: DEFAULT_FILTER }, server.registerRoute.bind(server, root)); } diff --git a/util/src/util/cdn.ts b/src/util/util/cdn.ts
index ea950cd1..5573b848 100644 --- a/util/src/util/cdn.ts +++ b/src/util/util/cdn.ts
@@ -1,8 +1,7 @@ import FormData from "form-data"; -import { HTTPError } from "lambert-server"; import fetch from "node-fetch"; +import { HTTPError } from ".."; import { Config } from "./Config"; -import multer from "multer"; export async function uploadFile(path: string, file?: Express.Multer.File) { if (!file?.buffer) throw new HTTPError("Missing file in body"); @@ -10,16 +9,16 @@ export async function uploadFile(path: string, file?: Express.Multer.File) { const form = new FormData(); form.append("file", file.buffer, { contentType: file.mimetype, - filename: file.originalname, + filename: file.originalname }); const response = await fetch(`${Config.get().cdn.endpointPrivate || "http://localhost:3003"}${path}`, { headers: { signature: Config.get().security.requestSignature, - ...form.getHeaders(), + ...form.getHeaders() }, method: "POST", - body: form, + body: form }); const result = await response.json(); @@ -45,9 +44,9 @@ export async function handleFile(path: string, body?: string): Promise<string | export async function deleteFile(path: string) { const response = await fetch(`${Config.get().cdn.endpointPrivate || "http://localhost:3003"}${path}`, { headers: { - signature: Config.get().security.requestSignature, + signature: Config.get().security.requestSignature }, - method: "DELETE", + method: "DELETE" }); const result = await response.json(); diff --git a/src/util/util/imports/Checks.ts b/src/util/util/imports/Checks.ts new file mode 100644
index 00000000..bd3c6343 --- /dev/null +++ b/src/util/util/imports/Checks.ts
@@ -0,0 +1,121 @@ +//source: https://github.com/Flam3rboy/-server/blob/master/src/check.ts +import { NextFunction, Request, Response } from "express"; +import { HTTPError } from "."; + +const OPTIONAL_PREFIX = "$"; +const EMAIL_REGEX = + /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/; + +export function check(schema: any) { + return (req: Request, res: Response, next: NextFunction) => { + try { + const result = instanceOf(schema, req.body, { path: "body" }); + if (result === true) return next(); + throw result; + } catch (error) { + next(new HTTPError((error as any).toString(), 400)); + } + }; +} +export class Tuple { + public types: any[]; + constructor(...types: any[]) { + this.types = types; + } +} + +export class Email { + constructor(public email: string) {} + check() { + return !!this.email.match(EMAIL_REGEX); + } +} +export function instanceOf(type: any, value: any, { path = "", optional = false }: { path?: string; optional?: boolean } = {}): boolean { + if (!type) return true; // no type was specified + + if (value == null) { + if (optional) return true; + throw `${path} is required`; + } + + switch (type) { + case String: + if (typeof value === "string") return true; + throw `${path} must be a string`; + case Number: + value = Number(value); + if (typeof value === "number" && !isNaN(value)) return true; + throw `${path} must be a number`; + case BigInt: + try { + value = BigInt(value); + if (typeof value === "bigint") return true; + } catch (error) {} + throw `${path} must be a bigint`; + case Boolean: + if (value == "true") value = true; + if (value == "false") value = false; + if (typeof value === "boolean") return true; + throw `${path} must be a boolean`; + case Object: + if (typeof value === "object" && value !== null) return true; + throw `${path} must be a object`; + } + + if (typeof type === "object") { + if (Array.isArray(type)) { + if (!Array.isArray(value)) throw `${path} must be an array`; + if (!type.length) return true; // type array didn't specify any type + + return value.every((val, i) => instanceOf(type[0], val, { path: `${path}[${i}]`, optional })); + } + if (type?.constructor?.name != "Object") { + if (type instanceof Tuple) { + if ( + (<Tuple>type).types.some((x) => { + try { + return instanceOf(x, value, { path, optional }); + } catch (error) { + return false; + } + }) + ) { + return true; + } + throw `${path} must be one of ${type.types}`; + } + if (type instanceof Email) { + if ((<Email>type).check()) return true; + throw `${path} is not a valid E-Mail`; + } + if (value instanceof type) return true; + throw `${path} must be an instance of ${type}`; + } + if (typeof value !== "object") throw `${path} must be a object`; + + const diff = Object.keys(value).missing( + Object.keys(type).map((x) => (x.startsWith(OPTIONAL_PREFIX) ? x.slice(OPTIONAL_PREFIX.length) : x)) + ); + + if (diff.length) throw `Unkown key ${diff}`; + + return Object.keys(type).every((key) => { + let newKey = key; + const OPTIONAL = key.startsWith(OPTIONAL_PREFIX); + if (OPTIONAL) newKey = newKey.slice(OPTIONAL_PREFIX.length); + + return instanceOf(type[key], value[newKey], { + path: `${path}.${newKey}`, + optional: OPTIONAL + }); + }); + } else if (typeof type === "number" || typeof type === "string" || typeof type === "boolean") { + if (value === type) return true; + throw `${path} must be ${value}`; + } else if (typeof type === "bigint") { + if (BigInt(value) === type) return true; + throw `${path} must be ${value}`; + } + + return type == value; +} diff --git a/src/util/util/imports/HTTPError.ts b/src/util/util/imports/HTTPError.ts new file mode 100644
index 00000000..70ba92a8 --- /dev/null +++ b/src/util/util/imports/HTTPError.ts
@@ -0,0 +1,5 @@ +export class HTTPError extends Error { + constructor(message: string, public code: number = 400) { + super(message); + } +} diff --git a/src/util/util/imports/OrmUtils.ts b/src/util/util/imports/OrmUtils.ts new file mode 100644
index 00000000..0556994b --- /dev/null +++ b/src/util/util/imports/OrmUtils.ts
@@ -0,0 +1,96 @@ +//source: https://github.com/typeorm/typeorm/blob/master/src/util/OrmUtils.ts +export class OrmUtils { + // Checks if it's an object made by Object.create(null), {} or new Object() + private static isPlainObject(item: any) { + if (item === null || item === undefined) { + return false; + } + + return !item.constructor || item.constructor === Object; + } + + private static mergeArrayKey(target: any, key: number, value: any, memo: Map<any, any>) { + // Have we seen this before? Prevent infinite recursion. + if (memo.has(value)) { + target[key] = memo.get(value); + return; + } + + if (value instanceof Promise) { + // Skip promises entirely. + // This is a hold-over from the old code & is because we don't want to pull in + // the lazy fields. Ideally we'd remove these promises via another function first + // but for now we have to do it here. + return; + } + + if (!this.isPlainObject(value) && !Array.isArray(value)) { + target[key] = value; + return; + } + + if (!target[key]) { + target[key] = Array.isArray(value) ? [] : {}; + } + + memo.set(value, target[key]); + this.merge(target[key], value, memo); + memo.delete(value); + } + + private static mergeObjectKey(target: any, key: string, value: any, memo: Map<any, any>) { + // Have we seen this before? Prevent infinite recursion. + if (memo.has(value)) { + Object.assign(target, { [key]: memo.get(value) }); + return; + } + + if (value instanceof Promise) { + // Skip promises entirely. + // This is a hold-over from the old code & is because we don't want to pull in + // the lazy fields. Ideally we'd remove these promises via another function first + // but for now we have to do it here. + return; + } + + if (!this.isPlainObject(value) && !Array.isArray(value)) { + Object.assign(target, { [key]: value }); + return; + } + + if (!target[key]) { + Object.assign(target, { [key]: value }); + } + + memo.set(value, target[key]); + this.merge(target[key], value, memo); + memo.delete(value); + } + + private static merge(target: any, source: any, memo: Map<any, any> = new Map()): any { + if (Array.isArray(target) && Array.isArray(source)) { + for (let key = 0; key < source.length; key++) { + this.mergeArrayKey(target, key, source[key], memo); + } + } else { + for (const key of Object.keys(source)) { + this.mergeObjectKey(target, key, source[key], memo); + } + } + } + + /** + * Deep Object.assign. + */ + static mergeDeep(target: any, ...sources: any[]): any { + if (!sources.length) { + return target; + } + + for (const source of sources) { + OrmUtils.merge(target, source); + } + + return target; + } +} diff --git a/src/util/util/imports/index.ts b/src/util/util/imports/index.ts new file mode 100644
index 00000000..120cff11 --- /dev/null +++ b/src/util/util/imports/index.ts
@@ -0,0 +1,3 @@ +export * from "./Checks"; +export * from "./HTTPError"; +export * from "./OrmUtils"; diff --git a/util/src/util/index.ts b/src/util/util/index.ts
index f7a273cb..11f0b72a 100644 --- a/util/src/util/index.ts +++ b/src/util/util/index.ts
@@ -1,6 +1,6 @@ export * from "./ApiError"; +export * from "./Array"; export * from "./BitField"; -export * from "./Token"; //export * from "./Categories"; export * from "./cdn"; export * from "./Config"; @@ -9,7 +9,11 @@ export * from "./Database"; export * from "./Email"; export * from "./Event"; export * from "./FieldError"; +export * from "./imports/HTTPError"; +export * from "./imports/index"; +export * from "./imports/OrmUtils"; export * from "./Intents"; +export * from "./InvisibleCharacters"; export * from "./MessageFlags"; export * from "./Permissions"; export * from "./RabbitMQ"; @@ -17,6 +21,5 @@ export * from "./Regex"; export * from "./Rights"; export * from "./Snowflake"; export * from "./String"; -export * from "./Array"; +export * from "./Token"; export * from "./TraverseDirectory"; -export * from "./InvisibleCharacters"; \ No newline at end of file