Skip to content

Commit de5793e

Browse files
committed
Can now submit new news items. URL gets validated but validation messages are not implemented in UI fully.
1 parent 7b1a9ee commit de5793e

File tree

11 files changed

+197
-151
lines changed

11 files changed

+197
-151
lines changed

Diff for: src/components/presentational/NewsFeed.js

-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import NewsTitle from '../container/NewsTitleWithData';
66
import NewsDetail from '../container/NewsDetailWithData';
77

88
const NewsFeed = (props) => {
9-
// props.newsItems.sort((a, b) => (a.rank - b.rank));
109
const nextPage = Math.ceil((props.skip || 1) / props.first) + 1;
1110

1211
const rows = [];

Diff for: src/data/Database.js

+8-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
import debug from 'debug';
22

3+
import {
4+
Feed,
5+
} from './Models';
36
import data from './SampleData';
47
import cache from './Cache';
58

@@ -78,7 +81,11 @@ export function hideNewsItem(id, userId) {
7881

7982
export function submitNewsItem(id, newsItem) {
8083
// Submit the News Item in the DB
81-
if (cache.setNewsItem(id, newsItem)) return newsItem;
84+
if (cache.setNewsItem(id, newsItem)) {
85+
Feed.new.unshift(id);
86+
Feed.new.pop();
87+
return newsItem;
88+
}
8289
throw new Error('Unable to submit News Item.');
8390
}
8491

Diff for: src/data/SampleData.js

+3-3
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ export default {
131131
title: 'Clinton wins again',
132132
text: null,
133133
url: 'https://www.shavelikeaboss.com.au',
134-
upvotes: new Set(['clintonwoo', 'john']),
134+
upvotes: ['clintonwoo', 'john'],
135135
hides: [],
136136
hidden: false,
137137
hiddenCount: 0,
@@ -659,8 +659,8 @@ export default {
659659
creationTime: 1504740146516,
660660
commenterId: 'clintonwoo',
661661
text: 'I know this might come accross as bragging, but I just won the internet again.',
662-
upvotes: new Set([1, 2, 3, 4]),
663-
upvoteCount: 4,
662+
upvotes: ['clintonwoo', 'john'],
663+
upvoteCount: 2,
664664
hides: [],
665665
hidden: false,
666666
hiddenCount: 0,

Diff for: src/data/Schema.js

+2-10
Original file line numberDiff line numberDiff line change
@@ -168,12 +168,9 @@ const typeDefs = `
168168
169169
submitNewsItem (
170170
title: String!
171+
url: String
172+
text: String
171173
): NewsItem
172-
173-
registerUser (
174-
id: String!
175-
password: String!
176-
): User
177174
}
178175
179176
`;
@@ -238,11 +235,6 @@ export const resolvers = {
238235
if (!context.userId) throw new Error('Must be logged in to submit a news item.');
239236
return context.NewsItem.submitNewsItem({ ...newsItem, submitterId: context.userId });
240237
},
241-
242-
registerUser: (_, user, context) => {
243-
if (context.userId) throw new Error('Logged in user cannot register a user');
244-
return context.User.registerUser(user);
245-
},
246238
},
247239

248240
/* GRAPHQL TYPE RESOLVERS */

Diff for: src/data/models/Feed.js

+3-2
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,10 @@ class Feed {
1717
return Promise.all(
1818
this.top.slice(skip, first + skip)
1919
.map(id => cache.getNewsItem(id) || HNDB.fetchNewsItem(id)));
20-
// return this.topNewsItems.slice(skip, first + skip);
2120
case 'NEW':
22-
return this.newNewsItems.slice(skip, first + skip);
21+
return Promise.all(
22+
this.new.slice(skip, first + skip)
23+
.map(id => cache.getNewsItem(id) || HNDB.fetchNewsItem(id)));
2324
case 'SHOW':
2425
return this.showNewsItems.slice(skip, first + skip);
2526
case 'ASK':

Diff for: src/data/models/NewsItem.js

+7-5
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,18 @@ import debug from 'debug';
33
import cache from '../Cache';
44
import * as HNDB from '../HNDataAPI';
55
import * as DB from '../Database';
6+
import isValidUrl from '../../helpers/isValidUrl';
67

78
const logger = debug('app:NewsItem');
89

910
let newPostIdCounter = 100;
1011

1112
export default class NewsItem {
1213
constructor(props) {
13-
if (!props.id) throw new Error('Instantiating News Item failed, id is required:', props.id);
14-
if (!props.submitterId) throw new Error('Instantiating News Item failed, submitterId is required:', props.id);
15-
if (!props.title) throw new Error('Instantiating News Item failed, title is required:', props.id);
14+
if (!props.id) throw new Error('Error instantiating News Item, id is required:', props.id);
15+
if (!props.submitterId) throw new Error('Error instantiating News Item, submitterId is required:', props.id);
16+
if (!props.title) throw new Error('Error instantiating News Item, title is required:', props.id);
17+
if (props.url && !isValidUrl(props.url)) throw new Error(`Error instantiating News Item ${props.id}, invalid URL: ${props.url}`);
1618

1719
this.id = props.id || (newPostIdCounter += 1);
1820
this.creationTime = props.creationTime || +new Date();
@@ -22,8 +24,8 @@ export default class NewsItem {
2224
this.submitterId = props.submitterId;
2325
this.text = props.text || null;
2426
this.title = props.title;
25-
this.upvotes = props.upvotes || [];
26-
this.upvoteCount = props.upvoteCount || 0;
27+
this.upvotes = props.upvotes || [props.submitterId];
28+
this.upvoteCount = props.upvoteCount || 1;
2729
this.url = props.url;
2830
}
2931

Diff for: src/data/mutations/submitNewsItem.js

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { gql } from 'react-apollo';
2+
import NewsFeed from '../../components/presentational/NewsFeed';
3+
4+
export default gql`
5+
mutation SubmitNewsItem($title: String!, $url: String) {
6+
submitNewsItem(title: $title, url: $url) {
7+
id
8+
...NewsFeed
9+
}
10+
}
11+
${NewsFeed.fragments.newsItem}
12+
`;

Diff for: src/helpers/isValidUrl.js

+40
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
2+
export default (str) => {
3+
const pattern = new RegExp(
4+
'^' +
5+
// protocol identifier
6+
'(?:(?:https?|ftp)://)' +
7+
// user:pass authentication
8+
'(?:\\S+(?::\\S*)?@)?' +
9+
'(?:' +
10+
// IP address exclusion
11+
// private & local networks
12+
'(?!(?:10|127)(?:\\.\\d{1,3}){3})' +
13+
'(?!(?:169\\.254|192\\.168)(?:\\.\\d{1,3}){2})' +
14+
'(?!172\\.(?:1[6-9]|2\\d|3[0-1])(?:\\.\\d{1,3}){2})' +
15+
// IP address dotted notation octets
16+
// excludes loopback network 0.0.0.0
17+
// excludes reserved space >= 224.0.0.0
18+
// excludes network & broacast addresses
19+
// (first & last IP address of each class)
20+
'(?:[1-9]\\d?|1\\d\\d|2[01]\\d|22[0-3])' +
21+
'(?:\\.(?:1?\\d{1,2}|2[0-4]\\d|25[0-5])){2}' +
22+
'(?:\\.(?:[1-9]\\d?|1\\d\\d|2[0-4]\\d|25[0-4]))' +
23+
'|' +
24+
// host name
25+
'(?:(?:[a-z\\u00a1-\\uffff0-9]-*)*[a-z\\u00a1-\\uffff0-9]+)' +
26+
// domain name
27+
'(?:\\.(?:[a-z\\u00a1-\\uffff0-9]-*)*[a-z\\u00a1-\\uffff0-9]+)*' +
28+
// TLD identifier
29+
'(?:\\.(?:[a-z\\u00a1-\\uffff]{2,}))' +
30+
// TLD may end with dot
31+
'\\.?' +
32+
')' +
33+
// port number
34+
'(?::\\d{2,5})?' +
35+
// resource path
36+
'(?:[/?#]\\S*)?' +
37+
'$', 'i',
38+
);
39+
return pattern.test(str);
40+
};

Diff for: src/pages/login.js

+20-44
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,38 @@
11
import React from 'react';
22
import Link from 'next/link';
33
import Router from 'next/router';
4-
import { gql, graphql } from 'react-apollo';
54
import PropTypes from 'prop-types';
65

76
import Blank from '../layouts/Blank';
87
import withData from '../helpers/withData';
98

109

11-
const Page = ({ registerUser, url }) => {
12-
// const onClick = () => {
13-
// props.mutate({
14-
// variables: { id: id, password: password }
15-
// })
16-
// .then(data)
17-
// .catch(error)
18-
// }
19-
// const registerUser = () => {
20-
// registerUser(user, pass)
21-
// }
22-
10+
const Page = ({ url }) => {
2311
let message;
2412
switch (url && url.query.how) {
2513
case 'up':
2614
message = 'You have to be logged in to vote.';
2715
break;
16+
case 'pw':
17+
message = 'Incorrect password.';
18+
break;
19+
case 'id':
20+
message = 'Username is taken.';
21+
break;
22+
case 'user':
23+
message = 'Logged in user cannot register a new user.';
24+
break;
2825
default:
2926
message = undefined;
27+
break;
3028
}
3129

3230
let user = '';
33-
let pass = '';
3431
const onUserChange = (e) => { user = e.target.value; };
35-
const onPasswordChange = (e) => { pass = e.target.value; };
32+
3633
return (
3734
<Blank>
38-
{message && <div>{message}</div>}
35+
{message && <p>{message}</p>}
3936
<b>Login</b>
4037
<br />
4138
<br />
@@ -70,7 +67,7 @@ const Page = ({ registerUser, url }) => {
7067
<br />
7168
<form method="post" action="/login" /* onSubmit={e => e.preventDefault()} */ style={{ marginBottom: '1em' }}>
7269
<input type="hidden" name="goto" value={`user?id=${user}`} />
73-
<input type="hidden" name="creating" value={true} />
70+
<input type="hidden" name="creating" value />
7471
<table style={{ border: '0px' }} >
7572
<tbody>
7673
<tr>
@@ -82,44 +79,23 @@ const Page = ({ registerUser, url }) => {
8279
<tr>
8380
<td>password:</td>
8481
<td>
85-
<input type="password" name="password" onChange={onPasswordChange} size="20" />
82+
<input type="password" name="password" size="20" />
8683
</td>
8784
</tr>
8885
</tbody>
8986
</table>
9087
<br />
91-
<input type="submit" value="create account" /* onClick={() => registerUser(user, pass)} */ />
88+
<input type="submit" value="create account" />
9289
</form>
9390
</Blank>
9491
);
9592
};
9693
Page.propTypes = {
97-
registerUser: PropTypes.func.isRequired,
94+
url: PropTypes.shape({
95+
query: PropTypes.shape(),
96+
}).isRequired,
9897
};
9998

100-
const registerUser = gql`
101-
mutation RegisterUser($id: String!, $password: String!) {
102-
registerUser(id: $id, password: $password) {
103-
id
104-
karma
105-
}
106-
}
107-
`;
108-
109-
const PageWithData = graphql(registerUser, {
110-
props: ({ ownProps, mutate, url }) => ({
111-
url,
112-
registerUser: (id, password) => {
113-
return mutate({
114-
variables: { id, password },
115-
})
116-
// .then(() => Router.push(`/login?id=${id}&password=${password}`))
117-
.catch(reason => console.error(reason));
118-
},
119-
120-
}),
121-
})(Page);
122-
12399
export default withData(props => (
124-
<PageWithData url={props.url} />
100+
<Page url={props.url} />
125101
));

0 commit comments

Comments
 (0)