feat(knex): create database interfaces and knexjs nest integration

Co-authored-by: Philip Molares <philip.molares@udo.edu>
Signed-off-by: Philip Molares <philip.molares@udo.edu>
Signed-off-by: Erik Michelson <github@erik.michelson.eu>
This commit is contained in:
Erik Michelson 2025-03-06 12:59:59 +01:00
parent 902abf72e6
commit a9183e82bf
No known key found for this signature in database
GPG key ID: DB99ADDDC5C0AF82
93 changed files with 760 additions and 2927 deletions

View file

@ -36,26 +36,27 @@
"@nestjs/platform-ws": "10.4.15",
"@nestjs/schedule": "4.1.2",
"@nestjs/swagger": "8.0.7",
"@nestjs/typeorm": "10.0.2",
"@nestjs/websockets": "10.4.15",
"@node-rs/argon2": "2.0.2",
"@zxcvbn-ts/core": "3.0.4",
"@zxcvbn-ts/language-common": "3.0.4",
"@zxcvbn-ts/language-en": "3.0.2",
"base32-encode": "1.2.0",
"better-sqlite3": "11.8.1",
"class-transformer": "0.5.1",
"class-validator": "0.14.1",
"cli-color": "2.0.4",
"connect-typeorm": "2.0.0",
"cookie": "1.0.2",
"diff": "7.0.0",
"express-session": "1.18.1",
"file-type": "16.5.4",
"htmlparser2": "9.1.0",
"knex": "3.1.0",
"ldapauth-fork": "6.1.0",
"markdown-it": "13.0.2",
"minio": "8.0.4",
"mysql": "2.18.1",
"nestjs-knex": "2.0.0",
"nestjs-zod": "4.3.1",
"node-fetch": "2.7.0",
"openid-client": "5.7.1",
@ -64,8 +65,6 @@
"reflect-metadata": "0.2.2",
"rimraf": "6.0.1",
"rxjs": "7.8.1",
"sqlite3": "5.1.7",
"typeorm": "0.3.20",
"uuid": "11.0.5",
"ws": "8.18.0",
"yjs": "13.6.23",
@ -78,6 +77,7 @@
"@nestjs/testing": "10.4.15",
"@trivago/prettier-plugin-sort-imports": "4.3.0",
"@tsconfig/node18": "18.2.4",
"@types/better-sqlite3": "7.6.12",
"@types/cli-color": "2.0.6",
"@types/cookie": "1.0.0",
"@types/cookie-signature": "1.1.2",

View file

@ -1,63 +0,0 @@
/*
* SPDX-FileCopyrightText: 2024 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import {
Column,
CreateDateColumn,
Entity,
ManyToOne,
PrimaryGeneratedColumn,
} from 'typeorm';
import { User } from '../users/user.entity';
@Entity()
export class ApiToken {
@PrimaryGeneratedColumn()
id: number;
@Column({ unique: true })
keyId: string;
@ManyToOne((_) => User, (user) => user.apiTokens, {
onDelete: 'CASCADE', // This deletes the PublicAuthToken, when the associated User is deleted
})
user: Promise<User>;
@Column()
label: string;
@CreateDateColumn()
createdAt: Date;
@Column({ unique: true })
hash: string;
@Column()
validUntil: Date;
@Column({
nullable: true,
type: 'date',
})
lastUsedAt: Date | null;
public static create(
keyId: string,
user: User,
label: string,
tokenString: string,
validUntil: Date,
): Omit<ApiToken, 'id' | 'createdAt'> {
const token = new ApiToken();
token.keyId = keyId;
token.user = Promise.resolve(user);
token.label = label;
token.hash = tokenString;
token.validUntil = validUntil;
token.lastUsedAt = null;
return token;
}
}

View file

@ -12,6 +12,7 @@ import { Repository } from 'typeorm';
import { Identity } from '../auth/identity.entity';
import appConfigMock from '../config/mock/app.config.mock';
import authConfigMock from '../config/mock/auth.config.mock';
import { User } from '../database/user.entity';
import {
NotInDBError,
TokenNotValidError,
@ -19,7 +20,6 @@ import {
} from '../errors/errors';
import { LoggerModule } from '../logger/logger.module';
import { Session } from '../sessions/session.entity';
import { User } from '../users/user.entity';
import { UsersModule } from '../users/users.module';
import { ApiToken } from './api-token.entity';
import { ApiTokenService } from './api-token.service';

View file

@ -10,13 +10,13 @@ import { InjectRepository } from '@nestjs/typeorm';
import { createHash, randomBytes, timingSafeEqual } from 'crypto';
import { Repository } from 'typeorm';
import { User } from '../database/user.entity';
import {
NotInDBError,
TokenNotValidError,
TooManyTokensError,
} from '../errors/errors';
import { ConsoleLoggerService } from '../logger/console-logger.service';
import { User } from '../users/user.entity';
import { bufferToBase64Url } from '../utils/password';
import { ApiToken } from './api-token.entity';

View file

@ -6,7 +6,7 @@
import { ExecutionContext, Injectable } from '@nestjs/common';
import { CompleteRequest } from '../api/utils/request.type';
import { User } from '../users/user.entity';
import { User } from '../database/user.entity';
import { UsersService } from '../users/users.service';
@Injectable()

View file

@ -20,11 +20,11 @@ import {
import { ApiTags } from '@nestjs/swagger';
import { SessionGuard } from '../../../auth/session.guard';
import { User } from '../../../database/user.entity';
import { ConsoleLoggerService } from '../../../logger/console-logger.service';
import { AliasService } from '../../../notes/alias.service';
import { NotesService } from '../../../notes/notes.service';
import { PermissionsService } from '../../../permissions/permissions.service';
import { User } from '../../../users/user.entity';
import { UsersService } from '../../../users/users.service';
import { OpenApi } from '../../utils/openapi.decorator';
import { RequestUser } from '../../utils/request-user.decorator';

View file

@ -25,8 +25,8 @@ import {
RequestWithSession,
SessionGuard,
} from '../../../../auth/session.guard';
import { User } from '../../../../database/user.entity';
import { ConsoleLoggerService } from '../../../../logger/console-logger.service';
import { User } from '../../../../users/user.entity';
import { UsersService } from '../../../../users/users.service';
import { LoginEnabledGuard } from '../../../utils/login-enabled.guard';
import { OpenApi } from '../../../utils/openapi.decorator';

View file

@ -16,13 +16,13 @@ import {
import { ApiTags } from '@nestjs/swagger';
import { SessionGuard } from '../../../../auth/session.guard';
import { User } from '../../../../database/user.entity';
import { HistoryEntryImportListDto } from '../../../../history/history-entry-import.dto';
import { HistoryEntryUpdateDto } from '../../../../history/history-entry-update.dto';
import { HistoryEntryDto } from '../../../../history/history-entry.dto';
import { HistoryService } from '../../../../history/history.service';
import { ConsoleLoggerService } from '../../../../logger/console-logger.service';
import { Note } from '../../../../notes/note.entity';
import { User } from '../../../../users/user.entity';
import { GetNoteInterceptor } from '../../../utils/get-note.interceptor';
import { OpenApi } from '../../../utils/openapi.decorator';
import { RequestNote } from '../../../utils/request-note.decorator';

View file

@ -12,6 +12,7 @@ import { Body, Controller, Delete, Get, Put, UseGuards } from '@nestjs/common';
import { ApiTags } from '@nestjs/swagger';
import { SessionGuard } from '../../../auth/session.guard';
import { User } from '../../../database/user.entity';
import { ConsoleLoggerService } from '../../../logger/console-logger.service';
import { MediaService } from '../../../media/media.service';
import { User } from '../../../users/user.entity';

View file

@ -21,6 +21,7 @@ import { ApiBody, ApiConsumes, ApiHeader, ApiTags } from '@nestjs/swagger';
import { Response } from 'express';
import { SessionGuard } from '../../../auth/session.guard';
import { User } from '../../../database/user.entity';
import { PermissionError } from '../../../errors/errors';
import { ConsoleLoggerService } from '../../../logger/console-logger.service';
import { MediaService } from '../../../media/media.service';
@ -30,7 +31,6 @@ import { PermissionsGuard } from '../../../permissions/permissions.guard';
import { PermissionsService } from '../../../permissions/permissions.service';
import { RequirePermission } from '../../../permissions/require-permission.decorator';
import { RequiredPermission } from '../../../permissions/required-permission.enum';
import { User } from '../../../users/user.entity';
import { NoteHeaderInterceptor } from '../../utils/note-header.interceptor';
import { OpenApi } from '../../utils/openapi.decorator';
import { RequestNote } from '../../utils/request-note.decorator';

View file

@ -32,6 +32,7 @@ import {
import { ApiTags } from '@nestjs/swagger';
import { SessionGuard } from '../../../auth/session.guard';
import { User } from '../../../database/user.entity';
import { NotInDBError } from '../../../errors/errors';
import { GroupsService } from '../../../groups/groups.service';
import { HistoryService } from '../../../history/history.service';
@ -44,7 +45,6 @@ import { PermissionsService } from '../../../permissions/permissions.service';
import { RequirePermission } from '../../../permissions/require-permission.decorator';
import { RequiredPermission } from '../../../permissions/required-permission.enum';
import { RevisionsService } from '../../../revisions/revisions.service';
import { User } from '../../../users/user.entity';
import { UsersService } from '../../../users/users.service';
import { GetNoteInterceptor } from '../../utils/get-note.interceptor';
import { MarkdownBody } from '../../utils/markdown-body.decorator';

View file

@ -22,8 +22,8 @@ import { ApiTags } from '@nestjs/swagger';
import { ApiTokenService } from '../../../api-token/api-token.service';
import { SessionGuard } from '../../../auth/session.guard';
import { User } from '../../../database/user.entity';
import { ConsoleLoggerService } from '../../../logger/console-logger.service';
import { User } from '../../../users/user.entity';
import { OpenApi } from '../../utils/openapi.decorator';
import { RequestUser } from '../../utils/request-user.decorator';

View file

@ -23,11 +23,11 @@ import {
import { ApiSecurity, ApiTags } from '@nestjs/swagger';
import { ApiTokenGuard } from '../../../api-token/api-token.guard';
import { User } from '../../../database/user.entity';
import { ConsoleLoggerService } from '../../../logger/console-logger.service';
import { AliasService } from '../../../notes/alias.service';
import { NotesService } from '../../../notes/notes.service';
import { PermissionsService } from '../../../permissions/permissions.service';
import { User } from '../../../users/user.entity';
import { OpenApi } from '../../utils/openapi.decorator';
import { RequestUser } from '../../utils/request-user.decorator';

View file

@ -23,6 +23,7 @@ import {
import { ApiSecurity, ApiTags } from '@nestjs/swagger';
import { ApiTokenGuard } from '../../../api-token/api-token.guard';
import { User } from '../../../database/user.entity';
import { HistoryEntryUpdateDto } from '../../../history/history-entry-update.dto';
import { HistoryEntryDto } from '../../../history/history-entry.dto';
import { HistoryService } from '../../../history/history.service';
@ -30,7 +31,6 @@ import { ConsoleLoggerService } from '../../../logger/console-logger.service';
import { MediaService } from '../../../media/media.service';
import { Note } from '../../../notes/note.entity';
import { NotesService } from '../../../notes/notes.service';
import { User } from '../../../users/user.entity';
import { UsersService } from '../../../users/users.service';
import { GetNoteInterceptor } from '../../utils/get-note.interceptor';
import { OpenApi } from '../../utils/openapi.decorator';

View file

@ -27,6 +27,7 @@ import {
import { Response } from 'express';
import { ApiTokenGuard } from '../../../api-token/api-token.guard';
import { User } from '../../../database/user.entity';
import { PermissionError } from '../../../errors/errors';
import { ConsoleLoggerService } from '../../../logger/console-logger.service';
import { MediaService } from '../../../media/media.service';
@ -36,7 +37,6 @@ import { PermissionsGuard } from '../../../permissions/permissions.guard';
import { PermissionsService } from '../../../permissions/permissions.service';
import { RequirePermission } from '../../../permissions/require-permission.decorator';
import { RequiredPermission } from '../../../permissions/required-permission.enum';
import { User } from '../../../users/user.entity';
import { NoteHeaderInterceptor } from '../../utils/note-header.interceptor';
import { OpenApi } from '../../utils/openapi.decorator';
import { RequestNote } from '../../utils/request-note.decorator';

View file

@ -34,6 +34,7 @@ import {
import { ApiSecurity, ApiTags } from '@nestjs/swagger';
import { ApiTokenGuard } from '../../../api-token/api-token.guard';
import { User } from '../../../database/user.entity';
import { NotInDBError } from '../../../errors/errors';
import { GroupsService } from '../../../groups/groups.service';
import { HistoryService } from '../../../history/history.service';
@ -46,7 +47,6 @@ import { PermissionsService } from '../../../permissions/permissions.service';
import { RequirePermission } from '../../../permissions/require-permission.decorator';
import { RequiredPermission } from '../../../permissions/required-permission.enum';
import { RevisionsService } from '../../../revisions/revisions.service';
import { User } from '../../../users/user.entity';
import { UsersService } from '../../../users/users.service';
import { GetNoteInterceptor } from '../../utils/get-note.interceptor';
import { MarkdownBody } from '../../utils/markdown-body.decorator';

View file

@ -5,9 +5,9 @@
*/
import { Request } from 'express';
import { User } from '../../database/user.entity';
import { Note } from '../../notes/note.entity';
import { SessionState } from '../../sessions/session.service';
import { User } from '../../users/user.entity';
export type CompleteRequest = Request & {
user?: User;

View file

@ -8,7 +8,7 @@ import { ConfigModule } from '@nestjs/config';
import { RouterModule, Routes } from '@nestjs/core';
import { EventEmitterModule } from '@nestjs/event-emitter';
import { ScheduleModule } from '@nestjs/schedule';
import { TypeOrmModule } from '@nestjs/typeorm';
import { KnexModule } from 'nestjs-knex';
import { ApiTokenModule } from './api-token/api-token.module';
import { PrivateApiModule } from './api/private/private-api.module';
@ -21,6 +21,7 @@ import cspConfig from './config/csp.config';
import customizationConfig from './config/customization.config';
import databaseConfig, {
PostgresDatabaseConfig,
getKnexConfig,
} from './config/database.config';
import externalConfig from './config/external-services.config';
import mediaConfig from './config/media.config';
@ -30,8 +31,8 @@ import { FrontendConfigModule } from './frontend-config/frontend-config.module';
import { FrontendConfigService } from './frontend-config/frontend-config.service';
import { GroupsModule } from './groups/groups.module';
import { HistoryModule } from './history/history.module';
import { KnexLoggerService } from './logger/knex-logger.service';
import { LoggerModule } from './logger/logger.module';
import { TypeormLoggerService } from './logger/typeorm-logger.service';
import { MediaRedirectModule } from './media-redirect/media-redirect.module';
import { MediaModule } from './media/media.module';
import { MonitoringModule } from './monitoring/monitoring.module';
@ -41,7 +42,6 @@ import { WebsocketModule } from './realtime/websocket/websocket.module';
import { RevisionsModule } from './revisions/revisions.module';
import { SessionModule } from './sessions/session.module';
import { UsersModule } from './users/users.module';
import { detectTsNode } from './utils/detectTsNode';
const routes: Routes = [
{
@ -61,31 +61,23 @@ const routes: Routes = [
@Module({
imports: [
RouterModule.register(routes),
TypeOrmModule.forRootAsync({
KnexModule.forRootAsync({
imports: [ConfigModule, LoggerModule],
inject: [databaseConfig.KEY, TypeormLoggerService],
inject: [databaseConfig.KEY, KnexLoggerService],
useFactory: (
databaseConfig: PostgresDatabaseConfig,
logger: TypeormLoggerService,
) => {
return {
type: databaseConfig.type,
host: databaseConfig.host,
port: databaseConfig.port,
username: databaseConfig.username,
password: databaseConfig.password,
database: databaseConfig.name,
autoLoadEntities: true,
logging: true,
logger: logger,
migrations: [
`**/migrations/${databaseConfig.type}-*.${
detectTsNode() ? 'ts' : 'js'
}`,
],
migrationsRun: true,
};
},
knexLoggerService: KnexLoggerService,
) => ({
config: {
...getKnexConfig(databaseConfig),
log: {
warn: knexLoggerService.warn.bind(knexLoggerService),
error: knexLoggerService.error.bind(knexLoggerService),
deprecate: knexLoggerService.deprecate.bind(knexLoggerService),
debug: knexLoggerService.debug.bind(knexLoggerService),
},
},
}),
}),
ConfigModule.forRoot({
load: [

View file

@ -6,8 +6,8 @@
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { User } from '../database/user.entity';
import { LoggerModule } from '../logger/logger.module';
import { User } from '../users/user.entity';
import { UsersModule } from '../users/users.module';
import { Identity } from './identity.entity';
import { IdentityService } from './identity.service';

View file

@ -1,97 +0,0 @@
/*
* SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { ProviderType } from '@hedgedoc/commons';
import {
Column,
CreateDateColumn,
Entity,
ManyToOne,
PrimaryGeneratedColumn,
UpdateDateColumn,
} from 'typeorm';
import { User } from '../users/user.entity';
/**
* The identity represents a single way for a user to login.
* A 'user' can have any number of these.
* Each one holds a type (local, github, etc.), if this type can have multiple instances (e.g. gitlab),
* it also saves the name of the instance. Also if this identity shall be the syncSource is saved.
*/
@Entity()
export class Identity {
@PrimaryGeneratedColumn()
id: number;
/**
* User that this identity corresponds to
*/
@ManyToOne((_) => User, (user) => user.identities, {
onDelete: 'CASCADE', // This deletes the Identity, when the associated User is deleted
})
user: Promise<User>;
/**
* The ProviderType of the identity
*/
@Column()
providerType: string;
/**
* The identifier of the provider.
* Only set if there are multiple providers of that type (e.g. OIDC)
*/
@Column({
nullable: true,
type: 'text',
})
providerIdentifier: string | null;
/**
* When the identity was created.
*/
@CreateDateColumn()
createdAt: Date;
/**
* When the identity was last updated.
*/
@UpdateDateColumn()
updatedAt: Date;
/**
* The unique identifier of a user from the login provider
*/
@Column({
nullable: true,
type: 'text',
})
providerUserId: string | null;
/**
* The hash of the password
* Only set when the type of the identity is local
*/
@Column({
nullable: true,
type: 'text',
})
passwordHash: string | null;
public static create(
user: User,
providerType: ProviderType,
providerIdentifier: string | null,
): Omit<Identity, 'id' | 'createdAt' | 'updatedAt'> {
const newIdentity = new Identity();
newIdentity.user = Promise.resolve(user);
newIdentity.providerType = providerType;
newIdentity.providerIdentifier = providerIdentifier;
newIdentity.providerUserId = null;
newIdentity.passwordHash = null;
return newIdentity;
}
}

View file

@ -17,9 +17,9 @@ import { InjectDataSource, InjectRepository } from '@nestjs/typeorm';
import { DataSource, Repository } from 'typeorm';
import AuthConfiguration, { AuthConfig } from '../config/auth.config';
import { User } from '../database/user.entity';
import { NotInDBError } from '../errors/errors';
import { ConsoleLoggerService } from '../logger/console-logger.service';
import { User } from '../users/user.entity';
import { UsersService } from '../users/users.service';
import { Identity } from './identity.entity';

View file

@ -23,13 +23,13 @@ import {
import { Repository } from 'typeorm';
import authConfiguration, { AuthConfig } from '../../config/auth.config';
import { User } from '../../database/user.entity';
import {
InvalidCredentialsError,
NoLocalIdentityError,
PasswordTooWeakError,
} from '../../errors/errors';
import { ConsoleLoggerService } from '../../logger/console-logger.service';
import { User } from '../../users/user.entity';
import { checkPassword, hashPassword } from '../../utils/password';
import { Identity } from '../identity.entity';
import { IdentityService } from '../identity.service';

View file

@ -1,69 +0,0 @@
/*
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import {
Column,
Entity,
ManyToOne,
OneToMany,
PrimaryGeneratedColumn,
} from 'typeorm';
import { Edit } from '../revisions/edit.entity';
import { Session } from '../sessions/session.entity';
import { User } from '../users/user.entity';
export type AuthorColor = number;
/**
* The author represents a single user editing a note.
* A 'user' can either be a registered and logged-in user or a browser session identified by its cookie.
* All edits of one user in a note must belong to the same author, so that the same color can be displayed.
*/
@Entity()
export class Author {
@PrimaryGeneratedColumn()
id: number;
/**
* The id of the color of this author
* The application maps the id to an actual color
*/
@Column({ type: 'int' })
color: AuthorColor;
/**
* A list of (browser) sessions this author has
* Only contains sessions for anonymous users, which don't have a user set
*/
@OneToMany(() => Session, (session) => session.author)
sessions: Promise<Session[]>;
/**
* User that this author corresponds to
* Only set when the user was identified (by a browser session) as a registered user at edit-time
*/
@ManyToOne(() => User, (user) => user.authors, { nullable: true })
user: Promise<User | null>;
/**
* List of edits that this author created
* All edits must belong to the same note
*/
@OneToMany(() => Edit, (edit) => edit.author)
edits: Promise<Edit[]>;
// eslint-disable-next-line @typescript-eslint/no-empty-function
private constructor() {}
public static create(color: number): Omit<Author, 'id'> {
const newAuthor = new Author();
newAuthor.color = color;
newAuthor.sessions = Promise.resolve([]);
newAuthor.user = Promise.resolve(null);
newAuthor.edits = Promise.resolve([]);
return newAuthor;
}
}

View file

@ -6,7 +6,6 @@
export enum DatabaseType {
POSTGRES = 'postgres',
MYSQL = 'mysql',
MARIADB = 'mariadb',
SQLITE = 'sqlite',
}

View file

@ -5,6 +5,7 @@
*/
import { registerAs } from '@nestjs/config';
import z from 'zod';
import { Knex } from 'knex';
import { DatabaseType } from './database-type.enum';
import { parseOptionalNumber } from './utils';
@ -90,3 +91,39 @@ export default registerAs('databaseConfig', () => {
}
return databaseConfig.data;
});
export function getKnexConfig(databaseConfig: DatabaseConfig): Knex.Config {
switch (databaseConfig.type) {
case DatabaseType.SQLITE:
return {
client: 'better-sqlite3',
connection: {
filename: databaseConfig.database,
},
};
case DatabaseType.POSTGRES:
return {
client: 'pg',
connection: {
host: databaseConfig.host,
port: databaseConfig.port,
user: databaseConfig.username,
database: databaseConfig.database,
password: databaseConfig.password,
// eslint-disable-next-line @typescript-eslint/naming-convention
application_name: 'HedgeDoc',
},
};
case DatabaseType.MARIADB:
return {
client: 'mysql',
connection: {
host: databaseConfig.host,
port: databaseConfig.port,
user: databaseConfig.username,
database: databaseConfig.database,
password: databaseConfig.password,
},
};
}
}

View file

@ -0,0 +1,22 @@
/*
* SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
/**
* An alias is a unique identifier for a {@link Note}, making the note accessible under /n/<alias>.
* A note can have unlimited user-defined aliases.
* The primary alias is the one used in URLs generated by HedgeDoc.
* For each note, there can only be one primary alias.
*/
export interface Alias {
/** The alias as defined by the user. Is unique. */
alias: string;
/** The id of the associated {@link Note}. */
noteId: number;
/** Whether the alias is the primary one for the note. */
isPrimary: boolean;
}

View file

@ -0,0 +1,30 @@
/*
* SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
/**
* An API token can be used to access the public API.
* A token can be created by a {@link User} and is valid until a specified date, at most two years in the future.
* A token has the following format: hd2.<id>.<secret>
*/
export interface ApiToken {
/** The id of the token, a short random ASCII string. Is unique */
id: string;
/** The {@link User} whose permissions the token has */
userId: number;
/** The user-defined label for the token, such as "CLI" */
label: string;
/** Hashed version of the token's secret */
secretHash: string;
/** Expiry date of the token */
validUntil: Date;
/** When the token was last used. When it was never used yet, this field is null */
lastUsedAt: Date | null;
}

View file

@ -0,0 +1,27 @@
/*
* SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
/**
* The AuthorshipInfo holds the information from where to where one {@link User} has changed a {@link Note}
*
* These AuthorshipInfos are combined in a {@link Revision}, which represents one save in the
* {@link Note}'s change history.
*/
export interface AuthorshipInfo {
/** The id of the {@link Revision} this belongs to. */
revisionId: number;
/** The id of the author of the edit. */
authorId: number;
/** The start position of the change in the note as a positive index. */
startPos: number;
/** The end position of the change in the note as a positive index. */
endPos: number;
/** The creation datetime of the edit. */
createdAt: Date;
}

View file

@ -0,0 +1,16 @@
/*
* SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
/**
* Maps {@link User}s to {@link Group}s
*/
export interface GroupUser {
/** The id of the {@link Group} a {@link User} is part of */
groupId: number;
/** The id of the {@link User} */
userId: number;
}

View file

@ -0,0 +1,24 @@
/*
* SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
/**
* A group represents one or multiple {@link User}s and can be used for permission management.
* There are special groups that are created by the system and cannot be deleted, these include the set of all
* registered users, as well as all unauthenticated users.
*/
export interface Group {
/** The unique id for internal referencing */
id: number;
/** The public identifier of the group (username for the group) */
name: string;
/** The display name of the group */
displayName: string;
/** Whether the group is one of the special groups */
isSpecial: boolean;
}

View file

@ -0,0 +1,32 @@
/*
* SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { ProviderType } from '../auth/provider-type.enum';
/**
* An auth identity holds the information how a {@link User} can authenticate themself using a certain auth provider
*/
export interface Identity {
/** The id of the user */
userId: number;
/** The type of the auth provider */
providerType: ProviderType;
/** The identifier of the auth provider, e.g. gitlab */
providerIdentifier: string | null;
/** Timestamp when this identity was created */
createdAt: Date;
/** Timestamp when this identity was last updated */
updatedAt: Date;
/** The remote id of the user at the auth provider or null for local identities */
providerUserId: string | null;
/** The hashed password for local identities or null for other auth providers */
passwordHash: string | null;
}

View file

@ -0,0 +1,83 @@
/*
* SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { Knex } from 'knex';
import { Alias } from './alias';
import { ApiToken } from './api-token';
import { AuthorshipInfo } from './authorship-info';
import { Group } from './group';
import { GroupUser } from './group-user';
import { Identity } from './identity';
import { MediaUpload } from './media-upload';
import { Note } from './note';
import { NoteGroupPermission } from './note-group-permission';
import { NoteUserPermission } from './note-user-permission';
import { Revision } from './revision';
import { RevisionTag } from './revision-tag';
import { User } from './user';
import { UserPinnedNote } from './user-pinned-note';
/* eslint-disable @typescript-eslint/naming-convention */
declare module 'knex/types/tables' {
interface Tables {
alias_composite: Knex.CompositeTableType<
Alias,
Alias,
Pick<Alias, 'isPrimary'>
>;
api_token_composite: Knex.CompositeTableType<
ApiToken,
Omit<ApiToken, 'createdAt' | 'lastUsedAt'>,
Pick<ApiToken, 'lastUsedAt'>
>;
authorship_info_composite: Knex.CompositeTableType<
AuthorshipInfo,
Omit<AuthorshipInfo, 'createdAt'>
>;
group_composite: Knex.CompositeTableType<
Group,
Omit<Group, 'id'>,
Pick<Group, 'name' | 'displayName'>
>;
group_user_composite: Knex.CompositeTableType<GroupUser>;
identity_composite: Knex.CompositeTableType<
Identity,
Omit<Identity, 'createdAt'>,
Pick<Identity, 'passwordHash' | 'updatedAt'>
>;
media_upload_composite: Knex.CompositeTableType<
MediaUpload,
Omit<MediaUpload, 'createdAt' | 'uuid'>,
Pick<MediaUpload, 'noteId'>
>;
note_composite: Knex.CompositeTableType<
Note,
Omit<Note, 'createdAt' | 'id'>,
Pick<Note, 'ownerId'>
>;
note_group_permission_composite: Knex.CompositeTableType<
NoteGroupPermission,
NoteGroupPermission,
Pick<NoteGroupPermission, 'canEdit'>
>;
note_user_permission_composite: Knex.CompositeTableType<
NoteUserPermission,
NoteUserPermission,
Pick<NoteUserPermission, 'canEdit'>
>;
revision_composite: Knex.CompositeTableType<
Revision,
Omit<Revision, 'createdAt' | 'id'>
>;
revision_tag_composite: Knex.CompositeTableType<RevisionTag>;
user_composite: Knex.CompositeTableType<
User,
Omit<User, 'id' | 'createdAt'>,
Pick<User, 'displayName' | 'photoUrl' | 'email' | 'authorStyle'>
>;
user_pinned_note_composite: Knex.CompositeTableType<UserPinnedNote>;
}
}

View file

@ -0,0 +1,34 @@
/*
* SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { BackendType } from '../media/backends/backend-type.enum';
/**
* A media upload object represents an uploaded file. While the file itself is stored in the configured storage backend,
* the metadata is stored in the database. Uploads are attached to the {@link Note} where they were uploaded, but can be
* detached by deleting the note and setting the option to keep the media files.
*/
export interface MediaUpload {
/** UUID (v7) identifying the media upload. Is public and unique */
uuid: string;
/** The id of the attached {@link Note} or null if the media upload was detached from a note */
noteId: number | null;
/** The id of the {@link User} who uploaded the media file */
userId: number;
/** The name of the uploaded file */
fileName: string;
/** The backend where this upload is stored */
backendType: BackendType;
/** Additional data required by the backend storage to identify the uploaded file */
backendData: string | null;
/** Timestamp when the file was uploaded */
createdAt: Date;
}

View file

@ -0,0 +1,18 @@
/*
* SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
/**
* Represents a permission for a {@link Note} to be accessed by a {@link Group}.
*/
export interface NoteGroupPermission {
/** The id of the {@link Group} to give the {@link Note} permission to. */
groupId: number;
/** The id of the {@link Note} to give the {@link Group} permission to. */
noteId: number;
/** Whether the {@link Group} can edit the {@link Note} or not. */
canEdit: boolean;
}

View file

@ -0,0 +1,18 @@
/*
* SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
/**
* Represents a permission for a {@link Note} to be accessed by a {@link User}.
*/
export interface NoteUserPermission {
/** The id of the {@link User} to give the {@link Note} permission to. */
noteId: number;
/** The id of the {@link Note} to give the {@link User} permission to. */
userId: number;
/** Whether the {@link User} can edit the {@link Note} or not. */
canEdit: boolean;
}

View file

@ -0,0 +1,24 @@
/*
* SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
/**
* Notes are the central part of HedgeDoc. A note is accessed and identified by a list of aliases.
* The note object itself only contains basic metadata about the note. The content is stored in note revisions.
* The permission management is extracted into the note-user and note-group permission tables.
*/
export interface Note {
/** The unique id of the note for internal referencing */
id: number;
/** The {@link User} id of the note owner */
ownerId: string;
/** The HedgeDoc major version this note was created in. This is used to migrate certain features from HD1 to HD2 */
version: number;
/** Timestamp when the note was created */
createdAt: Date;
}

View file

@ -0,0 +1,15 @@
/*
* SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
/**
* All {@link RevisionTag Tags} that are associated with a {@link Revision}.
*/
export interface RevisionTag {
/** The id of {@link Revision} the {@link RevisionTag Tags} are asspcoated with. */
revisionId: number;
/** The {@link RevisionTag Tag} text. */
tag: string;
}

View file

@ -0,0 +1,38 @@
/*
* SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { NoteType } from '@hedgedoc/commons';
/**
* A revision represents the content of a {@link Note} at a specific point in time.
*/
export interface Revision {
/** The unique id of the revision for internal referencing */
id: number;
/** The id of the note that this revision belongs to */
noteId: number;
/** The changes between this revision and the previous one in patch file format */
patch: string;
/** The content of the note at this revision */
content: string;
/** The stored Y.js state for realtime editing */
yjsStateVector: null | ArrayBuffer;
/** Whether the note is a document or presentation at this revision */
noteType: NoteType;
/** The extracted note title from this revision */
title: string;
/** The extracted description from this revision */
description: string;
/** Timestamp when this revision was created */
createdAt: Date;
}

View file

@ -0,0 +1,17 @@
/*
* SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
/**
* A user-pinned-note object represents a {@link Note} that a {@link User} has pinned on their explore page.
* Users can pin any arbitrary amount of notes.
*/
export interface UserPinnedNote {
/** The id of the {@link User} */
userId: number;
/** The id of the {@link Note} */
noteId: number;
}

View file

@ -0,0 +1,43 @@
/*
* SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import type { Username } from '../utils/username';
/**
* The user object represents either a registered user in the instance or a guest user.
*
* Registered users have a unique username and a display name. They can authenticate using auth identities
* attached to their account. A registered user can have one or more auth identities.
*
* Guest users are anonymous users that are only identified by a guest UUID. They have a limited set of features
* available and cannot authenticate otherwise than with their UUID. Anonymous note creation or media uploads
* create a guest user, or use an existing one if the UUID is known by the browser. The reason for guest users
* is to allow them to delete their own notes and media files without the need to register an account.
*/
export interface User {
/** The unique id of the user for internal referencing */
id: number;
/** The user's chosen username or null if it is a guest user */
username: Username | null;
/** The guest user's UUID or null if it is a registered user */
guestUuid: string | null;
/** The user's chosen display name */
displayName: string;
/** Timestamp when the user was created */
createdAt: Date;
/** URL to the user's profile picture if present */
photoUrl: string | null;
/** The user's email address if present */
email: string | null;
/** The index which author style (e.g. color) should be used for this user */
authorStyle: number;
}

View file

@ -1,58 +0,0 @@
/*
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import {
Column,
Entity,
JoinTable,
ManyToMany,
PrimaryGeneratedColumn,
} from 'typeorm';
import { User } from '../users/user.entity';
@Entity()
export class Group {
@PrimaryGeneratedColumn()
id: number;
@Column({
unique: true,
})
name: string;
@Column()
displayName: string;
/**
* Is set to denote a special group
* Special groups are used to map the old share settings like "everyone can edit"
* or "logged in users can view" to the group permission system
*/
@Column()
special: boolean;
@ManyToMany((_) => User, (user) => user.groups, {
eager: true,
})
@JoinTable()
members: Promise<User[]>;
// eslint-disable-next-line @typescript-eslint/no-empty-function
private constructor() {}
public static create(
name: string,
displayName: string,
special: boolean,
): Omit<Group, 'id'> {
const newGroup = new Group();
newGroup.name = name;
newGroup.displayName = displayName;
newGroup.special = special; // this attribute should only be true for the two special groups
newGroup.members = Promise.resolve([]);
return newGroup;
}
}

View file

@ -1,59 +0,0 @@
/*
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import {
Column,
Entity,
Index,
ManyToOne,
PrimaryGeneratedColumn,
UpdateDateColumn,
} from 'typeorm';
import { Note } from '../notes/note.entity';
import { User } from '../users/user.entity';
@Entity()
@Index(['note', 'user'], { unique: true })
export class HistoryEntry {
@PrimaryGeneratedColumn()
id: number;
@ManyToOne((_) => User, (user) => user.historyEntries, {
onDelete: 'CASCADE',
orphanedRowAction: 'delete', // This ensures the row of the history entry is deleted when no user references it anymore
})
user: Promise<User>;
@ManyToOne((_) => Note, (note) => note.historyEntries, {
onDelete: 'CASCADE',
orphanedRowAction: 'delete', // This ensures the row of the history entry is deleted when no note references it anymore
})
note: Promise<Note>;
@Column()
pinStatus: boolean;
@UpdateDateColumn()
updatedAt: Date;
/**
* Create a history entry
* @param user the user the history entry is associated with
* @param note the note the history entry is associated with
* @param [pinStatus=false] if the history entry should be pinned
*/
public static create(
user: User,
note: Note,
pinStatus = false,
): Omit<HistoryEntry, 'updatedAt'> {
const newHistoryEntry = new HistoryEntry();
newHistoryEntry.user = Promise.resolve(user);
newHistoryEntry.note = Promise.resolve(note);
newHistoryEntry.pinStatus = pinStatus;
return newHistoryEntry;
}
}

View file

@ -18,6 +18,7 @@ import appConfigMock from '../config/mock/app.config.mock';
import authConfigMock from '../config/mock/auth.config.mock';
import databaseConfigMock from '../config/mock/database.config.mock';
import noteConfigMock from '../config/mock/note.config.mock';
import { User } from '../database/user.entity';
import { NotInDBError } from '../errors/errors';
import { eventModuleConfig } from '../events';
import { Group } from '../groups/group.entity';
@ -33,7 +34,6 @@ import { Revision } from '../revisions/revision.entity';
import { RevisionsModule } from '../revisions/revisions.module';
import { RevisionsService } from '../revisions/revisions.service';
import { Session } from '../sessions/session.entity';
import { User } from '../users/user.entity';
import { UsersModule } from '../users/users.module';
import { mockSelectQueryBuilderInRepo } from '../utils/test-utils/mockSelectQueryBuilder';
import { HistoryEntryImportDto } from './history-entry-import.dto';

View file

@ -7,12 +7,12 @@ import { Injectable } from '@nestjs/common';
import { InjectConnection, InjectRepository } from '@nestjs/typeorm';
import { Connection, Repository } from 'typeorm';
import { User } from '../database/user.entity';
import { NotInDBError } from '../errors/errors';
import { ConsoleLoggerService } from '../logger/console-logger.service';
import { Note } from '../notes/note.entity';
import { NotesService } from '../notes/notes.service';
import { RevisionsService } from '../revisions/revisions.service';
import { User } from '../users/user.entity';
import { UsersService } from '../users/users.service';
import { HistoryEntryImportDto } from './history-entry-import.dto';
import { HistoryEntryUpdateDto } from './history-entry-update.dto';

View file

@ -3,9 +3,9 @@
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { User } from '../database/user.entity';
import { Alias } from '../notes/alias.entity';
import { Note } from '../notes/note.entity';
import { User } from '../users/user.entity';
import { HistoryEntry } from './history-entry.entity';
import { getIdentifier } from './utils';

View file

@ -0,0 +1,34 @@
/*
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*
* The code in this class is based on:
* https://github.com/typeorm/typeorm/blob/master/src/logger/AdvancedConsoleLogger.ts
*/
import { Injectable } from '@nestjs/common';
import { ConsoleLoggerService } from './console-logger.service';
@Injectable()
export class KnexLoggerService {
constructor(private readonly logger: ConsoleLoggerService) {
this.logger.setContext('Knex');
}
warn(message: string): void {
this.logger.warn(message);
}
error(message: string): void {
this.logger.error(message);
}
deprecate(message: string): void {
this.logger.warn(message);
}
debug(message: string): void {
this.logger.debug(message);
}
}

View file

@ -6,10 +6,10 @@
import { Module } from '@nestjs/common';
import { ConsoleLoggerService } from './console-logger.service';
import { TypeormLoggerService } from './typeorm-logger.service';
import { KnexLoggerService } from './knex-logger.service';
@Module({
providers: [ConsoleLoggerService, TypeormLoggerService],
exports: [ConsoleLoggerService, TypeormLoggerService],
providers: [ConsoleLoggerService, KnexLoggerService],
exports: [ConsoleLoggerService, KnexLoggerService],
})
export class LoggerModule {}

View file

@ -1,98 +0,0 @@
/*
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*
* The code in this class is based on:
* https://github.com/typeorm/typeorm/blob/master/src/logger/AdvancedConsoleLogger.ts
*/
import { Injectable } from '@nestjs/common';
import { Logger, QueryRunner } from 'typeorm';
import { PlatformTools } from 'typeorm/platform/PlatformTools';
import { ConsoleLoggerService } from './console-logger.service';
@Injectable()
export class TypeormLoggerService implements Logger {
constructor(private readonly logger: ConsoleLoggerService) {
this.logger.setContext('TypeORM');
this.logger.setSkipColor(true);
}
log(level: 'log' | 'info' | 'warn', message: unknown, _?: QueryRunner): void {
switch (level) {
case 'log':
case 'info':
this.logger.log(message);
break;
case 'warn':
this.logger.warn(message);
}
}
logMigration(message: string, _?: QueryRunner): void {
// eslint-disable-next-line local-rules/correct-logger-context
this.logger.log(message, 'migration');
}
logQuery(query: string, parameters?: unknown[], _?: QueryRunner): void {
const sql =
query +
(parameters && parameters.length
? ' -- PARAMETERS: ' + this.stringifyParams(parameters)
: '');
// eslint-disable-next-line local-rules/correct-logger-context
this.logger.debug(PlatformTools.highlightSql(sql), 'query');
}
logQueryError(
error: string | Error,
query: string,
parameters?: unknown[],
_?: QueryRunner,
): void {
const sql =
query +
(parameters && parameters.length
? ` -- PARAMETERS: ${this.stringifyParams(parameters)}`
: '');
this.logger.debug(PlatformTools.highlightSql(sql));
// eslint-disable-next-line local-rules/correct-logger-context
this.logger.debug(error.toString(), 'queryError');
}
logQuerySlow(
time: number,
query: string,
parameters?: unknown[],
_?: QueryRunner,
): void {
const sql =
query +
(parameters && parameters.length
? ` -- PARAMETERS: ${this.stringifyParams(parameters)}`
: '');
/* eslint-disable local-rules/correct-logger-context */
this.logger.warn(PlatformTools.highlightSql(sql), 'querySlow');
this.logger.warn(`execution time: ${time}`, 'querySlow');
/* eslint-enable local-rules/correct-logger-context */
}
logSchemaBuild(message: string, _?: QueryRunner): void {
// eslint-disable-next-line local-rules/correct-logger-context
this.logger.debug(message, 'schemaBuild');
}
/**
* Converts parameters to a string.
* Sometimes parameters can have circular objects and therefore we are handle this case too.
*/
protected stringifyParams(parameters: unknown[]): string {
try {
return JSON.stringify(parameters);
} catch (error) {
// most probably circular objects in parameters
return parameters.toString();
}
}
}

View file

@ -1,94 +0,0 @@
/*
* SPDX-FileCopyrightText: 2024 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import {
Column,
CreateDateColumn,
Entity,
ManyToOne,
PrimaryColumn,
} from 'typeorm';
import { Note } from '../notes/note.entity';
import { User } from '../users/user.entity';
import { BackendType } from './backends/backend-type.enum';
@Entity()
export class MediaUpload {
/** The unique identifier of a media upload */
@PrimaryColumn()
uuid: string;
/**
* The note where a media file was uploaded, required for the media browser in the note editor.
* Can be set to null after creation when the note was deleted without the associated uploads
*/
@ManyToOne((_) => Note, (note) => note.mediaUploads, {
nullable: true,
})
note: Promise<Note | null>;
/** The user who uploaded the media file or {@code null} if uploaded by a guest user */
@ManyToOne((_) => User, (user) => user.mediaUploads, {
nullable: true,
})
user: Promise<User | null>;
/** The original filename of the media upload */
@Column()
fileName: string;
/** The backend type where this upload is stored */
@Column({
nullable: false,
})
backendType: string;
/**
* Additional data, depending on the backend type, serialized as JSON.
* This can include for example required additional identifiers for retrieving the file from the backend or to
* delete the file afterward again.
*/
@Column({
nullable: true,
type: 'text',
})
backendData: string | null;
/** The date when the upload was created */
@CreateDateColumn()
createdAt: Date;
// eslint-disable-next-line @typescript-eslint/no-empty-function
private constructor() {}
/**
* Create a new media upload entity
*
* @param uuid the unique identifier of the upload
* @param fileName the original filename of the uploaded file
* @param note the note the upload should be associated with. This is required despite the fact the note field is optional, because it's possible to delete a note without also deleting the associated media uploads, but a note is required for the initial creation.
* @param user the user that owns the upload
* @param backendType on which type of media backend the upload is saved
* @param backendData the backend data returned by the media backend
*/
public static create(
uuid: string,
fileName: string,
note: Note,
user: User | null,
backendType: BackendType,
backendData: string | null,
): Omit<MediaUpload, 'createdAt'> {
const upload = new MediaUpload();
upload.uuid = uuid;
upload.fileName = fileName;
upload.note = Promise.resolve(note);
upload.user = Promise.resolve(user);
upload.backendType = backendType;
upload.backendData = backendData;
return upload;
}
}

View file

@ -18,6 +18,7 @@ import authConfigMock from '../config/mock/auth.config.mock';
import databaseConfigMock from '../config/mock/database.config.mock';
import mediaConfigMock from '../config/mock/media.config.mock';
import noteConfigMock from '../config/mock/note.config.mock';
import { User } from '../database/user.entity';
import { ClientError, NotInDBError } from '../errors/errors';
import { eventModuleConfig } from '../events';
import { Group } from '../groups/group.entity';
@ -31,7 +32,6 @@ import { NoteUserPermission } from '../permissions/note-user-permission.entity';
import { Edit } from '../revisions/edit.entity';
import { Revision } from '../revisions/revision.entity';
import { Session } from '../sessions/session.entity';
import { User } from '../users/user.entity';
import { UsersModule } from '../users/users.module';
import { BackendType } from './backends/backend-type.enum';
import { FilesystemBackend } from './backends/filesystem-backend';

View file

@ -12,10 +12,10 @@ import { Repository } from 'typeorm';
import { v7 as uuidV7 } from 'uuid';
import mediaConfiguration, { MediaConfig } from '../config/media.config';
import { User } from '../database/user.entity';
import { ClientError, NotInDBError } from '../errors/errors';
import { ConsoleLoggerService } from '../logger/console-logger.service';
import { Note } from '../notes/note.entity';
import { User } from '../users/user.entity';
import { AzureBackend } from './backends/azure-backend';
import { BackendType } from './backends/backend-type.enum';
import { FilesystemBackend } from './backends/filesystem-backend';

View file

@ -1,268 +0,0 @@
/*
* SPDX-FileCopyrightText: 2024 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { MigrationInterface, QueryRunner } from 'typeorm';
export class Init1726271650566 implements MigrationInterface {
name = 'Init1726271650566';
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`CREATE TABLE \`api_token\` (\`id\` int NOT NULL AUTO_INCREMENT, \`keyId\` varchar(255) NOT NULL, \`label\` varchar(255) NOT NULL, \`createdAt\` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6), \`hash\` varchar(255) NOT NULL, \`validUntil\` datetime NOT NULL, \`lastUsedAt\` date NULL, \`userId\` int NULL, UNIQUE INDEX \`IDX_3e254e2eb542a65da7c405d068\` (\`keyId\`), UNIQUE INDEX \`IDX_60221392192b32c7560c128a6f\` (\`hash\`), PRIMARY KEY (\`id\`)) ENGINE=InnoDB`,
);
await queryRunner.query(
`CREATE TABLE \`history_entry\` (\`id\` int NOT NULL AUTO_INCREMENT, \`pinStatus\` tinyint NOT NULL, \`updatedAt\` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6), \`userId\` int NULL, \`noteId\` int NULL, UNIQUE INDEX \`IDX_928dd947355b0837366470a916\` (\`noteId\`, \`userId\`), PRIMARY KEY (\`id\`)) ENGINE=InnoDB`,
);
await queryRunner.query(
`CREATE TABLE \`media_upload\` (\`uuid\` varchar(255) NOT NULL, \`fileName\` varchar(255) NOT NULL, \`backendType\` varchar(255) NOT NULL, \`backendData\` text NULL, \`createdAt\` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6), \`noteId\` int NULL, \`userId\` int NULL, PRIMARY KEY (\`uuid\`)) ENGINE=InnoDB`,
);
await queryRunner.query(
`CREATE TABLE \`group\` (\`id\` int NOT NULL AUTO_INCREMENT, \`name\` varchar(255) NOT NULL, \`displayName\` varchar(255) NOT NULL, \`special\` tinyint NOT NULL, UNIQUE INDEX \`IDX_8a45300fd825918f3b40195fbd\` (\`name\`), PRIMARY KEY (\`id\`)) ENGINE=InnoDB`,
);
await queryRunner.query(
`CREATE TABLE \`note_group_permission\` (\`id\` int NOT NULL AUTO_INCREMENT, \`canEdit\` tinyint NOT NULL, \`groupId\` int NULL, \`noteId\` int NULL, UNIQUE INDEX \`IDX_ee1744842a9ef3ffbc05a7016a\` (\`groupId\`, \`noteId\`), PRIMARY KEY (\`id\`)) ENGINE=InnoDB`,
);
await queryRunner.query(
`CREATE TABLE \`note_user_permission\` (\`id\` int NOT NULL AUTO_INCREMENT, \`canEdit\` tinyint NOT NULL, \`userId\` int NULL, \`noteId\` int NULL, UNIQUE INDEX \`IDX_5a3e91233d8878f98f5ad86b71\` (\`userId\`, \`noteId\`), PRIMARY KEY (\`id\`)) ENGINE=InnoDB`,
);
await queryRunner.query(
`CREATE TABLE \`alias\` (\`id\` int NOT NULL AUTO_INCREMENT, \`name\` varchar(255) NOT NULL, \`primary\` tinyint NULL COMMENT 'This field tells you if this is the primary alias of the note. If this field is null, that means this alias is not primary.', \`noteId\` int NULL, UNIQUE INDEX \`IDX_89f27e45cc5c1e43abd9132c9b\` (\`name\`), UNIQUE INDEX \`Only one primary alias per note\` (\`noteId\`, \`primary\`), PRIMARY KEY (\`id\`)) ENGINE=InnoDB`,
);
await queryRunner.query(
`CREATE TABLE \`note\` (\`id\` int NOT NULL AUTO_INCREMENT, \`publicId\` text NOT NULL, \`viewCount\` int NOT NULL DEFAULT '0', \`version\` int NOT NULL DEFAULT '2', \`createdAt\` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6), \`ownerId\` int NULL, PRIMARY KEY (\`id\`)) ENGINE=InnoDB`,
);
await queryRunner.query(
`CREATE TABLE \`tag\` (\`id\` int NOT NULL AUTO_INCREMENT, \`name\` varchar(255) NOT NULL, PRIMARY KEY (\`id\`)) ENGINE=InnoDB`,
);
await queryRunner.query(
`CREATE TABLE \`revision\` (\`id\` int NOT NULL AUTO_INCREMENT, \`patch\` text NOT NULL, \`title\` text NOT NULL, \`description\` text NOT NULL, \`content\` text NOT NULL, \`length\` int NOT NULL, \`yjsStateVector\` text NULL, \`createdAt\` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6), \`noteId\` int NULL, PRIMARY KEY (\`id\`)) ENGINE=InnoDB`,
);
await queryRunner.query(
`CREATE TABLE \`edit\` (\`id\` int NOT NULL AUTO_INCREMENT, \`startPos\` int NOT NULL, \`endPos\` int NOT NULL, \`createdAt\` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6), \`updatedAt\` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6), \`authorId\` int NULL, PRIMARY KEY (\`id\`)) ENGINE=InnoDB`,
);
await queryRunner.query(
`CREATE TABLE \`session\` (\`id\` varchar(255) NOT NULL, \`expiredAt\` bigint NOT NULL, \`json\` text NOT NULL, \`destroyedAt\` datetime(6) NULL, \`authorId\` int NULL, INDEX \`IDX_28c5d1d16da7908c97c9bc2f74\` (\`expiredAt\`), PRIMARY KEY (\`id\`)) ENGINE=InnoDB`,
);
await queryRunner.query(
`CREATE TABLE \`author\` (\`id\` int NOT NULL AUTO_INCREMENT, \`color\` int NOT NULL, \`userId\` int NULL, PRIMARY KEY (\`id\`)) ENGINE=InnoDB`,
);
await queryRunner.query(
`CREATE TABLE \`user\` (\`id\` int NOT NULL AUTO_INCREMENT, \`username\` varchar(255) NOT NULL, \`displayName\` varchar(255) NOT NULL, \`createdAt\` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6), \`updatedAt\` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6), \`photo\` text NULL, \`email\` text NULL, UNIQUE INDEX \`IDX_78a916df40e02a9deb1c4b75ed\` (\`username\`), PRIMARY KEY (\`id\`)) ENGINE=InnoDB`,
);
await queryRunner.query(
`CREATE TABLE \`identity\` (\`id\` int NOT NULL AUTO_INCREMENT, \`providerType\` varchar(255) NOT NULL, \`providerIdentifier\` text NULL, \`createdAt\` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6), \`updatedAt\` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6), \`providerUserId\` text NULL, \`passwordHash\` text NULL, \`userId\` int NULL, PRIMARY KEY (\`id\`)) ENGINE=InnoDB`,
);
await queryRunner.query(
`CREATE TABLE \`group_members_user\` (\`groupId\` int NOT NULL, \`userId\` int NOT NULL, INDEX \`IDX_bfa303089d367a2e3c02b002b8\` (\`groupId\`), INDEX \`IDX_427107c650638bcb2f1e167d2e\` (\`userId\`), PRIMARY KEY (\`groupId\`, \`userId\`)) ENGINE=InnoDB`,
);
await queryRunner.query(
`CREATE TABLE \`revision_tags_tag\` (\`revisionId\` int NOT NULL, \`tagId\` int NOT NULL, INDEX \`IDX_3382f45eefeb40f91e45cfd418\` (\`revisionId\`), INDEX \`IDX_19dbafe2a8b456c0ef40858d49\` (\`tagId\`), PRIMARY KEY (\`revisionId\`, \`tagId\`)) ENGINE=InnoDB`,
);
await queryRunner.query(
`CREATE TABLE \`revision_edits_edit\` (\`revisionId\` int NOT NULL, \`editId\` int NOT NULL, INDEX \`IDX_52c6a61e1a646768391c7854fe\` (\`revisionId\`), INDEX \`IDX_470886feb50e30114e39c42698\` (\`editId\`), PRIMARY KEY (\`revisionId\`, \`editId\`)) ENGINE=InnoDB`,
);
await queryRunner.query(
`ALTER TABLE \`api_token\` ADD CONSTRAINT \`FK_cbfc4e2b85b78207afb0b2d7fbc\` FOREIGN KEY (\`userId\`) REFERENCES \`user\`(\`id\`) ON DELETE CASCADE ON UPDATE NO ACTION`,
);
await queryRunner.query(
`ALTER TABLE \`history_entry\` ADD CONSTRAINT \`FK_42b8ae461cb58747a24340e6c64\` FOREIGN KEY (\`userId\`) REFERENCES \`user\`(\`id\`) ON DELETE CASCADE ON UPDATE NO ACTION`,
);
await queryRunner.query(
`ALTER TABLE \`history_entry\` ADD CONSTRAINT \`FK_8f3595373fc9f6a32f126270422\` FOREIGN KEY (\`noteId\`) REFERENCES \`note\`(\`id\`) ON DELETE CASCADE ON UPDATE NO ACTION`,
);
await queryRunner.query(
`ALTER TABLE \`media_upload\` ADD CONSTRAINT \`FK_edba6d4e0f3bcf6605772f0af6b\` FOREIGN KEY (\`noteId\`) REFERENCES \`note\`(\`id\`) ON DELETE NO ACTION ON UPDATE NO ACTION`,
);
await queryRunner.query(
`ALTER TABLE \`media_upload\` ADD CONSTRAINT \`FK_73ce66b082df1df2003e305e9ac\` FOREIGN KEY (\`userId\`) REFERENCES \`user\`(\`id\`) ON DELETE NO ACTION ON UPDATE NO ACTION`,
);
await queryRunner.query(
`ALTER TABLE \`note_group_permission\` ADD CONSTRAINT \`FK_743ea3d9e0e26d7cbb9c174e56b\` FOREIGN KEY (\`groupId\`) REFERENCES \`group\`(\`id\`) ON DELETE CASCADE ON UPDATE NO ACTION`,
);
await queryRunner.query(
`ALTER TABLE \`note_group_permission\` ADD CONSTRAINT \`FK_fd5c9329d8b45cb160676f8d8c1\` FOREIGN KEY (\`noteId\`) REFERENCES \`note\`(\`id\`) ON DELETE CASCADE ON UPDATE NO ACTION`,
);
await queryRunner.query(
`ALTER TABLE \`note_user_permission\` ADD CONSTRAINT \`FK_03cea81e07bab8864de026d517d\` FOREIGN KEY (\`userId\`) REFERENCES \`user\`(\`id\`) ON DELETE CASCADE ON UPDATE NO ACTION`,
);
await queryRunner.query(
`ALTER TABLE \`note_user_permission\` ADD CONSTRAINT \`FK_bc1ca3a87a9d662350d281a7f16\` FOREIGN KEY (\`noteId\`) REFERENCES \`note\`(\`id\`) ON DELETE CASCADE ON UPDATE NO ACTION`,
);
await queryRunner.query(
`ALTER TABLE \`alias\` ADD CONSTRAINT \`FK_63012a303e6ca53144a8b7b64b0\` FOREIGN KEY (\`noteId\`) REFERENCES \`note\`(\`id\`) ON DELETE CASCADE ON UPDATE NO ACTION`,
);
await queryRunner.query(
`ALTER TABLE \`note\` ADD CONSTRAINT \`FK_b09836eba01a8653c0628a78af8\` FOREIGN KEY (\`ownerId\`) REFERENCES \`user\`(\`id\`) ON DELETE CASCADE ON UPDATE NO ACTION`,
);
await queryRunner.query(
`ALTER TABLE \`revision\` ADD CONSTRAINT \`FK_8ac498c7c70de43d01b94fe7905\` FOREIGN KEY (\`noteId\`) REFERENCES \`note\`(\`id\`) ON DELETE CASCADE ON UPDATE NO ACTION`,
);
await queryRunner.query(
`ALTER TABLE \`edit\` ADD CONSTRAINT \`FK_bbab22ed1a0e243b28623f4f48a\` FOREIGN KEY (\`authorId\`) REFERENCES \`author\`(\`id\`) ON DELETE NO ACTION ON UPDATE NO ACTION`,
);
await queryRunner.query(
`ALTER TABLE \`session\` ADD CONSTRAINT \`FK_e5da4837ed9d236532b3215a84e\` FOREIGN KEY (\`authorId\`) REFERENCES \`author\`(\`id\`) ON DELETE NO ACTION ON UPDATE NO ACTION`,
);
await queryRunner.query(
`ALTER TABLE \`author\` ADD CONSTRAINT \`FK_645811deaaaa772f9e6c2a4b927\` FOREIGN KEY (\`userId\`) REFERENCES \`user\`(\`id\`) ON DELETE NO ACTION ON UPDATE NO ACTION`,
);
await queryRunner.query(
`ALTER TABLE \`identity\` ADD CONSTRAINT \`FK_12915039d2868ab654567bf5181\` FOREIGN KEY (\`userId\`) REFERENCES \`user\`(\`id\`) ON DELETE CASCADE ON UPDATE NO ACTION`,
);
await queryRunner.query(
`ALTER TABLE \`group_members_user\` ADD CONSTRAINT \`FK_bfa303089d367a2e3c02b002b8f\` FOREIGN KEY (\`groupId\`) REFERENCES \`group\`(\`id\`) ON DELETE CASCADE ON UPDATE CASCADE`,
);
await queryRunner.query(
`ALTER TABLE \`group_members_user\` ADD CONSTRAINT \`FK_427107c650638bcb2f1e167d2e5\` FOREIGN KEY (\`userId\`) REFERENCES \`user\`(\`id\`) ON DELETE NO ACTION ON UPDATE NO ACTION`,
);
await queryRunner.query(
`ALTER TABLE \`revision_tags_tag\` ADD CONSTRAINT \`FK_3382f45eefeb40f91e45cfd4180\` FOREIGN KEY (\`revisionId\`) REFERENCES \`revision\`(\`id\`) ON DELETE CASCADE ON UPDATE CASCADE`,
);
await queryRunner.query(
`ALTER TABLE \`revision_tags_tag\` ADD CONSTRAINT \`FK_19dbafe2a8b456c0ef40858d49f\` FOREIGN KEY (\`tagId\`) REFERENCES \`tag\`(\`id\`) ON DELETE NO ACTION ON UPDATE NO ACTION`,
);
await queryRunner.query(
`ALTER TABLE \`revision_edits_edit\` ADD CONSTRAINT \`FK_52c6a61e1a646768391c7854feb\` FOREIGN KEY (\`revisionId\`) REFERENCES \`revision\`(\`id\`) ON DELETE CASCADE ON UPDATE CASCADE`,
);
await queryRunner.query(
`ALTER TABLE \`revision_edits_edit\` ADD CONSTRAINT \`FK_470886feb50e30114e39c426987\` FOREIGN KEY (\`editId\`) REFERENCES \`edit\`(\`id\`) ON DELETE NO ACTION ON UPDATE NO ACTION`,
);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`ALTER TABLE \`revision_edits_edit\` DROP FOREIGN KEY \`FK_470886feb50e30114e39c426987\``,
);
await queryRunner.query(
`ALTER TABLE \`revision_edits_edit\` DROP FOREIGN KEY \`FK_52c6a61e1a646768391c7854feb\``,
);
await queryRunner.query(
`ALTER TABLE \`revision_tags_tag\` DROP FOREIGN KEY \`FK_19dbafe2a8b456c0ef40858d49f\``,
);
await queryRunner.query(
`ALTER TABLE \`revision_tags_tag\` DROP FOREIGN KEY \`FK_3382f45eefeb40f91e45cfd4180\``,
);
await queryRunner.query(
`ALTER TABLE \`group_members_user\` DROP FOREIGN KEY \`FK_427107c650638bcb2f1e167d2e5\``,
);
await queryRunner.query(
`ALTER TABLE \`group_members_user\` DROP FOREIGN KEY \`FK_bfa303089d367a2e3c02b002b8f\``,
);
await queryRunner.query(
`ALTER TABLE \`identity\` DROP FOREIGN KEY \`FK_12915039d2868ab654567bf5181\``,
);
await queryRunner.query(
`ALTER TABLE \`author\` DROP FOREIGN KEY \`FK_645811deaaaa772f9e6c2a4b927\``,
);
await queryRunner.query(
`ALTER TABLE \`session\` DROP FOREIGN KEY \`FK_e5da4837ed9d236532b3215a84e\``,
);
await queryRunner.query(
`ALTER TABLE \`edit\` DROP FOREIGN KEY \`FK_bbab22ed1a0e243b28623f4f48a\``,
);
await queryRunner.query(
`ALTER TABLE \`revision\` DROP FOREIGN KEY \`FK_8ac498c7c70de43d01b94fe7905\``,
);
await queryRunner.query(
`ALTER TABLE \`note\` DROP FOREIGN KEY \`FK_b09836eba01a8653c0628a78af8\``,
);
await queryRunner.query(
`ALTER TABLE \`alias\` DROP FOREIGN KEY \`FK_63012a303e6ca53144a8b7b64b0\``,
);
await queryRunner.query(
`ALTER TABLE \`note_user_permission\` DROP FOREIGN KEY \`FK_bc1ca3a87a9d662350d281a7f16\``,
);
await queryRunner.query(
`ALTER TABLE \`note_user_permission\` DROP FOREIGN KEY \`FK_03cea81e07bab8864de026d517d\``,
);
await queryRunner.query(
`ALTER TABLE \`note_group_permission\` DROP FOREIGN KEY \`FK_fd5c9329d8b45cb160676f8d8c1\``,
);
await queryRunner.query(
`ALTER TABLE \`note_group_permission\` DROP FOREIGN KEY \`FK_743ea3d9e0e26d7cbb9c174e56b\``,
);
await queryRunner.query(
`ALTER TABLE \`media_upload\` DROP FOREIGN KEY \`FK_73ce66b082df1df2003e305e9ac\``,
);
await queryRunner.query(
`ALTER TABLE \`media_upload\` DROP FOREIGN KEY \`FK_edba6d4e0f3bcf6605772f0af6b\``,
);
await queryRunner.query(
`ALTER TABLE \`history_entry\` DROP FOREIGN KEY \`FK_8f3595373fc9f6a32f126270422\``,
);
await queryRunner.query(
`ALTER TABLE \`history_entry\` DROP FOREIGN KEY \`FK_42b8ae461cb58747a24340e6c64\``,
);
await queryRunner.query(
`ALTER TABLE \`api_token\` DROP FOREIGN KEY \`FK_cbfc4e2b85b78207afb0b2d7fbc\``,
);
await queryRunner.query(
`DROP INDEX \`IDX_470886feb50e30114e39c42698\` ON \`revision_edits_edit\``,
);
await queryRunner.query(
`DROP INDEX \`IDX_52c6a61e1a646768391c7854fe\` ON \`revision_edits_edit\``,
);
await queryRunner.query(`DROP TABLE \`revision_edits_edit\``);
await queryRunner.query(
`DROP INDEX \`IDX_19dbafe2a8b456c0ef40858d49\` ON \`revision_tags_tag\``,
);
await queryRunner.query(
`DROP INDEX \`IDX_3382f45eefeb40f91e45cfd418\` ON \`revision_tags_tag\``,
);
await queryRunner.query(`DROP TABLE \`revision_tags_tag\``);
await queryRunner.query(
`DROP INDEX \`IDX_427107c650638bcb2f1e167d2e\` ON \`group_members_user\``,
);
await queryRunner.query(
`DROP INDEX \`IDX_bfa303089d367a2e3c02b002b8\` ON \`group_members_user\``,
);
await queryRunner.query(`DROP TABLE \`group_members_user\``);
await queryRunner.query(`DROP TABLE \`identity\``);
await queryRunner.query(
`DROP INDEX \`IDX_78a916df40e02a9deb1c4b75ed\` ON \`user\``,
);
await queryRunner.query(`DROP TABLE \`user\``);
await queryRunner.query(`DROP TABLE \`author\``);
await queryRunner.query(
`DROP INDEX \`IDX_28c5d1d16da7908c97c9bc2f74\` ON \`session\``,
);
await queryRunner.query(`DROP TABLE \`session\``);
await queryRunner.query(`DROP TABLE \`edit\``);
await queryRunner.query(`DROP TABLE \`revision\``);
await queryRunner.query(`DROP TABLE \`tag\``);
await queryRunner.query(`DROP TABLE \`note\``);
await queryRunner.query(
`DROP INDEX \`Only one primary alias per note\` ON \`alias\``,
);
await queryRunner.query(
`DROP INDEX \`IDX_89f27e45cc5c1e43abd9132c9b\` ON \`alias\``,
);
await queryRunner.query(`DROP TABLE \`alias\``);
await queryRunner.query(
`DROP INDEX \`IDX_5a3e91233d8878f98f5ad86b71\` ON \`note_user_permission\``,
);
await queryRunner.query(`DROP TABLE \`note_user_permission\``);
await queryRunner.query(
`DROP INDEX \`IDX_ee1744842a9ef3ffbc05a7016a\` ON \`note_group_permission\``,
);
await queryRunner.query(`DROP TABLE \`note_group_permission\``);
await queryRunner.query(
`DROP INDEX \`IDX_8a45300fd825918f3b40195fbd\` ON \`group\``,
);
await queryRunner.query(`DROP TABLE \`group\``);
await queryRunner.query(`DROP TABLE \`media_upload\``);
await queryRunner.query(
`DROP INDEX \`IDX_928dd947355b0837366470a916\` ON \`history_entry\``,
);
await queryRunner.query(`DROP TABLE \`history_entry\``);
await queryRunner.query(
`DROP INDEX \`IDX_60221392192b32c7560c128a6f\` ON \`api_token\``,
);
await queryRunner.query(
`DROP INDEX \`IDX_3e254e2eb542a65da7c405d068\` ON \`api_token\``,
);
await queryRunner.query(`DROP TABLE \`api_token\``);
}
}

View file

@ -1,280 +0,0 @@
/*
* SPDX-FileCopyrightText: 2024 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { MigrationInterface, QueryRunner } from 'typeorm';
export class Init1726271794336 implements MigrationInterface {
name = 'Init1726271794336';
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`CREATE TABLE "api_token" ("id" SERIAL NOT NULL, "keyId" character varying NOT NULL, "label" character varying NOT NULL, "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "hash" character varying NOT NULL, "validUntil" TIMESTAMP NOT NULL, "lastUsedAt" date, "userId" integer, CONSTRAINT "UQ_3e254e2eb542a65da7c405d0683" UNIQUE ("keyId"), CONSTRAINT "UQ_60221392192b32c7560c128a6fa" UNIQUE ("hash"), CONSTRAINT "PK_d862311c568d175c26f41bc6f98" PRIMARY KEY ("id"))`,
);
await queryRunner.query(
`CREATE TABLE "history_entry" ("id" SERIAL NOT NULL, "pinStatus" boolean NOT NULL, "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "userId" integer, "noteId" integer, CONSTRAINT "PK_b65bd95b0d2929668589d57b97a" PRIMARY KEY ("id"))`,
);
await queryRunner.query(
`CREATE UNIQUE INDEX "IDX_928dd947355b0837366470a916" ON "history_entry" ("noteId", "userId") `,
);
await queryRunner.query(
`CREATE TABLE "media_upload" ("uuid" character varying NOT NULL, "fileName" character varying NOT NULL, "backendType" character varying NOT NULL, "backendData" text, "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "noteId" integer, "userId" integer, CONSTRAINT "PK_573c2a4f2a8f8382f2a8758444e" PRIMARY KEY ("uuid"))`,
);
await queryRunner.query(
`CREATE TABLE "group" ("id" SERIAL NOT NULL, "name" character varying NOT NULL, "displayName" character varying NOT NULL, "special" boolean NOT NULL, CONSTRAINT "UQ_8a45300fd825918f3b40195fbdc" UNIQUE ("name"), CONSTRAINT "PK_256aa0fda9b1de1a73ee0b7106b" PRIMARY KEY ("id"))`,
);
await queryRunner.query(
`CREATE TABLE "note_group_permission" ("id" SERIAL NOT NULL, "canEdit" boolean NOT NULL, "groupId" integer, "noteId" integer, CONSTRAINT "PK_6327989190949e6a55d02a080c3" PRIMARY KEY ("id"))`,
);
await queryRunner.query(
`CREATE UNIQUE INDEX "IDX_ee1744842a9ef3ffbc05a7016a" ON "note_group_permission" ("groupId", "noteId") `,
);
await queryRunner.query(
`CREATE TABLE "note_user_permission" ("id" SERIAL NOT NULL, "canEdit" boolean NOT NULL, "userId" integer, "noteId" integer, CONSTRAINT "PK_672af1e0c89837f0aa390d34b8d" PRIMARY KEY ("id"))`,
);
await queryRunner.query(
`CREATE UNIQUE INDEX "IDX_5a3e91233d8878f98f5ad86b71" ON "note_user_permission" ("userId", "noteId") `,
);
await queryRunner.query(
`CREATE TABLE "alias" ("id" SERIAL NOT NULL, "name" character varying NOT NULL, "primary" boolean, "noteId" integer, CONSTRAINT "UQ_89f27e45cc5c1e43abd9132c9b9" UNIQUE ("name"), CONSTRAINT "Only one primary alias per note" UNIQUE ("noteId", "primary"), CONSTRAINT "PK_b1848d04b41d10a5712fc2e673c" PRIMARY KEY ("id")); COMMENT ON COLUMN "alias"."primary" IS 'This field tells you if this is the primary alias of the note. If this field is null, that means this alias is not primary.'`,
);
await queryRunner.query(
`CREATE TABLE "note" ("id" SERIAL NOT NULL, "publicId" text NOT NULL, "viewCount" integer NOT NULL DEFAULT '0', "version" integer NOT NULL DEFAULT '2', "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "ownerId" integer, CONSTRAINT "PK_96d0c172a4fba276b1bbed43058" PRIMARY KEY ("id"))`,
);
await queryRunner.query(
`CREATE TABLE "tag" ("id" SERIAL NOT NULL, "name" character varying NOT NULL, CONSTRAINT "PK_8e4052373c579afc1471f526760" PRIMARY KEY ("id"))`,
);
await queryRunner.query(
`CREATE TABLE "revision" ("id" SERIAL NOT NULL, "patch" text NOT NULL, "title" text NOT NULL, "description" text NOT NULL, "content" text NOT NULL, "length" integer NOT NULL, "yjsStateVector" text, "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "noteId" integer, CONSTRAINT "PK_f4767cdf0c0e78041514e5a94be" PRIMARY KEY ("id"))`,
);
await queryRunner.query(
`CREATE TABLE "edit" ("id" SERIAL NOT NULL, "startPos" integer NOT NULL, "endPos" integer NOT NULL, "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "authorId" integer, CONSTRAINT "PK_062a4b68154c101205e4595810d" PRIMARY KEY ("id"))`,
);
await queryRunner.query(
`CREATE TABLE "session" ("id" character varying(255) NOT NULL, "expiredAt" bigint NOT NULL, "json" text NOT NULL, "destroyedAt" TIMESTAMP, "authorId" integer, CONSTRAINT "PK_f55da76ac1c3ac420f444d2ff11" PRIMARY KEY ("id"))`,
);
await queryRunner.query(
`CREATE INDEX "IDX_28c5d1d16da7908c97c9bc2f74" ON "session" ("expiredAt") `,
);
await queryRunner.query(
`CREATE TABLE "author" ("id" SERIAL NOT NULL, "color" integer NOT NULL, "userId" integer, CONSTRAINT "PK_5a0e79799d372fe56f2f3fa6871" PRIMARY KEY ("id"))`,
);
await queryRunner.query(
`CREATE TABLE "user" ("id" SERIAL NOT NULL, "username" character varying NOT NULL, "displayName" character varying NOT NULL, "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "photo" text, "email" text, CONSTRAINT "UQ_78a916df40e02a9deb1c4b75edb" UNIQUE ("username"), CONSTRAINT "PK_cace4a159ff9f2512dd42373760" PRIMARY KEY ("id"))`,
);
await queryRunner.query(
`CREATE TABLE "identity" ("id" SERIAL NOT NULL, "providerType" character varying NOT NULL, "providerIdentifier" text, "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "providerUserId" text, "passwordHash" text, "userId" integer, CONSTRAINT "PK_ff16a44186b286d5e626178f726" PRIMARY KEY ("id"))`,
);
await queryRunner.query(
`CREATE TABLE "group_members_user" ("groupId" integer NOT NULL, "userId" integer NOT NULL, CONSTRAINT "PK_7170c9a27e7b823d391d9e11f2e" PRIMARY KEY ("groupId", "userId"))`,
);
await queryRunner.query(
`CREATE INDEX "IDX_bfa303089d367a2e3c02b002b8" ON "group_members_user" ("groupId") `,
);
await queryRunner.query(
`CREATE INDEX "IDX_427107c650638bcb2f1e167d2e" ON "group_members_user" ("userId") `,
);
await queryRunner.query(
`CREATE TABLE "revision_tags_tag" ("revisionId" integer NOT NULL, "tagId" integer NOT NULL, CONSTRAINT "PK_006354d3ecad6cb1e606320647b" PRIMARY KEY ("revisionId", "tagId"))`,
);
await queryRunner.query(
`CREATE INDEX "IDX_3382f45eefeb40f91e45cfd418" ON "revision_tags_tag" ("revisionId") `,
);
await queryRunner.query(
`CREATE INDEX "IDX_19dbafe2a8b456c0ef40858d49" ON "revision_tags_tag" ("tagId") `,
);
await queryRunner.query(
`CREATE TABLE "revision_edits_edit" ("revisionId" integer NOT NULL, "editId" integer NOT NULL, CONSTRAINT "PK_2651ab66ea1c428e844a80d81ed" PRIMARY KEY ("revisionId", "editId"))`,
);
await queryRunner.query(
`CREATE INDEX "IDX_52c6a61e1a646768391c7854fe" ON "revision_edits_edit" ("revisionId") `,
);
await queryRunner.query(
`CREATE INDEX "IDX_470886feb50e30114e39c42698" ON "revision_edits_edit" ("editId") `,
);
await queryRunner.query(
`ALTER TABLE "api_token" ADD CONSTRAINT "FK_cbfc4e2b85b78207afb0b2d7fbc" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`,
);
await queryRunner.query(
`ALTER TABLE "history_entry" ADD CONSTRAINT "FK_42b8ae461cb58747a24340e6c64" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`,
);
await queryRunner.query(
`ALTER TABLE "history_entry" ADD CONSTRAINT "FK_8f3595373fc9f6a32f126270422" FOREIGN KEY ("noteId") REFERENCES "note"("id") ON DELETE CASCADE ON UPDATE NO ACTION`,
);
await queryRunner.query(
`ALTER TABLE "media_upload" ADD CONSTRAINT "FK_edba6d4e0f3bcf6605772f0af6b" FOREIGN KEY ("noteId") REFERENCES "note"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`,
);
await queryRunner.query(
`ALTER TABLE "media_upload" ADD CONSTRAINT "FK_73ce66b082df1df2003e305e9ac" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`,
);
await queryRunner.query(
`ALTER TABLE "note_group_permission" ADD CONSTRAINT "FK_743ea3d9e0e26d7cbb9c174e56b" FOREIGN KEY ("groupId") REFERENCES "group"("id") ON DELETE CASCADE ON UPDATE NO ACTION`,
);
await queryRunner.query(
`ALTER TABLE "note_group_permission" ADD CONSTRAINT "FK_fd5c9329d8b45cb160676f8d8c1" FOREIGN KEY ("noteId") REFERENCES "note"("id") ON DELETE CASCADE ON UPDATE NO ACTION`,
);
await queryRunner.query(
`ALTER TABLE "note_user_permission" ADD CONSTRAINT "FK_03cea81e07bab8864de026d517d" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`,
);
await queryRunner.query(
`ALTER TABLE "note_user_permission" ADD CONSTRAINT "FK_bc1ca3a87a9d662350d281a7f16" FOREIGN KEY ("noteId") REFERENCES "note"("id") ON DELETE CASCADE ON UPDATE NO ACTION`,
);
await queryRunner.query(
`ALTER TABLE "alias" ADD CONSTRAINT "FK_63012a303e6ca53144a8b7b64b0" FOREIGN KEY ("noteId") REFERENCES "note"("id") ON DELETE CASCADE ON UPDATE NO ACTION`,
);
await queryRunner.query(
`ALTER TABLE "note" ADD CONSTRAINT "FK_b09836eba01a8653c0628a78af8" FOREIGN KEY ("ownerId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`,
);
await queryRunner.query(
`ALTER TABLE "revision" ADD CONSTRAINT "FK_8ac498c7c70de43d01b94fe7905" FOREIGN KEY ("noteId") REFERENCES "note"("id") ON DELETE CASCADE ON UPDATE NO ACTION`,
);
await queryRunner.query(
`ALTER TABLE "edit" ADD CONSTRAINT "FK_bbab22ed1a0e243b28623f4f48a" FOREIGN KEY ("authorId") REFERENCES "author"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`,
);
await queryRunner.query(
`ALTER TABLE "session" ADD CONSTRAINT "FK_e5da4837ed9d236532b3215a84e" FOREIGN KEY ("authorId") REFERENCES "author"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`,
);
await queryRunner.query(
`ALTER TABLE "author" ADD CONSTRAINT "FK_645811deaaaa772f9e6c2a4b927" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`,
);
await queryRunner.query(
`ALTER TABLE "identity" ADD CONSTRAINT "FK_12915039d2868ab654567bf5181" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`,
);
await queryRunner.query(
`ALTER TABLE "group_members_user" ADD CONSTRAINT "FK_bfa303089d367a2e3c02b002b8f" FOREIGN KEY ("groupId") REFERENCES "group"("id") ON DELETE CASCADE ON UPDATE CASCADE`,
);
await queryRunner.query(
`ALTER TABLE "group_members_user" ADD CONSTRAINT "FK_427107c650638bcb2f1e167d2e5" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`,
);
await queryRunner.query(
`ALTER TABLE "revision_tags_tag" ADD CONSTRAINT "FK_3382f45eefeb40f91e45cfd4180" FOREIGN KEY ("revisionId") REFERENCES "revision"("id") ON DELETE CASCADE ON UPDATE CASCADE`,
);
await queryRunner.query(
`ALTER TABLE "revision_tags_tag" ADD CONSTRAINT "FK_19dbafe2a8b456c0ef40858d49f" FOREIGN KEY ("tagId") REFERENCES "tag"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`,
);
await queryRunner.query(
`ALTER TABLE "revision_edits_edit" ADD CONSTRAINT "FK_52c6a61e1a646768391c7854feb" FOREIGN KEY ("revisionId") REFERENCES "revision"("id") ON DELETE CASCADE ON UPDATE CASCADE`,
);
await queryRunner.query(
`ALTER TABLE "revision_edits_edit" ADD CONSTRAINT "FK_470886feb50e30114e39c426987" FOREIGN KEY ("editId") REFERENCES "edit"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`,
);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`ALTER TABLE "revision_edits_edit" DROP CONSTRAINT "FK_470886feb50e30114e39c426987"`,
);
await queryRunner.query(
`ALTER TABLE "revision_edits_edit" DROP CONSTRAINT "FK_52c6a61e1a646768391c7854feb"`,
);
await queryRunner.query(
`ALTER TABLE "revision_tags_tag" DROP CONSTRAINT "FK_19dbafe2a8b456c0ef40858d49f"`,
);
await queryRunner.query(
`ALTER TABLE "revision_tags_tag" DROP CONSTRAINT "FK_3382f45eefeb40f91e45cfd4180"`,
);
await queryRunner.query(
`ALTER TABLE "group_members_user" DROP CONSTRAINT "FK_427107c650638bcb2f1e167d2e5"`,
);
await queryRunner.query(
`ALTER TABLE "group_members_user" DROP CONSTRAINT "FK_bfa303089d367a2e3c02b002b8f"`,
);
await queryRunner.query(
`ALTER TABLE "identity" DROP CONSTRAINT "FK_12915039d2868ab654567bf5181"`,
);
await queryRunner.query(
`ALTER TABLE "author" DROP CONSTRAINT "FK_645811deaaaa772f9e6c2a4b927"`,
);
await queryRunner.query(
`ALTER TABLE "session" DROP CONSTRAINT "FK_e5da4837ed9d236532b3215a84e"`,
);
await queryRunner.query(
`ALTER TABLE "edit" DROP CONSTRAINT "FK_bbab22ed1a0e243b28623f4f48a"`,
);
await queryRunner.query(
`ALTER TABLE "revision" DROP CONSTRAINT "FK_8ac498c7c70de43d01b94fe7905"`,
);
await queryRunner.query(
`ALTER TABLE "note" DROP CONSTRAINT "FK_b09836eba01a8653c0628a78af8"`,
);
await queryRunner.query(
`ALTER TABLE "alias" DROP CONSTRAINT "FK_63012a303e6ca53144a8b7b64b0"`,
);
await queryRunner.query(
`ALTER TABLE "note_user_permission" DROP CONSTRAINT "FK_bc1ca3a87a9d662350d281a7f16"`,
);
await queryRunner.query(
`ALTER TABLE "note_user_permission" DROP CONSTRAINT "FK_03cea81e07bab8864de026d517d"`,
);
await queryRunner.query(
`ALTER TABLE "note_group_permission" DROP CONSTRAINT "FK_fd5c9329d8b45cb160676f8d8c1"`,
);
await queryRunner.query(
`ALTER TABLE "note_group_permission" DROP CONSTRAINT "FK_743ea3d9e0e26d7cbb9c174e56b"`,
);
await queryRunner.query(
`ALTER TABLE "media_upload" DROP CONSTRAINT "FK_73ce66b082df1df2003e305e9ac"`,
);
await queryRunner.query(
`ALTER TABLE "media_upload" DROP CONSTRAINT "FK_edba6d4e0f3bcf6605772f0af6b"`,
);
await queryRunner.query(
`ALTER TABLE "history_entry" DROP CONSTRAINT "FK_8f3595373fc9f6a32f126270422"`,
);
await queryRunner.query(
`ALTER TABLE "history_entry" DROP CONSTRAINT "FK_42b8ae461cb58747a24340e6c64"`,
);
await queryRunner.query(
`ALTER TABLE "api_token" DROP CONSTRAINT "FK_cbfc4e2b85b78207afb0b2d7fbc"`,
);
await queryRunner.query(
`DROP INDEX "public"."IDX_470886feb50e30114e39c42698"`,
);
await queryRunner.query(
`DROP INDEX "public"."IDX_52c6a61e1a646768391c7854fe"`,
);
await queryRunner.query(`DROP TABLE "revision_edits_edit"`);
await queryRunner.query(
`DROP INDEX "public"."IDX_19dbafe2a8b456c0ef40858d49"`,
);
await queryRunner.query(
`DROP INDEX "public"."IDX_3382f45eefeb40f91e45cfd418"`,
);
await queryRunner.query(`DROP TABLE "revision_tags_tag"`);
await queryRunner.query(
`DROP INDEX "public"."IDX_427107c650638bcb2f1e167d2e"`,
);
await queryRunner.query(
`DROP INDEX "public"."IDX_bfa303089d367a2e3c02b002b8"`,
);
await queryRunner.query(`DROP TABLE "group_members_user"`);
await queryRunner.query(`DROP TABLE "identity"`);
await queryRunner.query(`DROP TABLE "user"`);
await queryRunner.query(`DROP TABLE "author"`);
await queryRunner.query(
`DROP INDEX "public"."IDX_28c5d1d16da7908c97c9bc2f74"`,
);
await queryRunner.query(`DROP TABLE "session"`);
await queryRunner.query(`DROP TABLE "edit"`);
await queryRunner.query(`DROP TABLE "revision"`);
await queryRunner.query(`DROP TABLE "tag"`);
await queryRunner.query(`DROP TABLE "note"`);
await queryRunner.query(`DROP TABLE "alias"`);
await queryRunner.query(
`DROP INDEX "public"."IDX_5a3e91233d8878f98f5ad86b71"`,
);
await queryRunner.query(`DROP TABLE "note_user_permission"`);
await queryRunner.query(
`DROP INDEX "public"."IDX_ee1744842a9ef3ffbc05a7016a"`,
);
await queryRunner.query(`DROP TABLE "note_group_permission"`);
await queryRunner.query(`DROP TABLE "group"`);
await queryRunner.query(`DROP TABLE "media_upload"`);
await queryRunner.query(
`DROP INDEX "public"."IDX_928dd947355b0837366470a916"`,
);
await queryRunner.query(`DROP TABLE "history_entry"`);
await queryRunner.query(`DROP TABLE "api_token"`);
}
}

View file

@ -1,496 +0,0 @@
/*
* SPDX-FileCopyrightText: 2024 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { MigrationInterface, QueryRunner } from 'typeorm';
export class Init1726271503150 implements MigrationInterface {
name = 'Init1726271503150';
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`CREATE TABLE "api_token" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "keyId" varchar NOT NULL, "label" varchar NOT NULL, "createdAt" datetime NOT NULL DEFAULT (datetime('now')), "hash" varchar NOT NULL, "validUntil" datetime NOT NULL, "lastUsedAt" date, "userId" integer, CONSTRAINT "UQ_3e254e2eb542a65da7c405d0683" UNIQUE ("keyId"), CONSTRAINT "UQ_60221392192b32c7560c128a6fa" UNIQUE ("hash"))`,
);
await queryRunner.query(
`CREATE TABLE "history_entry" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "pinStatus" boolean NOT NULL, "updatedAt" datetime NOT NULL DEFAULT (datetime('now')), "userId" integer, "noteId" integer)`,
);
await queryRunner.query(
`CREATE UNIQUE INDEX "IDX_928dd947355b0837366470a916" ON "history_entry" ("noteId", "userId") `,
);
await queryRunner.query(
`CREATE TABLE "media_upload" ("uuid" varchar PRIMARY KEY NOT NULL, "fileName" varchar NOT NULL, "backendType" varchar NOT NULL, "backendData" text, "createdAt" datetime NOT NULL DEFAULT (datetime('now')), "noteId" integer, "userId" integer)`,
);
await queryRunner.query(
`CREATE TABLE "group" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "name" varchar NOT NULL, "displayName" varchar NOT NULL, "special" boolean NOT NULL, CONSTRAINT "UQ_8a45300fd825918f3b40195fbdc" UNIQUE ("name"))`,
);
await queryRunner.query(
`CREATE TABLE "note_group_permission" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "canEdit" boolean NOT NULL, "groupId" integer, "noteId" integer)`,
);
await queryRunner.query(
`CREATE UNIQUE INDEX "IDX_ee1744842a9ef3ffbc05a7016a" ON "note_group_permission" ("groupId", "noteId") `,
);
await queryRunner.query(
`CREATE TABLE "note_user_permission" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "canEdit" boolean NOT NULL, "userId" integer, "noteId" integer)`,
);
await queryRunner.query(
`CREATE UNIQUE INDEX "IDX_5a3e91233d8878f98f5ad86b71" ON "note_user_permission" ("userId", "noteId") `,
);
await queryRunner.query(
`CREATE TABLE "alias" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "name" varchar NOT NULL, "primary" boolean, "noteId" integer, CONSTRAINT "UQ_89f27e45cc5c1e43abd9132c9b9" UNIQUE ("name"), CONSTRAINT "Only one primary alias per note" UNIQUE ("noteId", "primary"))`,
);
await queryRunner.query(
`CREATE TABLE "note" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "publicId" text NOT NULL, "viewCount" integer NOT NULL DEFAULT (0), "version" integer NOT NULL DEFAULT (2), "createdAt" datetime NOT NULL DEFAULT (datetime('now')), "ownerId" integer)`,
);
await queryRunner.query(
`CREATE TABLE "tag" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "name" varchar NOT NULL)`,
);
await queryRunner.query(
`CREATE TABLE "revision" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "patch" text NOT NULL, "title" text NOT NULL, "description" text NOT NULL, "content" text NOT NULL, "length" integer NOT NULL, "yjsStateVector" text, "createdAt" datetime NOT NULL DEFAULT (datetime('now')), "noteId" integer)`,
);
await queryRunner.query(
`CREATE TABLE "edit" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "startPos" integer NOT NULL, "endPos" integer NOT NULL, "createdAt" datetime NOT NULL DEFAULT (datetime('now')), "updatedAt" datetime NOT NULL DEFAULT (datetime('now')), "authorId" integer)`,
);
await queryRunner.query(
`CREATE TABLE "session" ("id" varchar(255) PRIMARY KEY NOT NULL, "expiredAt" bigint NOT NULL, "json" text NOT NULL, "destroyedAt" datetime, "authorId" integer)`,
);
await queryRunner.query(
`CREATE INDEX "IDX_28c5d1d16da7908c97c9bc2f74" ON "session" ("expiredAt") `,
);
await queryRunner.query(
`CREATE TABLE "author" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "color" integer NOT NULL, "userId" integer)`,
);
await queryRunner.query(
`CREATE TABLE "user" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "username" varchar NOT NULL, "displayName" varchar NOT NULL, "createdAt" datetime NOT NULL DEFAULT (datetime('now')), "updatedAt" datetime NOT NULL DEFAULT (datetime('now')), "photo" text, "email" text, CONSTRAINT "UQ_78a916df40e02a9deb1c4b75edb" UNIQUE ("username"))`,
);
await queryRunner.query(
`CREATE TABLE "identity" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "providerType" varchar NOT NULL, "providerIdentifier" text, "createdAt" datetime NOT NULL DEFAULT (datetime('now')), "updatedAt" datetime NOT NULL DEFAULT (datetime('now')), "providerUserId" text, "passwordHash" text, "userId" integer)`,
);
await queryRunner.query(
`CREATE TABLE "group_members_user" ("groupId" integer NOT NULL, "userId" integer NOT NULL, PRIMARY KEY ("groupId", "userId"))`,
);
await queryRunner.query(
`CREATE INDEX "IDX_bfa303089d367a2e3c02b002b8" ON "group_members_user" ("groupId") `,
);
await queryRunner.query(
`CREATE INDEX "IDX_427107c650638bcb2f1e167d2e" ON "group_members_user" ("userId") `,
);
await queryRunner.query(
`CREATE TABLE "revision_tags_tag" ("revisionId" integer NOT NULL, "tagId" integer NOT NULL, PRIMARY KEY ("revisionId", "tagId"))`,
);
await queryRunner.query(
`CREATE INDEX "IDX_3382f45eefeb40f91e45cfd418" ON "revision_tags_tag" ("revisionId") `,
);
await queryRunner.query(
`CREATE INDEX "IDX_19dbafe2a8b456c0ef40858d49" ON "revision_tags_tag" ("tagId") `,
);
await queryRunner.query(
`CREATE TABLE "revision_edits_edit" ("revisionId" integer NOT NULL, "editId" integer NOT NULL, PRIMARY KEY ("revisionId", "editId"))`,
);
await queryRunner.query(
`CREATE INDEX "IDX_52c6a61e1a646768391c7854fe" ON "revision_edits_edit" ("revisionId") `,
);
await queryRunner.query(
`CREATE INDEX "IDX_470886feb50e30114e39c42698" ON "revision_edits_edit" ("editId") `,
);
await queryRunner.query(
`CREATE TABLE "temporary_api_token" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "keyId" varchar NOT NULL, "label" varchar NOT NULL, "createdAt" datetime NOT NULL DEFAULT (datetime('now')), "hash" varchar NOT NULL, "validUntil" datetime NOT NULL, "lastUsedAt" date, "userId" integer, CONSTRAINT "UQ_3e254e2eb542a65da7c405d0683" UNIQUE ("keyId"), CONSTRAINT "UQ_60221392192b32c7560c128a6fa" UNIQUE ("hash"), CONSTRAINT "FK_cbfc4e2b85b78207afb0b2d7fbc" FOREIGN KEY ("userId") REFERENCES "user" ("id") ON DELETE CASCADE ON UPDATE NO ACTION)`,
);
await queryRunner.query(
`INSERT INTO "temporary_api_token"("id", "keyId", "label", "createdAt", "hash", "validUntil", "lastUsedAt", "userId") SELECT "id", "keyId", "label", "createdAt", "hash", "validUntil", "lastUsedAt", "userId" FROM "api_token"`,
);
await queryRunner.query(`DROP TABLE "api_token"`);
await queryRunner.query(
`ALTER TABLE "temporary_api_token" RENAME TO "api_token"`,
);
await queryRunner.query(`DROP INDEX "IDX_928dd947355b0837366470a916"`);
await queryRunner.query(
`CREATE TABLE "temporary_history_entry" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "pinStatus" boolean NOT NULL, "updatedAt" datetime NOT NULL DEFAULT (datetime('now')), "userId" integer, "noteId" integer, CONSTRAINT "FK_42b8ae461cb58747a24340e6c64" FOREIGN KEY ("userId") REFERENCES "user" ("id") ON DELETE CASCADE ON UPDATE NO ACTION, CONSTRAINT "FK_8f3595373fc9f6a32f126270422" FOREIGN KEY ("noteId") REFERENCES "note" ("id") ON DELETE CASCADE ON UPDATE NO ACTION)`,
);
await queryRunner.query(
`INSERT INTO "temporary_history_entry"("id", "pinStatus", "updatedAt", "userId", "noteId") SELECT "id", "pinStatus", "updatedAt", "userId", "noteId" FROM "history_entry"`,
);
await queryRunner.query(`DROP TABLE "history_entry"`);
await queryRunner.query(
`ALTER TABLE "temporary_history_entry" RENAME TO "history_entry"`,
);
await queryRunner.query(
`CREATE UNIQUE INDEX "IDX_928dd947355b0837366470a916" ON "history_entry" ("noteId", "userId") `,
);
await queryRunner.query(
`CREATE TABLE "temporary_media_upload" ("uuid" varchar PRIMARY KEY NOT NULL, "fileName" varchar NOT NULL, "backendType" varchar NOT NULL, "backendData" text, "createdAt" datetime NOT NULL DEFAULT (datetime('now')), "noteId" integer, "userId" integer, CONSTRAINT "FK_edba6d4e0f3bcf6605772f0af6b" FOREIGN KEY ("noteId") REFERENCES "note" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION, CONSTRAINT "FK_73ce66b082df1df2003e305e9ac" FOREIGN KEY ("userId") REFERENCES "user" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION)`,
);
await queryRunner.query(
`INSERT INTO "temporary_media_upload"("uuid", "fileName", "backendType", "backendData", "createdAt", "noteId", "userId") SELECT "uuid", "fileName", "backendType", "backendData", "createdAt", "noteId", "userId" FROM "media_upload"`,
);
await queryRunner.query(`DROP TABLE "media_upload"`);
await queryRunner.query(
`ALTER TABLE "temporary_media_upload" RENAME TO "media_upload"`,
);
await queryRunner.query(`DROP INDEX "IDX_ee1744842a9ef3ffbc05a7016a"`);
await queryRunner.query(
`CREATE TABLE "temporary_note_group_permission" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "canEdit" boolean NOT NULL, "groupId" integer, "noteId" integer, CONSTRAINT "FK_743ea3d9e0e26d7cbb9c174e56b" FOREIGN KEY ("groupId") REFERENCES "group" ("id") ON DELETE CASCADE ON UPDATE NO ACTION, CONSTRAINT "FK_fd5c9329d8b45cb160676f8d8c1" FOREIGN KEY ("noteId") REFERENCES "note" ("id") ON DELETE CASCADE ON UPDATE NO ACTION)`,
);
await queryRunner.query(
`INSERT INTO "temporary_note_group_permission"("id", "canEdit", "groupId", "noteId") SELECT "id", "canEdit", "groupId", "noteId" FROM "note_group_permission"`,
);
await queryRunner.query(`DROP TABLE "note_group_permission"`);
await queryRunner.query(
`ALTER TABLE "temporary_note_group_permission" RENAME TO "note_group_permission"`,
);
await queryRunner.query(
`CREATE UNIQUE INDEX "IDX_ee1744842a9ef3ffbc05a7016a" ON "note_group_permission" ("groupId", "noteId") `,
);
await queryRunner.query(`DROP INDEX "IDX_5a3e91233d8878f98f5ad86b71"`);
await queryRunner.query(
`CREATE TABLE "temporary_note_user_permission" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "canEdit" boolean NOT NULL, "userId" integer, "noteId" integer, CONSTRAINT "FK_03cea81e07bab8864de026d517d" FOREIGN KEY ("userId") REFERENCES "user" ("id") ON DELETE CASCADE ON UPDATE NO ACTION, CONSTRAINT "FK_bc1ca3a87a9d662350d281a7f16" FOREIGN KEY ("noteId") REFERENCES "note" ("id") ON DELETE CASCADE ON UPDATE NO ACTION)`,
);
await queryRunner.query(
`INSERT INTO "temporary_note_user_permission"("id", "canEdit", "userId", "noteId") SELECT "id", "canEdit", "userId", "noteId" FROM "note_user_permission"`,
);
await queryRunner.query(`DROP TABLE "note_user_permission"`);
await queryRunner.query(
`ALTER TABLE "temporary_note_user_permission" RENAME TO "note_user_permission"`,
);
await queryRunner.query(
`CREATE UNIQUE INDEX "IDX_5a3e91233d8878f98f5ad86b71" ON "note_user_permission" ("userId", "noteId") `,
);
await queryRunner.query(
`CREATE TABLE "temporary_alias" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "name" varchar NOT NULL, "primary" boolean, "noteId" integer, CONSTRAINT "UQ_89f27e45cc5c1e43abd9132c9b9" UNIQUE ("name"), CONSTRAINT "Only one primary alias per note" UNIQUE ("noteId", "primary"), CONSTRAINT "FK_63012a303e6ca53144a8b7b64b0" FOREIGN KEY ("noteId") REFERENCES "note" ("id") ON DELETE CASCADE ON UPDATE NO ACTION)`,
);
await queryRunner.query(
`INSERT INTO "temporary_alias"("id", "name", "primary", "noteId") SELECT "id", "name", "primary", "noteId" FROM "alias"`,
);
await queryRunner.query(`DROP TABLE "alias"`);
await queryRunner.query(`ALTER TABLE "temporary_alias" RENAME TO "alias"`);
await queryRunner.query(
`CREATE TABLE "temporary_note" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "publicId" text NOT NULL, "viewCount" integer NOT NULL DEFAULT (0), "version" integer NOT NULL DEFAULT (2), "createdAt" datetime NOT NULL DEFAULT (datetime('now')), "ownerId" integer, CONSTRAINT "FK_b09836eba01a8653c0628a78af8" FOREIGN KEY ("ownerId") REFERENCES "user" ("id") ON DELETE CASCADE ON UPDATE NO ACTION)`,
);
await queryRunner.query(
`INSERT INTO "temporary_note"("id", "publicId", "viewCount", "version", "createdAt", "ownerId") SELECT "id", "publicId", "viewCount", "version", "createdAt", "ownerId" FROM "note"`,
);
await queryRunner.query(`DROP TABLE "note"`);
await queryRunner.query(`ALTER TABLE "temporary_note" RENAME TO "note"`);
await queryRunner.query(
`CREATE TABLE "temporary_revision" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "patch" text NOT NULL, "title" text NOT NULL, "description" text NOT NULL, "content" text NOT NULL, "length" integer NOT NULL, "yjsStateVector" text, "createdAt" datetime NOT NULL DEFAULT (datetime('now')), "noteId" integer, CONSTRAINT "FK_8ac498c7c70de43d01b94fe7905" FOREIGN KEY ("noteId") REFERENCES "note" ("id") ON DELETE CASCADE ON UPDATE NO ACTION)`,
);
await queryRunner.query(
`INSERT INTO "temporary_revision"("id", "patch", "title", "description", "content", "length", "yjsStateVector", "createdAt", "noteId") SELECT "id", "patch", "title", "description", "content", "length", "yjsStateVector", "createdAt", "noteId" FROM "revision"`,
);
await queryRunner.query(`DROP TABLE "revision"`);
await queryRunner.query(
`ALTER TABLE "temporary_revision" RENAME TO "revision"`,
);
await queryRunner.query(
`CREATE TABLE "temporary_edit" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "startPos" integer NOT NULL, "endPos" integer NOT NULL, "createdAt" datetime NOT NULL DEFAULT (datetime('now')), "updatedAt" datetime NOT NULL DEFAULT (datetime('now')), "authorId" integer, CONSTRAINT "FK_bbab22ed1a0e243b28623f4f48a" FOREIGN KEY ("authorId") REFERENCES "author" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION)`,
);
await queryRunner.query(
`INSERT INTO "temporary_edit"("id", "startPos", "endPos", "createdAt", "updatedAt", "authorId") SELECT "id", "startPos", "endPos", "createdAt", "updatedAt", "authorId" FROM "edit"`,
);
await queryRunner.query(`DROP TABLE "edit"`);
await queryRunner.query(`ALTER TABLE "temporary_edit" RENAME TO "edit"`);
await queryRunner.query(`DROP INDEX "IDX_28c5d1d16da7908c97c9bc2f74"`);
await queryRunner.query(
`CREATE TABLE "temporary_session" ("id" varchar(255) PRIMARY KEY NOT NULL, "expiredAt" bigint NOT NULL, "json" text NOT NULL, "destroyedAt" datetime, "authorId" integer, CONSTRAINT "FK_e5da4837ed9d236532b3215a84e" FOREIGN KEY ("authorId") REFERENCES "author" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION)`,
);
await queryRunner.query(
`INSERT INTO "temporary_session"("id", "expiredAt", "json", "destroyedAt", "authorId") SELECT "id", "expiredAt", "json", "destroyedAt", "authorId" FROM "session"`,
);
await queryRunner.query(`DROP TABLE "session"`);
await queryRunner.query(
`ALTER TABLE "temporary_session" RENAME TO "session"`,
);
await queryRunner.query(
`CREATE INDEX "IDX_28c5d1d16da7908c97c9bc2f74" ON "session" ("expiredAt") `,
);
await queryRunner.query(
`CREATE TABLE "temporary_author" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "color" integer NOT NULL, "userId" integer, CONSTRAINT "FK_645811deaaaa772f9e6c2a4b927" FOREIGN KEY ("userId") REFERENCES "user" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION)`,
);
await queryRunner.query(
`INSERT INTO "temporary_author"("id", "color", "userId") SELECT "id", "color", "userId" FROM "author"`,
);
await queryRunner.query(`DROP TABLE "author"`);
await queryRunner.query(
`ALTER TABLE "temporary_author" RENAME TO "author"`,
);
await queryRunner.query(
`CREATE TABLE "temporary_identity" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "providerType" varchar NOT NULL, "providerIdentifier" text, "createdAt" datetime NOT NULL DEFAULT (datetime('now')), "updatedAt" datetime NOT NULL DEFAULT (datetime('now')), "providerUserId" text, "passwordHash" text, "userId" integer, CONSTRAINT "FK_12915039d2868ab654567bf5181" FOREIGN KEY ("userId") REFERENCES "user" ("id") ON DELETE CASCADE ON UPDATE NO ACTION)`,
);
await queryRunner.query(
`INSERT INTO "temporary_identity"("id", "providerType", "providerIdentifier", "createdAt", "updatedAt", "providerUserId", "passwordHash", "userId") SELECT "id", "providerType", "providerIdentifier", "createdAt", "updatedAt", "providerUserId", "passwordHash", "userId" FROM "identity"`,
);
await queryRunner.query(`DROP TABLE "identity"`);
await queryRunner.query(
`ALTER TABLE "temporary_identity" RENAME TO "identity"`,
);
await queryRunner.query(`DROP INDEX "IDX_bfa303089d367a2e3c02b002b8"`);
await queryRunner.query(`DROP INDEX "IDX_427107c650638bcb2f1e167d2e"`);
await queryRunner.query(
`CREATE TABLE "temporary_group_members_user" ("groupId" integer NOT NULL, "userId" integer NOT NULL, CONSTRAINT "FK_bfa303089d367a2e3c02b002b8f" FOREIGN KEY ("groupId") REFERENCES "group" ("id") ON DELETE CASCADE ON UPDATE CASCADE, CONSTRAINT "FK_427107c650638bcb2f1e167d2e5" FOREIGN KEY ("userId") REFERENCES "user" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION, PRIMARY KEY ("groupId", "userId"))`,
);
await queryRunner.query(
`INSERT INTO "temporary_group_members_user"("groupId", "userId") SELECT "groupId", "userId" FROM "group_members_user"`,
);
await queryRunner.query(`DROP TABLE "group_members_user"`);
await queryRunner.query(
`ALTER TABLE "temporary_group_members_user" RENAME TO "group_members_user"`,
);
await queryRunner.query(
`CREATE INDEX "IDX_bfa303089d367a2e3c02b002b8" ON "group_members_user" ("groupId") `,
);
await queryRunner.query(
`CREATE INDEX "IDX_427107c650638bcb2f1e167d2e" ON "group_members_user" ("userId") `,
);
await queryRunner.query(`DROP INDEX "IDX_3382f45eefeb40f91e45cfd418"`);
await queryRunner.query(`DROP INDEX "IDX_19dbafe2a8b456c0ef40858d49"`);
await queryRunner.query(
`CREATE TABLE "temporary_revision_tags_tag" ("revisionId" integer NOT NULL, "tagId" integer NOT NULL, CONSTRAINT "FK_3382f45eefeb40f91e45cfd4180" FOREIGN KEY ("revisionId") REFERENCES "revision" ("id") ON DELETE CASCADE ON UPDATE CASCADE, CONSTRAINT "FK_19dbafe2a8b456c0ef40858d49f" FOREIGN KEY ("tagId") REFERENCES "tag" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION, PRIMARY KEY ("revisionId", "tagId"))`,
);
await queryRunner.query(
`INSERT INTO "temporary_revision_tags_tag"("revisionId", "tagId") SELECT "revisionId", "tagId" FROM "revision_tags_tag"`,
);
await queryRunner.query(`DROP TABLE "revision_tags_tag"`);
await queryRunner.query(
`ALTER TABLE "temporary_revision_tags_tag" RENAME TO "revision_tags_tag"`,
);
await queryRunner.query(
`CREATE INDEX "IDX_3382f45eefeb40f91e45cfd418" ON "revision_tags_tag" ("revisionId") `,
);
await queryRunner.query(
`CREATE INDEX "IDX_19dbafe2a8b456c0ef40858d49" ON "revision_tags_tag" ("tagId") `,
);
await queryRunner.query(`DROP INDEX "IDX_52c6a61e1a646768391c7854fe"`);
await queryRunner.query(`DROP INDEX "IDX_470886feb50e30114e39c42698"`);
await queryRunner.query(
`CREATE TABLE "temporary_revision_edits_edit" ("revisionId" integer NOT NULL, "editId" integer NOT NULL, CONSTRAINT "FK_52c6a61e1a646768391c7854feb" FOREIGN KEY ("revisionId") REFERENCES "revision" ("id") ON DELETE CASCADE ON UPDATE CASCADE, CONSTRAINT "FK_470886feb50e30114e39c426987" FOREIGN KEY ("editId") REFERENCES "edit" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION, PRIMARY KEY ("revisionId", "editId"))`,
);
await queryRunner.query(
`INSERT INTO "temporary_revision_edits_edit"("revisionId", "editId") SELECT "revisionId", "editId" FROM "revision_edits_edit"`,
);
await queryRunner.query(`DROP TABLE "revision_edits_edit"`);
await queryRunner.query(
`ALTER TABLE "temporary_revision_edits_edit" RENAME TO "revision_edits_edit"`,
);
await queryRunner.query(
`CREATE INDEX "IDX_52c6a61e1a646768391c7854fe" ON "revision_edits_edit" ("revisionId") `,
);
await queryRunner.query(
`CREATE INDEX "IDX_470886feb50e30114e39c42698" ON "revision_edits_edit" ("editId") `,
);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`DROP INDEX "IDX_470886feb50e30114e39c42698"`);
await queryRunner.query(`DROP INDEX "IDX_52c6a61e1a646768391c7854fe"`);
await queryRunner.query(
`ALTER TABLE "revision_edits_edit" RENAME TO "temporary_revision_edits_edit"`,
);
await queryRunner.query(
`CREATE TABLE "revision_edits_edit" ("revisionId" integer NOT NULL, "editId" integer NOT NULL, PRIMARY KEY ("revisionId", "editId"))`,
);
await queryRunner.query(
`INSERT INTO "revision_edits_edit"("revisionId", "editId") SELECT "revisionId", "editId" FROM "temporary_revision_edits_edit"`,
);
await queryRunner.query(`DROP TABLE "temporary_revision_edits_edit"`);
await queryRunner.query(
`CREATE INDEX "IDX_470886feb50e30114e39c42698" ON "revision_edits_edit" ("editId") `,
);
await queryRunner.query(
`CREATE INDEX "IDX_52c6a61e1a646768391c7854fe" ON "revision_edits_edit" ("revisionId") `,
);
await queryRunner.query(`DROP INDEX "IDX_19dbafe2a8b456c0ef40858d49"`);
await queryRunner.query(`DROP INDEX "IDX_3382f45eefeb40f91e45cfd418"`);
await queryRunner.query(
`ALTER TABLE "revision_tags_tag" RENAME TO "temporary_revision_tags_tag"`,
);
await queryRunner.query(
`CREATE TABLE "revision_tags_tag" ("revisionId" integer NOT NULL, "tagId" integer NOT NULL, PRIMARY KEY ("revisionId", "tagId"))`,
);
await queryRunner.query(
`INSERT INTO "revision_tags_tag"("revisionId", "tagId") SELECT "revisionId", "tagId" FROM "temporary_revision_tags_tag"`,
);
await queryRunner.query(`DROP TABLE "temporary_revision_tags_tag"`);
await queryRunner.query(
`CREATE INDEX "IDX_19dbafe2a8b456c0ef40858d49" ON "revision_tags_tag" ("tagId") `,
);
await queryRunner.query(
`CREATE INDEX "IDX_3382f45eefeb40f91e45cfd418" ON "revision_tags_tag" ("revisionId") `,
);
await queryRunner.query(`DROP INDEX "IDX_427107c650638bcb2f1e167d2e"`);
await queryRunner.query(`DROP INDEX "IDX_bfa303089d367a2e3c02b002b8"`);
await queryRunner.query(
`ALTER TABLE "group_members_user" RENAME TO "temporary_group_members_user"`,
);
await queryRunner.query(
`CREATE TABLE "group_members_user" ("groupId" integer NOT NULL, "userId" integer NOT NULL, PRIMARY KEY ("groupId", "userId"))`,
);
await queryRunner.query(
`INSERT INTO "group_members_user"("groupId", "userId") SELECT "groupId", "userId" FROM "temporary_group_members_user"`,
);
await queryRunner.query(`DROP TABLE "temporary_group_members_user"`);
await queryRunner.query(
`CREATE INDEX "IDX_427107c650638bcb2f1e167d2e" ON "group_members_user" ("userId") `,
);
await queryRunner.query(
`CREATE INDEX "IDX_bfa303089d367a2e3c02b002b8" ON "group_members_user" ("groupId") `,
);
await queryRunner.query(
`ALTER TABLE "identity" RENAME TO "temporary_identity"`,
);
await queryRunner.query(
`CREATE TABLE "identity" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "providerType" varchar NOT NULL, "providerIdentifier" text, "createdAt" datetime NOT NULL DEFAULT (datetime('now')), "updatedAt" datetime NOT NULL DEFAULT (datetime('now')), "providerUserId" text, "passwordHash" text, "userId" integer)`,
);
await queryRunner.query(
`INSERT INTO "identity"("id", "providerType", "providerIdentifier", "createdAt", "updatedAt", "providerUserId", "passwordHash", "userId") SELECT "id", "providerType", "providerIdentifier", "createdAt", "updatedAt", "providerUserId", "passwordHash", "userId" FROM "temporary_identity"`,
);
await queryRunner.query(`DROP TABLE "temporary_identity"`);
await queryRunner.query(
`ALTER TABLE "author" RENAME TO "temporary_author"`,
);
await queryRunner.query(
`CREATE TABLE "author" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "color" integer NOT NULL, "userId" integer)`,
);
await queryRunner.query(
`INSERT INTO "author"("id", "color", "userId") SELECT "id", "color", "userId" FROM "temporary_author"`,
);
await queryRunner.query(`DROP TABLE "temporary_author"`);
await queryRunner.query(`DROP INDEX "IDX_28c5d1d16da7908c97c9bc2f74"`);
await queryRunner.query(
`ALTER TABLE "session" RENAME TO "temporary_session"`,
);
await queryRunner.query(
`CREATE TABLE "session" ("id" varchar(255) PRIMARY KEY NOT NULL, "expiredAt" bigint NOT NULL, "json" text NOT NULL, "destroyedAt" datetime, "authorId" integer)`,
);
await queryRunner.query(
`INSERT INTO "session"("id", "expiredAt", "json", "destroyedAt", "authorId") SELECT "id", "expiredAt", "json", "destroyedAt", "authorId" FROM "temporary_session"`,
);
await queryRunner.query(`DROP TABLE "temporary_session"`);
await queryRunner.query(
`CREATE INDEX "IDX_28c5d1d16da7908c97c9bc2f74" ON "session" ("expiredAt") `,
);
await queryRunner.query(`ALTER TABLE "edit" RENAME TO "temporary_edit"`);
await queryRunner.query(
`CREATE TABLE "edit" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "startPos" integer NOT NULL, "endPos" integer NOT NULL, "createdAt" datetime NOT NULL DEFAULT (datetime('now')), "updatedAt" datetime NOT NULL DEFAULT (datetime('now')), "authorId" integer)`,
);
await queryRunner.query(
`INSERT INTO "edit"("id", "startPos", "endPos", "createdAt", "updatedAt", "authorId") SELECT "id", "startPos", "endPos", "createdAt", "updatedAt", "authorId" FROM "temporary_edit"`,
);
await queryRunner.query(`DROP TABLE "temporary_edit"`);
await queryRunner.query(
`ALTER TABLE "revision" RENAME TO "temporary_revision"`,
);
await queryRunner.query(
`CREATE TABLE "revision" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "patch" text NOT NULL, "title" text NOT NULL, "description" text NOT NULL, "content" text NOT NULL, "length" integer NOT NULL, "yjsStateVector" text, "createdAt" datetime NOT NULL DEFAULT (datetime('now')), "noteId" integer)`,
);
await queryRunner.query(
`INSERT INTO "revision"("id", "patch", "title", "description", "content", "length", "yjsStateVector", "createdAt", "noteId") SELECT "id", "patch", "title", "description", "content", "length", "yjsStateVector", "createdAt", "noteId" FROM "temporary_revision"`,
);
await queryRunner.query(`DROP TABLE "temporary_revision"`);
await queryRunner.query(`ALTER TABLE "note" RENAME TO "temporary_note"`);
await queryRunner.query(
`CREATE TABLE "note" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "publicId" text NOT NULL, "viewCount" integer NOT NULL DEFAULT (0), "version" integer NOT NULL DEFAULT (2), "createdAt" datetime NOT NULL DEFAULT (datetime('now')), "ownerId" integer)`,
);
await queryRunner.query(
`INSERT INTO "note"("id", "publicId", "viewCount", "version", "createdAt", "ownerId") SELECT "id", "publicId", "viewCount", "version", "createdAt", "ownerId" FROM "temporary_note"`,
);
await queryRunner.query(`DROP TABLE "temporary_note"`);
await queryRunner.query(`ALTER TABLE "alias" RENAME TO "temporary_alias"`);
await queryRunner.query(
`CREATE TABLE "alias" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "name" varchar NOT NULL, "primary" boolean, "noteId" integer, CONSTRAINT "UQ_89f27e45cc5c1e43abd9132c9b9" UNIQUE ("name"), CONSTRAINT "Only one primary alias per note" UNIQUE ("noteId", "primary"))`,
);
await queryRunner.query(
`INSERT INTO "alias"("id", "name", "primary", "noteId") SELECT "id", "name", "primary", "noteId" FROM "temporary_alias"`,
);
await queryRunner.query(`DROP TABLE "temporary_alias"`);
await queryRunner.query(`DROP INDEX "IDX_5a3e91233d8878f98f5ad86b71"`);
await queryRunner.query(
`ALTER TABLE "note_user_permission" RENAME TO "temporary_note_user_permission"`,
);
await queryRunner.query(
`CREATE TABLE "note_user_permission" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "canEdit" boolean NOT NULL, "userId" integer, "noteId" integer)`,
);
await queryRunner.query(
`INSERT INTO "note_user_permission"("id", "canEdit", "userId", "noteId") SELECT "id", "canEdit", "userId", "noteId" FROM "temporary_note_user_permission"`,
);
await queryRunner.query(`DROP TABLE "temporary_note_user_permission"`);
await queryRunner.query(
`CREATE UNIQUE INDEX "IDX_5a3e91233d8878f98f5ad86b71" ON "note_user_permission" ("userId", "noteId") `,
);
await queryRunner.query(`DROP INDEX "IDX_ee1744842a9ef3ffbc05a7016a"`);
await queryRunner.query(
`ALTER TABLE "note_group_permission" RENAME TO "temporary_note_group_permission"`,
);
await queryRunner.query(
`CREATE TABLE "note_group_permission" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "canEdit" boolean NOT NULL, "groupId" integer, "noteId" integer)`,
);
await queryRunner.query(
`INSERT INTO "note_group_permission"("id", "canEdit", "groupId", "noteId") SELECT "id", "canEdit", "groupId", "noteId" FROM "temporary_note_group_permission"`,
);
await queryRunner.query(`DROP TABLE "temporary_note_group_permission"`);
await queryRunner.query(
`CREATE UNIQUE INDEX "IDX_ee1744842a9ef3ffbc05a7016a" ON "note_group_permission" ("groupId", "noteId") `,
);
await queryRunner.query(
`ALTER TABLE "media_upload" RENAME TO "temporary_media_upload"`,
);
await queryRunner.query(
`CREATE TABLE "media_upload" ("uuid" varchar PRIMARY KEY NOT NULL, "fileName" varchar NOT NULL, "backendType" varchar NOT NULL, "backendData" text, "createdAt" datetime NOT NULL DEFAULT (datetime('now')), "noteId" integer, "userId" integer)`,
);
await queryRunner.query(
`INSERT INTO "media_upload"("uuid", "fileName", "backendType", "backendData", "createdAt", "noteId", "userId") SELECT "uuid", "fileName", "backendType", "backendData", "createdAt", "noteId", "userId" FROM "temporary_media_upload"`,
);
await queryRunner.query(`DROP TABLE "temporary_media_upload"`);
await queryRunner.query(`DROP INDEX "IDX_928dd947355b0837366470a916"`);
await queryRunner.query(
`ALTER TABLE "history_entry" RENAME TO "temporary_history_entry"`,
);
await queryRunner.query(
`CREATE TABLE "history_entry" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "pinStatus" boolean NOT NULL, "updatedAt" datetime NOT NULL DEFAULT (datetime('now')), "userId" integer, "noteId" integer)`,
);
await queryRunner.query(
`INSERT INTO "history_entry"("id", "pinStatus", "updatedAt", "userId", "noteId") SELECT "id", "pinStatus", "updatedAt", "userId", "noteId" FROM "temporary_history_entry"`,
);
await queryRunner.query(`DROP TABLE "temporary_history_entry"`);
await queryRunner.query(
`CREATE UNIQUE INDEX "IDX_928dd947355b0837366470a916" ON "history_entry" ("noteId", "userId") `,
);
await queryRunner.query(
`ALTER TABLE "api_token" RENAME TO "temporary_api_token"`,
);
await queryRunner.query(
`CREATE TABLE "api_token" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "keyId" varchar NOT NULL, "label" varchar NOT NULL, "createdAt" datetime NOT NULL DEFAULT (datetime('now')), "hash" varchar NOT NULL, "validUntil" datetime NOT NULL, "lastUsedAt" date, "userId" integer, CONSTRAINT "UQ_3e254e2eb542a65da7c405d0683" UNIQUE ("keyId"), CONSTRAINT "UQ_60221392192b32c7560c128a6fa" UNIQUE ("hash"))`,
);
await queryRunner.query(
`INSERT INTO "api_token"("id", "keyId", "label", "createdAt", "hash", "validUntil", "lastUsedAt", "userId") SELECT "id", "keyId", "label", "createdAt", "hash", "validUntil", "lastUsedAt", "userId" FROM "temporary_api_token"`,
);
await queryRunner.query(`DROP TABLE "temporary_api_token"`);
await queryRunner.query(`DROP INDEX "IDX_470886feb50e30114e39c42698"`);
await queryRunner.query(`DROP INDEX "IDX_52c6a61e1a646768391c7854fe"`);
await queryRunner.query(`DROP TABLE "revision_edits_edit"`);
await queryRunner.query(`DROP INDEX "IDX_19dbafe2a8b456c0ef40858d49"`);
await queryRunner.query(`DROP INDEX "IDX_3382f45eefeb40f91e45cfd418"`);
await queryRunner.query(`DROP TABLE "revision_tags_tag"`);
await queryRunner.query(`DROP INDEX "IDX_427107c650638bcb2f1e167d2e"`);
await queryRunner.query(`DROP INDEX "IDX_bfa303089d367a2e3c02b002b8"`);
await queryRunner.query(`DROP TABLE "group_members_user"`);
await queryRunner.query(`DROP TABLE "identity"`);
await queryRunner.query(`DROP TABLE "user"`);
await queryRunner.query(`DROP TABLE "author"`);
await queryRunner.query(`DROP INDEX "IDX_28c5d1d16da7908c97c9bc2f74"`);
await queryRunner.query(`DROP TABLE "session"`);
await queryRunner.query(`DROP TABLE "edit"`);
await queryRunner.query(`DROP TABLE "revision"`);
await queryRunner.query(`DROP TABLE "tag"`);
await queryRunner.query(`DROP TABLE "note"`);
await queryRunner.query(`DROP TABLE "alias"`);
await queryRunner.query(`DROP INDEX "IDX_5a3e91233d8878f98f5ad86b71"`);
await queryRunner.query(`DROP TABLE "note_user_permission"`);
await queryRunner.query(`DROP INDEX "IDX_ee1744842a9ef3ffbc05a7016a"`);
await queryRunner.query(`DROP TABLE "note_group_permission"`);
await queryRunner.query(`DROP TABLE "group"`);
await queryRunner.query(`DROP TABLE "media_upload"`);
await queryRunner.query(`DROP INDEX "IDX_928dd947355b0837366470a916"`);
await queryRunner.query(`DROP TABLE "history_entry"`);
await queryRunner.query(`DROP TABLE "api_token"`);
}
}

View file

@ -1,64 +0,0 @@
/*
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import {
Column,
Entity,
ManyToOne,
PrimaryGeneratedColumn,
Unique,
} from 'typeorm';
import { Note } from './note.entity';
import { PrimaryValueTransformer } from './primary.value-transformer';
@Entity()
@Unique('Only one primary alias per note', ['note', 'primary'])
export class Alias {
@PrimaryGeneratedColumn()
id: number;
/**
* the actual alias
*/
@Column({
nullable: false,
unique: true,
})
name: string;
/**
* Is this alias the primary alias, by which people access the note?
*/
@Column({
/*
Because of the @Unique at the top of this entity, this field must be saved as null instead of false in the DB.
If a non-primary alias would be saved with `primary: false` it would only be possible to have one non-primary and one primary alias.
But a nullable field does not have such problems.
This way the DB keeps track that one note really only has one primary alias.
*/
comment:
'This field tells you if this is the primary alias of the note. If this field is null, that means this alias is not primary.',
nullable: true,
transformer: new PrimaryValueTransformer(),
})
primary: boolean;
@ManyToOne((_) => Note, (note) => note.aliases, {
onDelete: 'CASCADE', // This deletes the Alias, when the associated Note is deleted
})
note: Promise<Note>;
// eslint-disable-next-line @typescript-eslint/no-empty-function
private constructor() {}
static create(name: string, note: Note, primary: boolean): Omit<Alias, 'id'> {
const alias = new Alias();
alias.name = name;
alias.primary = primary;
alias.note = Promise.resolve(note);
return alias;
}
}

View file

@ -17,6 +17,7 @@ import appConfigMock from '../config/mock/app.config.mock';
import authConfigMock from '../config/mock/auth.config.mock';
import databaseConfigMock from '../config/mock/database.config.mock';
import noteConfigMock from '../config/mock/note.config.mock';
import { User } from '../database/user.entity';
import {
AlreadyInDBError,
ForbiddenIdError,
@ -34,7 +35,6 @@ import { Edit } from '../revisions/edit.entity';
import { Revision } from '../revisions/revision.entity';
import { RevisionsModule } from '../revisions/revisions.module';
import { Session } from '../sessions/session.entity';
import { User } from '../users/user.entity';
import { UsersModule } from '../users/users.module';
import { mockSelectQueryBuilderInRepo } from '../utils/test-utils/mockSelectQueryBuilder';
import { Alias } from './alias.entity';

View file

@ -1,109 +0,0 @@
/*
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import {
Column,
CreateDateColumn,
Entity,
ManyToOne,
OneToMany,
PrimaryGeneratedColumn,
} from 'typeorm';
import { HistoryEntry } from '../history/history-entry.entity';
import { MediaUpload } from '../media/media-upload.entity';
import { NoteGroupPermission } from '../permissions/note-group-permission.entity';
import { NoteUserPermission } from '../permissions/note-user-permission.entity';
import { Revision } from '../revisions/revision.entity';
import { User } from '../users/user.entity';
import { Alias } from './alias.entity';
import { generatePublicId } from './utils';
@Entity()
export class Note {
@PrimaryGeneratedColumn()
id: number;
@Column({ type: 'text' })
publicId: string;
@OneToMany(
(_) => Alias,
(alias) => alias.note,
{ cascade: true }, // This ensures that embedded Aliases are automatically saved to the database
)
aliases: Promise<Alias[]>;
@OneToMany(
(_) => NoteGroupPermission,
(groupPermission) => groupPermission.note,
{ cascade: true }, // This ensures that embedded NoteGroupPermissions are automatically saved to the database
)
groupPermissions: Promise<NoteGroupPermission[]>;
@OneToMany(
(_) => NoteUserPermission,
(userPermission) => userPermission.note,
{ cascade: true }, // This ensures that embedded NoteUserPermission are automatically saved to the database
)
userPermissions: Promise<NoteUserPermission[]>;
@Column({
nullable: false,
default: 0,
})
viewCount: number;
@ManyToOne((_) => User, (user) => user.ownedNotes, {
onDelete: 'CASCADE', // This deletes the Note, when the associated User is deleted
nullable: true,
})
owner: Promise<User | null>;
@OneToMany((_) => Revision, (revision) => revision.note, { cascade: true })
revisions: Promise<Revision[]>;
@OneToMany((_) => HistoryEntry, (historyEntry) => historyEntry.user)
historyEntries: Promise<HistoryEntry[]>;
@OneToMany((_) => MediaUpload, (mediaUpload) => mediaUpload.note)
mediaUploads: Promise<MediaUpload[]>;
@Column({
default: 2,
})
version: number;
@CreateDateColumn()
createdAt: Date;
// eslint-disable-next-line @typescript-eslint/no-empty-function
private constructor() {}
/**
* Creates a new Note
* @param owner The owner of the note
* @param alias Optional primary alias
*/
public static create(
owner: User | null,
alias?: string,
): Omit<Note, 'id' | 'createdAt'> {
const newNote = new Note();
newNote.publicId = generatePublicId();
newNote.aliases = alias
? Promise.resolve([Alias.create(alias, newNote, true) as Alias])
: Promise.resolve([]);
newNote.userPermissions = Promise.resolve([]);
newNote.groupPermissions = Promise.resolve([]);
newNote.viewCount = 0;
newNote.owner = Promise.resolve(owner);
newNote.revisions = Promise.resolve([]);
newNote.historyEntries = Promise.resolve([]);
newNote.mediaUploads = Promise.resolve([]);
newNote.version = 2;
return newNote;
}
}

View file

@ -7,13 +7,13 @@ import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import { TypeOrmModule } from '@nestjs/typeorm';
import { User } from '../database/user.entity';
import { GroupsModule } from '../groups/groups.module';
import { LoggerModule } from '../logger/logger.module';
import { NoteGroupPermission } from '../permissions/note-group-permission.entity';
import { NoteUserPermission } from '../permissions/note-user-permission.entity';
import { RealtimeNoteModule } from '../realtime/realtime-note/realtime-note.module';
import { RevisionsModule } from '../revisions/revisions.module';
import { User } from '../users/user.entity';
import { UsersModule } from '../users/users.module';
import { Alias } from './alias.entity';
import { AliasService } from './alias.service';

View file

@ -27,6 +27,7 @@ import {
registerNoteConfig,
} from '../config/mock/note.config.mock';
import { NoteConfig } from '../config/note.config';
import { User } from '../database/user.entity';
import {
AlreadyInDBError,
ForbiddenIdError,
@ -46,7 +47,6 @@ import { Revision } from '../revisions/revision.entity';
import { RevisionsModule } from '../revisions/revisions.module';
import { RevisionsService } from '../revisions/revisions.service';
import { Session } from '../sessions/session.entity';
import { User } from '../users/user.entity';
import { UsersModule } from '../users/users.module';
import { mockSelectQueryBuilderInRepo } from '../utils/test-utils/mockSelectQueryBuilder';
import { Alias } from './alias.entity';

View file

@ -16,6 +16,7 @@ import { Repository } from 'typeorm';
import { DefaultAccessLevel } from '../config/default-access-level.enum';
import noteConfiguration, { NoteConfig } from '../config/note.config';
import { User } from '../database/user.entity';
import {
AlreadyInDBError,
ForbiddenIdError,
@ -31,7 +32,6 @@ import { NoteGroupPermission } from '../permissions/note-group-permission.entity
import { RealtimeNoteStore } from '../realtime/realtime-note/realtime-note-store';
import { RealtimeNoteService } from '../realtime/realtime-note/realtime-note.service';
import { RevisionsService } from '../revisions/revisions.service';
import { User } from '../users/user.entity';
import { UsersService } from '../users/users.service';
import { Alias } from './alias.entity';
import { AliasService } from './alias.service';

View file

@ -1,22 +0,0 @@
/*
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { Column, Entity, ManyToMany, PrimaryGeneratedColumn } from 'typeorm';
import { Revision } from '../revisions/revision.entity';
@Entity()
export class Tag {
@PrimaryGeneratedColumn()
id: number;
@Column({
nullable: false,
})
name: string;
@ManyToMany((_) => Revision, (revision) => revision.tags)
revisions: Promise<Revision[]>;
}

View file

@ -5,7 +5,7 @@
*/
import { randomBytes } from 'crypto';
import { User } from '../users/user.entity';
import { User } from '../database/user.entity';
import { Alias } from './alias.entity';
import { Note } from './note.entity';
import { generatePublicId, getPrimaryAlias } from './utils';

View file

@ -1,52 +0,0 @@
/*
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import {
Column,
Entity,
Index,
ManyToOne,
PrimaryGeneratedColumn,
} from 'typeorm';
import { Group } from '../groups/group.entity';
import { Note } from '../notes/note.entity';
@Entity()
@Index(['group', 'note'], { unique: true })
export class NoteGroupPermission {
@PrimaryGeneratedColumn()
id: number;
@ManyToOne((_) => Group, {
onDelete: 'CASCADE', // This deletes the NoteGroupPermission, when the associated Group is deleted
orphanedRowAction: 'delete', // This ensures the row of the NoteGroupPermission is deleted when no group references it anymore
})
group: Promise<Group>;
@ManyToOne((_) => Note, (note) => note.groupPermissions, {
onDelete: 'CASCADE', // This deletes the NoteGroupPermission, when the associated Note is deleted
orphanedRowAction: 'delete', // This ensures the row of the NoteGroupPermission is deleted when no note references it anymore
})
note: Promise<Note>;
@Column()
canEdit: boolean;
// eslint-disable-next-line @typescript-eslint/no-empty-function
private constructor() {}
public static create(
group: Group,
note: Note,
canEdit: boolean,
): NoteGroupPermission {
const groupPermission = new NoteGroupPermission();
groupPermission.group = Promise.resolve(group);
groupPermission.note = Promise.resolve(note);
groupPermission.canEdit = canEdit;
return groupPermission;
}
}

View file

@ -1,52 +0,0 @@
/*
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import {
Column,
Entity,
Index,
ManyToOne,
PrimaryGeneratedColumn,
} from 'typeorm';
import { Note } from '../notes/note.entity';
import { User } from '../users/user.entity';
@Entity()
@Index(['user', 'note'], { unique: true })
export class NoteUserPermission {
@PrimaryGeneratedColumn()
id: number;
@ManyToOne((_) => User, {
onDelete: 'CASCADE', // This deletes the NoteUserPermission, when the associated Note is deleted
orphanedRowAction: 'delete', // This ensures the row of the NoteUserPermission is deleted when no user references it anymore
})
user: Promise<User>;
@ManyToOne((_) => Note, (note) => note.userPermissions, {
onDelete: 'CASCADE', // This deletes the NoteUserPermission, when the associated Note is deleted
orphanedRowAction: 'delete', // This ensures the row of the NoteUserPermission is deleted when no note references it anymore
})
note: Promise<Note>;
@Column()
canEdit: boolean;
// eslint-disable-next-line @typescript-eslint/no-empty-function
private constructor() {}
public static create(
user: User,
note: Note,
canEdit: boolean,
): NoteUserPermission {
const userPermission = new NoteUserPermission();
userPermission.user = Promise.resolve(user);
userPermission.note = Promise.resolve(note);
userPermission.canEdit = canEdit;
return userPermission;
}
}

View file

@ -9,9 +9,9 @@ import { Mock } from 'ts-mockery';
import * as ExtractNoteIdOrAliasModule from '../api/utils/extract-note-from-request';
import { CompleteRequest } from '../api/utils/request.type';
import { User } from '../database/user.entity';
import { ConsoleLoggerService } from '../logger/console-logger.service';
import { Note } from '../notes/note.entity';
import { User } from '../users/user.entity';
import {
getNotePermissionDisplayName,
NotePermission,

View file

@ -27,6 +27,7 @@ import {
registerNoteConfig,
} from '../config/mock/note.config.mock';
import { NoteConfig } from '../config/note.config';
import { User } from '../database/user.entity';
import { PermissionsUpdateInconsistentError } from '../errors/errors';
import { eventModuleConfig, NoteEvent } from '../events';
import { Group } from '../groups/group.entity';
@ -41,7 +42,6 @@ import { Tag } from '../notes/tag.entity';
import { Edit } from '../revisions/edit.entity';
import { Revision } from '../revisions/revision.entity';
import { Session } from '../sessions/session.entity';
import { User } from '../users/user.entity';
import { UsersModule } from '../users/users.module';
import { NoteGroupPermission } from './note-group-permission.entity';
import {

View file

@ -10,6 +10,7 @@ import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import noteConfiguration, { NoteConfig } from '../config/note.config';
import { User } from '../database/user.entity';
import { PermissionsUpdateInconsistentError } from '../errors/errors';
import { NoteEvent, NoteEventMap } from '../events';
import { Group } from '../groups/group.entity';
@ -17,7 +18,6 @@ import { GroupsService } from '../groups/groups.service';
import { ConsoleLoggerService } from '../logger/console-logger.service';
import { MediaUpload } from '../media/media-upload.entity';
import { Note } from '../notes/note.entity';
import { User } from '../users/user.entity';
import { UsersService } from '../users/users.service';
import { checkArrayForDuplicates } from '../utils/arrayDuplicatCheck';
import { NoteGroupPermission } from './note-group-permission.entity';

View file

@ -5,9 +5,9 @@
*/
import { Mock } from 'ts-mockery';
import { User } from '../../database/user.entity';
import { Group } from '../../groups/group.entity';
import { SpecialGroup } from '../../groups/groups.special';
import { User } from '../../users/user.entity';
import { NoteGroupPermission } from '../note-group-permission.entity';
import { NotePermission } from '../note-permission.enum';
import { findHighestNotePermissionByGroup } from './find-highest-note-permission-by-group';

View file

@ -3,9 +3,9 @@
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { User } from '../../database/user.entity';
import { Group } from '../../groups/group.entity';
import { SpecialGroup } from '../../groups/groups.special';
import { User } from '../../users/user.entity';
import { NoteGroupPermission } from '../note-group-permission.entity';
import { NotePermission } from '../note-permission.enum';

View file

@ -5,7 +5,7 @@
*/
import { Mock } from 'ts-mockery';
import { User } from '../../users/user.entity';
import { User } from '../../database/user.entity';
import { NotePermission } from '../note-permission.enum';
import { NoteUserPermission } from '../note-user-permission.entity';
import { findHighestNotePermissionByUser } from './find-highest-note-permission-by-user';

View file

@ -3,7 +3,7 @@
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { User } from '../../users/user.entity';
import { User } from '../../database/user.entity';
import { NotePermission } from '../note-permission.enum';
import { NoteUserPermission } from '../note-user-permission.entity';

View file

@ -11,8 +11,8 @@ import {
import * as HedgeDocCommonsModule from '@hedgedoc/commons';
import { Mock } from 'ts-mockery';
import { User } from '../../database/user.entity';
import { Note } from '../../notes/note.entity';
import { User } from '../../users/user.entity';
import * as NameRandomizerModule from './random-word-lists/name-randomizer';
import { RealtimeConnection } from './realtime-connection';
import { RealtimeNote } from './realtime-note';

View file

@ -6,7 +6,7 @@
import { MessageTransporter, YDocSyncServerAdapter } from '@hedgedoc/commons';
import { Logger } from '@nestjs/common';
import { User } from '../../users/user.entity';
import { User } from '../../database/user.entity';
import { generateRandomName } from './random-word-lists/name-randomizer';
import { RealtimeNote } from './realtime-note';
import { RealtimeUserStatusAdapter } from './realtime-user-status-adapter';

View file

@ -7,13 +7,13 @@ import { SchedulerRegistry } from '@nestjs/schedule';
import { Mock } from 'ts-mockery';
import { AppConfig } from '../../config/app.config';
import { User } from '../../database/user.entity';
import { ConsoleLoggerService } from '../../logger/console-logger.service';
import { Note } from '../../notes/note.entity';
import { NotePermission } from '../../permissions/note-permission.enum';
import { PermissionsService } from '../../permissions/permissions.service';
import { Revision } from '../../revisions/revision.entity';
import { RevisionsService } from '../../revisions/revisions.service';
import { User } from '../../users/user.entity';
import { RealtimeConnection } from './realtime-connection';
import { RealtimeNote } from './realtime-note';
import { RealtimeNoteStore } from './realtime-note-store';

View file

@ -9,7 +9,7 @@ import {
} from '@hedgedoc/commons';
import { Mock } from 'ts-mockery';
import { User } from '../../../users/user.entity';
import { User } from '../../../database/user.entity';
import { RealtimeConnection } from '../realtime-connection';
import { RealtimeNote } from '../realtime-note';
import { RealtimeUserStatusAdapter } from '../realtime-user-status-adapter';

View file

@ -20,6 +20,7 @@ import appConfigMock from '../../config/mock/app.config.mock';
import authConfigMock from '../../config/mock/auth.config.mock';
import databaseConfigMock from '../../config/mock/database.config.mock';
import noteConfigMock from '../../config/mock/note.config.mock';
import { User } from '../../database/user.entity';
import { eventModuleConfig } from '../../events';
import { Group } from '../../groups/group.entity';
import { LoggerModule } from '../../logger/logger.module';
@ -38,7 +39,6 @@ import { Revision } from '../../revisions/revision.entity';
import { Session } from '../../sessions/session.entity';
import { SessionModule } from '../../sessions/session.module';
import { SessionService } from '../../sessions/session.service';
import { User } from '../../users/user.entity';
import { UsersModule } from '../../users/users.module';
import { UsersService } from '../../users/users.service';
import * as websocketConnectionModule from '../realtime-note/realtime-connection';

View file

@ -12,12 +12,12 @@ import { OnGatewayConnection, WebSocketGateway } from '@nestjs/websockets';
import { IncomingMessage } from 'http';
import WebSocket from 'ws';
import { User } from '../../database/user.entity';
import { ConsoleLoggerService } from '../../logger/console-logger.service';
import { NotesService } from '../../notes/notes.service';
import { NotePermission } from '../../permissions/note-permission.enum';
import { PermissionsService } from '../../permissions/permissions.service';
import { SessionService } from '../../sessions/session.service';
import { User } from '../../users/user.entity';
import { UsersService } from '../../users/users.service';
import { RealtimeConnection } from '../realtime-note/realtime-connection';
import { RealtimeNoteService } from '../realtime-note/realtime-note.service';

View file

@ -1,66 +0,0 @@
/*
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import {
Column,
CreateDateColumn,
Entity,
ManyToMany,
ManyToOne,
PrimaryGeneratedColumn,
UpdateDateColumn,
} from 'typeorm';
import { Author } from '../authors/author.entity';
import { Revision } from './revision.entity';
/**
* The Edit represents a change in the content of a note by a particular {@link Author}
*/
@Entity()
export class Edit {
@PrimaryGeneratedColumn()
id: number;
/**
* Revisions this edit appears in
*/
@ManyToMany((_) => Revision, (revision) => revision.edits)
revisions: Promise<Revision[]>;
/**
* Author that created the change
*/
@ManyToOne(() => Author, (author) => author.edits)
author: Promise<Author>;
@Column()
startPos: number;
@Column()
endPos: number;
@CreateDateColumn()
createdAt: Date;
@UpdateDateColumn()
updatedAt: Date;
// eslint-disable-next-line @typescript-eslint/no-empty-function
private constructor() {}
public static create(
author: Author,
startPos: number,
endPos: number,
): Omit<Edit, 'id' | 'createdAt' | 'updatedAt'> {
const newEdit = new Edit();
newEdit.revisions = Promise.resolve([]);
newEdit.author = Promise.resolve(author);
newEdit.startPos = startPos;
newEdit.endPos = endPos;
return newEdit;
}
}

View file

@ -1,115 +0,0 @@
/*
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import {
Column,
CreateDateColumn,
Entity,
JoinTable,
ManyToMany,
ManyToOne,
PrimaryGeneratedColumn,
} from 'typeorm';
import { Note } from '../notes/note.entity';
import { Tag } from '../notes/tag.entity';
import { Edit } from './edit.entity';
/**
* The state of a note at a particular point in time,
* with the content at that time and the diff to the previous revision.
*
*/
@Entity()
export class Revision {
@PrimaryGeneratedColumn()
id: number;
/**
* The patch from the previous revision to this one.
*/
@Column({
type: 'text',
})
patch: string;
@Column({
type: 'text',
})
title: string;
@Column({
type: 'text',
})
description: string;
@ManyToMany((_) => Tag, (tag) => tag.revisions, {
eager: true,
cascade: true,
})
@JoinTable()
tags: Promise<Tag[]>;
/**
* The note content at this revision.
*/
@Column({
type: 'text',
})
content: string;
/**
* The length of the note content.
*/
@Column()
length: number;
@Column('simple-array', { nullable: true })
yjsStateVector: null | number[];
/**
* Date at which the revision was created.
*/
@CreateDateColumn()
createdAt: Date;
/**
* Note this revision belongs to.
*/
@ManyToOne((_) => Note, (note) => note.revisions, { onDelete: 'CASCADE' })
note: Promise<Note>;
/**
* All edit objects which are used in the revision.
*/
@ManyToMany((_) => Edit, (edit) => edit.revisions)
@JoinTable()
edits: Promise<Edit[]>;
// eslint-disable-next-line @typescript-eslint/no-empty-function
private constructor() {}
static create(
content: string,
patch: string,
note: Note,
yjsStateVector: number[] | null,
title: string,
description: string,
tags: Tag[],
): Omit<Revision, 'id' | 'createdAt'> {
const newRevision = new Revision();
newRevision.patch = patch;
newRevision.content = content;
newRevision.length = content.length;
newRevision.title = title;
newRevision.description = description;
newRevision.tags = Promise.resolve(tags);
newRevision.note = Promise.resolve(note);
newRevision.edits = Promise.resolve([]);
newRevision.yjsStateVector = yjsStateVector ?? null;
return newRevision;
}
}

View file

@ -23,6 +23,7 @@ import {
registerNoteConfig,
} from '../config/mock/note.config.mock';
import { NoteConfig } from '../config/note.config';
import { User } from '../database/user.entity';
import { NotInDBError } from '../errors/errors';
import { eventModuleConfig } from '../events';
import { Group } from '../groups/group.entity';
@ -34,7 +35,6 @@ import { Tag } from '../notes/tag.entity';
import { NoteGroupPermission } from '../permissions/note-group-permission.entity';
import { NoteUserPermission } from '../permissions/note-user-permission.entity';
import { Session } from '../sessions/session.entity';
import { User } from '../users/user.entity';
import { Edit } from './edit.entity';
import { EditService } from './edit.service';
import { Revision } from './revision.entity';

View file

@ -9,6 +9,7 @@ import { DataSource } from 'typeorm';
import { ApiToken } from './api-token/api-token.entity';
import { Identity } from './auth/identity.entity';
import { Author } from './authors/author.entity';
import { User } from './database/user.entity';
import { Group } from './groups/group.entity';
import { HistoryEntry } from './history/history-entry.entity';
import { MediaUpload } from './media/media-upload.entity';
@ -20,7 +21,6 @@ import { NoteUserPermission } from './permissions/note-user-permission.entity';
import { Edit } from './revisions/edit.entity';
import { Revision } from './revisions/revision.entity';
import { Session } from './sessions/session.entity';
import { User } from './users/user.entity';
import { hashPassword } from './utils/password';
/**

View file

@ -1,99 +0,0 @@
/*
* SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import {
Column,
CreateDateColumn,
Entity,
ManyToMany,
OneToMany,
PrimaryGeneratedColumn,
UpdateDateColumn,
} from 'typeorm';
import { ApiToken } from '../api-token/api-token.entity';
import { Identity } from '../auth/identity.entity';
import { Author } from '../authors/author.entity';
import { Group } from '../groups/group.entity';
import { HistoryEntry } from '../history/history-entry.entity';
import { MediaUpload } from '../media/media-upload.entity';
import { Note } from '../notes/note.entity';
@Entity()
export class User {
@PrimaryGeneratedColumn()
id: number;
@Column({
unique: true,
})
username: string;
@Column()
displayName: string;
@CreateDateColumn()
createdAt: Date;
@UpdateDateColumn()
updatedAt: Date;
@Column({
nullable: true,
type: 'text',
})
photo: string | null;
@Column({
nullable: true,
type: 'text',
})
email: string | null;
@OneToMany((_) => Note, (note) => note.owner)
ownedNotes: Promise<Note[]>;
@OneToMany((_) => ApiToken, (apiToken) => apiToken.user)
apiTokens: Promise<ApiToken[]>;
@OneToMany((_) => Identity, (identity) => identity.user)
identities: Promise<Identity[]>;
@ManyToMany((_) => Group, (group) => group.members)
groups: Promise<Group[]>;
@OneToMany((_) => HistoryEntry, (historyEntry) => historyEntry.user)
historyEntries: Promise<HistoryEntry[]>;
@OneToMany((_) => MediaUpload, (mediaUpload) => mediaUpload.user)
mediaUploads: Promise<MediaUpload[]>;
@OneToMany(() => Author, (author) => author.user)
authors: Promise<Author[]>;
// eslint-disable-next-line @typescript-eslint/no-empty-function
private constructor() {}
public static create(
username: string,
displayName: string,
email?: string,
photoUrl?: string,
): Omit<User, 'id' | 'createdAt' | 'updatedAt'> {
const newUser = new User();
newUser.username = username;
newUser.displayName = displayName;
newUser.photo = photoUrl ?? null;
newUser.email = email ?? null;
newUser.ownedNotes = Promise.resolve([]);
newUser.apiTokens = Promise.resolve([]);
newUser.identities = Promise.resolve([]);
newUser.groups = Promise.resolve([]);
newUser.historyEntries = Promise.resolve([]);
newUser.mediaUploads = Promise.resolve([]);
newUser.authors = Promise.resolve([]);
return newUser;
}
}

View file

@ -7,9 +7,9 @@ import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { Identity } from '../auth/identity.entity';
import { User } from '../database/user.entity';
import { LoggerModule } from '../logger/logger.module';
import { Session } from '../sessions/session.entity';
import { User } from './user.entity';
import { UsersService } from './users.service';
@Module({

View file

@ -10,9 +10,9 @@ import { Repository } from 'typeorm';
import appConfigMock from '../config/mock/app.config.mock';
import authConfigMock from '../config/mock/auth.config.mock';
import { User } from '../database/user.entity';
import { AlreadyInDBError, NotInDBError } from '../errors/errors';
import { LoggerModule } from '../logger/logger.module';
import { User } from './user.entity';
import { UsersService } from './users.service';
describe('UsersService', () => {

View file

@ -15,10 +15,10 @@ import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import AuthConfiguration, { AuthConfig } from '../config/auth.config';
import { User } from '../database/user.entity';
import { AlreadyInDBError, NotInDBError } from '../errors/errors';
import { ConsoleLoggerService } from '../logger/console-logger.service';
import { UserRelationEnum } from './user-relation.enum';
import { User } from './user.entity';
@Injectable()
export class UsersService {

View file

@ -6,8 +6,8 @@
import { AliasCreateDto, AliasUpdateDto } from '@hedgedoc/commons';
import request from 'supertest';
import { User } from '../../src/database/user.entity';
import { Note } from '../../src/notes/note.entity';
import { User } from '../../src/users/user.entity';
import {
password1,
password2,

View file

@ -6,12 +6,12 @@
import request from 'supertest';
import { LocalService } from '../../src/auth/local/local.service';
import { User } from '../../src/database/user.entity';
import { HistoryEntryImportDto } from '../../src/history/history-entry-import.dto';
import { HistoryEntry } from '../../src/history/history-entry.entity';
import { HistoryService } from '../../src/history/history.service';
import { Note } from '../../src/notes/note.entity';
import { NotesService } from '../../src/notes/notes.service';
import { User } from '../../src/users/user.entity';
import { UsersService } from '../../src/users/users.service';
import { TestSetup, TestSetupBuilder } from '../test-setup';

View file

@ -7,9 +7,9 @@ import { LoginUserInfoDto, ProviderType } from '@hedgedoc/commons';
import { promises as fs } from 'fs';
import request from 'supertest';
import { User } from '../../src/database/user.entity';
import { NotInDBError } from '../../src/errors/errors';
import { Note } from '../../src/notes/note.entity';
import { User } from '../../src/users/user.entity';
import { TestSetup, TestSetupBuilder } from '../test-setup';
describe('Me', () => {

View file

@ -5,7 +5,7 @@
*/
import { promises as fs } from 'fs';
import { join } from 'path';
import { User } from 'src/users/user.entity';
import { User } from 'src/database/user.entity';
import request from 'supertest';
import { ConsoleLoggerService } from '../../src/logger/console-logger.service';

View file

@ -7,9 +7,9 @@ import { promises as fs } from 'fs';
import { join } from 'path';
import request from 'supertest';
import { User } from '../../src/database/user.entity';
import { NotInDBError } from '../../src/errors/errors';
import { Group } from '../../src/groups/group.entity';
import { User } from '../../src/users/user.entity';
import { TestSetup, TestSetupBuilder } from '../test-setup';
describe('Notes', () => {

View file

@ -6,11 +6,11 @@
import { NoteMetadataDto } from '@hedgedoc/commons';
import { promises as fs } from 'fs';
import { join } from 'path';
import { HistoryEntryDto } from 'src/history/history-entry.dto';
import request from 'supertest';
import { User } from '../../src/database/user.entity';
import { HistoryEntryUpdateDto } from '../../src/history/history-entry-update.dto';
import { User } from '../../src/users/user.entity';
import { HistoryEntryDto } from '../../src/history/history-entry.dto';
import { TestSetup, TestSetupBuilder } from '../test-setup';
describe('Me', () => {

View file

@ -60,6 +60,7 @@ import {
registerNoteConfig,
} from '../src/config/mock/note.config.mock';
import { NoteConfig } from '../src/config/note.config';
import { User } from '../src/database/user.entity';
import { ErrorExceptionMapping } from '../src/errors/error-mapping';
import { eventModuleConfig } from '../src/events';
import { FrontendConfigModule } from '../src/frontend-config/frontend-config.module';
@ -82,7 +83,6 @@ import { RevisionsModule } from '../src/revisions/revisions.module';
import { RevisionsService } from '../src/revisions/revisions.service';
import { SessionModule } from '../src/sessions/session.module';
import { SessionService } from '../src/sessions/session.service';
import { User } from '../src/users/user.entity';
import { UsersModule } from '../src/users/users.module';
import { UsersService } from '../src/users/users.service';

850
yarn.lock

File diff suppressed because it is too large Load diff