Skip to content

Add Link Resolver Module with CRUD functionality and DTOs #1

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 11 commits into
base: staging
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion .env
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,13 @@ DATABASE_URL="postgres://tkawcvceqtdlrt:b63809a94a8d366a92eca1d489dbc43bcc7a58fd
SHADOW_DATABASE_URL="postgres://kyrhydpwvpavke:7917c48b5ad1e3cfc294df930e053075270752c19bd13c1ea6fd31280722735c@ec2-44-205-112-253.compute-1.amazonaws.com:5432/dfdm5lo7eed2pb"


JWT_SECRET='mSSS9Zrd'
JWT_SECRET='mSSS9Zrd'

# MinIO Configuration
MINIO_ENDPOINT=localhost
MINIO_PORT=9000
MINIO_USE_SSL=false
MINIO_ACCESS_KEY=minioadmin
MINIO_SECRET_KEY=minioadmin
MINIO_BUCKET=link-resolvers
MINIO_REGION=us-east-1
17 changes: 17 additions & 0 deletions docker-compose.minio.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
version: '3.7'

services:
minio:
image: minio/minio
ports:
- "9000:9000"
- "9001:9001"
environment:
MINIO_ROOT_USER: minioadmin
MINIO_ROOT_PASSWORD: minioadmin
volumes:
- minio-data:/data
command: server --console-address ":9001" /data

volumes:
minio-data:
12 changes: 9 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,21 +18,27 @@
"test:watch": "jest --watch",
"test:cov": "jest --coverage",
"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",
"cli:dev": "ts-node src/cli.ts",
"cli:build": "nest build && node dist/cli.js",
"save-sample": "ts-node src/cli.ts save-sample"
},
"dependencies": {
"@aws-sdk/client-s3": "^3.777.0",
"@aws-sdk/s3-request-presigner": "^3.777.0",
"@nestjs/common": "^9.0.0",
"@nestjs/config": "^2.2.0",
"@nestjs/config": "^4.0.2",
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Verify compatibility with NestJS.

The @nestjs/config version has been updated to ^4.0.2. Verify that this version is compatible with the other NestJS packages still at version ^9.0.0.


🌐 Web query:

Is @nestjs/config version 4.0.2 compatible with @nestjs/core version 9.0.0?

💡 Result:

The compatibility between @nestjs/[email protected] and @nestjs/[email protected] poses potential issues due to breaking changes introduced in @nestjs/config versions beyond v3.x. Here's the analysis:

  1. Breaking Changes in @nestjs/[email protected]
    Version 4.0.0 introduced significant adjustments to environment variable handling:

    • Modified priority order for configuration sources (now internal config > validated env vars > process.env) [3][7]
    • Deprecated ignoreEnvVars option in favor of skipProcessEnv [3][7]
      These changes were designed for NestJS v11+ infrastructure.
  2. Core Version Context
    @nestjs/[email protected] is part of the NestJS v9 ecosystem (released in 2023), while @nestjs/[email protected] targets NestJS v11 (released January 2025) [6][8]. The major version mismatch suggests incompatibility.

  3. Version Compatibility Guidance
    NestJS follows semantic versioning where major versions indicate breaking changes. The @nestjs/config package at v4.x requires:

  4. Recommendation
    For @nestjs/[email protected] projects:

The configuration module's v4.x release notes explicitly require NestJS v11+ core dependencies [3][7], making it incompatible with older core versions like 9.0.0.

Citations:


Action: Resolve incompatibility between @nestjs/config and NestJS core

The analysis confirms that using @nestjs/[email protected] with @nestjs/[email protected] is incompatible. Version 4.x of @nestjs/config introduces breaking changes (such as changes in environment variable handling and deprecation of certain options) and explicitly requires a minimum of @nestjs/[email protected]. You’ll need to either:

  • Downgrade @nestjs/config to version ^3.x (for example, 3.2.0) to maintain compatibility with NestJS v9
    or
  • Upgrade your NestJS core packages to v11+ to support the features and breaking changes in @nestjs/[email protected].

Please update the dependency in your package.json accordingly.

"@nestjs/core": "^9.0.0",
"@nestjs/jwt": "^9.0.0",
"@nestjs/passport": "^9.0.0",
"@nestjs/platform-express": "^9.0.0",
"@prisma/client": "^4.1.1",
"bcrypt": "^5.0.1",
"class-transformer": "^0.5.1",
"class-validator": "^0.13.2",
"class-validator": "^0.14.1",
"cookie-parser": "^1.4.6",
"csurf": "^1.11.0",
"nest-commander": "^3.17.0",
"passport": "^0.6.0",
"passport-jwt": "^4.0.0",
"reflect-metadata": "^0.1.13",
Expand Down
17 changes: 17 additions & 0 deletions save-to-minio.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#!/bin/bash

# Start MinIO in the background
echo "Starting MinIO..."
docker-compose -f docker-compose.minio.yml up -d

# Wait for MinIO to start
echo "Waiting for MinIO to start..."
sleep 5

# Save the sample data
echo "Saving sample data to MinIO..."
yarn save-sample

echo "Done!"
echo "The MinIO console is available at http://localhost:9001 (login with minioadmin/minioadmin)"
echo "The link resolver data is saved to the 'link-resolvers' bucket"
4 changes: 4 additions & 0 deletions src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import { UserModule } from './user/user.module';
import { TodoModule } from './todo/todo.module';
import { PrismaModule } from './prisma/prisma.module';
import { ConfigModule } from '@nestjs/config';
import { LinkResolverModule } from './link-resolver/link-resolver.module';
import { StorageModule } from './storage/storage.module';

@Module({
imports: [
Expand All @@ -14,6 +16,8 @@ import { ConfigModule } from '@nestjs/config';
UserModule,
TodoModule,
PrismaModule,
LinkResolverModule,
StorageModule,
],
controllers: [AppController],
providers: [AppService],
Expand Down
11 changes: 11 additions & 0 deletions src/cli.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { CommandFactory } from 'nest-commander';
import { LinkResolverCommandsModule } from './link-resolver/commands/commands.module';
import { StorageModule } from './storage/storage.module';

async function bootstrap() {
await CommandFactory.run(LinkResolverCommandsModule, {
logger: ['error', 'warn'],
});
}

bootstrap();
8 changes: 8 additions & 0 deletions src/common/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/**
* Common Barrel File
*
* This file exports all common elements from the common directory,
* making them easier to import elsewhere in the application.
*/

export * from './interfaces';
8 changes: 8 additions & 0 deletions src/common/interfaces/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/**
* Interfaces Barrel File
*
* This file exports all interfaces from the interfaces directory,
* making them easier to import elsewhere in the application.
*/

export * from './repository.interface';
55 changes: 55 additions & 0 deletions src/common/interfaces/repository.interface.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/**
* Repository Provider Interface
*
* This file defines a TypeScript interface for a repository provider,
* which is a common pattern in software architecture for abstracting data access operations.
*/

/**
* Defines the structure for data being saved
* Requires an id property as a string
* Allows any additional properties
*/
export type SaveParams = {
id: string;
[k: string]: any;
};

/**
* Repository Provider Interface
*
* Defines four standard CRUD operations:
* - save: Stores data with the given parameters
* - one: Retrieves a single item by ID
* - all: Retrieves all items of a specific category
* - delete: Removes an item by ID
*/
export interface IRepositoryProvider {
/**
* Stores data with the given parameters
* @param data The data to be saved
* @returns A promise resolving to void
*/
save(data: SaveParams): Promise<void>;

/**
* Retrieves a single item by ID
* @param id The unique identifier of the item
* @returns A promise resolving to the requested item or null if not found
*/
one<T>(id: string): Promise<T | null>;

/**
* Retrieves all items of a specific category
* @param filter Optional filtering criteria
* @returns A promise resolving to an array of items
*/
all<T>(filter?: object): Promise<T[]>;

/**
* Removes an item by ID
* @param id The unique identifier of the item to delete
* @returns A promise resolving to void
*/
delete(id: string): Promise<void>;
}
10 changes: 10 additions & 0 deletions src/link-resolver/commands/commands.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { Module } from '@nestjs/common';
import { SaveSampleDataCommand } from './save-sample-data.command';
import { LinkResolverModule } from '../link-resolver.module';

@Module({
imports: [LinkResolverModule],
providers: [SaveSampleDataCommand],
exports: [SaveSampleDataCommand],
})
export class LinkResolverCommandsModule {}
69 changes: 69 additions & 0 deletions src/link-resolver/commands/save-sample-data.command.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import { Command, CommandRunner } from 'nest-commander';
import { Injectable } from '@nestjs/common';
import { LinkResolverService } from '../link-resolver.service';

@Injectable()
@Command({
name: 'save-sample',
description: 'Save sample link resolver data to MinIO',
})
export class SaveSampleDataCommand extends CommandRunner {
constructor(private readonly linkResolverService: LinkResolverService) {
super();
}

async run(): Promise<void> {
console.log('Saving sample link resolver data to MinIO...');

const sampleData = {
id: 'gs1/01/12345678901234/10/123456789012345678902.json',
createdAt: '2024-09-02T06:19:58.783Z',
linkset: {
anchor:
'http://localhost:3000/gs1/01/12345678901234/10/123456789012345678902',
'http://localhost:3000/voc/certificationInfo': [
{
href: 'https://example.com',
title: 'Certification Information',
type: 'application/json',
hreflang: ['en'],
'title*': [{ value: 'Certification Information', language: 'en' }],
},
],
},
linkHeaderText:
'<https://example.com>; rel="gs1:certificationInfo"; type="application/json"; hreflang="en"; title="Certification Information", <http://localhost:3000/gs1/01/12345678901234/10/123456789012345678902>; rel="owl:sameAs"',
namespace: 'gs1',
identificationKeyType: 'gtin',
identificationKey: '12345678901234',
itemDescription: 'Product description',
qualifierPath: '/10/123456789012345678902',
active: true,
responses: [
{
defaultLinkType: true,
defaultMimeType: true,
fwqs: false,
active: true,
linkType: 'gs1:certificationInfo',
title: 'Certification Information',
targetUrl: 'https://example.com',
mimeType: 'application/json',
ianaLanguage: 'en',
context: 'au',
defaultContext: true,
defaultIanaLanguage: true,
},
],
};

try {
await this.linkResolverService.create(sampleData);
console.log('Sample data saved successfully!');
console.log(`ID: ${sampleData.id}`);
} catch (error: unknown) {
const errorMessage = error instanceof Error ? error.message : String(error);
console.error('Failed to save sample data:', errorMessage);
}
}
}
83 changes: 83 additions & 0 deletions src/link-resolver/dto/create-link-resolver.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import {
IsString,
IsBoolean,
IsOptional,
IsArray,
IsObject,
ValidateNested,
} from 'class-validator';
import { Type } from 'class-transformer';
import { Linkset, Response } from '../entities/link-resolver.entity';

/**
* Data Transfer Object for creating a Link Resolver entry
*/
export class CreateLinkResolverDto {
@IsString()
namespace: string;

@IsString()
identificationKeyType: string;

@IsString()
identificationKey: string;

@IsString()
@IsOptional()
itemDescription?: string;

@IsString()
@IsOptional()
qualifierPath?: string;

@IsBoolean()
active: boolean;

@IsObject()
@ValidateNested()
@Type(() => Object) // We can't directly validate Linkset due to dynamic keys
linkset: Linkset;

@IsArray()
@ValidateNested({ each: true })
@Type(() => ResponseDto)
responses: Response[];
}

export class ResponseDto implements Response {
@IsBoolean()
defaultLinkType: boolean;

@IsBoolean()
defaultMimeType: boolean;

@IsBoolean()
fwqs: boolean;

@IsBoolean()
active: boolean;

@IsString()
linkType: string;

@IsString()
title: string;

@IsString()
targetUrl: string;

@IsString()
mimeType: string;

@IsString()
ianaLanguage: string;

@IsString()
context: string;

@IsBoolean()
defaultContext: boolean;

@IsBoolean()
defaultIanaLanguage: boolean;
}
Loading