Basic structure of the project
- Added a model with in-memory database, and routes, controller and middleware to enable making initial CRUD calls to endpoints; - Added jest and an initial test.
This commit is contained in:
parent
74c1ee1b7b
commit
9c4ea4ca90
11 changed files with 3841 additions and 1 deletions
11
jest.config.ts
Normal file
11
jest.config.ts
Normal file
|
@ -0,0 +1,11 @@
|
|||
module.exports = {
|
||||
preset: 'ts-jest',
|
||||
testEnvironment: 'node',
|
||||
moduleFileExtensions: ['ts', 'js'],
|
||||
testMatch: ['**/tests/**/*.test.(ts|js)'],
|
||||
globals: {
|
||||
'ts-jest': {
|
||||
tsconfig: 'tsconfig.json',
|
||||
},
|
||||
},
|
||||
};
|
3646
package-lock.json
generated
3646
package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
@ -5,8 +5,9 @@
|
|||
"main": "dist/app.js",
|
||||
"scripts": {
|
||||
"start": "tsc && node dist/app.js",
|
||||
"dev": "nodemon --watch 'src/**/*.ts' --exec 'ts-node' src/app.ts",
|
||||
"lint": "eslint . --ext .ts",
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
"test": "jest --watch"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
@ -23,8 +24,12 @@
|
|||
"devDependencies": {
|
||||
"@eslint/js": "^9.23.0",
|
||||
"@types/express": "^5.0.1",
|
||||
"@types/jest": "^29.5.14",
|
||||
"eslint": "^9.23.0",
|
||||
"globals": "^16.0.0",
|
||||
"jest": "^29.7.0",
|
||||
"ts-jest": "^29.3.1",
|
||||
"ts-node": "^10.9.2",
|
||||
"typescript": "^5.8.2",
|
||||
"typescript-eslint": "^8.29.0"
|
||||
},
|
||||
|
|
11
src/app.ts
11
src/app.ts
|
@ -1,11 +1,22 @@
|
|||
import express from 'express'
|
||||
import topicRoutes from './routes/topicRoutes'
|
||||
import { errorHandler } from './middleware/errorHandler'
|
||||
|
||||
const app = express()
|
||||
const port = 3000
|
||||
|
||||
app.use(express.json())
|
||||
|
||||
// Routes
|
||||
app.get('/', (req, res) => {
|
||||
res.send('Hello World!')
|
||||
})
|
||||
|
||||
app.use('/api/topics', topicRoutes);
|
||||
|
||||
// Global error handler (should be after routes)
|
||||
app.use(errorHandler);
|
||||
|
||||
app.listen(port, () => {
|
||||
return console.log(`Express is listening at http://localhost:${port}`)
|
||||
})
|
||||
|
|
79
src/controllers/topicController.ts
Normal file
79
src/controllers/topicController.ts
Normal file
|
@ -0,0 +1,79 @@
|
|||
import { Request, Response, NextFunction } from 'express';
|
||||
import { topics, Topic } from '../models/topic';
|
||||
|
||||
// Create a topic
|
||||
export const createTopic = (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const { name, content } = req.body;
|
||||
const newTopic: Topic = {
|
||||
id: Date.now(),
|
||||
name,
|
||||
content,
|
||||
createdAt: new Date().toString(),
|
||||
updatedAt: new Date().toString(),
|
||||
version: 1,
|
||||
parentTopicId: 1
|
||||
};
|
||||
topics.push(newTopic);
|
||||
res.status(201).json(newTopic);
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
};
|
||||
|
||||
// Read all topics
|
||||
export const getTopics = (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
res.json(topics);
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
};
|
||||
|
||||
// Read single topic
|
||||
export const getTopicById = (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const id = parseInt(req.params.id, 10);
|
||||
const topic = topics.find((i) => i.id === id);
|
||||
if (!topic) {
|
||||
res.status(404).json({ message: 'Topic not found' });
|
||||
return;
|
||||
}
|
||||
res.json(topic);
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
};
|
||||
|
||||
// Update a topic
|
||||
export const updateTopic = (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const id = parseInt(req.params.id, 10);
|
||||
const { name } = req.body;
|
||||
const topicIndex = topics.findIndex((i) => i.id === id);
|
||||
if (topicIndex === -1) {
|
||||
res.status(404).json({ message: 'Topic not found' });
|
||||
return;
|
||||
}
|
||||
topics[topicIndex].name = name;
|
||||
res.json(topics[topicIndex]);
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
};
|
||||
|
||||
// Delete a topic
|
||||
export const deleteTopic = (req: Request, res: Response, next: NextFunction) => {
|
||||
try {
|
||||
const id = parseInt(req.params.id, 10);
|
||||
const topicIndex = topics.findIndex((i) => i.id === id);
|
||||
if (topicIndex === -1) {
|
||||
res.status(404).json({ message: 'Topic not found' });
|
||||
return;
|
||||
}
|
||||
const deletedTopic = topics.splice(topicIndex, 1)[0];
|
||||
res.json(deletedTopic);
|
||||
} catch (error) {
|
||||
next(error);
|
||||
}
|
||||
};
|
17
src/middleware/errorHandler.ts
Normal file
17
src/middleware/errorHandler.ts
Normal file
|
@ -0,0 +1,17 @@
|
|||
import { Request, Response, NextFunction } from 'express';
|
||||
|
||||
export interface AppError extends Error {
|
||||
status?: number;
|
||||
}
|
||||
|
||||
export const errorHandler = (
|
||||
err: AppError,
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
) => {
|
||||
console.error(err);
|
||||
res.status(err.status || 500).json({
|
||||
message: err.message || 'Internal Server Error',
|
||||
});
|
||||
};
|
11
src/models/resource.ts
Normal file
11
src/models/resource.ts
Normal file
|
@ -0,0 +1,11 @@
|
|||
export interface Resource {
|
||||
id: number;
|
||||
topicId: string;
|
||||
url: string;
|
||||
description: string;
|
||||
type: string;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
}
|
||||
|
||||
export let resources: Resource[] = [];
|
11
src/models/topic.ts
Normal file
11
src/models/topic.ts
Normal file
|
@ -0,0 +1,11 @@
|
|||
export interface Topic {
|
||||
id: number;
|
||||
name: string;
|
||||
content: string;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
version: number;
|
||||
parentTopicId: number;
|
||||
}
|
||||
|
||||
export let topics: Topic[] = [];
|
9
src/models/user.ts
Normal file
9
src/models/user.ts
Normal file
|
@ -0,0 +1,9 @@
|
|||
export interface User {
|
||||
id: number;
|
||||
name: string;
|
||||
email: string;
|
||||
role: string;
|
||||
createdAt: string;
|
||||
}
|
||||
|
||||
export let users: User[] = []
|
18
src/routes/topicRoutes.ts
Normal file
18
src/routes/topicRoutes.ts
Normal file
|
@ -0,0 +1,18 @@
|
|||
import { Router } from 'express';
|
||||
import {
|
||||
createTopic,
|
||||
getTopics,
|
||||
getTopicById,
|
||||
updateTopic,
|
||||
deleteTopic,
|
||||
} from '../controllers/topicController';
|
||||
|
||||
const router = Router();
|
||||
|
||||
router.get('/', getTopics);
|
||||
router.get('/:id', getTopicById);
|
||||
router.post('/', createTopic);
|
||||
router.put('/:id', updateTopic);
|
||||
router.delete('/:id', deleteTopic);
|
||||
|
||||
export default router;
|
22
tests/topicController.test.ts
Normal file
22
tests/topicController.test.ts
Normal file
|
@ -0,0 +1,22 @@
|
|||
import { Request, Response } from 'express';
|
||||
import { getTopics } from '../src/controllers/topicController';
|
||||
import { topics } from '../src/models/topic';
|
||||
|
||||
describe('Topic Controller', () => {
|
||||
it('should return an empty array when no topics exist', () => {
|
||||
// Create mock objects for Request, Response, and NextFunction
|
||||
const req = {} as Request;
|
||||
const res = {
|
||||
json: jest.fn(),
|
||||
} as unknown as Response;
|
||||
|
||||
// Ensure that our in-memory store is empty
|
||||
topics.length = 0;
|
||||
|
||||
// Execute our controller function
|
||||
getTopics(req, res, jest.fn());
|
||||
|
||||
// Expect that res.json was called with an empty array
|
||||
expect(res.json).toHaveBeenCalledWith([]);
|
||||
});
|
||||
});
|
Loading…
Add table
Add a link
Reference in a new issue