Merge branch 'master' of https://github.com/Ocelot-Social-Community/Ocelot-Social into allow-only-supported-file-formats

This commit is contained in:
Wolfgang Huß 2020-11-02 12:41:15 +01:00
commit 0095a94863
24 changed files with 406 additions and 35 deletions

View File

@ -45,10 +45,12 @@ script:
- docker-compose down
- docker-compose -f docker-compose.yml up -d
- wait-on http://localhost:7474
- yarn run cypress:run --record
- yarn run cucumber
# disable for last deploy, because of flakiness!
# - yarn run cypress:run --record
# - yarn run cucumber
# Coverage
- yarn run codecov
# disable this uneffective thing for last deploy, because of easyness!
# - yarn run codecov
after_success:
- wget https://raw.githubusercontent.com/DiscordHooks/travis-ci-discord-webhook/master/send.sh

View File

@ -4,6 +4,36 @@ All notable changes to this project will be documented in this file. Dates are d
Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
#### [v0.6.3](https://github.com/Human-Connection/Human-Connection/compare/v0.6.0...v0.6.3)
> 16 October 2020
- feat: Export User Data Update [`#3954`](https://github.com/Human-Connection/Human-Connection/pull/3954)
- chore: Upgrade to v0.6.2 [`#3947`](https://github.com/Human-Connection/Human-Connection/pull/3947)
#### [v0.6.2](https://github.com/Human-Connection/Human-Connection/compare/v0.6.1...v0.6.2)
> 15 October 2020
- build: 🍰 Disable Codecov for last deploy [`#3946`](https://github.com/Human-Connection/Human-Connection/pull/3946)
- feat: Export User Data [`#3899`](https://github.com/Human-Connection/Human-Connection/pull/3899)
- build: 💥 Disable full stack tests, Fix deployment to develop, tryout [`#3937`](https://github.com/Human-Connection/Human-Connection/pull/3937)
- build: 💥 Disable full stack tests [`#3935`](https://github.com/Human-Connection/Human-Connection/pull/3935)
- fix: 🍰 Sign Up Page On Safari [`#3882`](https://github.com/Human-Connection/Human-Connection/pull/3882)
- build: Add semantic PR config [`#3884`](https://github.com/Human-Connection/Human-Connection/pull/3884)
- feat: 🍰 Admin - Remove User Profile [`#3140`](https://github.com/Human-Connection/Human-Connection/pull/3140)
- fix: 🍰 Comment Counters Are Now Equal [`#3769`](https://github.com/Human-Connection/Human-Connection/pull/3769)
- feat: 🍰 Redesign Data Privacy Warning Box [`#3780`](https://github.com/Human-Connection/Human-Connection/pull/3780)
- fix: 🍰 Checkboxes Not Missing Anymore On Delete User Account Page [`#3506`](https://github.com/Human-Connection/Human-Connection/pull/3506)
- feat: 🍰 Increase Margin Of Header And Ruler For Better Legibility [`#3774`](https://github.com/Human-Connection/Human-Connection/pull/3774)
- chore: 💬 Rename stale.yml to stale-disabled.yml [`#3662`](https://github.com/Human-Connection/Human-Connection/pull/3662)
- build(deps): [security] bump apollo-server-core from 2.12.0 to 2.15.0 in /backend [`#3650`](https://github.com/Human-Connection/Human-Connection/pull/3650)
- fix: Corrected Code-of-Conduct Mail Link [`#3609`](https://github.com/Human-Connection/Human-Connection/pull/3609)
- feat: 🍰 Hero image height on post page is now set without having to wait for… [`#3583`](https://github.com/Human-Connection/Human-Connection/pull/3583)
- feat: 🍰 Alphabetically sorting tags using compute functions on index and more… [`#3589`](https://github.com/Human-Connection/Human-Connection/pull/3589)
- fix: Fixed webapp unit test command. [`#3584`](https://github.com/Human-Connection/Human-Connection/pull/3584)
- chore: Upgrade to v0.6.1 [`#3525`](https://github.com/Human-Connection/Human-Connection/pull/3525)
#### [v0.6.1](https://github.com/Human-Connection/Human-Connection/compare/v0.6.0...v0.6.1)
> 4 May 2020

View File

@ -1,6 +1,6 @@
{
"name": "human-connection-backend",
"version": "0.6.1",
"version": "0.6.3",
"description": "GraphQL Backend for Human Connection",
"main": "src/index.js",
"scripts": {

View File

@ -105,6 +105,7 @@ export default shield(
blockedUsers: isAuthenticated,
notifications: isAuthenticated,
Donations: isAuthenticated,
userData: isAuthenticated,
},
Mutation: {
'*': deny,

View File

@ -0,0 +1,61 @@
export default {
Query: {
userData: async (object, args, context, resolveInfo) => {
const id = context.user.id
const cypher = `
MATCH (user:User { id: $id })
WITH user
OPTIONAL MATCH (posts:Post)
WHERE (user)-[:WROTE]->(posts)
AND posts.deleted = FALSE
AND posts.disabled = FALSE
RETURN { user: properties(user),
posts: collect(
posts {
.*,
author: [
(posts)<-[:WROTE]-(author:User) |
author {
.*
}
][0],
comments: [
(posts)<-[:COMMENTS]-(comment:Comment)
WHERE comment.disabled = FALSE
AND comment.deleted = FALSE |
comment {
.*,
author: [ (comment)<-[:WROTE]-(commentator:User) |
commentator { .name, .slug, .id } ][0]
}
],
categories: [ (posts)-[:CATEGORIZED]->(category:Category) |
category { .name, .id } ]
})
} AS result`
const session = context.driver.session()
const resultPromise = session.readTransaction(async (transaction) => {
const transactionResponse = transaction.run(cypher, {
id,
})
return transactionResponse
})
try {
const result = await resultPromise
const userData = result.records[0].get('result')
userData.posts.sort(byCreationDate)
userData.posts.forEach((post) => post.comments.sort(byCreationDate))
return userData
} finally {
session.close()
}
},
},
}
const byCreationDate = (a, b) => {
if (a.createdAt < b.createdAt) return -1
if (a.createdAt > b.createdAt) return 1
return 0
}

View File

@ -0,0 +1,143 @@
import Factory, { cleanDatabase } from '../../db/factories'
import { gql } from '../../helpers/jest'
import { getNeode, getDriver } from '../../db/neo4j'
import createServer from '../../server'
import { createTestClient } from 'apollo-server-testing'
let query, authenticatedUser
const driver = getDriver()
const neode = getNeode()
beforeAll(async () => {
await cleanDatabase()
const user = await Factory.build('user', {
id: 'a-user',
name: 'John Doe',
slug: 'john-doe',
})
await Factory.build('user', {
id: 'o-user',
name: 'Unauthenticated User',
slug: 'unauthenticated-user',
})
authenticatedUser = await user.toJson()
const { server } = createServer({
context: () => {
return {
driver,
neode,
user: authenticatedUser,
}
},
})
query = createTestClient(server).query
})
afterAll(async () => {
await cleanDatabase()
})
const userDataQuery = gql`
query($id: ID!) {
userData(id: $id) {
user {
id
name
slug
}
posts {
id
title
content
comments {
content
author {
slug
}
}
}
}
}
`
describe('resolvers/userData', () => {
let variables = { id: 'a-user' }
describe('given one authenticated user who did not write anything so far', () => {
it("returns the user's data and no posts", async () => {
await expect(query({ query: userDataQuery, variables })).resolves.toMatchObject({
data: {
userData: {
user: {
id: 'a-user',
name: 'John Doe',
slug: 'john-doe',
},
posts: [],
},
},
})
})
describe('the user writes a post', () => {
beforeAll(async () => {
await Factory.build(
'post',
{
id: 'a-post',
title: 'A post',
content: 'A post',
},
{ authorId: 'a-user' },
)
})
it("returns the user's data and the post", async () => {
await expect(query({ query: userDataQuery, variables })).resolves.toMatchObject({
data: {
userData: {
user: {
id: 'a-user',
name: 'John Doe',
slug: 'john-doe',
},
posts: [
{
id: 'a-post',
title: 'A post',
content: 'A post',
},
],
},
},
})
})
})
})
describe('try to request data of another user', () => {
variables = { id: 'o-user' }
it('returns the data of the authenticated user', async () => {
await expect(query({ query: userDataQuery, variables })).resolves.toMatchObject({
data: {
userData: {
user: {
id: 'a-user',
name: 'John Doe',
slug: 'john-doe',
},
posts: expect.arrayContaining([
{
id: 'a-post',
title: 'A post',
content: 'A post',
comments: [],
},
]),
},
},
})
})
})
})

View File

@ -0,0 +1,10 @@
type UserData {
user: User!
posts: [Post]
}
type Query {
userData(
id: ID
): UserData
}

View File

@ -1,6 +1,6 @@
{
"name": "human-connection",
"version": "0.6.1",
"version": "0.6.3",
"description": "Fullstack and API tests with cypress and cucumber for Human Connection",
"author": "Human Connection gGmbh",
"license": "MIT",

View File

@ -0,0 +1,5 @@
<!-- Generated by IcoMoon.io -->
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
<title>download</title>
<path d="M15 4h2v16.563l5.281-5.281 1.438 1.438-7 7-0.719 0.688-0.719-0.688-7-7 1.438-1.438 5.281 5.281v-16.563zM7 26h18v2h-18v-2z"></path>
</svg>

After

Width:  |  Height:  |  Size: 302 B

View File

@ -279,7 +279,7 @@ $size-avatar-large: 114px;
$size-image-max-height: 2000px;
$size-image-cropper-max-height: 600px;
$size-image-cropper-min-height: 400px;
$size-image-uploader-min-height: 200px;
$size-image-uploader-min-height: 250px;
/**
* @tokens Size Icons

View File

@ -20,6 +20,9 @@
:title="$t('actions.delete')"
@click.stop="deleteImage"
/>
<div v-if="!hasImage" class="supported-formats">
{{ $t('contribution.teaserImage.supportedFormats') }}
</div>
</vue-dropzone>
<div v-show="showCropper" class="crop-overlay">
<img id="cropping-image" />
@ -209,6 +212,11 @@ export default {
right: $space-small;
z-index: $z-index-surface;
}
> .supported-formats {
margin-top: 150px;
font-weight: bold;
}
}
}
</style>

View File

@ -292,3 +292,32 @@ export const currentUserCountQuery = () => gql`
}
}
`
export const userDataQuery = (i18n) => {
return gql`
${userFragment}
${postFragment}
${commentFragment}
query($id: ID!) {
userData(id: $id) {
user {
...user
}
posts {
...post
categories {
id
name
}
comments {
author {
id
slug
}
...comment
}
}
}
}
`
}

View File

@ -258,7 +258,8 @@
"newPost": "Erstelle einen neuen Beitrag",
"success": "Gespeichert!",
"teaserImage": {
"cropperConfirm": "Bestätigen"
"cropperConfirm": "Bestätigen",
"supportedFormats": "Füge ein Bild im Dateiformat JPG, PNG oder GIF ein"
},
"title": "Titel"
},
@ -648,6 +649,8 @@
"success": "Konto erfolgreich gelöscht!"
},
"download": {
"description": "Klicke auf den Knopf oben, um den Inhalt deiner Beiträge und Kommentare herunterzuladen. Um die Bilder der Beiträge herunterzuladen, musst du auf den jeweiligen Link unten klicken.",
"json": "als JSON",
"name": "Daten herunterladen"
},
"email": {

View File

@ -258,7 +258,8 @@
"newPost": "Create a new Post",
"success": "Saved!",
"teaserImage": {
"cropperConfirm": "Confirm"
"cropperConfirm": "Confirm",
"supportedFormats": "Insert a picture of file format JPG , PNG or GIF"
},
"title": "Title"
},
@ -648,6 +649,8 @@
"success": "Account successfully deleted!"
},
"download": {
"description": "Click on the button above to download the content of your posts and comments. To download the images of your posts, you have to click on the corresponding link below.",
"json": "as JSON",
"name": "Download Data"
},
"email": {

View File

@ -256,7 +256,8 @@
"newPost": "Crear una nueva contribución",
"success": "¡Guardado!",
"teaserImage": {
"cropperConfirm": "Confirmar"
"cropperConfirm": "Confirmar",
"supportedFormats": "Insertar una imagen de formato de archivo JPG, PNG o GIF"
},
"title": "Título"
},

View File

@ -256,7 +256,8 @@
"newPost": "Créer un nouveau Post",
"success": "Enregistré!",
"teaserImage": {
"cropperConfirm": "Confirmer"
"cropperConfirm": "Confirmer",
"supportedFormats": "Insérer une image au format de fichier JPG, PNG ou GIF"
},
"title": "Titre"
},

View File

@ -261,7 +261,8 @@
"newPost": "",
"success": "",
"teaserImage": {
"cropperConfirm": "Confermare"
"cropperConfirm": "Confermare",
"supportedFormats": "Inserisci un'immagine in formato file JPG, PNG o GIF"
},
"title": ""
},

View File

@ -68,7 +68,8 @@
"delete": "Bijdrage verwijderen",
"edit": "Bijdrage bewerken",
"teaserImage": {
"cropperConfirm": "Bevestigen"
"cropperConfirm": "Bevestigen",
"supportedFormats": "Voeg een afbeelding in met het bestandsformaat JPG, PNG of GIF"
}
},
"disable": {

View File

@ -119,7 +119,8 @@
"newPost": "Utwórz nowy wpis",
"success": "Zapisano!",
"teaserImage": {
"cropperConfirm": "Potwierdzać"
"cropperConfirm": "Potwierdzać",
"supportedFormats": "Wstaw zdjęcie w formacie pliku JPG, PNG lub GIF"
}
},
"delete": {

View File

@ -252,7 +252,8 @@
"newPost": "Criar uma nova publicação",
"success": "Salvo!",
"teaserImage": {
"cropperConfirm": "Confirmar"
"cropperConfirm": "Confirmar",
"supportedFormats": "Insira uma imagem do formato JPG, PNG ou GIF"
},
"title": "Título"
},

View File

@ -256,7 +256,8 @@
"newPost": "Создать пост",
"success": "Сохранено!",
"teaserImage": {
"cropperConfirm": "Подтвердить"
"cropperConfirm": "Подтвердить",
"supportedFormats": "Вставьте изображение файла формата JPG, PNG или GIF"
},
"title": "Заголовок"
},

View File

@ -1,6 +1,6 @@
{
"name": "human-connection-webapp",
"version": "0.6.1",
"version": "0.6.3",
"description": "Human Connection Frontend",
"authors": [
"Grzegorz Leoniec (appinteractive)",

View File

@ -51,32 +51,30 @@ export default {
name: this.$t('settings.embeds.name'),
path: `/settings/embeds`,
},
{
name: this.$t('settings.download.name'),
path: `/settings/data-download`,
},
{
name: this.$t('settings.deleteUserAccount.name'),
path: `/settings/delete-account`,
},
// TODO implement
/* {
name: this.$t('settings.invites.name'),
path: `/settings/invites`
}, */
name: this.$t('settings.invites.name'),
path: `/settings/invites`
}, */
// TODO implement
/* {
name: this.$t('settings.download.name'),
path: `/settings/data-download`
}, */
// TODO implement
name: this.$t('settings.organizations.name'),
path: `/settings/my-organizations`
}, */
// TODO implement
/* {
name: this.$t('settings.organizations.name'),
path: `/settings/my-organizations`
}, */
// TODO implement
/* {
name: this.$t('settings.languages.name'),
path: `/settings/languages`
},
} */
name: this.$t('settings.languages.name'),
path: `/settings/languages`
},
} */
]
},
},

View File

@ -1,16 +1,87 @@
<template>
<base-card>
<h2 class="title">{{ $t('settings.download.name') }}</h2>
<hc-empty icon="tasks" message="Coming Soon…" />
<base-button
@click="onClick(jsonData)"
icon="download"
secondary
filled
:disabled="loading"
:loading="loading"
>
{{ $t('settings.download.json') }}
</base-button>
<ds-space margin="large" />
<ds-text>{{ $t('settings.download.description') }}</ds-text>
<ds-space margin="large" />
<base-card v-for="image in imageList" :key="image.key">
<a :href="image.url" target="_blank" rel="noopener noreferrer">{{ image.title }}</a>
<ds-space margin="xxx-small" />
</base-card>
</base-card>
</template>
<script>
import HcEmpty from '~/components/Empty/Empty'
import { mapGetters } from 'vuex'
import { userDataQuery } from '~/graphql/User'
import BaseButton from '~/components/_new/generic/BaseButton/BaseButton.vue'
import isEmpty from 'lodash/isEmpty'
export default {
components: {
HcEmpty,
BaseButton,
},
data() {
return {
userData: {},
loading: true,
imageList: [],
}
},
computed: {
...mapGetters({
user: 'auth/user',
}),
jsonData() {
return { data: JSON.stringify(this.userData, null, 2), type: 'json' }
},
},
methods: {
onClick(method) {
var fileURL = window.URL.createObjectURL(new Blob([method.data]))
var fileLink = document.createElement('a')
fileLink.href = fileURL
fileLink.setAttribute('download', 'userData.' + method.type)
document.body.appendChild(fileLink)
fileLink.click()
},
},
apollo: {
queryUserData: {
query() {
return userDataQuery()
},
variables() {
return { id: this.user.id }
},
update({ userData }) {
this.userData = userData
this.loading = false
if (isEmpty(this.userData)) return null
const userId = this.userData.user.id
if (isEmpty(userId)) return null
this.imageList = this.userData.posts
.filter((post) => post.author.id === userId && post.image)
.map((post) => {
const obj = {}
obj.key = post.id
obj.url = post.image.url
obj.title = post.title
return obj
})
},
fetchPolicy: 'cache-and-network',
},
},
}
</script>