chore: import upstream snapshot with attribution
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,36 @@
|
||||
import 'dotenv/config';
|
||||
import Database from 'better-sqlite3';
|
||||
import { sql } from 'drizzle-orm';
|
||||
import { drizzle } from 'drizzle-orm/better-sqlite3';
|
||||
import util from 'node:util';
|
||||
import * as schema from './tables.ts';
|
||||
|
||||
async function main() {
|
||||
const bdb = new Database(process.env['SQLITE_DB_PATH']!);
|
||||
const db = drizzle(bdb, { schema, logger: true });
|
||||
|
||||
const result = db.query.users.findMany({
|
||||
columns: {
|
||||
id: true,
|
||||
name: true,
|
||||
},
|
||||
with: {
|
||||
posts: {
|
||||
columns: {
|
||||
authorId: true,
|
||||
},
|
||||
with: {
|
||||
comments: true,
|
||||
},
|
||||
extras: {
|
||||
lower: sql<string>`lower(${schema.posts.title})`.as('lower_name'),
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
console.log(util.inspect(result, false, null, true));
|
||||
bdb.close();
|
||||
}
|
||||
|
||||
main();
|
||||
+274
@@ -0,0 +1,274 @@
|
||||
import 'dotenv/config';
|
||||
import Docker from 'dockerode';
|
||||
import { sql } from 'drizzle-orm';
|
||||
import { drizzle, type MySql2Database } from 'drizzle-orm/mysql2';
|
||||
import getPort from 'get-port';
|
||||
import * as mysql from 'mysql2/promise';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
import { afterAll, beforeAll, beforeEach, expect, expectTypeOf, test } from 'vitest';
|
||||
import * as schema from './mysql.duplicates.ts';
|
||||
|
||||
const ENABLE_LOGGING = false;
|
||||
|
||||
/*
|
||||
Test cases:
|
||||
- querying nested relation without PK with additional fields
|
||||
*/
|
||||
|
||||
let mysqlContainer: Docker.Container;
|
||||
let db: MySql2Database<typeof schema>;
|
||||
let client: mysql.Connection;
|
||||
|
||||
async function createDockerDB(): Promise<string> {
|
||||
const docker = new Docker();
|
||||
const port = await getPort({ port: 3306 });
|
||||
const image = 'mysql:8';
|
||||
|
||||
const pullStream = await docker.pull(image);
|
||||
await new Promise((resolve, reject) =>
|
||||
docker.modem.followProgress(pullStream, (err) => (err ? reject(err) : resolve(err)))
|
||||
);
|
||||
|
||||
mysqlContainer = await docker.createContainer({
|
||||
Image: image,
|
||||
Env: ['MYSQL_ROOT_PASSWORD=mysql', 'MYSQL_DATABASE=drizzle'],
|
||||
name: `drizzle-integration-tests-${uuid()}`,
|
||||
HostConfig: {
|
||||
AutoRemove: true,
|
||||
PortBindings: {
|
||||
'3306/tcp': [{ HostPort: `${port}` }],
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
await mysqlContainer.start();
|
||||
|
||||
return `mysql://root:mysql@127.0.0.1:${port}/drizzle`;
|
||||
}
|
||||
|
||||
beforeAll(async () => {
|
||||
const connectionString = process.env['MYSQL_CONNECTION_STRING'] ?? await createDockerDB();
|
||||
|
||||
const sleep = 1000;
|
||||
let timeLeft = 30000;
|
||||
let connected = false;
|
||||
let lastError: unknown | undefined;
|
||||
do {
|
||||
try {
|
||||
client = await mysql.createConnection(connectionString);
|
||||
await client.connect();
|
||||
connected = true;
|
||||
break;
|
||||
} catch (e) {
|
||||
lastError = e;
|
||||
await new Promise((resolve) => setTimeout(resolve, sleep));
|
||||
timeLeft -= sleep;
|
||||
}
|
||||
} while (timeLeft > 0);
|
||||
if (!connected) {
|
||||
console.error('Cannot connect to MySQL');
|
||||
await client?.end().catch(console.error);
|
||||
await mysqlContainer?.stop().catch(console.error);
|
||||
throw lastError;
|
||||
}
|
||||
db = drizzle(client, { schema, logger: ENABLE_LOGGING, mode: 'default' });
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await client?.end().catch(console.error);
|
||||
await mysqlContainer?.stop().catch(console.error);
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
await db.execute(sql`drop table if exists \`members\``);
|
||||
await db.execute(sql`drop table if exists \`artist_to_member\``);
|
||||
await db.execute(sql`drop table if exists \`artists\``);
|
||||
await db.execute(sql`drop table if exists \`albums\``);
|
||||
|
||||
await db.execute(
|
||||
sql`
|
||||
CREATE TABLE \`members\` (
|
||||
\`id\` serial AUTO_INCREMENT PRIMARY KEY NOT NULL,
|
||||
\`created_at\` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
\`updated_at\` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
\`name_en\` varchar(50) NOT NULL,
|
||||
\`name_kr\` varchar(50) NOT NULL,
|
||||
\`stage_name_en\` varchar(50) NOT NULL,
|
||||
\`stage_name_kr\` varchar(50) NOT NULL,
|
||||
\`image\` varchar(255) NOT NULL,
|
||||
\`instagram\` varchar(255) NOT NULL);
|
||||
`,
|
||||
);
|
||||
await db.execute(
|
||||
sql`
|
||||
CREATE TABLE \`artist_to_member\` (
|
||||
\`id\` serial AUTO_INCREMENT PRIMARY KEY NOT NULL,
|
||||
\`member_id\` int NOT NULL,
|
||||
\`artist_id\` int NOT NULL);
|
||||
`,
|
||||
);
|
||||
await db.execute(
|
||||
sql`
|
||||
CREATE TABLE \`artists\` (
|
||||
\`id\` serial AUTO_INCREMENT PRIMARY KEY NOT NULL,
|
||||
\`created_at\` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
\`updated_at\` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
\`name_en\` varchar(50) NOT NULL,
|
||||
\`name_kr\` varchar(50) NOT NULL,
|
||||
\`debut\` date NOT NULL,
|
||||
\`company_id\` int NOT NULL,
|
||||
\`is_group\` boolean NOT NULL DEFAULT true,
|
||||
\`image\` varchar(255) NOT NULL,
|
||||
\`twitter\` varchar(255) NOT NULL,
|
||||
\`instagram\` varchar(255) NOT NULL,
|
||||
\`youtube\` varchar(255) NOT NULL,
|
||||
\`website\` varchar(255) NOT NULL,
|
||||
\`spotify_id\` varchar(32));
|
||||
`,
|
||||
);
|
||||
await db.execute(
|
||||
sql`
|
||||
CREATE TABLE \`albums\` (
|
||||
\`id\` serial AUTO_INCREMENT PRIMARY KEY NOT NULL,
|
||||
\`created_at\` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
\`updated_at\` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
\`artist_id\` int NOT NULL,
|
||||
\`name\` varchar(50) NOT NULL,
|
||||
\`region\` enum('en','kr','jp','other') NOT NULL,
|
||||
\`release_date\` date NOT NULL,
|
||||
\`image\` varchar(255) NOT NULL,
|
||||
\`spotify_id\` varchar(32));
|
||||
`,
|
||||
);
|
||||
});
|
||||
|
||||
test('Simple case from GH', async () => {
|
||||
await db.insert(schema.artists).values([
|
||||
{
|
||||
id: 1,
|
||||
nameEn: 'Dan',
|
||||
nameKr: '',
|
||||
debut: new Date(),
|
||||
companyId: 1,
|
||||
image: '',
|
||||
twitter: '',
|
||||
instagram: '',
|
||||
youtube: '',
|
||||
website: '',
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
nameEn: 'Andrew',
|
||||
nameKr: '',
|
||||
debut: new Date(),
|
||||
companyId: 1,
|
||||
image: '',
|
||||
twitter: '',
|
||||
instagram: '',
|
||||
youtube: '',
|
||||
website: '',
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
nameEn: 'Alex',
|
||||
nameKr: '',
|
||||
debut: new Date(),
|
||||
companyId: 1,
|
||||
image: '',
|
||||
twitter: '',
|
||||
instagram: '',
|
||||
youtube: '',
|
||||
website: '',
|
||||
},
|
||||
]);
|
||||
|
||||
await db.insert(schema.albums).values([
|
||||
{ id: 1, artistId: 1, name: 'Album1', region: 'en', releaseDate: new Date(), image: '' },
|
||||
{ id: 2, artistId: 2, name: 'Album2', region: 'en', releaseDate: new Date(), image: '' },
|
||||
{ id: 3, artistId: 3, name: 'Album3', region: 'en', releaseDate: new Date(), image: '' },
|
||||
]);
|
||||
|
||||
await db.insert(schema.members).values([
|
||||
{ id: 1, nameEn: 'MemberA', nameKr: '', stageNameEn: '', stageNameKr: '', image: '', instagram: '' },
|
||||
{ id: 2, nameEn: 'MemberB', nameKr: '', stageNameEn: '', stageNameKr: '', image: '', instagram: '' },
|
||||
{ id: 3, nameEn: 'MemberC', nameKr: '', stageNameEn: '', stageNameKr: '', image: '', instagram: '' },
|
||||
]);
|
||||
|
||||
await db.insert(schema.artistsToMembers).values([
|
||||
{ memberId: 1, artistId: 1 },
|
||||
{ memberId: 2, artistId: 1 },
|
||||
{ memberId: 2, artistId: 2 },
|
||||
{ memberId: 3, artistId: 3 },
|
||||
]);
|
||||
|
||||
const response = await db.query.artists.findFirst({
|
||||
where: (artists, { eq }) => eq(artists.id, 1),
|
||||
with: {
|
||||
albums: true,
|
||||
members: {
|
||||
columns: {},
|
||||
with: {
|
||||
member: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expectTypeOf(response).toEqualTypeOf<
|
||||
{
|
||||
id: number;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
nameEn: string;
|
||||
nameKr: string;
|
||||
debut: Date;
|
||||
companyId: number;
|
||||
isGroup: boolean;
|
||||
image: string;
|
||||
twitter: string;
|
||||
instagram: string;
|
||||
youtube: string;
|
||||
website: string;
|
||||
spotifyId: string | null;
|
||||
members: {
|
||||
member: {
|
||||
id: number;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
nameEn: string;
|
||||
nameKr: string;
|
||||
image: string;
|
||||
instagram: string;
|
||||
stageNameEn: string;
|
||||
stageNameKr: string;
|
||||
};
|
||||
}[];
|
||||
albums: {
|
||||
id: number;
|
||||
name: string;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
image: string;
|
||||
spotifyId: string | null;
|
||||
artistId: number;
|
||||
region: 'en' | 'kr' | 'jp' | 'other';
|
||||
releaseDate: Date;
|
||||
}[];
|
||||
} | undefined
|
||||
>();
|
||||
|
||||
expect(response?.members.length).eq(2);
|
||||
expect(response?.albums.length).eq(1);
|
||||
|
||||
expect(response?.albums[0]).toEqual({
|
||||
id: 1,
|
||||
createdAt: response?.albums[0]?.createdAt,
|
||||
updatedAt: response?.albums[0]?.updatedAt,
|
||||
artistId: 1,
|
||||
name: 'Album1',
|
||||
region: 'en',
|
||||
releaseDate: response?.albums[0]?.releaseDate,
|
||||
image: '',
|
||||
spotifyId: null,
|
||||
});
|
||||
});
|
||||
+111
@@ -0,0 +1,111 @@
|
||||
import { relations, sql } from 'drizzle-orm';
|
||||
import { boolean, date, index, int, mysqlEnum, mysqlTable, serial, timestamp, varchar } from 'drizzle-orm/mysql-core';
|
||||
|
||||
export const artists = mysqlTable(
|
||||
'artists',
|
||||
{
|
||||
id: serial('id').primaryKey(),
|
||||
createdAt: timestamp('created_at')
|
||||
.notNull()
|
||||
.default(sql`CURRENT_TIMESTAMP`),
|
||||
updatedAt: timestamp('updated_at')
|
||||
.notNull()
|
||||
.default(sql`CURRENT_TIMESTAMP`),
|
||||
nameEn: varchar('name_en', { length: 50 }).notNull(),
|
||||
nameKr: varchar('name_kr', { length: 50 }).notNull(),
|
||||
debut: date('debut').notNull(),
|
||||
companyId: int('company_id').notNull(),
|
||||
isGroup: boolean('is_group').notNull().default(true),
|
||||
image: varchar('image', { length: 255 }).notNull(),
|
||||
twitter: varchar('twitter', { length: 255 }).notNull(),
|
||||
instagram: varchar('instagram', { length: 255 }).notNull(),
|
||||
youtube: varchar('youtube', { length: 255 }).notNull(),
|
||||
website: varchar('website', { length: 255 }).notNull(),
|
||||
spotifyId: varchar('spotify_id', { length: 32 }),
|
||||
},
|
||||
(table) => ({
|
||||
nameEnIndex: index('artists__name_en__idx').on(table.nameEn),
|
||||
}),
|
||||
);
|
||||
|
||||
export const members = mysqlTable('members', {
|
||||
id: serial('id').primaryKey(),
|
||||
createdAt: timestamp('created_at')
|
||||
.notNull()
|
||||
.default(sql`CURRENT_TIMESTAMP`),
|
||||
updatedAt: timestamp('updated_at')
|
||||
.notNull()
|
||||
.default(sql`CURRENT_TIMESTAMP`),
|
||||
nameEn: varchar('name_en', { length: 50 }).notNull(),
|
||||
nameKr: varchar('name_kr', { length: 50 }).notNull(),
|
||||
stageNameEn: varchar('stage_name_en', { length: 50 }).notNull(),
|
||||
stageNameKr: varchar('stage_name_kr', { length: 50 }).notNull(),
|
||||
image: varchar('image', { length: 255 }).notNull(),
|
||||
instagram: varchar('instagram', { length: 255 }).notNull(),
|
||||
});
|
||||
|
||||
export const artistsToMembers = mysqlTable(
|
||||
'artist_to_member',
|
||||
{
|
||||
id: serial('id').primaryKey(),
|
||||
memberId: int('member_id').notNull(),
|
||||
artistId: int('artist_id').notNull(),
|
||||
},
|
||||
(table) => ({
|
||||
memberArtistIndex: index('artist_to_member__artist_id__member_id__idx').on(
|
||||
table.memberId,
|
||||
table.artistId,
|
||||
),
|
||||
}),
|
||||
);
|
||||
|
||||
export const albums = mysqlTable(
|
||||
'albums',
|
||||
{
|
||||
id: serial('id').primaryKey(),
|
||||
createdAt: timestamp('created_at')
|
||||
.notNull()
|
||||
.default(sql`CURRENT_TIMESTAMP`),
|
||||
updatedAt: timestamp('updated_at')
|
||||
.notNull()
|
||||
.default(sql`CURRENT_TIMESTAMP`),
|
||||
artistId: int('artist_id').notNull(),
|
||||
name: varchar('name', { length: 50 }).notNull(),
|
||||
region: mysqlEnum('region', ['en', 'kr', 'jp', 'other']).notNull(),
|
||||
releaseDate: date('release_date').notNull(),
|
||||
image: varchar('image', { length: 255 }).notNull(),
|
||||
spotifyId: varchar('spotify_id', { length: 32 }),
|
||||
},
|
||||
(table) => ({
|
||||
artistIndex: index('albums__artist_id__idx').on(table.artistId),
|
||||
nameIndex: index('albums__name__idx').on(table.name),
|
||||
}),
|
||||
);
|
||||
|
||||
// relations
|
||||
export const artistRelations = relations(artists, ({ many }) => ({
|
||||
albums: many(albums),
|
||||
members: many(artistsToMembers),
|
||||
}));
|
||||
|
||||
export const albumRelations = relations(albums, ({ one }) => ({
|
||||
artist: one(artists, {
|
||||
fields: [albums.artistId],
|
||||
references: [artists.id],
|
||||
}),
|
||||
}));
|
||||
|
||||
export const memberRelations = relations(members, ({ many }) => ({
|
||||
artists: many(artistsToMembers),
|
||||
}));
|
||||
|
||||
export const artistsToMembersRelations = relations(artistsToMembers, ({ one }) => ({
|
||||
artist: one(artists, {
|
||||
fields: [artistsToMembers.artistId],
|
||||
references: [artists.id],
|
||||
}),
|
||||
member: one(members, {
|
||||
fields: [artistsToMembers.memberId],
|
||||
references: [members.id],
|
||||
}),
|
||||
}));
|
||||
+210
@@ -0,0 +1,210 @@
|
||||
import 'dotenv/config';
|
||||
import Docker from 'dockerode';
|
||||
import { sql } from 'drizzle-orm';
|
||||
import { drizzle, type NodePgDatabase } from 'drizzle-orm/node-postgres';
|
||||
import getPort from 'get-port';
|
||||
import pg from 'pg';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
import { afterAll, beforeAll, beforeEach, expect, expectTypeOf, test } from 'vitest';
|
||||
import * as schema from './pg.duplicates.ts';
|
||||
|
||||
const { Client } = pg;
|
||||
|
||||
const ENABLE_LOGGING = false;
|
||||
|
||||
/*
|
||||
Test cases:
|
||||
- querying nested relation without PK with additional fields
|
||||
*/
|
||||
|
||||
let pgContainer: Docker.Container;
|
||||
let db: NodePgDatabase<typeof schema>;
|
||||
let client: pg.Client;
|
||||
|
||||
async function createDockerDB(): Promise<string> {
|
||||
const docker = new Docker();
|
||||
const port = await getPort({ port: 5432 });
|
||||
const image = 'postgres:14';
|
||||
|
||||
const pullStream = await docker.pull(image);
|
||||
await new Promise((resolve, reject) =>
|
||||
docker.modem.followProgress(pullStream, (err) => err ? reject(err) : resolve(err))
|
||||
);
|
||||
|
||||
pgContainer = await docker.createContainer({
|
||||
Image: image,
|
||||
Env: [
|
||||
'POSTGRES_PASSWORD=postgres',
|
||||
'POSTGRES_USER=postgres',
|
||||
'POSTGRES_DB=postgres',
|
||||
],
|
||||
name: `drizzle-integration-tests-${uuid()}`,
|
||||
HostConfig: {
|
||||
AutoRemove: true,
|
||||
PortBindings: {
|
||||
'5432/tcp': [{ HostPort: `${port}` }],
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
await pgContainer.start();
|
||||
|
||||
return `postgres://postgres:postgres@localhost:${port}/postgres`;
|
||||
}
|
||||
|
||||
beforeAll(async () => {
|
||||
const connectionString = process.env['PG_CONNECTION_STRING'] ?? (await createDockerDB());
|
||||
|
||||
const sleep = 250;
|
||||
let timeLeft = 5000;
|
||||
let connected = false;
|
||||
let lastError: unknown | undefined;
|
||||
do {
|
||||
try {
|
||||
client = new Client(connectionString);
|
||||
await client.connect();
|
||||
connected = true;
|
||||
break;
|
||||
} catch (e) {
|
||||
lastError = e;
|
||||
await new Promise((resolve) => setTimeout(resolve, sleep));
|
||||
timeLeft -= sleep;
|
||||
}
|
||||
} while (timeLeft > 0);
|
||||
if (!connected) {
|
||||
console.error('Cannot connect to Postgres');
|
||||
await client?.end().catch(console.error);
|
||||
await pgContainer?.stop().catch(console.error);
|
||||
throw lastError;
|
||||
}
|
||||
db = drizzle(client, { schema, logger: ENABLE_LOGGING });
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await client?.end().catch(console.error);
|
||||
await pgContainer?.stop().catch(console.error);
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
await db.execute(sql`drop table if exists "members"`);
|
||||
await db.execute(sql`drop table if exists "artist_to_member"`);
|
||||
await db.execute(sql`drop table if exists "artists"`);
|
||||
await db.execute(sql`drop table if exists "albums"`);
|
||||
|
||||
await db.execute(
|
||||
sql`
|
||||
CREATE TABLE "members" (
|
||||
"id" serial PRIMARY KEY NOT NULL,
|
||||
"created_at" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updated_at" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
`,
|
||||
);
|
||||
await db.execute(
|
||||
sql`
|
||||
CREATE TABLE "artist_to_member" (
|
||||
"id" serial PRIMARY KEY NOT NULL,
|
||||
"member_id" int NOT NULL,
|
||||
"artist_id" int NOT NULL);
|
||||
`,
|
||||
);
|
||||
await db.execute(
|
||||
sql`
|
||||
CREATE TABLE "artists" (
|
||||
"id" serial PRIMARY KEY NOT NULL,
|
||||
"created_at" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updated_at" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"company_id" int NOT NULL);
|
||||
`,
|
||||
);
|
||||
await db.execute(
|
||||
sql`
|
||||
CREATE TABLE "albums" (
|
||||
"id" serial PRIMARY KEY NOT NULL,
|
||||
"created_at" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updated_at" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"artist_id" int NOT NULL);
|
||||
`,
|
||||
);
|
||||
});
|
||||
|
||||
test('Simple case from GH', async () => {
|
||||
await db.insert(schema.artists).values([
|
||||
{
|
||||
id: 1,
|
||||
companyId: 1,
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
companyId: 1,
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
companyId: 1,
|
||||
},
|
||||
]);
|
||||
|
||||
await db.insert(schema.albums).values([
|
||||
{ id: 1, artistId: 1 },
|
||||
{ id: 2, artistId: 2 },
|
||||
{ id: 3, artistId: 3 },
|
||||
]);
|
||||
|
||||
await db.insert(schema.members).values([
|
||||
{ id: 1 },
|
||||
{ id: 2 },
|
||||
{ id: 3 },
|
||||
]);
|
||||
|
||||
await db.insert(schema.artistsToMembers).values([
|
||||
{ memberId: 1, artistId: 1 },
|
||||
{ memberId: 2, artistId: 1 },
|
||||
{ memberId: 2, artistId: 2 },
|
||||
{ memberId: 3, artistId: 3 },
|
||||
]);
|
||||
|
||||
const response = await db.query.artists.findFirst({
|
||||
where: (artists, { eq }) => eq(artists.id, 1),
|
||||
with: {
|
||||
albums: true,
|
||||
members: {
|
||||
columns: {},
|
||||
with: {
|
||||
member: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expectTypeOf(response).toEqualTypeOf<
|
||||
{
|
||||
id: number;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
companyId: number;
|
||||
albums: {
|
||||
id: number;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
artistId: number;
|
||||
}[];
|
||||
members: {
|
||||
member: {
|
||||
id: number;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
};
|
||||
}[];
|
||||
} | undefined
|
||||
>();
|
||||
|
||||
expect(response?.members.length).eq(2);
|
||||
expect(response?.albums.length).eq(1);
|
||||
|
||||
expect(response?.albums[0]).toEqual({
|
||||
id: 1,
|
||||
createdAt: response?.albums[0]?.createdAt,
|
||||
updatedAt: response?.albums[0]?.updatedAt,
|
||||
artistId: 1,
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,86 @@
|
||||
import { relations, sql } from 'drizzle-orm';
|
||||
import { index, integer, pgTable, serial, timestamp } from 'drizzle-orm/pg-core';
|
||||
|
||||
export const artists = pgTable(
|
||||
'artists',
|
||||
{
|
||||
id: serial('id').primaryKey(),
|
||||
createdAt: timestamp('created_at')
|
||||
.notNull()
|
||||
.default(sql`CURRENT_TIMESTAMP`),
|
||||
updatedAt: timestamp('updated_at')
|
||||
.notNull()
|
||||
.default(sql`CURRENT_TIMESTAMP`),
|
||||
companyId: integer('company_id').notNull(),
|
||||
},
|
||||
);
|
||||
|
||||
export const members = pgTable('members', {
|
||||
id: serial('id').primaryKey(),
|
||||
createdAt: timestamp('created_at')
|
||||
.notNull()
|
||||
.default(sql`CURRENT_TIMESTAMP`),
|
||||
updatedAt: timestamp('updated_at')
|
||||
.notNull()
|
||||
.default(sql`CURRENT_TIMESTAMP`),
|
||||
});
|
||||
|
||||
export const artistsToMembers = pgTable(
|
||||
'artist_to_member',
|
||||
{
|
||||
id: serial('id').primaryKey(),
|
||||
memberId: integer('member_id').notNull(),
|
||||
artistId: integer('artist_id').notNull(),
|
||||
},
|
||||
(table) => ({
|
||||
memberArtistIndex: index('artist_to_member__artist_id__member_id__idx').on(
|
||||
table.memberId,
|
||||
table.artistId,
|
||||
),
|
||||
}),
|
||||
);
|
||||
|
||||
export const albums = pgTable(
|
||||
'albums',
|
||||
{
|
||||
id: serial('id').primaryKey(),
|
||||
createdAt: timestamp('created_at')
|
||||
.notNull()
|
||||
.default(sql`CURRENT_TIMESTAMP`),
|
||||
updatedAt: timestamp('updated_at')
|
||||
.notNull()
|
||||
.default(sql`CURRENT_TIMESTAMP`),
|
||||
artistId: integer('artist_id').notNull(),
|
||||
},
|
||||
(table) => ({
|
||||
artistIndex: index('albums__artist_id__idx').on(table.artistId),
|
||||
}),
|
||||
);
|
||||
|
||||
// relations
|
||||
export const artistRelations = relations(artists, ({ many }) => ({
|
||||
albums: many(albums),
|
||||
members: many(artistsToMembers),
|
||||
}));
|
||||
|
||||
export const albumRelations = relations(albums, ({ one }) => ({
|
||||
artist: one(artists, {
|
||||
fields: [albums.artistId],
|
||||
references: [artists.id],
|
||||
}),
|
||||
}));
|
||||
|
||||
export const memberRelations = relations(members, ({ many }) => ({
|
||||
artists: many(artistsToMembers),
|
||||
}));
|
||||
|
||||
export const artistsToMembersRelations = relations(artistsToMembers, ({ one }) => ({
|
||||
artist: one(artists, {
|
||||
fields: [artistsToMembers.artistId],
|
||||
references: [artists.id],
|
||||
}),
|
||||
member: one(members, {
|
||||
fields: [artistsToMembers.memberId],
|
||||
references: [members.id],
|
||||
}),
|
||||
}));
|
||||
@@ -0,0 +1,156 @@
|
||||
import { relations } from 'drizzle-orm';
|
||||
import { boolean, integer, pgTable, primaryKey, text, uuid } from 'drizzle-orm/pg-core';
|
||||
|
||||
export const menuItems = pgTable('menu_items', {
|
||||
id: uuid('id').defaultRandom().primaryKey(),
|
||||
});
|
||||
|
||||
export const modifierGroups = pgTable('modifier_groups', {
|
||||
id: uuid('id').defaultRandom().primaryKey(),
|
||||
});
|
||||
|
||||
export const menuItemModifierGroups = pgTable(
|
||||
'menu_item_modifier_groups',
|
||||
{
|
||||
menuItemId: uuid('menu_item_id')
|
||||
.notNull()
|
||||
.references(() => menuItems.id),
|
||||
modifierGroupId: uuid('modifier_group_id')
|
||||
.notNull()
|
||||
.references(() => modifierGroups.id),
|
||||
order: integer('order').default(0),
|
||||
},
|
||||
(table) => ({
|
||||
menuItemIdModifierGroupIdOrderPk: primaryKey(
|
||||
table.menuItemId,
|
||||
table.modifierGroupId,
|
||||
table.order,
|
||||
),
|
||||
}),
|
||||
);
|
||||
|
||||
export const ingredients = pgTable('ingredients', {
|
||||
id: uuid('id').defaultRandom().primaryKey(),
|
||||
name: text('name').notNull(),
|
||||
description: text('description'),
|
||||
imageUrl: text('image_url'),
|
||||
inStock: boolean('in_stock').default(true),
|
||||
});
|
||||
|
||||
export const modifiers = pgTable('modifiers', {
|
||||
id: uuid('id').defaultRandom().primaryKey(),
|
||||
ingredientId: uuid('ingredient_id').references(() => ingredients.id),
|
||||
itemId: uuid('item_id').references(() => menuItems.id),
|
||||
});
|
||||
|
||||
export const menuItemIngredients = pgTable(
|
||||
'menu_item_ingredients',
|
||||
{
|
||||
menuItemId: uuid('menu_item_id')
|
||||
.notNull()
|
||||
.references(() => menuItems.id),
|
||||
ingredientId: uuid('ingredient_id')
|
||||
.notNull()
|
||||
.references(() => ingredients.id),
|
||||
order: integer('order').default(0),
|
||||
},
|
||||
(table) => ({
|
||||
menuItemIdIngredientIdOrderPk: primaryKey(
|
||||
table.menuItemId,
|
||||
table.ingredientId,
|
||||
table.order,
|
||||
),
|
||||
}),
|
||||
);
|
||||
|
||||
export const modifierGroupModifiers = pgTable(
|
||||
'modifier_group_modifiers',
|
||||
{
|
||||
modifierGroupId: uuid('modifier_group_id')
|
||||
.notNull()
|
||||
.references(() => modifierGroups.id),
|
||||
modifierId: uuid('modifier_id')
|
||||
.notNull()
|
||||
.references(() => modifiers.id),
|
||||
order: integer('order').default(0),
|
||||
},
|
||||
(table) => ({
|
||||
modifierGroupIdModifierIdOrderPk: primaryKey(
|
||||
table.modifierGroupId,
|
||||
table.modifierId,
|
||||
table.order,
|
||||
),
|
||||
}),
|
||||
);
|
||||
|
||||
export const menuItemRelations = relations(menuItems, ({ many }) => ({
|
||||
ingredients: many(menuItemIngredients),
|
||||
modifierGroups: many(menuItemModifierGroups),
|
||||
// category: one(menuCategories, {
|
||||
// fields: [menuItems.categoryId],
|
||||
// references: [menuCategories.id],
|
||||
// }),
|
||||
}));
|
||||
|
||||
export const menuItemIngredientRelations = relations(
|
||||
menuItemIngredients,
|
||||
({ one }) => ({
|
||||
menuItem: one(menuItems, {
|
||||
fields: [menuItemIngredients.menuItemId],
|
||||
references: [menuItems.id],
|
||||
}),
|
||||
ingredient: one(ingredients, {
|
||||
fields: [menuItemIngredients.ingredientId],
|
||||
references: [ingredients.id],
|
||||
}),
|
||||
}),
|
||||
);
|
||||
|
||||
export const ingredientRelations = relations(ingredients, ({ many }) => ({
|
||||
menuItems: many(menuItemIngredients),
|
||||
}));
|
||||
|
||||
export const modifierGroupRelations = relations(modifierGroups, ({ many }) => ({
|
||||
menuItems: many(menuItemModifierGroups),
|
||||
modifiers: many(modifierGroupModifiers),
|
||||
}));
|
||||
|
||||
export const modifierRelations = relations(modifiers, ({ one, many }) => ({
|
||||
modifierGroups: many(modifierGroupModifiers),
|
||||
ingredient: one(ingredients, {
|
||||
fields: [modifiers.ingredientId],
|
||||
references: [ingredients.id],
|
||||
}),
|
||||
item: one(menuItems, {
|
||||
fields: [modifiers.itemId],
|
||||
references: [menuItems.id],
|
||||
}),
|
||||
}));
|
||||
|
||||
export const menuItemModifierGroupRelations = relations(
|
||||
menuItemModifierGroups,
|
||||
({ one }) => ({
|
||||
menuItem: one(menuItems, {
|
||||
fields: [menuItemModifierGroups.menuItemId],
|
||||
references: [menuItems.id],
|
||||
}),
|
||||
modifierGroup: one(modifierGroups, {
|
||||
fields: [menuItemModifierGroups.modifierGroupId],
|
||||
references: [modifierGroups.id],
|
||||
}),
|
||||
}),
|
||||
);
|
||||
|
||||
export const modifierGroupModifierRelations = relations(
|
||||
modifierGroupModifiers,
|
||||
({ one }) => ({
|
||||
modifierGroup: one(modifierGroups, {
|
||||
fields: [modifierGroupModifiers.modifierGroupId],
|
||||
references: [modifierGroups.id],
|
||||
}),
|
||||
modifier: one(modifiers, {
|
||||
fields: [modifierGroupModifiers.modifierId],
|
||||
references: [modifiers.id],
|
||||
}),
|
||||
}),
|
||||
);
|
||||
@@ -0,0 +1,318 @@
|
||||
import 'dotenv/config';
|
||||
import Docker from 'dockerode';
|
||||
import { desc, sql } from 'drizzle-orm';
|
||||
import { drizzle, type NodePgDatabase } from 'drizzle-orm/node-postgres';
|
||||
import getPort from 'get-port';
|
||||
import pg from 'pg';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
import { afterAll, beforeAll, beforeEach, expectTypeOf, test } from 'vitest';
|
||||
import * as schema from './pg.schema.ts';
|
||||
|
||||
const { Client } = pg;
|
||||
|
||||
const ENABLE_LOGGING = false;
|
||||
|
||||
/*
|
||||
Test cases:
|
||||
- querying nested relation without PK with additional fields
|
||||
*/
|
||||
|
||||
let pgContainer: Docker.Container;
|
||||
let db: NodePgDatabase<typeof schema>;
|
||||
let client: pg.Client;
|
||||
|
||||
async function createDockerDB(): Promise<string> {
|
||||
const docker = new Docker();
|
||||
const port = await getPort({ port: 5432 });
|
||||
const image = 'postgres:14';
|
||||
|
||||
const pullStream = await docker.pull(image);
|
||||
await new Promise((resolve, reject) =>
|
||||
docker.modem.followProgress(pullStream, (err) => err ? reject(err) : resolve(err))
|
||||
);
|
||||
|
||||
pgContainer = await docker.createContainer({
|
||||
Image: image,
|
||||
Env: [
|
||||
'POSTGRES_PASSWORD=postgres',
|
||||
'POSTGRES_USER=postgres',
|
||||
'POSTGRES_DB=postgres',
|
||||
],
|
||||
name: `drizzle-integration-tests-${uuid()}`,
|
||||
HostConfig: {
|
||||
AutoRemove: true,
|
||||
PortBindings: {
|
||||
'5432/tcp': [{ HostPort: `${port}` }],
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
await pgContainer.start();
|
||||
|
||||
return `postgres://postgres:postgres@localhost:${port}/postgres`;
|
||||
}
|
||||
|
||||
beforeAll(async () => {
|
||||
const connectionString = process.env['PG_CONNECTION_STRING'] ?? (await createDockerDB());
|
||||
|
||||
const sleep = 250;
|
||||
let timeLeft = 5000;
|
||||
let connected = false;
|
||||
let lastError: unknown | undefined;
|
||||
do {
|
||||
try {
|
||||
client = new Client(connectionString);
|
||||
await client.connect();
|
||||
connected = true;
|
||||
break;
|
||||
} catch (e) {
|
||||
lastError = e;
|
||||
await new Promise((resolve) => setTimeout(resolve, sleep));
|
||||
timeLeft -= sleep;
|
||||
}
|
||||
} while (timeLeft > 0);
|
||||
if (!connected) {
|
||||
console.error('Cannot connect to Postgres');
|
||||
await client?.end().catch(console.error);
|
||||
await pgContainer?.stop().catch(console.error);
|
||||
throw lastError;
|
||||
}
|
||||
db = drizzle(client, { schema, logger: ENABLE_LOGGING });
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await client?.end().catch(console.error);
|
||||
await pgContainer?.stop().catch(console.error);
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
await db.execute(sql`drop schema public cascade`);
|
||||
await db.execute(sql`create schema public`);
|
||||
|
||||
await db.execute(
|
||||
sql`
|
||||
CREATE TABLE IF NOT EXISTS "ingredients" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"name" text NOT NULL,
|
||||
"description" text,
|
||||
"image_url" text,
|
||||
"in_stock" boolean DEFAULT true
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS "menu_item_ingredients" (
|
||||
"menu_item_id" uuid NOT NULL,
|
||||
"ingredient_id" uuid NOT NULL,
|
||||
"order" integer DEFAULT 0
|
||||
);
|
||||
|
||||
ALTER TABLE "menu_item_ingredients" ADD CONSTRAINT "menu_item_ingredients_menu_item_id_ingredient_id_order" PRIMARY KEY("menu_item_id","ingredient_id","order");
|
||||
|
||||
CREATE TABLE IF NOT EXISTS "menu_item_modifier_groups" (
|
||||
"menu_item_id" uuid NOT NULL,
|
||||
"modifier_group_id" uuid NOT NULL,
|
||||
"order" integer DEFAULT 0
|
||||
);
|
||||
|
||||
ALTER TABLE "menu_item_modifier_groups" ADD CONSTRAINT "menu_item_modifier_groups_menu_item_id_modifier_group_id_order" PRIMARY KEY("menu_item_id","modifier_group_id","order");
|
||||
|
||||
CREATE TABLE IF NOT EXISTS "menu_items" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS "modifier_group_modifiers" (
|
||||
"modifier_group_id" uuid NOT NULL,
|
||||
"modifier_id" uuid NOT NULL,
|
||||
"order" integer DEFAULT 0
|
||||
);
|
||||
|
||||
ALTER TABLE "modifier_group_modifiers" ADD CONSTRAINT "modifier_group_modifiers_modifier_group_id_modifier_id_order" PRIMARY KEY("modifier_group_id","modifier_id","order");
|
||||
|
||||
CREATE TABLE IF NOT EXISTS "modifier_groups" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS "modifiers" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
"ingredient_id" uuid,
|
||||
"item_id" uuid
|
||||
);
|
||||
|
||||
DO $$ BEGIN
|
||||
ALTER TABLE "menu_item_ingredients" ADD CONSTRAINT "menu_item_ingredients_menu_item_id_menu_items_id_fk" FOREIGN KEY ("menu_item_id") REFERENCES "menu_items"("id") ON DELETE no action ON UPDATE no action;
|
||||
EXCEPTION
|
||||
WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
|
||||
DO $$ BEGIN
|
||||
ALTER TABLE "menu_item_ingredients" ADD CONSTRAINT "menu_item_ingredients_ingredient_id_ingredients_id_fk" FOREIGN KEY ("ingredient_id") REFERENCES "ingredients"("id") ON DELETE no action ON UPDATE no action;
|
||||
EXCEPTION
|
||||
WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
|
||||
DO $$ BEGIN
|
||||
ALTER TABLE "menu_item_modifier_groups" ADD CONSTRAINT "menu_item_modifier_groups_menu_item_id_menu_items_id_fk" FOREIGN KEY ("menu_item_id") REFERENCES "menu_items"("id") ON DELETE no action ON UPDATE no action;
|
||||
EXCEPTION
|
||||
WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
|
||||
DO $$ BEGIN
|
||||
ALTER TABLE "menu_item_modifier_groups" ADD CONSTRAINT "menu_item_modifier_groups_modifier_group_id_modifier_groups_id_fk" FOREIGN KEY ("modifier_group_id") REFERENCES "modifier_groups"("id") ON DELETE no action ON UPDATE no action;
|
||||
EXCEPTION
|
||||
WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
|
||||
DO $$ BEGIN
|
||||
ALTER TABLE "modifier_group_modifiers" ADD CONSTRAINT "modifier_group_modifiers_modifier_group_id_modifier_groups_id_fk" FOREIGN KEY ("modifier_group_id") REFERENCES "modifier_groups"("id") ON DELETE no action ON UPDATE no action;
|
||||
EXCEPTION
|
||||
WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
|
||||
DO $$ BEGIN
|
||||
ALTER TABLE "modifier_group_modifiers" ADD CONSTRAINT "modifier_group_modifiers_modifier_id_modifiers_id_fk" FOREIGN KEY ("modifier_id") REFERENCES "modifiers"("id") ON DELETE no action ON UPDATE no action;
|
||||
EXCEPTION
|
||||
WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
|
||||
DO $$ BEGIN
|
||||
ALTER TABLE "modifiers" ADD CONSTRAINT "modifiers_ingredient_id_ingredients_id_fk" FOREIGN KEY ("ingredient_id") REFERENCES "ingredients"("id") ON DELETE no action ON UPDATE no action;
|
||||
EXCEPTION
|
||||
WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
|
||||
DO $$ BEGIN
|
||||
ALTER TABLE "modifiers" ADD CONSTRAINT "modifiers_item_id_menu_items_id_fk" FOREIGN KEY ("item_id") REFERENCES "menu_items"("id") ON DELETE no action ON UPDATE no action;
|
||||
EXCEPTION
|
||||
WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
`,
|
||||
);
|
||||
});
|
||||
|
||||
test('Simple case from GH', async () => {
|
||||
const firstMenuItemId = uuid();
|
||||
const secondMenuItemId = uuid();
|
||||
|
||||
const firstModGroupsId = uuid();
|
||||
const secondModGroupsId = uuid();
|
||||
|
||||
await db.insert(schema.menuItems).values([{ id: firstMenuItemId }, { id: secondMenuItemId }]);
|
||||
await db.insert(schema.modifierGroups).values([{ id: firstModGroupsId }, { id: secondModGroupsId }]);
|
||||
await db.insert(schema.menuItemModifierGroups).values([{
|
||||
modifierGroupId: firstModGroupsId,
|
||||
menuItemId: firstMenuItemId,
|
||||
}, {
|
||||
modifierGroupId: firstModGroupsId,
|
||||
menuItemId: secondMenuItemId,
|
||||
}, {
|
||||
modifierGroupId: secondModGroupsId,
|
||||
menuItemId: firstMenuItemId,
|
||||
}]);
|
||||
|
||||
const firstIngredientId = uuid();
|
||||
const secondIngredientId = uuid();
|
||||
|
||||
await db.insert(schema.ingredients).values([{
|
||||
id: firstIngredientId,
|
||||
name: 'first',
|
||||
}, {
|
||||
id: secondIngredientId,
|
||||
name: 'second',
|
||||
}]);
|
||||
|
||||
const firstModifierId = uuid();
|
||||
const secondModifierId = uuid();
|
||||
|
||||
await db.insert(schema.modifiers).values([{
|
||||
id: firstModifierId,
|
||||
ingredientId: firstIngredientId,
|
||||
itemId: firstMenuItemId,
|
||||
}, {
|
||||
id: secondModifierId,
|
||||
ingredientId: secondIngredientId,
|
||||
itemId: secondMenuItemId,
|
||||
}]);
|
||||
|
||||
await db.insert(schema.modifierGroupModifiers).values([
|
||||
{
|
||||
modifierGroupId: firstModGroupsId,
|
||||
modifierId: firstModifierId,
|
||||
},
|
||||
{
|
||||
modifierGroupId: secondModGroupsId,
|
||||
modifierId: secondModifierId,
|
||||
},
|
||||
]);
|
||||
|
||||
const response = await db.query.menuItems
|
||||
.findMany({
|
||||
with: {
|
||||
modifierGroups: {
|
||||
with: {
|
||||
modifierGroup: {
|
||||
with: {
|
||||
modifiers: {
|
||||
with: {
|
||||
modifier: {
|
||||
with: {
|
||||
ingredient: true,
|
||||
item: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
orderBy: desc(schema.modifierGroupModifiers.order),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
orderBy: schema.menuItemModifierGroups.order,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expectTypeOf(response).toEqualTypeOf<
|
||||
{
|
||||
id: string;
|
||||
modifierGroups: {
|
||||
menuItemId: string;
|
||||
modifierGroupId: string;
|
||||
order: number | null;
|
||||
modifierGroup: {
|
||||
id: string;
|
||||
modifiers: {
|
||||
modifierGroupId: string;
|
||||
order: number | null;
|
||||
modifierId: string;
|
||||
modifier: {
|
||||
id: string;
|
||||
ingredientId: string | null;
|
||||
itemId: string | null;
|
||||
ingredient: {
|
||||
id: string;
|
||||
name: string;
|
||||
description: string | null;
|
||||
imageUrl: string | null;
|
||||
inStock: boolean | null;
|
||||
} | null;
|
||||
item: {
|
||||
id: string;
|
||||
} | null;
|
||||
};
|
||||
}[];
|
||||
};
|
||||
}[];
|
||||
}[]
|
||||
>();
|
||||
|
||||
// TODO: don't rely on items order
|
||||
// expect(response.length).eq(2);
|
||||
// expect(response[0]?.modifierGroups.length).eq(1);
|
||||
// expect(response[0]?.modifierGroups[0]?.modifierGroup.modifiers.length).eq(1);
|
||||
|
||||
// TODO: add correct IDs
|
||||
// expect(response[0]?.modifierGroups[0]?.modifierGroup.modifiers[0]?.modifier.ingredient?.id).eq(
|
||||
// '0b2b9abc-5975-4a1d-ba3d-6fc3b3149902',
|
||||
// );
|
||||
// expect(response[0]?.modifierGroups[0]?.modifierGroup.modifiers[0]?.modifier.item?.id).eq(
|
||||
// 'a867133e-60b7-4003-aaa0-deeefad7e518',
|
||||
// );
|
||||
});
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,147 @@
|
||||
import {
|
||||
type AnyMySqlColumn,
|
||||
bigint,
|
||||
boolean,
|
||||
mysqlSchema,
|
||||
mysqlTable,
|
||||
primaryKey,
|
||||
serial,
|
||||
text,
|
||||
timestamp,
|
||||
} from 'drizzle-orm/mysql-core';
|
||||
|
||||
import { relations } from 'drizzle-orm';
|
||||
|
||||
export const usersTable = mysqlTable('users', {
|
||||
id: serial('id').primaryKey(),
|
||||
name: text('name').notNull(),
|
||||
verified: boolean('verified').notNull().default(false),
|
||||
invitedBy: bigint('invited_by', { mode: 'number' }).references(
|
||||
(): AnyMySqlColumn => usersTable.id,
|
||||
),
|
||||
});
|
||||
|
||||
const schemaV1 = mysqlSchema('schemaV1');
|
||||
|
||||
export const usersV1 = schemaV1.table('usersV1', {
|
||||
id: serial('id').primaryKey(),
|
||||
name: text('name').notNull(),
|
||||
verified: boolean('verified').notNull().default(false),
|
||||
invitedBy: bigint('invited_by', { mode: 'number' }),
|
||||
});
|
||||
|
||||
export const usersTableV1 = schemaV1.table('users_table_V1', {
|
||||
id: serial('id').primaryKey(),
|
||||
name: text('name').notNull(),
|
||||
verified: boolean('verified').notNull().default(false),
|
||||
invitedBy: bigint('invited_by', { mode: 'number' }),
|
||||
});
|
||||
|
||||
export const usersConfig = relations(usersTable, ({ one, many }) => ({
|
||||
invitee: one(usersTable, {
|
||||
fields: [usersTable.invitedBy],
|
||||
references: [usersTable.id],
|
||||
}),
|
||||
usersToGroups: many(usersToGroupsTable),
|
||||
posts: many(postsTable),
|
||||
comments: many(commentsTable),
|
||||
}));
|
||||
|
||||
export const groupsTable = mysqlTable('groups', {
|
||||
id: serial('id').primaryKey(),
|
||||
name: text('name').notNull(),
|
||||
description: text('description'),
|
||||
});
|
||||
export const groupsConfig = relations(groupsTable, ({ many }) => ({
|
||||
usersToGroups: many(usersToGroupsTable),
|
||||
}));
|
||||
|
||||
export const usersToGroupsTable = mysqlTable(
|
||||
'users_to_groups',
|
||||
{
|
||||
id: serial('id').primaryKey(),
|
||||
userId: bigint('user_id', { mode: 'number' }).notNull().references(
|
||||
() => usersTable.id,
|
||||
),
|
||||
groupId: bigint('group_id', { mode: 'number' }).notNull().references(
|
||||
() => groupsTable.id,
|
||||
),
|
||||
},
|
||||
(t) => ({
|
||||
pk: primaryKey(t.userId, t.groupId),
|
||||
}),
|
||||
);
|
||||
export const usersToGroupsConfig = relations(usersToGroupsTable, ({ one }) => ({
|
||||
group: one(groupsTable, {
|
||||
fields: [usersToGroupsTable.groupId],
|
||||
references: [groupsTable.id],
|
||||
}),
|
||||
user: one(usersTable, {
|
||||
fields: [usersToGroupsTable.userId],
|
||||
references: [usersTable.id],
|
||||
}),
|
||||
}));
|
||||
|
||||
export const postsTable = mysqlTable('posts', {
|
||||
id: serial('id').primaryKey(),
|
||||
content: text('content').notNull(),
|
||||
ownerId: bigint('owner_id', { mode: 'number' }).references(
|
||||
() => usersTable.id,
|
||||
),
|
||||
createdAt: timestamp('created_at')
|
||||
.notNull()
|
||||
.defaultNow(),
|
||||
});
|
||||
export const postsConfig = relations(postsTable, ({ one, many }) => ({
|
||||
author: one(usersTable, {
|
||||
fields: [postsTable.ownerId],
|
||||
references: [usersTable.id],
|
||||
}),
|
||||
comments: many(commentsTable),
|
||||
}));
|
||||
|
||||
export const commentsTable = mysqlTable('comments', {
|
||||
id: serial('id').primaryKey(),
|
||||
content: text('content').notNull(),
|
||||
creator: bigint('creator', { mode: 'number' }).references(
|
||||
() => usersTable.id,
|
||||
),
|
||||
postId: bigint('post_id', { mode: 'number' }).references(() => postsTable.id),
|
||||
createdAt: timestamp('created_at')
|
||||
.notNull()
|
||||
.defaultNow(),
|
||||
});
|
||||
export const commentsConfig = relations(commentsTable, ({ one, many }) => ({
|
||||
post: one(postsTable, {
|
||||
fields: [commentsTable.postId],
|
||||
references: [postsTable.id],
|
||||
}),
|
||||
author: one(usersTable, {
|
||||
fields: [commentsTable.creator],
|
||||
references: [usersTable.id],
|
||||
}),
|
||||
likes: many(commentLikesTable),
|
||||
}));
|
||||
|
||||
export const commentLikesTable = mysqlTable('comment_likes', {
|
||||
id: serial('id').primaryKey(),
|
||||
creator: bigint('creator', { mode: 'number' }).references(
|
||||
() => usersTable.id,
|
||||
),
|
||||
commentId: bigint('comment_id', { mode: 'number' }).references(
|
||||
() => commentsTable.id,
|
||||
),
|
||||
createdAt: timestamp('created_at')
|
||||
.notNull()
|
||||
.defaultNow(),
|
||||
});
|
||||
export const commentLikesConfig = relations(commentLikesTable, ({ one }) => ({
|
||||
comment: one(commentsTable, {
|
||||
fields: [commentLikesTable.commentId],
|
||||
references: [commentsTable.id],
|
||||
}),
|
||||
author: one(usersTable, {
|
||||
fields: [commentLikesTable.creator],
|
||||
references: [usersTable.id],
|
||||
}),
|
||||
}));
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,103 @@
|
||||
import {
|
||||
boolean,
|
||||
integer,
|
||||
type PgColumn,
|
||||
pgSchema,
|
||||
pgTable,
|
||||
primaryKey,
|
||||
serial,
|
||||
text,
|
||||
timestamp,
|
||||
} from 'drizzle-orm/pg-core';
|
||||
|
||||
import { relations } from 'drizzle-orm';
|
||||
|
||||
export const usersTable = pgTable('users', {
|
||||
id: serial('id').primaryKey(),
|
||||
name: text('name').notNull(),
|
||||
verified: boolean('verified').notNull().default(false),
|
||||
invitedBy: integer('invited_by').references((): PgColumn => usersTable.id),
|
||||
});
|
||||
|
||||
export const schemaV1 = pgSchema('schemaV1');
|
||||
|
||||
export const usersV1 = schemaV1.table('usersV1', {
|
||||
id: serial('id').primaryKey(),
|
||||
name: text('name').notNull(),
|
||||
verified: boolean('verified').notNull().default(false),
|
||||
invitedBy: integer('invited_by'),
|
||||
});
|
||||
|
||||
export const usersTableV1 = schemaV1.table('users_table_V1', {
|
||||
id: serial('id').primaryKey(),
|
||||
name: text('name').notNull(),
|
||||
verified: boolean('verified').notNull().default(false),
|
||||
invitedBy: integer('invited_by'),
|
||||
});
|
||||
|
||||
export const usersConfig = relations(usersTable, ({ one, many }) => ({
|
||||
invitee: one(usersTable, { fields: [usersTable.invitedBy], references: [usersTable.id] }),
|
||||
usersToGroups: many(usersToGroupsTable),
|
||||
posts: many(postsTable),
|
||||
}));
|
||||
|
||||
export const groupsTable = pgTable('groups', {
|
||||
id: serial('id').primaryKey(),
|
||||
name: text('name').notNull(),
|
||||
description: text('description'),
|
||||
});
|
||||
|
||||
export const groupsConfig = relations(groupsTable, ({ many }) => ({
|
||||
usersToGroups: many(usersToGroupsTable),
|
||||
}));
|
||||
|
||||
export const usersToGroupsTable = pgTable('users_to_groups', {
|
||||
id: serial('id').primaryKey(),
|
||||
userId: integer('user_id').notNull().references(() => usersTable.id),
|
||||
groupId: integer('group_id').notNull().references(() => groupsTable.id),
|
||||
}, (t) => ({
|
||||
pk: primaryKey(t.groupId, t.userId),
|
||||
}));
|
||||
|
||||
export const usersToGroupsConfig = relations(usersToGroupsTable, ({ one }) => ({
|
||||
group: one(groupsTable, { fields: [usersToGroupsTable.groupId], references: [groupsTable.id] }),
|
||||
user: one(usersTable, { fields: [usersToGroupsTable.userId], references: [usersTable.id] }),
|
||||
}));
|
||||
|
||||
export const postsTable = pgTable('posts', {
|
||||
id: serial('id').primaryKey(),
|
||||
content: text('content').notNull(),
|
||||
ownerId: integer('owner_id').references(() => usersTable.id),
|
||||
createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
|
||||
});
|
||||
|
||||
export const postsConfig = relations(postsTable, ({ one, many }) => ({
|
||||
author: one(usersTable, { fields: [postsTable.ownerId], references: [usersTable.id] }),
|
||||
comments: many(commentsTable),
|
||||
}));
|
||||
|
||||
export const commentsTable = pgTable('comments', {
|
||||
id: serial('id').primaryKey(),
|
||||
content: text('content').notNull(),
|
||||
creator: integer('creator').references(() => usersTable.id),
|
||||
postId: integer('post_id').references(() => postsTable.id),
|
||||
createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
|
||||
});
|
||||
|
||||
export const commentsConfig = relations(commentsTable, ({ one, many }) => ({
|
||||
post: one(postsTable, { fields: [commentsTable.postId], references: [postsTable.id] }),
|
||||
author: one(usersTable, { fields: [commentsTable.creator], references: [usersTable.id] }),
|
||||
likes: many(commentLikesTable),
|
||||
}));
|
||||
|
||||
export const commentLikesTable = pgTable('comment_likes', {
|
||||
id: serial('id').primaryKey(),
|
||||
creator: integer('creator').references(() => usersTable.id),
|
||||
commentId: integer('comment_id').references(() => commentsTable.id),
|
||||
createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
|
||||
});
|
||||
|
||||
export const commentLikesConfig = relations(commentLikesTable, ({ one }) => ({
|
||||
comment: one(commentsTable, { fields: [commentLikesTable.commentId], references: [commentsTable.id] }),
|
||||
author: one(usersTable, { fields: [commentLikesTable.creator], references: [usersTable.id] }),
|
||||
}));
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,106 @@
|
||||
import { bigint, boolean, primaryKey, serial, singlestoreTable, text, timestamp } from 'drizzle-orm/singlestore-core';
|
||||
|
||||
import { relations } from 'drizzle-orm';
|
||||
|
||||
export const usersTable = singlestoreTable('users', {
|
||||
id: serial('id').primaryKey(),
|
||||
name: text('name').notNull(),
|
||||
verified: boolean('verified').notNull().default(false),
|
||||
invitedBy: bigint('invited_by', { mode: 'number' }),
|
||||
});
|
||||
export const usersConfig = relations(usersTable, ({ one, many }) => ({
|
||||
invitee: one(usersTable, {
|
||||
fields: [usersTable.invitedBy],
|
||||
references: [usersTable.id],
|
||||
}),
|
||||
usersToGroups: many(usersToGroupsTable),
|
||||
posts: many(postsTable),
|
||||
comments: many(commentsTable),
|
||||
}));
|
||||
|
||||
export const groupsTable = singlestoreTable('groups', {
|
||||
id: serial('id').primaryKey(),
|
||||
name: text('name').notNull(),
|
||||
description: text('description'),
|
||||
});
|
||||
export const groupsConfig = relations(groupsTable, ({ many }) => ({
|
||||
usersToGroups: many(usersToGroupsTable),
|
||||
}));
|
||||
|
||||
export const usersToGroupsTable = singlestoreTable(
|
||||
'users_to_groups',
|
||||
{
|
||||
id: serial('id').primaryKey(),
|
||||
userId: bigint('user_id', { mode: 'number' }).notNull(),
|
||||
groupId: bigint('group_id', { mode: 'number' }).notNull(),
|
||||
},
|
||||
(t) => ({
|
||||
pk: primaryKey(t.userId, t.groupId),
|
||||
}),
|
||||
);
|
||||
export const usersToGroupsConfig = relations(usersToGroupsTable, ({ one }) => ({
|
||||
group: one(groupsTable, {
|
||||
fields: [usersToGroupsTable.groupId],
|
||||
references: [groupsTable.id],
|
||||
}),
|
||||
user: one(usersTable, {
|
||||
fields: [usersToGroupsTable.userId],
|
||||
references: [usersTable.id],
|
||||
}),
|
||||
}));
|
||||
|
||||
export const postsTable = singlestoreTable('posts', {
|
||||
id: serial('id').primaryKey(),
|
||||
content: text('content').notNull(),
|
||||
ownerId: bigint('owner_id', { mode: 'number' }),
|
||||
createdAt: timestamp('created_at')
|
||||
.notNull()
|
||||
.defaultNow(),
|
||||
});
|
||||
export const postsConfig = relations(postsTable, ({ one, many }) => ({
|
||||
author: one(usersTable, {
|
||||
fields: [postsTable.ownerId],
|
||||
references: [usersTable.id],
|
||||
}),
|
||||
comments: many(commentsTable),
|
||||
}));
|
||||
|
||||
export const commentsTable = singlestoreTable('comments', {
|
||||
id: serial('id').primaryKey(),
|
||||
content: text('content').notNull(),
|
||||
creator: bigint('creator', { mode: 'number' }),
|
||||
postId: bigint('post_id', { mode: 'number' }),
|
||||
createdAt: timestamp('created_at')
|
||||
.notNull()
|
||||
.defaultNow(),
|
||||
});
|
||||
export const commentsConfig = relations(commentsTable, ({ one, many }) => ({
|
||||
post: one(postsTable, {
|
||||
fields: [commentsTable.postId],
|
||||
references: [postsTable.id],
|
||||
}),
|
||||
author: one(usersTable, {
|
||||
fields: [commentsTable.creator],
|
||||
references: [usersTable.id],
|
||||
}),
|
||||
likes: many(commentLikesTable),
|
||||
}));
|
||||
|
||||
export const commentLikesTable = singlestoreTable('comment_likes', {
|
||||
id: serial('id').primaryKey(),
|
||||
creator: bigint('creator', { mode: 'number' }),
|
||||
commentId: bigint('comment_id', { mode: 'number' }),
|
||||
createdAt: timestamp('created_at')
|
||||
.notNull()
|
||||
.defaultNow(),
|
||||
});
|
||||
export const commentLikesConfig = relations(commentLikesTable, ({ one }) => ({
|
||||
comment: one(commentsTable, {
|
||||
fields: [commentLikesTable.commentId],
|
||||
references: [commentsTable.id],
|
||||
}),
|
||||
author: one(usersTable, {
|
||||
fields: [commentLikesTable.creator],
|
||||
references: [usersTable.id],
|
||||
}),
|
||||
}));
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,114 @@
|
||||
import { type AnySQLiteColumn, integer, primaryKey, sqliteTable, text } from 'drizzle-orm/sqlite-core';
|
||||
|
||||
import { relations, sql } from 'drizzle-orm';
|
||||
|
||||
export const usersTable = sqliteTable('users', {
|
||||
id: integer('id').primaryKey({ autoIncrement: true }),
|
||||
name: text('name').notNull(),
|
||||
verified: integer('verified').notNull().default(0),
|
||||
invitedBy: integer('invited_by').references((): AnySQLiteColumn => usersTable.id),
|
||||
});
|
||||
export const usersConfig = relations(usersTable, ({ one, many }) => ({
|
||||
invitee: one(usersTable, {
|
||||
fields: [usersTable.invitedBy],
|
||||
references: [usersTable.id],
|
||||
}),
|
||||
usersToGroups: many(usersToGroupsTable),
|
||||
posts: many(postsTable),
|
||||
}));
|
||||
|
||||
export const groupsTable = sqliteTable('groups', {
|
||||
id: integer('id').primaryKey({ autoIncrement: true }),
|
||||
name: text('name').notNull(),
|
||||
description: text('description'),
|
||||
});
|
||||
export const groupsConfig = relations(groupsTable, ({ many }) => ({
|
||||
usersToGroups: many(usersToGroupsTable),
|
||||
}));
|
||||
|
||||
export const usersToGroupsTable = sqliteTable(
|
||||
'users_to_groups',
|
||||
{
|
||||
id: integer('id').primaryKey({ autoIncrement: true }),
|
||||
userId: integer('user_id', { mode: 'number' }).notNull().references(
|
||||
() => usersTable.id,
|
||||
),
|
||||
groupId: integer('group_id', { mode: 'number' }).notNull().references(
|
||||
() => groupsTable.id,
|
||||
),
|
||||
},
|
||||
(t) => ({
|
||||
pk: primaryKey(t.userId, t.groupId),
|
||||
}),
|
||||
);
|
||||
export const usersToGroupsConfig = relations(usersToGroupsTable, ({ one }) => ({
|
||||
group: one(groupsTable, {
|
||||
fields: [usersToGroupsTable.groupId],
|
||||
references: [groupsTable.id],
|
||||
}),
|
||||
user: one(usersTable, {
|
||||
fields: [usersToGroupsTable.userId],
|
||||
references: [usersTable.id],
|
||||
}),
|
||||
}));
|
||||
|
||||
export const postsTable = sqliteTable('posts', {
|
||||
id: integer('id').primaryKey({ autoIncrement: true }),
|
||||
content: text('content').notNull(),
|
||||
ownerId: integer('owner_id', { mode: 'number' }).references(
|
||||
() => usersTable.id,
|
||||
),
|
||||
createdAt: integer('created_at', { mode: 'timestamp_ms' })
|
||||
.notNull().default(sql`current_timestamp`),
|
||||
});
|
||||
export const postsConfig = relations(postsTable, ({ one, many }) => ({
|
||||
author: one(usersTable, {
|
||||
fields: [postsTable.ownerId],
|
||||
references: [usersTable.id],
|
||||
}),
|
||||
comments: many(commentsTable),
|
||||
}));
|
||||
|
||||
export const commentsTable = sqliteTable('comments', {
|
||||
id: integer('id').primaryKey({ autoIncrement: true }),
|
||||
content: text('content').notNull(),
|
||||
creator: integer('creator', { mode: 'number' }).references(
|
||||
() => usersTable.id,
|
||||
),
|
||||
postId: integer('post_id', { mode: 'number' }).references(() => postsTable.id),
|
||||
createdAt: integer('created_at', { mode: 'timestamp_ms' })
|
||||
.notNull().default(sql`current_timestamp`),
|
||||
});
|
||||
export const commentsConfig = relations(commentsTable, ({ one, many }) => ({
|
||||
post: one(postsTable, {
|
||||
fields: [commentsTable.postId],
|
||||
references: [postsTable.id],
|
||||
}),
|
||||
author: one(usersTable, {
|
||||
fields: [commentsTable.creator],
|
||||
references: [usersTable.id],
|
||||
}),
|
||||
likes: many(commentLikesTable),
|
||||
}));
|
||||
|
||||
export const commentLikesTable = sqliteTable('comment_likes', {
|
||||
id: integer('id').primaryKey({ autoIncrement: true }),
|
||||
creator: integer('creator', { mode: 'number' }).references(
|
||||
() => usersTable.id,
|
||||
),
|
||||
commentId: integer('comment_id', { mode: 'number' }).references(
|
||||
() => commentsTable.id,
|
||||
),
|
||||
createdAt: integer('created_at', { mode: 'timestamp_ms' })
|
||||
.notNull().default(sql`current_timestamp`),
|
||||
});
|
||||
export const commentLikesConfig = relations(commentLikesTable, ({ one }) => ({
|
||||
comment: one(commentsTable, {
|
||||
fields: [commentLikesTable.commentId],
|
||||
references: [commentsTable.id],
|
||||
}),
|
||||
author: one(usersTable, {
|
||||
fields: [commentLikesTable.creator],
|
||||
references: [usersTable.id],
|
||||
}),
|
||||
}));
|
||||
@@ -0,0 +1,79 @@
|
||||
import { relations } from 'drizzle-orm';
|
||||
import { foreignKey, int, integer, sqliteTable, text } from 'drizzle-orm/sqlite-core';
|
||||
|
||||
export const users = sqliteTable('users', {
|
||||
id: integer('id').primaryKey(),
|
||||
name: text('name').notNull(),
|
||||
cityId: int('city_id').references(() => cities.id).notNull(),
|
||||
homeCityId: int('home_city_id').references(() => cities.id),
|
||||
createdAt: integer('created_at', { mode: 'timestamp' }).notNull(),
|
||||
});
|
||||
export const usersConfig = relations(users, ({ one, many }) => ({
|
||||
city: one(cities, { relationName: 'UsersInCity', fields: [users.cityId], references: [cities.id] }),
|
||||
homeCity: one(cities, { fields: [users.homeCityId], references: [cities.id] }),
|
||||
posts: many(posts),
|
||||
comments: many(comments),
|
||||
}));
|
||||
|
||||
export const cities = sqliteTable('cities', {
|
||||
id: integer('id').primaryKey(),
|
||||
name: text('name').notNull(),
|
||||
});
|
||||
export const citiesConfig = relations(cities, ({ many }) => ({
|
||||
users: many(users, { relationName: 'UsersInCity' }),
|
||||
}));
|
||||
|
||||
export const posts = sqliteTable('posts', {
|
||||
id: integer('id').primaryKey(),
|
||||
title: text('title').notNull(),
|
||||
authorId: int('author_id').references(() => users.id),
|
||||
});
|
||||
export const postsConfig = relations(posts, ({ one, many }) => ({
|
||||
author: one(users, { fields: [posts.authorId], references: [users.id] }),
|
||||
comments: many(comments),
|
||||
}));
|
||||
|
||||
export const comments = sqliteTable('comments', {
|
||||
id: integer('id').primaryKey(),
|
||||
postId: int('post_id').references(() => posts.id).notNull(),
|
||||
authorId: int('author_id').references(() => users.id),
|
||||
text: text('text').notNull(),
|
||||
});
|
||||
export const commentsConfig = relations(comments, ({ one }) => ({
|
||||
post: one(posts, { fields: [comments.postId], references: [posts.id] }),
|
||||
author: one(users, { fields: [comments.authorId], references: [users.id] }),
|
||||
}));
|
||||
|
||||
export const books = sqliteTable('books', {
|
||||
id: integer('id').primaryKey(),
|
||||
name: text('name').notNull(),
|
||||
});
|
||||
export const booksConfig = relations(books, ({ many }) => ({
|
||||
authors: many(bookAuthors),
|
||||
}));
|
||||
|
||||
export const bookAuthors = sqliteTable('book_authors', {
|
||||
bookId: int('book_id').references(() => books.id).notNull(),
|
||||
authorId: int('author_id').references(() => users.id).notNull(),
|
||||
role: text('role').notNull(),
|
||||
});
|
||||
export const bookAuthorsConfig = relations(bookAuthors, ({ one }) => ({
|
||||
book: one(books, { fields: [bookAuthors.bookId], references: [books.id] }),
|
||||
author: one(users, { fields: [bookAuthors.authorId], references: [users.id] }),
|
||||
}));
|
||||
|
||||
export const node = sqliteTable('node', {
|
||||
id: integer('id').primaryKey(),
|
||||
parentId: int('parent_id'),
|
||||
leftId: int('left_id'),
|
||||
rightId: int('right_id'),
|
||||
}, (node) => ({
|
||||
fk1: foreignKey(() => ({ columns: [node.parentId], foreignColumns: [node.id] })),
|
||||
fk2: foreignKey(() => ({ columns: [node.leftId], foreignColumns: [node.id] })),
|
||||
fk3: foreignKey(() => ({ columns: [node.rightId], foreignColumns: [node.id] })),
|
||||
}));
|
||||
export const nodeRelations = relations(node, ({ one }) => ({
|
||||
parent: one(node, { fields: [node.parentId], references: [node.id] }),
|
||||
left: one(node, { fields: [node.leftId], references: [node.id] }),
|
||||
right: one(node, { fields: [node.rightId], references: [node.id] }),
|
||||
}));
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user