2018-10-10 18:03:03 -04:00
|
|
|
|
---
|
|
|
|
|
id: 587d824a367417b2b2512c43
|
2021-03-22 07:52:28 -06:00
|
|
|
|
title: 个人图书馆
|
2018-10-10 18:03:03 -04:00
|
|
|
|
challengeType: 4
|
2021-02-06 04:42:36 +00:00
|
|
|
|
forumTopicId: 301571
|
2021-01-13 03:31:00 +01:00
|
|
|
|
dashedName: personal-library
|
2018-10-10 18:03:03 -04:00
|
|
|
|
---
|
|
|
|
|
|
2020-12-16 00:37:30 -07:00
|
|
|
|
# --description--
|
|
|
|
|
|
2021-03-22 07:52:28 -06:00
|
|
|
|
构建一个 JavaScript 的全栈应用,在功能上与这个应用相似:<https://personal-library.freecodecamp.rocks/>。 可以采用下面的任意一种方式完成这个挑战:
|
2020-12-16 00:37:30 -07:00
|
|
|
|
|
2021-03-22 07:52:28 -06:00
|
|
|
|
- 克隆 [这个 GitHub 仓库](https://github.com/freeCodeCamp/boilerplate-project-library) 并在本地完成项目。
|
2021-05-12 21:25:58 +05:30
|
|
|
|
- 使用[我们的 Replit 初始化项目](https://replit.com/github/freeCodeCamp/boilerplate-project-library)来完成你的项目。
|
2021-03-22 07:52:28 -06:00
|
|
|
|
- 使用一个你喜欢的站点生成器来完成项目。 需要确定包含了我们 GitHub 仓库的所有文件。
|
2020-12-16 00:37:30 -07:00
|
|
|
|
|
2021-03-22 07:52:28 -06:00
|
|
|
|
完成本项目后,请将一个正常运行的 demo(项目演示)托管在可以公开访问的平台。 然后在 `Solution Link` 框中提交你的项目 URL。 此外,还可以将项目的源码提交到 `GitHub Link` 中。
|
2020-12-16 00:37:30 -07:00
|
|
|
|
|
2021-02-06 04:42:36 +00:00
|
|
|
|
# --instructions--
|
2020-12-16 00:37:30 -07:00
|
|
|
|
|
2021-06-21 19:13:43 +05:30
|
|
|
|
1. 将你的 MongoDB 连接字符串添加到 `.env` 中,作为 `DB` 示例:`DB=mongodb://admin:pass@1234.mlab.com:1234/fccpersonallib`
|
2021-03-22 07:52:28 -06:00
|
|
|
|
2. 在 `.env` 文件中设置 `NODE_ENV` 为 `test`中,没有引号
|
|
|
|
|
3. 需要在 `routes/api.js` 中创建所有路由
|
|
|
|
|
4. 在 `tests/2_functional-tests.js` 中创建所有的功能测试
|
2018-10-10 18:03:03 -04:00
|
|
|
|
|
2021-02-06 04:42:36 +00:00
|
|
|
|
# --hints--
|
2018-10-10 18:03:03 -04:00
|
|
|
|
|
2021-03-22 07:52:28 -06:00
|
|
|
|
提交自己的项目,而不是示例的 URL。
|
2018-10-10 18:03:03 -04:00
|
|
|
|
|
2020-12-16 00:37:30 -07:00
|
|
|
|
```js
|
2021-02-06 04:42:36 +00:00
|
|
|
|
(getUserInput) => {
|
|
|
|
|
assert(
|
|
|
|
|
!/.*\/personal-library\.freecodecamp\.rocks/.test(getUserInput('url'))
|
|
|
|
|
);
|
|
|
|
|
};
|
2018-10-10 18:03:03 -04:00
|
|
|
|
```
|
|
|
|
|
|
2021-03-22 07:52:28 -06:00
|
|
|
|
可以发送 <b>POST</b> 请求到 `/api/books`,带有 `title` 作为表单数据的一部分,来添加一本书。 返回的响应将是一个包含 `title` 和唯一的 `_id` 作为键的对象。 如果 `title` 未包含在请求中,返回的响应应该是字符串 `missing required field title`。
|
2018-10-10 18:03:03 -04:00
|
|
|
|
|
2020-12-16 00:37:30 -07:00
|
|
|
|
```js
|
2021-02-06 04:42:36 +00:00
|
|
|
|
async (getUserInput) => {
|
|
|
|
|
try {
|
|
|
|
|
let data1 = await $.post(getUserInput('url') + '/api/books', {
|
|
|
|
|
title: 'Faux Book 1'
|
|
|
|
|
});
|
|
|
|
|
assert.isObject(data1);
|
|
|
|
|
assert.property(data1, 'title');
|
|
|
|
|
assert.equal(data1.title, 'Faux Book 1');
|
|
|
|
|
assert.property(data1, '_id');
|
|
|
|
|
let data2 = await $.post(getUserInput('url') + '/api/books');
|
|
|
|
|
assert.isString(data2);
|
|
|
|
|
assert.equal(data2, 'missing required field title');
|
|
|
|
|
} catch (err) {
|
|
|
|
|
throw new Error(err.responseText || err.message);
|
|
|
|
|
}
|
|
|
|
|
};
|
2020-12-16 00:37:30 -07:00
|
|
|
|
```
|
2018-10-10 18:03:03 -04:00
|
|
|
|
|
2021-03-22 07:52:28 -06:00
|
|
|
|
可以向 `/api/books` 发送 <b>GET</b> 请求,并返回代表所有书的 JSON 响应。 JSON 响应应该是一个包含有 `title`、`_id` 和 `commentcount` 属性的对象数组 。
|
2018-10-10 18:03:03 -04:00
|
|
|
|
|
|
|
|
|
```js
|
2021-02-06 04:42:36 +00:00
|
|
|
|
async (getUserInput) => {
|
|
|
|
|
try {
|
|
|
|
|
let url = getUserInput('url') + '/api/books';
|
|
|
|
|
let a = $.post(url, { title: 'Faux Book A' });
|
|
|
|
|
let b = $.post(url, { title: 'Faux Book B' });
|
|
|
|
|
let c = $.post(url, { title: 'Faux Book C' });
|
2021-06-21 19:13:43 +05:30
|
|
|
|
await Promise.all([a, b, c]).then(async () => {
|
2021-02-06 04:42:36 +00:00
|
|
|
|
let data = await $.get(url);
|
|
|
|
|
assert.isArray(data);
|
|
|
|
|
assert.isAtLeast(data.length, 3);
|
|
|
|
|
data.forEach((book) => {
|
|
|
|
|
assert.isObject(book);
|
|
|
|
|
assert.property(book, 'title');
|
|
|
|
|
assert.isString(book.title);
|
|
|
|
|
assert.property(book, '_id');
|
|
|
|
|
assert.property(book, 'commentcount');
|
|
|
|
|
assert.isNumber(book.commentcount);
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
} catch (err) {
|
|
|
|
|
throw new Error(err.responseText || err.message);
|
|
|
|
|
}
|
|
|
|
|
};
|
2020-12-16 00:37:30 -07:00
|
|
|
|
```
|
|
|
|
|
|
2021-03-22 07:52:28 -06:00
|
|
|
|
可以发送 <b>GET</b> 请求到 `/api/books/{_id}` 来检索一本书的单个对象,返回属性 `title`、`_id` 和 `comments` 数组 (如果没有评论,则展示空数组)。 如果找不到书, 返回字符串 `no book exists`。
|
2020-12-16 00:37:30 -07:00
|
|
|
|
|
|
|
|
|
```js
|
2021-02-06 04:42:36 +00:00
|
|
|
|
async (getUserInput) => {
|
|
|
|
|
try {
|
|
|
|
|
let url = getUserInput('url') + '/api/books';
|
|
|
|
|
let noBook = await $.get(url + '/5f665eb46e296f6b9b6a504d');
|
|
|
|
|
assert.isString(noBook);
|
|
|
|
|
assert.equal(noBook, 'no book exists');
|
|
|
|
|
let sampleBook = await $.post(url, { title: 'Faux Book Alpha' });
|
|
|
|
|
assert.isObject(sampleBook);
|
|
|
|
|
let bookId = sampleBook._id;
|
|
|
|
|
let bookQuery = await $.get(url + '/' + bookId);
|
|
|
|
|
assert.isObject(bookQuery);
|
|
|
|
|
assert.property(bookQuery, 'title');
|
|
|
|
|
assert.equal(bookQuery.title, 'Faux Book Alpha');
|
|
|
|
|
assert.property(bookQuery, 'comments');
|
|
|
|
|
assert.isArray(bookQuery.comments);
|
|
|
|
|
} catch (err) {
|
|
|
|
|
throw new Error(err.responseText || err.message);
|
|
|
|
|
}
|
|
|
|
|
};
|
2018-10-10 18:03:03 -04:00
|
|
|
|
```
|
2020-08-13 17:24:35 +02:00
|
|
|
|
|
2021-03-22 07:52:28 -06:00
|
|
|
|
可以发送一个 <b>POST</b> 请求,其中包含 `comment` 作为表单正文数据,请求到 `/api/books/{_id}` 以便将评论添加到书中。 返回的响应将是书对象,在先前测试中 <b>GET</b> `/api/books/{_id}` 类似。 如果请求中没有包含 `comment` ,返回字符串 `missing required field comment`。 如果找不到书, 返回字符串 `no book exists`。
|
2020-12-16 00:37:30 -07:00
|
|
|
|
|
|
|
|
|
```js
|
2021-02-06 04:42:36 +00:00
|
|
|
|
async (getUserInput) => {
|
|
|
|
|
try {
|
|
|
|
|
let url = getUserInput('url') + '/api/books';
|
|
|
|
|
let commentTarget = await $.post(url, { title: 'Notable Book' });
|
|
|
|
|
assert.isObject(commentTarget);
|
|
|
|
|
let bookId = commentTarget._id;
|
|
|
|
|
let bookCom1 = await $.post(url + '/' + bookId, {
|
|
|
|
|
comment: 'This book is fab!'
|
|
|
|
|
});
|
|
|
|
|
let bookCom2 = await $.post(url + '/' + bookId, {
|
|
|
|
|
comment: 'I did not care for it'
|
|
|
|
|
});
|
|
|
|
|
assert.isObject(bookCom2);
|
|
|
|
|
assert.property(bookCom2, '_id');
|
|
|
|
|
assert.property(bookCom2, 'title');
|
|
|
|
|
assert.property(bookCom2, 'comments');
|
|
|
|
|
assert.lengthOf(bookCom2.comments, 2);
|
|
|
|
|
bookCom2.comments.forEach((comment) => {
|
|
|
|
|
assert.isString(comment);
|
|
|
|
|
assert.oneOf(comment, ['This book is fab!', 'I did not care for it']);
|
|
|
|
|
});
|
|
|
|
|
let commentErr = await $.post(url + '/' + bookId);
|
|
|
|
|
assert.isString(commentErr);
|
|
|
|
|
assert.equal(commentErr, 'missing required field comment');
|
|
|
|
|
let failingComment = await $.post(url + '/5f665eb46e296f6b9b6a504d', {
|
|
|
|
|
comment: 'Never Seen Comment'
|
|
|
|
|
});
|
|
|
|
|
assert.isString(failingComment);
|
|
|
|
|
assert.equal(failingComment, 'no book exists');
|
|
|
|
|
} catch (err) {
|
|
|
|
|
throw new Error(err.responseText || err.message);
|
|
|
|
|
}
|
|
|
|
|
};
|
2020-12-16 00:37:30 -07:00
|
|
|
|
```
|
|
|
|
|
|
2021-03-22 07:52:28 -06:00
|
|
|
|
可以向 `/api/books/{_id}` 发送 <b>DELETE</b> 请求,从收藏中删除一本书。 如果成功,返回的响应将是字符串 `delete successful`。 如果找不到书, 返回字符串 `no book exists`。
|
2020-12-16 00:37:30 -07:00
|
|
|
|
|
|
|
|
|
```js
|
2021-02-06 04:42:36 +00:00
|
|
|
|
async (getUserInput) => {
|
|
|
|
|
try {
|
|
|
|
|
let url = getUserInput('url') + '/api/books';
|
|
|
|
|
let deleteTarget = await $.post(url, { title: 'Deletable Book' });
|
|
|
|
|
assert.isObject(deleteTarget);
|
|
|
|
|
let bookId = deleteTarget._id;
|
|
|
|
|
let doDelete = await $.ajax({ url: url + '/' + bookId, type: 'DELETE' });
|
|
|
|
|
assert.isString(doDelete);
|
|
|
|
|
assert.equal(doDelete, 'delete successful');
|
|
|
|
|
let failingDelete = await $.ajax({
|
|
|
|
|
url: url + '/5f665eb46e296f6b9b6a504d',
|
|
|
|
|
type: 'DELETE'
|
|
|
|
|
});
|
|
|
|
|
assert.isString(failingDelete);
|
|
|
|
|
assert.equal(failingDelete, 'no book exists');
|
|
|
|
|
} catch (err) {
|
|
|
|
|
throw new Error(err.responseText || err.message);
|
|
|
|
|
}
|
|
|
|
|
};
|
2020-12-16 00:37:30 -07:00
|
|
|
|
```
|
|
|
|
|
|
2021-03-22 07:52:28 -06:00
|
|
|
|
可以向 `/api/books` 发送 <b>DELETE</b> 请求来删除数据库中的所有书籍。 如果成功,返回的响应将是字符串 `'complete delete successful`。
|
2020-12-16 00:37:30 -07:00
|
|
|
|
|
|
|
|
|
```js
|
2021-02-06 04:42:36 +00:00
|
|
|
|
async (getUserInput) => {
|
|
|
|
|
try {
|
|
|
|
|
const deleteAll = await $.ajax({
|
|
|
|
|
url: getUserInput('url') + '/api/books',
|
|
|
|
|
type: 'DELETE'
|
|
|
|
|
});
|
|
|
|
|
assert.isString(deleteAll);
|
|
|
|
|
assert.equal(deleteAll, 'complete delete successful');
|
|
|
|
|
} catch (err) {
|
|
|
|
|
throw new Error(err.responseText || err.message);
|
|
|
|
|
}
|
|
|
|
|
};
|
2020-12-16 00:37:30 -07:00
|
|
|
|
```
|
|
|
|
|
|
2021-03-22 07:52:28 -06:00
|
|
|
|
所有 10 项功能测试都已完成并通过。
|
2020-12-16 00:37:30 -07:00
|
|
|
|
|
|
|
|
|
```js
|
2021-02-06 04:42:36 +00:00
|
|
|
|
async (getUserInput) => {
|
|
|
|
|
try {
|
|
|
|
|
const getTests = await $.get(getUserInput('url') + '/_api/get-tests');
|
|
|
|
|
assert.isArray(getTests);
|
|
|
|
|
assert.isAtLeast(getTests.length, 10, 'At least 10 tests passed');
|
|
|
|
|
getTests.forEach((test) => {
|
|
|
|
|
assert.equal(test.state, 'passed', 'Test in Passed State');
|
|
|
|
|
assert.isAtLeast(
|
|
|
|
|
test.assertions.length,
|
|
|
|
|
1,
|
|
|
|
|
'At least one assertion per test'
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
} catch (err) {
|
|
|
|
|
throw new Error(err.responseText || err.message);
|
|
|
|
|
}
|
|
|
|
|
};
|
2020-12-16 00:37:30 -07:00
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
# --solutions--
|
|
|
|
|
|
2021-01-13 03:31:00 +01:00
|
|
|
|
```js
|
|
|
|
|
/**
|
2021-06-21 19:13:43 +05:30
|
|
|
|
Backend challenges don't need solutions,
|
|
|
|
|
because they would need to be tested against a full working project.
|
2021-01-13 03:31:00 +01:00
|
|
|
|
Please check our contributing guidelines to learn more.
|
|
|
|
|
*/
|
|
|
|
|
```
|