Skip to content

Commit e47a513

Browse files
committed
fix: backend app.ts & configs
1 parent dd5eda2 commit e47a513

File tree

10 files changed

+523
-123
lines changed

10 files changed

+523
-123
lines changed

backend/ipfs/IPFSClient.ts

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import { MemoryBlockstore } from 'blockstore-core'
1212
import { MemoryDatastore } from 'datastore-core'
1313
import { httpGatewayRouting } from '@helia/routers'
1414
import { trustlessGateway } from '@helia/block-brokers'
15-
import { kuboBlockBroker } from './KuboBlockBroker'
15+
import { kuboBlockBroker } from './KuboBlockBroker.ts'
1616
import { CID } from 'multiformats/cid'
1717
import { PinataSDK } from 'pinata'
1818

@@ -37,13 +37,18 @@ export type IPFSConfig = {
3737
pinataImagesGroupId?: string // For PRODUCTION mode, required
3838
}
3939

40-
export interface IPFSClient {
40+
export interface IPFSClientInterface {
4141
uploadBytes: (data: Uint8Array) => Promise<string>
4242
downloadBytes: (cid: string) => Promise<Uint8Array>
43+
uploadJson: (data: JSON) => Promise<string>
44+
downloadJson: (cid: string) => Promise<JSON>
45+
uploadFile: (file: File) => Promise<string>
46+
downloadFile: (cid: string) => Promise<Blob>
47+
delete: (cid: string) => Promise<void>
4348
close: () => Promise<void>
4449
}
4550

46-
export class HeliaIPFSClient implements IPFSClient {
51+
export class IPFSClient implements IPFSClientInterface {
4752
private config: IPFSConfig
4853
private helia: any
4954
private pinata: PinataSDK | null = null

backend/ipfs/TaskBoardIPFS.ts

Lines changed: 32 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -6,20 +6,20 @@
66
*/
77

88

9-
import { HeliaIPFSClient, IPFSMode } from './IPFSClient'
10-
import { Task } from '../../objects/BoardTask'
11-
const dotenv = require('dotenv')
9+
import { IPFSClient, IPFSMode } from './IPFSClient.ts'
10+
import { Task } from '../../objects/BoardTask.ts'
11+
import dotenv from 'dotenv'
1212

1313
// Load env variables
1414
dotenv.config()
1515

1616
// Singletone pattern
17-
export class TaskBoardIPFS {
17+
export default class TaskBoardIPFS {
1818
private static instance: TaskBoardIPFS
19-
private ipfs: HeliaIPFSClient
19+
private ipfs: IPFSClient
2020

2121
private constructor () {
22-
this.ipfs = new HeliaIPFSClient({
22+
this.ipfs = new IPFSClient({
2323
mode: (process.env.IPFS_STORAGE_MODE! as IPFSMode) || IPFSMode.DEVELOPMENT,
2424
localIPFSApiUrl: process.env.IPFS_LOCAL_API_URL!,
2525
localIPFSGatewayUrl: process.env.IPFS_LOCAL_GATEWAY_URL!,
@@ -44,43 +44,41 @@ export class TaskBoardIPFS {
4444
* @returns identifier of task
4545
* @throws if upload was not successful.
4646
*/
47-
public async publishTask(task: Task): Promise<string> {
48-
try {
49-
const taskData = JSON.stringify(task.toJSON())
50-
const taskBytes = new TextEncoder().encode(taskData)
51-
const cid = await this.ipfs.uploadBytes(taskBytes)
52-
task.ipfsCid = cid
53-
task.createdAt = Date.now()
54-
55-
console.log(`Task "${task.title}" published to IPFS, CID: ${cid}`)
56-
return cid
57-
} catch (error) {
58-
console.error('Failed to publish task:', error)
59-
throw new Error(`Failed to publish task "${task.title}": ${error}`)
60-
}
47+
public async addTask(task: Task): Promise<string> {
48+
const cid = await this.ipfs.uploadJson(task.toJSON() as JSON)
49+
task.ipfsCid = cid
50+
task.createdAt = Date.now()
51+
return cid
6152
}
6253

6354
/**
64-
* Download task from IPFS
55+
* Download tasks from IPFS
6556
* @param cid identifier of task
6657
* @returns object representing the received task.
6758
* @throws if download was not successful.
6859
*/
69-
public async loadTaskFromIPFS(cid: string): Promise<Task> {
70-
try {
71-
const taskBytes = await this.ipfs.downloadBytes(cid)
72-
const taskData = JSON.parse(new TextDecoder().decode(taskBytes))
73-
const task = Task.fromJSON(taskData)
74-
75-
task.ipfsCid = cid
60+
public async getTasks(limit: number, offset: number, filters?: Partial<Task>): Promise<Task[]> {
61+
let tasks: Task[] = []
7662

77-
console.log(`Task "${task.title}" loaded from IPFS, CID: ${cid}`)
78-
return task
79-
} catch (error) {
80-
console.error('Failed to load task from IPFS:', error)
81-
throw new Error(`Failed to load task from IPFS ${cid}: ${error}`)
63+
// TODO: fetch taskIDs from DB based on filters, limit and offset
64+
/*
65+
for (const cid in cids) {
66+
const jsonTask = await this.ipfs.downloadJson(cid)
67+
const task = Task.fromJSON(jsonTask)
68+
task.ipfsCid = cid
69+
tasks.push(task)
8270
}
71+
*/
72+
return tasks
8373
}
8474

85-
/// TODO: Unpin task from IPFS
75+
/**
76+
* Delete task from IPFS
77+
* @param cid identifier of task
78+
* @throws if deletion was not successful.
79+
*/
80+
public async deleteTask(cid: string): Promise<void> {
81+
// TODO: DB adjustments
82+
await this.ipfs.delete(cid)
83+
}
8684
}

backend/package.json

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
{
22
"name": "backend",
33
"version": "1.0.0",
4-
"main": "index.js",
4+
"main": "app.js",
55
"type": "module",
66
"scripts": {
7-
"test": "echo \"Error: no test specified\" && exit 1",
8-
"compile-schemas": "json2ts -i schemas -o types"
7+
"dev": "node --loader ts-node/esm src/app.ts",
8+
"test": "echo \"Error: no test specified\" && exit 1"
99
},
1010
"keywords": [],
1111
"author": "",
@@ -27,6 +27,7 @@
2727
"devDependencies": {
2828
"@types/node": "^24.3.0",
2929
"ts-node": "^10.9.2",
30+
"ts-node-dev": "^2.0.0",
3031
"typescript": "^5.9.2"
3132
}
3233
}

backend/src/TaskManager.ts

Lines changed: 0 additions & 31 deletions
This file was deleted.

backend/src/app.ts

Lines changed: 37 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,45 +1,53 @@
1-
import Fastify from "fastify";
1+
import Fastify from "fastify"
22
import fastifySwagger from '@fastify/swagger'
33
import fastifySwaggerUi from '@fastify/swagger-ui'
4-
const dotenv = require('dotenv')
4+
import dotenv from 'dotenv'
5+
6+
import taskRoutes from './routes/taskRoutes.ts';
57

68
// Load env variables
79
dotenv.config()
810

911

10-
const app = Fastify({ logger: true })
12+
async function buildApp() {
13+
const app = Fastify({ logger: true })
1114

12-
// Swagger for API documentation
13-
app.register(fastifySwagger, {
14-
swagger: {
15-
info: {
16-
title: 'CTF Backend API',
17-
description: 'API docs for CTF challenge backend',
18-
version: '0.1.0'
15+
// Swagger for API documentation
16+
app.register(fastifySwagger, {
17+
swagger: {
18+
info: {
19+
title: 'CTF Backend API',
20+
description: 'API docs for CTF challenge backend',
21+
version: '0.1.0'
22+
}
1923
}
20-
}
21-
})
24+
})
25+
26+
app.register(fastifySwaggerUi, {
27+
routePrefix: '/docs',
28+
uiConfig: { docExpansion: 'full', deepLinking: false }
29+
})
2230

23-
app.register(fastifySwaggerUi, {
24-
routePrefix: '/docs',
25-
uiConfig: { docExpansion: 'full', deepLinking: false }
26-
})
2731

32+
// Register routes
33+
app.register(taskRoutes, { prefix: '/api' })
2834

29-
// Register routes
30-
import taskRoutes from "./routes/taskRoutes";
31-
app.register(taskRoutes, { prefix: '/api' })
35+
// Error handler (good practice)
36+
app.setErrorHandler((error, request, reply) => {
37+
app.log.error(error)
38+
reply.status(500).send({ error: 'Internal Server Error' })
39+
})
40+
41+
return app
42+
}
3243

3344
// Start server
34-
const start = async () => {
35-
try {
36-
await app.listen({ port: Number(process.env.SERVER_PORT) || 3000, host: '0.0.0.0' })
37-
app.log.info(`Server running on port ${process.env.SERVER_PORT || 3000}`)
38-
} catch (err) {
45+
const app = await buildApp()
46+
app.listen({ port: 3000, host: '0.0.0.0' })
47+
.then((address) => {
48+
app.log.info(`Server listening at ${address}`)
49+
})
50+
.catch((err) => {
3951
app.log.error(err)
4052
process.exit(1)
41-
}
42-
}
43-
44-
45-
start()
53+
})
Lines changed: 47 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,73 @@
11
import { type FastifyReply, type FastifyRequest } from 'fastify';
22
import { type Static } from '@sinclair/typebox'
3-
import { TaskSchema } from '../schemas/taskSchema'
4-
import TaskManager from '../TaskManager'
5-
import { Task } from '../../../objects/BoardTask';
3+
import { TaskSchema } from '../schemas/taskSchema.ts'
4+
import TaskBoardIPFS from '../../ipfs/TaskBoardIPFS.ts'
5+
import { Task } from '../../../objects/BoardTask.ts';
66

77
type TaskBody = Static<typeof TaskSchema>
88

9-
const IPFS = new TaskManager()
9+
const IPFS = TaskBoardIPFS.getInstance()
1010

1111
export async function createTaskController(
1212
request: FastifyRequest<{ Body: TaskBody }>,
1313
reply: FastifyReply
1414
) {
15-
const task = new Task(request.body)
16-
const cid = await IPFS.addTask(task)
17-
reply.code(201).send({ ...task.toJSON(), createdAt: task.createdAt, ipfsCid: cid })
15+
try {
16+
const task = new Task(request.body)
17+
const cid = await IPFS.addTask(task)
18+
return reply.code(201).send({ ...task.toJSON(), createdAt: task.createdAt, ipfsCid: cid })
19+
} catch (error) {
20+
request.log.error(error)
21+
return reply.code(500).send({
22+
error: 'Failed to create task',
23+
message: 'Could not create task. Please try again later.'
24+
})
25+
}
26+
1827
}
1928

2029

2130
export async function getTasksController(
2231
request: FastifyRequest<{ Querystring: { limit?: number; offset?: number; difficulty?: string } }>,
2332
reply: FastifyReply
2433
) {
25-
const { limit = 10, offset = 0, difficulty } = request.query
26-
const filters: any = {}
27-
if (difficulty) filters.difficulty = difficulty
34+
try {
35+
const { limit = 10, offset = 0, difficulty } = request.query
36+
const filters: any = {}
37+
if (difficulty) filters.difficulty = difficulty
2838

29-
const tasks = await IPFS.getTasks(limit, offset, filters)
30-
reply.send(tasks.map(t => ({ ...t.toJSON(), createdAt: t.createdAt, ipfsCid: t.ipfsCid })))
39+
const tasks = await IPFS.getTasks(limit, offset, filters)
40+
41+
if (tasks.length === 0) {
42+
return reply.code(404).send({
43+
error: 'No tasks found',
44+
message: 'No tasks match the provided criteria.'
45+
})
46+
} else {
47+
reply.code(200).send(tasks.map(t => ({ ...t.toJSON(), createdAt: t.createdAt, ipfsCid: t.ipfsCid })))
48+
}
49+
} catch (error) {
50+
reply.code(500).send({
51+
error: 'Failed to fetch tasks',
52+
message: 'Could not retrieve tasks. Please try again later.'
53+
})
54+
}
3155
}
3256

3357

3458
export async function deleteTaskController(
3559
request: FastifyRequest<{ Params: { cid: string } }>,
3660
reply: FastifyReply
3761
) {
38-
await IPFS.deleteTask(request.params.cid)
39-
reply.code(204).send()
62+
try {
63+
const { cid } = request.params
64+
await IPFS.deleteTask(cid)
65+
return reply.code(204).send()
66+
} catch (error) {
67+
request.log.error(error)
68+
return reply.code(500).send({
69+
error: 'Failed to delete task',
70+
message: 'Could not delete task. Please try again later.'
71+
})
72+
}
4073
}

backend/src/routes/taskRoutes.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { type FastifyPluginAsync } from 'fastify';
2-
import { TaskResponseSchema, TaskSchema } from '../schemas/taskSchema'
3-
import { createTaskController, deleteTaskController, getTasksController } from '../controllers/taskController'
2+
import { TaskResponseSchema, TaskSchema } from '../schemas/taskSchema.ts'
3+
import { createTaskController, deleteTaskController, getTasksController } from '../controllers/taskController.ts'
44

55
const taskRoutes: FastifyPluginAsync = async (app) => {
66

backend/tsconfig.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111
"moduleResolution": "node",
1212
"esModuleInterop": true,
1313
"target": "esnext",
14+
"allowImportingTsExtensions": true,
15+
"noEmit": true,
1416
"types": [],
1517
// For nodejs:
1618
"lib": ["esnext"],

0 commit comments

Comments
 (0)