Skip to content

Commit

Permalink
good cache in frontend!! & generally cleaned up
Browse files Browse the repository at this point in the history
  • Loading branch information
M1chaCH committed Jan 20, 2024
1 parent 8dbbbe2 commit 52cef09
Show file tree
Hide file tree
Showing 51 changed files with 971 additions and 624 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import jakarta.ws.rs.Consumes;
import jakarta.ws.rs.DefaultValue;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.POST;
import jakarta.ws.rs.PUT;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
Expand Down Expand Up @@ -58,17 +59,17 @@ public Response getTransactions(

UUID[] tags = new UUID[0];
if (!tagIds.isBlank()) {
tags = ParsingUtils.toUUIDArray(Arrays.stream(tagIds.split(";")).filter(s -> !s.isBlank()).toArray());
tags = ParsingUtils.toUUIDArray(Arrays.stream(tagIds.split(",")).filter(s -> !s.isBlank()).toArray());
}

return Response.status(Status.OK).entity(service.getTransactions(query, tags, from, to, needAttention, page)).build();
}

@GET
@POST
@Path("/import")
@Produces(MediaType.APPLICATION_JSON)
public Response getImportTransactions() {
return Response.status(Status.OK).entity(service.importTransactions()).build();
Status status = service.importTransactions() ? Status.OK : Status.NO_CONTENT;
return Response.status(status).build();
}

@PUT
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
import java.util.List;
import java.util.Optional;
import java.util.UUID;
import lombok.Getter;
import org.eclipse.microprofile.config.inject.ConfigProperty;
import org.jooq.Condition;
import org.jooq.DSLContext;
Expand All @@ -44,8 +45,6 @@
import org.jooq.SelectLimitStep;
import org.jooq.impl.UpdatableRecordImpl;

// TODO cache for improved read times
// TODO write tests with demo data
@ApplicationScoped
public class TransactionProvider implements BaseRecordProvider<TransactionRecord, UUID> {

Expand All @@ -55,11 +54,12 @@ public class TransactionProvider implements BaseRecordProvider<TransactionRecord
protected final TagProvider tagProvider;
protected final KeywordProvider keywordProvider;
protected final TransactionTagDuplicateProvider transactionTagDuplicateProvider;
@Getter
protected final int pageSize;

@Inject
public TransactionProvider(
@ConfigProperty(name = "db.limit.page.size", defaultValue = "100") int pageSize,
@ConfigProperty(name = "db.limit.page.size", defaultValue = "50") int pageSize,
TransactionMailProvider transactionMailProvider,
TagProvider tagProvider,
KeywordProvider keywordProvider,
Expand All @@ -72,6 +72,68 @@ public TransactionProvider(
this.pageSize = pageSize;
}

@LoggedStatement
public long countTransactions(
DSLContext db, UUID userId,
String query,
UUID[] tagIds,
LocalDate from,
LocalDate to,
boolean needAttention
) {
SelectConditionStep<?> conditions = db.select(TRANSACTION.ID)
.from(TRANSACTION)
.where(TRANSACTION.USER_ID.eq(userId));
conditions = buildTransactionsFilterCondition(conditions, query, tagIds, from, to, needAttention);
return conditions.fetch().size();
}

protected <T extends Record> SelectConditionStep<T> buildTransactionsFilterCondition(
SelectConditionStep<T> step,
String query,
UUID[] tagIds,
LocalDate from,
LocalDate to,
boolean needAttention
) {
final LocalDate tomorrow = localDateNow().plusDays(1);

if (query != null && !query.isBlank()) {
if (!query.startsWith("%")) {
query = "%" + query;
}
if (!query.endsWith("%")) {
query += "%";
}

step = step.and(TRANSACTION.ALIAS.likeIgnoreCase(query))
.or(TRANSACTION.NOTE.likeIgnoreCase(query))
.or(TRANSACTION.RECEIVER.likeIgnoreCase(query));
}

if (needAttention) {
step = step.and(TRANSACTION.NEED_USER_ATTENTION.eq(true));
}

if (tagIds != null && tagIds.length > 0) {
Condition tagsCondition = TRANSACTION.TAG_ID.eq(tagIds[0]);
for (int i = 1; i < tagIds.length; i++) {
tagsCondition = tagsCondition.or(TRANSACTION.TAG_ID.eq(tagIds[i]));
}
step = step.and(tagsCondition);
}

if (from != null && from.isBefore(tomorrow)) {
step = step.and(TRANSACTION.TRANSACTION_DATE.ge(from));
}

if (to != null && to.isBefore(tomorrow)) {
step = step.and(TRANSACTION.TRANSACTION_DATE.le(to));
}

return step;
}

@LoggedStatement
public Optional<TransactionRecord> selectTransaction(DSLContext db, UUID userId, UUID transactionId) {
TransactionRecord transaction = db.fetchOne(TRANSACTION, TRANSACTION.USER_ID.eq(userId).and(TRANSACTION.ID.eq(transactionId)));
Expand Down Expand Up @@ -197,7 +259,6 @@ public List<TransactionDto> selectTransactionsWithDependenciesWithFilterWithPage
boolean needAttention,
int page
) {
LocalDate tomorrow = localDateNow().plusDays(1);
SelectConditionStep<?> conditions = db
.select(TRANSACTION.asterisk(), TAG.asterisk(), KEYWORD.asterisk(),
count(TRANSACTION_TAG_DUPLICATE.ID).as(DUPLICATES_COUNT_COLUMN))
Expand All @@ -210,41 +271,7 @@ public List<TransactionDto> selectTransactionsWithDependenciesWithFilterWithPage
.on(TRANSACTION.ID.eq(TRANSACTION_TAG_DUPLICATE.TRANSACTION_ID))
.where(TRANSACTION.USER_ID.eq(userId));

if (query != null && !query.isBlank()) {
if (!query.startsWith("%")) {
query = "%" + query;
}
if (!query.endsWith("%")) {
query += "%";
}

conditions = conditions
.and(TRANSACTION.ALIAS.likeIgnoreCase(query)
.or(TRANSACTION.NOTE.likeIgnoreCase(query)
.or(TRANSACTION.RECEIVER.likeIgnoreCase(query))));
}

if (needAttention) {
conditions = conditions.and(TRANSACTION.NEED_USER_ATTENTION.eq(true));
}

if (tagIds != null && tagIds.length > 0) {
Condition tagsCondition = TRANSACTION.TAG_ID.eq(tagIds[0]);
for (int i = 1; i < tagIds.length; i++) {
tagsCondition = tagsCondition.or(TRANSACTION.TAG_ID.eq(tagIds[i]));
}
conditions = conditions.and(tagsCondition);
}

if (from != null && from.isBefore(tomorrow)) {
conditions = conditions.and(TRANSACTION.TRANSACTION_DATE.ge(from));
}

if (to != null && to.isBefore(tomorrow)) {
conditions = conditions.and(TRANSACTION.TRANSACTION_DATE.le(to));
}

SelectLimitStep<?> limitStep = conditions
SelectLimitStep<?> limitStep = buildTransactionsFilterCondition(conditions, query, tagIds, from, to, needAttention)
.groupBy(TRANSACTION.ID, TAG.ID, KEYWORD.ID)
.orderBy(TRANSACTION.TRANSACTION_DATE.desc());

Expand Down Expand Up @@ -388,7 +415,7 @@ public void insertTransactions(DSLContext db, List<TransactionRecord> transactio
}

@LoggedStatement
public void updateTransactionUserInput(DSLContext db, TransactionRecord transaction) {
public void updateTransactionUserInput(TransactionRecord transaction) {
if (transaction.get(TRANSACTION.TAG_ID) == null) {
transaction.store(TRANSACTION.ALIAS, TRANSACTION.NOTE);
} else if (transaction.get(TRANSACTION.MATCHING_KEYWORD_ID) == null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import ch.michu.tech.swissbudget.app.provider.TransactionProvider;
import ch.michu.tech.swissbudget.app.transaction.TransactionImporter;
import ch.michu.tech.swissbudget.framework.data.RequestSupport;
import ch.michu.tech.swissbudget.framework.dto.PaginationResultDto;
import ch.michu.tech.swissbudget.framework.error.exception.ResourceNotFoundException;
import ch.michu.tech.swissbudget.generated.jooq.tables.records.TransactionRecord;
import jakarta.enterprise.context.ApplicationScoped;
Expand All @@ -27,25 +28,40 @@ public TransactionService(Provider<RequestSupport> supportProvider, TransactionI
this.provider = provider;
}

public List<TransactionDto> getTransactions(String query, UUID[] tagIds, LocalDate from, LocalDate to, boolean needAttention,
int page) {
public PaginationResultDto<TransactionDto> getTransactions(
String query, UUID[] tagIds, LocalDate from, LocalDate to, boolean needAttention, int page
) {
final RequestSupport support = supportProvider.get();
return provider.selectTransactionsWithDependenciesWithFilterWithPageAsDto(support.db(), support.getUserIdOrThrow(),
query, tagIds, from, to, needAttention, page);
List<TransactionDto> data = provider.selectTransactionsWithDependenciesWithFilterWithPageAsDto(support.db(),
support.getUserIdOrThrow(),
query,
tagIds,
from,
to,
needAttention,
page);
long count = provider.countTransactions(support.db(), support.getUserIdOrThrow(), query, tagIds, from, to, needAttention);

return new PaginationResultDto<>(provider.getPageSize(), count, data);
}

public List<TransactionDto> importTransactions() {
/**
* imports all new transactions for a user.
*
* @return true: if some transactions were imported
*/
public boolean importTransactions() {
final RequestSupport support = supportProvider.get();
return importer.importTransactions(support.db(), support.getUserIdOrThrow()).stream().map(TransactionDto::new).toList();
return !importer.importTransactions(support.db(), support.getUserIdOrThrow()).isEmpty();
}

public void updateTransactionUserInput(TransactionDto toUpdate) {
final RequestSupport support = supportProvider.get();
TransactionRecord transaction = provider.selectTransaction(support.db(), support.getUserIdOrThrow(), toUpdate.getId())
.orElseThrow(() -> new ResourceNotFoundException("transaction", toUpdate.getId()));
.orElseThrow(() -> new ResourceNotFoundException("transaction", toUpdate.getId()));

transaction.setAlias(toUpdate.getAlias());
transaction.setNote(toUpdate.getNote());
provider.updateTransactionUserInput(support.db(), transaction);
provider.updateTransactionUserInput(transaction);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package ch.michu.tech.swissbudget.framework.dto;

import java.util.List;
import lombok.AllArgsConstructor;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.ToString;

@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
@ToString
@EqualsAndHashCode
public class PaginationResultDto<T> {
private int pageSize;
private long totalSize;
private List<T> pageData;
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,21 @@

public class LocalDateDeserializer {

public static final String DEFAULT_LOCAL_DATE_PATTERN = "yyyy-MM-dd'T'HH:mm:ss.SSSZ";
public static final String DEFAULT_LOCAL_DATE_PATTERN = "yyyy-MM-dd'T'HH:mm:ss.SSSX";
public static final String FALLBACK_LOCAL_DATE_PATTERN = "yyyy-MM-dd";
private static final Logger LOGGER = Logger.getLogger(LocalDateDeserializer.class.getSimpleName());

private LocalDateDeserializer() {
}

public static LocalDate parseLocalDate(String dateString) {
try {
return LocalDate.parse(dateString, DateTimeFormatter.ofPattern(DEFAULT_LOCAL_DATE_PATTERN));
} catch (DateTimeParseException e) {
try {
LOGGER.log(Level.FINE,
"custom LocalDate deserializer WARN: could not parse date (string:{0} - pattern:{1}), using ISO / fallback {2}",
new Object[]{dateString, DEFAULT_LOCAL_DATE_PATTERN, FALLBACK_LOCAL_DATE_PATTERN});
"custom LocalDate deserializer WARN: could not parse date (string:{0} - pattern:{1}), using ISO / fallback {2}",
new Object[]{dateString, DEFAULT_LOCAL_DATE_PATTERN, FALLBACK_LOCAL_DATE_PATTERN});
return LocalDate.parse(dateString, DateTimeFormatter.ISO_DATE);
} catch (DateTimeParseException e2) {
return LocalDate.parse(dateString, DateTimeFormatter.ofPattern(FALLBACK_LOCAL_DATE_PATTERN));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,24 +35,43 @@ void getTransactions_happy() {
Response happyResponse = client.createAsRootUser().get();
String result = happyResponse.readEntity(String.class);
String expectedTransactionId = data.getGeneratedId("tra_cpm").toString();
JSONArray items = new JSONArray(result);

JSONObject data = new JSONObject(result);
assertEquals(3, data.getInt("pageSize"));
assertEquals(11, data.getInt("totalSize"));

JSONArray items = data.getJSONArray("pageData");
assertEquals(3, items.length());

JSONObject first = items.getJSONObject(0);
assertEquals(expectedTransactionId, first.getString("id"));
assertEquals(12.5, first.getDouble("amount"));

Response secondPage = client.createAsRootUser().queryParam("page", "2").get();
JSONArray secondPageItems = new JSONArray(secondPage.readEntity(String.class));
JSONArray secondPageItems = new JSONObject(secondPage.readEntity(String.class)).getJSONArray("pageData");
assertEquals(3, secondPageItems.length());
String expectedDate = DateBuilder.today().addMonths(-1).firstDayOfMonth().addDays(26).formatted("yyyy-MM-dd");
assertEquals(expectedDate, secondPageItems.getJSONObject(0).getString("transactionDate"));

Response lastPage = client.createAsRootUser().queryParam("page", "4").get();
JSONArray lastPageItems = new JSONArray(lastPage.readEntity(String.class));
JSONArray lastPageItems = new JSONObject(lastPage.readEntity(String.class)).getJSONArray("pageData");
assertEquals(2, lastPageItems.length()); // in total, there are 11 data rows (11 % 3 = 2)
}

@Test
void getTransactions_happyFilter() {
Response queryResponse = client.createAsRootUser().queryParam("query", "auerauftra").get();
JSONObject result = new JSONObject(queryResponse.readEntity(String.class));
assertEquals(2, result.getInt("totalSize"));
assertEquals(2, result.getJSONArray("pageData").length());

UUID tagId = super.data.getGeneratedId("tag_res");
Response tagResponse = client.createAsRootUser().queryParam("tagIds", tagId.toString()).get();
JSONObject tagResult = new JSONObject(tagResponse.readEntity(String.class));
assertEquals(2, tagResult.getInt("totalSize"));
assertEquals(2, tagResult.getJSONArray("pageData").length());
}

@Test
void putTransaction_happy() {
UUID transactionId = data.getGeneratedId("tra_gsg");
Expand All @@ -74,7 +93,7 @@ void putTransaction_happy() {
assertEquals(originalTransaction.getExpense(), changedTransaction.getExpense());
assertEquals(originalTransaction.getAmount(), changedTransaction.getAmount());
assertEquals(originalTransaction.getTransactionDate().format(DateBuilder.DEFAULT_DATE_FORMATTER),
changedTransaction.getTransactionDate().format(DateBuilder.DEFAULT_DATE_FORMATTER));
changedTransaction.getTransactionDate().format(DateBuilder.DEFAULT_DATE_FORMATTER));
assertEquals("cool note", changedTransaction.getNote());
assertEquals("test", changedTransaction.getAlias());

Expand All @@ -95,7 +114,7 @@ void putTransaction_invalid() {

Response unauthorized = client.create().withUserAgent("other-agent").loginUser("bak@test.ch").put(transaction);
assertEquals(Status.NOT_FOUND.getStatusCode(), unauthorized.getStatus());

transaction.setId(UUID.randomUUID());
Response notFount = client.createAsRootUser().put(transaction);
assertEquals(Status.NOT_FOUND.getStatusCode(), notFount.getStatus());
Expand Down
3 changes: 3 additions & 0 deletions frontend/angular.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@
"style": "scss",
"standalone": false,
"skipTests": true
},
"@schematics/angular:service": {
"skipTests": true
}
},
"root": "",
Expand Down
13 changes: 13 additions & 0 deletions frontend/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit 52cef09

Please sign in to comment.