Skip to content

Commit

Permalink
feat: user data-export self-service, closes #47
Browse files Browse the repository at this point in the history
- more or less plain JSON dump
- simple link from user page
- relies on being authenticated in browser
  • Loading branch information
neopostmodern committed Nov 19, 2022
1 parent 7cdad45 commit 7f1ed24
Show file tree
Hide file tree
Showing 2 changed files with 47 additions and 5 deletions.
17 changes: 13 additions & 4 deletions client/src/renderer/containers/UserPage.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import { useQuery } from '@apollo/client';
import { Launch } from '@mui/icons-material';
import { Button, Link, Typography } from '@mui/material';
import gql from 'graphql-tag';
import { FC } from 'react';
import { useDispatch } from 'react-redux';
import styled from 'styled-components';
import { requestLogout } from '../actions/userInterface';
import ErrorSnackbar from '../components/ErrorSnackbar';
import Gap from '../components/Gap';
Expand All @@ -25,6 +27,10 @@ const USER_QUERY = gql`
}
`;

const LinkButton = styled(Button)`
text-decoration: none;
` as typeof Button;

const UserPage: FC = () => {
const dispatch = useDispatch();
const userQuery = useDataState(
Expand All @@ -37,13 +43,16 @@ const UserPage: FC = () => {
<ComplexLayout
primaryActions={
<Menu direction="vertical-horizontal">
<Button
<LinkButton
size="huge"
onClick={(): void => alert('This feature is not yet available')}
disabled
component={Link}
href={BACKEND_URL + '/export.json'}
target="_blank"
rel="noopener noreferrer"
endIcon={<Launch />}
>
Export my data
</Button>
</LinkButton>
<Button
size="huge"
onClick={(): void => alert('This feature is not yet available')}
Expand Down
35 changes: 34 additions & 1 deletion server/lib/restApi.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { rssFeedUrl } from '@structure/common'
import config from '@structure/config'
import { Feed } from 'feed'
import _ from 'lodash'
import { addTagByNameToNote, submitLink } from './methods.js'
import { Link, User } from './mongo.js'
import { Link, Note, Tag, User } from './mongo.js'

const restApi = (app) => {
app.get('/bookmarklet', (request, response) => {
Expand Down Expand Up @@ -115,6 +116,38 @@ const restApi = (app) => {
})
})

app.get('/export.json', async (request, response) => {
const { user } = request
if (!user) {
response.status(400).send({
error: `Must be logged in to export! Please go to ${config.WEB_FRONTEND_HOST} to sign in.`,
})
return
}

const processEntities = async (mongooseQuery) =>
(await mongooseQuery.exec()).map((mongooseObject) => {
const plainObject = mongooseObject.toObject()
delete plainObject.__v
return plainObject
})
const tags = await processEntities(Tag.find({ user: user._id }))
const notes = await processEntities(Note.find({ user: user._id }))
const allData = {
user: _.pick(user, [
'_id',
'name',
'authenticationProvider',
'createdAt',
'updatedAt',
]),
tags,
notes,
meta: { exportedAt: new Date(), exportedFrom: config.BACKEND_URL },
}
response.send(allData)
})

app.use('/hello', (request, response) => {
response.status(200).send('OK')
})
Expand Down

0 comments on commit 7f1ed24

Please sign in to comment.