diff --git a/PersonalFinancesService/src/main/java/dev/mvvasilev/finances/controllers/StatisticsController.java b/PersonalFinancesService/src/main/java/dev/mvvasilev/finances/controllers/StatisticsController.java index f4d2072..b19f009 100644 --- a/PersonalFinancesService/src/main/java/dev/mvvasilev/finances/controllers/StatisticsController.java +++ b/PersonalFinancesService/src/main/java/dev/mvvasilev/finances/controllers/StatisticsController.java @@ -2,21 +2,24 @@ package dev.mvvasilev.finances.controllers; import dev.mvvasilev.common.controller.AbstractRestController; import dev.mvvasilev.common.web.APIResponseDTO; -import dev.mvvasilev.finances.dtos.SpendingByCategoryDTO; +import dev.mvvasilev.finances.dtos.SpendingByCategoriesDTO; import dev.mvvasilev.finances.dtos.SpendingOverTimeByCategoryDTO; import dev.mvvasilev.finances.enums.TimePeriod; import dev.mvvasilev.finances.services.StatisticsService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.core.Authentication; import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import java.time.LocalDateTime; import java.util.Collection; -@RestController("/statistics") +@RestController +@RequestMapping("/statistics") public class StatisticsController extends AbstractRestController { private final StatisticsService statisticsService; @@ -27,23 +30,24 @@ public class StatisticsController extends AbstractRestController { } @GetMapping("/totalSpendingByCategory") - public ResponseEntity> fetchSpendingByCategory( + @PreAuthorize("@authService.isOwner(#categoryId, T(dev.mvvasilev.finances.entity.RawStatement))") + public ResponseEntity> fetchSpendingByCategory( + Long[] categoryId, @RequestParam(defaultValue = "1970-01-01T00:00:00") LocalDateTime from, - @RequestParam(defaultValue = "2099-01-01T00:00:00") LocalDateTime to, - Authentication authentication + @RequestParam(defaultValue = "2099-01-01T00:00:00") LocalDateTime to ) { - return ok(statisticsService.spendingByCategory(from, to, Integer.parseInt(authentication.getName()))); + return ok(statisticsService.spendingByCategory(categoryId, from, to)); } @GetMapping("/spendingOverTimeByCategory") + @PreAuthorize("@authService.isOwner(#categoryId, T(dev.mvvasilev.finances.entity.RawStatement))") public ResponseEntity> fetchSpendingOverTimeByCategory( - Collection categoryId, - TimePeriod period, + Long[] categoryId, + @RequestParam(defaultValue = "DAILY") TimePeriod period, @RequestParam(defaultValue = "1970-01-01T00:00:00") LocalDateTime from, - @RequestParam(defaultValue = "2099-01-01T00:00:00") LocalDateTime to, - Authentication authentication + @RequestParam(defaultValue = "2099-01-01T00:00:00") LocalDateTime to ) { - throw new UnsupportedOperationException(); + return ok(statisticsService.spendingByCategoryOverTime(categoryId, period, from, to)); } } diff --git a/PersonalFinancesService/src/main/java/dev/mvvasilev/finances/controllers/WidgetsController.java b/PersonalFinancesService/src/main/java/dev/mvvasilev/finances/controllers/WidgetsController.java new file mode 100644 index 0000000..710a278 --- /dev/null +++ b/PersonalFinancesService/src/main/java/dev/mvvasilev/finances/controllers/WidgetsController.java @@ -0,0 +1,20 @@ +package dev.mvvasilev.finances.controllers; + +import dev.mvvasilev.common.controller.AbstractRestController; +import dev.mvvasilev.common.web.APIResponseDTO; +import dev.mvvasilev.common.web.CrudResponseDTO; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/widgets") +public class WidgetsController extends AbstractRestController { + +// ResponseEntity> create(CreateDTO dto); +// +// ResponseEntity> update(Long id, UpdateDTO dto); +// +// ResponseEntity> delete(Long id); + +} diff --git a/PersonalFinancesService/src/main/java/dev/mvvasilev/finances/dtos/SpendingByCategoriesDTO.java b/PersonalFinancesService/src/main/java/dev/mvvasilev/finances/dtos/SpendingByCategoriesDTO.java new file mode 100644 index 0000000..7b5467a --- /dev/null +++ b/PersonalFinancesService/src/main/java/dev/mvvasilev/finances/dtos/SpendingByCategoriesDTO.java @@ -0,0 +1,11 @@ +package dev.mvvasilev.finances.dtos; + +import java.util.Collection; +import java.util.Map; + +public record SpendingByCategoriesDTO( + Collection categories, + + Map spendingByCategory +) { +} diff --git a/PersonalFinancesService/src/main/java/dev/mvvasilev/finances/dtos/SpendingByCategoryDTO.java b/PersonalFinancesService/src/main/java/dev/mvvasilev/finances/dtos/SpendingByCategoryDTO.java index 1acfe24..0daa93f 100644 --- a/PersonalFinancesService/src/main/java/dev/mvvasilev/finances/dtos/SpendingByCategoryDTO.java +++ b/PersonalFinancesService/src/main/java/dev/mvvasilev/finances/dtos/SpendingByCategoryDTO.java @@ -1,11 +1,11 @@ package dev.mvvasilev.finances.dtos; -import java.util.Collection; -import java.util.Map; +import java.time.LocalDateTime; -public record SpendingByCategoryDTO( - Collection categories, +public record SpendingByCategoryDTO ( + Long categoryId, - Map spendingByCategory -) { -} + LocalDateTime timestamp, + + Double amount +) {} diff --git a/PersonalFinancesService/src/main/java/dev/mvvasilev/finances/dtos/SpendingOverTimeByCategoryDTO.java b/PersonalFinancesService/src/main/java/dev/mvvasilev/finances/dtos/SpendingOverTimeByCategoryDTO.java index e1f1986..55856e2 100644 --- a/PersonalFinancesService/src/main/java/dev/mvvasilev/finances/dtos/SpendingOverTimeByCategoryDTO.java +++ b/PersonalFinancesService/src/main/java/dev/mvvasilev/finances/dtos/SpendingOverTimeByCategoryDTO.java @@ -1,11 +1,16 @@ package dev.mvvasilev.finances.dtos; +import java.time.Duration; +import java.time.LocalDateTime; +import java.time.Period; import java.util.Collection; import java.util.Map; public record SpendingOverTimeByCategoryDTO( Collection categories, - Map> spendingOverTime + Period timePeriodDuration, + + Collection spending ) { } diff --git a/PersonalFinancesService/src/main/java/dev/mvvasilev/finances/entity/Widget.java b/PersonalFinancesService/src/main/java/dev/mvvasilev/finances/entity/Widget.java new file mode 100644 index 0000000..aa09e2b --- /dev/null +++ b/PersonalFinancesService/src/main/java/dev/mvvasilev/finances/entity/Widget.java @@ -0,0 +1,77 @@ +package dev.mvvasilev.finances.entity; + +import dev.mvvasilev.common.data.AbstractEntity; +import dev.mvvasilev.finances.enums.WidgetType; +import jakarta.persistence.Convert; +import jakarta.persistence.Converter; +import jakarta.persistence.Entity; +import jakarta.persistence.Table; + +@Entity +@Table(schema = "widgets") +public class Widget extends AbstractEntity { + + @Convert(converter = WidgetType.JpaConverter.class) + private WidgetType type; + + private int positionX; + + private int positionY; + + private int sizeX; + + private int sizeY; + + private String name; + + public Widget() { + } + + public WidgetType getType() { + return type; + } + + public void setType(WidgetType type) { + this.type = type; + } + + public int getPositionX() { + return positionX; + } + + public void setPositionX(int positionX) { + this.positionX = positionX; + } + + public int getPositionY() { + return positionY; + } + + public void setPositionY(int positionY) { + this.positionY = positionY; + } + + public int getSizeX() { + return sizeX; + } + + public void setSizeX(int sizeX) { + this.sizeX = sizeX; + } + + public int getSizeY() { + return sizeY; + } + + public void setSizeY(int sizeY) { + this.sizeY = sizeY; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } +} diff --git a/PersonalFinancesService/src/main/java/dev/mvvasilev/finances/entity/WidgetParameter.java b/PersonalFinancesService/src/main/java/dev/mvvasilev/finances/entity/WidgetParameter.java new file mode 100644 index 0000000..38fee31 --- /dev/null +++ b/PersonalFinancesService/src/main/java/dev/mvvasilev/finances/entity/WidgetParameter.java @@ -0,0 +1,75 @@ +package dev.mvvasilev.finances.entity; + +import dev.mvvasilev.common.data.AbstractEntity; +import jakarta.persistence.Entity; +import jakarta.persistence.Table; + +import java.time.LocalDateTime; + +@Entity +@Table(schema = "widgets") +public class WidgetParameter extends AbstractEntity { + + private Long widgetId; + + private String name; + + private String stringValue; + + private Double numericValue; + + private LocalDateTime timestampValue; + + private Boolean booleanValue; + + public WidgetParameter() { + } + + public Long getWidgetId() { + return widgetId; + } + + public void setWidgetId(Long widgetId) { + this.widgetId = widgetId; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getStringValue() { + return stringValue; + } + + public void setStringValue(String stringValue) { + this.stringValue = stringValue; + } + + public Double getNumericValue() { + return numericValue; + } + + public void setNumericValue(Double numericValue) { + this.numericValue = numericValue; + } + + public LocalDateTime getTimestampValue() { + return timestampValue; + } + + public void setTimestampValue(LocalDateTime timestampValue) { + this.timestampValue = timestampValue; + } + + public Boolean getBooleanValue() { + return booleanValue; + } + + public void setBooleanValue(Boolean booleanValue) { + this.booleanValue = booleanValue; + } +} diff --git a/PersonalFinancesService/src/main/java/dev/mvvasilev/finances/enums/TimePeriod.java b/PersonalFinancesService/src/main/java/dev/mvvasilev/finances/enums/TimePeriod.java index 9932a85..9135e95 100644 --- a/PersonalFinancesService/src/main/java/dev/mvvasilev/finances/enums/TimePeriod.java +++ b/PersonalFinancesService/src/main/java/dev/mvvasilev/finances/enums/TimePeriod.java @@ -1,13 +1,27 @@ package dev.mvvasilev.finances.enums; +import java.time.Duration; +import java.time.Period; +import java.time.temporal.ChronoUnit; + public enum TimePeriod { - SECONDLY, - MINUTELY, - HOURLY, - DAILY, - WEEKLY, - BIWEEKLY, - MONTHLY, - QUARTERLY, - YEARLY +// SECONDLY(Duration.of(1, ChronoUnit.SECONDS)), +// MINUTELY(Duration.of(1, ChronoUnit.MINUTES)), +// HOURLY(Duration.of(1, ChronoUnit.HOURS)), + DAILY(Period.ofDays(1)), + WEEKLY(Period.ofDays(7)), + BIWEEKLY(Period.ofDays(14)), + MONTHLY(Period.ofMonths(1)), + QUARTERLY(Period.ofMonths(3)), + YEARLY(Period.ofYears(1)); + + private final Period duration; + + TimePeriod(Period duration) { + this.duration = duration; + } + + public Period getDuration() { + return duration; + } } diff --git a/PersonalFinancesService/src/main/java/dev/mvvasilev/finances/enums/WidgetType.java b/PersonalFinancesService/src/main/java/dev/mvvasilev/finances/enums/WidgetType.java new file mode 100644 index 0000000..2f79d5e --- /dev/null +++ b/PersonalFinancesService/src/main/java/dev/mvvasilev/finances/enums/WidgetType.java @@ -0,0 +1,20 @@ +package dev.mvvasilev.finances.enums; + +import dev.mvvasilev.common.data.AbstractEnumConverter; +import dev.mvvasilev.common.data.PersistableEnum; + +public enum WidgetType implements PersistableEnum { + TOTAL_SPENDING_PER_CATEGORY, + SPENDING_OVER_TIME_PER_CATEGORY; + + @Override + public String value() { + return name(); + } + + public static class JpaConverter extends AbstractEnumConverter { + public JpaConverter() { + super(WidgetType.class); + } + } +} diff --git a/PersonalFinancesService/src/main/java/dev/mvvasilev/finances/persistence/StatisticsRepository.java b/PersonalFinancesService/src/main/java/dev/mvvasilev/finances/persistence/StatisticsRepository.java index ff68bbc..b8d0a14 100644 --- a/PersonalFinancesService/src/main/java/dev/mvvasilev/finances/persistence/StatisticsRepository.java +++ b/PersonalFinancesService/src/main/java/dev/mvvasilev/finances/persistence/StatisticsRepository.java @@ -1,12 +1,18 @@ package dev.mvvasilev.finances.persistence; +import dev.mvvasilev.finances.dtos.SpendingOverTimeByCategoryDTO; +import dev.mvvasilev.finances.enums.TimePeriod; +import dev.mvvasilev.finances.persistence.dtos.SpendingOverTimeDTO; import jakarta.persistence.EntityManager; import jakarta.persistence.Query; import jakarta.persistence.Tuple; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.stereotype.Repository; +import java.sql.Timestamp; import java.time.LocalDateTime; +import java.util.Collection; import java.util.List; import java.util.Map; import java.util.stream.Collectors; @@ -21,7 +27,7 @@ public class StatisticsRepository { this.entityManager = entityManager; } - public Map fetchSpendingByCategory(LocalDateTime from, LocalDateTime to, List categoryIds) { + public Map fetchSpendingByCategory(Long[] categoryId, LocalDateTime from, LocalDateTime to) { Query nativeQuery = entityManager.createNativeQuery( """ SELECT ptc.category_id AS category_id, ROUND(CAST(SUM(pt.amount) AS NUMERIC), 2) AS total_spending @@ -37,7 +43,7 @@ public class StatisticsRepository { Tuple.class ); - nativeQuery.setParameter(1, categoryIds); + nativeQuery.setParameter(1, categoryId); nativeQuery.setParameter(2, from); nativeQuery.setParameter(3, to); @@ -47,4 +53,21 @@ public class StatisticsRepository { (Tuple tuple) -> ((Number) tuple.get("total_spending")).doubleValue() )); } + + public Collection fetchSpendingByCategoryOverTime(LocalDateTime from, LocalDateTime to, TimePeriod period, Long[] categoryId) { + Query nativeQuery = entityManager.createNativeQuery("SELECT * FROM statistics.spending_over_time(?1, ?2, ?3, ?4) ORDER BY period_beginning_timestamp;", Tuple.class); + + nativeQuery.setParameter(1, categoryId); + nativeQuery.setParameter(2, period.toString()); + nativeQuery.setParameter(3, from); + nativeQuery.setParameter(4, to); + + + //noinspection unchecked + return nativeQuery.getResultStream().map(r -> new SpendingOverTimeDTO( + ((Tuple) r).get("category_id", Long.class), + ((Tuple) r).get("amount_for_period", Double.class), + ((Tuple) r).get("period_beginning_timestamp", Timestamp.class).toLocalDateTime() + )).toList(); + } } diff --git a/PersonalFinancesService/src/main/java/dev/mvvasilev/finances/persistence/dtos/SpendingOverTimeDTO.java b/PersonalFinancesService/src/main/java/dev/mvvasilev/finances/persistence/dtos/SpendingOverTimeDTO.java new file mode 100644 index 0000000..f10e3fc --- /dev/null +++ b/PersonalFinancesService/src/main/java/dev/mvvasilev/finances/persistence/dtos/SpendingOverTimeDTO.java @@ -0,0 +1,11 @@ +package dev.mvvasilev.finances.persistence.dtos; + +import java.time.LocalDateTime; + +public record SpendingOverTimeDTO( + Long categoryId, + + Double amountForPeriod, + + LocalDateTime periodBeginningTimestamp +) {} diff --git a/PersonalFinancesService/src/main/java/dev/mvvasilev/finances/services/AuthorizationService.java b/PersonalFinancesService/src/main/java/dev/mvvasilev/finances/services/AuthorizationService.java index dd18eaf..44746d4 100644 --- a/PersonalFinancesService/src/main/java/dev/mvvasilev/finances/services/AuthorizationService.java +++ b/PersonalFinancesService/src/main/java/dev/mvvasilev/finances/services/AuthorizationService.java @@ -5,6 +5,8 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.stereotype.Service; +import java.util.Collection; + @Service("authService") public class AuthorizationService { @@ -49,4 +51,8 @@ public class AuthorizationService { return !entityManager.createQuery(finalQuery).setMaxResults(1).getResultList().isEmpty(); } + public boolean isOwner(Collection ids, Class userOwnedEntity) { + return ids.stream().allMatch(id -> isOwner(id, userOwnedEntity)); + } + } diff --git a/PersonalFinancesService/src/main/java/dev/mvvasilev/finances/services/StatisticsService.java b/PersonalFinancesService/src/main/java/dev/mvvasilev/finances/services/StatisticsService.java index c9ab059..dd37009 100644 --- a/PersonalFinancesService/src/main/java/dev/mvvasilev/finances/services/StatisticsService.java +++ b/PersonalFinancesService/src/main/java/dev/mvvasilev/finances/services/StatisticsService.java @@ -2,12 +2,17 @@ package dev.mvvasilev.finances.services; import dev.mvvasilev.common.data.AbstractEntity; import dev.mvvasilev.finances.dtos.CategoryDTO; +import dev.mvvasilev.finances.dtos.SpendingByCategoriesDTO; import dev.mvvasilev.finances.dtos.SpendingByCategoryDTO; +import dev.mvvasilev.finances.dtos.SpendingOverTimeByCategoryDTO; +import dev.mvvasilev.finances.enums.TimePeriod; import dev.mvvasilev.finances.persistence.StatisticsRepository; import dev.mvvasilev.finances.persistence.TransactionCategoryRepository; import org.springframework.stereotype.Service; import java.time.LocalDateTime; +import java.time.temporal.ChronoUnit; +import java.util.*; @Service public class StatisticsService { @@ -21,17 +26,53 @@ public class StatisticsService { this.statisticsRepository = statisticsRepository; } - public SpendingByCategoryDTO spendingByCategory(LocalDateTime from, LocalDateTime to, int userId) { - final var categories = transactionCategoryRepository.fetchTransactionCategoriesWithUserId(userId); + public SpendingByCategoriesDTO spendingByCategory(Long[] categoryId, LocalDateTime from, LocalDateTime to) { + final var categories = transactionCategoryRepository.findAllById(Arrays.stream(categoryId).toList()).stream().map(c -> new CategoryDTO(c.getId(), c.getName(), null)).toList(); + final var spendingByCategory = statisticsRepository.fetchSpendingByCategory( + categoryId, from, - to, - categories.stream().map(AbstractEntity::getId).toList() + to ); - return new SpendingByCategoryDTO( - categories.stream().map(c -> new CategoryDTO(c.getId(), c.getName(), null)).toList(), + return new SpendingByCategoriesDTO( + categories, spendingByCategory ); } + + public SpendingOverTimeByCategoryDTO spendingByCategoryOverTime( + Long[] categoryId, + TimePeriod period, + LocalDateTime from, + LocalDateTime to + ) { + return new SpendingOverTimeByCategoryDTO( + transactionCategoryRepository.findAllById(Arrays.stream(categoryId).toList()).stream().map(c -> new CategoryDTO(c.getId(), c.getName(), null)).toList(), + period.getDuration(), + statisticsRepository.fetchSpendingByCategoryOverTime(from, to, period, categoryId).stream().map(dto -> new SpendingByCategoryDTO( + dto.categoryId(), + dto.periodBeginningTimestamp(), + dto.amountForPeriod() + )).toList() + ); + } + + // Impose limits if necessary + private boolean validatePeriodWithinLimits(LocalDateTime from, LocalDateTime to, TimePeriod period) { + return switch (period) { + // Can't request daily spending breakdown for a period longer than a month + case DAILY -> ChronoUnit.MONTHS.between(from, to) <= 1; + // Can't request weekly spending breakdown for a period longer than a year + case WEEKLY -> ChronoUnit.YEARS.between(from, to) <= 1; + // Can't request bi-weekly spending breakdown for a period longer than a year + case BIWEEKLY -> ChronoUnit.YEARS.between(from, to) <= 1; + // Can't request monthly spending breakdown for a period longer than 3 years + case MONTHLY -> ChronoUnit.YEARS.between(from, to) <= 3; + // Can't request quarterly spending breakdown for a period longer than 5 years + case QUARTERLY -> ChronoUnit.YEARS.between(from, to) <= 5; + // Can't request yearly spending breakdown for a period longer than 30 years + case YEARLY -> ChronoUnit.YEARS.between(from, to) <= 30; + }; + } } diff --git a/PersonalFinancesService/src/main/resources/db/migration/V1.17__AddSpendingOverTimeFunction.sql b/PersonalFinancesService/src/main/resources/db/migration/V1.17__AddSpendingOverTimeFunction.sql new file mode 100644 index 0000000..ac53fc4 --- /dev/null +++ b/PersonalFinancesService/src/main/resources/db/migration/V1.17__AddSpendingOverTimeFunction.sql @@ -0,0 +1,73 @@ +CREATE OR REPLACE FUNCTION statistics.spending_over_time( + category_ids BIGINT[], + time_period TEXT, + from_date TIMESTAMP, + to_date TIMESTAMP +) +RETURNS TABLE ( + category_id BIGINT, + amount_for_period FLOAT, + period_beginning_timestamp TIMESTAMP + ) +LANGUAGE plpgsql +AS $$ +DECLARE + time_interval interval; +BEGIN + time_interval := CASE + WHEN time_period = 'DAILY' THEN interval '1 day' + WHEN time_period = 'WEEKLY' THEN interval '1 week' + WHEN time_period = 'BIWEEKLY' THEN interval '2 weeks' + WHEN time_period = 'MONTHLY' THEN interval '1 month' + WHEN time_period = 'QUARTERLY' THEN interval '3 months' + WHEN time_period = 'YEARLY' THEN interval '1 year' + ELSE interval '1 day' + END; + + RETURN QUERY WITH start_end AS ( + SELECT + c.id as category_id, + generate_series( + from_date, + to_date, + time_interval + ) as period_beginning_timestamp, + generate_series( + from_date, + to_date, + time_interval + ) + time_interval AS period_ending_timestamp + FROM categories.transaction_category AS c + WHERE c.id = any(category_ids) + ), + amounts AS ( + SELECT + ptc.category_id, + SUM(pt.amount) AS amount_for_period, + start_end.period_beginning_timestamp + FROM start_end + JOIN categories.processed_transaction_category AS ptc ON ptc.category_id = start_end.category_id + JOIN transactions.processed_transaction AS pt ON ptc.processed_transaction_id = pt.id + WHERE + ( + start_end.period_ending_timestamp > to_date + AND pt.timestamp BETWEEN start_end.period_beginning_timestamp AND to_date + ) + OR ( + start_end.period_ending_timestamp <= to_date + AND pt.timestamp BETWEEN start_end.period_beginning_timestamp AND start_end.period_ending_timestamp + ) + GROUP BY ( + start_end.period_beginning_timestamp, + start_end.period_ending_timestamp, + ptc.category_id + ) + ) + SELECT + start_end.category_id, + COALESCE(amounts.amount_for_period, 0) AS amount_for_period, + start_end.period_beginning_timestamp + FROM start_end + LEFT OUTER JOIN amounts ON amounts.category_id = start_end.category_id AND amounts.period_beginning_timestamp = start_end.period_beginning_timestamp; +END +$$; \ No newline at end of file diff --git a/PersonalFinancesService/src/main/resources/db/migration/V1.18__AddWidget.sql b/PersonalFinancesService/src/main/resources/db/migration/V1.18__AddWidget.sql new file mode 100644 index 0000000..0b5ee76 --- /dev/null +++ b/PersonalFinancesService/src/main/resources/db/migration/V1.18__AddWidget.sql @@ -0,0 +1,14 @@ +CREATE SCHEMA IF NOT EXISTS widgets; + +CREATE TABLE IF NOT EXISTS widgets.widget ( + id BIGSERIAL, + type VARCHAR(255), + positionX INTEGER, + positionY INTEGER, + sizeX INTEGER, + sizeY INTEGER, + name VARCHAR(255), + time_created TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL, + time_last_modified TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL, + CONSTRAINT PK_widget PRIMARY KEY (id) +) \ No newline at end of file diff --git a/PersonalFinancesService/src/main/resources/db/migration/V1.19__AddWidgetParameter.sql b/PersonalFinancesService/src/main/resources/db/migration/V1.19__AddWidgetParameter.sql new file mode 100644 index 0000000..88336c0 --- /dev/null +++ b/PersonalFinancesService/src/main/resources/db/migration/V1.19__AddWidgetParameter.sql @@ -0,0 +1,13 @@ +CREATE TABLE IF NOT EXISTS widgets.widget_parameter ( + id BIGSERIAL, + widget_id BIGINT NOT NULL, + name VARCHAR(255) NOT NULL, + string_value VARCHAR(1024), + numeric_value FLOAT, + timestamp_value TIMESTAMP, + boolean_value BOOLEAN, + time_created TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL, + time_last_modified TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL, + CONSTRAINT PK_widget_parameter PRIMARY KEY (id), + CONSTRAINT FK_widget_parameter_widget FOREIGN KEY (widget_id) REFERENCES widgets.widget(id) +); \ No newline at end of file