Skip to content

Commit 3129678

Browse files
authored
Merge pull request #15 from garamb1/football-data-org
Add football data support
2 parents a65dc69 + 974c519 commit 3129678

24 files changed

+2889
-16
lines changed

Diff for: README.md

+17
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ RetroSearch is a Spring Web Application that presents very simple HTML pages whi
1717
It provides the ability to search the Web using DuckDuckGo with a custom scraper that loads the first page of results and allows you to browse pages in plain text.
1818
You can deploy it on your local network and access it from your old computer!
1919

20+
## News and sports APIs support
21+
2022
### Enabling the News API
2123

2224
RetroSearch can fetch news articles by using the GNews API, to allow this, add the environment variables as follows when running the Docker image:
@@ -25,8 +27,23 @@ RetroSearch can fetch news articles by using the GNews API, to allow this, add t
2527
docker run -e NEWS_ACTIVE=true -e NEWS_API_KEY={your GNews API Key} -d -p80:8080 garambo/retrosearch:{Retro Search Version} --restart unless-stopped
2628
```
2729

30+
### Enabling the football-data.org API
31+
32+
RetroSearch can fetch the latest football scores using the football-data.org API, to allow this, add the environment variables as follows when running the Docker image:
33+
34+
```
35+
docker run -e FOOTBALL_API_ACTIVE=true -e FOOTBALL_API_KEY={your football-data.org API Key} -d -p80:8080 garambo/retrosearch:{Retro Search Version} --restart unless-stopped
36+
```
37+
38+
### Enabling both
39+
40+
```
41+
docker run -e NEWS_ACTIVE=true -e NEWS_API_KEY={your GNews API Key} FOOTBALL_API_ACTIVE=true -e FOOTBALL_API_KEY={your football-data.org API Key} -d -p80:8080 garambo/retrosearch:{Retro Search Version} --restart unless-stopped
42+
```
43+
2844
If running locally, just replace the property values in `application.properties` or create a new Spring run configuration.
2945

46+
3047
### WIP
3148
Currently in progress:
3249
- Improve the parsing abilities for the browsing functionality

Diff for: VERSION

+1-1
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
0.5.2-testing
1+
0.6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package it.garambo.retrosearch.controller;
2+
3+
import it.garambo.retrosearch.sports.football.repository.FootballRepository;
4+
import org.springframework.beans.factory.annotation.Autowired;
5+
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
6+
import org.springframework.stereotype.Controller;
7+
import org.springframework.ui.Model;
8+
import org.springframework.web.bind.annotation.GetMapping;
9+
10+
@Controller
11+
@ConditionalOnBean(FootballRepository.class)
12+
public class FootballController {
13+
14+
@Autowired private FootballRepository footballRepository;
15+
16+
@GetMapping(path = {"/football", "/sports/football"})
17+
public String football(Model model) {
18+
model.addAttribute("updatedAt", footballRepository.getUpdatedAt());
19+
model.addAttribute("results", footballRepository.getAllMatches());
20+
return "football";
21+
}
22+
}

Diff for: src/main/java/it/garambo/retrosearch/http/HttpService.java

+7
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,21 @@
33
import java.io.IOException;
44
import java.net.URI;
55
import java.net.URISyntaxException;
6+
import java.util.List;
67
import java.util.Map;
8+
import org.apache.http.Header;
79

810
public interface HttpService {
911

1012
String get(URI uri) throws IOException, URISyntaxException;
1113

1214
String get(URI uri, Map<String, String> params) throws IOException, URISyntaxException;
1315

16+
String get(URI uri, List<Header> additionalHeaders) throws IOException, URISyntaxException;
17+
18+
String get(URI uri, Map<String, String> params, List<Header> additionalHeaders)
19+
throws IOException, URISyntaxException;
20+
1421
String post(URI uri, String body) throws IOException;
1522

1623
String post(URI uri, Map<String, String> formData) throws IOException, URISyntaxException;

Diff for: src/main/java/it/garambo/retrosearch/http/HttpServiceImpl.java

+29-8
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import java.io.IOException;
44
import java.net.URI;
55
import java.net.URISyntaxException;
6+
import java.util.ArrayList;
67
import java.util.Collections;
78
import java.util.List;
89
import java.util.Map;
@@ -27,12 +28,12 @@ public class HttpServiceImpl implements HttpService {
2728

2829
private final ResponseHandler<String> responseHandler;
2930

30-
private final Header[] defaultClientHeaders = {
31-
new BasicHeader("charset", "UTF-8"),
32-
new BasicHeader(
33-
"User-Agent",
34-
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36")
35-
};
31+
private final List<BasicHeader> defaultClientHeaders =
32+
List.of(
33+
new BasicHeader("charset", "UTF-8"),
34+
new BasicHeader(
35+
"User-Agent",
36+
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"));
3637

3738
public HttpServiceImpl(
3839
@Autowired HttpClientFactory clientFactory,
@@ -48,18 +49,38 @@ public String get(URI uri) throws IOException, URISyntaxException {
4849

4950
@Override
5051
public String get(URI uri, Map<String, String> params) throws IOException, URISyntaxException {
52+
return get(uri, params, Collections.emptyList());
53+
}
54+
55+
@Override
56+
public String get(URI uri, List<Header> additionalHeaders)
57+
throws IOException, URISyntaxException {
58+
return get(uri, Collections.emptyMap(), additionalHeaders);
59+
}
60+
61+
@Override
62+
public String get(URI uri, Map<String, String> params, List<Header> additionalHeaders)
63+
throws IOException, URISyntaxException {
5164
URIBuilder newUri = new URIBuilder(uri).setParameters(mapToNameValuePair(params));
5265
final HttpGet get = new HttpGet(newUri.build());
5366

54-
get.setHeaders(defaultClientHeaders);
67+
Header[] requestHeaders = defaultClientHeaders.toArray(new Header[0]);
68+
69+
if (!CollectionUtils.isEmpty(additionalHeaders)) {
70+
List<Header> newHeaders = new ArrayList<>(defaultClientHeaders);
71+
newHeaders.addAll(additionalHeaders);
72+
requestHeaders = newHeaders.toArray(new Header[0]);
73+
}
74+
75+
get.setHeaders(requestHeaders);
5576
return clientFactory.createHttpClient().execute(get, responseHandler);
5677
}
5778

5879
@Override
5980
public String post(URI uri, String body) throws IOException {
6081
final HttpPost post = new HttpPost(uri);
6182
post.setEntity(new StringEntity(body));
62-
post.setHeaders(defaultClientHeaders);
83+
post.setHeaders(defaultClientHeaders.toArray(new Header[0]));
6384
return clientFactory.createHttpClient().execute(post, responseHandler);
6485
}
6586

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package it.garambo.retrosearch.sports.football.client;
2+
3+
import com.fasterxml.jackson.databind.ObjectMapper;
4+
import it.garambo.retrosearch.http.HttpService;
5+
import it.garambo.retrosearch.sports.football.model.FootballDataResponse;
6+
import java.io.IOException;
7+
import java.net.URI;
8+
import java.net.URISyntaxException;
9+
import java.util.List;
10+
import org.apache.http.message.BasicHeader;
11+
import org.springframework.beans.factory.annotation.Autowired;
12+
import org.springframework.beans.factory.annotation.Value;
13+
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
14+
import org.springframework.stereotype.Component;
15+
16+
@Component
17+
@ConditionalOnProperty(value = "retrosearch.sports.football.enable", havingValue = "true")
18+
public class FootballDataOrgClient {
19+
20+
private final String API_URL = "https://api.football-data.org/v4/matches/";
21+
22+
@Value("${retrosearch.sports.football.api.key:}")
23+
private String apiKey;
24+
25+
@Autowired HttpService httpService;
26+
27+
public FootballDataResponse fetchFootballData() throws IOException, URISyntaxException {
28+
URI apiUri = new URI(API_URL);
29+
BasicHeader apiKeyHeader = new BasicHeader("X-Auth-Token", apiKey);
30+
31+
String response = httpService.get(apiUri, List.of(apiKeyHeader));
32+
return new ObjectMapper().readValue(response, FootballDataResponse.class);
33+
}
34+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package it.garambo.retrosearch.sports.football.model;
2+
3+
import it.garambo.retrosearch.sports.football.model.match.Match;
4+
import java.util.List;
5+
import java.util.Map;
6+
7+
public record FootballDataResponse(
8+
Map<String, String> filters, Map<String, String> resultSet, List<Match> matches) {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
package it.garambo.retrosearch.sports.football.model.match;
2+
3+
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
4+
5+
@JsonIgnoreProperties(ignoreUnknown = true)
6+
public record Area(int id, String name, String code) {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
package it.garambo.retrosearch.sports.football.model.match;
2+
3+
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
4+
5+
@JsonIgnoreProperties(ignoreUnknown = true)
6+
public record Competition(int id, String name, String code) {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
package it.garambo.retrosearch.sports.football.model.match;
2+
3+
public record HomeAwayScore(int home, int away) {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package it.garambo.retrosearch.sports.football.model.match;
2+
3+
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
4+
import it.garambo.retrosearch.sports.football.model.match.enums.Status;
5+
import java.util.Date;
6+
import org.jetbrains.annotations.NotNull;
7+
8+
@JsonIgnoreProperties(ignoreUnknown = true)
9+
public record Match(
10+
int id,
11+
Date utcDate,
12+
Date lastUpdated,
13+
Area area,
14+
Status status,
15+
Competition competition,
16+
Team homeTeam,
17+
Team awayTeam,
18+
Score score)
19+
implements Comparable<Match> {
20+
21+
@Override
22+
public int compareTo(@NotNull Match o) {
23+
return this.area.id()
24+
- o.area.id()
25+
+ this.competition.id()
26+
- o.competition.id()
27+
+ this.id
28+
- o.id
29+
+ this.status.ordinal()
30+
- this.status.ordinal();
31+
}
32+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
package it.garambo.retrosearch.sports.football.model.match;
2+
3+
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
4+
5+
@JsonIgnoreProperties(ignoreUnknown = true)
6+
public record Score(HomeAwayScore halfTime, HomeAwayScore fullTime) {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
package it.garambo.retrosearch.sports.football.model.match;
2+
3+
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
4+
5+
@JsonIgnoreProperties(ignoreUnknown = true)
6+
public record Team(int id, String name, String shortName) {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package it.garambo.retrosearch.sports.football.model.match.enums;
2+
3+
public enum Status {
4+
SCHEDULED("Scheduled"),
5+
TIMED("Timed"),
6+
IN_PLAY("In Play"),
7+
PAUSED("Paused"),
8+
FINISHED("Finished"),
9+
SUSPENDED("Suspended"),
10+
POSTPONED("Postponed"),
11+
CANCELLED("Canceled"),
12+
AWARDED("Awarded");
13+
14+
final String description;
15+
16+
private Status(String description) {
17+
this.description = description;
18+
}
19+
20+
public String getDescription() {
21+
return description;
22+
}
23+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package it.garambo.retrosearch.sports.football.repository;
2+
3+
import it.garambo.retrosearch.sports.football.model.match.Match;
4+
import java.util.Date;
5+
import java.util.List;
6+
import java.util.Map;
7+
import java.util.Set;
8+
9+
public interface FootballRepository {
10+
11+
Map<String, Set<Match>> getAllMatches();
12+
13+
Set<Match> getAllMatchesByArea(String areaName);
14+
15+
void updateAll(List<Match> newMatches);
16+
17+
Date getUpdatedAt();
18+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
package it.garambo.retrosearch.sports.football.repository;
2+
3+
import it.garambo.retrosearch.sports.football.model.match.Match;
4+
import java.util.*;
5+
import lombok.extern.slf4j.Slf4j;
6+
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
7+
import org.springframework.stereotype.Component;
8+
9+
@Slf4j
10+
@Component
11+
@ConditionalOnProperty(value = "retrosearch.sports.football.enable", havingValue = "true")
12+
public class InMemoryFootballRepository implements FootballRepository {
13+
14+
private Map<String, Set<Match>> matchesByArea;
15+
private Date updatedAt;
16+
17+
@Override
18+
public Map<String, Set<Match>> getAllMatches() {
19+
return matchesByArea;
20+
}
21+
22+
@Override
23+
public Set<Match> getAllMatchesByArea(String areaName) {
24+
return matchesByArea.get(areaName);
25+
}
26+
27+
@Override
28+
public void updateAll(List<Match> newMatches) {
29+
Map<String, Set<Match>> updatedMatches = new HashMap<>();
30+
newMatches.forEach(
31+
match -> {
32+
String areaName = match.area().name();
33+
updatedMatches.putIfAbsent(areaName, new HashSet<>());
34+
updatedMatches.get(areaName).add(match);
35+
});
36+
log.info("Football scores updated");
37+
matchesByArea = updatedMatches;
38+
updatedAt = new Date();
39+
}
40+
41+
@Override
42+
public Date getUpdatedAt() {
43+
return updatedAt;
44+
}
45+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package it.garambo.retrosearch.sports.football.scheduled;
2+
3+
import it.garambo.retrosearch.sports.football.client.FootballDataOrgClient;
4+
import it.garambo.retrosearch.sports.football.model.FootballDataResponse;
5+
import it.garambo.retrosearch.sports.football.repository.FootballRepository;
6+
import lombok.extern.slf4j.Slf4j;
7+
import org.springframework.beans.factory.annotation.Autowired;
8+
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
9+
import org.springframework.scheduling.annotation.Scheduled;
10+
import org.springframework.stereotype.Component;
11+
12+
@Slf4j
13+
@Component
14+
@ConditionalOnProperty(value = "retrosearch.sports.football.enable", havingValue = "true")
15+
public class FootballDataScheduledTask {
16+
17+
@Autowired private FootballDataOrgClient apiClient;
18+
19+
@Autowired private FootballRepository repository;
20+
21+
@Scheduled(fixedRate = 30 * 60 * 1000)
22+
private void updateFootballData() {
23+
try {
24+
log.info("Updating football result list...");
25+
FootballDataResponse footballData = apiClient.fetchFootballData();
26+
repository.updateAll(footballData.matches());
27+
} catch (Exception e) {
28+
log.error("Football result list update failed:", e);
29+
}
30+
}
31+
}

Diff for: src/main/resources/templates/error.html

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
<body>
77
<h1>Error - RetroSearch</h1>
88
<h2>Sorry, something went wrong :(</h2>
9-
<img th:src="@{img/error.gif}">
9+
<img th:src="@{/img/error.gif}">
1010
<br>
1111
<a href="/">Go Back</a>
1212
<br>

0 commit comments

Comments
 (0)