diff --git a/app/client/helpers/ui_helpers.coffee b/app/client/helpers/ui_helpers.coffee index 6df896f..0165ee5 100644 --- a/app/client/helpers/ui_helpers.coffee +++ b/app/client/helpers/ui_helpers.coffee @@ -1,28 +1,39 @@ Template.registerHelper "printBool", (bool) -> return "yes" if bool return "no" Template.registerHelper "isProduction", -> process.env.NODE_ENV? Template.registerHelper "headTitle", (title) -> document.title = title "" Template.registerHelper "headDescription", (desc) -> document.description = desc "" Template.registerHelper "fullDateTime", (date) -> date = moment(date) if date.dayOfYear() is moment().dayOfYear() return "today #{date.format('HH:mm')}" else return date.format("DD.MM.YYYY HH:mm") Template.registerHelper "fullDate", (date) -> date = moment(date) if date.dayOfYear() is moment().dayOfYear() return "today" else return date.format("DD.MM.YYYY") + +Template.registerHelper "fileSizeSani", (size) -> + if size > 1000 + "#{(size/1000).toFixed(1)} kB" + else if size > 1000000 + "#{(size/1000000).toFixed(1)} MB" + else if size > 1000000000 + "#{(size/1000000000).toFixed(1)} GB" + else + "#{(size)} B" + diff --git a/app/client/views/empatica/empatica_recorder.coffee b/app/client/views/empatica/empatica_recorder.coffee index 663240d..d3433bb 100644 --- a/app/client/views/empatica/empatica_recorder.coffee +++ b/app/client/views/empatica/empatica_recorder.coffee @@ -1,186 +1,205 @@ Template.empaticaRecorder.rendered = -> - Session.set 'empatica_sessionId', @data.sessionId - authenticate() + if !Session.get('empatica_isRecording') + Session.set 'empatica_sessionId', @data.sessionId + authenticate() Template.empaticaRecorder.helpers + isUsedForAnotherSession: -> + Session.get('empatica_isRecording') and Session.get("empatica_sessionId") and Session.get("empatica_sessionId") isnt @sessionId + sessionId: -> + Session.get("empatica_sessionId") isAuthenticating: -> Session.get("empatica_isAuthenticating") isAuthenticated: -> Session.get("empatica_isAuthenticated") authenticationError: -> Session.get("empatica_authenticationError") isDiscovering: -> Session.get("empatica_isDiscovering") discoveredDevices: -> Session.get("empatica_discoveredDevices") #hasSelectedDevices: -> # Session.get("selectedDevices") isConnecting: -> Session.get("empatica_isConnecting") isConnected: -> Session.get("empatica_isConnected") isRecording: -> Session.get("empatica_isRecording") Template.empaticaRecorder.events "click #authenticate": (evt) -> authenticate() "click #discoverDevices": (evt) -> discover() "click #connectDevices": (evt) -> connect() "click #disconnectDevices": (evt) -> disconnect() "click #startRecording": (evt) -> startRecording() "click #stopRecording": (evt) -> stopRecording() "click #listRecords": (evt) -> listRecords() authenticate = -> Session.set("empatica_isAuthenticating", true) window.plugins.Empatica.authenticateWithAPIKey("47a21adb8f154caba25b50feaa9a5eec", (msg) -> Session.set("empatica_isAuthenticated", true) Session.set("empatica_isAuthenticating", false) Session.set("empatica_authenticationError", false) discover() , (msg) -> Session.set("empatica_isAuthenticated", false) Session.set("empatica_isAuthenticating", false) Session.set("empatica_authenticationError", "Initializing the Empatica API failed with the following error: '#{msg}'. Are you sure you are connected to the internet?") ) false discover = -> Session.set("empatica_isDiscovering", true) window.plugins.Empatica.discoverDevices( (devices)-> console.log "did discover devices!" Session.set("empatica_isDiscovering", false) Session.set("empatica_discoveredDevices", devices) #TODO set 1 to 2 if devices.length < 2 and !Session.get('empatica_isConnecting') and !Session.get('empatica_isConnected') discover() else if devices.length is 2 connect() , (error) -> console.log "discovery error:" console.log error Session.set("empatica_isDiscovering", false) ) false connect = -> Session.set("empatica_isConnecting", true) devices = Session.get("empatica_discoveredDevices") if devices.length is 0 return false window.plugins.Empatica.connectDevices(devices, (msg)-> console.log "did connect devices!" console.log msg Session.set("empatica_isConnected", true) Session.set("empatica_isConnecting", false) , (error) -> console.log "discovery error:" console.log error Session.set("empatica_isConnected", false) Session.set("empatica_isConnecting", false) ) false disconnect = -> window.plugins.Empatica.disconnectAllDevices( (msg)-> console.log "disconnectAllDevices done!" console.log msg Session.set("empatica_isConnected", false) , (error) -> console.log "disconnectAllDevices error:" console.log error Session.set("empatica_isConnected", false) ) false #uploadInterval = null startRecording = -> sessionId = Session.get("empatica_sessionId") console.log "startRecording with sessionId:"+sessionId window.plugins.Empatica.startRecording(sessionId, (msg)-> console.log "recording: " console.log msg Session.set("empatica_isRecording", true) #uploadInterval = Meteor.setInterval(uploadRecords, 5000) , (error) -> console.log "recording error:" console.log error if error.code is "missing_device" Session.set("empatica_discoveredDevices", null) Session.set("empatica_isConnected", false) discover() Session.set("empatica_isRecording", false) ) false stopRecording = -> window.plugins.Empatica.stopRecording( (msg)-> console.log "stopRecording: " console.log msg Session.set("empatica_isRecording", false) #Meteor.clearInterval(uploadInterval) uploadRecords() , (error) -> console.log "stopRecording error:" console.log error Session.set("empatica_isRecording", false) ) false uploadRecords = -> window.plugins.Empatica.listRecords( (records)-> console.log "listRecords: " - sessionId = Session.get('empatica_sessionId') + regex = /(.*?)_(.*?)_(.*?).csv/g _.each records, (record) -> filename = record.substr(record.lastIndexOf('/')+1) - er = EmpaticaRecords.findOne - name: filename - unless er? - upload(record, sessionId) + console.log filename + match = regex.exec(filename) + if match? and match.length >= 4 + sessionId = match[1] + deviceName = match[2] + sensor = match[3] + er = PhysioRecords.findOne + 'metadata.visitId': sessionId + 'metadata.deviceName': deviceName + 'metadata.sensor': sensor + #name: filename + unless er? + console.log "creating new FS.File" + newFile = new FS.File(null) + newFile.metadata = + visitId: sessionId + deviceName: deviceName + sensor: sensor + file = PhysioRecords.insert newFile + console.log "uploading new record" + console.log file + upload(record, file._id) , (error) -> console.log "listRecords error:" console.log error ) -upload = (fileURL, sessionId)-> +upload = (fileURL, fileId)-> win = (r) -> console.log 'Code = ' + r.responseCode console.log 'Response = ' + r.response console.log 'Sent = ' + r.bytesSent return fail = (error) -> alert 'An error has occurred: Code = ' + error.code console.log 'upload error source ' + error.source console.log 'upload error target ' + error.target return options = new FileUploadOptions options.fileKey = 'file' options.fileName = fileURL.substr(fileURL.lastIndexOf('/') + 1) options.mimeType = 'text/plain' options.httpMethod = 'PUT' options.headers = 'Content-Type': "text/plain" - params = {} - params.filename = options.fileName - params.sessionId = sessionId - options.params = params ft = new FileTransfer - ft.upload fileURL, encodeURI(Meteor.absoluteUrl()+'/cfs/files/empaticaRecords?filename='+options.fileName), win, fail, options + ft.upload fileURL, encodeURI(Meteor.absoluteUrl()+'/cfs/files/physioRecords/'+fileId+'/'), win, fail, options diff --git a/app/client/views/empatica/empatica_recorder.html b/app/client/views/empatica/empatica_recorder.html index 388af3d..624f52f 100644 --- a/app/client/views/empatica/empatica_recorder.html +++ b/app/client/views/empatica/empatica_recorder.html @@ -1,78 +1,82 @@ <template name="empaticaRecorder"> - {{#unless isAuthenticated}} - {{#if isAuthenticating}} - <i class="fa fa-spinner fa-spin"></i> - Authenticating Empatica... - {{else}} - <span class="text-danger"> - {{authenticationError}} - </span> - <br> - <br> - <button id="authenticate" class="btn btn-default"> - Authenticate - </button> - {{/if}} + {{#if isUsedForAnotherSession}} + empatica data is recorded for another visit. You need to end this session before you can record here. {{else}} - {{#unless isConnected}} - {{#if isDiscovering}} + {{#unless isAuthenticated}} + {{#if isAuthenticating}} <i class="fa fa-spinner fa-spin"></i> - Discovering devices... + Authenticating Empatica... {{else}} - {{#if isConnecting}} + <span class="text-danger"> + {{authenticationError}} + </span> + <br> + <br> + <button id="authenticate" class="btn btn-default"> + Authenticate + </button> + {{/if}} + {{else}} + {{#unless isConnected}} + {{#if isDiscovering}} <i class="fa fa-spinner fa-spin"></i> - Connecting devices... + Discovering devices... {{else}} - <button id="discoverDevices" class="btn btn-default"> - Discover devices <i class="fa fa-wifi"></i> + {{#if isConnecting}} + <i class="fa fa-spinner fa-spin"></i> + Connecting devices... + {{else}} + <button id="discoverDevices" class="btn btn-default"> + Discover devices <i class="fa fa-wifi"></i> + </button> + {{/if}} + {{/if}} + <h5>Devices</h5> + {{#if discoveredDevices}} + <table class="table devicesTable"> + {{#each discoveredDevices}} + <tr> + <td>{{this}}</td> + <!-- <td><input type="checkbox"></input></td> --> + </tr> + {{/each}} + </table> + <br> + <button id="connectDevices" class="btn btn-default"> + connect devices <i class="fa fa-wifi"></i> </button> + {{else}} + no devices found! {{/if}} - {{/if}} - <h5>Devices</h5> - {{#if discoveredDevices}} - <table class="table devicesTable"> - {{#each discoveredDevices}} - <tr> - <td>{{this}}</td> - <!-- <td><input type="checkbox"></input></td> --> - </tr> - {{/each}} - </table> - <br> - <button id="connectDevices" class="btn btn-default"> - connect devices <i class="fa fa-wifi"></i> - </button> {{else}} - no devices found! - {{/if}} - {{else}} - {{#unless isRecording}} - <button id="startRecording" class="btn btn-warning"> - record physiological data <i class="fa fa-heartbeat"></i> - </button> - <br> - <br> - <button id="disconnectDevices" class="btn btn-default"> - disconnect devices - </button> - {{else}} - <button id="stopRecording" class="btn btn-warning"> - stop recording <i class="fa fa-heartbeat"></i> - </button> - <br> - <br> - <span class="text-success"> - Recording physiological data... - </span> + {{#unless isRecording}} + <button id="startRecording" class="btn btn-warning"> + record physiological data <i class="fa fa-heartbeat"></i> + </button> + <br> + <br> + <button id="disconnectDevices" class="btn btn-default"> + disconnect devices + </button> + {{else}} + <button id="stopRecording" class="btn btn-warning"> + stop recording <i class="fa fa-heartbeat"></i> + </button> + <br> + <br> + <span class="text-success"> + Recording physiological data... + </span> + {{/unless}} {{/unless}} {{/unless}} - {{/unless}} + {{/if}} <!-- <br> <br> <button id="listRecords" class="btn btn-warning"> list records </button> --> </template> diff --git a/app/client/views/patients/patient.coffee b/app/client/views/patients/patient.coffee index b61fb4f..8842d96 100644 --- a/app/client/views/patients/patient.coffee +++ b/app/client/views/patients/patient.coffee @@ -1,104 +1,101 @@ Template.patient.created = -> @subscribe "studyForPatient", @data._id @subscribe "studyDesignForPatient", @data._id @subscribe "visitsForPatient", @data._id Template.patient.helpers tabs: -> tabs = [ title: "Visits" template: "patientVisits" ] if @activeVisitId? tabs.push title: "active visit" template: "patientVisit" tabs #this tab tabClasses: -> tab = Session.get("patientTabTemplate") if @template is tab return "active" "" template: -> tab = Session.get("patientTabTemplate") if !tab? or !@activeVisitId tab = "patientVisits" tab numVisits: -> Visits.find( patientId: @_id ).count() Template.patient.events "click .switchTab": (evt) -> Session.set("patientTabTemplate", @template) false Template.patientVisits.helpers visits: -> studyDesign = @studyDesign() if studyDesign? visits = @studyDesign().visits visits Template.patientVisits.events "click .openVisit": (evt) -> visit = Visits.findOne patientId: @patient._id designVisitId: @visit._id unless visit? #we copy the data here from the visit template to #an actuall existing visit here #TODO cleanup copy visit = patientId: @patient._id designVisitId: @visit._id title: @visit.title questionnaireIds: @visit.questionnaireIds recordPhysicalData: @visit.recordPhysicalData id = Visits.insert visit visit = Visits.findOne id Patients.update @patient._id, $set: activeVisitId: visit._id Session.set("patientTabTemplate", "patientVisit") false Template.patientVisit.created = -> - @subscribe "empaticaRecordsForVisit", @data.activeVisitId + @subscribe "physioRecordsForVisit", @data.activeVisitId @subscribe "questionnaires" Template.patientVisit.helpers visit: -> Visits.findOne @activeVisitId #this visit questionnaires: -> qIds = @questionnaireIds or [] Questionnaires.find _id: {$in: qIds} #this visit showEmpaticaRecorder: -> @recordPhysicalData and Meteor.isCordova #this visit empaticaSessionId: -> - #TODO add a dedicated empaticaSessionId to visit - #and use it here @_id #this visit - empaticaRecords: -> - #TODO use empaticaSessionId - EmpaticaRecords.find() - # sessionId: @activeVisitId - #) + physioRecords: -> + PhysioRecords.find + 'metadata.visitId': @_id + diff --git a/app/client/views/patients/patient.html b/app/client/views/patients/patient.html index ee97933..f19574f 100644 --- a/app/client/views/patients/patient.html +++ b/app/client/views/patients/patient.html @@ -1,126 +1,126 @@ <template name="patient"> <div class="patient"> <br> <div class="row profile"> <div class="col-xs-12 col-md-3 col-lg-2"><!-- col-md-push-9 text-right"> --> <div class="well"> <table class="table"> <tr> <th> <img src="{{identicon _id size=10 scale=10}}"> </th> <td> <p><b>{{id}}</b></p> <p>{{hrid}}</p> </td> </tr> <tr> <th>Created at</th> <td>{{fullDateTime createdAt}}</td> </tr> <tr> <th>Study</th> <td>{{study.title}}</td> </tr> <tr> <th>Visits</th> <td>{{numVisits}}</td> </tr> </table> </div> </div> <div class="col-xs-12 col-md-9 col-lg-10"><!-- col-md-pull-3"> --> <ul class="nav nav-tabs"> {{#each tabs}} <li role="presentation" class={{tabClasses}}> <a href="" class="switchTab" data-id={{../_id}}> {{title}} </a> </li> {{/each}} </ul> <div id="studyPage"> {{> Template.dynamic template=template data=this}} </div> </div> </div> </div> </template> <template name="patientVisits"> <div id="visits"> <div id="timeline"></div> <div id="hoverRes"> <div class="coloredDiv"></div> <div id="name"></div> <div id="scrolled_date"></div> </div> </div> <table class="table studyDesignTable"> <thead> <tr> <th></th> </tr> </thead> <tbody> {{#each visits}} <tr> {{> patientVisitsTd visit=this patient=..}} </tr> {{/each}} </tbody> </table> </template> <template name="patientVisitsTd"> <td>{{visit.title}}</td> <td class="openVisit"><i class="fa fa-lg fa-play-circle-o"></i></td> </template> <template name="patientVisit"> <div id="visit"> {{#with visit}} <h3> {{title}} <!-- <small>{{_id}}</small> --> </h3> <div class="row"> <div class="col-xs-12 col-md-6"> <h4>Questionnaires <i class="fa fa-file-text-o"></i></h4> <table class="table"> {{#each questionnaires}} <tr> <th>{{title}}</th> <td><i class="fa fa-lg fa-pencil-square-o"></i></td> </tr> {{else}} No questionnaires are scheduled for this visit. {{/each}} </table> </div> <div class="col-xs-12 col-md-6"> - <h4>Physical data <i class="fa fa-heartbeat"></i></h4> + <h4>Physiological data <i class="fa fa-heartbeat"></i></h4> {{#if recordPhysicalData}} {{#if showEmpaticaRecorder}} {{> empaticaRecorder sessionId=empaticaSessionId}} {{/if}} <h5>Records</h5> <table class="table"> - {{#each empaticaRecords}} + {{#each physioRecords}} <tr> - <td>{{name}}</td> - <td>{{size}}B</td> + <td>{{metadata.sensor}}</td> + <td>{{fileSizeSani size}}</td> <td><a href="{{this.url}}" target="_blank"><i class="fa fa-download"></i></a></td> </tr> {{else}} no records found. {{/each}} </table> {{else}} Recording physical data is not scheduled for this visit. {{/if}} </div> </div> {{/with}} </div> </template> diff --git a/app/lib/collections/empatica_records.coffee b/app/lib/collections/empatica_records.coffee deleted file mode 100644 index ada6438..0000000 --- a/app/lib/collections/empatica_records.coffee +++ /dev/null @@ -1,20 +0,0 @@ -@EmpaticaRecords = new (FS.Collection)('empaticaRecords', - stores: [ - new (FS.Store.FileSystem)('empaticaRecords', - path: '~/empatica_records' - ) - ] - beforeWrite: (fileObj) -> - console.log fileObj -) - -#FIXME -EmpaticaRecords.allow - insert: (userId, doc) -> - true - update: (userId, doc, fieldNames, modifier) -> - true - remove: (userId, doc) -> - true - download: (userId, doc) -> - true diff --git a/app/lib/collections/physio_records.coffee b/app/lib/collections/physio_records.coffee new file mode 100644 index 0000000..57d593d --- /dev/null +++ b/app/lib/collections/physio_records.coffee @@ -0,0 +1,37 @@ +@PhysioRecords = new (FS.Collection)('physioRecords', + stores: [ + new (FS.Store.FileSystem)('physioRecords', + path: '~/physio_records' + ) + ] + beforeWrite: (fileObj) -> + console.log fileObj + name = "#{fileObj.metadata.visitId}_#{fileObj.metadata.sensor}.csv" + { + name: name + extension: 'csv' + type: 'text/csv' + } +) + +#FIXME +allowPhysioRecordAccess = (userId, doc) -> + if doc.metadata? and doc.metadata.visitId? + visit = Visits.findOne doc.metadata.visitId + if visit? + patient = Patients.findOne visit.patientId + if patient? + if Roles.userIsInRole(userId, ['admin']) or + (Roles.userIsInRole(userId, 'therapist') and patient.therapistId is userId) + return true + false + +PhysioRecords.allow + insert: (userId, doc) -> + allowPhysioRecordAccess(userId, doc) + update: (userId, doc, fieldNames, modifier) -> + allowPhysioRecordAccess(userId, doc) + remove: (userId, doc) -> + allowPhysioRecordAccess(userId, doc) + download: (userId, doc) -> + allowPhysioRecordAccess(userId, doc) diff --git a/app/server/publications.coffee b/app/server/publications.coffee index 9b9e7a9..86a2edd 100644 --- a/app/server/publications.coffee +++ b/app/server/publications.coffee @@ -1,138 +1,139 @@ onlyIfAdmin = -> if Roles.userIsInRole(@userId, ['admin']) return true else @ready() return +onlyIfTherapist = -> + if Roles.userIsInRole(@userId, ['therapist']) + return true + else + @ready() + return + +onlyIfUser = -> + if @userId + return true + else + @ready() + return + +################################################# Meteor.publish "therapists", -> return unless onlyIfAdmin.call(@) Meteor.users.find( roles: "therapist" , fields: _id: 1 username: 1 emails: 1 profile: 1 roles: 1 status: 1 createdAt: 1 ) Meteor.publish "users", -> - return unless onlyIfAdmin.call(@) + return unless onlyIfAdmin.call(@) Meteor.users.find( {}, fields: _id: 1 username: 1 emails: 1 profile: 1 roles: 1 status: 1 createdAt: 1 ) Meteor.publish "userProfiles", -> + return unless onlyIfAdmin.call(@) Meteor.users.find( {}, fields: _id: 1 username: 1 emails: 1 profile: 1 ) Meteor.publish "studies", -> return unless onlyIfAdmin.call(@) Studies.find() Meteor.publish "study", (_id) -> return unless onlyIfAdmin.call(@) Studies.find(_id: _id) Meteor.publish "studyForPatient", (_id) -> if Roles.userIsInRole(@userId, ['admin']) patient = Patients.findOne _id: _id if patient? return Studies.find _id: patient.studyId else if Roles.userIsInRole(@userId, ['therapist']) patient = Patients.findOne _id: _id therapistId: @userId if patient? return Studies.find _id: patient.studyId @ready() Meteor.publish "studyDesignForPatient", (_id) -> patient = Patients.findOne _id: _id if patient? studyDesign = StudyDesigns.find studyId: patient.studyId if Roles.userIsInRole(@userId, ['admin']) or (Roles.userIsInRole(@userId, 'therapist') and patient.therapistId is @userId) return studyDesign @ready() Meteor.publish "studyDesignsForStudy", (studyId) -> return unless onlyIfAdmin.call(@) StudyDesigns.find(studyId: studyId) Meteor.publish "patients", -> if Roles.userIsInRole(@userId, ['admin']) return Patients.find() else if Roles.userIsInRole(@userId, ['therapist']) return Patients.find therapistId: @userId else @ready() return Meteor.publish "patientsForStudy", (studyID) -> return unless onlyIfAdmin.call(@) Patients.find studyId: studyID Meteor.publish "visitsForPatient", (patientId) -> patient = Patients.findOne patientId if patient? if Roles.userIsInRole(@userId, ['admin']) or (Roles.userIsInRole(@userId, 'therapist') and patient.therapistId is @userId) return Visits.find patientId: patientId @ready() -Meteor.publish "empaticaRecordsForVisit", (visitId) -> +Meteor.publish "physioRecordsForVisit", (visitId) -> visit = Visits.findOne visitId if visit? patient = Patients.findOne visit.patientId if patient? if Roles.userIsInRole(@userId, ['admin']) or (Roles.userIsInRole(@userId, 'therapist') and patient.therapistId is @userId) - #use visit.empaticaSessionId - return EmpaticaRecords.find() - # sessionId: visit._id + return PhysioRecords.find + 'metadata.visitId': visit._id @ready() ##################################### -onlyIfTherapist = -> - if Roles.userIsInRole(@userId, ['therapist']) - return true - else - @ready() - return - -##################################### -onlyIfUser = -> - if @userId - return true - else - @ready() - return Meteor.publish "questionnaires", -> return unless onlyIfUser.call(@) Questionnaires.find() Meteor.publish "questions", -> return unless onlyIfUser.call(@) Questions.find() Meteor.publish "questionsForQuestionnaire", (questionnaireId)-> return unless onlyIfUser.call(@) Questions.find questionnaireId: questionnaireId