Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
89 changes: 89 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
name: CI

on:
push:
branches:
- main
- develop
pull_request:
branches:
- main
- develop

jobs:
lint-imports:
name: lint-imports
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Lint absolute imports
shell: bash
run: |
set -euo pipefail
pattern="^[[:space:]]*(import|export)[^;]*from[[:space:]]+['\"]src/|^[[:space:]]*import[[:space:]]+['\"]src/"
if command -v rg >/dev/null 2>&1; then
matches=$(rg -n \
--glob '!**/node_modules/**' \
--glob '!**/dist/**' \
--glob '!**/build/**' \
--glob '!**/.next/**' \
--glob '!**/coverage/**' \
--glob '!**/out/**' \
--glob '!**/tmp/**' \
--glob '!**/.turbo/**' \
--glob '!**/.cache/**' \
--glob '*.ts' \
--glob '*.tsx' \
"$pattern" . || true)
else
matches=$(grep -RIn \
--include='*.ts' \
--include='*.tsx' \
--exclude-dir=node_modules \
--exclude-dir=dist \
--exclude-dir=build \
--exclude-dir=.next \
--exclude-dir=coverage \
--exclude-dir=out \
--exclude-dir=tmp \
--exclude-dir=.turbo \
--exclude-dir=.cache \
-E "$pattern" . || true)
fi
if [ -n "$matches" ]; then
echo "ERROR: Absolute imports from \"src/\" are not allowed. Use relative paths instead."
echo "$matches"
exit 1
fi

build:
name: build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20.x
cache: npm
- name: Install dependencies
run: npm ci
- name: Build frontend
run: npm --workspace frontend run build
- name: Build backend
run: npm --workspace backend run build

type-check:
name: type-check
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 18
cache: npm
- name: Install dependencies
run: npm ci
- name: Type-check frontend
run: npm --workspace frontend exec -- tsc --noEmit -p tsconfig.json
- name: Type-check backend
run: npm --workspace backend exec -- tsc --noEmit -p tsconfig.json
35 changes: 35 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Contributing

## Import Guidelines
Rule: Always use relative imports.

Bad:
```ts
import X from "src/components/X";
```

Good:
```ts
import X from "../../components/X";
```

CI will reject PRs containing src/* imports.

Issue/PR: https://github.com/MindBlockLabs/mindBlock_app/pull/0000 (placeholder)

**MUST RUN** Local check to before submitting a pr:
```bash
npm ci
npm --workspace frontend run build
npm --workspace backend run build

npm --workspace frontend run lint
npm --workspace backend run lint

npm --workspace frontend exec -- tsc --noEmit -p tsconfig.json
npm --workspace backend exec -- tsc --noEmit -p tsconfig.json.
```

## Branch Protection
main and develop require status checks: lint-imports, build, type-check.
Require branches to be up-to-date before merging.
18 changes: 16 additions & 2 deletions backend/src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,22 @@ import { CategoriesModule } from './categories/categories.module';
TypeOrmModule.forRootAsync({
imports: [ConfigModule],
inject: [ConfigService],
useFactory: async (configService: ConfigService) => {
const dbConfig = configService.get('database');
useFactory: (configService: ConfigService) => {
interface DatabaseConfig {
url?: string;
host?: string;
port?: number;
user?: string;
password?: string;
name?: string;
autoload?: boolean;
synchronize?: boolean;
}
const dbConfig = configService.get<DatabaseConfig>('database');

if (!dbConfig) {
throw new Error('Database configuration not found');
}

// If DATABASE_URL is set, use connection string (production)
if (dbConfig.url) {
Expand Down
2 changes: 1 addition & 1 deletion backend/src/auth/authConfig/jwt.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,4 @@ export default registerAs('jwt', () => {
issuer: process.env.JWT_TOKEN_ISSUER,
ttl: parseInt(process.env.JWT_ACCESS_TOKEN_TTL ?? '3600'),
};
});
});
4 changes: 2 additions & 2 deletions backend/src/auth/constants/auth.constant.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/**request user key */
export const REQUEST_USER_KEY = 'user'
export const REQUEST_USER_KEY = 'user';

/**auth type key */
export const AUTH_TYPE_KEY = 'auth'
export const AUTH_TYPE_KEY = 'auth';
10 changes: 5 additions & 5 deletions backend/src/auth/controllers/auth.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,10 +90,10 @@ export class AuthController {
status: 400,
description: 'Invalid Stellar wallet address format',
})
public async generateStellarWalletNonce(
public generateStellarWalletNonce(
@Query('walletAddress') walletAddress: string,
): Promise<NonceResponseDto> {
return await this.authservice.generateNonce(walletAddress);
): NonceResponseDto {
return this.authservice.generateNonce(walletAddress);
}

@Get('/stellar-wallet-nonce/status')
Expand Down Expand Up @@ -127,8 +127,8 @@ export class AuthController {
},
},
})
public async checkNonceStatus(@Query('nonce') nonce: string) {
return await this.authservice.checkNonceStatus(nonce);
public checkNonceStatus(@Query('nonce') nonce: string) {
return this.authservice.checkNonceStatus(nonce);
}

@Post('/forgot-password')
Expand Down
7 changes: 3 additions & 4 deletions backend/src/auth/decorators/activeUser.decorator.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,17 @@

import { createParamDecorator, ExecutionContext } from '@nestjs/common';
import { REQUEST_USER_KEY } from '../constants/auth.constant';
import { ActiveUserData } from '../interfaces/activeInterface';

/**Active user class */
export const ActiveUser = createParamDecorator(
(field: keyof ActiveUserData | undefined, ctx: ExecutionContext) => {
const request = ctx.switchToHttp().getRequest();
const request = ctx.switchToHttp().getRequest<Record<string, any>>();

const user: ActiveUserData = request[REQUEST_USER_KEY]
const user = request[REQUEST_USER_KEY] as ActiveUserData;

console.log('ActiveUser decorator - user:', user);
console.log('ActiveUser decorator - field:', field);
console.log('ActiveUser decorator - value:', field ? user?.[field] : user);
return field ? user?.[field] : user
return field ? user?.[field] : user;
},
);
2 changes: 1 addition & 1 deletion backend/src/auth/decorators/auth.decorator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,4 @@ export const Auth = (...authTypes: authType[]) => {
SetMetadata(AUTH_TYPE_KEY, authTypes),
UseGuards(AuthGuard('jwt')), // ← This applies the Passport JWT guard
);
};
};
2 changes: 1 addition & 1 deletion backend/src/auth/decorators/role-decorator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@ import { Role } from '../enum/roles.enum';

export const ROLES_KEY = 'roles';
export const RoleDecorator = (...roles: [Role, ...Role[]]) =>
SetMetadata(ROLES_KEY, roles);
SetMetadata(ROLES_KEY, roles);
22 changes: 11 additions & 11 deletions backend/src/auth/dtos/login.dto.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import { ApiProperty } from "@nestjs/swagger";
import { IsEmail, IsString, MinLength } from "class-validator";
import { ApiProperty } from '@nestjs/swagger';
import { IsEmail, IsString, MinLength } from 'class-validator';

export class LoginDto {
@ApiProperty({ example: 'user@example.com' })
@IsEmail()
email: string;
@ApiProperty({ example: 'SecurePassword123!' })
@IsString()
@MinLength(8)
password: string;
}
@ApiProperty({ example: 'user@example.com' })
@IsEmail()
email: string;

@ApiProperty({ example: 'SecurePassword123!' })
@IsString()
@MinLength(8)
password: string;
}
12 changes: 6 additions & 6 deletions backend/src/auth/dtos/nonceResponse.dto.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
import { ApiProperty } from "@nestjs/swagger";
import { ApiProperty } from '@nestjs/swagger';

export class NonceResponseDto {
@ApiProperty({
@ApiProperty({
example: 'nonce_1693123456789_abc123_456789',
description: 'Unique nonce to be signed by the wallet'
description: 'Unique nonce to be signed by the wallet',
})
nonce: string;

@ApiProperty({
@ApiProperty({
example: 1693123456789,
description: 'Unix timestamp when the nonce expires'
description: 'Unix timestamp when the nonce expires',
})
expiresAt: number;
}
}
23 changes: 13 additions & 10 deletions backend/src/auth/dtos/refreshTokenDto.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
import { IsNotEmpty, IsString } from "class-validator";
import { ApiProperty } from "@nestjs/swagger";
import { IsNotEmpty, IsString } from 'class-validator';
import { ApiProperty } from '@nestjs/swagger';

/**
* Data Transfer Object (DTO) for refresh token.
*/
export class RefreshTokenDto {
/**
* The refresh token used for authentication.
*/
@ApiProperty({ description: "The refresh token used for authentication.", example: "some-refresh-token" })
@IsString()
@IsNotEmpty()
refreshToken: string;
}
/**
* The refresh token used for authentication.
*/
@ApiProperty({
description: 'The refresh token used for authentication.',
example: 'some-refresh-token',
})
@IsString()
@IsNotEmpty()
refreshToken: string;
}
42 changes: 21 additions & 21 deletions backend/src/auth/dtos/register.dto.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,23 @@
import { ApiProperty } from "@nestjs/swagger";
import { IsEmail, IsOptional, IsString, MinLength } from "class-validator";
import { ApiProperty } from '@nestjs/swagger';
import { IsEmail, IsOptional, IsString, MinLength } from 'class-validator';

export class RegisterDto {
@ApiProperty({ example: 'user@example.com' })
@IsEmail()
email: string;
@ApiProperty({ example: 'SecurePassword123!' })
@IsString()
@MinLength(8)
password: string;
@ApiProperty({ example: '0xWalletAddress' })
@IsOptional()
@IsString()
walletAddress?: string;
@ApiProperty({ example: 'GoogleOAuthToken' })
@IsOptional()
@IsString()
googleToken?: string;
}
@ApiProperty({ example: 'user@example.com' })
@IsEmail()
email: string;

@ApiProperty({ example: 'SecurePassword123!' })
@IsString()
@MinLength(8)
password: string;

@ApiProperty({ example: '0xWalletAddress' })
@IsOptional()
@IsString()
walletAddress?: string;

@ApiProperty({ example: 'GoogleOAuthToken' })
@IsOptional()
@IsString()
googleToken?: string;
}
17 changes: 9 additions & 8 deletions backend/src/auth/dtos/walletLogin.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,27 +12,28 @@ export class StellarWalletLoginDto {
})
walletAddress: string;

@ApiProperty({
@ApiProperty({
example: 'base64SignatureString==',
description: 'Base64 encoded ed25519 signature',
})
@IsString()
signature: string;

@ApiProperty({
@ApiProperty({
example: 'stellar_nonce_1693123456789_abc123_BTODB4A',
description: 'Server-generated nonce for this authentication attempt'
description: 'Server-generated nonce for this authentication attempt',
})
@IsString()
nonce: string;

@ApiProperty({
@ApiProperty({
example: 'GAHK7EEG2WWHVKDNT4CEQFZGKF2LGDSW2IVM4S5DP42RBW3K6BTODB4A',
description: 'Stellar public key (same as wallet address for account-based wallets)'
description:
'Stellar public key (same as wallet address for account-based wallets)',
})
@IsString()
@Matches(/^[GM][A-Z2-7]{55}$/, {
message: 'Invalid Stellar public key format'
@Matches(/^[GM][A-Z2-7]{55}$/, {
message: 'Invalid Stellar public key format',
})
publicKey: string;
}
}
2 changes: 1 addition & 1 deletion backend/src/auth/enum/authProvider.enum.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@ export enum AuthProvider {
LOCAL = 'local',
GOOGLE = 'google',
WALLET = 'wallet',
}
}
Loading