Skip to content

Commit 28a0d8a

Browse files
author
Tammo Pape
committed
initial commit
0 parents  commit 28a0d8a

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

48 files changed

+10336
-0
lines changed

.babelrc

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"plugins": [
3+
"transform-class-properties",
4+
"transform-object-rest-spread",
5+
"jsx-display-if"
6+
],
7+
"presets": [
8+
"env",
9+
"next/babel"
10+
]
11+
}

.editorconfig

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
# top-most EditorConfig file
2+
root = true
3+
4+
# Unix-style newlines with a newline ending every file
5+
[*]
6+
end_of_line = lf
7+
insert_final_newline = true
8+
trim_trailing_whitespace = true
9+
indent_style = space
10+
indent_size = 2

.env.example

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
PORT=3000
2+
NODE_ENV=development
3+
MONGODB_URI=mongodb://localhost/openform-app

.gitignore

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
.DS_STORE
2+
node_modules
3+
build
4+
.idea
5+
.next
6+
.vscode
7+
yarn-error.log
8+
static/react-table.css
9+
static/overlay.min.js
10+
.env

.prettierrc

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"semi": false,
3+
"tabWidth": 2,
4+
"singleQuote": true,
5+
"trailingComma": "all",
6+
"jsxBracketSameLine": true,
7+
"printWidth": 140
8+
}

api/auth.js

+45
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import err from 'http-errors'
2+
3+
import User from './userSchema'
4+
5+
function findAndSetUser(req, res, next) {
6+
if (!req.auth || !req.auth.token) {
7+
return next(err(401))
8+
}
9+
10+
User.findOne()
11+
.elemMatch('tokens', { val: req.auth.token })
12+
.then(user => {
13+
if (!user) {
14+
return next(err(401))
15+
}
16+
17+
req.user = user
18+
19+
next()
20+
})
21+
.catch(next)
22+
}
23+
24+
function extractBearer(string) {
25+
if (string && string.split(' ')[0] === 'Bearer') {
26+
return string.split(' ')[1]
27+
}
28+
}
29+
30+
function getToken(req) {
31+
const headerBearer = extractBearer(req.headers.authorization)
32+
const cookieBearer = req.cookies.authorization
33+
34+
return headerBearer || cookieBearer
35+
}
36+
37+
export default function authenticate(req, res, next) {
38+
const token = getToken(req)
39+
40+
req.auth = {
41+
token,
42+
}
43+
44+
findAndSetUser(req, res, next)
45+
}

api/connect.js

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import mongoose from 'mongoose'
2+
3+
const CONN_URI = process.env.MONGODB_URI
4+
5+
if (!CONN_URI) {
6+
console.log('no MONGODB_URI set')
7+
} else {
8+
console.log('MONGODB_URI set')
9+
}
10+
11+
export function connectSync() {
12+
return mongoose.connect(CONN_URI, {
13+
reconnectTries: 5,
14+
})
15+
}
16+
17+
export function isEnvSetup() {
18+
return !!CONN_URI
19+
}

api/feedbackSchema.js

+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import mongoose, { Schema } from 'mongoose'
2+
3+
const FeedbackSchema = new Schema(
4+
{
5+
rating: {
6+
type: Number,
7+
required: true,
8+
},
9+
10+
comment: {
11+
type: String,
12+
},
13+
14+
href: {
15+
type: String,
16+
},
17+
},
18+
{
19+
timestamps: true,
20+
},
21+
)
22+
23+
const Feedback = mongoose.model('Feedback', FeedbackSchema)
24+
25+
export default Feedback

api/index.js

+57
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import express from 'express'
2+
import passport from 'passport'
3+
import { Strategy as LocalStrategy } from 'passport-local'
4+
5+
import User from './userSchema'
6+
import Feedback from './feedbackSchema'
7+
import { login, initialSetup } from './userMiddleware'
8+
import authenticate from './auth'
9+
10+
const router = new express.Router()
11+
12+
router.use(passport.initialize())
13+
passport.use(new LocalStrategy(User.authenticate()))
14+
15+
router.get('/', (req, res, next) => {
16+
req.result = {
17+
ok: true,
18+
}
19+
20+
next()
21+
})
22+
23+
router.get('/feedback', authenticate, (req, res, next) => {
24+
Feedback.find()
25+
.sort({ createdAt: -1 })
26+
.then(docs => {
27+
req.result = docs
28+
29+
next()
30+
})
31+
.catch(next)
32+
})
33+
34+
router.post('/feedback', (req, res, next) => {
35+
Feedback.create(req.body)
36+
.then(doc => {
37+
if (doc) {
38+
req.result = doc
39+
}
40+
41+
next()
42+
})
43+
.catch(next)
44+
})
45+
46+
router.post('/initialSetup', initialSetup)
47+
router.post('/login', passport.authenticate('local', { session: false }), login)
48+
49+
router.use((req, res, next) => {
50+
if (req.result) {
51+
return res.json(req.result)
52+
}
53+
54+
next()
55+
})
56+
57+
export default router

api/userMiddleware.js

+70
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
import err from 'http-errors'
2+
import uuidv4 from 'uuid/v4'
3+
4+
import User from './userSchema'
5+
6+
const oneYear = 60 * 60 * 24 * 365
7+
8+
export function login(req, res, next) {
9+
if (!req.user) {
10+
return next(err(500))
11+
}
12+
13+
const token = uuidv4()
14+
15+
req.user.tokens.push({
16+
val: token,
17+
})
18+
19+
req.user
20+
.save()
21+
.then(savedUser => {
22+
res.cookie('authorization', token, {
23+
httpOnly: true,
24+
sameSite: true,
25+
expires: new Date(Date.now() + 1000 * oneYear),
26+
secure: process.env.UNSECURE_COOKIES ? false : process.env.NODE_ENV === 'production',
27+
})
28+
29+
req.result = {
30+
ok: true,
31+
}
32+
33+
next()
34+
})
35+
.catch(next)
36+
}
37+
38+
export function noUserNoAccess(req, res, next) {
39+
if (!req.user) {
40+
return next(err(403))
41+
}
42+
43+
next()
44+
}
45+
46+
export function initialSetup(req, res, next) {
47+
User.findOne()
48+
.then(async user => {
49+
if (user) {
50+
return next(err(403))
51+
}
52+
53+
const newUser = new User({ username: 'user' })
54+
await newUser.setPassword(req.body.password)
55+
await newUser.save()
56+
57+
req.result = {
58+
ok: true,
59+
}
60+
61+
next()
62+
})
63+
.catch(next)
64+
}
65+
66+
export async function isUserSetup() {
67+
const user = await User.findOne()
68+
69+
return !!user
70+
}

api/userSchema.js

+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import mongoose, { Schema } from 'mongoose'
2+
import passportLocalMongoose from 'passport-local-mongoose'
3+
4+
const TokenSchema = new Schema(
5+
{
6+
val: {
7+
type: String,
8+
required: true,
9+
},
10+
11+
invalidAt: {
12+
type: Date,
13+
},
14+
},
15+
{
16+
timestamps: true,
17+
},
18+
)
19+
20+
const UserSchema = new Schema(
21+
{
22+
tokens: [TokenSchema],
23+
},
24+
{
25+
timestamps: true,
26+
},
27+
)
28+
29+
UserSchema.plugin(passportLocalMongoose)
30+
31+
const User = mongoose.model('User', UserSchema)
32+
33+
export default User

app.json

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"name": "open form.",
3+
"description": "A feedback gathering tool",
4+
"repository": "https://github.com/tammo/openform",
5+
"keywords": ["node", "express", "feedback", "react"]
6+
}

components/FeedbackDetails.js

+41
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import * as React from 'react'
2+
import Label from './Label'
3+
4+
export class FeedbackDetails extends React.Component {
5+
render() {
6+
return (
7+
<div className="box">
8+
<span className="value">
9+
<Label>created at:</Label>
10+
{this.props.createdAt}
11+
</span>
12+
<span className="value">
13+
<Label>href:</Label>
14+
<a target="_blank" href={this.props.href}>
15+
{this.props.href}
16+
</a>
17+
</span>
18+
19+
<Label className="d-block label">feedback:</Label>
20+
<p className="text">{this.props.comment}</p>
21+
22+
<style jsx="">{`
23+
.box {
24+
background: white;
25+
padding: 0.5em 1em;
26+
}
27+
.value {
28+
font-size: small;
29+
padding: 0 2em 0 0;
30+
}
31+
.text {
32+
padding: 0;
33+
margin: 0;
34+
}
35+
`}</style>
36+
</div>
37+
)
38+
}
39+
}
40+
41+
export default FeedbackDetails

0 commit comments

Comments
 (0)