trello-cli-unofficial 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.eslintignore +26 -0
- package/CHANGELOG.md +48 -0
- package/LICENSE +21 -0
- package/README.md +261 -0
- package/bun.lock +788 -0
- package/dist/main.js +27751 -0
- package/install.sh +45 -0
- package/main.ts +19 -0
- package/package.json +75 -0
- package/src/application/index.ts +1 -0
- package/src/application/use-cases/AuthenticateUserUseCase.ts +47 -0
- package/src/application/use-cases/CreateCardUseCase.ts +14 -0
- package/src/application/use-cases/DeleteCardUseCase.ts +9 -0
- package/src/application/use-cases/GetBoardsUseCase.ts +10 -0
- package/src/application/use-cases/GetCardsUseCase.ts +10 -0
- package/src/application/use-cases/GetListsUseCase.ts +10 -0
- package/src/application/use-cases/MoveCardUseCase.ts +10 -0
- package/src/application/use-cases/UpdateCardUseCase.ts +17 -0
- package/src/application/use-cases/index.ts +8 -0
- package/src/domain/entities/Board.ts +17 -0
- package/src/domain/entities/Card.ts +53 -0
- package/src/domain/entities/Config.ts +20 -0
- package/src/domain/entities/List.ts +12 -0
- package/src/domain/entities/index.ts +4 -0
- package/src/domain/index.ts +3 -0
- package/src/domain/repositories/ConfigRepository.ts +6 -0
- package/src/domain/repositories/TrelloRepository.ts +11 -0
- package/src/domain/repositories/index.ts +2 -0
- package/src/domain/services/AuthenticationService.ts +26 -0
- package/src/domain/services/index.ts +1 -0
- package/src/index.ts +5 -0
- package/src/infrastructure/index.ts +1 -0
- package/src/infrastructure/repositories/FileConfigRepository.ts +51 -0
- package/src/infrastructure/repositories/TrelloApiRepository.ts +93 -0
- package/src/infrastructure/repositories/index.ts +2 -0
- package/src/presentation/cli/CommandController.ts +59 -0
- package/src/presentation/cli/TrelloCliController.ts +408 -0
- package/src/presentation/cli/index.ts +2 -0
- package/src/presentation/index.ts +1 -0
- package/src/shared/index.ts +1 -0
- package/src/shared/types.ts +31 -0
package/install.sh
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
|
|
3
|
+
# Trello CLI Unofficial - Global Installation Script
|
|
4
|
+
# This script installs the CLI globally on the system
|
|
5
|
+
|
|
6
|
+
set -e
|
|
7
|
+
|
|
8
|
+
echo "🚀 Installing Trello CLI Unofficial..."
|
|
9
|
+
|
|
10
|
+
# Check if bun is installed
|
|
11
|
+
if ! command -v bun &> /dev/null; then
|
|
12
|
+
echo "❌ Bun is not installed. Install Bun first:"
|
|
13
|
+
echo " curl -fsSL https://bun.sh/install | bash"
|
|
14
|
+
exit 1
|
|
15
|
+
fi
|
|
16
|
+
|
|
17
|
+
# Check if we're in the correct directory
|
|
18
|
+
if [ ! -f "package.json" ] || [ ! -f "main.ts" ]; then
|
|
19
|
+
echo "❌ Run this script inside the trello-cli-unofficial project directory"
|
|
20
|
+
exit 1
|
|
21
|
+
fi
|
|
22
|
+
|
|
23
|
+
# Install dependencies
|
|
24
|
+
echo "📦 Installing dependencies..."
|
|
25
|
+
bun install
|
|
26
|
+
|
|
27
|
+
# Create global link
|
|
28
|
+
echo "🔗 Creating global link..."
|
|
29
|
+
bun link
|
|
30
|
+
|
|
31
|
+
# Verify installation
|
|
32
|
+
if command -v trello-cli-unofficial &> /dev/null; then
|
|
33
|
+
echo "✅ Installation complete!"
|
|
34
|
+
echo ""
|
|
35
|
+
echo "🎯 How to use:"
|
|
36
|
+
echo " trello-cli-unofficial # Interactive mode"
|
|
37
|
+
echo " tcu # Shortcut (interactive mode)"
|
|
38
|
+
echo " tcu boards # View boards"
|
|
39
|
+
echo " tcu setup # Configure token"
|
|
40
|
+
echo ""
|
|
41
|
+
echo "📚 For more information, check the README.md"
|
|
42
|
+
else
|
|
43
|
+
echo "❌ Installation failed. Check permissions and try again."
|
|
44
|
+
exit 1
|
|
45
|
+
fi
|
package/main.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
import { config } from 'dotenv';
|
|
3
|
+
import { CommandController } from './src/presentation';
|
|
4
|
+
|
|
5
|
+
// Load environment variables
|
|
6
|
+
config();
|
|
7
|
+
|
|
8
|
+
async function main() {
|
|
9
|
+
try {
|
|
10
|
+
const commandController = new CommandController();
|
|
11
|
+
await commandController.run();
|
|
12
|
+
}
|
|
13
|
+
catch (error) {
|
|
14
|
+
console.error('❌ Erro fatal:', (error as Error).message);
|
|
15
|
+
process.exit(1);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
main().catch(console.error);
|
package/package.json
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "trello-cli-unofficial",
|
|
3
|
+
"type": "module",
|
|
4
|
+
"version": "0.1.0",
|
|
5
|
+
"private": false,
|
|
6
|
+
"description": "Unofficial Trello CLI using Power-Up authentication, built with Bun for maximum performance",
|
|
7
|
+
"author": "Matheus Caiser <matheus.kaiser@gmail.com> (https://www.mrdeveloper.com.br/)",
|
|
8
|
+
"license": "MIT",
|
|
9
|
+
"homepage": "https://github.com/JaegerCaiser/trello-cli-unofficial#readme",
|
|
10
|
+
"repository": {
|
|
11
|
+
"type": "git",
|
|
12
|
+
"url": "git+https://github.com/JaegerCaiser/trello-cli-unofficial.git"
|
|
13
|
+
},
|
|
14
|
+
"bugs": {
|
|
15
|
+
"url": "https://github.com/JaegerCaiser/trello-cli-unofficial/issues"
|
|
16
|
+
},
|
|
17
|
+
"keywords": [
|
|
18
|
+
"trello",
|
|
19
|
+
"cli",
|
|
20
|
+
"power-up",
|
|
21
|
+
"api",
|
|
22
|
+
"bun",
|
|
23
|
+
"typescript"
|
|
24
|
+
],
|
|
25
|
+
"module": "main.ts",
|
|
26
|
+
"bin": {
|
|
27
|
+
"trello-cli-unofficial": "./main.ts",
|
|
28
|
+
"tcu": "./main.ts"
|
|
29
|
+
},
|
|
30
|
+
"engines": {
|
|
31
|
+
"bun": ">=1.0.0"
|
|
32
|
+
},
|
|
33
|
+
"scripts": {
|
|
34
|
+
"start": "bun run main.ts",
|
|
35
|
+
"dev": "bun run --watch main.ts",
|
|
36
|
+
"build": "bun build main.ts --outdir ./dist --target node",
|
|
37
|
+
"install-global": "./install.sh",
|
|
38
|
+
"test": "bun test",
|
|
39
|
+
"test:watch": "bun test --watch",
|
|
40
|
+
"test:unit": "bun test tests/unit",
|
|
41
|
+
"test:integration": "bun test tests/integration",
|
|
42
|
+
"test:coverage": "bun test --coverage",
|
|
43
|
+
"lint": "eslint .",
|
|
44
|
+
"lint:fix": "eslint . --fix",
|
|
45
|
+
"typecheck": "tsc --noEmit",
|
|
46
|
+
"validate": "bun run lint && bun run typecheck && bun run test",
|
|
47
|
+
"version:patch": "bun version patch && git push --follow-tags",
|
|
48
|
+
"version:minor": "bun version minor && git push --follow-tags",
|
|
49
|
+
"version:major": "bun version major && git push --follow-tags",
|
|
50
|
+
"prepublishOnly": "bun run validate && bun run build"
|
|
51
|
+
},
|
|
52
|
+
"peerDependencies": {
|
|
53
|
+
"typescript": "^5"
|
|
54
|
+
},
|
|
55
|
+
"dependencies": {
|
|
56
|
+
"@types/node": "^24.10.0",
|
|
57
|
+
"commander": "^14.0.2",
|
|
58
|
+
"dotenv": "^17.2.3",
|
|
59
|
+
"fs-extra": "^11.3.2",
|
|
60
|
+
"inquirer": "^12.10.0"
|
|
61
|
+
},
|
|
62
|
+
"devDependencies": {
|
|
63
|
+
"@antfu/eslint-config": "^6.2.0",
|
|
64
|
+
"@types/bun": "latest",
|
|
65
|
+
"@types/fs-extra": "^11.0.4",
|
|
66
|
+
"@types/inquirer": "^9.0.9",
|
|
67
|
+
"eslint": "^9.39.1",
|
|
68
|
+
"eslint-plugin-format": "^1.0.2"
|
|
69
|
+
},
|
|
70
|
+
"os": [
|
|
71
|
+
"darwin",
|
|
72
|
+
"linux",
|
|
73
|
+
"win32"
|
|
74
|
+
]
|
|
75
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './use-cases';
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type { ConfigRepository } from '../../domain/repositories';
|
|
2
|
+
import { AuthenticationService } from '../../domain/services';
|
|
3
|
+
|
|
4
|
+
export class AuthenticateUserUseCase {
|
|
5
|
+
private authService: AuthenticationService;
|
|
6
|
+
|
|
7
|
+
constructor(configRepository: ConfigRepository) {
|
|
8
|
+
this.authService = new AuthenticationService(configRepository);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
async execute(
|
|
12
|
+
token?: string,
|
|
13
|
+
): Promise<{ success: boolean; message: string }> {
|
|
14
|
+
if (token) {
|
|
15
|
+
if (!this.authService.validateToken(token)) {
|
|
16
|
+
return {
|
|
17
|
+
success: false,
|
|
18
|
+
message:
|
|
19
|
+
'❌ Token inválido. Deve começar com ATTA e ter pelo menos 10 caracteres.',
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
await this.authService.saveToken(token);
|
|
24
|
+
return {
|
|
25
|
+
success: true,
|
|
26
|
+
message: '✅ Token configurado com sucesso!',
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const isAuthenticated = await this.authService.isAuthenticated();
|
|
31
|
+
if (isAuthenticated) {
|
|
32
|
+
return {
|
|
33
|
+
success: true,
|
|
34
|
+
message: 'Usuário já autenticado.',
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return {
|
|
39
|
+
success: false,
|
|
40
|
+
message: '🔐 Você precisa configurar seu token do Trello primeiro.',
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async getConfig() {
|
|
45
|
+
return await this.authService.getConfig();
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { CardEntity, CreateCardData } from '../../domain/entities';
|
|
2
|
+
import type { TrelloRepository } from '../../domain/repositories';
|
|
3
|
+
|
|
4
|
+
export class CreateCardUseCase {
|
|
5
|
+
constructor(private trelloRepository: TrelloRepository) {}
|
|
6
|
+
|
|
7
|
+
async execute(cardData: CreateCardData): Promise<CardEntity> {
|
|
8
|
+
if (!cardData.name.trim()) {
|
|
9
|
+
throw new Error('Nome do cartão é obrigatório');
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
return await this.trelloRepository.createCard(cardData);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { TrelloRepository } from '../../domain/repositories';
|
|
2
|
+
|
|
3
|
+
export class DeleteCardUseCase {
|
|
4
|
+
constructor(private trelloRepository: TrelloRepository) {}
|
|
5
|
+
|
|
6
|
+
async execute(cardId: string): Promise<void> {
|
|
7
|
+
await this.trelloRepository.deleteCard(cardId);
|
|
8
|
+
}
|
|
9
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { BoardEntity } from '../../domain/entities';
|
|
2
|
+
import type { TrelloRepository } from '../../domain/repositories';
|
|
3
|
+
|
|
4
|
+
export class GetBoardsUseCase {
|
|
5
|
+
constructor(private trelloRepository: TrelloRepository) {}
|
|
6
|
+
|
|
7
|
+
async execute(): Promise<BoardEntity[]> {
|
|
8
|
+
return await this.trelloRepository.getBoards();
|
|
9
|
+
}
|
|
10
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { CardEntity } from '../../domain/entities';
|
|
2
|
+
import type { TrelloRepository } from '../../domain/repositories';
|
|
3
|
+
|
|
4
|
+
export class GetCardsUseCase {
|
|
5
|
+
constructor(private trelloRepository: TrelloRepository) {}
|
|
6
|
+
|
|
7
|
+
async execute(listId: string): Promise<CardEntity[]> {
|
|
8
|
+
return await this.trelloRepository.getCards(listId);
|
|
9
|
+
}
|
|
10
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { ListEntity } from '../../domain/entities';
|
|
2
|
+
import type { TrelloRepository } from '../../domain/repositories';
|
|
3
|
+
|
|
4
|
+
export class GetListsUseCase {
|
|
5
|
+
constructor(private trelloRepository: TrelloRepository) {}
|
|
6
|
+
|
|
7
|
+
async execute(boardId: string): Promise<ListEntity[]> {
|
|
8
|
+
return await this.trelloRepository.getLists(boardId);
|
|
9
|
+
}
|
|
10
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { CardEntity } from '../../domain/entities';
|
|
2
|
+
import type { TrelloRepository } from '../../domain/repositories';
|
|
3
|
+
|
|
4
|
+
export class MoveCardUseCase {
|
|
5
|
+
constructor(private trelloRepository: TrelloRepository) {}
|
|
6
|
+
|
|
7
|
+
async execute(cardId: string, targetListId: string): Promise<CardEntity> {
|
|
8
|
+
return await this.trelloRepository.moveCard(cardId, targetListId);
|
|
9
|
+
}
|
|
10
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { CardEntity, UpdateCardData } from '../../domain/entities';
|
|
2
|
+
import type { TrelloRepository } from '../../domain/repositories';
|
|
3
|
+
|
|
4
|
+
export class UpdateCardUseCase {
|
|
5
|
+
constructor(private trelloRepository: TrelloRepository) {}
|
|
6
|
+
|
|
7
|
+
async execute(
|
|
8
|
+
cardId: string,
|
|
9
|
+
updateData: UpdateCardData,
|
|
10
|
+
): Promise<CardEntity> {
|
|
11
|
+
if (updateData.name !== undefined && !updateData.name.trim()) {
|
|
12
|
+
throw new Error('Nome do cartão não pode estar vazio');
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
return await this.trelloRepository.updateCard(cardId, updateData);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export * from './AuthenticateUserUseCase';
|
|
2
|
+
export * from './CreateCardUseCase';
|
|
3
|
+
export * from './DeleteCardUseCase';
|
|
4
|
+
export * from './GetBoardsUseCase';
|
|
5
|
+
export * from './GetCardsUseCase';
|
|
6
|
+
export * from './GetListsUseCase';
|
|
7
|
+
export * from './MoveCardUseCase';
|
|
8
|
+
export * from './UpdateCardUseCase';
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export interface Board {
|
|
2
|
+
id: string;
|
|
3
|
+
name: string;
|
|
4
|
+
url: string;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export class BoardEntity implements Board {
|
|
8
|
+
constructor(
|
|
9
|
+
public readonly id: string,
|
|
10
|
+
public readonly name: string,
|
|
11
|
+
public readonly url: string,
|
|
12
|
+
) {}
|
|
13
|
+
|
|
14
|
+
static fromApiResponse(data: any): BoardEntity {
|
|
15
|
+
return new BoardEntity(data.id, data.name, data.url);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
export interface Card {
|
|
2
|
+
id: string;
|
|
3
|
+
name: string;
|
|
4
|
+
desc?: string;
|
|
5
|
+
idList: string;
|
|
6
|
+
url?: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface CreateCardData {
|
|
10
|
+
name: string;
|
|
11
|
+
desc?: string;
|
|
12
|
+
listId: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface UpdateCardData {
|
|
16
|
+
name?: string;
|
|
17
|
+
desc?: string;
|
|
18
|
+
idList?: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export class CardEntity implements Card {
|
|
22
|
+
constructor(
|
|
23
|
+
public readonly id: string,
|
|
24
|
+
public readonly name: string,
|
|
25
|
+
public readonly idList: string,
|
|
26
|
+
public readonly desc?: string,
|
|
27
|
+
public readonly url?: string,
|
|
28
|
+
) {}
|
|
29
|
+
|
|
30
|
+
static fromApiResponse(data: any): CardEntity {
|
|
31
|
+
return new CardEntity(data.id, data.name, data.idList, data.desc, data.url);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
static create(
|
|
35
|
+
data: CreateCardData,
|
|
36
|
+
): Pick<CardEntity, 'name' | 'desc' | 'idList'> {
|
|
37
|
+
return {
|
|
38
|
+
name: data.name,
|
|
39
|
+
desc: data.desc,
|
|
40
|
+
idList: data.listId,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
update(data: UpdateCardData): CardEntity {
|
|
45
|
+
return new CardEntity(
|
|
46
|
+
this.id,
|
|
47
|
+
data.name ?? this.name,
|
|
48
|
+
data.idList ?? this.idList,
|
|
49
|
+
data.desc ?? this.desc,
|
|
50
|
+
this.url,
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export interface Config {
|
|
2
|
+
apiKey: string;
|
|
3
|
+
token?: string;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
export class ConfigEntity implements Config {
|
|
7
|
+
constructor(public readonly apiKey: string, public readonly token?: string) {}
|
|
8
|
+
|
|
9
|
+
static createDefault(): ConfigEntity {
|
|
10
|
+
return new ConfigEntity('630a01228b85df706aa520f3611e6490');
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
withToken(token: string): ConfigEntity {
|
|
14
|
+
return new ConfigEntity(this.apiKey, token);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
hasValidToken(): boolean {
|
|
18
|
+
return !!(this.token && this.token.startsWith('ATTA'));
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export interface List {
|
|
2
|
+
id: string;
|
|
3
|
+
name: string;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
export class ListEntity implements List {
|
|
7
|
+
constructor(public readonly id: string, public readonly name: string) {}
|
|
8
|
+
|
|
9
|
+
static fromApiResponse(data: any): ListEntity {
|
|
10
|
+
return new ListEntity(data.id, data.name);
|
|
11
|
+
}
|
|
12
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { BoardEntity, CardEntity, CreateCardData, ListEntity, UpdateCardData } from '../entities';
|
|
2
|
+
|
|
3
|
+
export interface TrelloRepository {
|
|
4
|
+
getBoards: () => Promise<BoardEntity[]>;
|
|
5
|
+
getLists: (boardId: string) => Promise<ListEntity[]>;
|
|
6
|
+
getCards: (listId: string) => Promise<CardEntity[]>;
|
|
7
|
+
createCard: (data: CreateCardData) => Promise<CardEntity>;
|
|
8
|
+
updateCard: (cardId: string, data: UpdateCardData) => Promise<CardEntity>;
|
|
9
|
+
deleteCard: (cardId: string) => Promise<void>;
|
|
10
|
+
moveCard: (cardId: string, targetListId: string) => Promise<CardEntity>;
|
|
11
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { ConfigEntity } from '../entities';
|
|
2
|
+
import type { ConfigRepository } from '../repositories';
|
|
3
|
+
|
|
4
|
+
export class AuthenticationService {
|
|
5
|
+
constructor(private configRepository: ConfigRepository) {}
|
|
6
|
+
|
|
7
|
+
async getConfig(): Promise<ConfigEntity> {
|
|
8
|
+
return await this.configRepository.load();
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
async saveToken(token: string): Promise<ConfigEntity> {
|
|
12
|
+
const config = await this.configRepository.load();
|
|
13
|
+
const updatedConfig = config.withToken(token);
|
|
14
|
+
await this.configRepository.save(updatedConfig);
|
|
15
|
+
return updatedConfig;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
async isAuthenticated(): Promise<boolean> {
|
|
19
|
+
const config = await this.configRepository.load();
|
|
20
|
+
return config.hasValidToken();
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
validateToken(token: string): boolean {
|
|
24
|
+
return token.startsWith('ATTA') && token.length > 10;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './AuthenticationService';
|
package/src/index.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './repositories';
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import type { ConfigRepository } from '../../domain/repositories';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import fs from 'fs-extra';
|
|
4
|
+
import { ConfigEntity } from '../../domain/entities';
|
|
5
|
+
|
|
6
|
+
export class FileConfigRepository implements ConfigRepository {
|
|
7
|
+
private readonly configDir: string;
|
|
8
|
+
private readonly configFile: string;
|
|
9
|
+
|
|
10
|
+
constructor() {
|
|
11
|
+
this.configDir = path.join(
|
|
12
|
+
process.env.HOME || '~',
|
|
13
|
+
'.trello-cli-unofficial',
|
|
14
|
+
);
|
|
15
|
+
this.configFile = path.join(this.configDir, 'config.json');
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
async load(): Promise<ConfigEntity> {
|
|
19
|
+
try {
|
|
20
|
+
if (await fs.pathExists(this.configFile)) {
|
|
21
|
+
const data = await fs.readJson(this.configFile);
|
|
22
|
+
return new ConfigEntity(
|
|
23
|
+
data.apiKey || '630a01228b85df706aa520f3611e6490',
|
|
24
|
+
data.token || process.env.TRELLO_TOKEN,
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
catch (error) {
|
|
29
|
+
console.error('Error loading config:', (error as Error).message);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return ConfigEntity.createDefault();
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
async save(config: ConfigEntity): Promise<void> {
|
|
36
|
+
try {
|
|
37
|
+
await fs.ensureDir(this.configDir);
|
|
38
|
+
await fs.writeJson(
|
|
39
|
+
this.configFile,
|
|
40
|
+
{
|
|
41
|
+
apiKey: config.apiKey,
|
|
42
|
+
token: config.token,
|
|
43
|
+
},
|
|
44
|
+
{ spaces: 2 },
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
catch (error) {
|
|
48
|
+
console.error('Error saving config:', (error as Error).message);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import type { CreateCardData, UpdateCardData } from '../../domain/entities';
|
|
2
|
+
import type { TrelloRepository } from '../../domain/repositories';
|
|
3
|
+
import { BoardEntity, CardEntity, ListEntity } from '../../domain/entities';
|
|
4
|
+
|
|
5
|
+
export class TrelloApiRepository implements TrelloRepository {
|
|
6
|
+
private readonly baseUrl = 'https://api.trello.com/1';
|
|
7
|
+
|
|
8
|
+
constructor(
|
|
9
|
+
private readonly apiKey: string,
|
|
10
|
+
private readonly token: string,
|
|
11
|
+
) {}
|
|
12
|
+
|
|
13
|
+
private async request(endpoint: string, options?: RequestInit): Promise<any> {
|
|
14
|
+
const url = `${this.baseUrl}${endpoint}?key=${this.apiKey}&token=${this.token}`;
|
|
15
|
+
|
|
16
|
+
const response = await fetch(url, options);
|
|
17
|
+
|
|
18
|
+
if (!response.ok) {
|
|
19
|
+
const errorText = await response.text();
|
|
20
|
+
throw new Error(
|
|
21
|
+
`Trello API error: ${response.status} ${response.statusText}\n${errorText}`,
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return response.json();
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async getBoards(): Promise<BoardEntity[]> {
|
|
29
|
+
const data = await this.request('/members/me/boards');
|
|
30
|
+
return data.map((board: any) => BoardEntity.fromApiResponse(board));
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async getLists(boardId: string): Promise<ListEntity[]> {
|
|
34
|
+
const data = await this.request(`/boards/${boardId}/lists`);
|
|
35
|
+
return data.map((list: any) => ListEntity.fromApiResponse(list));
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
async getCards(listId: string): Promise<CardEntity[]> {
|
|
39
|
+
const data = await this.request(`/lists/${listId}/cards`);
|
|
40
|
+
return data.map((card: any) => CardEntity.fromApiResponse(card));
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
async createCard(cardData: CreateCardData): Promise<CardEntity> {
|
|
44
|
+
const body = new URLSearchParams({
|
|
45
|
+
idList: cardData.listId,
|
|
46
|
+
name: cardData.name,
|
|
47
|
+
desc: cardData.desc || '',
|
|
48
|
+
key: this.apiKey,
|
|
49
|
+
token: this.token,
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
const data = await this.request('/cards', {
|
|
53
|
+
method: 'POST',
|
|
54
|
+
headers: {
|
|
55
|
+
'Content-Type': 'application/x-www-form-urlencoded',
|
|
56
|
+
},
|
|
57
|
+
body: body.toString(),
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
return CardEntity.fromApiResponse(data);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
async updateCard(
|
|
64
|
+
cardId: string,
|
|
65
|
+
updates: UpdateCardData,
|
|
66
|
+
): Promise<CardEntity> {
|
|
67
|
+
const body = new URLSearchParams({
|
|
68
|
+
key: this.apiKey,
|
|
69
|
+
token: this.token,
|
|
70
|
+
...updates,
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
const data = await this.request(`/cards/${cardId}`, {
|
|
74
|
+
method: 'PUT',
|
|
75
|
+
headers: {
|
|
76
|
+
'Content-Type': 'application/x-www-form-urlencoded',
|
|
77
|
+
},
|
|
78
|
+
body: body.toString(),
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
return CardEntity.fromApiResponse(data);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
async deleteCard(cardId: string): Promise<void> {
|
|
85
|
+
await this.request(`/cards/${cardId}`, {
|
|
86
|
+
method: 'DELETE',
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
async moveCard(cardId: string, targetListId: string): Promise<CardEntity> {
|
|
91
|
+
return this.updateCard(cardId, { idList: targetListId });
|
|
92
|
+
}
|
|
93
|
+
}
|