Skip to content

Commit

Permalink
Added image generation and s3 uploads
Browse files Browse the repository at this point in the history
  • Loading branch information
This-Is-Ko committed Oct 6, 2023
1 parent 8456a7c commit 06463bb
Show file tree
Hide file tree
Showing 18 changed files with 562 additions and 66 deletions.
52 changes: 52 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
# Football Updater

Check for player match performance data from their latest game and generate images which contain their statistic for the selected game.

These are uploaded to a S3 bucket and an email is sent to the configured email containing a generated post caption, S3 file links and Google Image search links for recent photos for the player.

The players and teams to check are stored in RDS MySQL database and configured with an external cron job to run at scheduled times.

### Build
To build jar use
Expand All @@ -23,6 +28,53 @@ Additional env params can be passed in with
--MAILER_TO_ADDRESS=****
--ENDPOINT_SECRET=****

### Configurable

The main configurable groupings are:

Sprint Datasource (Database)

SPRING_DATASOURCE_HOST
SPRING_DATASOURCE_NAME
SPRING_DATASOURCE_USERNAME
SPRING_DATASOURCE_PASSWORD

Endpoint secret

ENDPOINT_SECRET

Datasource (Stat sources)

DATASOURCE_PRIORITY

Post Version

IG_POST_VERSION

Stat Image generator

IMAGE_GENERATOR_ENABLED
IMAGE_GENERATOR_INPUT_PATH
IMAGE_GENERATOR_OUTPUT_PATH

Mailer

MAILER_IS_ENABLED
MAILER_SUBJECT
MAILER_FROM_NAME
MAILER_FROM_ADDRESS
MAILER_FROM_PASSWORD
MAILER_TO_NAME
MAILER_TO_ADDRESS
MAILER_IS_ATTACH_IMAGES

AWS S3 Uploads

AWS_S3_IS_ENABLED
AWS_ACCESS_KEY
AWS_SECRET_KEY
AWS_S3_BUCKET_NAME

### Local MYSQL

Use to login to local mysql
Expand Down
3 changes: 3 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ dependencies {
implementation 'ch.qos.logback:logback-classic:1.4.8'
implementation 'org.jsoup:jsoup:1.15.3'
implementation 'org.simplejavamail:simple-java-mail:8.1.3'

implementation 'com.amazonaws:aws-java-sdk-s3:1.12.562'

}

springBoot {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package com.ko.footballupdater.configuration;

import com.amazonaws.auth.AWSStaticCredentialsProvider;
import com.amazonaws.auth.BasicAWSCredentials;
import com.amazonaws.regions.Regions;
import com.amazonaws.services.s3.AmazonS3;
import com.amazonaws.services.s3.AmazonS3ClientBuilder;
import jakarta.validation.constraints.NotNull;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;

@Slf4j
@Getter
@Setter
@Component
@ConfigurationProperties(prefix = "aws.s3")
public class AmazonS3Properties {

@NotNull
private boolean enabled;

@NotNull
private String accessKey;

@NotNull
private String secretKey;

@NotNull
private String bucketName;

@Bean
public AmazonS3 s3Client() {
// Set up S3 client
log.info("Initialising S3 client");
Regions clientRegion = Regions.AP_SOUTHEAST_2;
BasicAWSCredentials credentials = new BasicAWSCredentials(accessKey, secretKey);
return AmazonS3ClientBuilder.standard()
.withRegion(clientRegion)
.withCredentials(new AWSStaticCredentialsProvider(credentials))
.build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package com.ko.footballupdater.configuration;

import jakarta.validation.constraints.NotNull;
import lombok.Getter;
import lombok.Setter;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

@Getter
@Setter
@Component
@ConfigurationProperties(prefix = "image.generator")
public class ImageGeneratorProperies {

@NotNull
private boolean enabled;

@NotNull
private String inputPath;

@NotNull
private String outputPath;

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package com.ko.footballupdater.configuration;

import jakarta.validation.constraints.NotNull;
import lombok.Getter;
import lombok.Setter;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

@Getter
@Setter
@Component
@ConfigurationProperties(prefix = "ig.post")
public class InstagramPostProperies {

@NotNull
private int version;

}
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,17 @@
@ConfigurationProperties(prefix = "mailer")
public class MailerProperties {

@NotNull
private boolean enabled;

@NotNull
private String subject;

private MailerFromProperties from;

private MailerToProperties to;

@NotNull
private boolean attachImages;

}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
Expand Down Expand Up @@ -38,17 +39,27 @@ public class PlayerController {
}

@GetMapping(path="/data/update")
public @ResponseBody ResponseEntity<UpdatePlayersResponse> dataUpdate() {
UpdatePlayersResponse response = new UpdatePlayersResponse();
public @ResponseBody ResponseEntity<UpdatePlayersResponse> dataUpdateForAllPlayers() {
try {
playerService.updateDataForAllPlayers(response);
return ResponseEntity.ok(response);
return ResponseEntity.ok(playerService.updateDataForAllPlayers());
} catch (Exception ex) {
throw new ResponseStatusException(
HttpStatus.BAD_REQUEST, "Updating players failed", ex);
}
}

@GetMapping(path="/data/update/{playerId}")
public @ResponseBody ResponseEntity<UpdatePlayersResponse> dataUpdateForPlayer(
@PathVariable("playerId") Integer playerId
) {
try {
return ResponseEntity.ok(playerService.updateDataForPlayer(playerId));
} catch (Exception ex) {
throw new ResponseStatusException(
HttpStatus.BAD_REQUEST, "Updating player failed", ex);
}
}

@PostMapping(path="/data-source/update")
public @ResponseBody DataSource updatePlayerDataSource(@RequestBody DataSource dataSource) {
try {
Expand Down
13 changes: 13 additions & 0 deletions src/main/java/com/ko/footballupdater/models/ImageStatEntry.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.ko.footballupdater.models;

import lombok.Getter;
import lombok.Setter;
import lombok.AllArgsConstructor;

@Getter
@Setter
@AllArgsConstructor
public class ImageStatEntry {
private String name;
private String value;
}
46 changes: 9 additions & 37 deletions src/main/java/com/ko/footballupdater/models/InstagramPost.java
Original file line number Diff line number Diff line change
@@ -1,50 +1,22 @@
package com.ko.footballupdater.models;

import com.ko.footballupdater.utils.PostHelper;
import lombok.Getter;
import lombok.Setter;

import java.util.ArrayList;
import java.util.List;

@Getter
@Setter
public class InstagramPost {

private Player player;
private PlayerMatchPerformanceStats playerMatchPerformanceStats;
private String caption;
private String imageSearchUrl;
private List<String> imagesFileNames = new ArrayList<>();
private List<String> imagesS3Urls = new ArrayList<>();

public InstagramPost(Player player, PlayerMatchPerformanceStats playerMatchPerformanceStats) {
this.player = player;
this.playerMatchPerformanceStats = playerMatchPerformanceStats;
this.caption = PostHelper.generatePostDefaultPlayerCaption(player, playerMatchPerformanceStats);
this.imageSearchUrl = PostHelper.generatePostImageSearchUrl(player, playerMatchPerformanceStats);
}

public Player getPlayer() {
return player;
}

public void setPlayer(Player player) {
this.player = player;
}

public PlayerMatchPerformanceStats getPlayerMatchPerformanceStats() {
return playerMatchPerformanceStats;
}

public void setPlayerMatchPerformanceStats(PlayerMatchPerformanceStats playerMatchPerformanceStats) {
this.playerMatchPerformanceStats = playerMatchPerformanceStats;
}

public String getCaption() {
return caption;
}

public void setCaption(String caption) {
this.caption = caption;
}

public String getImageSearchUrl() {
return imageSearchUrl;
}

public void setImageSearchUrl(String imageSearchUrl) {
this.imageSearchUrl = imageSearchUrl;
}
}
5 changes: 5 additions & 0 deletions src/main/java/com/ko/footballupdater/models/Match.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ public class Match {
private final String relevantTeam;

private final SimpleDateFormat formatter = new SimpleDateFormat("yyyy/MM/dd");
private final SimpleDateFormat formatterFileName = new SimpleDateFormat("yyyy_MM_dd");

public Match(String url, Date date, String homeTeamName, String awayTeamName, String relevantTeam) {
this.url = url;
Expand All @@ -27,4 +28,8 @@ public Match(String url, Date date, String homeTeamName, String awayTeamName, St
public String getDateAsFormattedString() {
return formatter.format(date);
}

public String getDateAsFormattedStringForFileName() {
return formatterFileName.format(date);
}
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
package com.ko.footballupdater.repositories;

import com.ko.footballupdater.models.Player;
import org.springframework.data.repository.CrudRepository;

import com.ko.footballupdater.models.Player;
import java.util.List;

public interface PlayerRepository extends CrudRepository<Player, Integer> {
Expand Down
60 changes: 60 additions & 0 deletions src/main/java/com/ko/footballupdater/services/AmazonS3Service.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package com.ko.footballupdater.services;

import com.amazonaws.AmazonServiceException;
import com.amazonaws.SdkClientException;
import com.amazonaws.services.s3.AmazonS3;
import com.amazonaws.services.s3.model.CannedAccessControlList;
import com.amazonaws.services.s3.model.PutObjectRequest;
import com.ko.footballupdater.configuration.AmazonS3Properties;
import com.ko.footballupdater.configuration.ImageGeneratorProperies;
import com.ko.footballupdater.models.InstagramPost;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.io.File;

@Slf4j
@Service
public class AmazonS3Service {

@Autowired
private ImageGeneratorProperies imageGeneratorProperies;

@Autowired
private AmazonS3Properties amazonS3Properties;

@Autowired
private AmazonS3 s3Client;

public void uploadtoS3(InstagramPost post) {
if (!amazonS3Properties.isEnabled()) {
return;
}
if (!post.getImagesFileNames().isEmpty()) {
try {
// Upload images and save urls
// Overwrites any file with the same name
for (String imageFileName : post.getImagesFileNames()) {
String filePath = imageGeneratorProperies.getOutputPath() + imageFileName;
PutObjectRequest request = new PutObjectRequest(amazonS3Properties.getBucketName(),imageFileName, new File(filePath))
.withCannedAcl(CannedAccessControlList.PublicRead);
s3Client.putObject(request);
String imageUrl = s3Client.getUrl(amazonS3Properties.getBucketName(), imageFileName).toString();
log.atInfo().setMessage(post.getPlayer().getName() + " - Successfully uploaded image " + imageFileName + " to S3 @ " + imageUrl).log();
post.getImagesS3Urls().add(imageUrl);
}
} catch (AmazonServiceException ex) {
// The call was transmitted successfully, but Amazon S3 couldn't process
// it, so it returned an error response.
log.warn(post.getPlayer().getName() + " - Error attempting to upload", ex);
} catch (SdkClientException ex) {
// Amazon S3 couldn't be contacted for a response, or the client
// couldn't parse the response from Amazon S3.
log.warn(post.getPlayer().getName() + " - Error attempting to upload", ex);
}
} else {
log.atInfo().setMessage(post.getPlayer().getName() + " - No images to upload").log();
}
}
}
Loading

0 comments on commit 06463bb

Please sign in to comment.