Commit e24f4bf0 authored by Jesper Zedlitz's avatar Jesper Zedlitz
Browse files

Merge branch 'master' into jze

parents f4aae7f7 07859796
# Data Entry System (DES)
DES is a web-based collaborative system to transcribe serial historic sources to structured data. The web-based system runs completely in the web browser without additional plug-ins. Its key feature is the fact that data entry is performed directly on scanned images: The scan is used as background image of the browser window; it is overlaid by the entry mask as well as text boxes with already transcribed data. The system has been successfully used to transcribe more than 31,000 pages from the German WW1 casualty lists to structured data resulting in more than 8.5 million entries.
You can access CompGen's productive installation at http://des.genealogy.net
## Testing
We are using [BrowserStack](https://www.browserstack.com) for end-to-end tests. Here is the result of the last test: [![BrowserStack Status](https://www.browserstack.com/automate/badge.svg?badge_key=VUVzY2M2NzZjMlBUV3RZOC9MN2tXUFNDZkV1eWJRQUt6K2tYbTE3ajMwYz0tLTZjZ1JZUFZmTmRNNmhuZ0wrcWxUQlE9PQ==--5ea8e3624c3a45a135fccf0d527207cec69018e1)](https://www.browserstack.com/automate/public-build/VUVzY2M2NzZjMlBUV3RZOC9MN2tXUFNDZkV1eWJRQUt6K2tYbTE3ajMwYz0tLTZjZ1JZUFZmTmRNNmhuZ0wrcWxUQlE9PQ==--5ea8e3624c3a45a135fccf0d527207cec69018e1)
\ No newline at end of file
......@@ -15,7 +15,7 @@ modules = {
resource url: '/css/style.css'
}
scanGov {
dependsOn 'jquery-ui', 'contextmenu', 'bootstrap', 'dragscroll'
dependsOn 'jquery-ui', 'contextmenu', 'bootstrap', 'dragscroll', 'leaflet'
resource url: '/js/gov.js'
resource url: '/css/style.css'
}
......
......@@ -4,12 +4,14 @@ import net.genealogy.des.*
import net.genealogy.des.decaptcha.MyKey
import net.genealogy.des.decaptcha.Snippet
import net.genealogy.des.decaptcha.SnippetService
import net.genealogy.des.mobile.DataAcquisition
import net.genealogy.user.LocalRole
import net.genealogy.user.LocalUser
import org.apache.commons.io.IOUtils
import org.apache.commons.lang.math.NumberUtils
import org.apache.commons.pool2.impl.GenericObjectPoolConfig
import org.elasticsearch.action.admin.indices.create.CreateIndexRequestBuilder
import org.elasticsearch.action.admin.indices.create.CreateIndexRequest
import org.elasticsearch.common.util.concurrent.UncategorizedExecutionException
import org.elasticsearch.common.xcontent.XContentBuilder
import org.elasticsearch.node.Node
import org.springframework.core.io.Resource
......@@ -51,6 +53,7 @@ class BootStrap {
createWk1Casualties()
createBreslauCivilRegister()
createGovSource()
createGovSource2()
createAustrianHungarianCasualtyLists()
createDemo()
......@@ -134,7 +137,7 @@ class BootStrap {
}
}
private File constructSnippetFile( long id ) {
private File constructSnippetFile(long id) {
int dir1 = id % 10
int dir2 = id % 100
return new File("${grailsApplication.config.thumbnailDirectory}${File.separator}snippet${File.separator}${dir1}${File.separator}${dir2}${File.separator}${id}.jpg")
......@@ -202,11 +205,18 @@ class BootStrap {
new AreaData(page: page17002, posX1: 1400, posX2: 4000, posY1: 310, posY2: 6070, value02: 'Sachsen 372').save()
new AreaData(page: page17002, posX1: 150, posX2: 1160, posY1: 1500, posY2: 6030, value02: 'Sachsen 372').save()
/*
* Configure mobile data acquisition
*/
new DataAcquisition(page: page17000, field: 'value04').save()
new DataAcquisition(page: page17001, field: 'value04').save()
new DataAcquisition(page: page17002, field: 'value04').save()
/*
* Prepare snippets
*/
File csvFile = new File("test/integration/snippets/data.txt")
for (String line : csvFile.readLines()) {
for (String line : csvFile.readLines()) {
String[] data = line.split("\t")
int entryId = NumberUtils.toInt(data[0])
......@@ -220,7 +230,7 @@ class BootStrap {
}
s.save(flush: true, failOnError: true)
File snippetFile = constructSnippetFile(entryId)
File snippetFile = constructSnippetFile(entryId)
snippetFile.parentFile.mkdirs()
IOUtils.copy(new FileInputStream("test/integration/snippets/${entryId}.jpg"), new FileOutputStream(snippetFile))
}
......@@ -360,21 +370,22 @@ class BootStrap {
value14Name: 'Korrektur', shortTitle: 'vlöu', visible: true,
pageValue01Name: 'Ausgabe', pageValue02Name: 'Datum', pageValue03Name: '', pageValue04Name: '',
pageValue05Name: '', infoURL: '', editionGuidelinesURL: '', admins: [LocalUser.get(130238)],
clientVersion: 1, inputFormMode2: new File("test/integration/verlustliste2.html").text,
javascriptMode2: new File('test/integration/verlustliste2.js').text ).save()
clientVersion: 1, inputFormMode2: new File("test/integration/verlustliste2.html").text,
javascriptMode2: new File('test/integration/verlustliste2.js').text).save()
Page page = new Page(list:project, pageName: '175_06', fileName: 'http://files.genealogy.net/verlustlisten/oesterreich/175/v-005.jpg', pageNr: 1, status: 'OPEN').save()
Page page = new Page(list: project, pageName: '175_06', fileName: 'http://files.genealogy.net/verlustlisten/oesterreich/175/v-005.jpg', pageNr: 1, status: 'OPEN').save()
File csvFile = new File("test/integration/verlustliste.csv")
BufferedReader br = new BufferedReader(new FileReader(csvFile))
String line = br.readLine()
while( line != null ) {
while (line != null) {
String[] data = line.split("\t")
Entry e = new Entry(page: page)
e.incomplete = data[0].toBoolean()
e.posX = data[1].toInteger()
e.posY = data[2].toInteger()
if (data.length > 3) e.lastName = data[3]
if (data.length > 4) e.firstName = data[4]
if (data.length > 5) e.value02 = data[5]
......@@ -425,7 +436,7 @@ class BootStrap {
private void insertTestEntries(Page page) {
File csvFile = new File("test/integration/vl-entries.csv")
for (String line : csvFile.readLines()) {
for (String line : csvFile.readLines()) {
String[] data = line.split("\t")
int pageNr = NumberUtils.toInt(data[0])
if (pageNr == page.pageNr) {
......@@ -443,8 +454,18 @@ class BootStrap {
if (!searchService.indexExist) {
// Create index
println "Index does not exist, yet."
CreateIndexRequestBuilder cirb = searchService.getClient().admin().indices().prepareCreate(SearchService.INDEX)
cirb.execute().actionGet()
CreateIndexRequest req = new CreateIndexRequest(SearchService.INDEX)
String settings = getClass().getResourceAsStream('/elasticsearch/settings.json')?.text
req.settings(settings)
try {
searchService.getClient().admin().indices().create(req).actionGet()
} catch (UncategorizedExecutionException ignore) {
// try fallback settings (without certain Elasticsearch plugins
String fallbackSettings = getClass().getResourceAsStream('/elasticsearch/fallback-settings.json')?.text
req.settings(fallbackSettings)
searchService.getClient().admin().indices().create(req).actionGet()
}
}
File confDir = new File(getClass().getResource("/elasticsearch/mappings").file)
......@@ -465,20 +486,21 @@ class BootStrap {
def list = new List(shortTitle: 'sh', name: "GOV Schleswig-Holstein", inputForm: "<table>\n" +
" <tr>\n" +
" <td>Landkreis</td>\n" +
" <td><button id=\"delete1\" type=\"button\">&gt;</button> <input class=\"initialFocus CookieValue\" id=\"inputFormValue01\" data-language=\"deu\" data-parent=\"adm_369010\" data-type-id =\"32,36\" type=\"text\" size=\"30\" name=\"value01\"/></td>\n" +
" <td><button id=\"delete1\" type=\"button\">&gt;</button> <input class=\"initialFocus CookieValue\" id=\"inputFormValue01\" data-language=\"deu\" data-polygon=\"1\" data-parent=\"adm_369010\" data-type-id =\"32,36\" type=\"text\" size=\"30\" name=\"value01\"/></td>\n" +
" </tr>\n" +
" <tr>\n" +
" <td>Amt</td>\n" +
" <td><button id=\"delete2\" type=\"button\">&gt;</button> <input id=\"inputFormValue02\" name=\"value02\" class=\"CookieValue\" data-language=\"deu\" data-type-id=\"1,2,78,48\" data-parent=\"{value01}\" data-grandparent=\"adm_369010\" data-update-properties=\"value06,value05\" type=\"text\" size=\"30\" /></td>\n" +
" <td><button id=\"delete2\" type=\"button\">&gt;</button> <input id=\"inputFormValue02\" name=\"value02\" class=\"CookieValue\" data-language=\"deu\" data-polygon=\"1\" data-type-id=\"1,2,78,48\" data-parent=\"{value01}\" data-grandparent=\"adm_369010\" data-update-properties=\"value06,value05\" type=\"text\" size=\"30\" /></td>\n" +
" </tr>\n" +
" <tr>\n" +
" <td>Gemeinde</td>\n" +
" <td><button id=\"delete3\" type=\"button\">&gt;</button> <input id=\"inputFormValue03\" name=\"value03\" class=\"CookieValue\" data-language=\"deu\" data-type-id=\"18,14,95,85,108,109,150\" data-parent=\"{value01}\" data-grandparent=\"adm_369010\" data-update-properties=\"value06,value05\" type=\"text\" size=\"30\" name=\"value03\"/></td>\n" +
" <td><button id=\"delete3\" type=\"button\">&gt;</button> <input id=\"inputFormValue03\" name=\"value03\" class=\"CookieValue\" data-language=\"deu\" data-polygon=\"1\" data-type-id=\"18,14,95,85,108,109,150\" data-parent=\"{value01}\" data-grandparent=\"adm_369010\" data-update-properties=\"value06,value05\" type=\"text\" size=\"30\" name=\"value03\"/></td>\n" +
" </tr>\n" +
" <tr>\n" +
" <td>Wohnplatz</td>\n" +
" <td><button id=\"delete4\" type=\"button\">&gt;</button> <input id=\"inputFormValue04\" name=\"value04\" data-language=\"deu\" data-parent=\"{value03}\" data-grandparent=\"{value01}\" data-type-id=\"193,8,159,54,55,139,67,233,102,17,21,68,24,236,229,231,66,121,87,39,40,65,232,111,120,238,51,230,64,69,129\" data-update-properties=\"value05,value06,value07\" type=\"text\" size=\"30\" /></td>\n" +
" </tr>\n" +
" <tr> <td>Koordinaten</td><td><input id=\"inputFormValue09\" type=\"number\" step=\"0.0001\" class=\"geo latitude\" name=\"value09\"/>°N <input id=\"inputFormValue10\" type=\"number\" step=\"0.0001\" class=\"geo longitude\" name=\"value10\"/>°O</td></tr>" +
" <tr>\n" +
" <td>Typ</td>\n" +
" <td><select id=\"inputFormValue05\" name=\"value05\" data-property=\"type\" type=\"text\" >\n" +
......@@ -522,7 +544,7 @@ class BootStrap {
" </div>\n" +
" </td>\n" +
" </tr>\n" +
"</table>", topLevelObject: 'adm_369010', date: '1961-06-06', value01Name: 'Landkreis', value02Name: 'Amt',
"</table>", topLevelObject: 'adm_369010', date: '1961-06-06', value01Name: 'Landkreis', value02Name: 'Amt',
value03Name: 'Gemeinde', value04Name: 'Wohnplatz', value05Name: 'Typ', value06Name: 'Einwohner',
value07Name: 'PLZ', visible: true, infoURL: '', editionGuidelinesURL: '',
admins: [LocalUser.get(130238)]).save(failOnError: true)
......@@ -533,4 +555,35 @@ class BootStrap {
}
/**
* Create a demo source for entering information from topographies into the GOV.
* This is the "Gemeindelexikon für die im Reichsrate vertretenen Länder" (a municipaliy lexicon for Austria-Hungary)
* from 1900.
*/
private void createGovSource2() {
//*modified* Added attribute data-polygon and IDs for coordinates fields
def list = new List(shortTitle: 'glrr3', name: "GOV Österreich-Ungarn Salzburg", inputForm: '<table>' +
' <tr> <td>Bezirkshauptmannschaft</td> <td><button id="delete1" tabindex="-1" type="button">&gt;</button> <input class="CookieValue" data-language="deu" data-polygon="1" id="inputFormValue01" data-parent="object_215345" data-type-id ="146" type="text" size="30" data-update-properties="value06,value05,value08" name="value01"/></td> </tr> ' +
' <tr> <td>Gerichtsbezirk</td> <td><button id="delete2" tabindex="-1" type="button">&gt;</button> <input id="inputFormValue02" name="value02" class="CookieValue" data-language="deu" data-polygon="1" data-type-id="19" data-parent="{value01}" data-grandparent="object_215345" data-update-properties="value06,value05,value08" type="text" size="30" /></td> </tr>' +
' <tr> <td>Ortsgemeinde</td> <td><button id="delete3" tabindex="-1" type="button">&gt;</button> <input id="inputFormValue03" name="value03" class="CookieValue" data-language="deu" data-polygon="1" data-type-id="18,14,95,85,150,180" data-parent="{value01}" data-grandparent="adm_368374" data-update-properties="value05,value06,value07,value08" type="text" size="30" /></td> </tr>' +
' <tr> <td>Ortschaft</td> <td><button id="delete4" tabindex="-1" type="button">&gt;</button> <input id="inputFormValue04" name="value04" class="initialFocus" data-language="deu" data-parent="{value03}" data-grandparent="{value01}" data-type-id="193,8,159,54,55,139,67,233,102,17,21,68,24,236,229,231,66,121,87,39,40,65,232,111,120,238,51,230,64,69,129" data-update-properties="value05,value06,value07,value08" type="text" size="30" /></td> </tr>' +
' <tr> <td>Koordinaten</td> <td><input id="inputFormValue09" type="number" step="0.0001" class="geo latitude" name="value09"/>°N <input id="inputFormValue10" type="number" step="0.0001" class="geo longitude" name="value10"/>°O</td></tr>' +
' <tr> <td>Typ</td> <td><select id="inputFormValue08" name="value08" data-property="type" type="text" > <option></option> <optgroup label="Wohnplatz"> <option value="55" >Dorf</option> <option value="21" >Gut</option> <option value="68" >Hauptort</option> <option value="17" >Haus</option> <option value="236">Häuser</option> <option value="229">Häusergruppe</option> <option value="24" >Hof</option> <option value="231">Höfe</option> <option value="66" >Kirchdorf</option> <option value="39" >Ort</option> <option value="40" >Ortsteil</option> <option value="181" >Rotte</option> <option value="120">Siedlung</option> <option value="51" >Stadtkern</option> <option value="54" >Stadtteil</option> <option value="230">Streusiedlung</option> </optgroup> <optgroup label="Verwaltung"> <option value="146" >Bezirkshauptmannschaft</option> <option value="18" >Gemeinde</option> <option value="19" >Gerichtsbezirk</option> <option value="180">Marktgemeinde</option> <option value="150">Stadt</option> </optgroup> </select></td> </tr>' +
' <tr> <td>Areal</td> <td><input id="inputFormValue05" name="value05" data-property="area" type="text" size="30" /></td> </tr> ' +
' <tr> <td>Bevölkerung</td> <td><input id="inputFormValue06" name="value06" data-property="population" type="text" size="30" /></td> </tr> ' +
' <tr> <td>Häuser</td> <td><input id="inputFormValue07" name="value07" data-property="buildings" type="text" size="30" /></td> </tr> ' +
' <tr> <td colspan="2"> <div align="right"> <input id="inputFormSave" type="submit" value="Speichern"/> <input id="inputFormCancel" type="button" value="Abbrechen"/> </div> </td> </tr></table>', topLevelObject: 'object_215345', date: '1900-12-31', value01Name: 'Bezirkshauptmannschaft',
value02Name: 'Gerichtsbezirk', value03Name: 'Ortsgemeinde', value04Name: 'Ortschaft', value05Name: 'Areal', value06Name: 'Bevölkerung',
value07Name: 'Häuser', value08Name: 'Typ', visible: true, infoURL: '', editionGuidelinesURL: '',
admins: [LocalUser.get(130238)]).save(failOnError: true)
new Page(id: 61, pageNr: 61, pageName: "54", fileName: "http://files.genealogy.net/DES/Gemeindelexikon_Reichsrat/03-0061.jpg",
list: list, width: -1, height: -1, status: 'OPEN', statusArea: 'OPEN').save(failOnError: true)
new Page(id: 62, pageNr: 62, pageName: "55", fileName: "http://files.genealogy.net/DES/Gemeindelexikon_Reichsrat/03-0062.jpg",
list: list, width: -1, height: -1, status: 'LOCKED', statusArea: 'LOCKED').save(failOnError: true)
new Page(id: 63, pageNr: 63, pageName: "56", fileName: "http://files.genealogy.net/DES/Gemeindelexikon_Reichsrat/03-0063.jpg",
list: list, width: -1, height: -1, status: 'OPEN', statusArea: 'OPEN').save(failOnError: true)
}
}
......@@ -44,6 +44,7 @@ grails.project.dependency.resolution = {
compile 'org.elasticsearch:elasticsearch:1.7.3'
compile 'org.imgscalr:imgscalr-lib:4.2'
compile 'org.jodd:jodd-core:3.6.7'
compile group: 'org.json', name: 'json', version: '20180130'
test 'com.fiftyonred:mock-jedis:0.3.1'
test "org.gebish:geb-junit4:$gebVersion"
......
{
"index": {
"analysis": {
"analyzer": {
"latin": {
"tokenizer": "keyword",
"filter": []
}
}
}
}
}
{
"index": {
"analysis": {
"analyzer": {
"latin": {
"tokenizer": "keyword",
"filter": [
"icu_folding"
]
}
}
}
}
}
......@@ -8,13 +8,15 @@ import org.apache.commons.lang.StringUtils
class ListController {
def springSecurityService
SecurityService securityService
def statisticsService
@Secured(['ROLE_ADMIN'])
def create() {
def listUserPrivacyStatement = securityService.listUsersWithRole('ROLE_PRIVACY_STATEMENT')
def listUserPrivacyStatement = securityService.listUsersWithRole('ROLE_PRIVACY_STATEMENT')
def listUserAdmin = securityService.listUsersWithRole('ROLE_ADMIN')
return [listInstance: new List(), listUserPrivacyStatement:listUserPrivacyStatement,listUserAdmin:listUserAdmin]
return [listInstance: new List(), listUserPrivacyStatement: listUserPrivacyStatement, listUserAdmin: listUserAdmin]
}
@Secured(['ROLE_ADMIN'])
......@@ -43,10 +45,10 @@ class ListController {
} else {
securityService.enforceProjectAdmin(listInstance)
def listUserPrivacyStatement = securityService.listUsersWithRole('ROLE_PRIVACY_STATEMENT')
def listUserPrivacyStatement = securityService.listUsersWithRole('ROLE_PRIVACY_STATEMENT')
def listUserAdmin = securityService.listUsersWithRole('ROLE_ADMIN')
return [listInstance: listInstance, listUserPrivacyStatement:listUserPrivacyStatement,listUserAdmin:listUserAdmin]
return [listInstance: listInstance, listUserPrivacyStatement: listUserPrivacyStatement, listUserAdmin: listUserAdmin]
}
}
......@@ -57,7 +59,7 @@ class ListController {
if (listInstance) {
securityService.enforceProjectAdmin(listInstance)
def listUserPrivacyStatement = securityService.listUsersWithRole('ROLE_PRIVACY_STATEMENT')
def listUserPrivacyStatement = securityService.listUsersWithRole('ROLE_PRIVACY_STATEMENT')
def listUserAdmin = securityService.listUsersWithRole('ROLE_ADMIN')
if (params.version) {
......@@ -67,7 +69,7 @@ class ListController {
listInstance.errors.rejectValue("version", "default.optimistic.locking.failure", [message(code: 'list.label', default: 'List')] as Object[], "Another user has updated this List while you were editing")
render(view: "edit", model: [listInstance: listInstance, listUserPrivacyStatement:listUserPrivacyStatement,listUserAdmin:listUserAdmin])
render(view: "edit", model: [listInstance: listInstance, listUserPrivacyStatement: listUserPrivacyStatement, listUserAdmin: listUserAdmin])
return
}
}
......@@ -82,7 +84,7 @@ class ListController {
flash.message = "${message(code: 'default.updated.message', args: [message(code: 'list.label', default: 'List'), listInstance.id])}"
redirect(action: "show", id: listInstance.id)
} else {
render(view: "edit", model: [listInstance: listInstance, listUserPrivacyStatement:listUserPrivacyStatement,listUserAdmin:listUserAdmin])
render(view: "edit", model: [listInstance: listInstance, listUserPrivacyStatement: listUserPrivacyStatement, listUserAdmin: listUserAdmin])
}
} else {
flash.message = "${message(code: 'default.not.found.message', args: [message(code: 'list.label', default: 'List'), params.id])}"
......@@ -169,7 +171,6 @@ class ListController {
}
}
/**
* Return the entry form as a PNG image.
......@@ -201,11 +202,79 @@ class ListController {
def overview() {
List project = List.findByShortTitle(params.listId)
if (project) {
return [project: project]
return [listInstance: project, project: project]
} else {
redirect(controller: 'start', action: 'index')
}
}
/**
* Calculate the users that contributed most entries.
*/
def leaderboard() {
List project = List.findByShortTitle(params.listId)
if (project) {
def user = springSecurityService.principal
int uid = (user instanceof String) ? 0 : user.id
Date firstDayOfCurrentWeek = new Date()
firstDayOfCurrentWeek.set(hourOfDay: 0, minute: 0, second: 0)
firstDayOfCurrentWeek = firstDayOfCurrentWeek - firstDayOfCurrentWeek[Calendar.DAY_OF_WEEK] + 2
Date firstDayOfCurrentMonth = new Date()
firstDayOfCurrentMonth.set(date: 1, hourOfDay: 0, minute: 0, second: 0)
Date firstDayOfCurrentYear = new Date()
firstDayOfCurrentYear.set(month: 0, date: 1, hourOfDay: 0, minute: 0, second: 0)
Date today = new Date()
today.set(hourOfDay: 0, minute: 0, second: 0)
Date weekAgo = new Date() - 7
return [listInstance: project, project : project, user: uid,
last7Days: statisticsService.getLeaderboard(project, weekAgo, uid, 10),
today: statisticsService.getLeaderboard(project, today, uid, 10),
thisWeek : statisticsService.getLeaderboard(project, firstDayOfCurrentWeek, uid, 10),
thisYear : statisticsService.getLeaderboard(project, firstDayOfCurrentYear, uid, 10),
thisMonth: statisticsService.getLeaderboard(project, firstDayOfCurrentMonth, uid, 10)]
} else {
redirect(controller: 'start', action: 'index')
}
}
@Secured(['ROLE_USER'])
def personalHighscore() {
List project = List.findByShortTitle(params.listId)
if (project) {
int uid = springSecurityService.principal.id
Date firstDayOfCurrentWeek = new Date()
firstDayOfCurrentWeek.set(hourOfDay: 0, minute: 0, second: 0)
firstDayOfCurrentWeek = firstDayOfCurrentWeek - firstDayOfCurrentWeek[Calendar.DAY_OF_WEEK] + 2
Date firstDayOfCurrentMonth = new Date()
firstDayOfCurrentMonth.set(date: 1, hourOfDay: 0, minute: 0, second: 0)
Date firstDayOfCurrentYear = new Date()
firstDayOfCurrentYear.set(month: 0, date: 1, hourOfDay: 0, minute: 0, second: 0)
Date today = new Date()
today.set(hourOfDay: 0, minute: 0, second: 0)
Date weekAgo = new Date() - 7
return [listInstance: project, project : project, user: uid,
last7Days: statisticsService.getPersonalLeaderboard(project, weekAgo, uid),
today: statisticsService.getPersonalLeaderboard(project, today, uid),
thisWeek : statisticsService.getPersonalLeaderboard(project, firstDayOfCurrentWeek, uid),
thisYear : statisticsService.getPersonalLeaderboard(project, firstDayOfCurrentYear, uid),
thisMonth: statisticsService.getPersonalLeaderboard(project, firstDayOfCurrentMonth, uid)]
} else {
redirect(controller: 'start', action: 'index')
}
}
}
......@@ -6,6 +6,7 @@ import grails.converters.JSON
import grails.plugins.springsecurity.Secured
import net.genealogy.des.decaptcha.Snippet
import net.genealogy.des.mobile.*
import org.json.JSONArray
class MobileController {
def springSecurityService
......@@ -117,41 +118,73 @@ class MobileController {
*/
def nextPage() {
Page page
String clientId = params.clientId
String field = 'value04'
if (!clientId) {
response.contentType = 'application/json;charset=utf-8'
render '{"error":"missing clientId}'
return
}
if (params.id) {
page = Page.get(params.id)
} else {
page = Page.get(3935)
// Find a free page for the user. Try to find a page without any editors.
DataAcquisition da = DataAcquisition.findByFieldAndSeenByUsers(field, '')
if (da) {
page = da.page
} else {
page = Page.get(3935)
}
}
response.contentType = 'application/json'
OutputStream out = response.outputStream
// Reserve the page for the user.
DataAcquisition da = DataAcquisition.findByFieldAndPage(field, page)
if( !da) {
log.error("No DataAcquisition object for page "+page)
response.status = 500
render '{"error": "missing DataAcquisition object "}'
return
}
if (!da.seenByUsers.contains(clientId)) {
da.seenByUsers = da.seenByUsers + ' ' + clientId
}
da.save()
response.contentType = 'application/json;charset=utf-8'
PrintWriter out = response.getWriter()
out.println('{ "page": {')
out.println("\"imageURL\": \"${page.fileName}\",")
out.println("\"id\": \"${page.id}\"")
out.println('}, "data": {')
out.print(' "id": [')
boolean first = true
boolean first = true
for (Entry entry : page.entries) {
if( first) first= false else out.print(',')
if (first) first = false else out.print(',')
out.print(entry.id)
}
out.print('],"x":[')
first = true
for (Entry entry : page.entries) {
if( first) first= false else out.print(',')
if (first) first = false else out.print(',')
out.print(entry.posX)
}
out.print('],"y":[');
first = true
for (Entry entry : page.entries) {
if( first) first= false else out.print(',')
if (first) first = false else out.print(',')
out.print(entry.posY)
}
out.print('],"value1":[');
first = true
for (Entry entry : page.entries) {
if( first) first= false else out.print(',')
if (first) first = false else out.print(',')
out.print('"')
out.print(entry.lastName)
out.print('"')
......@@ -159,7 +192,7 @@ class MobileController {
out.print('],"value2":[');
first = true
for (Entry entry : page.entries) {
if( first) first= false else out.print(',')
if (first) first = false else out.print(',')
out.print('"')
out.print(entry.firstName)
out.print('"')
......@@ -167,7 +200,7 @@ class MobileController {
out.print('],"value3":[');
first = true
for (Entry entry : page.entries) {
if( first) first= false else out.print(',')
if (first) first = false else out.print(',')
out.print('"')
out.print(entry.value01)
out.print('"')
......@@ -185,7 +218,66 @@ class MobileController {
out.println('}')
out.flush()
}
def receiveData() {
response.contentType = 'application/json'
boolean containsErrors = false
def data = request.JSON
String clientId = data.clientId
String pageId = data.page
Page page = Page.get(pageId)
if (page) {
DataAcquisition da = DataAcquisition.findByFieldAndPage('value04', page)
if( da == null) {
log.error("No DataAcquisition object found for page "+page.id)
response.status = 500
render '{"error": "missing DataAcquisition object "}'
return
}
if (!da.seenByUsers?.contains(clientId)) {
render '{"error": "page not reserved for client"}'
return
}
JSONArray ids = data.id
JSONArray values = data.value;
for (int i = 0; i < ids.length(); i++) {
String id = ids.get(i)
String value = values.get(i)
Entry entry = Entry.get(id)
if( entry) {
if (entry.page == page) {
DataAcquisitionEntry dae = DataAcquisitionEntry.findByEntryAndClientId(entry, clientId)
if (dae) {
// Update of an existing entry.
dae.value = value
} else {
dae = new DataAcquisitionEntry()
dae.entry = entry
dae.value = value
dae.clientId = clientId
dae.save()
da.addToEntries(dae)
}
} else {
containsErrors = true
break
}
}
}
render "{\"ok\": ${!containsErrors}}"
} else {
render '{"error": "wrong page"}'
}
}
}
......@@ -4,12 +4,14 @@ import org.apache.commons.lang.StringUtils
import org.elasticsearch.action.count.CountResponse
import org.elasticsearch.action.search.SearchRequestBuilder
import org.elasticsearch.action.search.SearchResponse
import org.elasticsearch.common.unit.TimeValue
import org.elasticsearch.index.query.QueryBuilder
import org.elasticsearch.search.SearchHit
import static org.elasticsearch.index.query.QueryBuilders.*
class PreselectController {
private static final TIMEOUT = new TimeValue(1000) // 1 second
SearchService searchService
......@@ -30,7 +32,7 @@ class PreselectController {
searchRequest.setSize(20)
searchRequest.setQuery(query)