Skip to content

Pull request for homework 7 #10

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 23 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
9d73893
Adjusted storybook
florianthom Jan 14, 2021
3d9211c
WIP user-login
florianthom Jan 14, 2021
ec2d3e0
Extended JWT payload
christophstach Jan 19, 2021
cd8ee21
Vuex store is now typed and persisted
christophstach Jan 19, 2021
d639fd4
WIP
christophstach Jan 20, 2021
0075061
WIP
christophstach Jan 20, 2021
609f5bf
Changed name
christophstach Jan 20, 2021
c48a4a4
Make table responsive.
christophstach Jan 20, 2021
136ed8a
Minor changes
florianthom Jan 20, 2021
f1fcbf3
Minor changes
florianthom Jan 20, 2021
20ca926
Started fixing the tests
s0557917 Jan 21, 2021
6e6c803
Smaller tests on test functions to determine errors
s0557917 Jan 21, 2021
bf8382d
Fixed typescript support for tests and added store support.
christophstach Jan 22, 2021
ef2bcd7
Fixed lint errors in backend.
christophstach Jan 22, 2021
f032d61
Removed local vue instance from test and adjusted test name according…
florianthom Jan 26, 2021
57bc982
Continued work on tests
florianthom Jan 26, 2021
6cea8f9
Added mock functions for apolloHelpers and apolloClient
s0557917 Jan 26, 2021
258b88f
Finished LoginForm tests
s0557917 Jan 26, 2021
8eb467a
Reworked README to add production installation steps
s0557917 Jan 26, 2021
846365e
Added dev command to the webapp README
s0557917 Jan 26, 2021
9800388
Fixed small error when throwing an exception, according to the linter
s0557917 Jan 26, 2021
87e7dff
Removed bearer from nuxt config
s0557917 Jan 26, 2021
7ee4b2b
Add loading indicator and cleaned unit tests
christophstach Jan 27, 2021
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
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
.nuxt-storybook
storybook-static

.DS_Store
node_modules/
/dist/
Expand All @@ -23,4 +26,4 @@ yarn-error.log*
git-crypt-key


backend/.git
backend/.git
4 changes: 4 additions & 0 deletions backend/src/datasources/persons.datasource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ export default class PersonsDatasource extends DataSource {
query($email: String!) {
person(where: { email: $email }) {
id
name
email
passwordHash
passwordSalt
}
Expand Down Expand Up @@ -46,6 +48,8 @@ export default class PersonsDatasource extends DataSource {
}
) {
id
name
email
}
}
`;
Expand Down
2 changes: 2 additions & 0 deletions backend/src/jwt-payload.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
export interface JwtPayload {
id: string;
name: string;
email: string;
iat?: number;
}
12 changes: 10 additions & 2 deletions backend/src/resolvers/persons.resolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,11 @@ export function mutation(subSchemas: GraphQLSchema[], executor: Executor) {
);
} else {
const person: Partial<Person> = result.data.person;
const payload: JwtPayload = { id: person.id };
const payload: JwtPayload = {
id: person.id,
name: person.name,
email: person.email,
};

return jwt.sign(payload, process.env.JWT_SECRET, {
expiresIn: process.env.JWT_EXPIRES_IN,
Expand Down Expand Up @@ -76,7 +80,11 @@ export function mutation(subSchemas: GraphQLSchema[], executor: Executor) {
const hash = await bcrypt.hash(password, person.passwordSalt);

if (hash === result.data.person.passwordHash) {
const payload: JwtPayload = { id: person.id };
const payload: JwtPayload = {
id: person.id,
name: person.name,
email: person.email,
};

return jwt.sign(payload, process.env.JWT_SECRET, {
expiresIn: process.env.JWT_EXPIRES_IN,
Expand Down
11 changes: 0 additions & 11 deletions webapp/.storybook/main.js

This file was deleted.

3 changes: 0 additions & 3 deletions webapp/.storybook/preview-head.html

This file was deleted.

4 changes: 0 additions & 4 deletions webapp/.storybook/preview.js

This file was deleted.

29 changes: 19 additions & 10 deletions webapp/README.md
Original file line number Diff line number Diff line change
@@ -1,20 +1,29 @@
# nuxt-webapp
# TheCodeNinjas Webapp

## Build Setup
## Installation Steps

Follow these steps to build the webapp for production

1. Clone this project

2. Go to the "webapp" folder (Where this README is located)

3. Run these commands

```bash
# install dependencies

# Install all necessary dependencies
$ npm install

# serve with hot reload at localhost:3000
$ npm run dev
# Generate the static project
$ npm run generate

# build for production and launch server
# Build the app for production
$ npm run build

# Start the production server
$ npm run start

# generate static project
$ npm run generate
#(Optional) Run the project in a development environment with hot reload
$ npm run dev
```

For detailed explanation on how things work, check out [Nuxt.js docs](https://nuxtjs.org).
51 changes: 51 additions & 0 deletions webapp/components/LoginForm/LoginForm.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import LoginForm from '~/components/LoginForm/LoginForm.vue'
import { setupWrapperAndStore } from '~/utils/tests'

jest.mock('~/utils/globals')

describe('Login form tests', () => {
it('should set up test utilities properly', () => {
const { wrapper, storeAccessor, localVue } = setupWrapperAndStore(LoginForm)

expect(wrapper).toBeTruthy()
expect(storeAccessor).toBeTruthy()
expect(localVue).toBeTruthy()
})

describe('Form submit', () => {
const { wrapper, storeAccessor, localVue } = setupWrapperAndStore(LoginForm)
const inputEmail = wrapper.find('input#input-email')
const inputPassword = wrapper.find('input#input-password')
const form = wrapper.find('form')

it('should shows no error', async () => {
inputEmail.setValue('[email protected]')
inputPassword.setValue('rightPassword')

await form.trigger('submit')
await localVue.nextTick()

expect(storeAccessor.auth.getError).toBe(null)
})

it('should show wrong credentials error', async () => {
inputEmail.setValue('[email protected]')
inputPassword.setValue('wrongPassword')

await form.trigger('submit')
await localVue.nextTick()

expect(storeAccessor.auth.getError).toBe('Wrong credentials!')
})

it('should show friendly error message', async () => {
inputEmail.setValue('[email protected]')
inputPassword.setValue('somePassword')

await form.trigger('submit')
await localVue.nextTick()

expect(storeAccessor.auth.getError).toBe('Oops! Something went wrong.')
})
})
})
27 changes: 27 additions & 0 deletions webapp/components/LoginForm/LoginForm.stories.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import NewsItem from './NewsItem.vue'

const newsItem = {
id: 1,
title: 'Test News Item',
votes: 0,
}

export default {
title: 'NewsItem',
component: NewsItem,
argTypes: {
update: { action: 'down- or upvote event action' },
remove: { action: 'remove-event action' },
},
}

const Template = (args, { argTypes }) => ({
props: Object.keys(argTypes),
components: { NewsItem },
template: '<NewsItem @update="update" @remove="remove" v-bind="$props" />',
})

export const Default = Template.bind({})
Default.args = {
newsItem,
}
71 changes: 35 additions & 36 deletions webapp/components/LoginForm/LoginForm.vue
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
<template>
<div>
<div v-if="!login">
<form @submit.prevent="signIn">
<div v-if="!isLoggedIn">
<form @submit.prevent="login">
<div class="uk-margin">
<div class="uk-inline">
<input
id="input-email"
v-model="email"
class="uk-input"
type="text"
Expand All @@ -16,6 +17,7 @@
<div class="uk-margin">
<div class="uk-inline">
<input
id="input-password"
v-model="password"
class="uk-input"
type="password"
Expand All @@ -26,69 +28,66 @@

<div>
<div class="uk-inline">
<button class="uk-button" type="submit">Sign in</button>
<button class="uk-button" type="submit" :disabled="loading">
Sign in
</button>

<div v-if="loading" uk-spinner></div>
</div>
</div>

<div v-if="error" class="uk-alert uk-alert-danger">
{{ error }}
</div>
</form>
</div>
<div v-else>
<div class="uk-margin">Hallo {{ userId }}</div>

<div class="uk-margin">
<textarea v-model="jwt" class="uk-textarea" rows="10" />
</div>

<button class="uk-button" @click="signOut">Sign out</button>
<h1 class="uk-margin">Hallo {{ user.name }}</h1>
</div>
</div>
</template>

<script lang="ts">
import Vue from 'vue'
import jwtDecode, { JwtPayload } from 'jwt-decode'

export default Vue.extend({
name: 'LoginForm',
props: {
login: {
type: Boolean,
required: true,
},
},

data() {
return {
email: null,
password: null,
}
},
computed: {
jwt(): string | null {
// TODO: get from vuex store
// return window.localStorage.getItem('token')
return ''
isLoggedIn() {
return this.$accessor.auth.isLoggedIn
},
userId(): string | null {
if (this.jwt) {
const payload = jwtDecode<JwtPayload & { id: string }>(this.jwt)

if (payload) {
return payload.id
}
}

return null
user() {
return this.$accessor.auth.getUser
},
token() {
return this.$accessor.auth.getToken
},
error() {
return this.$accessor.auth.getError
},
loading() {
return this.$accessor.auth.getLoading
},
},
created() {
this.$accessor.auth.setLoading(false)
this.$accessor.auth.setError(null)
},
methods: {
signIn() {
async login() {
const email = this.email
const password = this.password

this.$emit('signIn', { email, password })
await this.$accessor.auth.login({ email, password })
},
signOut() {
this.$emit('signOut')
async logout() {
await this.$accessor.auth.logout()
},
},
})
Expand Down
9 changes: 9 additions & 0 deletions webapp/components/NewsItem/NewsItem.vue
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,15 @@
<i class="mdi mdi-18px mdi-thumb-down" aria-hidden="true"></i>
</button>
</td>
<td v-if="login" class="uk-table-shrink">
<button
v-if="newsItem.id && isOwner"
class="uk-icon-button uk-button-default"
aria-label="Edit"
>
<i class="mdi mdi-18px mdi-pencil" aria-hidden="true"></i>
</button>
</td>
<td v-if="login" class="uk-table-shrink">
<button
v-if="newsItem.id && isOwner"
Expand Down
Loading