Progress on API routes

This commit is contained in:
2026-02-02 03:35:41 -05:00
parent 82128e444e
commit 7bd0bbe2d8
15 changed files with 927 additions and 99 deletions

View File

@@ -1,29 +1,9 @@
<p align="center"> # Glossary API
<a href="http://nestjs.com/" target="blank"><img src="https://nestjs.com/img/logo-small.svg" width="120" alt="Nest Logo" /></a>
</p>
[circleci-image]: https://img.shields.io/circleci/build/github/nestjs/nest/master?token=abc123def456 ## Stack
[circleci-url]: https://circleci.com/gh/nestjs/nest
<p align="center">A progressive <a href="http://nodejs.org" target="_blank">Node.js</a> framework for building efficient and scalable server-side applications.</p> - [Nest](https://github.com/nestjs/nest)
<p align="center"> - TypeORM
<a href="https://www.npmjs.com/~nestjscore" target="_blank"><img src="https://img.shields.io/npm/v/@nestjs/core.svg" alt="NPM Version" /></a>
<a href="https://www.npmjs.com/~nestjscore" target="_blank"><img src="https://img.shields.io/npm/l/@nestjs/core.svg" alt="Package License" /></a>
<a href="https://www.npmjs.com/~nestjscore" target="_blank"><img src="https://img.shields.io/npm/dm/@nestjs/common.svg" alt="NPM Downloads" /></a>
<a href="https://circleci.com/gh/nestjs/nest" target="_blank"><img src="https://img.shields.io/circleci/build/github/nestjs/nest/master" alt="CircleCI" /></a>
<a href="https://discord.gg/G7Qnnhy" target="_blank"><img src="https://img.shields.io/badge/discord-online-brightgreen.svg" alt="Discord"/></a>
<a href="https://opencollective.com/nest#backer" target="_blank"><img src="https://opencollective.com/nest/backers/badge.svg" alt="Backers on Open Collective" /></a>
<a href="https://opencollective.com/nest#sponsor" target="_blank"><img src="https://opencollective.com/nest/sponsors/badge.svg" alt="Sponsors on Open Collective" /></a>
<a href="https://paypal.me/kamilmysliwiec" target="_blank"><img src="https://img.shields.io/badge/Donate-PayPal-ff3f59.svg" alt="Donate us"/></a>
<a href="https://opencollective.com/nest#sponsor" target="_blank"><img src="https://img.shields.io/badge/Support%20us-Open%20Collective-41B883.svg" alt="Support us"></a>
<a href="https://twitter.com/nestframework" target="_blank"><img src="https://img.shields.io/twitter/follow/nestframework.svg?style=social&label=Follow" alt="Follow us on Twitter"></a>
</p>
<!--[![Backers on Open Collective](https://opencollective.com/nest/backers/badge.svg)](https://opencollective.com/nest#backer)
[![Sponsors on Open Collective](https://opencollective.com/nest/sponsors/badge.svg)](https://opencollective.com/nest#sponsor)-->
## Description
[Nest](https://github.com/nestjs/nest) framework TypeScript starter repository.
## Project setup ## Project setup
@@ -31,9 +11,18 @@
$ npm install $ npm install
``` ```
## Running local infrastructure
To simplify development, all local infrastructure is handled with Docker. The folder `dev_infra` contains the folloeing:
- `compose.yaml`: A Docker compose file that stands up a Postgres database and a pgAdmin instance to visualize.
## Compile and run the project ## Compile and run the project
```bash ```bash
# Set env variables
$ . ./set-env
# development # development
$ npm run start $ npm run start
@@ -87,12 +76,6 @@ Check out a few resources that may come in handy when working with NestJS:
Nest is an MIT-licensed open source project. It can grow thanks to the sponsors and support by the amazing backers. If you'd like to join them, please [read more here](https://docs.nestjs.com/support). Nest is an MIT-licensed open source project. It can grow thanks to the sponsors and support by the amazing backers. If you'd like to join them, please [read more here](https://docs.nestjs.com/support).
## Stay in touch
- Author - [Kamil Myśliwiec](https://twitter.com/kammysliwiec)
- Website - [https://nestjs.com](https://nestjs.com/)
- Twitter - [@nestframework](https://twitter.com/nestframework)
## License ## License
Nest is [MIT licensed](https://github.com/nestjs/nest/blob/master/LICENSE). Nest is [MIT licensed](https://github.com/nestjs/nest/blob/master/LICENSE).

31
dev_infra/compose.yaml Normal file
View File

@@ -0,0 +1,31 @@
services:
# PostgreSQL database
postgresql_db:
image: postgres:latest
container_name: postgresql-db
restart: always
ports:
- "5432:5432" # Default PostgreSQL port
environment:
POSTGRES_USER: glossary_dev_user
POSTGRES_DB: glossary_dev_db
POSTGRES_PASSWORD: glossary_dev_password
volumes:
- postgresql_db_data:/var/lib/postgresql # Docker managed volume
# pgAdmin4 Web UI for PostgreSQL database
pgadmin:
image: dpage/pgadmin4:latest
container_name: pgadmin
restart: always
ports:
- "8888:80" # Expose Web UI to port 8888
environment:
PGADMIN_DEFAULT_EMAIL: admin@example.com
PGADMIN_DEFAULT_PASSWORD: postgresql
volumes:
- pgadmin_data:/var/lib/pgadmin # Docker managed volume
volumes:
postgresql_db_data:
pgadmin_data:

747
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -17,14 +17,26 @@
"test:watch": "jest --watch", "test:watch": "jest --watch",
"test:cov": "jest --coverage", "test:cov": "jest --coverage",
"test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
"test:e2e": "jest --config ./test/jest-e2e.json" "test:e2e": "jest --config ./test/jest-e2e.json",
"typeorm": "ts-node ./node_modules/typeorm/cli.js",
"db:gen_migration": "ts-node ./node_modules/typeorm/cli.js migration:generate src/migrations/migration -d src/appDataSource.ts",
"db:gen_migration_named": "ts-node ./node_modules/typeorm/cli.js migration:generate src/migrations/$npm_config_name -d src/appDataSource.ts",
"db:show_migrations": "ts-node ./node_modules/typeorm/cli.js migration:show -d src/appDataSource.ts",
"db:run_migrations": "ts-node ./node_modules/typeorm/cli.js migration:run -d src/appDataSource.ts",
"db:run_migrations_prod": "ts-node ./node_modules/typeorm/cli.js migration:run -d dist/appDataSource.ts"
}, },
"dependencies": { "dependencies": {
"@nestjs/common": "^11.0.1", "@nestjs/common": "^11.0.1",
"@nestjs/config": "^4.0.2",
"@nestjs/core": "^11.0.1", "@nestjs/core": "^11.0.1",
"@nestjs/platform-express": "^11.0.1", "@nestjs/platform-express": "^11.0.1",
"@nestjs/typeorm": "^11.0.0",
"pg": "^8.18.0",
"reflect-metadata": "^0.2.2", "reflect-metadata": "^0.2.2",
"rxjs": "^7.8.1" "rxjs": "^7.8.1",
"typeorm": "^0.3.28"
}, },
"devDependencies": { "devDependencies": {
"@eslint/eslintrc": "^3.2.0", "@eslint/eslintrc": "^3.2.0",

8
set-env.sh Normal file
View File

@@ -0,0 +1,8 @@
export DB_TYPE=postgres
export DB_HOST=localhost
export DB_PORT=5432
export DB_USERNAME=glossary_dev_user
export DB_PASSWORD=glossary_dev_password
export DB_NAME=glossary_dev_db
npm run db:run_migrations

View File

@@ -1,10 +1,25 @@
import { Module } from '@nestjs/common'; import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import { TypeOrmModule } from '@nestjs/typeorm';
import { AppController } from './app.controller'; import { AppController } from './app.controller';
import { AppService } from './app.service'; import { AppService } from './app.service';
import { dataSourceOptions } from './appDataSource';
import { EntryModule } from './entries/entries.module';
import { DataSource, EntityManager } from 'typeorm';
@Module({ @Module({
imports: [], imports: [
ConfigModule.forRoot(),
TypeOrmModule.forRoot(dataSourceOptions),
EntryModule
],
controllers: [AppController], controllers: [AppController],
providers: [AppService], providers: [AppService],
}) })
export class AppModule {} export class AppModule {
constructor(
private dataSource: DataSource,
private entityManager: EntityManager,
) {}
}

17
src/appDataSource.ts Normal file
View File

@@ -0,0 +1,17 @@
import { DataSource, DataSourceOptions } from "typeorm";
import { Entry } from "./entries/entries.entity";
import { Initial1770020781006 } from "./migrations/1770020781006-initial";
export const dataSourceOptions: DataSourceOptions = {
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],
synchronize: false,
}
export const appDataSource = new DataSource(dataSourceOptions)

View File

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

View File

@@ -0,0 +1,22 @@
import { Body, Controller, Delete, Inject, Param, Post } from '@nestjs/common';
import { EntryService } from './entries.service';
import { EntryDTO } from './entries.dto';
@Controller('entries')
export class EntriesController {
constructor(
@Inject(EntryService)
private readonly entryService: EntryService
) {}
@Post()
async saveEntry(@Body() entry: EntryDTO) {
return (await this.entryService.save(entry)).uuid
}
@Delete(":uuid")
async softDelete(@Param("uuid") uuid: string): Promise<void>
{
await this.entryService.softDeleteByUuid(uuid)
}
}

View File

@@ -0,0 +1,8 @@
import { Entry } from "./entries.entity";
export class EntryDTO implements Partial<Entry> {
title?: string;
description?: string;
source?: string;
sourceUrl?: string;
}

View File

@@ -0,0 +1,23 @@
import { Column, Entity, Generated, PrimaryGeneratedColumn } from "typeorm";
@Entity({ name: "entities"})
export class Entry {
@Column()
@Generated("uuid")
uuid?: string;
@PrimaryGeneratedColumn("increment")
id?: number;
@Column({ type: String, default: "" })
title?: string;
@Column({ type: String, default: "" })
description?: string;
@Column({ type: String, default: "" })
source?: string;
@Column({ type: String, default: "" })
sourceUrl?: string;
}

View File

@@ -0,0 +1,15 @@
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]),
],
exports: [EntryService],
providers: [EntryService],
controllers: [EntriesController],
})
export class EntryModule {}

View File

@@ -0,0 +1,36 @@
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 readonly logger = new Logger(EntryService.name)
async save(data: EntryDTO): Promise<Entry> {
this.logger.log("Creating entry")
const lEntry = new Entry();
lEntry.description = data.description
lEntry.source = data.source
lEntry.sourceUrl = data.sourceUrl
lEntry.title = data.title
await this.entryRepository.save(lEntry)
return lEntry
}
async softDeleteByUuid(uuid: string): Promise<void> {
this.logger.log(`Soft deleting entry with UUID ${uuid}`)
const deleteResponse = await this.entryRepository.softDelete({
uuid: uuid
})
if (!deleteResponse.affected) {
throw new EntryNotFoundException(uuid)
}
}
}

View File

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

View File

@@ -0,0 +1,14 @@
import { MigrationInterface, QueryRunner } from "typeorm";
export class Initial1770020781006 implements MigrationInterface {
name = 'Initial1770020781006'
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`CREATE TABLE "entities" ("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 "entities"`);
}
}