From 3459bf26f82d03f8bc48abe85313a1cb6b08b30a Mon Sep 17 00:00:00 2001 From: cleverbeagle Date: Thu, 25 May 2017 16:31:01 -0500 Subject: [PATCH] wip wiring up account forms --- .meteor/packages | 2 + .meteor/versions | 1 + imports/api/Documents/Documents.js | 2 +- imports/api/Documents/methods.js | 52 ++++++++++ imports/api/Documents/server/publications.js | 17 ++++ imports/modules/rate-limit.js | 18 ++++ imports/startup/server/api.js | 2 + imports/startup/server/index.js | 1 + .../DocumentEditor/DocumentEditor.js | 97 +++++++++++++++++++ .../OAuthLoginButton/OAuthLoginButton.js | 2 +- imports/ui/pages/Documents/Documents.js | 80 +++++++++++++++ imports/ui/pages/Documents/Documents.scss | 3 + imports/ui/pages/EditDocument/EditDocument.js | 29 ++++++ imports/ui/pages/Login/Login.js | 7 +- imports/ui/pages/Login/Login.scss | 20 ---- imports/ui/pages/NewDocument/NewDocument.js | 16 +++ imports/ui/pages/NotFound/NotFound.js | 12 +++ imports/ui/pages/Signup/Signup.js | 2 +- imports/ui/pages/Signup/Signup.scss | 21 ---- imports/ui/pages/ViewDocument/ViewDocument.js | 54 +++++++++++ .../ui/stylesheets/_bootstrap-overrides.scss | 11 +++ imports/ui/stylesheets/_forms.scss | 5 + imports/ui/stylesheets/app.scss | 1 + package.json | 5 +- server/main.js | 6 +- 25 files changed, 413 insertions(+), 53 deletions(-) create mode 100644 imports/api/Documents/methods.js create mode 100644 imports/api/Documents/server/publications.js create mode 100644 imports/modules/rate-limit.js create mode 100644 imports/startup/server/api.js create mode 100644 imports/ui/components/DocumentEditor/DocumentEditor.js create mode 100644 imports/ui/pages/Documents/Documents.scss create mode 100644 imports/ui/stylesheets/_bootstrap-overrides.scss diff --git a/.meteor/packages b/.meteor/packages index 388cfde..2921f6c 100644 --- a/.meteor/packages +++ b/.meteor/packages @@ -30,3 +30,5 @@ accounts-google themeteorchef:bert fortawesome:fontawesome aldeed:collection2-core +audit-argument-checks +ddp-rate-limiter diff --git a/.meteor/versions b/.meteor/versions index e2110b2..e690fd9 100644 --- a/.meteor/versions +++ b/.meteor/versions @@ -8,6 +8,7 @@ alanning:roles@1.2.16 aldeed:collection2-core@1.2.0 aldeed:simple-schema@1.5.3 allow-deny@1.0.5 +audit-argument-checks@1.0.7 autoupdate@1.3.12 babel-compiler@6.18.2 babel-runtime@1.0.1 diff --git a/imports/api/Documents/Documents.js b/imports/api/Documents/Documents.js index 4974aab..f428830 100644 --- a/imports/api/Documents/Documents.js +++ b/imports/api/Documents/Documents.js @@ -46,6 +46,6 @@ Documents.schema = new SimpleSchema({ }, }); -Documents.attachSchema(Documents.schema); +// Documents.attachSchema(Documents.schema); export default Documents; diff --git a/imports/api/Documents/methods.js b/imports/api/Documents/methods.js new file mode 100644 index 0000000..6a2140c --- /dev/null +++ b/imports/api/Documents/methods.js @@ -0,0 +1,52 @@ +import { Meteor } from 'meteor/meteor'; +import { check } from 'meteor/check'; +import Documents from './Documents'; +import rateLimit from '../../modules/rate-limit'; + +Meteor.methods({ + 'documents.insert': function documentsInsert(doc) { + check(doc, { + title: String, + body: String, + }); + + try { + return Documents.insert({ owner: this.userId, ...doc }); + } catch (exception) { + throw new Meteor.Error('500', exception); + } + }, + 'documents.update': function documentsUpdate(doc) { + check(doc, { + _id: String, + title: String, + body: String, + }); + + try { + Documents.update(doc._id, { $set: doc }); + return doc._id; // Return _id so we can redirect to document after update. + } catch (exception) { + throw new Meteor.Error('500', exception); + } + }, + 'documents.remove': function documentsRemove(documentId) { + check(documentId, String); + + try { + return Documents.remove(documentId); + } catch (exception) { + throw new Meteor.Error('500', exception); + } + }, +}); + +rateLimit({ + methods: [ + 'documents.insert', + 'documents.update', + 'documents.remove', + ], + limit: 5, + timeRange: 1000, +}); diff --git a/imports/api/Documents/server/publications.js b/imports/api/Documents/server/publications.js new file mode 100644 index 0000000..721a522 --- /dev/null +++ b/imports/api/Documents/server/publications.js @@ -0,0 +1,17 @@ +import { Meteor } from 'meteor/meteor'; +import { check } from 'meteor/check'; +import Documents from '../Documents'; + +Meteor.publish('documents', function documentsView() { + return Documents.find({ owner: this.userId }); +}); + +// Note: documents.view is also used when editing an existing document. +Meteor.publish('documents.view', function documentsView(documentId) { + check(documentId, String); + + const doc = Documents.find(documentId); + const isOwner = doc.fetch()[0].owner === this.userId; + + return isOwner ? doc : this.ready(); +}); diff --git a/imports/modules/rate-limit.js b/imports/modules/rate-limit.js new file mode 100644 index 0000000..04d4b1c --- /dev/null +++ b/imports/modules/rate-limit.js @@ -0,0 +1,18 @@ +import { Meteor } from 'meteor/meteor'; +import { DDPRateLimiter } from 'meteor/ddp-rate-limiter'; +import { _ } from 'meteor/underscore'; + +const fetchMethodNames = methods => _.pluck(methods, 'name'); + +const assignLimits = ({ methods, limit, timeRange }) => { + const methodNames = fetchMethodNames(methods); + + if (Meteor.isServer) { + DDPRateLimiter.addRule({ + name(name) { return _.contains(methodNames, name); }, + connectionId() { return true; }, + }, limit, timeRange); + } +}; + +export default function rateLimit(options) { return assignLimits(options); } diff --git a/imports/startup/server/api.js b/imports/startup/server/api.js new file mode 100644 index 0000000..2311511 --- /dev/null +++ b/imports/startup/server/api.js @@ -0,0 +1,2 @@ +import '../../api/Documents/methods'; +import '../../api/Documents/server/publications'; diff --git a/imports/startup/server/index.js b/imports/startup/server/index.js index e69de29..acb0b20 100644 --- a/imports/startup/server/index.js +++ b/imports/startup/server/index.js @@ -0,0 +1 @@ +import './api'; diff --git a/imports/ui/components/DocumentEditor/DocumentEditor.js b/imports/ui/components/DocumentEditor/DocumentEditor.js new file mode 100644 index 0000000..ff66cbd --- /dev/null +++ b/imports/ui/components/DocumentEditor/DocumentEditor.js @@ -0,0 +1,97 @@ +/* eslint-disable max-len, no-return-assign */ + +import React from 'react'; +import PropTypes from 'prop-types'; +import { FormGroup, ControlLabel, Button } from 'react-bootstrap'; +import { Meteor } from 'meteor/meteor'; +import { Bert } from 'meteor/themeteorchef:bert'; +import validate from '../../../modules/validate'; + +class DocumentEditor extends React.Component { + componentDidMount() { + const component = this; + validate(component.form, { + rules: { + title: { + required: true, + }, + body: { + required: true, + }, + }, + messages: { + title: { + required: 'Need a title in here, Seuss.', + }, + body: { + required: 'This thneeds a body, please.', + }, + }, + submitHandler() { component.handleSubmit(); }, + }); + } + + handleSubmit() { + const { history } = this.props; + const existingDocument = this.props.doc && this.props.doc._id; + const methodToCall = existingDocument ? 'documents.update' : 'documents.insert'; + const doc = { + title: this.title.value.trim(), + body: this.body.value.trim(), + }; + + if (existingDocument) doc._id = existingDocument; + + Meteor.call(methodToCall, doc, (error, documentId) => { + if (error) { + Bert.alert(error.reason, 'danger'); + } else { + const confirmation = existingDocument ? 'Document updated!' : 'Document added!'; + this.form.reset(); + Bert.alert(confirmation, 'success'); + history.push(`/documents/${documentId}`); + } + }); + } + + render() { + const { doc } = this.props; + return (
(this.form = form)} onSubmit={event => event.preventDefault()}> + + Title + (this.title = title)} + defaultValue={doc && doc.title} + placeholder="Oh, The Places You'll Go!" + /> + + + Body +