Added users entity; formatted all files

This commit is contained in:
2026-02-03 14:34:18 -05:00
parent 223558d480
commit 3d8d23ed69
17 changed files with 379 additions and 200 deletions

View File

@@ -7,12 +7,14 @@ import { AppService } from './app.service';
import { dataSourceOptions } from './appDataSource';
import { EntryModule } from './entries/entries.module';
import { DataSource, EntityManager } from 'typeorm';
import { UsersModule } from './users/users.module';
@Module({
imports: [
ConfigModule.forRoot(),
TypeOrmModule.forRoot(dataSourceOptions),
EntryModule
EntryModule,
UsersModule,
],
controllers: [AppController],
providers: [AppService],

View File

@@ -1,18 +1,24 @@
import { DataSource, DataSourceOptions } from "typeorm";
import { Entry } from "./entries/entries.entity";
import { Initial1770020781006 } from "./migrations/1770020781006-initial";
import { AddCreateUpdateDeleteColsAndRenameSourceToSourceText1770067407619 } from "./migrations/1770067407619-add_create_update_delete_cols_and_rename_source_to_sourceText";
import { DataSource, DataSourceOptions } from 'typeorm';
import { Entry } from './entries/entries.entity';
import { Initial1770020781006 } from './migrations/1770020781006-initial';
import { AddCreateUpdateDeleteColsAndRenameSourceToSourceText1770067407619 } from './migrations/1770067407619-add_create_update_delete_cols_and_rename_source_to_sourceText';
import { User } from './users/users.entity';
import { AddUserEntity1770147074119 } from './migrations/1770147074119-add_user_entity';
export const dataSourceOptions: DataSourceOptions = {
type: "postgres",
type: 'postgres',
host: process.env.DB_HOST,
port: parseInt(process.env.DB_PORT as string, 10) || 5432,
username: process.env.DB_USERNAME,
password: process.env.DB_PASSWORD,
database: process.env.DB_NAME,
entities: [Entry],
migrations: [Initial1770020781006, AddCreateUpdateDeleteColsAndRenameSourceToSourceText1770067407619],
entities: [Entry, User],
migrations: [
Initial1770020781006,
AddCreateUpdateDeleteColsAndRenameSourceToSourceText1770067407619,
AddUserEntity1770147074119
],
synchronize: false,
}
};
export const appDataSource = new DataSource(dataSourceOptions)
export const appDataSource = new DataSource(dataSourceOptions);

View File

@@ -1,4 +1,13 @@
import { Body, Controller, Delete, Get, Inject, Param, Post, Put } from '@nestjs/common';
import {
Body,
Controller,
Delete,
Get,
Inject,
Param,
Post,
Put,
} from '@nestjs/common';
import { EntryService } from './entries.service';
import { EntryDTO } from './entries.dto';
import { Entry } from './entries.entity';
@@ -7,44 +16,45 @@ import { Entry } from './entries.entity';
export class EntriesController {
constructor(
@Inject(EntryService)
private readonly entryService: EntryService
private readonly entryService: EntryService,
) {}
@Post()
async saveEntry(@Body() entry: EntryDTO): Promise<string | undefined> {
return (await this.entryService.save(entry)).uuid
return (await this.entryService.save(entry)).uuid;
}
@Put(":uuid")
async updateEntry(@Param("uuid") uuid: string, @Body() entry: EntryDTO): Promise<string | undefined> {
return (await this.entryService.updateByUuid(uuid, entry)).uuid
@Put(':uuid')
async updateEntry(
@Param('uuid') uuid: string,
@Body() entry: EntryDTO,
): Promise<string | undefined> {
return (await this.entryService.updateByUuid(uuid, entry)).uuid;
}
@Get()
async findAll(): Promise<Entry[]>
{
const entries = await this.entryService.findAll()
async findAll(): Promise<Entry[]> {
const entries = await this.entryService.findAll();
return entries.sort((a, b) => {
return (
new Date(b.created_at as Date).getTime() - new Date(a.created_at as Date).getTime()
)
})
new Date(b.created_at as Date).getTime() -
new Date(a.created_at as Date).getTime()
);
});
}
@Get(":uuid")
async findOneByUuid(@Param("uuid") uuid: string): Promise<Entry | null> {
return await this.entryService.findOneByUuid(uuid)
@Get(':uuid')
async findOneByUuid(@Param('uuid') uuid: string): Promise<Entry | null> {
return await this.entryService.findOneByUuid(uuid);
}
@Delete(":uuid")
async softDelete(@Param("uuid") uuid: string): Promise<void>
{
await this.entryService.softDeleteByUuid(uuid)
@Delete(':uuid')
async softDelete(@Param('uuid') uuid: string): Promise<void> {
await this.entryService.softDeleteByUuid(uuid);
}
@Put("/restore/:uuid")
async restoreSoftDeleted(@Param("uuid") uuid: string): Promise<Entry | null> {
return await this.entryService.restoreDeletedByUuid(uuid)
@Put('/restore/:uuid')
async restoreSoftDeleted(@Param('uuid') uuid: string): Promise<Entry | null> {
return await this.entryService.restoreDeletedByUuid(uuid);
}
}

View File

@@ -1,4 +1,4 @@
import { Entry } from "./entries.entity";
import { Entry } from './entries.entity';
export class EntryDTO implements Partial<Entry> {
title?: string;

View File

@@ -1,24 +1,32 @@
import { Column, CreateDateColumn, DeleteDateColumn, Entity, Generated, PrimaryGeneratedColumn, UpdateDateColumn } from "typeorm";
import {
Column,
CreateDateColumn,
DeleteDateColumn,
Entity,
Generated,
PrimaryGeneratedColumn,
UpdateDateColumn,
} from 'typeorm';
@Entity({ name: "entries"})
@Entity({ name: 'entries' })
export class Entry {
@Column()
@Generated("uuid")
@Generated('uuid')
uuid?: string;
@PrimaryGeneratedColumn("increment")
@PrimaryGeneratedColumn('increment')
id?: number;
@Column({ type: String, default: "" })
@Column({ type: String, default: '' })
title?: string;
@Column({ type: String, default: "" })
@Column({ type: String, default: '' })
description?: string;
@Column({ type: String, default: "" })
@Column({ type: String, default: '' })
sourceText?: string;
@Column({ type: String, default: "" })
@Column({ type: String, default: '' })
sourceUrl?: string;
@CreateDateColumn()

View File

@@ -1,13 +1,11 @@
import { Module } from "@nestjs/common";
import { TypeOrmModule } from "@nestjs/typeorm";
import { Entry } from "./entries.entity";
import { EntryService } from "./entries.service";
import { EntriesController } from "./entries.controller";
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { Entry } from './entries.entity';
import { EntryService } from './entries.service';
import { EntriesController } from './entries.controller';
@Module({
imports: [
TypeOrmModule.forFeature([Entry]),
],
imports: [TypeOrmModule.forFeature([Entry])],
exports: [EntryService],
providers: [EntryService],
controllers: [EntriesController],

View File

@@ -1,85 +1,85 @@
import { Injectable, Logger } from "@nestjs/common";
import { InjectRepository } from "@nestjs/typeorm";
import { Entry } from "./entries.entity";
import { Repository } from "typeorm";
import { EntryDTO } from "./entries.dto";
import EntryNotFoundException from "./exceptions/entryNotFound.exception";
import { Injectable, Logger } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Entry } from './entries.entity';
import { Repository } from 'typeorm';
import { EntryDTO } from './entries.dto';
import EntryNotFoundException from './exceptions/entryNotFound.exception';
@Injectable()
export class EntryService {
constructor(
@InjectRepository(Entry)
private entryRepository: Repository<Entry>
private entryRepository: Repository<Entry>,
) {}
private readonly logger = new Logger(EntryService.name)
private readonly logger = new Logger(EntryService.name);
async save(data: EntryDTO): Promise<Entry> {
this.logger.log("Creating entry")
this.logger.log('Creating entry');
const lEntry = new Entry();
lEntry.description = data.description
lEntry.sourceText = data.sourceText
lEntry.sourceUrl = data.sourceUrl
lEntry.title = data.title
await this.entryRepository.save(lEntry)
return lEntry
lEntry.description = data.description;
lEntry.sourceText = data.sourceText;
lEntry.sourceUrl = data.sourceUrl;
lEntry.title = data.title;
await this.entryRepository.save(lEntry);
return lEntry;
}
async updateByUuid(uuid: string, data: EntryDTO): Promise<Entry> {
this.logger.log(`Updating entry with UUID ${uuid}`)
const oldEntry = await this.entryRepository.findOneBy({ uuid: uuid})
this.logger.log(`Updating entry with UUID ${uuid}`);
const oldEntry = await this.entryRepository.findOneBy({ uuid: uuid });
if (!oldEntry) {
throw new EntryNotFoundException(uuid)
throw new EntryNotFoundException(uuid);
} else {
const lEntry = new Entry();
// `id` is the primary key
lEntry.id = oldEntry.id
lEntry.id = oldEntry.id;
// These options can be updated
lEntry.description = data.description
lEntry.sourceText = data.sourceText
lEntry.sourceUrl = data.sourceUrl
lEntry.title = data.title
await this.entryRepository.save(lEntry)
return lEntry
lEntry.description = data.description;
lEntry.sourceText = data.sourceText;
lEntry.sourceUrl = data.sourceUrl;
lEntry.title = data.title;
await this.entryRepository.save(lEntry);
return lEntry;
}
}
async findOneByUuid(uuid: string): Promise<Entry | null> {
this.logger.log(`Returning entry with UUID ${uuid}`)
return this.entryRepository.findOneByOrFail({uuid: uuid})
this.logger.log(`Returning entry with UUID ${uuid}`);
return this.entryRepository.findOneByOrFail({ uuid: uuid });
}
async findAll(): Promise<Entry[]> {
this.logger.log("Returning all entries")
return this.entryRepository.find()
this.logger.log('Returning all entries');
return this.entryRepository.find();
}
async findAllAndDeleted(): Promise<Entry[]> {
this.logger.log("Returning all entries, active and deleted")
return this.entryRepository.find({ withDeleted: true })
this.logger.log('Returning all entries, active and deleted');
return this.entryRepository.find({ withDeleted: true });
}
async softDeleteByUuid(uuid: string): Promise<void> {
this.logger.log(`Soft deleting entry with UUID ${uuid}`)
this.logger.log(`Soft deleting entry with UUID ${uuid}`);
const deleteResponse = await this.entryRepository.softDelete({
uuid: uuid
})
uuid: uuid,
});
if (!deleteResponse.affected) {
throw new EntryNotFoundException(uuid)
throw new EntryNotFoundException(uuid);
}
}
async restoreDeletedByUuid(uuid: string): Promise<Entry | null> {
this.logger.log(`Restoring entry with UUID ${uuid}`)
this.logger.log(`Restoring entry with UUID ${uuid}`);
const restoreResponse = await this.entryRepository.restore({
uuid: uuid
})
uuid: uuid,
});
if (!restoreResponse.affected) {
throw new EntryNotFoundException(uuid)
throw new EntryNotFoundException(uuid);
} else {
return this.entryRepository.findOneByOrFail({
uuid: uuid
})
uuid: uuid,
});
}
}
}

View File

@@ -1,9 +1,9 @@
import { NotFoundException } from "@nestjs/common";
import { NotFoundException } from '@nestjs/common';
class EntryNotFoundException extends NotFoundException {
constructor(uuid: string) {
super(`Entry with UUID ${uuid} not found`)
super(`Entry with UUID ${uuid} not found`);
}
}
export default EntryNotFoundException
export default EntryNotFoundException;

View File

@@ -1,14 +1,15 @@
import { MigrationInterface, QueryRunner } from "typeorm";
import { MigrationInterface, QueryRunner } from 'typeorm';
export class Initial1770020781006 implements MigrationInterface {
name = 'Initial1770020781006'
name = 'Initial1770020781006';
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`CREATE TABLE "entries" ("uuid" uuid NOT NULL DEFAULT uuid_generate_v4(), "id" SERIAL NOT NULL, "title" character varying NOT NULL DEFAULT '', "description" character varying NOT NULL DEFAULT '', "source" character varying NOT NULL DEFAULT '', "sourceUrl" character varying NOT NULL DEFAULT '', CONSTRAINT "PK_8640855ae82083455cbb806173d" PRIMARY KEY ("id"))`);
await queryRunner.query(
`CREATE TABLE "entries" ("uuid" uuid NOT NULL DEFAULT uuid_generate_v4(), "id" SERIAL NOT NULL, "title" character varying NOT NULL DEFAULT '', "description" character varying NOT NULL DEFAULT '', "source" character varying NOT NULL DEFAULT '', "sourceUrl" character varying NOT NULL DEFAULT '', CONSTRAINT "PK_8640855ae82083455cbb806173d" PRIMARY KEY ("id"))`,
);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`DROP TABLE "entries"`);
}
}

View File

@@ -1,13 +1,19 @@
import { MigrationInterface, QueryRunner } from "typeorm";
import { MigrationInterface, QueryRunner } from 'typeorm';
export class AddCreateUpdateDeleteColsAndRenameSourceToSourceText1770067407619 implements MigrationInterface {
name = 'AddCreateUpdateDeleteColsAndRenameSourceToSourceText1770067407619'
name = 'AddCreateUpdateDeleteColsAndRenameSourceToSourceText1770067407619';
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`ALTER TABLE "entries" DROP COLUMN "source"`);
await queryRunner.query(`ALTER TABLE "entries" ADD "sourceText" character varying NOT NULL DEFAULT ''`);
await queryRunner.query(`ALTER TABLE "entries" ADD "created_at" TIMESTAMP NOT NULL DEFAULT now()`);
await queryRunner.query(`ALTER TABLE "entries" ADD "updated_at" TIMESTAMP NOT NULL DEFAULT now()`);
await queryRunner.query(
`ALTER TABLE "entries" ADD "sourceText" character varying NOT NULL DEFAULT ''`,
);
await queryRunner.query(
`ALTER TABLE "entries" ADD "created_at" TIMESTAMP NOT NULL DEFAULT now()`,
);
await queryRunner.query(
`ALTER TABLE "entries" ADD "updated_at" TIMESTAMP NOT NULL DEFAULT now()`,
);
await queryRunner.query(`ALTER TABLE "entries" ADD "deleted_at" TIMESTAMP`);
}
@@ -16,7 +22,8 @@ export class AddCreateUpdateDeleteColsAndRenameSourceToSourceText1770067407619 i
await queryRunner.query(`ALTER TABLE "entries" DROP COLUMN "updated_at"`);
await queryRunner.query(`ALTER TABLE "entries" DROP COLUMN "created_at"`);
await queryRunner.query(`ALTER TABLE "entries" DROP COLUMN "sourceText"`);
await queryRunner.query(`ALTER TABLE "entries" ADD "source" character varying NOT NULL DEFAULT ''`);
await queryRunner.query(
`ALTER TABLE "entries" ADD "source" character varying NOT NULL DEFAULT ''`,
);
}
}

View File

@@ -0,0 +1,15 @@
import { MigrationInterface, QueryRunner } from 'typeorm';
export class AddUserEntity1770147074119 implements MigrationInterface {
name = 'AddUserEntity1770147074119';
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`CREATE TABLE "users" ("uuid" uuid NOT NULL DEFAULT uuid_generate_v4(), "id" SERIAL NOT NULL, "username" character varying NOT NULL DEFAULT '', "email" character varying NOT NULL DEFAULT '', "firstName" character varying NOT NULL DEFAULT '', "lastName" character varying NOT NULL DEFAULT '', "isAdmin" boolean NOT NULL DEFAULT false, "created_at" TIMESTAMP NOT NULL DEFAULT now(), "updated_at" TIMESTAMP NOT NULL DEFAULT now(), "deleted_at" TIMESTAMP, CONSTRAINT "PK_a3ffb1c0c8416b9fc6f907b7433" PRIMARY KEY ("id"))`,
);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`DROP TABLE "users"`);
}
}

View File

@@ -0,0 +1,18 @@
import { Test, TestingModule } from '@nestjs/testing';
import { UsersController } from './users.controller';
describe('UsersController', () => {
let controller: UsersController;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
controllers: [UsersController],
}).compile();
controller = module.get<UsersController>(UsersController);
});
it('should be defined', () => {
expect(controller).toBeDefined();
});
});

View File

@@ -0,0 +1,22 @@
import { Controller, Inject } from '@nestjs/common';
import { UserService } from './users.service';
@Controller('users')
export class UsersController {
constructor(
@Inject(UserService)
private readonly userService: UserService,
) {}
// TODO: POST: save (create new) user
// TODO: PUT: update user by UUID
// TODO: GET: find all users
// TODO: GET: find user by UUID
// TODO: DELETE: soft delete user
// TODO: PUT: restore soft deleted user
}

9
src/users/users.dto.ts Normal file
View File

@@ -0,0 +1,9 @@
import { User } from './users.entity';
export class UserDTO implements Partial<User> {
email?: string;
firstName?: string;
isAdmin?: boolean;
lastName?: string;
username?: string;
}

43
src/users/users.entity.ts Normal file
View File

@@ -0,0 +1,43 @@
import {
Column,
CreateDateColumn,
DeleteDateColumn,
Entity,
Generated,
PrimaryGeneratedColumn,
UpdateDateColumn,
} from 'typeorm';
@Entity({ name: 'users' })
export class User {
@Column()
@Generated('uuid')
uuid?: string;
@PrimaryGeneratedColumn('increment')
id?: number;
@Column({ type: String, default: '' })
username?: string;
@Column({ type: String, default: '' })
email?: string;
@Column({ type: String, default: '' })
firstName?: string;
@Column({ type: String, default: '' })
lastName?: string;
@Column({ type: Boolean, default: false })
isAdmin?: boolean;
@CreateDateColumn()
created_at?: Date;
@UpdateDateColumn()
updated_at?: Date;
@DeleteDateColumn()
deleted_at?: Date;
}

13
src/users/users.module.ts Normal file
View File

@@ -0,0 +1,13 @@
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { User } from './users.entity';
import { UserService } from './users.service';
import { UsersController } from './users.controller';
@Module({
imports: [TypeOrmModule.forFeature([User])],
exports: [UserService],
providers: [UserService],
controllers: [UsersController],
})
export class UsersModule {}

View File

@@ -0,0 +1,27 @@
import { Injectable, Logger } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { User } from './users.entity';
import { Repository } from 'typeorm';
@Injectable()
export class UserService {
constructor(
@InjectRepository(User)
private userRepository: Repository<User>,
) {}
private readonly logger = new Logger(UserService.name);
// TODO: save (create new)
// TODO: update by UUID
// TODO: find one by UUID
// TODO: find all
// TODO: find all and deleted
// TODO: soft delete
// TODO: restore soft deleted by UUID
}