// https://github.com/typeorm/typeorm/issues/2400 import pg from "pg"; pg.types.setTypeParser(20, Number); import { DataSource } from "typeorm"; import * as highlight from "cli-highlight"; import config from "../config/index.js"; import { User } from "../models/entities/user.js"; import { DriveFile } from "../models/entities/drive-file.js"; import { DriveFolder } from "../models/entities/drive-folder.js"; import { AccessToken } from "../models/entities/access-token.js"; import { App } from "../models/entities/app.js"; import { PollVote } from "../models/entities/poll-vote.js"; import { Note } from "../models/entities/note.js"; import { NoteReaction } from "../models/entities/note-reaction.js"; import { NoteWatching } from "../models/entities/note-watching.js"; import { NoteThreadMuting } from "../models/entities/note-thread-muting.js"; import { NoteUnread } from "../models/entities/note-unread.js"; import { Notification } from "../models/entities/notification.js"; import { Meta } from "../models/entities/meta.js"; import { Following } from "../models/entities/following.js"; import { Instance } from "../models/entities/instance.js"; import { Muting } from "../models/entities/muting.js"; import { RenoteMuting } from "../models/entities/renote-muting.js"; import { SwSubscription } from "../models/entities/sw-subscription.js"; import { Blocking } from "../models/entities/blocking.js"; import { CallBlocking } from "../models/entities/call-blocking.js"; import { UserList } from "../models/entities/user-list.js"; import { UserListJoining } from "../models/entities/user-list-joining.js"; import { UserGroup } from "../models/entities/user-group.js"; import { UserGroupJoining } from "../models/entities/user-group-joining.js"; import { UserGroupInvitation } from "../models/entities/user-group-invitation.js"; import { Hashtag } from "../models/entities/hashtag.js"; import { NoteFavorite } from "../models/entities/note-favorite.js"; import { AbuseUserReport } from "../models/entities/abuse-user-report.js"; import { RegistrationTicket } from "../models/entities/registration-tickets.js"; import { MessagingMessage } from "../models/entities/messaging-message.js"; import { Signin } from "../models/entities/signin.js"; import { AuthSession } from "../models/entities/auth-session.js"; import { FollowRequest } from "../models/entities/follow-request.js"; import { Emoji } from "../models/entities/emoji.js"; import { UserNotePining } from "../models/entities/user-note-pining.js"; import { Poll } from "../models/entities/poll.js"; import { UserKeypair } from "../models/entities/user-keypair.js"; import { UserPublickey } from "../models/entities/user-publickey.js"; import { UserProfile } from "../models/entities/user-profile.js"; import { UserSecurityKey } from "../models/entities/user-security-key.js"; import { AttestationChallenge } from "../models/entities/attestation-challenge.js"; import { Page } from "../models/entities/page.js"; import { PageLike } from "../models/entities/page-like.js"; import { GalleryPost } from "../models/entities/gallery-post.js"; import { GalleryLike } from "../models/entities/gallery-like.js"; import { ModerationLog } from "../models/entities/moderation-log.js"; import { UsedUsername } from "../models/entities/used-username.js"; import { Announcement } from "../models/entities/announcement.js"; import { AnnouncementRead } from "../models/entities/announcement-read.js"; import { Clip } from "../models/entities/clip.js"; import { ClipNote } from "../models/entities/clip-note.js"; import { Antenna } from "../models/entities/antenna.js"; import { PromoNote } from "../models/entities/promo-note.js"; import { PromoRead } from "../models/entities/promo-read.js"; import { Relay } from "../models/entities/relay.js"; import { Channel } from "../models/entities/channel.js"; import { ChannelFollowing } from "../models/entities/channel-following.js"; import { ChannelNotePining } from "../models/entities/channel-note-pining.js"; import { RegistryItem } from "../models/entities/registry-item.js"; import { PasswordResetRequest } from "../models/entities/password-reset-request.js"; import { UserPending } from "../models/entities/user-pending.js"; import { Webhook } from "../models/entities/webhook.js"; import { UserIp } from "../models/entities/user-ip.js"; import { UserEmoji } from "../models/entities/user-emoji.js"; import { NoteEdit } from "../models/entities/note-edit.js"; import { entities as charts } from "../services/chart/entities.js"; import { dbLogger } from "./logger.js"; import { OAuthApp } from "../models/entities/oauth-app.js"; import { OAuthToken } from "../models/entities/oauth-token.js"; import { HtmlNoteCacheEntry } from "../models/entities/html-note-cache-entry.js"; import { HtmlUserCacheEntry } from "../models/entities/html-user-cache-entry.js"; import { Bite } from "../models/entities/bite.js"; import { InteractionStamp } from "../models/entities/interaction-stamp.js"; import { ReversiGame } from "../models/entities/reversi-game.js"; import { ReversiMatching } from "../models/entities/reversi-matching.js"; import { ShogiGame } from "../models/entities/shogi-game.js"; import { ScheduledNote } from "../models/entities/scheduled-note.js"; import { Memoriet } from "../models/entities/memoriet.js"; import { MemorietArchive } from "../models/entities/memoriet-archive.js"; import { MemorietView } from "../models/entities/memoriet-view.js"; import { VerifiedBadgeRequest } from "../models/entities/verified-badge-request.js"; import { Plan } from "../models/entities/plan.js"; import { UserPlan } from "../models/entities/user-plan.js"; const sqlLogger = dbLogger.createSubLogger("sql", "gray", false); const isLogEnabled = (level)=>{ const logLevel = config.db.logging; return Array.isArray(logLevel) ? logLevel.includes(level) : logLevel === level || logLevel?.trim()?.toLowerCase() === "all"; }; const isLoggingEnabled = ()=>{ const logLevel = config.db.logging; return Array.isArray(logLevel) ? logLevel.length > 0 : logLevel != null; }; class MyCustomLogger { highlight(sql) { return highlight.highlight(sql, { language: "sql", ignoreIllegals: true }); } logQuery(query, parameters) { if (isLogEnabled("query")) sqlLogger.info(this.highlight(query).substring(0, 100)); } logQueryError(error, query, parameters) { if (isLogEnabled("error")) sqlLogger.error(this.highlight(query)); } logQuerySlow(time, query, parameters) { if (isLogEnabled("slow")) sqlLogger.warn(this.highlight(query)); } logSchemaBuild(message) { if (isLogEnabled("schema")) sqlLogger.info(message); } log(message) { if (isLogEnabled("log")) sqlLogger.info(message); } logMigration(message) { if (isLogEnabled("info")) sqlLogger.info(message); } } export const entities = [ Announcement, AnnouncementRead, Meta, Instance, App, AuthSession, AccessToken, User, UserProfile, UserKeypair, UserPublickey, UserList, UserListJoining, UserGroup, UserGroupJoining, UserGroupInvitation, UserNotePining, UserSecurityKey, UsedUsername, AttestationChallenge, Following, FollowRequest, Muting, RenoteMuting, Blocking, CallBlocking, Note, NoteEdit, NoteFavorite, NoteReaction, NoteWatching, NoteThreadMuting, NoteUnread, Page, PageLike, GalleryPost, GalleryLike, DriveFile, DriveFolder, Poll, PollVote, Notification, Emoji, Hashtag, SwSubscription, AbuseUserReport, RegistrationTicket, MessagingMessage, Signin, ModerationLog, Clip, ClipNote, Antenna, PromoNote, PromoRead, Relay, Channel, ChannelFollowing, ChannelNotePining, RegistryItem, PasswordResetRequest, UserPending, Webhook, UserIp, UserEmoji, OAuthApp, OAuthToken, HtmlNoteCacheEntry, HtmlUserCacheEntry, Bite, InteractionStamp, ReversiGame, ReversiMatching, ShogiGame, ScheduledNote, Memoriet, MemorietArchive, MemorietView, VerifiedBadgeRequest, Plan, UserPlan, ...charts ]; const log = isLoggingEnabled() || process.env.LOG_SQL === "true"; export const db = new DataSource({ type: "postgres", host: config.db.host, port: config.db.port, username: config.db.user, password: config.db.pass, database: config.db.db, extra: { statement_timeout: 1000 * 10, ...config.db.extra }, synchronize: process.env.NODE_ENV === "test", dropSchema: process.env.NODE_ENV === "test", cache: !config.db.disableCache ? { type: "ioredis", options: { host: config.redis.host, port: config.redis.port, family: config.redis.family == null ? 0 : config.redis.family, username: config.redis.user ?? "default", password: config.redis.pass, keyPrefix: `${config.redis.prefix}:query:`, db: config.redis.db || 0, tls: config.redis.tls } } : false, logging: log, logger: new MyCustomLogger(), maxQueryExecutionTime: 300, entities: entities, migrations: [ "../../migration/*.js" ] }); export async function initDb(force = false) { if (force) { if (db.isInitialized) { await db.destroy(); } await db.initialize(); return; } if (db.isInitialized) { // nop } else { await db.initialize(); } } export async function resetDb() { const reset = async ()=>{ const { redisClient } = await import("./redis.js"); await redisClient.flushdb(); const tables = await db.query(`SELECT relname AS "table" FROM pg_class C LEFT JOIN pg_namespace N ON (N.oid = C.relnamespace) WHERE nspname NOT IN ('pg_catalog', 'information_schema') AND C.relkind = 'r' AND nspname !~ '^pg_toast';`); for (const table of tables){ await db.query(`DELETE FROM "${table.table}" CASCADE`); } }; for(let i = 1; i <= 3; i++){ try { await reset(); } catch (e) { if (i === 3) { throw e; } else { await new Promise((resolve)=>setTimeout(resolve, 1000)); continue; } } break; } }