feat: consolidate entities create

This was done to give better typings to the function signatures of entities `create` methods.
It also ensures that each field that should be set to `null` is set to `null` and doesn't leave that up to the typeorm handlers.

See: #1641
Signed-off-by: Philip Molares <philip.molares@udo.edu>
This commit is contained in:
Philip Molares 2021-09-25 11:50:28 +02:00
parent bb7561b9ad
commit b896f954b9
14 changed files with 89 additions and 40 deletions

View file

@ -45,21 +45,17 @@ export class AuthToken {
lastUsed: Date | null; lastUsed: Date | null;
public static create( public static create(
keyId: string,
user: User, user: User,
label: string, label: string,
keyId: string,
accessToken: string, accessToken: string,
validUntil: Date, validUntil: Date,
): Pick< ): Omit<AuthToken, 'id' | 'createdAt'> {
AuthToken,
'user' | 'label' | 'keyId' | 'accessTokenHash' | 'createdAt' | 'validUntil'
> {
const newToken = new AuthToken(); const newToken = new AuthToken();
newToken.keyId = keyId;
newToken.user = user; newToken.user = user;
newToken.label = label; newToken.label = label;
newToken.keyId = keyId;
newToken.accessTokenHash = accessToken; newToken.accessTokenHash = accessToken;
newToken.createdAt = new Date();
newToken.validUntil = validUntil; newToken.validUntil = validUntil;
newToken.lastUsed = null; newToken.lastUsed = null;
return newToken; return newToken;

View file

@ -58,9 +58,7 @@ export class Author {
// eslint-disable-next-line @typescript-eslint/no-empty-function // eslint-disable-next-line @typescript-eslint/no-empty-function
private constructor() {} private constructor() {}
public static create( public static create(color: number): Omit<Author, 'id'> {
color: number,
): Pick<Author, 'color' | 'sessions' | 'user' | 'edits'> {
const newAuthor = new Author(); const newAuthor = new Author();
newAuthor.color = color; newAuthor.color = color;
newAuthor.sessions = []; newAuthor.sessions = [];

View file

@ -43,11 +43,12 @@ export class Group {
// eslint-disable-next-line @typescript-eslint/no-empty-function // eslint-disable-next-line @typescript-eslint/no-empty-function
private constructor() {} private constructor() {}
public static create(name: string, displayName: string): Group { public static create(name: string, displayName: string): Omit<Group, 'id'> {
const newGroup = new Group(); const newGroup = new Group();
newGroup.special = false; // this attribute should only be true for the two special groups
newGroup.name = name; newGroup.name = name;
newGroup.displayName = displayName; newGroup.displayName = displayName;
newGroup.special = false; // this attribute should only be true for the two special groups
newGroup.members = [];
return newGroup; return newGroup;
} }
} }

View file

@ -28,15 +28,21 @@ export class HistoryEntry {
@UpdateDateColumn() @UpdateDateColumn()
updatedAt: Date; updatedAt: Date;
// The optional note parameter is necessary for the createNote method in the NotesService, /**
// as we create the note then and don't need to add it to the HistoryEntry. * Create a history entry
public static create(user: User, note?: Note): HistoryEntry { * @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(); const newHistoryEntry = new HistoryEntry();
newHistoryEntry.user = user; newHistoryEntry.user = user;
if (note) { newHistoryEntry.note = note;
newHistoryEntry.note = note; newHistoryEntry.pinStatus = pinStatus;
}
newHistoryEntry.pinStatus = false;
return newHistoryEntry; return newHistoryEntry;
} }
} }

View file

@ -101,11 +101,15 @@ export class Identity {
user: User, user: User,
providerType: ProviderType, providerType: ProviderType,
syncSource = false, syncSource = false,
): Identity { ): Omit<Identity, 'id' | 'createdAt' | 'updatedAt'> {
const newIdentity = new Identity(); const newIdentity = new Identity();
newIdentity.user = user; newIdentity.user = user;
newIdentity.providerType = providerType; newIdentity.providerType = providerType;
newIdentity.providerName = null;
newIdentity.syncSource = syncSource; newIdentity.syncSource = syncSource;
newIdentity.providerUserId = null;
newIdentity.oAuthAccessToken = null;
newIdentity.passwordHash = null;
return newIdentity; return newIdentity;
} }
} }

View file

@ -59,7 +59,7 @@ export class MediaUpload {
extension: string, extension: string,
backendType: BackendType, backendType: BackendType,
backendData?: string, backendData?: string,
): MediaUpload { ): Omit<MediaUpload, 'createdAt'> {
const upload = new MediaUpload(); const upload = new MediaUpload();
const randomBytes = crypto.randomBytes(16); const randomBytes = crypto.randomBytes(16);
upload.id = randomBytes.toString('hex') + '.' + extension; upload.id = randomBytes.toString('hex') + '.' + extension;

View file

@ -54,10 +54,11 @@ export class Alias {
// eslint-disable-next-line @typescript-eslint/no-empty-function // eslint-disable-next-line @typescript-eslint/no-empty-function
private constructor() {} private constructor() {}
static create(name: string, primary = false): Alias { static create(name: string, note: Note, primary = false): Omit<Alias, 'id'> {
const alias = new Alias(); const alias = new Alias();
alias.name = name; alias.name = name;
alias.primary = primary; alias.primary = primary;
alias.note = note;
return alias; return alias;
} }
} }

View file

@ -27,40 +27,49 @@ import { generatePublicId } from './utils';
export class Note { export class Note {
@PrimaryGeneratedColumn('uuid') @PrimaryGeneratedColumn('uuid')
id: string; id: string;
@Column({ type: 'text' }) @Column({ type: 'text' })
publicId: string; publicId: string;
@OneToMany( @OneToMany(
(_) => Alias, (_) => Alias,
(alias) => alias.note, (alias) => alias.note,
{ cascade: true }, // This ensures that embedded Aliases are automatically saved to the database { cascade: true }, // This ensures that embedded Aliases are automatically saved to the database
) )
aliases: Alias[]; aliases: Alias[];
@OneToMany( @OneToMany(
(_) => NoteGroupPermission, (_) => NoteGroupPermission,
(groupPermission) => groupPermission.note, (groupPermission) => groupPermission.note,
{ cascade: true }, // This ensures that embedded NoteGroupPermissions are automatically saved to the database { cascade: true }, // This ensures that embedded NoteGroupPermissions are automatically saved to the database
) )
groupPermissions: NoteGroupPermission[]; groupPermissions: NoteGroupPermission[];
@OneToMany( @OneToMany(
(_) => NoteUserPermission, (_) => NoteUserPermission,
(userPermission) => userPermission.note, (userPermission) => userPermission.note,
{ cascade: true }, // This ensures that embedded NoteUserPermission are automatically saved to the database { cascade: true }, // This ensures that embedded NoteUserPermission are automatically saved to the database
) )
userPermissions: NoteUserPermission[]; userPermissions: NoteUserPermission[];
@Column({ @Column({
nullable: false, nullable: false,
default: 0, default: 0,
}) })
viewCount: number; viewCount: number;
@ManyToOne((_) => User, (user) => user.ownedNotes, { @ManyToOne((_) => User, (user) => user.ownedNotes, {
onDelete: 'CASCADE', // This deletes the Note, when the associated User is deleted onDelete: 'CASCADE', // This deletes the Note, when the associated User is deleted
nullable: true, nullable: true,
}) })
owner: User | null; owner: User | null;
@OneToMany((_) => Revision, (revision) => revision.note, { cascade: true }) @OneToMany((_) => Revision, (revision) => revision.note, { cascade: true })
revisions: Promise<Revision[]>; revisions: Promise<Revision[]>;
@OneToMany((_) => HistoryEntry, (historyEntry) => historyEntry.user) @OneToMany((_) => HistoryEntry, (historyEntry) => historyEntry.user)
historyEntries: HistoryEntry[]; historyEntries: HistoryEntry[];
@OneToMany((_) => MediaUpload, (mediaUpload) => mediaUpload.note) @OneToMany((_) => MediaUpload, (mediaUpload) => mediaUpload.note)
mediaUploads: MediaUpload[]; mediaUploads: MediaUpload[];
@ -69,6 +78,7 @@ export class Note {
type: 'text', type: 'text',
}) })
description: string | null; description: string | null;
@Column({ @Column({
nullable: true, nullable: true,
type: 'text', type: 'text',
@ -82,15 +92,19 @@ export class Note {
// eslint-disable-next-line @typescript-eslint/no-empty-function // eslint-disable-next-line @typescript-eslint/no-empty-function
private constructor() {} private constructor() {}
public static create(owner?: User, alias?: string): Note { public static create(owner?: User, alias?: string): Omit<Note, 'id'> {
const newNote = new Note(); const newNote = new Note();
newNote.publicId = generatePublicId(); newNote.publicId = generatePublicId();
newNote.aliases = alias ? [Alias.create(alias, true)] : []; newNote.aliases = alias
newNote.viewCount = 0; ? [Alias.create(alias, newNote, true) as Alias]
newNote.owner = owner ?? null; : [];
newNote.userPermissions = []; newNote.userPermissions = [];
newNote.groupPermissions = []; newNote.groupPermissions = [];
newNote.revisions = Promise.resolve([]) as Promise<Revision[]>; newNote.viewCount = 0;
newNote.owner = owner ?? null;
newNote.revisions = Promise.resolve([]);
newNote.historyEntries = [];
newNote.mediaUploads = [];
newNote.description = null; newNote.description = null;
newNote.title = null; newNote.title = null;
newNote.tags = []; newNote.tags = [];

View file

@ -28,9 +28,14 @@ export class NoteGroupPermission {
// eslint-disable-next-line @typescript-eslint/no-empty-function // eslint-disable-next-line @typescript-eslint/no-empty-function
private constructor() {} private constructor() {}
public static create(group: Group, canEdit: boolean): NoteGroupPermission { public static create(
group: Group,
note: Note,
canEdit: boolean,
): NoteGroupPermission {
const groupPermission = new NoteGroupPermission(); const groupPermission = new NoteGroupPermission();
groupPermission.group = group; groupPermission.group = group;
groupPermission.note = note;
groupPermission.canEdit = canEdit; groupPermission.canEdit = canEdit;
return groupPermission; return groupPermission;
} }

View file

@ -28,9 +28,14 @@ export class NoteUserPermission {
// eslint-disable-next-line @typescript-eslint/no-empty-function // eslint-disable-next-line @typescript-eslint/no-empty-function
private constructor() {} private constructor() {}
public static create(user: User, canEdit: boolean): NoteUserPermission { public static create(
user: User,
note: Note,
canEdit: boolean,
): NoteUserPermission {
const userPermission = new NoteUserPermission(); const userPermission = new NoteUserPermission();
userPermission.user = user; userPermission.user = user;
userPermission.note = note;
userPermission.canEdit = canEdit; userPermission.canEdit = canEdit;
return userPermission; return userPermission;
} }

View file

@ -51,8 +51,13 @@ export class Edit {
// eslint-disable-next-line @typescript-eslint/no-empty-function // eslint-disable-next-line @typescript-eslint/no-empty-function
private constructor() {} private constructor() {}
public static create(author: Author, startPos: number, endPos: number) { public static create(
author: Author,
startPos: number,
endPos: number,
): Omit<Edit, 'id' | 'createdAt' | 'updatedAt'> {
const newEdit = new Edit(); const newEdit = new Edit();
newEdit.revisions = [];
newEdit.author = author; newEdit.author = author;
newEdit.startPos = startPos; newEdit.startPos = startPos;
newEdit.endPos = endPos; newEdit.endPos = endPos;

View file

@ -59,6 +59,7 @@ export class Revision {
*/ */
@ManyToOne((_) => Note, (note) => note.revisions, { onDelete: 'CASCADE' }) @ManyToOne((_) => Note, (note) => note.revisions, { onDelete: 'CASCADE' })
note: Note; note: Note;
/** /**
* All edit objects which are used in the revision. * All edit objects which are used in the revision.
*/ */
@ -69,11 +70,17 @@ export class Revision {
// eslint-disable-next-line @typescript-eslint/no-empty-function // eslint-disable-next-line @typescript-eslint/no-empty-function
private constructor() {} private constructor() {}
static create(content: string, patch: string): Revision { static create(
content: string,
patch: string,
note: Note,
): Omit<Revision, 'id' | 'createdAt'> {
const newRevision = new Revision(); const newRevision = new Revision();
newRevision.patch = patch; newRevision.patch = patch;
newRevision.content = content; newRevision.content = content;
newRevision.length = content.length; newRevision.length = content.length;
newRevision.note = note;
newRevision.edits = [];
return newRevision; return newRevision;
} }
} }

View file

@ -57,9 +57,9 @@ createConnection({
users.push(User.create('hardcoded_2', 'Test User 2')); users.push(User.create('hardcoded_2', 'Test User 2'));
users.push(User.create('hardcoded_3', 'Test User 3')); users.push(User.create('hardcoded_3', 'Test User 3'));
const notes: Note[] = []; const notes: Note[] = [];
notes.push(Note.create(undefined, 'test')); notes.push(Note.create(undefined, 'test') as Note);
notes.push(Note.create(undefined, 'test2')); notes.push(Note.create(undefined, 'test2') as Note);
notes.push(Note.create(undefined, 'test3')); notes.push(Note.create(undefined, 'test3') as Note);
for (let i = 0; i < 3; i++) { for (let i = 0; i < 3; i++) {
const author = connection.manager.create(Author, Author.create(1)); const author = connection.manager.create(Author, Author.create(1));
@ -71,8 +71,9 @@ createConnection({
const revision = Revision.create( const revision = Revision.create(
'This is a test note', 'This is a test note',
'This is a test note', 'This is a test note',
); notes[i],
const edit = Edit.create(author, 1, 42); ) as Revision;
const edit = Edit.create(author, 1, 42) as Edit;
revision.edits = [edit]; revision.edits = [edit];
notes[i].revisions = Promise.all([revision]); notes[i].revisions = Promise.all([revision]);
notes[i].userPermissions = []; notes[i].userPermissions = [];

View file

@ -79,13 +79,19 @@ export class User {
public static create( public static create(
username: string, username: string,
displayName: string, displayName: string,
): Pick< ): Omit<User, 'id' | 'createdAt' | 'updatedAt'> {
User,
'username' | 'displayName' | 'ownedNotes' | 'authTokens' | 'identities'
> {
const newUser = new User(); const newUser = new User();
newUser.username = username; newUser.username = username;
newUser.displayName = displayName; newUser.displayName = displayName;
newUser.photo = null;
newUser.email = null;
newUser.ownedNotes = [];
newUser.authTokens = [];
newUser.identities = Promise.resolve([]);
newUser.groups = [];
newUser.historyEntries = [];
newUser.mediaUploads = [];
newUser.authors = [];
return newUser; return newUser;
} }
} }