Skip to content
This repository has been archived by the owner on Dec 18, 2024. It is now read-only.

Create example PR for review #50

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions client/src/App.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
Drafts,
Home,
NotFound,
Resource,
Suggest,
} from "./pages";

Expand All @@ -24,6 +25,7 @@ const App = () => (
<Route path="/drafts" element={<Authenticated adminOnly />}>
<Route index element={<Drafts />} />
</Route>
<Route path="/resource/:id" element={<Resource />} />
<Route path="/suggest" element={<Authenticated />}>
<Route index element={<Suggest />} />
</Route>
Expand Down
2 changes: 1 addition & 1 deletion client/src/App.scss
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
main > article {
padding: calc(#{layout.$baseline} * 4);

> h2 {
> h2 :last-child {
border-bottom: 2px solid palette.$red;
display: inline-block;
padding-bottom: calc(#{layout.$baseline} / 2);
Expand Down
5 changes: 4 additions & 1 deletion client/src/components/ResourceList/index.jsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import PropTypes from "prop-types";
import { Link } from "react-router-dom";

import "./ResourceList.scss";

Expand All @@ -13,7 +14,9 @@ export default function ResourceList({ publish, resources }) {
{resources.map(({ description, id, title, topic_name, url }) => (
<li key={id}>
<div>
<h3>{title}</h3>
<h3>
<Link to={`/resource/${id}`}>{title}</Link>
</h3>
{topic_name && <span className="topic">{topic_name}</span>}
</div>
{description && <p className="resource-description">{description}</p>}
Expand Down
4 changes: 3 additions & 1 deletion client/src/pages/About.jsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
const About = () => (
<>
<h2>About</h2>
<h2>
<span>About</span>
</h2>
<p>Demonstration project for CYF Tech Products.</p>
<p>
Check out the code{" "}
Expand Down
4 changes: 3 additions & 1 deletion client/src/pages/Account/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@ export default function Account() {
}
return (
<>
<h2>Account</h2>
<h2>
<span>Account</span>
</h2>
<table>
<tbody>
<tr>
Expand Down
4 changes: 3 additions & 1 deletion client/src/pages/NotFound.jsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
export default function NotFound() {
return (
<>
<h2>Not Found</h2>
<h2>
<span>Not Found</span>
</h2>
<p>Sorry, we couldn&apos;t find what you&apos;re looking for.</p>
</>
);
Expand Down
14 changes: 14 additions & 0 deletions client/src/pages/Resource/Resource.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
@use "../../styles/layout";

table.resource-details {
width: 100%;

tbody th {
text-align: end;

&:after {
content: ":";
margin-right: layout.$baseline;
}
}
}
78 changes: 78 additions & 0 deletions client/src/pages/Resource/index.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import { useEffect, useState } from "react";
import { useParams } from "react-router-dom";

import { ResourceService, useService } from "../../services";

import "./Resource.scss";

export default function Resource() {
const [resource, setResource] = useState(undefined);
const resourceService = useService(ResourceService);
const { id } = useParams();

useEffect(() => {
resourceService.getOne(id).then(setResource);
}, [id, resourceService]);

if (!resource) {
return null;
}

return (
<>
<h2>
Resource &gt; <span>{resource.title}</span>
</h2>
<section>
<table aria-label="resource details" className="resource-details">
<tbody>
<tr>
<th>Title</th>
<td>{resource.title}</td>
</tr>
<tr>
<th>ID</th>
<td>{resource.id}</td>
</tr>
<tr>
<th>URL</th>
<td>
<a href={resource.url}>{formatUrl(resource.url)}</a>
</td>
</tr>
<tr>
<th>Topic</th>
<td>{resource.topic_name ?? <em>N/A</em>}</td>
</tr>
<tr>
<th>Suggested</th>
<td>
<time dateTime={resource.accession.toISOString()}>
{resource.accession.toLocaleString()} by{" "}
{resource.source_name}
</time>
</td>
</tr>
<tr>
<th>Published</th>
<td>
<time dateTime={resource.publication.toISOString()}>
{resource.publication.toLocaleString()} by{" "}
{resource.publisher_name}
</time>
</td>
</tr>
</tbody>
</table>
</section>
</>
);
}

function formatUrl(url) {
const host = new URL(url).host;
if (host.startsWith("www.")) {
return host.slice(4);
}
return host;
}
4 changes: 3 additions & 1 deletion client/src/pages/Suggest/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,9 @@ export default function Suggest() {

return (
<>
<h2>Suggest a resource</h2>
<h2>
<span>Suggest a resource</span>
</h2>
<p>
Please use the form below to submit a suggestion. Note that it will not
appear on the home page immediately, as it needs to be reviewed by an
Expand Down
1 change: 1 addition & 0 deletions client/src/pages/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@ export { default as Authenticated } from "./Authenticated";
export { default as Drafts } from "./Drafts";
export { default as Home } from "./Home";
export { default as NotFound } from "./NotFound";
export { default as Resource } from "./Resource";
export { default as Suggest } from "./Suggest";
7 changes: 7 additions & 0 deletions client/src/services/resourceService.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,13 @@ export default class ResourceService {
return [];
}

async getOne(id) {
const res = await this.fetch(`${ResourceService.ENDPOINT}/${id}`);
if (res.ok) {
return this._revive(await res.json());
}
}

async getPublished({ page, perPage } = {}) {
const res = await this.fetch(
`${ResourceService.ENDPOINT}?${new URLSearchParams(
Expand Down
39 changes: 39 additions & 0 deletions pull_request.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
This is a:

- [x] ✨ **New feature** - new behaviour has been implemented
- [ ] 🐛 **Bug fix** - existing behaviour has been made to behave
- [ ] ♻️ **Refactor** - the behaviour has not changed, just the implementation
- [ ] ✅ **Test backfill** - tests for existing behaviour were added but the behaviour itself hasn't changed
- [ ] ⚙️ **Chore** - maintenance task, behaviour and implementation haven't changed

### Description

- **Purpose** - Adds a resource details page accessible from the resource list:

![Screenshot 2023-08-15 at 11 40 44](https://github.com/CodeYourFuture/tech-products-demo/assets/785939/b51b0cf8-c8b1-4a5a-84b2-825b071ce3ba)

- **How to check** -
- Ensure there are some published resources
- Visit the home page
- Click on a resource title
- See resource details per screenshot above

### Links

- CodeYourFuture/tech-products-demo#18

### Author checklist

<!-- All PRs -->

- [ ] I have written a title that reflects the relevant ticket
- [ ] I have written a description that says what the PR does and how to validate it
- [ ] I have linked to the project board ticket (and any related PRs/issues) in the Links section
- [ ] I have added a link to this PR to the ticket
- [ ] I have made the PR to `main` from a branch named `<category>/<name>`, e.g. `feature/edit-spaceships` or `bugfix/restore-oxygen`
- [ ] I have manually tested that the app still works correctly
- [ ] I have requested reviewers here and in my team chat channel
<!-- depending on the task, the following may be optional -->
- [ ] I have spoken with my PM or TL about any parts of this task that may have become out-of-scope, or any additional improvements that I now realise may benefit my project
- [ ] I have added tests, or new tests were not required
- [ ] I have updated any documentation (e.g. diagrams, schemas), or documentation updates were not required
58 changes: 58 additions & 0 deletions server/resources/resources.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -281,6 +281,64 @@ describe("/api/resources", () => {
});
});

describe("GET /:id", () => {
it("returns details of a specific resource", async () => {
const { agent: anonAgent } = await authenticateAs("anonymous");
const { agent: userAgent, user } = await authenticateAs("user");
const { agent: adminAgent, user: admin } = await authenticateAs("admin");
const { body: topics } = await anonAgent
.get("/api/topics")
.set("User-Agent", "supertest")
.expect(200);

const title = "The Art of Agile Development (2nd Ed.)";
const topic = topics.find(
({ name }) => name === "Professional Development"
);
const url = "https://www.jamesshore.com/v2/books/aoad2";

const { body: created } = await userAgent
.post("/api/resources")
.send({ title, topic: topic.id, url })
.set("User-Agent", "supertest")
.expect(201);
await adminAgent
.patch(`/api/resources/${created.id}`)
.send({ draft: false })
.set("User-Agent", "supertest")
.expect(200);

const { body: resource } = await anonAgent
.get(`/api/resources/${created.id}`)
.set("User-Agent", "supertest")
.expect(200);
expect(resource).toEqual({
accession: expect.stringMatching(patterns.DATETIME),
description: null,
draft: false,
id: expect.stringMatching(patterns.UUID),
publication: expect.stringMatching(patterns.DATETIME),
publisher: admin.id,
publisher_name: admin.name,
source: user.id,
source_name: user.name,
title,
topic: topic.id,
topic_name: topic.name,
url,
});
});

it("returns 404 for missing resource", async () => {
const { agent } = await authenticateAs("anonymous");

await agent
.get(`/api/resources/${randomUUID()}`)
.set("User-Agent", "supertest")
.expect(404);
});
});

describe("PATCH /:id", () => {
it("allows superusers to publish a draft resource", async () => {
const { agent: anonAgent } = await authenticateAs("anonymous");
Expand Down
22 changes: 22 additions & 0 deletions server/resources/resourcesController.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Router } from "express";
import { Joi } from "express-validation";

import db, { singleLine } from "../db";
import { topicsService } from "../topics";
import logger from "../utils/logger";
import {
Expand Down Expand Up @@ -63,6 +64,27 @@ router

router
.route("/:id")
.get(
asyncHandler(async (req, res) => {
const {
rows: [resource],
} = await db.query(
singleLine`
SELECT r.*, t.name AS topic_name, us.name AS source_name, up.name AS publisher_name
FROM resources AS r
INNER JOIN users AS us ON r.source = us.id
LEFT JOIN users AS up ON r.publisher = up.id
LEFT JOIN topics AS t ON r.topic = t.id
WHERE r.id = $1;
`,
[req.params.id]
);
if (!resource) {
return res.sendStatus(404);
}
res.json(resource);
})
)
.patch(
sudoOnly,
validated({
Expand Down
Loading