diff --git a/IntelliQ/.gitignore b/IntelliQ/.gitignore index 42b5b24..7c68061 100755 --- a/IntelliQ/.gitignore +++ b/IntelliQ/.gitignore @@ -1,8 +1,8 @@ *.iml +**/*.iml .gradle /local.properties -/.idea/workspace.xml -/.idea/libraries +/.idea/ .DS_Store /build /*/build diff --git a/IntelliQ/.idea/gradle.xml b/IntelliQ/.idea/gradle.xml index 9dbb2e9..faf06f1 100755 --- a/IntelliQ/.idea/gradle.xml +++ b/IntelliQ/.idea/gradle.xml @@ -13,14 +13,7 @@ - + diff --git a/IntelliQ/.idea/misc.xml b/IntelliQ/.idea/misc.xml index 1a3eaff..5d19981 100755 --- a/IntelliQ/.idea/misc.xml +++ b/IntelliQ/.idea/misc.xml @@ -37,7 +37,7 @@ - + diff --git a/IntelliQ/backend/build.gradle b/IntelliQ/backend/build.gradle index 7d1bf79..25e4c83 100644 --- a/IntelliQ/backend/build.gradle +++ b/IntelliQ/backend/build.gradle @@ -24,7 +24,7 @@ sourceCompatibility = JavaVersion.VERSION_1_7 targetCompatibility = JavaVersion.VERSION_1_7 dependencies { - //compile fileTree(dir: 'libs', include: ['*.jar']) + compile fileTree(dir: 'libs', include: ['*.jar']) appengineSdk 'com.google.appengine:appengine-java-sdk:1.9.38' compile group: 'com.google.apis', name: 'google-api-services-oauth2', version: 'v1-rev120-1.22.0' @@ -42,8 +42,12 @@ dependencies { compile group: 'commons-fileupload', name: 'commons-fileupload', version: '1.3.2' compile 'javax.servlet:servlet-api:2.5' -} + testCompile 'com.google.appengine:appengine-api-labs:1.9.8' + testCompile 'com.google.appengine:appengine-api-stubs:1.9.8' + testCompile 'com.google.appengine:appengine-testing:1.9.8' + testCompile 'junit:junit:4.12' +} appengine { downloadSdk = true @@ -56,3 +60,31 @@ appengine { enhanceOnBuild = true } } + +task backupDataStore << { + copy { + from 'build/exploded-app/WEB-INF/appengine-generated' + into 'datastore/' + include '**/*.xml' + include '**/*.bin' + } + println "Backed up datastore" +} + +task restoreDataStore << { + copy { + from 'datastore/' + into 'build/exploded-app/WEB-INF/appengine-generated' + include '**/*.xml' + include '**/*.bin' + } + println "Restored datastore" +} + +appengineExplodeApp.doFirst { + backupDataStore.execute() +} + +appengineExplodeApp.doLast { + restoreDataStore.execute() +} \ No newline at end of file diff --git a/IntelliQ/backend/datastore/datastore-indexes-auto.xml b/IntelliQ/backend/datastore/datastore-indexes-auto.xml new file mode 100644 index 0000000..dd4f99f --- /dev/null +++ b/IntelliQ/backend/datastore/datastore-indexes-auto.xml @@ -0,0 +1,4 @@ + + + + diff --git a/IntelliQ/backend/datastore/local_db.bin b/IntelliQ/backend/datastore/local_db.bin new file mode 100644 index 0000000..36934e1 Binary files /dev/null and b/IntelliQ/backend/datastore/local_db.bin differ diff --git a/IntelliQ/backend/libs/slackmessagebuilder.jar b/IntelliQ/backend/libs/slackmessagebuilder.jar new file mode 100644 index 0000000..dec02a1 Binary files /dev/null and b/IntelliQ/backend/libs/slackmessagebuilder.jar differ diff --git a/IntelliQ/backend/src/main/java/com/intelliq/appengine/ImageServlet.java b/IntelliQ/backend/src/main/java/com/intelliq/appengine/ImageServlet.java index 9836dec..2983ada 100644 --- a/IntelliQ/backend/src/main/java/com/intelliq/appengine/ImageServlet.java +++ b/IntelliQ/backend/src/main/java/com/intelliq/appengine/ImageServlet.java @@ -28,128 +28,128 @@ @SuppressWarnings("serial") public class ImageServlet extends HttpServlet { - private static final Logger log = Logger.getLogger(ImageServlet.class.getName()); - - @Override - public void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException, ServletException { - - String requestUrl = req.getRequestURL().toString(); - - try { - // image request pattern: .../image/[imageKeyId]/[size].jpg - - String imageSizeString = ParserHelper.getStringAfter("/", req.getPathInfo(), ParserHelper.MODE_LAST_LAST); - if (imageSizeString.contains(".png")) { - imageSizeString = ParserHelper.getStringBefore(".png", imageSizeString); - } else if (imageSizeString.contains(".jpg")) { - imageSizeString = ParserHelper.getStringBefore(".jpg", imageSizeString); - } else { - throw new Exception("Unknown file type requested"); - } - - String imageKeyIdParam = ParserHelper.getStringBefore("/" + imageSizeString, req.getPathInfo(), ParserHelper.MODE_LAST_LAST); - imageKeyIdParam = ParserHelper.getStringAfter("/", imageKeyIdParam, ParserHelper.MODE_LAST_LAST); - long imageKeyId = Long.parseLong(imageKeyIdParam); - - if (imageKeyId < 0) { - throw new Exception("Image key ID is invalid"); - } - - ImageQuery imageQuery = new ImageQuery(); - ImageEntry image = imageQuery.getImageByKeyId(imageKeyId); - - if (image.getImageType() == null || image.getImage() == null) { - // image = EntryManager.fetchImageFromUrl(image.getUrl()); - throw new Exception("Image data unavailable"); - } - - resp.setContentType(image.getImageType()); - resp.getOutputStream().write(ImageHelper.resizeImage(image.getImage(), imageSizeString)); - } catch (Exception e) { - resp.sendRedirect("/static/images/not_found.jpg"); - } - } - - @Override - public void doPost(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { - ApiResponse responseObject = new ApiResponse(); - String response = ""; - - try { - ServletFileUpload upload = new ServletFileUpload(); - res.setContentType("text/plain"); - - FileItemIterator iterator = upload.getItemIterator(req); - - ImageEntry image = new ImageEntry(); - - while (iterator.hasNext()) { - FileItemStream item = iterator.next(); - InputStream stream = item.openStream(); - - if (item.isFormField()) { - String key = item.getFieldName(); - String value = Streams.asString(stream); - log.info("Form field: " + key + ", value = " + value); - - if (key.equals("parentKeyId")) { - image.setParentKeyId(Long.parseLong(value)); - } else if (key.equals("type")) { - image.setType(Byte.parseByte(value)); - } - } else { - log.info("File: " + item.getFieldName() + ", key = " + item.getName()); - - String contentType = item.getContentType(); - - image.setImageType(contentType); - image.setImage(ByteStreams.toByteArray(stream)); - } - } - - if (image.getParentKeyId() > 0) { - - Key imageKey = ImageHelper.saveEntry(image); - image.setKey(imageKey); - - // add the new image key id to the parent business or queue - if (image.getType() == ImageEntry.TYPE_LOGO) { - BusinessEntry businessEntry = BusinessHelper.getEntryByKeyId(image.getParentKeyId()); - if (businessEntry != null) { - businessEntry.setLogoImageKeyId(image.getKey().getId()); - } else { - throw new Exception("Can't find parent business with Id: " + image.getParentKeyId()); - } - BusinessHelper.saveEntry(businessEntry); - } else { - QueueEntry queueEntry = QueueHelper.getEntryByKeyId(image.getParentKeyId()); - if (queueEntry != null) { - queueEntry.setPhotoImageKeyId(image.getKey().getId()); - } else { - throw new Exception("Can't find parent queue with Id: " + image.getParentKeyId()); - } - QueueHelper.saveEntry(queueEntry); - } - - // remove the image data to use the same object as response - image.setImage(null); - - responseObject.setContent(image); - response = responseObject.toJSON(); - } else { - throw new Exception("Invalid parentKeyId specified"); - } - } catch (Exception e) { - responseObject.setException(e); - response = responseObject.toJSON(); - e.printStackTrace(); - } - - res.setContentType("application/json"); - res.addHeader("Access-Control-Allow-Origin", "*"); + private static final Logger log = Logger.getLogger(ImageServlet.class.getName()); + + @Override + public void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException, ServletException { + + String requestUrl = req.getRequestURL().toString(); + + try { + // image request pattern: .../image/[imageKeyId]/[size].jpg + + String imageSizeString = ParserHelper.getStringAfter("/", req.getPathInfo(), ParserHelper.MODE_LAST_LAST); + if (imageSizeString.contains(".png")) { + imageSizeString = ParserHelper.getStringBefore(".png", imageSizeString); + } else if (imageSizeString.contains(".jpg")) { + imageSizeString = ParserHelper.getStringBefore(".jpg", imageSizeString); + } else { + throw new Exception("Unknown file type requested"); + } + + String imageKeyIdParam = ParserHelper.getStringBefore("/" + imageSizeString, req.getPathInfo(), ParserHelper.MODE_LAST_LAST); + imageKeyIdParam = ParserHelper.getStringAfter("/", imageKeyIdParam, ParserHelper.MODE_LAST_LAST); + long imageKeyId = Long.parseLong(imageKeyIdParam); + + if (imageKeyId < 0) { + throw new Exception("Image key ID is invalid"); + } + + ImageQuery imageQuery = new ImageQuery(); + ImageEntry image = imageQuery.getImageByKeyId(imageKeyId); + + if (image.getImageType() == null || image.getImage() == null) { + // image = EntryManager.fetchImageFromUrl(image.getUrl()); + throw new Exception("Image data unavailable"); + } + + resp.setContentType(image.getImageType()); + resp.getOutputStream().write(ImageHelper.resizeImage(image.getImage(), imageSizeString)); + } catch (Exception e) { + resp.sendRedirect("/static/images/not_found.jpg"); + } + } + + @Override + public void doPost(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { + ApiResponse responseObject = new ApiResponse(); + String response = ""; + + try { + ServletFileUpload upload = new ServletFileUpload(); + res.setContentType("text/plain"); + + FileItemIterator iterator = upload.getItemIterator(req); + + ImageEntry image = new ImageEntry(); + + while (iterator.hasNext()) { + FileItemStream item = iterator.next(); + InputStream stream = item.openStream(); + + if (item.isFormField()) { + String key = item.getFieldName(); + String value = Streams.asString(stream); + log.info("Form field: " + key + ", value = " + value); + + if (key.equals("parentKeyId")) { + image.setParentKeyId(Long.parseLong(value)); + } else if (key.equals("type")) { + image.setType(Byte.parseByte(value)); + } + } else { + log.info("File: " + item.getFieldName() + ", key = " + item.getName()); + + String contentType = item.getContentType(); + + image.setImageType(contentType); + image.setImage(ByteStreams.toByteArray(stream)); + } + } + + if (image.getParentKeyId() > 0) { + + Key imageKey = ImageHelper.saveEntry(image); + image.setKey(imageKey); + + // add the new image key id to the parent business or queue + if (image.getType() == ImageEntry.TYPE_LOGO) { + BusinessEntry businessEntry = BusinessHelper.getEntryByKeyId(image.getParentKeyId()); + if (businessEntry != null) { + businessEntry.setLogoImageKeyId(image.getKey().getId()); + } else { + throw new Exception("Can't find parent business with Id: " + image.getParentKeyId()); + } + BusinessHelper.saveEntry(businessEntry); + } else { + QueueEntry queueEntry = QueueHelper.getEntryByKeyId(image.getParentKeyId()); + if (queueEntry != null) { + queueEntry.setPhotoImageKeyId(image.getKey().getId()); + } else { + throw new Exception("Can't find parent queue with Id: " + image.getParentKeyId()); + } + QueueHelper.saveEntry(queueEntry); + } + + // remove the image data to use the same object as response + image.setImage(null); + + responseObject.setContent(image); + response = responseObject.toJSON(); + } else { + throw new Exception("Invalid parentKeyId specified"); + } + } catch (Exception e) { + responseObject.setException(e); + response = responseObject.toJSON(); + e.printStackTrace(); + } + + res.setContentType("application/json"); + res.addHeader("Access-Control-Allow-Origin", "*"); res.getWriter().write(response); res.getWriter().flush(); res.getWriter().close(); - } + } } diff --git a/IntelliQ/backend/src/main/java/com/intelliq/appengine/MergeHelper.java b/IntelliQ/backend/src/main/java/com/intelliq/appengine/MergeHelper.java index 3abd0c4..2889525 100644 --- a/IntelliQ/backend/src/main/java/com/intelliq/appengine/MergeHelper.java +++ b/IntelliQ/backend/src/main/java/com/intelliq/appengine/MergeHelper.java @@ -4,36 +4,36 @@ public class MergeHelper { - public static String mergeStrings(String existingValue, String newValue) { - if (!ParserHelper.containsAnyValue(existingValue)) { - return newValue; - } else { - return existingValue; - } - } - - public static List mergeListsOfString(List existingList, List newList) { - if (existingList == null || existingList.size() == 0) { - return newList; - } - for (String newString: newList) { - if (!existingList.contains(newString)) { - existingList.add(newString); - } - } - return existingList; - } - - public static List mergeListsOfLong(List existingList, List newList) { - if (existingList == null || existingList.size() == 0) { - return newList; - } - for (long newValue: newList) { - if (!existingList.contains(newValue)) { - existingList.add(newValue); - } - } - return existingList; - } - + public static String mergeStrings(String existingValue, String newValue) { + if (!ParserHelper.containsAnyValue(existingValue)) { + return newValue; + } else { + return existingValue; + } + } + + public static List mergeListsOfString(List existingList, List newList) { + if (existingList == null || existingList.size() == 0) { + return newList; + } + for (String newString : newList) { + if (!existingList.contains(newString)) { + existingList.add(newString); + } + } + return existingList; + } + + public static List mergeListsOfLong(List existingList, List newList) { + if (existingList == null || existingList.size() == 0) { + return newList; + } + for (long newValue : newList) { + if (!existingList.contains(newValue)) { + existingList.add(newValue); + } + } + return existingList; + } + } diff --git a/IntelliQ/backend/src/main/java/com/intelliq/appengine/ParserHelper.java b/IntelliQ/backend/src/main/java/com/intelliq/appengine/ParserHelper.java index 2d059bb..86c8379 100644 --- a/IntelliQ/backend/src/main/java/com/intelliq/appengine/ParserHelper.java +++ b/IntelliQ/backend/src/main/java/com/intelliq/appengine/ParserHelper.java @@ -12,216 +12,216 @@ public class ParserHelper { - public static final int MODE_FIRST_FIRST = 0; - public static final int MODE_FIRST_LAST = 1; - public static final int MODE_LAST_FIRST = 2; - public static final int MODE_LAST_LAST = 3; - - public static final int TOLERANCE_NONE = 0; - public static final int TOLERANCE_LOW = 1; - public static final int TOLERANCE_MEDIUM = 2; - public static final int TOLERANCE_HIGH = 3; - - public static String getStringBetween(String startIdentifier, String endIdentifier, String sourceString) throws Exception { - return getStringBetween(startIdentifier, endIdentifier, sourceString, MODE_FIRST_FIRST); - } - - public static String getStringBetween(String startIdentifier, String endIdentifier, String sourceString, int mode) throws Exception { - String result = sourceString; - - int startIndex; - int endIndex; - - switch (mode) { - case MODE_FIRST_FIRST: { - startIndex = sourceString.indexOf(startIdentifier) + startIdentifier.length(); - sourceString = sourceString.substring(startIndex); - endIndex = sourceString.indexOf(endIdentifier); - break; - } - case MODE_FIRST_LAST: { - startIndex = sourceString.indexOf(startIdentifier) + startIdentifier.length(); - sourceString = sourceString.substring(startIndex); - endIndex = sourceString.lastIndexOf(endIdentifier); - break; - } - case MODE_LAST_FIRST: { - startIndex = sourceString.lastIndexOf(startIdentifier) + startIdentifier.length(); - sourceString = sourceString.substring(startIndex); - endIndex = sourceString.indexOf(endIdentifier); - break; - } - case MODE_LAST_LAST: { - startIndex = sourceString.lastIndexOf(startIdentifier) + startIdentifier.length(); - sourceString = sourceString.substring(startIndex); - endIndex = sourceString.lastIndexOf(endIdentifier); - break; - } - default: { - startIndex = sourceString.indexOf(startIdentifier) + startIdentifier.length(); - sourceString = sourceString.substring(startIndex); - endIndex = sourceString.indexOf(endIdentifier); - break; - } - } - - result = sourceString.substring(0, endIndex); - - return result; - } - - public static String getStringAfter(String startIdentifier, String sourceString) throws Exception { - return getStringAfter(startIdentifier, sourceString, MODE_FIRST_FIRST); - } - - public static String getStringAfter(String startIdentifier, String sourceString, int mode) throws Exception { - String result = sourceString; - - int startIndex; - int endIndex; - - if (mode == MODE_FIRST_FIRST || mode == MODE_FIRST_LAST) { - startIndex = sourceString.indexOf(startIdentifier) + startIdentifier.length(); - } else { - startIndex = sourceString.lastIndexOf(startIdentifier) + startIdentifier.length(); - } - - result = sourceString.substring(startIndex); - - return result; - } - - public static String getStringBefore(String startIdentifier, String sourceString) throws Exception { - return getStringBefore(startIdentifier, sourceString, MODE_FIRST_FIRST); - } - - public static String getStringBefore(String startIdentifier, String sourceString, int mode) throws Exception { - String result = sourceString; - - int startIndex; - int endIndex; - - if (mode == MODE_FIRST_FIRST || mode == MODE_FIRST_LAST) { - startIndex = sourceString.indexOf(startIdentifier); - } else { - startIndex = sourceString.lastIndexOf(startIdentifier); - } - - result = sourceString.substring(0, startIndex); - - return result; - } - - public static String getStringInTag(String tagName, String sourceString) throws Exception { - String result = sourceString; - - String startIdentifier = "<" + tagName + " "; - String endIdentifier = ""; - - if (sourceString.contains(startIdentifier) && sourceString.contains(endIdentifier)) { - result = getStringBetween(startIdentifier, endIdentifier, result, MODE_FIRST_FIRST); - result = getStringAfter(">", result); - } - - return result; - } - - public static String getPageSource(String requestUrl) throws Exception { - //URLFetchService fetchService = URLFetchServiceFactory.getURLFetchService(); - //HTTPResponse response = fetchService.fetch(new URL(requestUrl)); - - URL url = new URL(requestUrl); - BufferedReader reader = new BufferedReader(new InputStreamReader(url.openStream())); - StringBuilder sb = new StringBuilder(); - String line; - - while ((line = reader.readLine()) != null) { - sb.append(line); - } - reader.close(); - - return sb.toString(); - } - - public static boolean isSameString(String value1, String value2, int tolerance) { - value1 = value1.toLowerCase().trim(); - value2 = value2.toLowerCase().trim(); - - if (tolerance > TOLERANCE_NONE) { - value1 = removeSeperatorsFromString(value1); - value2 = removeSeperatorsFromString(value2); - } - - return value1.equals(value2); - } - - public static boolean containsAnyValue(String value) { - if (value != null && value.length() > 0) { - return true; - } else { - return false; - } - } - - public static String removeSeperatorsFromString(String value) { - value = value.replace(" ", ""); - value = value.replace("_", ""); - value = value.replace("-", ""); - value = value.replace(".", ""); - return value; - } - - public static String removeRegexInString(String sourceString, String regexString) { - return replaceRegexInString(sourceString, "", regexString); - } - - public static String replaceRegexInString(String sourceString, String replace, String regexString) { - String result = sourceString; - try { - String foundPart = null; - Pattern regex = Pattern.compile(regexString); - Matcher regexMatcher = regex.matcher(sourceString); - if (regexMatcher.find()) { - foundPart = regexMatcher.group(); - } - result = sourceString.replace(foundPart, replace); - } catch (Exception ex) { - // Syntax error in the regular expression - } - return result; - } - - public static int countOccurrencesInString(String search, String source) { - int lastIndex = 0; - int count = 0; - - while (lastIndex != -1){ - lastIndex = source.indexOf(search, lastIndex); - if (lastIndex != -1){ - count ++; - lastIndex+=search.length(); - } - } - - return count; - } - - public static boolean containsSpecialCharacters(String source) { - if (source.contains("<") || source.contains(">")) { - return true; - } - return false; - } - - public static boolean isValidUrl(String urlString) { - if (!urlString.startsWith("http")) { - return false; - } - try { - URL url = new URL(urlString); - return true; - } catch (Exception e) { - return false; - } - } + public static final int MODE_FIRST_FIRST = 0; + public static final int MODE_FIRST_LAST = 1; + public static final int MODE_LAST_FIRST = 2; + public static final int MODE_LAST_LAST = 3; + + public static final int TOLERANCE_NONE = 0; + public static final int TOLERANCE_LOW = 1; + public static final int TOLERANCE_MEDIUM = 2; + public static final int TOLERANCE_HIGH = 3; + + public static String getStringBetween(String startIdentifier, String endIdentifier, String sourceString) throws Exception { + return getStringBetween(startIdentifier, endIdentifier, sourceString, MODE_FIRST_FIRST); + } + + public static String getStringBetween(String startIdentifier, String endIdentifier, String sourceString, int mode) throws Exception { + String result = sourceString; + + int startIndex; + int endIndex; + + switch (mode) { + case MODE_FIRST_FIRST: { + startIndex = sourceString.indexOf(startIdentifier) + startIdentifier.length(); + sourceString = sourceString.substring(startIndex); + endIndex = sourceString.indexOf(endIdentifier); + break; + } + case MODE_FIRST_LAST: { + startIndex = sourceString.indexOf(startIdentifier) + startIdentifier.length(); + sourceString = sourceString.substring(startIndex); + endIndex = sourceString.lastIndexOf(endIdentifier); + break; + } + case MODE_LAST_FIRST: { + startIndex = sourceString.lastIndexOf(startIdentifier) + startIdentifier.length(); + sourceString = sourceString.substring(startIndex); + endIndex = sourceString.indexOf(endIdentifier); + break; + } + case MODE_LAST_LAST: { + startIndex = sourceString.lastIndexOf(startIdentifier) + startIdentifier.length(); + sourceString = sourceString.substring(startIndex); + endIndex = sourceString.lastIndexOf(endIdentifier); + break; + } + default: { + startIndex = sourceString.indexOf(startIdentifier) + startIdentifier.length(); + sourceString = sourceString.substring(startIndex); + endIndex = sourceString.indexOf(endIdentifier); + break; + } + } + + result = sourceString.substring(0, endIndex); + + return result; + } + + public static String getStringAfter(String startIdentifier, String sourceString) throws Exception { + return getStringAfter(startIdentifier, sourceString, MODE_FIRST_FIRST); + } + + public static String getStringAfter(String startIdentifier, String sourceString, int mode) throws Exception { + String result = sourceString; + + int startIndex; + int endIndex; + + if (mode == MODE_FIRST_FIRST || mode == MODE_FIRST_LAST) { + startIndex = sourceString.indexOf(startIdentifier) + startIdentifier.length(); + } else { + startIndex = sourceString.lastIndexOf(startIdentifier) + startIdentifier.length(); + } + + result = sourceString.substring(startIndex); + + return result; + } + + public static String getStringBefore(String startIdentifier, String sourceString) throws Exception { + return getStringBefore(startIdentifier, sourceString, MODE_FIRST_FIRST); + } + + public static String getStringBefore(String startIdentifier, String sourceString, int mode) throws Exception { + String result = sourceString; + + int startIndex; + int endIndex; + + if (mode == MODE_FIRST_FIRST || mode == MODE_FIRST_LAST) { + startIndex = sourceString.indexOf(startIdentifier); + } else { + startIndex = sourceString.lastIndexOf(startIdentifier); + } + + result = sourceString.substring(0, startIndex); + + return result; + } + + public static String getStringInTag(String tagName, String sourceString) throws Exception { + String result = sourceString; + + String startIdentifier = "<" + tagName + " "; + String endIdentifier = ""; + + if (sourceString.contains(startIdentifier) && sourceString.contains(endIdentifier)) { + result = getStringBetween(startIdentifier, endIdentifier, result, MODE_FIRST_FIRST); + result = getStringAfter(">", result); + } + + return result; + } + + public static String getPageSource(String requestUrl) throws Exception { + //URLFetchService fetchService = URLFetchServiceFactory.getURLFetchService(); + //HTTPResponse response = fetchService.fetch(new URL(requestUrl)); + + URL url = new URL(requestUrl); + BufferedReader reader = new BufferedReader(new InputStreamReader(url.openStream())); + StringBuilder sb = new StringBuilder(); + String line; + + while ((line = reader.readLine()) != null) { + sb.append(line); + } + reader.close(); + + return sb.toString(); + } + + public static boolean isSameString(String value1, String value2, int tolerance) { + value1 = value1.toLowerCase().trim(); + value2 = value2.toLowerCase().trim(); + + if (tolerance > TOLERANCE_NONE) { + value1 = removeSeperatorsFromString(value1); + value2 = removeSeperatorsFromString(value2); + } + + return value1.equals(value2); + } + + public static boolean containsAnyValue(String value) { + if (value != null && value.length() > 0) { + return true; + } else { + return false; + } + } + + public static String removeSeperatorsFromString(String value) { + value = value.replace(" ", ""); + value = value.replace("_", ""); + value = value.replace("-", ""); + value = value.replace(".", ""); + return value; + } + + public static String removeRegexInString(String sourceString, String regexString) { + return replaceRegexInString(sourceString, "", regexString); + } + + public static String replaceRegexInString(String sourceString, String replace, String regexString) { + String result = sourceString; + try { + String foundPart = null; + Pattern regex = Pattern.compile(regexString); + Matcher regexMatcher = regex.matcher(sourceString); + if (regexMatcher.find()) { + foundPart = regexMatcher.group(); + } + result = sourceString.replace(foundPart, replace); + } catch (Exception ex) { + // Syntax error in the regular expression + } + return result; + } + + public static int countOccurrencesInString(String search, String source) { + int lastIndex = 0; + int count = 0; + + while (lastIndex != -1) { + lastIndex = source.indexOf(search, lastIndex); + if (lastIndex != -1) { + count++; + lastIndex += search.length(); + } + } + + return count; + } + + public static boolean containsSpecialCharacters(String source) { + if (source.contains("<") || source.contains(">")) { + return true; + } + return false; + } + + public static boolean isValidUrl(String urlString) { + if (!urlString.startsWith("http")) { + return false; + } + try { + URL url = new URL(urlString); + return true; + } catch (Exception e) { + return false; + } + } } diff --git a/IntelliQ/backend/src/main/java/com/intelliq/appengine/RequestFilter.java b/IntelliQ/backend/src/main/java/com/intelliq/appengine/RequestFilter.java index 881adc9..2f2566c 100644 --- a/IntelliQ/backend/src/main/java/com/intelliq/appengine/RequestFilter.java +++ b/IntelliQ/backend/src/main/java/com/intelliq/appengine/RequestFilter.java @@ -31,115 +31,121 @@ public class RequestFilter implements Filter { * to its real destination. We can use it to filter requests. */ public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) - throws IOException, ServletException { - + throws IOException, ServletException { + HttpServletRequest req = (HttpServletRequest) request; String uri = req.getRequestURI(); String path = uri.substring(req.getContextPath().length()); - + if (path.startsWith("/static") || path.startsWith("/_ah") || path.startsWith("/intelliq")) { - // preserve special routes - filterChain.doFilter(request, response); + // preserve special routes + filterChain.doFilter(request, response); + } else if(uri.contains(".well-known/assetlinks.json")) { + // redirect to Digital Asset Links JSON file + RequestDispatcher rd = request.getRequestDispatcher("/static/assetlinks.json"); + rd.forward(request, response); } else { - // forward request to matching servlet - RequestDispatcher rd = request.getRequestDispatcher("/intelliq" + path); - rd.forward(request, response); + // forward request to matching servlet + RequestDispatcher rd = request.getRequestDispatcher("/intelliq" + path); + rd.forward(request, response); } } - + // method that can be used to forward a request to a given path public static void forwardRequest(ServletContext context, HttpServletRequest req, HttpServletResponse resp, String requestPath) { - RequestDispatcher rd = context.getRequestDispatcher(requestPath); - HttpServletRequest wrapped = new HttpServletRequestWrapper(req) { - public String getServletPath() { return ""; } - }; - try { - rd.forward(wrapped, resp); - } catch (Exception e) { - e.printStackTrace(); - } - } - + RequestDispatcher rd = context.getRequestDispatcher(requestPath); + HttpServletRequest wrapped = new HttpServletRequestWrapper(req) { + public String getServletPath() { + return ""; + } + }; + try { + rd.forward(wrapped, resp); + } catch (Exception e) { + e.printStackTrace(); + } + } + // creates a map that contains all available header fields - public static Map getHeaderInfo(HttpServletRequest req) { - Map map = new HashMap(); - Enumeration headerNames = req.getHeaderNames(); - while (headerNames.hasMoreElements()) { - String key = (String) headerNames.nextElement(); - String value = req.getHeader(key); - map.put(key, value); - } - map.put("RemoteHost", req.getRemoteHost()); - return map; + public static Map getHeaderInfo(HttpServletRequest req) { + Map map = new HashMap(); + Enumeration headerNames = req.getHeaderNames(); + while (headerNames.hasMoreElements()) { + String key = (String) headerNames.nextElement(); + String value = req.getHeader(key); + map.put(key, value); + } + map.put("RemoteHost", req.getRemoteHost()); + return map; } - + // reads a cookie from a given requests public static String getCookieValue(HttpServletRequest req, String name) { - String value = null; + String value = null; Cookie cookie; Cookie[] allCookies = req.getCookies(); if (allCookies != null) { - for (int i = 0; i < allCookies.length; i++) { - cookie = allCookies[i]; - if (cookie.getName().equals(name)) { - value = cookie.getValue(); - } - } + for (int i = 0; i < allCookies.length; i++) { + cookie = allCookies[i]; + if (cookie.getName().equals(name)) { + value = cookie.getValue(); + } + } } - return value; + return value; } - + // creates a url containing all request parameters public static String getFullUrlFromRequest(HttpServletRequest req) { - StringBuilder builder = new StringBuilder(); - builder.append(req.getRequestURL().toString() + "?"); - - Iterator entries = req.getParameterMap().entrySet().iterator(); - while (entries.hasNext()) { - Entry thisEntry = (Entry) entries.next(); - String key = (String) thisEntry.getKey(); - String value = ((String[]) thisEntry.getValue())[0]; - builder.append(key + "=" + value + "&"); - } - - String url = builder.toString(); - url = url.substring(0, url.length() - 1); - return url; + StringBuilder builder = new StringBuilder(); + builder.append(req.getRequestURL().toString() + "?"); + + Iterator entries = req.getParameterMap().entrySet().iterator(); + while (entries.hasNext()) { + Entry thisEntry = (Entry) entries.next(); + String key = (String) thisEntry.getKey(); + String value = ((String[]) thisEntry.getValue())[0]; + builder.append(key + "=" + value + "&"); + } + + String url = builder.toString(); + url = url.substring(0, url.length() - 1); + return url; } - + // gets the (lowercase) country code if present public static String getRequestCountryCode(HttpServletRequest req) { - String countryCode = req.getHeader("X-AppEngine-Country"); - if (countryCode != null) { - countryCode = countryCode.toLowerCase(); - } else { - countryCode = "us"; // serve English as default - } - return countryCode; + String countryCode = req.getHeader("X-AppEngine-Country"); + if (countryCode != null) { + countryCode = countryCode.toLowerCase(); + } else { + countryCode = "us"; // serve English as default + } + return countryCode; } - + // tries to detect the language of the request origin public static String getRequestLanguageCode(HttpServletRequest req) { - // check if request url already contains a known locale - String requestUrl = req.getRequestURL().toString(); - if (requestUrl.contains("/de/")) { - return "de"; - } else if (requestUrl.contains("/en/")) { - return "en"; - } - - // if no locale is specified, use country code - String countryCode = getRequestCountryCode(req); - - // German speaking - if (countryCode.equals("de") || countryCode.equals("au") || countryCode.equals("ch") || countryCode.equals("lu")) { - return "de"; - } - - // for everyone else English will do - return "en"; + // check if request url already contains a known locale + String requestUrl = req.getRequestURL().toString(); + if (requestUrl.contains("/de/")) { + return "de"; + } else if (requestUrl.contains("/en/")) { + return "en"; + } + + // if no locale is specified, use country code + String countryCode = getRequestCountryCode(req); + + // German speaking + if (countryCode.equals("de") || countryCode.equals("au") || countryCode.equals("ch") || countryCode.equals("lu")) { + return "de"; + } + + // for everyone else English will do + return "en"; } - + public FilterConfig getFilterConfig() { return filterConfig; } @@ -148,6 +154,7 @@ public void init(FilterConfig filterConfig) { this.filterConfig = filterConfig; } - public void destroy() {} - + public void destroy() { + } + } \ No newline at end of file diff --git a/IntelliQ/backend/src/main/java/com/intelliq/appengine/api/ApiRequest.java b/IntelliQ/backend/src/main/java/com/intelliq/appengine/api/ApiRequest.java index aa1ff8a..a1f4a25 100644 --- a/IntelliQ/backend/src/main/java/com/intelliq/appengine/api/ApiRequest.java +++ b/IntelliQ/backend/src/main/java/com/intelliq/appengine/api/ApiRequest.java @@ -11,156 +11,156 @@ import com.intelliq.appengine.datastore.entries.UserEntry; public class ApiRequest { - - private static final Logger log = Logger.getLogger(ApiRequest.class.getName()); - - HttpServletRequest request; - UserEntry user; - boolean triedToParseToken = false; - boolean triedToGetUserFromToken = false; - - public ApiRequest (HttpServletRequest req) { - request = req; - } - - public UserEntry getUserFromToken() throws Exception { - triedToGetUserFromToken = true; - UserEntry parsedUser = parseUserFromToken(); - user = getUserFromToken(parsedUser); - return user; - } - - public UserEntry getUserFromToken(UserEntry parsedUser) throws Exception { - UserEntry existingUser = null; - if (parsedUser.getGoogleUserId() != null && existingUser == null) { - existingUser = UserHelper.getUserByGoogleUserId(parsedUser.getGoogleUserId()); - } - if (parsedUser.getFacebookUserId() != null && existingUser == null) { - existingUser = UserHelper.getUserByFacebookUserId(parsedUser.getGoogleUserId()); - } - if (existingUser == null) { - throw new Exception("No registered user found"); - } - return existingUser; - } - - public UserEntry parseUserFromToken() throws Exception { - triedToParseToken = true; - - // check for a Google login - String googleIdToken = getGoogleIdToken(); - if (ParserHelper.containsAnyValue(googleIdToken)) { - Payload payload = Authenticator.validateGoogleIdToken(googleIdToken); - UserEntry googleUser = new UserEntry().parseFromGooglePayload(payload); - return googleUser; - } - - // check for a Facebook login - String facebookIdToken = getFacebookIdToken(); - if (ParserHelper.containsAnyValue(facebookIdToken)) { - // TODO: Facebook token processing - throw new Exception("Facebook login not yet supported"); - } - - throw new Exception("No token specified"); - } - - public String getGoogleIdToken() { - return request.getParameter("googleIdToken"); - } - - public String getFacebookIdToken() { - return request.getParameter("facebookIdToken"); - } - - /** - * Parameter helper - */ - public String getParameter(String key) { - return getParameter(key, null); - } - - public String getParameter(String key, String defaultValue) { - String value = request.getParameter(key); - if (value == null || value.length() < 1) { - value = defaultValue; - } - return value; - } - - public long getParameterAsLong(String key, long defaultValue) { - try { - return Long.parseLong(request.getParameter(key)); - } catch (Exception ex) { - return defaultValue; - } - } - - public int getParameterAsInt(String key, int defaultValue) { - try { - return Integer.parseInt(request.getParameter(key)); - } catch (Exception ex) { - return defaultValue; - } - } - - public boolean getParameterAsBoolean(String key, boolean defaultValue) { - try { - return request.getParameter(key).equals("true"); - } catch (Exception ex) { - return defaultValue; - } - } - - public float getParameterAsFloat(String key, float defaultValue) { - try { - return Float.parseFloat(request.getParameter(key)); - } catch (Exception ex) { - return defaultValue; - } - } - - public String getHost() { - return request.getHeader("Host"); - } - - public String getCity() { - return request.getHeader("X-AppEngine-City"); - } - - public String getCountry() { - return request.getHeader("X-AppEngine-Country"); - } - - public String getRequestIP() { - return request.getHeader("X-Forwarded-For"); - } - - public HttpServletRequest getRequest() { - return request; - } - - public void setRequest(HttpServletRequest request) { - this.request = request; - } - - public UserEntry getUser() { - if (!triedToGetUserFromToken) { - try { - user = getUserFromToken(); - } catch (Exception ex) { - log.warning("Unable to get user from token: " + ex.getMessage()); - } - } - return user; - } - - public void setUser(UserEntry user) { - this.user = user; - } - - public String getUrl() { - return request.getRequestURL().toString(); - } - + + private static final Logger log = Logger.getLogger(ApiRequest.class.getName()); + + HttpServletRequest request; + UserEntry user; + boolean triedToParseToken = false; + boolean triedToGetUserFromToken = false; + + public ApiRequest(HttpServletRequest req) { + request = req; + } + + public UserEntry getUserFromToken() throws Exception { + triedToGetUserFromToken = true; + UserEntry parsedUser = parseUserFromToken(); + user = getUserFromToken(parsedUser); + return user; + } + + public UserEntry getUserFromToken(UserEntry parsedUser) throws Exception { + UserEntry existingUser = null; + if (parsedUser.getGoogleUserId() != null && existingUser == null) { + existingUser = UserHelper.getUserByGoogleUserId(parsedUser.getGoogleUserId()); + } + if (parsedUser.getFacebookUserId() != null && existingUser == null) { + existingUser = UserHelper.getUserByFacebookUserId(parsedUser.getGoogleUserId()); + } + if (existingUser == null) { + throw new Exception("No registered user found"); + } + return existingUser; + } + + public UserEntry parseUserFromToken() throws Exception { + triedToParseToken = true; + + // check for a Google login + String googleIdToken = getGoogleIdToken(); + if (ParserHelper.containsAnyValue(googleIdToken)) { + Payload payload = Authenticator.validateGoogleIdToken(googleIdToken); + UserEntry googleUser = new UserEntry().parseFromGooglePayload(payload); + return googleUser; + } + + // check for a Facebook login + String facebookIdToken = getFacebookIdToken(); + if (ParserHelper.containsAnyValue(facebookIdToken)) { + // TODO: Facebook token processing + throw new Exception("Facebook login not yet supported"); + } + + throw new Exception("No token specified"); + } + + public String getGoogleIdToken() { + return request.getParameter("googleIdToken"); + } + + public String getFacebookIdToken() { + return request.getParameter("facebookIdToken"); + } + + /** + * Parameter helper + */ + public String getParameter(String key) { + return getParameter(key, null); + } + + public String getParameter(String key, String defaultValue) { + String value = request.getParameter(key); + if (value == null || value.length() < 1) { + value = defaultValue; + } + return value; + } + + public long getParameterAsLong(String key, long defaultValue) { + try { + return Long.parseLong(request.getParameter(key)); + } catch (Exception ex) { + return defaultValue; + } + } + + public int getParameterAsInt(String key, int defaultValue) { + try { + return Integer.parseInt(request.getParameter(key)); + } catch (Exception ex) { + return defaultValue; + } + } + + public boolean getParameterAsBoolean(String key, boolean defaultValue) { + try { + return request.getParameter(key).equals("true"); + } catch (Exception ex) { + return defaultValue; + } + } + + public float getParameterAsFloat(String key, float defaultValue) { + try { + return Float.parseFloat(request.getParameter(key)); + } catch (Exception ex) { + return defaultValue; + } + } + + public String getHost() { + return request.getHeader("Host"); + } + + public String getCity() { + return request.getHeader("X-AppEngine-City"); + } + + public String getCountry() { + return request.getHeader("X-AppEngine-Country"); + } + + public String getRequestIP() { + return request.getHeader("X-Forwarded-For"); + } + + public HttpServletRequest getRequest() { + return request; + } + + public void setRequest(HttpServletRequest request) { + this.request = request; + } + + public UserEntry getUser() { + if (!triedToGetUserFromToken) { + try { + user = getUserFromToken(); + } catch (Exception ex) { + log.warning("Unable to get user from token: " + ex.getMessage()); + } + } + return user; + } + + public void setUser(UserEntry user) { + this.user = user; + } + + public String getUrl() { + return request.getRequestURL().toString(); + } + } diff --git a/IntelliQ/backend/src/main/java/com/intelliq/appengine/api/ApiResponse.java b/IntelliQ/backend/src/main/java/com/intelliq/appengine/api/ApiResponse.java index 7ab506b..c2242b8 100644 --- a/IntelliQ/backend/src/main/java/com/intelliq/appengine/api/ApiResponse.java +++ b/IntelliQ/backend/src/main/java/com/intelliq/appengine/api/ApiResponse.java @@ -6,59 +6,59 @@ public class ApiResponse { - int statusCode = HttpServletResponse.SC_OK; - String statusMessage = "OK"; - Object content; - - public String toJSON() { - String json = "{}"; - try { - Gson gson = new Gson(); - json = gson.toJson(this); - } catch (Exception ex) { - - } - return json; - } - - public void setException(Exception exception) { - if (exception != null) { - if (exception.getMessage() != null) { - statusMessage = exception.getMessage(); - } else { - statusMessage = exception.toString(); - } - } else { - statusMessage = "Unknown internal server error"; - } - if (statusCode == HttpServletResponse.SC_OK) { - statusCode = HttpServletResponse.SC_INTERNAL_SERVER_ERROR; - } - } + int statusCode = HttpServletResponse.SC_OK; + String statusMessage = "OK"; + Object content; - public int getStatusCode() { - return statusCode; - } + public String toJSON() { + String json = "{}"; + try { + Gson gson = new Gson(); + json = gson.toJson(this); + } catch (Exception ex) { - public void setStatusCode(int statusCode) { - this.statusCode = statusCode; - } + } + return json; + } - public String getStatusMessage() { - return statusMessage; - } + public void setException(Exception exception) { + if (exception != null) { + if (exception.getMessage() != null) { + statusMessage = exception.getMessage(); + } else { + statusMessage = exception.toString(); + } + } else { + statusMessage = "Unknown internal server error"; + } + if (statusCode == HttpServletResponse.SC_OK) { + statusCode = HttpServletResponse.SC_INTERNAL_SERVER_ERROR; + } + } - public void setStatusMessage(String statusMessage) { - this.statusMessage = statusMessage; - } + public int getStatusCode() { + return statusCode; + } + + public void setStatusCode(int statusCode) { + this.statusCode = statusCode; + } + + public String getStatusMessage() { + return statusMessage; + } + + public void setStatusMessage(String statusMessage) { + this.statusMessage = statusMessage; + } + + public Object getContent() { + return content; + } + + public void setContent(Object content) { + this.content = content; + } - public Object getContent() { - return content; - } - public void setContent(Object content) { - this.content = content; - } - - } diff --git a/IntelliQ/backend/src/main/java/com/intelliq/appengine/api/ApiServlet.java b/IntelliQ/backend/src/main/java/com/intelliq/appengine/api/ApiServlet.java index 004cfea..01c2707 100644 --- a/IntelliQ/backend/src/main/java/com/intelliq/appengine/api/ApiServlet.java +++ b/IntelliQ/backend/src/main/java/com/intelliq/appengine/api/ApiServlet.java @@ -21,151 +21,151 @@ @SuppressWarnings("serial") public class ApiServlet extends HttpServlet { - private static final Logger log = Logger.getLogger(ApiServlet.class.getName()); - - private Cache cache; - - @Override - public void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException, ServletException { - - ApiRequest apiRequest = new ApiRequest(req); - String requestUrl = apiRequest.getUrl(); - - String response = null; - ApiResponse apiResponse = new ApiResponse(); - - try { - Endpoint endpoint = EndpointManager.getEndpointForRequest(apiRequest); - if (endpoint == null) { - throw new Exception("Unknown endpoint called"); - } - - if (shouldUseCache(req)) { - response = getCachedResponse(req); - } - - if (response == null) { - // no cached response available - apiResponse = endpoint.processRequest(apiRequest); - response = apiResponse.toJSON(); - addResponseToCache(req, response); - } else { - log.severe("Response loaded from cache"); - } - } catch (Exception e) { - apiResponse.setException(e); - response = apiResponse.toJSON(); - e.printStackTrace(); - } - - resp.setContentType("application/json; charset=UTF-8"); - resp.addHeader("Access-Control-Allow-Origin", "*"); - resp.getWriter().write(response); - resp.getWriter().flush(); - resp.getWriter().close(); - } - - public void doGetOld(HttpServletRequest req, HttpServletResponse resp) throws IOException, ServletException { - - ApiRequest apiRequest = new ApiRequest(req); - String requestUrl = req.getRequestURL().toString(); - - String response = null; - ApiResponse responseObject = new ApiResponse(); - - try { - if (shouldUseCache(req)) { - response = getCachedResponse(req); - } - - if (response == null) { - // no cached response available - if (requestUrl.contains("/test/")) { - responseObject = processTestRequest(req); - } else if (requestUrl.contains("/header/")) { - responseObject = processHeaderRequest(req); - } else { - RequestFilter.forwardRequest(getServletContext(), req, resp, "/intelliq/"); - return; - } - response = responseObject.toJSON(); - addResponseToCache(req, response); - } else { - log.severe("Response loaded from cache"); - } - } catch (Exception e) { - responseObject.setException(e); - response = responseObject.toJSON(); - e.printStackTrace(); - } - - resp.setContentType("application/json; charset=UTF-8"); - resp.addHeader("Access-Control-Allow-Origin", "*"); - resp.getWriter().write(response); - resp.getWriter().flush(); - resp.getWriter().close(); - } - - public boolean shouldUseCache(HttpServletRequest req) { - cache = getCache(); - String invalidateCacheParam = req.getParameter("invalidateCache"); - boolean invalidateCache = invalidateCacheParam != null && invalidateCacheParam.equals("true"); - - if (!invalidateCache && !req.getRequestURL().toString().contains("/header/")) { - //TODO: return true - return false; - } else { - return false; - } - } - - public Cache getCache() { - Cache newCache = null; - try { - CacheFactory cacheFactory = CacheManager.getInstance().getCacheFactory(); - Map properties = new HashMap<>(); - properties.put(GCacheFactory.EXPIRATION_DELTA, 30); // cache for 30 seconds - newCache = cacheFactory.createCache(properties); - } catch (Exception ex) { - log.warning("Unable to create cache"); - } - return newCache; - } - - public String getCachedResponse(HttpServletRequest req) { - String response = null; - try { - String cacheKey = RequestFilter.getFullUrlFromRequest(req); - response = (String) cache.get(cacheKey); - } catch (Exception ex) { - - } - return response; - } - - public void addResponseToCache(HttpServletRequest req, String response) { - try { - String cacheKey = RequestFilter.getFullUrlFromRequest(req); - cache.put(cacheKey, response); - } catch (Exception ex) { - - } - } - - public ApiResponse processHeaderRequest(HttpServletRequest req) throws Exception { - ApiResponse responseObject = new ApiResponse(); - responseObject.setContent(RequestFilter.getHeaderInfo(req)); - return responseObject; - } - - public ApiResponse processTestRequest(HttpServletRequest req) throws Exception { - ApiResponse responseObject = new ApiResponse(); - - String idToken = req.getParameter("googleIdToken"); - Object result = Authenticator.validateGoogleIdToken(idToken); - - responseObject.setContent(result); - return responseObject; - } + private static final Logger log = Logger.getLogger(ApiServlet.class.getName()); + + private Cache cache; + + @Override + public void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException, ServletException { + + ApiRequest apiRequest = new ApiRequest(req); + String requestUrl = apiRequest.getUrl(); + + String response = null; + ApiResponse apiResponse = new ApiResponse(); + + try { + Endpoint endpoint = EndpointManager.getEndpointForRequest(apiRequest); + if (endpoint == null) { + throw new Exception("Unknown endpoint called"); + } + + if (shouldUseCache(req)) { + response = getCachedResponse(req); + } + + if (response == null) { + // no cached response available + apiResponse = endpoint.processRequest(apiRequest); + response = apiResponse.toJSON(); + addResponseToCache(req, response); + } else { + log.severe("Response loaded from cache"); + } + } catch (Exception e) { + apiResponse.setException(e); + response = apiResponse.toJSON(); + e.printStackTrace(); + } + + resp.setContentType("application/json; charset=UTF-8"); + resp.addHeader("Access-Control-Allow-Origin", "*"); + resp.getWriter().write(response); + resp.getWriter().flush(); + resp.getWriter().close(); + } + + public void doGetOld(HttpServletRequest req, HttpServletResponse resp) throws IOException, ServletException { + + ApiRequest apiRequest = new ApiRequest(req); + String requestUrl = req.getRequestURL().toString(); + + String response = null; + ApiResponse responseObject = new ApiResponse(); + + try { + if (shouldUseCache(req)) { + response = getCachedResponse(req); + } + + if (response == null) { + // no cached response available + if (requestUrl.contains("/test/")) { + responseObject = processTestRequest(req); + } else if (requestUrl.contains("/header/")) { + responseObject = processHeaderRequest(req); + } else { + RequestFilter.forwardRequest(getServletContext(), req, resp, "/intelliq/"); + return; + } + response = responseObject.toJSON(); + addResponseToCache(req, response); + } else { + log.severe("Response loaded from cache"); + } + } catch (Exception e) { + responseObject.setException(e); + response = responseObject.toJSON(); + e.printStackTrace(); + } + + resp.setContentType("application/json; charset=UTF-8"); + resp.addHeader("Access-Control-Allow-Origin", "*"); + resp.getWriter().write(response); + resp.getWriter().flush(); + resp.getWriter().close(); + } + + public boolean shouldUseCache(HttpServletRequest req) { + cache = getCache(); + String invalidateCacheParam = req.getParameter("invalidateCache"); + boolean invalidateCache = invalidateCacheParam != null && invalidateCacheParam.equals("true"); + + if (!invalidateCache && !req.getRequestURL().toString().contains("/header/")) { + //TODO: return true + return false; + } else { + return false; + } + } + + public Cache getCache() { + Cache newCache = null; + try { + CacheFactory cacheFactory = CacheManager.getInstance().getCacheFactory(); + Map properties = new HashMap<>(); + properties.put(GCacheFactory.EXPIRATION_DELTA, 30); // cache for 30 seconds + newCache = cacheFactory.createCache(properties); + } catch (Exception ex) { + log.warning("Unable to create cache"); + } + return newCache; + } + + public String getCachedResponse(HttpServletRequest req) { + String response = null; + try { + String cacheKey = RequestFilter.getFullUrlFromRequest(req); + response = (String) cache.get(cacheKey); + } catch (Exception ex) { + + } + return response; + } + + public void addResponseToCache(HttpServletRequest req, String response) { + try { + String cacheKey = RequestFilter.getFullUrlFromRequest(req); + cache.put(cacheKey, response); + } catch (Exception ex) { + + } + } + + public ApiResponse processHeaderRequest(HttpServletRequest req) throws Exception { + ApiResponse responseObject = new ApiResponse(); + responseObject.setContent(RequestFilter.getHeaderInfo(req)); + return responseObject; + } + + public ApiResponse processTestRequest(HttpServletRequest req) throws Exception { + ApiResponse responseObject = new ApiResponse(); + + String idToken = req.getParameter("googleIdToken"); + Object result = Authenticator.validateGoogleIdToken(idToken); + + responseObject.setContent(result); + return responseObject; + } } diff --git a/IntelliQ/backend/src/main/java/com/intelliq/appengine/api/Authenticator.java b/IntelliQ/backend/src/main/java/com/intelliq/appengine/api/Authenticator.java index 6a24922..f8b56ec 100644 --- a/IntelliQ/backend/src/main/java/com/intelliq/appengine/api/Authenticator.java +++ b/IntelliQ/backend/src/main/java/com/intelliq/appengine/api/Authenticator.java @@ -12,69 +12,69 @@ public final class Authenticator { - private static final Logger log = Logger.getLogger(Authenticator.class.getName()); - - public static final String CLIENT_ID_APP_ENGINE = "1008259459239-ic0lmsu9hhl6i929pav41u8smjbbc86s.apps.googleusercontent.com"; - public static final String CLIENT_ID_ANDROID = "1008259459239-hlmd8qnm626idv1b9b2p4bd8pfjdtndp.apps.googleusercontent.com"; - public static final String CLIENT_ID_ANDROID_DEBUG = "1008259459239-hj2tlbdbvk3qo8ar56dqf24s3atck2br.apps.googleusercontent.com"; - public static final String CLIENT_ID_IOS = ""; - public static final String CLIENT_ID_WEB = "1008259459239-t1huos5n6bhkin3is2jlqgkjv9h7mheh.apps.googleusercontent.com"; - - public static final String ISSUER_GOOGLE = "https://accounts.google.com"; - public static final String ISSUER_GOOGLE_LEGACY = "accounts.google.com"; - - private static final Set allowedClients = buildAllowedClients(); - private static final Set allowedIssuers = buildAllowedIssuers(); - - private static final GsonFactory jsonFactory = new GsonFactory(); - - private Authenticator() { - } - - public static Payload validateGoogleIdToken(String idTokenString) throws GeneralSecurityException, Exception { - GoogleIdToken idToken = GoogleIdToken.parse(jsonFactory, idTokenString); - if (idToken != null) { - Payload payload = idToken.getPayload(); - - // validate timestamps - long timestampNow = (new Date()).getTime(); - long timestampIssued = payload.getIssuedAtTimeSeconds() * 1000; - long timestampExpired = payload.getExpirationTimeSeconds() * 1000; - if (timestampIssued > timestampNow || timestampExpired < timestampNow) { - throw new GeneralSecurityException("Token expired"); - } - - // validate issuer - if (!allowedIssuers.contains(payload.getIssuer())) { - throw new GeneralSecurityException("Invalid token, issuer not allowed: " + payload.getIssuer()); - } - - // validate client - if (!allowedClients.contains(payload.getAuthorizedParty())) { - throw new GeneralSecurityException("Invalid token, client not allowed: " + payload.getAuthorizedParty()); - } - - return payload; - } else { - throw new GeneralSecurityException("No valid Google ID Token specified"); - } - } - - private static Set buildAllowedClients() { - Set allowedClients = new HashSet<>(); - allowedClients.add(CLIENT_ID_APP_ENGINE); - allowedClients.add(CLIENT_ID_ANDROID); - allowedClients.add(CLIENT_ID_ANDROID_DEBUG); - allowedClients.add(CLIENT_ID_IOS); - allowedClients.add(CLIENT_ID_WEB); - return allowedClients; - } - - private static Set buildAllowedIssuers() { - Set allowedIssuers = new HashSet<>(); - allowedIssuers.add(ISSUER_GOOGLE); - allowedIssuers.add(ISSUER_GOOGLE_LEGACY); - return allowedIssuers; - } - + private static final Logger log = Logger.getLogger(Authenticator.class.getName()); + + public static final String CLIENT_ID_APP_ENGINE = "1008259459239-ic0lmsu9hhl6i929pav41u8smjbbc86s.apps.googleusercontent.com"; + public static final String CLIENT_ID_ANDROID = "1008259459239-hlmd8qnm626idv1b9b2p4bd8pfjdtndp.apps.googleusercontent.com"; + public static final String CLIENT_ID_ANDROID_DEBUG = "1008259459239-hj2tlbdbvk3qo8ar56dqf24s3atck2br.apps.googleusercontent.com"; + public static final String CLIENT_ID_IOS = ""; + public static final String CLIENT_ID_WEB = "1008259459239-t1huos5n6bhkin3is2jlqgkjv9h7mheh.apps.googleusercontent.com"; + + public static final String ISSUER_GOOGLE = "https://accounts.google.com"; + public static final String ISSUER_GOOGLE_LEGACY = "accounts.google.com"; + + private static final Set allowedClients = buildAllowedClients(); + private static final Set allowedIssuers = buildAllowedIssuers(); + + private static final GsonFactory jsonFactory = new GsonFactory(); + + private Authenticator() { + } + + public static Payload validateGoogleIdToken(String idTokenString) throws GeneralSecurityException, Exception { + GoogleIdToken idToken = GoogleIdToken.parse(jsonFactory, idTokenString); + if (idToken != null) { + Payload payload = idToken.getPayload(); + + // validate timestamps + long timestampNow = (new Date()).getTime(); + long timestampIssued = payload.getIssuedAtTimeSeconds() * 1000; + long timestampExpired = payload.getExpirationTimeSeconds() * 1000; + if (timestampIssued > timestampNow || timestampExpired < timestampNow) { + throw new GeneralSecurityException("Token expired"); + } + + // validate issuer + if (!allowedIssuers.contains(payload.getIssuer())) { + throw new GeneralSecurityException("Invalid token, issuer not allowed: " + payload.getIssuer()); + } + + // validate client + if (!allowedClients.contains(payload.getAuthorizedParty())) { + throw new GeneralSecurityException("Invalid token, client not allowed: " + payload.getAuthorizedParty()); + } + + return payload; + } else { + throw new GeneralSecurityException("No valid Google ID Token specified"); + } + } + + private static Set buildAllowedClients() { + Set allowedClients = new HashSet<>(); + allowedClients.add(CLIENT_ID_APP_ENGINE); + allowedClients.add(CLIENT_ID_ANDROID); + allowedClients.add(CLIENT_ID_ANDROID_DEBUG); + allowedClients.add(CLIENT_ID_IOS); + allowedClients.add(CLIENT_ID_WEB); + return allowedClients; + } + + private static Set buildAllowedIssuers() { + Set allowedIssuers = new HashSet<>(); + allowedIssuers.add(ISSUER_GOOGLE); + allowedIssuers.add(ISSUER_GOOGLE_LEGACY); + return allowedIssuers; + } + } diff --git a/IntelliQ/backend/src/main/java/com/intelliq/appengine/api/PermissionSet.java b/IntelliQ/backend/src/main/java/com/intelliq/appengine/api/PermissionSet.java index c1a4619..e0f32cb 100644 --- a/IntelliQ/backend/src/main/java/com/intelliq/appengine/api/PermissionSet.java +++ b/IntelliQ/backend/src/main/java/com/intelliq/appengine/api/PermissionSet.java @@ -7,37 +7,37 @@ public class PermissionSet { - public static final byte REQUIRE_ANY = 0; - public static final byte REQUIRE_ALL = 1; - - private List permissions; - private byte mode; - - public PermissionSet() { - super(); - this.permissions = new ArrayList(); - this.mode = REQUIRE_ANY; - } - - public PermissionSet(List permissions, byte mode) { - super(); - this.permissions = permissions; - this.mode = mode; - } - - public List getPermissions() { - return permissions; - } - - public void setPermissions(List permissions) { - this.permissions = permissions; - } - - public byte getMode() { - return mode; - } - - public void setMode(byte mode) { - this.mode = mode; - } + public static final byte REQUIRE_ANY = 0; + public static final byte REQUIRE_ALL = 1; + + private List permissions; + private byte mode; + + public PermissionSet() { + super(); + this.permissions = new ArrayList(); + this.mode = REQUIRE_ANY; + } + + public PermissionSet(List permissions, byte mode) { + super(); + this.permissions = permissions; + this.mode = mode; + } + + public List getPermissions() { + return permissions; + } + + public void setPermissions(List permissions) { + this.permissions = permissions; + } + + public byte getMode() { + return mode; + } + + public void setMode(byte mode) { + this.mode = mode; + } } diff --git a/IntelliQ/backend/src/main/java/com/intelliq/appengine/api/endpoint/Endpoint.java b/IntelliQ/backend/src/main/java/com/intelliq/appengine/api/endpoint/Endpoint.java index d9e111d..3981308 100644 --- a/IntelliQ/backend/src/main/java/com/intelliq/appengine/api/endpoint/Endpoint.java +++ b/IntelliQ/backend/src/main/java/com/intelliq/appengine/api/endpoint/Endpoint.java @@ -16,143 +16,143 @@ /** * Abstract endpoint that every API endpoint needs to extend. - * Contains all basic methods and some that may need to be overwritten. + * Contains all basic methods and some that may need to be overwritten. */ public abstract class Endpoint { - private static final Logger log = Logger.getLogger(Endpoint.class.getName()); - - /** - * Used for assigning requests to endpoints - */ - public String getEndpointPath() { - return EndpointManager.ENDPOINT_API; - } - - /** - * Checks if the requested URL can be handled by an endpoint - */ - public boolean shouldHandleRequest(ApiRequest request) { - if (request.getUrl().contains(getEndpointPath())) { - return true; - } - return false; - } - - /** - * Main method for actually handling an request. Avoid overwriting this, - * use @generateRequestResponse() instead - */ - public ApiResponse processRequest(ApiRequest request) { - ApiResponse response = new ApiResponse(); - - try { - checkParameters(request); - } catch (Exception ex) { - log.warning("Malformed request: " + ex.getMessage()); - response.setStatusCode(HttpServletResponse.SC_BAD_REQUEST); - response.setException(ex); - return response; - } - - try { - authorizeRequest(request); - } catch (Exception ex) { - log.warning("Request blocked: " + ex.getMessage()); - response.setStatusCode(HttpServletResponse.SC_FORBIDDEN); - response.setException(ex); - return response; - } - - try { - response = generateRequestResponse(request); - } catch (Exception ex) { - log.warning("Unable to handle request: " + ex.getMessage()); - ex.printStackTrace(); - response.setStatusCode(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); - response.setException(ex); - return response; - } - - return response; - } - - /** - * Checks if the request initiator is authorized to call the endpoint - */ - private void authorizeRequest(ApiRequest request) throws Exception { - // optional: authorize all request on the dev server - if (request.getHost().contains(EndpointManager.HOST_LOCAL_DEV_SERVER)) { - //return - } - - // check if the requested endpoint requires an authenticated user - if (!requiresAuthorization(request)) { - return; - } - - // get the user that initiated the request - UserEntry user; - try { - user = request.getUserFromToken(); - } catch (Exception ex) { - throw new Exception("Request requires an authorized user: " + ex.getMessage()); - } - - // check if the user has the required permissions - PermissionSet permissionSet = getRequiredPermissions(request); - for (int i = 0; i < permissionSet.getPermissions().size(); i++) { - if (user.hasPermission(permissionSet.getPermissions().get(i))) { - if (permissionSet.getMode() == PermissionSet.REQUIRE_ANY) { - break; - } - } else { - if (permissionSet.getMode() == PermissionSet.REQUIRE_ALL || i == permissionSet.getPermissions().size() - 1) { - throw new Exception("User has not the required permission to perform this request"); - } - } - } - } - - /** - * Overwrite if the endpoint requires an authorized user - */ - public boolean requiresAuthorization(ApiRequest request) { - return false; - } - - /** - * Overwrite if the endpoint requires some mandatory parameters to be set. - * An exception will be thrown if one of the parameters is not set - */ - public List getRequiredParameters(ApiRequest request) { - return new ArrayList(); - } - - /** - * Checks if all parameters specified in @getRequiredParameters() are set - */ - private void checkParameters(ApiRequest request) throws Exception { - for (String parameter : getRequiredParameters(request)) { - if (!ParserHelper.containsAnyValue(request.getRequest().getParameter(parameter))) { - throw new Exception("Required parameter missing: " + parameter); - } - } - } - - /** - * Overwrite if the endpoint requires the authorized user to have - * some special permission granted (e.g. for editing a business) - */ - public PermissionSet getRequiredPermissions(ApiRequest request) { - return new PermissionSet(); - } - - /** - * Overwrite this to do the endpoint specific work and return some @ApiResponse object - */ - public ApiResponse generateRequestResponse(ApiRequest request) throws Exception { - throw new Exception("Endpoint not implemenetd"); - } - + private static final Logger log = Logger.getLogger(Endpoint.class.getName()); + + /** + * Used for assigning requests to endpoints + */ + public String getEndpointPath() { + return EndpointManager.ENDPOINT_API; + } + + /** + * Checks if the requested URL can be handled by an endpoint + */ + public boolean shouldHandleRequest(ApiRequest request) { + if (request.getUrl().contains(getEndpointPath())) { + return true; + } + return false; + } + + /** + * Main method for actually handling an request. Avoid overwriting this, + * use @generateRequestResponse() instead + */ + public ApiResponse processRequest(ApiRequest request) { + ApiResponse response = new ApiResponse(); + + try { + checkParameters(request); + } catch (Exception ex) { + log.warning("Malformed request: " + ex.getMessage()); + response.setStatusCode(HttpServletResponse.SC_BAD_REQUEST); + response.setException(ex); + return response; + } + + try { + authorizeRequest(request); + } catch (Exception ex) { + log.warning("Request blocked: " + ex.getMessage()); + response.setStatusCode(HttpServletResponse.SC_FORBIDDEN); + response.setException(ex); + return response; + } + + try { + response = generateRequestResponse(request); + } catch (Exception ex) { + log.warning("Unable to handle request: " + ex.getMessage()); + ex.printStackTrace(); + response.setStatusCode(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); + response.setException(ex); + return response; + } + + return response; + } + + /** + * Checks if the request initiator is authorized to call the endpoint + */ + private void authorizeRequest(ApiRequest request) throws Exception { + // optional: authorize all request on the dev server + if (request.getHost().contains(EndpointManager.HOST_LOCAL_DEV_SERVER)) { + //return + } + + // check if the requested endpoint requires an authenticated user + if (!requiresAuthorization(request)) { + return; + } + + // get the user that initiated the request + UserEntry user; + try { + user = request.getUserFromToken(); + } catch (Exception ex) { + throw new Exception("Request requires an authorized user: " + ex.getMessage()); + } + + // check if the user has the required permissions + PermissionSet permissionSet = getRequiredPermissions(request); + for (int i = 0; i < permissionSet.getPermissions().size(); i++) { + if (user.hasPermission(permissionSet.getPermissions().get(i))) { + if (permissionSet.getMode() == PermissionSet.REQUIRE_ANY) { + break; + } + } else { + if (permissionSet.getMode() == PermissionSet.REQUIRE_ALL || i == permissionSet.getPermissions().size() - 1) { + throw new Exception("User has not the required permission to perform this request"); + } + } + } + } + + /** + * Overwrite if the endpoint requires an authorized user + */ + public boolean requiresAuthorization(ApiRequest request) { + return false; + } + + /** + * Overwrite if the endpoint requires some mandatory parameters to be set. + * An exception will be thrown if one of the parameters is not set + */ + public List getRequiredParameters(ApiRequest request) { + return new ArrayList(); + } + + /** + * Checks if all parameters specified in @getRequiredParameters() are set + */ + private void checkParameters(ApiRequest request) throws Exception { + for (String parameter : getRequiredParameters(request)) { + if (!ParserHelper.containsAnyValue(request.getRequest().getParameter(parameter))) { + throw new Exception("Required parameter missing: " + parameter); + } + } + } + + /** + * Overwrite if the endpoint requires the authorized user to have + * some special permission granted (e.g. for editing a business) + */ + public PermissionSet getRequiredPermissions(ApiRequest request) { + return new PermissionSet(); + } + + /** + * Overwrite this to do the endpoint specific work and return some @ApiResponse object + */ + public ApiResponse generateRequestResponse(ApiRequest request) throws Exception { + throw new Exception("Endpoint not implemenetd"); + } + } diff --git a/IntelliQ/backend/src/main/java/com/intelliq/appengine/api/endpoint/EndpointManager.java b/IntelliQ/backend/src/main/java/com/intelliq/appengine/api/endpoint/EndpointManager.java index 80ab1b5..e480b36 100644 --- a/IntelliQ/backend/src/main/java/com/intelliq/appengine/api/endpoint/EndpointManager.java +++ b/IntelliQ/backend/src/main/java/com/intelliq/appengine/api/endpoint/EndpointManager.java @@ -11,9 +11,9 @@ public final class EndpointManager { - public static final int API_VERSION = 1; - - public static final String DEFAULT_HOST = "http://intelliq.me/"; + public static final int API_VERSION = 1; + + public static final String DEFAULT_HOST = "http://intelliq.me/"; public static final String VERSIONED_HOST = "http://" + API_VERSION + "-dot-intelliq-me.appspot.com/"; public static final String HOST = DEFAULT_HOST; public static final String HOST_LOCAL_DEV_SERVER = "localhost:8888"; @@ -44,6 +44,7 @@ public final class EndpointManager { public static final String ENDPOINT_QUEUE_ITEM = ENDPOINT_API + "item/"; public static final String ENDPOINT_QUEUE_ITEM_GET = ENDPOINT_QUEUE_ITEM + "get/"; public static final String ENDPOINT_QUEUE_ITEM_ADD = ENDPOINT_QUEUE_ITEM + "add/"; + public static final String ENDPOINT_QUEUE_ITEM_FROM = ENDPOINT_QUEUE_ITEM + "from/"; public static final String ENDPOINT_QUEUE_ITEM_STATUS = ENDPOINT_QUEUE_ITEM + "status/"; public static final String ENDPOINT_QUEUE_ITEM_DELETE = ENDPOINT_QUEUE_ITEM + "delete/"; public static final String ENDPOINT_QUEUE_ITEM_LAST = ENDPOINT_QUEUE_ITEM + "last/"; @@ -54,36 +55,36 @@ public final class EndpointManager { public static final String ENDPOINT_USER_SIGNIN = ENDPOINT_USER + "signin/"; public static final String ENDPOINT_USER_SET_LOCATION = ENDPOINT_USER + "set/location/"; public static final String ENDPOINT_USER_SET_STATUS = ENDPOINT_USER + "set/status/"; - + // Images public static final String ENDPOINT_IMAGE = "image/"; - + public static final List endpoints = getAvailableEndpoints(); - + private EndpointManager() { - + } - + private static List getAvailableEndpoints() { - List endpoints = new ArrayList(); - endpoints.add(new QueueEndpoint()); - endpoints.add(new QueueItemEndpoint()); - endpoints.add(new BusinessEndpoint()); - endpoints.add(new UserEndpoint()); - return endpoints; + List endpoints = new ArrayList(); + endpoints.add(new QueueEndpoint()); + endpoints.add(new QueueItemEndpoint()); + endpoints.add(new BusinessEndpoint()); + endpoints.add(new UserEndpoint()); + return endpoints; } - + public static Endpoint getEndpointForRequest(ApiRequest request) { - return getEndpointForRequest(request, endpoints); + return getEndpointForRequest(request, endpoints); } - + public static Endpoint getEndpointForRequest(ApiRequest request, List availableEndpoints) { - for (Endpoint endpoint : availableEndpoints) { - if (endpoint.shouldHandleRequest(request)) { - return endpoint; - } - } - return null; + for (Endpoint endpoint : availableEndpoints) { + if (endpoint.shouldHandleRequest(request)) { + return endpoint; + } + } + return null; } - + } diff --git a/IntelliQ/backend/src/main/java/com/intelliq/appengine/api/endpoint/business/AddBusinessEndpoint.java b/IntelliQ/backend/src/main/java/com/intelliq/appengine/api/endpoint/business/AddBusinessEndpoint.java index ed4d4ef..bb9a9a2 100644 --- a/IntelliQ/backend/src/main/java/com/intelliq/appengine/api/endpoint/business/AddBusinessEndpoint.java +++ b/IntelliQ/backend/src/main/java/com/intelliq/appengine/api/endpoint/business/AddBusinessEndpoint.java @@ -23,82 +23,84 @@ import com.intelliq.appengine.datastore.entries.QueueEntry; import com.intelliq.appengine.datastore.entries.QueueItemEntry; import com.intelliq.appengine.datastore.entries.UserEntry; +import com.intelliq.appengine.logging.BusinessLogging; public class AddBusinessEndpoint extends Endpoint { - public static final long MAXIMUM_BUSINESS_CREATIONS = 10; - private static final Logger log = Logger.getLogger(AddBusinessEndpoint.class.getName()); - - @Override - public String getEndpointPath() { - return EndpointManager.ENDPOINT_BUSINESS_ADD; - } - - @Override - public List getRequiredParameters(ApiRequest request) { - List parameters = new ArrayList(); - parameters.add("name"); - parameters.add("mail"); - return parameters; - } - - @Override - public boolean requiresAuthorization(ApiRequest request) { - return true; - } - - @Override - public ApiResponse generateRequestResponse(ApiRequest request) throws Exception { - ApiResponse response = new ApiResponse(); - - // prevent spam - UserEntry user = request.getUser(); - long businessesCreated = user.getStats().getBusinessesCreated(); - if (businessesCreated >= MAXIMUM_BUSINESS_CREATIONS) { - log.warning("Prevented business creation from user: " + user.getKey().getId()); - response.setStatusCode(HttpServletResponse.SC_FORBIDDEN); - response.setException(new Exception("Spam prevention: You may not create any more businesses. Please contact us if you want to increase your quota.")); - return response; - } - - String name = request.getParameter("name"); - String mail = request.getParameter("mail"); - boolean addQueue = request.getParameterAsBoolean("addQueue", false); - - // create the business - BusinessEntry businessEntry = new BusinessEntry(); - businessEntry.setName(name); - businessEntry.setMail(mail); - Key businessKey = BusinessHelper.saveEntry(businessEntry); - businessEntry.setKey(businessKey); - - // update user stats - user.getStats().setBusinessesCreated(businessesCreated+ 1); - UserHelper.saveEntry(user); - - // add permission for business - PermissionHelper.grantPermission(user, businessEntry, PermissionEntry.PERMISSION_OWN); - - if (addQueue) { - // create a default queue - QueueEntry queueEntry = new QueueEntry(businessKey.getId()); - queueEntry.parseFromRequest(request); - - Key queueKey = QueueHelper.saveEntry(queueEntry); - queueEntry.setKey(queueKey); - - // add permission for queue - PermissionHelper.grantPermission(request.getUser(), queueEntry, PermissionEntry.PERMISSION_OWN); - - // add the new queue to the business in order to return both as one response - ArrayList queues = new ArrayList(); - queues.add(queueEntry); - businessEntry.setQueues(queues); - } - - response.setContent(businessEntry); - return response; - } - + public static final long MAXIMUM_BUSINESS_CREATIONS = 10; + private static final Logger log = Logger.getLogger(AddBusinessEndpoint.class.getName()); + + @Override + public String getEndpointPath() { + return EndpointManager.ENDPOINT_BUSINESS_ADD; + } + + @Override + public List getRequiredParameters(ApiRequest request) { + List parameters = new ArrayList(); + parameters.add("name"); + parameters.add("mail"); + return parameters; + } + + @Override + public boolean requiresAuthorization(ApiRequest request) { + return true; + } + + @Override + public ApiResponse generateRequestResponse(ApiRequest request) throws Exception { + ApiResponse response = new ApiResponse(); + + // prevent spam + UserEntry user = request.getUser(); + long businessesCreated = user.getStats().getBusinessesCreated(); + if (businessesCreated >= MAXIMUM_BUSINESS_CREATIONS) { + log.warning("Prevented business creation from user: " + user.getKey().getId()); + response.setStatusCode(HttpServletResponse.SC_FORBIDDEN); + response.setException(new Exception("Spam prevention: You may not create any more businesses. Please contact us if you want to increase your quota.")); + return response; + } + + String name = request.getParameter("name"); + String mail = request.getParameter("mail"); + boolean addQueue = request.getParameterAsBoolean("addQueue", false); + + // create the business + BusinessEntry businessEntry = new BusinessEntry(); + businessEntry.setName(name); + businessEntry.setMail(mail); + Key businessKey = BusinessHelper.saveEntry(businessEntry); + businessEntry.setKey(businessKey); + + // update user stats + user.getStats().setBusinessesCreated(businessesCreated + 1); + UserHelper.saveEntry(user); + + // add permission for business + PermissionHelper.grantPermission(user, businessEntry, PermissionEntry.PERMISSION_OWN); + + if (addQueue) { + // create a default queue + QueueEntry queueEntry = new QueueEntry(businessKey.getId()); + queueEntry.parseFromRequest(request); + + Key queueKey = QueueHelper.saveEntry(queueEntry); + queueEntry.setKey(queueKey); + + // add permission for queue + PermissionHelper.grantPermission(request.getUser(), queueEntry, PermissionEntry.PERMISSION_OWN); + + // add the new queue to the business in order to return both as one response + ArrayList queues = new ArrayList(); + queues.add(queueEntry); + businessEntry.setQueues(queues); + } + + response.setContent(businessEntry); + BusinessLogging.logCreation(businessEntry, user); + return response; + } + } diff --git a/IntelliQ/backend/src/main/java/com/intelliq/appengine/api/endpoint/business/BusinessEndpoint.java b/IntelliQ/backend/src/main/java/com/intelliq/appengine/api/endpoint/business/BusinessEndpoint.java index 01597fb..513bb53 100644 --- a/IntelliQ/backend/src/main/java/com/intelliq/appengine/api/endpoint/business/BusinessEndpoint.java +++ b/IntelliQ/backend/src/main/java/com/intelliq/appengine/api/endpoint/business/BusinessEndpoint.java @@ -10,30 +10,30 @@ public class BusinessEndpoint extends Endpoint { - public static final List endpoints = getAvailableEndpoints(); - - @Override - public String getEndpointPath() { - return EndpointManager.ENDPOINT_BUSINESS; - } - - @Override - public ApiResponse processRequest(ApiRequest request) { - Endpoint endpoint = EndpointManager.getEndpointForRequest(request, endpoints); - if (endpoint != null) { - return endpoint.processRequest(request); - } else { - return super.processRequest(request); - } - } - - private static List getAvailableEndpoints() { - List endpoints = new ArrayList(); - endpoints.add(new GetBusinessEndpoint()); - endpoints.add(new AddBusinessEndpoint()); - endpoints.add(new EditBusinessEndpoint()); - endpoints.add(new FromBusinessEndpoint()); - return endpoints; + public static final List endpoints = getAvailableEndpoints(); + + @Override + public String getEndpointPath() { + return EndpointManager.ENDPOINT_BUSINESS; + } + + @Override + public ApiResponse processRequest(ApiRequest request) { + Endpoint endpoint = EndpointManager.getEndpointForRequest(request, endpoints); + if (endpoint != null) { + return endpoint.processRequest(request); + } else { + return super.processRequest(request); + } } - + + private static List getAvailableEndpoints() { + List endpoints = new ArrayList(); + endpoints.add(new GetBusinessEndpoint()); + endpoints.add(new AddBusinessEndpoint()); + endpoints.add(new EditBusinessEndpoint()); + endpoints.add(new FromBusinessEndpoint()); + return endpoints; + } + } diff --git a/IntelliQ/backend/src/main/java/com/intelliq/appengine/api/endpoint/business/EditBusinessEndpoint.java b/IntelliQ/backend/src/main/java/com/intelliq/appengine/api/endpoint/business/EditBusinessEndpoint.java index e5374e4..433c3d3 100644 --- a/IntelliQ/backend/src/main/java/com/intelliq/appengine/api/endpoint/business/EditBusinessEndpoint.java +++ b/IntelliQ/backend/src/main/java/com/intelliq/appengine/api/endpoint/business/EditBusinessEndpoint.java @@ -24,61 +24,64 @@ import com.intelliq.appengine.datastore.entries.QueueEntry; import com.intelliq.appengine.datastore.entries.QueueItemEntry; import com.intelliq.appengine.datastore.entries.UserEntry; +import com.intelliq.appengine.logging.BusinessLogging; public class EditBusinessEndpoint extends Endpoint { - public static final long MAXIMUM_BUSINESS_CREATIONS = 10; - - @Override - public String getEndpointPath() { - return EndpointManager.ENDPOINT_BUSINESS_EDIT; - } - - @Override - public List getRequiredParameters(ApiRequest request) { - List parameters = new ArrayList(); - parameters.add("businessKeyId"); - parameters.add("name"); - parameters.add("mail"); - return parameters; - } - - @Override - public boolean requiresAuthorization(ApiRequest request) { - return true; - } - - @Override - public PermissionSet getRequiredPermissions(ApiRequest request) { - PermissionSet permissionSet = new PermissionSet(); - - PermissionEntry permissionEntry = new PermissionEntry(); - permissionEntry.setPermission(PermissionEntry.PERMISSION_EDIT); - permissionEntry.setSubjectKeyId(request.getParameterAsLong("businessKeyId", -1)); - permissionSet.getPermissions().add(permissionEntry); - - return permissionSet; - } - - @Override - public ApiResponse generateRequestResponse(ApiRequest request) throws Exception { - ApiResponse response = new ApiResponse(); - long businessKeyId = request.getParameterAsLong("businessKeyId", -1); - - try { - BusinessEntry businessEntry = BusinessHelper.getEntryByKeyId(businessKeyId); - businessEntry.parseFromRequest(request); - BusinessHelper.saveEntry(businessEntry); - - //TODO: add action - - response.setContent(businessEntry); - } catch (JDOObjectNotFoundException exception) { - response.setStatusCode(HttpServletResponse.SC_NOT_FOUND); - response.setException(new Exception("Unable to find requested business")); - } - return response; - } - + public static final long MAXIMUM_BUSINESS_CREATIONS = 10; + + @Override + public String getEndpointPath() { + return EndpointManager.ENDPOINT_BUSINESS_EDIT; + } + + @Override + public List getRequiredParameters(ApiRequest request) { + List parameters = new ArrayList(); + parameters.add("businessKeyId"); + parameters.add("name"); + parameters.add("mail"); + return parameters; + } + + @Override + public boolean requiresAuthorization(ApiRequest request) { + return true; + } + + @Override + public PermissionSet getRequiredPermissions(ApiRequest request) { + PermissionSet permissionSet = new PermissionSet(); + + PermissionEntry permissionEntry = new PermissionEntry(); + permissionEntry.setPermission(PermissionEntry.PERMISSION_EDIT); + permissionEntry.setSubjectKeyId(request.getParameterAsLong("businessKeyId", -1)); + permissionSet.getPermissions().add(permissionEntry); + + return permissionSet; + } + + @Override + public ApiResponse generateRequestResponse(ApiRequest request) throws Exception { + ApiResponse response = new ApiResponse(); + long businessKeyId = request.getParameterAsLong("businessKeyId", -1); + + try { + UserEntry user = request.getUser(); + BusinessEntry businessEntry = BusinessHelper.getEntryByKeyId(businessKeyId); + businessEntry.parseFromRequest(request); + BusinessHelper.saveEntry(businessEntry); + + //TODO: add action + + response.setContent(businessEntry); + BusinessLogging.logEdit(businessEntry, user); + } catch (JDOObjectNotFoundException exception) { + response.setStatusCode(HttpServletResponse.SC_NOT_FOUND); + response.setException(new Exception("Unable to find requested business")); + } + return response; + } + } diff --git a/IntelliQ/backend/src/main/java/com/intelliq/appengine/api/endpoint/business/FromBusinessEndpoint.java b/IntelliQ/backend/src/main/java/com/intelliq/appengine/api/endpoint/business/FromBusinessEndpoint.java index afe43b3..7dd307c 100644 --- a/IntelliQ/backend/src/main/java/com/intelliq/appengine/api/endpoint/business/FromBusinessEndpoint.java +++ b/IntelliQ/backend/src/main/java/com/intelliq/appengine/api/endpoint/business/FromBusinessEndpoint.java @@ -24,31 +24,31 @@ public class FromBusinessEndpoint extends Endpoint { - @Override - public String getEndpointPath() { - return EndpointManager.ENDPOINT_BUSINESS_FROM; - } - - @Override - public List getRequiredParameters(ApiRequest request) { - List parameters = new ArrayList(); - parameters.add("userKeyId"); - return parameters; - } - - @Override - public ApiResponse generateRequestResponse(ApiRequest request) throws Exception { - ApiResponse response = new ApiResponse(); - - long userKeyId = request.getParameterAsLong("userKeyId", -1); - try { - List businesses = BusinessHelper.getBusinessesByUserKeyId(userKeyId, PermissionEntry.PERMISSION_VIEW); - response.setContent(businesses); - } catch (NucleusObjectNotFoundException exception) { - response.setStatusCode(HttpServletResponse.SC_NOT_FOUND); - response.setException(new Exception("Unable to find businesses")); - } - return response; - } - + @Override + public String getEndpointPath() { + return EndpointManager.ENDPOINT_BUSINESS_FROM; + } + + @Override + public List getRequiredParameters(ApiRequest request) { + List parameters = new ArrayList(); + parameters.add("userKeyId"); + return parameters; + } + + @Override + public ApiResponse generateRequestResponse(ApiRequest request) throws Exception { + ApiResponse response = new ApiResponse(); + + long userKeyId = request.getParameterAsLong("userKeyId", -1); + try { + List businesses = BusinessHelper.getBusinessesByUserKeyId(userKeyId, PermissionEntry.PERMISSION_VIEW); + response.setContent(businesses); + } catch (NucleusObjectNotFoundException exception) { + response.setStatusCode(HttpServletResponse.SC_NOT_FOUND); + response.setException(new Exception("Unable to find businesses")); + } + return response; + } + } diff --git a/IntelliQ/backend/src/main/java/com/intelliq/appengine/api/endpoint/business/GetBusinessEndpoint.java b/IntelliQ/backend/src/main/java/com/intelliq/appengine/api/endpoint/business/GetBusinessEndpoint.java index 457edca..bac5119 100644 --- a/IntelliQ/backend/src/main/java/com/intelliq/appengine/api/endpoint/business/GetBusinessEndpoint.java +++ b/IntelliQ/backend/src/main/java/com/intelliq/appengine/api/endpoint/business/GetBusinessEndpoint.java @@ -23,40 +23,40 @@ public class GetBusinessEndpoint extends Endpoint { - @Override - public String getEndpointPath() { - return EndpointManager.ENDPOINT_BUSINESS_GET; - } - - @Override - public List getRequiredParameters(ApiRequest request) { - List parameters = new ArrayList(); - parameters.add("businessKeyId"); - return parameters; - } - - @Override - public ApiResponse generateRequestResponse(ApiRequest request) throws Exception { - ApiResponse response = new ApiResponse(); - - long businessKeyId = request.getParameterAsLong("businessKeyId", -1); - boolean includeQueues = request.getParameterAsBoolean("includeQueues", true); - - try { - BusinessEntry businessEntry = BusinessHelper.getEntryByKeyId(businessKeyId); - if (includeQueues) { - businessEntry.setQueues(QueueHelper.getQueuesByBusiness(businessKeyId)); - for (QueueEntry queue : businessEntry.getQueues()) { - queue.setWaitingPeople(QueueHelper.getNumberOfItemsInQueue(queue.getKey().getId(), QueueItemEntry.STATUS_WAITING)); - } - } - - response.setContent(businessEntry); - } catch (NucleusObjectNotFoundException exception) { - response.setStatusCode(HttpServletResponse.SC_NOT_FOUND); - response.setException(new Exception("Unable to find requested business")); - } - return response; - } - + @Override + public String getEndpointPath() { + return EndpointManager.ENDPOINT_BUSINESS_GET; + } + + @Override + public List getRequiredParameters(ApiRequest request) { + List parameters = new ArrayList(); + parameters.add("businessKeyId"); + return parameters; + } + + @Override + public ApiResponse generateRequestResponse(ApiRequest request) throws Exception { + ApiResponse response = new ApiResponse(); + + long businessKeyId = request.getParameterAsLong("businessKeyId", -1); + boolean includeQueues = request.getParameterAsBoolean("includeQueues", true); + + try { + BusinessEntry businessEntry = BusinessHelper.getEntryByKeyId(businessKeyId); + if (includeQueues) { + businessEntry.setQueues(QueueHelper.getQueuesByBusiness(businessKeyId)); + for (QueueEntry queue : businessEntry.getQueues()) { + queue.setWaitingPeople(QueueHelper.getNumberOfItemsInQueue(queue.getKey().getId(), QueueItemEntry.STATUS_WAITING)); + } + } + + response.setContent(businessEntry); + } catch (NucleusObjectNotFoundException exception) { + response.setStatusCode(HttpServletResponse.SC_NOT_FOUND); + response.setException(new Exception("Unable to find requested business")); + } + return response; + } + } diff --git a/IntelliQ/backend/src/main/java/com/intelliq/appengine/api/endpoint/queue/AddQueueEndpoint.java b/IntelliQ/backend/src/main/java/com/intelliq/appengine/api/endpoint/queue/AddQueueEndpoint.java index 5a66f20..d82989e 100644 --- a/IntelliQ/backend/src/main/java/com/intelliq/appengine/api/endpoint/queue/AddQueueEndpoint.java +++ b/IntelliQ/backend/src/main/java/com/intelliq/appengine/api/endpoint/queue/AddQueueEndpoint.java @@ -18,77 +18,79 @@ import com.intelliq.appengine.datastore.entries.PermissionEntry; import com.intelliq.appengine.datastore.entries.QueueEntry; import com.intelliq.appengine.datastore.entries.UserEntry; +import com.intelliq.appengine.logging.QueueLogging; public class AddQueueEndpoint extends Endpoint { - public static final long MAXIMUM_QUEUE_CREATIONS = 25; - private static final Logger log = Logger.getLogger(AddQueueEndpoint.class.getName()); - - @Override - public String getEndpointPath() { - return EndpointManager.ENDPOINT_QUEUE_ADD; - } - - @Override - public boolean requiresAuthorization(ApiRequest request) { - return true; - } - - @Override - public PermissionSet getRequiredPermissions(ApiRequest request) { - PermissionSet permissionSet = new PermissionSet(); - - // only business owners can add queues - PermissionEntry permissionEntry = new PermissionEntry(); - permissionEntry.setPermission(PermissionEntry.PERMISSION_OWN); - permissionEntry.setSubjectKeyId(request.getParameterAsLong("businessKeyId", -1)); - permissionSet.getPermissions().add(permissionEntry); - - return permissionSet; - } - - @Override - public List getRequiredParameters(ApiRequest request) { - List parameters = new ArrayList(); - parameters.add("businessKeyId"); - parameters.add("latitude"); - parameters.add("longitude"); - parameters.add("name"); - parameters.add("averageWaitingTime"); - return parameters; - } - - @Override - public ApiResponse generateRequestResponse(ApiRequest request) throws Exception { - ApiResponse response = new ApiResponse(); - - // prevent spam - UserEntry user = request.getUser(); - long queuesCreated = user.getStats().getQueuesCreated(); - if (queuesCreated >= MAXIMUM_QUEUE_CREATIONS) { - log.warning("Prevented queue creation from user: " + user.getKey().getId()); - response.setStatusCode(HttpServletResponse.SC_FORBIDDEN); - response.setException(new Exception("Spam prevention: You may not create any more queues. Please contact us if you want to increase your quota.")); - return response; - } - - // save queue - long businessKeyId = request.getParameterAsLong("businessKeyId", -1); - QueueEntry queueEntry = new QueueEntry(businessKeyId); - queueEntry.parseFromRequest(request); - Key entryKey = QueueHelper.saveEntry(queueEntry); - queueEntry.setKey(entryKey); - - // update user stats - user.getStats().setQueuesCreated(queuesCreated + 1); - UserHelper.saveEntry(user); - - // add permission for queue - PermissionHelper.grantPermission(user, queueEntry, PermissionEntry.PERMISSION_OWN); - - response.setContent(queueEntry); - return response; - } - + public static final long MAXIMUM_QUEUE_CREATIONS = 25; + private static final Logger log = Logger.getLogger(AddQueueEndpoint.class.getName()); + + @Override + public String getEndpointPath() { + return EndpointManager.ENDPOINT_QUEUE_ADD; + } + + @Override + public boolean requiresAuthorization(ApiRequest request) { + return true; + } + + @Override + public PermissionSet getRequiredPermissions(ApiRequest request) { + PermissionSet permissionSet = new PermissionSet(); + + // only business owners can add queues + PermissionEntry permissionEntry = new PermissionEntry(); + permissionEntry.setPermission(PermissionEntry.PERMISSION_OWN); + permissionEntry.setSubjectKeyId(request.getParameterAsLong("businessKeyId", -1)); + permissionSet.getPermissions().add(permissionEntry); + + return permissionSet; + } + + @Override + public List getRequiredParameters(ApiRequest request) { + List parameters = new ArrayList(); + parameters.add("businessKeyId"); + parameters.add("latitude"); + parameters.add("longitude"); + parameters.add("name"); + parameters.add("averageWaitingTime"); + return parameters; + } + + @Override + public ApiResponse generateRequestResponse(ApiRequest request) throws Exception { + ApiResponse response = new ApiResponse(); + + // prevent spam + UserEntry user = request.getUser(); + long queuesCreated = user.getStats().getQueuesCreated(); + if (queuesCreated >= MAXIMUM_QUEUE_CREATIONS) { + log.warning("Prevented queue creation from user: " + user.getKey().getId()); + response.setStatusCode(HttpServletResponse.SC_FORBIDDEN); + response.setException(new Exception("Spam prevention: You may not create any more queues. Please contact us if you want to increase your quota.")); + return response; + } + + // save queue + long businessKeyId = request.getParameterAsLong("businessKeyId", -1); + QueueEntry queueEntry = new QueueEntry(businessKeyId); + queueEntry.parseFromRequest(request); + Key entryKey = QueueHelper.saveEntry(queueEntry); + queueEntry.setKey(entryKey); + + // update user stats + user.getStats().setQueuesCreated(queuesCreated + 1); + UserHelper.saveEntry(user); + + // add permission for queue + PermissionHelper.grantPermission(user, queueEntry, PermissionEntry.PERMISSION_OWN); + + response.setContent(queueEntry); + QueueLogging.logCreation(queueEntry, user); + return response; + } + } diff --git a/IntelliQ/backend/src/main/java/com/intelliq/appengine/api/endpoint/queue/ClearQueueItemsEndpoint.java b/IntelliQ/backend/src/main/java/com/intelliq/appengine/api/endpoint/queue/ClearQueueItemsEndpoint.java index 1aaa8ff..2a728fe 100644 --- a/IntelliQ/backend/src/main/java/com/intelliq/appengine/api/endpoint/queue/ClearQueueItemsEndpoint.java +++ b/IntelliQ/backend/src/main/java/com/intelliq/appengine/api/endpoint/queue/ClearQueueItemsEndpoint.java @@ -17,66 +17,66 @@ public class ClearQueueItemsEndpoint extends Endpoint { - @Override - public String getEndpointPath() { - return EndpointManager.ENDPOINT_QUEUE_CLEAR; - } - - @Override - public boolean requiresAuthorization(ApiRequest request) { - return true; - } - - @Override - public List getRequiredParameters(ApiRequest request) { - List parameters = new ArrayList(); - parameters.add("queueKeyId"); - return parameters; - } - - @Override - public PermissionSet getRequiredPermissions(ApiRequest request) { - PermissionSet permissionSet = new PermissionSet(); - long queueKeyId = request.getParameterAsLong("queueKeyId", -1); - - PermissionEntry permissionEntry = new PermissionEntry(); - permissionEntry.setPermission(PermissionEntry.PERMISSION_EDIT); - permissionEntry.setSubjectKeyId(queueKeyId); - - permissionSet.getPermissions().add(permissionEntry); - return permissionSet; - } - - @Override - public ApiResponse generateRequestResponse(ApiRequest request) throws Exception { - ApiResponse response = new ApiResponse(); - - long queueKeyId = request.getParameterAsLong("queueKeyId", -1); - byte status = (byte) request.getParameterAsInt("status", QueueItemEntry.STATUS_ALL); - boolean clearWaiting = request.getParameterAsBoolean("clearWaiting", true); - boolean clearCalled = request.getParameterAsBoolean("clearCalled", true); - - if (status == QueueItemEntry.STATUS_ALL) { - if (clearWaiting && clearCalled) { - QueueHelper.deleteItemsInQueue(queueKeyId, QueueItemEntry.STATUS_ALL); - } else { - if (clearWaiting) { - QueueHelper.deleteItemsInQueue(queueKeyId, QueueItemEntry.STATUS_WAITING); - } - if (clearCalled) { - QueueHelper.deleteItemsInQueue(queueKeyId, QueueItemEntry.STATUS_CALLED); - } - QueueHelper.deleteItemsInQueue(queueKeyId, QueueItemEntry.STATUS_CANCELED); - QueueHelper.deleteItemsInQueue(queueKeyId, QueueItemEntry.STATUS_DONE); - } - } else { - QueueHelper.deleteItemsInQueue(queueKeyId, status); - } - - List entries = QueueHelper.getItemsInQueue(queueKeyId, 0, 100); - response.setContent(entries); - return response; - } - - + @Override + public String getEndpointPath() { + return EndpointManager.ENDPOINT_QUEUE_CLEAR; + } + + @Override + public boolean requiresAuthorization(ApiRequest request) { + return true; + } + + @Override + public List getRequiredParameters(ApiRequest request) { + List parameters = new ArrayList(); + parameters.add("queueKeyId"); + return parameters; + } + + @Override + public PermissionSet getRequiredPermissions(ApiRequest request) { + PermissionSet permissionSet = new PermissionSet(); + long queueKeyId = request.getParameterAsLong("queueKeyId", -1); + + PermissionEntry permissionEntry = new PermissionEntry(); + permissionEntry.setPermission(PermissionEntry.PERMISSION_EDIT); + permissionEntry.setSubjectKeyId(queueKeyId); + + permissionSet.getPermissions().add(permissionEntry); + return permissionSet; + } + + @Override + public ApiResponse generateRequestResponse(ApiRequest request) throws Exception { + ApiResponse response = new ApiResponse(); + + long queueKeyId = request.getParameterAsLong("queueKeyId", -1); + byte status = (byte) request.getParameterAsInt("status", QueueItemEntry.STATUS_ALL); + boolean clearWaiting = request.getParameterAsBoolean("clearWaiting", true); + boolean clearCalled = request.getParameterAsBoolean("clearCalled", true); + + if (status == QueueItemEntry.STATUS_ALL) { + if (clearWaiting && clearCalled) { + QueueHelper.deleteItemsInQueue(queueKeyId, QueueItemEntry.STATUS_ALL); + } else { + if (clearWaiting) { + QueueHelper.deleteItemsInQueue(queueKeyId, QueueItemEntry.STATUS_WAITING); + } + if (clearCalled) { + QueueHelper.deleteItemsInQueue(queueKeyId, QueueItemEntry.STATUS_CALLED); + } + QueueHelper.deleteItemsInQueue(queueKeyId, QueueItemEntry.STATUS_CANCELED); + QueueHelper.deleteItemsInQueue(queueKeyId, QueueItemEntry.STATUS_DONE); + } + } else { + QueueHelper.deleteItemsInQueue(queueKeyId, status); + } + + List entries = QueueHelper.getItemsInQueue(queueKeyId, 0, 100); + response.setContent(entries); + return response; + } + + } diff --git a/IntelliQ/backend/src/main/java/com/intelliq/appengine/api/endpoint/queue/EditQueueEndpoint.java b/IntelliQ/backend/src/main/java/com/intelliq/appengine/api/endpoint/queue/EditQueueEndpoint.java index 8b6e612..f842ed1 100644 --- a/IntelliQ/backend/src/main/java/com/intelliq/appengine/api/endpoint/queue/EditQueueEndpoint.java +++ b/IntelliQ/backend/src/main/java/com/intelliq/appengine/api/endpoint/queue/EditQueueEndpoint.java @@ -20,67 +20,70 @@ import com.intelliq.appengine.datastore.entries.PermissionEntry; import com.intelliq.appengine.datastore.entries.QueueEntry; import com.intelliq.appengine.datastore.entries.UserEntry; +import com.intelliq.appengine.logging.QueueLogging; public class EditQueueEndpoint extends Endpoint { - public static final long MAXIMUM_QUEUE_CREATIONS = 25; - - @Override - public String getEndpointPath() { - return EndpointManager.ENDPOINT_QUEUE_EDIT; - } - - @Override - public boolean requiresAuthorization(ApiRequest request) { - return true; - } - - @Override - public PermissionSet getRequiredPermissions(ApiRequest request) { - PermissionSet permissionSet = new PermissionSet(); - - // business editors can also edit queues - PermissionEntry permissionEntry = new PermissionEntry(); - permissionEntry.setPermission(PermissionEntry.PERMISSION_EDIT); - permissionEntry.setSubjectKeyId(request.getParameterAsLong("businessKeyId", -1)); - permissionSet.getPermissions().add(permissionEntry); - - // queue editors can also edit queues - permissionEntry = new PermissionEntry(); - permissionEntry.setPermission(PermissionEntry.PERMISSION_EDIT); - permissionEntry.setSubjectKeyId(request.getParameterAsLong("queueKeyId", -1)); - permissionSet.getPermissions().add(permissionEntry); - - return permissionSet; - } - - @Override - public List getRequiredParameters(ApiRequest request) { - List parameters = new ArrayList(); - parameters.add("queueKeyId"); - return parameters; - } - - @Override - public ApiResponse generateRequestResponse(ApiRequest request) throws Exception { - ApiResponse response = new ApiResponse(); - long queueKeyId = request.getParameterAsLong("queueKeyId", -1); - - try { - QueueEntry queueEntry = QueueHelper.getEntryByKeyId(queueKeyId); - queueEntry.parseFromRequest(request); - QueueHelper.saveEntry(queueEntry); - - //TODO: add action - - response.setContent(queueEntry); - } catch (JDOObjectNotFoundException exception) { - response.setStatusCode(HttpServletResponse.SC_NOT_FOUND); - response.setException(new Exception("Unable to find requested queue")); - } - - return response; - } - + public static final long MAXIMUM_QUEUE_CREATIONS = 25; + + @Override + public String getEndpointPath() { + return EndpointManager.ENDPOINT_QUEUE_EDIT; + } + + @Override + public boolean requiresAuthorization(ApiRequest request) { + return true; + } + + @Override + public PermissionSet getRequiredPermissions(ApiRequest request) { + PermissionSet permissionSet = new PermissionSet(); + + // business editors can also edit queues + PermissionEntry permissionEntry = new PermissionEntry(); + permissionEntry.setPermission(PermissionEntry.PERMISSION_EDIT); + permissionEntry.setSubjectKeyId(request.getParameterAsLong("businessKeyId", -1)); + permissionSet.getPermissions().add(permissionEntry); + + // queue editors can also edit queues + permissionEntry = new PermissionEntry(); + permissionEntry.setPermission(PermissionEntry.PERMISSION_EDIT); + permissionEntry.setSubjectKeyId(request.getParameterAsLong("queueKeyId", -1)); + permissionSet.getPermissions().add(permissionEntry); + + return permissionSet; + } + + @Override + public List getRequiredParameters(ApiRequest request) { + List parameters = new ArrayList(); + parameters.add("queueKeyId"); + return parameters; + } + + @Override + public ApiResponse generateRequestResponse(ApiRequest request) throws Exception { + ApiResponse response = new ApiResponse(); + long queueKeyId = request.getParameterAsLong("queueKeyId", -1); + + try { + UserEntry user = request.getUser(); + QueueEntry queueEntry = QueueHelper.getEntryByKeyId(queueKeyId); + queueEntry.parseFromRequest(request); + QueueHelper.saveEntry(queueEntry); + + //TODO: add action + + response.setContent(queueEntry); + QueueLogging.logEdit(queueEntry, user); + } catch (JDOObjectNotFoundException exception) { + response.setStatusCode(HttpServletResponse.SC_NOT_FOUND); + response.setException(new Exception("Unable to find requested queue")); + } + + return response; + } + } diff --git a/IntelliQ/backend/src/main/java/com/intelliq/appengine/api/endpoint/queue/GetItemsInQueueEndpoint.java b/IntelliQ/backend/src/main/java/com/intelliq/appengine/api/endpoint/queue/GetItemsInQueueEndpoint.java index 459c89d..8f58f93 100644 --- a/IntelliQ/backend/src/main/java/com/intelliq/appengine/api/endpoint/queue/GetItemsInQueueEndpoint.java +++ b/IntelliQ/backend/src/main/java/com/intelliq/appengine/api/endpoint/queue/GetItemsInQueueEndpoint.java @@ -16,34 +16,34 @@ public class GetItemsInQueueEndpoint extends Endpoint { - @Override - public String getEndpointPath() { - return EndpointManager.ENDPOINT_QUEUE_ITEMS; - } - - @Override - public List getRequiredParameters(ApiRequest request) { - List parameters = new ArrayList(); - parameters.add("queueKeyId"); - return parameters; - } - - @Override - public ApiResponse generateRequestResponse(ApiRequest request) throws Exception { - ApiResponse response = new ApiResponse(); - - long queueKeyId = request.getParameterAsLong("queueKeyId", -1); - int offset = request.getParameterAsInt("offset", 0); - int count = request.getParameterAsInt("count", 100); - - try { - List entries = QueueHelper.getItemsInQueue(queueKeyId, offset, count); - response.setContent(entries); - } catch (JDOObjectNotFoundException exception) { - response.setStatusCode(HttpServletResponse.SC_NOT_FOUND); - response.setException(new Exception("Unable to find requested queue")); - } - return response; - } - + @Override + public String getEndpointPath() { + return EndpointManager.ENDPOINT_QUEUE_ITEMS; + } + + @Override + public List getRequiredParameters(ApiRequest request) { + List parameters = new ArrayList(); + parameters.add("queueKeyId"); + return parameters; + } + + @Override + public ApiResponse generateRequestResponse(ApiRequest request) throws Exception { + ApiResponse response = new ApiResponse(); + + long queueKeyId = request.getParameterAsLong("queueKeyId", -1); + int offset = request.getParameterAsInt("offset", 0); + int count = request.getParameterAsInt("count", 100); + + try { + List entries = QueueHelper.getItemsInQueue(queueKeyId, offset, count); + response.setContent(entries); + } catch (JDOObjectNotFoundException exception) { + response.setStatusCode(HttpServletResponse.SC_NOT_FOUND); + response.setException(new Exception("Unable to find requested queue")); + } + return response; + } + } diff --git a/IntelliQ/backend/src/main/java/com/intelliq/appengine/api/endpoint/queue/GetNearbyQueuesEndpoint.java b/IntelliQ/backend/src/main/java/com/intelliq/appengine/api/endpoint/queue/GetNearbyQueuesEndpoint.java index 1674e98..94ccaac 100644 --- a/IntelliQ/backend/src/main/java/com/intelliq/appengine/api/endpoint/queue/GetNearbyQueuesEndpoint.java +++ b/IntelliQ/backend/src/main/java/com/intelliq/appengine/api/endpoint/queue/GetNearbyQueuesEndpoint.java @@ -16,55 +16,55 @@ public class GetNearbyQueuesEndpoint extends Endpoint { - @Override - public String getEndpointPath() { - return EndpointManager.ENDPOINT_QUEUE_NEARBY; - } - - @Override - public List getRequiredParameters(ApiRequest request) { - List parameters = new ArrayList(); - - String postalCode = request.getParameter("postalCode"); - if (!ParserHelper.containsAnyValue(postalCode)) { - parameters.add("latitude"); - parameters.add("longitude"); - } - - return parameters; - } + @Override + public String getEndpointPath() { + return EndpointManager.ENDPOINT_QUEUE_NEARBY; + } + + @Override + public List getRequiredParameters(ApiRequest request) { + List parameters = new ArrayList(); + + String postalCode = request.getParameter("postalCode"); + if (!ParserHelper.containsAnyValue(postalCode)) { + parameters.add("latitude"); + parameters.add("longitude"); + } + + return parameters; + } + + @Override + public ApiResponse generateRequestResponse(ApiRequest request) throws Exception { + ApiResponse response = new ApiResponse(); + + boolean includeBusinesses = request.getParameterAsBoolean("includeBusinesses", false); + String postalCode = request.getParameter("postalCode"); + List nearbyQueues = new ArrayList(); + + if (ParserHelper.containsAnyValue(postalCode)) { + // use postal code + nearbyQueues = QueueHelper.getQueuesByPostalCode(postalCode); + } else { + // use latitude & longitude + float latitude = request.getParameterAsFloat("latitude", -1); + float longitude = request.getParameterAsFloat("longitude", -1); + long distance = request.getParameterAsLong("distance", Location.DISTANCE_DEFAULT); + + nearbyQueues = QueueHelper.getQueuesByLocation(latitude, longitude, distance); + } + + // update queue number entries + for (int i = 0; i < nearbyQueues.size(); i++) { + nearbyQueues.get(i).setWaitingPeople(QueueHelper.getNumberOfItemsInQueue(nearbyQueues.get(i).getKey().getId(), QueueItemEntry.STATUS_WAITING)); + } + + if (includeBusinesses) { + response.setContent(QueueHelper.getBusinessesForQueues(nearbyQueues)); + } else { + response.setContent(nearbyQueues); + } + return response; + } - @Override - public ApiResponse generateRequestResponse(ApiRequest request) throws Exception { - ApiResponse response = new ApiResponse(); - - boolean includeBusinesses = request.getParameterAsBoolean("includeBusinesses", false); - String postalCode = request.getParameter("postalCode"); - List nearbyQueues = new ArrayList(); - - if (ParserHelper.containsAnyValue(postalCode)) { - // use postal code - nearbyQueues = QueueHelper.getQueuesByPostalCode(postalCode); - } else { - // use latitude & longitude - float latitude = request.getParameterAsFloat("latitude", -1); - float longitude = request.getParameterAsFloat("longitude", -1); - long distance = request.getParameterAsLong("distance", Location.DISTANCE_DEFAULT); - - nearbyQueues = QueueHelper.getQueuesByLocation(latitude, longitude, distance); - } - - // update queue number entries - for (int i = 0; i < nearbyQueues.size(); i++) { - nearbyQueues.get(i).setWaitingPeople(QueueHelper.getNumberOfItemsInQueue(nearbyQueues.get(i).getKey().getId(), QueueItemEntry.STATUS_WAITING)); - } - - if (includeBusinesses) { - response.setContent(QueueHelper.getBusinessesForQueues(nearbyQueues)); - } else { - response.setContent(nearbyQueues); - } - return response; - } - } diff --git a/IntelliQ/backend/src/main/java/com/intelliq/appengine/api/endpoint/queue/GetNumberOfItemsInQueueEndpoint.java b/IntelliQ/backend/src/main/java/com/intelliq/appengine/api/endpoint/queue/GetNumberOfItemsInQueueEndpoint.java index 74ee9d3..73c43db 100644 --- a/IntelliQ/backend/src/main/java/com/intelliq/appengine/api/endpoint/queue/GetNumberOfItemsInQueueEndpoint.java +++ b/IntelliQ/backend/src/main/java/com/intelliq/appengine/api/endpoint/queue/GetNumberOfItemsInQueueEndpoint.java @@ -16,33 +16,33 @@ public class GetNumberOfItemsInQueueEndpoint extends Endpoint { - @Override - public String getEndpointPath() { - return EndpointManager.ENDPOINT_QUEUE_COUNT; - } - - @Override - public List getRequiredParameters(ApiRequest request) { - List parameters = new ArrayList(); - parameters.add("queueKeyId"); - return parameters; - } - - @Override - public ApiResponse generateRequestResponse(ApiRequest request) throws Exception { - ApiResponse response = new ApiResponse(); - - long queueKeyId = request.getParameterAsLong("queueKeyId", -1); - byte status = (byte) request.getParameterAsInt("status", QueueItemEntry.STATUS_ALL); - - try { - int number = QueueHelper.getNumberOfItemsInQueue(queueKeyId, status); - response.setContent(number); - } catch (JDOObjectNotFoundException exception) { - response.setStatusCode(HttpServletResponse.SC_NOT_FOUND); - response.setException(new Exception("Unable to find requested queue")); - } - return response; - } - + @Override + public String getEndpointPath() { + return EndpointManager.ENDPOINT_QUEUE_COUNT; + } + + @Override + public List getRequiredParameters(ApiRequest request) { + List parameters = new ArrayList(); + parameters.add("queueKeyId"); + return parameters; + } + + @Override + public ApiResponse generateRequestResponse(ApiRequest request) throws Exception { + ApiResponse response = new ApiResponse(); + + long queueKeyId = request.getParameterAsLong("queueKeyId", -1); + byte status = (byte) request.getParameterAsInt("status", QueueItemEntry.STATUS_ALL); + + try { + int number = QueueHelper.getNumberOfItemsInQueue(queueKeyId, status); + response.setContent(number); + } catch (JDOObjectNotFoundException exception) { + response.setStatusCode(HttpServletResponse.SC_NOT_FOUND); + response.setException(new Exception("Unable to find requested queue")); + } + return response; + } + } diff --git a/IntelliQ/backend/src/main/java/com/intelliq/appengine/api/endpoint/queue/GetQueueEndpoint.java b/IntelliQ/backend/src/main/java/com/intelliq/appengine/api/endpoint/queue/GetQueueEndpoint.java index a8eab91..2bfa43b 100644 --- a/IntelliQ/backend/src/main/java/com/intelliq/appengine/api/endpoint/queue/GetQueueEndpoint.java +++ b/IntelliQ/backend/src/main/java/com/intelliq/appengine/api/endpoint/queue/GetQueueEndpoint.java @@ -17,39 +17,39 @@ public class GetQueueEndpoint extends Endpoint { - @Override - public String getEndpointPath() { - return EndpointManager.ENDPOINT_QUEUE_GET; - } - - @Override - public List getRequiredParameters(ApiRequest request) { - List parameters = new ArrayList(); - parameters.add("queueKeyId"); - return parameters; - } - - @Override - public ApiResponse generateRequestResponse(ApiRequest request) throws Exception { - ApiResponse response = new ApiResponse(); - - long queueKeyId = request.getParameterAsLong("queueKeyId", -1); - boolean includeBusinesses = request.getParameterAsBoolean("includeBusiness", false); - - try { - QueueEntry queueEntry = QueueHelper.getEntryByKeyId(queueKeyId); - queueEntry.setWaitingPeople(QueueHelper.getNumberOfItemsInQueue(queueKeyId, QueueItemEntry.STATUS_WAITING)); - - if (includeBusinesses) { - response.setContent(QueueHelper.getBusinessForQueue(queueEntry)); - } else { - response.setContent(queueEntry); - } - } catch (JDOObjectNotFoundException exception) { - response.setStatusCode(HttpServletResponse.SC_NOT_FOUND); - response.setException(new Exception("Unable to find requested queue")); - } - return response; - } - + @Override + public String getEndpointPath() { + return EndpointManager.ENDPOINT_QUEUE_GET; + } + + @Override + public List getRequiredParameters(ApiRequest request) { + List parameters = new ArrayList(); + parameters.add("queueKeyId"); + return parameters; + } + + @Override + public ApiResponse generateRequestResponse(ApiRequest request) throws Exception { + ApiResponse response = new ApiResponse(); + + long queueKeyId = request.getParameterAsLong("queueKeyId", -1); + boolean includeBusinesses = request.getParameterAsBoolean("includeBusiness", false); + + try { + QueueEntry queueEntry = QueueHelper.getEntryByKeyId(queueKeyId); + queueEntry.setWaitingPeople(QueueHelper.getNumberOfItemsInQueue(queueKeyId, QueueItemEntry.STATUS_WAITING)); + + if (includeBusinesses) { + response.setContent(QueueHelper.getBusinessForQueue(queueEntry)); + } else { + response.setContent(queueEntry); + } + } catch (JDOObjectNotFoundException exception) { + response.setStatusCode(HttpServletResponse.SC_NOT_FOUND); + response.setException(new Exception("Unable to find requested queue")); + } + return response; + } + } diff --git a/IntelliQ/backend/src/main/java/com/intelliq/appengine/api/endpoint/queue/MarkAllQueueItemsAsDoneEndpoint.java b/IntelliQ/backend/src/main/java/com/intelliq/appengine/api/endpoint/queue/MarkAllQueueItemsAsDoneEndpoint.java index 2edf146..bc8aa5a 100644 --- a/IntelliQ/backend/src/main/java/com/intelliq/appengine/api/endpoint/queue/MarkAllQueueItemsAsDoneEndpoint.java +++ b/IntelliQ/backend/src/main/java/com/intelliq/appengine/api/endpoint/queue/MarkAllQueueItemsAsDoneEndpoint.java @@ -17,50 +17,50 @@ public class MarkAllQueueItemsAsDoneEndpoint extends Endpoint { - @Override - public String getEndpointPath() { - return EndpointManager.ENDPOINT_QUEUE_DONE; - } - - @Override - public boolean requiresAuthorization(ApiRequest request) { - return true; - } - - @Override - public List getRequiredParameters(ApiRequest request) { - List parameters = new ArrayList(); - parameters.add("queueKeyId"); - return parameters; - } - - @Override - public PermissionSet getRequiredPermissions(ApiRequest request) { - PermissionSet permissionSet = new PermissionSet(); - long queueKeyId = request.getParameterAsLong("queueKeyId", -1); - - PermissionEntry permissionEntry = new PermissionEntry(); - permissionEntry.setPermission(PermissionEntry.PERMISSION_EDIT); - permissionEntry.setSubjectKeyId(queueKeyId); - - permissionSet.getPermissions().add(permissionEntry); - return permissionSet; - } - - @Override - public ApiResponse generateRequestResponse(ApiRequest request) throws Exception { - ApiResponse response = new ApiResponse(); - - long queueKeyId = request.getParameterAsLong("queueKeyId", -1); - - try { - QueueHelper.markAllQueueItemsAsDone(queueKeyId); - } catch (JDOObjectNotFoundException exception) { - response.setStatusCode(HttpServletResponse.SC_NOT_FOUND); - response.setException(new Exception("Unable to find requested queue")); - } - return response; - } - - + @Override + public String getEndpointPath() { + return EndpointManager.ENDPOINT_QUEUE_DONE; + } + + @Override + public boolean requiresAuthorization(ApiRequest request) { + return true; + } + + @Override + public List getRequiredParameters(ApiRequest request) { + List parameters = new ArrayList(); + parameters.add("queueKeyId"); + return parameters; + } + + @Override + public PermissionSet getRequiredPermissions(ApiRequest request) { + PermissionSet permissionSet = new PermissionSet(); + long queueKeyId = request.getParameterAsLong("queueKeyId", -1); + + PermissionEntry permissionEntry = new PermissionEntry(); + permissionEntry.setPermission(PermissionEntry.PERMISSION_EDIT); + permissionEntry.setSubjectKeyId(queueKeyId); + + permissionSet.getPermissions().add(permissionEntry); + return permissionSet; + } + + @Override + public ApiResponse generateRequestResponse(ApiRequest request) throws Exception { + ApiResponse response = new ApiResponse(); + + long queueKeyId = request.getParameterAsLong("queueKeyId", -1); + + try { + QueueHelper.markAllQueueItemsAsDone(queueKeyId); + } catch (JDOObjectNotFoundException exception) { + response.setStatusCode(HttpServletResponse.SC_NOT_FOUND); + response.setException(new Exception("Unable to find requested queue")); + } + return response; + } + + } diff --git a/IntelliQ/backend/src/main/java/com/intelliq/appengine/api/endpoint/queue/PopulateQueueEndpoint.java b/IntelliQ/backend/src/main/java/com/intelliq/appengine/api/endpoint/queue/PopulateQueueEndpoint.java index 6ddd528..8ce153a 100644 --- a/IntelliQ/backend/src/main/java/com/intelliq/appengine/api/endpoint/queue/PopulateQueueEndpoint.java +++ b/IntelliQ/backend/src/main/java/com/intelliq/appengine/api/endpoint/queue/PopulateQueueEndpoint.java @@ -17,52 +17,52 @@ public class PopulateQueueEndpoint extends Endpoint { - @Override - public String getEndpointPath() { - return EndpointManager.ENDPOINT_QUEUE_POPULATE; - } - - @Override - public boolean requiresAuthorization(ApiRequest request) { - return true; - } - - @Override - public List getRequiredParameters(ApiRequest request) { - List parameters = new ArrayList(); - parameters.add("queueKeyId"); - return parameters; - } - - @Override - public PermissionSet getRequiredPermissions(ApiRequest request) { - PermissionSet permissionSet = new PermissionSet(); - long queueKeyId = request.getParameterAsLong("queueKeyId", -1); - - PermissionEntry permissionEntry = new PermissionEntry(); - permissionEntry.setPermission(PermissionEntry.PERMISSION_EDIT); - permissionEntry.setSubjectKeyId(queueKeyId); - - permissionSet.getPermissions().add(permissionEntry); - return permissionSet; - } - - @Override - public ApiResponse generateRequestResponse(ApiRequest request) throws Exception { - ApiResponse response = new ApiResponse(); - - long queueKeyId = request.getParameterAsLong("queueKeyId", -1); - int count = request.getParameterAsInt("count", 25); - - try { - QueueHelper.populateQueueWithSampleItems(queueKeyId, count); - response.setContent(count); - } catch (JDOObjectNotFoundException exception) { - response.setStatusCode(HttpServletResponse.SC_NOT_FOUND); - response.setException(new Exception("Unable to find requested queue")); - } - return response; - } - - + @Override + public String getEndpointPath() { + return EndpointManager.ENDPOINT_QUEUE_POPULATE; + } + + @Override + public boolean requiresAuthorization(ApiRequest request) { + return true; + } + + @Override + public List getRequiredParameters(ApiRequest request) { + List parameters = new ArrayList(); + parameters.add("queueKeyId"); + return parameters; + } + + @Override + public PermissionSet getRequiredPermissions(ApiRequest request) { + PermissionSet permissionSet = new PermissionSet(); + long queueKeyId = request.getParameterAsLong("queueKeyId", -1); + + PermissionEntry permissionEntry = new PermissionEntry(); + permissionEntry.setPermission(PermissionEntry.PERMISSION_EDIT); + permissionEntry.setSubjectKeyId(queueKeyId); + + permissionSet.getPermissions().add(permissionEntry); + return permissionSet; + } + + @Override + public ApiResponse generateRequestResponse(ApiRequest request) throws Exception { + ApiResponse response = new ApiResponse(); + + long queueKeyId = request.getParameterAsLong("queueKeyId", -1); + int count = request.getParameterAsInt("count", 25); + + try { + QueueHelper.populateQueueWithSampleItems(queueKeyId, count); + response.setContent(count); + } catch (JDOObjectNotFoundException exception) { + response.setStatusCode(HttpServletResponse.SC_NOT_FOUND); + response.setException(new Exception("Unable to find requested queue")); + } + return response; + } + + } diff --git a/IntelliQ/backend/src/main/java/com/intelliq/appengine/api/endpoint/queue/QueueEndpoint.java b/IntelliQ/backend/src/main/java/com/intelliq/appengine/api/endpoint/queue/QueueEndpoint.java index 4bdb792..bea6ef0 100644 --- a/IntelliQ/backend/src/main/java/com/intelliq/appengine/api/endpoint/queue/QueueEndpoint.java +++ b/IntelliQ/backend/src/main/java/com/intelliq/appengine/api/endpoint/queue/QueueEndpoint.java @@ -10,35 +10,35 @@ public class QueueEndpoint extends Endpoint { - public static final List endpoints = getAvailableEndpoints(); - - @Override - public String getEndpointPath() { - return EndpointManager.ENDPOINT_QUEUE; - } - - @Override - public ApiResponse processRequest(ApiRequest request) { - Endpoint endpoint = EndpointManager.getEndpointForRequest(request, endpoints); - if (endpoint != null) { - return endpoint.processRequest(request); - } else { - return super.processRequest(request); - } - } - - private static List getAvailableEndpoints() { - List endpoints = new ArrayList(); - endpoints.add(new GetQueueEndpoint()); - endpoints.add(new AddQueueEndpoint()); - endpoints.add(new EditQueueEndpoint()); - endpoints.add(new GetNearbyQueuesEndpoint()); - endpoints.add(new GetNumberOfItemsInQueueEndpoint()); - endpoints.add(new GetItemsInQueueEndpoint()); - endpoints.add(new MarkAllQueueItemsAsDoneEndpoint()); - endpoints.add(new PopulateQueueEndpoint()); - endpoints.add(new ClearQueueItemsEndpoint()); - return endpoints; + public static final List endpoints = getAvailableEndpoints(); + + @Override + public String getEndpointPath() { + return EndpointManager.ENDPOINT_QUEUE; + } + + @Override + public ApiResponse processRequest(ApiRequest request) { + Endpoint endpoint = EndpointManager.getEndpointForRequest(request, endpoints); + if (endpoint != null) { + return endpoint.processRequest(request); + } else { + return super.processRequest(request); + } } - + + private static List getAvailableEndpoints() { + List endpoints = new ArrayList(); + endpoints.add(new GetQueueEndpoint()); + endpoints.add(new AddQueueEndpoint()); + endpoints.add(new EditQueueEndpoint()); + endpoints.add(new GetNearbyQueuesEndpoint()); + endpoints.add(new GetNumberOfItemsInQueueEndpoint()); + endpoints.add(new GetItemsInQueueEndpoint()); + endpoints.add(new MarkAllQueueItemsAsDoneEndpoint()); + endpoints.add(new PopulateQueueEndpoint()); + endpoints.add(new ClearQueueItemsEndpoint()); + return endpoints; + } + } diff --git a/IntelliQ/backend/src/main/java/com/intelliq/appengine/api/endpoint/queueitem/AddQueueItemEndpoint.java b/IntelliQ/backend/src/main/java/com/intelliq/appengine/api/endpoint/queueitem/AddQueueItemEndpoint.java index 68459eb..ae0d08f 100644 --- a/IntelliQ/backend/src/main/java/com/intelliq/appengine/api/endpoint/queueitem/AddQueueItemEndpoint.java +++ b/IntelliQ/backend/src/main/java/com/intelliq/appengine/api/endpoint/queueitem/AddQueueItemEndpoint.java @@ -21,70 +21,82 @@ public class AddQueueItemEndpoint extends Endpoint { - @Override - public String getEndpointPath() { - return EndpointManager.ENDPOINT_QUEUE_ITEM_ADD; - } - - @Override - public boolean requiresAuthorization(ApiRequest request) { - QueueEntry queueEntry = QueueHelper.getEntryByKeyId(request.getParameterAsLong("queueKeyId", -1)); - if (queueEntry != null && queueEntry.getRequiresSignIn()) { - return true; - } - return false; - } - - @Override - public List getRequiredParameters(ApiRequest request) { - List parameters = new ArrayList(); - parameters.add("queueKeyId"); - parameters.add("name"); - return parameters; - } - - @Override - public ApiResponse generateRequestResponse(ApiRequest request) throws Exception { - ApiResponse response = new ApiResponse(); - - long queueKeyId = request.getParameterAsLong("queueKeyId", -1); - - // create queue item - QueueItemEntry queueItemEntry = new QueueItemEntry(queueKeyId); - queueItemEntry.parseFromRequest(request); - - // assign the queue item to the current user - UserEntry user = request.getUser(); - if (user != null) { - queueItemEntry.setUserKeyId(user.getKey().getId()); - } - - // make sure that user is not already in this queue - QueueItemEntry existingQueueItemEntry = QueueItemHelper.getQueueItemByUserKeyId(queueItemEntry.getUserKeyId(), queueItemEntry.getQueueKeyId()); - if (existingQueueItemEntry != null) { - // return the existing queue item - response.setContent(existingQueueItemEntry); - return response; - } - - // get next available ticket number - int lastTicketNumber = QueueHelper.getLastTicketNumberInQueue(queueKeyId); - queueItemEntry.setTicketNumber(lastTicketNumber + 1); - - Key entryKey = QueueItemHelper.saveEntry(queueItemEntry); - queueItemEntry.setKey(entryKey); - - if (user != null) { - // update user stats - user.getStats().setQueuesJoined(user.getStats().getQueuesJoined() + 1); - UserHelper.saveEntry(user); - - // add permission for queue item - PermissionHelper.grantPermission(user, queueItemEntry, PermissionEntry.PERMISSION_OWN); - } - - response.setContent(queueItemEntry); - return response; - } - + @Override + public String getEndpointPath() { + return EndpointManager.ENDPOINT_QUEUE_ITEM_ADD; + } + + @Override + public boolean requiresAuthorization(ApiRequest request) { + QueueEntry queueEntry = QueueHelper.getEntryByKeyId(request.getParameterAsLong("queueKeyId", -1)); + if (queueEntry != null && queueEntry.getRequiresSignIn()) { + return true; + } + return false; + } + + @Override + public List getRequiredParameters(ApiRequest request) { + List parameters = new ArrayList(); + parameters.add("queueKeyId"); + parameters.add("name"); + return parameters; + } + + @Override + public ApiResponse generateRequestResponse(ApiRequest request) throws Exception { + ApiResponse response = new ApiResponse(); + + long queueKeyId = request.getParameterAsLong("queueKeyId", -1); + + // create queue item + QueueItemEntry queueItemEntry = new QueueItemEntry(queueKeyId); + queueItemEntry.parseFromRequest(request); + + // indicates that the item was created by the queue management + boolean addedByManagement = false; + + // assign the queue item to the current user + UserEntry user = request.getUser(); + if (user != null) { + PermissionEntry editQueuePermission = new PermissionEntry(); + editQueuePermission.setPermission(PermissionEntry.PERMISSION_EDIT); + editQueuePermission.setSubjectKeyId(queueKeyId); + addedByManagement = user.hasPermission(editQueuePermission); + + queueItemEntry.setUserKeyId(user.getKey().getId()); + } + + // make sure that user is not already in this queue + if (queueItemEntry.getUserKeyId() > -1 && !addedByManagement) { + QueueItemEntry existingQueueItemEntry = QueueItemHelper.getQueueItemByUserKeyId(queueItemEntry.getUserKeyId(), queueItemEntry.getQueueKeyId()); + if (existingQueueItemEntry != null) { + // return the existing queue item + response.setContent(existingQueueItemEntry); + return response; + } + } + + // get next available ticket number + int lastTicketNumber = QueueHelper.getLastTicketNumberInQueue(queueKeyId); + queueItemEntry.setTicketNumber(lastTicketNumber + 1); + + Key entryKey = QueueItemHelper.saveEntry(queueItemEntry); + queueItemEntry.setKey(entryKey); + + if (user != null) { + // update user stats + if (!addedByManagement) { + user.getStats().setQueuesJoined(user.getStats().getQueuesJoined() + 1); + UserHelper.saveEntry(user); + } + + // add permission for queue item + PermissionHelper.grantPermission(user, queueItemEntry, PermissionEntry.PERMISSION_OWN); + } + + response.setContent(queueItemEntry); + return response; + } + } diff --git a/IntelliQ/backend/src/main/java/com/intelliq/appengine/api/endpoint/queueitem/DeleteQueueItemEndpoint.java b/IntelliQ/backend/src/main/java/com/intelliq/appengine/api/endpoint/queueitem/DeleteQueueItemEndpoint.java index 013edb5..174b5d8 100644 --- a/IntelliQ/backend/src/main/java/com/intelliq/appengine/api/endpoint/queueitem/DeleteQueueItemEndpoint.java +++ b/IntelliQ/backend/src/main/java/com/intelliq/appengine/api/endpoint/queueitem/DeleteQueueItemEndpoint.java @@ -19,61 +19,60 @@ public class DeleteQueueItemEndpoint extends Endpoint { - @Override - public String getEndpointPath() { - return EndpointManager.ENDPOINT_QUEUE_ITEM_DELETE; - } - - @Override - public boolean requiresAuthorization(ApiRequest request) { - QueueEntry queueEntry = QueueHelper.getEntryByKeyId(request.getParameterAsLong("queueKeyId", -1)); - if (queueEntry != null && queueEntry.getRequiresSignIn()) { - return true; - } - return false; - } - - @Override - public List getRequiredParameters(ApiRequest request) { - List parameters = new ArrayList(); - parameters.add("queueKeyId"); - parameters.add("queueItemKeyId"); - return parameters; - } - - @Override - public PermissionSet getRequiredPermissions(ApiRequest request) { - PermissionSet permissionSet = new PermissionSet(); - - // user wants to delete his own ticket - PermissionEntry permissionEntry = new PermissionEntry(); - permissionEntry.setPermission(PermissionEntry.PERMISSION_OWN); - permissionEntry.setSubjectKeyId(request.getParameterAsLong("queueItemKeyId", -1)); - permissionSet.getPermissions().add(permissionEntry); - - // queue management wants to remove an item - permissionEntry = new PermissionEntry(); - permissionEntry.setPermission(PermissionEntry.PERMISSION_EDIT); - permissionEntry.setSubjectKeyId(request.getParameterAsLong("queueKeyId", -1)); - permissionSet.getPermissions().add(permissionEntry); - - return permissionSet; - } - - @Override - public ApiResponse generateRequestResponse(ApiRequest request) throws Exception { - ApiResponse response = new ApiResponse(); - - long queueItemKeyId = request.getParameterAsLong("queueItemKeyId", -1); - - - try { - QueueItemHelper.deleteEntryByKeyId(queueItemKeyId); - } catch (JDOObjectNotFoundException exception) { - response.setStatusCode(HttpServletResponse.SC_NOT_FOUND); - response.setException(new Exception("Unable to find requested queue item")); - } - return response; - } - + @Override + public String getEndpointPath() { + return EndpointManager.ENDPOINT_QUEUE_ITEM_DELETE; + } + + @Override + public boolean requiresAuthorization(ApiRequest request) { + QueueEntry queueEntry = QueueHelper.getEntryByKeyId(request.getParameterAsLong("queueKeyId", -1)); + if (queueEntry != null && queueEntry.getRequiresSignIn()) { + return true; + } + return false; + } + + @Override + public List getRequiredParameters(ApiRequest request) { + List parameters = new ArrayList(); + parameters.add("queueKeyId"); + parameters.add("queueItemKeyId"); + return parameters; + } + + @Override + public PermissionSet getRequiredPermissions(ApiRequest request) { + PermissionSet permissionSet = new PermissionSet(); + + // user wants to delete his own ticket + PermissionEntry permissionEntry = new PermissionEntry(); + permissionEntry.setPermission(PermissionEntry.PERMISSION_OWN); + permissionEntry.setSubjectKeyId(request.getParameterAsLong("queueItemKeyId", -1)); + permissionSet.getPermissions().add(permissionEntry); + + // queue management wants to remove an item + permissionEntry = new PermissionEntry(); + permissionEntry.setPermission(PermissionEntry.PERMISSION_EDIT); + permissionEntry.setSubjectKeyId(request.getParameterAsLong("queueKeyId", -1)); + permissionSet.getPermissions().add(permissionEntry); + + return permissionSet; + } + + @Override + public ApiResponse generateRequestResponse(ApiRequest request) throws Exception { + ApiResponse response = new ApiResponse(); + + long queueItemKeyId = request.getParameterAsLong("queueItemKeyId", -1); + + try { + QueueItemHelper.deleteEntryByKeyId(queueItemKeyId); + } catch (JDOObjectNotFoundException exception) { + response.setStatusCode(HttpServletResponse.SC_NOT_FOUND); + response.setException(new Exception("Unable to find requested queue item")); + } + return response; + } + } diff --git a/IntelliQ/backend/src/main/java/com/intelliq/appengine/api/endpoint/queueitem/GetLastItemInQueueEndpoint.java b/IntelliQ/backend/src/main/java/com/intelliq/appengine/api/endpoint/queueitem/GetLastItemInQueueEndpoint.java index d32b364..5d309d4 100644 --- a/IntelliQ/backend/src/main/java/com/intelliq/appengine/api/endpoint/queueitem/GetLastItemInQueueEndpoint.java +++ b/IntelliQ/backend/src/main/java/com/intelliq/appengine/api/endpoint/queueitem/GetLastItemInQueueEndpoint.java @@ -15,28 +15,28 @@ public class GetLastItemInQueueEndpoint extends Endpoint { - @Override - public String getEndpointPath() { - return EndpointManager.ENDPOINT_QUEUE_ITEM_LAST; - } - - @Override - public List getRequiredParameters(ApiRequest request) { - List parameters = new ArrayList(); - parameters.add("queueKeyId"); - return parameters; - } - - @Override - public ApiResponse generateRequestResponse(ApiRequest request) throws Exception { - ApiResponse response = new ApiResponse(); - - long queueKeyId = request.getParameterAsLong("queueKeyId", -1); - byte status = (byte) request.getParameterAsInt("status", QueueItemEntry.STATUS_ALL); - - QueueItemEntry lastQueueItemEntry = QueueHelper.getLastAssignedTicketInQueue(queueKeyId, status); - response.setContent(lastQueueItemEntry); - return response; - } - + @Override + public String getEndpointPath() { + return EndpointManager.ENDPOINT_QUEUE_ITEM_LAST; + } + + @Override + public List getRequiredParameters(ApiRequest request) { + List parameters = new ArrayList(); + parameters.add("queueKeyId"); + return parameters; + } + + @Override + public ApiResponse generateRequestResponse(ApiRequest request) throws Exception { + ApiResponse response = new ApiResponse(); + + long queueKeyId = request.getParameterAsLong("queueKeyId", -1); + byte status = (byte) request.getParameterAsInt("status", QueueItemEntry.STATUS_ALL); + + QueueItemEntry lastQueueItemEntry = QueueHelper.getLastAssignedTicketInQueue(queueKeyId, status); + response.setContent(lastQueueItemEntry); + return response; + } + } diff --git a/IntelliQ/backend/src/main/java/com/intelliq/appengine/api/endpoint/queueitem/GetQueueItemEndpoint.java b/IntelliQ/backend/src/main/java/com/intelliq/appengine/api/endpoint/queueitem/GetQueueItemEndpoint.java index 92efb17..d9bf1ef 100644 --- a/IntelliQ/backend/src/main/java/com/intelliq/appengine/api/endpoint/queueitem/GetQueueItemEndpoint.java +++ b/IntelliQ/backend/src/main/java/com/intelliq/appengine/api/endpoint/queueitem/GetQueueItemEndpoint.java @@ -16,32 +16,32 @@ public class GetQueueItemEndpoint extends Endpoint { - @Override - public String getEndpointPath() { - return EndpointManager.ENDPOINT_QUEUE_ITEM_GET; - } - - @Override - public List getRequiredParameters(ApiRequest request) { - List parameters = new ArrayList(); - parameters.add("queueItemKeyId"); - return parameters; - } - - @Override - public ApiResponse generateRequestResponse(ApiRequest request) throws Exception { - ApiResponse response = new ApiResponse(); - - long queueItemKeyId = request.getParameterAsLong("queueItemKeyId", -1); - - try { - QueueItemEntry queueItemEntry = QueueItemHelper.getEntryByKeyId(queueItemKeyId); - response.setContent(queueItemEntry); - } catch (JDOObjectNotFoundException exception) { - response.setStatusCode(HttpServletResponse.SC_NOT_FOUND); - response.setException(new Exception("Unable to find requested queue item")); - } - return response; - } - + @Override + public String getEndpointPath() { + return EndpointManager.ENDPOINT_QUEUE_ITEM_GET; + } + + @Override + public List getRequiredParameters(ApiRequest request) { + List parameters = new ArrayList(); + parameters.add("queueItemKeyId"); + return parameters; + } + + @Override + public ApiResponse generateRequestResponse(ApiRequest request) throws Exception { + ApiResponse response = new ApiResponse(); + + long queueItemKeyId = request.getParameterAsLong("queueItemKeyId", -1); + + try { + QueueItemEntry queueItemEntry = QueueItemHelper.getEntryByKeyId(queueItemKeyId); + response.setContent(queueItemEntry); + } catch (JDOObjectNotFoundException exception) { + response.setStatusCode(HttpServletResponse.SC_NOT_FOUND); + response.setException(new Exception("Unable to find requested queue item")); + } + return response; + } + } diff --git a/IntelliQ/backend/src/main/java/com/intelliq/appengine/api/endpoint/queueitem/GetQueueItemsFromUserEndpoint.java b/IntelliQ/backend/src/main/java/com/intelliq/appengine/api/endpoint/queueitem/GetQueueItemsFromUserEndpoint.java new file mode 100644 index 0000000..2830917 --- /dev/null +++ b/IntelliQ/backend/src/main/java/com/intelliq/appengine/api/endpoint/queueitem/GetQueueItemsFromUserEndpoint.java @@ -0,0 +1,47 @@ +package com.intelliq.appengine.api.endpoint.queueitem; + +import com.intelliq.appengine.api.ApiRequest; +import com.intelliq.appengine.api.ApiResponse; +import com.intelliq.appengine.api.endpoint.Endpoint; +import com.intelliq.appengine.api.endpoint.EndpointManager; +import com.intelliq.appengine.datastore.QueueItemHelper; +import com.intelliq.appengine.datastore.entries.QueueItemEntry; + +import java.util.ArrayList; +import java.util.List; + +import javax.jdo.JDOObjectNotFoundException; +import javax.servlet.http.HttpServletResponse; + + +public class GetQueueItemsFromUserEndpoint extends Endpoint { + + @Override + public String getEndpointPath() { + return EndpointManager.ENDPOINT_QUEUE_ITEM_FROM; + } + + @Override + public List getRequiredParameters(ApiRequest request) { + List parameters = new ArrayList(); + parameters.add("userKeyId"); + return parameters; + } + + @Override + public ApiResponse generateRequestResponse(ApiRequest request) throws Exception { + ApiResponse response = new ApiResponse(); + + long userKeyId = request.getParameterAsLong("userKeyId", -1); + + try { + List queueItemEntries = QueueItemHelper.getQueueItemsByUserKeyId(userKeyId); + response.setContent(queueItemEntries); + } catch (JDOObjectNotFoundException exception) { + response.setStatusCode(HttpServletResponse.SC_NOT_FOUND); + response.setException(new Exception("Unable to find queue items for the user")); + } + return response; + } + +} diff --git a/IntelliQ/backend/src/main/java/com/intelliq/appengine/api/endpoint/queueitem/QueueItemEndpoint.java b/IntelliQ/backend/src/main/java/com/intelliq/appengine/api/endpoint/queueitem/QueueItemEndpoint.java index a665df5..b4fc677 100644 --- a/IntelliQ/backend/src/main/java/com/intelliq/appengine/api/endpoint/queueitem/QueueItemEndpoint.java +++ b/IntelliQ/backend/src/main/java/com/intelliq/appengine/api/endpoint/queueitem/QueueItemEndpoint.java @@ -11,31 +11,32 @@ public class QueueItemEndpoint extends Endpoint { - public static final List endpoints = getAvailableEndpoints(); - - @Override - public String getEndpointPath() { - return EndpointManager.ENDPOINT_QUEUE_ITEM; - } - - @Override - public ApiResponse processRequest(ApiRequest request) { - Endpoint endpoint = EndpointManager.getEndpointForRequest(request, endpoints); - if (endpoint != null) { - return endpoint.processRequest(request); - } else { - return super.processRequest(request); - } - } - - private static List getAvailableEndpoints() { - List endpoints = new ArrayList(); - endpoints.add(new GetQueueItemEndpoint()); - endpoints.add(new AddQueueItemEndpoint()); - endpoints.add(new GetLastItemInQueueEndpoint()); - endpoints.add(new SetQueueItemStatusEndpoint()); - endpoints.add(new DeleteQueueItemEndpoint()); - return endpoints; + public static final List endpoints = getAvailableEndpoints(); + + @Override + public String getEndpointPath() { + return EndpointManager.ENDPOINT_QUEUE_ITEM; + } + + @Override + public ApiResponse processRequest(ApiRequest request) { + Endpoint endpoint = EndpointManager.getEndpointForRequest(request, endpoints); + if (endpoint != null) { + return endpoint.processRequest(request); + } else { + return super.processRequest(request); + } } - + + private static List getAvailableEndpoints() { + List endpoints = new ArrayList(); + endpoints.add(new GetQueueItemEndpoint()); + endpoints.add(new AddQueueItemEndpoint()); + endpoints.add(new GetQueueItemsFromUserEndpoint()); + endpoints.add(new GetLastItemInQueueEndpoint()); + endpoints.add(new SetQueueItemStatusEndpoint()); + endpoints.add(new DeleteQueueItemEndpoint()); + return endpoints; + } + } diff --git a/IntelliQ/backend/src/main/java/com/intelliq/appengine/api/endpoint/queueitem/SetQueueItemStatusEndpoint.java b/IntelliQ/backend/src/main/java/com/intelliq/appengine/api/endpoint/queueitem/SetQueueItemStatusEndpoint.java index afda95d..989d859 100644 --- a/IntelliQ/backend/src/main/java/com/intelliq/appengine/api/endpoint/queueitem/SetQueueItemStatusEndpoint.java +++ b/IntelliQ/backend/src/main/java/com/intelliq/appengine/api/endpoint/queueitem/SetQueueItemStatusEndpoint.java @@ -21,78 +21,78 @@ public class SetQueueItemStatusEndpoint extends Endpoint { - @Override - public String getEndpointPath() { - return EndpointManager.ENDPOINT_QUEUE_ITEM_STATUS; - } - - @Override - public List getRequiredParameters(ApiRequest request) { - List parameters = new ArrayList(); - parameters.add("queueKeyId"); - parameters.add("queueItemKeyId"); - parameters.add("status"); - return parameters; - } - - @Override - public boolean requiresAuthorization(ApiRequest request) { - QueueEntry queueEntry = QueueHelper.getEntryByKeyId(request.getParameterAsLong("queueKeyId", -1)); - if (queueEntry != null && queueEntry.getRequiresSignIn()) { - return true; - } - - // only queue management can call customers - byte status = (byte) request.getParameterAsInt("status", 0); - if (status == QueueItemEntry.STATUS_CALLED) { - return true; - } - - return false; - } - - @Override - public PermissionSet getRequiredPermissions(ApiRequest request) { - PermissionSet permissionSet = new PermissionSet(); - PermissionEntry permissionEntry; - - byte status = (byte) request.getParameterAsInt("status", 0); - - // a user may cancel his own ticket or mark it as done - if (status == QueueItemEntry.STATUS_CANCELED || status == QueueItemEntry.STATUS_DONE) { - permissionEntry = new PermissionEntry(); - permissionEntry.setPermission(PermissionEntry.PERMISSION_OWN); - permissionEntry.setSubjectKeyId(request.getParameterAsLong("queueItemKeyId", -1)); - permissionSet.getPermissions().add(permissionEntry); - } - - permissionEntry = new PermissionEntry(); - permissionEntry.setPermission(PermissionEntry.PERMISSION_EDIT); - permissionEntry.setSubjectKeyId(request.getParameterAsLong("queueKeyId", -1)); - permissionSet.getPermissions().add(permissionEntry); - - return permissionSet; - } - - @Override - public ApiResponse generateRequestResponse(ApiRequest request) throws Exception { - ApiResponse response = new ApiResponse(); - - long queueItemKeyId = request.getParameterAsLong("queueItemKeyId", -1); - byte status = (byte) request.getParameterAsInt("status", 0); - - try { - QueueItemEntry queueItemEntry = QueueItemHelper.getEntryByKeyId(queueItemKeyId); - queueItemEntry.setStatus(status); - queueItemEntry.setLastStatusChangeTimestamp((new Date()).getTime()); - QueueItemHelper.saveEntry(queueItemEntry); - - response.setContent(queueItemEntry); - } catch (JDOObjectNotFoundException exception) { - response.setStatusCode(HttpServletResponse.SC_NOT_FOUND); - response.setException(new Exception("Unable to find requested queue item")); - } - return response; - } - + @Override + public String getEndpointPath() { + return EndpointManager.ENDPOINT_QUEUE_ITEM_STATUS; + } + + @Override + public List getRequiredParameters(ApiRequest request) { + List parameters = new ArrayList(); + parameters.add("queueKeyId"); + parameters.add("queueItemKeyId"); + parameters.add("status"); + return parameters; + } + + @Override + public boolean requiresAuthorization(ApiRequest request) { + QueueEntry queueEntry = QueueHelper.getEntryByKeyId(request.getParameterAsLong("queueKeyId", -1)); + if (queueEntry != null && queueEntry.getRequiresSignIn()) { + return true; + } + + // only queue management can call customers + byte status = (byte) request.getParameterAsInt("status", 0); + if (status == QueueItemEntry.STATUS_CALLED) { + return true; + } + + return false; + } + + @Override + public PermissionSet getRequiredPermissions(ApiRequest request) { + PermissionSet permissionSet = new PermissionSet(); + PermissionEntry permissionEntry; + + byte status = (byte) request.getParameterAsInt("status", 0); + + // a user may cancel his own ticket or mark it as done + if (status == QueueItemEntry.STATUS_CANCELED || status == QueueItemEntry.STATUS_DONE) { + permissionEntry = new PermissionEntry(); + permissionEntry.setPermission(PermissionEntry.PERMISSION_OWN); + permissionEntry.setSubjectKeyId(request.getParameterAsLong("queueItemKeyId", -1)); + permissionSet.getPermissions().add(permissionEntry); + } + + permissionEntry = new PermissionEntry(); + permissionEntry.setPermission(PermissionEntry.PERMISSION_EDIT); + permissionEntry.setSubjectKeyId(request.getParameterAsLong("queueKeyId", -1)); + permissionSet.getPermissions().add(permissionEntry); + + return permissionSet; + } + + @Override + public ApiResponse generateRequestResponse(ApiRequest request) throws Exception { + ApiResponse response = new ApiResponse(); + + long queueItemKeyId = request.getParameterAsLong("queueItemKeyId", -1); + byte status = (byte) request.getParameterAsInt("status", 0); + + try { + QueueItemEntry queueItemEntry = QueueItemHelper.getEntryByKeyId(queueItemKeyId); + queueItemEntry.setStatus(status); + queueItemEntry.setLastStatusChangeTimestamp((new Date()).getTime()); + QueueItemHelper.saveEntry(queueItemEntry); + + response.setContent(queueItemEntry); + } catch (JDOObjectNotFoundException exception) { + response.setStatusCode(HttpServletResponse.SC_NOT_FOUND); + response.setException(new Exception("Unable to find requested queue item")); + } + return response; + } + } diff --git a/IntelliQ/backend/src/main/java/com/intelliq/appengine/api/endpoint/user/GetUserEndpoint.java b/IntelliQ/backend/src/main/java/com/intelliq/appengine/api/endpoint/user/GetUserEndpoint.java index f14f7ab..e371221 100644 --- a/IntelliQ/backend/src/main/java/com/intelliq/appengine/api/endpoint/user/GetUserEndpoint.java +++ b/IntelliQ/backend/src/main/java/com/intelliq/appengine/api/endpoint/user/GetUserEndpoint.java @@ -4,78 +4,75 @@ import java.util.List; import javax.jdo.JDOObjectNotFoundException; -import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletResponse; -import org.datanucleus.exceptions.NucleusObjectNotFoundException; - import com.intelliq.appengine.api.ApiRequest; import com.intelliq.appengine.api.ApiResponse; import com.intelliq.appengine.api.PermissionSet; import com.intelliq.appengine.api.endpoint.Endpoint; import com.intelliq.appengine.api.endpoint.EndpointManager; -import com.intelliq.appengine.datastore.QueueHelper; import com.intelliq.appengine.datastore.UserHelper; import com.intelliq.appengine.datastore.entries.PermissionEntry; import com.intelliq.appengine.datastore.entries.QueueEntry; -import com.intelliq.appengine.datastore.entries.QueueItemEntry; import com.intelliq.appengine.datastore.entries.UserEntry; +import com.intelliq.appengine.logging.SlackLog; public class GetUserEndpoint extends Endpoint { - @Override - public String getEndpointPath() { - return EndpointManager.ENDPOINT_USER_GET; - } - - @Override - public boolean requiresAuthorization(ApiRequest request) { - return true; - } - - @Override - public PermissionSet getRequiredPermissions(ApiRequest request) { - PermissionSet permissionSet = new PermissionSet(); - - // user wants to get his own entry - PermissionEntry permissionEntry = new PermissionEntry(); - permissionEntry.setPermission(PermissionEntry.PERMISSION_OWN); - permissionEntry.setSubjectKeyId(request.getParameterAsLong("userKeyId", -1)); - permissionEntry.setSubjectKind(UserEntry.class.getSimpleName()); - permissionSet.getPermissions().add(permissionEntry); - - // queue management want's to get user details - permissionEntry = new PermissionEntry(); - permissionEntry.setPermission(PermissionEntry.PERMISSION_VIEW); - permissionEntry.setSubjectKeyId(request.getParameterAsLong("queueKeyId", -1)); - permissionEntry.setSubjectKind(QueueEntry.class.getSimpleName()); - permissionSet.getPermissions().add(permissionEntry); - - return permissionSet; - } - - @Override - public List getRequiredParameters(ApiRequest request) { - List parameters = new ArrayList(); - parameters.add("userKeyId"); - return parameters; - } - - @Override - public ApiResponse generateRequestResponse(ApiRequest request) throws Exception { - ApiResponse response = new ApiResponse(); - - long userKeyId = request.getParameterAsLong("userKeyId", -1); - - try { - UserEntry userEntry = UserHelper.getEntryByKeyId(userKeyId); - response.setContent(userEntry); - } catch (JDOObjectNotFoundException exception) { - response.setStatusCode(HttpServletResponse.SC_NOT_FOUND); - response.setException(new Exception("Unable to find requested user")); - } - return response; - } - + @Override + public String getEndpointPath() { + return EndpointManager.ENDPOINT_USER_GET; + } + + @Override + public boolean requiresAuthorization(ApiRequest request) { + return true; + } + + @Override + public PermissionSet getRequiredPermissions(ApiRequest request) { + PermissionSet permissionSet = new PermissionSet(); + + // user wants to get his own entry + PermissionEntry permissionEntry = new PermissionEntry(); + permissionEntry.setPermission(PermissionEntry.PERMISSION_OWN); + permissionEntry.setSubjectKeyId(request.getParameterAsLong("userKeyId", -1)); + permissionEntry.setSubjectKind(UserEntry.class.getSimpleName()); + permissionSet.getPermissions().add(permissionEntry); + + // queue management want's to get user details + permissionEntry = new PermissionEntry(); + permissionEntry.setPermission(PermissionEntry.PERMISSION_VIEW); + permissionEntry.setSubjectKeyId(request.getParameterAsLong("queueKeyId", -1)); + permissionEntry.setSubjectKind(QueueEntry.class.getSimpleName()); + permissionSet.getPermissions().add(permissionEntry); + + return permissionSet; + } + + @Override + public List getRequiredParameters(ApiRequest request) { + List parameters = new ArrayList(); + parameters.add("userKeyId"); + return parameters; + } + + @Override + public ApiResponse generateRequestResponse(ApiRequest request) throws Exception { + ApiResponse response = new ApiResponse(); + + long userKeyId = request.getParameterAsLong("userKeyId", -1); + + try { + UserEntry userEntry = UserHelper.getEntryByKeyId(userKeyId); + response.setContent(userEntry); + } catch (JDOObjectNotFoundException exception) { + response.setStatusCode(HttpServletResponse.SC_NOT_FOUND); + response.setException(new Exception("Unable to find requested user")); + SlackLog.v(this, "User not found: " + userKeyId); + } + return response; + } + } diff --git a/IntelliQ/backend/src/main/java/com/intelliq/appengine/api/endpoint/user/SetUserLocationEndpoint.java b/IntelliQ/backend/src/main/java/com/intelliq/appengine/api/endpoint/user/SetUserLocationEndpoint.java index 12a5725..cba28a7 100644 --- a/IntelliQ/backend/src/main/java/com/intelliq/appengine/api/endpoint/user/SetUserLocationEndpoint.java +++ b/IntelliQ/backend/src/main/java/com/intelliq/appengine/api/endpoint/user/SetUserLocationEndpoint.java @@ -20,59 +20,59 @@ public class SetUserLocationEndpoint extends Endpoint { - private static final Logger log = Logger.getLogger(SetUserLocationEndpoint.class.getSimpleName()); - - @Override - public String getEndpointPath() { - return EndpointManager.ENDPOINT_USER_SET_LOCATION; - } - - @Override - public boolean requiresAuthorization(ApiRequest request) { - return true; - } - - @Override - public List getRequiredParameters(ApiRequest request) { - List parameters = new ArrayList(); - parameters.add("latitude"); - parameters.add("longitude"); - parameters.add("userKeyId"); - return parameters; - } - - @Override - public PermissionSet getRequiredPermissions(ApiRequest request) { - PermissionSet permissionSet = new PermissionSet(); - - // user wants to update his own location - PermissionEntry permissionEntry = new PermissionEntry(); - permissionEntry.setPermission(PermissionEntry.PERMISSION_OWN); - permissionEntry.setSubjectKeyId(request.getParameterAsLong("userKeyId", -1)); - permissionEntry.setSubjectKind(UserEntry.class.getSimpleName()); - permissionSet.getPermissions().add(permissionEntry); - - return permissionSet; - } - - @Override - public ApiResponse generateRequestResponse(ApiRequest request) throws Exception { - ApiResponse response = new ApiResponse(); - float latitude = request.getParameterAsFloat("latitude", -1); - float longitude = request.getParameterAsFloat("longitude", -1); - - // get the user that initiated the request - UserEntry user = request.getUser(); - - // update the location - user.setLatitude(latitude); - user.setLongitude(longitude); - user.setLastLocationUpdate((new Date()).getTime()); - UserHelper.saveEntry(user); - - log.info("User " + user.getName() + " location updated"); - response.setContent(user); - return response; - } - + private static final Logger log = Logger.getLogger(SetUserLocationEndpoint.class.getSimpleName()); + + @Override + public String getEndpointPath() { + return EndpointManager.ENDPOINT_USER_SET_LOCATION; + } + + @Override + public boolean requiresAuthorization(ApiRequest request) { + return true; + } + + @Override + public List getRequiredParameters(ApiRequest request) { + List parameters = new ArrayList(); + parameters.add("latitude"); + parameters.add("longitude"); + parameters.add("userKeyId"); + return parameters; + } + + @Override + public PermissionSet getRequiredPermissions(ApiRequest request) { + PermissionSet permissionSet = new PermissionSet(); + + // user wants to update his own location + PermissionEntry permissionEntry = new PermissionEntry(); + permissionEntry.setPermission(PermissionEntry.PERMISSION_OWN); + permissionEntry.setSubjectKeyId(request.getParameterAsLong("userKeyId", -1)); + permissionEntry.setSubjectKind(UserEntry.class.getSimpleName()); + permissionSet.getPermissions().add(permissionEntry); + + return permissionSet; + } + + @Override + public ApiResponse generateRequestResponse(ApiRequest request) throws Exception { + ApiResponse response = new ApiResponse(); + float latitude = request.getParameterAsFloat("latitude", -1); + float longitude = request.getParameterAsFloat("longitude", -1); + + // get the user that initiated the request + UserEntry user = request.getUser(); + + // update the location + user.setLatitude(latitude); + user.setLongitude(longitude); + user.setLastLocationUpdate((new Date()).getTime()); + UserHelper.saveEntry(user); + + log.info("User " + user.getName() + " location updated"); + response.setContent(user); + return response; + } + } diff --git a/IntelliQ/backend/src/main/java/com/intelliq/appengine/api/endpoint/user/SignInUserEndpoint.java b/IntelliQ/backend/src/main/java/com/intelliq/appengine/api/endpoint/user/SignInUserEndpoint.java index 1d508b9..70807e3 100644 --- a/IntelliQ/backend/src/main/java/com/intelliq/appengine/api/endpoint/user/SignInUserEndpoint.java +++ b/IntelliQ/backend/src/main/java/com/intelliq/appengine/api/endpoint/user/SignInUserEndpoint.java @@ -1,6 +1,8 @@ package com.intelliq.appengine.api.endpoint.user; +import java.util.ArrayList; import java.util.Date; +import java.util.List; import java.util.logging.Logger; import javax.servlet.http.HttpServletResponse; @@ -12,51 +14,56 @@ import com.intelliq.appengine.api.endpoint.EndpointManager; import com.intelliq.appengine.datastore.UserHelper; import com.intelliq.appengine.datastore.entries.UserEntry; +import com.intelliq.appengine.logging.SlackLog; +import com.intelliq.appengine.logging.UserLogging; + +import net.steppschuh.slackmessagebuilder.message.attachment.Attachment; +import net.steppschuh.slackmessagebuilder.message.attachment.AttachmentField; public class SignInUserEndpoint extends Endpoint { - private static final Logger log = Logger.getLogger(SignInUserEndpoint.class.getSimpleName()); - - @Override - public String getEndpointPath() { - return EndpointManager.ENDPOINT_USER_SIGNIN; - } - - @Override - public ApiResponse generateRequestResponse(ApiRequest request) throws Exception { - ApiResponse response = new ApiResponse(); - - // get the user that initiated the request - UserEntry parsedUser = null; - try { - parsedUser = request.parseUserFromToken(); - } catch (Exception ex) { - response.setStatusCode(HttpServletResponse.SC_BAD_REQUEST); - response.setException(new Exception("Unable to parse user from token: " + ex.getMessage())); - return response; - } - - UserEntry existingUser = null; - try { - existingUser = request.getUserFromToken(parsedUser); - existingUser.getStats().setLastSignIn((new Date()).getTime()); - UserHelper.saveEntry(existingUser); - - log.info("User " + existingUser.getName() + " signed in"); - response.setContent(existingUser); - return response; - } catch (Exception ex) { - // user is not registered yet - } - - // add the user - Key userKey = UserHelper.saveEntry(parsedUser); - parsedUser.setKey(userKey); - - log.info("User " + parsedUser.getName() + " signed up"); - response.setContent(parsedUser); - return response; - } - + private static final Logger log = Logger.getLogger(SignInUserEndpoint.class.getSimpleName()); + + @Override + public String getEndpointPath() { + return EndpointManager.ENDPOINT_USER_SIGNIN; + } + + @Override + public ApiResponse generateRequestResponse(ApiRequest request) throws Exception { + ApiResponse response = new ApiResponse(); + + // get the user that initiated the request + UserEntry parsedUser = null; + try { + parsedUser = request.parseUserFromToken(); + } catch (Exception ex) { + response.setStatusCode(HttpServletResponse.SC_BAD_REQUEST); + response.setException(new Exception("Unable to parse user from token: " + ex.getMessage())); + return response; + } + + UserEntry existingUser = null; + try { + existingUser = request.getUserFromToken(parsedUser); + existingUser.getStats().setLastSignIn((new Date()).getTime()); + UserHelper.saveEntry(existingUser); + + //UserLogging.logSignIn(existingUser); + response.setContent(existingUser); + return response; + } catch (Exception ex) { + // user is not registered yet + } + + // add the user + Key userKey = UserHelper.saveEntry(parsedUser); + parsedUser.setKey(userKey); + + UserLogging.logSignUp(parsedUser); + response.setContent(parsedUser); + return response; + } + } diff --git a/IntelliQ/backend/src/main/java/com/intelliq/appengine/api/endpoint/user/UserEndpoint.java b/IntelliQ/backend/src/main/java/com/intelliq/appengine/api/endpoint/user/UserEndpoint.java index 5ccc8e3..e39ab77 100644 --- a/IntelliQ/backend/src/main/java/com/intelliq/appengine/api/endpoint/user/UserEndpoint.java +++ b/IntelliQ/backend/src/main/java/com/intelliq/appengine/api/endpoint/user/UserEndpoint.java @@ -10,29 +10,29 @@ public class UserEndpoint extends Endpoint { - public static final List endpoints = getAvailableEndpoints(); - - @Override - public String getEndpointPath() { - return EndpointManager.ENDPOINT_USER; - } - - @Override - public ApiResponse processRequest(ApiRequest request) { - Endpoint endpoint = EndpointManager.getEndpointForRequest(request, endpoints); - if (endpoint != null) { - return endpoint.processRequest(request); - } else { - return super.processRequest(request); - } - } - - private static List getAvailableEndpoints() { - List endpoints = new ArrayList(); - endpoints.add(new GetUserEndpoint()); - endpoints.add(new SignInUserEndpoint()); - endpoints.add(new SetUserLocationEndpoint()); - return endpoints; + public static final List endpoints = getAvailableEndpoints(); + + @Override + public String getEndpointPath() { + return EndpointManager.ENDPOINT_USER; + } + + @Override + public ApiResponse processRequest(ApiRequest request) { + Endpoint endpoint = EndpointManager.getEndpointForRequest(request, endpoints); + if (endpoint != null) { + return endpoint.processRequest(request); + } else { + return super.processRequest(request); + } } - + + private static List getAvailableEndpoints() { + List endpoints = new ArrayList(); + endpoints.add(new GetUserEndpoint()); + endpoints.add(new SignInUserEndpoint()); + endpoints.add(new SetUserLocationEndpoint()); + return endpoints; + } + } diff --git a/IntelliQ/backend/src/main/java/com/intelliq/appengine/chron/ChronServlet.java b/IntelliQ/backend/src/main/java/com/intelliq/appengine/chron/ChronServlet.java index 8ad87c3..365a5df 100644 --- a/IntelliQ/backend/src/main/java/com/intelliq/appengine/chron/ChronServlet.java +++ b/IntelliQ/backend/src/main/java/com/intelliq/appengine/chron/ChronServlet.java @@ -34,53 +34,53 @@ @SuppressWarnings("serial") public class ChronServlet extends HttpServlet { - - private static final Logger log = Logger.getLogger(ChronServlet.class.getName()); - @Override + private static final Logger log = Logger.getLogger(ChronServlet.class.getName()); + + @Override public void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException, ServletException { - - String requestUrl = req.getRequestURL().toString(); - - String response = ""; - Object responseObject = null; - - try { - if (requestUrl.contains("/clean/datastore/")) { - responseObject = processCleanDatastoreRequest(req); - } else { - throw new Exception("Unknown chron job"); - } - - if (responseObject != null) { - Gson gson = new Gson(); - response = gson.toJson(responseObject); - } else { - response = "{}"; - } - } catch (Exception e) { - if (e.getMessage() != null) { - response = e.getMessage(); - } else { - response = e.toString(); - } - e.printStackTrace(); - } - - resp.setContentType("application/json"); - resp.addHeader("Access-Control-Allow-Origin", "*"); + + String requestUrl = req.getRequestURL().toString(); + + String response = ""; + Object responseObject = null; + + try { + if (requestUrl.contains("/clean/datastore/")) { + responseObject = processCleanDatastoreRequest(req); + } else { + throw new Exception("Unknown chron job"); + } + + if (responseObject != null) { + Gson gson = new Gson(); + response = gson.toJson(responseObject); + } else { + response = "{}"; + } + } catch (Exception e) { + if (e.getMessage() != null) { + response = e.getMessage(); + } else { + response = e.toString(); + } + e.printStackTrace(); + } + + resp.setContentType("application/json"); + resp.addHeader("Access-Control-Allow-Origin", "*"); resp.getWriter().write(response); resp.getWriter().flush(); resp.getWriter().close(); } - - public Object processCleanDatastoreRequest(HttpServletRequest req) throws Exception { - log.info("Datastore cleaning invoked"); - - // TODO: implement datastore cleaning - - return null; - } + + public Object processCleanDatastoreRequest(HttpServletRequest req) throws Exception { + log.info("Datastore cleaning invoked"); + + // TODO: implement datastore cleaning + + return null; + } } diff --git a/IntelliQ/backend/src/main/java/com/intelliq/appengine/datastore/BusinessHelper.java b/IntelliQ/backend/src/main/java/com/intelliq/appengine/datastore/BusinessHelper.java index 9477dc5..1cea1dd 100644 --- a/IntelliQ/backend/src/main/java/com/intelliq/appengine/datastore/BusinessHelper.java +++ b/IntelliQ/backend/src/main/java/com/intelliq/appengine/datastore/BusinessHelper.java @@ -13,62 +13,62 @@ public class BusinessHelper { - private static final Logger log = Logger.getLogger(BusinessHelper.class.getName()); + private static final Logger log = Logger.getLogger(BusinessHelper.class.getName()); - public static Key saveEntry(BusinessEntry entry) throws Exception { - if (entry == null) { - throw new Exception("EntryItem is null"); - } + public static Key saveEntry(BusinessEntry entry) throws Exception { + if (entry == null) { + throw new Exception("EntryItem is null"); + } + + Key entryKey = null; + PersistenceManager pm = PMF.get().getPersistenceManager(); + try { + entryKey = pm.makePersistent(entry).getKey(); + } finally { + pm.close(); + } + return entryKey; + } + + public static List getBusinessesByUserKeyId(long userKeyId, int minimumPermission) { + List businessKeyIds = getBusinessKeyIdsByUserKeyId(userKeyId, minimumPermission); + List results = new ArrayList(); + for (Long businessKeyId : businessKeyIds) { + try { + BusinessEntry businessEntry = getEntryByKeyId(businessKeyId); + if (businessEntry == null) { + throw new Exception("Business is null"); + } + results.add(businessEntry); + } catch (Exception ex) { + log.warning("Unable to get business with key ID: " + businessKeyId + ": " + ex.getMessage()); + } + } + return results; + } + + public static List getBusinessKeyIdsByUserKeyId(long userKeyId, int minimumPermission) { + List permissions = PermissionHelper.getPermissions(userKeyId, BusinessEntry.class.getSimpleName()); + List businessKeyIds = new ArrayList<>(); + for (PermissionEntry permission : permissions) { + if (permission.getPermission() >= minimumPermission) { + businessKeyIds.add(permission.getSubjectKeyId()); + } + } + return businessKeyIds; + } + + public static BusinessEntry getEntryByKeyId(String idString) { + long id = Long.parseLong(idString); + return getEntryByKeyId(id); + } + + public static BusinessEntry getEntryByKeyId(long id) { + PersistenceManager pm = PMF.get().getPersistenceManager(); + Key key = KeyFactory.createKey(BusinessEntry.class.getSimpleName(), id); + BusinessEntry entry = pm.getObjectById(BusinessEntry.class, key); + pm.close(); + return entry; + } - Key entryKey = null; - PersistenceManager pm = PMF.get().getPersistenceManager(); - try { - entryKey = pm.makePersistent(entry).getKey(); - } finally { - pm.close(); - } - return entryKey; - } - - public static List getBusinessesByUserKeyId(long userKeyId, int minimumPermission) { - List businessKeyIds = getBusinessKeyIdsByUserKeyId(userKeyId, minimumPermission); - List results = new ArrayList(); - for (Long businessKeyId : businessKeyIds) { - try { - BusinessEntry businessEntry = getEntryByKeyId(businessKeyId); - if (businessEntry == null) { - throw new Exception("Business is null"); - } - results.add(businessEntry); - } catch (Exception ex) { - log.warning("Unable to get business with key ID: " + businessKeyId + ": " + ex.getMessage()); - } - } - return results; - } - - public static List getBusinessKeyIdsByUserKeyId(long userKeyId, int minimumPermission) { - List permissions = PermissionHelper.getPermissions(userKeyId, BusinessEntry.class.getSimpleName()); - List businessKeyIds = new ArrayList<>(); - for (PermissionEntry permission : permissions) { - if (permission.getPermission() >= minimumPermission) { - businessKeyIds.add(permission.getSubjectKeyId()); - } - } - return businessKeyIds; - } - - public static BusinessEntry getEntryByKeyId(String idString) { - long id = Long.parseLong(idString); - return getEntryByKeyId(id); - } - - public static BusinessEntry getEntryByKeyId(long id) { - PersistenceManager pm = PMF.get().getPersistenceManager(); - Key key = KeyFactory.createKey(BusinessEntry.class.getSimpleName(), id); - BusinessEntry entry = pm.getObjectById(BusinessEntry.class, key); - pm.close(); - return entry; - } - } diff --git a/IntelliQ/backend/src/main/java/com/intelliq/appengine/datastore/EntryManager.java b/IntelliQ/backend/src/main/java/com/intelliq/appengine/datastore/EntryManager.java index c26a2f5..d7b2b6d 100644 --- a/IntelliQ/backend/src/main/java/com/intelliq/appengine/datastore/EntryManager.java +++ b/IntelliQ/backend/src/main/java/com/intelliq/appengine/datastore/EntryManager.java @@ -5,8 +5,7 @@ public class EntryManager { - private static final Logger log = Logger.getLogger(EntryManager.class.getName()); - - - + private static final Logger log = Logger.getLogger(EntryManager.class.getName()); + + } diff --git a/IntelliQ/backend/src/main/java/com/intelliq/appengine/datastore/ImageHelper.java b/IntelliQ/backend/src/main/java/com/intelliq/appengine/datastore/ImageHelper.java index 42bb1cb..fb09f71 100644 --- a/IntelliQ/backend/src/main/java/com/intelliq/appengine/datastore/ImageHelper.java +++ b/IntelliQ/backend/src/main/java/com/intelliq/appengine/datastore/ImageHelper.java @@ -18,84 +18,84 @@ public class ImageHelper { - private static final Logger log = Logger.getLogger(ImageHelper.class.getName()); - - public static Key saveEntry(ImageEntry entry) throws Exception { - if (entry == null) { - throw new Exception("EntryItem is null"); - } - - Key entryKey = null; - PersistenceManager pm = PMF.get().getPersistenceManager(); - try { - entryKey = pm.makePersistent(entry).getKey(); - } finally { - pm.close(); - } - return entryKey; - } - - public static ImageEntry fetchImageFromUrl(String url) throws Exception { - log.info("Fetching image from " + url); - - URLFetchService fetchService = URLFetchServiceFactory.getURLFetchService(); - HTTPResponse fetchResponse = fetchService.fetch(new URL(url)); - - String fetchResponseContentType = null; - for (HTTPHeader header : fetchResponse.getHeaders()) { - if (header.getName().equalsIgnoreCase("content-type")) { - fetchResponseContentType = header.getValue(); - break; - } - } - - if (fetchResponseContentType != null) { - ImageEntry image = new ImageEntry(); - image.setImageType(fetchResponseContentType); - image.setImage(fetchResponse.getContent()); - return image; - } - - return null; - } - - public static byte[] resizeImage(byte[] originalImage, String sizeString) { - if (sizeString.equals(ImageEntry.SIZE_ORIGINAL)) { - return originalImage; - } else { - try { - int newWidth = Integer.parseInt(sizeString); - return resizeImage(originalImage, newWidth, false); - } catch (Exception e) { - return originalImage; - } - } - } - - public static byte[] resizeImage(byte[] originalImage, int newWidth) { - return resizeImage(originalImage, newWidth, false); - } - - public static byte[] resizeImage(byte[] originalImage, int newWidth, boolean scaleUp) { - try { - ImagesService imagesService = ImagesServiceFactory.getImagesService(); - - Image oldImage = ImagesServiceFactory.makeImage(originalImage); - - // avoid scaling up images - if (newWidth > oldImage.getWidth() && !scaleUp) { - return originalImage; - } - - // get new height to keep aspect ratio - int newHeight = Math.round((newWidth * oldImage.getHeight()) / oldImage.getWidth()); - - Transform resize = ImagesServiceFactory.makeResize(newWidth, newHeight); - Image newImage = imagesService.applyTransform(resize, oldImage); - return newImage.getImageData(); - } catch (Exception e) { - e.printStackTrace(); - return originalImage; - } - } + private static final Logger log = Logger.getLogger(ImageHelper.class.getName()); + + public static Key saveEntry(ImageEntry entry) throws Exception { + if (entry == null) { + throw new Exception("EntryItem is null"); + } + + Key entryKey = null; + PersistenceManager pm = PMF.get().getPersistenceManager(); + try { + entryKey = pm.makePersistent(entry).getKey(); + } finally { + pm.close(); + } + return entryKey; + } + + public static ImageEntry fetchImageFromUrl(String url) throws Exception { + log.info("Fetching image from " + url); + + URLFetchService fetchService = URLFetchServiceFactory.getURLFetchService(); + HTTPResponse fetchResponse = fetchService.fetch(new URL(url)); + + String fetchResponseContentType = null; + for (HTTPHeader header : fetchResponse.getHeaders()) { + if (header.getName().equalsIgnoreCase("content-type")) { + fetchResponseContentType = header.getValue(); + break; + } + } + + if (fetchResponseContentType != null) { + ImageEntry image = new ImageEntry(); + image.setImageType(fetchResponseContentType); + image.setImage(fetchResponse.getContent()); + return image; + } + + return null; + } + + public static byte[] resizeImage(byte[] originalImage, String sizeString) { + if (sizeString.equals(ImageEntry.SIZE_ORIGINAL)) { + return originalImage; + } else { + try { + int newWidth = Integer.parseInt(sizeString); + return resizeImage(originalImage, newWidth, false); + } catch (Exception e) { + return originalImage; + } + } + } + + public static byte[] resizeImage(byte[] originalImage, int newWidth) { + return resizeImage(originalImage, newWidth, false); + } + + public static byte[] resizeImage(byte[] originalImage, int newWidth, boolean scaleUp) { + try { + ImagesService imagesService = ImagesServiceFactory.getImagesService(); + + Image oldImage = ImagesServiceFactory.makeImage(originalImage); + + // avoid scaling up images + if (newWidth > oldImage.getWidth() && !scaleUp) { + return originalImage; + } + + // get new height to keep aspect ratio + int newHeight = Math.round((newWidth * oldImage.getHeight()) / oldImage.getWidth()); + + Transform resize = ImagesServiceFactory.makeResize(newWidth, newHeight); + Image newImage = imagesService.applyTransform(resize, oldImage); + return newImage.getImageData(); + } catch (Exception e) { + e.printStackTrace(); + return originalImage; + } + } } diff --git a/IntelliQ/backend/src/main/java/com/intelliq/appengine/datastore/Location.java b/IntelliQ/backend/src/main/java/com/intelliq/appengine/datastore/Location.java index b921ad5..89c74e6 100644 --- a/IntelliQ/backend/src/main/java/com/intelliq/appengine/datastore/Location.java +++ b/IntelliQ/backend/src/main/java/com/intelliq/appengine/datastore/Location.java @@ -5,148 +5,148 @@ public class Location { - public static final int DISTANCE_ANY = -1; - public static final int DISTANCE_NARROW = 1000; - public static final int DISTANCE_DEFAULT = 3 * DISTANCE_NARROW; - public static final int DISTANCE_FAR = 3 * DISTANCE_DEFAULT; - - float latitude; - float longitude; - - String country; - String city; - String postalCode; - String street; - String number; - - public Location() { - latitude = -1; - longitude = -1; - } - - /* - * Methods for calculating the distance between two locations in meters - */ - public float getDistanceTo(Location destination) { - return getDistance(this, destination); - } - - public static float getDistance(Location source, Location destination) { - if (source.latitude == -1 || source.longitude == -1 || destination.latitude == -1 || destination.longitude == -1) { - return -1; - } else { - return getDistance(source.latitude, source.longitude, destination.latitude, destination.longitude); - } - } - - public static float getDistance(float lat1, float lng1, float lat2, float lng2) { - double earthRadius = 6371000; - double dLat = Math.toRadians(lat2 - lat1); - double dLng = Math.toRadians(lng2 - lng1); - double a = Math.sin(dLat / 2) * Math.sin(dLat / 2) + Math.cos(Math.toRadians(lat1)) * Math.cos(Math.toRadians(lat2)) * Math.sin(dLng / 2) * Math.sin(dLng / 2); - double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); - float dist = (float) (earthRadius * c); - - return dist; - } - - public void parseFromRequest(ApiRequest req) { - String countryParam = req.getParameter("country"); - String cityParam = req.getParameter("city"); - String postalCodeParam = req.getParameter("postalCode"); - String streetParam = req.getParameter("street"); - String numberParam = req.getParameter("number"); - - String latitudeParam = req.getParameter("latitude"); - String longitudeParam = req.getParameter("longitude"); - - if (ParserHelper.containsAnyValue(countryParam)) { - setCountry(countryParam); - } - if (ParserHelper.containsAnyValue(cityParam)) { - setCity(cityParam); - } - if (ParserHelper.containsAnyValue(postalCodeParam)) { - setPostalCode(postalCodeParam); - } - if (ParserHelper.containsAnyValue(streetParam)) { - setStreet(streetParam); - } - if (ParserHelper.containsAnyValue(numberParam)) { - setNumber(numberParam); - } - if (ParserHelper.containsAnyValue(latitudeParam)) { - setLatitude(Float.parseFloat(latitudeParam)); - } - if (ParserHelper.containsAnyValue(longitudeParam)) { - setLongitude(Float.parseFloat(longitudeParam)); - } - } - - public boolean isValidLocation() { - if (latitude == -1 && longitude == -1) { - return false; - } - return true; - } - - /* - * getter & setter - */ - public float getLatitude() { - return latitude; - } - - public void setLatitude(float latitude) { - this.latitude = latitude; - } - - public float getLongitude() { - return longitude; - } - - public void setLongitude(float longitude) { - this.longitude = longitude; - } - - public String getCountry() { - return country; - } - - public void setCountry(String country) { - this.country = country; - } - - public String getCity() { - return city; - } - - public void setCity(String city) { - this.city = city; - } - - public String getPostalCode() { - return postalCode; - } - - public void setPostalCode(String postalCode) { - this.postalCode = postalCode; - } - - public String getStreet() { - return street; - } - - public void setStreet(String street) { - this.street = street; - } - - public String getNumber() { - return number; - } - - public void setNumber(String number) { - this.number = number; - } - + public static final int DISTANCE_ANY = -1; + public static final int DISTANCE_NARROW = 1000; + public static final int DISTANCE_DEFAULT = 3 * DISTANCE_NARROW; + public static final int DISTANCE_FAR = 3 * DISTANCE_DEFAULT; + + float latitude; + float longitude; + + String country; + String city; + String postalCode; + String street; + String number; + + public Location() { + latitude = -1; + longitude = -1; + } + + /* + * Methods for calculating the distance between two locations in meters + */ + public float getDistanceTo(Location destination) { + return getDistance(this, destination); + } + + public static float getDistance(Location source, Location destination) { + if (source.latitude == -1 || source.longitude == -1 || destination.latitude == -1 || destination.longitude == -1) { + return -1; + } else { + return getDistance(source.latitude, source.longitude, destination.latitude, destination.longitude); + } + } + + public static float getDistance(float lat1, float lng1, float lat2, float lng2) { + double earthRadius = 6371000; + double dLat = Math.toRadians(lat2 - lat1); + double dLng = Math.toRadians(lng2 - lng1); + double a = Math.sin(dLat / 2) * Math.sin(dLat / 2) + Math.cos(Math.toRadians(lat1)) * Math.cos(Math.toRadians(lat2)) * Math.sin(dLng / 2) * Math.sin(dLng / 2); + double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); + float dist = (float) (earthRadius * c); + + return dist; + } + + public void parseFromRequest(ApiRequest req) { + String countryParam = req.getParameter("country"); + String cityParam = req.getParameter("city"); + String postalCodeParam = req.getParameter("postalCode"); + String streetParam = req.getParameter("street"); + String numberParam = req.getParameter("number"); + + String latitudeParam = req.getParameter("latitude"); + String longitudeParam = req.getParameter("longitude"); + + if (ParserHelper.containsAnyValue(countryParam)) { + setCountry(countryParam); + } + if (ParserHelper.containsAnyValue(cityParam)) { + setCity(cityParam); + } + if (ParserHelper.containsAnyValue(postalCodeParam)) { + setPostalCode(postalCodeParam); + } + if (ParserHelper.containsAnyValue(streetParam)) { + setStreet(streetParam); + } + if (ParserHelper.containsAnyValue(numberParam)) { + setNumber(numberParam); + } + if (ParserHelper.containsAnyValue(latitudeParam)) { + setLatitude(Float.parseFloat(latitudeParam)); + } + if (ParserHelper.containsAnyValue(longitudeParam)) { + setLongitude(Float.parseFloat(longitudeParam)); + } + } + + public boolean isValidLocation() { + if (latitude == -1 && longitude == -1) { + return false; + } + return true; + } + + /* + * getter & setter + */ + public float getLatitude() { + return latitude; + } + + public void setLatitude(float latitude) { + this.latitude = latitude; + } + + public float getLongitude() { + return longitude; + } + + public void setLongitude(float longitude) { + this.longitude = longitude; + } + + public String getCountry() { + return country; + } + + public void setCountry(String country) { + this.country = country; + } + + public String getCity() { + return city; + } + + public void setCity(String city) { + this.city = city; + } + + public String getPostalCode() { + return postalCode; + } + + public void setPostalCode(String postalCode) { + this.postalCode = postalCode; + } + + public String getStreet() { + return street; + } + + public void setStreet(String street) { + this.street = street; + } + + public String getNumber() { + return number; + } + + public void setNumber(String number) { + this.number = number; + } + } diff --git a/IntelliQ/backend/src/main/java/com/intelliq/appengine/datastore/MergeHelper.java b/IntelliQ/backend/src/main/java/com/intelliq/appengine/datastore/MergeHelper.java index 639b48d..6f52861 100644 --- a/IntelliQ/backend/src/main/java/com/intelliq/appengine/datastore/MergeHelper.java +++ b/IntelliQ/backend/src/main/java/com/intelliq/appengine/datastore/MergeHelper.java @@ -7,36 +7,36 @@ public class MergeHelper { - public static String mergeStrings(String existingValue, String newValue) { - if (!ParserHelper.containsAnyValue(existingValue)) { - return newValue; - } else { - return existingValue; - } - } - - public static List mergeListsOfString(List existingList, List newList) { - if (existingList == null || existingList.size() == 0) { - return newList; - } - for (String newString: newList) { - if (!existingList.contains(newString)) { - existingList.add(newString); - } - } - return existingList; - } - - public static List mergeListsOfLong(List existingList, List newList) { - if (existingList == null || existingList.size() == 0) { - return newList; - } - for (long newValue: newList) { - if (!existingList.contains(newValue)) { - existingList.add(newValue); - } - } - return existingList; - } - + public static String mergeStrings(String existingValue, String newValue) { + if (!ParserHelper.containsAnyValue(existingValue)) { + return newValue; + } else { + return existingValue; + } + } + + public static List mergeListsOfString(List existingList, List newList) { + if (existingList == null || existingList.size() == 0) { + return newList; + } + for (String newString : newList) { + if (!existingList.contains(newString)) { + existingList.add(newString); + } + } + return existingList; + } + + public static List mergeListsOfLong(List existingList, List newList) { + if (existingList == null || existingList.size() == 0) { + return newList; + } + for (long newValue : newList) { + if (!existingList.contains(newValue)) { + existingList.add(newValue); + } + } + return existingList; + } + } diff --git a/IntelliQ/backend/src/main/java/com/intelliq/appengine/datastore/PMF.java b/IntelliQ/backend/src/main/java/com/intelliq/appengine/datastore/PMF.java index d6c8748..9c6f81a 100644 --- a/IntelliQ/backend/src/main/java/com/intelliq/appengine/datastore/PMF.java +++ b/IntelliQ/backend/src/main/java/com/intelliq/appengine/datastore/PMF.java @@ -4,10 +4,11 @@ import javax.jdo.PersistenceManagerFactory; public final class PMF { - + private static final PersistenceManagerFactory pmfInstance = JDOHelper.getPersistenceManagerFactory("transactions-optional"); - private PMF() {} + private PMF() { + } public static PersistenceManagerFactory get() { return pmfInstance; diff --git a/IntelliQ/backend/src/main/java/com/intelliq/appengine/datastore/PermissionHelper.java b/IntelliQ/backend/src/main/java/com/intelliq/appengine/datastore/PermissionHelper.java index 4b75dfc..68b847b 100644 --- a/IntelliQ/backend/src/main/java/com/intelliq/appengine/datastore/PermissionHelper.java +++ b/IntelliQ/backend/src/main/java/com/intelliq/appengine/datastore/PermissionHelper.java @@ -17,207 +17,207 @@ public class PermissionHelper { - private static final Logger log = Logger.getLogger(PermissionHelper.class.getSimpleName()); - - public static Key saveEntry(PermissionEntry entry) throws Exception { - if (entry == null) { - throw new Exception("PermissionEntry is null"); - } - - Key entryKey = null; - PersistenceManager pm = PMF.get().getPersistenceManager(); - try { - entryKey = pm.makePersistent(entry).getKey(); - } finally { - pm.close(); - } - return entryKey; - } - - public static boolean hasPermission(PermissionEntry permissionEntry) { - return hasPermission(permissionEntry.getUserKeyId(), permissionEntry.getSubjectKeyId(), permissionEntry.getSubjectKind(), permissionEntry.getPermission()); - } - - public static boolean hasPermission(long userKeyId, long subjectKeyId, String subjectKind, int permission) { - // check if this permission exists - if (getPermission(userKeyId, subjectKeyId, permission) != null) { - return true; - } - return false; - } - - public static PermissionEntry getPermission(long userKeyId, long subjectKeyId, int permission) { - try { - List permissionEntries = getPermissions(userKeyId, subjectKeyId); - for (PermissionEntry permissionEntry : permissionEntries) { - if (permissionEntry.matches(userKeyId, subjectKeyId, permission)) { - return permissionEntry; - } - } - return null; - } catch (Exception e) { - log.warning("Unable to get permission"); - e.printStackTrace(); - return null; - } - } - - public static boolean grantPermission(UserEntry user, BusinessEntry businessEntry, int permission) { - try { - return grantPermission(user.getKey().getId(), businessEntry.getKey().getId(), BusinessEntry.class.getSimpleName(), permission); - } catch (Exception ex) { - log.warning("Unable to grant permission: " + ex.getMessage()); - return false; - } - } - - public static boolean grantPermission(UserEntry user, QueueEntry queueEntry, int permission) { - try { - return grantPermission(user.getKey().getId(), queueEntry.getKey().getId(), QueueEntry.class.getSimpleName(), permission); - } catch (Exception ex) { - log.warning("Unable to grant permission: " + ex.getMessage()); - return false; - } - } - - public static boolean grantPermission(UserEntry user, QueueItemEntry queueItemEntry, int permission) { - try { - return grantPermission(user.getKey().getId(), queueItemEntry.getKey().getId(), QueueItemEntry.class.getSimpleName(), permission); - } catch (Exception ex) { - log.warning("Unable to grant permission: " + ex.getMessage()); - return false; - } - } - - public static boolean grantPermission(long userKeyId, long subjectKeyId, String subjectKind, int permission) { - try { - PermissionEntry entry = new PermissionEntry(userKeyId, subjectKeyId, subjectKind, permission); - saveEntry(entry); - log.info("New permission granted: " + userKeyId + " - " + subjectKeyId + " (" + subjectKind + "): " + permission); - return true; - } catch (Exception e) { - log.warning("Unable to grant permission: " + e.getMessage()); - e.printStackTrace(); - return false; - } - } - - public static boolean revokePermission(long userKeyId, long subjectKeyId, int permission) { - try { - PermissionEntry permissionEntry = getPermission(userKeyId, subjectKeyId, permission); - if (permissionEntry == null) { - throw new Exception("Permission does not exist"); - } - deleteEntryByKeyId(permissionEntry.getKey().getId()); - log.info("Permission revoked"); - return true; - } catch (Exception e) { - log.warning("Unable to revoke permission: " + e.getMessage()); - e.printStackTrace(); - return false; - } - } - - public static List getPermissions(long userKeyId, long subjectKeyId) { - PersistenceManager pm = PMF.get().getPersistenceManager(); - Query query = pm.newQuery(PermissionEntry.class); - query.setFilter("userKeyId == value && subjectKeyId == value2"); - query.declareParameters("long value, long value2"); - query.setOrdering("permission descending"); - query.setRange(0, 100); - - List results = new ArrayList(); - try { - results = (List) query.execute(userKeyId, subjectKeyId); - log.info("PermissionQuery execution returned " + results.size() + " item(s)"); - } catch (Exception e) { - e.printStackTrace(); - } finally { - query.closeAll(); - pm.close(); - } - return results; - } - - public static List getPermissions(long userKeyId, String subjectKind) { - PersistenceManager pm = PMF.get().getPersistenceManager(); - Query query = pm.newQuery(PermissionEntry.class); - query.setFilter("userKeyId == value && subjectKind == value2"); - query.declareParameters("long value, String value2"); - query.setOrdering("permission descending"); - query.setRange(0, 100); - - List results = new ArrayList(); - try { - results = (List) query.execute(userKeyId, subjectKind); - log.info("PermissionQuery execution returned " + results.size() + " item(s)"); - } catch (Exception e) { - e.printStackTrace(); - } finally { - query.closeAll(); - pm.close(); - } - return results; - } - - public static List getPermissionsByUserKeyId(long userKeyId) { - PersistenceManager pm = PMF.get().getPersistenceManager(); - Query query = pm.newQuery(PermissionEntry.class); - query.setFilter("userKeyId == '" + userKeyId + "'"); - query.setOrdering("permission descending"); - query.setRange(0, 1000); - - List results = new ArrayList(); - try { - results = (List) query.execute(); - log.info("PermissionQuery execution returned " + results.size() + " item(s)"); - } catch (Exception e) { - e.printStackTrace(); - } finally { - query.closeAll(); - pm.close(); - } - return results; - } - - public static List getPermissionsBySubjectKeyId(long subjectKeyId) { - PersistenceManager pm = PMF.get().getPersistenceManager(); - Query query = pm.newQuery(PermissionEntry.class); - query.setFilter("subjectKeyId == '" + subjectKeyId + "'"); - query.setOrdering("permission descending"); - query.setRange(0, 1000); - - List results = new ArrayList(); - try { - results = (List) query.execute(); - log.info("PermissionQuery execution returned " + results.size() + " item(s)"); - } catch (Exception e) { - e.printStackTrace(); - } finally { - query.closeAll(); - pm.close(); - } - return results; - } - - public static PermissionEntry getEntryByKeyId(String idString) { - long id = Long.parseLong(idString); - return getEntryByKeyId(id); - } - - public static PermissionEntry getEntryByKeyId(long id) { - PersistenceManager pm = PMF.get().getPersistenceManager(); - Key key = KeyFactory.createKey(PermissionEntry.class.getSimpleName(), id); - PermissionEntry entry = pm.getObjectById(PermissionEntry.class, key); - pm.close(); - return entry; - } - - public static void deleteEntryByKeyId(long id) { - PersistenceManager pm = PMF.get().getPersistenceManager(); - Key key = KeyFactory.createKey(PermissionEntry.class.getSimpleName(), id); - PermissionEntry entry = pm.getObjectById(PermissionEntry.class, key); - pm.deletePersistent(entry); - pm.close(); - } + private static final Logger log = Logger.getLogger(PermissionHelper.class.getSimpleName()); + + public static Key saveEntry(PermissionEntry entry) throws Exception { + if (entry == null) { + throw new Exception("PermissionEntry is null"); + } + + Key entryKey = null; + PersistenceManager pm = PMF.get().getPersistenceManager(); + try { + entryKey = pm.makePersistent(entry).getKey(); + } finally { + pm.close(); + } + return entryKey; + } + + public static boolean hasPermission(PermissionEntry permissionEntry) { + return hasPermission(permissionEntry.getUserKeyId(), permissionEntry.getSubjectKeyId(), permissionEntry.getSubjectKind(), permissionEntry.getPermission()); + } + + public static boolean hasPermission(long userKeyId, long subjectKeyId, String subjectKind, int permission) { + // check if this permission exists + if (getPermission(userKeyId, subjectKeyId, permission) != null) { + return true; + } + return false; + } + + public static PermissionEntry getPermission(long userKeyId, long subjectKeyId, int permission) { + try { + List permissionEntries = getPermissions(userKeyId, subjectKeyId); + for (PermissionEntry permissionEntry : permissionEntries) { + if (permissionEntry.matches(userKeyId, subjectKeyId, permission)) { + return permissionEntry; + } + } + return null; + } catch (Exception e) { + log.warning("Unable to get permission"); + e.printStackTrace(); + return null; + } + } + + public static boolean grantPermission(UserEntry user, BusinessEntry businessEntry, int permission) { + try { + return grantPermission(user.getKey().getId(), businessEntry.getKey().getId(), BusinessEntry.class.getSimpleName(), permission); + } catch (Exception ex) { + log.warning("Unable to grant permission: " + ex.getMessage()); + return false; + } + } + + public static boolean grantPermission(UserEntry user, QueueEntry queueEntry, int permission) { + try { + return grantPermission(user.getKey().getId(), queueEntry.getKey().getId(), QueueEntry.class.getSimpleName(), permission); + } catch (Exception ex) { + log.warning("Unable to grant permission: " + ex.getMessage()); + return false; + } + } + + public static boolean grantPermission(UserEntry user, QueueItemEntry queueItemEntry, int permission) { + try { + return grantPermission(user.getKey().getId(), queueItemEntry.getKey().getId(), QueueItemEntry.class.getSimpleName(), permission); + } catch (Exception ex) { + log.warning("Unable to grant permission: " + ex.getMessage()); + return false; + } + } + + public static boolean grantPermission(long userKeyId, long subjectKeyId, String subjectKind, int permission) { + try { + PermissionEntry entry = new PermissionEntry(userKeyId, subjectKeyId, subjectKind, permission); + saveEntry(entry); + log.info("New permission granted: " + userKeyId + " - " + subjectKeyId + " (" + subjectKind + "): " + permission); + return true; + } catch (Exception e) { + log.warning("Unable to grant permission: " + e.getMessage()); + e.printStackTrace(); + return false; + } + } + + public static boolean revokePermission(long userKeyId, long subjectKeyId, int permission) { + try { + PermissionEntry permissionEntry = getPermission(userKeyId, subjectKeyId, permission); + if (permissionEntry == null) { + throw new Exception("Permission does not exist"); + } + deleteEntryByKeyId(permissionEntry.getKey().getId()); + log.info("Permission revoked"); + return true; + } catch (Exception e) { + log.warning("Unable to revoke permission: " + e.getMessage()); + e.printStackTrace(); + return false; + } + } + + public static List getPermissions(long userKeyId, long subjectKeyId) { + PersistenceManager pm = PMF.get().getPersistenceManager(); + Query query = pm.newQuery(PermissionEntry.class); + query.setFilter("userKeyId == value && subjectKeyId == value2"); + query.declareParameters("long value, long value2"); + query.setOrdering("permission descending"); + query.setRange(0, 100); + + List results = new ArrayList(); + try { + results = (List) query.execute(userKeyId, subjectKeyId); + log.info("PermissionQuery execution returned " + results.size() + " item(s)"); + } catch (Exception e) { + e.printStackTrace(); + } finally { + query.closeAll(); + pm.close(); + } + return results; + } + + public static List getPermissions(long userKeyId, String subjectKind) { + PersistenceManager pm = PMF.get().getPersistenceManager(); + Query query = pm.newQuery(PermissionEntry.class); + query.setFilter("userKeyId == value && subjectKind == value2"); + query.declareParameters("long value, String value2"); + query.setOrdering("permission descending"); + query.setRange(0, 100); + + List results = new ArrayList(); + try { + results = (List) query.execute(userKeyId, subjectKind); + log.info("PermissionQuery execution returned " + results.size() + " item(s)"); + } catch (Exception e) { + e.printStackTrace(); + } finally { + query.closeAll(); + pm.close(); + } + return results; + } + + public static List getPermissionsByUserKeyId(long userKeyId) { + PersistenceManager pm = PMF.get().getPersistenceManager(); + Query query = pm.newQuery(PermissionEntry.class); + query.setFilter("userKeyId == '" + userKeyId + "'"); + query.setOrdering("permission descending"); + query.setRange(0, 1000); + + List results = new ArrayList(); + try { + results = (List) query.execute(); + log.info("PermissionQuery execution returned " + results.size() + " item(s)"); + } catch (Exception e) { + e.printStackTrace(); + } finally { + query.closeAll(); + pm.close(); + } + return results; + } + + public static List getPermissionsBySubjectKeyId(long subjectKeyId) { + PersistenceManager pm = PMF.get().getPersistenceManager(); + Query query = pm.newQuery(PermissionEntry.class); + query.setFilter("subjectKeyId == '" + subjectKeyId + "'"); + query.setOrdering("permission descending"); + query.setRange(0, 1000); + + List results = new ArrayList(); + try { + results = (List) query.execute(); + log.info("PermissionQuery execution returned " + results.size() + " item(s)"); + } catch (Exception e) { + e.printStackTrace(); + } finally { + query.closeAll(); + pm.close(); + } + return results; + } + + public static PermissionEntry getEntryByKeyId(String idString) { + long id = Long.parseLong(idString); + return getEntryByKeyId(id); + } + + public static PermissionEntry getEntryByKeyId(long id) { + PersistenceManager pm = PMF.get().getPersistenceManager(); + Key key = KeyFactory.createKey(PermissionEntry.class.getSimpleName(), id); + PermissionEntry entry = pm.getObjectById(PermissionEntry.class, key); + pm.close(); + return entry; + } + + public static void deleteEntryByKeyId(long id) { + PersistenceManager pm = PMF.get().getPersistenceManager(); + Key key = KeyFactory.createKey(PermissionEntry.class.getSimpleName(), id); + PermissionEntry entry = pm.getObjectById(PermissionEntry.class, key); + pm.deletePersistent(entry); + pm.close(); + } } diff --git a/IntelliQ/backend/src/main/java/com/intelliq/appengine/datastore/QueueHelper.java b/IntelliQ/backend/src/main/java/com/intelliq/appengine/datastore/QueueHelper.java index 078f85f..e024d94 100644 --- a/IntelliQ/backend/src/main/java/com/intelliq/appengine/datastore/QueueHelper.java +++ b/IntelliQ/backend/src/main/java/com/intelliq/appengine/datastore/QueueHelper.java @@ -17,443 +17,444 @@ public class QueueHelper { - private static final Logger log = Logger.getLogger(QueueHelper.class.getName()); - - public static Key saveEntry(QueueEntry entry) throws Exception { - if (entry == null) { - throw new Exception("EntryItem is null"); - } - - Key entryKey = null; - PersistenceManager pm = PMF.get().getPersistenceManager(); - try { - entryKey = pm.makePersistent(entry).getKey(); - } finally { - pm.close(); - } - return entryKey; - } - - /** - * Gets a list of QueueEntries that are in range of the given location - */ - public static List getQueuesByLocation(float latitude, float longitude, long distance) { - PersistenceManager pm = PMF.get().getPersistenceManager(); - - float[] offsetLocation = QueueEntry.offsetLocationToRange(latitude, longitude, distance); - - float latitudeMin = offsetLocation[0]; - float longitudeMin = offsetLocation[1]; - float latitudeMax = offsetLocation[2]; - float longitudeMax = offsetLocation[3]; - - //log.severe("Latitude: " + latitude + " Min: " + latitudeMin + " Max: " + latitudeMax); - //log.severe("Longitude: " + longitude + " Min: " + longitudeMin + " Max: " + longitudeMax); - - // get queues that match the latitude range - Query latitudeQuery = pm.newQuery(QueueEntry.class); - latitudeQuery.setFilter("latitude < latitudeMax && latitude > latitudeMin"); - latitudeQuery.setOrdering("latitude ascending"); - latitudeQuery.declareParameters("float latitudeMin, float latitudeMax"); - - List latitudeResults = new ArrayList(); - try { - latitudeResults = (List) latitudeQuery.execute(latitudeMin, latitudeMax); - latitudeResults.size(); - for (QueueEntry entry : latitudeResults) { - log.info("Matching latitude items: " + entry.getName()); - } - log.info("Query execution returned " + latitudeResults.size() + " item(s)"); - } catch (Exception e) { - e.printStackTrace(); - } finally { - latitudeQuery.closeAll(); - } - - // get queues that match the longitude range - Query longitudeQuery = pm.newQuery(QueueEntry.class); - longitudeQuery.setFilter("longitude < longitudeMax && longitude > longitudeMin"); - longitudeQuery.setOrdering("longitude ascending"); - longitudeQuery.declareParameters("float longitudeMin, float longitudeMax"); - - List longitudeResults = new ArrayList(); - try { - longitudeResults = (List) longitudeQuery.execute(longitudeMin, longitudeMax); - longitudeResults.size(); - for (QueueEntry entry : longitudeResults) { - log.info("Matching longitude items: " + entry.getName()); - } - log.info("Query execution returned " + longitudeResults.size() + " item(s)"); - } catch (Exception e) { - e.printStackTrace(); - } finally { - longitudeQuery.closeAll(); - pm.close(); - } - - // merge latitude and longitude results into one list - List results = mergeQueueEntries(latitudeResults, longitudeResults); - return results; - } - - /** - * This method will get the parent business for each queue - * and group queues from the same business inside them - */ - public static ArrayList getBusinessesForQueues(List queueEntries) { - ArrayList results = new ArrayList(); - - // get all the business key IDs that are needed - List businessKeyIds = new ArrayList(); - for (QueueEntry queueEntry : queueEntries) { - if (!businessKeyIds.contains(queueEntry.getBusinessKeyId())) { - businessKeyIds.add(queueEntry.getBusinessKeyId()); - } - } - - // get all business entries for the needed key IDs - for (long businessKeyId : businessKeyIds) { - try { - BusinessEntry businessEntry = BusinessHelper.getEntryByKeyId(businessKeyId); - - // add all queues owned by this business - if (businessEntry != null) { - businessEntry.setQueues(filterQueuesByBusinessKeyId(queueEntries, businessKeyId)); - results.add(businessEntry); - } - } catch (Exception ex) { - ex.printStackTrace(); - } - } - - return results; - } - - /** - * This method will get the parent business for the given queue - */ - public static BusinessEntry getBusinessForQueue(QueueEntry queueEntry) { - BusinessEntry result = null; - - try { - BusinessEntry businessEntry = BusinessHelper.getEntryByKeyId(queueEntry.getBusinessKeyId()); - - // add the queue owned by this business - if (businessEntry != null) { - ArrayList queueEntries = new ArrayList(); - queueEntries.add(queueEntry); - businessEntry.setQueues(queueEntries); - result = businessEntry; - } - } catch (Exception ex) { - ex.printStackTrace(); - } - - return result; - } - - /** - * Returns only queues with a matching business key ID - * from a given list - */ - public static ArrayList filterQueuesByBusinessKeyId(List queueEntries, long businessKeyId) { - ArrayList results = new ArrayList(); - for (QueueEntry queueEntry : queueEntries) { - if (queueEntry.getBusinessKeyId() == businessKeyId) { - results.add(queueEntry); - } - } - return results; - } - - public static ArrayList getQueuesByPostalCode(String postalCode) { - PersistenceManager pm = PMF.get().getPersistenceManager(); - Query query = pm.newQuery(QueueEntry.class); - - query.setFilter("postalCode == value"); - query.declareParameters("String value"); - query.setRange(0, 1000); - - ArrayList results = new ArrayList(); - try { - List queryResults = (List) query.execute(postalCode); - for (QueueEntry entry : queryResults) { - results.add(entry); - } - //results = pm.detachCopy(queryResults); - } catch (Exception e) { - e.printStackTrace(); - } finally { - query.closeAll(); - pm.close(); - } - - return results; - } - - public static int getLastTicketNumberInQueue(long queueKeyId) { - return getLastTicketNumberInQueue(queueKeyId, (byte) QueueItemEntry.STATUS_ALL); - } - - public static int getLastTicketNumberInQueue(long queueKeyId, byte status) { - QueueItemEntry lastQueueItemEntry = getLastAssignedTicketInQueue(queueKeyId, status); - if (lastQueueItemEntry != null) { - return lastQueueItemEntry.getTicketNumber(); - } - return -1; - } - - public static List getItemsInQueue(long queueKeyId, int startIndex, int count) { - PersistenceManager pm = PMF.get().getPersistenceManager(); - Query query = pm.newQuery(QueueItemEntry.class); - - query.setFilter("queueKeyId == value"); - query.setOrdering("entryTimestamp descending"); - query.declareParameters("long value"); - query.setRange(startIndex, count); - - List results = new ArrayList(); - try { - results = (List) query.execute(queueKeyId); - results.size(); - for (QueueItemEntry entry : results) { - entry.getName(); - //log.info("Item found: " + entry.getName()); - } - //log.info("Query execution returned " + results.size() + " item(s)"); - } catch (Exception e) { - e.printStackTrace(); - } finally { - query.closeAll(); - pm.close(); - } - - return results; - } - - public static ArrayList getQueuesByBusiness(long businessKeyId) { - PersistenceManager pm = PMF.get().getPersistenceManager(); - Query query = pm.newQuery(QueueEntry.class); - - query.setFilter("businessKeyId == value"); - query.declareParameters("long value"); - query.setRange(0, 1000); - - ArrayList results = new ArrayList(); - try { - List queryResults = (List) query.execute(businessKeyId); - for (QueueEntry entry : queryResults) { - results.add(entry); - } - //results = pm.detachCopy(queryResults); - } catch (Exception e) { - e.printStackTrace(); - } finally { - query.closeAll(); - pm.close(); - } - - return results; - } - - public static void markAllQueueItemsAsDone(long queueKeyId) { - PersistenceManager pm = PMF.get().getPersistenceManager(); - Query query = pm.newQuery(QueueItemEntry.class); - - query.setFilter("queueKeyId == value && status == value2"); - query.declareParameters("long value, long value2"); - query.setRange(0, 10000); - - try { - List queryResults = (List) query.execute(queueKeyId, QueueItemEntry.STATUS_CALLED); - for (QueueItemEntry entry : queryResults) { - entry.setStatus(QueueItemEntry.STATUS_DONE); - } - } catch (Exception e) { - e.printStackTrace(); - } finally { - query.closeAll(); - pm.close(); - } - } - - public static int getNumberOfItemsInQueue(long queueKeyId, byte status) { - PersistenceManager pm = PMF.get().getPersistenceManager(); - Query query = pm.newQuery(QueueItemEntry.class); - - query.setFilter("queueKeyId == value && status == value2"); - query.declareParameters("long value, byte value2"); - query.setRange(0, 10000); - - int count = 0; - try { - List queryResults = (List) query.execute(queueKeyId, status); - count = queryResults.size(); - } catch (Exception e) { - e.printStackTrace(); - } finally { - query.closeAll(); - pm.close(); - } - - return count; - } - - public static QueueItemEntry getLastAssignedTicketInQueue(long queueKeyId, byte status) { - PersistenceManager pm = PMF.get().getPersistenceManager(); - Query query = pm.newQuery(QueueItemEntry.class); - - if (status > QueueItemEntry.STATUS_ALL) { - query.setFilter("queueKeyId == value && status == value2"); - query.declareParameters("long value, byte value2"); - } else { - query.setFilter("queueKeyId == value"); - query.declareParameters("long value"); - } - query.setOrdering("entryTimestamp descending"); - query.setRange(0, 5); - - List results = new ArrayList(); - try { - if (status > -1) { - results = (List) query.execute(queueKeyId, status); - } else { - results = (List) query.execute(queueKeyId); - } - results.size(); - //log.info("Query execution returned " + results.size() + " item(s)"); - } catch (Exception e) { - e.printStackTrace(); - } finally { - query.closeAll(); - pm.close(); - } - - // just in case some items have the same entry timestamp - // return the item with the highest ticket number - QueueItemEntry lastQueueItemEntry = null; - int highestTicketNumber = -1; - for (QueueItemEntry queueItemEntry : results) { - if (queueItemEntry.getTicketNumber() > highestTicketNumber) { - lastQueueItemEntry = queueItemEntry; - highestTicketNumber = lastQueueItemEntry.getTicketNumber(); - } - } - - return lastQueueItemEntry; - } - - public static QueueItemEntry getHighestTicketNumberInQueue(long queueKeyId) { - PersistenceManager pm = PMF.get().getPersistenceManager(); - Query query = pm.newQuery(QueueItemEntry.class); - query.setFilter("queueKeyId == '" + queueKeyId + "'"); - query.setOrdering("ticketNumber descending"); - query.setRange(0, 1); - - List results = new ArrayList(); - try { - results = (List) query.execute(); - //log.info("ActorQuery execution returned " + results.size() + " item(s)"); - } catch (Exception e) { - e.printStackTrace(); - } finally { - query.closeAll(); - pm.close(); - } - - if (results.size() > 0) { - return results.get(0); - } else { - return null; - } - } - - public static void deleteItemsInQueue(long queueKeyId, byte status) { - PersistenceManager pm = PMF.get().getPersistenceManager(); - Query query = pm.newQuery(QueueItemEntry.class); - - try { - if (status != QueueItemEntry.STATUS_ALL) { - query.setFilter("queueKeyId == value && status == value2"); - query.declareParameters("long value, byte value2"); - query.deletePersistentAll(queueKeyId, status); - } else { - query.setFilter("queueKeyId == value"); - query.declareParameters("long value"); - query.deletePersistentAll(queueKeyId); - } - } catch (Exception e) { - e.printStackTrace(); - } finally { - query.closeAll(); - pm.close(); - } - } - - public static void populateQueueWithSampleItems(long queueKeyId, int count) { - ArrayList dummyItems = new ArrayList(); - - for (int i = 0; i < count; i++) { - QueueItemEntry item = new QueueItemEntry(queueKeyId); - item.makeDummyItem(); - dummyItems.add(item); - } - - Collections.sort(dummyItems, new QueueItemEntry.EntryTimestampComparator()); - - String[] dummyNames = FakeData.getNames(count); - - int lastTicketNumber = -1; - for (int i = 0; i < count; i++) { - try { - if (lastTicketNumber < 0) { - lastTicketNumber = getLastTicketNumberInQueue(queueKeyId, QueueItemEntry.STATUS_ALL) + 1; - } - - lastTicketNumber += 1; - - QueueItemEntry item = dummyItems.get(i); - item.setTicketNumber(lastTicketNumber); - item.setName(dummyNames[i]); - QueueItemHelper.saveEntry(item); - } catch (Exception e) { - e.printStackTrace(); - } - } - } - - /** - * Returns a list of QueueEntries that are presenst in both given lists - */ - public static List mergeQueueEntries(List list1, List list2) { - List entries = new ArrayList(); - ArrayList keyIds = new ArrayList(); - - //log.severe("Merging lists with " + list1.size() + " and " + list2.size() + " items"); - - for (int i = 0; i < list1.size(); i++) { - keyIds.add(list1.get(i).getKey().getId()); - } - - for (int i = 0; i < list2.size(); i++) { - if (keyIds.contains(list2.get(i).getKey().getId())) { - entries.add(list2.get(i)); - } - } - - return entries; - } - - public static QueueEntry getEntryByKeyId(String idString) { - long id = Long.parseLong(idString); - return getEntryByKeyId(id); - } - - public static QueueEntry getEntryByKeyId(long id) { - PersistenceManager pm = PMF.get().getPersistenceManager(); - Key key = KeyFactory.createKey(QueueEntry.class.getSimpleName(), id); - QueueEntry entry = pm.getObjectById(QueueEntry.class, key); - pm.close(); - return entry; - } + private static final Logger log = Logger.getLogger(QueueHelper.class.getName()); + + public static Key saveEntry(QueueEntry entry) throws Exception { + if (entry == null) { + throw new Exception("EntryItem is null"); + } + + Key entryKey = null; + PersistenceManager pm = PMF.get().getPersistenceManager(); + try { + entryKey = pm.makePersistent(entry).getKey(); + } finally { + pm.close(); + } + return entryKey; + } + + /** + * Gets a list of QueueEntries that are in range of the given location + */ + public static List getQueuesByLocation(float latitude, float longitude, long distance) { + PersistenceManager pm = PMF.get().getPersistenceManager(); + + float[] offsetLocation = QueueEntry.offsetLocationToRange(latitude, longitude, distance); + + float latitudeMin = offsetLocation[0]; + float longitudeMin = offsetLocation[1]; + float latitudeMax = offsetLocation[2]; + float longitudeMax = offsetLocation[3]; + + //log.severe("Latitude: " + latitude + " Min: " + latitudeMin + " Max: " + latitudeMax); + //log.severe("Longitude: " + longitude + " Min: " + longitudeMin + " Max: " + longitudeMax); + + // get queues that match the latitude range + Query latitudeQuery = pm.newQuery(QueueEntry.class); + latitudeQuery.setFilter("latitude < latitudeMax && latitude > latitudeMin"); + latitudeQuery.setOrdering("latitude ascending"); + latitudeQuery.declareParameters("float latitudeMin, float latitudeMax"); + + List latitudeResults = new ArrayList(); + try { + latitudeResults = (List) latitudeQuery.execute(latitudeMin, latitudeMax); + latitudeResults.size(); + for (QueueEntry entry : latitudeResults) { + log.info("Matching latitude items: " + entry.getName()); + } + log.info("Query execution returned " + latitudeResults.size() + " item(s)"); + } catch (Exception e) { + e.printStackTrace(); + } finally { + latitudeQuery.closeAll(); + } + + // get queues that match the longitude range + Query longitudeQuery = pm.newQuery(QueueEntry.class); + longitudeQuery.setFilter("longitude < longitudeMax && longitude > longitudeMin"); + longitudeQuery.setOrdering("longitude ascending"); + longitudeQuery.declareParameters("float longitudeMin, float longitudeMax"); + + List longitudeResults = new ArrayList(); + try { + longitudeResults = (List) longitudeQuery.execute(longitudeMin, longitudeMax); + longitudeResults.size(); + for (QueueEntry entry : longitudeResults) { + log.info("Matching longitude items: " + entry.getName()); + } + log.info("Query execution returned " + longitudeResults.size() + " item(s)"); + } catch (Exception e) { + e.printStackTrace(); + } finally { + longitudeQuery.closeAll(); + pm.close(); + } + + // merge latitude and longitude results into one list + List results = mergeQueueEntries(latitudeResults, longitudeResults); + return results; + } + + /** + * This method will get the parent business for each queue + * and group queues from the same business inside them + */ + public static ArrayList getBusinessesForQueues(List queueEntries) { + ArrayList results = new ArrayList(); + + // get all the business key IDs that are needed + List businessKeyIds = new ArrayList(); + for (QueueEntry queueEntry : queueEntries) { + if (!businessKeyIds.contains(queueEntry.getBusinessKeyId())) { + businessKeyIds.add(queueEntry.getBusinessKeyId()); + } + } + + // get all business entries for the needed key IDs + for (long businessKeyId : businessKeyIds) { + try { + BusinessEntry businessEntry = BusinessHelper.getEntryByKeyId(businessKeyId); + + // add all queues owned by this business + if (businessEntry != null) { + businessEntry.setQueues(filterQueuesByBusinessKeyId(queueEntries, businessKeyId)); + results.add(businessEntry); + } + } catch (Exception ex) { + ex.printStackTrace(); + } + } + + return results; + } + + /** + * This method will get the parent business for the given queue + */ + public static BusinessEntry getBusinessForQueue(QueueEntry queueEntry) { + BusinessEntry result = null; + + try { + BusinessEntry businessEntry = BusinessHelper.getEntryByKeyId(queueEntry.getBusinessKeyId()); + + // add the queue owned by this business + if (businessEntry != null) { + ArrayList queueEntries = new ArrayList(); + queueEntries.add(queueEntry); + businessEntry.setQueues(queueEntries); + result = businessEntry; + } + } catch (Exception ex) { + ex.printStackTrace(); + } + + return result; + } + + /** + * Returns only queues with a matching business key ID + * from a given list + */ + public static ArrayList filterQueuesByBusinessKeyId(List queueEntries, long businessKeyId) { + ArrayList results = new ArrayList(); + for (QueueEntry queueEntry : queueEntries) { + if (queueEntry.getBusinessKeyId() == businessKeyId) { + results.add(queueEntry); + } + } + return results; + } + + public static ArrayList getQueuesByPostalCode(String postalCode) { + PersistenceManager pm = PMF.get().getPersistenceManager(); + Query query = pm.newQuery(QueueEntry.class); + + query.setFilter("postalCode == value"); + query.declareParameters("String value"); + query.setRange(0, 1000); + + ArrayList results = new ArrayList(); + try { + List queryResults = (List) query.execute(postalCode); + for (QueueEntry entry : queryResults) { + results.add(entry); + } + //results = pm.detachCopy(queryResults); + } catch (Exception e) { + e.printStackTrace(); + } finally { + query.closeAll(); + pm.close(); + } + + return results; + } + + public static int getLastTicketNumberInQueue(long queueKeyId) { + return getLastTicketNumberInQueue(queueKeyId, (byte) QueueItemEntry.STATUS_ALL); + } + + public static int getLastTicketNumberInQueue(long queueKeyId, byte status) { + QueueItemEntry lastQueueItemEntry = getLastAssignedTicketInQueue(queueKeyId, status); + if (lastQueueItemEntry != null) { + return lastQueueItemEntry.getTicketNumber(); + } + return -1; + } + + public static List getItemsInQueue(long queueKeyId, int startIndex, int count) { + PersistenceManager pm = PMF.get().getPersistenceManager(); + Query query = pm.newQuery(QueueItemEntry.class); + + query.setFilter("queueKeyId == value"); + query.setOrdering("entryTimestamp descending"); + query.declareParameters("long value"); + query.setRange(startIndex, count); + query.setOrdering("ticketNumber ascending"); + + List results = new ArrayList(); + try { + results = (List) query.execute(queueKeyId); + results.size(); + for (QueueItemEntry entry : results) { + entry.getName(); + //log.info("Item found: " + entry.getName()); + } + //log.info("Query execution returned " + results.size() + " item(s)"); + } catch (Exception e) { + e.printStackTrace(); + } finally { + query.closeAll(); + pm.close(); + } + + return results; + } + + public static ArrayList getQueuesByBusiness(long businessKeyId) { + PersistenceManager pm = PMF.get().getPersistenceManager(); + Query query = pm.newQuery(QueueEntry.class); + + query.setFilter("businessKeyId == value"); + query.declareParameters("long value"); + query.setRange(0, 1000); + + ArrayList results = new ArrayList(); + try { + List queryResults = (List) query.execute(businessKeyId); + for (QueueEntry entry : queryResults) { + results.add(entry); + } + //results = pm.detachCopy(queryResults); + } catch (Exception e) { + e.printStackTrace(); + } finally { + query.closeAll(); + pm.close(); + } + + return results; + } + + public static void markAllQueueItemsAsDone(long queueKeyId) { + PersistenceManager pm = PMF.get().getPersistenceManager(); + Query query = pm.newQuery(QueueItemEntry.class); + + query.setFilter("queueKeyId == value && status == value2"); + query.declareParameters("long value, long value2"); + query.setRange(0, 10000); + + try { + List queryResults = (List) query.execute(queueKeyId, QueueItemEntry.STATUS_CALLED); + for (QueueItemEntry entry : queryResults) { + entry.setStatus(QueueItemEntry.STATUS_DONE); + } + } catch (Exception e) { + e.printStackTrace(); + } finally { + query.closeAll(); + pm.close(); + } + } + + public static int getNumberOfItemsInQueue(long queueKeyId, byte status) { + PersistenceManager pm = PMF.get().getPersistenceManager(); + Query query = pm.newQuery(QueueItemEntry.class); + + query.setFilter("queueKeyId == value && status == value2"); + query.declareParameters("long value, byte value2"); + query.setRange(0, 10000); + + int count = 0; + try { + List queryResults = (List) query.execute(queueKeyId, status); + count = queryResults.size(); + } catch (Exception e) { + e.printStackTrace(); + } finally { + query.closeAll(); + pm.close(); + } + + return count; + } + + public static QueueItemEntry getLastAssignedTicketInQueue(long queueKeyId, byte status) { + PersistenceManager pm = PMF.get().getPersistenceManager(); + Query query = pm.newQuery(QueueItemEntry.class); + + if (status > QueueItemEntry.STATUS_ALL) { + query.setFilter("queueKeyId == value && status == value2"); + query.declareParameters("long value, byte value2"); + } else { + query.setFilter("queueKeyId == value"); + query.declareParameters("long value"); + } + query.setOrdering("entryTimestamp descending"); + query.setRange(0, 5); + + List results = new ArrayList(); + try { + if (status > -1) { + results = (List) query.execute(queueKeyId, status); + } else { + results = (List) query.execute(queueKeyId); + } + results.size(); + //log.info("Query execution returned " + results.size() + " item(s)"); + } catch (Exception e) { + e.printStackTrace(); + } finally { + query.closeAll(); + pm.close(); + } + + // just in case some items have the same entry timestamp + // return the item with the highest ticket number + QueueItemEntry lastQueueItemEntry = null; + int highestTicketNumber = -1; + for (QueueItemEntry queueItemEntry : results) { + if (queueItemEntry.getTicketNumber() > highestTicketNumber) { + lastQueueItemEntry = queueItemEntry; + highestTicketNumber = lastQueueItemEntry.getTicketNumber(); + } + } + + return lastQueueItemEntry; + } + + public static QueueItemEntry getHighestTicketNumberInQueue(long queueKeyId) { + PersistenceManager pm = PMF.get().getPersistenceManager(); + Query query = pm.newQuery(QueueItemEntry.class); + query.setFilter("queueKeyId == '" + queueKeyId + "'"); + query.setOrdering("ticketNumber descending"); + query.setRange(0, 1); + + List results = new ArrayList(); + try { + results = (List) query.execute(); + //log.info("ActorQuery execution returned " + results.size() + " item(s)"); + } catch (Exception e) { + e.printStackTrace(); + } finally { + query.closeAll(); + pm.close(); + } + + if (results.size() > 0) { + return results.get(0); + } else { + return null; + } + } + + public static void deleteItemsInQueue(long queueKeyId, byte status) { + PersistenceManager pm = PMF.get().getPersistenceManager(); + Query query = pm.newQuery(QueueItemEntry.class); + + try { + if (status != QueueItemEntry.STATUS_ALL) { + query.setFilter("queueKeyId == value && status == value2"); + query.declareParameters("long value, byte value2"); + query.deletePersistentAll(queueKeyId, status); + } else { + query.setFilter("queueKeyId == value"); + query.declareParameters("long value"); + query.deletePersistentAll(queueKeyId); + } + } catch (Exception e) { + e.printStackTrace(); + } finally { + query.closeAll(); + pm.close(); + } + } + + public static void populateQueueWithSampleItems(long queueKeyId, int count) { + ArrayList dummyItems = new ArrayList(); + + for (int i = 0; i < count; i++) { + QueueItemEntry item = new QueueItemEntry(queueKeyId); + item.makeDummyItem(); + dummyItems.add(item); + } + + Collections.sort(dummyItems, new QueueItemEntry.EntryTimestampComparator()); + + String[] dummyNames = FakeData.getNames(count); + + int lastTicketNumber = -1; + for (int i = 0; i < count; i++) { + try { + if (lastTicketNumber < 0) { + lastTicketNumber = getLastTicketNumberInQueue(queueKeyId, QueueItemEntry.STATUS_ALL) + 1; + } + + lastTicketNumber += 1; + + QueueItemEntry item = dummyItems.get(i); + item.setTicketNumber(lastTicketNumber); + item.setName(dummyNames[i]); + QueueItemHelper.saveEntry(item); + } catch (Exception e) { + e.printStackTrace(); + } + } + } + + /** + * Returns a list of QueueEntries that are presenst in both given lists + */ + public static List mergeQueueEntries(List list1, List list2) { + List entries = new ArrayList(); + ArrayList keyIds = new ArrayList(); + + //log.severe("Merging lists with " + list1.size() + " and " + list2.size() + " items"); + + for (int i = 0; i < list1.size(); i++) { + keyIds.add(list1.get(i).getKey().getId()); + } + + for (int i = 0; i < list2.size(); i++) { + if (keyIds.contains(list2.get(i).getKey().getId())) { + entries.add(list2.get(i)); + } + } + + return entries; + } + + public static QueueEntry getEntryByKeyId(String idString) { + long id = Long.parseLong(idString); + return getEntryByKeyId(id); + } + + public static QueueEntry getEntryByKeyId(long id) { + PersistenceManager pm = PMF.get().getPersistenceManager(); + Key key = KeyFactory.createKey(QueueEntry.class.getSimpleName(), id); + QueueEntry entry = pm.getObjectById(QueueEntry.class, key); + pm.close(); + return entry; + } } diff --git a/IntelliQ/backend/src/main/java/com/intelliq/appengine/datastore/QueueItemHelper.java b/IntelliQ/backend/src/main/java/com/intelliq/appengine/datastore/QueueItemHelper.java index 810dc9e..efb6719 100644 --- a/IntelliQ/backend/src/main/java/com/intelliq/appengine/datastore/QueueItemHelper.java +++ b/IntelliQ/backend/src/main/java/com/intelliq/appengine/datastore/QueueItemHelper.java @@ -14,96 +14,96 @@ public class QueueItemHelper { - private static final Logger log = Logger.getLogger(QueueItemHelper.class.getName()); - - public static Key saveEntry(QueueItemEntry entry) throws Exception { - if (entry == null) { - throw new Exception("EntryItem is null"); - } - - Key entryKey = null; - PersistenceManager pm = PMF.get().getPersistenceManager(); - try { - entryKey = pm.makePersistent(entry).getKey(); - } finally { - pm.close(); - } - return entryKey; - } - - public static QueueItemEntry getEntryByKeyId(String idString) { - long id = Long.parseLong(idString); - return getEntryByKeyId(id); - } - - public static QueueItemEntry getEntryByKeyId(long id) { - PersistenceManager pm = PMF.get().getPersistenceManager(); - Key key = KeyFactory.createKey(QueueItemEntry.class.getSimpleName(), id); - QueueItemEntry entry = pm.getObjectById(QueueItemEntry.class, key); - pm.close(); - return entry; - } - - public static void deleteEntryByKeyId(long id) { - PersistenceManager pm = PMF.get().getPersistenceManager(); - Key key = KeyFactory.createKey(QueueItemEntry.class.getSimpleName(), id); - QueueItemEntry entry = pm.getObjectById(QueueItemEntry.class, key); - pm.deletePersistent(entry); - pm.close(); - } - - public static List getQueueItemsByUserKeyId(long userKeyId) { - PersistenceManager pm = PMF.get().getPersistenceManager(); - Query query = pm.newQuery(QueueItemEntry.class); - - query.setFilter("userKeyId == value"); - query.declareParameters("long value"); - query.setRange(0, 25); - - ArrayList results = new ArrayList(); - try { - List queryResults = (List) query.execute(userKeyId); - for (QueueItemEntry entry : queryResults) { - results.add(entry); - } - //results = pm.detachCopy(queryResults); - } catch (Exception e) { - e.printStackTrace(); - } finally { - query.closeAll(); - pm.close(); - } - - return results; - } - - public static QueueItemEntry getQueueItemByUserKeyId(long userKeyId, long queueKeyId) { - PersistenceManager pm = PMF.get().getPersistenceManager(); - Query query = pm.newQuery(QueueItemEntry.class); - - query.setFilter("userKeyId == value && queueKeyId == value2"); - query.declareParameters("long value, long value2"); - query.setRange(0, 25); - - ArrayList results = new ArrayList(); - try { - List queryResults = (List) query.execute(userKeyId, queueKeyId); - for (QueueItemEntry entry : queryResults) { - results.add(entry); - } - //results = pm.detachCopy(queryResults); - } catch (Exception e) { - e.printStackTrace(); - } finally { - query.closeAll(); - pm.close(); - } - - if (results.size() > 0) { - return results.get(0); - } else { - return null; - } - } + private static final Logger log = Logger.getLogger(QueueItemHelper.class.getName()); + + public static Key saveEntry(QueueItemEntry entry) throws Exception { + if (entry == null) { + throw new Exception("EntryItem is null"); + } + + Key entryKey = null; + PersistenceManager pm = PMF.get().getPersistenceManager(); + try { + entryKey = pm.makePersistent(entry).getKey(); + } finally { + pm.close(); + } + return entryKey; + } + + public static QueueItemEntry getEntryByKeyId(String idString) { + long id = Long.parseLong(idString); + return getEntryByKeyId(id); + } + + public static QueueItemEntry getEntryByKeyId(long id) { + PersistenceManager pm = PMF.get().getPersistenceManager(); + Key key = KeyFactory.createKey(QueueItemEntry.class.getSimpleName(), id); + QueueItemEntry entry = pm.getObjectById(QueueItemEntry.class, key); + pm.close(); + return entry; + } + + public static void deleteEntryByKeyId(long id) { + PersistenceManager pm = PMF.get().getPersistenceManager(); + Key key = KeyFactory.createKey(QueueItemEntry.class.getSimpleName(), id); + QueueItemEntry entry = pm.getObjectById(QueueItemEntry.class, key); + pm.deletePersistent(entry); + pm.close(); + } + + public static List getQueueItemsByUserKeyId(long userKeyId) { + PersistenceManager pm = PMF.get().getPersistenceManager(); + Query query = pm.newQuery(QueueItemEntry.class); + + query.setFilter("userKeyId == value"); + query.declareParameters("long value"); + query.setRange(0, 25); + + ArrayList results = new ArrayList(); + try { + List queryResults = (List) query.execute(userKeyId); + for (QueueItemEntry entry : queryResults) { + results.add(entry); + } + //results = pm.detachCopy(queryResults); + } catch (Exception e) { + e.printStackTrace(); + } finally { + query.closeAll(); + pm.close(); + } + + return results; + } + + public static QueueItemEntry getQueueItemByUserKeyId(long userKeyId, long queueKeyId) { + PersistenceManager pm = PMF.get().getPersistenceManager(); + Query query = pm.newQuery(QueueItemEntry.class); + + query.setFilter("userKeyId == value && queueKeyId == value2"); + query.declareParameters("long value, long value2"); + query.setRange(0, 25); + + ArrayList results = new ArrayList(); + try { + List queryResults = (List) query.execute(userKeyId, queueKeyId); + for (QueueItemEntry entry : queryResults) { + results.add(entry); + } + //results = pm.detachCopy(queryResults); + } catch (Exception e) { + e.printStackTrace(); + } finally { + query.closeAll(); + pm.close(); + } + + if (results.size() > 0) { + return results.get(0); + } else { + return null; + } + } } diff --git a/IntelliQ/backend/src/main/java/com/intelliq/appengine/datastore/UserHelper.java b/IntelliQ/backend/src/main/java/com/intelliq/appengine/datastore/UserHelper.java index 1dbc74c..448e15b 100644 --- a/IntelliQ/backend/src/main/java/com/intelliq/appengine/datastore/UserHelper.java +++ b/IntelliQ/backend/src/main/java/com/intelliq/appengine/datastore/UserHelper.java @@ -18,101 +18,101 @@ public class UserHelper { - private static final Logger log = Logger.getLogger(UserHelper.class.getName()); - - public static Key saveEntry(UserEntry entry) throws Exception { - if (entry == null) { - throw new Exception("EntryItem is null"); - } - - Key entryKey = null; - PersistenceManager pm = PMF.get().getPersistenceManager(); - try { - entryKey = pm.makePersistent(entry).getKey(); - } finally { - pm.close(); - } - return entryKey; - } - - public static UserEntry getUserByGoogleUserId(String googleUserId) { - PersistenceManager pm = PMF.get().getPersistenceManager(); - Query query = pm.newQuery(UserEntry.class); - - query.setFilter("googleUserId == value"); - query.declareParameters("String value"); - query.setRange(0, 10); - - ArrayList results = new ArrayList(); - try { - List queryResults = (List) query.execute(googleUserId); - for (UserEntry entry : queryResults) { - // touch child entities - if (entry.getStats() != null) { - entry.getStats().getLastSignIn(); - } else { - entry.setStats(new UserStatsEntry()); - } - results.add(entry); - } - //results = pm.detachCopy(queryResults); - } catch (Exception e) { - e.printStackTrace(); - } finally { - query.closeAll(); - pm.close(); - } - - if (results.size() > 0) { - return results.get(0); - } else { - return null; - } - } - - public static UserEntry getUserByFacebookUserId(String googleUserId) { - PersistenceManager pm = PMF.get().getPersistenceManager(); - Query query = pm.newQuery(UserEntry.class); - - query.setFilter("facebookUserId == value"); - query.declareParameters("String value"); - query.setRange(0, 10); - - ArrayList results = new ArrayList(); - try { - List queryResults = (List) query.execute(googleUserId); - for (UserEntry entry : queryResults) { - // touch child entities - entry.getStats(); - results.add(entry); - } - //results = pm.detachCopy(queryResults); - } catch (Exception e) { - e.printStackTrace(); - } finally { - query.closeAll(); - pm.close(); - } - - if (results.size() > 0) { - return results.get(0); - } else { - return null; - } - } - - public static UserEntry getEntryByKeyId(String idString) { - long id = Long.parseLong(idString); - return getEntryByKeyId(id); - } - - public static UserEntry getEntryByKeyId(long id) throws NucleusObjectNotFoundException { - PersistenceManager pm = PMF.get().getPersistenceManager(); - Key key = KeyFactory.createKey(UserEntry.class.getSimpleName(), id); - UserEntry entry = pm.getObjectById(UserEntry.class, key); - // touch child entities - entry.getStats(); - pm.close(); - return entry; - } + private static final Logger log = Logger.getLogger(UserHelper.class.getName()); + + public static Key saveEntry(UserEntry entry) throws Exception { + if (entry == null) { + throw new Exception("EntryItem is null"); + } + + Key entryKey = null; + PersistenceManager pm = PMF.get().getPersistenceManager(); + try { + entryKey = pm.makePersistent(entry).getKey(); + } finally { + pm.close(); + } + return entryKey; + } + + public static UserEntry getUserByGoogleUserId(String googleUserId) { + PersistenceManager pm = PMF.get().getPersistenceManager(); + Query query = pm.newQuery(UserEntry.class); + + query.setFilter("googleUserId == value"); + query.declareParameters("String value"); + query.setRange(0, 10); + + ArrayList results = new ArrayList(); + try { + List queryResults = (List) query.execute(googleUserId); + for (UserEntry entry : queryResults) { + // touch child entities + if (entry.getStats() != null) { + entry.getStats().getLastSignIn(); + } else { + entry.setStats(new UserStatsEntry()); + } + results.add(entry); + } + //results = pm.detachCopy(queryResults); + } catch (Exception e) { + e.printStackTrace(); + } finally { + query.closeAll(); + pm.close(); + } + + if (results.size() > 0) { + return results.get(0); + } else { + return null; + } + } + + public static UserEntry getUserByFacebookUserId(String googleUserId) { + PersistenceManager pm = PMF.get().getPersistenceManager(); + Query query = pm.newQuery(UserEntry.class); + + query.setFilter("facebookUserId == value"); + query.declareParameters("String value"); + query.setRange(0, 10); + + ArrayList results = new ArrayList(); + try { + List queryResults = (List) query.execute(googleUserId); + for (UserEntry entry : queryResults) { + // touch child entities + entry.getStats(); + results.add(entry); + } + //results = pm.detachCopy(queryResults); + } catch (Exception e) { + e.printStackTrace(); + } finally { + query.closeAll(); + pm.close(); + } + + if (results.size() > 0) { + return results.get(0); + } else { + return null; + } + } + + public static UserEntry getEntryByKeyId(String idString) { + long id = Long.parseLong(idString); + return getEntryByKeyId(id); + } + + public static UserEntry getEntryByKeyId(long id) throws NucleusObjectNotFoundException { + PersistenceManager pm = PMF.get().getPersistenceManager(); + Key key = KeyFactory.createKey(UserEntry.class.getSimpleName(), id); + UserEntry entry = pm.getObjectById(UserEntry.class, key); + // touch child entities + entry.getStats(); + pm.close(); + return entry; + } } diff --git a/IntelliQ/backend/src/main/java/com/intelliq/appengine/datastore/entries/BusinessEntry.java b/IntelliQ/backend/src/main/java/com/intelliq/appengine/datastore/entries/BusinessEntry.java index 972fb03..336ac00 100644 --- a/IntelliQ/backend/src/main/java/com/intelliq/appengine/datastore/entries/BusinessEntry.java +++ b/IntelliQ/backend/src/main/java/com/intelliq/appengine/datastore/entries/BusinessEntry.java @@ -14,73 +14,73 @@ @PersistenceCapable(detachable = "true") public class BusinessEntry { - @PrimaryKey - @Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY) - Key key; - - @Persistent - String name; - - @Persistent - String mail; - - @Persistent - long logoImageKeyId; - - ArrayList queues; - - public BusinessEntry() { - logoImageKeyId = -1; - } - - public void parseFromRequest(ApiRequest req) { - name = req.getParameter("name", name); - mail = req.getParameter("mail", mail); - logoImageKeyId = req.getParameterAsLong("logoImageKeyId", logoImageKeyId); - } - - public Key getKey() { - return key; - } - - public void setKey(Key key) { - this.key = key; - } - - public Key generateKey() { - return KeyFactory.createKey(BusinessEntry.class.getSimpleName(), name); - } - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public String getMail() { - return mail; - } - - public void setMail(String email) { - this.mail = email; - } - - public long getLogoImageKeyId() { - return logoImageKeyId; - } - - public void setLogoImageKeyId(long logoImageKeyId) { - this.logoImageKeyId = logoImageKeyId; - } - - public ArrayList getQueues() { - return queues; - } - - public void setQueues(ArrayList queues) { - this.queues = queues; - } - + @PrimaryKey + @Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY) + Key key; + + @Persistent + String name; + + @Persistent + String mail; + + @Persistent + long logoImageKeyId; + + ArrayList queues; + + public BusinessEntry() { + logoImageKeyId = -1; + } + + public void parseFromRequest(ApiRequest req) { + name = req.getParameter("name", name); + mail = req.getParameter("mail", mail); + logoImageKeyId = req.getParameterAsLong("logoImageKeyId", logoImageKeyId); + } + + public Key getKey() { + return key; + } + + public void setKey(Key key) { + this.key = key; + } + + public Key generateKey() { + return KeyFactory.createKey(BusinessEntry.class.getSimpleName(), name); + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getMail() { + return mail; + } + + public void setMail(String email) { + this.mail = email; + } + + public long getLogoImageKeyId() { + return logoImageKeyId; + } + + public void setLogoImageKeyId(long logoImageKeyId) { + this.logoImageKeyId = logoImageKeyId; + } + + public ArrayList getQueues() { + return queues; + } + + public void setQueues(ArrayList queues) { + this.queues = queues; + } + } diff --git a/IntelliQ/backend/src/main/java/com/intelliq/appengine/datastore/entries/ImageEntry.java b/IntelliQ/backend/src/main/java/com/intelliq/appengine/datastore/entries/ImageEntry.java index 9810973..47e31ce 100644 --- a/IntelliQ/backend/src/main/java/com/intelliq/appengine/datastore/entries/ImageEntry.java +++ b/IntelliQ/backend/src/main/java/com/intelliq/appengine/datastore/entries/ImageEntry.java @@ -13,83 +13,83 @@ @PersistenceCapable(detachable = "true") public class ImageEntry { - public static final int TYPE_LOGO = 0; - public static final int TYPE_PHOTO = 1; + public static final int TYPE_LOGO = 0; + public static final int TYPE_PHOTO = 1; - public static final String SIZE_ORIGINAL = "original"; + public static final String SIZE_ORIGINAL = "original"; - @PrimaryKey - @Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY) - Key key; + @PrimaryKey + @Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY) + Key key; - @Persistent - long parentKeyId; + @Persistent + long parentKeyId; - @Persistent - int type = TYPE_LOGO; - - @Persistent - @Extension(vendorName="datanucleus", key="gae.unindexed", value="true") + @Persistent + int type = TYPE_LOGO; + + @Persistent + @Extension(vendorName = "datanucleus", key = "gae.unindexed", value = "true") private String imageType; - - @Persistent - private Blob image; - - public ImageEntry() { - - } - - public Key getKey() { - return key; - } - - public void setKey(Key key) { - this.key = key; - } - - public Key generateKey() { - String keyName = String.valueOf(parentKeyId); - if (type == TYPE_LOGO) { - keyName += "_logo"; - } else if (type == TYPE_PHOTO) { - keyName += "_photo"; - } - return KeyFactory.createKey(ImageEntry.class.getSimpleName(), keyName); - } - - public int getType() { - return type; - } - - public void setType(int type) { - this.type = type; - } - - public long getParentKeyId() { - return parentKeyId; - } - - public void setParentKeyId(long parentKeyId) { - this.parentKeyId = parentKeyId; - } - - public String getImageType() { - return imageType; - } - - public void setImageType(String imageType) { - this.imageType = imageType; - } - - public byte[] getImage() { - if (image == null) { - return null; - } - return image.getBytes(); - } - - public void setImage(byte[] bytes) { - this.image = new Blob(bytes); - } + + @Persistent + private Blob image; + + public ImageEntry() { + + } + + public Key getKey() { + return key; + } + + public void setKey(Key key) { + this.key = key; + } + + public Key generateKey() { + String keyName = String.valueOf(parentKeyId); + if (type == TYPE_LOGO) { + keyName += "_logo"; + } else if (type == TYPE_PHOTO) { + keyName += "_photo"; + } + return KeyFactory.createKey(ImageEntry.class.getSimpleName(), keyName); + } + + public int getType() { + return type; + } + + public void setType(int type) { + this.type = type; + } + + public long getParentKeyId() { + return parentKeyId; + } + + public void setParentKeyId(long parentKeyId) { + this.parentKeyId = parentKeyId; + } + + public String getImageType() { + return imageType; + } + + public void setImageType(String imageType) { + this.imageType = imageType; + } + + public byte[] getImage() { + if (image == null) { + return null; + } + return image.getBytes(); + } + + public void setImage(byte[] bytes) { + this.image = new Blob(bytes); + } } diff --git a/IntelliQ/backend/src/main/java/com/intelliq/appengine/datastore/entries/PermissionEntry.java b/IntelliQ/backend/src/main/java/com/intelliq/appengine/datastore/entries/PermissionEntry.java index 20ce822..f9a7144 100644 --- a/IntelliQ/backend/src/main/java/com/intelliq/appengine/datastore/entries/PermissionEntry.java +++ b/IntelliQ/backend/src/main/java/com/intelliq/appengine/datastore/entries/PermissionEntry.java @@ -11,91 +11,91 @@ @PersistenceCapable(detachable = "true") public class PermissionEntry { - public static final int PERMISSION_NONE = -1; - public static final int PERMISSION_VIEW = 0; - public static final int PERMISSION_EDIT = 1; - public static final int PERMISSION_OWN = 2; - - @PrimaryKey - @Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY) - Key key; - - @Persistent - long userKeyId; - - @Persistent - long subjectKeyId; - - @Persistent - String subjectKind; - - @Persistent - int permission; - - public PermissionEntry() { - super(); - this.permission = PERMISSION_NONE; - } - - public PermissionEntry(long userKeyId, long subjectKeyId, String subjectKind, int permission) { - super(); - this.userKeyId = userKeyId; - this.subjectKeyId = subjectKeyId; - this.subjectKind = subjectKind; - this.permission = permission; - } - - public boolean matches(long userKeyId, long subjectKeyId, int permission) { - if (this.userKeyId != userKeyId || this.subjectKeyId != subjectKeyId) { - return false; - } - - // required permission level is higher than the granted one - if (this.permission < permission) { - return false; - } - - return true; - } - - public Key getKey() { - return key; - } - - public void setKey(Key key) { - this.key = key; - } - - public long getUserKeyId() { - return userKeyId; - } - - public void setUserKeyId(long userKeyId) { - this.userKeyId = userKeyId; - } - - public long getSubjectKeyId() { - return subjectKeyId; - } - - public void setSubjectKeyId(long subbjectKeyId) { - this.subjectKeyId = subbjectKeyId; - } - - public int getPermission() { - return permission; - } - - public void setPermission(int permission) { - this.permission = permission; - } - - public String getSubjectKind() { - return subjectKind; - } - - public void setSubjectKind(String subjectKind) { - this.subjectKind = subjectKind; - } - + public static final int PERMISSION_NONE = -1; + public static final int PERMISSION_VIEW = 0; + public static final int PERMISSION_EDIT = 1; + public static final int PERMISSION_OWN = 2; + + @PrimaryKey + @Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY) + Key key; + + @Persistent + long userKeyId; + + @Persistent + long subjectKeyId; + + @Persistent + String subjectKind; + + @Persistent + int permission; + + public PermissionEntry() { + super(); + this.permission = PERMISSION_NONE; + } + + public PermissionEntry(long userKeyId, long subjectKeyId, String subjectKind, int permission) { + super(); + this.userKeyId = userKeyId; + this.subjectKeyId = subjectKeyId; + this.subjectKind = subjectKind; + this.permission = permission; + } + + public boolean matches(long userKeyId, long subjectKeyId, int permission) { + if (this.userKeyId != userKeyId || this.subjectKeyId != subjectKeyId) { + return false; + } + + // required permission level is higher than the granted one + if (this.permission < permission) { + return false; + } + + return true; + } + + public Key getKey() { + return key; + } + + public void setKey(Key key) { + this.key = key; + } + + public long getUserKeyId() { + return userKeyId; + } + + public void setUserKeyId(long userKeyId) { + this.userKeyId = userKeyId; + } + + public long getSubjectKeyId() { + return subjectKeyId; + } + + public void setSubjectKeyId(long subbjectKeyId) { + this.subjectKeyId = subbjectKeyId; + } + + public int getPermission() { + return permission; + } + + public void setPermission(int permission) { + this.permission = permission; + } + + public String getSubjectKind() { + return subjectKind; + } + + public void setSubjectKind(String subjectKind) { + this.subjectKind = subjectKind; + } + } diff --git a/IntelliQ/backend/src/main/java/com/intelliq/appengine/datastore/entries/QueueEntry.java b/IntelliQ/backend/src/main/java/com/intelliq/appengine/datastore/entries/QueueEntry.java index 8a43064..1959d64 100644 --- a/IntelliQ/backend/src/main/java/com/intelliq/appengine/datastore/entries/QueueEntry.java +++ b/IntelliQ/backend/src/main/java/com/intelliq/appengine/datastore/entries/QueueEntry.java @@ -17,255 +17,262 @@ @PersistenceCapable(detachable = "true") public class QueueEntry { - public static final byte VISIBILITY_PRIVATE = 0; - public static final byte VISIBILITY_PUBLIC = 1; - - @PrimaryKey - @Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY) - Key key; - - @Persistent - long businessKeyId; - - @Persistent - String name; - - @Persistent - String description; - - @Persistent - byte visibility; - - @Persistent - long photoImageKeyId; - - @Persistent - long averageWaitingTime; - - @Persistent - boolean requiresSignIn; - - int waitingPeople; - - /** - * Location - */ - @Persistent float latitude; - @Persistent float longitude; - - @Persistent String country; - @Persistent String city; - @Persistent String postalCode; - @Persistent String street; - @Persistent String number; - - public QueueEntry(long businessKeyId) { - this.businessKeyId = businessKeyId; - visibility = VISIBILITY_PUBLIC; - photoImageKeyId = -1; - waitingPeople = -1; - latitude = -1; - longitude = -1; - requiresSignIn = false; - averageWaitingTime = TimeUnit.MINUTES.toMillis(5); - } - - public void parseFromRequest(ApiRequest req) { - businessKeyId = req.getParameterAsLong("businessKeyId", businessKeyId); - name = req.getParameter("name", name); - description = req.getParameter("description", description); - visibility = (byte) req.getParameterAsInt("visibility", visibility); - averageWaitingTime = req.getParameterAsLong("averageWaitingTime", averageWaitingTime); - country = req.getParameter("country", country); - city = req.getParameter("city", city); - postalCode = req.getParameter("postalCode", postalCode); - street = req.getParameter("street", street); - number = req.getParameter("number", number); - latitude = req.getParameterAsFloat("latitude", latitude); - longitude = req.getParameterAsFloat("longitude", longitude); - requiresSignIn = req.getParameterAsBoolean("requiresSignIn", requiresSignIn); - } - - /* - * Methods for calculating the distance between two locations in meters - */ - public boolean hasValidLocation() { - if (latitude == -1 && longitude == -1) { - return false; - } - return true; - } - - public float getDistanceTo(float latitude, float longitude) { - return getDistance(this, latitude, longitude); - } - - public static float getDistance(QueueEntry queue, float latitude, float longitude) { - if (queue.latitude == -1 || queue.longitude == -1 || latitude == -1 || longitude == -1) { - return -1; - } else { - return getDistance(queue.latitude, queue.longitude, latitude, longitude); - } - } - - public static float getDistance(float lat1, float lng1, float lat2, float lng2) { - double earthRadius = 6371000; - double dLat = Math.toRadians(lat2 - lat1); - double dLng = Math.toRadians(lng2 - lng1); - double a = Math.sin(dLat / 2) * Math.sin(dLat / 2) + Math.cos(Math.toRadians(lat1)) * Math.cos(Math.toRadians(lat2)) * Math.sin(dLng / 2) * Math.sin(dLng / 2); - double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); - float dist = (float) (earthRadius * c); - - return dist; - } - - public float[] offsetLocationToRange(long distance) { - return offsetLocationToRange(latitude, longitude, distance); - } - - public static float[] offsetLocationToRange(float latitude, float longitude, long distance) { - float latitudeDelta = Math.abs((float) (distance / 1000 / 111.111)); - float longitudeDelta = Math.abs((float) (distance / 1000 / (111.111 * Math.cos(latitude)))); - - float latitudeMin = latitude - latitudeDelta; - float latitudeMax = latitude + latitudeDelta; - float longitudeMin = longitude - longitudeDelta; - float longitudeMax = longitude + longitudeDelta; - - return new float[] {latitudeMin, longitudeMin, latitudeMax, longitudeMax}; - } - - public Key getKey() { - return key; - } - - public void setKey(Key key) { - this.key = key; - } - - public Key generateKey() { - return KeyFactory.createKey(QueueEntry.class.getSimpleName(), name); - } - - public long getBusinessKeyId() { - return businessKeyId; - } - - public void setBusinessKeyId(long businessKeyId) { - this.businessKeyId = businessKeyId; - } - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - - public String getDescription() { - return description; - } - - public void setDescription(String description) { - this.description = description; - } - - public byte getVisibility() { - return visibility; - } - - public void setVisibility(byte visibility) { - this.visibility = visibility; - } - - - public long getPhotoImageKeyId() { - return photoImageKeyId; - } - - public void setPhotoImageKeyId(long photoImageKeyId) { - this.photoImageKeyId = photoImageKeyId; - } - - public long getAverageWaitingTime() { - return averageWaitingTime; - } - - public void setAverageWaitingTime(long averageWaitingTime) { - this.averageWaitingTime = averageWaitingTime; - } - - public float getLatitude() { - return latitude; - } - - public void setLatitude(float latitude) { - this.latitude = latitude; - } - - public float getLongitude() { - return longitude; - } - - public void setLongitude(float longitude) { - this.longitude = longitude; - } - - public String getCountry() { - return country; - } - - public void setCountry(String country) { - this.country = country; - } - - public String getCity() { - return city; - } - - public void setCity(String city) { - this.city = city; - } - - public String getPostalCode() { - return postalCode; - } - - public void setPostalCode(String postalCode) { - this.postalCode = postalCode; - } - - public String getStreet() { - return street; - } - - public void setStreet(String street) { - this.street = street; - } - - public String getNumber() { - return number; - } - - public void setNumber(String number) { - this.number = number; - } - - public int getWaitingPeople() { - return waitingPeople; - } - - public void setWaitingPeople(int waitingPeople) { - this.waitingPeople = waitingPeople; - } - - public boolean getRequiresSignIn() { - return requiresSignIn; - } - - public void setRequiresSignIn(boolean requiresSignIn) { - this.requiresSignIn = requiresSignIn; - } + public static final byte VISIBILITY_PRIVATE = 0; + public static final byte VISIBILITY_PUBLIC = 1; + + @PrimaryKey + @Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY) + Key key; + + @Persistent + long businessKeyId; + + @Persistent + String name; + + @Persistent + String description; + + @Persistent + byte visibility; + + @Persistent + long photoImageKeyId; + + @Persistent + long averageWaitingTime; + + @Persistent + boolean requiresSignIn; + + int waitingPeople; + + /** + * Location + */ + @Persistent + float latitude; + @Persistent + float longitude; + + @Persistent + String country; + @Persistent + String city; + @Persistent + String postalCode; + @Persistent + String street; + @Persistent + String number; + + public QueueEntry(long businessKeyId) { + this.businessKeyId = businessKeyId; + visibility = VISIBILITY_PUBLIC; + photoImageKeyId = -1; + waitingPeople = -1; + latitude = -1; + longitude = -1; + requiresSignIn = false; + averageWaitingTime = TimeUnit.MINUTES.toMillis(5); + } + + public void parseFromRequest(ApiRequest req) { + businessKeyId = req.getParameterAsLong("businessKeyId", businessKeyId); + name = req.getParameter("name", name); + description = req.getParameter("description", description); + visibility = (byte) req.getParameterAsInt("visibility", visibility); + averageWaitingTime = req.getParameterAsLong("averageWaitingTime", averageWaitingTime); + country = req.getParameter("country", country); + city = req.getParameter("city", city); + postalCode = req.getParameter("postalCode", postalCode); + street = req.getParameter("street", street); + number = req.getParameter("number", number); + latitude = req.getParameterAsFloat("latitude", latitude); + longitude = req.getParameterAsFloat("longitude", longitude); + requiresSignIn = req.getParameterAsBoolean("requiresSignIn", requiresSignIn); + } + + /* + * Methods for calculating the distance between two locations in meters + */ + public boolean hasValidLocation() { + if (latitude == -1 && longitude == -1) { + return false; + } + return true; + } + + public float getDistanceTo(float latitude, float longitude) { + return getDistance(this, latitude, longitude); + } + + public static float getDistance(QueueEntry queue, float latitude, float longitude) { + if (queue.latitude == -1 || queue.longitude == -1 || latitude == -1 || longitude == -1) { + return -1; + } else { + return getDistance(queue.latitude, queue.longitude, latitude, longitude); + } + } + + public static float getDistance(float lat1, float lng1, float lat2, float lng2) { + double earthRadius = 6371000; + double dLat = Math.toRadians(lat2 - lat1); + double dLng = Math.toRadians(lng2 - lng1); + double a = Math.sin(dLat / 2) * Math.sin(dLat / 2) + Math.cos(Math.toRadians(lat1)) * Math.cos(Math.toRadians(lat2)) * Math.sin(dLng / 2) * Math.sin(dLng / 2); + double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); + float dist = (float) (earthRadius * c); + + return dist; + } + + public float[] offsetLocationToRange(long distance) { + return offsetLocationToRange(latitude, longitude, distance); + } + + public static float[] offsetLocationToRange(float latitude, float longitude, long distance) { + float latitudeDelta = Math.abs((float) (distance / 1000 / 111.111)); + float longitudeDelta = Math.abs((float) (distance / 1000 / (111.111 * Math.cos(latitude)))); + + float latitudeMin = latitude - latitudeDelta; + float latitudeMax = latitude + latitudeDelta; + float longitudeMin = longitude - longitudeDelta; + float longitudeMax = longitude + longitudeDelta; + + return new float[]{latitudeMin, longitudeMin, latitudeMax, longitudeMax}; + } + + public Key getKey() { + return key; + } + + public void setKey(Key key) { + this.key = key; + } + + public Key generateKey() { + return KeyFactory.createKey(QueueEntry.class.getSimpleName(), name); + } + + public long getBusinessKeyId() { + return businessKeyId; + } + + public void setBusinessKeyId(long businessKeyId) { + this.businessKeyId = businessKeyId; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public byte getVisibility() { + return visibility; + } + + public void setVisibility(byte visibility) { + this.visibility = visibility; + } + + + public long getPhotoImageKeyId() { + return photoImageKeyId; + } + + public void setPhotoImageKeyId(long photoImageKeyId) { + this.photoImageKeyId = photoImageKeyId; + } + + public long getAverageWaitingTime() { + return averageWaitingTime; + } + + public void setAverageWaitingTime(long averageWaitingTime) { + this.averageWaitingTime = averageWaitingTime; + } + + public float getLatitude() { + return latitude; + } + + public void setLatitude(float latitude) { + this.latitude = latitude; + } + + public float getLongitude() { + return longitude; + } + + public void setLongitude(float longitude) { + this.longitude = longitude; + } + + public String getCountry() { + return country; + } + + public void setCountry(String country) { + this.country = country; + } + + public String getCity() { + return city; + } + + public void setCity(String city) { + this.city = city; + } + + public String getPostalCode() { + return postalCode; + } + + public void setPostalCode(String postalCode) { + this.postalCode = postalCode; + } + + public String getStreet() { + return street; + } + + public void setStreet(String street) { + this.street = street; + } + + public String getNumber() { + return number; + } + + public void setNumber(String number) { + this.number = number; + } + + public int getWaitingPeople() { + return waitingPeople; + } + + public void setWaitingPeople(int waitingPeople) { + this.waitingPeople = waitingPeople; + } + + public boolean getRequiresSignIn() { + return requiresSignIn; + } + + public void setRequiresSignIn(boolean requiresSignIn) { + this.requiresSignIn = requiresSignIn; + } } diff --git a/IntelliQ/backend/src/main/java/com/intelliq/appengine/datastore/entries/QueueItemEntry.java b/IntelliQ/backend/src/main/java/com/intelliq/appengine/datastore/entries/QueueItemEntry.java index 9f3d12c..a4a74ae 100644 --- a/IntelliQ/backend/src/main/java/com/intelliq/appengine/datastore/entries/QueueItemEntry.java +++ b/IntelliQ/backend/src/main/java/com/intelliq/appengine/datastore/entries/QueueItemEntry.java @@ -19,177 +19,177 @@ @PersistenceCapable(detachable = "true") public class QueueItemEntry { - public static final byte STATUS_ALL = -1; - public static final byte STATUS_WAITING = 0; - public static final byte STATUS_CANCELED = 1; - public static final byte STATUS_CALLED = 2; - public static final byte STATUS_DONE = 3; - - @PrimaryKey - @Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY) - Key key; - - @Persistent - long queueKeyId; - - @Persistent - long userKeyId; - - @Persistent - String name; - - @Persistent - boolean showName; - - @Persistent - boolean usingApp; - - @Persistent - long entryTimestamp; - - @Persistent - long lastStatusChangeTimestamp; - - @Persistent - int ticketNumber; - - @Persistent - byte status; - - public QueueItemEntry(long queueKeyId) { - this.queueKeyId = queueKeyId; - entryTimestamp = new Date().getTime(); - lastStatusChangeTimestamp = entryTimestamp; - status = STATUS_WAITING; - showName = true; - usingApp = true; - ticketNumber = -1; - userKeyId = -1; - } - - public void parseFromRequest(ApiRequest req) { - queueKeyId = req.getParameterAsLong("queueKeyId", queueKeyId); - userKeyId = req.getParameterAsLong("userKeyId", userKeyId); - name = req.getParameter("name", name); - showName = req.getParameterAsBoolean("showName", showName); - usingApp = req.getParameterAsBoolean("usingApp", usingApp); - } - - public void makeDummyItem() { - name = "Dummy Customer"; - entryTimestamp = (long) (new Date().getTime() - (Math.random() * TimeUnit.MINUTES.toMillis(60))); - lastStatusChangeTimestamp = entryTimestamp; - - if (Math.random() < 0.5) { - // make sure we have more people waiting - status = STATUS_WAITING; - } else { - // set random status - status = (byte) Math.round(Math.random() * 3); - } - - showName = Math.random() < 0.5; - usingApp = Math.random() < 0.5; - } - - public Key getKey() { - return key; - } - - public void setKey(Key key) { - this.key = key; - } - - public Key generateKey() { - return KeyFactory.createKey(QueueItemEntry.class.getSimpleName(), name); - } - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public boolean getShowName() { - return showName; - } - - public void setShowName(boolean showName) { - this.showName = showName; - } - - public boolean isUsingApp() { - return usingApp; - } - - public void setUsingApp(boolean usingApp) { - this.usingApp = usingApp; - } - - public long getEntryTimestamp() { - return entryTimestamp; - } - - public void setEntryTimestamp(long entryTimestamp) { - this.entryTimestamp = entryTimestamp; - this.lastStatusChangeTimestamp = entryTimestamp; - } - - public long getQueueKeyId() { - return queueKeyId; - } - - public void setQueueKeyId(long queueKeyId) { - this.queueKeyId = queueKeyId; - } - - public long getUserKeyId() { - return userKeyId; - } - - public void setUserKeyId(long userKeyId) { - this.userKeyId = userKeyId; - } - - public long getLastStatusChangeTimestamp() { - return lastStatusChangeTimestamp; - } - - public void setLastStatusChangeTimestamp(long lastStatusChangeTimestamp) { - this.lastStatusChangeTimestamp = lastStatusChangeTimestamp; - } - - public int getTicketNumber() { - return ticketNumber; - } - - public void setTicketNumber(int ticketNumber) { - this.ticketNumber = ticketNumber; - } - - public byte getStatus() { - return status; - } - - public void setStatus(byte status) { - this.status = status; - } - - public static class EntryTimestampComparator implements Comparator { - @Override - public int compare(QueueItemEntry one, QueueItemEntry another) { - int returnVal = 0; - if (one.getEntryTimestamp() < another.getEntryTimestamp()) { - returnVal = -1; - } else if (one.getEntryTimestamp() > another.getEntryTimestamp()) { - returnVal = 1; - } else if (one.getEntryTimestamp() == another.getEntryTimestamp()) { - returnVal = 0; - } - return returnVal; - } - } + public static final byte STATUS_ALL = -1; + public static final byte STATUS_WAITING = 0; + public static final byte STATUS_CANCELED = 1; + public static final byte STATUS_CALLED = 2; + public static final byte STATUS_DONE = 3; + + @PrimaryKey + @Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY) + Key key; + + @Persistent + long queueKeyId; + + @Persistent + long userKeyId; + + @Persistent + String name; + + @Persistent + boolean showName; + + @Persistent + boolean usingApp; + + @Persistent + long entryTimestamp; + + @Persistent + long lastStatusChangeTimestamp; + + @Persistent + int ticketNumber; + + @Persistent + byte status; + + public QueueItemEntry(long queueKeyId) { + this.queueKeyId = queueKeyId; + entryTimestamp = new Date().getTime(); + lastStatusChangeTimestamp = entryTimestamp; + status = STATUS_WAITING; + showName = true; + usingApp = true; + ticketNumber = -1; + userKeyId = -1; + } + + public void parseFromRequest(ApiRequest req) { + queueKeyId = req.getParameterAsLong("queueKeyId", queueKeyId); + userKeyId = req.getParameterAsLong("userKeyId", userKeyId); + name = req.getParameter("name", name); + showName = req.getParameterAsBoolean("showName", showName); + usingApp = req.getParameterAsBoolean("usingApp", usingApp); + } + + public void makeDummyItem() { + name = "Dummy Customer"; + entryTimestamp = (long) (new Date().getTime() - (Math.random() * TimeUnit.MINUTES.toMillis(60))); + lastStatusChangeTimestamp = entryTimestamp; + + if (Math.random() < 0.5) { + // make sure we have more people waiting + status = STATUS_WAITING; + } else { + // set random status + status = (byte) Math.round(Math.random() * 3); + } + + showName = Math.random() < 0.5; + usingApp = Math.random() < 0.5; + } + + public Key getKey() { + return key; + } + + public void setKey(Key key) { + this.key = key; + } + + public Key generateKey() { + return KeyFactory.createKey(QueueItemEntry.class.getSimpleName(), name); + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public boolean getShowName() { + return showName; + } + + public void setShowName(boolean showName) { + this.showName = showName; + } + + public boolean isUsingApp() { + return usingApp; + } + + public void setUsingApp(boolean usingApp) { + this.usingApp = usingApp; + } + + public long getEntryTimestamp() { + return entryTimestamp; + } + + public void setEntryTimestamp(long entryTimestamp) { + this.entryTimestamp = entryTimestamp; + this.lastStatusChangeTimestamp = entryTimestamp; + } + + public long getQueueKeyId() { + return queueKeyId; + } + + public void setQueueKeyId(long queueKeyId) { + this.queueKeyId = queueKeyId; + } + + public long getUserKeyId() { + return userKeyId; + } + + public void setUserKeyId(long userKeyId) { + this.userKeyId = userKeyId; + } + + public long getLastStatusChangeTimestamp() { + return lastStatusChangeTimestamp; + } + + public void setLastStatusChangeTimestamp(long lastStatusChangeTimestamp) { + this.lastStatusChangeTimestamp = lastStatusChangeTimestamp; + } + + public int getTicketNumber() { + return ticketNumber; + } + + public void setTicketNumber(int ticketNumber) { + this.ticketNumber = ticketNumber; + } + + public byte getStatus() { + return status; + } + + public void setStatus(byte status) { + this.status = status; + } + + public static class EntryTimestampComparator implements Comparator { + @Override + public int compare(QueueItemEntry one, QueueItemEntry another) { + int returnVal = 0; + if (one.getEntryTimestamp() < another.getEntryTimestamp()) { + returnVal = -1; + } else if (one.getEntryTimestamp() > another.getEntryTimestamp()) { + returnVal = 1; + } else if (one.getEntryTimestamp() == another.getEntryTimestamp()) { + returnVal = 0; + } + return returnVal; + } + } } diff --git a/IntelliQ/backend/src/main/java/com/intelliq/appengine/datastore/entries/UserEntry.java b/IntelliQ/backend/src/main/java/com/intelliq/appengine/datastore/entries/UserEntry.java index 3268158..7ce8d8b 100644 --- a/IntelliQ/backend/src/main/java/com/intelliq/appengine/datastore/entries/UserEntry.java +++ b/IntelliQ/backend/src/main/java/com/intelliq/appengine/datastore/entries/UserEntry.java @@ -13,187 +13,196 @@ @PersistenceCapable(detachable = "true") public class UserEntry { - public static final byte STATUS_DEFAULT = 0; - public static final byte STATUS_BANNED = -1; - - @PrimaryKey - @Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY) - Key key; - - @Persistent - String googleUserId; - - @Persistent - String facebookUserId; - - @Persistent - String name; - - @Persistent - String mail; - - @Persistent - String pictureUrl; - - @Persistent - String locale; - - @Persistent - byte status; - - @Persistent - float latitude; - - @Persistent - float longitude; - - @Persistent - long lastLocationUpdate; - - @Persistent(defaultFetchGroup = "true") - @Element(dependent = "true") - UserStatsEntry stats; - - public UserEntry() { - super(); - status = STATUS_DEFAULT; - stats = new UserStatsEntry(); - } - - public UserEntry parseFromGooglePayload(Payload payload) { - if (payload != null) { - googleUserId = payload.getSubject(); - name = (String) payload.get("name"); - mail = payload.getEmail(); - pictureUrl = (String) payload.get("picture"); - locale = (String) payload.get("locale"); - } - return this; - } - - public boolean hasPermission(PermissionEntry requestedPermission) { - // set the current user - requestedPermission.setUserKeyId(key.getId()); - - // check if the permission contains any requirements - if (requestedPermission.getPermission() == PermissionEntry.PERMISSION_NONE) { - return true; - } - - // check if the permission subject is valid - if (requestedPermission.getSubjectKeyId() < 1) { - return true; - } - - // check if the permission is about this user - if (UserEntry.class.getSimpleName().equals(requestedPermission.getSubjectKind())) { - if (requestedPermission.getSubjectKeyId() == this.key.getId()) { - return true; - } - } - - // look up the PermissionEntry in the data store - if (this.key != null) { - return PermissionHelper.hasPermission(requestedPermission); - } else { - return false; - } - } - - public Key getKey() { - return key; - } - - public void setKey(Key key) { - this.key = key; - } - - public String getGoogleUserId() { - return googleUserId; - } - - public void setGoogleUserId(String googleUserId) { - this.googleUserId = googleUserId; - } - - public String getFacebookUserId() { - return facebookUserId; - } - - public void setFacebookUserId(String facebookUserId) { - this.facebookUserId = facebookUserId; - } - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public String getMail() { - return mail; - } - - public void setMail(String mail) { - this.mail = mail; - } - - public String getPictureUrl() { - return pictureUrl; - } - - public void setPictureUrl(String pictureUrl) { - this.pictureUrl = pictureUrl; - } - - public String getLocale() { - return locale; - } - - public void setLocale(String locale) { - this.locale = locale; - } - - public byte getStatus() { - return status; - } - - public void setStatus(byte status) { - this.status = status; - } - - public UserStatsEntry getStats() { - return stats; - } - - public void setStats(UserStatsEntry stats) { - this.stats = stats; - } - - public float getLatitude() { - return latitude; - } - - public void setLatitude(float latitude) { - this.latitude = latitude; - } - - public float getLongitude() { - return longitude; - } - - public void setLongitude(float longitude) { - this.longitude = longitude; - } - - public long getLastLocationUpdate() { - return lastLocationUpdate; - } - - public void setLastLocationUpdate(long lastLocationUpdate) { - this.lastLocationUpdate = lastLocationUpdate; - } - + public static final byte STATUS_DEFAULT = 0; + public static final byte STATUS_BANNED = -1; + + @PrimaryKey + @Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY) + Key key; + + @Persistent + String googleUserId; + + @Persistent + String facebookUserId; + + @Persistent + String name; + + @Persistent + String mail; + + @Persistent + String pictureUrl; + + @Persistent + String locale; + + @Persistent + byte status; + + @Persistent + float latitude; + + @Persistent + float longitude; + + @Persistent + long lastLocationUpdate; + + @Persistent(defaultFetchGroup = "true") + @Element(dependent = "true") + UserStatsEntry stats; + + public UserEntry() { + super(); + status = STATUS_DEFAULT; + stats = new UserStatsEntry(); + } + + public UserEntry parseFromGooglePayload(Payload payload) { + if (payload != null) { + googleUserId = payload.getSubject(); + name = (String) payload.get("name"); + mail = payload.getEmail(); + pictureUrl = (String) payload.get("picture"); + locale = (String) payload.get("locale"); + } + return this; + } + + public boolean hasPermission(PermissionEntry requestedPermission) { + // set the current user + requestedPermission.setUserKeyId(key.getId()); + + // check if the permission contains any requirements + if (requestedPermission.getPermission() == PermissionEntry.PERMISSION_NONE) { + return true; + } + + // check if the permission subject is valid + if (requestedPermission.getSubjectKeyId() < 1) { + return true; + } + + // check if the permission is about this user + if (UserEntry.class.getSimpleName().equals(requestedPermission.getSubjectKind())) { + if (requestedPermission.getSubjectKeyId() == this.key.getId()) { + return true; + } + } + + // look up the PermissionEntry in the data store + if (this.key != null) { + return PermissionHelper.hasPermission(requestedPermission); + } else { + return false; + } + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(name); + if (key != null) { + sb.append(" (").append(key.getId()).append(")"); + } + return sb.toString(); + } + + public Key getKey() { + return key; + } + + public void setKey(Key key) { + this.key = key; + } + + public String getGoogleUserId() { + return googleUserId; + } + + public void setGoogleUserId(String googleUserId) { + this.googleUserId = googleUserId; + } + + public String getFacebookUserId() { + return facebookUserId; + } + + public void setFacebookUserId(String facebookUserId) { + this.facebookUserId = facebookUserId; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getMail() { + return mail; + } + + public void setMail(String mail) { + this.mail = mail; + } + + public String getPictureUrl() { + return pictureUrl; + } + + public void setPictureUrl(String pictureUrl) { + this.pictureUrl = pictureUrl; + } + + public String getLocale() { + return locale; + } + + public void setLocale(String locale) { + this.locale = locale; + } + + public byte getStatus() { + return status; + } + + public void setStatus(byte status) { + this.status = status; + } + + public UserStatsEntry getStats() { + return stats; + } + + public void setStats(UserStatsEntry stats) { + this.stats = stats; + } + + public float getLatitude() { + return latitude; + } + + public void setLatitude(float latitude) { + this.latitude = latitude; + } + + public float getLongitude() { + return longitude; + } + + public void setLongitude(float longitude) { + this.longitude = longitude; + } + + public long getLastLocationUpdate() { + return lastLocationUpdate; + } + + public void setLastLocationUpdate(long lastLocationUpdate) { + this.lastLocationUpdate = lastLocationUpdate; + } + } diff --git a/IntelliQ/backend/src/main/java/com/intelliq/appengine/datastore/entries/UserStatsEntry.java b/IntelliQ/backend/src/main/java/com/intelliq/appengine/datastore/entries/UserStatsEntry.java index 61d7387..6799725 100644 --- a/IntelliQ/backend/src/main/java/com/intelliq/appengine/datastore/entries/UserStatsEntry.java +++ b/IntelliQ/backend/src/main/java/com/intelliq/appengine/datastore/entries/UserStatsEntry.java @@ -12,102 +12,102 @@ @PersistenceCapable(detachable = "true") public class UserStatsEntry { - @PrimaryKey - @Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY) - transient Key key; - - @Persistent(mappedBy = "stats") - transient UserEntry user; - - @Persistent - long queuesJoined; - - @Persistent - long queuesCreated; - - @Persistent - long businessesCreated; - - @Persistent - long lastSignIn; - - @Persistent - long firstSignIn; - - @Persistent - long reports; - - public UserStatsEntry() { - super(); - queuesJoined = 0; - queuesCreated = 0; - firstSignIn = (new Date()).getTime(); - lastSignIn = firstSignIn; - reports = 0; - } - - public Key getKey() { - return key; - } - - public void setKey(Key key) { - this.key = key; - } - - public UserEntry getUser() { - return user; - } - - public void setUser(UserEntry user) { - this.user = user; - } - - public long getQueuesJoined() { - return queuesJoined; - } - - public void setQueuesJoined(long queuesJoined) { - this.queuesJoined = queuesJoined; - } - - public long getQueuesCreated() { - return queuesCreated; - } - - public void setQueuesCreated(long queuesCreated) { - this.queuesCreated = queuesCreated; - } - - public long getLastSignIn() { - return lastSignIn; - } - - public void setLastSignIn(long lastSignIn) { - this.lastSignIn = lastSignIn; - } - - public long getFirstSignIn() { - return firstSignIn; - } - - public void setFirstSignIn(long firstSignIn) { - this.firstSignIn = firstSignIn; - } - - public long getReports() { - return reports; - } - - public void setReports(long reports) { - this.reports = reports; - } - - public long getBusinessesCreated() { - return businessesCreated; - } - - public void setBusinessesCreated(long businessesCreated) { - this.businessesCreated = businessesCreated; - } - + @PrimaryKey + @Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY) + transient Key key; + + @Persistent(mappedBy = "stats") + transient UserEntry user; + + @Persistent + long queuesJoined; + + @Persistent + long queuesCreated; + + @Persistent + long businessesCreated; + + @Persistent + long lastSignIn; + + @Persistent + long firstSignIn; + + @Persistent + long reports; + + public UserStatsEntry() { + super(); + queuesJoined = 0; + queuesCreated = 0; + firstSignIn = (new Date()).getTime(); + lastSignIn = firstSignIn; + reports = 0; + } + + public Key getKey() { + return key; + } + + public void setKey(Key key) { + this.key = key; + } + + public UserEntry getUser() { + return user; + } + + public void setUser(UserEntry user) { + this.user = user; + } + + public long getQueuesJoined() { + return queuesJoined; + } + + public void setQueuesJoined(long queuesJoined) { + this.queuesJoined = queuesJoined; + } + + public long getQueuesCreated() { + return queuesCreated; + } + + public void setQueuesCreated(long queuesCreated) { + this.queuesCreated = queuesCreated; + } + + public long getLastSignIn() { + return lastSignIn; + } + + public void setLastSignIn(long lastSignIn) { + this.lastSignIn = lastSignIn; + } + + public long getFirstSignIn() { + return firstSignIn; + } + + public void setFirstSignIn(long firstSignIn) { + this.firstSignIn = firstSignIn; + } + + public long getReports() { + return reports; + } + + public void setReports(long reports) { + this.reports = reports; + } + + public long getBusinessesCreated() { + return businessesCreated; + } + + public void setBusinessesCreated(long businessesCreated) { + this.businessesCreated = businessesCreated; + } + } diff --git a/IntelliQ/backend/src/main/java/com/intelliq/appengine/datastore/queries/ImageQuery.java b/IntelliQ/backend/src/main/java/com/intelliq/appengine/datastore/queries/ImageQuery.java index 03785ab..7b4e336 100644 --- a/IntelliQ/backend/src/main/java/com/intelliq/appengine/datastore/queries/ImageQuery.java +++ b/IntelliQ/backend/src/main/java/com/intelliq/appengine/datastore/queries/ImageQuery.java @@ -13,47 +13,47 @@ import com.intelliq.appengine.datastore.entries.ImageEntry; public class ImageQuery { - - private static final Logger log = Logger.getLogger(ImageQuery.class.getName()); - - PersistenceManager pm = PMF.get().getPersistenceManager(); + + private static final Logger log = Logger.getLogger(ImageQuery.class.getName()); + + PersistenceManager pm = PMF.get().getPersistenceManager(); Query query = pm.newQuery(ImageEntry.class); - + public ImageQuery() { - super(); - } - - public List execute(Query query, String value) { - List results = new ArrayList(); - try { - results = (List) query.execute(value); - results.size(); - log.info("ImageEntry execution returned " + results.size() + " item(s)"); - } catch(Exception e) { - e.printStackTrace(); - } finally { - query.closeAll(); - pm.close(); - } - return results; - } - - public ImageEntry getImageByKeyId(String idString) { - long id = Long.parseLong(idString); - return getImageByKeyId(id); - } - - public ImageEntry getImageByKeyId(long id) { - Key key = KeyFactory.createKey(ImageEntry.class.getSimpleName(), id); - ImageEntry image = pm.getObjectById(ImageEntry.class, key); - return image; - } - - public Query getQueryWithUrlEquals() { - query.setFilter("url == valueParam"); - query.declareParameters("String valueParam"); - query.setRange(0, 10); - return query; - } - + super(); + } + + public List execute(Query query, String value) { + List results = new ArrayList(); + try { + results = (List) query.execute(value); + results.size(); + log.info("ImageEntry execution returned " + results.size() + " item(s)"); + } catch (Exception e) { + e.printStackTrace(); + } finally { + query.closeAll(); + pm.close(); + } + return results; + } + + public ImageEntry getImageByKeyId(String idString) { + long id = Long.parseLong(idString); + return getImageByKeyId(id); + } + + public ImageEntry getImageByKeyId(long id) { + Key key = KeyFactory.createKey(ImageEntry.class.getSimpleName(), id); + ImageEntry image = pm.getObjectById(ImageEntry.class, key); + return image; + } + + public Query getQueryWithUrlEquals() { + query.setFilter("url == valueParam"); + query.declareParameters("String valueParam"); + query.setRange(0, 10); + return query; + } + } diff --git a/IntelliQ/backend/src/main/java/com/intelliq/appengine/logging/BusinessLogging.java b/IntelliQ/backend/src/main/java/com/intelliq/appengine/logging/BusinessLogging.java new file mode 100644 index 0000000..bd9088e --- /dev/null +++ b/IntelliQ/backend/src/main/java/com/intelliq/appengine/logging/BusinessLogging.java @@ -0,0 +1,50 @@ +package com.intelliq.appengine.logging; + +import com.intelliq.appengine.api.endpoint.business.BusinessEndpoint; +import com.intelliq.appengine.api.endpoint.queue.QueueEndpoint; +import com.intelliq.appengine.datastore.entries.BusinessEntry; +import com.intelliq.appengine.datastore.entries.QueueEntry; +import com.intelliq.appengine.datastore.entries.UserEntry; + +import net.steppschuh.slackmessagebuilder.message.attachment.Attachment; +import net.steppschuh.slackmessagebuilder.message.attachment.AttachmentField; + +import java.util.ArrayList; +import java.util.List; + +public abstract class BusinessLogging { + + public static void logCreation(BusinessEntry business, UserEntry user) { + Attachment attachment = generateBusinessAttachment(business); + attachment.setTitle("Business Created"); + attachment.setText(user.getName() + " created a new business"); + attachment.setThumbUrl(user.getPictureUrl()); + SlackLog.post(attachment); + } + + public static void logEdit(BusinessEntry business, UserEntry user) { + Attachment attachment = generateBusinessAttachment(business); + attachment.setTitle("Business Edited"); + attachment.setText(user.getName() + " edited an existing business"); + attachment.setThumbUrl(user.getPictureUrl()); + SlackLog.post(attachment); + } + + public static Attachment generateBusinessAttachment(BusinessEntry business) { + List fields = new ArrayList<>(); + fields.add(new AttachmentField("Name", business.getName())); + fields.add(new AttachmentField("Mail", business.getMail())); + + if (business.getQueues() != null) { + fields.add(new AttachmentField("Queues", String.valueOf(business.getQueues().size()))); + } + if (business.getKey() != null) { + fields.add(new AttachmentField("ID", String.valueOf(business.getKey().getId()))); + } + + Attachment attachment = SlackLog.generateAttachment(SlackLog.INFO, BusinessEndpoint.class.getSimpleName(), ""); + attachment.setFields(fields); + return attachment; + } + +} diff --git a/IntelliQ/backend/src/main/java/com/intelliq/appengine/logging/QueueLogging.java b/IntelliQ/backend/src/main/java/com/intelliq/appengine/logging/QueueLogging.java new file mode 100644 index 0000000..daff60a --- /dev/null +++ b/IntelliQ/backend/src/main/java/com/intelliq/appengine/logging/QueueLogging.java @@ -0,0 +1,52 @@ +package com.intelliq.appengine.logging; + +import com.intelliq.appengine.api.endpoint.queue.QueueEndpoint; +import com.intelliq.appengine.api.endpoint.user.SignInUserEndpoint; +import com.intelliq.appengine.datastore.entries.QueueEntry; +import com.intelliq.appengine.datastore.entries.UserEntry; + +import net.steppschuh.slackmessagebuilder.message.attachment.Attachment; +import net.steppschuh.slackmessagebuilder.message.attachment.AttachmentField; + +import java.util.ArrayList; +import java.util.List; + +public abstract class QueueLogging { + + public static void logCreation(QueueEntry queue, UserEntry user) { + Attachment attachment = generateQueueAttachment(queue); + attachment.setTitle("Queue Created"); + attachment.setText(user.getName() + " created a new queue"); + attachment.setThumbUrl(user.getPictureUrl()); + SlackLog.post(attachment); + } + + public static void logEdit(QueueEntry queue, UserEntry user) { + Attachment attachment = generateQueueAttachment(queue); + attachment.setTitle("Queue Edited"); + attachment.setText(user.getName() + " edited an existing queue"); + attachment.setThumbUrl(user.getPictureUrl()); + SlackLog.post(attachment); + } + + public static Attachment generateQueueAttachment(QueueEntry queue) { + List fields = new ArrayList<>(); + fields.add(new AttachmentField("Name", queue.getName())); + fields.add(new AttachmentField("City", queue.getCity())); + + if (queue.getVisibility() == QueueEntry.VISIBILITY_PUBLIC) { + fields.add(new AttachmentField("Visibility", "Public")); + } else { + fields.add(new AttachmentField("Visibility", "Private")); + } + fields.add(new AttachmentField("Business ID", String.valueOf(queue.getBusinessKeyId()))); + if (queue.getKey() != null) { + fields.add(new AttachmentField("ID", String.valueOf(queue.getKey().getId()))); + } + + Attachment attachment = SlackLog.generateAttachment(SlackLog.INFO, QueueEndpoint.class.getSimpleName(), ""); + attachment.setFields(fields); + return attachment; + } + +} diff --git a/IntelliQ/backend/src/main/java/com/intelliq/appengine/logging/SlackLog.java b/IntelliQ/backend/src/main/java/com/intelliq/appengine/logging/SlackLog.java new file mode 100644 index 0000000..b6b8498 --- /dev/null +++ b/IntelliQ/backend/src/main/java/com/intelliq/appengine/logging/SlackLog.java @@ -0,0 +1,187 @@ +package com.intelliq.appengine.logging; + +import net.steppschuh.slackmessagebuilder.message.MessageBuilder; +import net.steppschuh.slackmessagebuilder.message.attachment.Attachment; +import net.steppschuh.slackmessagebuilder.message.attachment.AttachmentBuilder; +import net.steppschuh.slackmessagebuilder.message.attachment.AttachmentField; +import net.steppschuh.slackmessagebuilder.request.Webhook; + +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.Date; +import java.util.List; +import java.util.logging.Logger; + +public final class SlackLog { + + private static final String WEBHOOK_URL = "https://hooks.slack.com/services/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"; + + public static final int VERBOSE = 2; + public static final int DEBUG = 3; + public static final int INFO = 4; + public static final int WARN = 5; + public static final int ERROR = 6; + + private static final Logger log = Logger.getLogger(SlackLog.class.getName()); + private static final Webhook slackWebhook = new Webhook(WEBHOOK_URL); + private static final MessageBuilder defaultMessageBuilder = getDefaultMessageBuilder(); + + public static MessageBuilder getDefaultMessageBuilder() { + MessageBuilder messageBuilder = new MessageBuilder(); + resetToDefaultMessageBuilder(messageBuilder); + return messageBuilder; + } + + public static void resetToDefaultMessageBuilder() { + resetToDefaultMessageBuilder(defaultMessageBuilder); + } + + public static void resetToDefaultMessageBuilder(MessageBuilder messageBuilder) { + messageBuilder + .setChannel("#log") + .setUsername("Backend") + .setText(null) + .setAttachments(null) + .setIconEmoji(":intelliq:") + .setIconUrl(null); + } + + public static void v(Object tag, Object content) { + v(tag.getClass().getSimpleName(), content); + } + + public static void v(String tag, Object content) { + log.finer(tag + ": " + content.toString()); + Attachment attachment = generateAttachment(VERBOSE, tag, content); + post(attachment); + } + + public static void d(Object tag, Object content) { + d(tag.getClass().getSimpleName(), content); + } + + public static void d(String tag, Object content) { + log.fine(tag + ": " + content.toString()); + Attachment attachment = generateAttachment(DEBUG, tag, content); + post(attachment); + } + + public static void i(Object tag, Object content) { + i(tag.getClass().getSimpleName(), content); + } + + public static void i(String tag, Object content) { + log.info(tag + ": " + content.toString()); + Attachment attachment = generateAttachment(INFO, tag, content); + post(attachment); + } + + public static void w(Object tag, Object content) { + w(tag.getClass().getSimpleName(), content); + } + + public static void w(String tag, Object content) { + log.warning(tag + ": " + content.toString()); + Attachment attachment = generateAttachment(WARN, tag, content); + post(attachment); + } + + public static void e(Object tag, Object content) { + e(tag.getClass().getSimpleName(), content); + } + + public static void e(String tag, Object content) { + log.severe(tag + ": " + content.toString()); + Attachment attachment = generateAttachment(ERROR, tag, content); + post(attachment); + } + + public static void e(Exception exception) { + e("Exception", exception); + } + + public static void e(Object tag, Exception exception) { + e(tag.getClass().getSimpleName(), exception); + } + + public static void e(String tag, Exception exception) { + log.warning(tag + ": " + exception.getMessage()); + String message = exception.getClass().getSimpleName() + ": " + exception.getMessage(); + Attachment attachment = generateAttachment(ERROR, tag, message); + + List fields = new ArrayList<>(); + StackTraceElement[] traces = exception.getStackTrace(); + if (traces != null && traces.length > 0) { + fields.add(new AttachmentField("Method", traces[0].getMethodName())); + fields.add(new AttachmentField("Line", String.valueOf(traces[0].getLineNumber()))); + String className = traces[0].getClassName(); + int dotIndex = className.lastIndexOf('.'); + if (dotIndex != -1) { + className = className.substring(dotIndex + 1); + } + fields.add(new AttachmentField("Class", className)); + } + attachment.setFields(fields); + + post(attachment); + } + + public static void post(Attachment attachment) { + post(null, attachment); + } + + public static void post(String message, Attachment attachment) { + resetToDefaultMessageBuilder(); + defaultMessageBuilder.setText(message); + defaultMessageBuilder.addAttachment(attachment); + try { + slackWebhook.postMessageSynchronous(defaultMessageBuilder.build()); + } catch (Exception ex) { + log.warning("Unable to post to Slack: " + ex.getMessage()); + ex.printStackTrace(); + } + } + + public static Attachment generateAttachment(int logLevel, String tag, Object content) { + DateFormat df = new SimpleDateFormat("HH:mm:ss"); + Date now = Calendar.getInstance().getTime(); + String readableTime = df.format(now); + + StringBuilder sb = new StringBuilder(tag) + .append(" - ").append(readableTime) + .append(" (").append(now.getTime()).append(")"); + + return getAttachmentBuilder(logLevel) + .setText(content.toString()) + .setFooter(sb.toString()) + .build(); + } + + public static AttachmentBuilder getAttachmentBuilder(int logLevel) { + return new AttachmentBuilder() + .setColor(getHexCode(logLevel)); + } + + public static String getHexCode(int logLevel) { + switch (logLevel) { + case DEBUG: { + return "#607D8B"; + } + case INFO: { + return "#009688"; + } + case WARN: { + return "#FFC107"; + } + case ERROR: { + return "#f44336"; + } + default: { + return "#9E9E9E"; + } + } + } + +} \ No newline at end of file diff --git a/IntelliQ/backend/src/main/java/com/intelliq/appengine/logging/UserLogging.java b/IntelliQ/backend/src/main/java/com/intelliq/appengine/logging/UserLogging.java new file mode 100644 index 0000000..24d868e --- /dev/null +++ b/IntelliQ/backend/src/main/java/com/intelliq/appengine/logging/UserLogging.java @@ -0,0 +1,44 @@ +package com.intelliq.appengine.logging; + +import com.intelliq.appengine.api.endpoint.user.SignInUserEndpoint; +import com.intelliq.appengine.api.endpoint.user.UserEndpoint; +import com.intelliq.appengine.datastore.entries.UserEntry; + +import net.steppschuh.slackmessagebuilder.message.attachment.Attachment; +import net.steppschuh.slackmessagebuilder.message.attachment.AttachmentField; + +import java.util.ArrayList; +import java.util.List; + +public abstract class UserLogging { + + public static void logSignIn(UserEntry user) { + Attachment attachment = generateUserAttachment(user); + attachment.setTitle("Sign In"); + attachment.setText("A user signed in"); + attachment.setThumbUrl(user.getPictureUrl()); + SlackLog.post(attachment); + } + + public static void logSignUp(UserEntry user) { + Attachment attachment = generateUserAttachment(user); + attachment.setTitle("Sign Up"); + attachment.setText("A user signed in for the first time"); + attachment.setThumbUrl(user.getPictureUrl()); + SlackLog.post(attachment); + } + + public static Attachment generateUserAttachment(UserEntry user) { + List fields = new ArrayList<>(); + fields.add(new AttachmentField("Name", user.getName())); + fields.add(new AttachmentField("Mail", user.getMail())); + if (user.getKey() != null) { + fields.add(new AttachmentField("ID", String.valueOf(user.getKey().getId()))); + } + + Attachment attachment = SlackLog.generateAttachment(SlackLog.INFO, UserEndpoint.class.getSimpleName(), ""); + attachment.setFields(fields); + return attachment; + } + +} diff --git a/IntelliQ/backend/src/main/java/com/intelliq/appengine/stuff/FakeData.java b/IntelliQ/backend/src/main/java/com/intelliq/appengine/stuff/FakeData.java index db03405..efa37b6 100644 --- a/IntelliQ/backend/src/main/java/com/intelliq/appengine/stuff/FakeData.java +++ b/IntelliQ/backend/src/main/java/com/intelliq/appengine/stuff/FakeData.java @@ -5,31 +5,31 @@ public class FakeData { - public static final String[] names = { "Adam Ashton", "Adria Amor", "Aja Adelson", "Alecia Agarwal", "Alissa Ackman", "Allie Abila", "Anastasia Abarca", "Arlene Alba", "Arminda Acuff", - "Audria Aderholt", "Bernadine Bucholtz", "Brittney Bruning", "Cameron Coonrod", "Carmine Caul", "Carmon Cabral", "Chantell Council", "Charlette Corsi", "Charlie Castaldi", "Daren Dorman", - "Darren Difranco", "Delbert Dray", "Della Donaldson", "Dot Deaner", "Douglas Donnell", "Edra Emanuel", "Edyth Elsasser", "Elyse Ecklund", "Emelina Eagar", "Erlinda Eccles", - "Ernest Ebert", "Eun Estridge", "Fatimah Fujiwara", "Floy Foulk", "Frances Finnegan", "Freeda Frink", "Georgeann Gwozdz", "Geraldo Goulette", "Hanh Holiday", "Hyacinth Hallinan", - "Iliana Ismail", "Inger Icenhour", "Isiah Imes", "Jacklyn Johansen", "Jackson Jarnagin", "Jadwiga Jelinek", "Jake Jo", "Jeffery Jacobi", "Jesenia Jock", "Joella Jaworski", - "Jonas Jiminez", "Jonell Jone", "Julietta Jordon", "Karren Kong", "Katharine Kothari", "Katia Kubala", "Ken Koonce", "Kristel Kerr", "Lashaunda Longfellow", "Lawrence Lumpkin", - "Leeann Legette", "Lorilee Lovick", "Loris Landers", "Louis Laroche", "Lucie Leighton", "Lucille Lague", "Luetta Ligon", "Magnolia Mirsky", "Majorie Mcgrory", "Mandi Maynor", - "Marianne Mauck", "Maribel Morison", "Maryanne Mirabella", "Mervin Miers", "Miranda Mclin", "Mireille Marson", "Mohammad Maharaj", "Myron Munday", "Nanette Necaise", "Nikole Nightingale", - "Noelle Newhard", "Norah Nogle", "Qiana Quinlan", "Ramonita Rye", "Reinaldo Rochin", "Rhonda Rittenhouse", "Rolf Rickenbacker", "Sabra Stelter", "Samira Schildgen", "Sanford Strozier", - "Sara Stahler", "Shan Storms", "Shannon Salmons", "Shon Stennett", "Tashia Troutt", "Terresa Tapley", "Toby Trottier", "Tommy Tookes", "Trinity Terrel", "Wilbert Winner", "Yang Yutzy" }; - - public static String[] getNames(int count) { - String[] randomNames = names; - shuffleArray(randomNames); - return Arrays.copyOfRange(randomNames, 0, count < 100 ? count : 99); - } - - static void shuffleArray(String[] ar) { - Random rnd = new Random(); - for (int i = ar.length - 1; i > 0; i--) { - int index = rnd.nextInt(i + 1); - String a = ar[index]; - ar[index] = ar[i]; - ar[i] = a; - } - } + public static final String[] names = {"Adam Ashton", "Adria Amor", "Aja Adelson", "Alecia Agarwal", "Alissa Ackman", "Allie Abila", "Anastasia Abarca", "Arlene Alba", "Arminda Acuff", + "Audria Aderholt", "Bernadine Bucholtz", "Brittney Bruning", "Cameron Coonrod", "Carmine Caul", "Carmon Cabral", "Chantell Council", "Charlette Corsi", "Charlie Castaldi", "Daren Dorman", + "Darren Difranco", "Delbert Dray", "Della Donaldson", "Dot Deaner", "Douglas Donnell", "Edra Emanuel", "Edyth Elsasser", "Elyse Ecklund", "Emelina Eagar", "Erlinda Eccles", + "Ernest Ebert", "Eun Estridge", "Fatimah Fujiwara", "Floy Foulk", "Frances Finnegan", "Freeda Frink", "Georgeann Gwozdz", "Geraldo Goulette", "Hanh Holiday", "Hyacinth Hallinan", + "Iliana Ismail", "Inger Icenhour", "Isiah Imes", "Jacklyn Johansen", "Jackson Jarnagin", "Jadwiga Jelinek", "Jake Jo", "Jeffery Jacobi", "Jesenia Jock", "Joella Jaworski", + "Jonas Jiminez", "Jonell Jone", "Julietta Jordon", "Karren Kong", "Katharine Kothari", "Katia Kubala", "Ken Koonce", "Kristel Kerr", "Lashaunda Longfellow", "Lawrence Lumpkin", + "Leeann Legette", "Lorilee Lovick", "Loris Landers", "Louis Laroche", "Lucie Leighton", "Lucille Lague", "Luetta Ligon", "Magnolia Mirsky", "Majorie Mcgrory", "Mandi Maynor", + "Marianne Mauck", "Maribel Morison", "Maryanne Mirabella", "Mervin Miers", "Miranda Mclin", "Mireille Marson", "Mohammad Maharaj", "Myron Munday", "Nanette Necaise", "Nikole Nightingale", + "Noelle Newhard", "Norah Nogle", "Qiana Quinlan", "Ramonita Rye", "Reinaldo Rochin", "Rhonda Rittenhouse", "Rolf Rickenbacker", "Sabra Stelter", "Samira Schildgen", "Sanford Strozier", + "Sara Stahler", "Shan Storms", "Shannon Salmons", "Shon Stennett", "Tashia Troutt", "Terresa Tapley", "Toby Trottier", "Tommy Tookes", "Trinity Terrel", "Wilbert Winner", "Yang Yutzy"}; + + public static String[] getNames(int count) { + String[] randomNames = names; + shuffleArray(randomNames); + return Arrays.copyOfRange(randomNames, 0, count < 100 ? count : 99); + } + + static void shuffleArray(String[] ar) { + Random rnd = new Random(); + for (int i = ar.length - 1; i > 0; i--) { + int index = rnd.nextInt(i + 1); + String a = ar[index]; + ar[index] = ar[i]; + ar[i] = a; + } + } } diff --git a/IntelliQ/backend/src/main/java/com/intelliq/appengine/website/IntelliQServlet.java b/IntelliQ/backend/src/main/java/com/intelliq/appengine/website/IntelliQServlet.java index f8458cd..85aee15 100644 --- a/IntelliQ/backend/src/main/java/com/intelliq/appengine/website/IntelliQServlet.java +++ b/IntelliQ/backend/src/main/java/com/intelliq/appengine/website/IntelliQServlet.java @@ -10,70 +10,80 @@ @SuppressWarnings("serial") public class IntelliQServlet extends HttpServlet { - - private static final Logger log = Logger.getLogger(IntelliQServlet.class.getName()); - - /* - * Handles all requests for the IntelliQ website and redirects to the matching JSP. - */ - public void doGet(HttpServletRequest req, HttpServletResponse resp) - throws IOException { - - String forwardUrl; - String requestUrl = req.getRequestURL().toString(); + + private static final Logger log = Logger.getLogger(IntelliQServlet.class.getName()); + + /* + * Handles all requests for the IntelliQ website and redirects to the matching JSP. + */ + public void doGet(HttpServletRequest req, HttpServletResponse resp) + throws IOException { + + String forwardUrl; + String requestUrl = req.getRequestURL().toString(); String uri = req.getRequestURI(); - String rootUrl = requestUrl.substring( 0, requestUrl.indexOf(uri)); - + String rootUrl = requestUrl.substring(0, requestUrl.indexOf(uri)); + // detect language of request origin String languageCode = RequestFilter.getRequestLanguageCode(req); forwardUrl = "/intelliq/" + languageCode + "/"; - + // forward requests to available JSPs - if (requestUrl.contains("/manage/")) { - if (requestUrl.contains("/queue/")) { - forwardUrl += "manage/queueservlet/"; - } else if (requestUrl.contains("/business/")) { - forwardUrl += "manage/businessservlet/"; - } else { - forwardUrl += "manage/overviewservlet/"; - } - } else if (requestUrl.contains("/edit/")) { - if (requestUrl.contains("/queue/")) { - forwardUrl += "edit/queueservlet/"; - } else if (requestUrl.contains("/business/")) { - forwardUrl += "edit/businessservlet/"; - } - } else if (requestUrl.contains("/display/")) { - if (requestUrl.contains("/queue/")) { - forwardUrl += "display/queueservlet/"; - } else if (requestUrl.contains("/business/")) { - //forwardUrl += "display/businessservlet/"; - } else { - //forwardUrl += "manage/overviewservlet/"; - } - } else if (requestUrl.contains("/apps/")) { - forwardUrl += "appsservlet/"; - } else if (requestUrl.contains("/unternehmen/") || requestUrl.contains("/business/")) { - forwardUrl += "businessservlet/"; - } else if (requestUrl.contains("/signin/")) { - forwardUrl += "signinservlet/"; - } else { - // landing page - forwardUrl += "homeservlet/"; - } - - // get dispatcher for the generated URL + if (requestUrl.contains("/manage/")) { + if (requestUrl.contains("/queue/")) { + forwardUrl += "manage/queueservlet/"; + } else if (requestUrl.contains("/business/")) { + forwardUrl += "manage/businessservlet/"; + } else { + forwardUrl += "manage/overviewservlet/"; + } + } else if (requestUrl.contains("/edit/")) { + if (requestUrl.contains("/queue/")) { + forwardUrl += "edit/queueservlet/"; + } else if (requestUrl.contains("/business/")) { + forwardUrl += "edit/businessservlet/"; + } + } else if (requestUrl.contains("/display/")) { + if (requestUrl.contains("/queue/")) { + forwardUrl += "display/queueservlet/"; + } else if (requestUrl.contains("/business/")) { + //forwardUrl += "display/businessservlet/"; + } else { + //forwardUrl += "manage/overviewservlet/"; + } + } else if (requestUrl.contains("/apps/")) { + if (requestUrl.contains("/web/")) { + if (requestUrl.contains("/queue/")) { + forwardUrl += "webapp/queueservlet/"; + } else { + forwardUrl += "webappservlet/"; + } + } else { + forwardUrl += "appsservlet/"; + } + } else if (requestUrl.contains("/unternehmen/") || requestUrl.contains("/business/")) { + forwardUrl += "businessservlet/"; + } else if (requestUrl.contains("/signin/")) { + forwardUrl += "signinservlet/"; + } else { + // landing page + forwardUrl += "homeservlet/"; + } + + // get dispatcher for the generated URL RequestDispatcher rd = getServletContext().getRequestDispatcher(forwardUrl); - + // set JSP attributes req.setAttribute("rootUrl", rootUrl); req.setAttribute("requestUrl", requestUrl.replace("/intelliq", "")); req.setAttribute("staticUrl", rootUrl + "/static/"); - + req.setAttribute("appUrl", rootUrl + "/apps/web/"); + req.setAttribute("manageUrl", rootUrl + "/manage/"); + try { - rd.forward(req, resp); - } catch (Exception e) { - e.printStackTrace(); - } - } + rd.forward(req, resp); + } catch (Exception e) { + e.printStackTrace(); + } + } } diff --git a/IntelliQ/backend/src/main/webapp/WEB-INF/datastore-indexes-auto.xml b/IntelliQ/backend/src/main/webapp/WEB-INF/datastore-indexes-auto.xml new file mode 100644 index 0000000..8cbb0de --- /dev/null +++ b/IntelliQ/backend/src/main/webapp/WEB-INF/datastore-indexes-auto.xml @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/IntelliQ/backend/src/main/webapp/WEB-INF/datastore-indexes.xml b/IntelliQ/backend/src/main/webapp/WEB-INF/datastore-indexes.xml new file mode 100644 index 0000000..8cbb0de --- /dev/null +++ b/IntelliQ/backend/src/main/webapp/WEB-INF/datastore-indexes.xml @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/IntelliQ/backend/src/main/webapp/WEB-INF/web.xml b/IntelliQ/backend/src/main/webapp/WEB-INF/web.xml index 7e1abf0..44e0439 100644 --- a/IntelliQ/backend/src/main/webapp/WEB-INF/web.xml +++ b/IntelliQ/backend/src/main/webapp/WEB-INF/web.xml @@ -1,8 +1,19 @@ - - + + + + everything + /* + + + CONFIDENTIAL + + + + + requestFilter com.intelliq.appengine.RequestFilter @@ -41,11 +52,21 @@ /intelliq/image/* - - - + + + + IntelliQ + com.intelliq.appengine.website.IntelliQServlet + + + IntelliQ + /intelliq/* + + + + de/home - /website/de/home.jsp + /website/en/home.jsp de/home @@ -61,9 +82,9 @@ - + de/apps - /website/de/apps.jsp + /website/en/apps.jsp de/apps @@ -79,9 +100,9 @@ - + de/business - /website/de/business.jsp + /website/en/business.jsp de/business @@ -97,7 +118,7 @@ - + de/signin /website/en/signin.jsp @@ -115,7 +136,7 @@ - + de/manage_overview /website/en/manage_overview.jsp @@ -133,7 +154,7 @@ - + de/manage_queue /website/en/manage_queue.jsp @@ -151,7 +172,7 @@ - + de/edit_queue /website/en/edit_queue.jsp @@ -169,7 +190,7 @@ - + de/manage_business /website/en/manage_business.jsp @@ -187,7 +208,7 @@ - + de/edit_business /website/en/edit_business.jsp @@ -203,9 +224,9 @@ en/edit_business /intelliq/en/edit/businessservlet/* - - - + + + de/display_queue /website/en/display_queue.jsp @@ -222,18 +243,40 @@ /intelliq/en/display/queueservlet/* - + + + de/apps/web + /website/en/webapp.jsp + + + de/apps/web + /intelliq/de/webappservlet/* + - IntelliQ - com.intelliq.appengine.website.IntelliQServlet - - - IntelliQ - /intelliq/* - - - - index.html - - + en/apps/web + /website/en/webapp.jsp + + + en/apps/web + /intelliq/en/webappservlet/* + + + + + de/apps/web/queue + /website/en/webapp_queue.jsp + + + de/apps/web/queue + /intelliq/de/webapp/queueservlet/* + + + en/apps/web/queue + /website/en/webapp_queue.jsp + + + en/apps/web/queue + /intelliq/en/webapp/queueservlet/* + + diff --git a/IntelliQ/backend/src/main/webapp/static/assetlinks.json b/IntelliQ/backend/src/main/webapp/static/assetlinks.json new file mode 100644 index 0000000..e44e0ae --- /dev/null +++ b/IntelliQ/backend/src/main/webapp/static/assetlinks.json @@ -0,0 +1,11 @@ +[{ + "relation": ["delegate_permission/common.handle_all_urls"], + "target": { + "namespace": "android_app", + "package_name": "com.steppschuh.intelliq", + "sha256_cert_fingerprints": [ + "69:36:21:B8:B9:5D:F2:C6:0F:5A:B9:AE:36:35:89:CD:0C:36:51:FE", + "65:D6:85:5D:3B:55:33:25:2B:32:97:A7:02:F6:BA:A7:76:F3:6C:4B" + ] + } +}] \ No newline at end of file diff --git a/IntelliQ/backend/src/main/webapp/static/css/materialize.css b/IntelliQ/backend/src/main/webapp/static/css/materialize.css index f0cde86..81041d2 100644 --- a/IntelliQ/backend/src/main/webapp/static/css/materialize.css +++ b/IntelliQ/backend/src/main/webapp/static/css/materialize.css @@ -1,5 +1,5 @@ /*! - * Materialize v0.97.6 (http://materializecss.com) + * Materialize v0.97.7 (http://materializecss.com) * Copyright 2014-2015 Materialize * MIT License (https://raw.githubusercontent.com/Dogfalo/materialize/master/LICENSE) */ @@ -2115,30 +2115,6 @@ color: #212121 !important; } -.shades.black { - background-color: #000000 !important; -} - -.shades-text.text-black { - color: #000000 !important; -} - -.shades.white { - background-color: #FFFFFF !important; -} - -.shades-text.text-white { - color: #FFFFFF !important; -} - -.shades.transparent { - background-color: transparent !important; -} - -.shades-text.text-transparent { - color: transparent !important; -} - .black { background-color: #000000 !important; } @@ -2645,13 +2621,19 @@ html { } ul { + padding: 0; list-style-type: none; } -ul.browser-default { +ul.browser-default, +ul.browser-default li { list-style-type: initial; } +ul li { + list-style-type: none; +} + a { color: #039be5; text-decoration: none; @@ -2671,14 +2653,6 @@ a { display: block; } -ul { - padding: 0; -} - -ul li { - list-style-type: none; -} - .clearfix { clear: both; } @@ -2771,15 +2745,18 @@ video.responsive-video { .pagination li { display: inline-block; - font-size: 1.2rem; - padding: 0 10px; - line-height: 30px; border-radius: 2px; text-align: center; + vertical-align: top; + height: 30px; } .pagination li a { color: #444; + display: inline-block; + font-size: 1.2rem; + padding: 0 10px; + line-height: 30px; } .pagination li.active a { @@ -2796,8 +2773,7 @@ video.responsive-video { } .pagination li i { - font-size: 2.2rem; - vertical-align: middle; + font-size: 2rem; } .pagination li.pages ul li { @@ -3232,6 +3208,10 @@ span.badge.new:after { content: " new"; } +span.badge[data-badge-caption]::after { + content: " " attr(data-badge-caption); +} + nav ul a span.badge { position: static; margin-left: 4px; @@ -3484,6 +3464,7 @@ input[type=range] + .thumb { float: left; box-sizing: border-box; padding: 0 0.75rem; + min-height: 1px; } .row .col[class*="push-"], .row .col[class*="pull-"] { @@ -4169,6 +4150,13 @@ nav .brand-logo.right { padding: 0; } +nav .brand-logo i, +nav .brand-logo [class^="mdi-"], nav .brand-logo [class*="mdi-"], +nav .brand-logo i.material-icons { + float: left; + margin-right: 15px; +} + nav ul { margin: 0; } @@ -4187,7 +4175,7 @@ nav ul a { transition: background-color .3s; font-size: 1rem; color: #fff; - display: inline-block; + display: block; padding: 0 15px; cursor: pointer; } @@ -4206,8 +4194,13 @@ nav ul.left { float: left; } +nav form { + height: 100%; +} + nav .input-field { margin: 0; + height: 100%; } nav .input-field input { @@ -4560,8 +4553,12 @@ small { overflow: hidden; } -.card.small .card-content, .card.medium .card-content, .card.large .card-content { +.card.small .card-image + .card-content, .card.medium .card-image + .card-content, .card.large .card-image + .card-content { max-height: 40%; +} + +.card.small .card-content, .card.medium .card-content, .card.large .card-content { + max-height: 100%; overflow: hidden; } @@ -4584,6 +4581,59 @@ small { height: 500px; } +.card.horizontal { + display: -webkit-flex; + display: -ms-flexbox; + display: flex; +} + +.card.horizontal.small .card-image, .card.horizontal.medium .card-image, .card.horizontal.large .card-image { + height: 100%; + max-height: none; + overflow: visible; +} + +.card.horizontal.small .card-image img, .card.horizontal.medium .card-image img, .card.horizontal.large .card-image img { + height: 100%; +} + +.card.horizontal .card-image { + max-width: 50%; +} + +.card.horizontal .card-image img { + max-width: 100%; + width: auto; +} + +.card.horizontal .card-stacked { + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-flex-direction: column; + -ms-flex-direction: column; + flex-direction: column; + -webkit-flex: 1; + -ms-flex: 1; + flex: 1; + position: relative; +} + +.card.horizontal .card-stacked .card-content { + -webkit-flex-grow: 1; + -ms-flex-positive: 1; + flex-grow: 1; +} + +.card.sticky-action .card-action { + z-index: 2; +} + +.card.sticky-action .card-reveal { + z-index: 1; + padding-bottom: 64px; +} + .card .card-image { position: relative; } @@ -4626,7 +4676,6 @@ small { background-color: inherit; border-top: 1px solid rgba(160, 160, 160, 0.2); padding: 20px; - z-index: 2; } .card .card-action a:not(.btn):not(.btn-large):not(.btn-floating) { @@ -4640,11 +4689,6 @@ small { color: #ffd8a6; } -.card .card-action + .card-reveal { - z-index: 1; - padding-bottom: 64px; -} - .card .card-reveal { padding: 20px; position: absolute; @@ -4826,7 +4870,6 @@ small { left: 0; top: 0; pointer-events: none; - will-change: top, left; } .backdrop { @@ -4835,12 +4878,13 @@ small { display: none; height: 7px; width: 14px; - border-radius: 0 0 14px 14px; + border-radius: 0 0 50% 50%; background-color: #323232; z-index: -1; - -webkit-transform-origin: 50% 10%; - transform-origin: 50% 10%; - will-change: transform, opacity; + -webkit-transform-origin: 50% 0%; + transform-origin: 50% 0%; + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); } .btn, .btn-large, @@ -4860,9 +4904,14 @@ small { .btn.disabled, .disabled.btn-large, .btn-floating.disabled, .btn-large.disabled, -.btn:disabled -.btn-large:disabled, .btn-large:disabled .btn-large:disabled, -.btn-floating:disabled { +.btn:disabled, +.btn-large:disabled, +.btn-large:disabled, +.btn-floating:disabled, +.btn[disabled], +[disabled].btn-large, +.btn-large[disabled], +.btn-floating[disabled] { background-color: #DFDFDF !important; box-shadow: none; color: #9F9F9F !important; @@ -4872,18 +4921,26 @@ small { .btn.disabled *, .disabled.btn-large *, .btn-floating.disabled *, .btn-large.disabled *, -.btn:disabled -.btn-large:disabled *, .btn-large:disabled .btn-large:disabled *, -.btn-floating:disabled * { +.btn:disabled *, .btn-large:disabled *, +.btn-large:disabled *, +.btn-floating:disabled *, +.btn[disabled] *, [disabled].btn-large *, +.btn-large[disabled] *, +.btn-floating[disabled] * { pointer-events: none; } .btn.disabled:hover, .disabled.btn-large:hover, .btn-floating.disabled:hover, .btn-large.disabled:hover, -.btn:disabled -.btn-large:disabled:hover, .btn-large:disabled .btn-large:disabled:hover, -.btn-floating:disabled:hover { +.btn:disabled:hover, +.btn-large:disabled:hover, +.btn-large:disabled:hover, +.btn-floating:disabled:hover, +.btn[disabled]:hover, +[disabled].btn-large:hover, +.btn-large[disabled]:hover, +.btn-floating[disabled]:hover { background-color: #DFDFDF !important; color: #9F9F9F !important; } @@ -5014,6 +5071,16 @@ button.btn-floating { background-color: transparent; color: #343434; cursor: pointer; + transition: background-color .2s; +} + +.btn-flat:focus, .btn-flat:active { + background-color: transparent; +} + +.btn-flat:hover { + background-color: rgba(0, 0, 0, 0.1); + box-shadow: none; } .btn-flat.disabled { @@ -5168,6 +5235,11 @@ button.btn-floating { background: none; } +.waves-effect img { + position: relative; + z-index: -1; +} + .waves-notransition { transition: none !important; } @@ -5204,7 +5276,7 @@ button.btn-floating { } /* Firefox Bug: link not triggered */ -a.waves-effect .waves-ripple { +.waves-effect .waves-ripple { z-index: -1; } @@ -5352,7 +5424,7 @@ a.waves-effect .waves-ripple { border: none; line-height: inherit; height: inherit; - padding: 0 30px; + padding: 0 16px; } .side-nav .collapsible-header:hover, @@ -5373,7 +5445,7 @@ a.waves-effect .waves-ripple { .side-nav .collapsible-body li a, .side-nav.fixed .collapsible-body li a { - padding: 0 37.5px 0 45px; + padding: 0 23.5px 0 31px; } .collapsible.popout { @@ -5402,6 +5474,8 @@ a.waves-effect .waves-ripple { padding: 0 12px; border-radius: 16px; background-color: #e4e4e4; + margin-bottom: 5px; + margin-right: 5px; } .chip img { @@ -5412,7 +5486,7 @@ a.waves-effect .waves-ripple { border-radius: 50%; } -.chip i.material-icons { +.chip .close { cursor: pointer; float: right; font-size: 16px; @@ -5420,6 +5494,51 @@ a.waves-effect .waves-ripple { padding-left: 8px; } +.chips { + border: none; + border-bottom: 1px solid #9e9e9e; + box-shadow: none; + margin-bottom: 30px; + min-height: 45px; + outline: none; + padding-bottom: 5px; + transition: all .3s; +} + +.chips.focus { + border-bottom: 1px solid #26a69a; + box-shadow: 0 1px 0 0 #26a69a; +} + +.chips:hover { + cursor: text; +} + +.chips .chip.selected { + background-color: #26a69a; + color: #fff; +} + +.chips .input { + background: none; + border: 0; + color: rgba(0, 0, 0, 0.6); + display: inline-block; + font-size: 13px; + font-weight: 500; + height: 32px; + margin-right: 20px; + line-height: 32px; + outline: 0; + padding: 0 !important; + width: 120px !important; +} + +.chips .input:focus { + border: 0 !important; + box-shadow: none !important; +} + .materialboxed { display: block; cursor: -webkit-zoom-in; @@ -5523,7 +5642,7 @@ textarea.materialize-textarea { height: 3rem; width: 100%; font-size: 1rem; - margin: 0 0 15px 0; + margin: 0 0 20px 0; padding: 0; box-shadow: none; box-sizing: content-box; @@ -5774,7 +5893,7 @@ textarea.materialize-textarea + label:after { display: block; content: ""; position: absolute; - top: 65px; + top: 60px; opacity: 0; transition: .2s opacity ease-out, .2s color ease-out; } @@ -5784,11 +5903,19 @@ textarea.materialize-textarea + label:after { margin-top: 1rem; } +.input-field.col label { + left: 0.75rem; +} + +.input-field.col .prefix ~ label, +.input-field.col .prefix ~ .validate ~ label { + width: calc(100% - 3rem - 1.5rem); +} + .input-field label { color: #9e9e9e; position: absolute; top: 0.8rem; - left: 0.75rem; font-size: 1rem; cursor: text; transition: .2s ease-out; @@ -5812,16 +5939,15 @@ textarea.materialize-textarea + label:after { } .input-field .prefix ~ input, -.input-field .prefix ~ textarea { +.input-field .prefix ~ textarea, +.input-field .prefix ~ label, +.input-field .prefix ~ .validate ~ label, +.input-field .prefix ~ .autocomplete-content { margin-left: 3rem; width: 92%; width: calc(100% - 3rem); } -.input-field .prefix ~ textarea { - padding-top: .8rem; -} - .input-field .prefix ~ label { margin-left: 3rem; } @@ -5886,7 +6012,7 @@ textarea { textarea.materialize-textarea { overflow-y: hidden; /* prevents scroll bar flash */ - padding: 1.6rem 0; + padding: .8rem 0 1.6rem 0; /* prevents text jump on Enter keypress */ resize: none; min-height: 3rem; @@ -5902,6 +6028,24 @@ textarea.materialize-textarea { /* prevents text jump on Enter keypress */ } +/* Autocomplete */ +.autocomplete-content { + margin-top: -15px; + display: block; + opacity: 1; + position: static; +} + +.autocomplete-content li .highlight { + color: #444; +} + +.autocomplete-content li img { + height: 40px; + width: 40px; + margin: 5px 15px; +} + /* Radio Buttons ========================================================================== */ [type="radio"]:not(:checked), @@ -6351,7 +6495,7 @@ select { line-height: 3rem; width: 100%; font-size: 1rem; - margin: 0 0 15px 0; + margin: 0 0 20px 0; padding: 0; display: block; } @@ -6661,7 +6805,7 @@ input[type=range]:focus::-ms-fill-upper { .side-nav { position: fixed; - width: 240px; + width: 300px; left: 0; top: 0; margin: 0; @@ -6698,7 +6842,7 @@ input[type=range]:focus::-ms-fill-upper { .side-nav li { float: none; - line-height: 64px; + line-height: 48px; } .side-nav li.active { @@ -6706,12 +6850,13 @@ input[type=range]:focus::-ms-fill-upper { } .side-nav a { - color: #444; + color: rgba(0, 0, 0, 0.87); display: block; - font-size: 1rem; - height: 64px; - line-height: 64px; - padding: 0 30px; + font-size: 14px; + font-weight: 500; + height: 48px; + line-height: 48px; + padding: 0 32px; } .side-nav a:hover { @@ -6738,6 +6883,83 @@ input[type=range]:focus::-ms-fill-upper { background-color: #26a69a; } +.side-nav li > a > i, +.side-nav li > a > [class^="mdi-"], .side-nav li > a > [class*="mdi-"], +.side-nav li > a > i.material-icons { + float: left; + line-height: 48px; + margin: 0 32px 0 0; + width: 24px; + color: rgba(0, 0, 0, 0.54); +} + +.side-nav .divider { + margin: 8px 0 0 0; +} + +.side-nav .subheader { + cursor: initial; + pointer-events: none; + color: rgba(0, 0, 0, 0.54); + font-size: 14px; + font-weight: 500; + line-height: 48px; +} + +.side-nav .subheader:hover { + background-color: transparent; +} + +.side-nav .userView { + overflow: hidden; + position: relative; + padding: 32px 32px 0; + margin-bottom: 8px; +} + +.side-nav .userView a { + height: auto; + padding: 0; +} + +.side-nav .userView a:hover { + background-color: transparent; +} + +.side-nav .userView .background { + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + z-index: -1; +} + +.side-nav .userView .circle, .side-nav .userView .name, .side-nav .userView .email { + display: block; +} + +.side-nav .userView .circle { + height: 64px; + width: 64px; +} + +.side-nav .userView .name, +.side-nav .userView .email { + font-weight: 14px; + line-height: 24px; +} + +.side-nav .userView .name { + margin-top: 16px; + font-weight: 500; +} + +.side-nav .userView .email { + padding-bottom: 16px; + font-weight: 400; +} + .drag-target { height: 100%; width: 10px; @@ -6748,8 +6970,8 @@ input[type=range]:focus::-ms-fill-upper { .side-nav.fixed a { display: block; - padding: 0 30px; - color: #444; + padding: 0 16px; + color: rgba(0, 0, 0, 0.87); } .side-nav.fixed { @@ -6773,6 +6995,12 @@ input[type=range]:focus::-ms-fill-upper { -webkit-transform: translateX(105%); transform: translateX(105%); } + .side-nav a { + padding: 0 16px; + } + .side-nav .userView { + padding: 16px 16px 0; + } } .side-nav .collapsible-body li.active, @@ -7414,31 +7642,81 @@ input[type=range]:focus::-ms-fill-upper { transform-origin: 0% 50%; } -.carousel .carousel-item { - width: 200px; - position: absolute; +.carousel.carousel-slider { top: 0; left: 0; + height: 0; } -.carousel .carousel-item img { - width: 100%; +.carousel.carousel-slider .carousel-fixed-item { + position: absolute; + left: 0; + right: 0; + bottom: 20px; + z-index: 1; } -.carousel.carousel-slider { - top: 0; - left: 0; - height: 0; +.carousel.carousel-slider .carousel-fixed-item.with-indicators { + bottom: 68px; } .carousel.carousel-slider .carousel-item { width: 100%; height: 100%; + min-height: 400px; position: absolute; top: 0; left: 0; } +.carousel.carousel-slider .carousel-item h2 { + font-size: 24px; + font-weight: 500; + line-height: 32px; +} + +.carousel.carousel-slider .carousel-item p { + font-size: 15px; +} + +.carousel .carousel-item { + display: none; + width: 200px; + height: 400px; + position: absolute; + top: 0; + left: 0; +} + +.carousel .carousel-item img { + width: 100%; +} + +.carousel .indicators { + position: absolute; + text-align: center; + left: 0; + right: 0; + bottom: 0; + margin: 0; +} + +.carousel .indicators .indicator-item { + display: inline-block; + position: relative; + cursor: pointer; + height: 8px; + width: 8px; + margin: 24px 4px; + background-color: rgba(255, 255, 255, 0.5); + transition: background-color .3s; + border-radius: 50%; +} + +.carousel .indicators .indicator-item.active { + background-color: #fff; +} + /* ========================================================================== $BASE-PICKER ========================================================================== */ diff --git a/IntelliQ/backend/src/main/webapp/static/css/materialize.min.css b/IntelliQ/backend/src/main/webapp/static/css/materialize.min.css index 105f576..b98c20e 100644 --- a/IntelliQ/backend/src/main/webapp/static/css/materialize.min.css +++ b/IntelliQ/backend/src/main/webapp/static/css/materialize.min.css @@ -1,16 +1,16 @@ /*! - * Materialize v0.97.6 (http://materializecss.com) + * Materialize v0.97.7 (http://materializecss.com) * Copyright 2014-2015 Materialize * MIT License (https://raw.githubusercontent.com/Dogfalo/materialize/master/LICENSE) */ -.materialize-red{background-color:#e51c23 !important}.materialize-red-text{color:#e51c23 !important}.materialize-red.lighten-5{background-color:#fdeaeb !important}.materialize-red-text.text-lighten-5{color:#fdeaeb !important}.materialize-red.lighten-4{background-color:#f8c1c3 !important}.materialize-red-text.text-lighten-4{color:#f8c1c3 !important}.materialize-red.lighten-3{background-color:#f3989b !important}.materialize-red-text.text-lighten-3{color:#f3989b !important}.materialize-red.lighten-2{background-color:#ee6e73 !important}.materialize-red-text.text-lighten-2{color:#ee6e73 !important}.materialize-red.lighten-1{background-color:#ea454b !important}.materialize-red-text.text-lighten-1{color:#ea454b !important}.materialize-red.darken-1{background-color:#d0181e !important}.materialize-red-text.text-darken-1{color:#d0181e !important}.materialize-red.darken-2{background-color:#b9151b !important}.materialize-red-text.text-darken-2{color:#b9151b !important}.materialize-red.darken-3{background-color:#a21318 !important}.materialize-red-text.text-darken-3{color:#a21318 !important}.materialize-red.darken-4{background-color:#8b1014 !important}.materialize-red-text.text-darken-4{color:#8b1014 !important}.red{background-color:#F44336 !important}.red-text{color:#F44336 !important}.red.lighten-5{background-color:#FFEBEE !important}.red-text.text-lighten-5{color:#FFEBEE !important}.red.lighten-4{background-color:#FFCDD2 !important}.red-text.text-lighten-4{color:#FFCDD2 !important}.red.lighten-3{background-color:#EF9A9A !important}.red-text.text-lighten-3{color:#EF9A9A !important}.red.lighten-2{background-color:#E57373 !important}.red-text.text-lighten-2{color:#E57373 !important}.red.lighten-1{background-color:#EF5350 !important}.red-text.text-lighten-1{color:#EF5350 !important}.red.darken-1{background-color:#E53935 !important}.red-text.text-darken-1{color:#E53935 !important}.red.darken-2{background-color:#D32F2F !important}.red-text.text-darken-2{color:#D32F2F !important}.red.darken-3{background-color:#C62828 !important}.red-text.text-darken-3{color:#C62828 !important}.red.darken-4{background-color:#B71C1C !important}.red-text.text-darken-4{color:#B71C1C !important}.red.accent-1{background-color:#FF8A80 !important}.red-text.text-accent-1{color:#FF8A80 !important}.red.accent-2{background-color:#FF5252 !important}.red-text.text-accent-2{color:#FF5252 !important}.red.accent-3{background-color:#FF1744 !important}.red-text.text-accent-3{color:#FF1744 !important}.red.accent-4{background-color:#D50000 !important}.red-text.text-accent-4{color:#D50000 !important}.pink{background-color:#e91e63 !important}.pink-text{color:#e91e63 !important}.pink.lighten-5{background-color:#fce4ec !important}.pink-text.text-lighten-5{color:#fce4ec !important}.pink.lighten-4{background-color:#f8bbd0 !important}.pink-text.text-lighten-4{color:#f8bbd0 !important}.pink.lighten-3{background-color:#f48fb1 !important}.pink-text.text-lighten-3{color:#f48fb1 !important}.pink.lighten-2{background-color:#f06292 !important}.pink-text.text-lighten-2{color:#f06292 !important}.pink.lighten-1{background-color:#ec407a !important}.pink-text.text-lighten-1{color:#ec407a !important}.pink.darken-1{background-color:#d81b60 !important}.pink-text.text-darken-1{color:#d81b60 !important}.pink.darken-2{background-color:#c2185b !important}.pink-text.text-darken-2{color:#c2185b !important}.pink.darken-3{background-color:#ad1457 !important}.pink-text.text-darken-3{color:#ad1457 !important}.pink.darken-4{background-color:#880e4f !important}.pink-text.text-darken-4{color:#880e4f !important}.pink.accent-1{background-color:#ff80ab !important}.pink-text.text-accent-1{color:#ff80ab !important}.pink.accent-2{background-color:#ff4081 !important}.pink-text.text-accent-2{color:#ff4081 !important}.pink.accent-3{background-color:#f50057 !important}.pink-text.text-accent-3{color:#f50057 !important}.pink.accent-4{background-color:#c51162 !important}.pink-text.text-accent-4{color:#c51162 !important}.purple{background-color:#9c27b0 !important}.purple-text{color:#9c27b0 !important}.purple.lighten-5{background-color:#f3e5f5 !important}.purple-text.text-lighten-5{color:#f3e5f5 !important}.purple.lighten-4{background-color:#e1bee7 !important}.purple-text.text-lighten-4{color:#e1bee7 !important}.purple.lighten-3{background-color:#ce93d8 !important}.purple-text.text-lighten-3{color:#ce93d8 !important}.purple.lighten-2{background-color:#ba68c8 !important}.purple-text.text-lighten-2{color:#ba68c8 !important}.purple.lighten-1{background-color:#ab47bc !important}.purple-text.text-lighten-1{color:#ab47bc !important}.purple.darken-1{background-color:#8e24aa !important}.purple-text.text-darken-1{color:#8e24aa !important}.purple.darken-2{background-color:#7b1fa2 !important}.purple-text.text-darken-2{color:#7b1fa2 !important}.purple.darken-3{background-color:#6a1b9a !important}.purple-text.text-darken-3{color:#6a1b9a !important}.purple.darken-4{background-color:#4a148c !important}.purple-text.text-darken-4{color:#4a148c !important}.purple.accent-1{background-color:#ea80fc !important}.purple-text.text-accent-1{color:#ea80fc !important}.purple.accent-2{background-color:#e040fb !important}.purple-text.text-accent-2{color:#e040fb !important}.purple.accent-3{background-color:#d500f9 !important}.purple-text.text-accent-3{color:#d500f9 !important}.purple.accent-4{background-color:#a0f !important}.purple-text.text-accent-4{color:#a0f !important}.deep-purple{background-color:#673ab7 !important}.deep-purple-text{color:#673ab7 !important}.deep-purple.lighten-5{background-color:#ede7f6 !important}.deep-purple-text.text-lighten-5{color:#ede7f6 !important}.deep-purple.lighten-4{background-color:#d1c4e9 !important}.deep-purple-text.text-lighten-4{color:#d1c4e9 !important}.deep-purple.lighten-3{background-color:#b39ddb !important}.deep-purple-text.text-lighten-3{color:#b39ddb !important}.deep-purple.lighten-2{background-color:#9575cd !important}.deep-purple-text.text-lighten-2{color:#9575cd !important}.deep-purple.lighten-1{background-color:#7e57c2 !important}.deep-purple-text.text-lighten-1{color:#7e57c2 !important}.deep-purple.darken-1{background-color:#5e35b1 !important}.deep-purple-text.text-darken-1{color:#5e35b1 !important}.deep-purple.darken-2{background-color:#512da8 !important}.deep-purple-text.text-darken-2{color:#512da8 !important}.deep-purple.darken-3{background-color:#4527a0 !important}.deep-purple-text.text-darken-3{color:#4527a0 !important}.deep-purple.darken-4{background-color:#311b92 !important}.deep-purple-text.text-darken-4{color:#311b92 !important}.deep-purple.accent-1{background-color:#b388ff !important}.deep-purple-text.text-accent-1{color:#b388ff !important}.deep-purple.accent-2{background-color:#7c4dff !important}.deep-purple-text.text-accent-2{color:#7c4dff !important}.deep-purple.accent-3{background-color:#651fff !important}.deep-purple-text.text-accent-3{color:#651fff !important}.deep-purple.accent-4{background-color:#6200ea !important}.deep-purple-text.text-accent-4{color:#6200ea !important}.indigo{background-color:#3f51b5 !important}.indigo-text{color:#3f51b5 !important}.indigo.lighten-5{background-color:#e8eaf6 !important}.indigo-text.text-lighten-5{color:#e8eaf6 !important}.indigo.lighten-4{background-color:#c5cae9 !important}.indigo-text.text-lighten-4{color:#c5cae9 !important}.indigo.lighten-3{background-color:#9fa8da !important}.indigo-text.text-lighten-3{color:#9fa8da !important}.indigo.lighten-2{background-color:#7986cb !important}.indigo-text.text-lighten-2{color:#7986cb !important}.indigo.lighten-1{background-color:#5c6bc0 !important}.indigo-text.text-lighten-1{color:#5c6bc0 !important}.indigo.darken-1{background-color:#3949ab !important}.indigo-text.text-darken-1{color:#3949ab !important}.indigo.darken-2{background-color:#303f9f !important}.indigo-text.text-darken-2{color:#303f9f !important}.indigo.darken-3{background-color:#283593 !important}.indigo-text.text-darken-3{color:#283593 !important}.indigo.darken-4{background-color:#1a237e !important}.indigo-text.text-darken-4{color:#1a237e !important}.indigo.accent-1{background-color:#8c9eff !important}.indigo-text.text-accent-1{color:#8c9eff !important}.indigo.accent-2{background-color:#536dfe !important}.indigo-text.text-accent-2{color:#536dfe !important}.indigo.accent-3{background-color:#3d5afe !important}.indigo-text.text-accent-3{color:#3d5afe !important}.indigo.accent-4{background-color:#304ffe !important}.indigo-text.text-accent-4{color:#304ffe !important}.blue{background-color:#2196F3 !important}.blue-text{color:#2196F3 !important}.blue.lighten-5{background-color:#E3F2FD !important}.blue-text.text-lighten-5{color:#E3F2FD !important}.blue.lighten-4{background-color:#BBDEFB !important}.blue-text.text-lighten-4{color:#BBDEFB !important}.blue.lighten-3{background-color:#90CAF9 !important}.blue-text.text-lighten-3{color:#90CAF9 !important}.blue.lighten-2{background-color:#64B5F6 !important}.blue-text.text-lighten-2{color:#64B5F6 !important}.blue.lighten-1{background-color:#42A5F5 !important}.blue-text.text-lighten-1{color:#42A5F5 !important}.blue.darken-1{background-color:#1E88E5 !important}.blue-text.text-darken-1{color:#1E88E5 !important}.blue.darken-2{background-color:#1976D2 !important}.blue-text.text-darken-2{color:#1976D2 !important}.blue.darken-3{background-color:#1565C0 !important}.blue-text.text-darken-3{color:#1565C0 !important}.blue.darken-4{background-color:#0D47A1 !important}.blue-text.text-darken-4{color:#0D47A1 !important}.blue.accent-1{background-color:#82B1FF !important}.blue-text.text-accent-1{color:#82B1FF !important}.blue.accent-2{background-color:#448AFF !important}.blue-text.text-accent-2{color:#448AFF !important}.blue.accent-3{background-color:#2979FF !important}.blue-text.text-accent-3{color:#2979FF !important}.blue.accent-4{background-color:#2962FF !important}.blue-text.text-accent-4{color:#2962FF !important}.light-blue{background-color:#03a9f4 !important}.light-blue-text{color:#03a9f4 !important}.light-blue.lighten-5{background-color:#e1f5fe !important}.light-blue-text.text-lighten-5{color:#e1f5fe !important}.light-blue.lighten-4{background-color:#b3e5fc !important}.light-blue-text.text-lighten-4{color:#b3e5fc !important}.light-blue.lighten-3{background-color:#81d4fa !important}.light-blue-text.text-lighten-3{color:#81d4fa !important}.light-blue.lighten-2{background-color:#4fc3f7 !important}.light-blue-text.text-lighten-2{color:#4fc3f7 !important}.light-blue.lighten-1{background-color:#29b6f6 !important}.light-blue-text.text-lighten-1{color:#29b6f6 !important}.light-blue.darken-1{background-color:#039be5 !important}.light-blue-text.text-darken-1{color:#039be5 !important}.light-blue.darken-2{background-color:#0288d1 !important}.light-blue-text.text-darken-2{color:#0288d1 !important}.light-blue.darken-3{background-color:#0277bd !important}.light-blue-text.text-darken-3{color:#0277bd !important}.light-blue.darken-4{background-color:#01579b !important}.light-blue-text.text-darken-4{color:#01579b !important}.light-blue.accent-1{background-color:#80d8ff !important}.light-blue-text.text-accent-1{color:#80d8ff !important}.light-blue.accent-2{background-color:#40c4ff !important}.light-blue-text.text-accent-2{color:#40c4ff !important}.light-blue.accent-3{background-color:#00b0ff !important}.light-blue-text.text-accent-3{color:#00b0ff !important}.light-blue.accent-4{background-color:#0091ea !important}.light-blue-text.text-accent-4{color:#0091ea !important}.cyan{background-color:#00bcd4 !important}.cyan-text{color:#00bcd4 !important}.cyan.lighten-5{background-color:#e0f7fa !important}.cyan-text.text-lighten-5{color:#e0f7fa !important}.cyan.lighten-4{background-color:#b2ebf2 !important}.cyan-text.text-lighten-4{color:#b2ebf2 !important}.cyan.lighten-3{background-color:#80deea !important}.cyan-text.text-lighten-3{color:#80deea !important}.cyan.lighten-2{background-color:#4dd0e1 !important}.cyan-text.text-lighten-2{color:#4dd0e1 !important}.cyan.lighten-1{background-color:#26c6da !important}.cyan-text.text-lighten-1{color:#26c6da !important}.cyan.darken-1{background-color:#00acc1 !important}.cyan-text.text-darken-1{color:#00acc1 !important}.cyan.darken-2{background-color:#0097a7 !important}.cyan-text.text-darken-2{color:#0097a7 !important}.cyan.darken-3{background-color:#00838f !important}.cyan-text.text-darken-3{color:#00838f !important}.cyan.darken-4{background-color:#006064 !important}.cyan-text.text-darken-4{color:#006064 !important}.cyan.accent-1{background-color:#84ffff !important}.cyan-text.text-accent-1{color:#84ffff !important}.cyan.accent-2{background-color:#18ffff !important}.cyan-text.text-accent-2{color:#18ffff !important}.cyan.accent-3{background-color:#00e5ff !important}.cyan-text.text-accent-3{color:#00e5ff !important}.cyan.accent-4{background-color:#00b8d4 !important}.cyan-text.text-accent-4{color:#00b8d4 !important}.teal{background-color:#009688 !important}.teal-text{color:#009688 !important}.teal.lighten-5{background-color:#e0f2f1 !important}.teal-text.text-lighten-5{color:#e0f2f1 !important}.teal.lighten-4{background-color:#b2dfdb !important}.teal-text.text-lighten-4{color:#b2dfdb !important}.teal.lighten-3{background-color:#80cbc4 !important}.teal-text.text-lighten-3{color:#80cbc4 !important}.teal.lighten-2{background-color:#4db6ac !important}.teal-text.text-lighten-2{color:#4db6ac !important}.teal.lighten-1{background-color:#26a69a !important}.teal-text.text-lighten-1{color:#26a69a !important}.teal.darken-1{background-color:#00897b !important}.teal-text.text-darken-1{color:#00897b !important}.teal.darken-2{background-color:#00796b !important}.teal-text.text-darken-2{color:#00796b !important}.teal.darken-3{background-color:#00695c !important}.teal-text.text-darken-3{color:#00695c !important}.teal.darken-4{background-color:#004d40 !important}.teal-text.text-darken-4{color:#004d40 !important}.teal.accent-1{background-color:#a7ffeb !important}.teal-text.text-accent-1{color:#a7ffeb !important}.teal.accent-2{background-color:#64ffda !important}.teal-text.text-accent-2{color:#64ffda !important}.teal.accent-3{background-color:#1de9b6 !important}.teal-text.text-accent-3{color:#1de9b6 !important}.teal.accent-4{background-color:#00bfa5 !important}.teal-text.text-accent-4{color:#00bfa5 !important}.green{background-color:#4CAF50 !important}.green-text{color:#4CAF50 !important}.green.lighten-5{background-color:#E8F5E9 !important}.green-text.text-lighten-5{color:#E8F5E9 !important}.green.lighten-4{background-color:#C8E6C9 !important}.green-text.text-lighten-4{color:#C8E6C9 !important}.green.lighten-3{background-color:#A5D6A7 !important}.green-text.text-lighten-3{color:#A5D6A7 !important}.green.lighten-2{background-color:#81C784 !important}.green-text.text-lighten-2{color:#81C784 !important}.green.lighten-1{background-color:#66BB6A !important}.green-text.text-lighten-1{color:#66BB6A !important}.green.darken-1{background-color:#43A047 !important}.green-text.text-darken-1{color:#43A047 !important}.green.darken-2{background-color:#388E3C !important}.green-text.text-darken-2{color:#388E3C !important}.green.darken-3{background-color:#2E7D32 !important}.green-text.text-darken-3{color:#2E7D32 !important}.green.darken-4{background-color:#1B5E20 !important}.green-text.text-darken-4{color:#1B5E20 !important}.green.accent-1{background-color:#B9F6CA !important}.green-text.text-accent-1{color:#B9F6CA !important}.green.accent-2{background-color:#69F0AE !important}.green-text.text-accent-2{color:#69F0AE !important}.green.accent-3{background-color:#00E676 !important}.green-text.text-accent-3{color:#00E676 !important}.green.accent-4{background-color:#00C853 !important}.green-text.text-accent-4{color:#00C853 !important}.light-green{background-color:#8bc34a !important}.light-green-text{color:#8bc34a !important}.light-green.lighten-5{background-color:#f1f8e9 !important}.light-green-text.text-lighten-5{color:#f1f8e9 !important}.light-green.lighten-4{background-color:#dcedc8 !important}.light-green-text.text-lighten-4{color:#dcedc8 !important}.light-green.lighten-3{background-color:#c5e1a5 !important}.light-green-text.text-lighten-3{color:#c5e1a5 !important}.light-green.lighten-2{background-color:#aed581 !important}.light-green-text.text-lighten-2{color:#aed581 !important}.light-green.lighten-1{background-color:#9ccc65 !important}.light-green-text.text-lighten-1{color:#9ccc65 !important}.light-green.darken-1{background-color:#7cb342 !important}.light-green-text.text-darken-1{color:#7cb342 !important}.light-green.darken-2{background-color:#689f38 !important}.light-green-text.text-darken-2{color:#689f38 !important}.light-green.darken-3{background-color:#558b2f !important}.light-green-text.text-darken-3{color:#558b2f !important}.light-green.darken-4{background-color:#33691e !important}.light-green-text.text-darken-4{color:#33691e !important}.light-green.accent-1{background-color:#ccff90 !important}.light-green-text.text-accent-1{color:#ccff90 !important}.light-green.accent-2{background-color:#b2ff59 !important}.light-green-text.text-accent-2{color:#b2ff59 !important}.light-green.accent-3{background-color:#76ff03 !important}.light-green-text.text-accent-3{color:#76ff03 !important}.light-green.accent-4{background-color:#64dd17 !important}.light-green-text.text-accent-4{color:#64dd17 !important}.lime{background-color:#cddc39 !important}.lime-text{color:#cddc39 !important}.lime.lighten-5{background-color:#f9fbe7 !important}.lime-text.text-lighten-5{color:#f9fbe7 !important}.lime.lighten-4{background-color:#f0f4c3 !important}.lime-text.text-lighten-4{color:#f0f4c3 !important}.lime.lighten-3{background-color:#e6ee9c !important}.lime-text.text-lighten-3{color:#e6ee9c !important}.lime.lighten-2{background-color:#dce775 !important}.lime-text.text-lighten-2{color:#dce775 !important}.lime.lighten-1{background-color:#d4e157 !important}.lime-text.text-lighten-1{color:#d4e157 !important}.lime.darken-1{background-color:#c0ca33 !important}.lime-text.text-darken-1{color:#c0ca33 !important}.lime.darken-2{background-color:#afb42b !important}.lime-text.text-darken-2{color:#afb42b !important}.lime.darken-3{background-color:#9e9d24 !important}.lime-text.text-darken-3{color:#9e9d24 !important}.lime.darken-4{background-color:#827717 !important}.lime-text.text-darken-4{color:#827717 !important}.lime.accent-1{background-color:#f4ff81 !important}.lime-text.text-accent-1{color:#f4ff81 !important}.lime.accent-2{background-color:#eeff41 !important}.lime-text.text-accent-2{color:#eeff41 !important}.lime.accent-3{background-color:#c6ff00 !important}.lime-text.text-accent-3{color:#c6ff00 !important}.lime.accent-4{background-color:#aeea00 !important}.lime-text.text-accent-4{color:#aeea00 !important}.yellow{background-color:#ffeb3b !important}.yellow-text{color:#ffeb3b !important}.yellow.lighten-5{background-color:#fffde7 !important}.yellow-text.text-lighten-5{color:#fffde7 !important}.yellow.lighten-4{background-color:#fff9c4 !important}.yellow-text.text-lighten-4{color:#fff9c4 !important}.yellow.lighten-3{background-color:#fff59d !important}.yellow-text.text-lighten-3{color:#fff59d !important}.yellow.lighten-2{background-color:#fff176 !important}.yellow-text.text-lighten-2{color:#fff176 !important}.yellow.lighten-1{background-color:#ffee58 !important}.yellow-text.text-lighten-1{color:#ffee58 !important}.yellow.darken-1{background-color:#fdd835 !important}.yellow-text.text-darken-1{color:#fdd835 !important}.yellow.darken-2{background-color:#fbc02d !important}.yellow-text.text-darken-2{color:#fbc02d !important}.yellow.darken-3{background-color:#f9a825 !important}.yellow-text.text-darken-3{color:#f9a825 !important}.yellow.darken-4{background-color:#f57f17 !important}.yellow-text.text-darken-4{color:#f57f17 !important}.yellow.accent-1{background-color:#ffff8d !important}.yellow-text.text-accent-1{color:#ffff8d !important}.yellow.accent-2{background-color:#ff0 !important}.yellow-text.text-accent-2{color:#ff0 !important}.yellow.accent-3{background-color:#ffea00 !important}.yellow-text.text-accent-3{color:#ffea00 !important}.yellow.accent-4{background-color:#ffd600 !important}.yellow-text.text-accent-4{color:#ffd600 !important}.amber{background-color:#ffc107 !important}.amber-text{color:#ffc107 !important}.amber.lighten-5{background-color:#fff8e1 !important}.amber-text.text-lighten-5{color:#fff8e1 !important}.amber.lighten-4{background-color:#ffecb3 !important}.amber-text.text-lighten-4{color:#ffecb3 !important}.amber.lighten-3{background-color:#ffe082 !important}.amber-text.text-lighten-3{color:#ffe082 !important}.amber.lighten-2{background-color:#ffd54f !important}.amber-text.text-lighten-2{color:#ffd54f !important}.amber.lighten-1{background-color:#ffca28 !important}.amber-text.text-lighten-1{color:#ffca28 !important}.amber.darken-1{background-color:#ffb300 !important}.amber-text.text-darken-1{color:#ffb300 !important}.amber.darken-2{background-color:#ffa000 !important}.amber-text.text-darken-2{color:#ffa000 !important}.amber.darken-3{background-color:#ff8f00 !important}.amber-text.text-darken-3{color:#ff8f00 !important}.amber.darken-4{background-color:#ff6f00 !important}.amber-text.text-darken-4{color:#ff6f00 !important}.amber.accent-1{background-color:#ffe57f !important}.amber-text.text-accent-1{color:#ffe57f !important}.amber.accent-2{background-color:#ffd740 !important}.amber-text.text-accent-2{color:#ffd740 !important}.amber.accent-3{background-color:#ffc400 !important}.amber-text.text-accent-3{color:#ffc400 !important}.amber.accent-4{background-color:#ffab00 !important}.amber-text.text-accent-4{color:#ffab00 !important}.orange{background-color:#ff9800 !important}.orange-text{color:#ff9800 !important}.orange.lighten-5{background-color:#fff3e0 !important}.orange-text.text-lighten-5{color:#fff3e0 !important}.orange.lighten-4{background-color:#ffe0b2 !important}.orange-text.text-lighten-4{color:#ffe0b2 !important}.orange.lighten-3{background-color:#ffcc80 !important}.orange-text.text-lighten-3{color:#ffcc80 !important}.orange.lighten-2{background-color:#ffb74d !important}.orange-text.text-lighten-2{color:#ffb74d !important}.orange.lighten-1{background-color:#ffa726 !important}.orange-text.text-lighten-1{color:#ffa726 !important}.orange.darken-1{background-color:#fb8c00 !important}.orange-text.text-darken-1{color:#fb8c00 !important}.orange.darken-2{background-color:#f57c00 !important}.orange-text.text-darken-2{color:#f57c00 !important}.orange.darken-3{background-color:#ef6c00 !important}.orange-text.text-darken-3{color:#ef6c00 !important}.orange.darken-4{background-color:#e65100 !important}.orange-text.text-darken-4{color:#e65100 !important}.orange.accent-1{background-color:#ffd180 !important}.orange-text.text-accent-1{color:#ffd180 !important}.orange.accent-2{background-color:#ffab40 !important}.orange-text.text-accent-2{color:#ffab40 !important}.orange.accent-3{background-color:#ff9100 !important}.orange-text.text-accent-3{color:#ff9100 !important}.orange.accent-4{background-color:#ff6d00 !important}.orange-text.text-accent-4{color:#ff6d00 !important}.deep-orange{background-color:#ff5722 !important}.deep-orange-text{color:#ff5722 !important}.deep-orange.lighten-5{background-color:#fbe9e7 !important}.deep-orange-text.text-lighten-5{color:#fbe9e7 !important}.deep-orange.lighten-4{background-color:#ffccbc !important}.deep-orange-text.text-lighten-4{color:#ffccbc !important}.deep-orange.lighten-3{background-color:#ffab91 !important}.deep-orange-text.text-lighten-3{color:#ffab91 !important}.deep-orange.lighten-2{background-color:#ff8a65 !important}.deep-orange-text.text-lighten-2{color:#ff8a65 !important}.deep-orange.lighten-1{background-color:#ff7043 !important}.deep-orange-text.text-lighten-1{color:#ff7043 !important}.deep-orange.darken-1{background-color:#f4511e !important}.deep-orange-text.text-darken-1{color:#f4511e !important}.deep-orange.darken-2{background-color:#e64a19 !important}.deep-orange-text.text-darken-2{color:#e64a19 !important}.deep-orange.darken-3{background-color:#d84315 !important}.deep-orange-text.text-darken-3{color:#d84315 !important}.deep-orange.darken-4{background-color:#bf360c !important}.deep-orange-text.text-darken-4{color:#bf360c !important}.deep-orange.accent-1{background-color:#ff9e80 !important}.deep-orange-text.text-accent-1{color:#ff9e80 !important}.deep-orange.accent-2{background-color:#ff6e40 !important}.deep-orange-text.text-accent-2{color:#ff6e40 !important}.deep-orange.accent-3{background-color:#ff3d00 !important}.deep-orange-text.text-accent-3{color:#ff3d00 !important}.deep-orange.accent-4{background-color:#dd2c00 !important}.deep-orange-text.text-accent-4{color:#dd2c00 !important}.brown{background-color:#795548 !important}.brown-text{color:#795548 !important}.brown.lighten-5{background-color:#efebe9 !important}.brown-text.text-lighten-5{color:#efebe9 !important}.brown.lighten-4{background-color:#d7ccc8 !important}.brown-text.text-lighten-4{color:#d7ccc8 !important}.brown.lighten-3{background-color:#bcaaa4 !important}.brown-text.text-lighten-3{color:#bcaaa4 !important}.brown.lighten-2{background-color:#a1887f !important}.brown-text.text-lighten-2{color:#a1887f !important}.brown.lighten-1{background-color:#8d6e63 !important}.brown-text.text-lighten-1{color:#8d6e63 !important}.brown.darken-1{background-color:#6d4c41 !important}.brown-text.text-darken-1{color:#6d4c41 !important}.brown.darken-2{background-color:#5d4037 !important}.brown-text.text-darken-2{color:#5d4037 !important}.brown.darken-3{background-color:#4e342e !important}.brown-text.text-darken-3{color:#4e342e !important}.brown.darken-4{background-color:#3e2723 !important}.brown-text.text-darken-4{color:#3e2723 !important}.blue-grey{background-color:#607d8b !important}.blue-grey-text{color:#607d8b !important}.blue-grey.lighten-5{background-color:#eceff1 !important}.blue-grey-text.text-lighten-5{color:#eceff1 !important}.blue-grey.lighten-4{background-color:#cfd8dc !important}.blue-grey-text.text-lighten-4{color:#cfd8dc !important}.blue-grey.lighten-3{background-color:#b0bec5 !important}.blue-grey-text.text-lighten-3{color:#b0bec5 !important}.blue-grey.lighten-2{background-color:#90a4ae !important}.blue-grey-text.text-lighten-2{color:#90a4ae !important}.blue-grey.lighten-1{background-color:#78909c !important}.blue-grey-text.text-lighten-1{color:#78909c !important}.blue-grey.darken-1{background-color:#546e7a !important}.blue-grey-text.text-darken-1{color:#546e7a !important}.blue-grey.darken-2{background-color:#455a64 !important}.blue-grey-text.text-darken-2{color:#455a64 !important}.blue-grey.darken-3{background-color:#37474f !important}.blue-grey-text.text-darken-3{color:#37474f !important}.blue-grey.darken-4{background-color:#263238 !important}.blue-grey-text.text-darken-4{color:#263238 !important}.grey{background-color:#9e9e9e !important}.grey-text{color:#9e9e9e !important}.grey.lighten-5{background-color:#fafafa !important}.grey-text.text-lighten-5{color:#fafafa !important}.grey.lighten-4{background-color:#f5f5f5 !important}.grey-text.text-lighten-4{color:#f5f5f5 !important}.grey.lighten-3{background-color:#eee !important}.grey-text.text-lighten-3{color:#eee !important}.grey.lighten-2{background-color:#e0e0e0 !important}.grey-text.text-lighten-2{color:#e0e0e0 !important}.grey.lighten-1{background-color:#bdbdbd !important}.grey-text.text-lighten-1{color:#bdbdbd !important}.grey.darken-1{background-color:#757575 !important}.grey-text.text-darken-1{color:#757575 !important}.grey.darken-2{background-color:#616161 !important}.grey-text.text-darken-2{color:#616161 !important}.grey.darken-3{background-color:#424242 !important}.grey-text.text-darken-3{color:#424242 !important}.grey.darken-4{background-color:#212121 !important}.grey-text.text-darken-4{color:#212121 !important}.shades.black{background-color:#000 !important}.shades-text.text-black{color:#000 !important}.shades.white{background-color:#fff !important}.shades-text.text-white{color:#fff !important}.shades.transparent{background-color:transparent !important}.shades-text.text-transparent{color:transparent !important}.black{background-color:#000 !important}.black-text{color:#000 !important}.white{background-color:#fff !important}.white-text{color:#fff !important}.transparent{background-color:transparent !important}.transparent-text{color:transparent !important}/*! normalize.css v3.0.3 | MIT License | github.com/necolas/normalize.css */html{font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}article,aside,details,figcaption,figure,footer,header,hgroup,main,menu,nav,section,summary{display:block}audio,canvas,progress,video{display:inline-block;vertical-align:baseline}audio:not([controls]){display:none;height:0}[hidden],template{display:none}a{background-color:transparent}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:bold}dfn{font-style:italic}h1{font-size:2em;margin:0.67em 0}mark{background:#ff0;color:#000}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-0.5em}sub{bottom:-0.25em}img{border:0}svg:not(:root){overflow:hidden}figure{margin:1em 40px}hr{box-sizing:content-box;height:0}pre{overflow:auto}code,kbd,pre,samp{font-family:monospace, monospace;font-size:1em}button,input,optgroup,select,textarea{color:inherit;font:inherit;margin:0}button{overflow:visible}button,select{text-transform:none}button,html input[type="button"],input[type="reset"],input[type="submit"]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}input{line-height:normal}input[type="checkbox"],input[type="radio"]{box-sizing:border-box;padding:0}input[type="number"]::-webkit-inner-spin-button,input[type="number"]::-webkit-outer-spin-button{height:auto}input[type="search"]{-webkit-appearance:textfield;box-sizing:content-box}input[type="search"]::-webkit-search-cancel-button,input[type="search"]::-webkit-search-decoration{-webkit-appearance:none}fieldset{border:1px solid #c0c0c0;margin:0 2px;padding:0.35em 0.625em 0.75em}legend{border:0;padding:0}textarea{overflow:auto}optgroup{font-weight:bold}table{border-collapse:collapse;border-spacing:0}td,th{padding:0}html{box-sizing:border-box}*,*:before,*:after{box-sizing:inherit}ul{list-style-type:none}ul.browser-default{list-style-type:initial}a{color:#039be5;text-decoration:none;-webkit-tap-highlight-color:transparent}.valign-wrapper{display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-align-items:center;-ms-flex-align:center;align-items:center}.valign-wrapper .valign{display:block}ul{padding:0}ul li{list-style-type:none}.clearfix{clear:both}.z-depth-0{box-shadow:none !important}.z-depth-1,nav,.card-panel,.card,.toast,.btn,.btn-large,.btn-floating,.dropdown-content,.collapsible,.side-nav{box-shadow:0 2px 5px 0 rgba(0,0,0,0.16),0 2px 10px 0 rgba(0,0,0,0.12)}.z-depth-1-half,.btn:hover,.btn-large:hover,.btn-floating:hover{box-shadow:0 5px 11px 0 rgba(0,0,0,0.18),0 4px 15px 0 rgba(0,0,0,0.15)}.z-depth-2{box-shadow:0 8px 17px 0 rgba(0,0,0,0.2),0 6px 20px 0 rgba(0,0,0,0.19)}.z-depth-3{box-shadow:0 12px 15px 0 rgba(0,0,0,0.24),0 17px 50px 0 rgba(0,0,0,0.19)}.z-depth-4,.modal{box-shadow:0 16px 28px 0 rgba(0,0,0,0.22),0 25px 55px 0 rgba(0,0,0,0.21)}.z-depth-5{box-shadow:0 27px 24px 0 rgba(0,0,0,0.2),0 40px 77px 0 rgba(0,0,0,0.22)}.hoverable{transition:box-shadow .25s;box-shadow:0}.hoverable:hover{transition:box-shadow .25s;box-shadow:0 8px 17px 0 rgba(0,0,0,0.2),0 6px 20px 0 rgba(0,0,0,0.19)}.divider{height:1px;overflow:hidden;background-color:#e0e0e0}blockquote{margin:20px 0;padding-left:1.5rem;border-left:5px solid #ee6e73}i{line-height:inherit}i.left{float:left;margin-right:15px}i.right{float:right;margin-left:15px}i.tiny{font-size:1rem}i.small{font-size:2rem}i.medium{font-size:4rem}i.large{font-size:6rem}img.responsive-img,video.responsive-video{max-width:100%;height:auto}.pagination li{display:inline-block;font-size:1.2rem;padding:0 10px;line-height:30px;border-radius:2px;text-align:center}.pagination li a{color:#444}.pagination li.active a{color:#fff}.pagination li.active{background-color:#ee6e73}.pagination li.disabled a{cursor:default;color:#999}.pagination li i{font-size:2.2rem;vertical-align:middle}.pagination li.pages ul li{display:inline-block;float:none}@media only screen and (max-width: 992px){.pagination{width:100%}.pagination li.prev,.pagination li.next{width:10%}.pagination li.pages{width:80%;overflow:hidden;white-space:nowrap}}.breadcrumb{font-size:18px;color:rgba(255,255,255,0.7)}.breadcrumb i,.breadcrumb [class^="mdi-"],.breadcrumb [class*="mdi-"],.breadcrumb i.material-icons{display:inline-block;float:left;font-size:24px}.breadcrumb:before{content:'\E5CC';color:rgba(255,255,255,0.7);vertical-align:top;display:inline-block;font-family:'Material Icons';font-weight:normal;font-style:normal;font-size:25px;margin:0 10px 0 8px;-webkit-font-smoothing:antialiased}.breadcrumb:first-child:before{display:none}.breadcrumb:last-child{color:#fff}.parallax-container{position:relative;overflow:hidden;height:500px}.parallax{position:absolute;top:0;left:0;right:0;bottom:0;z-index:-1}.parallax img{display:none;position:absolute;left:50%;bottom:0;min-width:100%;min-height:100%;-webkit-transform:translate3d(0, 0, 0);transform:translate3d(0, 0, 0);-webkit-transform:translateX(-50%);transform:translateX(-50%)}.pin-top,.pin-bottom{position:relative}.pinned{position:fixed !important}ul.staggered-list li{opacity:0}.fade-in{opacity:0;-webkit-transform-origin:0 50%;transform-origin:0 50%}@media only screen and (max-width: 600px){.hide-on-small-only,.hide-on-small-and-down{display:none !important}}@media only screen and (max-width: 992px){.hide-on-med-and-down{display:none !important}}@media only screen and (min-width: 601px){.hide-on-med-and-up{display:none !important}}@media only screen and (min-width: 600px) and (max-width: 992px){.hide-on-med-only{display:none !important}}@media only screen and (min-width: 993px){.hide-on-large-only{display:none !important}}@media only screen and (min-width: 993px){.show-on-large{display:block !important}}@media only screen and (min-width: 600px) and (max-width: 992px){.show-on-medium{display:block !important}}@media only screen and (max-width: 600px){.show-on-small{display:block !important}}@media only screen and (min-width: 601px){.show-on-medium-and-up{display:block !important}}@media only screen and (max-width: 992px){.show-on-medium-and-down{display:block !important}}@media only screen and (max-width: 600px){.center-on-small-only{text-align:center}}footer.page-footer{margin-top:20px;padding-top:20px;background-color:#ee6e73}footer.page-footer .footer-copyright{overflow:hidden;height:50px;line-height:50px;color:rgba(255,255,255,0.8);background-color:rgba(51,51,51,0.08)}table,th,td{border:none}table{width:100%;display:table}table.bordered>thead>tr,table.bordered>tbody>tr{border-bottom:1px solid #d0d0d0}table.striped>tbody>tr:nth-child(odd){background-color:#f2f2f2}table.striped>tbody>tr>td{border-radius:0}table.highlight>tbody>tr{transition:background-color .25s ease}table.highlight>tbody>tr:hover{background-color:#f2f2f2}table.centered thead tr th,table.centered tbody tr td{text-align:center}thead{border-bottom:1px solid #d0d0d0}td,th{padding:15px 5px;display:table-cell;text-align:left;vertical-align:middle;border-radius:2px}@media only screen and (max-width: 992px){table.responsive-table{width:100%;border-collapse:collapse;border-spacing:0;display:block;position:relative}table.responsive-table td:empty:before{content:'\00a0'}table.responsive-table th,table.responsive-table td{margin:0;vertical-align:top}table.responsive-table th{text-align:left}table.responsive-table thead{display:block;float:left}table.responsive-table thead tr{display:block;padding:0 10px 0 0}table.responsive-table thead tr th::before{content:"\00a0"}table.responsive-table tbody{display:block;width:auto;position:relative;overflow-x:auto;white-space:nowrap}table.responsive-table tbody tr{display:inline-block;vertical-align:top}table.responsive-table th{display:block;text-align:right}table.responsive-table td{display:block;min-height:1.25em;text-align:left}table.responsive-table tr{padding:0 10px}table.responsive-table thead{border:0;border-right:1px solid #d0d0d0}table.responsive-table.bordered th{border-bottom:0;border-left:0}table.responsive-table.bordered td{border-left:0;border-right:0;border-bottom:0}table.responsive-table.bordered tr{border:0}table.responsive-table.bordered tbody tr{border-right:1px solid #d0d0d0}}.collection{margin:0.5rem 0 1rem 0;border:1px solid #e0e0e0;border-radius:2px;overflow:hidden;position:relative}.collection .collection-item{background-color:#fff;line-height:1.5rem;padding:10px 20px;margin:0;border-bottom:1px solid #e0e0e0}.collection .collection-item.avatar{min-height:84px;padding-left:72px;position:relative}.collection .collection-item.avatar .circle{position:absolute;width:42px;height:42px;overflow:hidden;left:15px;display:inline-block;vertical-align:middle}.collection .collection-item.avatar i.circle{font-size:18px;line-height:42px;color:#fff;background-color:#999;text-align:center}.collection .collection-item.avatar .title{font-size:16px}.collection .collection-item.avatar p{margin:0}.collection .collection-item.avatar .secondary-content{position:absolute;top:16px;right:16px}.collection .collection-item:last-child{border-bottom:none}.collection .collection-item.active{background-color:#26a69a;color:#eafaf9}.collection .collection-item.active .secondary-content{color:#fff}.collection a.collection-item{display:block;transition:.25s;color:#26a69a}.collection a.collection-item:not(.active):hover{background-color:#ddd}.collection.with-header .collection-header{background-color:#fff;border-bottom:1px solid #e0e0e0;padding:10px 20px}.collection.with-header .collection-item{padding-left:30px}.collection.with-header .collection-item.avatar{padding-left:72px}.secondary-content{float:right;color:#26a69a}.collapsible .collection{margin:0;border:none}span.badge{min-width:3rem;padding:0 6px;text-align:center;font-size:1rem;line-height:inherit;color:#757575;position:absolute;right:15px;box-sizing:border-box}span.badge.new{font-weight:300;font-size:0.8rem;color:#fff;background-color:#26a69a;border-radius:2px}span.badge.new:after{content:" new"}nav ul a span.badge{position:static;margin-left:4px;line-height:0}.video-container{position:relative;padding-bottom:56.25%;height:0;overflow:hidden}.video-container iframe,.video-container object,.video-container embed{position:absolute;top:0;left:0;width:100%;height:100%}.progress{position:relative;height:4px;display:block;width:100%;background-color:#acece6;border-radius:2px;margin:0.5rem 0 1rem 0;overflow:hidden}.progress .determinate{position:absolute;top:0;left:0;bottom:0;background-color:#26a69a;transition:width .3s linear}.progress .indeterminate{background-color:#26a69a}.progress .indeterminate:before{content:'';position:absolute;background-color:inherit;top:0;left:0;bottom:0;will-change:left, right;-webkit-animation:indeterminate 2.1s cubic-bezier(0.65, 0.815, 0.735, 0.395) infinite;animation:indeterminate 2.1s cubic-bezier(0.65, 0.815, 0.735, 0.395) infinite}.progress .indeterminate:after{content:'';position:absolute;background-color:inherit;top:0;left:0;bottom:0;will-change:left, right;-webkit-animation:indeterminate-short 2.1s cubic-bezier(0.165, 0.84, 0.44, 1) infinite;animation:indeterminate-short 2.1s cubic-bezier(0.165, 0.84, 0.44, 1) infinite;-webkit-animation-delay:1.15s;animation-delay:1.15s}@-webkit-keyframes indeterminate{0%{left:-35%;right:100%}60%{left:100%;right:-90%}100%{left:100%;right:-90%}}@keyframes indeterminate{0%{left:-35%;right:100%}60%{left:100%;right:-90%}100%{left:100%;right:-90%}}@-webkit-keyframes indeterminate-short{0%{left:-200%;right:100%}60%{left:107%;right:-8%}100%{left:107%;right:-8%}}@keyframes indeterminate-short{0%{left:-200%;right:100%}60%{left:107%;right:-8%}100%{left:107%;right:-8%}}.hide{display:none !important}.left-align{text-align:left}.right-align{text-align:right}.center,.center-align{text-align:center}.left{float:left !important}.right{float:right !important}.no-select,input[type=range],input[type=range]+.thumb{-webkit-touch-callout:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.circle{border-radius:50%}.center-block{display:block;margin-left:auto;margin-right:auto}.truncate{display:block;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.no-padding{padding:0 !important}.material-icons{text-rendering:optimizeLegibility;-webkit-font-feature-settings:'liga';-moz-font-feature-settings:'liga';font-feature-settings:'liga'}.container{margin:0 auto;max-width:1280px;width:90%}@media only screen and (min-width: 601px){.container{width:85%}}@media only screen and (min-width: 993px){.container{width:70%}}.container .row{margin-left:-0.75rem;margin-right:-0.75rem}.section{padding-top:1rem;padding-bottom:1rem}.section.no-pad{padding:0}.section.no-pad-bot{padding-bottom:0}.section.no-pad-top{padding-top:0}.row{margin-left:auto;margin-right:auto;margin-bottom:20px}.row:after{content:"";display:table;clear:both}.row .col{float:left;box-sizing:border-box;padding:0 0.75rem}.row .col[class*="push-"],.row .col[class*="pull-"]{position:relative}.row .col.s1{width:8.3333333333%;margin-left:auto;left:auto;right:auto}.row .col.s2{width:16.6666666667%;margin-left:auto;left:auto;right:auto}.row .col.s3{width:25%;margin-left:auto;left:auto;right:auto}.row .col.s4{width:33.3333333333%;margin-left:auto;left:auto;right:auto}.row .col.s5{width:41.6666666667%;margin-left:auto;left:auto;right:auto}.row .col.s6{width:50%;margin-left:auto;left:auto;right:auto}.row .col.s7{width:58.3333333333%;margin-left:auto;left:auto;right:auto}.row .col.s8{width:66.6666666667%;margin-left:auto;left:auto;right:auto}.row .col.s9{width:75%;margin-left:auto;left:auto;right:auto}.row .col.s10{width:83.3333333333%;margin-left:auto;left:auto;right:auto}.row .col.s11{width:91.6666666667%;margin-left:auto;left:auto;right:auto}.row .col.s12{width:100%;margin-left:auto;left:auto;right:auto}.row .col.offset-s1{margin-left:8.3333333333%}.row .col.pull-s1{right:8.3333333333%}.row .col.push-s1{left:8.3333333333%}.row .col.offset-s2{margin-left:16.6666666667%}.row .col.pull-s2{right:16.6666666667%}.row .col.push-s2{left:16.6666666667%}.row .col.offset-s3{margin-left:25%}.row .col.pull-s3{right:25%}.row .col.push-s3{left:25%}.row .col.offset-s4{margin-left:33.3333333333%}.row .col.pull-s4{right:33.3333333333%}.row .col.push-s4{left:33.3333333333%}.row .col.offset-s5{margin-left:41.6666666667%}.row .col.pull-s5{right:41.6666666667%}.row .col.push-s5{left:41.6666666667%}.row .col.offset-s6{margin-left:50%}.row .col.pull-s6{right:50%}.row .col.push-s6{left:50%}.row .col.offset-s7{margin-left:58.3333333333%}.row .col.pull-s7{right:58.3333333333%}.row .col.push-s7{left:58.3333333333%}.row .col.offset-s8{margin-left:66.6666666667%}.row .col.pull-s8{right:66.6666666667%}.row .col.push-s8{left:66.6666666667%}.row .col.offset-s9{margin-left:75%}.row .col.pull-s9{right:75%}.row .col.push-s9{left:75%}.row .col.offset-s10{margin-left:83.3333333333%}.row .col.pull-s10{right:83.3333333333%}.row .col.push-s10{left:83.3333333333%}.row .col.offset-s11{margin-left:91.6666666667%}.row .col.pull-s11{right:91.6666666667%}.row .col.push-s11{left:91.6666666667%}.row .col.offset-s12{margin-left:100%}.row .col.pull-s12{right:100%}.row .col.push-s12{left:100%}@media only screen and (min-width: 601px){.row .col.m1{width:8.3333333333%;margin-left:auto;left:auto;right:auto}.row .col.m2{width:16.6666666667%;margin-left:auto;left:auto;right:auto}.row .col.m3{width:25%;margin-left:auto;left:auto;right:auto}.row .col.m4{width:33.3333333333%;margin-left:auto;left:auto;right:auto}.row .col.m5{width:41.6666666667%;margin-left:auto;left:auto;right:auto}.row .col.m6{width:50%;margin-left:auto;left:auto;right:auto}.row .col.m7{width:58.3333333333%;margin-left:auto;left:auto;right:auto}.row .col.m8{width:66.6666666667%;margin-left:auto;left:auto;right:auto}.row .col.m9{width:75%;margin-left:auto;left:auto;right:auto}.row .col.m10{width:83.3333333333%;margin-left:auto;left:auto;right:auto}.row .col.m11{width:91.6666666667%;margin-left:auto;left:auto;right:auto}.row .col.m12{width:100%;margin-left:auto;left:auto;right:auto}.row .col.offset-m1{margin-left:8.3333333333%}.row .col.pull-m1{right:8.3333333333%}.row .col.push-m1{left:8.3333333333%}.row .col.offset-m2{margin-left:16.6666666667%}.row .col.pull-m2{right:16.6666666667%}.row .col.push-m2{left:16.6666666667%}.row .col.offset-m3{margin-left:25%}.row .col.pull-m3{right:25%}.row .col.push-m3{left:25%}.row .col.offset-m4{margin-left:33.3333333333%}.row .col.pull-m4{right:33.3333333333%}.row .col.push-m4{left:33.3333333333%}.row .col.offset-m5{margin-left:41.6666666667%}.row .col.pull-m5{right:41.6666666667%}.row .col.push-m5{left:41.6666666667%}.row .col.offset-m6{margin-left:50%}.row .col.pull-m6{right:50%}.row .col.push-m6{left:50%}.row .col.offset-m7{margin-left:58.3333333333%}.row .col.pull-m7{right:58.3333333333%}.row .col.push-m7{left:58.3333333333%}.row .col.offset-m8{margin-left:66.6666666667%}.row .col.pull-m8{right:66.6666666667%}.row .col.push-m8{left:66.6666666667%}.row .col.offset-m9{margin-left:75%}.row .col.pull-m9{right:75%}.row .col.push-m9{left:75%}.row .col.offset-m10{margin-left:83.3333333333%}.row .col.pull-m10{right:83.3333333333%}.row .col.push-m10{left:83.3333333333%}.row .col.offset-m11{margin-left:91.6666666667%}.row .col.pull-m11{right:91.6666666667%}.row .col.push-m11{left:91.6666666667%}.row .col.offset-m12{margin-left:100%}.row .col.pull-m12{right:100%}.row .col.push-m12{left:100%}}@media only screen and (min-width: 993px){.row .col.l1{width:8.3333333333%;margin-left:auto;left:auto;right:auto}.row .col.l2{width:16.6666666667%;margin-left:auto;left:auto;right:auto}.row .col.l3{width:25%;margin-left:auto;left:auto;right:auto}.row .col.l4{width:33.3333333333%;margin-left:auto;left:auto;right:auto}.row .col.l5{width:41.6666666667%;margin-left:auto;left:auto;right:auto}.row .col.l6{width:50%;margin-left:auto;left:auto;right:auto}.row .col.l7{width:58.3333333333%;margin-left:auto;left:auto;right:auto}.row .col.l8{width:66.6666666667%;margin-left:auto;left:auto;right:auto}.row .col.l9{width:75%;margin-left:auto;left:auto;right:auto}.row .col.l10{width:83.3333333333%;margin-left:auto;left:auto;right:auto}.row .col.l11{width:91.6666666667%;margin-left:auto;left:auto;right:auto}.row .col.l12{width:100%;margin-left:auto;left:auto;right:auto}.row .col.offset-l1{margin-left:8.3333333333%}.row .col.pull-l1{right:8.3333333333%}.row .col.push-l1{left:8.3333333333%}.row .col.offset-l2{margin-left:16.6666666667%}.row .col.pull-l2{right:16.6666666667%}.row .col.push-l2{left:16.6666666667%}.row .col.offset-l3{margin-left:25%}.row .col.pull-l3{right:25%}.row .col.push-l3{left:25%}.row .col.offset-l4{margin-left:33.3333333333%}.row .col.pull-l4{right:33.3333333333%}.row .col.push-l4{left:33.3333333333%}.row .col.offset-l5{margin-left:41.6666666667%}.row .col.pull-l5{right:41.6666666667%}.row .col.push-l5{left:41.6666666667%}.row .col.offset-l6{margin-left:50%}.row .col.pull-l6{right:50%}.row .col.push-l6{left:50%}.row .col.offset-l7{margin-left:58.3333333333%}.row .col.pull-l7{right:58.3333333333%}.row .col.push-l7{left:58.3333333333%}.row .col.offset-l8{margin-left:66.6666666667%}.row .col.pull-l8{right:66.6666666667%}.row .col.push-l8{left:66.6666666667%}.row .col.offset-l9{margin-left:75%}.row .col.pull-l9{right:75%}.row .col.push-l9{left:75%}.row .col.offset-l10{margin-left:83.3333333333%}.row .col.pull-l10{right:83.3333333333%}.row .col.push-l10{left:83.3333333333%}.row .col.offset-l11{margin-left:91.6666666667%}.row .col.pull-l11{right:91.6666666667%}.row .col.push-l11{left:91.6666666667%}.row .col.offset-l12{margin-left:100%}.row .col.pull-l12{right:100%}.row .col.push-l12{left:100%}}nav{color:#fff;background-color:#ee6e73;width:100%;height:56px;line-height:56px}nav a{color:#fff}nav i,nav [class^="mdi-"],nav [class*="mdi-"],nav i.material-icons{display:block;font-size:2rem;height:56px;line-height:56px}nav .nav-wrapper{position:relative;height:100%}@media only screen and (min-width: 993px){nav a.button-collapse{display:none}}nav .button-collapse{float:left;position:relative;z-index:1;height:56px}nav .button-collapse i{font-size:2.7rem;height:56px;line-height:56px}nav .brand-logo{position:absolute;color:#fff;display:inline-block;font-size:2.1rem;padding:0;white-space:nowrap}nav .brand-logo.center{left:50%;-webkit-transform:translateX(-50%);transform:translateX(-50%)}@media only screen and (max-width: 992px){nav .brand-logo{left:50%;-webkit-transform:translateX(-50%);transform:translateX(-50%)}nav .brand-logo.left,nav .brand-logo.right{padding:0;-webkit-transform:none;transform:none}nav .brand-logo.left{left:0.5rem}nav .brand-logo.right{right:0.5rem;left:auto}}nav .brand-logo.right{right:0.5rem;padding:0}nav ul{margin:0}nav ul li{transition:background-color .3s;float:left;padding:0}nav ul li.active{background-color:rgba(0,0,0,0.1)}nav ul a{transition:background-color .3s;font-size:1rem;color:#fff;display:inline-block;padding:0 15px;cursor:pointer}nav ul a.btn,nav ul a.btn-large,nav ul a.btn-large,nav ul a.btn-flat,nav ul a.btn-floating{margin-top:-2px;margin-left:15px;margin-right:15px}nav ul a:hover{background-color:rgba(0,0,0,0.1)}nav ul.left{float:left}nav .input-field{margin:0}nav .input-field input{height:100%;font-size:1.2rem;border:none;padding-left:2rem}nav .input-field input:focus,nav .input-field input[type=text]:valid,nav .input-field input[type=password]:valid,nav .input-field input[type=email]:valid,nav .input-field input[type=url]:valid,nav .input-field input[type=date]:valid{border:none;box-shadow:none}nav .input-field label{top:0;left:0}nav .input-field label i{color:rgba(255,255,255,0.7);transition:color .3s}nav .input-field label.active i{color:#fff}nav .input-field label.active{-webkit-transform:translateY(0);transform:translateY(0)}.navbar-fixed{position:relative;height:56px;z-index:998}.navbar-fixed nav{position:fixed}@media only screen and (min-width: 601px){nav,nav .nav-wrapper i,nav a.button-collapse,nav a.button-collapse i{height:64px;line-height:64px}.navbar-fixed{height:64px}}@font-face{font-family:"Roboto";src:local(Roboto Thin),url("../fonts/roboto/Roboto-Thin.eot");src:url("../fonts/roboto/Roboto-Thin.eot?#iefix") format("embedded-opentype"),url("../fonts/roboto/Roboto-Thin.woff2") format("woff2"),url("../fonts/roboto/Roboto-Thin.woff") format("woff"),url("../fonts/roboto/Roboto-Thin.ttf") format("truetype");font-weight:200}@font-face{font-family:"Roboto";src:local(Roboto Light),url("../fonts/roboto/Roboto-Light.eot");src:url("../fonts/roboto/Roboto-Light.eot?#iefix") format("embedded-opentype"),url("../fonts/roboto/Roboto-Light.woff2") format("woff2"),url("../fonts/roboto/Roboto-Light.woff") format("woff"),url("../fonts/roboto/Roboto-Light.ttf") format("truetype");font-weight:300}@font-face{font-family:"Roboto";src:local(Roboto Regular),url("../fonts/roboto/Roboto-Regular.eot");src:url("../fonts/roboto/Roboto-Regular.eot?#iefix") format("embedded-opentype"),url("../fonts/roboto/Roboto-Regular.woff2") format("woff2"),url("../fonts/roboto/Roboto-Regular.woff") format("woff"),url("../fonts/roboto/Roboto-Regular.ttf") format("truetype");font-weight:400}@font-face{font-family:"Roboto";src:url("../fonts/roboto/Roboto-Medium.eot");src:url("../fonts/roboto/Roboto-Medium.eot?#iefix") format("embedded-opentype"),url("../fonts/roboto/Roboto-Medium.woff2") format("woff2"),url("../fonts/roboto/Roboto-Medium.woff") format("woff"),url("../fonts/roboto/Roboto-Medium.ttf") format("truetype");font-weight:500}@font-face{font-family:"Roboto";src:url("../fonts/roboto/Roboto-Bold.eot");src:url("../fonts/roboto/Roboto-Bold.eot?#iefix") format("embedded-opentype"),url("../fonts/roboto/Roboto-Bold.woff2") format("woff2"),url("../fonts/roboto/Roboto-Bold.woff") format("woff"),url("../fonts/roboto/Roboto-Bold.ttf") format("truetype");font-weight:700}a{text-decoration:none}html{line-height:1.5;font-family:"Roboto", sans-serif;font-weight:normal;color:rgba(0,0,0,0.87)}@media only screen and (min-width: 0){html{font-size:14px}}@media only screen and (min-width: 992px){html{font-size:14.5px}}@media only screen and (min-width: 1200px){html{font-size:15px}}h1,h2,h3,h4,h5,h6{font-weight:400;line-height:1.1}h1 a,h2 a,h3 a,h4 a,h5 a,h6 a{font-weight:inherit}h1{font-size:4.2rem;line-height:110%;margin:2.1rem 0 1.68rem 0}h2{font-size:3.56rem;line-height:110%;margin:1.78rem 0 1.424rem 0}h3{font-size:2.92rem;line-height:110%;margin:1.46rem 0 1.168rem 0}h4{font-size:2.28rem;line-height:110%;margin:1.14rem 0 0.912rem 0}h5{font-size:1.64rem;line-height:110%;margin:0.82rem 0 0.656rem 0}h6{font-size:1rem;line-height:110%;margin:0.5rem 0 0.4rem 0}em{font-style:italic}strong{font-weight:500}small{font-size:75%}.light,footer.page-footer .footer-copyright{font-weight:300}.thin{font-weight:200}.flow-text{font-weight:300}@media only screen and (min-width: 360px){.flow-text{font-size:1.2rem}}@media only screen and (min-width: 390px){.flow-text{font-size:1.224rem}}@media only screen and (min-width: 420px){.flow-text{font-size:1.248rem}}@media only screen and (min-width: 450px){.flow-text{font-size:1.272rem}}@media only screen and (min-width: 480px){.flow-text{font-size:1.296rem}}@media only screen and (min-width: 510px){.flow-text{font-size:1.32rem}}@media only screen and (min-width: 540px){.flow-text{font-size:1.344rem}}@media only screen and (min-width: 570px){.flow-text{font-size:1.368rem}}@media only screen and (min-width: 600px){.flow-text{font-size:1.392rem}}@media only screen and (min-width: 630px){.flow-text{font-size:1.416rem}}@media only screen and (min-width: 660px){.flow-text{font-size:1.44rem}}@media only screen and (min-width: 690px){.flow-text{font-size:1.464rem}}@media only screen and (min-width: 720px){.flow-text{font-size:1.488rem}}@media only screen and (min-width: 750px){.flow-text{font-size:1.512rem}}@media only screen and (min-width: 780px){.flow-text{font-size:1.536rem}}@media only screen and (min-width: 810px){.flow-text{font-size:1.56rem}}@media only screen and (min-width: 840px){.flow-text{font-size:1.584rem}}@media only screen and (min-width: 870px){.flow-text{font-size:1.608rem}}@media only screen and (min-width: 900px){.flow-text{font-size:1.632rem}}@media only screen and (min-width: 930px){.flow-text{font-size:1.656rem}}@media only screen and (min-width: 960px){.flow-text{font-size:1.68rem}}@media only screen and (max-width: 360px){.flow-text{font-size:1.2rem}}.card-panel{transition:box-shadow .25s;padding:20px;margin:0.5rem 0 1rem 0;border-radius:2px;background-color:#fff}.card{position:relative;margin:0.5rem 0 1rem 0;background-color:#fff;transition:box-shadow .25s;border-radius:2px}.card .card-title{font-size:24px;font-weight:300}.card .card-title.activator{cursor:pointer}.card.small,.card.medium,.card.large{position:relative}.card.small .card-image,.card.medium .card-image,.card.large .card-image{max-height:60%;overflow:hidden}.card.small .card-content,.card.medium .card-content,.card.large .card-content{max-height:40%;overflow:hidden}.card.small .card-action,.card.medium .card-action,.card.large .card-action{position:absolute;bottom:0;left:0;right:0}.card.small{height:300px}.card.medium{height:400px}.card.large{height:500px}.card .card-image{position:relative}.card .card-image img{display:block;border-radius:2px 2px 0 0;position:relative;left:0;right:0;top:0;bottom:0;width:100%}.card .card-image .card-title{color:#fff;position:absolute;bottom:0;left:0;padding:20px}.card .card-content{padding:20px;border-radius:0 0 2px 2px}.card .card-content p{margin:0;color:inherit}.card .card-content .card-title{line-height:48px}.card .card-action{position:relative;background-color:inherit;border-top:1px solid rgba(160,160,160,0.2);padding:20px;z-index:2}.card .card-action a:not(.btn):not(.btn-large):not(.btn-floating){color:#ffab40;margin-right:20px;transition:color .3s ease;text-transform:uppercase}.card .card-action a:not(.btn):not(.btn-large):not(.btn-floating):hover{color:#ffd8a6}.card .card-action+.card-reveal{z-index:1;padding-bottom:64px}.card .card-reveal{padding:20px;position:absolute;background-color:#fff;width:100%;overflow-y:auto;top:100%;height:100%;z-index:3;display:none}.card .card-reveal .card-title{cursor:pointer;display:block}#toast-container{display:block;position:fixed;z-index:10000}@media only screen and (max-width: 600px){#toast-container{min-width:100%;bottom:0%}}@media only screen and (min-width: 601px) and (max-width: 992px){#toast-container{left:5%;bottom:7%;max-width:90%}}@media only screen and (min-width: 993px){#toast-container{top:10%;right:7%;max-width:86%}}.toast{border-radius:2px;top:0;width:auto;clear:both;margin-top:10px;position:relative;max-width:100%;height:auto;min-height:48px;line-height:1.5em;word-break:break-all;background-color:#323232;padding:10px 25px;font-size:1.1rem;font-weight:300;color:#fff;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-align-items:center;-ms-flex-align:center;align-items:center;-webkit-justify-content:space-between;-ms-flex-pack:justify;justify-content:space-between}.toast .btn,.toast .btn-large,.toast .btn-flat{margin:0;margin-left:3rem}.toast.rounded{border-radius:24px}@media only screen and (max-width: 600px){.toast{width:100%;border-radius:0}}@media only screen and (min-width: 601px) and (max-width: 992px){.toast{float:left}}@media only screen and (min-width: 993px){.toast{float:right}}.tabs{display:-webkit-flex;display:-ms-flexbox;display:flex;position:relative;overflow-x:auto;overflow-y:hidden;height:48px;background-color:#fff;margin:0 auto;width:100%;white-space:nowrap}.tabs .tab{-webkit-box-flex:1;-webkit-flex-grow:1;-ms-flex-positive:1;flex-grow:1;display:block;float:left;text-align:center;line-height:48px;height:48px;padding:0;margin:0;text-transform:uppercase;text-overflow:ellipsis;overflow:hidden;letter-spacing:.8px;width:15%;min-width:80px}.tabs .tab a{color:#ee6e73;display:block;width:100%;height:100%;text-overflow:ellipsis;overflow:hidden;transition:color .28s ease}.tabs .tab a:hover{color:#f9c9cb}.tabs .tab.disabled a{color:#f9c9cb;cursor:default}.tabs .indicator{position:absolute;bottom:0;height:2px;background-color:#f6b2b5;will-change:left, right}.material-tooltip{padding:10px 8px;font-size:1rem;z-index:2000;background-color:transparent;border-radius:2px;color:#fff;min-height:36px;line-height:120%;opacity:0;display:none;position:absolute;text-align:center;max-width:calc(100% - 4px);overflow:hidden;left:0;top:0;pointer-events:none;will-change:top, left}.backdrop{position:absolute;opacity:0;display:none;height:7px;width:14px;border-radius:0 0 14px 14px;background-color:#323232;z-index:-1;-webkit-transform-origin:50% 10%;transform-origin:50% 10%;will-change:transform, opacity}.btn,.btn-large,.btn-flat{border:none;border-radius:2px;display:inline-block;height:36px;line-height:36px;outline:0;padding:0 2rem;text-transform:uppercase;vertical-align:middle;-webkit-tap-highlight-color:transparent}.btn.disabled,.disabled.btn-large,.btn-floating.disabled,.btn-large.disabled,.btn:disabled .btn-large:disabled,.btn-large:disabled .btn-large:disabled,.btn-floating:disabled{background-color:#DFDFDF !important;box-shadow:none;color:#9F9F9F !important;cursor:default}.btn.disabled *,.disabled.btn-large *,.btn-floating.disabled *,.btn-large.disabled *,.btn:disabled .btn-large:disabled *,.btn-large:disabled .btn-large:disabled *,.btn-floating:disabled *{pointer-events:none}.btn.disabled:hover,.disabled.btn-large:hover,.btn-floating.disabled:hover,.btn-large.disabled:hover,.btn:disabled .btn-large:disabled:hover,.btn-large:disabled .btn-large:disabled:hover,.btn-floating:disabled:hover{background-color:#DFDFDF !important;color:#9F9F9F !important}.btn i,.btn-large i,.btn-floating i,.btn-large i,.btn-flat i{font-size:1.3rem;line-height:inherit}.btn,.btn-large{text-decoration:none;color:#fff;background-color:#26a69a;text-align:center;letter-spacing:.5px;transition:.2s ease-out;cursor:pointer}.btn:hover,.btn-large:hover{background-color:#2bbbad}.btn-floating{display:inline-block;color:#fff;position:relative;overflow:hidden;z-index:1;width:37px;height:37px;line-height:37px;padding:0;background-color:#26a69a;border-radius:50%;transition:.3s;cursor:pointer;vertical-align:middle}.btn-floating i{width:inherit;display:inline-block;text-align:center;color:#fff;font-size:1.6rem;line-height:37px}.btn-floating:hover{background-color:#26a69a}.btn-floating:before{border-radius:0}.btn-floating.btn-large{width:55.5px;height:55.5px}.btn-floating.btn-large i{line-height:55.5px}button.btn-floating{border:none}.fixed-action-btn{position:fixed;right:23px;bottom:23px;padding-top:15px;margin-bottom:0;z-index:998}.fixed-action-btn.active ul{visibility:visible}.fixed-action-btn.horizontal{padding:0 0 0 15px}.fixed-action-btn.horizontal ul{text-align:right;right:64px;top:50%;-webkit-transform:translateY(-50%);transform:translateY(-50%);height:100%;left:auto;width:500px}.fixed-action-btn.horizontal ul li{display:inline-block;margin:15px 15px 0 0}.fixed-action-btn ul{left:0;right:0;text-align:center;position:absolute;bottom:64px;margin:0;visibility:hidden}.fixed-action-btn ul li{margin-bottom:15px}.fixed-action-btn ul a.btn-floating{opacity:0}.btn-flat{box-shadow:none;background-color:transparent;color:#343434;cursor:pointer}.btn-flat.disabled{color:#b3b3b3;cursor:default}.btn-large{height:54px;line-height:54px}.btn-large i{font-size:1.6rem}.btn-block{display:block}.dropdown-content{background-color:#fff;margin:0;display:none;min-width:100px;max-height:650px;overflow-y:auto;opacity:0;position:absolute;z-index:999;will-change:width, height}.dropdown-content li{clear:both;color:rgba(0,0,0,0.87);cursor:pointer;min-height:50px;line-height:1.5rem;width:100%;text-align:left;text-transform:none}.dropdown-content li:hover,.dropdown-content li.active,.dropdown-content li.selected{background-color:#eee}.dropdown-content li.active.selected{background-color:#e1e1e1}.dropdown-content li.divider{min-height:0;height:1px}.dropdown-content li>a,.dropdown-content li>span{font-size:16px;color:#26a69a;display:block;line-height:22px;padding:14px 16px}.dropdown-content li>span>label{top:1px;left:3px;height:18px}.dropdown-content li>a>i{height:inherit;line-height:inherit}/*! +.materialize-red{background-color:#e51c23 !important}.materialize-red-text{color:#e51c23 !important}.materialize-red.lighten-5{background-color:#fdeaeb !important}.materialize-red-text.text-lighten-5{color:#fdeaeb !important}.materialize-red.lighten-4{background-color:#f8c1c3 !important}.materialize-red-text.text-lighten-4{color:#f8c1c3 !important}.materialize-red.lighten-3{background-color:#f3989b !important}.materialize-red-text.text-lighten-3{color:#f3989b !important}.materialize-red.lighten-2{background-color:#ee6e73 !important}.materialize-red-text.text-lighten-2{color:#ee6e73 !important}.materialize-red.lighten-1{background-color:#ea454b !important}.materialize-red-text.text-lighten-1{color:#ea454b !important}.materialize-red.darken-1{background-color:#d0181e !important}.materialize-red-text.text-darken-1{color:#d0181e !important}.materialize-red.darken-2{background-color:#b9151b !important}.materialize-red-text.text-darken-2{color:#b9151b !important}.materialize-red.darken-3{background-color:#a21318 !important}.materialize-red-text.text-darken-3{color:#a21318 !important}.materialize-red.darken-4{background-color:#8b1014 !important}.materialize-red-text.text-darken-4{color:#8b1014 !important}.red{background-color:#F44336 !important}.red-text{color:#F44336 !important}.red.lighten-5{background-color:#FFEBEE !important}.red-text.text-lighten-5{color:#FFEBEE !important}.red.lighten-4{background-color:#FFCDD2 !important}.red-text.text-lighten-4{color:#FFCDD2 !important}.red.lighten-3{background-color:#EF9A9A !important}.red-text.text-lighten-3{color:#EF9A9A !important}.red.lighten-2{background-color:#E57373 !important}.red-text.text-lighten-2{color:#E57373 !important}.red.lighten-1{background-color:#EF5350 !important}.red-text.text-lighten-1{color:#EF5350 !important}.red.darken-1{background-color:#E53935 !important}.red-text.text-darken-1{color:#E53935 !important}.red.darken-2{background-color:#D32F2F !important}.red-text.text-darken-2{color:#D32F2F !important}.red.darken-3{background-color:#C62828 !important}.red-text.text-darken-3{color:#C62828 !important}.red.darken-4{background-color:#B71C1C !important}.red-text.text-darken-4{color:#B71C1C !important}.red.accent-1{background-color:#FF8A80 !important}.red-text.text-accent-1{color:#FF8A80 !important}.red.accent-2{background-color:#FF5252 !important}.red-text.text-accent-2{color:#FF5252 !important}.red.accent-3{background-color:#FF1744 !important}.red-text.text-accent-3{color:#FF1744 !important}.red.accent-4{background-color:#D50000 !important}.red-text.text-accent-4{color:#D50000 !important}.pink{background-color:#e91e63 !important}.pink-text{color:#e91e63 !important}.pink.lighten-5{background-color:#fce4ec !important}.pink-text.text-lighten-5{color:#fce4ec !important}.pink.lighten-4{background-color:#f8bbd0 !important}.pink-text.text-lighten-4{color:#f8bbd0 !important}.pink.lighten-3{background-color:#f48fb1 !important}.pink-text.text-lighten-3{color:#f48fb1 !important}.pink.lighten-2{background-color:#f06292 !important}.pink-text.text-lighten-2{color:#f06292 !important}.pink.lighten-1{background-color:#ec407a !important}.pink-text.text-lighten-1{color:#ec407a !important}.pink.darken-1{background-color:#d81b60 !important}.pink-text.text-darken-1{color:#d81b60 !important}.pink.darken-2{background-color:#c2185b !important}.pink-text.text-darken-2{color:#c2185b !important}.pink.darken-3{background-color:#ad1457 !important}.pink-text.text-darken-3{color:#ad1457 !important}.pink.darken-4{background-color:#880e4f !important}.pink-text.text-darken-4{color:#880e4f !important}.pink.accent-1{background-color:#ff80ab !important}.pink-text.text-accent-1{color:#ff80ab !important}.pink.accent-2{background-color:#ff4081 !important}.pink-text.text-accent-2{color:#ff4081 !important}.pink.accent-3{background-color:#f50057 !important}.pink-text.text-accent-3{color:#f50057 !important}.pink.accent-4{background-color:#c51162 !important}.pink-text.text-accent-4{color:#c51162 !important}.purple{background-color:#9c27b0 !important}.purple-text{color:#9c27b0 !important}.purple.lighten-5{background-color:#f3e5f5 !important}.purple-text.text-lighten-5{color:#f3e5f5 !important}.purple.lighten-4{background-color:#e1bee7 !important}.purple-text.text-lighten-4{color:#e1bee7 !important}.purple.lighten-3{background-color:#ce93d8 !important}.purple-text.text-lighten-3{color:#ce93d8 !important}.purple.lighten-2{background-color:#ba68c8 !important}.purple-text.text-lighten-2{color:#ba68c8 !important}.purple.lighten-1{background-color:#ab47bc !important}.purple-text.text-lighten-1{color:#ab47bc !important}.purple.darken-1{background-color:#8e24aa !important}.purple-text.text-darken-1{color:#8e24aa !important}.purple.darken-2{background-color:#7b1fa2 !important}.purple-text.text-darken-2{color:#7b1fa2 !important}.purple.darken-3{background-color:#6a1b9a !important}.purple-text.text-darken-3{color:#6a1b9a !important}.purple.darken-4{background-color:#4a148c !important}.purple-text.text-darken-4{color:#4a148c !important}.purple.accent-1{background-color:#ea80fc !important}.purple-text.text-accent-1{color:#ea80fc !important}.purple.accent-2{background-color:#e040fb !important}.purple-text.text-accent-2{color:#e040fb !important}.purple.accent-3{background-color:#d500f9 !important}.purple-text.text-accent-3{color:#d500f9 !important}.purple.accent-4{background-color:#a0f !important}.purple-text.text-accent-4{color:#a0f !important}.deep-purple{background-color:#673ab7 !important}.deep-purple-text{color:#673ab7 !important}.deep-purple.lighten-5{background-color:#ede7f6 !important}.deep-purple-text.text-lighten-5{color:#ede7f6 !important}.deep-purple.lighten-4{background-color:#d1c4e9 !important}.deep-purple-text.text-lighten-4{color:#d1c4e9 !important}.deep-purple.lighten-3{background-color:#b39ddb !important}.deep-purple-text.text-lighten-3{color:#b39ddb !important}.deep-purple.lighten-2{background-color:#9575cd !important}.deep-purple-text.text-lighten-2{color:#9575cd !important}.deep-purple.lighten-1{background-color:#7e57c2 !important}.deep-purple-text.text-lighten-1{color:#7e57c2 !important}.deep-purple.darken-1{background-color:#5e35b1 !important}.deep-purple-text.text-darken-1{color:#5e35b1 !important}.deep-purple.darken-2{background-color:#512da8 !important}.deep-purple-text.text-darken-2{color:#512da8 !important}.deep-purple.darken-3{background-color:#4527a0 !important}.deep-purple-text.text-darken-3{color:#4527a0 !important}.deep-purple.darken-4{background-color:#311b92 !important}.deep-purple-text.text-darken-4{color:#311b92 !important}.deep-purple.accent-1{background-color:#b388ff !important}.deep-purple-text.text-accent-1{color:#b388ff !important}.deep-purple.accent-2{background-color:#7c4dff !important}.deep-purple-text.text-accent-2{color:#7c4dff !important}.deep-purple.accent-3{background-color:#651fff !important}.deep-purple-text.text-accent-3{color:#651fff !important}.deep-purple.accent-4{background-color:#6200ea !important}.deep-purple-text.text-accent-4{color:#6200ea !important}.indigo{background-color:#3f51b5 !important}.indigo-text{color:#3f51b5 !important}.indigo.lighten-5{background-color:#e8eaf6 !important}.indigo-text.text-lighten-5{color:#e8eaf6 !important}.indigo.lighten-4{background-color:#c5cae9 !important}.indigo-text.text-lighten-4{color:#c5cae9 !important}.indigo.lighten-3{background-color:#9fa8da !important}.indigo-text.text-lighten-3{color:#9fa8da !important}.indigo.lighten-2{background-color:#7986cb !important}.indigo-text.text-lighten-2{color:#7986cb !important}.indigo.lighten-1{background-color:#5c6bc0 !important}.indigo-text.text-lighten-1{color:#5c6bc0 !important}.indigo.darken-1{background-color:#3949ab !important}.indigo-text.text-darken-1{color:#3949ab !important}.indigo.darken-2{background-color:#303f9f !important}.indigo-text.text-darken-2{color:#303f9f !important}.indigo.darken-3{background-color:#283593 !important}.indigo-text.text-darken-3{color:#283593 !important}.indigo.darken-4{background-color:#1a237e !important}.indigo-text.text-darken-4{color:#1a237e !important}.indigo.accent-1{background-color:#8c9eff !important}.indigo-text.text-accent-1{color:#8c9eff !important}.indigo.accent-2{background-color:#536dfe !important}.indigo-text.text-accent-2{color:#536dfe !important}.indigo.accent-3{background-color:#3d5afe !important}.indigo-text.text-accent-3{color:#3d5afe !important}.indigo.accent-4{background-color:#304ffe !important}.indigo-text.text-accent-4{color:#304ffe !important}.blue{background-color:#2196F3 !important}.blue-text{color:#2196F3 !important}.blue.lighten-5{background-color:#E3F2FD !important}.blue-text.text-lighten-5{color:#E3F2FD !important}.blue.lighten-4{background-color:#BBDEFB !important}.blue-text.text-lighten-4{color:#BBDEFB !important}.blue.lighten-3{background-color:#90CAF9 !important}.blue-text.text-lighten-3{color:#90CAF9 !important}.blue.lighten-2{background-color:#64B5F6 !important}.blue-text.text-lighten-2{color:#64B5F6 !important}.blue.lighten-1{background-color:#42A5F5 !important}.blue-text.text-lighten-1{color:#42A5F5 !important}.blue.darken-1{background-color:#1E88E5 !important}.blue-text.text-darken-1{color:#1E88E5 !important}.blue.darken-2{background-color:#1976D2 !important}.blue-text.text-darken-2{color:#1976D2 !important}.blue.darken-3{background-color:#1565C0 !important}.blue-text.text-darken-3{color:#1565C0 !important}.blue.darken-4{background-color:#0D47A1 !important}.blue-text.text-darken-4{color:#0D47A1 !important}.blue.accent-1{background-color:#82B1FF !important}.blue-text.text-accent-1{color:#82B1FF !important}.blue.accent-2{background-color:#448AFF !important}.blue-text.text-accent-2{color:#448AFF !important}.blue.accent-3{background-color:#2979FF !important}.blue-text.text-accent-3{color:#2979FF !important}.blue.accent-4{background-color:#2962FF !important}.blue-text.text-accent-4{color:#2962FF !important}.light-blue{background-color:#03a9f4 !important}.light-blue-text{color:#03a9f4 !important}.light-blue.lighten-5{background-color:#e1f5fe !important}.light-blue-text.text-lighten-5{color:#e1f5fe !important}.light-blue.lighten-4{background-color:#b3e5fc !important}.light-blue-text.text-lighten-4{color:#b3e5fc !important}.light-blue.lighten-3{background-color:#81d4fa !important}.light-blue-text.text-lighten-3{color:#81d4fa !important}.light-blue.lighten-2{background-color:#4fc3f7 !important}.light-blue-text.text-lighten-2{color:#4fc3f7 !important}.light-blue.lighten-1{background-color:#29b6f6 !important}.light-blue-text.text-lighten-1{color:#29b6f6 !important}.light-blue.darken-1{background-color:#039be5 !important}.light-blue-text.text-darken-1{color:#039be5 !important}.light-blue.darken-2{background-color:#0288d1 !important}.light-blue-text.text-darken-2{color:#0288d1 !important}.light-blue.darken-3{background-color:#0277bd !important}.light-blue-text.text-darken-3{color:#0277bd !important}.light-blue.darken-4{background-color:#01579b !important}.light-blue-text.text-darken-4{color:#01579b !important}.light-blue.accent-1{background-color:#80d8ff !important}.light-blue-text.text-accent-1{color:#80d8ff !important}.light-blue.accent-2{background-color:#40c4ff !important}.light-blue-text.text-accent-2{color:#40c4ff !important}.light-blue.accent-3{background-color:#00b0ff !important}.light-blue-text.text-accent-3{color:#00b0ff !important}.light-blue.accent-4{background-color:#0091ea !important}.light-blue-text.text-accent-4{color:#0091ea !important}.cyan{background-color:#00bcd4 !important}.cyan-text{color:#00bcd4 !important}.cyan.lighten-5{background-color:#e0f7fa !important}.cyan-text.text-lighten-5{color:#e0f7fa !important}.cyan.lighten-4{background-color:#b2ebf2 !important}.cyan-text.text-lighten-4{color:#b2ebf2 !important}.cyan.lighten-3{background-color:#80deea !important}.cyan-text.text-lighten-3{color:#80deea !important}.cyan.lighten-2{background-color:#4dd0e1 !important}.cyan-text.text-lighten-2{color:#4dd0e1 !important}.cyan.lighten-1{background-color:#26c6da !important}.cyan-text.text-lighten-1{color:#26c6da !important}.cyan.darken-1{background-color:#00acc1 !important}.cyan-text.text-darken-1{color:#00acc1 !important}.cyan.darken-2{background-color:#0097a7 !important}.cyan-text.text-darken-2{color:#0097a7 !important}.cyan.darken-3{background-color:#00838f !important}.cyan-text.text-darken-3{color:#00838f !important}.cyan.darken-4{background-color:#006064 !important}.cyan-text.text-darken-4{color:#006064 !important}.cyan.accent-1{background-color:#84ffff !important}.cyan-text.text-accent-1{color:#84ffff !important}.cyan.accent-2{background-color:#18ffff !important}.cyan-text.text-accent-2{color:#18ffff !important}.cyan.accent-3{background-color:#00e5ff !important}.cyan-text.text-accent-3{color:#00e5ff !important}.cyan.accent-4{background-color:#00b8d4 !important}.cyan-text.text-accent-4{color:#00b8d4 !important}.teal{background-color:#009688 !important}.teal-text{color:#009688 !important}.teal.lighten-5{background-color:#e0f2f1 !important}.teal-text.text-lighten-5{color:#e0f2f1 !important}.teal.lighten-4{background-color:#b2dfdb !important}.teal-text.text-lighten-4{color:#b2dfdb !important}.teal.lighten-3{background-color:#80cbc4 !important}.teal-text.text-lighten-3{color:#80cbc4 !important}.teal.lighten-2{background-color:#4db6ac !important}.teal-text.text-lighten-2{color:#4db6ac !important}.teal.lighten-1{background-color:#26a69a !important}.teal-text.text-lighten-1{color:#26a69a !important}.teal.darken-1{background-color:#00897b !important}.teal-text.text-darken-1{color:#00897b !important}.teal.darken-2{background-color:#00796b !important}.teal-text.text-darken-2{color:#00796b !important}.teal.darken-3{background-color:#00695c !important}.teal-text.text-darken-3{color:#00695c !important}.teal.darken-4{background-color:#004d40 !important}.teal-text.text-darken-4{color:#004d40 !important}.teal.accent-1{background-color:#a7ffeb !important}.teal-text.text-accent-1{color:#a7ffeb !important}.teal.accent-2{background-color:#64ffda !important}.teal-text.text-accent-2{color:#64ffda !important}.teal.accent-3{background-color:#1de9b6 !important}.teal-text.text-accent-3{color:#1de9b6 !important}.teal.accent-4{background-color:#00bfa5 !important}.teal-text.text-accent-4{color:#00bfa5 !important}.green{background-color:#4CAF50 !important}.green-text{color:#4CAF50 !important}.green.lighten-5{background-color:#E8F5E9 !important}.green-text.text-lighten-5{color:#E8F5E9 !important}.green.lighten-4{background-color:#C8E6C9 !important}.green-text.text-lighten-4{color:#C8E6C9 !important}.green.lighten-3{background-color:#A5D6A7 !important}.green-text.text-lighten-3{color:#A5D6A7 !important}.green.lighten-2{background-color:#81C784 !important}.green-text.text-lighten-2{color:#81C784 !important}.green.lighten-1{background-color:#66BB6A !important}.green-text.text-lighten-1{color:#66BB6A !important}.green.darken-1{background-color:#43A047 !important}.green-text.text-darken-1{color:#43A047 !important}.green.darken-2{background-color:#388E3C !important}.green-text.text-darken-2{color:#388E3C !important}.green.darken-3{background-color:#2E7D32 !important}.green-text.text-darken-3{color:#2E7D32 !important}.green.darken-4{background-color:#1B5E20 !important}.green-text.text-darken-4{color:#1B5E20 !important}.green.accent-1{background-color:#B9F6CA !important}.green-text.text-accent-1{color:#B9F6CA !important}.green.accent-2{background-color:#69F0AE !important}.green-text.text-accent-2{color:#69F0AE !important}.green.accent-3{background-color:#00E676 !important}.green-text.text-accent-3{color:#00E676 !important}.green.accent-4{background-color:#00C853 !important}.green-text.text-accent-4{color:#00C853 !important}.light-green{background-color:#8bc34a !important}.light-green-text{color:#8bc34a !important}.light-green.lighten-5{background-color:#f1f8e9 !important}.light-green-text.text-lighten-5{color:#f1f8e9 !important}.light-green.lighten-4{background-color:#dcedc8 !important}.light-green-text.text-lighten-4{color:#dcedc8 !important}.light-green.lighten-3{background-color:#c5e1a5 !important}.light-green-text.text-lighten-3{color:#c5e1a5 !important}.light-green.lighten-2{background-color:#aed581 !important}.light-green-text.text-lighten-2{color:#aed581 !important}.light-green.lighten-1{background-color:#9ccc65 !important}.light-green-text.text-lighten-1{color:#9ccc65 !important}.light-green.darken-1{background-color:#7cb342 !important}.light-green-text.text-darken-1{color:#7cb342 !important}.light-green.darken-2{background-color:#689f38 !important}.light-green-text.text-darken-2{color:#689f38 !important}.light-green.darken-3{background-color:#558b2f !important}.light-green-text.text-darken-3{color:#558b2f !important}.light-green.darken-4{background-color:#33691e !important}.light-green-text.text-darken-4{color:#33691e !important}.light-green.accent-1{background-color:#ccff90 !important}.light-green-text.text-accent-1{color:#ccff90 !important}.light-green.accent-2{background-color:#b2ff59 !important}.light-green-text.text-accent-2{color:#b2ff59 !important}.light-green.accent-3{background-color:#76ff03 !important}.light-green-text.text-accent-3{color:#76ff03 !important}.light-green.accent-4{background-color:#64dd17 !important}.light-green-text.text-accent-4{color:#64dd17 !important}.lime{background-color:#cddc39 !important}.lime-text{color:#cddc39 !important}.lime.lighten-5{background-color:#f9fbe7 !important}.lime-text.text-lighten-5{color:#f9fbe7 !important}.lime.lighten-4{background-color:#f0f4c3 !important}.lime-text.text-lighten-4{color:#f0f4c3 !important}.lime.lighten-3{background-color:#e6ee9c !important}.lime-text.text-lighten-3{color:#e6ee9c !important}.lime.lighten-2{background-color:#dce775 !important}.lime-text.text-lighten-2{color:#dce775 !important}.lime.lighten-1{background-color:#d4e157 !important}.lime-text.text-lighten-1{color:#d4e157 !important}.lime.darken-1{background-color:#c0ca33 !important}.lime-text.text-darken-1{color:#c0ca33 !important}.lime.darken-2{background-color:#afb42b !important}.lime-text.text-darken-2{color:#afb42b !important}.lime.darken-3{background-color:#9e9d24 !important}.lime-text.text-darken-3{color:#9e9d24 !important}.lime.darken-4{background-color:#827717 !important}.lime-text.text-darken-4{color:#827717 !important}.lime.accent-1{background-color:#f4ff81 !important}.lime-text.text-accent-1{color:#f4ff81 !important}.lime.accent-2{background-color:#eeff41 !important}.lime-text.text-accent-2{color:#eeff41 !important}.lime.accent-3{background-color:#c6ff00 !important}.lime-text.text-accent-3{color:#c6ff00 !important}.lime.accent-4{background-color:#aeea00 !important}.lime-text.text-accent-4{color:#aeea00 !important}.yellow{background-color:#ffeb3b !important}.yellow-text{color:#ffeb3b !important}.yellow.lighten-5{background-color:#fffde7 !important}.yellow-text.text-lighten-5{color:#fffde7 !important}.yellow.lighten-4{background-color:#fff9c4 !important}.yellow-text.text-lighten-4{color:#fff9c4 !important}.yellow.lighten-3{background-color:#fff59d !important}.yellow-text.text-lighten-3{color:#fff59d !important}.yellow.lighten-2{background-color:#fff176 !important}.yellow-text.text-lighten-2{color:#fff176 !important}.yellow.lighten-1{background-color:#ffee58 !important}.yellow-text.text-lighten-1{color:#ffee58 !important}.yellow.darken-1{background-color:#fdd835 !important}.yellow-text.text-darken-1{color:#fdd835 !important}.yellow.darken-2{background-color:#fbc02d !important}.yellow-text.text-darken-2{color:#fbc02d !important}.yellow.darken-3{background-color:#f9a825 !important}.yellow-text.text-darken-3{color:#f9a825 !important}.yellow.darken-4{background-color:#f57f17 !important}.yellow-text.text-darken-4{color:#f57f17 !important}.yellow.accent-1{background-color:#ffff8d !important}.yellow-text.text-accent-1{color:#ffff8d !important}.yellow.accent-2{background-color:#ff0 !important}.yellow-text.text-accent-2{color:#ff0 !important}.yellow.accent-3{background-color:#ffea00 !important}.yellow-text.text-accent-3{color:#ffea00 !important}.yellow.accent-4{background-color:#ffd600 !important}.yellow-text.text-accent-4{color:#ffd600 !important}.amber{background-color:#ffc107 !important}.amber-text{color:#ffc107 !important}.amber.lighten-5{background-color:#fff8e1 !important}.amber-text.text-lighten-5{color:#fff8e1 !important}.amber.lighten-4{background-color:#ffecb3 !important}.amber-text.text-lighten-4{color:#ffecb3 !important}.amber.lighten-3{background-color:#ffe082 !important}.amber-text.text-lighten-3{color:#ffe082 !important}.amber.lighten-2{background-color:#ffd54f !important}.amber-text.text-lighten-2{color:#ffd54f !important}.amber.lighten-1{background-color:#ffca28 !important}.amber-text.text-lighten-1{color:#ffca28 !important}.amber.darken-1{background-color:#ffb300 !important}.amber-text.text-darken-1{color:#ffb300 !important}.amber.darken-2{background-color:#ffa000 !important}.amber-text.text-darken-2{color:#ffa000 !important}.amber.darken-3{background-color:#ff8f00 !important}.amber-text.text-darken-3{color:#ff8f00 !important}.amber.darken-4{background-color:#ff6f00 !important}.amber-text.text-darken-4{color:#ff6f00 !important}.amber.accent-1{background-color:#ffe57f !important}.amber-text.text-accent-1{color:#ffe57f !important}.amber.accent-2{background-color:#ffd740 !important}.amber-text.text-accent-2{color:#ffd740 !important}.amber.accent-3{background-color:#ffc400 !important}.amber-text.text-accent-3{color:#ffc400 !important}.amber.accent-4{background-color:#ffab00 !important}.amber-text.text-accent-4{color:#ffab00 !important}.orange{background-color:#ff9800 !important}.orange-text{color:#ff9800 !important}.orange.lighten-5{background-color:#fff3e0 !important}.orange-text.text-lighten-5{color:#fff3e0 !important}.orange.lighten-4{background-color:#ffe0b2 !important}.orange-text.text-lighten-4{color:#ffe0b2 !important}.orange.lighten-3{background-color:#ffcc80 !important}.orange-text.text-lighten-3{color:#ffcc80 !important}.orange.lighten-2{background-color:#ffb74d !important}.orange-text.text-lighten-2{color:#ffb74d !important}.orange.lighten-1{background-color:#ffa726 !important}.orange-text.text-lighten-1{color:#ffa726 !important}.orange.darken-1{background-color:#fb8c00 !important}.orange-text.text-darken-1{color:#fb8c00 !important}.orange.darken-2{background-color:#f57c00 !important}.orange-text.text-darken-2{color:#f57c00 !important}.orange.darken-3{background-color:#ef6c00 !important}.orange-text.text-darken-3{color:#ef6c00 !important}.orange.darken-4{background-color:#e65100 !important}.orange-text.text-darken-4{color:#e65100 !important}.orange.accent-1{background-color:#ffd180 !important}.orange-text.text-accent-1{color:#ffd180 !important}.orange.accent-2{background-color:#ffab40 !important}.orange-text.text-accent-2{color:#ffab40 !important}.orange.accent-3{background-color:#ff9100 !important}.orange-text.text-accent-3{color:#ff9100 !important}.orange.accent-4{background-color:#ff6d00 !important}.orange-text.text-accent-4{color:#ff6d00 !important}.deep-orange{background-color:#ff5722 !important}.deep-orange-text{color:#ff5722 !important}.deep-orange.lighten-5{background-color:#fbe9e7 !important}.deep-orange-text.text-lighten-5{color:#fbe9e7 !important}.deep-orange.lighten-4{background-color:#ffccbc !important}.deep-orange-text.text-lighten-4{color:#ffccbc !important}.deep-orange.lighten-3{background-color:#ffab91 !important}.deep-orange-text.text-lighten-3{color:#ffab91 !important}.deep-orange.lighten-2{background-color:#ff8a65 !important}.deep-orange-text.text-lighten-2{color:#ff8a65 !important}.deep-orange.lighten-1{background-color:#ff7043 !important}.deep-orange-text.text-lighten-1{color:#ff7043 !important}.deep-orange.darken-1{background-color:#f4511e !important}.deep-orange-text.text-darken-1{color:#f4511e !important}.deep-orange.darken-2{background-color:#e64a19 !important}.deep-orange-text.text-darken-2{color:#e64a19 !important}.deep-orange.darken-3{background-color:#d84315 !important}.deep-orange-text.text-darken-3{color:#d84315 !important}.deep-orange.darken-4{background-color:#bf360c !important}.deep-orange-text.text-darken-4{color:#bf360c !important}.deep-orange.accent-1{background-color:#ff9e80 !important}.deep-orange-text.text-accent-1{color:#ff9e80 !important}.deep-orange.accent-2{background-color:#ff6e40 !important}.deep-orange-text.text-accent-2{color:#ff6e40 !important}.deep-orange.accent-3{background-color:#ff3d00 !important}.deep-orange-text.text-accent-3{color:#ff3d00 !important}.deep-orange.accent-4{background-color:#dd2c00 !important}.deep-orange-text.text-accent-4{color:#dd2c00 !important}.brown{background-color:#795548 !important}.brown-text{color:#795548 !important}.brown.lighten-5{background-color:#efebe9 !important}.brown-text.text-lighten-5{color:#efebe9 !important}.brown.lighten-4{background-color:#d7ccc8 !important}.brown-text.text-lighten-4{color:#d7ccc8 !important}.brown.lighten-3{background-color:#bcaaa4 !important}.brown-text.text-lighten-3{color:#bcaaa4 !important}.brown.lighten-2{background-color:#a1887f !important}.brown-text.text-lighten-2{color:#a1887f !important}.brown.lighten-1{background-color:#8d6e63 !important}.brown-text.text-lighten-1{color:#8d6e63 !important}.brown.darken-1{background-color:#6d4c41 !important}.brown-text.text-darken-1{color:#6d4c41 !important}.brown.darken-2{background-color:#5d4037 !important}.brown-text.text-darken-2{color:#5d4037 !important}.brown.darken-3{background-color:#4e342e !important}.brown-text.text-darken-3{color:#4e342e !important}.brown.darken-4{background-color:#3e2723 !important}.brown-text.text-darken-4{color:#3e2723 !important}.blue-grey{background-color:#607d8b !important}.blue-grey-text{color:#607d8b !important}.blue-grey.lighten-5{background-color:#eceff1 !important}.blue-grey-text.text-lighten-5{color:#eceff1 !important}.blue-grey.lighten-4{background-color:#cfd8dc !important}.blue-grey-text.text-lighten-4{color:#cfd8dc !important}.blue-grey.lighten-3{background-color:#b0bec5 !important}.blue-grey-text.text-lighten-3{color:#b0bec5 !important}.blue-grey.lighten-2{background-color:#90a4ae !important}.blue-grey-text.text-lighten-2{color:#90a4ae !important}.blue-grey.lighten-1{background-color:#78909c !important}.blue-grey-text.text-lighten-1{color:#78909c !important}.blue-grey.darken-1{background-color:#546e7a !important}.blue-grey-text.text-darken-1{color:#546e7a !important}.blue-grey.darken-2{background-color:#455a64 !important}.blue-grey-text.text-darken-2{color:#455a64 !important}.blue-grey.darken-3{background-color:#37474f !important}.blue-grey-text.text-darken-3{color:#37474f !important}.blue-grey.darken-4{background-color:#263238 !important}.blue-grey-text.text-darken-4{color:#263238 !important}.grey{background-color:#9e9e9e !important}.grey-text{color:#9e9e9e !important}.grey.lighten-5{background-color:#fafafa !important}.grey-text.text-lighten-5{color:#fafafa !important}.grey.lighten-4{background-color:#f5f5f5 !important}.grey-text.text-lighten-4{color:#f5f5f5 !important}.grey.lighten-3{background-color:#eee !important}.grey-text.text-lighten-3{color:#eee !important}.grey.lighten-2{background-color:#e0e0e0 !important}.grey-text.text-lighten-2{color:#e0e0e0 !important}.grey.lighten-1{background-color:#bdbdbd !important}.grey-text.text-lighten-1{color:#bdbdbd !important}.grey.darken-1{background-color:#757575 !important}.grey-text.text-darken-1{color:#757575 !important}.grey.darken-2{background-color:#616161 !important}.grey-text.text-darken-2{color:#616161 !important}.grey.darken-3{background-color:#424242 !important}.grey-text.text-darken-3{color:#424242 !important}.grey.darken-4{background-color:#212121 !important}.grey-text.text-darken-4{color:#212121 !important}.black{background-color:#000 !important}.black-text{color:#000 !important}.white{background-color:#fff !important}.white-text{color:#fff !important}.transparent{background-color:transparent !important}.transparent-text{color:transparent !important}/*! normalize.css v3.0.3 | MIT License | github.com/necolas/normalize.css */html{font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}article,aside,details,figcaption,figure,footer,header,hgroup,main,menu,nav,section,summary{display:block}audio,canvas,progress,video{display:inline-block;vertical-align:baseline}audio:not([controls]){display:none;height:0}[hidden],template{display:none}a{background-color:transparent}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:bold}dfn{font-style:italic}h1{font-size:2em;margin:0.67em 0}mark{background:#ff0;color:#000}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-0.5em}sub{bottom:-0.25em}img{border:0}svg:not(:root){overflow:hidden}figure{margin:1em 40px}hr{box-sizing:content-box;height:0}pre{overflow:auto}code,kbd,pre,samp{font-family:monospace, monospace;font-size:1em}button,input,optgroup,select,textarea{color:inherit;font:inherit;margin:0}button{overflow:visible}button,select{text-transform:none}button,html input[type="button"],input[type="reset"],input[type="submit"]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}input{line-height:normal}input[type="checkbox"],input[type="radio"]{box-sizing:border-box;padding:0}input[type="number"]::-webkit-inner-spin-button,input[type="number"]::-webkit-outer-spin-button{height:auto}input[type="search"]{-webkit-appearance:textfield;box-sizing:content-box}input[type="search"]::-webkit-search-cancel-button,input[type="search"]::-webkit-search-decoration{-webkit-appearance:none}fieldset{border:1px solid #c0c0c0;margin:0 2px;padding:0.35em 0.625em 0.75em}legend{border:0;padding:0}textarea{overflow:auto}optgroup{font-weight:bold}table{border-collapse:collapse;border-spacing:0}td,th{padding:0}html{box-sizing:border-box}*,*:before,*:after{box-sizing:inherit}ul{padding:0;list-style-type:none}ul.browser-default,ul.browser-default li{list-style-type:initial}ul li{list-style-type:none}a{color:#039be5;text-decoration:none;-webkit-tap-highlight-color:transparent}.valign-wrapper{display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-align-items:center;-ms-flex-align:center;align-items:center}.valign-wrapper .valign{display:block}.clearfix{clear:both}.z-depth-0{box-shadow:none !important}.z-depth-1,nav,.card-panel,.card,.toast,.btn,.btn-large,.btn-floating,.dropdown-content,.collapsible,.side-nav{box-shadow:0 2px 5px 0 rgba(0,0,0,0.16),0 2px 10px 0 rgba(0,0,0,0.12)}.z-depth-1-half,.btn:hover,.btn-large:hover,.btn-floating:hover{box-shadow:0 5px 11px 0 rgba(0,0,0,0.18),0 4px 15px 0 rgba(0,0,0,0.15)}.z-depth-2{box-shadow:0 8px 17px 0 rgba(0,0,0,0.2),0 6px 20px 0 rgba(0,0,0,0.19)}.z-depth-3{box-shadow:0 12px 15px 0 rgba(0,0,0,0.24),0 17px 50px 0 rgba(0,0,0,0.19)}.z-depth-4,.modal{box-shadow:0 16px 28px 0 rgba(0,0,0,0.22),0 25px 55px 0 rgba(0,0,0,0.21)}.z-depth-5{box-shadow:0 27px 24px 0 rgba(0,0,0,0.2),0 40px 77px 0 rgba(0,0,0,0.22)}.hoverable{transition:box-shadow .25s;box-shadow:0}.hoverable:hover{transition:box-shadow .25s;box-shadow:0 8px 17px 0 rgba(0,0,0,0.2),0 6px 20px 0 rgba(0,0,0,0.19)}.divider{height:1px;overflow:hidden;background-color:#e0e0e0}blockquote{margin:20px 0;padding-left:1.5rem;border-left:5px solid #ee6e73}i{line-height:inherit}i.left{float:left;margin-right:15px}i.right{float:right;margin-left:15px}i.tiny{font-size:1rem}i.small{font-size:2rem}i.medium{font-size:4rem}i.large{font-size:6rem}img.responsive-img,video.responsive-video{max-width:100%;height:auto}.pagination li{display:inline-block;border-radius:2px;text-align:center;vertical-align:top;height:30px}.pagination li a{color:#444;display:inline-block;font-size:1.2rem;padding:0 10px;line-height:30px}.pagination li.active a{color:#fff}.pagination li.active{background-color:#ee6e73}.pagination li.disabled a{cursor:default;color:#999}.pagination li i{font-size:2rem}.pagination li.pages ul li{display:inline-block;float:none}@media only screen and (max-width: 992px){.pagination{width:100%}.pagination li.prev,.pagination li.next{width:10%}.pagination li.pages{width:80%;overflow:hidden;white-space:nowrap}}.breadcrumb{font-size:18px;color:rgba(255,255,255,0.7)}.breadcrumb i,.breadcrumb [class^="mdi-"],.breadcrumb [class*="mdi-"],.breadcrumb i.material-icons{display:inline-block;float:left;font-size:24px}.breadcrumb:before{content:'\E5CC';color:rgba(255,255,255,0.7);vertical-align:top;display:inline-block;font-family:'Material Icons';font-weight:normal;font-style:normal;font-size:25px;margin:0 10px 0 8px;-webkit-font-smoothing:antialiased}.breadcrumb:first-child:before{display:none}.breadcrumb:last-child{color:#fff}.parallax-container{position:relative;overflow:hidden;height:500px}.parallax{position:absolute;top:0;left:0;right:0;bottom:0;z-index:-1}.parallax img{display:none;position:absolute;left:50%;bottom:0;min-width:100%;min-height:100%;-webkit-transform:translate3d(0, 0, 0);transform:translate3d(0, 0, 0);-webkit-transform:translateX(-50%);transform:translateX(-50%)}.pin-top,.pin-bottom{position:relative}.pinned{position:fixed !important}ul.staggered-list li{opacity:0}.fade-in{opacity:0;-webkit-transform-origin:0 50%;transform-origin:0 50%}@media only screen and (max-width: 600px){.hide-on-small-only,.hide-on-small-and-down{display:none !important}}@media only screen and (max-width: 992px){.hide-on-med-and-down{display:none !important}}@media only screen and (min-width: 601px){.hide-on-med-and-up{display:none !important}}@media only screen and (min-width: 600px) and (max-width: 992px){.hide-on-med-only{display:none !important}}@media only screen and (min-width: 993px){.hide-on-large-only{display:none !important}}@media only screen and (min-width: 993px){.show-on-large{display:block !important}}@media only screen and (min-width: 600px) and (max-width: 992px){.show-on-medium{display:block !important}}@media only screen and (max-width: 600px){.show-on-small{display:block !important}}@media only screen and (min-width: 601px){.show-on-medium-and-up{display:block !important}}@media only screen and (max-width: 992px){.show-on-medium-and-down{display:block !important}}@media only screen and (max-width: 600px){.center-on-small-only{text-align:center}}footer.page-footer{margin-top:20px;padding-top:20px;background-color:#ee6e73}footer.page-footer .footer-copyright{overflow:hidden;height:50px;line-height:50px;color:rgba(255,255,255,0.8);background-color:rgba(51,51,51,0.08)}table,th,td{border:none}table{width:100%;display:table}table.bordered>thead>tr,table.bordered>tbody>tr{border-bottom:1px solid #d0d0d0}table.striped>tbody>tr:nth-child(odd){background-color:#f2f2f2}table.striped>tbody>tr>td{border-radius:0}table.highlight>tbody>tr{transition:background-color .25s ease}table.highlight>tbody>tr:hover{background-color:#f2f2f2}table.centered thead tr th,table.centered tbody tr td{text-align:center}thead{border-bottom:1px solid #d0d0d0}td,th{padding:15px 5px;display:table-cell;text-align:left;vertical-align:middle;border-radius:2px}@media only screen and (max-width: 992px){table.responsive-table{width:100%;border-collapse:collapse;border-spacing:0;display:block;position:relative}table.responsive-table td:empty:before{content:'\00a0'}table.responsive-table th,table.responsive-table td{margin:0;vertical-align:top}table.responsive-table th{text-align:left}table.responsive-table thead{display:block;float:left}table.responsive-table thead tr{display:block;padding:0 10px 0 0}table.responsive-table thead tr th::before{content:"\00a0"}table.responsive-table tbody{display:block;width:auto;position:relative;overflow-x:auto;white-space:nowrap}table.responsive-table tbody tr{display:inline-block;vertical-align:top}table.responsive-table th{display:block;text-align:right}table.responsive-table td{display:block;min-height:1.25em;text-align:left}table.responsive-table tr{padding:0 10px}table.responsive-table thead{border:0;border-right:1px solid #d0d0d0}table.responsive-table.bordered th{border-bottom:0;border-left:0}table.responsive-table.bordered td{border-left:0;border-right:0;border-bottom:0}table.responsive-table.bordered tr{border:0}table.responsive-table.bordered tbody tr{border-right:1px solid #d0d0d0}}.collection{margin:0.5rem 0 1rem 0;border:1px solid #e0e0e0;border-radius:2px;overflow:hidden;position:relative}.collection .collection-item{background-color:#fff;line-height:1.5rem;padding:10px 20px;margin:0;border-bottom:1px solid #e0e0e0}.collection .collection-item.avatar{min-height:84px;padding-left:72px;position:relative}.collection .collection-item.avatar .circle{position:absolute;width:42px;height:42px;overflow:hidden;left:15px;display:inline-block;vertical-align:middle}.collection .collection-item.avatar i.circle{font-size:18px;line-height:42px;color:#fff;background-color:#999;text-align:center}.collection .collection-item.avatar .title{font-size:16px}.collection .collection-item.avatar p{margin:0}.collection .collection-item.avatar .secondary-content{position:absolute;top:16px;right:16px}.collection .collection-item:last-child{border-bottom:none}.collection .collection-item.active{background-color:#26a69a;color:#eafaf9}.collection .collection-item.active .secondary-content{color:#fff}.collection a.collection-item{display:block;transition:.25s;color:#26a69a}.collection a.collection-item:not(.active):hover{background-color:#ddd}.collection.with-header .collection-header{background-color:#fff;border-bottom:1px solid #e0e0e0;padding:10px 20px}.collection.with-header .collection-item{padding-left:30px}.collection.with-header .collection-item.avatar{padding-left:72px}.secondary-content{float:right;color:#26a69a}.collapsible .collection{margin:0;border:none}span.badge{min-width:3rem;padding:0 6px;text-align:center;font-size:1rem;line-height:inherit;color:#757575;position:absolute;right:15px;box-sizing:border-box}span.badge.new{font-weight:300;font-size:0.8rem;color:#fff;background-color:#26a69a;border-radius:2px}span.badge.new:after{content:" new"}span.badge[data-badge-caption]::after{content:" " attr(data-badge-caption)}nav ul a span.badge{position:static;margin-left:4px;line-height:0}.video-container{position:relative;padding-bottom:56.25%;height:0;overflow:hidden}.video-container iframe,.video-container object,.video-container embed{position:absolute;top:0;left:0;width:100%;height:100%}.progress{position:relative;height:4px;display:block;width:100%;background-color:#acece6;border-radius:2px;margin:0.5rem 0 1rem 0;overflow:hidden}.progress .determinate{position:absolute;top:0;left:0;bottom:0;background-color:#26a69a;transition:width .3s linear}.progress .indeterminate{background-color:#26a69a}.progress .indeterminate:before{content:'';position:absolute;background-color:inherit;top:0;left:0;bottom:0;will-change:left, right;-webkit-animation:indeterminate 2.1s cubic-bezier(0.65, 0.815, 0.735, 0.395) infinite;animation:indeterminate 2.1s cubic-bezier(0.65, 0.815, 0.735, 0.395) infinite}.progress .indeterminate:after{content:'';position:absolute;background-color:inherit;top:0;left:0;bottom:0;will-change:left, right;-webkit-animation:indeterminate-short 2.1s cubic-bezier(0.165, 0.84, 0.44, 1) infinite;animation:indeterminate-short 2.1s cubic-bezier(0.165, 0.84, 0.44, 1) infinite;-webkit-animation-delay:1.15s;animation-delay:1.15s}@-webkit-keyframes indeterminate{0%{left:-35%;right:100%}60%{left:100%;right:-90%}100%{left:100%;right:-90%}}@keyframes indeterminate{0%{left:-35%;right:100%}60%{left:100%;right:-90%}100%{left:100%;right:-90%}}@-webkit-keyframes indeterminate-short{0%{left:-200%;right:100%}60%{left:107%;right:-8%}100%{left:107%;right:-8%}}@keyframes indeterminate-short{0%{left:-200%;right:100%}60%{left:107%;right:-8%}100%{left:107%;right:-8%}}.hide{display:none !important}.left-align{text-align:left}.right-align{text-align:right}.center,.center-align{text-align:center}.left{float:left !important}.right{float:right !important}.no-select,input[type=range],input[type=range]+.thumb{-webkit-touch-callout:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.circle{border-radius:50%}.center-block{display:block;margin-left:auto;margin-right:auto}.truncate{display:block;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.no-padding{padding:0 !important}.material-icons{text-rendering:optimizeLegibility;-webkit-font-feature-settings:'liga';-moz-font-feature-settings:'liga';font-feature-settings:'liga'}.container{margin:0 auto;max-width:1280px;width:90%}@media only screen and (min-width: 601px){.container{width:85%}}@media only screen and (min-width: 993px){.container{width:70%}}.container .row{margin-left:-0.75rem;margin-right:-0.75rem}.section{padding-top:1rem;padding-bottom:1rem}.section.no-pad{padding:0}.section.no-pad-bot{padding-bottom:0}.section.no-pad-top{padding-top:0}.row{margin-left:auto;margin-right:auto;margin-bottom:20px}.row:after{content:"";display:table;clear:both}.row .col{float:left;box-sizing:border-box;padding:0 0.75rem;min-height:1px}.row .col[class*="push-"],.row .col[class*="pull-"]{position:relative}.row .col.s1{width:8.3333333333%;margin-left:auto;left:auto;right:auto}.row .col.s2{width:16.6666666667%;margin-left:auto;left:auto;right:auto}.row .col.s3{width:25%;margin-left:auto;left:auto;right:auto}.row .col.s4{width:33.3333333333%;margin-left:auto;left:auto;right:auto}.row .col.s5{width:41.6666666667%;margin-left:auto;left:auto;right:auto}.row .col.s6{width:50%;margin-left:auto;left:auto;right:auto}.row .col.s7{width:58.3333333333%;margin-left:auto;left:auto;right:auto}.row .col.s8{width:66.6666666667%;margin-left:auto;left:auto;right:auto}.row .col.s9{width:75%;margin-left:auto;left:auto;right:auto}.row .col.s10{width:83.3333333333%;margin-left:auto;left:auto;right:auto}.row .col.s11{width:91.6666666667%;margin-left:auto;left:auto;right:auto}.row .col.s12{width:100%;margin-left:auto;left:auto;right:auto}.row .col.offset-s1{margin-left:8.3333333333%}.row .col.pull-s1{right:8.3333333333%}.row .col.push-s1{left:8.3333333333%}.row .col.offset-s2{margin-left:16.6666666667%}.row .col.pull-s2{right:16.6666666667%}.row .col.push-s2{left:16.6666666667%}.row .col.offset-s3{margin-left:25%}.row .col.pull-s3{right:25%}.row .col.push-s3{left:25%}.row .col.offset-s4{margin-left:33.3333333333%}.row .col.pull-s4{right:33.3333333333%}.row .col.push-s4{left:33.3333333333%}.row .col.offset-s5{margin-left:41.6666666667%}.row .col.pull-s5{right:41.6666666667%}.row .col.push-s5{left:41.6666666667%}.row .col.offset-s6{margin-left:50%}.row .col.pull-s6{right:50%}.row .col.push-s6{left:50%}.row .col.offset-s7{margin-left:58.3333333333%}.row .col.pull-s7{right:58.3333333333%}.row .col.push-s7{left:58.3333333333%}.row .col.offset-s8{margin-left:66.6666666667%}.row .col.pull-s8{right:66.6666666667%}.row .col.push-s8{left:66.6666666667%}.row .col.offset-s9{margin-left:75%}.row .col.pull-s9{right:75%}.row .col.push-s9{left:75%}.row .col.offset-s10{margin-left:83.3333333333%}.row .col.pull-s10{right:83.3333333333%}.row .col.push-s10{left:83.3333333333%}.row .col.offset-s11{margin-left:91.6666666667%}.row .col.pull-s11{right:91.6666666667%}.row .col.push-s11{left:91.6666666667%}.row .col.offset-s12{margin-left:100%}.row .col.pull-s12{right:100%}.row .col.push-s12{left:100%}@media only screen and (min-width: 601px){.row .col.m1{width:8.3333333333%;margin-left:auto;left:auto;right:auto}.row .col.m2{width:16.6666666667%;margin-left:auto;left:auto;right:auto}.row .col.m3{width:25%;margin-left:auto;left:auto;right:auto}.row .col.m4{width:33.3333333333%;margin-left:auto;left:auto;right:auto}.row .col.m5{width:41.6666666667%;margin-left:auto;left:auto;right:auto}.row .col.m6{width:50%;margin-left:auto;left:auto;right:auto}.row .col.m7{width:58.3333333333%;margin-left:auto;left:auto;right:auto}.row .col.m8{width:66.6666666667%;margin-left:auto;left:auto;right:auto}.row .col.m9{width:75%;margin-left:auto;left:auto;right:auto}.row .col.m10{width:83.3333333333%;margin-left:auto;left:auto;right:auto}.row .col.m11{width:91.6666666667%;margin-left:auto;left:auto;right:auto}.row .col.m12{width:100%;margin-left:auto;left:auto;right:auto}.row .col.offset-m1{margin-left:8.3333333333%}.row .col.pull-m1{right:8.3333333333%}.row .col.push-m1{left:8.3333333333%}.row .col.offset-m2{margin-left:16.6666666667%}.row .col.pull-m2{right:16.6666666667%}.row .col.push-m2{left:16.6666666667%}.row .col.offset-m3{margin-left:25%}.row .col.pull-m3{right:25%}.row .col.push-m3{left:25%}.row .col.offset-m4{margin-left:33.3333333333%}.row .col.pull-m4{right:33.3333333333%}.row .col.push-m4{left:33.3333333333%}.row .col.offset-m5{margin-left:41.6666666667%}.row .col.pull-m5{right:41.6666666667%}.row .col.push-m5{left:41.6666666667%}.row .col.offset-m6{margin-left:50%}.row .col.pull-m6{right:50%}.row .col.push-m6{left:50%}.row .col.offset-m7{margin-left:58.3333333333%}.row .col.pull-m7{right:58.3333333333%}.row .col.push-m7{left:58.3333333333%}.row .col.offset-m8{margin-left:66.6666666667%}.row .col.pull-m8{right:66.6666666667%}.row .col.push-m8{left:66.6666666667%}.row .col.offset-m9{margin-left:75%}.row .col.pull-m9{right:75%}.row .col.push-m9{left:75%}.row .col.offset-m10{margin-left:83.3333333333%}.row .col.pull-m10{right:83.3333333333%}.row .col.push-m10{left:83.3333333333%}.row .col.offset-m11{margin-left:91.6666666667%}.row .col.pull-m11{right:91.6666666667%}.row .col.push-m11{left:91.6666666667%}.row .col.offset-m12{margin-left:100%}.row .col.pull-m12{right:100%}.row .col.push-m12{left:100%}}@media only screen and (min-width: 993px){.row .col.l1{width:8.3333333333%;margin-left:auto;left:auto;right:auto}.row .col.l2{width:16.6666666667%;margin-left:auto;left:auto;right:auto}.row .col.l3{width:25%;margin-left:auto;left:auto;right:auto}.row .col.l4{width:33.3333333333%;margin-left:auto;left:auto;right:auto}.row .col.l5{width:41.6666666667%;margin-left:auto;left:auto;right:auto}.row .col.l6{width:50%;margin-left:auto;left:auto;right:auto}.row .col.l7{width:58.3333333333%;margin-left:auto;left:auto;right:auto}.row .col.l8{width:66.6666666667%;margin-left:auto;left:auto;right:auto}.row .col.l9{width:75%;margin-left:auto;left:auto;right:auto}.row .col.l10{width:83.3333333333%;margin-left:auto;left:auto;right:auto}.row .col.l11{width:91.6666666667%;margin-left:auto;left:auto;right:auto}.row .col.l12{width:100%;margin-left:auto;left:auto;right:auto}.row .col.offset-l1{margin-left:8.3333333333%}.row .col.pull-l1{right:8.3333333333%}.row .col.push-l1{left:8.3333333333%}.row .col.offset-l2{margin-left:16.6666666667%}.row .col.pull-l2{right:16.6666666667%}.row .col.push-l2{left:16.6666666667%}.row .col.offset-l3{margin-left:25%}.row .col.pull-l3{right:25%}.row .col.push-l3{left:25%}.row .col.offset-l4{margin-left:33.3333333333%}.row .col.pull-l4{right:33.3333333333%}.row .col.push-l4{left:33.3333333333%}.row .col.offset-l5{margin-left:41.6666666667%}.row .col.pull-l5{right:41.6666666667%}.row .col.push-l5{left:41.6666666667%}.row .col.offset-l6{margin-left:50%}.row .col.pull-l6{right:50%}.row .col.push-l6{left:50%}.row .col.offset-l7{margin-left:58.3333333333%}.row .col.pull-l7{right:58.3333333333%}.row .col.push-l7{left:58.3333333333%}.row .col.offset-l8{margin-left:66.6666666667%}.row .col.pull-l8{right:66.6666666667%}.row .col.push-l8{left:66.6666666667%}.row .col.offset-l9{margin-left:75%}.row .col.pull-l9{right:75%}.row .col.push-l9{left:75%}.row .col.offset-l10{margin-left:83.3333333333%}.row .col.pull-l10{right:83.3333333333%}.row .col.push-l10{left:83.3333333333%}.row .col.offset-l11{margin-left:91.6666666667%}.row .col.pull-l11{right:91.6666666667%}.row .col.push-l11{left:91.6666666667%}.row .col.offset-l12{margin-left:100%}.row .col.pull-l12{right:100%}.row .col.push-l12{left:100%}}nav{color:#fff;background-color:#ee6e73;width:100%;height:56px;line-height:56px}nav a{color:#fff}nav i,nav [class^="mdi-"],nav [class*="mdi-"],nav i.material-icons{display:block;font-size:2rem;height:56px;line-height:56px}nav .nav-wrapper{position:relative;height:100%}@media only screen and (min-width: 993px){nav a.button-collapse{display:none}}nav .button-collapse{float:left;position:relative;z-index:1;height:56px}nav .button-collapse i{font-size:2.7rem;height:56px;line-height:56px}nav .brand-logo{position:absolute;color:#fff;display:inline-block;font-size:2.1rem;padding:0;white-space:nowrap}nav .brand-logo.center{left:50%;-webkit-transform:translateX(-50%);transform:translateX(-50%)}@media only screen and (max-width: 992px){nav .brand-logo{left:50%;-webkit-transform:translateX(-50%);transform:translateX(-50%)}nav .brand-logo.left,nav .brand-logo.right{padding:0;-webkit-transform:none;transform:none}nav .brand-logo.left{left:0.5rem}nav .brand-logo.right{right:0.5rem;left:auto}}nav .brand-logo.right{right:0.5rem;padding:0}nav .brand-logo i,nav .brand-logo [class^="mdi-"],nav .brand-logo [class*="mdi-"],nav .brand-logo i.material-icons{float:left;margin-right:15px}nav ul{margin:0}nav ul li{transition:background-color .3s;float:left;padding:0}nav ul li.active{background-color:rgba(0,0,0,0.1)}nav ul a{transition:background-color .3s;font-size:1rem;color:#fff;display:block;padding:0 15px;cursor:pointer}nav ul a.btn,nav ul a.btn-large,nav ul a.btn-large,nav ul a.btn-flat,nav ul a.btn-floating{margin-top:-2px;margin-left:15px;margin-right:15px}nav ul a:hover{background-color:rgba(0,0,0,0.1)}nav ul.left{float:left}nav form{height:100%}nav .input-field{margin:0;height:100%}nav .input-field input{height:100%;font-size:1.2rem;border:none;padding-left:2rem}nav .input-field input:focus,nav .input-field input[type=text]:valid,nav .input-field input[type=password]:valid,nav .input-field input[type=email]:valid,nav .input-field input[type=url]:valid,nav .input-field input[type=date]:valid{border:none;box-shadow:none}nav .input-field label{top:0;left:0}nav .input-field label i{color:rgba(255,255,255,0.7);transition:color .3s}nav .input-field label.active i{color:#fff}nav .input-field label.active{-webkit-transform:translateY(0);transform:translateY(0)}.navbar-fixed{position:relative;height:56px;z-index:998}.navbar-fixed nav{position:fixed}@media only screen and (min-width: 601px){nav,nav .nav-wrapper i,nav a.button-collapse,nav a.button-collapse i{height:64px;line-height:64px}.navbar-fixed{height:64px}}@font-face{font-family:"Roboto";src:local(Roboto Thin),url("../fonts/roboto/Roboto-Thin.eot");src:url("../fonts/roboto/Roboto-Thin.eot?#iefix") format("embedded-opentype"),url("../fonts/roboto/Roboto-Thin.woff2") format("woff2"),url("../fonts/roboto/Roboto-Thin.woff") format("woff"),url("../fonts/roboto/Roboto-Thin.ttf") format("truetype");font-weight:200}@font-face{font-family:"Roboto";src:local(Roboto Light),url("../fonts/roboto/Roboto-Light.eot");src:url("../fonts/roboto/Roboto-Light.eot?#iefix") format("embedded-opentype"),url("../fonts/roboto/Roboto-Light.woff2") format("woff2"),url("../fonts/roboto/Roboto-Light.woff") format("woff"),url("../fonts/roboto/Roboto-Light.ttf") format("truetype");font-weight:300}@font-face{font-family:"Roboto";src:local(Roboto Regular),url("../fonts/roboto/Roboto-Regular.eot");src:url("../fonts/roboto/Roboto-Regular.eot?#iefix") format("embedded-opentype"),url("../fonts/roboto/Roboto-Regular.woff2") format("woff2"),url("../fonts/roboto/Roboto-Regular.woff") format("woff"),url("../fonts/roboto/Roboto-Regular.ttf") format("truetype");font-weight:400}@font-face{font-family:"Roboto";src:url("../fonts/roboto/Roboto-Medium.eot");src:url("../fonts/roboto/Roboto-Medium.eot?#iefix") format("embedded-opentype"),url("../fonts/roboto/Roboto-Medium.woff2") format("woff2"),url("../fonts/roboto/Roboto-Medium.woff") format("woff"),url("../fonts/roboto/Roboto-Medium.ttf") format("truetype");font-weight:500}@font-face{font-family:"Roboto";src:url("../fonts/roboto/Roboto-Bold.eot");src:url("../fonts/roboto/Roboto-Bold.eot?#iefix") format("embedded-opentype"),url("../fonts/roboto/Roboto-Bold.woff2") format("woff2"),url("../fonts/roboto/Roboto-Bold.woff") format("woff"),url("../fonts/roboto/Roboto-Bold.ttf") format("truetype");font-weight:700}a{text-decoration:none}html{line-height:1.5;font-family:"Roboto", sans-serif;font-weight:normal;color:rgba(0,0,0,0.87)}@media only screen and (min-width: 0){html{font-size:14px}}@media only screen and (min-width: 992px){html{font-size:14.5px}}@media only screen and (min-width: 1200px){html{font-size:15px}}h1,h2,h3,h4,h5,h6{font-weight:400;line-height:1.1}h1 a,h2 a,h3 a,h4 a,h5 a,h6 a{font-weight:inherit}h1{font-size:4.2rem;line-height:110%;margin:2.1rem 0 1.68rem 0}h2{font-size:3.56rem;line-height:110%;margin:1.78rem 0 1.424rem 0}h3{font-size:2.92rem;line-height:110%;margin:1.46rem 0 1.168rem 0}h4{font-size:2.28rem;line-height:110%;margin:1.14rem 0 0.912rem 0}h5{font-size:1.64rem;line-height:110%;margin:0.82rem 0 0.656rem 0}h6{font-size:1rem;line-height:110%;margin:0.5rem 0 0.4rem 0}em{font-style:italic}strong{font-weight:500}small{font-size:75%}.light,footer.page-footer .footer-copyright{font-weight:300}.thin{font-weight:200}.flow-text{font-weight:300}@media only screen and (min-width: 360px){.flow-text{font-size:1.2rem}}@media only screen and (min-width: 390px){.flow-text{font-size:1.224rem}}@media only screen and (min-width: 420px){.flow-text{font-size:1.248rem}}@media only screen and (min-width: 450px){.flow-text{font-size:1.272rem}}@media only screen and (min-width: 480px){.flow-text{font-size:1.296rem}}@media only screen and (min-width: 510px){.flow-text{font-size:1.32rem}}@media only screen and (min-width: 540px){.flow-text{font-size:1.344rem}}@media only screen and (min-width: 570px){.flow-text{font-size:1.368rem}}@media only screen and (min-width: 600px){.flow-text{font-size:1.392rem}}@media only screen and (min-width: 630px){.flow-text{font-size:1.416rem}}@media only screen and (min-width: 660px){.flow-text{font-size:1.44rem}}@media only screen and (min-width: 690px){.flow-text{font-size:1.464rem}}@media only screen and (min-width: 720px){.flow-text{font-size:1.488rem}}@media only screen and (min-width: 750px){.flow-text{font-size:1.512rem}}@media only screen and (min-width: 780px){.flow-text{font-size:1.536rem}}@media only screen and (min-width: 810px){.flow-text{font-size:1.56rem}}@media only screen and (min-width: 840px){.flow-text{font-size:1.584rem}}@media only screen and (min-width: 870px){.flow-text{font-size:1.608rem}}@media only screen and (min-width: 900px){.flow-text{font-size:1.632rem}}@media only screen and (min-width: 930px){.flow-text{font-size:1.656rem}}@media only screen and (min-width: 960px){.flow-text{font-size:1.68rem}}@media only screen and (max-width: 360px){.flow-text{font-size:1.2rem}}.card-panel{transition:box-shadow .25s;padding:20px;margin:0.5rem 0 1rem 0;border-radius:2px;background-color:#fff}.card{position:relative;margin:0.5rem 0 1rem 0;background-color:#fff;transition:box-shadow .25s;border-radius:2px}.card .card-title{font-size:24px;font-weight:300}.card .card-title.activator{cursor:pointer}.card.small,.card.medium,.card.large{position:relative}.card.small .card-image,.card.medium .card-image,.card.large .card-image{max-height:60%;overflow:hidden}.card.small .card-image+.card-content,.card.medium .card-image+.card-content,.card.large .card-image+.card-content{max-height:40%}.card.small .card-content,.card.medium .card-content,.card.large .card-content{max-height:100%;overflow:hidden}.card.small .card-action,.card.medium .card-action,.card.large .card-action{position:absolute;bottom:0;left:0;right:0}.card.small{height:300px}.card.medium{height:400px}.card.large{height:500px}.card.horizontal{display:-webkit-flex;display:-ms-flexbox;display:flex}.card.horizontal.small .card-image,.card.horizontal.medium .card-image,.card.horizontal.large .card-image{height:100%;max-height:none;overflow:visible}.card.horizontal.small .card-image img,.card.horizontal.medium .card-image img,.card.horizontal.large .card-image img{height:100%}.card.horizontal .card-image{max-width:50%}.card.horizontal .card-image img{max-width:100%;width:auto}.card.horizontal .card-stacked{display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column;-webkit-flex:1;-ms-flex:1;flex:1;position:relative}.card.horizontal .card-stacked .card-content{-webkit-flex-grow:1;-ms-flex-positive:1;flex-grow:1}.card.sticky-action .card-action{z-index:2}.card.sticky-action .card-reveal{z-index:1;padding-bottom:64px}.card .card-image{position:relative}.card .card-image img{display:block;border-radius:2px 2px 0 0;position:relative;left:0;right:0;top:0;bottom:0;width:100%}.card .card-image .card-title{color:#fff;position:absolute;bottom:0;left:0;padding:20px}.card .card-content{padding:20px;border-radius:0 0 2px 2px}.card .card-content p{margin:0;color:inherit}.card .card-content .card-title{line-height:48px}.card .card-action{position:relative;background-color:inherit;border-top:1px solid rgba(160,160,160,0.2);padding:20px}.card .card-action a:not(.btn):not(.btn-large):not(.btn-floating){color:#ffab40;margin-right:20px;transition:color .3s ease;text-transform:uppercase}.card .card-action a:not(.btn):not(.btn-large):not(.btn-floating):hover{color:#ffd8a6}.card .card-reveal{padding:20px;position:absolute;background-color:#fff;width:100%;overflow-y:auto;top:100%;height:100%;z-index:3;display:none}.card .card-reveal .card-title{cursor:pointer;display:block}#toast-container{display:block;position:fixed;z-index:10000}@media only screen and (max-width: 600px){#toast-container{min-width:100%;bottom:0%}}@media only screen and (min-width: 601px) and (max-width: 992px){#toast-container{left:5%;bottom:7%;max-width:90%}}@media only screen and (min-width: 993px){#toast-container{top:10%;right:7%;max-width:86%}}.toast{border-radius:2px;top:0;width:auto;clear:both;margin-top:10px;position:relative;max-width:100%;height:auto;min-height:48px;line-height:1.5em;word-break:break-all;background-color:#323232;padding:10px 25px;font-size:1.1rem;font-weight:300;color:#fff;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-align-items:center;-ms-flex-align:center;align-items:center;-webkit-justify-content:space-between;-ms-flex-pack:justify;justify-content:space-between}.toast .btn,.toast .btn-large,.toast .btn-flat{margin:0;margin-left:3rem}.toast.rounded{border-radius:24px}@media only screen and (max-width: 600px){.toast{width:100%;border-radius:0}}@media only screen and (min-width: 601px) and (max-width: 992px){.toast{float:left}}@media only screen and (min-width: 993px){.toast{float:right}}.tabs{display:-webkit-flex;display:-ms-flexbox;display:flex;position:relative;overflow-x:auto;overflow-y:hidden;height:48px;background-color:#fff;margin:0 auto;width:100%;white-space:nowrap}.tabs .tab{-webkit-box-flex:1;-webkit-flex-grow:1;-ms-flex-positive:1;flex-grow:1;display:block;float:left;text-align:center;line-height:48px;height:48px;padding:0;margin:0;text-transform:uppercase;text-overflow:ellipsis;overflow:hidden;letter-spacing:.8px;width:15%;min-width:80px}.tabs .tab a{color:#ee6e73;display:block;width:100%;height:100%;text-overflow:ellipsis;overflow:hidden;transition:color .28s ease}.tabs .tab a:hover{color:#f9c9cb}.tabs .tab.disabled a{color:#f9c9cb;cursor:default}.tabs .indicator{position:absolute;bottom:0;height:2px;background-color:#f6b2b5;will-change:left, right}.material-tooltip{padding:10px 8px;font-size:1rem;z-index:2000;background-color:transparent;border-radius:2px;color:#fff;min-height:36px;line-height:120%;opacity:0;display:none;position:absolute;text-align:center;max-width:calc(100% - 4px);overflow:hidden;left:0;top:0;pointer-events:none}.backdrop{position:absolute;opacity:0;display:none;height:7px;width:14px;border-radius:0 0 50% 50%;background-color:#323232;z-index:-1;-webkit-transform-origin:50% 0%;transform-origin:50% 0%;-webkit-transform:translate3d(0, 0, 0);transform:translate3d(0, 0, 0)}.btn,.btn-large,.btn-flat{border:none;border-radius:2px;display:inline-block;height:36px;line-height:36px;outline:0;padding:0 2rem;text-transform:uppercase;vertical-align:middle;-webkit-tap-highlight-color:transparent}.btn.disabled,.disabled.btn-large,.btn-floating.disabled,.btn-large.disabled,.btn:disabled,.btn-large:disabled,.btn-large:disabled,.btn-floating:disabled,.btn[disabled],[disabled].btn-large,.btn-large[disabled],.btn-floating[disabled]{background-color:#DFDFDF !important;box-shadow:none;color:#9F9F9F !important;cursor:default}.btn.disabled *,.disabled.btn-large *,.btn-floating.disabled *,.btn-large.disabled *,.btn:disabled *,.btn-large:disabled *,.btn-large:disabled *,.btn-floating:disabled *,.btn[disabled] *,[disabled].btn-large *,.btn-large[disabled] *,.btn-floating[disabled] *{pointer-events:none}.btn.disabled:hover,.disabled.btn-large:hover,.btn-floating.disabled:hover,.btn-large.disabled:hover,.btn:disabled:hover,.btn-large:disabled:hover,.btn-large:disabled:hover,.btn-floating:disabled:hover,.btn[disabled]:hover,[disabled].btn-large:hover,.btn-large[disabled]:hover,.btn-floating[disabled]:hover{background-color:#DFDFDF !important;color:#9F9F9F !important}.btn i,.btn-large i,.btn-floating i,.btn-large i,.btn-flat i{font-size:1.3rem;line-height:inherit}.btn,.btn-large{text-decoration:none;color:#fff;background-color:#26a69a;text-align:center;letter-spacing:.5px;transition:.2s ease-out;cursor:pointer}.btn:hover,.btn-large:hover{background-color:#2bbbad}.btn-floating{display:inline-block;color:#fff;position:relative;overflow:hidden;z-index:1;width:37px;height:37px;line-height:37px;padding:0;background-color:#26a69a;border-radius:50%;transition:.3s;cursor:pointer;vertical-align:middle}.btn-floating i{width:inherit;display:inline-block;text-align:center;color:#fff;font-size:1.6rem;line-height:37px}.btn-floating:hover{background-color:#26a69a}.btn-floating:before{border-radius:0}.btn-floating.btn-large{width:55.5px;height:55.5px}.btn-floating.btn-large i{line-height:55.5px}button.btn-floating{border:none}.fixed-action-btn{position:fixed;right:23px;bottom:23px;padding-top:15px;margin-bottom:0;z-index:998}.fixed-action-btn.active ul{visibility:visible}.fixed-action-btn.horizontal{padding:0 0 0 15px}.fixed-action-btn.horizontal ul{text-align:right;right:64px;top:50%;-webkit-transform:translateY(-50%);transform:translateY(-50%);height:100%;left:auto;width:500px}.fixed-action-btn.horizontal ul li{display:inline-block;margin:15px 15px 0 0}.fixed-action-btn ul{left:0;right:0;text-align:center;position:absolute;bottom:64px;margin:0;visibility:hidden}.fixed-action-btn ul li{margin-bottom:15px}.fixed-action-btn ul a.btn-floating{opacity:0}.btn-flat{box-shadow:none;background-color:transparent;color:#343434;cursor:pointer;transition:background-color .2s}.btn-flat:focus,.btn-flat:active{background-color:transparent}.btn-flat:hover{background-color:rgba(0,0,0,0.1);box-shadow:none}.btn-flat.disabled{color:#b3b3b3;cursor:default}.btn-large{height:54px;line-height:54px}.btn-large i{font-size:1.6rem}.btn-block{display:block}.dropdown-content{background-color:#fff;margin:0;display:none;min-width:100px;max-height:650px;overflow-y:auto;opacity:0;position:absolute;z-index:999;will-change:width, height}.dropdown-content li{clear:both;color:rgba(0,0,0,0.87);cursor:pointer;min-height:50px;line-height:1.5rem;width:100%;text-align:left;text-transform:none}.dropdown-content li:hover,.dropdown-content li.active,.dropdown-content li.selected{background-color:#eee}.dropdown-content li.active.selected{background-color:#e1e1e1}.dropdown-content li.divider{min-height:0;height:1px}.dropdown-content li>a,.dropdown-content li>span{font-size:16px;color:#26a69a;display:block;line-height:22px;padding:14px 16px}.dropdown-content li>span>label{top:1px;left:3px;height:18px}.dropdown-content li>a>i{height:inherit;line-height:inherit}/*! * Waves v0.6.0 * http://fian.my.id/Waves * * Copyright 2014 Alfiana E. Sibuea and other contributors * Released under the MIT license * https://github.com/fians/Waves/blob/master/LICENSE - */.waves-effect{position:relative;cursor:pointer;display:inline-block;overflow:hidden;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;-webkit-tap-highlight-color:transparent;vertical-align:middle;z-index:1;will-change:opacity, transform;transition:all .3s ease-out}.waves-effect .waves-ripple{position:absolute;border-radius:50%;width:20px;height:20px;margin-top:-10px;margin-left:-10px;opacity:0;background:rgba(0,0,0,0.2);transition:all 0.7s ease-out;transition-property:opacity, -webkit-transform;transition-property:transform, opacity;transition-property:transform, opacity, -webkit-transform;-webkit-transform:scale(0);transform:scale(0);pointer-events:none}.waves-effect.waves-light .waves-ripple{background-color:rgba(255,255,255,0.45)}.waves-effect.waves-red .waves-ripple{background-color:rgba(244,67,54,0.7)}.waves-effect.waves-yellow .waves-ripple{background-color:rgba(255,235,59,0.7)}.waves-effect.waves-orange .waves-ripple{background-color:rgba(255,152,0,0.7)}.waves-effect.waves-purple .waves-ripple{background-color:rgba(156,39,176,0.7)}.waves-effect.waves-green .waves-ripple{background-color:rgba(76,175,80,0.7)}.waves-effect.waves-teal .waves-ripple{background-color:rgba(0,150,136,0.7)}.waves-effect input[type="button"],.waves-effect input[type="reset"],.waves-effect input[type="submit"]{border:0;font-style:normal;font-size:inherit;text-transform:inherit;background:none}.waves-notransition{transition:none !important}.waves-circle{-webkit-transform:translateZ(0);transform:translateZ(0);-webkit-mask-image:-webkit-radial-gradient(circle, #fff 100%, #000 100%)}.waves-input-wrapper{border-radius:0.2em;vertical-align:bottom}.waves-input-wrapper .waves-button-input{position:relative;top:0;left:0;z-index:1}.waves-circle{text-align:center;width:2.5em;height:2.5em;line-height:2.5em;border-radius:50%;-webkit-mask-image:none}.waves-block{display:block}a.waves-effect .waves-ripple{z-index:-1}.modal{display:none;position:fixed;left:0;right:0;background-color:#fafafa;padding:0;max-height:70%;width:55%;margin:auto;overflow-y:auto;border-radius:2px;will-change:top, opacity}@media only screen and (max-width: 992px){.modal{width:80%}}.modal h1,.modal h2,.modal h3,.modal h4{margin-top:0}.modal .modal-content{padding:24px}.modal .modal-close{cursor:pointer}.modal .modal-footer{border-radius:0 0 2px 2px;background-color:#fafafa;padding:4px 6px;height:56px;width:100%}.modal .modal-footer .btn,.modal .modal-footer .btn-large,.modal .modal-footer .btn-flat{float:right;margin:6px 0}.lean-overlay{position:fixed;z-index:999;top:-100px;left:0;bottom:0;right:0;height:125%;width:100%;background:#000;display:none;will-change:opacity}.modal.modal-fixed-footer{padding:0;height:70%}.modal.modal-fixed-footer .modal-content{position:absolute;height:calc(100% - 56px);max-height:100%;width:100%;overflow-y:auto}.modal.modal-fixed-footer .modal-footer{border-top:1px solid rgba(0,0,0,0.1);position:absolute;bottom:0}.modal.bottom-sheet{top:auto;bottom:-100%;margin:0;width:100%;max-height:45%;border-radius:0;will-change:bottom, opacity}.collapsible{border-top:1px solid #ddd;border-right:1px solid #ddd;border-left:1px solid #ddd;margin:0.5rem 0 1rem 0}.collapsible-header{display:block;cursor:pointer;min-height:3rem;line-height:3rem;padding:0 1rem;background-color:#fff;border-bottom:1px solid #ddd}.collapsible-header i{width:2rem;font-size:1.6rem;line-height:3rem;display:block;float:left;text-align:center;margin-right:1rem}.collapsible-body{display:none;border-bottom:1px solid #ddd;box-sizing:border-box}.collapsible-body p{margin:0;padding:2rem}.side-nav .collapsible,.side-nav.fixed .collapsible{border:none;box-shadow:none}.side-nav .collapsible li,.side-nav.fixed .collapsible li{padding:0}.side-nav .collapsible-header,.side-nav.fixed .collapsible-header{background-color:transparent;border:none;line-height:inherit;height:inherit;padding:0 30px}.side-nav .collapsible-header:hover,.side-nav.fixed .collapsible-header:hover{background-color:rgba(0,0,0,0.05)}.side-nav .collapsible-header i,.side-nav.fixed .collapsible-header i{line-height:inherit}.side-nav .collapsible-body,.side-nav.fixed .collapsible-body{border:0;background-color:#fff}.side-nav .collapsible-body li a,.side-nav.fixed .collapsible-body li a{padding:0 37.5px 0 45px}.collapsible.popout{border:none;box-shadow:none}.collapsible.popout>li{box-shadow:0 2px 5px 0 rgba(0,0,0,0.16),0 2px 10px 0 rgba(0,0,0,0.12);margin:0 24px;transition:margin 0.35s cubic-bezier(0.25, 0.46, 0.45, 0.94)}.collapsible.popout>li.active{box-shadow:0 5px 11px 0 rgba(0,0,0,0.18),0 4px 15px 0 rgba(0,0,0,0.15);margin:16px 0}.chip{display:inline-block;height:32px;font-size:13px;font-weight:500;color:rgba(0,0,0,0.6);line-height:32px;padding:0 12px;border-radius:16px;background-color:#e4e4e4}.chip img{float:left;margin:0 8px 0 -12px;height:32px;width:32px;border-radius:50%}.chip i.material-icons{cursor:pointer;float:right;font-size:16px;line-height:32px;padding-left:8px}.materialboxed{display:block;cursor:-webkit-zoom-in;cursor:zoom-in;position:relative;transition:opacity .4s}.materialboxed:hover{will-change:left, top, width, height}.materialboxed:hover:not(.active){opacity:.8}.materialboxed.active{cursor:-webkit-zoom-out;cursor:zoom-out}#materialbox-overlay{position:fixed;top:0;left:0;right:0;bottom:0;background-color:#292929;z-index:1000;will-change:opacity}.materialbox-caption{position:fixed;display:none;color:#fff;line-height:50px;bottom:0;width:100%;text-align:center;padding:0% 15%;height:50px;z-index:1000;-webkit-font-smoothing:antialiased}select:focus{outline:1px solid #c9f3ef}button:focus{outline:none;background-color:#2ab7a9}label{font-size:0.8rem;color:#9e9e9e}::-webkit-input-placeholder{color:#d1d1d1}:-moz-placeholder{color:#d1d1d1}::-moz-placeholder{color:#d1d1d1}:-ms-input-placeholder{color:#d1d1d1}input:not([type]),input[type=text],input[type=password],input[type=email],input[type=url],input[type=time],input[type=date],input[type=datetime],input[type=datetime-local],input[type=tel],input[type=number],input[type=search],textarea.materialize-textarea{background-color:transparent;border:none;border-bottom:1px solid #9e9e9e;border-radius:0;outline:none;height:3rem;width:100%;font-size:1rem;margin:0 0 15px 0;padding:0;box-shadow:none;box-sizing:content-box;transition:all 0.3s}input:not([type]):disabled,input:not([type])[readonly="readonly"],input[type=text]:disabled,input[type=text][readonly="readonly"],input[type=password]:disabled,input[type=password][readonly="readonly"],input[type=email]:disabled,input[type=email][readonly="readonly"],input[type=url]:disabled,input[type=url][readonly="readonly"],input[type=time]:disabled,input[type=time][readonly="readonly"],input[type=date]:disabled,input[type=date][readonly="readonly"],input[type=datetime]:disabled,input[type=datetime][readonly="readonly"],input[type=datetime-local]:disabled,input[type=datetime-local][readonly="readonly"],input[type=tel]:disabled,input[type=tel][readonly="readonly"],input[type=number]:disabled,input[type=number][readonly="readonly"],input[type=search]:disabled,input[type=search][readonly="readonly"],textarea.materialize-textarea:disabled,textarea.materialize-textarea[readonly="readonly"]{color:rgba(0,0,0,0.26);border-bottom:1px dotted rgba(0,0,0,0.26)}input:not([type]):disabled+label,input:not([type])[readonly="readonly"]+label,input[type=text]:disabled+label,input[type=text][readonly="readonly"]+label,input[type=password]:disabled+label,input[type=password][readonly="readonly"]+label,input[type=email]:disabled+label,input[type=email][readonly="readonly"]+label,input[type=url]:disabled+label,input[type=url][readonly="readonly"]+label,input[type=time]:disabled+label,input[type=time][readonly="readonly"]+label,input[type=date]:disabled+label,input[type=date][readonly="readonly"]+label,input[type=datetime]:disabled+label,input[type=datetime][readonly="readonly"]+label,input[type=datetime-local]:disabled+label,input[type=datetime-local][readonly="readonly"]+label,input[type=tel]:disabled+label,input[type=tel][readonly="readonly"]+label,input[type=number]:disabled+label,input[type=number][readonly="readonly"]+label,input[type=search]:disabled+label,input[type=search][readonly="readonly"]+label,textarea.materialize-textarea:disabled+label,textarea.materialize-textarea[readonly="readonly"]+label{color:rgba(0,0,0,0.26)}input:not([type]):focus:not([readonly]),input[type=text]:focus:not([readonly]),input[type=password]:focus:not([readonly]),input[type=email]:focus:not([readonly]),input[type=url]:focus:not([readonly]),input[type=time]:focus:not([readonly]),input[type=date]:focus:not([readonly]),input[type=datetime]:focus:not([readonly]),input[type=datetime-local]:focus:not([readonly]),input[type=tel]:focus:not([readonly]),input[type=number]:focus:not([readonly]),input[type=search]:focus:not([readonly]),textarea.materialize-textarea:focus:not([readonly]){border-bottom:1px solid #26a69a;box-shadow:0 1px 0 0 #26a69a}input:not([type]):focus:not([readonly])+label,input[type=text]:focus:not([readonly])+label,input[type=password]:focus:not([readonly])+label,input[type=email]:focus:not([readonly])+label,input[type=url]:focus:not([readonly])+label,input[type=time]:focus:not([readonly])+label,input[type=date]:focus:not([readonly])+label,input[type=datetime]:focus:not([readonly])+label,input[type=datetime-local]:focus:not([readonly])+label,input[type=tel]:focus:not([readonly])+label,input[type=number]:focus:not([readonly])+label,input[type=search]:focus:not([readonly])+label,textarea.materialize-textarea:focus:not([readonly])+label{color:#26a69a}input:not([type]).valid,input:not([type]):focus.valid,input[type=text].valid,input[type=text]:focus.valid,input[type=password].valid,input[type=password]:focus.valid,input[type=email].valid,input[type=email]:focus.valid,input[type=url].valid,input[type=url]:focus.valid,input[type=time].valid,input[type=time]:focus.valid,input[type=date].valid,input[type=date]:focus.valid,input[type=datetime].valid,input[type=datetime]:focus.valid,input[type=datetime-local].valid,input[type=datetime-local]:focus.valid,input[type=tel].valid,input[type=tel]:focus.valid,input[type=number].valid,input[type=number]:focus.valid,input[type=search].valid,input[type=search]:focus.valid,textarea.materialize-textarea.valid,textarea.materialize-textarea:focus.valid{border-bottom:1px solid #4CAF50;box-shadow:0 1px 0 0 #4CAF50}input:not([type]).valid+label:after,input:not([type]):focus.valid+label:after,input[type=text].valid+label:after,input[type=text]:focus.valid+label:after,input[type=password].valid+label:after,input[type=password]:focus.valid+label:after,input[type=email].valid+label:after,input[type=email]:focus.valid+label:after,input[type=url].valid+label:after,input[type=url]:focus.valid+label:after,input[type=time].valid+label:after,input[type=time]:focus.valid+label:after,input[type=date].valid+label:after,input[type=date]:focus.valid+label:after,input[type=datetime].valid+label:after,input[type=datetime]:focus.valid+label:after,input[type=datetime-local].valid+label:after,input[type=datetime-local]:focus.valid+label:after,input[type=tel].valid+label:after,input[type=tel]:focus.valid+label:after,input[type=number].valid+label:after,input[type=number]:focus.valid+label:after,input[type=search].valid+label:after,input[type=search]:focus.valid+label:after,textarea.materialize-textarea.valid+label:after,textarea.materialize-textarea:focus.valid+label:after{content:attr(data-success);color:#4CAF50;opacity:1}input:not([type]).invalid,input:not([type]):focus.invalid,input[type=text].invalid,input[type=text]:focus.invalid,input[type=password].invalid,input[type=password]:focus.invalid,input[type=email].invalid,input[type=email]:focus.invalid,input[type=url].invalid,input[type=url]:focus.invalid,input[type=time].invalid,input[type=time]:focus.invalid,input[type=date].invalid,input[type=date]:focus.invalid,input[type=datetime].invalid,input[type=datetime]:focus.invalid,input[type=datetime-local].invalid,input[type=datetime-local]:focus.invalid,input[type=tel].invalid,input[type=tel]:focus.invalid,input[type=number].invalid,input[type=number]:focus.invalid,input[type=search].invalid,input[type=search]:focus.invalid,textarea.materialize-textarea.invalid,textarea.materialize-textarea:focus.invalid{border-bottom:1px solid #F44336;box-shadow:0 1px 0 0 #F44336}input:not([type]).invalid+label:after,input:not([type]):focus.invalid+label:after,input[type=text].invalid+label:after,input[type=text]:focus.invalid+label:after,input[type=password].invalid+label:after,input[type=password]:focus.invalid+label:after,input[type=email].invalid+label:after,input[type=email]:focus.invalid+label:after,input[type=url].invalid+label:after,input[type=url]:focus.invalid+label:after,input[type=time].invalid+label:after,input[type=time]:focus.invalid+label:after,input[type=date].invalid+label:after,input[type=date]:focus.invalid+label:after,input[type=datetime].invalid+label:after,input[type=datetime]:focus.invalid+label:after,input[type=datetime-local].invalid+label:after,input[type=datetime-local]:focus.invalid+label:after,input[type=tel].invalid+label:after,input[type=tel]:focus.invalid+label:after,input[type=number].invalid+label:after,input[type=number]:focus.invalid+label:after,input[type=search].invalid+label:after,input[type=search]:focus.invalid+label:after,textarea.materialize-textarea.invalid+label:after,textarea.materialize-textarea:focus.invalid+label:after{content:attr(data-error);color:#F44336;opacity:1}input:not([type]).validate+label,input[type=text].validate+label,input[type=password].validate+label,input[type=email].validate+label,input[type=url].validate+label,input[type=time].validate+label,input[type=date].validate+label,input[type=datetime].validate+label,input[type=datetime-local].validate+label,input[type=tel].validate+label,input[type=number].validate+label,input[type=search].validate+label,textarea.materialize-textarea.validate+label{width:100%;pointer-events:none}input:not([type])+label:after,input[type=text]+label:after,input[type=password]+label:after,input[type=email]+label:after,input[type=url]+label:after,input[type=time]+label:after,input[type=date]+label:after,input[type=datetime]+label:after,input[type=datetime-local]+label:after,input[type=tel]+label:after,input[type=number]+label:after,input[type=search]+label:after,textarea.materialize-textarea+label:after{display:block;content:"";position:absolute;top:65px;opacity:0;transition:.2s opacity ease-out, .2s color ease-out}.input-field{position:relative;margin-top:1rem}.input-field label{color:#9e9e9e;position:absolute;top:0.8rem;left:0.75rem;font-size:1rem;cursor:text;transition:.2s ease-out}.input-field label.active{font-size:0.8rem;-webkit-transform:translateY(-140%);transform:translateY(-140%)}.input-field .prefix{position:absolute;width:3rem;font-size:2rem;transition:color .2s}.input-field .prefix.active{color:#26a69a}.input-field .prefix ~ input,.input-field .prefix ~ textarea{margin-left:3rem;width:92%;width:calc(100% - 3rem)}.input-field .prefix ~ textarea{padding-top:.8rem}.input-field .prefix ~ label{margin-left:3rem}@media only screen and (max-width: 992px){.input-field .prefix ~ input{width:86%;width:calc(100% - 3rem)}}@media only screen and (max-width: 600px){.input-field .prefix ~ input{width:80%;width:calc(100% - 3rem)}}.input-field input[type=search]{display:block;line-height:inherit;padding-left:4rem;width:calc(100% - 4rem)}.input-field input[type=search]:focus{background-color:#fff;border:0;box-shadow:none;color:#444}.input-field input[type=search]:focus+label i,.input-field input[type=search]:focus ~ .mdi-navigation-close,.input-field input[type=search]:focus ~ .material-icons{color:#444}.input-field input[type=search]+label{left:1rem}.input-field input[type=search] ~ .mdi-navigation-close,.input-field input[type=search] ~ .material-icons{position:absolute;top:0;right:1rem;color:transparent;cursor:pointer;font-size:2rem;transition:.3s color}textarea{width:100%;height:3rem;background-color:transparent}textarea.materialize-textarea{overflow-y:hidden;padding:1.6rem 0;resize:none;min-height:3rem}.hiddendiv{display:none;white-space:pre-wrap;word-wrap:break-word;overflow-wrap:break-word;padding-top:1.2rem}[type="radio"]:not(:checked),[type="radio"]:checked{position:absolute;left:-9999px;opacity:0}[type="radio"]:not(:checked)+label,[type="radio"]:checked+label{position:relative;padding-left:35px;cursor:pointer;display:inline-block;height:25px;line-height:25px;font-size:1rem;transition:.28s ease;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}[type="radio"]+label:before,[type="radio"]+label:after{content:'';position:absolute;left:0;top:0;margin:4px;width:16px;height:16px;z-index:0;transition:.28s ease}[type="radio"]:not(:checked)+label:before,[type="radio"]:not(:checked)+label:after,[type="radio"]:checked+label:before,[type="radio"]:checked+label:after,[type="radio"].with-gap:checked+label:before,[type="radio"].with-gap:checked+label:after{border-radius:50%}[type="radio"]:not(:checked)+label:before,[type="radio"]:not(:checked)+label:after{border:2px solid #5a5a5a}[type="radio"]:not(:checked)+label:after{z-index:-1;-webkit-transform:scale(0);transform:scale(0)}[type="radio"]:checked+label:before{border:2px solid transparent}[type="radio"]:checked+label:after,[type="radio"].with-gap:checked+label:before,[type="radio"].with-gap:checked+label:after{border:2px solid #26a69a}[type="radio"]:checked+label:after,[type="radio"].with-gap:checked+label:after{background-color:#26a69a;z-index:0}[type="radio"]:checked+label:after{-webkit-transform:scale(1.02);transform:scale(1.02)}[type="radio"].with-gap:checked+label:after{-webkit-transform:scale(0.5);transform:scale(0.5)}[type="radio"].tabbed:focus+label:before{box-shadow:0 0 0 10px rgba(0,0,0,0.1)}[type="radio"].with-gap:disabled:checked+label:before{border:2px solid rgba(0,0,0,0.26)}[type="radio"].with-gap:disabled:checked+label:after{border:none;background-color:rgba(0,0,0,0.26)}[type="radio"]:disabled:not(:checked)+label:before,[type="radio"]:disabled:checked+label:before{background-color:transparent;border-color:rgba(0,0,0,0.26)}[type="radio"]:disabled+label{color:rgba(0,0,0,0.26)}[type="radio"]:disabled:not(:checked)+label:before{border-color:rgba(0,0,0,0.26)}[type="radio"]:disabled:checked+label:after{background-color:rgba(0,0,0,0.26);border-color:#BDBDBD}form p{margin-bottom:10px;text-align:left}form p:last-child{margin-bottom:0}[type="checkbox"]:not(:checked),[type="checkbox"]:checked{position:absolute;left:-9999px;opacity:0}[type="checkbox"]+label{position:relative;padding-left:35px;cursor:pointer;display:inline-block;height:25px;line-height:25px;font-size:1rem;-webkit-user-select:none;-moz-user-select:none;-khtml-user-select:none;-ms-user-select:none}[type="checkbox"]+label:before,[type="checkbox"]:not(.filled-in)+label:after{content:'';position:absolute;top:0;left:0;width:18px;height:18px;z-index:0;border:2px solid #5a5a5a;border-radius:1px;margin-top:2px;transition:.2s}[type="checkbox"]:not(.filled-in)+label:after{border:0;-webkit-transform:scale(0);transform:scale(0)}[type="checkbox"]:not(:checked):disabled+label:before{border:none;background-color:rgba(0,0,0,0.26)}[type="checkbox"].tabbed:focus+label:after{-webkit-transform:scale(1);transform:scale(1);border:0;border-radius:50%;box-shadow:0 0 0 10px rgba(0,0,0,0.1);background-color:rgba(0,0,0,0.1)}[type="checkbox"]:checked+label:before{top:-4px;left:-5px;width:12px;height:22px;border-top:2px solid transparent;border-left:2px solid transparent;border-right:2px solid #26a69a;border-bottom:2px solid #26a69a;-webkit-transform:rotate(40deg);transform:rotate(40deg);-webkit-backface-visibility:hidden;backface-visibility:hidden;-webkit-transform-origin:100% 100%;transform-origin:100% 100%}[type="checkbox"]:checked:disabled+label:before{border-right:2px solid rgba(0,0,0,0.26);border-bottom:2px solid rgba(0,0,0,0.26)}[type="checkbox"]:indeterminate+label:before{top:-11px;left:-12px;width:10px;height:22px;border-top:none;border-left:none;border-right:2px solid #26a69a;border-bottom:none;-webkit-transform:rotate(90deg);transform:rotate(90deg);-webkit-backface-visibility:hidden;backface-visibility:hidden;-webkit-transform-origin:100% 100%;transform-origin:100% 100%}[type="checkbox"]:indeterminate:disabled+label:before{border-right:2px solid rgba(0,0,0,0.26);background-color:transparent}[type="checkbox"].filled-in+label:after{border-radius:2px}[type="checkbox"].filled-in+label:before,[type="checkbox"].filled-in+label:after{content:'';left:0;position:absolute;transition:border .25s, background-color .25s, width .20s .1s, height .20s .1s, top .20s .1s, left .20s .1s;z-index:1}[type="checkbox"].filled-in:not(:checked)+label:before{width:0;height:0;border:3px solid transparent;left:6px;top:10px;-webkit-transform:rotateZ(37deg);transform:rotateZ(37deg);-webkit-transform-origin:20% 40%;transform-origin:100% 100%}[type="checkbox"].filled-in:not(:checked)+label:after{height:20px;width:20px;background-color:transparent;border:2px solid #5a5a5a;top:0px;z-index:0}[type="checkbox"].filled-in:checked+label:before{top:0;left:1px;width:8px;height:13px;border-top:2px solid transparent;border-left:2px solid transparent;border-right:2px solid #fff;border-bottom:2px solid #fff;-webkit-transform:rotateZ(37deg);transform:rotateZ(37deg);-webkit-transform-origin:100% 100%;transform-origin:100% 100%}[type="checkbox"].filled-in:checked+label:after{top:0;width:20px;height:20px;border:2px solid #26a69a;background-color:#26a69a;z-index:0}[type="checkbox"].filled-in.tabbed:focus+label:after{border-radius:2px;border-color:#5a5a5a;background-color:rgba(0,0,0,0.1)}[type="checkbox"].filled-in.tabbed:checked:focus+label:after{border-radius:2px;background-color:#26a69a;border-color:#26a69a}[type="checkbox"].filled-in:disabled:not(:checked)+label:before{background-color:transparent;border:2px solid transparent}[type="checkbox"].filled-in:disabled:not(:checked)+label:after{border-color:transparent;background-color:#BDBDBD}[type="checkbox"].filled-in:disabled:checked+label:before{background-color:transparent}[type="checkbox"].filled-in:disabled:checked+label:after{background-color:#BDBDBD;border-color:#BDBDBD}.switch,.switch *{-webkit-user-select:none;-moz-user-select:none;-khtml-user-select:none;-ms-user-select:none}.switch label{cursor:pointer}.switch label input[type=checkbox]{opacity:0;width:0;height:0}.switch label input[type=checkbox]:checked+.lever{background-color:#84c7c1}.switch label input[type=checkbox]:checked+.lever:after{background-color:#26a69a;left:24px}.switch label .lever{content:"";display:inline-block;position:relative;width:40px;height:15px;background-color:#818181;border-radius:15px;margin-right:10px;transition:background 0.3s ease;vertical-align:middle;margin:0 16px}.switch label .lever:after{content:"";position:absolute;display:inline-block;width:21px;height:21px;background-color:#F1F1F1;border-radius:21px;box-shadow:0 1px 3px 1px rgba(0,0,0,0.4);left:-5px;top:-3px;transition:left 0.3s ease, background .3s ease, box-shadow 0.1s ease}input[type=checkbox]:checked:not(:disabled) ~ .lever:active::after,input[type=checkbox]:checked:not(:disabled).tabbed:focus ~ .lever::after{box-shadow:0 1px 3px 1px rgba(0,0,0,0.4),0 0 0 15px rgba(38,166,154,0.1)}input[type=checkbox]:not(:disabled) ~ .lever:active:after,input[type=checkbox]:not(:disabled).tabbed:focus ~ .lever::after{box-shadow:0 1px 3px 1px rgba(0,0,0,0.4),0 0 0 15px rgba(0,0,0,0.08)}.switch input[type=checkbox][disabled]+.lever{cursor:default}.switch label input[type=checkbox][disabled]+.lever:after,.switch label input[type=checkbox][disabled]:checked+.lever:after{background-color:#BDBDBD}select{display:none}select.browser-default{display:block}select{background-color:rgba(255,255,255,0.9);width:100%;padding:5px;border:1px solid #f2f2f2;border-radius:2px;height:3rem}.select-label{position:absolute}.select-wrapper{position:relative}.select-wrapper input.select-dropdown{position:relative;cursor:pointer;background-color:transparent;border:none;border-bottom:1px solid #9e9e9e;outline:none;height:3rem;line-height:3rem;width:100%;font-size:1rem;margin:0 0 15px 0;padding:0;display:block}.select-wrapper span.caret{color:initial;position:absolute;right:0;top:16px;font-size:10px}.select-wrapper span.caret.disabled{color:rgba(0,0,0,0.26)}.select-wrapper+label{position:absolute;top:-14px;font-size:0.8rem}select:disabled{color:rgba(0,0,0,0.3)}.select-wrapper input.select-dropdown:disabled{color:rgba(0,0,0,0.3);cursor:default;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;border-bottom:1px solid rgba(0,0,0,0.3)}.select-wrapper i{color:rgba(0,0,0,0.3)}.select-dropdown li.disabled,.select-dropdown li.disabled>span,.select-dropdown li.optgroup{color:rgba(0,0,0,0.3);background-color:transparent}.prefix ~ .select-wrapper{margin-left:3rem;width:92%;width:calc(100% - 3rem)}.prefix ~ label{margin-left:3rem}.select-dropdown li img{height:40px;width:40px;margin:5px 15px;float:right}.select-dropdown li.optgroup{border-top:1px solid #eee}.select-dropdown li.optgroup.selected>span{color:rgba(0,0,0,0.7)}.select-dropdown li.optgroup>span{color:rgba(0,0,0,0.4)}.select-dropdown li.optgroup ~ li.optgroup-option{padding-left:1rem}.file-field{position:relative}.file-field .file-path-wrapper{overflow:hidden;padding-left:10px}.file-field input.file-path{width:100%}.file-field .btn,.file-field .btn-large{float:left;height:3rem;line-height:3rem}.file-field span{cursor:pointer}.file-field input[type=file]{position:absolute;top:0;right:0;left:0;bottom:0;width:100%;margin:0;padding:0;font-size:20px;cursor:pointer;opacity:0;filter:alpha(opacity=0)}.range-field{position:relative}input[type=range],input[type=range]+.thumb{cursor:pointer}input[type=range]{position:relative;background-color:transparent;border:none;outline:none;width:100%;margin:15px 0;padding:0}input[type=range]:focus{outline:none}input[type=range]+.thumb{position:absolute;border:none;height:0;width:0;border-radius:50%;background-color:#26a69a;top:10px;margin-left:-6px;-webkit-transform-origin:50% 50%;transform-origin:50% 50%;-webkit-transform:rotate(-45deg);transform:rotate(-45deg)}input[type=range]+.thumb .value{display:block;width:30px;text-align:center;color:#26a69a;font-size:0;-webkit-transform:rotate(45deg);transform:rotate(45deg)}input[type=range]+.thumb.active{border-radius:50% 50% 50% 0}input[type=range]+.thumb.active .value{color:#fff;margin-left:-1px;margin-top:8px;font-size:10px}input[type=range]{-webkit-appearance:none}input[type=range]::-webkit-slider-runnable-track{height:3px;background:#c2c0c2;border:none}input[type=range]::-webkit-slider-thumb{-webkit-appearance:none;border:none;height:14px;width:14px;border-radius:50%;background-color:#26a69a;-webkit-transform-origin:50% 50%;transform-origin:50% 50%;margin:-5px 0 0 0;transition:.3s}input[type=range]:focus::-webkit-slider-runnable-track{background:#ccc}input[type=range]{border:1px solid white}input[type=range]::-moz-range-track{height:3px;background:#ddd;border:none}input[type=range]::-moz-range-thumb{border:none;height:14px;width:14px;border-radius:50%;background:#26a69a;margin-top:-5px}input[type=range]:-moz-focusring{outline:1px solid #fff;outline-offset:-1px}input[type=range]:focus::-moz-range-track{background:#ccc}input[type=range]::-ms-track{height:3px;background:transparent;border-color:transparent;border-width:6px 0;color:transparent}input[type=range]::-ms-fill-lower{background:#777}input[type=range]::-ms-fill-upper{background:#ddd}input[type=range]::-ms-thumb{border:none;height:14px;width:14px;border-radius:50%;background:#26a69a}input[type=range]:focus::-ms-fill-lower{background:#888}input[type=range]:focus::-ms-fill-upper{background:#ccc}.table-of-contents.fixed{position:fixed}.table-of-contents li{padding:2px 0}.table-of-contents a{display:inline-block;font-weight:300;color:#757575;padding-left:20px;height:1.5rem;line-height:1.5rem;letter-spacing:.4;display:inline-block}.table-of-contents a:hover{color:#a8a8a8;padding-left:19px;border-left:1px solid #ea4a4f}.table-of-contents a.active{font-weight:500;padding-left:18px;border-left:2px solid #ea4a4f}.side-nav{position:fixed;width:240px;left:0;top:0;margin:0;-webkit-transform:translateX(-100%);transform:translateX(-100%);height:100%;height:calc(100% + 60px);height:-moz-calc(100%);padding-bottom:60px;background-color:#fff;z-index:999;-webkit-backface-visibility:hidden;backface-visibility:hidden;overflow-y:auto;will-change:transform;-webkit-backface-visibility:hidden;backface-visibility:hidden;-webkit-transform:translateX(-105%);transform:translateX(-105%)}.side-nav.right-aligned{right:0;-webkit-transform:translateX(105%);transform:translateX(105%);left:auto;-webkit-transform:translateX(100%);transform:translateX(100%)}.side-nav .collapsible{margin:0}.side-nav li{float:none;line-height:64px}.side-nav li.active{background-color:rgba(0,0,0,0.05)}.side-nav a{color:#444;display:block;font-size:1rem;height:64px;line-height:64px;padding:0 30px}.side-nav a:hover{background-color:rgba(0,0,0,0.05)}.side-nav a.btn,.side-nav a.btn-large,.side-nav a.btn-large,.side-nav a.btn-flat,.side-nav a.btn-floating{margin:10px 15px}.side-nav a.btn,.side-nav a.btn-large,.side-nav a.btn-large,.side-nav a.btn-floating{color:#fff}.side-nav a.btn-flat{color:#343434}.side-nav a.btn:hover,.side-nav a.btn-large:hover,.side-nav a.btn-large:hover{background-color:#2bbbad}.side-nav a.btn-floating:hover{background-color:#26a69a}.drag-target{height:100%;width:10px;position:fixed;top:0;z-index:998}.side-nav.fixed a{display:block;padding:0 30px;color:#444}.side-nav.fixed{left:0;-webkit-transform:translateX(0);transform:translateX(0);position:fixed}.side-nav.fixed.right-aligned{right:0;left:auto}@media only screen and (max-width: 992px){.side-nav.fixed{-webkit-transform:translateX(-105%);transform:translateX(-105%)}.side-nav.fixed.right-aligned{-webkit-transform:translateX(105%);transform:translateX(105%)}}.side-nav .collapsible-body li.active,.side-nav.fixed .collapsible-body li.active{background-color:#ee6e73}.side-nav .collapsible-body li.active a,.side-nav.fixed .collapsible-body li.active a{color:#fff}#sidenav-overlay{position:fixed;top:0;left:0;right:0;height:120vh;background-color:rgba(0,0,0,0.5);z-index:997;will-change:opacity}.preloader-wrapper{display:inline-block;position:relative;width:48px;height:48px}.preloader-wrapper.small{width:36px;height:36px}.preloader-wrapper.big{width:64px;height:64px}.preloader-wrapper.active{-webkit-animation:container-rotate 1568ms linear infinite;animation:container-rotate 1568ms linear infinite}@-webkit-keyframes container-rotate{to{-webkit-transform:rotate(360deg)}}@keyframes container-rotate{to{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}.spinner-layer{position:absolute;width:100%;height:100%;opacity:0;border-color:#26a69a}.spinner-blue,.spinner-blue-only{border-color:#4285f4}.spinner-red,.spinner-red-only{border-color:#db4437}.spinner-yellow,.spinner-yellow-only{border-color:#f4b400}.spinner-green,.spinner-green-only{border-color:#0f9d58}.active .spinner-layer.spinner-blue{-webkit-animation:fill-unfill-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both,blue-fade-in-out 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both;animation:fill-unfill-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both,blue-fade-in-out 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both}.active .spinner-layer.spinner-red{-webkit-animation:fill-unfill-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both,red-fade-in-out 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both;animation:fill-unfill-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both,red-fade-in-out 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both}.active .spinner-layer.spinner-yellow{-webkit-animation:fill-unfill-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both,yellow-fade-in-out 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both;animation:fill-unfill-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both,yellow-fade-in-out 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both}.active .spinner-layer.spinner-green{-webkit-animation:fill-unfill-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both,green-fade-in-out 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both;animation:fill-unfill-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both,green-fade-in-out 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both}.active .spinner-layer,.active .spinner-layer.spinner-blue-only,.active .spinner-layer.spinner-red-only,.active .spinner-layer.spinner-yellow-only,.active .spinner-layer.spinner-green-only{opacity:1;-webkit-animation:fill-unfill-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both;animation:fill-unfill-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both}@-webkit-keyframes fill-unfill-rotate{12.5%{-webkit-transform:rotate(135deg)}25%{-webkit-transform:rotate(270deg)}37.5%{-webkit-transform:rotate(405deg)}50%{-webkit-transform:rotate(540deg)}62.5%{-webkit-transform:rotate(675deg)}75%{-webkit-transform:rotate(810deg)}87.5%{-webkit-transform:rotate(945deg)}to{-webkit-transform:rotate(1080deg)}}@keyframes fill-unfill-rotate{12.5%{-webkit-transform:rotate(135deg);transform:rotate(135deg)}25%{-webkit-transform:rotate(270deg);transform:rotate(270deg)}37.5%{-webkit-transform:rotate(405deg);transform:rotate(405deg)}50%{-webkit-transform:rotate(540deg);transform:rotate(540deg)}62.5%{-webkit-transform:rotate(675deg);transform:rotate(675deg)}75%{-webkit-transform:rotate(810deg);transform:rotate(810deg)}87.5%{-webkit-transform:rotate(945deg);transform:rotate(945deg)}to{-webkit-transform:rotate(1080deg);transform:rotate(1080deg)}}@-webkit-keyframes blue-fade-in-out{from{opacity:1}25%{opacity:1}26%{opacity:0}89%{opacity:0}90%{opacity:1}100%{opacity:1}}@keyframes blue-fade-in-out{from{opacity:1}25%{opacity:1}26%{opacity:0}89%{opacity:0}90%{opacity:1}100%{opacity:1}}@-webkit-keyframes red-fade-in-out{from{opacity:0}15%{opacity:0}25%{opacity:1}50%{opacity:1}51%{opacity:0}}@keyframes red-fade-in-out{from{opacity:0}15%{opacity:0}25%{opacity:1}50%{opacity:1}51%{opacity:0}}@-webkit-keyframes yellow-fade-in-out{from{opacity:0}40%{opacity:0}50%{opacity:1}75%{opacity:1}76%{opacity:0}}@keyframes yellow-fade-in-out{from{opacity:0}40%{opacity:0}50%{opacity:1}75%{opacity:1}76%{opacity:0}}@-webkit-keyframes green-fade-in-out{from{opacity:0}65%{opacity:0}75%{opacity:1}90%{opacity:1}100%{opacity:0}}@keyframes green-fade-in-out{from{opacity:0}65%{opacity:0}75%{opacity:1}90%{opacity:1}100%{opacity:0}}.gap-patch{position:absolute;top:0;left:45%;width:10%;height:100%;overflow:hidden;border-color:inherit}.gap-patch .circle{width:1000%;left:-450%}.circle-clipper{display:inline-block;position:relative;width:50%;height:100%;overflow:hidden;border-color:inherit}.circle-clipper .circle{width:200%;height:100%;border-width:3px;border-style:solid;border-color:inherit;border-bottom-color:transparent !important;border-radius:50%;-webkit-animation:none;animation:none;position:absolute;top:0;right:0;bottom:0}.circle-clipper.left .circle{left:0;border-right-color:transparent !important;-webkit-transform:rotate(129deg);transform:rotate(129deg)}.circle-clipper.right .circle{left:-100%;border-left-color:transparent !important;-webkit-transform:rotate(-129deg);transform:rotate(-129deg)}.active .circle-clipper.left .circle{-webkit-animation:left-spin 1333ms cubic-bezier(0.4, 0, 0.2, 1) infinite both;animation:left-spin 1333ms cubic-bezier(0.4, 0, 0.2, 1) infinite both}.active .circle-clipper.right .circle{-webkit-animation:right-spin 1333ms cubic-bezier(0.4, 0, 0.2, 1) infinite both;animation:right-spin 1333ms cubic-bezier(0.4, 0, 0.2, 1) infinite both}@-webkit-keyframes left-spin{from{-webkit-transform:rotate(130deg)}50%{-webkit-transform:rotate(-5deg)}to{-webkit-transform:rotate(130deg)}}@keyframes left-spin{from{-webkit-transform:rotate(130deg);transform:rotate(130deg)}50%{-webkit-transform:rotate(-5deg);transform:rotate(-5deg)}to{-webkit-transform:rotate(130deg);transform:rotate(130deg)}}@-webkit-keyframes right-spin{from{-webkit-transform:rotate(-130deg)}50%{-webkit-transform:rotate(5deg)}to{-webkit-transform:rotate(-130deg)}}@keyframes right-spin{from{-webkit-transform:rotate(-130deg);transform:rotate(-130deg)}50%{-webkit-transform:rotate(5deg);transform:rotate(5deg)}to{-webkit-transform:rotate(-130deg);transform:rotate(-130deg)}}#spinnerContainer.cooldown{-webkit-animation:container-rotate 1568ms linear infinite,fade-out 400ms cubic-bezier(0.4, 0, 0.2, 1);animation:container-rotate 1568ms linear infinite,fade-out 400ms cubic-bezier(0.4, 0, 0.2, 1)}@-webkit-keyframes fade-out{from{opacity:1}to{opacity:0}}@keyframes fade-out{from{opacity:1}to{opacity:0}}.slider{position:relative;height:400px;width:100%}.slider.fullscreen{height:100%;width:100%;position:absolute;top:0;left:0;right:0;bottom:0}.slider.fullscreen ul.slides{height:100%}.slider.fullscreen ul.indicators{z-index:2;bottom:30px}.slider .slides{background-color:#9e9e9e;margin:0;height:400px}.slider .slides li{opacity:0;position:absolute;top:0;left:0;z-index:1;width:100%;height:inherit;overflow:hidden}.slider .slides li img{height:100%;width:100%;background-size:cover;background-position:center}.slider .slides li .caption{color:#fff;position:absolute;top:15%;left:15%;width:70%;opacity:0}.slider .slides li .caption p{color:#e0e0e0}.slider .slides li.active{z-index:2}.slider .indicators{position:absolute;text-align:center;left:0;right:0;bottom:0;margin:0}.slider .indicators .indicator-item{display:inline-block;position:relative;cursor:pointer;height:16px;width:16px;margin:0 12px;background-color:#e0e0e0;transition:background-color .3s;border-radius:50%}.slider .indicators .indicator-item.active{background-color:#4CAF50}.carousel{overflow:hidden;position:relative;width:100%;height:400px;-webkit-perspective:500px;perspective:500px;-webkit-transform-style:preserve-3d;transform-style:preserve-3d;-webkit-transform-origin:0% 50%;transform-origin:0% 50%}.carousel .carousel-item{width:200px;position:absolute;top:0;left:0}.carousel .carousel-item img{width:100%}.carousel.carousel-slider{top:0;left:0;height:0}.carousel.carousel-slider .carousel-item{width:100%;height:100%;position:absolute;top:0;left:0}.picker{font-size:16px;text-align:left;line-height:1.2;color:#000000;position:absolute;z-index:10000;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.picker__input{cursor:default}.picker__input.picker__input--active{border-color:#0089ec}.picker__holder{width:100%;overflow-y:auto;-webkit-overflow-scrolling:touch}/*! + */.waves-effect{position:relative;cursor:pointer;display:inline-block;overflow:hidden;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;-webkit-tap-highlight-color:transparent;vertical-align:middle;z-index:1;will-change:opacity, transform;transition:all .3s ease-out}.waves-effect .waves-ripple{position:absolute;border-radius:50%;width:20px;height:20px;margin-top:-10px;margin-left:-10px;opacity:0;background:rgba(0,0,0,0.2);transition:all 0.7s ease-out;transition-property:opacity, -webkit-transform;transition-property:transform, opacity;transition-property:transform, opacity, -webkit-transform;-webkit-transform:scale(0);transform:scale(0);pointer-events:none}.waves-effect.waves-light .waves-ripple{background-color:rgba(255,255,255,0.45)}.waves-effect.waves-red .waves-ripple{background-color:rgba(244,67,54,0.7)}.waves-effect.waves-yellow .waves-ripple{background-color:rgba(255,235,59,0.7)}.waves-effect.waves-orange .waves-ripple{background-color:rgba(255,152,0,0.7)}.waves-effect.waves-purple .waves-ripple{background-color:rgba(156,39,176,0.7)}.waves-effect.waves-green .waves-ripple{background-color:rgba(76,175,80,0.7)}.waves-effect.waves-teal .waves-ripple{background-color:rgba(0,150,136,0.7)}.waves-effect input[type="button"],.waves-effect input[type="reset"],.waves-effect input[type="submit"]{border:0;font-style:normal;font-size:inherit;text-transform:inherit;background:none}.waves-effect img{position:relative;z-index:-1}.waves-notransition{transition:none !important}.waves-circle{-webkit-transform:translateZ(0);transform:translateZ(0);-webkit-mask-image:-webkit-radial-gradient(circle, #fff 100%, #000 100%)}.waves-input-wrapper{border-radius:0.2em;vertical-align:bottom}.waves-input-wrapper .waves-button-input{position:relative;top:0;left:0;z-index:1}.waves-circle{text-align:center;width:2.5em;height:2.5em;line-height:2.5em;border-radius:50%;-webkit-mask-image:none}.waves-block{display:block}.waves-effect .waves-ripple{z-index:-1}.modal{display:none;position:fixed;left:0;right:0;background-color:#fafafa;padding:0;max-height:70%;width:55%;margin:auto;overflow-y:auto;border-radius:2px;will-change:top, opacity}@media only screen and (max-width: 992px){.modal{width:80%}}.modal h1,.modal h2,.modal h3,.modal h4{margin-top:0}.modal .modal-content{padding:24px}.modal .modal-close{cursor:pointer}.modal .modal-footer{border-radius:0 0 2px 2px;background-color:#fafafa;padding:4px 6px;height:56px;width:100%}.modal .modal-footer .btn,.modal .modal-footer .btn-large,.modal .modal-footer .btn-flat{float:right;margin:6px 0}.lean-overlay{position:fixed;z-index:999;top:-100px;left:0;bottom:0;right:0;height:125%;width:100%;background:#000;display:none;will-change:opacity}.modal.modal-fixed-footer{padding:0;height:70%}.modal.modal-fixed-footer .modal-content{position:absolute;height:calc(100% - 56px);max-height:100%;width:100%;overflow-y:auto}.modal.modal-fixed-footer .modal-footer{border-top:1px solid rgba(0,0,0,0.1);position:absolute;bottom:0}.modal.bottom-sheet{top:auto;bottom:-100%;margin:0;width:100%;max-height:45%;border-radius:0;will-change:bottom, opacity}.collapsible{border-top:1px solid #ddd;border-right:1px solid #ddd;border-left:1px solid #ddd;margin:0.5rem 0 1rem 0}.collapsible-header{display:block;cursor:pointer;min-height:3rem;line-height:3rem;padding:0 1rem;background-color:#fff;border-bottom:1px solid #ddd}.collapsible-header i{width:2rem;font-size:1.6rem;line-height:3rem;display:block;float:left;text-align:center;margin-right:1rem}.collapsible-body{display:none;border-bottom:1px solid #ddd;box-sizing:border-box}.collapsible-body p{margin:0;padding:2rem}.side-nav .collapsible,.side-nav.fixed .collapsible{border:none;box-shadow:none}.side-nav .collapsible li,.side-nav.fixed .collapsible li{padding:0}.side-nav .collapsible-header,.side-nav.fixed .collapsible-header{background-color:transparent;border:none;line-height:inherit;height:inherit;padding:0 16px}.side-nav .collapsible-header:hover,.side-nav.fixed .collapsible-header:hover{background-color:rgba(0,0,0,0.05)}.side-nav .collapsible-header i,.side-nav.fixed .collapsible-header i{line-height:inherit}.side-nav .collapsible-body,.side-nav.fixed .collapsible-body{border:0;background-color:#fff}.side-nav .collapsible-body li a,.side-nav.fixed .collapsible-body li a{padding:0 23.5px 0 31px}.collapsible.popout{border:none;box-shadow:none}.collapsible.popout>li{box-shadow:0 2px 5px 0 rgba(0,0,0,0.16),0 2px 10px 0 rgba(0,0,0,0.12);margin:0 24px;transition:margin 0.35s cubic-bezier(0.25, 0.46, 0.45, 0.94)}.collapsible.popout>li.active{box-shadow:0 5px 11px 0 rgba(0,0,0,0.18),0 4px 15px 0 rgba(0,0,0,0.15);margin:16px 0}.chip{display:inline-block;height:32px;font-size:13px;font-weight:500;color:rgba(0,0,0,0.6);line-height:32px;padding:0 12px;border-radius:16px;background-color:#e4e4e4;margin-bottom:5px;margin-right:5px}.chip img{float:left;margin:0 8px 0 -12px;height:32px;width:32px;border-radius:50%}.chip .close{cursor:pointer;float:right;font-size:16px;line-height:32px;padding-left:8px}.chips{border:none;border-bottom:1px solid #9e9e9e;box-shadow:none;margin-bottom:30px;min-height:45px;outline:none;padding-bottom:5px;transition:all .3s}.chips.focus{border-bottom:1px solid #26a69a;box-shadow:0 1px 0 0 #26a69a}.chips:hover{cursor:text}.chips .chip.selected{background-color:#26a69a;color:#fff}.chips .input{background:none;border:0;color:rgba(0,0,0,0.6);display:inline-block;font-size:13px;font-weight:500;height:32px;margin-right:20px;line-height:32px;outline:0;padding:0 !important;width:120px !important}.chips .input:focus{border:0 !important;box-shadow:none !important}.materialboxed{display:block;cursor:-webkit-zoom-in;cursor:zoom-in;position:relative;transition:opacity .4s}.materialboxed:hover{will-change:left, top, width, height}.materialboxed:hover:not(.active){opacity:.8}.materialboxed.active{cursor:-webkit-zoom-out;cursor:zoom-out}#materialbox-overlay{position:fixed;top:0;left:0;right:0;bottom:0;background-color:#292929;z-index:1000;will-change:opacity}.materialbox-caption{position:fixed;display:none;color:#fff;line-height:50px;bottom:0;width:100%;text-align:center;padding:0% 15%;height:50px;z-index:1000;-webkit-font-smoothing:antialiased}select:focus{outline:1px solid #c9f3ef}button:focus{outline:none;background-color:#2ab7a9}label{font-size:0.8rem;color:#9e9e9e}::-webkit-input-placeholder{color:#d1d1d1}:-moz-placeholder{color:#d1d1d1}::-moz-placeholder{color:#d1d1d1}:-ms-input-placeholder{color:#d1d1d1}input:not([type]),input[type=text],input[type=password],input[type=email],input[type=url],input[type=time],input[type=date],input[type=datetime],input[type=datetime-local],input[type=tel],input[type=number],input[type=search],textarea.materialize-textarea{background-color:transparent;border:none;border-bottom:1px solid #9e9e9e;border-radius:0;outline:none;height:3rem;width:100%;font-size:1rem;margin:0 0 20px 0;padding:0;box-shadow:none;box-sizing:content-box;transition:all 0.3s}input:not([type]):disabled,input:not([type])[readonly="readonly"],input[type=text]:disabled,input[type=text][readonly="readonly"],input[type=password]:disabled,input[type=password][readonly="readonly"],input[type=email]:disabled,input[type=email][readonly="readonly"],input[type=url]:disabled,input[type=url][readonly="readonly"],input[type=time]:disabled,input[type=time][readonly="readonly"],input[type=date]:disabled,input[type=date][readonly="readonly"],input[type=datetime]:disabled,input[type=datetime][readonly="readonly"],input[type=datetime-local]:disabled,input[type=datetime-local][readonly="readonly"],input[type=tel]:disabled,input[type=tel][readonly="readonly"],input[type=number]:disabled,input[type=number][readonly="readonly"],input[type=search]:disabled,input[type=search][readonly="readonly"],textarea.materialize-textarea:disabled,textarea.materialize-textarea[readonly="readonly"]{color:rgba(0,0,0,0.26);border-bottom:1px dotted rgba(0,0,0,0.26)}input:not([type]):disabled+label,input:not([type])[readonly="readonly"]+label,input[type=text]:disabled+label,input[type=text][readonly="readonly"]+label,input[type=password]:disabled+label,input[type=password][readonly="readonly"]+label,input[type=email]:disabled+label,input[type=email][readonly="readonly"]+label,input[type=url]:disabled+label,input[type=url][readonly="readonly"]+label,input[type=time]:disabled+label,input[type=time][readonly="readonly"]+label,input[type=date]:disabled+label,input[type=date][readonly="readonly"]+label,input[type=datetime]:disabled+label,input[type=datetime][readonly="readonly"]+label,input[type=datetime-local]:disabled+label,input[type=datetime-local][readonly="readonly"]+label,input[type=tel]:disabled+label,input[type=tel][readonly="readonly"]+label,input[type=number]:disabled+label,input[type=number][readonly="readonly"]+label,input[type=search]:disabled+label,input[type=search][readonly="readonly"]+label,textarea.materialize-textarea:disabled+label,textarea.materialize-textarea[readonly="readonly"]+label{color:rgba(0,0,0,0.26)}input:not([type]):focus:not([readonly]),input[type=text]:focus:not([readonly]),input[type=password]:focus:not([readonly]),input[type=email]:focus:not([readonly]),input[type=url]:focus:not([readonly]),input[type=time]:focus:not([readonly]),input[type=date]:focus:not([readonly]),input[type=datetime]:focus:not([readonly]),input[type=datetime-local]:focus:not([readonly]),input[type=tel]:focus:not([readonly]),input[type=number]:focus:not([readonly]),input[type=search]:focus:not([readonly]),textarea.materialize-textarea:focus:not([readonly]){border-bottom:1px solid #26a69a;box-shadow:0 1px 0 0 #26a69a}input:not([type]):focus:not([readonly])+label,input[type=text]:focus:not([readonly])+label,input[type=password]:focus:not([readonly])+label,input[type=email]:focus:not([readonly])+label,input[type=url]:focus:not([readonly])+label,input[type=time]:focus:not([readonly])+label,input[type=date]:focus:not([readonly])+label,input[type=datetime]:focus:not([readonly])+label,input[type=datetime-local]:focus:not([readonly])+label,input[type=tel]:focus:not([readonly])+label,input[type=number]:focus:not([readonly])+label,input[type=search]:focus:not([readonly])+label,textarea.materialize-textarea:focus:not([readonly])+label{color:#26a69a}input:not([type]).valid,input:not([type]):focus.valid,input[type=text].valid,input[type=text]:focus.valid,input[type=password].valid,input[type=password]:focus.valid,input[type=email].valid,input[type=email]:focus.valid,input[type=url].valid,input[type=url]:focus.valid,input[type=time].valid,input[type=time]:focus.valid,input[type=date].valid,input[type=date]:focus.valid,input[type=datetime].valid,input[type=datetime]:focus.valid,input[type=datetime-local].valid,input[type=datetime-local]:focus.valid,input[type=tel].valid,input[type=tel]:focus.valid,input[type=number].valid,input[type=number]:focus.valid,input[type=search].valid,input[type=search]:focus.valid,textarea.materialize-textarea.valid,textarea.materialize-textarea:focus.valid{border-bottom:1px solid #4CAF50;box-shadow:0 1px 0 0 #4CAF50}input:not([type]).valid+label:after,input:not([type]):focus.valid+label:after,input[type=text].valid+label:after,input[type=text]:focus.valid+label:after,input[type=password].valid+label:after,input[type=password]:focus.valid+label:after,input[type=email].valid+label:after,input[type=email]:focus.valid+label:after,input[type=url].valid+label:after,input[type=url]:focus.valid+label:after,input[type=time].valid+label:after,input[type=time]:focus.valid+label:after,input[type=date].valid+label:after,input[type=date]:focus.valid+label:after,input[type=datetime].valid+label:after,input[type=datetime]:focus.valid+label:after,input[type=datetime-local].valid+label:after,input[type=datetime-local]:focus.valid+label:after,input[type=tel].valid+label:after,input[type=tel]:focus.valid+label:after,input[type=number].valid+label:after,input[type=number]:focus.valid+label:after,input[type=search].valid+label:after,input[type=search]:focus.valid+label:after,textarea.materialize-textarea.valid+label:after,textarea.materialize-textarea:focus.valid+label:after{content:attr(data-success);color:#4CAF50;opacity:1}input:not([type]).invalid,input:not([type]):focus.invalid,input[type=text].invalid,input[type=text]:focus.invalid,input[type=password].invalid,input[type=password]:focus.invalid,input[type=email].invalid,input[type=email]:focus.invalid,input[type=url].invalid,input[type=url]:focus.invalid,input[type=time].invalid,input[type=time]:focus.invalid,input[type=date].invalid,input[type=date]:focus.invalid,input[type=datetime].invalid,input[type=datetime]:focus.invalid,input[type=datetime-local].invalid,input[type=datetime-local]:focus.invalid,input[type=tel].invalid,input[type=tel]:focus.invalid,input[type=number].invalid,input[type=number]:focus.invalid,input[type=search].invalid,input[type=search]:focus.invalid,textarea.materialize-textarea.invalid,textarea.materialize-textarea:focus.invalid{border-bottom:1px solid #F44336;box-shadow:0 1px 0 0 #F44336}input:not([type]).invalid+label:after,input:not([type]):focus.invalid+label:after,input[type=text].invalid+label:after,input[type=text]:focus.invalid+label:after,input[type=password].invalid+label:after,input[type=password]:focus.invalid+label:after,input[type=email].invalid+label:after,input[type=email]:focus.invalid+label:after,input[type=url].invalid+label:after,input[type=url]:focus.invalid+label:after,input[type=time].invalid+label:after,input[type=time]:focus.invalid+label:after,input[type=date].invalid+label:after,input[type=date]:focus.invalid+label:after,input[type=datetime].invalid+label:after,input[type=datetime]:focus.invalid+label:after,input[type=datetime-local].invalid+label:after,input[type=datetime-local]:focus.invalid+label:after,input[type=tel].invalid+label:after,input[type=tel]:focus.invalid+label:after,input[type=number].invalid+label:after,input[type=number]:focus.invalid+label:after,input[type=search].invalid+label:after,input[type=search]:focus.invalid+label:after,textarea.materialize-textarea.invalid+label:after,textarea.materialize-textarea:focus.invalid+label:after{content:attr(data-error);color:#F44336;opacity:1}input:not([type]).validate+label,input[type=text].validate+label,input[type=password].validate+label,input[type=email].validate+label,input[type=url].validate+label,input[type=time].validate+label,input[type=date].validate+label,input[type=datetime].validate+label,input[type=datetime-local].validate+label,input[type=tel].validate+label,input[type=number].validate+label,input[type=search].validate+label,textarea.materialize-textarea.validate+label{width:100%;pointer-events:none}input:not([type])+label:after,input[type=text]+label:after,input[type=password]+label:after,input[type=email]+label:after,input[type=url]+label:after,input[type=time]+label:after,input[type=date]+label:after,input[type=datetime]+label:after,input[type=datetime-local]+label:after,input[type=tel]+label:after,input[type=number]+label:after,input[type=search]+label:after,textarea.materialize-textarea+label:after{display:block;content:"";position:absolute;top:60px;opacity:0;transition:.2s opacity ease-out, .2s color ease-out}.input-field{position:relative;margin-top:1rem}.input-field.col label{left:0.75rem}.input-field.col .prefix ~ label,.input-field.col .prefix ~ .validate ~ label{width:calc(100% - 3rem - 1.5rem)}.input-field label{color:#9e9e9e;position:absolute;top:0.8rem;font-size:1rem;cursor:text;transition:.2s ease-out}.input-field label.active{font-size:0.8rem;-webkit-transform:translateY(-140%);transform:translateY(-140%)}.input-field .prefix{position:absolute;width:3rem;font-size:2rem;transition:color .2s}.input-field .prefix.active{color:#26a69a}.input-field .prefix ~ input,.input-field .prefix ~ textarea,.input-field .prefix ~ label,.input-field .prefix ~ .validate ~ label,.input-field .prefix ~ .autocomplete-content{margin-left:3rem;width:92%;width:calc(100% - 3rem)}.input-field .prefix ~ label{margin-left:3rem}@media only screen and (max-width: 992px){.input-field .prefix ~ input{width:86%;width:calc(100% - 3rem)}}@media only screen and (max-width: 600px){.input-field .prefix ~ input{width:80%;width:calc(100% - 3rem)}}.input-field input[type=search]{display:block;line-height:inherit;padding-left:4rem;width:calc(100% - 4rem)}.input-field input[type=search]:focus{background-color:#fff;border:0;box-shadow:none;color:#444}.input-field input[type=search]:focus+label i,.input-field input[type=search]:focus ~ .mdi-navigation-close,.input-field input[type=search]:focus ~ .material-icons{color:#444}.input-field input[type=search]+label{left:1rem}.input-field input[type=search] ~ .mdi-navigation-close,.input-field input[type=search] ~ .material-icons{position:absolute;top:0;right:1rem;color:transparent;cursor:pointer;font-size:2rem;transition:.3s color}textarea{width:100%;height:3rem;background-color:transparent}textarea.materialize-textarea{overflow-y:hidden;padding:.8rem 0 1.6rem 0;resize:none;min-height:3rem}.hiddendiv{display:none;white-space:pre-wrap;word-wrap:break-word;overflow-wrap:break-word;padding-top:1.2rem}.autocomplete-content{margin-top:-15px;display:block;opacity:1;position:static}.autocomplete-content li .highlight{color:#444}.autocomplete-content li img{height:40px;width:40px;margin:5px 15px}[type="radio"]:not(:checked),[type="radio"]:checked{position:absolute;left:-9999px;opacity:0}[type="radio"]:not(:checked)+label,[type="radio"]:checked+label{position:relative;padding-left:35px;cursor:pointer;display:inline-block;height:25px;line-height:25px;font-size:1rem;transition:.28s ease;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}[type="radio"]+label:before,[type="radio"]+label:after{content:'';position:absolute;left:0;top:0;margin:4px;width:16px;height:16px;z-index:0;transition:.28s ease}[type="radio"]:not(:checked)+label:before,[type="radio"]:not(:checked)+label:after,[type="radio"]:checked+label:before,[type="radio"]:checked+label:after,[type="radio"].with-gap:checked+label:before,[type="radio"].with-gap:checked+label:after{border-radius:50%}[type="radio"]:not(:checked)+label:before,[type="radio"]:not(:checked)+label:after{border:2px solid #5a5a5a}[type="radio"]:not(:checked)+label:after{z-index:-1;-webkit-transform:scale(0);transform:scale(0)}[type="radio"]:checked+label:before{border:2px solid transparent}[type="radio"]:checked+label:after,[type="radio"].with-gap:checked+label:before,[type="radio"].with-gap:checked+label:after{border:2px solid #26a69a}[type="radio"]:checked+label:after,[type="radio"].with-gap:checked+label:after{background-color:#26a69a;z-index:0}[type="radio"]:checked+label:after{-webkit-transform:scale(1.02);transform:scale(1.02)}[type="radio"].with-gap:checked+label:after{-webkit-transform:scale(0.5);transform:scale(0.5)}[type="radio"].tabbed:focus+label:before{box-shadow:0 0 0 10px rgba(0,0,0,0.1)}[type="radio"].with-gap:disabled:checked+label:before{border:2px solid rgba(0,0,0,0.26)}[type="radio"].with-gap:disabled:checked+label:after{border:none;background-color:rgba(0,0,0,0.26)}[type="radio"]:disabled:not(:checked)+label:before,[type="radio"]:disabled:checked+label:before{background-color:transparent;border-color:rgba(0,0,0,0.26)}[type="radio"]:disabled+label{color:rgba(0,0,0,0.26)}[type="radio"]:disabled:not(:checked)+label:before{border-color:rgba(0,0,0,0.26)}[type="radio"]:disabled:checked+label:after{background-color:rgba(0,0,0,0.26);border-color:#BDBDBD}form p{margin-bottom:10px;text-align:left}form p:last-child{margin-bottom:0}[type="checkbox"]:not(:checked),[type="checkbox"]:checked{position:absolute;left:-9999px;opacity:0}[type="checkbox"]+label{position:relative;padding-left:35px;cursor:pointer;display:inline-block;height:25px;line-height:25px;font-size:1rem;-webkit-user-select:none;-moz-user-select:none;-khtml-user-select:none;-ms-user-select:none}[type="checkbox"]+label:before,[type="checkbox"]:not(.filled-in)+label:after{content:'';position:absolute;top:0;left:0;width:18px;height:18px;z-index:0;border:2px solid #5a5a5a;border-radius:1px;margin-top:2px;transition:.2s}[type="checkbox"]:not(.filled-in)+label:after{border:0;-webkit-transform:scale(0);transform:scale(0)}[type="checkbox"]:not(:checked):disabled+label:before{border:none;background-color:rgba(0,0,0,0.26)}[type="checkbox"].tabbed:focus+label:after{-webkit-transform:scale(1);transform:scale(1);border:0;border-radius:50%;box-shadow:0 0 0 10px rgba(0,0,0,0.1);background-color:rgba(0,0,0,0.1)}[type="checkbox"]:checked+label:before{top:-4px;left:-5px;width:12px;height:22px;border-top:2px solid transparent;border-left:2px solid transparent;border-right:2px solid #26a69a;border-bottom:2px solid #26a69a;-webkit-transform:rotate(40deg);transform:rotate(40deg);-webkit-backface-visibility:hidden;backface-visibility:hidden;-webkit-transform-origin:100% 100%;transform-origin:100% 100%}[type="checkbox"]:checked:disabled+label:before{border-right:2px solid rgba(0,0,0,0.26);border-bottom:2px solid rgba(0,0,0,0.26)}[type="checkbox"]:indeterminate+label:before{top:-11px;left:-12px;width:10px;height:22px;border-top:none;border-left:none;border-right:2px solid #26a69a;border-bottom:none;-webkit-transform:rotate(90deg);transform:rotate(90deg);-webkit-backface-visibility:hidden;backface-visibility:hidden;-webkit-transform-origin:100% 100%;transform-origin:100% 100%}[type="checkbox"]:indeterminate:disabled+label:before{border-right:2px solid rgba(0,0,0,0.26);background-color:transparent}[type="checkbox"].filled-in+label:after{border-radius:2px}[type="checkbox"].filled-in+label:before,[type="checkbox"].filled-in+label:after{content:'';left:0;position:absolute;transition:border .25s, background-color .25s, width .20s .1s, height .20s .1s, top .20s .1s, left .20s .1s;z-index:1}[type="checkbox"].filled-in:not(:checked)+label:before{width:0;height:0;border:3px solid transparent;left:6px;top:10px;-webkit-transform:rotateZ(37deg);transform:rotateZ(37deg);-webkit-transform-origin:20% 40%;transform-origin:100% 100%}[type="checkbox"].filled-in:not(:checked)+label:after{height:20px;width:20px;background-color:transparent;border:2px solid #5a5a5a;top:0px;z-index:0}[type="checkbox"].filled-in:checked+label:before{top:0;left:1px;width:8px;height:13px;border-top:2px solid transparent;border-left:2px solid transparent;border-right:2px solid #fff;border-bottom:2px solid #fff;-webkit-transform:rotateZ(37deg);transform:rotateZ(37deg);-webkit-transform-origin:100% 100%;transform-origin:100% 100%}[type="checkbox"].filled-in:checked+label:after{top:0;width:20px;height:20px;border:2px solid #26a69a;background-color:#26a69a;z-index:0}[type="checkbox"].filled-in.tabbed:focus+label:after{border-radius:2px;border-color:#5a5a5a;background-color:rgba(0,0,0,0.1)}[type="checkbox"].filled-in.tabbed:checked:focus+label:after{border-radius:2px;background-color:#26a69a;border-color:#26a69a}[type="checkbox"].filled-in:disabled:not(:checked)+label:before{background-color:transparent;border:2px solid transparent}[type="checkbox"].filled-in:disabled:not(:checked)+label:after{border-color:transparent;background-color:#BDBDBD}[type="checkbox"].filled-in:disabled:checked+label:before{background-color:transparent}[type="checkbox"].filled-in:disabled:checked+label:after{background-color:#BDBDBD;border-color:#BDBDBD}.switch,.switch *{-webkit-user-select:none;-moz-user-select:none;-khtml-user-select:none;-ms-user-select:none}.switch label{cursor:pointer}.switch label input[type=checkbox]{opacity:0;width:0;height:0}.switch label input[type=checkbox]:checked+.lever{background-color:#84c7c1}.switch label input[type=checkbox]:checked+.lever:after{background-color:#26a69a;left:24px}.switch label .lever{content:"";display:inline-block;position:relative;width:40px;height:15px;background-color:#818181;border-radius:15px;margin-right:10px;transition:background 0.3s ease;vertical-align:middle;margin:0 16px}.switch label .lever:after{content:"";position:absolute;display:inline-block;width:21px;height:21px;background-color:#F1F1F1;border-radius:21px;box-shadow:0 1px 3px 1px rgba(0,0,0,0.4);left:-5px;top:-3px;transition:left 0.3s ease, background .3s ease, box-shadow 0.1s ease}input[type=checkbox]:checked:not(:disabled) ~ .lever:active::after,input[type=checkbox]:checked:not(:disabled).tabbed:focus ~ .lever::after{box-shadow:0 1px 3px 1px rgba(0,0,0,0.4),0 0 0 15px rgba(38,166,154,0.1)}input[type=checkbox]:not(:disabled) ~ .lever:active:after,input[type=checkbox]:not(:disabled).tabbed:focus ~ .lever::after{box-shadow:0 1px 3px 1px rgba(0,0,0,0.4),0 0 0 15px rgba(0,0,0,0.08)}.switch input[type=checkbox][disabled]+.lever{cursor:default}.switch label input[type=checkbox][disabled]+.lever:after,.switch label input[type=checkbox][disabled]:checked+.lever:after{background-color:#BDBDBD}select{display:none}select.browser-default{display:block}select{background-color:rgba(255,255,255,0.9);width:100%;padding:5px;border:1px solid #f2f2f2;border-radius:2px;height:3rem}.select-label{position:absolute}.select-wrapper{position:relative}.select-wrapper input.select-dropdown{position:relative;cursor:pointer;background-color:transparent;border:none;border-bottom:1px solid #9e9e9e;outline:none;height:3rem;line-height:3rem;width:100%;font-size:1rem;margin:0 0 20px 0;padding:0;display:block}.select-wrapper span.caret{color:initial;position:absolute;right:0;top:16px;font-size:10px}.select-wrapper span.caret.disabled{color:rgba(0,0,0,0.26)}.select-wrapper+label{position:absolute;top:-14px;font-size:0.8rem}select:disabled{color:rgba(0,0,0,0.3)}.select-wrapper input.select-dropdown:disabled{color:rgba(0,0,0,0.3);cursor:default;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;border-bottom:1px solid rgba(0,0,0,0.3)}.select-wrapper i{color:rgba(0,0,0,0.3)}.select-dropdown li.disabled,.select-dropdown li.disabled>span,.select-dropdown li.optgroup{color:rgba(0,0,0,0.3);background-color:transparent}.prefix ~ .select-wrapper{margin-left:3rem;width:92%;width:calc(100% - 3rem)}.prefix ~ label{margin-left:3rem}.select-dropdown li img{height:40px;width:40px;margin:5px 15px;float:right}.select-dropdown li.optgroup{border-top:1px solid #eee}.select-dropdown li.optgroup.selected>span{color:rgba(0,0,0,0.7)}.select-dropdown li.optgroup>span{color:rgba(0,0,0,0.4)}.select-dropdown li.optgroup ~ li.optgroup-option{padding-left:1rem}.file-field{position:relative}.file-field .file-path-wrapper{overflow:hidden;padding-left:10px}.file-field input.file-path{width:100%}.file-field .btn,.file-field .btn-large{float:left;height:3rem;line-height:3rem}.file-field span{cursor:pointer}.file-field input[type=file]{position:absolute;top:0;right:0;left:0;bottom:0;width:100%;margin:0;padding:0;font-size:20px;cursor:pointer;opacity:0;filter:alpha(opacity=0)}.range-field{position:relative}input[type=range],input[type=range]+.thumb{cursor:pointer}input[type=range]{position:relative;background-color:transparent;border:none;outline:none;width:100%;margin:15px 0;padding:0}input[type=range]:focus{outline:none}input[type=range]+.thumb{position:absolute;border:none;height:0;width:0;border-radius:50%;background-color:#26a69a;top:10px;margin-left:-6px;-webkit-transform-origin:50% 50%;transform-origin:50% 50%;-webkit-transform:rotate(-45deg);transform:rotate(-45deg)}input[type=range]+.thumb .value{display:block;width:30px;text-align:center;color:#26a69a;font-size:0;-webkit-transform:rotate(45deg);transform:rotate(45deg)}input[type=range]+.thumb.active{border-radius:50% 50% 50% 0}input[type=range]+.thumb.active .value{color:#fff;margin-left:-1px;margin-top:8px;font-size:10px}input[type=range]{-webkit-appearance:none}input[type=range]::-webkit-slider-runnable-track{height:3px;background:#c2c0c2;border:none}input[type=range]::-webkit-slider-thumb{-webkit-appearance:none;border:none;height:14px;width:14px;border-radius:50%;background-color:#26a69a;-webkit-transform-origin:50% 50%;transform-origin:50% 50%;margin:-5px 0 0 0;transition:.3s}input[type=range]:focus::-webkit-slider-runnable-track{background:#ccc}input[type=range]{border:1px solid white}input[type=range]::-moz-range-track{height:3px;background:#ddd;border:none}input[type=range]::-moz-range-thumb{border:none;height:14px;width:14px;border-radius:50%;background:#26a69a;margin-top:-5px}input[type=range]:-moz-focusring{outline:1px solid #fff;outline-offset:-1px}input[type=range]:focus::-moz-range-track{background:#ccc}input[type=range]::-ms-track{height:3px;background:transparent;border-color:transparent;border-width:6px 0;color:transparent}input[type=range]::-ms-fill-lower{background:#777}input[type=range]::-ms-fill-upper{background:#ddd}input[type=range]::-ms-thumb{border:none;height:14px;width:14px;border-radius:50%;background:#26a69a}input[type=range]:focus::-ms-fill-lower{background:#888}input[type=range]:focus::-ms-fill-upper{background:#ccc}.table-of-contents.fixed{position:fixed}.table-of-contents li{padding:2px 0}.table-of-contents a{display:inline-block;font-weight:300;color:#757575;padding-left:20px;height:1.5rem;line-height:1.5rem;letter-spacing:.4;display:inline-block}.table-of-contents a:hover{color:#a8a8a8;padding-left:19px;border-left:1px solid #ea4a4f}.table-of-contents a.active{font-weight:500;padding-left:18px;border-left:2px solid #ea4a4f}.side-nav{position:fixed;width:300px;left:0;top:0;margin:0;-webkit-transform:translateX(-100%);transform:translateX(-100%);height:100%;height:calc(100% + 60px);height:-moz-calc(100%);padding-bottom:60px;background-color:#fff;z-index:999;-webkit-backface-visibility:hidden;backface-visibility:hidden;overflow-y:auto;will-change:transform;-webkit-backface-visibility:hidden;backface-visibility:hidden;-webkit-transform:translateX(-105%);transform:translateX(-105%)}.side-nav.right-aligned{right:0;-webkit-transform:translateX(105%);transform:translateX(105%);left:auto;-webkit-transform:translateX(100%);transform:translateX(100%)}.side-nav .collapsible{margin:0}.side-nav li{float:none;line-height:48px}.side-nav li.active{background-color:rgba(0,0,0,0.05)}.side-nav a{color:rgba(0,0,0,0.87);display:block;font-size:14px;font-weight:500;height:48px;line-height:48px;padding:0 32px}.side-nav a:hover{background-color:rgba(0,0,0,0.05)}.side-nav a.btn,.side-nav a.btn-large,.side-nav a.btn-large,.side-nav a.btn-flat,.side-nav a.btn-floating{margin:10px 15px}.side-nav a.btn,.side-nav a.btn-large,.side-nav a.btn-large,.side-nav a.btn-floating{color:#fff}.side-nav a.btn-flat{color:#343434}.side-nav a.btn:hover,.side-nav a.btn-large:hover,.side-nav a.btn-large:hover{background-color:#2bbbad}.side-nav a.btn-floating:hover{background-color:#26a69a}.side-nav li>a>i,.side-nav li>a>[class^="mdi-"],.side-nav li>a>[class*="mdi-"],.side-nav li>a>i.material-icons{float:left;line-height:48px;margin:0 32px 0 0;width:24px;color:rgba(0,0,0,0.54)}.side-nav .divider{margin:8px 0 0 0}.side-nav .subheader{cursor:initial;pointer-events:none;color:rgba(0,0,0,0.54);font-size:14px;font-weight:500;line-height:48px}.side-nav .subheader:hover{background-color:transparent}.side-nav .userView{overflow:hidden;position:relative;padding:32px 32px 0;margin-bottom:8px}.side-nav .userView a{height:auto;padding:0}.side-nav .userView a:hover{background-color:transparent}.side-nav .userView .background{position:absolute;top:0;right:0;bottom:0;left:0;z-index:-1}.side-nav .userView .circle,.side-nav .userView .name,.side-nav .userView .email{display:block}.side-nav .userView .circle{height:64px;width:64px}.side-nav .userView .name,.side-nav .userView .email{font-weight:14px;line-height:24px}.side-nav .userView .name{margin-top:16px;font-weight:500}.side-nav .userView .email{padding-bottom:16px;font-weight:400}.drag-target{height:100%;width:10px;position:fixed;top:0;z-index:998}.side-nav.fixed a{display:block;padding:0 16px;color:rgba(0,0,0,0.87)}.side-nav.fixed{left:0;-webkit-transform:translateX(0);transform:translateX(0);position:fixed}.side-nav.fixed.right-aligned{right:0;left:auto}@media only screen and (max-width: 992px){.side-nav.fixed{-webkit-transform:translateX(-105%);transform:translateX(-105%)}.side-nav.fixed.right-aligned{-webkit-transform:translateX(105%);transform:translateX(105%)}.side-nav a{padding:0 16px}.side-nav .userView{padding:16px 16px 0}}.side-nav .collapsible-body li.active,.side-nav.fixed .collapsible-body li.active{background-color:#ee6e73}.side-nav .collapsible-body li.active a,.side-nav.fixed .collapsible-body li.active a{color:#fff}#sidenav-overlay{position:fixed;top:0;left:0;right:0;height:120vh;background-color:rgba(0,0,0,0.5);z-index:997;will-change:opacity}.preloader-wrapper{display:inline-block;position:relative;width:48px;height:48px}.preloader-wrapper.small{width:36px;height:36px}.preloader-wrapper.big{width:64px;height:64px}.preloader-wrapper.active{-webkit-animation:container-rotate 1568ms linear infinite;animation:container-rotate 1568ms linear infinite}@-webkit-keyframes container-rotate{to{-webkit-transform:rotate(360deg)}}@keyframes container-rotate{to{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}.spinner-layer{position:absolute;width:100%;height:100%;opacity:0;border-color:#26a69a}.spinner-blue,.spinner-blue-only{border-color:#4285f4}.spinner-red,.spinner-red-only{border-color:#db4437}.spinner-yellow,.spinner-yellow-only{border-color:#f4b400}.spinner-green,.spinner-green-only{border-color:#0f9d58}.active .spinner-layer.spinner-blue{-webkit-animation:fill-unfill-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both,blue-fade-in-out 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both;animation:fill-unfill-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both,blue-fade-in-out 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both}.active .spinner-layer.spinner-red{-webkit-animation:fill-unfill-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both,red-fade-in-out 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both;animation:fill-unfill-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both,red-fade-in-out 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both}.active .spinner-layer.spinner-yellow{-webkit-animation:fill-unfill-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both,yellow-fade-in-out 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both;animation:fill-unfill-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both,yellow-fade-in-out 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both}.active .spinner-layer.spinner-green{-webkit-animation:fill-unfill-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both,green-fade-in-out 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both;animation:fill-unfill-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both,green-fade-in-out 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both}.active .spinner-layer,.active .spinner-layer.spinner-blue-only,.active .spinner-layer.spinner-red-only,.active .spinner-layer.spinner-yellow-only,.active .spinner-layer.spinner-green-only{opacity:1;-webkit-animation:fill-unfill-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both;animation:fill-unfill-rotate 5332ms cubic-bezier(0.4, 0, 0.2, 1) infinite both}@-webkit-keyframes fill-unfill-rotate{12.5%{-webkit-transform:rotate(135deg)}25%{-webkit-transform:rotate(270deg)}37.5%{-webkit-transform:rotate(405deg)}50%{-webkit-transform:rotate(540deg)}62.5%{-webkit-transform:rotate(675deg)}75%{-webkit-transform:rotate(810deg)}87.5%{-webkit-transform:rotate(945deg)}to{-webkit-transform:rotate(1080deg)}}@keyframes fill-unfill-rotate{12.5%{-webkit-transform:rotate(135deg);transform:rotate(135deg)}25%{-webkit-transform:rotate(270deg);transform:rotate(270deg)}37.5%{-webkit-transform:rotate(405deg);transform:rotate(405deg)}50%{-webkit-transform:rotate(540deg);transform:rotate(540deg)}62.5%{-webkit-transform:rotate(675deg);transform:rotate(675deg)}75%{-webkit-transform:rotate(810deg);transform:rotate(810deg)}87.5%{-webkit-transform:rotate(945deg);transform:rotate(945deg)}to{-webkit-transform:rotate(1080deg);transform:rotate(1080deg)}}@-webkit-keyframes blue-fade-in-out{from{opacity:1}25%{opacity:1}26%{opacity:0}89%{opacity:0}90%{opacity:1}100%{opacity:1}}@keyframes blue-fade-in-out{from{opacity:1}25%{opacity:1}26%{opacity:0}89%{opacity:0}90%{opacity:1}100%{opacity:1}}@-webkit-keyframes red-fade-in-out{from{opacity:0}15%{opacity:0}25%{opacity:1}50%{opacity:1}51%{opacity:0}}@keyframes red-fade-in-out{from{opacity:0}15%{opacity:0}25%{opacity:1}50%{opacity:1}51%{opacity:0}}@-webkit-keyframes yellow-fade-in-out{from{opacity:0}40%{opacity:0}50%{opacity:1}75%{opacity:1}76%{opacity:0}}@keyframes yellow-fade-in-out{from{opacity:0}40%{opacity:0}50%{opacity:1}75%{opacity:1}76%{opacity:0}}@-webkit-keyframes green-fade-in-out{from{opacity:0}65%{opacity:0}75%{opacity:1}90%{opacity:1}100%{opacity:0}}@keyframes green-fade-in-out{from{opacity:0}65%{opacity:0}75%{opacity:1}90%{opacity:1}100%{opacity:0}}.gap-patch{position:absolute;top:0;left:45%;width:10%;height:100%;overflow:hidden;border-color:inherit}.gap-patch .circle{width:1000%;left:-450%}.circle-clipper{display:inline-block;position:relative;width:50%;height:100%;overflow:hidden;border-color:inherit}.circle-clipper .circle{width:200%;height:100%;border-width:3px;border-style:solid;border-color:inherit;border-bottom-color:transparent !important;border-radius:50%;-webkit-animation:none;animation:none;position:absolute;top:0;right:0;bottom:0}.circle-clipper.left .circle{left:0;border-right-color:transparent !important;-webkit-transform:rotate(129deg);transform:rotate(129deg)}.circle-clipper.right .circle{left:-100%;border-left-color:transparent !important;-webkit-transform:rotate(-129deg);transform:rotate(-129deg)}.active .circle-clipper.left .circle{-webkit-animation:left-spin 1333ms cubic-bezier(0.4, 0, 0.2, 1) infinite both;animation:left-spin 1333ms cubic-bezier(0.4, 0, 0.2, 1) infinite both}.active .circle-clipper.right .circle{-webkit-animation:right-spin 1333ms cubic-bezier(0.4, 0, 0.2, 1) infinite both;animation:right-spin 1333ms cubic-bezier(0.4, 0, 0.2, 1) infinite both}@-webkit-keyframes left-spin{from{-webkit-transform:rotate(130deg)}50%{-webkit-transform:rotate(-5deg)}to{-webkit-transform:rotate(130deg)}}@keyframes left-spin{from{-webkit-transform:rotate(130deg);transform:rotate(130deg)}50%{-webkit-transform:rotate(-5deg);transform:rotate(-5deg)}to{-webkit-transform:rotate(130deg);transform:rotate(130deg)}}@-webkit-keyframes right-spin{from{-webkit-transform:rotate(-130deg)}50%{-webkit-transform:rotate(5deg)}to{-webkit-transform:rotate(-130deg)}}@keyframes right-spin{from{-webkit-transform:rotate(-130deg);transform:rotate(-130deg)}50%{-webkit-transform:rotate(5deg);transform:rotate(5deg)}to{-webkit-transform:rotate(-130deg);transform:rotate(-130deg)}}#spinnerContainer.cooldown{-webkit-animation:container-rotate 1568ms linear infinite,fade-out 400ms cubic-bezier(0.4, 0, 0.2, 1);animation:container-rotate 1568ms linear infinite,fade-out 400ms cubic-bezier(0.4, 0, 0.2, 1)}@-webkit-keyframes fade-out{from{opacity:1}to{opacity:0}}@keyframes fade-out{from{opacity:1}to{opacity:0}}.slider{position:relative;height:400px;width:100%}.slider.fullscreen{height:100%;width:100%;position:absolute;top:0;left:0;right:0;bottom:0}.slider.fullscreen ul.slides{height:100%}.slider.fullscreen ul.indicators{z-index:2;bottom:30px}.slider .slides{background-color:#9e9e9e;margin:0;height:400px}.slider .slides li{opacity:0;position:absolute;top:0;left:0;z-index:1;width:100%;height:inherit;overflow:hidden}.slider .slides li img{height:100%;width:100%;background-size:cover;background-position:center}.slider .slides li .caption{color:#fff;position:absolute;top:15%;left:15%;width:70%;opacity:0}.slider .slides li .caption p{color:#e0e0e0}.slider .slides li.active{z-index:2}.slider .indicators{position:absolute;text-align:center;left:0;right:0;bottom:0;margin:0}.slider .indicators .indicator-item{display:inline-block;position:relative;cursor:pointer;height:16px;width:16px;margin:0 12px;background-color:#e0e0e0;transition:background-color .3s;border-radius:50%}.slider .indicators .indicator-item.active{background-color:#4CAF50}.carousel{overflow:hidden;position:relative;width:100%;height:400px;-webkit-perspective:500px;perspective:500px;-webkit-transform-style:preserve-3d;transform-style:preserve-3d;-webkit-transform-origin:0% 50%;transform-origin:0% 50%}.carousel.carousel-slider{top:0;left:0;height:0}.carousel.carousel-slider .carousel-fixed-item{position:absolute;left:0;right:0;bottom:20px;z-index:1}.carousel.carousel-slider .carousel-fixed-item.with-indicators{bottom:68px}.carousel.carousel-slider .carousel-item{width:100%;height:100%;min-height:400px;position:absolute;top:0;left:0}.carousel.carousel-slider .carousel-item h2{font-size:24px;font-weight:500;line-height:32px}.carousel.carousel-slider .carousel-item p{font-size:15px}.carousel .carousel-item{display:none;width:200px;height:400px;position:absolute;top:0;left:0}.carousel .carousel-item img{width:100%}.carousel .indicators{position:absolute;text-align:center;left:0;right:0;bottom:0;margin:0}.carousel .indicators .indicator-item{display:inline-block;position:relative;cursor:pointer;height:8px;width:8px;margin:24px 4px;background-color:rgba(255,255,255,0.5);transition:background-color .3s;border-radius:50%}.carousel .indicators .indicator-item.active{background-color:#fff}.picker{font-size:16px;text-align:left;line-height:1.2;color:#000000;position:absolute;z-index:10000;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.picker__input{cursor:default}.picker__input.picker__input--active{border-color:#0089ec}.picker__holder{width:100%;overflow-y:auto;-webkit-overflow-scrolling:touch}/*! * Default mobile-first, responsive styling for pickadate.js * Demo: http://amsul.github.io/pickadate.js */.picker__holder,.picker__frame{bottom:0;left:0;right:0;top:100%}.picker__holder{position:fixed;transition:background 0.15s ease-out, top 0s 0.15s;-webkit-backface-visibility:hidden}.picker__frame{position:absolute;margin:0 auto;min-width:256px;width:300px;max-height:350px;-ms-filter:"progid:DXImageTransform.Microsoft.Alpha(Opacity=0)";filter:alpha(opacity=0);-moz-opacity:0;opacity:0;transition:all 0.15s ease-out}@media (min-height: 28.875em){.picker__frame{overflow:visible;top:auto;bottom:-100%;max-height:80%}}@media (min-height: 40.125em){.picker__frame{margin-bottom:7.5%}}.picker__wrap{display:table;width:100%;height:100%}@media (min-height: 28.875em){.picker__wrap{display:block}}.picker__box{background:#ffffff;display:table-cell;vertical-align:middle}@media (min-height: 28.875em){.picker__box{display:block;border:1px solid #777777;border-top-color:#898989;border-bottom-width:0;border-radius:5px 5px 0 0;box-shadow:0 12px 36px 16px rgba(0,0,0,0.24)}}.picker--opened .picker__holder{top:0;background:transparent;-ms-filter:"progid:DXImageTransform.Microsoft.gradient(startColorstr=#1E000000,endColorstr=#1E000000)";zoom:1;background:rgba(0,0,0,0.32);transition:background 0.15s ease-out}.picker--opened .picker__frame{top:0;-ms-filter:"progid:DXImageTransform.Microsoft.Alpha(Opacity=100)";filter:alpha(opacity=100);-moz-opacity:1;opacity:1}@media (min-height: 35.875em){.picker--opened .picker__frame{top:10%;bottom:auto}}.picker__input.picker__input--active{border-color:#E3F2FD}.picker__frame{margin:0 auto;max-width:325px}@media (min-height: 38.875em){.picker--opened .picker__frame{top:10%;bottom:auto}}.picker__box{padding:0 1em}.picker__header{text-align:center;position:relative;margin-top:.75em}.picker__month,.picker__year{display:inline-block;margin-left:.25em;margin-right:.25em}.picker__select--month,.picker__select--year{height:2em;padding:0;margin-left:.25em;margin-right:.25em}.picker__select--month.browser-default{display:inline;background-color:#FFFFFF;width:40%}.picker__select--year.browser-default{display:inline;background-color:#FFFFFF;width:26%}.picker__select--month:focus,.picker__select--year:focus{border-color:rgba(0,0,0,0.05)}.picker__nav--prev,.picker__nav--next{position:absolute;padding:.5em 1.25em;width:1em;height:1em;box-sizing:content-box;top:-0.25em}.picker__nav--prev{left:-1em;padding-right:1.25em}.picker__nav--next{right:-1em;padding-left:1.25em}.picker__nav--disabled,.picker__nav--disabled:hover,.picker__nav--disabled:before,.picker__nav--disabled:before:hover{cursor:default;background:none;border-right-color:#f5f5f5;border-left-color:#f5f5f5}.picker__table{text-align:center;border-collapse:collapse;border-spacing:0;table-layout:fixed;font-size:1rem;width:100%;margin-top:.75em;margin-bottom:.5em}.picker__table th,.picker__table td{text-align:center}.picker__table td{margin:0;padding:0}.picker__weekday{width:14.285714286%;font-size:.75em;padding-bottom:.25em;color:#999999;font-weight:500}@media (min-height: 33.875em){.picker__weekday{padding-bottom:.5em}}.picker__day--today{position:relative;color:#595959;letter-spacing:-.3;padding:.75rem 0;font-weight:400;border:1px solid transparent}.picker__day--disabled:before{border-top-color:#aaaaaa}.picker__day--infocus:hover{cursor:pointer;color:#000;font-weight:500}.picker__day--outfocus{display:none;padding:.75rem 0;color:#fff}.picker__day--outfocus:hover{cursor:pointer;color:#dddddd;font-weight:500}.picker__day--highlighted:hover,.picker--focused .picker__day--highlighted{cursor:pointer}.picker__day--selected,.picker__day--selected:hover,.picker--focused .picker__day--selected{border-radius:50%;-webkit-transform:scale(0.75);transform:scale(0.75);background:#0089ec;color:#ffffff}.picker__day--disabled,.picker__day--disabled:hover,.picker--focused .picker__day--disabled{background:#f5f5f5;border-color:#f5f5f5;color:#dddddd;cursor:default}.picker__day--highlighted.picker__day--disabled,.picker__day--highlighted.picker__day--disabled:hover{background:#bbbbbb}.picker__footer{text-align:center;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-align-items:center;-ms-flex-align:center;align-items:center;-webkit-justify-content:space-between;-ms-flex-pack:justify;justify-content:space-between}.picker__button--today,.picker__button--clear,.picker__button--close{border:1px solid #ffffff;background:#ffffff;font-size:.8em;padding:.66em 0;font-weight:bold;width:33%;display:inline-block;vertical-align:bottom}.picker__button--today:hover,.picker__button--clear:hover,.picker__button--close:hover{cursor:pointer;color:#000000;background:#b1dcfb;border-bottom-color:#b1dcfb}.picker__button--today:focus,.picker__button--clear:focus,.picker__button--close:focus{background:#b1dcfb;border-color:rgba(0,0,0,0.05);outline:none}.picker__button--today:before,.picker__button--clear:before,.picker__button--close:before{position:relative;display:inline-block;height:0}.picker__button--today:before,.picker__button--clear:before{content:" ";margin-right:.45em}.picker__button--today:before{top:-0.05em;width:0;border-top:0.66em solid #0059bc;border-left:.66em solid transparent}.picker__button--clear:before{top:-0.25em;width:.66em;border-top:3px solid #ee2200}.picker__button--close:before{content:"\D7";top:-0.1em;vertical-align:top;font-size:1.1em;margin-right:.35em;color:#777777}.picker__button--today[disabled],.picker__button--today[disabled]:hover{background:#f5f5f5;border-color:#f5f5f5;color:#dddddd;cursor:default}.picker__button--today[disabled]:before{border-top-color:#aaaaaa}.picker__box{border-radius:2px;overflow:hidden}.picker__date-display{text-align:center;background-color:#26a69a;color:#fff;padding-bottom:15px;font-weight:300}.picker__nav--prev:hover,.picker__nav--next:hover{cursor:pointer;color:#000000;background:#a1ded8}.picker__weekday-display{background-color:#1f897f;padding:10px;font-weight:200;letter-spacing:.5;font-size:1rem;margin-bottom:15px}.picker__month-display{text-transform:uppercase;font-size:2rem}.picker__day-display{font-size:4.5rem;font-weight:400}.picker__year-display{font-size:1.8rem;color:rgba(255,255,255,0.4)}.picker__box{padding:0}.picker__calendar-container{padding:0 1rem}.picker__calendar-container thead{border:none}.picker__table{margin-top:0;margin-bottom:.5em}.picker__day--infocus{color:#595959;letter-spacing:-.3;padding:.75rem 0;font-weight:400;border:1px solid transparent}.picker__day.picker__day--today{color:#26a69a}.picker__day.picker__day--today.picker__day--selected{color:#fff}.picker__weekday{font-size:.9rem}.picker__day--selected,.picker__day--selected:hover,.picker--focused .picker__day--selected{border-radius:50%;-webkit-transform:scale(0.9);transform:scale(0.9);background-color:#26a69a;color:#ffffff}.picker__day--selected.picker__day--outfocus,.picker__day--selected:hover.picker__day--outfocus,.picker--focused .picker__day--selected.picker__day--outfocus{background-color:#a1ded8}.picker__footer{text-align:right;padding:5px 10px}.picker__close,.picker__today{font-size:1.1rem;padding:0 1rem;color:#26a69a}.picker__nav--prev:before,.picker__nav--next:before{content:" ";border-top:.5em solid transparent;border-bottom:.5em solid transparent;border-right:0.75em solid #676767;width:0;height:0;display:block;margin:0 auto}.picker__nav--next:before{border-right:0;border-left:0.75em solid #676767}button.picker__today:focus,button.picker__clear:focus,button.picker__close:focus{background-color:#a1ded8}.picker__list{list-style:none;padding:0.75em 0 4.2em;margin:0}.picker__list-item{border-bottom:1px solid #dddddd;border-top:1px solid #dddddd;margin-bottom:-1px;position:relative;background:#ffffff;padding:.75em 1.25em}@media (min-height: 46.75em){.picker__list-item{padding:.5em 1em}}.picker__list-item:hover{cursor:pointer;color:#000000;background:#b1dcfb;border-color:#0089ec;z-index:10}.picker__list-item--highlighted{border-color:#0089ec;z-index:10}.picker__list-item--highlighted:hover,.picker--focused .picker__list-item--highlighted{cursor:pointer;color:#000000;background:#b1dcfb}.picker__list-item--selected,.picker__list-item--selected:hover,.picker--focused .picker__list-item--selected{background:#0089ec;color:#ffffff;z-index:10}.picker__list-item--disabled,.picker__list-item--disabled:hover,.picker--focused .picker__list-item--disabled{background:#f5f5f5;border-color:#f5f5f5;color:#dddddd;cursor:default;border-color:#dddddd;z-index:auto}.picker--time .picker__button--clear{display:block;width:80%;margin:1em auto 0;padding:1em 1.25em;background:none;border:0;font-weight:500;font-size:.67em;text-align:center;text-transform:uppercase;color:#666}.picker--time .picker__button--clear:hover,.picker--time .picker__button--clear:focus{color:#000000;background:#b1dcfb;background:#ee2200;border-color:#ee2200;cursor:pointer;color:#ffffff;outline:none}.picker--time .picker__button--clear:before{top:-0.25em;color:#666;font-size:1.25em;font-weight:bold}.picker--time .picker__button--clear:hover:before,.picker--time .picker__button--clear:focus:before{color:#ffffff}.picker--time .picker__frame{min-width:256px;max-width:320px}.picker--time .picker__box{font-size:1em;background:#f2f2f2;padding:0}@media (min-height: 40.125em){.picker--time .picker__box{margin-bottom:5em}} diff --git a/IntelliQ/backend/src/main/webapp/static/css/style.css b/IntelliQ/backend/src/main/webapp/static/css/style.css index 0c09f4c..efe06a7 100644 --- a/IntelliQ/backend/src/main/webapp/static/css/style.css +++ b/IntelliQ/backend/src/main/webapp/static/css/style.css @@ -1,11 +1,12 @@ - - - nav ul a, nav .brand-logo { color: #FFFFFF; } +.side-nav { + width: 250px; +} + .nav-wrapper ul li a i { cursor: pointer; } @@ -30,6 +31,14 @@ a { cursor: pointer; } +.medium-blur { + -webkit-filter: blur(5px); + -moz-filter: blur(5px); + -o-filter: blur(5px); + -ms-filter: blur(5px); + filter: blur(5px); +} + .fill-width { width: 100%; } @@ -103,6 +112,10 @@ a { line-height: 1.5; } +.card .card-content .card-title { + font-size: 22px; +} + .card .card-action a { color: #663fb4; } @@ -133,6 +146,18 @@ a { vertical-align: middle; } +.card-image .overlay-content a { + color: #fff; + padding-top: 30px; + padding-right: 20px; + padding-bottom: 30px; + padding-left: 10px; + width: 100%; + font-size: 20px; + font-weight: 300; + text-shadow: 0px 0px 10px #000; +} + .card-image img { vertical-align: middle; } @@ -189,8 +214,12 @@ a { position: absolute; height: 100%; width: 100%; + top: 0px; + bottom: 0px; text-align: center; cursor: pointer; + background-color: rgba(0, 0, 0, 0.33); + background-blend-mode: multiply; } .overlay-content .valign-wrapper { @@ -268,6 +297,7 @@ a { position: absolute; top: 40%; } + #index-banner .section { top: 10%; } @@ -277,6 +307,42 @@ a { #index-banner .section { top: 0; } + + h1 { + font-size: 3.2rem; + } + + h5 { + font-size: 1.5rem; + } + + .card .card-content .card-title, .card-title .material-icons { + font-size: 20px; + font-weight: 300; + line-height: 1.5; + } + + .section { + padding-top: 0.2rem; + padding-bottom: 0.2rem; + } + + .vertical-spacing { + margin-top: 5px; + margin-bottom: 5px; + } + + .row { + margin-bottom: 5px; + } + + .row .col { + padding: 0 0.4rem; + } + + td, th { + padding: 7px 5px; + } } .background-image-container { @@ -442,4 +508,8 @@ footer.page-footer { .accent-color-text { color: #fd4482 !important; +} + +.warning-color { + background-color: #d50000 !important; } \ No newline at end of file diff --git a/IntelliQ/backend/src/main/webapp/static/js/api.js b/IntelliQ/backend/src/main/webapp/static/js/api.js index 89fd092..275cd3e 100644 --- a/IntelliQ/backend/src/main/webapp/static/js/api.js +++ b/IntelliQ/backend/src/main/webapp/static/js/api.js @@ -21,14 +21,15 @@ var intelliqApi = function(){ api.GOOGLE_API_TOKEN = "AIzaSyBtc8JtwfK8qT9TX8Tkln4nd7IwR0rP9dY"; // Website (for displaying links) - api.HOST_INTELLIQ_ME = "http://intelliq.me/"; + api.HOST_INTELLIQ_ME = "https://intelliq.me/"; // Development server api.HOST_LOCAL = "http://localhost:8080/"; // App Engine (for api requests) - api.APP_ENGINE_VERSION = 2; - api.HOST_APP_ENGINE = "http://" + api.APP_ENGINE_VERSION + "-dot-intelliq-me.appspot.com/"; + api.APP_ENGINE_VERSION = 3; + api.HOST_APP_ENGINE = "https://intelliq-me.appspot.com/"; + api.HOST_APP_ENGINE_VERSIONED = "https://" + api.APP_ENGINE_VERSION + "-dot-intelliq-me.appspot.com/"; // Request endpoints if (useDevelopmentServer()) { @@ -52,6 +53,7 @@ var intelliqApi = function(){ api.ENDPOINT_QUEUE = api.ENDPOINT_API + "queue/"; api.ENDPOINT_QUEUE_GET = api.ENDPOINT_QUEUE + "get/"; + api.ENDPOINT_QUEUE_NEARBY = api.ENDPOINT_QUEUE + "nearby/"; api.ENDPOINT_QUEUE_ADD = api.ENDPOINT_QUEUE + "add/"; api.ENDPOINT_QUEUE_EDIT = api.ENDPOINT_QUEUE + "edit/"; api.ENDPOINT_QUEUE_POPULATE = api.ENDPOINT_QUEUE + "populate/"; @@ -61,9 +63,11 @@ var intelliqApi = function(){ api.ENDPOINT_QUEUE_ITEM = api.ENDPOINT_API + "item/"; api.ENDPOINT_QUEUE_ITEM_GET = api.ENDPOINT_QUEUE_ITEM + "get/"; + api.ENDPOINT_QUEUE_ITEM_FROM = api.ENDPOINT_QUEUE_ITEM + "from/"; api.ENDPOINT_QUEUE_ITEM_ADD = api.ENDPOINT_QUEUE_ITEM + "add/"; api.ENDPOINT_QUEUE_ITEM_DELETE = api.ENDPOINT_QUEUE_ITEM + "delete/"; api.ENDPOINT_QUEUE_ITEM_STATUS = api.ENDPOINT_QUEUE_ITEM + "status/"; + api.ENDPOINT_QUEUE_ITEM_REPORT = api.ENDPOINT_QUEUE_ITEM + "report/"; // Webpages if (useDevelopmentServer()) { @@ -76,6 +80,11 @@ var intelliqApi = function(){ api.PAGE_LINK_CREATE = api.PAGE_LINK + "create/"; api.PAGE_LINK_DISPLAY = api.PAGE_LINK + "display/"; + api.PAGE_LINK_WEB_APP = api.PAGE_LINK + "apps/web/"; + api.PAGE_LINK_WEB_APP_NEARBY = api.PAGE_LINK_WEB_APP + "nearby/"; + api.PAGE_LINK_WEB_APP_QUEUE = api.PAGE_LINK_WEB_APP + "queue/"; + api.PAGE_LINK_WEB_APP_TICKETS = api.PAGE_LINK_WEB_APP + "tickets/"; + api.PATH_BUSINESS = "business/"; api.PATH_QUEUE = "queue/"; @@ -86,15 +95,23 @@ var intelliqApi = function(){ api.STATUS_CALLED = 2; api.STATUS_DONE = 3; + // Queue visibility api.VISIBILITY_PRIVATE = 0; api.VISIBILITY_PUBLIC = 1; + // Entry types api.ENTRY_TYPE_BUSINESS = "BusinessEntry"; api.ENTRY_TYPE_QUEUE = "QueueEntry"; api.ENTRY_TYPE_QUEUE_ITEM = "QueueItemEntry"; api.ENTRY_TYPE_USER = "UserEntry"; api.ENTRY_TYPE_PERMISSION = "PermissionEntry"; + // Update intervals + api.UPDATE_INTERVAL_CASUAL = 1000 * 30; + api.UPDATE_INTERVAL_DEFAULT = 1000 * 15; + api.UPDATE_INTERVAL_FAST = 1000 * 10; + api.UPDATE_INTERVAL_DEMO = 1000 * 5; + /* Requests */ @@ -345,6 +362,38 @@ var intelliqApi = function(){ api.getQueue = function(queueKeyId) { var request = api.request(api.ENDPOINT_QUEUE_GET); request.addParameter("queueKeyId", queueKeyId); + + request.includeBusiness = function(value) { + if (value) { + request.addParameter("includeBusiness", "true"); + } else { + request.addParameter("includeBusiness", "false"); + } + return request; + } + + return request; + } + + api.getNearbyQueues = function(latitude, longitude) { + var request = api.request(api.ENDPOINT_QUEUE_NEARBY); + request.addParameter("latitude", latitude); + request.addParameter("longitude", longitude); + + request.inRange = function(distance) { + request.addParameter("distance", distance); + return request; + } + + request.includeBusinesses = function(value) { + if (value) { + request.addParameter("includeBusinesses", "true"); + } else { + request.addParameter("includeBusinesses", "false"); + } + return request; + } + return request; } @@ -455,7 +504,7 @@ var intelliqApi = function(){ } api.populateQueue = function(queueKeyId) { - var request = api.request(api.ENDPOINT_QUEUE_GET); + var request = api.request(api.ENDPOINT_QUEUE_POPULATE); request.addParameter("queueKeyId", queueKeyId); request.addParameter("count", 25); @@ -476,6 +525,18 @@ var intelliqApi = function(){ return request; } + api.getQueueItem = function(queueItemKeyId) { + var request = api.request(api.ENDPOINT_QUEUE_ITEM_GET); + request.addParameter("queueItemKeyId", queueItemKeyId); + return request; + } + + api.getQueueItemsFrom = function(userKeyId) { + var request = api.request(api.ENDPOINT_QUEUE_ITEM_FROM); + request.addParameter("userKeyId", userKeyId); + return request; + } + api.addQueueItem = function(queueKeyId) { var request = api.request(api.ENDPOINT_QUEUE_ITEM_ADD); request.addParameter("queueKeyId", queueKeyId); @@ -496,6 +557,15 @@ var intelliqApi = function(){ return request; } + request.usingApp = function(value) { + if (value) { + request.addParameter("usingApp", "true"); + } else { + request.addParameter("usingApp", "false"); + } + return request; + } + return request; } @@ -506,6 +576,13 @@ var intelliqApi = function(){ return request; } + api.reportQueueItem = function(queueKeyId, queueItemKeyId) { + var request = api.request(api.ENDPOINT_QUEUE_ITEM_REPORT); + request.addParameter("queueKeyId", queueKeyId); + request.addParameter("queueItemKeyId", queueItemKeyId); + return request; + } + api.setQueueItemStatus = function(queueKeyId, queueItemKeyId, status) { var request = api.request(api.ENDPOINT_QUEUE_ITEM_STATUS); request.addParameter("queueKeyId", queueKeyId); @@ -537,17 +614,17 @@ var intelliqApi = function(){ } api.clearAllQueueItems = function(queueKeyId) { - return api.clearQueueItems(queueKeyId).withStatus(STATUS_ALL).keepWaiting(false); + return api.clearQueueItems(queueKeyId).withStatus(api.STATUS_ALL).keepWaiting(false); } api.clearProcessedQueueItems = function(queueKeyId) { - return api.clearQueueItems(queueKeyId).withStatus(STATUS_ALL).keepWaiting(true); + return api.clearQueueItems(queueKeyId).withStatus(api.STATUS_ALL).keepWaiting(true); } api.clearQueueItems = function(queueKeyId) { var request = api.request(api.ENDPOINT_QUEUE_CLEAR); request.addParameter("queueKeyId", queueKeyId); - request.addParameter("status", STATUS_ALL); + request.addParameter("status", api.STATUS_ALL); request.addParameter("clearWaiting", "false"); request.addParameter("clearCalled", "false"); @@ -644,7 +721,7 @@ var intelliqApi = function(){ filter.byStatus = function(status) { var items = []; if (queueItems == null) { - return entries; + return items; } for (var i = 0; i < queueItems.length; i++) { var item = queueItems[i]; @@ -658,6 +735,34 @@ var intelliqApi = function(){ return filter; } + api.sortQueueItems = function(queueItems) { + var sort = {}; + + sort.byTicketNumber = function() { + queueItems.sort(function(a, b) { + if (a.ticketNumber < b.ticketNumber) + return -1; + if (a.ticketNumber > b.ticketNumber) + return 1; + return 0; + }); + return queueItems; + } + + sort.byStatusChange = function() { + queueItems.sort(function(a, b) { + if (a.lastStatusChangeTimestamp > b.lastStatusChangeTimestamp) + return -1; + if (a.lastStatusChangeTimestamp < b.lastStatusChangeTimestamp) + return 1; + return 0; + }); + return queueItems; + } + + return sort; + } + /* URL helper */ @@ -698,7 +803,7 @@ var intelliqApi = function(){ urls.resizedTo = function(size) { if (top.location.origin == "file://") { - return "http://localhost:8888/image/" + imageKeyId + "/" + size + ".jpg"; + return api.HOST_LOCAL + "image/" + imageKeyId + "/" + size + ".jpg"; } else { return api.PAGE_LINK + "image/" + imageKeyId + "/" + size + ".jpg"; } @@ -737,6 +842,22 @@ var intelliqApi = function(){ return urls.replaceParameter("queueKeyId", queue.key.id, url); } + urls.openInWebApp = function() { + var url = api.PAGE_LINK_WEB_APP_QUEUE; + return urls.replaceParameter("queueKeyId", queue.key.id, url); + } + + return urls; + } + + urls.forQueueItem = function(queueItemEntry) { + var queueItem = queueItemEntry; + + urls.openInWebApp = function() { + var url = api.PAGE_LINK_WEB_APP_TICKETS; + return urls.replaceParameter("queueItemKeyId", queueItem.key.id, url); + } + return urls; } diff --git a/IntelliQ/backend/src/main/webapp/static/js/authenticator.js b/IntelliQ/backend/src/main/webapp/static/js/authenticator.js index ee4d259..075fe9e 100644 --- a/IntelliQ/backend/src/main/webapp/static/js/authenticator.js +++ b/IntelliQ/backend/src/main/webapp/static/js/authenticator.js @@ -1,218 +1,287 @@ -var authenticator = (function () { - - var instance; - - function createInstance() { - - function log(message) { - console.log("Authenticator: " + message); - } - - var CLIENT_ID_WEB = "1008259459239-t1huos5n6bhkin3is2jlqgkjv9h7mheh.apps.googleusercontent.com"; - var googleUser; - - var instanceInitializedCallback; - var statusChangedCallback; - - return { - - // initializes the instance - initialize: function () { - log("Initializing authenticator instance"); - instance.parseSite(); - instance.initializeGoogleSignIn(); - instance.onInstanceInitialized(); - }, - - // callback for the initialization - onInstanceInitialized: function () { - log("Authenticator initialized"); - if (instance.instanceInitializedCallback != null) { - instance.instanceInitializedCallback(instance); - } - }, - - // looks for sign in buttons and registers onclick listeners - parseSite: function () { - var signInWithGoogleButton = document.getElementById("signInWithGoogleButton"); - instance.registerSignInButton(signInWithGoogleButton); - }, - - // loads the Google auth API and initializes it - initializeGoogleSignIn: function () { - if (gapi.auth2 == null) { - log("Requesting Google auth2 API"); - gapi.load('auth2', instance.initializeGoogleSignIn); - return; - } - - log("Initializing Google sign in"); - - var params = { - client_id: CLIENT_ID_WEB, - fetch_basic_profile: true - } - gapi.auth2.init(params); - - var googleAuth = gapi.auth2.getAuthInstance(); - googleAuth.then(instance.onGoogleSignInInitialized, instance.onGoogleSignInInitializationFailed); - }, - - onGoogleSignInInitialized: function () { - log("Google sign in initialized"); - instance.onGoogleSignInStatusChanged(instance.isSignedIn()); - gapi.auth2.getAuthInstance().isSignedIn.listen(instance.onGoogleSignInStatusChanged); - }, - - onGoogleSignInInitializationFailed: function (error) { - log("Google sign in initialization failed: " + error); - }, - - // invokes a Google sign in - signInWithGoogle: function () { - try { - log("Google sign in invoked"); - var googleAuth = gapi.auth2.getAuthInstance(); - var params = { - fetch_basic_profile: true - } - - var promise = googleAuth.signIn(); - promise.then(function(value) { - log("Google sign in successfull"); - }, function(error) { - log("Google sign in failed: " + error); - }); - } catch (ex) { - log("Google sign in failed: " + ex); - } - }, - - // invokes a Google sign out - signOutFromGoogle: function () { - log("Google sign out invoked"); - var auth2 = gapi.auth2.getAuthInstance(); - auth2.signOut().then(function () { - log("User signed out"); - }, function(error) { - log("Google sign out failed: " + error); - }); - }, - - // revokes all permissions - disconnectFromGoogle: function () { - log("Disconnection from Google invoked"); - var auth2 = gapi.auth2.getAuthInstance(); - auth2.disconnect().then(function () { - log("User disconnected"); - }, function(error) { - log("Disconnecting from Google failed: " + error); - }); - }, - - // set the currently signed in Google user - setCurrentGoogleUser: function (user) { - try { - googleUser = user; - localStorage.setItem("googleUser", JSON.stringify(googleUser)); - } catch (ex) { - log("Unable to set current Google user.") - return null; - } - }, - - // returns the currently signed in Google user, if available - getCurrentGoogleUser: function () { - try { - googleUser = gapi.auth2.getAuthInstance().currentUser.get(); - if (googleUser == null) { - //googleUser = JSON.parse(localStorage.getItem("googleUser")); - } - return googleUser; - } catch (ex) { - log("Unable to get current Google user.") - return null; - } - }, - - // listener for the Google sign in status - onGoogleSignInStatusChanged: function (isSignedIn) { - if (isSignedIn) { - log("Google user status changed: signed in"); - instance.onGoogleSignIn(); - } else { - log("Google user status changed: signed out"); - instance.onGoogleSignOut(); - } - - if (instance.statusChangedCallback != null) { - instance.statusChangedCallback(isSignedIn); - } else { - log("Status change callback is null"); - } - }, - - // callback for the Google sign in - onGoogleSignIn: function () { - instance.setCurrentGoogleUser(instance.getCurrentGoogleUser()); - if (googleUser != null) { - var profile = googleUser.getBasicProfile(); - log("Current user set to: " + profile.getName()); - //log('ID: ' + profile.getId()); - //log('Name: ' + profile.getName()); - //log('Image URL: ' + profile.getImageUrl()); - //log('Email: ' + profile.getEmail()); - - var googleIdToken = googleUser.getAuthResponse().id_token; - log("Google ID Token: " + googleIdToken); - } - }, - - // callback for the Google sign out - onGoogleSignOut: function () { - googleUser = null; - }, - - // returns the id token of the current user - getUserIdToken: function () { - try { - return gapi.auth2.getAuthInstance().currentUser.get().getAuthResponse().id_token; - } catch (ex) { - log("Unable to get token") - return null; - } - }, - - isSignedIn: function () { - return gapi.auth2.getAuthInstance().isSignedIn.get(); - }, - - // adds onclick event listener to a passed DOM element - registerSignInButton: function (div) { - if (div != null) { - log("Registering sign in button: " + div.id); - div.onclick = instance.signInWithGoogle; - } - } - - }; - }; - - return { - // Get the Singleton instance if one exists - // or create one if it doesn't - getInstance: function (newInstanceInitializedCallback, newStatusChangedCallback) { - if (!instance) { - instance = createInstance(); - instance.instanceInitializedCallback = newInstanceInitializedCallback; - instance.statusChangedCallback = newStatusChangedCallback; - instance.initialize(); - } - return instance; - } - }; - -})(); - -// Usage: -// var auth = authenticator.getInstance(); \ No newline at end of file +var authenticator = function(){ + + function log(message) { + console.log("Authenticator: " + message); + } + + var authenticator = { + }; + + authenticator.googleAuthenticationInitialized = false; + authenticator.googleAuthenticationInitializing = false; + authenticator.statusChangeListeners = []; + + authenticator.CLIENT_ID_WEB = "1008259459239-t1huos5n6bhkin3is2jlqgkjv9h7mheh.apps.googleusercontent.com"; + + authenticator.initializeGoogleAuthentication = function() { + var promise = new Promise(function(resolve, reject) { + if (authenticator.googleAuthenticationInitialized) { + resolve(); + return; + } + + if (authenticator.googleAuthenticationInitializing) { + var sleep = function(timeout) { + return new Promise(function(resolve, reject) { + setTimeout(resolve, timeout); + }); + } + + var tries = 0; + var checkAvailablitity = function() { + if (authenticator.googleAuthenticationInitializing && tries < 10) { + tries++; + sleep(500).then(checkAvailablitity); + } else { + if (authenticator.googleAuthenticationInitialized) { + resolve(); + } else { + reject("Initialization failed"); + } + } + } + } + + var onAuthApiAvailable = function() { + log("Initializing Google authentication"); + var params = { + client_id: authenticator.CLIENT_ID_WEB, + fetch_basic_profile: true + } + gapi.auth2.init(params); + + var googleAuth = gapi.auth2.getAuthInstance(); + + var onGoogleSignInInitialized = function () { + log("Google authentication initialized"); + authenticator.googleAuthenticationInitializing = false; + authenticator.googleAuthenticationInitialized = true; + gapi.auth2.getAuthInstance().isSignedIn.listen(function(isSignedIn) { + if (isSignedIn) { + authenticator.onGoogleSignIn(); + } else { + authenticator.onGoogleSignOut(); + } + }); + resolve(); + } + + var onGoogleSignInInitializationFailed = function(error) { + log("Google authentication initialization failed: " + error); + authenticator.googleAuthenticationInitializing = false; + authenticator.googleAuthenticationInitialized = false; + reject(error); + } + + googleAuth.then(onGoogleSignInInitialized, onGoogleSignInInitializationFailed); + } + + if (gapi.auth2 == null) { + log("Requesting Google auth2 API"); + authenticator.googleAuthenticationInitializing = true; + gapi.load('auth2', onAuthApiAvailable); + } else { + onAuthApiAvailable(); + } + }); + return promise; + } + + authenticator.signInToGoogle = function() { + var promise = new Promise(function(resolve, reject) { + authenticator.initializeGoogleAuthentication().then(function() { + try { + log("Signing in to Google"); + var googleAuth = gapi.auth2.getAuthInstance(); + var params = { + fetch_basic_profile: true + } + googleAuth.signIn().then(function(value) { + log("Google sign in succeeded"); + resolve(); + }, function(error) { + log("Google sign in failed: " + error); + reject(error); + }); + } catch (ex) { + log("Google sign in invoking failed: " + ex); + reject(error); + } + }).catch(function(error) { + reject(error); + }); + }); + return promise; + } + + authenticator.signOutFromGoogle = function() { + var promise = new Promise(function(resolve, reject) { + authenticator.initializeGoogleAuthentication().then(function() { + try { + log("Signing out from Google"); + var googleAuth = gapi.auth2.getAuthInstance(); + googleAuth.signOut().then(function(value) { + log("Google sign out succeeded"); + resolve(); + }, function(error) { + log("Google sign out failed: " + error); + reject(error); + }); + } catch (ex) { + log("Google sign out invoking failed: " + ex); + reject(error); + } + }).catch(function(error) { + reject(error); + }); + }); + return promise; + } + + authenticator.disconnectFromGoogle = function() { + var promise = new Promise(function(resolve, reject) { + authenticator.initializeGoogleAuthentication().then(function() { + try { + log("Disconnecting from Google"); + var googleAuth = gapi.auth2.getAuthInstance(); + googleAuth.disconnect().then(function(value) { + log("Google disconnection succeeded"); + resolve(); + }, function(error) { + log("Google disconnection failed: " + error); + reject(error); + }); + } catch (ex) { + log("Google disconnection invoking failed: " + ex); + reject(error); + } + }).catch(function(error) { + reject(error); + }); + }); + return promise; + } + + /* + Asynchronous and synchronous getters + */ + authenticator.requestGoogleSignInStatus = function() { + var promise = new Promise(function(resolve, reject) { + authenticator.initializeGoogleAuthentication().then(function() { + resolve(gapi.auth2.getAuthInstance().isSignedIn.get()); + }).catch(function(error) { + reject(error); + }); + }); + return promise; + } + + authenticator.getGoogleSignInStatus = function() { + try { + return gapi.auth2.getAuthInstance().isSignedIn.get(); + } catch (ex) { + log("Unable to get Google sign in status") + return false; + } + } + + authenticator.requestGoogleUser = function() { + var promise = new Promise(function(resolve, reject) { + authenticator.initializeGoogleAuthentication().then(function() { + var googleUser = gapi.auth2.getAuthInstance().currentUser.get(); + resolve(googleUser); + }).catch(function(error) { + reject(error); + }); + }); + return promise; + } + + authenticator.getGoogleUser = function() { + try { + return gapi.auth2.getAuthInstance().currentUser.get(); + } catch (ex) { + log("Unable to get Google user") + return null; + } + } + + authenticator.getGoogleUserIdToken = function() { + try { + var googleUser = authenticator.getGoogleUser(); + return googleUser.getAuthResponse().id_token; + } catch (ex) { + log("Unable to get Google user ID token") + return null; + } + } + + authenticator.requestIntelliqUserFromGoogleIdToken = function() { + var promise = new Promise(function(resolve, reject) { + try { + var googleIdToken = authenticator.getGoogleUserIdToken(); + if (googleIdToken == null) { + throw "Token is null"; + } + + var signInRequest = intelliqApi.signInUser().setGoogleIdToken(googleIdToken); + signInRequest.send().then(function(data){ + var user = intelliqApi.getUsersFromResponse(data)[0]; + if (user == null) { + reject("Returned user is null"); + } + authenticator.onUserAvailable(user); + resolve(user); + }).catch(function(error) { + reject(error); + }); + } catch (ex) { + reject(ex); + } + }); + return promise; + } + + /* + Status callback handling + */ + authenticator.registerStatusChangeListener = function(listener) { + authenticator.statusChangeListeners.push(listener); + } + + authenticator.onUserAvailable = function(user) { + for (var i = 0; i < authenticator.statusChangeListeners.length; i++) { + var callback = authenticator.statusChangeListeners[i].onUserAvailable; + if (typeof callback === 'function') { + callback(user); + } + } + } + + authenticator.onGoogleSignIn = function() { + var googleUser = authenticator.getGoogleUser(); + var profile = googleUser.getBasicProfile(); + var googleIdToken = authenticator.getGoogleUserIdToken(); + log("Google user signed in: " + profile.getName()); + + for (var i = 0; i < authenticator.statusChangeListeners.length; i++) { + var callback = authenticator.statusChangeListeners[i].onGoogleSignIn; + if (typeof callback === 'function') { + callback(); + } + } + } + + authenticator.onGoogleSignOut = function() { + log("Google user signed out"); + + for (var i = 0; i < authenticator.statusChangeListeners.length; i++) { + var callback = authenticator.statusChangeListeners[i].onGoogleSignOut; + if (typeof callback === 'function') { + callback(); + } + } + } + + return authenticator; +}(); \ No newline at end of file diff --git a/IntelliQ/backend/src/main/webapp/static/js/edit_business.js b/IntelliQ/backend/src/main/webapp/static/js/edit_business.js index d017cfe..dc0a19e 100644 --- a/IntelliQ/backend/src/main/webapp/static/js/edit_business.js +++ b/IntelliQ/backend/src/main/webapp/static/js/edit_business.js @@ -1,22 +1,28 @@ var existingBusiness; // holds the existing business, as returned by the API var newBusiness; // holds the new business, created by the local changes -window.onload = function(event) { - $("#saveBusinessButton").click(saveNewBusiness); - updateFormWithUrlParameterData(); -}; - -var onUserReady = function(user) { - var businessKeyId = getUrlParam("businessKeyId"); - requestExistingBusinessData(businessKeyId); - - // update the UI - if (businessKeyId == null) { - showAddBusinessUi(); - } else { - showEditBusinessUi(); - } -} +(function($){ + $(function(){ + + $("#saveBusinessButton").click(saveNewBusiness); + updateFormWithUrlParameterData(); + + var statusChangeListener = { + onUserAvailable: function(user) { + var businessKeyId = getUrlParam("businessKeyId"); + requestExistingBusinessData(businessKeyId); + + // update the UI + if (businessKeyId == null) { + showAddBusinessUi(); + } else { + showEditBusinessUi(); + } + } + }; + authenticator.registerStatusChangeListener(statusChangeListener); + }); +})(jQuery); // fetches an existing business from the API, if the required // url param is set. Then updates the form with the business data @@ -36,7 +42,7 @@ function requestExistingBusinessData(businessKeyId) { }).catch(function(error){ console.log(error); $(".loadingState").hide(); - showErrorMessage(error); + ui.showErrorMessage(error); }); } @@ -96,12 +102,12 @@ function saveNewBusiness() { console.log(data); window.location.href = intelliqApi.PAGE_LINK_MANAGE; }, function(error) { - showErrorMessage(error); + ui.showErrorMessage(error); }); } function updateExistingBusiness(business) { - var googleIdToken = authenticator.getInstance().getUserIdToken() + var googleIdToken = authenticator.getGoogleUserIdToken(); return intelliqApi .editBusiness(business.key.id, business.name, business.mail) .setGoogleIdToken(googleIdToken) @@ -109,7 +115,7 @@ function updateExistingBusiness(business) { } function addNewBusiness(business) { - var googleIdToken = authenticator.getInstance().getUserIdToken() + var googleIdToken = authenticator.getGoogleUserIdToken(); return intelliqApi .addBusiness(business.name, business.mail) .addQueue(false) diff --git a/IntelliQ/backend/src/main/webapp/static/js/edit_queue.js b/IntelliQ/backend/src/main/webapp/static/js/edit_queue.js index 538902f..9c7dce6 100644 --- a/IntelliQ/backend/src/main/webapp/static/js/edit_queue.js +++ b/IntelliQ/backend/src/main/webapp/static/js/edit_queue.js @@ -5,23 +5,29 @@ var googleMap; // the Google Maps API object var deviceLocationMarker; // map marker for the current location var queueLocationMarker; // map marker for the new queue location -window.onload = function(event) { - $("#saveQueueButton").click(saveNewQueue); - updateFormWithUrlParameterData(); -}; +(function($){ + $(function(){ -var onUserReady = function(user) { - var queueKeyId = getUrlParam("queueKeyId"); - requestExistingQueueData(queueKeyId); + $("#saveQueueButton").click(saveNewQueue); + updateFormWithUrlParameterData(); - // update the UI - if (queueKeyId == null) { - showAddQueueUi(); - updateFormWithDeviceLocation(); - } else { - showEditQueueUi(); - } -} + var statusChangeListener = { + onUserAvailable: function(user) { + var queueKeyId = getUrlParam("queueKeyId"); + requestExistingQueueData(queueKeyId); + + // update the UI + if (queueKeyId == null) { + showAddQueueUi(); + updateFormWithDeviceLocation(); + } else { + showEditQueueUi(); + } + } + }; + authenticator.registerStatusChangeListener(statusChangeListener); + }); +})(jQuery); // fetches an existing queue from the API, if the required // url param is set. Then updates the form with the queue data @@ -41,7 +47,7 @@ function requestExistingQueueData(queueKeyId) { }).catch(function(error){ console.log(error); $(".loadingState").hide(); - showErrorMessage(error); + ui.showErrorMessage(error); }); } @@ -168,12 +174,12 @@ function saveNewQueue() { var business = { "key": { "id": newQueue.businessKeyId } }; window.location.href = intelliqApi.getUrls().forBusiness(business).manage(); }, function(error) { - showErrorMessage(error); + ui.showErrorMessage(error); }); } function updateExistingQueue(queue) { - var googleIdToken = authenticator.getInstance().getUserIdToken() + var googleIdToken = authenticator.getGoogleUserIdToken(); return intelliqApi .editQueue(queue) .setGoogleIdToken(googleIdToken) @@ -181,7 +187,7 @@ function updateExistingQueue(queue) { } function addNewQueue(queue) { - var googleIdToken = authenticator.getInstance().getUserIdToken() + var googleIdToken = authenticator.getGoogleUserIdToken(); return intelliqApi .addQueue(queue) .setGoogleIdToken(googleIdToken) @@ -236,7 +242,7 @@ function initializeMap() { updateQueueLocationMarker(location.latitude, location.longitude); }).catch(function(error){ console.log(error); - showErrorMessage(error); + ui.showErrorMessage(error); }); }); @@ -248,7 +254,7 @@ function initializeMap() { updateFormWithGoogleAddressData(results); }).catch(function(error){ console.log(error); - showErrorMessage(error); + ui.showErrorMessage(error); }); }); @@ -333,7 +339,7 @@ function updateQueueLocationMarker(latitude, longitude) { // and reverse-geocodes the address function updateFormWithDeviceLocation() { Materialize.toast(getString("locatingDevice"), 3000); - getDeviceLocation().then(function(location){ + requestDeviceLocation().then(function(location){ deviceLocation = location; var latitude = location.coords.latitude; @@ -354,11 +360,11 @@ function updateFormWithDeviceLocation() { updateFormWithGoogleAddressData(results); }).catch(function(error){ console.log(error); - showErrorMessage(error); + ui.showErrorMessage(error); }); }).catch(function(error){ console.log(error); - showErrorMessage(error); + ui.showErrorMessage(error); }); } diff --git a/IntelliQ/backend/src/main/webapp/static/js/general.js b/IntelliQ/backend/src/main/webapp/static/js/general.js index 107a8b7..f1ab861 100644 --- a/IntelliQ/backend/src/main/webapp/static/js/general.js +++ b/IntelliQ/backend/src/main/webapp/static/js/general.js @@ -1,7 +1,7 @@ var baseUrl = location.protocol + "//" + location.hostname + (location.port && ":" + location.port) + ""; function whenAvailable(name, callback) { - var interval = 50; // ms + var interval = 50; window.setTimeout(function() { if (window[name]) { callback(window[name]); @@ -11,24 +11,6 @@ function whenAvailable(name, callback) { }, interval); } -function getDeviceLocation() { - var promise = new Promise(function(resolve, reject) { - try { - navigator.geolocation.getCurrentPosition(function(position) { - console.log(position); - //var lat = position.coords.latitude; - //var long = position.coords.longitude; - resolve(position) - }, function() { - reject("Location request blocked"); - }); - } catch(ex) { - reject(ex); - } - }); - return promise; -} - function getDecodedUrlParam(sParam) { var value = getUrlParam(sParam); if (value != null) { @@ -72,12 +54,17 @@ function getHostNameFromUrl(url) { return l.hostname; } -function getString(key, value) { +function getString(key) { var string = res[key]; if (string == null) { string = "Resource Error"; } - string = string.replace("[VALUE]", value); + + if (arguments.length > 0) { + var values = Array.prototype.slice.call(arguments, 1); + string = String.prototype.format.apply(string, values); + } + return string; } @@ -103,6 +90,9 @@ function removeClassName(div, newClass) { } function setCookie(cname, cvalue, exdays) { + if (typeof exdays === 'undefined') { + exdays = 7; + } var d = new Date(); d.setTime(d.getTime() + (exdays*24*60*60*1000)); var expires = "expires=" + d.toUTCString(); @@ -120,4 +110,30 @@ function getCookie(cname) { if (c.indexOf(name) == 0) return c.substring(name.length, c.length); } return ""; +} + +function deleteCookie(cname) { + setCookie(cname, "", -1); +} + +function requestDeviceLocation() { + var promise = new Promise(function(resolve, reject) { + try { + navigator.geolocation.getCurrentPosition(function(position) { + console.log(position); + //var lat = position.coords.latitude; + //var long = position.coords.longitude; + resolve(position) + }, function(error) { + reject("Location request blocked"); + }); + } catch(ex) { + reject(ex); + } + }); + return promise; +} + +function navigateTo(url) { + window.location.href = url; } \ No newline at end of file diff --git a/IntelliQ/backend/src/main/webapp/static/js/init.js b/IntelliQ/backend/src/main/webapp/static/js/init.js index d4bda4c..a234fc8 100644 --- a/IntelliQ/backend/src/main/webapp/static/js/init.js +++ b/IntelliQ/backend/src/main/webapp/static/js/init.js @@ -1,101 +1,114 @@ (function($){ $(function(){ - - $('.button-collapse').sideNav(); + $('.button-collapse').sideNav({ + menuWidth: 240, + edge: 'left', + closeOnClick: true + }); $('.parallax').parallax(); $('.tooltipped').tooltip({delay: 50}); $('.modal-trigger').leanModal(); $('select').material_select(); setupNavigation(); - updateSignInForm(); - + setupAuthenticationButtons(); }); // end of document ready })(jQuery); // end of jQuery name space function setupNavigation() { - $("#nav-account-button").click(onAccountButtonClicked); - $("#nav-mobile-account-button").click(onAccountButtonClicked); -} - -function onAccountButtonClicked() { - try { - var auth = authenticator.getInstance(); - if (auth.isSignedIn()) { - auth.signOutFromGoogle(); - } else { - auth.signInWithGoogle(); - } - } catch (ex) { - showErrorMessage(getString("somethingWentWrongMessage"), ex); - console.log("Unable to toggle sign in status:") - console.log(ex); - } -} - -function openSignInForm() { - $("#modal-signin").openModal(); -} - -function closeSignInForm() { - $("#modal-signin").closeModal(); + $("#nav-account-button").click(onAccountButtonClicked); + $("#nav-mobile-account-button").click(onAccountButtonClicked); } -function updateSignInForm() { - //TODO: toggle buttons +function setupAuthenticationButtons() { + $("#signInWithGoogleButton").click(function() { + authenticator.signInToGoogle().then(function() { + ui.hideSignInForm(); + }).catch(function(error) { + ui.showErrorMessage(error); + }); + }); + + $("#signOutButton").click(function() { + authenticator.signOutFromGoogle().then(function() { + ui.hideSignOutForm(); + }).catch(function(error) { + ui.showErrorMessage(error); + }); + }); + + $("#switchAccountButton").click(function() { + authenticator.disconnectFromGoogle().then(function() { + ui.hideSignOutForm(); + }).catch(function(error) { + ui.showErrorMessage(error); + }); + }); } -function showErrorMessage(message) { - $("#modal-error-message").text(message); - $("#modal-error").openModal(); -} - -function hideErrorMessage() { - $("#modal-error").closeModal(); +function onAccountButtonClicked() { + authenticator.requestGoogleSignInStatus().then(function(isSignedIn) { + if (isSignedIn) { + ui.showSignOutForm(); + } else { + ui.showSignInForm(); + } + }).catch(function(error) { + ui.showErrorMessage(error); + }); } function openContactForm() { - $("#modal-contact").openModal(); + $("#modal-contact").openModal(); } function closeContactForm() { - $("#modal-contact").closeModal(); + $("#modal-contact").closeModal(); } function submitContactForm() { - try { - var email = document.getElementById("contact_email").value; - var name = document.getElementById("contact_first_name").value + " " + document.getElementById("contact_last_name").value; - var message = document.getElementById("contact_message").value; - - if (name.length < 5 || email.length < 5 || message.length < 5) { - throw "Invalid fields"; - } - - var url = "http://steppschuh.net/php/mail.php"; - var params = "?to=" + "mail@intelliq.me"; - params = params + "&from_mail=" + email; - params = params + "&from_name=" + encodeURIComponent(name); - params = params + "&reply_to=" + email; - params = params + "&subject=" + encodeURIComponent("Kontakt Anfrage"); - params = params + "&message=" + encodeURIComponent(message); - - var http = new XMLHttpRequest(); - http.open("GET", url+params, true); - - http.setRequestHeader("Content-type", "application/x-www-form-urlencoded"); + try { + var email = document.getElementById("contact_email").value; + var name = document.getElementById("contact_first_name").value + " " + document.getElementById("contact_last_name").value; + var message = document.getElementById("contact_message").value; + + if (name.length < 5 || email.length < 5 || message.length < 5) { + throw "Invalid fields"; + } + + var url = "http://steppschuh.net/php/mail.php"; + var params = "?to=" + "mail@intelliq.me"; + params = params + "&from_mail=" + email; + params = params + "&from_name=" + encodeURIComponent(name); + params = params + "&reply_to=" + email; + params = params + "&subject=" + encodeURIComponent("Kontakt Anfrage"); + params = params + "&message=" + encodeURIComponent(message); + + var http = new XMLHttpRequest(); + http.open("GET", url+params, true); + + http.setRequestHeader("Content-type", "application/x-www-form-urlencoded"); + + http.onreadystatechange = function() { + if (http.readyState == 4 && http.status == 200) { + Materialize.toast("Kontakt Anfrage wurde gesendet", 4000) + console.log(http.responseText); + } + } + http.send(null); + } catch(ex) { + Materialize.toast("Leider lief etwas schief", 4000) + var win = window.open("mailto:mailintelliq.me?body=" + document.getElementById("contact_message").value, '_blank'); + win.focus(); + } + return false; +} - http.onreadystatechange = function() { - if (http.readyState == 4 && http.status == 200) { - Materialize.toast("Kontakt Anfrage wurde gesendet", 4000) - console.log(http.responseText); - } - } - http.send(null); - } catch(ex) { - Materialize.toast("Leider lief etwas schief", 4000) - var win = window.open("mailto:mailintelliq.me?body=" + document.getElementById("contact_message").value, '_blank'); - win.focus(); - } - return false; -} \ No newline at end of file +String.prototype.format = function () { + var args = arguments; + return this.replace(/\{\{|\}\}|\{(\d+)\}/g, function (m, n) { + if (m == "{{") { return "{"; } + if (m == "}}") { return "}"; } + return args[n]; + }); +}; \ No newline at end of file diff --git a/IntelliQ/backend/src/main/webapp/static/js/manage.js b/IntelliQ/backend/src/main/webapp/static/js/manage.js index 7a54275..88e1c79 100644 --- a/IntelliQ/backend/src/main/webapp/static/js/manage.js +++ b/IntelliQ/backend/src/main/webapp/static/js/manage.js @@ -5,153 +5,73 @@ })(jQuery); /* - Makes sure that we have a valid token to send authorized - API requests. Invokes a sign-in if needed. + Initializes the authentication */ function initAuthentication() { - whenAvailable("authenticator", function() { - authenticator.getInstance(function(instance) { - // authenticator initialized - }, signInStatusChanged); - }) -} - -/* - Callback for the authentication flow -*/ -function signInStatusChanged(isSignedIn) { - if (isSignedIn) { - var googleUser = authenticator.getInstance().getCurrentGoogleUser(); - var profile = googleUser.getBasicProfile(); - //Materialize.toast(getString("signedInAs", profile.getName()), 5000); - closeSignInForm(); + var statusChangeListener = { + onGoogleSignIn: function() { + ui.hideSignInForm(); + authenticator.requestIntelliqUserFromGoogleIdToken().then(function(user) { + + }).catch(function(error) { + console.log("Unable to get IntelliQ user from Google ID token: " + error); + ui.showErrorMessage(error); + }); + }, - requestUserFromGoogleIdToken().then(function(user) { + onGoogleSignOut: function() { + ui.showSignInForm(); + }, + + onUserAvailable: function(user) { console.log(user); - if (typeof onUserReady !== "undefined") { - onUserReady(user); - } - }).catch(function(error) { - console.log("Unable to get IntelliQ user from Google ID token: " + error); - showErrorMessage(error); - }); - } else { - //Materialize.toast(getString("signedOut"), 5000); - openSignInForm(); - } -} - -/* - Tries to get an (IntelliQ-) user object from the currently - active Google ID token -*/ -function requestUserFromGoogleIdToken() { - var promise = new Promise(function(resolve, reject) { - try { - var googleIdToken = authenticator.getInstance().getUserIdToken(); - if (googleIdToken == null) { - throw "Token is null"; - } - - var signInRequest = intelliqApi.signInUser().setGoogleIdToken(googleIdToken); - signInRequest.send().then(function(data){ - var user = intelliqApi.getUsersFromResponse(data)[0]; - if (user == null) { - reject("Returned user is null"); - } - resolve(user); - }).catch(function(error){ - reject(error); - }); - } catch (ex) { - reject(ex); } + }; + authenticator.registerStatusChangeListener(statusChangeListener); + + authenticator.requestGoogleSignInStatus().then(function(isSignedIn) { + if (isSignedIn) { + statusChangeListener.onGoogleSignIn(); + } else { + statusChangeListener.onGoogleSignOut(); + } + }).catch(function(error) { + ui.showErrorMessage(error); }); - return promise; } function renderBusinesses(entries, container) { var generateCardWrapper = function() { - var className = intelliqUi.generateColumnClassName(12, 6, 6); - return intelliqUi.generateCardWrapper(className); + var className = ui.generateColumnClassName(12, 6, 6); + return ui.generateCardWrapper(className); } var options = {}; - options.itemGenerator = intelliqUi.generateBusinessCard; + options.itemGenerator = ui.generateManageBusinessCard; options.itemWrapperGenerator = generateCardWrapper; - renderEntries(entries, container, options); + ui.renderEntries(entries, container, options); } function renderQueues(entries, container) { var generateCardWrapper = function() { - var className = intelliqUi.generateColumnClassName(12, 6, 6); - return intelliqUi.generateCardWrapper(className); + var className = ui.generateColumnClassName(12, 6, 6); + return ui.generateCardWrapper(className); } var options = {}; - options.itemGenerator = intelliqUi.generateQueueCard; + options.itemGenerator = ui.generateManageQueueCard; options.itemWrapperGenerator = generateCardWrapper; - renderEntries(entries, container, options); + ui.renderEntries(entries, container, options); } function renderQueueItems(entries, container) { var wrapperGenerator = function() { - var wrapper = intelliqUi.generateCollection(); + var wrapper = ui.generateCollection(); return wrapper; } var options = {}; - options.itemGenerator = intelliqUi.generateQueueItemCollectionItem; + options.itemGenerator = ui.generateQueueItemCollectionItem; options.wrapperGenerator = wrapperGenerator; - renderEntries(entries, container, options); -} - -function renderEntries(entries, container, options) { - if (container == null || container.length < 1) { - return; - } - container.empty(); - var wrapper = container; - - if (options.wrapperGenerator != null) { - wrapper = options.wrapperGenerator(); - } - - for (var i = 0; i < entries.length; i++) { - try { - // create a item - var item = options.itemGenerator(entries[i]); - - if (options.itemWrapperGenerator != null) { - // create a div that wraps the item - var itemWrapper = options.itemWrapperGenerator(); - - // render the item in the item wrapper - item.renderIn(itemWrapper); - - // add the item wrapper to the wrapper - wrapper.append(itemWrapper); - } else { - // add the item to the wrapper - wrapper.append(item); - } - } catch (ex) { - console.log("Unable to render entry:"); - console.log(ex); - } - } - - if (options.wrapperGenerator != null) { - container.append(wrapper); - } else { - container = wrapper; - } - - if (entries.length < 1) { - container.hide(); - container.parent().find(".emptyState").removeClass("hide"); - } else { - container.show(); - container.parent().find(".emptyState").addClass("hide"); - } -} + ui.renderEntries(entries, container, options); +} \ No newline at end of file diff --git a/IntelliQ/backend/src/main/webapp/static/js/manage_old.js b/IntelliQ/backend/src/main/webapp/static/js/manage_old.js deleted file mode 100644 index 0879f69..0000000 --- a/IntelliQ/backend/src/main/webapp/static/js/manage_old.js +++ /dev/null @@ -1,820 +0,0 @@ -var CLIENT_ID_WEB = "1008259459239-t1huos5n6bhkin3is2jlqgkjv9h7mheh.apps.googleusercontent.com"; - -var STATUS_ALL = -1; -var STATUS_WAITING = 0; -var STATUS_CANCELED = 1; -var STATUS_CALLED = 2; -var STATUS_DONE = 3; - -var MINIMUM_REFRESH_INTERVAL = 3000; -var REFRESH_INTERVAL = 15000; -var lastRefresh = -1; - -var business = null; -var queueItems = null; - -function getCurrentQueueKeyId() { - return getUrlParamOrCookie("queueKeyId", true); -} - -function getCurrentBusinessKeyId() { - return getUrlParamOrCookie("businessKeyId", true); -} - -/* - * Methods for DOM manipulation - */ -function renderQueueItems(items) { - console.log("Rendering queue items:"); - console.log(items); - - - var nowCalledList = document.getElementById("nowCalledList"); - var currentlyWaitingList = document.getElementById("currentlyWaitingList"); - var recentlyProcessedList = document.getElementById("recentlyProcessedList"); - var nowCalledListEmpty = document.getElementById("nowCalledListEmpty"); - var currentlyWaitingListEmpty = document.getElementById("currentlyWaitingListEmpty"); - var recentlyProcessedListEmpty = document.getElementById("recentlyProcessedListEmpty"); - var markAllAsDoneButton = document.getElementById("markAllAsDoneButton"); - var clearAllProcessedButton = document.getElementById("clearAllProcessedButton"); - var callNextCustomerButton = document.getElementById("callNextCustomerButton"); - - nowCalledList.innerHTML = ""; - recentlyProcessedList.innerHTML = ""; - currentlyWaitingList.innerHTML = ""; - - var calledCount = 0; - var waitingCount = 0; - var processedCount = 0; - - try { - queueItems = items; - queueItems.sort(compareLastStatusChangeTimestamp); - - for (var i = 0; i < queueItems.length; i++) { - var item = queueItems[i]; - switch (item.status) { - case STATUS_WAITING: { - currentlyWaitingList.appendChild(generateListItemForQueueItem(item)); - waitingCount++; - break; - } - case STATUS_CALLED: { - nowCalledList.appendChild(generateListItemForQueueItem(item)); - calledCount++; - break; - } - default: { - if (recentlyProcessedList.children.length > 0) { - recentlyProcessedList.insertBefore(generateListItemForQueueItem(item), recentlyProcessedList.children[0]); - } else { - recentlyProcessedList.appendChild(generateListItemForQueueItem(item)); - } - processedCount++; - } - } - } - } catch(ex) { - console.log(ex); - } - - if (calledCount > 0) { - nowCalledList.style.display = "block"; - nowCalledListEmpty.style.display = "none"; - removeClassName(markAllAsDoneButton, "disabled"); - } else { - nowCalledList.style.display = "none"; - nowCalledListEmpty.style.display = "block"; - addClassName(markAllAsDoneButton, "disabled"); - } - - if (waitingCount > 0) { - currentlyWaitingList.style.display = "block"; - currentlyWaitingListEmpty.style.display = "none"; - removeClassName(callNextCustomerButton, "disabled"); - } else { - currentlyWaitingList.style.display = "none"; - currentlyWaitingListEmpty.style.display = "block"; - addClassName(callNextCustomerButton, "disabled"); - } - - if (processedCount > 0) { - recentlyProcessedList.style.display = "block"; - recentlyProcessedListEmpty.style.display = "none"; - removeClassName(clearAllProcessedButton, "disabled"); - } else { - recentlyProcessedList.style.display = "none"; - recentlyProcessedListEmpty.style.display = "block"; - addClassName(clearAllProcessedButton, "disabled"); - } -} - -function renderBusiness(entry) { - console.log("Rendering business:"); - console.log(entry); - - business = entry; - - var businessCard = document.getElementById("businessCard"); - var businessCardImage = document.getElementById("businessCardImage"); - var businessCardTitle = document.getElementById("businessCardTitle"); - var businessCardStatus = document.getElementById("businessCardStatus"); - var businessCardEditButton = document.getElementById("businessCardEditButton"); - var businessCardRevealTitle = document.getElementById("businessCardRevealTitle"); - - businessCard.style.display = "block"; - - if (entry.logoImageKeyId > 0) { - businessCardImage.style.backgroundImage = "url(" + getResizedImageUrlById(entry.logoImageKeyId, 700) + ")"; - } else { - businessCardImage.style.backgroundImage = "url(" + baseUrl + "/static/images/no_logo.png)"; - } - - businessCardEditButton.href = getEditBusinessUrl(entry.key.id); - - var cardMoreButton = document.createElement("i"); - cardMoreButton.className = "material-icons right"; - cardMoreButton.innerHTML = "more_vert"; - - businessCardTitle.innerHTML = entry.name + htmlElementToString(cardMoreButton); - - businessCardStatus.innerHTML = ""; - - var cardCloseButton = document.createElement("i"); - cardCloseButton.className = "material-icons right"; - cardCloseButton.innerHTML = "close"; - - businessCardRevealTitle.innerHTML = entry.name + htmlElementToString(cardCloseButton); - - - renderQueues(entry.queues); - -} - -function renderQueues(entries) { - console.log("Rendering queues:"); - console.log(entries); - - var queuesContainer = document.getElementById("queuesContainer"); - var queuesContainerEmpty = document.getElementById("queuesContainerEmpty"); - - queuesContainer.innerHTML = ""; - - try { - for (var i = 0; i < entries.length; i++) { - var queue = entries[i]; - queuesContainer.appendChild(generateCardForQueue(queue)); - } - } catch(ex) { - console.log(ex); - } - - if (entries && entries.length > 0) { - queuesContainer.style.display = "block"; - queuesContainerEmpty.style.display = "none"; - } else { - queuesContainer.style.display = "none"; - queuesContainerEmpty.style.display = "block"; - } - -} - -function generateListItemForQueueItem(item) { - /* - *
  • smartphone Markus Petrykowski
    - * waiting since 12 minutes - ticket #29 clear - *
  • - */ - try { - var listItem = document.createElement("li"); - listItem.className = "collection-item avatar"; - listItem.queueItemKeyId = item.key.id; - - var br = document.createElement("br"); - - var listItemIcon = document.createElement("i"); - listItemIcon.className = "material-icons circle"; - if (item.status == STATUS_WAITING) { - if (item.usingApp) { - listItemIcon.innerHTML = "smartphone"; - } else { - listItemIcon.innerHTML = "dvr"; - } - } else if (item.status == STATUS_CALLED) { - listItemIcon.innerHTML = "announcement"; - } else if (item.status == STATUS_CANCELED) { - listItemIcon.innerHTML = "clear"; - } else if (item.status == STATUS_DONE) { - listItemIcon.innerHTML = "done"; - } - - var listItemTitle = document.createElement("span"); - listItemTitle.className = "title"; - listItemTitle.innerHTML = item.name; - - var listItemStatus = document.createElement("span"); - listItemStatus.className = "status"; - listItemStatus.innerHTML = getQueueItemStatusString(item); - - var listItemTicket = document.createElement("span"); - listItemTicket.className = "ticket"; - listItemTicket.innerHTML = "ticket #" + item.ticketNumber; - - var listItemAction = document.createElement("a"); - listItemAction.className = "secondary-content grey-text text-lighten-1"; - listItemAction.href = "javascript:cancelQueueItem(" + item.key.id + ")"; - - var listItemActionIcon = document.createElement("i"); - listItemActionIcon.className = "material-icons"; - if (item.status == STATUS_WAITING) { - listItemActionIcon.innerHTML = "clear"; - listItemAction.href = "javascript:cancelQueueItem(" + item.key.id + ")"; - } else if (item.status == STATUS_CALLED) { - listItemActionIcon.innerHTML = "done"; - listItemAction.href = "javascript:markQueueItemAsDone(" + item.key.id + ")"; - } else { - listItemActionIcon.innerHTML = "delete"; - listItemAction.href = "javascript:deleteQueueItem(" + item.key.id + ")"; - } - - listItemAction.appendChild(listItemActionIcon); - - listItem.appendChild(listItemIcon); - listItem.appendChild(listItemTitle); - listItem.appendChild(br); - listItem.appendChild(listItemStatus); - listItem.appendChild(listItemTicket); - listItem.appendChild(listItemAction); - - return listItem; - } catch(ex) { - console.log(ex); - return null; - } -} - -function generateCardForQueue(item) { - /* -
    -
    -
    - -
    -
    - Card Titlemore_vert -
    - 12 customers in queue -
    - Manage - Edit -
    -
    -
    - Card Titleclose -

    Here is some more information about this product that is only revealed once clicked on.

    - group Manage Customers -
    group Edit Queue - -
    -
    -
    - */ - try { - var br = document.createElement("br"); - - var cardCol = document.createElement("div"); - cardCol.className = "col s12 m6 l4"; - cardCol.queueKeyId = item.key.id; - - var card = document.createElement("div"); - card.className = "card medium"; - - var cardImageContainer = document.createElement("div"); - cardImageContainer.className = "card-image waves-effect waves-block waves-light grey lighten-3"; - - var cardImage = document.createElement("div"); - cardImage.className = "card-image-div activator"; - - if (item.photoImageKeyId > 0) { - cardImage.style.backgroundImage = "url(" + getResizedImageUrlById(item.photoImageKeyId, 700) + ")"; - } else { - cardImage.style.backgroundImage = "url(" + baseUrl + "/static/images/no_photo.jpg)"; - } - - var cardContent = document.createElement("div"); - cardContent.className = "card-content"; - - var cardMoreButton = document.createElement("i"); - cardMoreButton.className = "material-icons right"; - cardMoreButton.innerHTML = "more_vert"; - - var cardTitle = document.createElement("span"); - cardTitle.className = "card-title activator grey-text text-darken-4"; - cardTitle.innerHTML = item.name + htmlElementToString(cardMoreButton); - - var cardStatus = document.createElement("span"); - cardStatus.className = "status"; - cardStatus.innerHTML = getProtoString("waiting_customers").replace("[VALUE]", item.waitingPeople); - - var cardActions = document.createElement("div"); - cardActions.className = "card-action"; - - var cardAction1 = document.createElement("a"); - cardAction1.innerHTML = "Manage"; - cardAction1.href = getManageQueueUrl(item.key.id); - - var cardAction2 = document.createElement("a"); - cardAction2.innerHTML = "Edit"; - cardAction2.href = getEditQueueUrl(item.key.id); - - var cardReveal = document.createElement("div"); - cardReveal.className = "card-reveal"; - - var cardCloseButton = document.createElement("i"); - cardCloseButton.className = "material-icons right"; - cardCloseButton.innerHTML = "close"; - - var cardRevealTitle = document.createElement("span"); - cardRevealTitle.className = "card-title grey-text text-darken-4"; - cardRevealTitle.innerHTML = item.name + htmlElementToString(cardCloseButton); - - var cardRevealDescription = document.createElement("p"); - cardRevealDescription.innerHTML = "Lorem Ipsum"; - - var button1Icon = document.createElement("i"); - button1Icon.className = "material-icons right"; - button1Icon.innerHTML = "delete"; - - var button1 = document.createElement("a"); - button1.innerHTML = htmlElementToString(button1Icon) + "Delete queue"; - button1.className = "btn-large waves-effect waves-light primary-color"; - button1.href = "#"; - button1.style.width = "100%"; - - - cardImageContainer.appendChild(cardImage); - - cardActions.appendChild(cardAction1); - cardActions.appendChild(cardAction2); - cardContent.appendChild(cardTitle); - cardContent.appendChild(br.cloneNode(true)); - cardContent.appendChild(cardStatus); - cardContent.appendChild(cardActions); - - cardReveal.appendChild(cardRevealTitle); - cardReveal.appendChild(cardRevealDescription); - cardReveal.appendChild(br.cloneNode(true)); - cardReveal.appendChild(button1); - - card.appendChild(cardImageContainer); - card.appendChild(cardContent); - card.appendChild(cardReveal); - - cardCol.appendChild(card); - - return cardCol; - } catch(ex) { - console.log(ex); - return null; - } -} - -/* - * Event handling - */ -function callNextCustomer(queueKeyId) { - if (queueKeyId != null) { - //TODO: don't use DOM for this - var currentlyWaitingList = document.getElementById("currentlyWaitingList"); - if (currentlyWaitingList.children.length > 0) { - var queueItemKeyId = currentlyWaitingList.children[0].queueItemKeyId; - changeQueueItemStatus(queueItemKeyId, STATUS_CALLED); - } else { - Materialize.toast(getProtoString("something_went_wrong"), 5000); - } - } else { - console.log("Invalid queueKeyId specified"); - } -} - -function addCustomerDialogSubmitted() { - var queueKeyId = getCurrentQueueKeyId(); - var name = document.getElementById("add_customer_first_name").value + " " + document.getElementById("add_customer_last_name").value; - var showName = document.getElementById("add_customer_show_name").checked; - - if (queueKeyId != null) { - addQueueItem(queueKeyId, name, showName) - } else { - console.log("Invalid queueKeyId specified"); - } -} - -/* - * API requests helper - */ -function getApiRequestUrl() { - var requestUrl; - if (baseUrl.indexOf("localhost") > -1) { - requestUrl = baseUrl + "/api/"; - } else { - requestUrl = "http://intelliq.me/api/"; - } - return requestUrl; -} - -function getEditQueueUrl(queueKeyId) { - return baseUrl + "/edit/queue/?queueKeyId=" + queueKeyId; -} - -function getEditBusinessUrl(businessKeyId) { - return baseUrl + "/edit/business/?businessKeyId=" + businessKeyId; -} - -function getManageQueueUrl(queueKeyId) { - return baseUrl + "/manage/queue/?queueKeyId=" + queueKeyId; -} - -function getManageBusinessUrl(businessKeyId) { - return baseUrl + "/manage/business/?businessKeyId=" + businessKeyId; -} - -function sendApiRequest(endpoint, params, callback) { - // build request url - var requestUrl = getApiRequestUrl() + endpoint; - if (params) { - requestUrl += "?"; - for (var i = 0; i < params.length; i++) { - requestUrl += params[i][0] + "=" + encodeURIComponent(params[i][1]) + "&"; - } - requestUrl = requestUrl.substring(0, requestUrl.length - 1); - } - - // request JSON - console.log("Requesting: " + requestUrl); - $.ajax({ - url : requestUrl, - type : 'GET', - data : {}, - dataType : 'json', - success : function(data) { - callback(data); - }, - error : function(err) { - console.log(err) - callback(null); - }, - beforeSend : function(xhr) { - // xhr.setRequestHeader("key", "value"); - } - }); -} - -/* - * API requests for Queue Item Management - */ - -function getQueue(queueKeyId) { - params = []; - params.push(["queueKeyId", queueKeyId]); - - var callback = function (data) { - var statusString = ""; - if (data) { - console.log(data) - if (data.statusCode != 200) { - console.log(data.statusMessage); - } else { - var editQueueButton = document.getElementById("editQueueButton") - editQueueButton.href = getEditQueueUrl(data.content.key.id); - - var manageBusinessButton = document.getElementById("manageBusinessButton") - manageBusinessButton.href = getManageBusinessUrl(data.content.businessKeyId); - } - } - } - - sendApiRequest("queue/get/", params, callback); -} - -function requestQueueItems(force) { - // skip if queue items have been updated recently - if (!force && Date.now() < lastRefresh + MINIMUM_REFRESH_INTERVAL) { - return; - } else { - lastRefresh = Date.now(); - } - - var queueKeyId = getCurrentQueueKeyId(); - if (queueKeyId != null) { - requestQueueItemsByKeyId(queueKeyId); - } else { - console.log("Invalid queueKeyId specified"); - } -} - -function requestQueueItemsByKeyId(queueKeyId) { - params = []; - params.push(["queueKeyId", queueKeyId]); - - var callback = function (data) { - var statusString = ""; - if (data) { - console.log(data) - if (data.statusCode != 200) { - console.log(data.statusMessage); - } else { - renderQueueItems(data.content); - } - } - } - - sendApiRequest("queue/items/", params, callback); -} - -function addQueueItem(queueKeyId, name, showName) { - params = []; - params.push(["queueKeyId", queueKeyId]); - params.push(["name", name]); - params.push(["showName", showName ? "true" : "false"]); - params.push(["usingApp", "false"]); - - var callback = function (data) { - var statusString = ""; - if (data) { - var ticketNumber = data.content.ticketNumber; - statusString = getProtoString("created_ticket"); - statusString = statusString.replace("[VALUE]", ticketNumber); - console.log(data) - } else { - statusString = getProtoString("something_went_wrong"); - } - Materialize.toast(statusString, 5000); - requestQueueItems(); - } - - sendApiRequest("item/add/", params, callback); -} - -function addDummyQueueItems(queueKeyId, count) { - params = []; - params.push(["queueKeyId", queueKeyId]); - params.push(["count", count]); - - var callback = function (data) { - var statusString = ""; - if (data) { - console.log(data) - if (data.statusCode != 200) { - statusString = data.statusMessage; - } else { - statusString = getProtoString("status_changed"); - } - } else { - statusString = getProtoString("something_went_wrong"); - } - Materialize.toast(statusString, 5000); - requestQueueItems(true); - } - - sendApiRequest("queue/populate/", params, callback); -} - -function cancelQueueItem(queueItemKeyId) { - changeQueueItemStatus(queueItemKeyId, STATUS_CANCELED); -} - -function callQueueItem(queueItemKeyId) { - changeQueueItemStatus(queueItemKeyId, STATUS_CALLED); -} - -function markQueueItemAsDone(queueItemKeyId) { - changeQueueItemStatus(queueItemKeyId, STATUS_DONE); -} - -function markAllQueueItemsAsDone(queueKeyId) { - params = []; - params.push(["queueKeyId", queueKeyId]); - - var callback = function (data) { - var statusString = ""; - if (data) { - console.log(data) - if (data.statusCode != 200) { - statusString = data.statusMessage; - } else { - statusString = getProtoString("status_changed"); - } - } else { - statusString = getProtoString("something_went_wrong"); - } - Materialize.toast(statusString, 5000); - requestQueueItems(true); - } - - sendApiRequest("queue/done/", params, callback); -} - - -function changeQueueItemStatus(queueItemKeyId, status) { - params = []; - params.push(["queueItemKeyId", queueItemKeyId]); - params.push(["status", status]); - - var callback = function (data) { - var statusString = ""; - if (data) { - console.log(data) - if (data.statusCode != 200) { - statusString = data.statusMessage; - } else { - var status = data.content.status; - statusString = getProtoString("status_changed"); - } - } else { - statusString = getProtoString("something_went_wrong"); - } - Materialize.toast(statusString, 5000); - requestQueueItems(true); - } - - sendApiRequest("item/status/", params, callback); -} - -function deleteQueueItem(queueItemKeyId) { - params = []; - params.push(["queueItemKeyId", queueItemKeyId]); - - var callback = function (data) { - var statusString = ""; - if (data) { - console.log(data) - if (data.statusCode != 200) { - statusString = data.statusMessage; - } else { - statusString = getProtoString("status_changed"); - } - } else { - statusString = getProtoString("something_went_wrong"); - } - Materialize.toast(statusString, 5000); - requestQueueItems(true); - } - - sendApiRequest("item/delete/", params, callback); -} - -function deleteAllQueueItems(queueKeyId) { - deleteQueueItems(queueKeyId, STATUS_ALL, true); -} - -function deleteProcessedQueueItems(queueKeyId) { - deleteQueueItems(queueKeyId, STATUS_ALL, false); -} - -function deleteQueueItems(queueKeyId, status, clearWaiting) { - params = []; - params.push(["queueKeyId", queueKeyId]); - params.push(["status", status]); - params.push(["clearWaiting", clearWaiting ? "true" : "false"]); - - var callback = function (data) { - var statusString = ""; - if (data) { - console.log(data) - if (data.statusCode != 200) { - statusString = data.statusMessage; - } else { - statusString = getProtoString("status_changed"); - } - } else { - statusString = getProtoString("something_went_wrong"); - } - Materialize.toast(statusString, 5000); - requestQueueItems(true); - } - - sendApiRequest("queue/clear/", params, callback); -} - -/* - * API requests for Business Management - */ -function getBusiness(businessKeyId) { - params = []; - params.push(["businessKeyId", businessKeyId]); - params.push(["includeQueues", "true"]); - - var callback = function (data) { - var statusString = ""; - if (data) { - console.log(data) - if (data.statusCode != 200) { - console.log(data.statusMessage); - } else { - renderBusiness(data.content); - } - } - } - - sendApiRequest("business/get/", params, callback); -} - -/* - * Helper functions - */ -function getQueueItemStatusString(item) { - var statusString = "unknown"; - switch (item.status) { - case STATUS_WAITING: { - statusString = getProtoString("status_waiting_since"); - var delay = getNumberOfMinutesSince(item.entryTimestamp); - statusString = statusString.replace("[VALUE]", delay); - if (delay == 1) { - statusString = statusString + getProtoString("unit_minute"); - } else { - statusString = statusString + getProtoString("unit_minutes"); - } - break; - } - case STATUS_CANCELED: { - statusString = getProtoString("status_canceled"); - break; - } - case STATUS_CALLED: { - statusString = getProtoString("status_called_since"); - var delay = getNumberOfMinutesSince(item.lastStatusChangeTimestamp); - statusString = statusString.replace("[VALUE]", delay); - if (delay == 1) { - statusString = statusString + getProtoString("unit_minute"); - } else { - statusString = statusString + getProtoString("unit_minutes"); - } - break; - } - case STATUS_DONE: { - statusString = getProtoString("status_done"); - break; - } - } - return statusString; -} - -function compareEntryTimestamp(a, b) { - if (a.entryTimestamp < b.entryTimestamp) - return -1; - if (a.entryTimestamp > b.entryTimestamp) - return 1; - return 0; -} - -function compareLastStatusChangeTimestamp(a, b) { - if (a.lastStatusChangeTimestamp < b.lastStatusChangeTimestamp) - return -1; - if (a.lastStatusChangeTimestamp > b.lastStatusChangeTimestamp) - return 1; - return 0; -} - -function getImageUrlById(imageKeyId) { - var size = "original"; - var defaultColumn = document.getElementsByClassName("container")[0]; - if (defaultColumn !== undefined) { - size = Math.max(defaultColumn.offsetWidth, 500); - } - return getResizedImageUrlById(imageKeyId, size); -} - -function getResizedImageUrlById(imageKeyId, size) { - return baseUrl + "/image/" + imageKeyId + "/" + size + ".jpg"; -} - -function getNumberOfMinutesSince(timestamp) { - var date = new Date(timestamp); - var seconds = Math.floor((new Date() - date) / 1000); - return Math.floor(seconds / 60); -} - - -function getProtoString(key) { - var proto = document.getElementById("proto_" + key); - if (proto) { - return proto.innerHTML; - } - return "undefined string"; -} - -function getProtoStringWithValue(key, value) { - return getProtoString(key).replace("[VALUE]", value); -} - -function htmlElementToString(element) { - var tmp = document.createElement("div"); - tmp.innerHTML = ""; - tmp.appendChild(element); - return tmp.innerHTML; -} \ No newline at end of file diff --git a/IntelliQ/backend/src/main/webapp/static/js/manage_queue.js b/IntelliQ/backend/src/main/webapp/static/js/manage_queue.js index 048b069..37988f4 100644 --- a/IntelliQ/backend/src/main/webapp/static/js/manage_queue.js +++ b/IntelliQ/backend/src/main/webapp/static/js/manage_queue.js @@ -1,26 +1,32 @@ -var UPDATE_INTERVAL_CASUAL = 1000 * 30; -var UPDATE_INTERVAL_DEFAULT = 1000 * 15; -var UPDATE_INTERVAL_DEMO = 1000 * 5; - var queue; var queueItems; var lastQueueItemsUpdate = -1; -var queueItemsUpdateInterval = UPDATE_INTERVAL_DEFAULT; +var queueItemsUpdateInterval; var queueItemsUpdateIntervalObject; -var onUserReady = function(user) { - updateQueue(); -} +(function($){ + $(function(){ + queueItemsUpdateInterval = intelliqApi.UPDATE_INTERVAL_FAST; + + var statusChangeListener = { + onUserAvailable: function(user) { + updateQueue(); + } + }; + authenticator.registerStatusChangeListener(statusChangeListener); + }); +})(jQuery); function updateQueue() { requestQueue().then(function(queue){ renderQueue(queue); + setupQueueManagementButtons(); updateQueueItems(); - startUpdatingQueueItems(UPDATE_INTERVAL_DEFAULT); + startUpdatingQueueItems(intelliqApi.UPDATE_INTERVAL_DEFAULT); }).catch(function(error){ console.log(error); - showErrorMessage(error); + ui.showErrorMessage(error); }); } @@ -31,14 +37,14 @@ function updateQueueItems() { lastQueueItemsUpdate = (new Date()).getTime(); }).catch(function(error){ console.log(error); - showErrorMessage(error); + ui.showErrorMessage(error); }); } function requestQueue() { var promise = new Promise(function(resolve, reject) { var queueKeyId = getUrlParamOrCookie("queueKeyId"); - var googleIdToken = authenticator.getInstance().getUserIdToken(); + var googleIdToken = authenticator.getGoogleUserIdToken(); var request = intelliqApi.getQueue(queueKeyId) .setGoogleIdToken(googleIdToken); @@ -53,6 +59,7 @@ function requestQueue() { } }).catch(function(error){ reject("Unable to get queue: " + error); + console.log(error); }); }); return promise; @@ -60,7 +67,7 @@ function requestQueue() { function requestQueueItems() { var promise = new Promise(function(resolve, reject) { - var googleIdToken = authenticator.getInstance().getUserIdToken(); + var googleIdToken = authenticator.getGoogleUserIdToken(); var request = intelliqApi.getQueueItems(queue.key.id) .setCount(100) @@ -68,9 +75,11 @@ function requestQueueItems() { request.send().then(function(data){ queueItems = intelliqApi.getQueueItemsFromResponse(data); + queueItems = intelliqApi.sortQueueItems(queueItems).byTicketNumber(); resolve(queueItems); }).catch(function(error){ reject("Unable to get queue items: " + error); + console.log(error); }); }); return promise; @@ -83,7 +92,7 @@ function startUpdatingQueueItems(interval) { // set the interval queueItemsUpdateInterval = interval; if (queueItemsUpdateInterval == null) { - queueItemsUpdateInterval = UPDATE_INTERVAL_DEFAULT; + queueItemsUpdateInterval = intelliqApi.UPDATE_INTERVAL_DEFAULT; } // clear existing interval @@ -145,6 +154,7 @@ function renderQueueItemChangesSinceLastUpdate(queueItems) { function renderQueue(queue) { console.log(queue); + $("#addNewCustomerButton").removeClass("disabled"); //TODO: update labels & buttons } @@ -161,6 +171,12 @@ function renderCalledQueueItems(queueItems) { var items = intelliqApi.filterQueueItems(queueItems) .byStatus(intelliqApi.STATUS_CALLED); renderQueueItems(items, container); + + if (items.length > 0) { + $("#markAllAsDoneButton").removeClass("disabled"); + } else { + $("#markAllAsDoneButton").addClass("disabled"); + } } function renderWaitingQueueItems(queueItems) { @@ -168,17 +184,35 @@ function renderWaitingQueueItems(queueItems) { var items = intelliqApi.filterQueueItems(queueItems) .byStatus(intelliqApi.STATUS_WAITING); renderQueueItems(items, container); + + if (items.length > 0) { + $("#callNextCustomerButton").removeClass("disabled"); + } else { + $("#callNextCustomerButton").addClass("disabled"); + } } function renderProcessedQueueItems(queueItems) { var container = $("#processedContainer"); - var items = intelliqApi.filterQueueItems(queueItems) + + var itemsDone = intelliqApi.filterQueueItems(queueItems) .byStatus(intelliqApi.STATUS_DONE); + + var itemsCanceled = intelliqApi.filterQueueItems(queueItems) + .byStatus(intelliqApi.STATUS_CANCELED); + + var items = itemsDone.concat(itemsCanceled); + items = intelliqApi.sortQueueItems(items).byStatusChange(); renderQueueItems(items, container); + + if (items.length > 0) { + $("#clearProcessedCustomersButton").removeClass("disabled"); + } else { + $("#clearProcessedCustomersButton").addClass("disabled"); + } } function onQueueItemsModified() { - //renderAllQueueItems(queueItems); updateQueueItems(); } @@ -186,58 +220,307 @@ function onQueueItemsModified() { Actions */ +function callNextQueueItem() { + try { + var items = intelliqApi.filterQueueItems(queueItems) + .byStatus(intelliqApi.STATUS_WAITING); + if (items.length == 0) { + throw getString("noQueueItems"); + } + var queueItem = items[0]; + callQueueItem(queueItem); + } catch(error) { + console.log(error); + ui.showErrorMessage(error); + } +} + function callQueueItem(queueItem) { - Materialize.toast(getString("calling", queueItem.name), 3000); - var request = intelliqApi.markQueueItemAsCalled(queue.key.id, queueItem.key.id) - .setGoogleIdToken(authenticator.getInstance().getUserIdToken()); + try { + Materialize.toast(getString("calling", queueItem.name), 3000); + var request = intelliqApi.markQueueItemAsCalled(queue.key.id, queueItem.key.id) + .setGoogleIdToken(authenticator.getGoogleUserIdToken()); - request.send().then(function(data){ - console.log(data); - onQueueItemsModified(); - }).catch(function(error){ + request.send().then(function(data){ + console.log(data); + onQueueItemsModified(); + }).catch(function(error){ + console.log(error); + ui.showErrorMessage(error); + }); + tracking.trackEvent(tracking.CATEGORY_QUEUE_MANAGE, "Called queue item", queueItem.name, queueItem.key.id); + } catch(error) { console.log(error); - showErrorMessage(error); - }); + ui.showErrorMessage(error); + } } function cancelQueueItem(queueItem) { - Materialize.toast(getString("cancelling", queueItem.name), 3000); - var request = intelliqApi.markQueueItemAsCanceled(queue.key.id, queueItem.key.id) - .setGoogleIdToken(authenticator.getInstance().getUserIdToken()); + try { + Materialize.toast(getString("cancelling", queueItem.name), 3000); + var request = intelliqApi.markQueueItemAsCanceled(queue.key.id, queueItem.key.id) + .setGoogleIdToken(authenticator.getGoogleUserIdToken()); - request.send().then(function(data){ - console.log(data); - onQueueItemsModified(); - }).catch(function(error){ + request.send().then(function(data){ + console.log(data); + onQueueItemsModified(); + }).catch(function(error){ + console.log(error); + ui.showErrorMessage(error); + }); + tracking.trackEvent(tracking.CATEGORY_QUEUE_MANAGE, "Canceled queue item", queueItem.name, queueItem.key.id); + } catch(error) { console.log(error); - showErrorMessage(error); - }); + ui.showErrorMessage(error); + } +} + +function markAllCalledQueueItemsAsDone() { + try { + Materialize.toast(getString("markingCalledAsDone"), 3000); + var request = intelliqApi.markAllQueueItemsAsDone(queue.key.id) + .setGoogleIdToken(authenticator.getGoogleUserIdToken()); + + request.send().then(function(data){ + console.log(data); + onQueueItemsModified(); + }).catch(function(error){ + console.log(error); + ui.showErrorMessage(error); + }); + tracking.trackEvent(tracking.CATEGORY_QUEUE_MANAGE, "Marked all called queue items as done", queue.name, queue.key.id); + } catch(error) { + console.log(error); + ui.showErrorMessage(error); + } } function markQueueItemAsDone(queueItem) { - Materialize.toast(getString("markingAsDone", queueItem.name), 3000); - var request = intelliqApi.markQueueItemAsDone(queue.key.id, queueItem.key.id) - .setGoogleIdToken(authenticator.getInstance().getUserIdToken()); + try { + Materialize.toast(getString("markingAsDone", queueItem.name), 3000); + var request = intelliqApi.markQueueItemAsDone(queue.key.id, queueItem.key.id) + .setGoogleIdToken(authenticator.getGoogleUserIdToken()); - request.send().then(function(data){ - console.log(data); - onQueueItemsModified(); - }).catch(function(error){ + request.send().then(function(data){ + console.log(data); + onQueueItemsModified(); + }).catch(function(error){ + console.log(error); + ui.showErrorMessage(error); + }); + tracking.trackEvent(tracking.CATEGORY_QUEUE_MANAGE, "Marked queue item as done", queueItem.name, queueItem.key.id); + } catch(error) { console.log(error); - showErrorMessage(error); - }); + ui.showErrorMessage(error); + } +} + +function deleteAllQueueItems() { + try { + Materialize.toast(getString("deletingQueueItems"), 3000); + var request = intelliqApi.clearAllQueueItems(queue.key.id) + .setGoogleIdToken(authenticator.getGoogleUserIdToken()); + + request.send().then(function(data){ + console.log(data); + onQueueItemsModified(); + }).catch(function(error){ + console.log(error); + ui.showErrorMessage(error); + }); + tracking.trackEvent(tracking.CATEGORY_QUEUE_MANAGE, "Deleted all queue items", queue.name, queue.key.id); + } catch(error) { + console.log(error); + ui.showErrorMessage(error); + } +} + +function deleteAllProcessedQueueItems() { + try { + Materialize.toast(getString("deletingQueueItems"), 3000); + var request = intelliqApi.clearProcessedQueueItems(queue.key.id) + .setGoogleIdToken(authenticator.getGoogleUserIdToken()); + + request.send().then(function(data){ + console.log(data); + onQueueItemsModified(); + }).catch(function(error){ + console.log(error); + ui.showErrorMessage(error); + }); + tracking.trackEvent(tracking.CATEGORY_QUEUE_MANAGE, "Deleted processed queue items", queue.name, queue.key.id); + } catch(error) { + console.log(error); + ui.showErrorMessage(error); + } } function deleteQueueItem(queueItem) { - Materialize.toast(getString("deleting", queueItem.name), 3000); - var request = intelliqApi.deleteQueueItem(queue.key.id, queueItem.key.id) - .setGoogleIdToken(authenticator.getInstance().getUserIdToken()); - - request.send().then(function(data){ - console.log(data); - onQueueItemsModified(); - }).catch(function(error){ + try { + Materialize.toast(getString("deleting", queueItem.name), 3000); + var request = intelliqApi.deleteQueueItem(queue.key.id, queueItem.key.id) + .setGoogleIdToken(authenticator.getGoogleUserIdToken()); + + request.send().then(function(data){ + console.log(data); + onQueueItemsModified(); + }).catch(function(error){ + console.log(error); + ui.showErrorMessage(error); + }); + tracking.trackEvent(tracking.CATEGORY_QUEUE_MANAGE, "Deleted queue item", queueItem.name, queueItem.key.id); + } catch(error) { + console.log(error); + ui.showErrorMessage(error); + } +} + +function reportQueueItem(queueItem) { + try { + Materialize.toast(getString("reporting", queueItem.name), 3000); + var request = intelliqApi.reportQueueItem(queue.key.id, queueItem.key.id) + .setGoogleIdToken(authenticator.getGoogleUserIdToken()); + + request.send().then(function(data){ + console.log(data); + onQueueItemsModified(); + }).catch(function(error){ + console.log(error); + ui.showErrorMessage(error); + }); + tracking.trackEvent(tracking.CATEGORY_QUEUE_MANAGE, "Reported queue item", queueItem.name, queueItem.key.id); + } catch(error) { + console.log(error); + ui.showErrorMessage(error); + } +} + +function populateQueue() { + try { + Materialize.toast(getString("populatingQueue"), 3000); + var request = intelliqApi.populateQueue(queue.key.id).withItems(25) + .setGoogleIdToken(authenticator.getGoogleUserIdToken()); + + request.send().then(function(data){ + console.log(data); + onQueueItemsModified(); + }).catch(function(error){ + console.log(error); + ui.showErrorMessage(error); + }); + tracking.trackEvent(tracking.CATEGORY_QUEUE_MANAGE, "Populated queue", queue.name, queue.key.id); + } catch(error) { console.log(error); - showErrorMessage(error); + ui.showErrorMessage(error); + } +} + +function showAddNewQueueItemModal() { + $("#newCustomerName").val(""); + $("#addCustomerModal").openModal(); + $("#newCustomerName").focus(); + tracking.trackEvent(tracking.CATEGORY_QUEUE_MANAGE, "Show new queue item modal"); +} + +function onAddNewCustomerModalSubmitted() { + try { + var name = $("#newCustomerName").val(); + var hideName = $("#newCustomerVisibility").prop("checked") == false; + + Materialize.toast(getString("adding", name), 3000); + var request = intelliqApi.addQueueItem(queue.key.id) + .withName(name) + .hideName(hideName) + .setGoogleIdToken(authenticator.getGoogleUserIdToken()); + + request.send().then(function(data){ + console.log(data); + onQueueItemsModified(); + }).catch(function(error){ + console.log(error); + showErrorMessage(error); + }); + $("#addCustomerModal").closeModal(); + tracking.trackEvent(tracking.CATEGORY_QUEUE_MANAGE, "Submit new queue item modal", name, hideName ? 1 : 0); + } catch(error) { + console.log(error); + ui.showErrorMessage(error); + } +} + +function showQueueItemDetailsModal(queueItem) { + var modal = $("#customerDetailsModal"); + modal.find("h4").text(queueItem.name); + + var joined = ui.time().since(new Date(queueItem.entryTimestamp)); + joined = getString("timeAgo", joined); + + var changed = ui.time().since(new Date(queueItem.lastStatusChangeTimestamp)); + changed = getString("timeAgo", changed); + + modal.find("#customerTicketNumber").text(queueItem.ticketNumber); + modal.find("#customerQueueEntry").text(joined); + modal.find("#customerStatusChange").text(changed); + + modal.find("#reportCustomerButton").off().click(function() { + reportQueueItem(queueItem); }); + + modal.openModal(); + + // request more user details + requestUser(queueItem.userKeyId).then(function(user) { + console.log(user); + }).catch(function(error) { + console.log(error); + }); + + tracking.trackEvent(tracking.CATEGORY_QUEUE_MANAGE, "Show queue item details", queueItem.name, queueItem.ticketNumber); } + +function requestUser(userKeyId) { + var promise = new Promise(function(resolve, reject) { + if (userKeyId > -1) { + var googleIdToken = authenticator.getGoogleUserIdToken(); + var request = intelliqApi.getUser(userKeyId) + .setGoogleIdToken(googleIdToken); + + request.send().then(function(data){ + var users = intelliqApi.getUsersFromResponse(data); + if (users.length > 0) { + resolve(users[0]); + } else { + reject("User not found"); + } + }).catch(function(error){ + reject(error); + }); + } else { + reject("Invalid user key ID"); + } + }); + return promise; +} + +function setupQueueManagementButtons() { + // Queue item lists + $("#markAllAsDoneButton").click(markAllCalledQueueItemsAsDone); + $("#callNextCustomerButton").click(callNextQueueItem); + $("#addNewCustomerButton").click(showAddNewQueueItemModal); + $("#clearProcessedCustomersButton").click(deleteAllProcessedQueueItems); + + // Miscellaneous + $("#editQueueButton").attr("href", intelliqApi.getUrls().forQueue(queue).edit()) + $("#manageBusinessButton").attr("href", intelliqApi.getUrls().forBusiness({key: {id: queue.businessKeyId}}).manage()) + $("#addDummCustomersButton").click(populateQueue); + $("#deleteAllCustomersButton").click(deleteAllQueueItems); + + // add customer modal + $("#sbmitNewCustomerButton").click(onAddNewCustomerModalSubmitted); + $('#newCustomerName').keypress(function(e) { + if (e.which == 13) { + onAddNewCustomerModalSubmitted(); + return false; + } + }); +} \ No newline at end of file diff --git a/IntelliQ/backend/src/main/webapp/static/js/tracking.js b/IntelliQ/backend/src/main/webapp/static/js/tracking.js new file mode 100644 index 0000000..49d38a1 --- /dev/null +++ b/IntelliQ/backend/src/main/webapp/static/js/tracking.js @@ -0,0 +1,48 @@ +var tracking = function(){ + + function log(message) { + console.log("Tracking: " + message); + } + + var tracking = { + }; + + // tracking categories + tracking.CATEGORY_QUEUE_MANAGE = "Manage Queue"; + tracking.CATEGORY_QUEUE_EDIT = "Edit Queue"; + tracking.CATEGORY_BUSINESS_MANAGE = "Manage Business"; + tracking.CATEGORY_BUSINESS_EDIT = "Edit Business"; + tracking.CATEGORY_WEBAPP = "Web App"; + + // for opt-out + tracking.enabled = true; + + tracking.trackEvent = function(category, action, label, value) { + if (!tracking.enabled) { + return; + } + var fields = { + hitType: "event", + eventCategory: category, + eventAction: action, + eventLabel: label, + eventValue: value + } + ga("send", fields); + log("Tracked event: " + category + ", " + action + ", " + label + ", " + value); + } + + tracking.trackException = function(description, fatal) { + if (!tracking.enabled) { + return; + } + var fields = { + "exDescription": description, + "exFatal": fatal + } + ga('send', 'exception', fields); + log("Tracked exception: " + description + ", " + fatal); + } + + return tracking; +}(); \ No newline at end of file diff --git a/IntelliQ/backend/src/main/webapp/static/js/ui.js b/IntelliQ/backend/src/main/webapp/static/js/ui.js index a588d2d..3a5d2c6 100644 --- a/IntelliQ/backend/src/main/webapp/static/js/ui.js +++ b/IntelliQ/backend/src/main/webapp/static/js/ui.js @@ -1,503 +1,903 @@ -var intelliqUi = function(){ - - function log(message) { - console.log("IntelliQ.me UI: " + message); - } - - var ui = { - }; - - ui.generateIcon = function(id) { - var icon = $("", { - "class": "material-icons" - }).html(id); - return icon; - } - - ui.generateClickableIcon = function(id) { - var icon = ui.generateIcon(id); - var link = $(""); - link.append(icon); - return link; - } - - ui.generateButton = function(text, url) { - var button = $("", { - "href": url, - "class": "btn waves-effect waves-light primary-color" - }).text(text); - - button.withIcon = function(id) { - var icon = ui.generateIcon(id); - icon.addClass("right"); - button.prepend(icon); - return button; - } - - button.large = function() { - button.removeClass("btn").addClass("btn-large"); - return button; - } - - button.fillWidth = function(value) { - if (value) { - button.addClass("fill-width"); - } else { - button.removeClass("fill-width") - } - return button; - } - - return button; - } - - ui.generateToastAction = function(text, url) { - var button = $("", { - "href": url, - "class": "btn-flat waves-effect waves-light accent-color-text" - }).text(text); - - return button; - } - - ui.generateCardWrapper = function(className) { - if (className == null) { - className = ui.generateColumnClassName(12, 6, 4); - } - var wrapper = $("
    ", { "class": className }); - return wrapper; - } - - ui.generateColumnClassName = function(small, medium, large) { - return "col s" + small + " m" + medium + " l" + large; - } - - ui.generateAction = function(name, href) { - var action = $("", { - "href": href - }).text(name); - return action; - } - - ui.generateCard = function() { - var card = $("
    ", { "class": "card hoverable" }); - - card.withImage = function(image) { - if (card.find(".card-image").length > 0) { - return card; - } - - var imageContainer = $("
    ", { "class": "card-image invisible" }) - imageContainer.append(image); - imageContainer.prependTo(card); - - card.withImageRatio = function(ratio) { - - card.resizeImageContainer = function() { - var imageContainer = card.find(".card-image"); - var currentImageContainerWidth = imageContainer.width(); - var currentImageContainerHeight = imageContainer.height(); - - var desiredHeight = Math.ceil(currentImageContainerWidth / ratio); - if (currentImageContainerHeight != desiredHeight) { - imageContainer.height(desiredHeight); - } - - card.fillImage(); - } - - card.fillImage = function() { - function adjustImageDimensions(originalWidth, originalHeight) { - var currentImageWidth = originalWidth; - var currentImageHeight = originalHeight; - - var imageContainer = card.find(".card-image"); - var currentImageContainerWidth = imageContainer.width(); - var currentImageContainerHeight = imageContainer.height(); - - var imageRatio = currentImageWidth / currentImageHeight; - var imageContainerRatio = currentImageContainerWidth / currentImageContainerHeight; - - var marginTop = 0; - var marginLeft = 0; - - if (imageRatio >= imageContainerRatio) { - // fit height - var newImageWidth = Math.ceil(currentImageContainerHeight * imageRatio); - var newImageHeight = currentImageContainerHeight; - marginLeft = 0 - (newImageWidth - currentImageContainerWidth) / 2; - } else { - // fit width - var newImageWidth = currentImageContainerWidth; - var newImageHeight = Math.ceil(currentImageContainerWidth / imageRatio); - marginTop = 0 - (newImageHeight - currentImageContainerHeight) / 2; - } - - marginTop -= 1; - marginLeft -= 1; - - var imageElement = imageContainer.find("img"); - imageElement.width(newImageWidth + 2); - imageElement.height(newImageHeight + 2); - imageElement.css({ - "margin-left" : marginLeft + "px", - "margin-top" : marginTop + "px" - }); - imageElement.attr("originalWidth", currentImageWidth); - imageElement.attr("originalHeight", currentImageHeight); - } - - var imageContainer = card.find(".card-image"); - var imageElement = imageContainer.find("img"); - - if (imageElement.attr("originalWidth") != null && imageElement.attr("originalHeight") != null) { - adjustImageDimensions(imageElement.attr("originalWidth"), imageElement.attr("originalHeight")); - } else { - var image = new Image(); - image.onload = function(){ - adjustImageDimensions(image.width, image.height); - animationUi.fadeIn(imageContainer); - } - image.src = imageElement.attr("src"); - } - - return card; - } - - $(window).resize(card.resizeImageContainer); - } - - return card; - } - - card.withContent = function(content) { - if (card.find(".card-content").length > 0) { - return card; - } - - var contentContainer = $("
    ", { "class": "card-content" }) - contentContainer.append(content); - - if (card.find(".card-image").length > 0) { - contentContainer.insertAfter(card.find(".card-image")); - } else if (card.find(".card-action").length > 0) { - contentContainer.insertBefore(card.find(".card-action")); - } else { - contentContainer.appendTo(card); - } - - card.withTitle = function(text, includeIcon) { - var title = $("", { - "class": "card-title activator grey-text text-darken-4 truncate" - }).html(text); - - if (includeIcon) { - var icon = $("", { - "class": "material-icons right" - }).html("more_vert"); - icon.appendTo(title); - } - - card.find(".card-content").prepend(title); - return card; - } - - return card; - } - - card.withRevealableContent = function(content) { - if (card.find(".card-reveal").length > 0) { - return card; - } - - var contentContainer = $("
    ", { "class": "card-reveal" }) - contentContainer.append(content); - contentContainer.appendTo(card); - - card.withTitle = function(text) { - var title = $("", { - "class": "card-title activator grey-text text-darken-4 truncate" - }).html(text); - - var icon = $("", { - "class": "material-icons right" - }).html("close"); - icon.appendTo(title); - - card.find(".card-reveal").prepend(title); - return card; - } - - return card; - } - - card.withTiles = function(tiles, options) { - if (card.find(".card-tiles").length > 0) { - return card; - } - - var defaultOptions = { - minimumTilesPerRow: 2, - maximumTilesPerRow: 4 - }; - - if (options == null) { - options = defaultOptions; - } - - var addTileImageLoadedHandler = function(tileImage) { - tileImage.load(function() { - // get swatches - var image = tileImage.get(0); - var vibrant = new Vibrant(image); - var swatches = vibrant.swatches() - - // adjust overlay color - var overlayColor = swatches.DarkMuted.getHex(); - var overlayContainer = tileImage.parent().find(".overlay-container"); - overlayContainer.css("opacity", "0"); - - var overlay = overlayContainer.find(".overlay"); - overlay.css("opacity", "0.75"); - overlay.css("background-color", overlayColor); - - // add event listeners - overlayContainer.hover(function() { - overlayContainer.css("opacity", "1"); - }, function() { - overlayContainer.css("opacity", "0"); - }); - }); - } - - var tileWrappers = []; - - var smallGrid = 12 / options.minimumTilesPerRow; - var mediumGrid = 12 / options.maximumTilesPerRow; - - var tilesCount = Math.floor(tiles.length / options.maximumTilesPerRow) * options.maximumTilesPerRow; - for (var i = 0; i < tiles.length && i < tilesCount; i++) { - var tileWrapper = $("
    ", { "class": "col s" + smallGrid + " m" + mediumGrid + " no-padding" }); - var tileContainer = $("
    ", { "class": "tile-container" }); - - addTileImageLoadedHandler(tiles[i].find("img")); - - tileContainer.append(tiles[i]); - tileWrapper.append(tileContainer); - tileWrappers.push(tileWrapper); - } - - var tilesContainer = $("
    ", { "class": "card-tiles row no-margin" }) - tilesContainer.append(tileWrappers); - - if (card.find(".card-action").length > 0) { - tilesContainer.insertBefore(card.find(".card-action")); - } else { - tilesContainer.appendTo(card); - } - - return card; - } - - card.withActions = function(actions) { - if (card.find(".card-action").length > 0) { - return card; - } - - var actionsContainer = $("
    ", { "class": "card-action" }) - actionsContainer.append(actions) - actionsContainer.appendTo(card); - return card; - } - - card.withLink = function(url) { - card.css("cursor", "pointer"); - card.click(function() { - redirect(url); - }); - } - - card.renderIn = function(container) { - container.html(card); - window.setTimeout(function() { - if (card.resizeImageContainer != null) { - card.resizeImageContainer(); - } - }, 1); - } - - return card; - } - - ui.generateCollection = function() { - var collection = $("