Skip to content

Commit d17f184

Browse files
committedOct 2, 2021
Update to release on NPM.
1 parent 40180bb commit d17f184

File tree

8 files changed

+734
-258
lines changed

8 files changed

+734
-258
lines changed
 

‎.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
*.log
22
.DS_Store
33
node_modules
4+
dist

‎README.md

+2-102
Original file line numberDiff line numberDiff line change
@@ -1,103 +1,3 @@
1-
# TSDX User Guide
1+
# React Query API
22

3-
Congrats! You just saved yourself hours of work by bootstrapping this project with TSDX. Let’s get you oriented with what’s here and how to use it.
4-
5-
> This TSDX setup is meant for developing libraries (not apps!) that can be published to NPM. If you’re looking to build a Node app, you could use `ts-node-dev`, plain `ts-node`, or simple `tsc`.
6-
7-
> If you’re new to TypeScript, checkout [this handy cheatsheet](https://devhints.io/typescript)
8-
9-
## Commands
10-
11-
TSDX scaffolds your new library inside `/src`.
12-
13-
To run TSDX, use:
14-
15-
```bash
16-
npm start # or yarn start
17-
```
18-
19-
This builds to `/dist` and runs the project in watch mode so any edits you save inside `src` causes a rebuild to `/dist`.
20-
21-
To do a one-off build, use `npm run build` or `yarn build`.
22-
23-
To run tests, use `npm test` or `yarn test`.
24-
25-
## Configuration
26-
27-
Code quality is set up for you with `prettier`, `husky`, and `lint-staged`. Adjust the respective fields in `package.json` accordingly.
28-
29-
### Jest
30-
31-
Jest tests are set up to run with `npm test` or `yarn test`.
32-
33-
### Bundle Analysis
34-
35-
[`size-limit`](https://github.com/ai/size-limit) is set up to calculate the real cost of your library with `npm run size` and visualize the bundle with `npm run analyze`.
36-
37-
#### Setup Files
38-
39-
This is the folder structure we set up for you:
40-
41-
```txt
42-
/src
43-
index.tsx # EDIT THIS
44-
/test
45-
blah.test.tsx # EDIT THIS
46-
.gitignore
47-
package.json
48-
README.md # EDIT THIS
49-
tsconfig.json
50-
```
51-
52-
### Rollup
53-
54-
TSDX uses [Rollup](https://rollupjs.org) as a bundler and generates multiple rollup configs for various module formats and build settings. See [Optimizations](#optimizations) for details.
55-
56-
### TypeScript
57-
58-
`tsconfig.json` is set up to interpret `dom` and `esnext` types, as well as `react` for `jsx`. Adjust according to your needs.
59-
60-
## Continuous Integration
61-
62-
### GitHub Actions
63-
64-
Two actions are added by default:
65-
66-
- `main` which installs deps w/ cache, lints, tests, and builds on all pushes against a Node and OS matrix
67-
- `size` which comments cost comparison of your library on every pull request using [`size-limit`](https://github.com/ai/size-limit)
68-
69-
## Optimizations
70-
71-
Please see the main `tsdx` [optimizations docs](https://github.com/palmerhq/tsdx#optimizations). In particular, know that you can take advantage of development-only optimizations:
72-
73-
```js
74-
// ./types/index.d.ts
75-
declare var __DEV__: boolean;
76-
77-
// inside your code...
78-
if (__DEV__) {
79-
console.log('foo');
80-
}
81-
```
82-
83-
You can also choose to install and use [invariant](https://github.com/palmerhq/tsdx#invariant) and [warning](https://github.com/palmerhq/tsdx#warning) functions.
84-
85-
## Module Formats
86-
87-
CJS, ESModules, and UMD module formats are supported.
88-
89-
The appropriate paths are configured in `package.json` and `dist/index.js` accordingly. Please report if any issues are found.
90-
91-
## Named Exports
92-
93-
Per Palmer Group guidelines, [always use named exports.](https://github.com/palmerhq/typescript#exports) Code split inside your React app instead of your React library.
94-
95-
## Including Styles
96-
97-
There are many ways to ship styles, including with CSS-in-JS. TSDX has no opinion on this, configure how you like.
98-
99-
For vanilla CSS, you can include it at the root directory and add it to the `files` section in your `package.json`, so that it can be imported separately by your users and run through their bundler's loader.
100-
101-
## Publishing to NPM
102-
103-
We recommend using [np](https://github.com/sindresorhus/np).
3+
This is the React Query API which is used for the React Query course on ui.dev. Built with [MSW](https://mswjs.io).

‎package.json

+6-2
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
"node": ">=10"
1212
},
1313
"scripts": {
14+
"dev": "nodemon --exec node -r ts-eager/register server.ts",
1415
"start": "tsdx watch",
1516
"build": "tsdx build",
1617
"lint": "tsdx lint",
@@ -30,7 +31,7 @@
3031
"singleQuote": true,
3132
"trailingComma": "es5"
3233
},
33-
"name": "react-query-api",
34+
"name": "@uidotdev/react-query-api",
3435
"author": "Alex Anderson",
3536
"module": "dist/react-query-api.esm.js",
3637
"size-limit": [
@@ -46,12 +47,15 @@
4647
"devDependencies": {
4748
"@size-limit/preset-small-lib": "^5.0.5",
4849
"husky": "^7.0.2",
50+
"nodemon": "^2.0.13",
4951
"size-limit": "^5.0.5",
5052
"tsdx": "^0.14.1",
5153
"tslib": "^2.3.1",
5254
"typescript": "^4.4.3"
5355
},
5456
"dependencies": {
55-
"msw": "^0.35.0"
57+
"msw": "^0.35.0",
58+
"node-fetch": "^3.0.0",
59+
"ts-eager": "^2.0.2"
5660
}
5761
}

‎server.ts

+54
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import { setupServer } from 'msw/node';
2+
import { handlers } from './src/handlers';
3+
import http from 'http';
4+
5+
const port = Number(process.env.PORT) || 3000;
6+
7+
(async function runServer() {
8+
const { default: fetch, Headers, Response } = await import('node-fetch');
9+
const mockServer = setupServer(...handlers);
10+
11+
mockServer.listen();
12+
13+
const server = http.createServer(async (req, res) => {
14+
const body: string = await new Promise(res => {
15+
let data = '';
16+
req.on('data', chunk => {
17+
data += chunk;
18+
});
19+
20+
req.on('end', () => {
21+
res(data);
22+
});
23+
});
24+
const headers = new Headers();
25+
for (let header in req.headers) {
26+
headers.append(header, req.headers[header].toString());
27+
}
28+
try {
29+
const response = await Promise.race([
30+
fetch(`http://localhost:3000${req.url}`, {
31+
method: req.method,
32+
body: req.method !== 'GET' ? body : undefined,
33+
headers,
34+
}),
35+
new Promise((_, reject) => setTimeout(reject, 1500)),
36+
]);
37+
if (!(response instanceof Response)) {
38+
throw new Error('Response is not instance of Response');
39+
}
40+
const result = await response.text();
41+
response.headers.forEach((v, k) => res.setHeader(k, v));
42+
res.statusCode = response.status;
43+
res.end(result);
44+
} catch (err) {
45+
console.log(err);
46+
res.statusCode = 500;
47+
res.end('Error making request');
48+
}
49+
});
50+
51+
server.listen(port, () => {
52+
console.log(`Server running at port ${port}`);
53+
});
54+
})();

‎src/db.ts

+67-59
Original file line numberDiff line numberDiff line change
@@ -1,55 +1,55 @@
1-
import { Issue, Label, User } from "./types";
1+
import { Issue, Label, User, IssueComment } from './types';
22

33
export let users: User[] = [
44
{
5-
id: "u_1",
6-
name: "Tyler",
5+
id: 'u_1',
6+
name: 'Tyler',
77
profilePictureUrl:
8-
"https://pbs.twimg.com/profile_images/1428205319616798721/xmr7q976_400x400.jpg",
8+
'https://pbs.twimg.com/profile_images/1428205319616798721/xmr7q976_400x400.jpg',
99
},
1010
{
11-
id: "u_2",
12-
name: "Bono",
11+
id: 'u_2',
12+
name: 'Bono',
1313
profilePictureUrl:
14-
"https://pbs.twimg.com/profile_images/1384860221110095873/f8s_E6a6_400x400.jpg",
14+
'https://pbs.twimg.com/profile_images/1384860221110095873/f8s_E6a6_400x400.jpg',
1515
},
1616
{
17-
id: "u_3",
18-
name: "Tanner",
17+
id: 'u_3',
18+
name: 'Tanner',
1919
profilePictureUrl:
20-
"https://pbs.twimg.com/profile_images/1164219021283094530/ACRln2kL_400x400.jpg",
20+
'https://pbs.twimg.com/profile_images/1164219021283094530/ACRln2kL_400x400.jpg',
2121
},
2222
{
23-
id: "u_4",
24-
name: "Alex",
23+
id: 'u_4',
24+
name: 'Alex',
2525
profilePictureUrl:
26-
"https://pbs.twimg.com/profile_images/1403026826075779075/cHtraFgQ_400x400.jpg",
26+
'https://pbs.twimg.com/profile_images/1403026826075779075/cHtraFgQ_400x400.jpg',
2727
},
2828
];
2929

3030
export let labels: Label[] = [
31-
{ id: "l_1", color: "red", name: "bug" },
32-
{ id: "l_2", color: "blue", name: "feature" },
33-
{ id: "l_3", color: "cyan", name: "enhancement" },
34-
{ id: "l_4", color: "orange", name: "question" },
35-
{ id: "l_5", color: "lime", name: "help wanted" },
36-
{ id: "l_6", color: "white", name: "wontfix" },
37-
{ id: "l_7", color: "rebeccapurple", name: "duplicate" },
38-
{ id: "l_8", color: "yellow", name: "help-wanted" },
31+
{ id: 'l_1', color: 'red', name: 'bug' },
32+
{ id: 'l_2', color: 'blue', name: 'feature' },
33+
{ id: 'l_3', color: 'cyan', name: 'enhancement' },
34+
{ id: 'l_4', color: 'orange', name: 'question' },
35+
{ id: 'l_5', color: 'lime', name: 'help wanted' },
36+
{ id: 'l_6', color: 'white', name: 'wontfix' },
37+
{ id: 'l_7', color: 'rebeccapurple', name: 'duplicate' },
38+
{ id: 'l_8', color: 'yellow', name: 'help-wanted' },
3939
];
4040

4141
const templateIssues = [
42-
"Dependencies need to be updated",
43-
"Poor performance on Windows devices",
44-
"Poor performance on macOS devices",
45-
"Poor performance on Android devices",
46-
"Holding down the space bar causes the processor to heat up",
42+
'Dependencies need to be updated',
43+
'Poor performance on Windows devices',
44+
'Poor performance on macOS devices',
45+
'Poor performance on Android devices',
46+
'Holding down the space bar causes the processor to heat up',
4747
`Error: "Cannot read property 'length' of undefined"`,
48-
"The app is crashing on iOS devices",
49-
"How am I supposed to create new tasks?",
50-
"Styling on the profile page looks weird.",
51-
"Feature: Build out multiplayer connectivity",
52-
"Feature: Build out a leaderboard",
48+
'The app is crashing on iOS devices',
49+
'How am I supposed to create new tasks?',
50+
'Styling on the profile page looks weird.',
51+
'Feature: Build out multiplayer connectivity',
52+
'Feature: Build out a leaderboard',
5353
];
5454

5555
const templateIssueComments = [
@@ -58,51 +58,59 @@ const templateIssueComments = [
5858
"I'm working on it.",
5959
"I'm not sure how to fix it.",
6060
"I'm not sure if I can reproduce the problem.",
61-
"This is a really big deal for me.",
62-
"Has there been any progress on this?",
63-
"What is the status of this issue?",
64-
"Never mind, I figured out how to fix this",
65-
"Can you send me a little bit more information about the problem.",
61+
'This is a really big deal for me.',
62+
'Has there been any progress on this?',
63+
'What is the status of this issue?',
64+
'Never mind, I figured out how to fix this',
65+
'Can you send me a little bit more information about the problem.',
6666
"I've reproduced this issue. Working on a fix now.",
6767
"I'm on it. I'll get back to you when I'm done.",
68-
"It would seem this is caused by user error.",
69-
"Whoops, I just dropped the production database. Hang on...",
68+
'It would seem this is caused by user error.',
69+
'Whoops, I just dropped the production database. Hang on...',
7070
];
7171

72-
const allStatus: ("backlog" | "todo" | "inProgress" | "done" | "cancelled")[] =
73-
["backlog", "todo", "inProgress", "done", "cancelled"];
72+
const allStatus: (
73+
| 'backlog'
74+
| 'todo'
75+
| 'inProgress'
76+
| 'done'
77+
| 'cancelled'
78+
)[] = ['backlog', 'todo', 'inProgress', 'done', 'cancelled'];
79+
80+
export let issueComments: IssueComment[] = [];
7481

7582
export const issues: Issue[] = Array.from({ length: 100 }, (_, i) => {
7683
const isCompleted = Math.random() > 0.9;
84+
const comments: IssueComment[] = Array.from(
85+
{ length: Math.floor(Math.random() * 10) + 1 },
86+
(_, j) => ({
87+
id: `c_${issueComments.length + j}`,
88+
createdDate: new Date(Date.now() - Math.floor(Math.random() * 100000)),
89+
createdBy: users[Math.floor(Math.random() * users.length)].id,
90+
issueId: `i_${i}`,
91+
comment:
92+
templateIssueComments[
93+
Math.floor(Math.random() * templateIssueComments.length)
94+
],
95+
})
96+
);
97+
issueComments = issueComments.concat(comments);
7798
return {
7899
id: `i_${i}`,
79100
title: templateIssues[Math.floor(Math.random() * templateIssues.length)],
80-
labels: [labels[Math.floor(Math.random() * labels.length)]],
81-
comments: Array.from(
82-
{ length: Math.floor(Math.random() * 10) + 1 },
83-
(_, j) => ({
84-
id: `c_${i}_${j}`,
85-
createdDate: new Date(Date.now() - Math.floor(Math.random() * 100000)),
86-
createdBy: users[Math.floor(Math.random() * users.length)],
87-
issueId: `i_${i}`,
88-
user: users[Math.floor(Math.random() * users.length)],
89-
comment:
90-
templateIssueComments[
91-
Math.floor(Math.random() * templateIssueComments.length)
92-
],
93-
})
94-
),
101+
labels: [labels[Math.floor(Math.random() * labels.length)].id],
102+
comments: comments.map(c => c.id),
95103
number: i + 1,
96104
status: isCompleted
97-
? "done"
98-
: allStatus.filter((f) => f !== "done")[
105+
? 'done'
106+
: allStatus.filter(f => f !== 'done')[
99107
Math.floor(Math.random() * allStatus.length)
100108
],
101109
createdDate: new Date(Date.now() - Math.floor(Math.random() * 10000000000)),
102-
createdBy: users[Math.floor(Math.random() * users.length)],
110+
createdBy: users[Math.floor(Math.random() * users.length)].id,
103111
assignee:
104112
Math.random() > 0.5
105-
? users[Math.floor(Math.random() * users.length)]
113+
? users[Math.floor(Math.random() * users.length)].id
106114
: null,
107115
dueDate:
108116
Math.random() > 0.5

‎src/handlers.ts

+146-78
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
import { rest } from 'msw';
2-
import { issues, labels, users } from './db';
2+
import { issueComments, issues, labels, users } from './db';
33
import { Issue, IssueComment } from './types';
44

5+
const makeUrl = (path: string) =>
6+
`${typeof window === 'undefined' ? 'http://localhost:3000' : ''}${path}`;
7+
58
export const handlers = [
6-
rest.get('/api/issues', (req, res, ctx) => {
9+
rest.get(makeUrl('/api/issues'), (req, res, ctx) => {
710
const query = req.url.searchParams;
811
const page = Number(query.get('page')) || 1;
912
const perPage = Number(query.get('limit')) || 10;
@@ -14,7 +17,7 @@ export const handlers = [
1417
| 'done'
1518
| 'cancelled'
1619
| null;
17-
const labels = query.getAll('labels[]');
20+
const labelQuery = query.getAll('labels[]');
1821
const order = query.get('order') || 'desc';
1922
const filteredIssues = issues.filter(issue => {
2023
if (statusFilter) {
@@ -23,10 +26,12 @@ export const handlers = [
2326
if (issue.status === 'done' || issue.status === 'cancelled')
2427
return false;
2528
}
26-
if (labels.length > 0) {
29+
if (labelQuery.length > 0) {
2730
if (
28-
!labels.some(label => {
29-
return issue.labels.find(l => l.name === label);
31+
!labelQuery.some(label => {
32+
const dbLabel = labels.find(l => l.name === label);
33+
if (!dbLabel) return false;
34+
return issue.labels.find(l => l === dbLabel.id);
3035
})
3136
) {
3237
return false;
@@ -51,53 +56,58 @@ export const handlers = [
5156
);
5257
return res(ctx.status(200), ctx.json(pagedIssues));
5358
}),
54-
rest.get('/api/issues/:number', (req, res, ctx) => {
59+
rest.get(makeUrl('/api/issues/:number'), (req, res, ctx) => {
5560
const number = Number(req.params.number);
5661
const issue = issues.find(issue => issue.number === number);
5762
if (!issue) {
5863
return res(ctx.status(404), ctx.json({ message: 'Not found' }));
5964
}
6065
return res(ctx.status(200), ctx.json(issue));
6166
}),
62-
rest.get('/api/issues/:number/comments', (req, res, ctx) => {
67+
rest.get(makeUrl('/api/issues/:number/comments'), (req, res, ctx) => {
6368
const number = Number(req.params.number);
6469
const issue = issues.find(issue => issue.number === number);
6570
if (!issue) {
6671
return res(ctx.status(404), ctx.json({ message: 'Not found' }));
6772
}
6873
return res(ctx.status(200), ctx.json(issue.comments));
6974
}),
70-
rest.post<string>('/api/issues/:number/comments', (req, res, ctx) => {
71-
const number = Number(req.params.number);
72-
const issue = issues.find(issue => issue.number === number);
73-
if (!issue) {
74-
return res(ctx.status(404), ctx.json({ message: 'Not found' }));
75-
}
76-
const body: { createdBy_id: string; comment: string } = JSON.parse(
77-
req.body
78-
);
79-
const createdBy = users.find(user => user.id === body.createdBy_id);
80-
if (!createdBy) {
81-
return res(ctx.status(400), ctx.json({ message: 'Not found' }));
82-
}
83-
const comment: IssueComment = {
84-
id: `c_${issue.id}_${issue.comments.length + 1}`,
85-
issueId: issue.id,
86-
createdDate: new Date(),
87-
createdBy,
88-
comment: body.comment,
89-
};
75+
rest.post<string>(
76+
makeUrl('/api/issues/:number/comments'),
77+
(req, res, ctx) => {
78+
const number = Number(req.params.number);
79+
const issue = issues.find(issue => issue.number === number);
80+
if (!issue) {
81+
return res(ctx.status(404), ctx.json({ message: 'Not found' }));
82+
}
83+
const body: { createdBy_id: string; comment: string } = JSON.parse(
84+
req.body
85+
);
86+
const createdBy = users.find(user => user.id === body.createdBy_id);
87+
if (!createdBy) {
88+
return res(ctx.status(400), ctx.json({ message: 'Not found' }));
89+
}
90+
const comment: IssueComment = {
91+
id: `c_${issue.id}_${issue.comments.length + 1}`,
92+
issueId: issue.id,
93+
createdDate: new Date(),
94+
createdBy: createdBy.id,
95+
comment: body.comment,
96+
};
9097

91-
issue.comments.push(comment);
92-
return res(ctx.status(201), ctx.json(comment));
93-
}),
94-
rest.put<string>('/api/issues/:number', (req, res, ctx) => {
98+
issue.comments.push();
99+
return res(ctx.status(201), ctx.json(comment));
100+
}
101+
),
102+
rest.put<string>(makeUrl('/api/issues/:number'), (req, res, ctx) => {
95103
const number = Number(req.params.number);
96104
const issue = issues.find(issue => issue.number === number);
97105
if (!issue) {
98106
return res(ctx.status(404), ctx.json({ message: 'Not found' }));
99107
}
100-
const body = JSON.parse(req.body);
108+
let body: Record<string, any> = {};
109+
if (typeof req.body === 'string') body = JSON.parse(req.body);
110+
if (typeof req.body === 'object') body = req.body;
101111

102112
if (body.title) {
103113
issue.title = body.title;
@@ -114,42 +124,49 @@ export const handlers = [
114124
issue.dueDate = body.dueDate;
115125
}
116126
if (body.assignee) {
117-
issue.assignee = users.find(user => user.id === body.assignee) || null;
127+
issue.assignee =
128+
users.find(user => user.id === body.assignee)?.id || null;
118129
}
119130

120131
return res(ctx.status(200), ctx.json(issue));
121132
}),
122-
rest.post<string>('/api/issues/:number/complete', (req, res, ctx) => {
123-
const number = Number(req.params.number);
124-
const issue = issues.find(issue => issue.number === number);
125-
if (!issue) {
126-
return res(ctx.status(404), ctx.json({ message: 'Not found' }));
127-
}
133+
rest.post<string>(
134+
makeUrl('/api/issues/:number/complete'),
135+
(req, res, ctx) => {
136+
const number = Number(req.params.number);
137+
const issue = issues.find(issue => issue.number === number);
138+
if (!issue) {
139+
return res(ctx.status(404), ctx.json({ message: 'Not found' }));
140+
}
128141

129-
issue.createdDate = new Date();
130-
issue.status = 'done';
142+
issue.createdDate = new Date();
143+
issue.status = 'done';
131144

132-
return res(ctx.status(200), ctx.json(issue));
133-
}),
134-
rest.post<string>('/api/issues', (req, res, ctx) => {
135-
const body = JSON.parse(req.body);
145+
return res(ctx.status(200), ctx.json(issue));
146+
}
147+
),
148+
rest.post<string>(makeUrl('/api/issues'), (req, res, ctx) => {
149+
let body: Record<string, any> = {};
150+
if (typeof req.body === 'string') body = JSON.parse(req.body);
151+
if (typeof req.body === 'object') body = req.body;
136152
const number = issues.length + 1;
153+
154+
const issueComment = {
155+
issueId: `i_${number}`,
156+
id: `c_${issueComments.length}`,
157+
createdDate: new Date(),
158+
createdBy: users[Math.floor(Math.random() * users.length)].id,
159+
comment: body.comment,
160+
};
161+
issueComments.push(issueComment);
137162
const issue: Issue = {
138163
id: `i_${number}`,
139164
number,
140165
title: body.title,
141166
status: 'backlog',
142-
comments: [
143-
{
144-
issueId: `i_${number}`,
145-
id: `c_${number}_1`,
146-
createdDate: new Date(),
147-
createdBy: users[Math.floor(Math.random() * users.length)],
148-
comment: body.comment,
149-
},
150-
],
167+
comments: [issueComment.id],
151168
createdDate: new Date(),
152-
createdBy: users[Math.floor(Math.random() * users.length)],
169+
createdBy: users[Math.floor(Math.random() * users.length)].id,
153170
dueDate: null,
154171
completedDate: null,
155172
assignee: null,
@@ -160,10 +177,10 @@ export const handlers = [
160177
return res(ctx.status(201), ctx.json(issue));
161178
}),
162179

163-
rest.get('/api/labels', (_req, res, ctx) => {
180+
rest.get(makeUrl('/api/labels'), (_req, res, ctx) => {
164181
return res(ctx.status(200), ctx.json(labels));
165182
}),
166-
rest.get('/api/labels/:labelId', (req, res, ctx) => {
183+
rest.get(makeUrl('/api/labels/:labelId'), (req, res, ctx) => {
167184
const { labelId } = req.params;
168185

169186
const label = labels.find(l => l.name === labelId);
@@ -172,44 +189,44 @@ export const handlers = [
172189
}
173190
return res(ctx.status(200), ctx.json(label));
174191
}),
175-
rest.post('/api/labels', (req, res, ctx) => {
176-
if (!req.body || typeof req.body !== 'string') {
177-
return res(ctx.status(400), ctx.json({ message: 'No body' }));
178-
}
179-
const parsedBody = JSON.parse(req.body);
180-
if (!parsedBody.name) {
192+
rest.post(makeUrl('/api/labels'), (req, res, ctx) => {
193+
let body: Record<string, any> = {};
194+
if (typeof req.body === 'string') body = JSON.parse(req.body);
195+
if (typeof req.body === 'object') body = req.body;
196+
197+
if (!body.name) {
181198
return res(ctx.status(400), ctx.json({ message: 'No name' }));
182199
}
183200
const label = {
184201
id: `l_${labels.length + 1}`,
185-
name: parsedBody.name,
186-
color: parsedBody.color || 'red',
202+
name: body.name,
203+
color: body.color || 'red',
187204
};
188205
labels.push(label);
189206
return res(ctx.status(200), ctx.json(label));
190207
}),
191-
rest.put('/api/labels/:labelId', (req, res, ctx) => {
208+
rest.put(makeUrl('/api/labels/:labelId'), (req, res, ctx) => {
192209
const { labelId } = req.params;
193210
const label = labels.find(l => l.name === labelId);
194211
if (!label) {
195212
return res(ctx.status(404), ctx.json({ message: 'Label not found' }));
196213
}
197-
if (!req.body || typeof req.body !== 'string') {
198-
return res(ctx.status(400), ctx.json({ message: 'No body' }));
199-
}
200-
const parsedBody = JSON.parse(req.body);
201-
if (!parsedBody.name) {
214+
let body: Record<string, any> = {};
215+
if (typeof req.body === 'string') body = JSON.parse(req.body);
216+
if (typeof req.body === 'object') body = req.body;
217+
218+
if (!body.name) {
202219
return res(ctx.status(400), ctx.json({ message: 'No name' }));
203220
}
204-
if (parsedBody.name) {
205-
label.name = parsedBody.name;
221+
if (body.name) {
222+
label.name = body.name;
206223
}
207-
if (parsedBody.color) {
208-
label.color = parsedBody.color;
224+
if (body.color) {
225+
label.color = body.color;
209226
}
210227
return res(ctx.status(200), ctx.json(label));
211228
}),
212-
rest.delete('/api/labels/:labelId', (req, res, ctx) => {
229+
rest.delete(makeUrl('/api/labels/:labelId'), (req, res, ctx) => {
213230
const { labelId } = req.params;
214231
const label = labels.find(l => l.name === labelId);
215232
if (!label) {
@@ -219,7 +236,58 @@ export const handlers = [
219236
return res(ctx.status(200), ctx.json(labels));
220237
}),
221238

222-
rest.get('/api/users', (_req, res, ctx) => {
239+
rest.get(makeUrl('/api/users'), (_req, res, ctx) => {
223240
return res(ctx.status(200), ctx.json(users));
224241
}),
242+
243+
rest.get(makeUrl('/api/search/issues'), (req, res, ctx) => {
244+
const query = req.url.searchParams.get('q') || '';
245+
if (!query) {
246+
return res(
247+
ctx.status(401),
248+
ctx.json({ message: 'Search query is required' })
249+
);
250+
}
251+
const filteredList = issues.filter(issue => issue.title.includes(query));
252+
return res(
253+
ctx.status(200),
254+
ctx.json({ count: filteredList.length, items: filteredList })
255+
);
256+
}),
257+
rest.get(makeUrl('/api/search/labels'), (req, res, ctx) => {
258+
const query = req.url.searchParams.get('q') || '';
259+
if (!query) {
260+
return res(
261+
ctx.status(401),
262+
ctx.json({ message: 'Search query is required' })
263+
);
264+
}
265+
const filteredList = labels.filter(label => label.name.includes(query));
266+
return res(
267+
ctx.status(200),
268+
ctx.json({ count: filteredList.length, items: filteredList })
269+
);
270+
}),
271+
rest.get(makeUrl('/api/search/comments'), (req, res, ctx) => {
272+
const query = req.url.searchParams.get('q') || '';
273+
if (!query) {
274+
return res(
275+
ctx.status(401),
276+
ctx.json({ message: 'Search query is required' })
277+
);
278+
}
279+
const filteredList = issueComments.filter(comment =>
280+
comment.comment.includes(query)
281+
);
282+
return res(
283+
ctx.status(200),
284+
ctx.json({ count: filteredList.length, items: filteredList })
285+
);
286+
}),
287+
rest.get('*', (req, res, ctx) => {
288+
return res(
289+
ctx.status(404),
290+
ctx.json({ error: { message: 'Invalid API route.' } })
291+
);
292+
}),
225293
];

‎src/types.ts

+6-6
Original file line numberDiff line numberDiff line change
@@ -14,20 +14,20 @@ export interface Issue {
1414
id: string;
1515
number: number;
1616
title: string;
17-
status: "backlog" | "todo" | "inProgress" | "done" | "cancelled";
17+
status: 'backlog' | 'todo' | 'inProgress' | 'done' | 'cancelled';
1818
dueDate: Date | null;
1919
createdDate: Date;
20-
createdBy: User;
20+
createdBy: string;
2121
completedDate: Date | null;
22-
assignee: User | null;
23-
labels: Label[];
24-
comments: IssueComment[];
22+
assignee: string | null;
23+
labels: string[];
24+
comments: string[];
2525
}
2626

2727
export interface IssueComment {
2828
id: string;
2929
issueId: string;
3030
comment: string;
3131
createdDate: Date;
32-
createdBy: User;
32+
createdBy: string;
3333
}

‎yarn.lock

+452-11
Large diffs are not rendered by default.

0 commit comments

Comments
 (0)
Please sign in to comment.