diff --git a/avni-server-api/src/main/java/org/avni/server/dao/metabase/CollectionRepository.java b/avni-server-api/src/main/java/org/avni/server/dao/metabase/CollectionRepository.java index 50c28e816..ac07057c2 100644 --- a/avni-server-api/src/main/java/org/avni/server/dao/metabase/CollectionRepository.java +++ b/avni-server-api/src/main/java/org/avni/server/dao/metabase/CollectionRepository.java @@ -1,6 +1,6 @@ package org.avni.server.dao.metabase; -import org.avni.server.domain.metabase.Collection; +import org.avni.server.domain.metabase.CreateCollectionRequest; import org.avni.server.domain.metabase.CollectionResponse; import org.springframework.boot.web.client.RestTemplateBuilder; import org.springframework.stereotype.Repository; @@ -11,8 +11,8 @@ public CollectionRepository(RestTemplateBuilder restTemplateBuilder) { super(restTemplateBuilder); } - public CollectionResponse save(Collection collection) { + public CollectionResponse save(CreateCollectionRequest createCollectionRequest) { String url = metabaseApiUrl + "/collection"; - return postForObject(url, collection, CollectionResponse.class); + return postForObject(url, createCollectionRequest, CollectionResponse.class); } } diff --git a/avni-server-api/src/main/java/org/avni/server/dao/metabase/DatabaseRepository.java b/avni-server-api/src/main/java/org/avni/server/dao/metabase/DatabaseRepository.java index bd06541b6..c419f822f 100644 --- a/avni-server-api/src/main/java/org/avni/server/dao/metabase/DatabaseRepository.java +++ b/avni-server-api/src/main/java/org/avni/server/dao/metabase/DatabaseRepository.java @@ -1,13 +1,23 @@ package org.avni.server.dao.metabase; -import org.avni.server.domain.metabase.Database; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ArrayNode; +import org.avni.server.domain.metabase.*; +import org.avni.server.util.S; import org.springframework.boot.web.client.RestTemplateBuilder; import org.springframework.stereotype.Repository; +import com.fasterxml.jackson.databind.ObjectMapper; + +import java.util.ArrayList; +import java.util.List; @Repository public class DatabaseRepository extends MetabaseConnector { + private final ObjectMapper objectMapper; public DatabaseRepository(RestTemplateBuilder restTemplateBuilder) { super(restTemplateBuilder); + this.objectMapper = new ObjectMapper(); } public Database save(Database database) { @@ -16,4 +26,175 @@ public Database save(Database database) { database.setId(response.getId()); return database; } + + public Database getDatabaseByName(Database database) { + String url = metabaseApiUrl + "/database"; + + String jsonResponse = getForObject(url, String.class); + + try { + ObjectMapper objectMapper = new ObjectMapper(); + JsonNode rootNode = objectMapper.readTree(jsonResponse); + JsonNode dataArray = rootNode.path("data"); + + for (JsonNode dbNode : dataArray) { + Database db = objectMapper.treeToValue(dbNode, Database.class); + if (db.getName().equals(database.getName())) { + return db; + } + } + throw new RuntimeException("Database with name " + database.getName() + " not found."); + } catch (Exception e) { + throw new RuntimeException("Failed to retrieve database", e); + } + } + + public List getExistingCollectionItems(int collectionId) { + String url = metabaseApiUrl + "/collection/" + collectionId + "/items"; + String jsonResponse = getForObject(url, String.class); + + try { + JsonNode rootNode = objectMapper.readTree(jsonResponse); + JsonNode dataArray = rootNode.path("data"); + + List items = new ArrayList<>(); + for (JsonNode itemNode : dataArray) { + CollectionItem item = new CollectionItem(); + item.setName(itemNode.get("name").asText()); + item.setId(itemNode.get("id").asInt()); + items.add(item); + } + return items; + } catch (Exception e) { + throw new RuntimeException("Failed to fetch collection items", e); + } + } + + public CollectionInfoResponse getCollectionByName(Database database) { + String url = metabaseApiUrl + "/collection"; + try { + String jsonResponse = getForObject(url, String.class); + + ObjectMapper objectMapper = new ObjectMapper(); + List collections = objectMapper.readValue( + jsonResponse, new TypeReference>() {} + ); + + return collections.stream() + .filter(collection -> collection.getName().equals(database.getName())) + .findFirst() + .orElseThrow(() -> new RuntimeException("Collection with name " + database.getName() + " not found.")); + } catch (Exception e) { + throw new RuntimeException("Failed to retrieve collection", e); + } + } + + public void createQuestionForTable(Database database, TableDetails tableDetails, TableDetails addressTableDetails, FieldDetails originField, FieldDetails destinationField) { + FieldDetails joinField1 = getFieldDetailsByName(database, addressTableDetails, originField); + FieldDetails joinField2 = getFieldDetailsByName(database, tableDetails, destinationField); + + ArrayNode joinsArray = objectMapper.createArrayNode(); + MetabaseQuery query = new MetabaseQueryBuilder(database, joinsArray, objectMapper) + .forTable(tableDetails) + .joinWith(addressTableDetails, joinField1, joinField2) + .build(); + + MetabaseRequestBody requestBody = new MetabaseRequestBody( + tableDetails.getDisplayName(), + query, + VisualizationType.TABLE, + null, + objectMapper.createObjectNode(), + getCollectionByName(database).getIdAsInt() + ); + + postForObject(metabaseApiUrl + "/card", requestBody.toJson(objectMapper), JsonNode.class); + } + + public void createQuestionForASingleTable(Database database, TableDetails tableDetails) { + MetabaseQuery query = new MetabaseQueryBuilder(database, objectMapper.createArrayNode(), objectMapper) + .forTable(tableDetails) + .build(); + + MetabaseRequestBody requestBody = new MetabaseRequestBody( + tableDetails.getDisplayName(), + query, + VisualizationType.TABLE, + null, + objectMapper.createObjectNode(), + getCollectionByName(database).getIdAsInt() + ); + + postForObject(metabaseApiUrl + "/card", requestBody.toJson(objectMapper), JsonNode.class); + } + + public FieldDetails getFieldDetailsByName(Database database, TableDetails tableDetails, FieldDetails fieldDetails) { + List fieldsList = getFields(database); + String snakeCaseTableName = S.toSnakeCase(tableDetails.getName()); + + return fieldsList.stream() + .filter(field -> snakeCaseTableName.equals(field.getTableName()) && fieldDetails.getName().equals(field.getName())) + .findFirst() + .orElseThrow(() -> new RuntimeException("Field " + fieldDetails.getName() + " not found in table " + tableDetails.getName())); + } + + public MetabaseDatabaseInfo getDatabaseDetails(Database database) { + String url = metabaseApiUrl + "/database/" + database.getId() + "?include=tables"; + String jsonResponse = getForObject(url, String.class); + + try { + return objectMapper.readValue(jsonResponse, MetabaseDatabaseInfo.class); + } catch (Exception e) { + throw new RuntimeException("Failed to parse database details", e); + } + } + + public List getFields(Database database) { + String url = metabaseApiUrl + "/database/" + database.getId() + "/fields"; + String jsonResponse = getForObject(url, String.class); + + try { + return objectMapper.readValue(jsonResponse, new TypeReference>() {}); + } catch (Exception e) { + throw new RuntimeException("Failed to parse fields", e); + } + } + + public TableDetails findTableDetailsByName(Database database, TableDetails targetTable) { + MetabaseDatabaseInfo databaseInfo = getDatabaseDetails(database); + return databaseInfo.getTables().stream() + .filter(tableDetail -> tableDetail.getName().equalsIgnoreCase(targetTable.getName())) + .findFirst() + .orElseThrow(() -> new RuntimeException("Table with name " + targetTable.getName() + " not found.")); + } + + public DatabaseSyncStatus getInitialSyncStatus(Database database) { + String url = metabaseApiUrl + "/database/" + database.getId(); + String jsonResponse = getForObject(url, String.class); + try { + return objectMapper.readValue(jsonResponse, DatabaseSyncStatus.class); + } catch (Exception e) { + throw new RuntimeException("Failed to parse sync status", e); + } + } + + public DatasetResponse getDataset(DatasetRequestBody requestBody) { + String url = metabaseApiUrl + "/dataset"; + String jsonRequestBody = requestBody.toJson(objectMapper).toString(); + String jsonResponse = postForObject(url, jsonRequestBody, String.class); + try { + return objectMapper.readValue(jsonResponse, DatasetResponse.class); + } catch (Exception e) { + throw new RuntimeException("Failed to parse dataset response", e); + } + } + + public DatasetResponse findAll(TableDetails table, Database database) { + DatasetRequestBody requestBody = createRequestBodyForDataset(database, table); + return getDataset(requestBody); + } + + private DatasetRequestBody createRequestBodyForDataset(Database database, TableDetails table) { + return new DatasetRequestBody(database, table); + } } diff --git a/avni-server-api/src/main/java/org/avni/server/dao/metabase/MetabaseConnector.java b/avni-server-api/src/main/java/org/avni/server/dao/metabase/MetabaseConnector.java index d7f227e24..a4d8bea48 100644 --- a/avni-server-api/src/main/java/org/avni/server/dao/metabase/MetabaseConnector.java +++ b/avni-server-api/src/main/java/org/avni/server/dao/metabase/MetabaseConnector.java @@ -19,7 +19,7 @@ public class MetabaseConnector { protected String metabaseApiUrl; @Value("${metabase.api.key}") - private String apiKey; + protected String apiKey; public MetabaseConnector(RestTemplateBuilder restTemplateBuilder) { this.restTemplate = restTemplateBuilder.build(); @@ -42,7 +42,7 @@ protected void sendPutRequest(String url, Map requestBody) { restTemplate.exchange(url, HttpMethod.PUT, entity, Map.class); } - protected T postForObject(String url, Object request, Class responseType) { + public T postForObject(String url, Object request, Class responseType) { HttpEntity entity = createHttpEntity(request); return restTemplate.postForObject(url, entity, responseType); } @@ -57,4 +57,5 @@ protected HttpEntity> createJsonEntity(GroupPermissionsBody HttpHeaders headers = getHeaders(); return new HttpEntity<>(body.getBody(), headers); } + } diff --git a/avni-server-api/src/main/java/org/avni/server/domain/metabase/CardType.java b/avni-server-api/src/main/java/org/avni/server/domain/metabase/CardType.java new file mode 100644 index 000000000..2c45436ee --- /dev/null +++ b/avni-server-api/src/main/java/org/avni/server/domain/metabase/CardType.java @@ -0,0 +1,16 @@ +package org.avni.server.domain.metabase; + +//Refer Documentation : https://www.metabase.com/docs/latest/api/card#post-apicard +public enum CardType { + QUESTION("question"); + + private final String type; + + CardType(String type) { + this.type = type; + } + + public String getType() { + return type; + } +} diff --git a/avni-server-api/src/main/java/org/avni/server/domain/metabase/CollectionInfoResponse.java b/avni-server-api/src/main/java/org/avni/server/domain/metabase/CollectionInfoResponse.java new file mode 100644 index 000000000..322aff320 --- /dev/null +++ b/avni-server-api/src/main/java/org/avni/server/domain/metabase/CollectionInfoResponse.java @@ -0,0 +1,56 @@ +package org.avni.server.domain.metabase; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; + +@JsonIgnoreProperties(ignoreUnknown = true) +public class CollectionInfoResponse { + + private String name; + private String id; + private boolean isPersonal; + + public CollectionInfoResponse() { + } + + public CollectionInfoResponse(@JsonProperty("name") String name, + @JsonProperty("id") Object id, + @JsonProperty("is_personal") boolean isPersonal) { + this.name = name; + this.isPersonal = isPersonal; + + if (id instanceof Integer) { + this.id = String.valueOf(id); + } else { + this.id = id.toString(); + } + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getId() { + return id; + } + + public int getIdAsInt() { + try { + return Integer.parseInt(id); + } catch (NumberFormatException e) { + throw new RuntimeException("Failed to convert id to integer: " + id, e); + } + } + + public boolean isPersonal() { + return isPersonal; + } + + public void setPersonal(boolean personal) { + isPersonal = personal; + } +} diff --git a/avni-server-api/src/main/java/org/avni/server/domain/metabase/CollectionItem.java b/avni-server-api/src/main/java/org/avni/server/domain/metabase/CollectionItem.java new file mode 100644 index 000000000..c19a70571 --- /dev/null +++ b/avni-server-api/src/main/java/org/avni/server/domain/metabase/CollectionItem.java @@ -0,0 +1,25 @@ +package org.avni.server.domain.metabase; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; + +@JsonIgnoreProperties(ignoreUnknown = true) +public class CollectionItem { + private String name; + private int id; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } +} diff --git a/avni-server-api/src/main/java/org/avni/server/domain/metabase/ConditionType.java b/avni-server-api/src/main/java/org/avni/server/domain/metabase/ConditionType.java new file mode 100644 index 000000000..0a9a1c6c1 --- /dev/null +++ b/avni-server-api/src/main/java/org/avni/server/domain/metabase/ConditionType.java @@ -0,0 +1,16 @@ +package org.avni.server.domain.metabase; + +public enum ConditionType { + EQUAL("="), + NOT_EQUAL("!="); + + private final String operator; + + ConditionType(String operator) { + this.operator = operator; + } + + public String getOperator() { + return operator; + } +} diff --git a/avni-server-api/src/main/java/org/avni/server/domain/metabase/Collection.java b/avni-server-api/src/main/java/org/avni/server/domain/metabase/CreateCollectionRequest.java similarity index 73% rename from avni-server-api/src/main/java/org/avni/server/domain/metabase/Collection.java rename to avni-server-api/src/main/java/org/avni/server/domain/metabase/CreateCollectionRequest.java index 3b24bfa2c..b9fc8e6c2 100644 --- a/avni-server-api/src/main/java/org/avni/server/domain/metabase/Collection.java +++ b/avni-server-api/src/main/java/org/avni/server/domain/metabase/CreateCollectionRequest.java @@ -1,10 +1,10 @@ package org.avni.server.domain.metabase; -public class Collection { +public class CreateCollectionRequest { private String name; private String description; - public Collection(String name, String description) { + public CreateCollectionRequest(String name, String description) { this.name = name; this.description = description; } diff --git a/avni-server-api/src/main/java/org/avni/server/domain/metabase/Database.java b/avni-server-api/src/main/java/org/avni/server/domain/metabase/Database.java index 86b0ce604..21d388917 100644 --- a/avni-server-api/src/main/java/org/avni/server/domain/metabase/Database.java +++ b/avni-server-api/src/main/java/org/avni/server/domain/metabase/Database.java @@ -1,20 +1,28 @@ package org.avni.server.domain.metabase; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; + +@JsonIgnoreProperties(ignoreUnknown = true) public class Database { private Integer id; private String name; private String engine; private DatabaseDetails details; - public Database() { + } - + + public Database(String name) { + this.name = name; + } + public Database(String name, String engine, DatabaseDetails details) { this(null,name,engine,details); } - public Database(Integer id,String name, String engine, DatabaseDetails details) { + + public Database(Integer id, String name, String engine, DatabaseDetails details) { this.id=id; this.name = name; this.engine = engine; diff --git a/avni-server-api/src/main/java/org/avni/server/domain/metabase/DatabaseDetails.java b/avni-server-api/src/main/java/org/avni/server/domain/metabase/DatabaseDetails.java index 5844499a2..519f002e6 100644 --- a/avni-server-api/src/main/java/org/avni/server/domain/metabase/DatabaseDetails.java +++ b/avni-server-api/src/main/java/org/avni/server/domain/metabase/DatabaseDetails.java @@ -1,5 +1,8 @@ package org.avni.server.domain.metabase; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; + +@JsonIgnoreProperties(ignoreUnknown = true) public class DatabaseDetails { private String host; private String port; diff --git a/avni-server-api/src/main/java/org/avni/server/domain/metabase/DatabaseSyncStatus.java b/avni-server-api/src/main/java/org/avni/server/domain/metabase/DatabaseSyncStatus.java new file mode 100644 index 000000000..832efd201 --- /dev/null +++ b/avni-server-api/src/main/java/org/avni/server/domain/metabase/DatabaseSyncStatus.java @@ -0,0 +1,49 @@ +package org.avni.server.domain.metabase; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; + +@JsonIgnoreProperties(ignoreUnknown = true) +public class DatabaseSyncStatus { + + @JsonProperty("initial_sync_status") + private String initialSyncStatus; + + @JsonProperty("name") + private String name; + + @JsonProperty("is_full_sync") + private boolean isFullSync; + + @JsonProperty("id") + private int id; + + @JsonProperty("engine") + private String engine; + + + public String getInitialSyncStatus() { + return initialSyncStatus; + } + + + public String getName() { + return name; + } + + + public boolean isFullSync() { + return isFullSync; + } + + + public int getId() { + return id; + } + + + public String getEngine() { + return engine; + } + +} diff --git a/avni-server-api/src/main/java/org/avni/server/domain/metabase/DatasetColumn.java b/avni-server-api/src/main/java/org/avni/server/domain/metabase/DatasetColumn.java new file mode 100644 index 000000000..48509b4c7 --- /dev/null +++ b/avni-server-api/src/main/java/org/avni/server/domain/metabase/DatasetColumn.java @@ -0,0 +1,16 @@ +package org.avni.server.domain.metabase; + +public enum DatasetColumn { + NAME(1), + TYPE(2); + + private final int index; + + DatasetColumn(int index) { + this.index = index; + } + + public int getIndex() { + return index; + } +} \ No newline at end of file diff --git a/avni-server-api/src/main/java/org/avni/server/domain/metabase/DatasetRequestBody.java b/avni-server-api/src/main/java/org/avni/server/domain/metabase/DatasetRequestBody.java new file mode 100644 index 000000000..75846eb05 --- /dev/null +++ b/avni-server-api/src/main/java/org/avni/server/domain/metabase/DatasetRequestBody.java @@ -0,0 +1,29 @@ +package org.avni.server.domain.metabase; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; + +public class DatasetRequestBody { + + private final Database database; + private final TableDetails table; + + public DatasetRequestBody(Database database, TableDetails table) { + this.database = database; + this.table = table; + } + + public ObjectNode toJson(ObjectMapper objectMapper) { + ObjectNode rootNode = objectMapper.createObjectNode(); + rootNode.put("database", database.getId()); + + ObjectNode queryNode = objectMapper.createObjectNode(); + queryNode.put("source-table", table.getId()); + + rootNode.set("query", queryNode); + rootNode.put("type", "query"); + rootNode.set("parameters", objectMapper.createArrayNode()); + + return rootNode; + } +} diff --git a/avni-server-api/src/main/java/org/avni/server/domain/metabase/DatasetResponse.java b/avni-server-api/src/main/java/org/avni/server/domain/metabase/DatasetResponse.java new file mode 100644 index 000000000..7bf33ba90 --- /dev/null +++ b/avni-server-api/src/main/java/org/avni/server/domain/metabase/DatasetResponse.java @@ -0,0 +1,35 @@ +package org.avni.server.domain.metabase; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.List; + +@JsonIgnoreProperties(ignoreUnknown = true) +public class DatasetResponse { + + @JsonProperty("data") + private Data data; + + public Data getData() { + return data; + } + + public void setData(Data data) { + this.data = data; + } + + @JsonIgnoreProperties(ignoreUnknown = true) + public static class Data { + + @JsonProperty("rows") + private List> rows; + + public List> getRows() { + return rows; + } + + public void setRows(List> rows) { + this.rows = rows; + } + } +} diff --git a/avni-server-api/src/main/java/org/avni/server/domain/metabase/FieldAttribute.java b/avni-server-api/src/main/java/org/avni/server/domain/metabase/FieldAttribute.java new file mode 100644 index 000000000..c00910731 --- /dev/null +++ b/avni-server-api/src/main/java/org/avni/server/domain/metabase/FieldAttribute.java @@ -0,0 +1,32 @@ +package org.avni.server.domain.metabase; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; + +public enum FieldAttribute { + ALL("all"), + FIELD("field"), + FIELDS("fields"), + SOURCE_TABLE("source-table"), + ALIAS("alias"), + CONDITION("condition"), + JOINS("joins"), + JOIN_ALIAS("join-alias"), + BASE_TYPE("base-type"); + + private final String attributeName; + + FieldAttribute(String attributeName) { + this.attributeName = attributeName; + } + + public String getAttributeName() { + return attributeName; + } + + public ObjectNode toJson(ObjectMapper objectMapper, Object value) { + ObjectNode attributeNode = objectMapper.createObjectNode(); + attributeNode.put(attributeName, value.toString()); + return attributeNode; + } +} diff --git a/avni-server-api/src/main/java/org/avni/server/domain/metabase/FieldDetails.java b/avni-server-api/src/main/java/org/avni/server/domain/metabase/FieldDetails.java new file mode 100644 index 000000000..9ae172d77 --- /dev/null +++ b/avni-server-api/src/main/java/org/avni/server/domain/metabase/FieldDetails.java @@ -0,0 +1,73 @@ +package org.avni.server.domain.metabase; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; + +@JsonIgnoreProperties(ignoreUnknown = true) +public class FieldDetails { + public FieldDetails() { + } + + public FieldDetails(String name) { + this.name = name; + } + + @JsonProperty("id") + private int id; + + public void setId(int id) { + this.id = id; + } + + @JsonProperty("name") + private String name; + + public void setName(String name) { + this.name = name; + } + + @JsonProperty("display_name") + private String displayName; + + @JsonProperty("base_type") + private String baseType; + + @JsonProperty("semantic_type") + private String semanticType; + + @JsonProperty("table_name") + private String tableName; + + @JsonProperty("schema") + private String schema; + + public int getId() { + return id; + } + + public String getName() { + return name; + } + + public String getDisplayName() { + return displayName; + } + + public String getBaseType() { + return baseType; + } + + public String getSemanticType() { + return semanticType; + } + + public String getTableName() { + return tableName; + } + + + public String getSchema() { + return schema; + } + +} diff --git a/avni-server-api/src/main/java/org/avni/server/domain/metabase/FieldType.java b/avni-server-api/src/main/java/org/avni/server/domain/metabase/FieldType.java new file mode 100644 index 000000000..f4f93b6d4 --- /dev/null +++ b/avni-server-api/src/main/java/org/avni/server/domain/metabase/FieldType.java @@ -0,0 +1,19 @@ +package org.avni.server.domain.metabase; + +// Refer Documentation here : https://www.metabase.com/docs/latest/data-modeling/field-types + +public enum FieldType { + INTEGER("type/Integer"), + TEXT("type/Text"), + BOOLEAN("type/Boolean"); + + private final String typeName; + + FieldType(String typeName) { + this.typeName = typeName; + } + + public String getTypeName() { + return typeName; + } +} diff --git a/avni-server-api/src/main/java/org/avni/server/domain/metabase/MetabaseDatabaseInfo.java b/avni-server-api/src/main/java/org/avni/server/domain/metabase/MetabaseDatabaseInfo.java new file mode 100644 index 000000000..28ef8f5c3 --- /dev/null +++ b/avni-server-api/src/main/java/org/avni/server/domain/metabase/MetabaseDatabaseInfo.java @@ -0,0 +1,70 @@ +package org.avni.server.domain.metabase; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; + +import java.util.List; + +@JsonIgnoreProperties(ignoreUnknown = true) +public class MetabaseDatabaseInfo { + + @JsonProperty("description") + private String description; + + @JsonProperty("features") + private List features; + + @JsonProperty("cache_field_values_schedule") + private String cacheFieldValuesSchedule; + + @JsonProperty("timezone") + private String timezone; + + @JsonProperty("auto_run_queries") + private boolean autoRunQueries; + + @JsonProperty("metadata_sync_schedule") + private String metadataSyncSchedule; + + @JsonProperty("name") + private String name; + + @JsonProperty("tables") + private List tables; + + public String getDescription() { + return description; + } + + public List getFeatures() { + return features; + } + + public String getCacheFieldValuesSchedule() { + return cacheFieldValuesSchedule; + } + + public String getTimezone() { + return timezone; + } + + public void setTimezone(String timezone) { + this.timezone = timezone; + } + + public boolean isAutoRunQueries() { + return autoRunQueries; + } + + public String getMetadataSyncSchedule() { + return metadataSyncSchedule; + } + + public String getName() { + return name; + } + + public List getTables() { + return tables; + } +} diff --git a/avni-server-api/src/main/java/org/avni/server/domain/metabase/MetabaseQuery.java b/avni-server-api/src/main/java/org/avni/server/domain/metabase/MetabaseQuery.java new file mode 100644 index 000000000..8323d79b0 --- /dev/null +++ b/avni-server-api/src/main/java/org/avni/server/domain/metabase/MetabaseQuery.java @@ -0,0 +1,20 @@ +package org.avni.server.domain.metabase; +import com.fasterxml.jackson.databind.node.ObjectNode; + +public class MetabaseQuery { + private final int databaseId; + private final ObjectNode queryNode; + + public MetabaseQuery(int databaseId, ObjectNode queryNode) { + this.databaseId = databaseId; + this.queryNode = queryNode; + } + + public int getDatabaseId() { + return databaseId; + } + + public ObjectNode toJson() { + return queryNode; + } +} diff --git a/avni-server-api/src/main/java/org/avni/server/domain/metabase/MetabaseQueryBuilder.java b/avni-server-api/src/main/java/org/avni/server/domain/metabase/MetabaseQueryBuilder.java new file mode 100644 index 000000000..1997cb8ec --- /dev/null +++ b/avni-server-api/src/main/java/org/avni/server/domain/metabase/MetabaseQueryBuilder.java @@ -0,0 +1,57 @@ +package org.avni.server.domain.metabase; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.fasterxml.jackson.databind.node.ObjectNode; + +public class MetabaseQueryBuilder { + private final Database database; + private final ArrayNode joinsArray; + private final ObjectMapper objectMapper; + private ObjectNode queryNode; + + public MetabaseQueryBuilder(Database database, ArrayNode joinsArray, ObjectMapper objectMapper) { + this.database = database; + this.joinsArray = joinsArray; + this.objectMapper = objectMapper; + this.queryNode = objectMapper.createObjectNode(); + } + + public MetabaseQueryBuilder forTable(TableDetails tableDetails) { + queryNode.put(FieldAttribute.SOURCE_TABLE.getAttributeName(), tableDetails.getId()); + return this; + } + + public MetabaseQueryBuilder joinWith(TableDetails addressTable, FieldDetails joinField1, FieldDetails joinField2) { + ObjectNode joinNode = objectMapper.createObjectNode(); + joinNode.put(FieldAttribute.FIELDS.getAttributeName(), FieldAttribute.ALL.getAttributeName()); + joinNode.put(FieldAttribute.ALIAS.getAttributeName(), addressTable.getName()); + + ArrayNode conditionArray = objectMapper.createArrayNode(); + conditionArray.add(ConditionType.EQUAL.getOperator()); + + ArrayNode leftField = objectMapper.createArrayNode(); + leftField.add(FieldAttribute.FIELD.getAttributeName()); + leftField.add(joinField2.getId()); + leftField.add(objectMapper.createObjectNode().put(FieldAttribute.BASE_TYPE.getAttributeName(), FieldType.INTEGER.getTypeName())); + conditionArray.add(leftField); + + ArrayNode rightField = objectMapper.createArrayNode(); + rightField.add(FieldAttribute.FIELD.getAttributeName()); + rightField.add(joinField1.getId()); + rightField.add(objectMapper.createObjectNode().put(FieldAttribute.BASE_TYPE.getAttributeName(), FieldType.INTEGER.getTypeName()).put(FieldAttribute.JOIN_ALIAS.getAttributeName(), addressTable.getName())); + conditionArray.add(rightField); + + joinNode.set(FieldAttribute.CONDITION.getAttributeName(), conditionArray); + joinNode.put(FieldAttribute.SOURCE_TABLE.getAttributeName(), addressTable.getId()); + joinsArray.add(joinNode); + queryNode.set(FieldAttribute.JOINS.getAttributeName(), joinsArray); + + return this; + } + + + public MetabaseQuery build() { + return new MetabaseQuery(database.getId(), queryNode); + } +} diff --git a/avni-server-api/src/main/java/org/avni/server/domain/metabase/MetabaseRequestBody.java b/avni-server-api/src/main/java/org/avni/server/domain/metabase/MetabaseRequestBody.java new file mode 100644 index 000000000..0de5a6fe5 --- /dev/null +++ b/avni-server-api/src/main/java/org/avni/server/domain/metabase/MetabaseRequestBody.java @@ -0,0 +1,46 @@ +package org.avni.server.domain.metabase; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; + +// Refer docs : https://www.metabase.com/docs/latest/api/card#post-apicard +public class MetabaseRequestBody { + private String name; + private MetabaseQuery datasetQuery; + private VisualizationType display; + private String description; + private ObjectNode visualizationSettings; + private int collectionId; + private Integer collectionPosition; + private JsonNode resultMetadata; + + public MetabaseRequestBody(String name, MetabaseQuery datasetQuery, VisualizationType display, String description, ObjectNode visualizationSettings, int collectionId) { + this.name = name; + this.datasetQuery = datasetQuery; + this.display = display; + this.description = description; + this.visualizationSettings = visualizationSettings; + this.collectionId = collectionId; + } + + public ObjectNode toJson(ObjectMapper objectMapper) { + ObjectNode rootNode = objectMapper.createObjectNode(); + rootNode.put("name", name); + + ObjectNode datasetQueryNode = objectMapper.createObjectNode(); + datasetQueryNode.put("database", datasetQuery.getDatabaseId()); + datasetQueryNode.put("type", "query"); + datasetQueryNode.set("query", datasetQuery.toJson()); + + rootNode.set("dataset_query", datasetQueryNode); + rootNode.put("display", display.toString()); + rootNode.putNull("description"); + rootNode.set("visualization_settings", visualizationSettings); + rootNode.put("collection_id", collectionId); + rootNode.putNull("collection_position"); + rootNode.putNull("result_metadata"); + + return rootNode; + } +} diff --git a/avni-server-api/src/main/java/org/avni/server/domain/metabase/QueryType.java b/avni-server-api/src/main/java/org/avni/server/domain/metabase/QueryType.java new file mode 100644 index 000000000..afe4c7758 --- /dev/null +++ b/avni-server-api/src/main/java/org/avni/server/domain/metabase/QueryType.java @@ -0,0 +1,21 @@ +package org.avni.server.domain.metabase; + +public enum QueryType { + QUERY("query"), + DASHBOARD("dashboard"); + + private final String typeName; + + QueryType(String typeName) { + this.typeName = typeName; + } + + public String getTypeName() { + return typeName; + } + + @Override + public String toString() { + return typeName; + } +} diff --git a/avni-server-api/src/main/java/org/avni/server/domain/metabase/SyncStatus.java b/avni-server-api/src/main/java/org/avni/server/domain/metabase/SyncStatus.java new file mode 100644 index 000000000..b9b732b8d --- /dev/null +++ b/avni-server-api/src/main/java/org/avni/server/domain/metabase/SyncStatus.java @@ -0,0 +1,26 @@ +package org.avni.server.domain.metabase; + +public enum SyncStatus { + COMPLETE("complete"), + INCOMPLETE("incomplete"); + + private final String status; + + SyncStatus(String status) { + this.status = status; + } + + public String getStatus() { + return status; + } + + public static SyncStatus fromString(String status) { + for (SyncStatus s : SyncStatus.values()) { + if (s.getStatus().equalsIgnoreCase(status)) { + return s; + } + } + return null; + } + +} diff --git a/avni-server-api/src/main/java/org/avni/server/domain/metabase/TableDetails.java b/avni-server-api/src/main/java/org/avni/server/domain/metabase/TableDetails.java new file mode 100644 index 000000000..7bc0965fb --- /dev/null +++ b/avni-server-api/src/main/java/org/avni/server/domain/metabase/TableDetails.java @@ -0,0 +1,85 @@ +package org.avni.server.domain.metabase; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; + +@JsonIgnoreProperties(ignoreUnknown = true) +public class TableDetails { + public TableDetails() { + } + + public TableDetails(String name) { + this.name = name; + } + + @JsonProperty("description") + private String description; + + @JsonProperty("entity_type") + private String entityType; + + @JsonProperty("schema") + private String schema; + + @JsonProperty("name") + private String name; + + @JsonProperty("active") + private boolean active; + + @JsonProperty("id") + private int id; + + @JsonProperty("db_id") + private int dbId; + + @JsonProperty("initial_sync_status") + private String initialSyncStatus; + + @JsonProperty("display_name") + private String displayName; + + public String getDescription() { + return description; + } + + public String getEntityType() { + return entityType; + } + + public String getSchema() { + return schema; + } + + public String getName() { + return name; + } + + public boolean isActive() { + return active; + } + + public int getId() { + return id; + } + + public int getDbId() { + return dbId; + } + + public String getInitialSyncStatus() { + return initialSyncStatus; + } + + public String getDisplayName() { + return displayName; + } + + public boolean nameMatches(String tableName) { + return tableName.equals(getDisplayName()); + } + + public void setName(String name) { + this.name = name; + } +} diff --git a/avni-server-api/src/main/java/org/avni/server/domain/metabase/TableType.java b/avni-server-api/src/main/java/org/avni/server/domain/metabase/TableType.java new file mode 100644 index 000000000..d1b221c6e --- /dev/null +++ b/avni-server-api/src/main/java/org/avni/server/domain/metabase/TableType.java @@ -0,0 +1,30 @@ +package org.avni.server.domain.metabase; + +public enum TableType { + INDIVIDUAL("Individual"), + HOUSEHOLD("Household"), + GROUP("Group"), + PERSON("Person"), + ENCOUNTER("Encounter"), + PROGRAM_ENCOUNTER("ProgramEncounter"), + PROGRAM_ENROLMENT("ProgramEnrolment"); + + private final String typeName; + + TableType(String typeName) { + this.typeName = typeName; + } + + public String getTypeName() { + return typeName; + } + + public static TableType fromString(String typeName) { + for (TableType type : TableType.values()) { + if (type.getTypeName().equalsIgnoreCase(typeName)) { + return type; + } + } + throw new IllegalArgumentException("Unknown table type: " + typeName); + } +} diff --git a/avni-server-api/src/main/java/org/avni/server/domain/metabase/VisualizationType.java b/avni-server-api/src/main/java/org/avni/server/domain/metabase/VisualizationType.java new file mode 100644 index 000000000..d44b50ac6 --- /dev/null +++ b/avni-server-api/src/main/java/org/avni/server/domain/metabase/VisualizationType.java @@ -0,0 +1,22 @@ +package org.avni.server.domain.metabase; + +//Refer documentation : https://www.metabase.com/docs/latest/questions/sharing/visualizing-results +public enum VisualizationType { + TABLE("table"), + CHART("chart"); + + private final String typeName; + + VisualizationType(String typeName) { + this.typeName = typeName; + } + + public String getTypeName() { + return typeName; + } + + @Override + public String toString() { + return typeName; + } +} diff --git a/avni-server-api/src/main/java/org/avni/server/service/metabase/DatabaseService.java b/avni-server-api/src/main/java/org/avni/server/service/metabase/DatabaseService.java new file mode 100644 index 000000000..fd3cec2e6 --- /dev/null +++ b/avni-server-api/src/main/java/org/avni/server/service/metabase/DatabaseService.java @@ -0,0 +1,162 @@ +package org.avni.server.service.metabase; + +import org.avni.server.dao.metabase.DatabaseRepository; +import org.avni.server.domain.metabase.*; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import java.util.Arrays; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +@Service +public class DatabaseService implements QuestionCreationService{ + + private final DatabaseRepository databaseRepository; + private final MetabaseService metabaseService; + + private static final String ADDRESS_TABLE = "Address"; + + @Autowired + public DatabaseService(DatabaseRepository databaseRepository, MetabaseService metabaseService) { + this.databaseRepository = databaseRepository; + this.metabaseService = metabaseService; + } + + public Database getGlobalDatabase() { + return metabaseService.getGlobalDatabase(); + } + + public CollectionInfoResponse getGlobalCollection() { + return metabaseService.getGlobalCollection(); + } + + public SyncStatus getInitialSyncStatus() { + DatabaseSyncStatus databaseSyncStatus = databaseRepository.getInitialSyncStatus(getGlobalDatabase()); + String status = databaseSyncStatus.getInitialSyncStatus(); + return SyncStatus.fromString(status); + } + + private void ensureSyncComplete() { + SyncStatus syncStatus = getInitialSyncStatus(); + if (syncStatus != SyncStatus.COMPLETE) { + throw new RuntimeException("Database sync is not complete. Cannot create questions."); + } + } + + private List filterOutExistingQuestions(List entityNames) { + Set existingItemNames = databaseRepository.getExistingCollectionItems(getGlobalCollection().getIdAsInt()).stream() + .map(item -> item.getName().trim().toLowerCase().replace(" ", "_")) + .collect(Collectors.toSet()); + + return entityNames.stream() + .filter(entityName -> !existingItemNames.contains(entityName.toLowerCase())) + .collect(Collectors.toList()); + } + + @Override + public void createQuestionForTable(TableDetails tableDetails, TableDetails addressTableDetails, FieldDetails addressFieldDetails, FieldDetails tableFieldDetails) { + Database database = getGlobalDatabase(); + databaseRepository.createQuestionForTable(database, tableDetails, addressTableDetails, addressFieldDetails, tableFieldDetails); + } + + @Override + public void createQuestionForTable(String tableName, String schema) { + Database database = getGlobalDatabase(); + + TableDetails tableDetails = new TableDetails(); + tableDetails.setName(tableName); + TableDetails fetchedTableDetails = databaseRepository.findTableDetailsByName(database, tableDetails); + + databaseRepository.createQuestionForASingleTable(database, fetchedTableDetails); + } + + public List getSubjectTypeNames() { + TableDetails fetchedMetadataTable = databaseRepository.findTableDetailsByName(getGlobalDatabase(), new TableDetails("table_metadata")); + + DatasetResponse datasetResponse = databaseRepository.findAll(fetchedMetadataTable, getGlobalDatabase()); + List> rows = datasetResponse.getData().getRows(); + + List subjectTypeNames = new ArrayList<>(); + + for (List row : rows) { + String type = row.get(DatasetColumn.TYPE.getIndex()); + if (type.equalsIgnoreCase(TableType.INDIVIDUAL.getTypeName()) || + type.equalsIgnoreCase(TableType.HOUSEHOLD.getTypeName()) || + type.equalsIgnoreCase(TableType.GROUP.getTypeName()) || + type.equalsIgnoreCase(TableType.PERSON.getTypeName())) { + subjectTypeNames.add(row.get(DatasetColumn.NAME.getIndex())); + } + } + + return subjectTypeNames; + } + + public List getProgramAndEncounterNames() { + TableDetails fetchedMetadataTable = databaseRepository.findTableDetailsByName(getGlobalDatabase(), new TableDetails("table_metadata")); + + DatasetResponse datasetResponse = databaseRepository.findAll(fetchedMetadataTable, getGlobalDatabase()); + List> rows = datasetResponse.getData().getRows(); + + List programNames = new ArrayList<>(); + + for (List row : rows) { + String type = row.get(DatasetColumn.TYPE.getIndex()); + if (type.equalsIgnoreCase(TableType.PROGRAM_ENCOUNTER.getTypeName()) || + type.equalsIgnoreCase(TableType.ENCOUNTER.getTypeName()) || + type.equalsIgnoreCase(TableType.PROGRAM_ENROLMENT.getTypeName())) { + programNames.add(row.get(DatasetColumn.NAME.getIndex())); + } + } + + return programNames; + } + + private void createQuestionsForEntities(List entityNames, FieldDetails addressFieldDetails, FieldDetails entityFieldDetails) { + ensureSyncComplete(); + TableDetails fetchedAddressTableDetails = databaseRepository.findTableDetailsByName(getGlobalDatabase(), new TableDetails(ADDRESS_TABLE)); + + List filteredEntities = filterOutExistingQuestions(entityNames); + + for (String entityName : filteredEntities) { + TableDetails entityTableDetails = new TableDetails(); + entityTableDetails.setName(entityName); + TableDetails fetchedEntityTableDetails = databaseRepository.findTableDetailsByName(getGlobalDatabase(), entityTableDetails); + createQuestionForTable(fetchedEntityTableDetails, fetchedAddressTableDetails, addressFieldDetails, entityFieldDetails); + } + } + + public void createQuestionsForSubjectTypes() { + List subjectTypeNames = getSubjectTypeNames(); + FieldDetails addressFieldDetails = new FieldDetails("id"); + FieldDetails subjectFieldDetails = new FieldDetails("address_id"); + createQuestionsForEntities(subjectTypeNames, addressFieldDetails, subjectFieldDetails); + } + + public void createQuestionsForProgramsAndEncounters() { + List programAndEncounterNames = getProgramAndEncounterNames(); + FieldDetails addressFieldDetails = new FieldDetails("id"); + FieldDetails programOrEncounterFieldDetails = new FieldDetails("address_id"); + createQuestionsForEntities(programAndEncounterNames, addressFieldDetails, programOrEncounterFieldDetails); + } + + public void createQuestionsForIndividualTables() { + ensureSyncComplete(); + List individualTables = Arrays.asList("address", "media", "sync_telemetry"); + + List filteredTables = filterOutExistingQuestions(individualTables); + + for (String tableName : filteredTables) { + createQuestionForTable(tableName, "!public"); + } + } + + public void createQuestions() { + createQuestionsForSubjectTypes(); + + createQuestionsForProgramsAndEncounters(); + + createQuestionsForIndividualTables(); + } +} diff --git a/avni-server-api/src/main/java/org/avni/server/service/MetabaseService.java b/avni-server-api/src/main/java/org/avni/server/service/metabase/MetabaseService.java similarity index 68% rename from avni-server-api/src/main/java/org/avni/server/service/MetabaseService.java rename to avni-server-api/src/main/java/org/avni/server/service/metabase/MetabaseService.java index b67316a55..050ee873d 100644 --- a/avni-server-api/src/main/java/org/avni/server/service/MetabaseService.java +++ b/avni-server-api/src/main/java/org/avni/server/service/metabase/MetabaseService.java @@ -1,42 +1,42 @@ -package org.avni.server.service; +package org.avni.server.service.metabase; import org.avni.server.dao.metabase.CollectionPermissionsRepository; import org.avni.server.dao.metabase.CollectionRepository; import org.avni.server.dao.metabase.DatabaseRepository; import org.avni.server.dao.metabase.GroupPermissionsRepository; import org.avni.server.domain.Organisation; -import org.avni.server.domain.metabase.AvniDatabase; -import org.avni.server.domain.metabase.Collection; -import org.avni.server.domain.metabase.CollectionPermissionsService; -import org.avni.server.domain.metabase.CollectionResponse; -import org.avni.server.domain.metabase.Database; -import org.avni.server.domain.metabase.DatabaseDetails; -import org.avni.server.domain.metabase.Group; -import org.avni.server.domain.metabase.GroupPermissionsService; +import org.avni.server.domain.metabase.*; +import org.avni.server.service.OrganisationService; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; + @Service public class MetabaseService { private final OrganisationService organisationService; private final AvniDatabase avniDatabase; private final DatabaseRepository databaseRepository; + private final DatabaseService databaseService; private final GroupPermissionsRepository groupPermissionsRepository; private final CollectionPermissionsRepository collectionPermissionsRepository; private final CollectionRepository collectionRepository; + private Database globalDatabase; + private CollectionInfoResponse globalCollection; @Autowired public MetabaseService(OrganisationService organisationService, AvniDatabase avniDatabase, DatabaseRepository databaseRepository, + @Lazy DatabaseService databaseService, GroupPermissionsRepository groupPermissionsRepository, - GroupPermissionsService permissions, CollectionPermissionsRepository collectionPermissionsRepository, CollectionRepository collectionRepository) { this.organisationService = organisationService; this.avniDatabase = avniDatabase; this.databaseRepository = databaseRepository; + this.databaseService = databaseService; this.groupPermissionsRepository = groupPermissionsRepository; this.collectionPermissionsRepository = collectionPermissionsRepository; this.collectionRepository = collectionRepository; @@ -48,8 +48,10 @@ public void setupMetabase() { String dbUser = currentOrganisation.getDbUser(); Database database = databaseRepository.save(new Database(name, "postgres", new DatabaseDetails(avniDatabase, dbUser))); - - CollectionResponse metabaseCollection = collectionRepository.save(new Collection(name, name + " collection")); + this.globalDatabase = database; + + CollectionResponse metabaseCollection = collectionRepository.save(new CreateCollectionRequest(name, name + " collection")); + this.globalCollection = new CollectionInfoResponse(null, metabaseCollection.getId(), false); Group metabaseGroup = groupPermissionsRepository.save(new Group(name)); @@ -61,4 +63,20 @@ public void setupMetabase() { collectionPermissions.updatePermissions(metabaseGroup.getId(), metabaseCollection.getId()); collectionPermissionsRepository.updateCollectionPermissions(collectionPermissions, metabaseGroup.getId(), metabaseCollection.getId()); } + + public Database getGlobalDatabase() { + if (globalDatabase == null) { + Organisation currentOrganisation = organisationService.getCurrentOrganisation(); + globalDatabase = databaseRepository.getDatabaseByName(new Database(currentOrganisation.getName())); + } + return globalDatabase; + } + + public CollectionInfoResponse getGlobalCollection() { + if (globalCollection == null) { + Organisation currentOrganisation = organisationService.getCurrentOrganisation(); + globalCollection = databaseRepository.getCollectionByName(new Database(currentOrganisation.getName())); + } + return globalCollection; + } } diff --git a/avni-server-api/src/main/java/org/avni/server/service/metabase/QuestionCreationService.java b/avni-server-api/src/main/java/org/avni/server/service/metabase/QuestionCreationService.java new file mode 100644 index 000000000..8ae178f68 --- /dev/null +++ b/avni-server-api/src/main/java/org/avni/server/service/metabase/QuestionCreationService.java @@ -0,0 +1,8 @@ +package org.avni.server.service.metabase; +import org.avni.server.domain.metabase.FieldDetails; +import org.avni.server.domain.metabase.TableDetails; + +public interface QuestionCreationService { + void createQuestionForTable(TableDetails tableDetails, TableDetails addressTableDetails, FieldDetails addressFieldDetails, FieldDetails tableFieldDetails) throws Exception; + void createQuestionForTable(String tableName, String schema) throws Exception; +} diff --git a/avni-server-api/src/main/java/org/avni/server/util/S.java b/avni-server-api/src/main/java/org/avni/server/util/S.java index 7d83bab0b..8022d10df 100644 --- a/avni-server-api/src/main/java/org/avni/server/util/S.java +++ b/avni-server-api/src/main/java/org/avni/server/util/S.java @@ -29,4 +29,24 @@ public static String[] splitMultiSelectAnswer(String answerValue) { .toArray(String[]::new); } + public static String toSnakeCase(String input) { + if (input == null) { + return null; + } + return input.trim().replaceAll(" +", "_").toLowerCase(); + } + + public static String formatName(String rawName) { + String[] parts = rawName.split("_"); + StringBuilder formattedName = new StringBuilder(); + + for (String part : parts) { + formattedName.append(part.substring(0, 1).toUpperCase()) + .append(part.substring(1)) + .append(" "); + } + + return formattedName.toString().trim(); + } + } diff --git a/avni-server-api/src/main/java/org/avni/server/web/MetabaseController.java b/avni-server-api/src/main/java/org/avni/server/web/MetabaseController.java index 0838c146b..3aac32847 100644 --- a/avni-server-api/src/main/java/org/avni/server/web/MetabaseController.java +++ b/avni-server-api/src/main/java/org/avni/server/web/MetabaseController.java @@ -1,7 +1,10 @@ package org.avni.server.web; - +import org.avni.server.domain.metabase.SyncStatus; +import org.avni.server.service.metabase.DatabaseService; import org.avni.server.domain.accessControl.PrivilegeType; -import org.avni.server.service.MetabaseService; +import org.avni.server.dao.metabase.MetabaseConnector; +import org.avni.server.dao.metabase.DatabaseRepository; +import org.avni.server.service.metabase.MetabaseService; import org.avni.server.service.UserService; import org.avni.server.service.accessControl.AccessControlService; import org.springframework.web.bind.annotation.*; @@ -9,10 +12,12 @@ @RestController @RequestMapping("/api/metabase") public class MetabaseController { + private final DatabaseService databaseService; private final MetabaseService metabaseService; private final AccessControlService accessControlService; - public MetabaseController(MetabaseService metabaseService, UserService userService,AccessControlService accessControlService) { + public MetabaseController(DatabaseService databaseService,MetabaseService metabaseService, MetabaseConnector metabaseConnector,DatabaseRepository databaseRepository, UserService userService,AccessControlService accessControlService) { + this.databaseService = databaseService; this.metabaseService = metabaseService; this.accessControlService= accessControlService; } @@ -21,5 +26,16 @@ public MetabaseController(MetabaseService metabaseService, UserService userServi public void setupMetabase() { accessControlService.checkPrivilege(PrivilegeType.EditOrganisationConfiguration); metabaseService.setupMetabase(); -} + } + + @PostMapping("/create-questions") + public void createQuestions() throws Exception{ + databaseService.createQuestions(); + } + + @GetMapping("/sync-status") + public SyncStatus getSyncStatus() { + return databaseService.getInitialSyncStatus(); + } + }