create database structure
This commit is contained in:
parent
e5b5d7e67d
commit
ad6ee64565
15 changed files with 483 additions and 14 deletions
|
|
@ -2,7 +2,6 @@ import "reflect-metadata";
|
|||
import { DataSource } from "typeorm";
|
||||
|
||||
import * as dotenv from "dotenv";
|
||||
import { UserEntity } from "./entities/UserEntity.js";
|
||||
|
||||
import { fileURLToPath } from "url";
|
||||
import { dirname, join } from "path";
|
||||
|
|
@ -26,7 +25,7 @@ export const AppDataSource = new DataSource({
|
|||
synchronize: NODE_ENV === "dev" ? false : false,
|
||||
//logging logs sql command on the terminal
|
||||
logging: NODE_ENV === "dev" ? false : false,
|
||||
entities: [join(__dirname, "/entities/*.{ts,js}")],
|
||||
migrations: [join(__dirname, "/migrations/*.{ts,js}")],
|
||||
entities: [join(__dirname, "/entities/*.{js, ts}")],
|
||||
migrations: [join(__dirname, "/migrations/*.js")],
|
||||
subscribers: [],
|
||||
});
|
||||
|
|
@ -8,9 +8,9 @@ export abstract class AbstractEntity {
|
|||
@PrimaryGeneratedColumn("uuid")
|
||||
id?: string;
|
||||
|
||||
@CreateDateColumn()
|
||||
createdAt?: Date;
|
||||
@CreateDateColumn({name: "create_date"})
|
||||
createDate?: Date;
|
||||
|
||||
@UpdateDateColumn()
|
||||
updatedAt?: Date;
|
||||
@UpdateDateColumn({name: "update_date"})
|
||||
updateDate?: Date;
|
||||
}
|
||||
|
|
|
|||
31
src/entities/RecipeEntity.ts
Normal file
31
src/entities/RecipeEntity.ts
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
import { Entity, Column, OneToMany, Relation } from "typeorm";
|
||||
import { AbstractEntity } from "./AbstractEntity.js";
|
||||
import { RecipeInstructionStepEntity } from "./RecipeInstructionStepEntity.js";
|
||||
import { RecipeIngredientGroupEntity } from "./RecipeIngredientGroupEntity.js";
|
||||
|
||||
/**
|
||||
* Entity describing a recipe
|
||||
*/
|
||||
@Entity({ name: "recipe" })
|
||||
export class RecipeEntity extends AbstractEntity {
|
||||
@Column({ nullable: false })
|
||||
title!: string;
|
||||
|
||||
@Column({ nullable: true })
|
||||
amount?: number;
|
||||
|
||||
@Column({ nullable: true, name: "amount_description" })
|
||||
amountDescription?: string;
|
||||
|
||||
// make sure not to induce a circular dependency! user arrow function without brackets!
|
||||
@OneToMany(() => RecipeInstructionStepEntity, (instructionStep) => instructionStep.recipe, {
|
||||
cascade: true,
|
||||
})
|
||||
instructionSteps!: RecipeInstructionStepEntity[];
|
||||
|
||||
// make sure not to induce a circular dependency! user arrow function without brackets!
|
||||
@OneToMany(() => RecipeIngredientGroupEntity, (ingredientGroup) => ingredientGroup.recipe, {
|
||||
cascade: true,
|
||||
})
|
||||
ingredientGroups!: Relation<RecipeIngredientGroupEntity>[];
|
||||
}
|
||||
20
src/entities/RecipeIngredientEntity.ts
Normal file
20
src/entities/RecipeIngredientEntity.ts
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
import { Column, Entity, ManyToOne, Relation} from "typeorm";
|
||||
import { AbstractEntity } from "./AbstractEntity.js";
|
||||
import { RecipeIngredientGroupEntity } from "./RecipeIngredientGroupEntity.js";
|
||||
|
||||
/**
|
||||
* Entity describing an ingredient group for a recipe
|
||||
*/
|
||||
@Entity({ name: "recipe_ingredient" })
|
||||
export class RecipeIngredientEntity extends AbstractEntity {
|
||||
@Column({ nullable: false })
|
||||
title!: string;
|
||||
|
||||
@Column({ nullable: false })
|
||||
sortOrder!: number;
|
||||
|
||||
@ManyToOne(() => RecipeIngredientGroupEntity, (ingredientGroup) => ingredientGroup.ingredients,
|
||||
{onDelete: "CASCADE"}
|
||||
)
|
||||
ingredientGroup!: Relation<RecipeIngredientGroupEntity>;
|
||||
}
|
||||
27
src/entities/RecipeIngredientGroupEntity.ts
Normal file
27
src/entities/RecipeIngredientGroupEntity.ts
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
import { Column, Entity, ManyToOne, OneToMany, Relation} from "typeorm";
|
||||
import { AbstractEntity } from "./AbstractEntity.js";
|
||||
import { RecipeEntity } from "./RecipeEntity.js";
|
||||
import { RecipeIngredientEntity } from "./RecipeIngredientEntity.js";
|
||||
|
||||
/**
|
||||
* Entity describing an ingredient group for a recipe
|
||||
*/
|
||||
@Entity({ name: "recipe_ingredient_group" })
|
||||
export class RecipeIngredientGroupEntity extends AbstractEntity {
|
||||
@Column({ nullable: false })
|
||||
title!: string;
|
||||
|
||||
@Column({ nullable: false })
|
||||
sortOrder!: number;
|
||||
|
||||
@ManyToOne(() => RecipeEntity, (recipe) => recipe.ingredientGroups,
|
||||
{onDelete: "CASCADE"}
|
||||
)
|
||||
recipe!: Relation<RecipeEntity>;
|
||||
|
||||
@OneToMany(() => RecipeIngredientEntity, (ingredient) => ingredient.ingredientGroup, {
|
||||
cascade: true,
|
||||
})
|
||||
ingredients!: Relation<RecipeIngredientEntity>[];
|
||||
|
||||
}
|
||||
21
src/entities/RecipeInstructionStepEntity.ts
Normal file
21
src/entities/RecipeInstructionStepEntity.ts
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
import { Column, Entity, ManyToOne, Relation} from "typeorm";
|
||||
import { AbstractEntity } from "./AbstractEntity.js";
|
||||
import { RecipeEntity } from "./RecipeEntity.js";
|
||||
|
||||
/**
|
||||
* Entity describing an instruction step for a recipe
|
||||
*/
|
||||
@Entity({ name: "recipe_instruction_step" })
|
||||
export class RecipeInstructionStepEntity extends AbstractEntity {
|
||||
@Column({ nullable: false })
|
||||
text!: string;
|
||||
|
||||
@Column({ nullable: false })
|
||||
sortOrder!: number;
|
||||
|
||||
@ManyToOne(() => RecipeEntity, (recipe) => recipe.instructionSteps,
|
||||
{onDelete: "CASCADE"}
|
||||
)
|
||||
recipe!: Relation<RecipeEntity>;
|
||||
|
||||
}
|
||||
|
|
@ -1,9 +1,10 @@
|
|||
import { Entity, Column } from "typeorm";
|
||||
import { AbstractEntity } from "./AbstractEntity.js";
|
||||
|
||||
// @todo Add migration to update table
|
||||
@Entity({ name: "user" })
|
||||
export class UserEntity extends AbstractEntity {
|
||||
@Column({ nullable: false })
|
||||
@Column({ nullable: false, name: "user_name" })
|
||||
userName!: string;
|
||||
|
||||
@Column({ nullable: false })
|
||||
|
|
@ -12,10 +13,10 @@ export class UserEntity extends AbstractEntity {
|
|||
@Column({ nullable: false })
|
||||
password!: string;
|
||||
|
||||
@Column({ nullable: true })
|
||||
@Column({ nullable: true, name: "first_name"})
|
||||
firstName?: string;
|
||||
|
||||
@Column({ nullable: true })
|
||||
@Column({ nullable: true, name: "last_name"})
|
||||
lastName?: string;
|
||||
|
||||
@Column({ default: "user" })
|
||||
|
|
|
|||
|
|
@ -16,11 +16,13 @@ app.use(errorHandler);
|
|||
|
||||
async function startServer() {
|
||||
try {
|
||||
console.log("starting server")
|
||||
// Initialize database
|
||||
await AppDataSource.initialize();
|
||||
console.log("Data Source initialized");
|
||||
|
||||
// Run pending migrations
|
||||
console.log(AppDataSource.migrations);
|
||||
await AppDataSource.runMigrations();
|
||||
console.log("Migrations executed");
|
||||
|
||||
|
|
|
|||
|
|
@ -10,8 +10,8 @@ export abstract class AbstractDtoEntityMapper<
|
|||
*/
|
||||
protected mapBaseEntityToDto(entity: E, dto: D): D {
|
||||
dto.id = entity.id;
|
||||
dto.createdAt = entity.createdAt;
|
||||
dto.updatedAt = entity.updatedAt;
|
||||
dto.createdAt = entity.createDate;
|
||||
dto.updatedAt = entity.updateDate;
|
||||
return dto;
|
||||
}
|
||||
|
||||
|
|
@ -20,8 +20,8 @@ export abstract class AbstractDtoEntityMapper<
|
|||
*/
|
||||
protected mapBaseDtoToEntity(dto: D, entity: E): E {
|
||||
entity.id = dto.id;
|
||||
entity.createdAt = dto.createdAt;
|
||||
entity.updatedAt = dto.updatedAt;
|
||||
entity.createDate = dto.createdAt;
|
||||
entity.updateDate = dto.updatedAt;
|
||||
return entity;
|
||||
}
|
||||
|
||||
|
|
|
|||
83
src/migrations/1758958856261-RenameUserColumns.ts
Normal file
83
src/migrations/1758958856261-RenameUserColumns.ts
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
import { MigrationInterface, QueryRunner, TableUnique } from "typeorm";
|
||||
|
||||
export class RenameUserColumns1758958856261 implements MigrationInterface {
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
// drop constraint
|
||||
await queryRunner.dropUniqueConstraint("user", "UQ_user_userName");
|
||||
// rename columns
|
||||
await queryRunner.renameColumn(
|
||||
"user",
|
||||
"userName",
|
||||
"user_name"
|
||||
)
|
||||
await queryRunner.renameColumn(
|
||||
"user",
|
||||
"firstName",
|
||||
"first_name"
|
||||
)
|
||||
await queryRunner.renameColumn(
|
||||
"user",
|
||||
"lastName",
|
||||
"last_name"
|
||||
)
|
||||
await queryRunner.renameColumn(
|
||||
"user",
|
||||
"createdAt",
|
||||
"create_date"
|
||||
)
|
||||
await queryRunner.renameColumn(
|
||||
"user",
|
||||
"updatedAt",
|
||||
"update_date"
|
||||
)
|
||||
// Add a unique constraint on user_name
|
||||
await queryRunner.createUniqueConstraint(
|
||||
"user",
|
||||
new TableUnique({
|
||||
columnNames: ["user_name"],
|
||||
name: "UQ_user_user_name",
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
// drop constraint
|
||||
await queryRunner.dropUniqueConstraint("user", "UQ_user_user_name");
|
||||
// rename columns
|
||||
await queryRunner.renameColumn(
|
||||
"user",
|
||||
"user_name",
|
||||
"userName",
|
||||
)
|
||||
await queryRunner.renameColumn(
|
||||
"user",
|
||||
"first_name",
|
||||
"firstName"
|
||||
)
|
||||
await queryRunner.renameColumn(
|
||||
"user",
|
||||
"last_name",
|
||||
"lastName"
|
||||
)
|
||||
await queryRunner.renameColumn(
|
||||
"user",
|
||||
"create_date",
|
||||
"createdAt"
|
||||
)
|
||||
await queryRunner.renameColumn(
|
||||
"user",
|
||||
"update_date",
|
||||
"updatedAt"
|
||||
)
|
||||
// Add a unique constraint on userName
|
||||
await queryRunner.createUniqueConstraint(
|
||||
"user",
|
||||
new TableUnique({
|
||||
columnNames: ["userName"],
|
||||
name: "UQ_user_userName",
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
48
src/migrations/1758959405239-CreateRecipeTable.ts
Normal file
48
src/migrations/1758959405239-CreateRecipeTable.ts
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
import { MigrationInterface, QueryRunner, Table } from "typeorm";
|
||||
|
||||
export class CreateRecipeTable1758959405239 implements MigrationInterface {
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
// Create table
|
||||
await queryRunner.createTable(
|
||||
new Table({
|
||||
name: "recipe",
|
||||
columns: [
|
||||
{
|
||||
name: "id",
|
||||
type: "uuid",
|
||||
isPrimary: true,
|
||||
isGenerated: true,
|
||||
generationStrategy: "uuid",
|
||||
},
|
||||
{
|
||||
name: "amount",
|
||||
type: "varchar",
|
||||
isNullable: true,
|
||||
},
|
||||
{
|
||||
name: "amount_description",
|
||||
type: "varchar",
|
||||
isNullable: true,
|
||||
},
|
||||
{
|
||||
name: "create_date",
|
||||
type: "timestamp",
|
||||
default: "now()",
|
||||
},
|
||||
{
|
||||
name: "update_date",
|
||||
type: "timestamp",
|
||||
default: "now()",
|
||||
},
|
||||
],
|
||||
}),
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
// Drop the table
|
||||
await queryRunner.dropTable("recipe");
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,79 @@
|
|||
import {
|
||||
MigrationInterface,
|
||||
QueryRunner,
|
||||
Table,
|
||||
TableForeignKey,
|
||||
} from "typeorm";
|
||||
|
||||
export class CreateRecipeIngredientGroupTable1758959437946
|
||||
implements MigrationInterface
|
||||
{
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
// Create table
|
||||
await queryRunner.createTable(
|
||||
new Table({
|
||||
name: "recipe_ingredient_group",
|
||||
columns: [
|
||||
{
|
||||
name: "id",
|
||||
type: "uuid",
|
||||
isPrimary: true,
|
||||
isGenerated: true,
|
||||
generationStrategy: "uuid",
|
||||
},
|
||||
{
|
||||
name: "create_date",
|
||||
type: "timestamp",
|
||||
default: "now()",
|
||||
},
|
||||
{
|
||||
name: "update_date",
|
||||
type: "timestamp",
|
||||
default: "now()",
|
||||
},
|
||||
{
|
||||
name: "title",
|
||||
type: "varchar",
|
||||
isNullable: false,
|
||||
},
|
||||
{
|
||||
name: "sortOrder",
|
||||
type: "int",
|
||||
isNullable: false,
|
||||
},
|
||||
{
|
||||
name: "recipe_id", // foreign key column
|
||||
type: "uuid",
|
||||
isNullable: false,
|
||||
},
|
||||
],
|
||||
}),
|
||||
true
|
||||
);
|
||||
|
||||
// Add foreign key to recipe table
|
||||
await queryRunner.createForeignKey(
|
||||
"recipe_ingredient_group",
|
||||
new TableForeignKey({
|
||||
columnNames: ["recipe_id"],
|
||||
referencedTableName: "recipe",
|
||||
referencedColumnNames: ["id"],
|
||||
onDelete: "CASCADE", // delete ingredient groups if recipe is deleted
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
// Drop foreign key first
|
||||
const table = await queryRunner.getTable("recipe_ingredient_group");
|
||||
const foreignKey = table?.foreignKeys.find(
|
||||
(fk) => fk.columnNames.indexOf("recipe_id") !== -1
|
||||
);
|
||||
if (foreignKey) {
|
||||
await queryRunner.dropForeignKey("recipe_ingredient_group", foreignKey);
|
||||
}
|
||||
|
||||
// Drop table
|
||||
await queryRunner.dropTable("recipe_ingredient_group");
|
||||
}
|
||||
}
|
||||
79
src/migrations/1758959442589-CreateRecipeIngredientTable.ts
Normal file
79
src/migrations/1758959442589-CreateRecipeIngredientTable.ts
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
import {
|
||||
MigrationInterface,
|
||||
QueryRunner,
|
||||
Table,
|
||||
TableForeignKey,
|
||||
} from "typeorm";
|
||||
|
||||
export class CreateRecipeIngredientTable1758959442589
|
||||
implements MigrationInterface
|
||||
{
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
// Create table
|
||||
await queryRunner.createTable(
|
||||
new Table({
|
||||
name: "recipe_ingredient",
|
||||
columns: [
|
||||
{
|
||||
name: "id",
|
||||
type: "uuid",
|
||||
isPrimary: true,
|
||||
isGenerated: true,
|
||||
generationStrategy: "uuid",
|
||||
},
|
||||
{
|
||||
name: "create_date",
|
||||
type: "timestamp",
|
||||
default: "now()",
|
||||
},
|
||||
{
|
||||
name: "update_date",
|
||||
type: "timestamp",
|
||||
default: "now()",
|
||||
},
|
||||
{
|
||||
name: "title",
|
||||
type: "varchar",
|
||||
isNullable: false,
|
||||
},
|
||||
{
|
||||
name: "sortOrder",
|
||||
type: "int",
|
||||
isNullable: false,
|
||||
},
|
||||
{
|
||||
name: "recipe_ingredient_group_id", // foreign key column
|
||||
type: "uuid",
|
||||
isNullable: false,
|
||||
},
|
||||
],
|
||||
}),
|
||||
true
|
||||
);
|
||||
|
||||
// Add foreign key to recipe table
|
||||
await queryRunner.createForeignKey(
|
||||
"recipe_ingredient",
|
||||
new TableForeignKey({
|
||||
columnNames: ["recipe_ingredient_group_id"],
|
||||
referencedTableName: "recipe_ingredient_group",
|
||||
referencedColumnNames: ["id"],
|
||||
onDelete: "CASCADE", // delete ingredient if ingredient_group is deleted
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
// Drop foreign key first
|
||||
const table = await queryRunner.getTable("recipe_ingredient");
|
||||
const foreignKey = table?.foreignKeys.find(
|
||||
(fk) => fk.columnNames.indexOf("recipe_ingredient_group_id") !== -1
|
||||
);
|
||||
if (foreignKey) {
|
||||
await queryRunner.dropForeignKey("recipe_ingredient", foreignKey);
|
||||
}
|
||||
|
||||
// Drop table
|
||||
await queryRunner.dropTable("recipe_ingredient");
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,79 @@
|
|||
import {
|
||||
MigrationInterface,
|
||||
QueryRunner,
|
||||
Table,
|
||||
TableForeignKey,
|
||||
} from "typeorm";
|
||||
|
||||
export class CreateRecipeInstructionStepTable1758959460127
|
||||
implements MigrationInterface
|
||||
{
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
// Create table
|
||||
await queryRunner.createTable(
|
||||
new Table({
|
||||
name: "recipe_instruction_step",
|
||||
columns: [
|
||||
{
|
||||
name: "id",
|
||||
type: "uuid",
|
||||
isPrimary: true,
|
||||
isGenerated: true,
|
||||
generationStrategy: "uuid",
|
||||
},
|
||||
{
|
||||
name: "create_date",
|
||||
type: "timestamp",
|
||||
default: "now()",
|
||||
},
|
||||
{
|
||||
name: "update_date",
|
||||
type: "timestamp",
|
||||
default: "now()",
|
||||
},
|
||||
{
|
||||
name: "text",
|
||||
type: "varchar",
|
||||
isNullable: false,
|
||||
},
|
||||
{
|
||||
name: "sortOrder",
|
||||
type: "int",
|
||||
isNullable: false,
|
||||
},
|
||||
{
|
||||
name: "recipe_id", // foreign key column
|
||||
type: "uuid",
|
||||
isNullable: false,
|
||||
},
|
||||
],
|
||||
}),
|
||||
true
|
||||
);
|
||||
|
||||
// Add foreign key to recipe table
|
||||
await queryRunner.createForeignKey(
|
||||
"recipe_instruction_step",
|
||||
new TableForeignKey({
|
||||
columnNames: ["recipe_id"],
|
||||
referencedTableName: "recipe",
|
||||
referencedColumnNames: ["id"],
|
||||
onDelete: "CASCADE", // delete instruction steps if recipe is deleted
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
// Drop foreign key first
|
||||
const table = await queryRunner.getTable("recipe_instruction_step");
|
||||
const foreignKey = table?.foreignKeys.find(
|
||||
(fk) => fk.columnNames.indexOf("recipe_id") !== -1
|
||||
);
|
||||
if (foreignKey) {
|
||||
await queryRunner.dropForeignKey("recipe_instruction_step", foreignKey);
|
||||
}
|
||||
|
||||
// Drop table
|
||||
await queryRunner.dropTable("recipe_instruction_step");
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue