diff --git a/application/sissi-modules/debra/src/main/java/dev/sheldan/sissi/module/debra/api/EndlessStreamController.java b/application/sissi-modules/debra/src/main/java/dev/sheldan/sissi/module/debra/api/EndlessStreamController.java new file mode 100644 index 00000000..14be81f2 --- /dev/null +++ b/application/sissi-modules/debra/src/main/java/dev/sheldan/sissi/module/debra/api/EndlessStreamController.java @@ -0,0 +1,51 @@ +package dev.sheldan.sissi.module.debra.api; + +import dev.sheldan.abstracto.core.service.ConfigService; +import dev.sheldan.sissi.module.debra.config.DebraFeatureConfig; +import dev.sheldan.sissi.module.debra.model.api.DonationsResponse; +import dev.sheldan.sissi.module.debra.model.api.EndlessStreamInfo; +import dev.sheldan.sissi.module.debra.model.database.EndlessStream; +import dev.sheldan.sissi.module.debra.service.DonationService; +import dev.sheldan.sissi.module.debra.service.management.EndlessStreamManagementServiceBean; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.math.BigDecimal; +import java.time.Instant; +import java.time.temporal.ChronoUnit; + +import static dev.sheldan.sissi.module.debra.config.DebraFeatureConfig.DEBRA_DONATION_NOTIFICATION_SERVER_ID_ENV_NAME; + +@RestController +@RequestMapping(value = "/stream") +public class EndlessStreamController { + + @Autowired + private EndlessStreamManagementServiceBean endlessStreamManagementServiceBean; + + @Autowired + private DonationService donationService; + + @Autowired + private ConfigService configService; + + @GetMapping(value = "/endlessStream/{id}", produces = "application/json") + public EndlessStreamInfo getLatestDonations(@PathVariable("id") Long id) { + Long serverId = Long.parseLong(System.getenv(DEBRA_DONATION_NOTIFICATION_SERVER_ID_ENV_NAME)); + EndlessStream endlessStream = endlessStreamManagementServiceBean.getEndlessStream(id); + DonationsResponse donationInfo = donationService.getSynchronizedCachedDonationAmount(serverId); + BigDecimal collectedAmount = donationInfo.getPage().getCollected(); + Long minuteRate = configService.getLongValueOrConfigDefault(DebraFeatureConfig.ENDLESS_STREAM_MINUTE_RATE, serverId); + Instant endDate = endlessStream.getStartTime().plus(collectedAmount.multiply(new BigDecimal(minuteRate)).toBigInteger().longValue(), ChronoUnit.MINUTES); + return EndlessStreamInfo + .builder() + .startDate(endlessStream.getStartTime()) + .endDate(endDate) + .donationAmount(collectedAmount.longValue()) + .minuteRate(minuteRate) + .build(); + } +} diff --git a/application/sissi-modules/debra/src/main/java/dev/sheldan/sissi/module/debra/config/DebraFeatureConfig.java b/application/sissi-modules/debra/src/main/java/dev/sheldan/sissi/module/debra/config/DebraFeatureConfig.java index 69651ecd..d5f2a018 100644 --- a/application/sissi-modules/debra/src/main/java/dev/sheldan/sissi/module/debra/config/DebraFeatureConfig.java +++ b/application/sissi-modules/debra/src/main/java/dev/sheldan/sissi/module/debra/config/DebraFeatureConfig.java @@ -12,6 +12,7 @@ import java.util.List; public class DebraFeatureConfig implements FeatureConfig { public static final String DEBRA_DONATION_NOTIFICATION_DELAY_CONFIG_KEY = "debraDonationNotificationDelayMillis"; + public static final String ENDLESS_STREAM_MINUTE_RATE = "endlessStreamMinuteRate"; public static final String DEBRA_DONATION_API_FETCH_SIZE_KEY = "debraDonationApiFetchSize"; public static final String DEBRA_DONATION_NOTIFICATION_SERVER_ID_ENV_NAME = "DEBRA_DONATION_NOTIFICATION_SERVER_ID"; @Override @@ -26,6 +27,6 @@ public class DebraFeatureConfig implements FeatureConfig { @Override public List getRequiredSystemConfigKeys() { - return Arrays.asList(DEBRA_DONATION_NOTIFICATION_DELAY_CONFIG_KEY, DEBRA_DONATION_API_FETCH_SIZE_KEY); + return Arrays.asList(DEBRA_DONATION_NOTIFICATION_DELAY_CONFIG_KEY, DEBRA_DONATION_API_FETCH_SIZE_KEY, ENDLESS_STREAM_MINUTE_RATE); } } diff --git a/application/sissi-modules/debra/src/main/java/dev/sheldan/sissi/module/debra/DonationAmountNotFoundException.java b/application/sissi-modules/debra/src/main/java/dev/sheldan/sissi/module/debra/exception/DonationAmountNotFoundException.java similarity index 87% rename from application/sissi-modules/debra/src/main/java/dev/sheldan/sissi/module/debra/DonationAmountNotFoundException.java rename to application/sissi-modules/debra/src/main/java/dev/sheldan/sissi/module/debra/exception/DonationAmountNotFoundException.java index 5e205bcc..70e9ecfa 100644 --- a/application/sissi-modules/debra/src/main/java/dev/sheldan/sissi/module/debra/DonationAmountNotFoundException.java +++ b/application/sissi-modules/debra/src/main/java/dev/sheldan/sissi/module/debra/exception/DonationAmountNotFoundException.java @@ -1,4 +1,4 @@ -package dev.sheldan.sissi.module.debra; +package dev.sheldan.sissi.module.debra.exception; import dev.sheldan.abstracto.core.exception.AbstractoTemplatableException; diff --git a/application/sissi-modules/debra/src/main/java/dev/sheldan/sissi/module/debra/model/api/EndlessStreamInfo.java b/application/sissi-modules/debra/src/main/java/dev/sheldan/sissi/module/debra/model/api/EndlessStreamInfo.java new file mode 100644 index 00000000..454864c8 --- /dev/null +++ b/application/sissi-modules/debra/src/main/java/dev/sheldan/sissi/module/debra/model/api/EndlessStreamInfo.java @@ -0,0 +1,15 @@ +package dev.sheldan.sissi.module.debra.model.api; + +import lombok.Builder; +import lombok.Getter; + +import java.time.Instant; + +@Builder +@Getter +public class EndlessStreamInfo { + private Instant endDate; + private Instant startDate; + private Long donationAmount; + private Long minuteRate; +} diff --git a/application/sissi-modules/debra/src/main/java/dev/sheldan/sissi/module/debra/model/database/EndlessStream.java b/application/sissi-modules/debra/src/main/java/dev/sheldan/sissi/module/debra/model/database/EndlessStream.java new file mode 100644 index 00000000..8fb5ec11 --- /dev/null +++ b/application/sissi-modules/debra/src/main/java/dev/sheldan/sissi/module/debra/model/database/EndlessStream.java @@ -0,0 +1,31 @@ +package dev.sheldan.sissi.module.debra.model.database; + +import jakarta.persistence.*; +import lombok.*; + +import java.time.Instant; + +@Builder +@Entity +@NoArgsConstructor +@AllArgsConstructor +@Table(name = "endless_stream") +@Getter +@Setter +@EqualsAndHashCode +public class EndlessStream { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id", nullable = false) + private Long id; + + @Column(name = "start_time") + private Instant startTime; + + @Column(name = "created") + private Instant created; + + @Column(name = "updated") + private Instant updated; +} diff --git a/application/sissi-modules/debra/src/main/java/dev/sheldan/sissi/module/debra/repository/EndlessStreamRepository.java b/application/sissi-modules/debra/src/main/java/dev/sheldan/sissi/module/debra/repository/EndlessStreamRepository.java new file mode 100644 index 00000000..82bebfce --- /dev/null +++ b/application/sissi-modules/debra/src/main/java/dev/sheldan/sissi/module/debra/repository/EndlessStreamRepository.java @@ -0,0 +1,9 @@ +package dev.sheldan.sissi.module.debra.repository; + +import dev.sheldan.sissi.module.debra.model.database.EndlessStream; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface EndlessStreamRepository extends JpaRepository { +} diff --git a/application/sissi-modules/debra/src/main/java/dev/sheldan/sissi/module/debra/service/DonationCacheService.java b/application/sissi-modules/debra/src/main/java/dev/sheldan/sissi/module/debra/service/DonationCacheService.java deleted file mode 100644 index d3cb789e..00000000 --- a/application/sissi-modules/debra/src/main/java/dev/sheldan/sissi/module/debra/service/DonationCacheService.java +++ /dev/null @@ -1,18 +0,0 @@ -package dev.sheldan.sissi.module.debra.service; - -import org.springframework.beans.factory.InitializingBean; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.cache.CacheManager; -import org.springframework.stereotype.Component; - -@Component -public class DonationCacheService implements InitializingBean { - - @Autowired - private CacheManager cacheManager; - - @Override - public void afterPropertiesSet() throws Exception { - cacheManager.getCache("donations"); - } -} diff --git a/application/sissi-modules/debra/src/main/java/dev/sheldan/sissi/module/debra/service/DonationService.java b/application/sissi-modules/debra/src/main/java/dev/sheldan/sissi/module/debra/service/DonationService.java index d37ad513..c4c38fda 100644 --- a/application/sissi-modules/debra/src/main/java/dev/sheldan/sissi/module/debra/service/DonationService.java +++ b/application/sissi-modules/debra/src/main/java/dev/sheldan/sissi/module/debra/service/DonationService.java @@ -13,7 +13,7 @@ import dev.sheldan.abstracto.core.service.management.ServerManagementService; import dev.sheldan.abstracto.core.templating.model.MessageToSend; import dev.sheldan.abstracto.core.templating.service.TemplateService; import dev.sheldan.abstracto.core.utils.FutureUtils; -import dev.sheldan.sissi.module.debra.DonationAmountNotFoundException; +import dev.sheldan.sissi.module.debra.exception.DonationAmountNotFoundException; import dev.sheldan.sissi.module.debra.config.DebraPostTarget; import dev.sheldan.sissi.module.debra.config.DebraProperties; import dev.sheldan.sissi.module.debra.converter.DonationConverter; diff --git a/application/sissi-modules/debra/src/main/java/dev/sheldan/sissi/module/debra/service/management/EndlessStreamManagementServiceBean.java b/application/sissi-modules/debra/src/main/java/dev/sheldan/sissi/module/debra/service/management/EndlessStreamManagementServiceBean.java new file mode 100644 index 00000000..002bc7c8 --- /dev/null +++ b/application/sissi-modules/debra/src/main/java/dev/sheldan/sissi/module/debra/service/management/EndlessStreamManagementServiceBean.java @@ -0,0 +1,17 @@ +package dev.sheldan.sissi.module.debra.service.management; + +import dev.sheldan.sissi.module.debra.model.database.EndlessStream; +import dev.sheldan.sissi.module.debra.repository.EndlessStreamRepository; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +@Component +public class EndlessStreamManagementServiceBean { + + @Autowired + private EndlessStreamRepository endlessStreamRepository; + + public EndlessStream getEndlessStream(Long id) { + return endlessStreamRepository.getReferenceById(id); + } +} diff --git a/application/sissi-modules/debra/src/main/resources/debra.properties b/application/sissi-modules/debra/src/main/resources/debra.properties index 5e63893f..82e4ab32 100644 --- a/application/sissi-modules/debra/src/main/resources/debra.properties +++ b/application/sissi-modules/debra/src/main/resources/debra.properties @@ -10,5 +10,8 @@ sissi.debra.donationAPIUrl=https://www.altruja.de/api/page/discord-gg-austria-fu abstracto.systemConfigs.debraDonationNotificationDelayMillis.name=debraDonationNotificationDelayMillis abstracto.systemConfigs.debraDonationNotificationDelayMillis.longValue=60000 +abstracto.systemConfigs.endlessStreamMinuteRate.name=endlessStreamMinuteRate +abstracto.systemConfigs.endlessStreamMinuteRate.longValue=1 + abstracto.systemConfigs.debraDonationApiFetchSize.name=debraDonationApiFetchSize abstracto.systemConfigs.debraDonationApiFetchSize.longValue=1000 \ No newline at end of file diff --git a/application/sissi-modules/debra/src/main/resources/migrations/1.4.29/collection.xml b/application/sissi-modules/debra/src/main/resources/migrations/1.4.29/collection.xml new file mode 100644 index 00000000..200facad --- /dev/null +++ b/application/sissi-modules/debra/src/main/resources/migrations/1.4.29/collection.xml @@ -0,0 +1,10 @@ + + + + \ No newline at end of file diff --git a/application/sissi-modules/debra/src/main/resources/migrations/1.4.29/tables/endless_stream.xml b/application/sissi-modules/debra/src/main/resources/migrations/1.4.29/tables/endless_stream.xml new file mode 100644 index 00000000..4926ced6 --- /dev/null +++ b/application/sissi-modules/debra/src/main/resources/migrations/1.4.29/tables/endless_stream.xml @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + DROP TRIGGER IF EXISTS endless_stream_update_trigger ON endless_stream; + CREATE TRIGGER endless_stream_update_trigger BEFORE UPDATE ON endless_stream FOR EACH ROW EXECUTE PROCEDURE update_trigger_procedure(); + + + DROP TRIGGER IF EXISTS endless_stream_insert_trigger ON endless_stream; + CREATE TRIGGER endless_stream_insert_trigger BEFORE INSERT ON endless_stream FOR EACH ROW EXECUTE PROCEDURE insert_trigger_procedure(); + + + + \ No newline at end of file diff --git a/application/sissi-modules/debra/src/main/resources/migrations/1.4.29/tables/tables.xml b/application/sissi-modules/debra/src/main/resources/migrations/1.4.29/tables/tables.xml new file mode 100644 index 00000000..da34e482 --- /dev/null +++ b/application/sissi-modules/debra/src/main/resources/migrations/1.4.29/tables/tables.xml @@ -0,0 +1,10 @@ + + + + \ No newline at end of file diff --git a/application/sissi-modules/debra/src/main/resources/migrations/debra-changeLog.xml b/application/sissi-modules/debra/src/main/resources/migrations/debra-changeLog.xml index 22ff272a..ba8c6c95 100644 --- a/application/sissi-modules/debra/src/main/resources/migrations/debra-changeLog.xml +++ b/application/sissi-modules/debra/src/main/resources/migrations/debra-changeLog.xml @@ -8,4 +8,5 @@ http://www.liquibase.org/xml/ns/pro dbchangelog.xsd" > + \ No newline at end of file diff --git a/deployment/image-packaging/src/main/docker/rest-api/requirements.txt b/deployment/image-packaging/src/main/docker/rest-api/requirements.txt index 3103c8e4..3d3e1406 100644 --- a/deployment/image-packaging/src/main/docker/rest-api/requirements.txt +++ b/deployment/image-packaging/src/main/docker/rest-api/requirements.txt @@ -14,3 +14,4 @@ urllib3==2.0.7 waitress==2.1.2 Werkzeug==3.0.1 zipp==3.17.0 +pytz==2023.3.post1 \ No newline at end of file diff --git a/pom.xml b/pom.xml index 8d7808ee..24448100 100644 --- a/pom.xml +++ b/pom.xml @@ -20,8 +20,8 @@ 17 - 1.5.13 - 1.4.24 + 1.5.14 + 1.4.25 4.9.0 3.5.0 diff --git a/python/modules/rest-api/debra.py b/python/modules/rest-api/debra.py index e800642a..e4e1539e 100644 --- a/python/modules/rest-api/debra.py +++ b/python/modules/rest-api/debra.py @@ -7,12 +7,16 @@ import logging import uuid from __main__ import app from utils import serve_pil_image +from datetime import timezone, datetime +import pytz + sissi_host = os.getenv('SISSI_HOST') sissi_port = os.getenv('SISSI_PORT') latest_donations_url = f'http://{sissi_host}:{sissi_port}/debra/latestDonations' highest_donations_url = f'http://{sissi_host}:{sissi_port}/debra/highestDonations' campaign_info_url = f'http://{sissi_host}:{sissi_port}/debra/campaignInfo' +endless_stream_info_url = f'http://{sissi_host}:{sissi_port}/stream/endlessStream' class DonationImageGenerationParameters: @@ -129,6 +133,60 @@ def total_donations_image(): return serve_pil_image(img) +@app.route('/debra/image/endlessStream/end') +def endless_stream_image(): + stream_id = int(request.args.get('streamId', type=int)) + endless_stream_info = json.loads(requests.get(f'{endless_stream_info_url}/{stream_id}').text) + logging.info(f'rendering endless stream end image') + parameters = parse_image_parameters() + if not parameters.validated: + return parameters.validation_message, 400 + img = Image.new('RGBA', (parameters.canvas_width, parameters.canvas_height), (255, 0, 0, 0)) + d1 = ImageDraw.Draw(img) + font = ImageFont.truetype(f'{parameters.font_name}.ttf', parameters.font_size) + end_time = datetime.strptime(endless_stream_info['endDate'], "%Y-%m-%dT%H:%M:%S%z") + tz = pytz.timezone('Europe/Vienna') + end_time_formatted = end_time.astimezone(tz).strftime('%d.%m %H:%M') + d1.text((0, 0), f"{end_time_formatted}", fill=parameters.color, font=font) + return serve_pil_image(img) + + +@app.route('/debra/image/endlessStream/end/html') +def endless_stream_html(): + refresh_interval = int(request.args.get('refreshInterval', 30, type=int)) + random_bit = str(uuid.uuid4()) + parameters_query = request.query_string.decode() + return render_template('image_refresh_wrapper.html', imagePath=f'/debra/image/endlessStream/end?{parameters_query}&{random_bit}', refreshInterval=refresh_interval) + + +@app.route('/debra/image/endlessStream/remaining') +def endless_stream_remaining(): + stream_id = int(request.args.get('streamId', type=int)) + endless_stream_info = json.loads(requests.get(f'{endless_stream_info_url}/{stream_id}').text) + logging.info(f'rendering endless stream remaining image') + parameters = parse_image_parameters() + if not parameters.validated: + return parameters.validation_message, 400 + img = Image.new('RGBA', (parameters.canvas_width, parameters.canvas_height), (255, 0, 0, 0)) + d1 = ImageDraw.Draw(img) + font = ImageFont.truetype(f'{parameters.font_name}.ttf', parameters.font_size) + end_time = datetime.strptime(endless_stream_info['endDate'], "%Y-%m-%dT%H:%M:%S%z").replace(tzinfo=pytz.utc) + current_time = datetime.now(timezone.utc) + remaining_time = end_time - current_time + total_seconds = remaining_time.total_seconds() + remaining_time_formatted = f'{int(total_seconds // 3600):02d}:{int((total_seconds % 3600) // 60):02d}:{int(total_seconds % 60):02d}' + d1.text((0, 0), f"{remaining_time_formatted}", fill=parameters.color, font=font) + return serve_pil_image(img) + + +@app.route('/debra/image/endlessStream/remaining/html') +def endless_stream_remaining_html(): + refresh_interval = int(request.args.get('refreshInterval', 30, type=int)) + random_bit = str(uuid.uuid4()) + parameters_query = request.query_string.decode() + return render_template('image_refresh_wrapper.html', imagePath=f'/debra/image/endlessStream/remaining?{parameters_query}&{random_bit}', refreshInterval=refresh_interval) + + @app.route('/debra/image/latestDonations') def latest_donation_image(): donation_stats = json.loads(requests.get(latest_donations_url).text) diff --git a/templates/sissi-translations/module-translations/debra-translations/src/main/resources/en_US/config/feature_setup_config_debraDonationApiFetchSize_en_US.ftl b/templates/sissi-translations/module-translations/debra-translations/src/main/resources/en_US/config/feature_setup_config_debraDonationApiFetchSize_en_US.ftl new file mode 100644 index 00000000..e4e68d3c --- /dev/null +++ b/templates/sissi-translations/module-translations/debra-translations/src/main/resources/en_US/config/feature_setup_config_debraDonationApiFetchSize_en_US.ftl @@ -0,0 +1 @@ +The amount of records fetched from the Debra api. Default: ${defaultValue} \ No newline at end of file diff --git a/templates/sissi-translations/module-translations/debra-translations/src/main/resources/en_US/config/feature_setup_config_debraDonationNotificationDelayMillis_en_US.ftl b/templates/sissi-translations/module-translations/debra-translations/src/main/resources/en_US/config/feature_setup_config_debraDonationNotificationDelayMillis_en_US.ftl new file mode 100644 index 00000000..860690c2 --- /dev/null +++ b/templates/sissi-translations/module-translations/debra-translations/src/main/resources/en_US/config/feature_setup_config_debraDonationNotificationDelayMillis_en_US.ftl @@ -0,0 +1 @@ +The amount of time (in milliseconds) after which the donation notification should be sent. Default: ${defaultValue} \ No newline at end of file diff --git a/templates/sissi-translations/module-translations/debra-translations/src/main/resources/en_US/config/feature_setup_config_endlessStreamMinuteRate_en_US.ftl b/templates/sissi-translations/module-translations/debra-translations/src/main/resources/en_US/config/feature_setup_config_endlessStreamMinuteRate_en_US.ftl new file mode 100644 index 00000000..7cb0f560 --- /dev/null +++ b/templates/sissi-translations/module-translations/debra-translations/src/main/resources/en_US/config/feature_setup_config_endlessStreamMinuteRate_en_US.ftl @@ -0,0 +1 @@ +The amount of minutes per euro donation. Default: ${defaultValue} \ No newline at end of file