Skip to content
Snippets Groups Projects
Commit c6b851e8 authored by Xiao Gui's avatar Xiao Gui
Browse files

feat: storage of user data

parent 84335811
No related branches found
No related tags found
No related merge requests found
......@@ -80,6 +80,11 @@ app.use((_req, res, next) => {
*/
app.use(require('./devBanner'))
/**
* User route, for user profile/management
*/
app.use('/user', require('./user'))
/**
* only use compression for production
* this allows locally built aot to be served without errors
......
......@@ -25,7 +25,7 @@ module.exports = async (app) => {
discoveryUrl,
redirectUri,
cb,
scope: 'openid offline_access profile',
scope: 'openid email offline_access profile collab.drive',
clientConfig: {
redirect_uris: [ redirectUri ],
response_types: [ 'code' ]
......
......@@ -23,14 +23,6 @@ module.exports = async (app) => {
await hbpOidc(app)
await hbpOidc2(app)
app.get('/user', (req, res) => {
if (req.user) {
return res.status(200).send(JSON.stringify(req.user))
} else {
return res.status(401).end()
}
})
app.get('/logout', (req, res) => {
if (req.user && req.user.id) objStoreDb.delete(req.user.id)
req.logout()
......
......@@ -17,6 +17,7 @@
"body-parser": "^1.19.0",
"express": "^4.16.4",
"express-session": "^1.15.6",
"hbp-seafile": "0.0.6",
"helmet-csp": "^2.8.0",
"jwt-decode": "^2.2.0",
"memorystore": "^1.6.1",
......
const router = require('express').Router()
const { readUserData, saveUserData } = require('./store')
const bodyParser = require('body-parser')
const loggedInOnlyMiddleware = (req, res, next) => {
const { user } = req
if (!user) return res.status(401).end()
return next()
}
router.get('', loggedInOnlyMiddleware, (req, res) => {
return res.status(200).send(JSON.stringify(req.user))
})
router.get('/config', loggedInOnlyMiddleware, async (req, res) => {
const { user } = req
try{
const data = await readUserData(user)
res.status(200).json(data)
} catch (e){
console.error(e)
res.status(500).send(e.toString())
}
})
router.post('/config', loggedInOnlyMiddleware, bodyParser.json(), async (req, res) => {
const { user, body } = req
try {
await saveUserData(user, body)
res.status(200).end()
} catch (e) {
console.error(e)
res.status(500).send(e.toString())
}
})
module.exports = router
\ No newline at end of file
const { Seafile } = require('hbp-seafile')
const { Readable } = require('stream')
const IAV_DIR_NAME = `interactive-atlas-viewer`
const IAV_DIRECTORY = `/${IAV_DIR_NAME}/`
const IAV_FILENAME = 'data.json'
const getNewSeafilehandle = async ({ accessToken }) => {
const seafileHandle = new Seafile({ accessToken })
await seafileHandle.init()
return seafileHandle
}
const saveUserData = async (user, data) => {
const { access_token } = user && user.tokenset || {}
if (!access_token) throw new Error(`user or user.tokenset not set can only save logged in user data`)
let handle = await getNewSeafilehandle({ accessToken: access_token })
const s = await handle.ls()
const found = s.find(({ type, name }) => type === 'dir' && name === IAV_DIR_NAME)
// if dir exists, check permission. throw if no writable or readable permission
if (found && !/w/.test(found.permission) && !/r/.test(found.permission)){
throw new Error(`Writing to file not permitted. Current permission: ${found.permission}`)
}
// create new dir if does not exist. Should have rw permission
if (!found) {
await handle.mkdir({ dir: IAV_DIR_NAME })
}
const fileLs = await handle.ls({ dir: IAV_DIRECTORY })
const fileFound = fileLs.find(({ type, name }) => type === 'file' && name === IAV_FILENAME )
const rStream = new Readable()
rStream.path = IAV_FILENAME
rStream.push(JSON.stringify(data))
rStream.push(null)
if(!fileFound) {
console.log('>>> file not found, upload')
return handle.uploadFile({ readStream: rStream, filename: `${IAV_FILENAME}` }, { dir: IAV_DIRECTORY })
}
if (fileFound && !/w/.test(fileFound.permission)) {
return new Error('file permission cannot be written')
}
console.log('>>> file found, udpate')
return handle.updateFile({ dir: IAV_DIRECTORY, replaceFilepath: `${IAV_DIRECTORY}${IAV_FILENAME}` }, { readStream: rStream, filename: IAV_FILENAME })
}
const readUserData = async (user) => {
const { access_token } = user && user.tokenset || {}
if (!access_token) throw new Error(`user or user.tokenset not set can only save logged in user data`)
let handle = await getNewSeafilehandle({ accessToken: access_token })
try {
const r = await handle.readFile({ dir: `${IAV_DIRECTORY}${IAV_FILENAME}` })
return JSON.parse(r)
}catch(e){
return {}
}
}
module.exports = {
saveUserData,
readUserData
}
import { Action, Store, select } from '@ngrx/store'
import { Injectable, OnDestroy } from '@angular/core';
import { Observable, combineLatest, fromEvent, Subscription } from 'rxjs';
import { Observable, combineLatest, fromEvent, Subscription, from, of } from 'rxjs';
import { Effect, Actions, ofType } from '@ngrx/effects';
import { withLatestFrom, map, distinctUntilChanged, scan, shareReplay, filter, mapTo } from 'rxjs/operators';
import { withLatestFrom, map, distinctUntilChanged, scan, shareReplay, filter, mapTo, debounceTime, catchError, skip, throttleTime } from 'rxjs/operators';
import { AtlasViewerConstantsServices } from 'src/atlasViewer/atlasViewer.constantService.service';
import { SNACKBAR_MESSAGE } from './uiState.store';
import { getNgIds, IavRootStoreInterface } from '../stateStore.service';
import { getNgIds, IavRootStoreInterface, GENERAL_ACTION_TYPES } from '../stateStore.service';
export const FOUR_PANEL = 'FOUR_PANEL'
export const V_ONE_THREE = 'V_ONE_THREE'
......@@ -137,6 +137,9 @@ export const getStateStore = ({ state = defaultState } = {}) => (prevState:State
...prevState,
nehubaReady
}
case GENERAL_ACTION_TYPES.APPLY_STATE:
const { ngViewerState } = (action as any).state
return ngViewerState
default: return prevState
}
}
......@@ -185,11 +188,54 @@ export class NgViewerUseEffect implements OnDestroy{
private subscriptions: Subscription[] = []
@Effect()
public applySavedUserConfig$: Observable<any>
constructor(
private actions: Actions,
private store$: Store<IavRootStoreInterface>,
private constantService: AtlasViewerConstantsServices
){
// TODO either split backend user to be more granular, or combine the user config into a single subscription
this.subscriptions.push(
this.store$.pipe(
select('ngViewerState'),
distinctUntilChanged(),
debounceTime(200),
skip(1),
// Max frequency save once every second
throttleTime(1000)
).subscribe(({panelMode, panelOrder}) => {
fetch(`${this.constantService.backendUrl}user/config`, {
method: 'POST',
headers: {
'Content-type': 'application/json'
},
body: JSON.stringify({ ngViewerState: { panelMode, panelOrder } })
})
})
)
this.applySavedUserConfig$ = from(fetch(`${this.constantService.backendUrl}user/config`).then(r => r.json())).pipe(
catchError((err,caught) => of(null)),
filter(v => !!v),
withLatestFrom(this.store$),
map(([{ngViewerState: fetchedNgViewerState}, state]) => {
const { ngViewerState } = state
return {
type: GENERAL_ACTION_TYPES.APPLY_STATE,
state: {
...state,
ngViewerState: {
...ngViewerState,
...fetchedNgViewerState
}
}
}
})
)
const toggleMaxmimise$ = this.actions.pipe(
ofType(ACTION_TYPES.TOGGLE_MAXIMISE),
shareReplay(1)
......
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment