projectIdsWithReadPermission = projectsIdWithPermission(Permission.READ);
+ if (projectId != null) {
+ if (!hasGlobalRead) {
+ Validate.isTrue(projectIdsWithReadPermission.contains(projectId));
+ }
+ projectIdFilter.add(projectId);
+ } else if (projectId == null && !hasGlobalRead) {
+ projectIdFilter.addAll(projectIdsWithReadPermission);
+ }
+ return projectIdFilter;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/io/lavagna/model/util/ShortNameGenerator.java b/src/main/java/io/lavagna/model/util/ShortNameGenerator.java
new file mode 100644
index 000000000..dcf7e047c
--- /dev/null
+++ b/src/main/java/io/lavagna/model/util/ShortNameGenerator.java
@@ -0,0 +1,100 @@
+/**
+ * This file is part of lavagna.
+ *
+ * lavagna is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * lavagna is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with lavagna. If not, see .
+ */
+package io.lavagna.model.util;
+
+import java.util.Locale;
+import java.util.regex.Pattern;
+
+import org.apache.commons.lang3.StringUtils;
+
+public final class ShortNameGenerator {
+
+ private ShortNameGenerator() {
+ }
+
+ public static boolean isShortNameValid(String shortName) {
+ Pattern acceptedChars = Pattern.compile("^[A-Z0-9_]+$");
+ return acceptedChars.matcher(shortName).matches();
+ }
+
+ /**
+ *
+ * Generate a short name given the full project name.
+ *
+ * Total length returned is equal or less than 8.
+ *
+ *
+ *
+ * Heuristic:
+ *
+ *
+ * - if name is less or equals than 6 chars and is a single word, the short name will be UPPER(name)
+ * - if the project has multiple words, it will concatenate each words. If a word has a length more than 4:
+ *
+ * - it will take only the upper case characters if there are more than 1.
+ * - else it will take the first two characters.
+ *
+ *
+ *
+ *
+ * @param name
+ * @return
+ */
+ public static String generateShortNameFrom(String name) {
+ if (StringUtils.isBlank(name)) {
+ return name;
+ }
+
+ String t = name.trim().replace("-", "");
+ String[] splitted = t.split("\\s+");
+ if (splitted.length == 1) {
+ return splitted[0].substring(0, Math.min(6, splitted[0].length())).toUpperCase(Locale.ENGLISH);
+ }
+ StringBuilder sb = new StringBuilder();
+ for (String token : splitted) {
+ if (token.length() <= 4) {
+ sb.append(token);
+ } else if (countUpperCase(token) > 1) {
+ sb.append(takeFirstFourUpperCaseChars(token));
+ } else {
+ sb.append(token.substring(0, 3));
+ }
+ }
+ return sb.toString().toUpperCase(Locale.ENGLISH).substring(0, Math.min(8, sb.length()));
+ }
+
+ private static String takeFirstFourUpperCaseChars(String s) {
+ StringBuilder sb = new StringBuilder();
+ for (char c : s.toCharArray()) {
+ if (Character.isUpperCase(c)) {
+ sb.append(c);
+ }
+ }
+ return sb.substring(0, Math.min(sb.length(), 4));
+ }
+
+ private static int countUpperCase(String s) {
+ int cnt = 0;
+ for (char c : s.toCharArray()) {
+ if (Character.isUpperCase(c)) {
+ cnt++;
+ }
+ }
+ return cnt;
+ }
+
+}
diff --git a/src/main/java/io/lavagna/query/BoardColumnQuery.java b/src/main/java/io/lavagna/query/BoardColumnQuery.java
new file mode 100644
index 000000000..23f71dbcd
--- /dev/null
+++ b/src/main/java/io/lavagna/query/BoardColumnQuery.java
@@ -0,0 +1,95 @@
+/**
+ * This file is part of lavagna.
+ *
+ * lavagna is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * lavagna is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with lavagna. If not, see .
+ */
+package io.lavagna.query;
+
+import io.lavagna.common.Bind;
+import io.lavagna.common.QueriesOverride;
+import io.lavagna.common.Query;
+import io.lavagna.common.QueryOverride;
+import io.lavagna.common.QueryRepository;
+import io.lavagna.common.QueryType;
+import io.lavagna.model.BoardColumn;
+import io.lavagna.model.BoardColumnInfo;
+
+import java.util.List;
+import java.util.Set;
+
+@QueryRepository
+public interface BoardColumnQuery {
+
+ @Query("SELECT * FROM LA_BOARD_COLUMN_FULL WHERE BOARD_COLUMN_ID = :columnId")
+ BoardColumn findById(@Bind("columnId") int columnId);
+
+ @Query("SELECT * FROM LA_BOARD_COLUMN_FULL WHERE BOARD_COLUMN_ID IN (:ids)")
+ List findByIds(@Bind("ids") Set ids);
+
+ @Query("SELECT BOARD_COLUMN_ID FROM LA_BOARD_COLUMN WHERE BOARD_COLUMN_ID IN (:ids) AND BOARD_COLUMN_LOCATION = :location AND BOARD_COLUMN_BOARD_ID_FK = :boardId")
+ List findColumnIdsInBoard(@Bind("ids") List ids, @Bind("location") String location, @Bind("boardId") int boardId);
+
+ @Query("SELECT CARD_ID FROM LA_CARD WHERE CARD_BOARD_COLUMN_ID_FK = :columnId")
+ List findCardsInColumnId(@Bind("columnId") int columnId);
+
+ @Query("INSERT INTO LA_BOARD_COLUMN(BOARD_COLUMN_NAME, BOARD_COLUMN_ORDER, BOARD_COLUMN_BOARD_ID_FK, BOARD_COLUMN_LOCATION, BOARD_COLUMN_DEFINITION_ID_FK) VALUES "
+ + "(:name, (SELECT * FROM (SELECT COALESCE(MAX(BOARD_COLUMN_ORDER),0) + 1 FROM LA_BOARD_COLUMN WHERE BOARD_COLUMN_BOARD_ID_FK = :boardId AND BOARD_COLUMN_LOCATION = :location) AS MAX_BOARD_COLUMN_ORDER), "
+ + ":boardId, :location, :definitionId)")
+ int addColumnToBoard(@Bind("name") String name, @Bind("boardId") int boardId, @Bind("location") String location,
+ @Bind("definitionId") int definitionId);
+
+ @Query("SELECT BOARD_COLUMN_ID, BOARD_COLUMN_NAME, BOARD_COLUMN_ORDER, BOARD_COLUMN_LOCATION, BOARD_COLUMN_BOARD_ID_FK, BOARD_COLUMN_DEFINITION_ID, BOARD_COLUMN_DEFINITION_VALUE,"
+ + " BOARD_COLUMN_DEFINITION_COLOR FROM LA_BOARD_COLUMN_FULL WHERE BOARD_COLUMN_BOARD_ID_FK = :boardId AND BOARD_COLUMN_LOCATION = :location "
+ + "ORDER BY BOARD_COLUMN_ORDER ASC, BOARD_COLUMN_NAME ASC")
+ List findAllColumnFor(@Bind("boardId") int boardId, @Bind("location") String location);
+
+ @Query("SELECT BOARD_COLUMN_ID, BOARD_COLUMN_NAME, BOARD_COLUMN_ORDER, BOARD_COLUMN_LOCATION, BOARD_COLUMN_BOARD_ID_FK, BOARD_COLUMN_DEFINITION_ID, BOARD_COLUMN_DEFINITION_VALUE,"
+ + " BOARD_COLUMN_DEFINITION_COLOR FROM LA_BOARD_COLUMN_FULL WHERE BOARD_COLUMN_BOARD_ID_FK = :boardId "
+ + "ORDER BY BOARD_COLUMN_ORDER ASC, BOARD_COLUMN_NAME ASC")
+ List findAllColumnFor(@Bind("boardId") int boardId);
+
+ @Query("SELECT BOARD_COLUMN_ID, BOARD_COLUMN_NAME, BOARD_COLUMN_ORDER, BOARD_COLUMN_LOCATION, BOARD_COLUMN_BOARD_ID_FK, BOARD_COLUMN_DEFINITION_ID, BOARD_COLUMN_DEFINITION_VALUE, BOARD_COLUMN_DEFINITION_COLOR FROM LA_BOARD_COLUMN_FULL "
+ + "WHERE BOARD_COLUMN_BOARD_ID_FK = :boardId AND BOARD_COLUMN_LOCATION = :location AND BOARD_COLUMN_NAME = :location")
+ BoardColumn findDefaultColumnFor(@Bind("boardId") int boardId, @Bind("location") String location);
+
+ @Query(type = QueryType.TEMPLATE, value = "UPDATE LA_BOARD_COLUMN SET BOARD_COLUMN_ORDER = :order WHERE BOARD_COLUMN_ID = :columnId AND BOARD_COLUMN_BOARD_ID_FK = :boardId AND BOARD_COLUMN_LOCATION = :location")
+ String updateColumnOrder();
+
+ @Query("UPDATE LA_BOARD_COLUMN SET BOARD_COLUMN_LOCATION = :location, BOARD_COLUMN_DEFINITION_ID_FK = :columnDefinitionId WHERE BOARD_COLUMN_ID = :columnId")
+ int moveToLocation(@Bind("columnId") int columnId, @Bind("location") String location,
+ @Bind("columnDefinitionId") int columnDefinitionId);
+
+ @Query("UPDATE LA_BOARD_COLUMN SET BOARD_COLUMN_ORDER = :order WHERE BOARD_COLUMN_ID = :columnId")
+ int updateOrder(@Bind("columnId") int columnId, @Bind("order") int order);
+
+ @Query("UPDATE LA_BOARD_COLUMN SET BOARD_COLUMN_NAME = :newName WHERE BOARD_COLUMN_ID = :columnId AND BOARD_COLUMN_BOARD_ID_FK = :boardId")
+ int renameColumn(@Bind("newName") String newName, @Bind("columnId") int columnId, @Bind("boardId") int boardId);
+
+ @Query("SELECT BOARD_COLUMN_ID, BOARD_COLUMN_NAME, BOARD_COLUMN_ORDER, BOARD_COLUMN_LOCATION, BOARD_COLUMN_BOARD_ID_FK, BOARD_COLUMN_DEFINITION_ID, BOARD_COLUMN_DEFINITION_VALUE, BOARD_COLUMN_DEFINITION_COLOR "
+ + " FROM LA_BOARD_COLUMN_FULL WHERE BOARD_COLUMN_ID = IDENTITY()")
+ @QueriesOverride({
+ @QueryOverride(db = DB.MYSQL, value = "SELECT BOARD_COLUMN_ID, BOARD_COLUMN_NAME, BOARD_COLUMN_ORDER, BOARD_COLUMN_LOCATION, BOARD_COLUMN_BOARD_ID_FK, BOARD_COLUMN_DEFINITION_ID, BOARD_COLUMN_DEFINITION_VALUE, BOARD_COLUMN_DEFINITION_COLOR "
+ + " FROM LA_BOARD_COLUMN_FULL WHERE BOARD_COLUMN_ID = LAST_INSERT_ID()"),//
+ @QueryOverride(db = DB.PGSQL, value = "SELECT BOARD_COLUMN_ID, BOARD_COLUMN_NAME, BOARD_COLUMN_ORDER, BOARD_COLUMN_LOCATION, BOARD_COLUMN_BOARD_ID_FK, BOARD_COLUMN_DEFINITION_ID, BOARD_COLUMN_DEFINITION_VALUE, BOARD_COLUMN_DEFINITION_COLOR "
+ + " FROM LA_BOARD_COLUMN_FULL WHERE BOARD_COLUMN_ID = (SELECT CURRVAL(pg_get_serial_sequence('la_board_column','board_column_id')))") })
+ BoardColumn findLastCreatedColumn();
+
+ @Query("SELECT * FROM LA_BOARD_COLUMN_INFO WHERE BOARD_COLUMN_ID = :columnId")
+ BoardColumnInfo getColumnInfoById(@Bind("columnId") int columnId);
+
+ @Query("UPDATE LA_BOARD_COLUMN SET BOARD_COLUMN_DEFINITION_ID_FK = :definitionId WHERE BOARD_COLUMN_ID = :columnId AND BOARD_COLUMN_BOARD_ID_FK = :boardId")
+ int redefineColumn(@Bind("definitionId") int definitionId, @Bind("columnId") int columnId,
+ @Bind("boardId") int boardId);
+
+}
diff --git a/src/main/java/io/lavagna/query/BoardQuery.java b/src/main/java/io/lavagna/query/BoardQuery.java
new file mode 100644
index 000000000..0079cddeb
--- /dev/null
+++ b/src/main/java/io/lavagna/query/BoardQuery.java
@@ -0,0 +1,82 @@
+/**
+ * This file is part of lavagna.
+ *
+ * lavagna is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * lavagna is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with lavagna. If not, see .
+ */
+package io.lavagna.query;
+
+import io.lavagna.common.Bind;
+import io.lavagna.common.QueriesOverride;
+import io.lavagna.common.Query;
+import io.lavagna.common.QueryOverride;
+import io.lavagna.common.QueryRepository;
+import io.lavagna.model.Board;
+import io.lavagna.model.BoardColumnDefinition;
+import io.lavagna.model.BoardInfo;
+import io.lavagna.model.ProjectAndBoard;
+
+import java.util.List;
+
+@QueryRepository
+public interface BoardQuery {
+
+ @Query("INSERT INTO LA_BOARD(BOARD_NAME, BOARD_SHORT_NAME, BOARD_DESCRIPTION, BOARD_PROJECT_ID_FK) VALUES(:name, :shortName, :description, :projectId)")
+ int createNewBoard(@Bind("name") String name, @Bind("shortName") String shortName,
+ @Bind("description") String description, @Bind("projectId") int projectId);
+
+ @Query("UPDATE LA_BOARD SET BOARD_NAME = :name, BOARD_DESCRIPTION = :description, BOARD_ARCHIVED = :archived WHERE BOARD_ID = :boardId")
+ int updateBoard(@Bind("boardId") int boardId, @Bind("name") String name, @Bind("description") String description,
+ @Bind("archived") boolean archived);
+
+ @Query("SELECT * FROM LA_BOARD WHERE BOARD_SHORT_NAME = :shortName")
+ Board findBoardByShortName(@Bind("shortName") String shortName);
+
+ @Query("SELECT * FROM LA_BOARD WHERE BOARD_ID = :boardId")
+ Board findBoardById(@Bind("boardId") int boardId);
+
+ @Query("SELECT BOARD_ID FROM LA_BOARD WHERE BOARD_SHORT_NAME = :shortName")
+ Integer findBoardIdByShortName(@Bind("shortName") String shortName);
+
+ @Query("SELECT * FROM LA_BOARD WHERE BOARD_ID = IDENTITY()")
+ @QueriesOverride({
+ @QueryOverride(db = DB.MYSQL, value = "SELECT * FROM LA_BOARD WHERE BOARD_ID = LAST_INSERT_ID()"),//
+ @QueryOverride(db = DB.PGSQL, value = "SELECT * FROM LA_BOARD WHERE BOARD_ID = (SELECT CURRVAL(pg_get_serial_sequence('la_board','board_id')))"), })
+ Board findLastCreatedBoard();
+
+ @Query("INSERT INTO LA_BOARD_COUNTER(BOARD_COUNTER_ID_FK, BOARD_COUNTER_CARD_SEQUENCE) VALUES(IDENTITY() , 1)")
+ @QueriesOverride({
+ @QueryOverride(db = DB.MYSQL, value = "INSERT INTO LA_BOARD_COUNTER(BOARD_COUNTER_ID_FK, BOARD_COUNTER_CARD_SEQUENCE) VALUES (LAST_INSERT_ID(), 1)"),//
+ @QueryOverride(db = DB.PGSQL, value = "INSERT INTO LA_BOARD_COUNTER(BOARD_COUNTER_ID_FK, BOARD_COUNTER_CARD_SEQUENCE) VALUES ((SELECT CURRVAL(pg_get_serial_sequence('la_board','board_id'))), 1)"), })
+ int initializeSequence();
+
+ @Query("SELECT * FROM LA_BOARD ORDER BY BOARD_SHORT_NAME")
+ List findAll();
+
+ @Query("SELECT * FROM LA_BOARD_COLUMN_DEFINITION where BOARD_COLUMN_DEFINITION_VALUE = :definition and BOARD_COLUMN_DEFINITION_PROJECT_ID_FK = :projectId")
+ BoardColumnDefinition findColumnDefinitionByProjectIdAndType(@Bind("projectId") int projectId,
+ @Bind("definition") String definition);
+
+ @Query("SELECT BOARD_SHORT_NAME, BOARD_NAME, BOARD_DESCRIPTION, BOARD_ARCHIVED FROM LA_BOARD WHERE BOARD_PROJECT_ID_FK = :projectId ORDER BY BOARD_SHORT_NAME")
+ List findBoardInfo(@Bind("projectId") int projectId);
+
+ @Query("SELECT LA_PROJECT.PROJECT_ID, LA_PROJECT.PROJECT_NAME, LA_PROJECT.PROJECT_SHORT_NAME, PROJECT_DESCRIPTION, PROJECT_ARCHIVED, "
+ + "BOARD_ID, LA_BOARD.BOARD_SHORT_NAME, BOARD_NAME, BOARD_DESCRIPTION, BOARD_ARCHIVED FROM LA_PROJECT "
+ + "INNER JOIN LA_BOARD ON LA_PROJECT.PROJECT_ID = LA_BOARD.BOARD_PROJECT_ID_FK "
+ + "INNER JOIN LA_BOARD_COLUMN ON LA_BOARD.BOARD_ID = LA_BOARD_COLUMN.BOARD_COLUMN_BOARD_ID_FK "
+ + "WHERE LA_BOARD_COLUMN.BOARD_COLUMN_ID = :columnId")
+ ProjectAndBoard findProjectAndBoardByColumnId(@Bind("columnId") int columnId);
+
+ @Query("SELECT COUNT(BOARD_SHORT_NAME) FROM LA_BOARD WHERE BOARD_SHORT_NAME = :shortName")
+ Integer existsWithShortName(@Bind("shortName") String shortName);
+}
diff --git a/src/main/java/io/lavagna/query/CardDataQuery.java b/src/main/java/io/lavagna/query/CardDataQuery.java
new file mode 100644
index 000000000..44299bcf5
--- /dev/null
+++ b/src/main/java/io/lavagna/query/CardDataQuery.java
@@ -0,0 +1,174 @@
+/**
+ * This file is part of lavagna.
+ *
+ * lavagna is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * lavagna is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with lavagna. If not, see .
+ */
+package io.lavagna.query;
+
+import io.lavagna.common.Bind;
+import io.lavagna.common.QueriesOverride;
+import io.lavagna.common.Query;
+import io.lavagna.common.QueryOverride;
+import io.lavagna.common.QueryRepository;
+import io.lavagna.common.QueryType;
+import io.lavagna.model.CardData;
+import io.lavagna.model.CardDataCount;
+import io.lavagna.model.CardDataFull;
+import io.lavagna.model.CardDataIdAndOrder;
+import io.lavagna.model.CardDataMetadata;
+import io.lavagna.model.CardDataUploadContentInfo;
+import io.lavagna.model.CardIdAndContent;
+import io.lavagna.model.FileDataLight;
+
+import java.util.Collection;
+import java.util.List;
+
+@QueryRepository
+public interface CardDataQuery {
+
+ @Query("SELECT CARD_DATA_ID, CARD_DATA_CONTENT FROM LA_CARD_DATA WHERE CARD_DATA_ID IN (:ids)")
+ List findDataByIds(@Bind("ids") Collection ids);
+
+ @Query("SELECT CARD_DATA_ID, CARD_DATA_CARD_ID_FK, CARD_DATA_REFERENCE_ID, CARD_DATA_DELETED, CARD_DATA_TYPE, CARD_DATA_ORDER FROM LA_CARD_DATA WHERE CARD_DATA_ID = :id")
+ CardDataMetadata findMetadataById(@Bind("id") int id);
+
+ @Query("SELECT CARD_DATA_ID, CARD_DATA_CONTENT FROM LA_CARD_DATA WHERE CARD_DATA_CARD_ID_FK = :cardId AND CARD_DATA_REFERENCE_ID = :refId AND CARD_DATA_TYPE = :type AND CARD_DATA_ORDER = :order")
+ List findContentWith(@Bind("cardId") int cardId, @Bind("refId") int refId,
+ @Bind("type") String type, @Bind("order") int order);
+
+ @Query("SELECT * FROM LA_CARD_DATA WHERE CARD_DATA_ID = :id AND CARD_DATA_DELETED = FALSE")
+ CardData getUndeletedDataLightById(@Bind("id") int id);
+
+ @Query("SELECT * FROM LA_CARD_DATA WHERE CARD_DATA_ID = :id")
+ CardData getDataLightById(@Bind("id") int id);
+
+ @Query("SELECT * FROM LA_CARD_DATA WHERE CARD_DATA_CARD_ID_FK = :cardId AND CARD_DATA_DELETED = FALSE ORDER BY CARD_DATA_REFERENCE_ID ASC, CARD_DATA_ORDER ASC")
+ @QueriesOverride(@QueryOverride(db = DB.PGSQL, value = "SELECT * FROM LA_CARD_DATA WHERE CARD_DATA_CARD_ID_FK = :cardId AND CARD_DATA_DELETED = FALSE ORDER BY CARD_DATA_REFERENCE_ID ASC NULLS FIRST, CARD_DATA_ORDER ASC"))
+ List findAllLightByCardId(@Bind("cardId") int cardId);
+
+ @Query("SELECT * FROM LA_CARD_DATA WHERE CARD_DATA_CARD_ID_FK = :cardId and CARD_DATA_TYPE = :type AND CARD_DATA_DELETED = FALSE ORDER BY CARD_DATA_REFERENCE_ID ASC, CARD_DATA_ORDER ASC")
+ @QueriesOverride(@QueryOverride(db = DB.PGSQL, value = "SELECT * FROM LA_CARD_DATA WHERE CARD_DATA_CARD_ID_FK = :cardId and CARD_DATA_TYPE = :type AND CARD_DATA_DELETED = FALSE ORDER BY CARD_DATA_REFERENCE_ID ASC NULLS FIRST, CARD_DATA_ORDER ASC"))
+ List findAllLightByCardIdAndType(@Bind("cardId") int cardId, @Bind("type") String type);
+
+ @Query("SELECT * FROM LA_CARD_DATA WHERE CARD_DATA_CARD_ID_FK = :cardId and CARD_DATA_TYPE IN (:types) AND CARD_DATA_DELETED = FALSE ORDER BY CARD_DATA_REFERENCE_ID ASC, CARD_DATA_ORDER ASC")
+ @QueriesOverride(@QueryOverride(db = DB.PGSQL, value = "SELECT * FROM LA_CARD_DATA WHERE CARD_DATA_CARD_ID_FK = :cardId and CARD_DATA_TYPE IN (:types) AND CARD_DATA_DELETED = FALSE ORDER BY CARD_DATA_REFERENCE_ID ASC NULLS FIRST, CARD_DATA_ORDER ASC"))
+ List findAllLightByCardIdAndTypes(@Bind("cardId") int cardId, @Bind("types") List types);
+
+ @Query("SELECT * FROM LA_CARD_DATA WHERE CARD_DATA_REFERENCE_ID = :referenceId AND CARD_DATA_DELETED = FALSE ORDER BY CARD_DATA_ORDER ASC")
+ List findAllLightByReferenceId(@Bind("referenceId") int referenceId);
+
+ @Query("SELECT CARD_DATA_ID, CARD_DATA_ORDER FROM LA_CARD_DATA WHERE CARD_DATA_TYPE IN (:types)")
+ List findAllCardDataIdAndOrderByType(@Bind("types") List types);
+
+ /**
+ * Will return the deleted one too.
+ */
+ @Query("SELECT * FROM LA_CARD_DATA WHERE CARD_DATA_REFERENCE_ID = :referenceId AND CARD_DATA_TYPE = :type ORDER BY CARD_DATA_ORDER ASC")
+ List findAllLightByReferenceIdAndType(@Bind("referenceId") int referenceId, @Bind("type") String type);
+
+ @Query("INSERT INTO LA_CARD_DATA(CARD_DATA_CARD_ID_FK,CARD_DATA_TYPE,CARD_DATA_CONTENT,CARD_DATA_ORDER) "
+ + " VALUES (:cardId, :type, :content, (SELECT * FROM (SELECT COALESCE(MAX(CARD_DATA_ORDER),0) + 1 FROM LA_CARD_DATA WHERE CARD_DATA_CARD_ID_FK = :cardId AND CARD_DATA_TYPE = :type) AS MAX_CARD_DATA_ORDER))")
+ int create(@Bind("cardId") int cardId, @Bind("type") String type, @Bind("content") String content);
+
+ @Query("INSERT INTO LA_CARD_DATA(CARD_DATA_CARD_ID_FK,CARD_DATA_REFERENCE_ID,CARD_DATA_TYPE,CARD_DATA_CONTENT,CARD_DATA_ORDER) "
+ + " VALUES (:cardId, :referenceId, :type, :content, (SELECT * FROM (SELECT COALESCE(MAX(CARD_DATA_ORDER),0) + 1 FROM LA_CARD_DATA WHERE CARD_DATA_CARD_ID_FK = :cardId AND CARD_DATA_REFERENCE_ID = :referenceId) AS MAX_CARD_DATA_ORDER))")
+ int createWithReferenceOrder(@Bind("cardId") int cardId, @Bind("referenceId") Integer referenceId,
+ @Bind("type") String type, @Bind("content") String content);
+
+ @Query("SELECT CARD_DATA_ID, CARD_DATA_CARD_ID_FK, CARD_DATA_REFERENCE_ID, CARD_DATA_TYPE, CARD_DATA_CONTENT, CARD_DATA_ORDER, EVENT_TIME, EVENT_TYPE, EVENT_PREV_CARD_DATA_ID_FK, EVENT_USER_ID_FK "
+ + " FROM LA_CARD_DATA_FULL WHERE CARD_DATA_CARD_ID_FK = :cardId and CARD_DATA_TYPE = :type AND CARD_DATA_DELETED = FALSE ORDER BY CARD_DATA_REFERENCE_ID ASC, CARD_DATA_ORDER ASC")
+ List findAllByCardIdAndType(@Bind("cardId") int cardId, @Bind("type") String type);
+
+ @Query("SELECT * FROM LA_CARD_DATA WHERE CARD_DATA_ID = IDENTITY()")
+ @QueriesOverride({
+ @QueryOverride(db = DB.MYSQL, value = "SELECT * FROM LA_CARD_DATA WHERE CARD_DATA_ID = LAST_INSERT_ID()"),
+ @QueryOverride(db = DB.PGSQL, value = "SELECT * FROM LA_CARD_DATA WHERE CARD_DATA_ID = (SELECT CURRVAL(pg_get_serial_sequence('la_card_data','card_data_id')))") })
+ CardData findLastCreatedLight();
+
+ @Query("UPDATE LA_CARD_DATA SET CARD_DATA_TYPE = :type WHERE CARD_DATA_ID = :id AND CARD_DATA_TYPE IN (:types)")
+ int updateType(@Bind("type") String type, @Bind("id") int id, @Bind("types") List types);
+
+ @Query(type = QueryType.TEMPLATE, value = "UPDATE LA_CARD_DATA SET CARD_DATA_ORDER = :order WHERE CARD_DATA_ID = :id AND CARD_DATA_CARD_ID_FK = :cardId")
+ String updateOrder();
+
+ @Query("SELECT CARD_DATA_ID FROM LA_CARD_DATA WHERE CARD_DATA_ID IN (:ids) AND CARD_DATA_CARD_ID_FK = :cardId AND CARD_DATA_TYPE = :cardDataType")
+ List findAllCardDataIdsBy(@Bind("ids") List ids, @Bind("cardId") int cardId,
+ @Bind("cardDataType") String cardDataType);
+
+ @Query("SELECT CARD_DATA_ID FROM LA_CARD_DATA WHERE CARD_DATA_ID IN (:ids) AND CARD_DATA_CARD_ID_FK = :cardId AND CARD_DATA_TYPE IN (:cardDataTypes) AND CARD_DATA_REFERENCE_ID = :referenceId")
+ List findAllCardDataIdsBy(@Bind("ids") List ids, @Bind("cardId") int cardId,
+ @Bind("referenceId") int referenceId, @Bind("cardDataTypes") List cardDataTypes);
+
+ @Query("UPDATE LA_CARD_DATA SET CARD_DATA_ORDER = :order WHERE CARD_DATA_ID = :id")
+ int updateOrderById(@Bind("id") int id, @Bind("order") int order);
+
+ @Query(type = QueryType.TEMPLATE, value = "UPDATE LA_CARD_DATA SET CARD_DATA_ORDER = :order WHERE CARD_DATA_ID = :id AND CARD_DATA_CARD_ID_FK = :cardId AND CARD_DATA_REFERENCE_ID = :referenceId")
+ String updateOrderByCardAndReferenceId();
+
+ @Query("UPDATE LA_CARD_DATA SET CARD_DATA_REFERENCE_ID = :referenceId WHERE CARD_DATA_ID = :id AND CARD_DATA_CARD_ID_FK = :cardId")
+ int updateReferenceId(@Bind("referenceId") Integer referenceId, @Bind("id") int id, @Bind("cardId") int cardId);
+
+ @Query("UPDATE LA_CARD_DATA SET CARD_DATA_CONTENT = :content WHERE CARD_DATA_ID = :id AND CARD_DATA_TYPE IN (:types)")
+ int updateContent(@Bind("content") String content, @Bind("id") int id, @Bind("types") List types);
+
+ @Query("UPDATE LA_CARD_DATA SET CARD_DATA_DELETED = TRUE WHERE CARD_DATA_ID = :id AND CARD_DATA_TYPE IN (:types)")
+ int softDelete(@Bind("id") int id, @Bind("types") List types);
+
+ @Query("UPDATE LA_CARD_DATA SET CARD_DATA_DELETED = FALSE WHERE CARD_DATA_ID = :id AND CARD_DATA_TYPE IN (:types)")
+ int undoSoftDelete(@Bind("id") int id, @Bind("types") List types);
+
+ @Query("UPDATE LA_CARD_DATA SET CARD_DATA_DELETED = TRUE WHERE (CARD_DATA_ID = :id AND CARD_DATA_TYPE IN (:types)) OR (CARD_DATA_REFERENCE_ID = :id)")
+ int softDeleteOnCascade(@Bind("id") int id, @Bind("types") List types);
+
+ @Query("UPDATE LA_CARD_DATA SET CARD_DATA_DELETED = FALSE WHERE "
+ + " ((CARD_DATA_ID = :id AND CARD_DATA_TYPE IN (:types)) OR "
+ + " (CARD_DATA_REFERENCE_ID = :id)) "
+ + " AND (CARD_DATA_ID NOT IN (SELECT * FROM (SELECT CARD_DATA_ID FROM LA_CARD_DATA INNER JOIN LA_EVENT ON CARD_DATA_ID = EVENT_CARD_DATA_ID_FK WHERE CARD_DATA_REFERENCE_ID = :id AND EVENT_TYPE IN (:filteredEvents)) AS CDATA_WITH_REF))")
+ int undoSoftDeleteOnCascade(@Bind("id") int id, @Bind("types") List types,
+ @Bind("filteredEvents") List filteredEvents);
+
+ @Query("SELECT CARD_ID, CARD_DATA_TYPE, CARD_DATA_TYPE_COUNT FROM LA_CARD_DATA_COUNT"
+ + " INNER JOIN LA_BOARD_COLUMN ON BOARD_COLUMN_ID = CARD_BOARD_COLUMN_ID_FK"
+ + " WHERE BOARD_ID = :boardId AND BOARD_COLUMN_LOCATION = :location")
+ List findCountsByBoardIdAndLocation(@Bind("boardId") int boardId, @Bind("location") String location);
+
+ @Query("SELECT CARD_DATA_CARD_ID_FK AS CARD_ID, CARD_DATA_TYPE, COUNT(CARD_DATA_TYPE) AS CARD_DATA_TYPE_COUNT FROM LA_CARD_DATA WHERE CARD_DATA_DELETED = FALSE AND CARD_DATA_CARD_ID_FK IN (:ids) GROUP BY CARD_DATA_CARD_ID_FK, CARD_DATA_TYPE")
+ List findCountsByCardIds(@Bind("ids") List ids);
+
+ @Query(type = QueryType.TEMPLATE, value = "INSERT INTO LA_CARD_DATA_UPLOAD_CONTENT(DIGEST,SIZE,CONTENT,CONTENT_TYPE) VALUES (?, ?, ?, ?)")
+ String addUploadContent();
+
+ @Query("SELECT COUNT(1) FROM LA_CARD_DATA_UPLOAD_CONTENT WHERE DIGEST = :digest")
+ Integer findDigest(@Bind("digest") String digest);
+
+ @Query("SELECT COUNT(1) FROM LA_CARD_DATA_UPLOAD_CONTENT_LIGHT WHERE CARD_DATA_CARD_ID_FK = :cardId AND CARD_DATA_CONTENT = :digest")
+ Integer isFileAvailableByCard(@Bind("cardId") int cardId, @Bind("digest") String digest);
+
+ @Query("INSERT INTO LA_CARD_DATA_UPLOAD(CARD_DATA_ID_FK,CARD_DATA_UPLOAD_CONTENT_DIGEST_FK,ORIGINAL_NAME,DISPLAYED_NAME) VALUES (:cardData, :digest, :name, :displayName)")
+ int mapUploadContent(@Bind("cardData") int cardData, @Bind("digest") String digest, @Bind("name") String name,
+ @Bind("displayName") String displayName);
+
+ @Query("SELECT * FROM LA_CARD_DATA_UPLOAD_CONTENT_LIGHT WHERE CARD_DATA_CARD_ID_FK = :cardId")
+ List findAllFilesByCardId(@Bind("cardId") int cardId);
+
+ @Query("SELECT * FROM LA_CARD_DATA_UPLOAD_CONTENT_LIGHT WHERE CARD_DATA_ID = :cardDataId")
+ FileDataLight getUndeletedFileByCardDataId(@Bind("cardDataId") int cardDataId);
+
+ @Query("SELECT DIGEST,SIZE,CONTENT_TYPE FROM LA_CARD_DATA_UPLOAD_CONTENT")
+ List findAllDataUploadContentInfo();
+
+ @Query(type = QueryType.TEMPLATE, value = "SELECT CONTENT, CONTENT_TYPE FROM LA_CARD_DATA_UPLOAD_CONTENT WHERE DIGEST = :digest")
+ String fileContent();
+
+}
diff --git a/src/main/java/io/lavagna/query/CardLabelQuery.java b/src/main/java/io/lavagna/query/CardLabelQuery.java
new file mode 100644
index 000000000..a47329aab
--- /dev/null
+++ b/src/main/java/io/lavagna/query/CardLabelQuery.java
@@ -0,0 +1,186 @@
+/**
+ * This file is part of lavagna.
+ *
+ * lavagna is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * lavagna is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with lavagna. If not, see .
+ */
+package io.lavagna.query;
+
+import io.lavagna.common.Bind;
+import io.lavagna.common.QueriesOverride;
+import io.lavagna.common.Query;
+import io.lavagna.common.QueryOverride;
+import io.lavagna.common.QueryRepository;
+import io.lavagna.common.QueryType;
+import io.lavagna.model.CardLabel;
+import io.lavagna.model.CardLabelValue;
+import io.lavagna.model.LabelAndValue;
+import io.lavagna.model.LabelListValue;
+
+import java.util.Date;
+import java.util.List;
+import java.util.Set;
+
+@QueryRepository
+public interface CardLabelQuery {
+
+ @Query("INSERT INTO LA_CARD_LABEL(CARD_LABEL_PROJECT_ID_FK, CARD_LABEL_UNIQUE, CARD_LABEL_TYPE, CARD_LABEL_DOMAIN, CARD_LABEL_NAME, CARD_LABEL_COLOR) VALUES (:projectId, :unique, :type, :domain, :name, :color)")
+ int addLabel(@Bind("projectId") int projectId, @Bind("unique") boolean unique, @Bind("type") String type,
+ @Bind("domain") String domain, @Bind("name") String name, @Bind("color") int color);
+
+ @Query("INSERT INTO LA_CARD_LABEL_LIST_VALUE(CARD_LABEL_ID_FK, CARD_LABEL_LIST_VALUE_ORDER, CARD_LABEL_LIST_VALUE) VALUES (:cardLabelId, (SELECT * FROM (SELECT COALESCE(MAX(CARD_LABEL_LIST_VALUE_ORDER),0) + 1 FROM LA_CARD_LABEL_LIST_VALUE WHERE CARD_LABEL_ID_FK = :cardLabelId) MAX_ORDER), :value)")
+ int addLabelListValue(@Bind("cardLabelId") int cardLabelId, @Bind("value") String value);
+
+ @Query("UPDATE LA_CARD_LABEL_LIST_VALUE SET CARD_LABEL_LIST_VALUE = :value WHERE CARD_LABEL_LIST_VALUE_ID = :id")
+ int updateLabelListValue(@Bind("id") int id, @Bind("value") String value);
+
+ @Query("SELECT COUNT(CARD_LABEL_VALUE_ID) FROM LA_CARD_LABEL_VALUE WHERE CARD_LABEL_ID_FK = :labelId")
+ Integer labelUsedCount(@Bind("labelId") int labelId);
+
+ @Query("INSERT INTO LA_CARD_LABEL(CARD_LABEL_PROJECT_ID_FK, CARD_LABEL_UNIQUE, CARD_LABEL_TYPE, CARD_LABEL_DOMAIN, CARD_LABEL_NAME, CARD_LABEL_COLOR) VALUES "
+ + " (:projectId, FALSE, 'USER', 'SYSTEM', 'ASSIGNED', 0), "
+ + " (:projectId, TRUE, 'TIMESTAMP', 'SYSTEM', 'DUE_DATE', 0), "
+ + " (:projectId, TRUE, 'LIST', 'SYSTEM', 'MILESTONE', 0), "
+ + " (:projectId, FALSE, 'USER', 'SYSTEM', 'WATCHED_BY', 0)")
+ int addSystemLabels(@Bind("projectId") int projectId);
+
+ @Query("SELECT * FROM LA_CARD_LABEL WHERE CARD_LABEL_PROJECT_ID_FK = :projectId")
+ List findLabelsByProject(@Bind("projectId") int projectId);
+
+ @Query("SELECT * FROM LA_CARD_LABEL WHERE CARD_LABEL_ID = :labelId")
+ CardLabel findLabelById(@Bind("labelId") int labelId);
+
+ @Query("SELECT * FROM LA_CARD_LABEL WHERE CARD_LABEL_NAME = :labelName AND CARD_LABEL_DOMAIN = :labelDomain AND CARD_LABEL_PROJECT_ID_FK = :projectId")
+ CardLabel findLabelByName(@Bind("projectId") int projectId, @Bind("labelName") String labelName,
+ @Bind("labelDomain") String labelDomain);
+
+ @Query("SELECT * FROM LA_CARD_LABEL WHERE CARD_LABEL_NAME = :labelName AND CARD_LABEL_DOMAIN = :labelDomain AND CARD_LABEL_PROJECT_ID_FK = :projectId")
+ List findLabelsByName(@Bind("projectId") int projectId, @Bind("labelName") String labelName,
+ @Bind("labelDomain") String labelDomain);
+
+ @Query("SELECT * FROM LA_CARD_LABEL_VALUE WHERE CARD_LABEL_VALUE_ID = :labelValueId")
+ CardLabelValue findLabelValueById(@Bind("labelValueId") int labelValueId);
+
+ @Query("SELECT * FROM LA_CARD_LABEL_LIST_VALUE WHERE CARD_LABEL_ID_FK = :labelId ORDER BY CARD_LABEL_LIST_VALUE_ORDER")
+ List findListValuesByLabelId(@Bind("labelId") int labelId);
+
+ @Query("SELECT * FROM LA_CARD_LABEL_LIST_VALUE WHERE CARD_LABEL_ID_FK = :labelId AND CARD_LABEL_LIST_VALUE = :value ORDER BY CARD_LABEL_LIST_VALUE_ORDER")
+ List findListValuesByLabelIdAndValue(@Bind("labelId") int labelId, @Bind("value") String value);
+
+ @Query("SELECT * FROM LA_CARD_LABEL_LIST_VALUE WHERE CARD_LABEL_LIST_VALUE_ID = :labelListValueId")
+ LabelListValue findListValueById(@Bind("labelListValueId") int labelListValueId);
+
+ @Query("UPDATE LA_CARD_LABEL SET CARD_LABEL_NAME = :name, CARD_LABEL_COLOR = :color, CARD_LABEL_TYPE = :type WHERE CARD_LABEL_ID = :cardLabelId")
+ int updateLabel(@Bind("name") String name, @Bind("color") int color, @Bind("type") String type,
+ @Bind("cardLabelId") int cardLabelId);
+
+ @Query("DELETE FROM LA_CARD_LABEL WHERE CARD_LABEL_ID = :labelId")
+ int removeLabel(@Bind("labelId") int labelId);
+
+ @Query("DELETE FROM LA_CARD_LABEL_LIST_VALUE WHERE CARD_LABEL_ID_FK = :labelId")
+ int removeLabelListValues(@Bind("labelId") int labelId);
+
+ @Query("INSERT INTO LA_CARD_LABEL_VALUE(CARD_ID_FK, CARD_LABEL_VALUE_USE_UNIQUE_INDEX, CARD_LABEL_ID_FK, CARD_LABEL_VALUE_TYPE, CARD_LABEL_VALUE_STRING, CARD_LABEL_VALUE_TIMESTAMP, CARD_LABEL_VALUE_INT, CARD_LABEL_VALUE_CARD_FK, CARD_LABEL_VALUE_USER_FK, CARD_LABEL_VALUE_LIST_VALUE_FK) VALUES (:cardId, :useUniqueIndex, :labelId, :valueType, :valueString, :valueTimestamp, :valueInt, :valueCard, :valueUser, :valueList)")
+ int addLabelValueToCard(@Bind("cardId") int cardId, @Bind("useUniqueIndex") Boolean useUniqueIndex,
+ @Bind("labelId") int labelId, @Bind("valueType") String valueType, @Bind("valueString") String valueString,
+ @Bind("valueTimestamp") Date valueTimestamp, @Bind("valueInt") Integer valueInt,
+ @Bind("valueCard") Integer valueCard, @Bind("valueUser") Integer valueUser,
+ @Bind("valueList") Integer valueList);
+
+ @Query("DELETE FROM LA_CARD_LABEL_VALUE WHERE CARD_LABEL_VALUE_ID = :cardLabelValueId")
+ int removeLabelValue(@Bind("cardLabelValueId") int cardLabelValueId);
+
+ @Query("DELETE FROM LA_CARD_LABEL_LIST_VALUE WHERE CARD_LABEL_LIST_VALUE_ID = :labelListValueId")
+ int removeLabelListValue(@Bind("labelListValueId") int labelListValueId);
+
+ @Query("SELECT CARD_LABEL_ID, CARD_LABEL_PROJECT_ID_FK, CARD_LABEL_UNIQUE, CARD_LABEL_TYPE, CARD_LABEL_DOMAIN, CARD_LABEL_NAME, CARD_LABEL_COLOR, "
+ + " CARD_LABEL_VALUE_ID, CARD_LABEL_VALUE_USE_UNIQUE_INDEX, CARD_ID_FK, CARD_LABEL_ID_FK, CARD_LABEL_VALUE_TYPE, CARD_LABEL_VALUE_STRING, "
+ + " CARD_LABEL_VALUE_TIMESTAMP, CARD_LABEL_VALUE_INT, CARD_LABEL_VALUE_CARD_FK, CARD_LABEL_VALUE_USER_FK, CARD_LABEL_VALUE_LIST_VALUE_FK "
+ + " FROM LA_CARD_LABEL_VALUE INNER JOIN LA_CARD_LABEL ON LA_CARD_LABEL.CARD_LABEL_ID = LA_CARD_LABEL_VALUE.CARD_LABEL_ID_FK WHERE CARD_ID_FK = :cardId AND CARD_LABEL_VALUE_DELETED = FALSE")
+ List findCardLabelValuesByCardId(@Bind("cardId") int cardId);
+
+ @Query("SELECT CARD_LABEL_ID, CARD_LABEL_PROJECT_ID_FK, CARD_LABEL_UNIQUE, CARD_LABEL_TYPE, CARD_LABEL_DOMAIN, CARD_LABEL_NAME, CARD_LABEL_COLOR, "
+ + "CARD_LABEL_VALUE_ID, CARD_LABEL_VALUE_USE_UNIQUE_INDEX, CARD_ID_FK, CARD_LABEL_ID_FK, CARD_LABEL_VALUE_TYPE, CARD_LABEL_VALUE_STRING, "
+ + "CARD_LABEL_VALUE_TIMESTAMP, CARD_LABEL_VALUE_INT, CARD_LABEL_VALUE_CARD_FK, CARD_LABEL_VALUE_USER_FK, CARD_LABEL_VALUE_LIST_VALUE_FK "
+ + "FROM LA_CARD_LABEL_VALUE INNER JOIN LA_CARD_LABEL ON LA_CARD_LABEL.CARD_LABEL_ID = LA_CARD_LABEL_VALUE.CARD_LABEL_ID_FK WHERE CARD_ID_FK in (:ids) AND CARD_LABEL_VALUE_DELETED = FALSE")
+ List findCardLabelValuesByCardIds(@Bind("ids") List ids);
+
+ @Query("SELECT CARD_LABEL_ID, CARD_LABEL_PROJECT_ID_FK, CARD_LABEL_UNIQUE, CARD_LABEL_TYPE, CARD_LABEL_DOMAIN, CARD_LABEL_NAME, CARD_LABEL_COLOR, "
+ + "CARD_LABEL_VALUE_ID, CARD_LABEL_VALUE_USE_UNIQUE_INDEX, CARD_ID_FK, CARD_LABEL_ID_FK, CARD_LABEL_VALUE_TYPE, CARD_LABEL_VALUE_STRING, "
+ + "CARD_LABEL_VALUE_TIMESTAMP, CARD_LABEL_VALUE_INT, CARD_LABEL_VALUE_CARD_FK, CARD_LABEL_VALUE_USER_FK, CARD_LABEL_VALUE_LIST_VALUE_FK "
+ + "FROM LA_CARD_LABEL_VALUE INNER JOIN LA_CARD_LABEL ON LA_CARD_LABEL.CARD_LABEL_ID = LA_CARD_LABEL_VALUE.CARD_LABEL_ID_FK "
+ + "INNER JOIN LA_CARD ON LA_CARD_LABEL_VALUE.CARD_ID_FK = LA_CARD.CARD_ID "
+ + "INNER JOIN LA_BOARD_COLUMN ON CARD_BOARD_COLUMN_ID_FK = BOARD_COLUMN_ID "
+ + "WHERE BOARD_COLUMN_BOARD_ID_FK = :boardId AND CARD_LABEL_VALUE_DELETED = FALSE AND BOARD_COLUMN_LOCATION = :location")
+ List findCardLabelValuesByBoardId(@Bind("boardId") int boardId, @Bind("location") String location);
+
+ @Query("SELECT * FROM LA_CARD_LABEL WHERE CARD_LABEL_ID = IDENTITY()")
+ @QueriesOverride({
+ @QueryOverride(db = DB.MYSQL, value = "SELECT * FROM LA_CARD_LABEL WHERE CARD_LABEL_ID = LAST_INSERT_ID()"),
+ @QueryOverride(db = DB.PGSQL, value = "SELECT * FROM LA_CARD_LABEL WHERE CARD_LABEL_ID = (SELECT CURRVAL(pg_get_serial_sequence('la_card_label','card_label_id')))") })
+ CardLabel findLastCreatedLabel();
+
+ @Query("SELECT * FROM LA_CARD_LABEL_VALUE WHERE CARD_LABEL_VALUE_ID = IDENTITY()")
+ @QueriesOverride({
+ @QueryOverride(db = DB.MYSQL, value = "SELECT * FROM LA_CARD_LABEL_VALUE WHERE CARD_LABEL_VALUE_ID = LAST_INSERT_ID()"),
+ @QueryOverride(db = DB.PGSQL, value = "SELECT * FROM LA_CARD_LABEL_VALUE WHERE CARD_LABEL_VALUE_ID = (SELECT CURRVAL(pg_get_serial_sequence('la_card_label_value','card_label_value_id')))") })
+ CardLabelValue findLastCreatedLabelValue();
+
+ @Query("SELECT * FROM LA_CARD_LABEL_LIST_VALUE WHERE CARD_LABEL_LIST_VALUE_ID = IDENTITY()")
+ @QueriesOverride({
+ @QueryOverride(db = DB.MYSQL, value = "SELECT * FROM LA_CARD_LABEL_LIST_VALUE WHERE CARD_LABEL_LIST_VALUE_ID = LAST_INSERT_ID()"),
+ @QueryOverride(db = DB.PGSQL, value = "SELECT * FROM LA_CARD_LABEL_LIST_VALUE WHERE CARD_LABEL_LIST_VALUE_ID = (SELECT CURRVAL(pg_get_serial_sequence('la_card_label_list_value','card_label_list_value_id')))") })
+ LabelListValue findLastCreatedLabelListValue();
+
+ @Query(type = QueryType.TEMPLATE, value = "UPDATE LA_CARD_LABEL_LIST_VALUE SET CARD_LABEL_LIST_VALUE_ORDER = :order WHERE CARD_LABEL_LIST_VALUE_ID = :id")
+ String swapLabelListValues();
+
+ @Query("SELECT DISTINCT CARD_LABEL_LIST_VALUE FROM LA_CARD_LABEL_LIST_VALUE INNER JOIN LA_CARD_LABEL ON CARD_LABEL_ID_FK = CARD_LABEL_ID "
+ + " WHERE CARD_LABEL_DOMAIN = :domain AND CARD_LABEL_NAME = :labelName AND CARD_LABEL_LIST_VALUE LIKE CONCAT(:term, '%') ORDER BY CARD_LABEL_LIST_VALUE ASC LIMIT 20")
+ List findListValuesBy(@Bind("domain") String domain, @Bind("labelName") String labelName, @Bind("term") String term);
+
+ @Query("SELECT DISTINCT CARD_LABEL_LIST_VALUE FROM LA_CARD_LABEL_LIST_VALUE INNER JOIN LA_CARD_LABEL ON CARD_LABEL_ID_FK = CARD_LABEL_ID "
+ + " WHERE CARD_LABEL_PROJECT_ID_FK IN (:projectIdFilter) AND CARD_LABEL_DOMAIN = :domain AND CARD_LABEL_NAME = :labelName AND CARD_LABEL_LIST_VALUE LIKE CONCAT(:term, '%') ORDER BY CARD_LABEL_LIST_VALUE ASC LIMIT 20")
+ List findListValuesBy(@Bind("domain") String domain, @Bind("labelName") String labelName, @Bind("term") String term, @Bind("projectIdFilter") Set projectIdFilter);
+
+ @Query("SELECT DISTINCT CARD_LABEL_NAME FROM LA_CARD_LABEL "
+ + " WHERE CARD_LABEL_DOMAIN = 'USER' AND LOWER(CARD_LABEL_NAME) LIKE CONCAT(LOWER(:term), '%') ORDER BY CARD_LABEL_NAME ASC LIMIT 20")
+ List findUserLabelNameBy(@Bind("term") String term);
+
+ @Query("SELECT DISTINCT CARD_LABEL_NAME FROM LA_CARD_LABEL "
+ + " WHERE CARD_LABEL_PROJECT_ID_FK IN (:projectIdFilter) AND CARD_LABEL_DOMAIN = 'USER' AND LOWER(CARD_LABEL_NAME) LIKE CONCAT(LOWER(:term), '%') ORDER BY CARD_LABEL_NAME ASC LIMIT 20")
+ List findUserLabelNameBy(@Bind("term") String term, @Bind("projectIdFilter") Set projectIdFilter);
+
+ @Query(type = QueryType.TEMPLATE, value = "SELECT CARD_LABEL_LIST_VALUE, CARD_LABEL_ID_FK, CARD_LABEL_LIST_VALUE_ID FROM LA_CARD_LABEL_LIST_VALUE WHERE CARD_LABEL_LIST_VALUE IN (:values)")
+ String findLabelListValueMapping();
+
+ @Query("SELECT * FROM LA_CARD_LABEL_VALUE WHERE CARD_ID_FK = :cardId AND CARD_LABEL_ID_FK = :labelId AND "
+ + " (CARD_LABEL_VALUE_STRING = :valueString OR (CARD_LABEL_VALUE_STRING IS NULL AND :valueString IS NULL)) AND "
+ + " (CARD_LABEL_VALUE_TIMESTAMP = :valueTimestamp OR (CARD_LABEL_VALUE_TIMESTAMP IS NULL AND :valueTimestamp IS NULL)) AND "
+ + " (CARD_LABEL_VALUE_INT = :valueInt OR (CARD_LABEL_VALUE_INT IS NULL AND :valueInt IS NULL)) AND "
+ + " (CARD_LABEL_VALUE_CARD_FK = :valueCard OR (CARD_LABEL_VALUE_CARD_FK IS NULL AND :valueCard IS NULL)) AND "
+ + " (CARD_LABEL_VALUE_USER_FK = :valueUser OR (CARD_LABEL_VALUE_USER_FK IS NULL AND :valueUser IS NULL)) AND "
+ + " (CARD_LABEL_VALUE_LIST_VALUE_FK = :valueList OR (CARD_LABEL_VALUE_LIST_VALUE_FK IS NULL AND :valueList IS NULL))")
+ // pgsql need to have a typecast...
+ @QueriesOverride({ @QueryOverride(db = DB.PGSQL, value = "SELECT * FROM LA_CARD_LABEL_VALUE WHERE CARD_ID_FK = :cardId AND CARD_LABEL_ID_FK = :labelId AND "
+ + " (CARD_LABEL_VALUE_STRING = :valueString OR (CARD_LABEL_VALUE_STRING IS NULL AND :valueString IS NULL)) AND "
+ + " (CARD_LABEL_VALUE_TIMESTAMP = :valueTimestamp OR (CARD_LABEL_VALUE_TIMESTAMP IS NULL AND :valueTimestamp::timestamp IS NULL)) AND "
+ + " (CARD_LABEL_VALUE_INT = :valueInt OR (CARD_LABEL_VALUE_INT IS NULL AND :valueInt IS NULL)) AND "
+ + " (CARD_LABEL_VALUE_CARD_FK = :valueCard OR (CARD_LABEL_VALUE_CARD_FK IS NULL AND :valueCard IS NULL)) AND "
+ + " (CARD_LABEL_VALUE_USER_FK = :valueUser OR (CARD_LABEL_VALUE_USER_FK IS NULL AND :valueUser IS NULL)) AND "
+ + " (CARD_LABEL_VALUE_LIST_VALUE_FK = :valueList OR (CARD_LABEL_VALUE_LIST_VALUE_FK IS NULL AND :valueList IS NULL))") })
+ List findLabelValueByLabelAndValue(@Bind("cardId") int cardId, @Bind("labelId") int labelId,
+ @Bind("valueString") String valueString, @Bind("valueTimestamp") Date valueTimestamp,
+ @Bind("valueInt") Integer valueInt, @Bind("valueCard") Integer valueCard,
+ @Bind("valueUser") Integer valueUser, @Bind("valueList") Integer valueList);
+}
diff --git a/src/main/java/io/lavagna/query/CardQuery.java b/src/main/java/io/lavagna/query/CardQuery.java
new file mode 100644
index 000000000..3257dd9f3
--- /dev/null
+++ b/src/main/java/io/lavagna/query/CardQuery.java
@@ -0,0 +1,161 @@
+/**
+ * This file is part of lavagna.
+ *
+ * lavagna is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * lavagna is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with lavagna. If not, see .
+ */
+package io.lavagna.query;
+
+import io.lavagna.common.Bind;
+import io.lavagna.common.QueriesOverride;
+import io.lavagna.common.Query;
+import io.lavagna.common.QueryOverride;
+import io.lavagna.common.QueryRepository;
+import io.lavagna.common.QueryType;
+import io.lavagna.model.Card;
+import io.lavagna.model.CardFull;
+import io.lavagna.model.Event;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Set;
+
+@QueryRepository
+public interface CardQuery {
+
+ @Query("INSERT INTO LA_CARD(CARD_NAME, CARD_BOARD_COLUMN_ID_FK, CARD_ORDER, CARD_USER_ID_FK, CARD_SEQ_NUMBER, CARD_LAST_UPDATED, CARD_LAST_UPDATED_USER_ID_FK) VALUES "
+ + " (:name, :columnId, (SELECT * FROM (SELECT COALESCE(MAX(CARD_ORDER), 0) + 1 FROM LA_CARD WHERE CARD_BOARD_COLUMN_ID_FK = :columnId) AS MAX_CARD_ORDER), :userId, :cardSequence, NOW(), :userId)")
+ int createCard(@Bind("name") String name, @Bind("columnId") int columnId, @Bind("userId") int userId,
+ @Bind("cardSequence") int cardSequence);
+
+ @Query("SELECT CARD_ID, CARD_NAME, CARD_BOARD_COLUMN_ID_FK, CARD_USER_ID_FK, CARD_ORDER, CARD_SEQ_NUMBER FROM LA_CARD_WITH_BOARD_ID WHERE BOARD_ID = :boardId AND "
+ + " BOARD_COLUMN_LOCATION = :location " + " ORDER BY CARD_ORDER ASC, CARD_NAME ASC")
+ List findAllByBoardIdAndLocation(@Bind("boardId") int boardId, @Bind("location") String location);
+
+ @Query("SELECT CARD_ID FROM LA_CARD WHERE CARD_ID IN (:cardIds) AND CARD_BOARD_COLUMN_ID_FK = :columnId")
+ List findCardIdsInColumnId(@Bind("cardIds") List cardIds, @Bind("columnId") int columnId);
+
+ @Query("SELECT * FROM LA_CARD_FULL WHERE BOARD_SHORT_NAME = :boardShortName")
+ List findAllByBoardShortName(@Bind("boardShortName") String boardShortName);
+
+ @Query("SELECT * FROM LA_CARD_FULL WHERE (LOWER(CARD_NAME) LIKE CONCAT('%', CONCAT(LOWER(:term), '%')) OR CARD_SEQ_NUMBER LIKE CONCAT(:term, '%')) AND PROJECT_ID IN (:projectIdFilter) ORDER BY BOARD_SHORT_NAME ASC, CARD_SEQ_NUMBER ASC LIMIT 10")
+ @QueriesOverride({
+ @QueryOverride(db = DB.PGSQL, value = "SELECT * FROM LA_CARD_FULL WHERE (LOWER(CARD_NAME) LIKE CONCAT('%', CONCAT(LOWER(:term), '%')) OR CAST(CARD_SEQ_NUMBER AS TEXT) LIKE CONCAT(:term, '%')) AND PROJECT_ID IN (:projectIdFilter) ORDER BY BOARD_SHORT_NAME ASC, CARD_SEQ_NUMBER ASC LIMIT 10")
+ })
+ List findCardBy(@Bind("term") String term, @Bind("projectIdFilter") Set projectIdFilter);
+
+ @Query("SELECT * FROM LA_CARD_FULL WHERE (LOWER(CARD_NAME) LIKE CONCAT('%', CONCAT(LOWER(:term), '%')) OR CARD_SEQ_NUMBER LIKE CONCAT(:term, '%')) ORDER BY BOARD_SHORT_NAME ASC, CARD_SEQ_NUMBER ASC LIMIT 10")
+ @QueriesOverride({
+ @QueryOverride(db = DB.PGSQL, value = "SELECT * FROM LA_CARD_FULL WHERE (LOWER(CARD_NAME) LIKE CONCAT('%', CONCAT(LOWER(:term), '%')) OR CAST(CARD_SEQ_NUMBER AS TEXT) LIKE CONCAT(:term, '%')) ORDER BY BOARD_SHORT_NAME ASC, CARD_SEQ_NUMBER ASC LIMIT 10")
+ })
+ List findCardBy(@Bind("term") String term);
+
+ @Query("SELECT CARD_ID, CARD_NAME, CARD_BOARD_COLUMN_ID_FK, CARD_ORDER, CARD_SEQ_NUMBER, CARD_USER_ID_FK FROM LA_CARD "
+ + " INNER JOIN LA_BOARD_COLUMN ON CARD_BOARD_COLUMN_ID_FK = BOARD_COLUMN_ID "
+ + " WHERE "
+ + " BOARD_COLUMN_BOARD_ID_FK = :boardId AND "
+ + " BOARD_COLUMN_LOCATION = :location "
+ + " ORDER BY CARD_LAST_UPDATED DESC " + " LIMIT :amount OFFSET :offset ")
+ List fetchPaginatedByBoardIdAndLocation(@Bind("boardId") int boardId, @Bind("location") String location,
+ @Bind("amount") int amount, @Bind("offset") int offset);
+
+ @Query("SELECT * FROM LA_CARD_FULL WHERE CARD_BOARD_COLUMN_ID_FK = :columnId ORDER BY CARD_ORDER ASC, CARD_NAME ASC")
+ List findAllFullByColumnId(@Bind("columnId") int columnId);
+
+ @Query("SELECT * FROM LA_CARD_FULL WHERE CARD_ID IN (:ids)")
+ List findAllByIds(@Bind("ids") Collection ids);
+
+ @Query("SELECT CARD_ID, CARD_NAME, CARD_SEQ_NUMBER, CARD_ORDER, CARD_BOARD_COLUMN_ID_FK, CREATE_USER, "
+ + "CREATE_TIME, LAST_UPDATE_USER, LAST_UPDATE_TIME, BOARD_COLUMN_DEFINITION_VALUE, BOARD_SHORT_NAME, PROJECT_SHORT_NAME FROM LA_ASSIGNED_CARD "
+ + "INNER JOIN LA_CARD_FULL ON ASSIGNED_CARD_ID = CARD_ID "
+ + "WHERE ASSIGNED_USER_ID = :userId AND BOARD_COLUMN_DEFINITION_VALUE = ASSIGNED_CARD_STATUS AND BOARD_COLUMN_DEFINITION_VALUE = 'OPEN' "
+ + "ORDER BY ASSIGNED_EVENT_TIME DESC LIMIT :amount OFFSET :offset")
+ List fetchAllOpenCardsByUserId(@Bind("userId") int userId, @Bind("amount") int amount,
+ @Bind("offset") int offset);
+
+ @Query("SELECT CARD_ID, CARD_NAME, CARD_SEQ_NUMBER, CARD_ORDER, CARD_BOARD_COLUMN_ID_FK, CREATE_USER, "
+ + " CREATE_TIME, LAST_UPDATE_USER, LAST_UPDATE_TIME, BOARD_COLUMN_DEFINITION_VALUE, BOARD_SHORT_NAME, PROJECT_SHORT_NAME FROM LA_ASSIGNED_CARD "
+ + " INNER JOIN LA_CARD_FULL ON ASSIGNED_CARD_ID = CARD_ID "
+ + " WHERE ASSIGNED_USER_ID = :userId AND BOARD_COLUMN_DEFINITION_VALUE = ASSIGNED_CARD_STATUS AND BOARD_COLUMN_DEFINITION_VALUE = 'OPEN' "
+ + " AND PROJECT_SHORT_NAME = :projectShortName ORDER BY ASSIGNED_EVENT_TIME DESC LIMIT :amount OFFSET :offset")
+ List fetchAllOpenCardsByProjectIdAndUserId(@Bind("userId") int userId,
+ @Bind("projectShortName") String projectShortName, @Bind("amount") int amount, @Bind("offset") int offset);
+
+ @Query("SELECT COUNT(LA_ASSIGNED_CARD.ASSIGNED_CARD_ID) FROM LA_ASSIGNED_CARD WHERE ASSIGNED_USER_ID = :userId AND ASSIGNED_CARD_STATUS = 'OPEN'")
+ Integer getOpenCardsCountByUserId(@Bind("userId") int userId);
+
+ @Query("SELECT COUNT(LA_ASSIGNED_CARD_PROJECT.ASSIGNED_CARD_ID) FROM LA_ASSIGNED_CARD_PROJECT WHERE ASSIGNED_USER_ID = :userId AND ASSIGNED_CARD_STATUS = 'OPEN' AND ASSIGNED_PROJECT_SHORT_NAME = :projectShortName")
+ Integer getOpenCardsCountByProjectAndUserId(@Bind("projectShortName") String projectShortName,
+ @Bind("userId") int userId);
+
+ @Query("SELECT CARD_ID, CARD_NAME,CARD_BOARD_COLUMN_ID_FK, CARD_ORDER, CARD_USER_ID_FK, CARD_SEQ_NUMBER FROM LA_CARD_WITH_BOARD_ID WHERE BOARD_ID = :boardId AND CARD_NAME LIKE CONCAT('%', :criteria,'%') ORDER BY CARD_NAME")
+ List findCards(@Bind("boardId") int boardId, @Bind("criteria") String criteria);
+
+ @Query("SELECT CARD_ID, CARD_NAME, CARD_BOARD_COLUMN_ID_FK, CARD_ORDER, CARD_USER_ID_FK, CARD_SEQ_NUMBER FROM LA_CARD WHERE CARD_ID = :cardId")
+ Card findBy(@Bind("cardId") int cardId);
+
+ @Query("SELECT CARD_ID, CARD_NAME, CARD_SEQ_NUMBER, CARD_ORDER, CARD_BOARD_COLUMN_ID_FK, CREATE_USER, CREATE_TIME, LAST_UPDATE_USER, LAST_UPDATE_TIME, BOARD_COLUMN_DEFINITION_VALUE, BOARD_SHORT_NAME, PROJECT_SHORT_NAME FROM LA_CARD_FULL WHERE CARD_ID = :cardId")
+ CardFull findFullBy(@Bind("cardId") int cardId);
+
+ @Query("SELECT CARD_ID, CARD_NAME, CARD_SEQ_NUMBER, CARD_ORDER, CARD_BOARD_COLUMN_ID_FK, CREATE_USER, CREATE_TIME, LAST_UPDATE_USER, LAST_UPDATE_TIME, BOARD_COLUMN_DEFINITION_VALUE, BOARD_SHORT_NAME, PROJECT_SHORT_NAME FROM LA_CARD_FULL "
+ + " WHERE CARD_SEQ_NUMBER = :seqNumber AND BOARD_SHORT_NAME = :shortName")
+ CardFull findFullBy(@Bind("shortName") String shortName, @Bind("seqNumber") int seqNumber);
+
+ @Query("SELECT CARD_ID FROM LA_CARD "
+ + " INNER JOIN LA_BOARD_COLUMN ON LA_BOARD_COLUMN.BOARD_COLUMN_ID = LA_CARD.CARD_BOARD_COLUMN_ID_FK "
+ + " INNER JOIN LA_BOARD ON LA_BOARD.BOARD_ID = LA_BOARD_COLUMN.BOARD_COLUMN_BOARD_ID_FK WHERE "
+ + " LA_CARD.CARD_SEQ_NUMBER = :seqNumber AND LA_BOARD.BOARD_SHORT_NAME = :shortName")
+ Integer findCardIdByBoardNameAndSeq(@Bind("shortName") String shortName, @Bind("seqNumber") int seqNumber);
+
+
+ @Query("SELECT COUNT(CARD_ID) FROM LA_CARD "
+ + " INNER JOIN LA_BOARD_COLUMN ON LA_BOARD_COLUMN.BOARD_COLUMN_ID = LA_CARD.CARD_BOARD_COLUMN_ID_FK "
+ + " INNER JOIN LA_BOARD ON LA_BOARD.BOARD_ID = LA_BOARD_COLUMN.BOARD_COLUMN_BOARD_ID_FK WHERE "
+ + " LA_CARD.CARD_SEQ_NUMBER = :seqNumber AND LA_BOARD.BOARD_SHORT_NAME = :shortName")
+ Integer countCardIdByBoardNameAndSeq(@Bind("shortName") String shortName, @Bind("seqNumber") int seqNumber);
+
+ @Query("UPDATE LA_CARD SET CARD_NAME = :name WHERE CARD_ID = :cardId")
+ int updateCard(@Bind("name") String name, @Bind("cardId") int cardId);
+
+ @Query("SELECT CARD_ID, CARD_NAME, CARD_BOARD_COLUMN_ID_FK, CARD_ORDER, CARD_USER_ID_FK, CARD_SEQ_NUMBER FROM LA_CARD WHERE CARD_ID = IDENTITY()")
+ @QueriesOverride({
+ @QueryOverride(db = DB.MYSQL, value = "SELECT CARD_ID, CARD_NAME, CARD_BOARD_COLUMN_ID_FK, CARD_ORDER, CARD_USER_ID_FK, CARD_SEQ_NUMBER FROM LA_CARD WHERE CARD_ID = LAST_INSERT_ID()"),
+ @QueryOverride(db = DB.PGSQL, value = "SELECT CARD_ID, CARD_NAME, CARD_BOARD_COLUMN_ID_FK, CARD_ORDER, CARD_USER_ID_FK, CARD_SEQ_NUMBER FROM LA_CARD WHERE CARD_ID = (SELECT CURRVAL(pg_get_serial_sequence('la_card','card_id')))") })
+ Card findLastCreatedCard();
+
+ @Query(type = QueryType.TEMPLATE, value = "UPDATE LA_CARD SET CARD_BOARD_COLUMN_ID_FK = :columnId WHERE CARD_ID = :cardId AND CARD_BOARD_COLUMN_ID_FK = :previousColumnId")
+ String moveCardToColumn();
+
+ @Query(type = QueryType.TEMPLATE, value = "UPDATE LA_CARD SET CARD_ORDER = :cardOrder WHERE CARD_ID = :cardId AND CARD_BOARD_COLUMN_ID_FK = :columnId")
+ String updateCardOrder();
+
+ @Query("UPDATE LA_CARD SET CARD_ORDER = :order WHERE CARD_ID = :cardId")
+ int updateCardOrder(@Bind("cardId") int cardId, @Bind("order") int order);
+
+ @Query("UPDATE LA_CARD SET CARD_ORDER = CARD_ORDER + 1 WHERE CARD_BOARD_COLUMN_ID_FK = :columnId")
+ int incrementCardsOrder(@Bind("columnId") int columnId);
+
+ @Query("SELECT BOARD_COUNTER_CARD_SEQUENCE FROM LA_BOARD_COUNTER WHERE BOARD_COUNTER_ID_FK = (SELECT BOARD_COLUMN_BOARD_ID_FK FROM LA_BOARD_COLUMN WHERE BOARD_COLUMN_ID = :columnId) FOR UPDATE")
+ Integer fetchAndLockCardSequence(@Bind("columnId") int columnId);
+
+ @Query("UPDATE LA_BOARD_COUNTER SET BOARD_COUNTER_CARD_SEQUENCE = BOARD_COUNTER_CARD_SEQUENCE + 1 WHERE BOARD_COUNTER_CARD_SEQUENCE = :expectedSequenceValue AND BOARD_COUNTER_ID_FK = (SELECT BOARD_COLUMN_BOARD_ID_FK FROM LA_BOARD_COLUMN WHERE BOARD_COLUMN_ID = :columnId)")
+ int incrementSequence(@Bind("expectedSequenceValue") int expectedSequenceValue, @Bind("columnId") int columnId);
+
+ @Query("SELECT * FROM LA_EVENT WHERE EVENT_CARD_ID_FK = :cardId ORDER BY EVENT_TIME DESC")
+ List fetchAllActivityByCardId(@Bind("cardId") int cardId);
+
+ @Query(type = QueryType.TEMPLATE, value = "SELECT CONCAT(CONCAT(BOARD_SHORT_NAME, '-'), CARD_SEQ_NUMBER) AS CARD_IDENTIFIER, CARD_ID FROM LA_CARD "
+ + " INNER JOIN LA_BOARD_COLUMN ON CARD_BOARD_COLUMN_ID_FK = BOARD_COLUMN_ID "
+ + " INNER JOIN LA_BOARD ON BOARD_COLUMN_BOARD_ID_FK = BOARD_ID WHERE (BOARD_SHORT_NAME, CARD_SEQ_NUMBER) IN (:projShortNameAndCardSeq)")
+ String findCardsIs();
+}
diff --git a/src/main/java/io/lavagna/query/ConfigurationQuery.java b/src/main/java/io/lavagna/query/ConfigurationQuery.java
new file mode 100644
index 000000000..967c3e9ed
--- /dev/null
+++ b/src/main/java/io/lavagna/query/ConfigurationQuery.java
@@ -0,0 +1,50 @@
+/**
+ * This file is part of lavagna.
+ *
+ * lavagna is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * lavagna is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with lavagna. If not, see .
+ */
+package io.lavagna.query;
+
+import io.lavagna.common.Bind;
+import io.lavagna.common.Query;
+import io.lavagna.common.QueryRepository;
+import io.lavagna.model.ConfigurationKeyValue;
+
+import java.util.List;
+import java.util.Set;
+
+@QueryRepository
+public interface ConfigurationQuery {
+
+ @Query("SELECT COUNT(*) FROM LA_CONF WHERE CONF_KEY = :key")
+ Integer hasKeyDefined(@Bind("key") String key);
+
+ @Query("SELECT * FROM LA_CONF WHERE CONF_KEY IN (:keys)")
+ List findConfigurationFor(@Bind("keys") Set keys);
+
+ @Query("SELECT CONF_VALUE FROM LA_CONF WHERE CONF_KEY = :key")
+ List getValue(@Bind("key") String key);
+
+ @Query("INSERT INTO LA_CONF(CONF_KEY, CONF_VALUE) VALUES(:key, :value)")
+ int set(@Bind("key") String key, @Bind("value") String value);
+
+ @Query("UPDATE LA_CONF SET CONF_VALUE = :value WHERE CONF_KEY = :key")
+ int update(@Bind("key") String key, @Bind("value") String value);
+
+ @Query("SELECT * FROM LA_CONF ORDER BY CONF_KEY")
+ List findAll();
+
+ @Query("DELETE FROM LA_CONF WHERE CONF_KEY = :key")
+ int delete(@Bind("key") String key);
+}
diff --git a/src/main/java/io/lavagna/query/DB.java b/src/main/java/io/lavagna/query/DB.java
new file mode 100644
index 000000000..b22300987
--- /dev/null
+++ b/src/main/java/io/lavagna/query/DB.java
@@ -0,0 +1,22 @@
+/**
+ * This file is part of lavagna.
+ *
+ * lavagna is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * lavagna is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with lavagna. If not, see .
+ */
+package io.lavagna.query;
+
+public class DB {
+ public static final String MYSQL = "MYSQL";
+ public static final String PGSQL = "PGSQL";
+}
diff --git a/src/main/java/io/lavagna/query/EventQuery.java b/src/main/java/io/lavagna/query/EventQuery.java
new file mode 100644
index 000000000..523fce92d
--- /dev/null
+++ b/src/main/java/io/lavagna/query/EventQuery.java
@@ -0,0 +1,125 @@
+/**
+ * This file is part of lavagna.
+ *
+ * lavagna is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * lavagna is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with lavagna. If not, see .
+ */
+package io.lavagna.query;
+
+import io.lavagna.common.Bind;
+import io.lavagna.common.QueriesOverride;
+import io.lavagna.common.Query;
+import io.lavagna.common.QueryOverride;
+import io.lavagna.common.QueryRepository;
+import io.lavagna.common.QueryType;
+import io.lavagna.model.Event;
+import io.lavagna.model.EventsCount;
+
+import java.util.Collection;
+import java.util.Date;
+import java.util.List;
+
+@QueryRepository
+public interface EventQuery {
+
+ @Query("SELECT * FROM LA_EVENT WHERE EVENT_ID = IDENTITY()")
+ @QueriesOverride({
+ @QueryOverride(db = DB.MYSQL, value = "SELECT * FROM LA_EVENT WHERE EVENT_ID = LAST_INSERT_ID()"),
+ @QueryOverride(db = DB.PGSQL, value = "SELECT * FROM LA_EVENT WHERE EVENT_ID = (SELECT CURRVAL(pg_get_serial_sequence('la_event','event_id')))") })
+ Event findLastCreated();
+
+ @Query("SELECT * FROM LA_EVENT WHERE EVENT_ID = :id")
+ Event getById(@Bind("id") int id);
+
+ @Query("SELECT * FROM LA_EVENT ORDER BY EVENT_ID ASC LIMIT :amount OFFSET :offset ")
+ List find(@Bind("offset") int offset, @Bind("amount") int amount);
+
+ @Query("SELECT COUNT(EVENT_ID) FROM LA_EVENT")
+ Integer count();
+
+ @Query("SELECT * FROM LA_EVENT WHERE EVENT_CARD_DATA_ID_FK = :cardDataId AND EVENT_ID > :eventId AND EVENT_TYPE = :eventType ORDER BY EVENT_ID ASC LIMIT 1")
+ List findNextEventFor(@Bind("cardDataId") int cardDataId, @Bind("eventId") int eventId,
+ @Bind("eventType") String eventType);
+
+ @Query("INSERT INTO LA_EVENT(EVENT_LABEL_NAME, EVENT_LABEL_TYPE, EVENT_CARD_ID_FK, EVENT_USER_ID_FK, EVENT_TIME, EVENT_TYPE, EVENT_VALUE_INT, EVENT_VALUE_STRING, EVENT_VALUE_TIMESTAMP, EVENT_VALUE_CARD_FK, EVENT_VALUE_USER_FK) "
+ + " VALUES (:labelName, :labelType, :cardId, :userId, :now, :event, :valueInt, :valueString, :valueTimestamp, :valueCard, :valueUser)")
+ int insertLabelEvent(@Bind("labelName") String labelName, @Bind("labelType") String labelType,
+ @Bind("cardId") int cardId, @Bind("userId") int userId, @Bind("now") Date now, @Bind("event") String event,
+ @Bind("valueInt") Integer valueInt, @Bind("valueString") String valueString,
+ @Bind("valueTimestamp") Date valueTimestamp, @Bind("valueCard") Integer valueCard,
+ @Bind("valueUser") Integer valueUser);
+
+ @Query(type = QueryType.TEMPLATE, value = "INSERT INTO LA_EVENT(EVENT_CARD_ID_FK, EVENT_PREV_COLUMN_ID_FK, EVENT_COLUMN_ID_FK, EVENT_USER_ID_FK, EVENT_TIME, EVENT_TYPE, EVENT_VALUE_STRING) "
+ + " VALUES (:cardId, :previousColumnId, :columnId, :userId, :time, :event, :valueString)")
+ String insertCardEvent();
+
+ @Query("INSERT INTO LA_EVENT(EVENT_CARD_DATA_ID_FK, EVENT_CARD_ID_FK, EVENT_USER_ID_FK, EVENT_TIME, EVENT_TYPE, EVENT_PREV_CARD_DATA_ID_FK, EVENT_NEW_CARD_DATA_ID_FK) "
+ + " VALUES (:cardDataId, :cardId, :userId, :time, :event, :referenceId, :newReferenceId)")
+ int insertCardDataEvent(@Bind("cardDataId") int cardDataId, @Bind("cardId") int cardId, @Bind("userId") int userId,
+ @Bind("time") Date time, @Bind("event") String event, @Bind("referenceId") Integer referenceId,
+ @Bind("newReferenceId") Integer newReferenceId);
+
+ @Query("INSERT INTO LA_EVENT(EVENT_CARD_DATA_ID_FK, EVENT_CARD_ID_FK, EVENT_USER_ID_FK, EVENT_TIME, EVENT_TYPE, EVENT_PREV_CARD_DATA_ID_FK, EVENT_VALUE_STRING) "
+ + " VALUES (:cardDataId, :cardId, :userId, :time, :event, :referenceId, :name)")
+ int insertFileEvent(@Bind("cardDataId") int cardDataId, @Bind("cardId") int cardId, @Bind("userId") int userId,
+ @Bind("time") Date time, @Bind("event") String event, @Bind("referenceId") Integer referenceId,
+ @Bind("name") String name);
+
+ @Query("SELECT EVENT_USER_ID_FK FROM LA_EVENT WHERE EVENT_CARD_DATA_ID_FK = :cardDataId AND EVENT_TYPE = :event")
+ List findUsersIdForCardData(@Bind("cardDataId") int cardDataId, @Bind("event") String event);
+
+ @Query("DELETE FROM LA_EVENT WHERE EVENT_ID = :id AND EVENT_CARD_ID_FK = :cardId AND EVENT_TYPE = :event")
+ int remove(@Bind("id") int id, @Bind("cardId") int cardId, @Bind("event") String event);
+
+ @Query("SELECT EVENT_ID, EVENT_CARD_ID_FK, EVENT_USER_ID_FK, EVENT_TYPE, EVENT_TIME, EVENT_CARD_DATA_ID_FK, "
+ + " EVENT_PREV_CARD_DATA_ID_FK, EVENT_NEW_CARD_DATA_ID_FK, EVENT_COLUMN_ID_FK, EVENT_PREV_COLUMN_ID_FK, EVENT_LABEL_NAME, EVENT_LABEL_TYPE, EVENT_VALUE_INT, "
+ + " EVENT_VALUE_STRING, EVENT_VALUE_TIMESTAMP, EVENT_VALUE_CARD_FK, EVENT_VALUE_USER_FK "
+ + " FROM LA_EVENT INNER JOIN LA_CARD ON LA_EVENT.EVENT_CARD_ID_FK = LA_CARD.CARD_ID WHERE LA_CARD.CARD_USER_ID_FK = :user "
+ + " UNION "
+ + " SELECT EVENT_ID, EVENT_CARD_ID_FK, EVENT_USER_ID_FK, EVENT_TYPE, EVENT_TIME, EVENT_CARD_DATA_ID_FK, EVENT_PREV_CARD_DATA_ID_FK, EVENT_NEW_CARD_DATA_ID_FK, "
+ + " EVENT_COLUMN_ID_FK, EVENT_PREV_COLUMN_ID_FK, EVENT_LABEL_NAME, EVENT_LABEL_TYPE, EVENT_VALUE_INT, EVENT_VALUE_STRING, "
+ + " EVENT_VALUE_TIMESTAMP, EVENT_VALUE_CARD_FK, EVENT_VALUE_USER_FK "
+ + " FROM LA_EVENT INNER JOIN LA_CARD_LABEL_VALUE ON LA_EVENT.EVENT_CARD_ID_FK = LA_CARD_LABEL_VALUE.CARD_ID_FK "
+ + " WHERE LA_CARD_LABEL_VALUE.CARD_LABEL_VALUE_USER_FK = :user "
+ + " ORDER BY EVENT_TIME DESC LIMIT :amount OFFSET :offset ")
+ List getUserFeedByPage(@Bind("user") int user, @Bind("amount") int amount, @Bind("offset") int offset);
+
+ // profile
+
+ @Query("SELECT EVENT_ID, EVENT_CARD_ID_FK, EVENT_USER_ID_FK, EVENT_TYPE, EVENT_TIME, EVENT_CARD_DATA_ID_FK,"
+ + " EVENT_PREV_CARD_DATA_ID_FK, EVENT_NEW_CARD_DATA_ID_FK, EVENT_COLUMN_ID_FK, EVENT_PREV_COLUMN_ID_FK, EVENT_LABEL_NAME, EVENT_LABEL_TYPE, EVENT_VALUE_INT,"
+ + " EVENT_VALUE_STRING, EVENT_VALUE_TIMESTAMP, EVENT_VALUE_CARD_FK, EVENT_VALUE_USER_FK FROM LA_EVENT"
+ + " WHERE EVENT_USER_ID_FK = :userId ORDER BY EVENT_TIME DESC LIMIT :amount OFFSET :offset")
+ List getLatestActivityByPage(@Bind("userId") int user, @Bind("amount") int amount, @Bind("offset") int offset);
+
+ @Query("SELECT EVENT_ID, EVENT_CARD_ID_FK, EVENT_USER_ID_FK, EVENT_TYPE, EVENT_TIME, EVENT_CARD_DATA_ID_FK,"
+ + " EVENT_PREV_CARD_DATA_ID_FK, EVENT_NEW_CARD_DATA_ID_FK, EVENT_COLUMN_ID_FK, EVENT_PREV_COLUMN_ID_FK, EVENT_LABEL_NAME, EVENT_LABEL_TYPE, EVENT_VALUE_INT,"
+ + " EVENT_VALUE_STRING, EVENT_VALUE_TIMESTAMP, EVENT_VALUE_CARD_FK, EVENT_VALUE_USER_FK"
+ + " FROM LA_EVENT INNER JOIN LA_CARD ON LA_EVENT.EVENT_CARD_ID_FK = LA_CARD.CARD_ID"
+ + " INNER JOIN LA_BOARD_COLUMN ON LA_BOARD_COLUMN.BOARD_COLUMN_ID = LA_CARD.CARD_BOARD_COLUMN_ID_FK"
+ + " INNER JOIN LA_BOARD ON LA_BOARD_COLUMN.BOARD_COLUMN_BOARD_ID_FK = LA_BOARD.BOARD_ID AND LA_BOARD.BOARD_PROJECT_ID_FK in (:projects)"
+ + " WHERE EVENT_USER_ID_FK = :userId ORDER BY EVENT_TIME DESC LIMIT :amount OFFSET :offset")
+ List getLatestActivityByPageAndProjects(@Bind("userId") int user,
+ @Bind("projects") Collection projects, @Bind("amount") int amount, @Bind("offset") int offset);
+
+ @Query("SELECT CAST(EVENT_TIME AS DATE) AS EVENT_DATE, COUNT(*) AS EVENT_COUNT FROM LA_EVENT "
+ + " WHERE EVENT_USER_ID_FK = :userId AND EVENT_TIME >= :fromDate GROUP BY EVENT_DATE ORDER BY EVENT_DATE")
+ List getUserActivity(@Bind("userId") int userId, @Bind("fromDate") Date fromDate);
+
+ @Query("SELECT CAST(EVENT_TIME AS DATE) AS EVENT_DATE, COUNT(*) AS EVENT_COUNT FROM LA_EVENT "
+ + " INNER JOIN LA_CARD_FULL ON EVENT_CARD_ID_FK = LA_CARD_FULL.CARD_ID AND PROJECT_ID IN (:projectIds) "
+ + " WHERE EVENT_USER_ID_FK = :userId AND EVENT_TIME >= :fromDate GROUP BY EVENT_DATE ORDER BY EVENT_DATE")
+ List getUserActivityByProjects(@Bind("userId") int userId,
+ @Bind("projectIds") Collection projectIds, @Bind("fromDate") Date fromDate);
+
+}
diff --git a/src/main/java/io/lavagna/query/MySqlFullTextSupportQuery.java b/src/main/java/io/lavagna/query/MySqlFullTextSupportQuery.java
new file mode 100644
index 000000000..64a951d4c
--- /dev/null
+++ b/src/main/java/io/lavagna/query/MySqlFullTextSupportQuery.java
@@ -0,0 +1,36 @@
+/**
+ * This file is part of lavagna.
+ *
+ * lavagna is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * lavagna is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with lavagna. If not, see .
+ */
+package io.lavagna.query;
+
+import io.lavagna.common.Query;
+import io.lavagna.common.QueryRepository;
+
+@QueryRepository
+public interface MySqlFullTextSupportQuery {
+
+ @Query("INSERT INTO LA_CARD_FTS_SUPPORT SELECT CARD_ID,CARD_NAME,CARD_LAST_UPDATED FROM LA_CARD left join LA_CARD_FTS_SUPPORT ON CARD_ID = CARD_FTS_SUPPORT_CARD_ID_FK WHERE CARD_FTS_SUPPORT_CARD_ID_FK IS NULL LIMIT 1000")
+ int syncNewCards();
+
+ @Query("REPLACE INTO LA_CARD_FTS_SUPPORT SELECT CARD_ID,CARD_NAME,CARD_LAST_UPDATED FROM LA_CARD left join LA_CARD_FTS_SUPPORT ON CARD_ID = CARD_FTS_SUPPORT_CARD_ID_FK WHERE CARD_LAST_UPDATED <> CARD_FTS_SUPPORT_LAST_UPDATED LIMIT 1000")
+ int syncUpdatedCards();
+
+ @Query("INSERT INTO LA_CARD_DATA_FTS_SUPPORT SELECT CARD_DATA_ID,CARD_DATA_CONTENT,CARD_DATA_LAST_UPDATED FROM LA_CARD_DATA left join LA_CARD_DATA_FTS_SUPPORT ON CARD_DATA_ID = CARD_DATA_FTS_SUPPORT_CARD_DATA_ID_FK WHERE CARD_DATA_FTS_SUPPORT_CARD_DATA_ID_FK IS NULL LIMIT 1000")
+ int syncNewCardData();
+
+ @Query("REPLACE INTO LA_CARD_DATA_FTS_SUPPORT SELECT CARD_DATA_ID,CARD_DATA_CONTENT,CARD_DATA_LAST_UPDATED FROM LA_CARD_DATA left join LA_CARD_DATA_FTS_SUPPORT ON CARD_DATA_ID = CARD_DATA_FTS_SUPPORT_CARD_DATA_ID_FK WHERE CARD_DATA_LAST_UPDATED <> CARD_DATA_FTS_SUPPORT_LAST_UPDATED LIMIT 1000")
+ int syncUpdatedCardData();
+}
diff --git a/src/main/java/io/lavagna/query/NotificationQuery.java b/src/main/java/io/lavagna/query/NotificationQuery.java
new file mode 100644
index 000000000..22f251d74
--- /dev/null
+++ b/src/main/java/io/lavagna/query/NotificationQuery.java
@@ -0,0 +1,66 @@
+/**
+ * This file is part of lavagna.
+ *
+ * lavagna is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * lavagna is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with lavagna. If not, see .
+ */
+package io.lavagna.query;
+
+import io.lavagna.common.Bind;
+import io.lavagna.common.Query;
+import io.lavagna.common.QueryRepository;
+import io.lavagna.common.QueryType;
+import io.lavagna.model.Event;
+
+import java.util.Date;
+import java.util.List;
+
+@QueryRepository
+public interface NotificationQuery {
+
+ @Query(type = QueryType.TEMPLATE, value = "SELECT COUNT(EVENT_ID) COUNT_EVENT_ID, USER_ID FROM LA_EVENT INNER JOIN (SELECT USER_ID, CARD_ID_FK, USER_LAST_CHECKED FROM LA_CARD_LABEL "
+ + " INNER JOIN LA_CARD_LABEL_VALUE ON CARD_LABEL_ID = CARD_LABEL_ID_FK "
+ + " INNER JOIN LA_USER ON CARD_LABEL_VALUE_USER_FK = USER_ID "
+ + " WHERE CARD_LABEL_DOMAIN = 'SYSTEM' AND CARD_LABEL_NAME IN ('ASSIGNED', 'WATCHED_BY')) CARD_FOR_USER "
+ + " ON CARD_ID_FK = EVENT_CARD_ID_FK "
+ + " WHERE USER_LAST_CHECKED IS NULL OR EVENT_TIME >= USER_LAST_CHECKED GROUP BY USER_ID HAVING COUNT(EVENT_ID) > 0")
+ String countNewForUsersId();
+
+ @Query(type = QueryType.TEMPLATE, value = "UPDATE LA_USER SET USER_LAST_CHECKPOINT_COUNT = USER_LAST_CHECKPOINT_COUNT + :count WHERE USER_ID = :userId")
+ String updateCount();
+
+ @Query("UPDATE LA_USER SET USER_LAST_CHECKED = :checkDate")
+ int updateCheckDate(@Bind("checkDate") Date checkDate);
+
+ @Query(type = QueryType.TEMPLATE, value = "SELECT USER_ID FROM LA_USER WHERE USER_LAST_CHECKPOINT_COUNT > 0")
+ String usersToNotify();
+
+ @Query(type = QueryType.TEMPLATE, value = "UPDATE LA_USER SET USER_LAST_CHECKPOINT_COUNT = 0 WHERE USER_LAST_CHECKPOINT_COUNT > 0 ")
+ String reset();
+
+ @Query(type = QueryType.TEMPLATE, value = " AND USER_ID NOT IN (:userWithChanges) ")
+ String notIn();
+
+ @Query("SELECT USER_LAST_EMAIL_SENT FROM LA_USER WHERE USER_ID = :userId")
+ Date lastEmailSent(@Bind("userId") int userId);
+
+ @Query("SELECT * FROM (SELECT DISTINCT CARD_ID_FK FROM LA_CARD_LABEL "
+ + " INNER JOIN LA_CARD_LABEL_VALUE ON CARD_LABEL_ID = CARD_LABEL_ID_FK "
+ + " WHERE CARD_LABEL_VALUE_USER_FK = :userId AND CARD_LABEL_DOMAIN = 'SYSTEM' AND "
+ + " CARD_LABEL_NAME IN ('ASSIGNED', 'WATCHED_BY') ) CARD_IDS "
+ + " INNER JOIN LA_EVENT ON CARD_ID_FK = EVENT_CARD_ID_FK WHERE EVENT_TIME BETWEEN :from AND :upTo ORDER BY EVENT_TIME ASC")
+ List eventsForUser(@Bind("userId") int userId, @Bind("from") Date from, @Bind("upTo") Date upTo);
+
+ @Query("UPDATE LA_USER SET USER_LAST_EMAIL_SENT = :sentDate WHERE USER_ID = :userId")
+ int updateSentEmailDate(@Bind("sentDate") Date sentDate, @Bind("userId") int userId);
+}
diff --git a/src/main/java/io/lavagna/query/PermissionQuery.java b/src/main/java/io/lavagna/query/PermissionQuery.java
new file mode 100644
index 000000000..fd1161fb2
--- /dev/null
+++ b/src/main/java/io/lavagna/query/PermissionQuery.java
@@ -0,0 +1,140 @@
+/**
+ * This file is part of lavagna.
+ *
+ * lavagna is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * lavagna is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with lavagna. If not, see .
+ */
+package io.lavagna.query;
+
+import io.lavagna.common.Bind;
+import io.lavagna.common.Query;
+import io.lavagna.common.QueryRepository;
+import io.lavagna.common.QueryType;
+import io.lavagna.model.ProjectRoleAndPermission;
+import io.lavagna.model.RoleAndMetadata;
+import io.lavagna.model.RoleAndPermission;
+import io.lavagna.model.User;
+import io.lavagna.model.UserIdentifier;
+
+import java.util.List;
+
+@QueryRepository
+public interface PermissionQuery {
+
+ @Query("SELECT ROLE_NAME, ROLE_REMOVABLE, ROLE_HIDDEN, ROLE_READONLY, PERMISSION FROM LA_USER_ROLE "
+ + " INNER JOIN LA_ROLE ON LA_USER_ROLE.ROLE_ID_FK = ROLE_ID "
+ + " LEFT JOIN LA_ROLE_PERMISSION ON LA_ROLE_PERMISSION.ROLE_ID_FK = ROLE_ID "
+ + " WHERE USER_ID_FK = :userId")
+ List findBaseRoleAndPermissionByUserId(@Bind("userId") int userId);
+
+ @Query("SELECT PROJECT_ROLE_NAME AS ROLE_NAME, PROJECT_ROLE_REMOVABLE AS ROLE_REMOVABLE, PROJECT_ROLE_HIDDEN AS ROLE_HIDDEN, PROJECT_ROLE_READONLY AS ROLE_READONLY, "
+ + " PERMISSION FROM LA_PROJECT_USER_ROLE "
+ + " INNER JOIN LA_PROJECT_ROLE ON LA_PROJECT_USER_ROLE.PROJECT_ROLE_ID_FK = PROJECT_ROLE_ID "
+ + " LEFT JOIN LA_PROJECT_ROLE_PERMISSION ON LA_PROJECT_ROLE_PERMISSION.PROJECT_ROLE_ID_FK = PROJECT_ROLE_ID "
+ + " WHERE USER_ID_FK = :userId AND LA_PROJECT_USER_ROLE.PROJECT_ID_FK = :projectId AND LA_PROJECT_ROLE.PROJECT_ID_FK = :projectId")
+ List findRoleAndPermissionByUserIdInProjectId(@Bind("userId") int userId,
+ @Bind("projectId") int projectId);
+
+ @Query("SELECT LA_PROJECT.PROJECT_ID AS PROJECT_ID, LA_PROJECT.PROJECT_SHORT_NAME AS PROJECT_SHORT_NAME, PROJECT_ROLE_NAME AS ROLE_NAME, PROJECT_ROLE_REMOVABLE AS ROLE_REMOVABLE, PERMISSION FROM LA_PROJECT_USER_ROLE "
+ + " INNER JOIN LA_PROJECT_ROLE ON LA_PROJECT_USER_ROLE.PROJECT_ROLE_ID_FK = PROJECT_ROLE_ID "
+ + " INNER JOIN LA_PROJECT ON LA_PROJECT_ROLE.PROJECT_ID_FK = LA_PROJECT.PROJECT_ID "
+ + " LEFT JOIN LA_PROJECT_ROLE_PERMISSION ON LA_PROJECT_ROLE_PERMISSION.PROJECT_ROLE_ID_FK = PROJECT_ROLE_ID "
+ + " WHERE USER_ID_FK = :userId")
+ List findPermissionsGroupedByProjectForUserId(@Bind("userId") int userId);
+
+ @Query("SELECT ROLE_NAME, ROLE_REMOVABLE, ROLE_HIDDEN, ROLE_READONLY, PERMISSION from LA_ROLE_PERMISSION RIGHT JOIN LA_ROLE ON ROLE_ID = ROLE_ID_FK")
+ List findAllRolesAndRelatedPermission();
+
+ @Query("SELECT ROLE_NAME, ROLE_REMOVABLE, ROLE_HIDDEN, ROLE_READONLY FROM LA_ROLE WHERE ROLE_NAME = :name")
+ RoleAndMetadata findRoleByName(@Bind("name") String name);
+
+ @Query("SELECT PROJECT_ROLE_NAME AS ROLE_NAME, PROJECT_ROLE_REMOVABLE AS ROLE_REMOVABLE, PROJECT_ROLE_HIDDEN AS ROLE_HIDDEN, PROJECT_ROLE_READONLY AS ROLE_READONLY,"
+ + " PERMISSION from LA_PROJECT_ROLE_PERMISSION RIGHT JOIN LA_PROJECT_ROLE ON PROJECT_ROLE_ID = PROJECT_ROLE_ID_FK WHERE PROJECT_ID_FK = :projectId")
+ List findAllRolesAndRelatedPermissionInProjectId(@Bind("projectId") int projectId);
+
+ @Query("SELECT PROJECT_ROLE_NAME AS ROLE_NAME, PROJECT_ROLE_REMOVABLE AS ROLE_REMOVABLE, PROJECT_ROLE_HIDDEN AS ROLE_HIDDEN, PROJECT_ROLE_READONLY AS ROLE_READONLY "
+ + " from LA_PROJECT_ROLE WHERE PROJECT_ID_FK = :projectId AND PROJECT_ROLE_NAME = :name")
+ RoleAndMetadata findRoleInProjectIdByName(@Bind("projectId") int projectId,
+ @Bind("name") String name);
+
+ @Query("INSERT INTO LA_ROLE(ROLE_NAME, ROLE_REMOVABLE) VALUES (:roleName, TRUE)")
+ int createRole(@Bind("roleName") String roleName);
+
+ @Query("INSERT INTO LA_ROLE(ROLE_NAME, ROLE_REMOVABLE, ROLE_HIDDEN, ROLE_READONLY) VALUES (:roleName, :removable, :hidden, :readOnly)")
+ int createFullRole(@Bind("roleName") String roleName, @Bind("removable") boolean removable,
+ @Bind("hidden") boolean hidden, @Bind("readOnly") boolean readOnly);
+
+ @Query("INSERT INTO LA_PROJECT_ROLE(PROJECT_ROLE_NAME, PROJECT_ID_FK) VALUES (:roleName, :projectId)")
+ int createRoleInProjectId(@Bind("roleName") String roleName, @Bind("projectId") int projectId);
+
+ @Query("INSERT INTO LA_PROJECT_ROLE(PROJECT_ROLE_NAME, PROJECT_ID_FK, PROJECT_ROLE_REMOVABLE, PROJECT_ROLE_HIDDEN, PROJECT_ROLE_READONLY) "
+ + " VALUES (:roleName, :projectId, :removable, :hidden, :readOnly)")
+ int createFullRoleInProjectId(@Bind("roleName") String roleName, @Bind("projectId") int projectId,
+ @Bind("removable") boolean removable, @Bind("hidden") boolean hidden, @Bind("readOnly") boolean readOnly);
+
+ @Query("DELETE FROM LA_ROLE WHERE ROLE_NAME = :roleName AND ROLE_REMOVABLE = TRUE")
+ int deleteRole(@Bind("roleName") String roleName);
+
+ @Query("DELETE FROM LA_PROJECT_ROLE WHERE PROJECT_ROLE_NAME = :roleName AND PROJECT_ID_FK = :projectId")
+ int deleteRoleInProjectId(@Bind("roleName") String roleName, @Bind("projectId") int projectId);
+
+ @Query("DELETE FROM LA_ROLE_PERMISSION WHERE ROLE_ID_FK = (SELECT ROLE_ID FROM LA_ROLE WHERE ROLE_NAME = :roleName)")
+ int deletePermissions(@Bind("roleName") String roleName);
+
+ @Query("DELETE FROM LA_PROJECT_ROLE_PERMISSION WHERE PROJECT_ROLE_ID_FK = (SELECT PROJECT_ROLE_ID FROM LA_PROJECT_ROLE WHERE PROJECT_ROLE_NAME = :roleName AND PROJECT_ID_FK = :projectId)")
+ int deletePermissionsInProjectId(@Bind("roleName") String roleName, @Bind("projectId") int projectId);
+
+ @Query(type = QueryType.TEMPLATE, value = "INSERT INTO LA_ROLE_PERMISSION(ROLE_ID_FK, PERMISSION) VALUES ((SELECT ROLE_ID FROM LA_ROLE WHERE ROLE_NAME = :roleName), :permission)")
+ String addPermission();
+
+ @Query(type = QueryType.TEMPLATE, value = "INSERT INTO LA_PROJECT_ROLE_PERMISSION(PROJECT_ROLE_ID_FK, PERMISSION) VALUES ((SELECT PROJECT_ROLE_ID FROM LA_PROJECT_ROLE WHERE PROJECT_ROLE_NAME = :roleName AND PROJECT_ID_FK = :projectId), :permission)")
+ String addPermissionInProjectId();
+
+ @Query(type = QueryType.TEMPLATE, value = "INSERT INTO LA_USER_ROLE(USER_ID_FK, ROLE_ID_FK) VALUES ((:userId), (SELECT ROLE_ID FROM LA_ROLE WHERE ROLE_NAME = :roleName))")
+ String assignRoleToUser();
+
+ @Query(type = QueryType.TEMPLATE, value = "INSERT INTO LA_PROJECT_USER_ROLE(PROJECT_ID_FK, USER_ID_FK, PROJECT_ROLE_ID_FK) VALUES (:projectId, (:userId), (SELECT PROJECT_ROLE_ID FROM LA_PROJECT_ROLE WHERE PROJECT_ROLE_NAME = :roleName AND PROJECT_ID_FK = :projectId))")
+ String assignRoleToUsersInProjectId();
+
+ @Query(type = QueryType.TEMPLATE, value = "DELETE FROM LA_USER_ROLE WHERE USER_ID_FK = (:userId) AND ROLE_ID_FK = (SELECT ROLE_ID FROM LA_ROLE WHERE ROLE_NAME = :roleName)")
+ String removeRoleToUsers();
+
+ @Query(type = QueryType.TEMPLATE, value = "DELETE FROM LA_PROJECT_USER_ROLE WHERE USER_ID_FK = (:userId) AND PROJECT_ROLE_ID_FK = (SELECT PROJECT_ROLE_ID FROM LA_PROJECT_ROLE WHERE PROJECT_ROLE_NAME = :roleName AND PROJECT_ID_FK = :projectId) AND PROJECT_ID_FK = :projectId")
+ String removeRoleToUsersInProjectId();
+
+ @Query("SELECT USER_ID, USER_PROVIDER, USER_NAME, USER_EMAIL, USER_DISPLAY_NAME, USER_ENABLED, USER_EMAIL_NOTIFICATION, USER_MEMBER_SINCE FROM LA_USER "
+ + " INNER JOIN LA_USER_ROLE ON USER_ID = USER_ID_FK "
+ + " INNER JOIN LA_ROLE ON ROLE_ID_FK = ROLE_ID WHERE ROLE_NAME = :roleName ORDER BY USER_PROVIDER, USER_NAME")
+ List findUserByRole(@Bind("roleName") String roleName);
+
+ @Query("SELECT USER_PROVIDER, USER_NAME FROM LA_USER "
+ + " INNER JOIN LA_USER_ROLE ON USER_ID = USER_ID_FK "
+ + " INNER JOIN LA_ROLE ON ROLE_ID_FK = ROLE_ID WHERE ROLE_NAME = :roleName ORDER BY USER_PROVIDER, USER_NAME")
+ List findUserIdentifierByRole(@Bind("roleName") String roleName);
+
+ @Query("SELECT USER_ID, USER_PROVIDER, USER_NAME, USER_EMAIL, USER_DISPLAY_NAME, USER_ENABLED, USER_EMAIL_NOTIFICATION, USER_MEMBER_SINCE FROM LA_USER "
+ + " INNER JOIN LA_PROJECT_USER_ROLE ON USER_ID = USER_ID_FK "
+ + " INNER JOIN LA_PROJECT_ROLE ON PROJECT_ROLE_ID_FK = PROJECT_ROLE_ID "
+ + " WHERE PROJECT_ROLE_NAME = :roleName AND LA_PROJECT_ROLE.PROJECT_ID_FK = :projectId AND "
+ + " LA_PROJECT_USER_ROLE.PROJECT_ID_FK = :projectId ORDER BY USER_PROVIDER, USER_NAME")
+ List findUserByRoleAndProjectId(@Bind("roleName") String roleName, @Bind("projectId") int projectId);
+
+ @Query("SELECT USER_PROVIDER, USER_NAME FROM LA_USER "
+ + " INNER JOIN LA_PROJECT_USER_ROLE ON USER_ID = USER_ID_FK "
+ + " INNER JOIN LA_PROJECT_ROLE ON PROJECT_ROLE_ID_FK = PROJECT_ROLE_ID "
+ + " WHERE PROJECT_ROLE_NAME = :roleName AND LA_PROJECT_ROLE.PROJECT_ID_FK = :projectId AND "
+ + " LA_PROJECT_USER_ROLE.PROJECT_ID_FK = :projectId ORDER BY USER_PROVIDER, USER_NAME")
+ List findUserIdentifierByRoleAndProjectId(@Bind("roleName") String roleName,
+ @Bind("projectId") int projectId);
+
+}
diff --git a/src/main/java/io/lavagna/query/ProjectQuery.java b/src/main/java/io/lavagna/query/ProjectQuery.java
new file mode 100644
index 000000000..6f72b70d7
--- /dev/null
+++ b/src/main/java/io/lavagna/query/ProjectQuery.java
@@ -0,0 +1,129 @@
+/**
+ * This file is part of lavagna.
+ *
+ * lavagna is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * lavagna is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with lavagna. If not, see .
+ */
+package io.lavagna.query;
+
+import io.lavagna.common.Bind;
+import io.lavagna.common.QueriesOverride;
+import io.lavagna.common.Query;
+import io.lavagna.common.QueryOverride;
+import io.lavagna.common.QueryRepository;
+import io.lavagna.common.QueryType;
+import io.lavagna.model.BoardColumnDefinition;
+import io.lavagna.model.Project;
+import io.lavagna.model.ProjectWithEventCounts;
+
+import java.util.Collection;
+import java.util.List;
+
+@QueryRepository
+public interface ProjectQuery {
+
+ @Query("INSERT INTO LA_PROJECT(PROJECT_NAME, PROJECT_SHORT_NAME, PROJECT_DESCRIPTION) VALUES (:name, :shortName, :description)")
+ int createProject(@Bind("name") String name, @Bind("shortName") String shortName,
+ @Bind("description") String description);
+
+ @Query("UPDATE LA_PROJECT SET PROJECT_NAME = :name, PROJECT_DESCRIPTION = :description, PROJECT_ARCHIVED = :archived WHERE PROJECT_ID = :projectId")
+ int updateProject(@Bind("projectId") int projectId, @Bind("name") String name,
+ @Bind("description") String description, @Bind("archived") boolean archived);
+
+ @Query("SELECT * FROM LA_PROJECT WHERE PROJECT_ID = IDENTITY()")
+ @QueriesOverride({
+ @QueryOverride(db = DB.MYSQL, value = "SELECT * FROM LA_PROJECT WHERE PROJECT_ID = LAST_INSERT_ID()"),
+ @QueryOverride(db = DB.PGSQL, value = "SELECT * FROM LA_PROJECT WHERE PROJECT_ID = (SELECT CURRVAL(pg_get_serial_sequence('la_project','project_id')))") })
+ Project findLastCreatedProject();
+
+ @Query("SELECT * FROM LA_PROJECT WHERE PROJECT_ID = :projectId")
+ Project findById(@Bind("projectId") int projectId);
+
+ @Query("SELECT * FROM LA_PROJECT WHERE PROJECT_SHORT_NAME = :shortName")
+ Project findByShortName(@Bind("shortName") String shortName);
+
+ @Query("SELECT * FROM LA_PROJECT ORDER BY PROJECT_SHORT_NAME")
+ List findAll();
+
+ @Query("SELECT DISTINCT PROJECT_ID, PROJECT_NAME, PROJECT_SHORT_NAME, PROJECT_DESCRIPTION, PROJECT_ARCHIVED FROM LA_PROJECT "//
+ + " INNER JOIN LA_PROJECT_USER_ROLE ON LA_PROJECT_USER_ROLE.PROJECT_ID_FK = PROJECT_ID "//
+ + " INNER JOIN LA_PROJECT_ROLE ON LA_PROJECT_ROLE.PROJECT_ID_FK = PROJECT_ID "//
+ + " INNER JOIN LA_PROJECT_ROLE_PERMISSION ON LA_PROJECT_ROLE_PERMISSION.PROJECT_ROLE_ID_FK = PROJECT_ROLE_ID "//
+ + " WHERE "//
+ + " USER_ID_FK = :userId AND PERMISSION = :permission")
+ List findAllForUser(@Bind("userId") int userId, @Bind("permission") String permission);
+
+ @Query("SELECT BOARD_PROJECT_ID_FK FROM LA_BOARD WHERE BOARD_SHORT_NAME = :shortName")
+ List findRelatedProjectIdByBoardShortname(@Bind("shortName") String shortName);
+
+ @Query("SELECT BOARD_PROJECT_ID_FK FROM LA_BOARD WHERE BOARD_ID = "//
+ + " (SELECT BOARD_COLUMN_BOARD_ID_FK FROM LA_BOARD_COLUMN WHERE BOARD_COLUMN_ID = "//
+ + " (SELECT CARD_BOARD_COLUMN_ID_FK FROM LA_CARD WHERE CARD_ID = :cardId))")
+ List findRelatedProjectIdByCardId(@Bind("cardId") int cardId);
+
+ @Query("SELECT BOARD_PROJECT_ID_FK FROM LA_BOARD WHERE BOARD_ID = (SELECT BOARD_COLUMN_BOARD_ID_FK FROM LA_BOARD_COLUMN WHERE BOARD_COLUMN_ID = :columnId)")
+ List findRelatedProjectIdByColumnId(@Bind("columnId") int columnId);
+
+ @Query("SELECT BOARD_PROJECT_ID_FK FROM LA_BOARD WHERE BOARD_ID = "//
+ + "(SELECT BOARD_COLUMN_BOARD_ID_FK FROM LA_BOARD_COLUMN WHERE BOARD_COLUMN_ID = "//
+ + "(SELECT CARD_BOARD_COLUMN_ID_FK FROM LA_CARD WHERE CARD_ID = "//
+ + "(SELECT CARD_DATA_CARD_ID_FK FROM LA_CARD_DATA WHERE CARD_DATA_ID = :cardDataId )))")
+ List findRelatedProjectIdByCardDataId(@Bind("cardDataId") int cardDataId);
+
+ @Query("SELECT CARD_LABEL_PROJECT_ID_FK FROM LA_CARD_LABEL WHERE CARD_LABEL_ID = :labelId")
+ List findRelatedProjectIdByLabelId(@Bind("labelId") int labelId);
+
+ @Query("SELECT BOARD_COLUMN_DEFINITION_PROJECT_ID_FK FROM LA_BOARD_COLUMN_DEFINITION WHERE BOARD_COLUMN_DEFINITION_ID = :id")
+ List findRelatedProjectIdByColumnDefinitionId(@Bind("id") int id);
+
+ @Query("SELECT CARD_LABEL_PROJECT_ID_FK FROM LA_CARD_LABEL WHERE CARD_LABEL_ID = (SELECT CARD_LABEL_ID_FK FROM LA_CARD_LABEL_VALUE WHERE CARD_LABEL_VALUE_ID = :labelValueId)")
+ List findRelatedProjectIdByLabelValueId(@Bind("labelValueId") int labelValueId);
+
+ @Query("SELECT BOARD_PROJECT_ID_FK FROM LA_BOARD WHERE BOARD_ID = "//
+ + "(SELECT BOARD_COLUMN_BOARD_ID_FK FROM LA_BOARD_COLUMN WHERE BOARD_COLUMN_ID = "//
+ + "(SELECT CARD_BOARD_COLUMN_ID_FK FROM LA_CARD WHERE CARD_ID = "//
+ + "(SELECT EVENT_CARD_ID_FK FROM LA_EVENT WHERE EVENT_ID = :eventId )))")
+ List findRelatedProjectIdByEventId(@Bind("eventId") int eventId);
+
+ @Query(type = QueryType.TEMPLATE, value = "INSERT INTO LA_BOARD_COLUMN_DEFINITION (BOARD_COLUMN_DEFINITION_PROJECT_ID_FK, BOARD_COLUMN_DEFINITION_VALUE, BOARD_COLUMN_DEFINITION_COLOR) VALUES (:projectId, :value, :color)")
+ String createColumnDefinition();
+
+ @Query("UPDATE LA_BOARD_COLUMN_DEFINITION SET BOARD_COLUMN_DEFINITION_COLOR = :color WHERE BOARD_COLUMN_DEFINITION_PROJECT_ID_FK = :projectId AND BOARD_COLUMN_DEFINITION_ID = :definitionId")
+ int updateColumnDefinition(@Bind("color") int color, @Bind("projectId") int projectId,
+ @Bind("definitionId") int definitionId);
+
+ @Query("SELECT * FROM LA_BOARD_COLUMN_DEFINITION WHERE BOARD_COLUMN_DEFINITION_PROJECT_ID_FK = :projectId")
+ List findColumnDefinitionsByProjectId(@Bind("projectId") int projectId);
+
+ @Query("SELECT COUNT(PROJECT_SHORT_NAME) FROM LA_PROJECT WHERE PROJECT_SHORT_NAME = :shortName")
+ Integer existsWithShortName(@Bind("shortName") String shortName);
+
+ @Query("SELECT LA_PROJECT.PROJECT_ID, LA_PROJECT.PROJECT_NAME, LA_PROJECT.PROJECT_SHORT_NAME, PROJECT_DESCRIPTION, PROJECT_ARCHIVED, "
+ + "COUNT( EVENT_ID ) AS EVENTS FROM LA_PROJECT "
+ + "INNER JOIN LA_CARD_FULL ON LA_CARD_FULL.PROJECT_ID = LA_PROJECT.PROJECT_ID "
+ + "INNER JOIN LA_EVENT ON EVENT_CARD_ID_FK = LA_CARD_FULL.CARD_ID AND EVENT_USER_ID_FK = :userId "
+ + "GROUP BY LA_PROJECT.PROJECT_ID, PROJECT_NAME, LA_PROJECT.PROJECT_SHORT_NAME, PROJECT_DESCRIPTION "
+ + "ORDER BY EVENTS DESC")
+ List findProjectsByUserActivity(@Bind("userId") int userId);
+
+ @Query("SELECT LA_PROJECT.PROJECT_ID, LA_PROJECT.PROJECT_NAME, LA_PROJECT.PROJECT_SHORT_NAME, PROJECT_DESCRIPTION, PROJECT_ARCHIVED, "
+ + "COUNT( EVENT_ID ) AS EVENTS FROM LA_PROJECT "
+ + "INNER JOIN LA_CARD_FULL ON LA_CARD_FULL.PROJECT_ID = LA_PROJECT.PROJECT_ID "
+ + "INNER JOIN LA_EVENT ON EVENT_CARD_ID_FK = LA_CARD_FULL.CARD_ID AND EVENT_USER_ID_FK = :userId "
+ + "WHERE LA_CARD_FULL.PROJECT_ID IN (:projects) "
+ + "GROUP BY LA_PROJECT.PROJECT_ID, PROJECT_NAME, LA_PROJECT.PROJECT_SHORT_NAME, PROJECT_DESCRIPTION "
+ + "ORDER BY EVENTS DESC")
+ List findProjectsByUserActivityInProjects(@Bind("userId") int userId,
+ @Bind("projects") Collection projectIds);
+
+}
diff --git a/src/main/java/io/lavagna/query/SearchQuery.java b/src/main/java/io/lavagna/query/SearchQuery.java
new file mode 100644
index 000000000..e49f92d40
--- /dev/null
+++ b/src/main/java/io/lavagna/query/SearchQuery.java
@@ -0,0 +1,140 @@
+/**
+ * This file is part of lavagna.
+ *
+ * lavagna is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * lavagna is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with lavagna. If not, see .
+ */
+package io.lavagna.query;
+
+import io.lavagna.common.QueriesOverride;
+import io.lavagna.common.Query;
+import io.lavagna.common.QueryOverride;
+import io.lavagna.common.QueryRepository;
+import io.lavagna.common.QueryType;
+
+@QueryRepository
+public interface SearchQuery {
+
+ @Query(type = QueryType.TEMPLATE, value = "SELECT COUNT(LA_CARD.CARD_ID) FROM ")
+ String findFirstSelectCount();
+
+ @Query(type = QueryType.TEMPLATE, value = "SELECT LA_CARD.CARD_ID FROM ")
+ String findFirstSelect();
+
+ @Query(type = QueryType.TEMPLATE, value = "LA_CARD "
+ + " INNER JOIN LA_BOARD_COLUMN ON LA_CARD.CARD_BOARD_COLUMN_ID_FK = LA_BOARD_COLUMN.BOARD_COLUMN_ID "
+ + " INNER JOIN LA_BOARD_COLUMN_DEFINITION ON BOARD_COLUMN_DEFINITION_ID_FK = BOARD_COLUMN_DEFINITION_ID "
+ + " INNER JOIN LA_BOARD ON LA_BOARD.BOARD_ID = LA_BOARD_COLUMN.BOARD_COLUMN_BOARD_ID_FK "
+ + " INNER JOIN LA_PROJECT ON LA_BOARD.BOARD_PROJECT_ID_FK = LA_PROJECT.PROJECT_ID " + " INNER JOIN (")
+ String findFirstFrom();
+
+ @Query(type = QueryType.TEMPLATE, value = " ) AS CARD_R ON LA_CARD.CARD_ID = CARD_R.CARD_ID ")
+ String findSecond();
+
+ @Query(type = QueryType.TEMPLATE, value = " WHERE ")
+ String findThirdWhere();
+
+ @Query(type = QueryType.TEMPLATE, value = " LA_BOARD.BOARD_ID = ? ")
+ String findFourthInBoardId();
+
+ @Query(type = QueryType.TEMPLATE, value = " LA_BOARD.BOARD_PROJECT_ID_FK = ? ")
+ String findInFifthProjectId();
+
+ @Query(type = QueryType.TEMPLATE, value = " LA_BOARD.BOARD_PROJECT_ID_FK IN ")
+ String findSixthRestrictedReadAccess();
+
+ @Query(type = QueryType.TEMPLATE, value = " ORDER BY LA_CARD.CARD_LAST_UPDATED DESC LIMIT ? OFFSET ?")
+ String findSeventhOrderByAndLimit();
+
+ @Query(type = QueryType.TEMPLATE, value = "SELECT LA_CARD.CARD_ID FROM LA_CARD LEFT JOIN (")
+ String findCardIdNotInOpen();
+
+ @Query(type = QueryType.TEMPLATE, value = ") TO_EXCLUDE ON LA_CARD.CARD_ID = TO_EXCLUDE.CARD_ID WHERE TO_EXCLUDE.CARD_ID IS NULL")
+ String findCardIdNotInClose();
+
+ @Query(type = QueryType.TEMPLATE, value = "(SELECT CARD_ID FROM LA_CARD WHERE CARD_SEQ_NUMBER LIKE CONCAT(?, '%')) UNION (SELECT CARD_ID FROM LA_CARD WHERE LA_TEXT_SEARCH(CARD_NAME, ?)) "
+ + " UNION (SELECT CARD_DATA_CARD_ID_FK CARD_ID FROM LA_CARD_DATA WHERE CARD_DATA_DELETED = FALSE AND "
+ + " CARD_DATA_TYPE IN ('COMMENT', 'ACTION_LIST', 'ACTION_CHECKED', 'ACTION_UNCHECKED', 'DESCRIPTION') AND LA_TEXT_SEARCH_CLOB(CARD_DATA_CONTENT, ?))")
+ @QueriesOverride({
+ @QueryOverride(db = DB.MYSQL, value = "SELECT CARD_ID FROM (SELECT CARD_ID FROM LA_CARD WHERE CARD_SEQ_NUMBER LIKE CONCAT(?, '%') UNION "
+ + " SELECT CARD_ID FROM (SELECT CARD_FTS_SUPPORT_CARD_ID_FK CARD_ID FROM LA_CARD_FTS_SUPPORT WHERE MATCH (CARD_FTS_SUPPORT_CARD_NAME) AGAINST (? IN BOOLEAN MODE)) AS CARD_SEQ_AND_CARD_NAME"
+ + " UNION "
+ + " SELECT CARD_DATA_CARD_ID_FK CARD_ID FROM LA_CARD_DATA "
+ + " INNER JOIN LA_CARD_DATA_FTS_SUPPORT ON "
+ + " CARD_DATA_FTS_SUPPORT_CARD_DATA_ID_FK = CARD_DATA_ID "
+ + " WHERE CARD_DATA_DELETED <> TRUE AND "
+ + " CARD_DATA_TYPE IN ('COMMENT', 'ACTION_LIST', 'ACTION_CHECKED', 'ACTION_UNCHECKED', 'DESCRIPTION') AND "
+ + " MATCH(CARD_DATA_FTS_SUPPORT_CARD_DATA_CONTENT) AGAINST (? IN BOOLEAN MODE)) AS FTS_RES"),
+ @QueryOverride(db = DB.PGSQL, value = "(SELECT CARD_ID FROM LA_CARD WHERE CAST(CARD_SEQ_NUMBER AS TEXT) LIKE CONCAT(?, '%')) UNION "
+ + " SELECT CARD_ID FROM LA_CARD WHERE card_name_tsvector @@ plainto_tsquery('english', unaccent(?)) UNION "
+ + " SELECT CARD_DATA_CARD_ID_FK CARD_ID FROM LA_CARD_DATA WHERE CARD_DATA_DELETED <> TRUE AND "
+ + " CARD_DATA_TYPE IN ('COMMENT', 'ACTION_LIST', 'ACTION_CHECKED', 'ACTION_UNCHECKED', 'DESCRIPTION') AND "
+ + " card_data_content_tsvector @@ plainto_tsquery('english', unaccent(?))") })
+ String findByFreeText();
+
+ @Query(type = QueryType.TEMPLATE, value = "SELECT CARD_ID FROM LA_CARD INNER JOIN LA_BOARD_COLUMN ON CARD_BOARD_COLUMN_ID_FK = BOARD_COLUMN_ID "
+ + " INNER JOIN LA_BOARD_COLUMN_DEFINITION ON BOARD_COLUMN_DEFINITION_ID_FK = BOARD_COLUMN_DEFINITION_ID "
+ + " WHERE BOARD_COLUMN_DEFINITION_VALUE = ?")
+ String findByStatus();
+
+ @Query(type = QueryType.TEMPLATE, value = "SELECT CARD_ID FROM LA_CARD INNER JOIN LA_BOARD_COLUMN ON CARD_BOARD_COLUMN_ID_FK = BOARD_COLUMN_ID "
+ + " INNER JOIN LA_BOARD ON LA_BOARD.BOARD_ID = LA_BOARD_COLUMN.BOARD_COLUMN_BOARD_ID_FK "
+ + " WHERE BOARD_ARCHIVED = ?")
+ String findByBoardStatus();
+
+ @Query(type = QueryType.TEMPLATE, value = "SELECT CARD_ID FROM LA_CARD INNER JOIN LA_BOARD_COLUMN ON CARD_BOARD_COLUMN_ID_FK = BOARD_COLUMN_ID "
+ + " INNER JOIN LA_BOARD_COLUMN_DEFINITION ON BOARD_COLUMN_DEFINITION_ID_FK = BOARD_COLUMN_DEFINITION_ID "
+ + " WHERE BOARD_COLUMN_LOCATION = ?")
+ String findByLocation();
+
+ @Query(type = QueryType.TEMPLATE, value = "SELECT CARD_ID_FK AS CARD_ID FROM LA_CARD_LABEL_VALUE "
+ + " INNER JOIN LA_CARD_LABEL ON CARD_LABEL_ID = CARD_LABEL_ID_FK "
+ + " WHERE CARD_LABEL_VALUE_DELETED <> TRUE AND CARD_LABEL_DOMAIN = 'SYSTEM' AND CARD_LABEL_NAME = ?")
+ String findBySystemLabel();
+
+ @Query(type = QueryType.TEMPLATE, value = "SELECT CARD_ID_FK AS CARD_ID FROM LA_CARD_LABEL_VALUE "
+ + " INNER JOIN LA_CARD_LABEL ON CARD_LABEL_ID = CARD_LABEL_ID_FK "
+ + " WHERE CARD_LABEL_VALUE_DELETED <> TRUE AND CARD_LABEL_DOMAIN = 'USER' AND CARD_LABEL_NAME LIKE CONCAT(? ,'%')")
+ String findByUserLabel();
+
+ @Query(type = QueryType.TEMPLATE, value = " AND (CASE "
+ + " WHEN CARD_LABEL_VALUE_TYPE = 'STRING' THEN CARD_LABEL_VALUE_STRING LIKE CONCAT(?, '%') "
+ + " WHEN CARD_LABEL_VALUE_TYPE = 'INT' THEN CARD_LABEL_VALUE_INT = ? "
+ + " WHEN CARD_LABEL_VALUE_TYPE = 'TIMESTAMP' THEN CARD_LABEL_VALUE_TIMESTAMP BETWEEN ? AND ? "
+ + " WHEN CARD_LABEL_VALUE_TYPE = 'USER' THEN CARD_LABEL_VALUE_USER_FK = ? "
+ + " WHEN CARD_LABEL_VALUE_TYPE = 'CARD' THEN CARD_LABEL_VALUE_CARD_FK = ? "
+ + " WHEN CARD_LABEL_VALUE_TYPE = 'LIST' THEN (CARD_LABEL_ID_FK, CARD_LABEL_VALUE_LIST_VALUE_FK) IN (SELECT CARD_LABEL_ID_FK, CARD_LABEL_LIST_VALUE_ID FROM LA_CARD_LABEL_LIST_VALUE WHERE CARD_LABEL_LIST_VALUE = ?) "
+ + " END)")
+ String andLabelValueString();
+
+ @Query(type = QueryType.TEMPLATE, value = " AND CARD_LABEL_VALUE_TIMESTAMP >= ? AND CARD_LABEL_VALUE_TIMESTAMP < ?")
+ String andLabelValueDate();
+
+ @Query(type = QueryType.TEMPLATE, value = " AND CARD_LABEL_VALUE_USER_FK = ? ")
+ String andLabelValueUser();
+
+ @Query(type = QueryType.TEMPLATE, value = " AND (CARD_LABEL_ID_FK, CARD_LABEL_VALUE_LIST_VALUE_FK) IN (SELECT CARD_LABEL_ID_FK, CARD_LABEL_LIST_VALUE_ID FROM LA_CARD_LABEL_LIST_VALUE WHERE CARD_LABEL_LIST_VALUE = ?) ")
+ String andLabelListValueEq();
+
+ @Query(type = QueryType.TEMPLATE, value = " SELECT CARD_ID FROM LA_CARD INNER JOIN LA_EVENT ON CARD_ID = EVENT_CARD_ID_FK WHERE EVENT_TYPE = 'CARD_CREATE' AND EVENT_TIME BETWEEN ? AND ? ")
+ String findByCardCreationEventDate();
+
+ @Query(type = QueryType.TEMPLATE, value = " SELECT CARD_ID FROM LA_CARD INNER JOIN LA_EVENT ON CARD_ID = EVENT_CARD_ID_FK WHERE EVENT_TYPE = 'CARD_CREATE' AND EVENT_USER_ID_FK = ? ")
+ String findByCardCreationEventUser();
+
+ @Query(type = QueryType.TEMPLATE, value = " SELECT CARD_ID FROM LA_CARD WHERE CARD_LAST_UPDATED BETWEEN ? AND ? ")
+ String findByUpdated();
+
+ @Query(type = QueryType.TEMPLATE, value = " SELECT CARD_ID FROM LA_CARD WHERE CARD_LAST_UPDATED_USER_ID_FK = ? ")
+ String findByUpdatedBy();
+}
diff --git a/src/main/java/io/lavagna/query/StatisticsQuery.java b/src/main/java/io/lavagna/query/StatisticsQuery.java
new file mode 100644
index 000000000..cb08f84e2
--- /dev/null
+++ b/src/main/java/io/lavagna/query/StatisticsQuery.java
@@ -0,0 +1,243 @@
+/**
+ * This file is part of lavagna.
+ *
+ * lavagna is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * lavagna is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with lavagna. If not, see .
+ */
+package io.lavagna.query;
+
+import io.lavagna.common.Bind;
+import io.lavagna.common.QueriesOverride;
+import io.lavagna.common.Query;
+import io.lavagna.common.QueryOverride;
+import io.lavagna.common.QueryRepository;
+import io.lavagna.model.CardFull;
+import io.lavagna.model.EventsCount;
+import io.lavagna.model.LabelAndValueWithCount;
+import io.lavagna.model.MilestoneCount;
+import io.lavagna.model.StatisticForExport;
+import io.lavagna.model.StatisticsResult;
+
+import java.util.Date;
+import java.util.List;
+
+@QueryRepository
+public interface StatisticsQuery {
+
+ @Query("INSERT INTO LA_BOARD_STATISTICS SELECT :date, BOARD_ID, BOARD_COLUMN_DEFINITION_ID_FK, BOARD_COLUMN_LOCATION, COUNT(*) AS CARDS_COUNT FROM LA_CARD "
+ + "INNER JOIN LA_BOARD_COLUMN ON LA_BOARD_COLUMN.BOARD_COLUMN_ID = LA_CARD.CARD_BOARD_COLUMN_ID_FK "
+ + "INNER JOIN LA_BOARD ON LA_BOARD_COLUMN.BOARD_COLUMN_BOARD_ID_FK = LA_BOARD.BOARD_ID "
+ + "GROUP BY BOARD_ID, BOARD_COLUMN_DEFINITION_ID_FK, BOARD_COLUMN_LOCATION")
+ void snapshotCardsStatus(@Bind("date") Date date);
+
+ @Query("DELETE FROM LA_BOARD_STATISTICS WHERE BOARD_STATISTICS_TIME NOT IN (SELECT DAY FROM LA_BOARD_STATISTICS_DAYS)")
+ @QueriesOverride({
+ @QueryOverride(db = DB.MYSQL, value = "DELETE BOARD_STATS FROM LA_BOARD_STATISTICS AS BOARD_STATS LEFT JOIN LA_BOARD_STATISTICS_DAYS ON BOARD_STATS.BOARD_STATISTICS_TIME = LA_BOARD_STATISTICS_DAYS.DAY WHERE LA_BOARD_STATISTICS_DAYS.DAY IS NULL")})
+ void cleanOldCardsStatusSnapshots();
+
+
+ @Query("SELECT BOARD_STATISTICS_TIME AS TIME, BOARD_COLUMN_DEFINITION_VALUE, SUM(BOARD_STATISTICS_COUNT) AS STATISTICS_COUNT FROM LA_BOARD_STATISTICS "
+ + "INNER JOIN LA_BOARD_COLUMN_DEFINITION ON BOARD_STATISTICS_COLUMN_DEFINITION_ID_FK = BOARD_COLUMN_DEFINITION_ID "
+ + "INNER JOIN LA_BOARD_STATISTICS_DAYS ON BOARD_STATISTICS_TIME = DAY "
+ + "INNER JOIN LA_BOARD ON BOARD_STATISTICS_BOARD_ID_FK = LA_BOARD.BOARD_ID "
+ + "WHERE BOARD_STATISTICS_LOCATION = 'BOARD' AND BOARD_STATISTICS_BOARD_ID_FK = :boardId AND BOARD_STATISTICS_TIME >= :fromDate "
+ + "GROUP BY BOARD_STATISTICS_TIME, BOARD_COLUMN_DEFINITION_VALUE")
+ List getCardsStatusByBoard(@Bind("boardId") int boardId, @Bind("fromDate") Date fromDate);
+
+ @Query("SELECT BOARD_STATISTICS_TIME, BOARD_COLUMN_DEFINITION_VALUE, BOARD_STATISTICS_LOCATION, BOARD_STATISTICS_COUNT FROM LA_BOARD_STATISTICS "
+ + "INNER JOIN LA_BOARD_COLUMN_DEFINITION ON BOARD_STATISTICS_COLUMN_DEFINITION_ID_FK = BOARD_COLUMN_DEFINITION_ID WHERE BOARD_STATISTICS_BOARD_ID_FK = :boardId")
+ List findForBoard(@Bind("boardId") int boardId);
+
+ @Query("INSERT INTO LA_BOARD_STATISTICS(BOARD_STATISTICS_TIME, BOARD_STATISTICS_BOARD_ID_FK, BOARD_STATISTICS_COLUMN_DEFINITION_ID_FK, BOARD_STATISTICS_LOCATION, BOARD_STATISTICS_COUNT) "
+ + " VALUES(:date, :boardId, :boardColumnDefinitionId, :location, :count)")
+ int addFromImport(@Bind("date") Date date, @Bind("boardId") int boardId,
+ @Bind("boardColumnDefinitionId") int boardColumnDefinitionId, @Bind("location") String location,
+ @Bind("count") long count);
+
+ @Query("SELECT BOARD_STATISTICS_TIME AS TIME, BOARD_COLUMN_DEFINITION_VALUE, SUM(BOARD_STATISTICS_COUNT) AS STATISTICS_COUNT FROM LA_BOARD_STATISTICS "
+ + "INNER JOIN LA_BOARD_COLUMN_DEFINITION ON BOARD_STATISTICS_COLUMN_DEFINITION_ID_FK = BOARD_COLUMN_DEFINITION_ID "
+ + "INNER JOIN LA_BOARD_STATISTICS_DAYS ON BOARD_STATISTICS_TIME = DAY "
+ + "INNER JOIN LA_BOARD ON BOARD_STATISTICS_BOARD_ID_FK = LA_BOARD.BOARD_ID "
+ + "WHERE BOARD_STATISTICS_LOCATION = 'BOARD' AND BOARD_ARCHIVED = FALSE AND BOARD_PROJECT_ID_FK = :projectId AND BOARD_STATISTICS_TIME >= :fromDate "
+ + "GROUP BY BOARD_STATISTICS_TIME, BOARD_COLUMN_DEFINITION_VALUE")
+ List getCardsStatusByProject(@Bind("projectId") int projectId, @Bind("fromDate") Date fromDate);
+
+ @Query("SELECT COUNT(*) FROM (SELECT DISTINCT EVENT_USER_ID_FK FROM LA_PROJECT "
+ + "INNER JOIN LA_CARD_FULL ON LA_CARD_FULL.PROJECT_SHORT_NAME = LA_PROJECT.PROJECT_SHORT_NAME "
+ + "INNER JOIN LA_BOARD ON LA_CARD_FULL.BOARD_SHORT_NAME = LA_BOARD.BOARD_SHORT_NAME "
+ + "INNER JOIN LA_EVENT ON EVENT_CARD_ID_FK = LA_CARD_FULL.CARD_ID "
+ + "WHERE BOARD_ID = :boardId AND EVENT_TIME >= :fromDate) AS USERS")
+ Integer getActiveUsersOnBoard(@Bind("boardId") int boardId, @Bind("fromDate") Date fromDate);
+
+ // Average users per card
+
+ @Query("SELECT COUNT(*) FROM (SELECT DISTINCT EVENT_USER_ID_FK FROM LA_PROJECT "
+ + "INNER JOIN LA_CARD_FULL ON LA_CARD_FULL.PROJECT_SHORT_NAME = LA_PROJECT.PROJECT_SHORT_NAME "
+ + "INNER JOIN LA_BOARD ON LA_CARD_FULL.BOARD_SHORT_NAME = LA_BOARD.BOARD_SHORT_NAME "
+ + "INNER JOIN LA_EVENT ON EVENT_CARD_ID_FK = LA_CARD_FULL.CARD_ID "
+ + "WHERE BOARD_ARCHIVED = FALSE AND BOARD_PROJECT_ID_FK = :projectId AND EVENT_TIME >= :fromDate) AS USERS")
+ Integer getActiveUsersOnProject(@Bind("projectId") int projectId, @Bind("fromDate") Date fromDate);
+
+ @Query("SELECT AVG(USERS) FROM ( " + "SELECT CARD_ID, COUNT(ASSIGNED_USER_ID) AS USERS " + "FROM LA_CARD_FULL "
+ + "LEFT JOIN LA_ASSIGNED_CARD ON LA_CARD_FULL.CARD_ID = LA_ASSIGNED_CARD.ASSIGNED_CARD_ID "
+ + "INNER JOIN LA_BOARD ON LA_CARD_FULL.BOARD_SHORT_NAME = LA_BOARD.BOARD_SHORT_NAME "
+ + "WHERE BOARD_ID = :boardId AND BOARD_COLUMN_LOCATION = 'BOARD' " + "GROUP BY CARD_ID) AS ASSIGNED ")
+ Double getAverageUsersPerCardOnBoard(@Bind("boardId") int boardId);
+
+ @Query("SELECT AVG(USERS) FROM ( " + "SELECT CARD_ID, COUNT(ASSIGNED_USER_ID) AS USERS " + "FROM LA_CARD_FULL "
+ + "LEFT JOIN LA_ASSIGNED_CARD ON LA_CARD_FULL.CARD_ID = LA_ASSIGNED_CARD.ASSIGNED_CARD_ID "
+ + "INNER JOIN LA_BOARD ON LA_CARD_FULL.BOARD_SHORT_NAME = LA_BOARD.BOARD_SHORT_NAME "
+ + "WHERE BOARD_ARCHIVED = FALSE AND BOARD_PROJECT_ID_FK = :projectId AND BOARD_COLUMN_LOCATION = 'BOARD' "
+ + "GROUP BY CARD_ID) AS ASSIGNED ")
+ Double getAverageUsersPerCardOnProject(@Bind("projectId") int projectId);
+
+ // Average cards per user
+
+ @Query("SELECT AVG(CARDS) FROM ( " + "SELECT COUNT(ASSIGNED_CARD_ID) AS CARDS, ASSIGNED_USER_ID "
+ + "FROM LA_ASSIGNED_CARD "
+ + "INNER JOIN LA_CARD_FULL ON LA_CARD_FULL.CARD_ID = LA_ASSIGNED_CARD.ASSIGNED_CARD_ID "
+ + "INNER JOIN LA_BOARD ON LA_CARD_FULL.BOARD_SHORT_NAME = LA_BOARD.BOARD_SHORT_NAME "
+ + "WHERE BOARD_ID = :boardId AND BOARD_COLUMN_LOCATION = 'BOARD' "
+ + "GROUP BY ASSIGNED_USER_ID) AS ASSIGNED ")
+ Double getAverageCardsPerUserOnBoard(@Bind("boardId") int boardId);
+
+ @Query("SELECT AVG(CARDS) FROM ( " + "SELECT COUNT(ASSIGNED_CARD_ID) AS CARDS, ASSIGNED_USER_ID "
+ + "FROM LA_ASSIGNED_CARD "
+ + "INNER JOIN LA_CARD_FULL ON LA_CARD_FULL.CARD_ID = LA_ASSIGNED_CARD.ASSIGNED_CARD_ID "
+ + "INNER JOIN LA_BOARD ON LA_CARD_FULL.BOARD_SHORT_NAME = LA_BOARD.BOARD_SHORT_NAME "
+ + "WHERE BOARD_ARCHIVED = FALSE AND BOARD_PROJECT_ID_FK = :projectId AND BOARD_COLUMN_LOCATION = 'BOARD' "
+ + "GROUP BY ASSIGNED_USER_ID) AS ASSIGNED ")
+ Double getAverageCardsPerUserOnProject(@Bind("projectId") int projectId);
+
+ // Cards by label
+
+ @Query("SELECT CARD_LABEL_ID, CARD_LABEL_NAME, CARD_LABEL_COLOR, "
+ + "CARD_LABEL_VALUE_TYPE, CARD_LABEL_VALUE_LIST_VALUE_FK, COUNT(*) AS LABEL_COUNT FROM LA_CARD "
+ + "INNER JOIN LA_CARD_LABEL_VALUE ON LA_CARD_LABEL_VALUE.CARD_ID_FK = LA_CARD.CARD_ID "
+ + "INNER JOIN LA_CARD_LABEL ON LA_CARD_LABEL.CARD_LABEL_ID = LA_CARD_LABEL_VALUE.CARD_LABEL_ID_FK "
+ + "INNER JOIN LA_BOARD_COLUMN ON LA_BOARD_COLUMN.BOARD_COLUMN_ID = LA_CARD.CARD_BOARD_COLUMN_ID_FK "
+ + "INNER JOIN LA_BOARD ON BOARD_COLUMN_BOARD_ID_FK = LA_BOARD.BOARD_ID "
+ + "WHERE LA_CARD_LABEL.CARD_LABEL_DOMAIN = 'USER' AND BOARD_ID = :boardId "
+ + "AND LA_CARD_LABEL_VALUE.CARD_LABEL_VALUE_DELETED = FALSE AND BOARD_COLUMN_LOCATION = 'BOARD' "
+ + "GROUP BY CARD_LABEL_ID, CARD_LABEL_NAME, CARD_LABEL_COLOR, CARD_LABEL_VALUE_TYPE, CARD_LABEL_VALUE_LIST_VALUE_FK "
+ + "ORDER BY CARD_LABEL_NAME")
+ List getCardsByLabelOnBoard(@Bind("boardId") int boardId);
+
+ @Query("SELECT CARD_LABEL_ID, CARD_LABEL_NAME, CARD_LABEL_COLOR, "
+ + "CARD_LABEL_VALUE_TYPE, CARD_LABEL_VALUE_LIST_VALUE_FK, COUNT(*) AS LABEL_COUNT FROM LA_CARD "
+ + "INNER JOIN LA_CARD_LABEL_VALUE ON LA_CARD_LABEL_VALUE.CARD_ID_FK = LA_CARD.CARD_ID "
+ + "INNER JOIN LA_CARD_LABEL ON LA_CARD_LABEL.CARD_LABEL_ID = LA_CARD_LABEL_VALUE.CARD_LABEL_ID_FK "
+ + "INNER JOIN LA_BOARD_COLUMN ON LA_BOARD_COLUMN.BOARD_COLUMN_ID = LA_CARD.CARD_BOARD_COLUMN_ID_FK "
+ + "INNER JOIN LA_BOARD ON BOARD_COLUMN_BOARD_ID_FK = LA_BOARD.BOARD_ID "
+ + "WHERE LA_CARD_LABEL.CARD_LABEL_DOMAIN = 'USER' AND BOARD_PROJECT_ID_FK = :projectId "
+ + "AND LA_CARD_LABEL_VALUE.CARD_LABEL_VALUE_DELETED = FALSE AND BOARD_ARCHIVED = FALSE AND BOARD_COLUMN_LOCATION = 'BOARD' "
+ + "GROUP BY CARD_LABEL_ID, CARD_LABEL_NAME, CARD_LABEL_COLOR, CARD_LABEL_VALUE_TYPE, CARD_LABEL_VALUE_LIST_VALUE_FK "
+ + "ORDER BY CARD_LABEL_NAME")
+ List getCardsByLabelOnProject(@Bind("projectId") int projectId);
+
+ // Created cards by date
+
+ @Query("SELECT CAST(EVENT_TIME AS DATE) AS EVENT_DATE, COUNT(*) AS EVENT_COUNT FROM LA_EVENT "
+ + "INNER JOIN LA_BOARD_COLUMN ON EVENT_COLUMN_ID_FK = BOARD_COLUMN_ID "
+ + "INNER JOIN LA_BOARD ON BOARD_COLUMN_BOARD_ID_FK = BOARD_ID "
+ + "WHERE EVENT_TYPE = 'CARD_CREATE' AND BOARD_ID = :boardId AND EVENT_TIME >= :fromDate "
+ + "GROUP BY EVENT_DATE ORDER BY EVENT_DATE")
+ List getCreatedCardsByBoard(@Bind("boardId") int boardId, @Bind("fromDate") Date fromDate);
+
+ @Query("SELECT CAST(EVENT_TIME AS DATE) AS EVENT_DATE, COUNT(*) AS EVENT_COUNT FROM LA_EVENT "
+ + "INNER JOIN LA_BOARD_COLUMN ON EVENT_COLUMN_ID_FK = BOARD_COLUMN_ID "
+ + "INNER JOIN LA_BOARD ON BOARD_COLUMN_BOARD_ID_FK = BOARD_ID "
+ + "WHERE EVENT_TYPE = 'CARD_CREATE' AND BOARD_ARCHIVED = FALSE AND BOARD_PROJECT_ID_FK = :projectId AND EVENT_TIME >= :fromDate "
+ + "GROUP BY EVENT_DATE ORDER BY EVENT_DATE")
+ List getCreatedCardsByProject(@Bind("projectId") int projectId, @Bind("fromDate") Date fromDate);
+
+ // Closed cards by date
+
+ @Query("SELECT CAST(EVENT_TIME AS DATE) AS EVENT_DATE, COUNT(*) AS EVENT_COUNT FROM LA_EVENT "
+ + "JOIN LA_BOARD_COLUMN old on LA_EVENT.EVENT_PREV_COLUMN_ID_FK = old.BOARD_COLUMN_ID "
+ + "JOIN LA_BOARD_COLUMN_DEFINITION oldDef on old.BOARD_COLUMN_DEFINITION_ID_FK = oldDef.BOARD_COLUMN_DEFINITION_ID "
+ + "JOIN LA_BOARD_COLUMN new on LA_EVENT.EVENT_COLUMN_ID_FK = new.BOARD_COLUMN_ID "
+ + "JOIN LA_BOARD_COLUMN_DEFINITION newDef on new.BOARD_COLUMN_DEFINITION_ID_FK = newDef.BOARD_COLUMN_DEFINITION_ID "
+ + "JOIN LA_BOARD ON new.BOARD_COLUMN_BOARD_ID_FK = LA_BOARD.BOARD_ID "
+ + "WHERE (EVENT_TYPE = 'CARD_MOVE' OR EVENT_TYPE = 'CARD_ARCHIVE' OR EVENT_TYPE = 'CARD_TRASH') AND "
+ + "BOARD_ID = :boardId AND EVENT_TIME >= :fromDate AND "
+ + "oldDef.BOARD_COLUMN_DEFINITION_VALUE <> 'CLOSED' AND newDef.BOARD_COLUMN_DEFINITION_VALUE = 'CLOSED' "
+ + "GROUP BY EVENT_DATE ORDER BY EVENT_DATE")
+ List getClosedCardsByBoard(@Bind("boardId") int boardId, @Bind("fromDate") Date fromDate);
+
+ @Query("SELECT CAST(EVENT_TIME AS DATE) AS EVENT_DATE, COUNT(*) AS EVENT_COUNT FROM LA_EVENT "
+ + "JOIN LA_BOARD_COLUMN old on LA_EVENT.EVENT_PREV_COLUMN_ID_FK = old.BOARD_COLUMN_ID "
+ + "JOIN LA_BOARD_COLUMN_DEFINITION oldDef on old.BOARD_COLUMN_DEFINITION_ID_FK = oldDef.BOARD_COLUMN_DEFINITION_ID "
+ + "JOIN LA_BOARD_COLUMN new on LA_EVENT.EVENT_COLUMN_ID_FK = new.BOARD_COLUMN_ID "
+ + "JOIN LA_BOARD_COLUMN_DEFINITION newDef on new.BOARD_COLUMN_DEFINITION_ID_FK = newDef.BOARD_COLUMN_DEFINITION_ID "
+ + "JOIN LA_BOARD ON new.BOARD_COLUMN_BOARD_ID_FK = LA_BOARD.BOARD_ID "
+ + "WHERE (EVENT_TYPE = 'CARD_MOVE' OR EVENT_TYPE = 'CARD_ARCHIVE' OR EVENT_TYPE = 'CARD_TRASH') AND "
+ + "BOARD_ARCHIVED = FALSE AND BOARD_PROJECT_ID_FK = :projectId AND EVENT_TIME >= :fromDate AND "
+ + "oldDef.BOARD_COLUMN_DEFINITION_VALUE <> 'CLOSED' AND newDef.BOARD_COLUMN_DEFINITION_VALUE = 'CLOSED' "
+ + "GROUP BY EVENT_DATE ORDER BY EVENT_DATE")
+ List getClosedCardsByProject(@Bind("projectId") int projectId, @Bind("fromDate") Date fromDate);
+
+ // Most active card
+
+ @Query("SELECT CARD_ID, CARD_NAME, CARD_SEQ_NUMBER, CARD_ORDER, CARD_BOARD_COLUMN_ID_FK, CREATE_USER, CREATE_TIME, LAST_UPDATE_USER, LAST_UPDATE_TIME, BOARD_COLUMN_DEFINITION_VALUE, LA_CARD_FULL.BOARD_SHORT_NAME, LA_CARD_FULL.PROJECT_SHORT_NAME, COUNT(*) AS EVENTS_COUNT FROM LA_EVENT "
+ + "INNER JOIN LA_CARD_FULL ON CARD_ID = EVENT_CARD_ID_FK "
+ + "INNER JOIN LA_BOARD_COLUMN ON CARD_BOARD_COLUMN_ID_FK = BOARD_COLUMN_ID "
+ + "INNER JOIN LA_BOARD ON BOARD_COLUMN_BOARD_ID_FK = BOARD_ID "
+ + "WHERE BOARD_ID = :boardId AND EVENT_TIME >= :fromDate AND LA_BOARD_COLUMN.BOARD_COLUMN_LOCATION = 'BOARD' "
+ + "GROUP BY CARD_ID, CARD_NAME, CARD_SEQ_NUMBER, CARD_ORDER, CARD_BOARD_COLUMN_ID_FK, CREATE_USER, CREATE_TIME, LAST_UPDATE_USER, LAST_UPDATE_TIME, BOARD_COLUMN_DEFINITION_VALUE, LA_CARD_FULL.BOARD_SHORT_NAME, PROJECT_SHORT_NAME ORDER BY EVENTS_COUNT DESC LIMIT 1")
+ CardFull getMostActiveCardByBoard(@Bind("boardId") int boardId, @Bind("fromDate") Date fromDate);
+
+ @Query("SELECT CARD_ID, CARD_NAME, CARD_SEQ_NUMBER, CARD_ORDER, CARD_BOARD_COLUMN_ID_FK, CREATE_USER, CREATE_TIME, LAST_UPDATE_USER, LAST_UPDATE_TIME, BOARD_COLUMN_DEFINITION_VALUE, LA_CARD_FULL.BOARD_SHORT_NAME, LA_CARD_FULL.PROJECT_SHORT_NAME, COUNT(*) AS EVENTS_COUNT FROM LA_EVENT "
+ + "INNER JOIN LA_CARD_FULL ON CARD_ID = EVENT_CARD_ID_FK "
+ + "INNER JOIN LA_BOARD_COLUMN ON CARD_BOARD_COLUMN_ID_FK = BOARD_COLUMN_ID "
+ + "INNER JOIN LA_BOARD ON BOARD_COLUMN_BOARD_ID_FK = BOARD_ID "
+ + "WHERE BOARD_ARCHIVED = FALSE AND BOARD_PROJECT_ID_FK = :projectId AND EVENT_TIME >= :fromDate AND LA_BOARD_COLUMN.BOARD_COLUMN_LOCATION = 'BOARD' "
+ + "GROUP BY CARD_ID, CARD_NAME, CARD_SEQ_NUMBER, CARD_ORDER, CARD_BOARD_COLUMN_ID_FK, CREATE_USER, CREATE_TIME, LAST_UPDATE_USER, LAST_UPDATE_TIME, BOARD_COLUMN_DEFINITION_VALUE, LA_CARD_FULL.BOARD_SHORT_NAME, PROJECT_SHORT_NAME ORDER BY EVENTS_COUNT DESC LIMIT 1")
+ CardFull getMostActiveCardByProject(@Bind("projectId") int projectId, @Bind("fromDate") Date fromDate);
+
+ // Milestones
+
+ @Query("SELECT CAST(EVENT_TIME AS DATE) AS EVENT_DATE, COUNT(*) AS EVENT_COUNT FROM LA_EVENT "
+ + "WHERE EVENT_TYPE = 'LABEL_CREATE' AND EVENT_VALUE_STRING = :milestone AND EVENT_LABEL_NAME = 'MILESTONE' AND EVENT_TIME >= :fromDate "
+ + "GROUP BY EVENT_DATE ORDER BY EVENT_DATE")
+ List getAssignedCardsByMilestone(@Bind("milestone") String milestone, @Bind("fromDate") Date fromDate);
+
+ @Query("SELECT CAST(EVENT_TIME AS DATE) AS EVENT_DATE, COUNT(*) AS EVENT_COUNT FROM LA_EVENT "
+ + "JOIN LA_BOARD_COLUMN old on LA_EVENT.EVENT_PREV_COLUMN_ID_FK = old.BOARD_COLUMN_ID "
+ + "JOIN LA_BOARD_COLUMN_DEFINITION oldDef on old.BOARD_COLUMN_DEFINITION_ID_FK = oldDef.BOARD_COLUMN_DEFINITION_ID "
+ + "JOIN LA_BOARD_COLUMN new on LA_EVENT.EVENT_COLUMN_ID_FK = new.BOARD_COLUMN_ID "
+ + "JOIN LA_BOARD_COLUMN_DEFINITION newDef on new.BOARD_COLUMN_DEFINITION_ID_FK = newDef.BOARD_COLUMN_DEFINITION_ID "
+ + "INNER JOIN LA_CARD_LABEL_VALUE ON EVENT_CARD_ID_FK = CARD_ID_FK AND CARD_LABEL_VALUE_DELETED <> TRUE "
+ + "WHERE (EVENT_TYPE = 'CARD_MOVE' OR EVENT_TYPE = 'CARD_ARCHIVE' OR EVENT_TYPE = 'CARD_TRASH') AND "
+ + "CARD_LABEL_VALUE_LIST_VALUE_FK = :milestoneId AND EVENT_TIME >= :fromDate AND "
+ + "oldDef.BOARD_COLUMN_DEFINITION_VALUE <> 'CLOSED' AND newDef.BOARD_COLUMN_DEFINITION_VALUE = 'CLOSED' "
+ + "GROUP BY EVENT_DATE ORDER BY EVENT_DATE")
+ List getClosedCardsByMilestone(@Bind("milestoneId") int milestoneId, @Bind("fromDate") Date fromDate);
+
+ @Query("(SELECT CARD_LABEL_VALUE_LIST_VALUE_FK, BOARD_COLUMN_DEFINITION_VALUE, COUNT(BOARD_COLUMN_DEFINITION_VALUE) AS MILESTONE_COUNT FROM LA_CARD "
+ + "INNER JOIN LA_BOARD_COLUMN ON CARD_BOARD_COLUMN_ID_FK = BOARD_COLUMN_ID AND BOARD_COLUMN_LOCATION <> 'TRASH' "
+ + "INNER JOIN LA_BOARD_COLUMN_DEFINITION ON BOARD_COLUMN_DEFINITION_ID_FK = BOARD_COLUMN_DEFINITION_ID "
+ + "INNER JOIN LA_BOARD ON BOARD_ID = BOARD_COLUMN_BOARD_ID_FK AND BOARD_PROJECT_ID_FK = :projectId "
+ + "INNER JOIN LA_CARD_LABEL_VALUE ON CARD_ID = CARD_ID_FK AND CARD_LABEL_VALUE_DELETED <> TRUE "
+ + "INNER JOIN LA_CARD_LABEL ON CARD_LABEL_ID = CARD_LABEL_ID_FK AND CARD_LABEL_NAME = 'MILESTONE' AND CARD_LABEL_DOMAIN = 'SYSTEM' "
+ + "GROUP BY CARD_LABEL_VALUE_LIST_VALUE_FK, BOARD_COLUMN_DEFINITION_VALUE) "
+ + "UNION (SELECT NULL, BOARD_COLUMN_DEFINITION_VALUE, COUNT(BOARD_COLUMN_DEFINITION_VALUE) FROM LA_CARD "
+ + "INNER JOIN LA_BOARD_COLUMN ON CARD_BOARD_COLUMN_ID_FK = BOARD_COLUMN_ID AND BOARD_COLUMN_LOCATION <> 'TRASH' "
+ + "INNER JOIN LA_BOARD_COLUMN_DEFINITION ON BOARD_COLUMN_DEFINITION_ID_FK = BOARD_COLUMN_DEFINITION_ID "
+ + "INNER JOIN LA_BOARD ON BOARD_ID = BOARD_COLUMN_BOARD_ID_FK AND BOARD_PROJECT_ID_FK = :projectId "
+ + "WHERE CARD_ID NOT IN (SELECT CARD_ID_FK AS CARD_ID FROM LA_CARD_LABEL_VALUE "
+ + "INNER JOIN LA_CARD_LABEL ON CARD_LABEL_ID = CARD_LABEL_ID_FK "
+ + "WHERE CARD_LABEL_VALUE_DELETED <> TRUE AND CARD_LABEL_DOMAIN = 'SYSTEM' AND CARD_LABEL_NAME = 'MILESTONE') "
+ + "GROUP BY BOARD_COLUMN_DEFINITION_VALUE) ")
+ List findCardsCountByMilestone(@Bind("projectId") int projectId);
+}
\ No newline at end of file
diff --git a/src/main/java/io/lavagna/query/UserQuery.java b/src/main/java/io/lavagna/query/UserQuery.java
new file mode 100644
index 000000000..eda3c1a24
--- /dev/null
+++ b/src/main/java/io/lavagna/query/UserQuery.java
@@ -0,0 +1,103 @@
+/**
+ * This file is part of lavagna.
+ *
+ * lavagna is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * lavagna is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with lavagna. If not, see .
+ */
+package io.lavagna.query;
+
+import io.lavagna.common.Bind;
+import io.lavagna.common.Query;
+import io.lavagna.common.QueryRepository;
+import io.lavagna.common.QueryType;
+import io.lavagna.model.User;
+
+import java.util.Collection;
+import java.util.Date;
+import java.util.List;
+
+@QueryRepository
+public interface UserQuery {
+
+ @Query("INSERT INTO LA_USER(USER_PROVIDER, USER_NAME, USER_EMAIL, USER_DISPLAY_NAME, USER_ENABLED) VALUES (:provider, :userName, :email, :displayName, :enabled)")
+ int createUser(@Bind("provider") String provider, @Bind("userName") String username, @Bind("email") String email,
+ @Bind("displayName") String displayName, @Bind("enabled") boolean enabled);
+
+ @Query(type = QueryType.TEMPLATE, value = "INSERT INTO LA_USER(USER_PROVIDER, USER_NAME, USER_EMAIL, USER_DISPLAY_NAME, USER_ENABLED, USER_EMAIL_NOTIFICATION, USER_MEMBER_SINCE) VALUES "
+ + " (:provider, :userName, :email, :displayName, :enabled, :emailNotification, :memberSince)")
+ String createUserFull();
+
+ @Query("SELECT * FROM LA_USER WHERE USER_NAME = :userName AND USER_PROVIDER = :provider")
+ User findUserByName(@Bind("provider") String provider, @Bind("userName") String userName);
+
+ @Query("SELECT * FROM LA_USER WHERE USER_ID = :userId")
+ User findUserById(@Bind("userId") int userId);
+
+ @Query("SELECT * FROM LA_USER WHERE USER_ID IN (:userIds)")
+ List findByIds(@Bind("userIds") Collection userIds);
+
+ @Query("SELECT COUNT(*) FROM LA_USER WHERE USER_NAME = :userName AND USER_PROVIDER = :provider AND (USER_ENABLED IS NULL OR USER_ENABLED = :enabled) ")
+ Integer userExistsAndEnabled(@Bind("provider") String provider, @Bind("userName") String userName,
+ @Bind("enabled") boolean enabled);
+
+ @Query("SELECT COUNT(*) FROM LA_USER WHERE USER_NAME = :userName AND USER_PROVIDER = :provider")
+ Integer userExistsAndEnabled(@Bind("provider") String provider, @Bind("userName") String userName);
+
+ String FIND_USER_COMMON_WHERE = " WHERE LOWER(USER_PROVIDER) <> 'system' AND "
+ + "(LOWER(USER_PROVIDER) LIKE CONCAT('%', LOWER(:criteria),'%') OR LOWER(USER_NAME) LIKE CONCAT('%', LOWER(:criteria),'%') OR LOWER(USER_EMAIL) LIKE CONCAT('%', LOWER(:criteria),'%') "
+ + " OR LOWER(USER_DISPLAY_NAME) LIKE CONCAT('%', LOWER(:criteria),'%') ) ORDER BY USER_PROVIDER, USER_NAME LIMIT 10";
+
+ @Query("SELECT * FROM LA_USER " + FIND_USER_COMMON_WHERE)
+ List findUsers(@Bind("criteria") String criteria);
+
+ @Query("SELECT * FROM LA_USER "//
+ + "inner join "//
+ + "(select USER_ID_FK from LA_PROJECT_ROLE_PERMISSION "//
+ + "inner join LA_PROJECT_ROLE on LA_PROJECT_ROLE_PERMISSION.project_role_id_fk = project_role_id "//
+ + "inner join LA_PROJECT_USER_ROLE on LA_PROJECT_USER_ROLE.project_role_id_fk = project_role_id "//
+ + "WHERE PERMISSION = :permission AND LA_PROJECT_ROLE.PROJECT_ID_FK = :projectId "//
+ + "union "//
+ + "select USER_ID_FK from LA_ROLE_PERMISSION "//
+ + "inner join LA_ROLE on LA_ROLE_PERMISSION.role_id_fk = role_id "//
+ + "inner join LA_USER_ROLE on LA_USER_ROLE .role_id_fk = role_id "//
+ + "WHERE PERMISSION = :permission) as filtered_users on user_id = user_id_fk "//
+ + FIND_USER_COMMON_WHERE)
+ List findUsers(@Bind("criteria") String criteria, @Bind("projectId") int projectId,
+ @Bind("permission") String permission);
+
+ @Query("UPDATE LA_USER SET USER_EMAIL = :email, USER_DISPLAY_NAME = :displayName, USER_EMAIL_NOTIFICATION = :emailNotification WHERE USER_ID = :userId")
+ int updateProfile(@Bind("email") String email, @Bind("displayName") String displayName,
+ @Bind("emailNotification") boolean emailNotification, @Bind("userId") int userId);
+
+ @Query("SELECT * FROM LA_USER ORDER BY USER_PROVIDER, USER_NAME")
+ List findAll();
+
+ @Query("UPDATE LA_USER SET USER_ENABLED = :enabled WHERE USER_ID = :userId")
+ int toggle(@Bind("enabled") boolean enabled, @Bind("userId") int userId);
+
+ @Query(type = QueryType.TEMPLATE, value = "SELECT CONCAT(CONCAT(USER_PROVIDER, ':'), USER_NAME) AS PROVIDER_USER, USER_ID FROM LA_USER WHERE (USER_PROVIDER, USER_NAME) IN (:users)")
+ String findUsersId();
+
+ @Query("INSERT INTO LA_USER_REMEMBER(USER_REMEMBER_HASHED_TOKEN, USER_REMEMBER_ID_FK, USER_REMEMBER_LAST_USE) VALUES (:hashedToken, :userId, :lastUse)")
+ int registerRememberMeToken(@Bind("hashedToken") String hashedToken, @Bind("userId") int userId,
+ @Bind("lastUse") Date lastUse);
+
+ @Query("DELETE FROM LA_USER_REMEMBER WHERE USER_REMEMBER_HASHED_TOKEN = :hashedToken AND USER_REMEMBER_ID_FK = :userId")
+ int deleteToken(@Bind("hashedToken") String hashedToken, @Bind("userId") int userId);
+
+ @Query("SELECT COUNT(*) FROM LA_USER_REMEMBER WHERE USER_REMEMBER_HASHED_TOKEN = :hashedToken AND USER_REMEMBER_ID_FK = :userId")
+ Integer tokenExists(@Bind("hashedToken") String hashedToken, @Bind("userId") int userId);
+
+ @Query("DELETE FROM LA_USER_REMEMBER WHERE USER_REMEMBER_ID_FK = :userId")
+ int deleteAllTokensForUserId(@Bind("userId") int id);
+}
diff --git a/src/main/java/io/lavagna/query/ValidationQuery.java b/src/main/java/io/lavagna/query/ValidationQuery.java
new file mode 100644
index 000000000..a70180cb9
--- /dev/null
+++ b/src/main/java/io/lavagna/query/ValidationQuery.java
@@ -0,0 +1,32 @@
+/**
+ * This file is part of lavagna.
+ *
+ * lavagna is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * lavagna is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with lavagna. If not, see .
+ */
+package io.lavagna.query;
+
+import io.lavagna.common.QueriesOverride;
+import io.lavagna.common.Query;
+import io.lavagna.common.QueryOverride;
+import io.lavagna.common.QueryRepository;
+import io.lavagna.common.QueryType;
+
+@QueryRepository
+public interface ValidationQuery {
+
+ @Query(type = QueryType.TEMPLATE, value = "SELECT 1 FROM INFORMATION_SCHEMA.SYSTEM_USERS")
+ @QueriesOverride({ @QueryOverride(db = DB.MYSQL, value = "SELECT 1"),
+ @QueryOverride(db = DB.PGSQL, value = "SELECT 1") })
+ String validation();
+}
diff --git a/src/main/java/io/lavagna/service/BoardColumnRepository.java b/src/main/java/io/lavagna/service/BoardColumnRepository.java
new file mode 100644
index 000000000..607944841
--- /dev/null
+++ b/src/main/java/io/lavagna/service/BoardColumnRepository.java
@@ -0,0 +1,156 @@
+/**
+ * This file is part of lavagna.
+ *
+ * lavagna is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * lavagna is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with lavagna. If not, see .
+ */
+package io.lavagna.service;
+
+import static org.apache.commons.lang3.StringUtils.trimToNull;
+import io.lavagna.model.BoardColumn;
+import io.lavagna.model.BoardColumn.BoardColumnLocation;
+import io.lavagna.model.BoardColumnInfo;
+import io.lavagna.model.User;
+import io.lavagna.query.BoardColumnQuery;
+
+import java.util.Collections;
+import java.util.Date;
+import java.util.List;
+import java.util.Objects;
+import java.util.Set;
+
+import org.apache.commons.lang3.Validate;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
+import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
+import org.springframework.jdbc.core.namedparam.SqlParameterSource;
+import org.springframework.stereotype.Repository;
+import org.springframework.transaction.annotation.Transactional;
+
+@Repository
+@Transactional(readOnly = true)
+public class BoardColumnRepository {
+
+ private final NamedParameterJdbcTemplate jdbc;
+ private final EventRepository eventRepository;
+ private final BoardColumnQuery queries;
+
+ @Autowired
+ public BoardColumnRepository(NamedParameterJdbcTemplate jdbc, EventRepository eventRepository,
+ BoardColumnQuery queries) {
+ this.jdbc = jdbc;
+ this.eventRepository = eventRepository;
+ this.queries = queries;
+ }
+
+ public BoardColumnInfo getColumnInfoById(int columnId) {
+ return queries.getColumnInfoById(columnId);
+ }
+
+ public BoardColumn findById(int columnId) {
+ return queries.findById(columnId);
+ }
+
+ public BoardColumn findDefaultColumnFor(int boardId, BoardColumnLocation location) {
+ return queries.findDefaultColumnFor(boardId, location.toString());
+ }
+
+ public List findAllColumnsFor(int boardId) {
+ return queries.findAllColumnFor(boardId);
+ }
+
+ public int updateOrder(int columnId, int order) {
+ return queries.updateOrder(columnId, order);
+ }
+
+ public List findAllColumnsFor(int boardId, BoardColumnLocation location) {
+ return queries.findAllColumnFor(boardId, location.toString());
+ }
+
+ public List findByIds(Set ids) {
+ if (ids.isEmpty()) {
+ return Collections.emptyList();
+ }
+ return queries.findByIds(ids);
+ }
+
+ /**
+ * Returns the new Column
+ *
+ * @param name
+ * @param boardId
+ * @return
+ */
+ @Transactional(readOnly = false)
+ public BoardColumn addColumnToBoard(String name, int definitionId, BoardColumnLocation location, int boardId) {
+ Objects.requireNonNull(name);
+ Objects.requireNonNull(location);
+
+ queries.addColumnToBoard(trimToNull(name), boardId, location.toString(), definitionId);
+
+ return queries.findLastCreatedColumn();
+ }
+
+ @Transactional(readOnly = false)
+ public int renameColumn(int columnId, String newName, int boardId) {
+ return queries.renameColumn(trimToNull(newName), columnId, boardId);
+ }
+
+ /**
+ * update column order in a given board/location. The column ids are filtered.
+ *
+ * @param columns
+ * @param boardId
+ * @param location
+ */
+ @Transactional(readOnly = false)
+ public void updateColumnOrder(List columns, int boardId, BoardColumnLocation location) {
+ Objects.requireNonNull(columns);
+ Objects.requireNonNull(location);
+
+ // keep only the columns that are inside the boardId and have location=board
+ List filteredColumns = Utils.filter(columns,
+ queries.findColumnIdsInBoard(columns, location.toString(), boardId));
+ //
+
+ SqlParameterSource[] params = new SqlParameterSource[filteredColumns.size()];
+ for (int i = 0; i < filteredColumns.size(); i++) {
+ params[i] = new MapSqlParameterSource("order", i + 1).addValue("columnId", filteredColumns.get(i))
+ .addValue("boardId", boardId).addValue("location", location.toString());
+ }
+
+ jdbc.batchUpdate(queries.updateColumnOrder(), params);
+ }
+
+ @Transactional(readOnly = false)
+ public int moveToLocation(int id, BoardColumnLocation location, User user) {
+ Validate.isTrue(location != BoardColumnLocation.BOARD);
+
+ // copy the column definition id of the default one
+ int columnDefinitionId = findDefaultColumnFor(findById(id).getBoardId(), location).getDefinitionId();
+ //
+
+ int res = queries.moveToLocation(id, location.toString(), columnDefinitionId);
+
+ List cardIds = queries.findCardsInColumnId(id);
+ eventRepository.insertCardEvent(cardIds, id, user.getId(), BoardColumnLocation.MAPPING.get(location),
+ new Date());
+
+ return res;
+ }
+
+ @Transactional(readOnly = false)
+ public int redefineColumn(int columnId, int definitionId, int boardId) {
+ return queries.redefineColumn(definitionId, columnId, boardId);
+ }
+}
diff --git a/src/main/java/io/lavagna/service/BoardRepository.java b/src/main/java/io/lavagna/service/BoardRepository.java
new file mode 100644
index 000000000..15f558910
--- /dev/null
+++ b/src/main/java/io/lavagna/service/BoardRepository.java
@@ -0,0 +1,122 @@
+/**
+ * This file is part of lavagna.
+ *
+ * lavagna is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * lavagna is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with lavagna. If not, see .
+ */
+package io.lavagna.service;
+
+import static org.apache.commons.lang3.StringUtils.trimToNull;
+import io.lavagna.model.Board;
+import io.lavagna.model.BoardColumn.BoardColumnLocation;
+import io.lavagna.model.BoardColumnDefinition;
+import io.lavagna.model.BoardInfo;
+import io.lavagna.model.ColumnDefinition;
+import io.lavagna.model.ProjectAndBoard;
+import io.lavagna.query.BoardQuery;
+
+import java.util.List;
+import java.util.Locale;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Repository;
+import org.springframework.transaction.annotation.Transactional;
+
+@Repository
+@Transactional(readOnly = true)
+public class BoardRepository {
+
+ private final BoardColumnRepository boardColumnRepository;
+ private final BoardQuery queries;
+
+ @Autowired
+ public BoardRepository(BoardQuery queries, BoardColumnRepository boardColumnRepository) {
+ this.boardColumnRepository = boardColumnRepository;
+ this.queries = queries;
+ }
+
+ @Transactional(readOnly = false)
+ public Board createEmptyBoard(String name, String shortName, String description, int projectId) {
+ queries.createNewBoard(trimToNull(name), trimToNull(shortName.toUpperCase(Locale.ENGLISH)),
+ trimToNull(description), projectId);
+ queries.initializeSequence();
+
+ return queries.findLastCreatedBoard();
+ }
+
+ /**
+ * Returns the new BOARD.
+ *
+ * Additionally, add some predefined SYSTEM labels.
+ *
+ * @param name
+ * @param shortName
+ * @param description
+ * @return
+ */
+ @Transactional(readOnly = false)
+ public Board createNewBoard(String name, String shortName, String description, int projectId) {
+ Board board = createEmptyBoard(name, shortName, description, projectId);
+
+ BoardColumnDefinition closedDefinition = findColumnDefinitionByProjectIdAndType(ColumnDefinition.CLOSED,
+ projectId);
+ BoardColumnDefinition backlogDefinition = findColumnDefinitionByProjectIdAndType(ColumnDefinition.BACKLOG,
+ projectId);
+ // Add the ARCHIVE and BACKLOG columns
+ boardColumnRepository.addColumnToBoard(BoardColumnLocation.ARCHIVE.toString(), closedDefinition.getId(),
+ BoardColumnLocation.ARCHIVE, board.getId());
+ boardColumnRepository.addColumnToBoard(BoardColumnLocation.BACKLOG.toString(), backlogDefinition.getId(),
+ BoardColumnLocation.BACKLOG, board.getId());
+ boardColumnRepository.addColumnToBoard(BoardColumnLocation.TRASH.toString(), closedDefinition.getId(),
+ BoardColumnLocation.TRASH, board.getId());
+ return board;
+ }
+
+ @Transactional(readOnly = false)
+ public Board updateBoard(int boardId, String name, String description, boolean archived) {
+ queries.updateBoard(boardId, name, description, archived);
+ return queries.findBoardById(boardId);
+ }
+
+ public Integer findBoardIdByShortName(String shortName) {
+ return queries.findBoardIdByShortName(shortName);
+ }
+
+ public Board findBoardByShortName(String shortName) {
+ return queries.findBoardByShortName(shortName);
+ }
+
+ public boolean existsWithShortName(String shortName) {
+ return Integer.valueOf(1).equals(queries.existsWithShortName(shortName));
+ }
+
+ public Board findBoardById(int boardId) {
+ return queries.findBoardById(boardId);
+ }
+
+ public List findAll() {
+ return queries.findAll();
+ }
+
+ public List findBoardInfo(int projectId) {
+ return queries.findBoardInfo(projectId);
+ }
+
+ public BoardColumnDefinition findColumnDefinitionByProjectIdAndType(ColumnDefinition definition, int projectId) {
+ return queries.findColumnDefinitionByProjectIdAndType(projectId, definition.toString());
+ }
+
+ public ProjectAndBoard findProjectAndBoardByColumnId(int columnId) {
+ return queries.findProjectAndBoardByColumnId(columnId);
+ }
+}
diff --git a/src/main/java/io/lavagna/service/BulkOperationService.java b/src/main/java/io/lavagna/service/BulkOperationService.java
new file mode 100644
index 000000000..5cdbe5b44
--- /dev/null
+++ b/src/main/java/io/lavagna/service/BulkOperationService.java
@@ -0,0 +1,295 @@
+/**
+ * This file is part of lavagna.
+ *
+ * lavagna is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * lavagna is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with lavagna. If not, see .
+ */
+package io.lavagna.service;
+
+import io.lavagna.model.CardFull;
+import io.lavagna.model.CardLabel;
+import io.lavagna.model.Project;
+import io.lavagna.model.CardLabel.LabelDomain;
+import io.lavagna.model.CardLabelValue.LabelValue;
+import io.lavagna.model.LabelAndValue;
+import io.lavagna.model.User;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import org.apache.commons.lang3.Validate;
+import org.apache.commons.lang3.tuple.ImmutablePair;
+
+@Service
+@Transactional(readOnly = false)
+public class BulkOperationService {
+
+ private final CardRepository cardRepository;
+ private final CardLabelRepository cardLabelRepository;
+ private final ProjectService projectService;
+ private final LabelService labelService;
+
+ @Autowired
+ public BulkOperationService(CardRepository cardRepository, CardLabelRepository cardLabelRepository,
+ LabelService labelService, ProjectService projectService) {
+ this.cardRepository = cardRepository;
+ this.cardLabelRepository = cardLabelRepository;
+ this.labelService = labelService;
+ this.projectService = projectService;
+ }
+
+ public List assign(String projectShortName, List cardIds, LabelValue value, User user) {
+
+ List filteredCardIds = keepCardIdsInProject(cardIds, projectShortName);
+ int labelId = findBy(projectShortName, "ASSIGNED", LabelDomain.SYSTEM).getId();
+
+ // we remove the cards that have _already_ the user assigned
+ Collection alreadyWithUserAssigned = keepCardWithMatching(filteredCardIds,
+ new FilterByLabelIdAndLabelValue(labelId, value)).keySet();
+ filteredCardIds.removeAll(alreadyWithUserAssigned);
+ //
+
+ labelService.addLabelValueToCards(labelId, filteredCardIds, value, user, new Date());
+ return filteredCardIds;
+ }
+
+ public List removeAssign(String projectShortName, List cardIds, LabelValue value, User user) {
+ List filteredCardIds = keepCardIdsInProject(cardIds, projectShortName);
+ int labelId = findBy(projectShortName, "ASSIGNED", LabelDomain.SYSTEM).getId();
+
+ List removedIds = new ArrayList<>();
+ for (LabelAndValue lv : flatten(keepCardWithMatching(filteredCardIds,
+ new FilterByLabelIdAndLabelValue(labelId, value)).values())) {
+ labelService.removeLabelValue(lv.labelValue(), user, new Date());
+ removedIds.add(lv.getLabelValueCardId());
+ }
+
+ return removedIds;
+ }
+
+ public List reAssign(String projectShortName, List cardIds, LabelValue value, User user) {
+ List filteredCardIds = keepCardIdsInProject(cardIds, projectShortName);
+ int labelId = findBy(projectShortName, "ASSIGNED", LabelDomain.SYSTEM).getId();
+
+ // remove all assigned labels
+ for (LabelAndValue lv : flatten(keepCardWithMatching(filteredCardIds, new FilterByLabelId(labelId)).values())) {
+ labelService.removeLabelValue(lv.labelValue(), user, new Date());
+ }
+ //
+ labelService.addLabelValueToCards(labelId, filteredCardIds, value, user, new Date());
+ return filteredCardIds;
+ }
+
+ public ImmutablePair, List> setDueDate(String projectShortName, List cardIds,
+ LabelValue value, User user) {
+ return addLabelOrUpdate(projectShortName, cardIds, value, user, "DUE_DATE", LabelDomain.SYSTEM);
+ }
+
+ public List removeDueDate(String projectShortName, List cardIds, User user) {
+ return removeLabelWithName(projectShortName, cardIds, user, "DUE_DATE", LabelDomain.SYSTEM);
+ }
+
+ public ImmutablePair, List> setMilestone(String projectShortName, List cardIds,
+ LabelValue value, User user) {
+ return addLabelOrUpdate(projectShortName, cardIds, value, user, "MILESTONE", LabelDomain.SYSTEM);
+ }
+
+ public List removeMilestone(String projectShortName, List cardIds, User user) {
+ return removeLabelWithName(projectShortName, cardIds, user, "MILESTONE", LabelDomain.SYSTEM);
+ }
+
+ public List