Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 20 additions & 10 deletions src/main/java/cc/wordview/api/controller/HomeController.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,15 @@
package cc.wordview.api.controller;

import cc.wordview.api.Application;
import cc.wordview.api.exception.NoSuchEntryException;
import cc.wordview.api.runtime.ResourceResolver;
import cc.wordview.api.runtime.cache.FeedCache;
import cc.wordview.gengolex.Language;
import cc.wordview.gengolex.LanguageNotFoundException;
import jakarta.annotation.PostConstruct;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.*;

import java.io.FileInputStream;
import java.io.IOException;
Expand All @@ -38,15 +40,23 @@
@RequestMapping(path = Application.API_PATH + "/home")
public class HomeController {
@Autowired
private ResourceResolver resourceResolver;
private FeedCache cache;

@GetMapping(produces = "application/json;charset=utf-8")
public ResponseEntity<?> getHome() throws IOException {
try (InputStream homeFile = new FileInputStream(resourceResolver.getOthersPath() + "/home.json")) {
String text = new String(homeFile.readAllBytes(), StandardCharsets.UTF_8)
.replace("\n", "");
public ResponseEntity<?> getHome(@RequestParam String learnLang) throws LanguageNotFoundException, NoSuchEntryException {
// Make gengolex itself check if the tag is valid
Language.Companion.byTag(learnLang);
var home = cache.get(learnLang);

return ok(text.trim());
if (home == null) {
throw new NoSuchEntryException("Unable to find a feed for this language");
}

return ok(home);
}

@PostConstruct
private void initializeFeeds() throws IOException {
cache.init();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import cc.wordview.wordfind.exception.LyricsNotFoundException;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

Expand All @@ -41,7 +42,10 @@ public ResponseEntity<ApiError> unauthorized(Exception e) {
return error(HttpStatus.UNAUTHORIZED, e);
}

@ExceptionHandler(RequestValidationException.class)
@ExceptionHandler({
RequestValidationException.class,
MissingServletRequestParameterException.class
})
public ResponseEntity<ApiError> badRequest(Exception e) {
return error(HttpStatus.BAD_REQUEST, e);
}
Expand Down
12 changes: 6 additions & 6 deletions src/main/java/cc/wordview/api/runtime/ResourceResolver.java
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,8 @@ public class ResourceResolver {
@Value("${wordview.translations_path}")
private String translationsPath;

@Value("${wordview.others_path}")
private String othersPath;
@Value("${wordview.feeds_path}")
private String feedsPath;

@PostConstruct
public void debugShowPaths() {
Expand All @@ -61,9 +61,9 @@ public void debugShowPaths() {
Lyrics Path: {}
Phrases Path: {}
Translations Path: {}
Others Path: {}
Feeds Path: {}
""",
dictionariesPath, imagesPath, lyricsPath, phrasesPath, translationsPath, othersPath);
dictionariesPath, imagesPath, lyricsPath, phrasesPath, translationsPath, feedsPath);
}

public String getDictionariesPath() throws IOException {
Expand All @@ -86,8 +86,8 @@ public String getTranslationsPath() throws IOException {
return resolvePathOrClasspath(translationsPath);
}

public String getOthersPath() throws IOException {
return resolvePathOrClasspath(othersPath);
public String getFeedsPath() throws IOException {
return resolvePathOrClasspath(feedsPath);
}

/**
Expand Down
67 changes: 67 additions & 0 deletions src/main/java/cc/wordview/api/runtime/cache/FeedCache.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/*
* Copyright (c) 2025 Arthur Araujo
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/

package cc.wordview.api.runtime.cache;

import cc.wordview.api.runtime.ResourceResolver;
import cc.wordview.gengolex.Language;
import cc.wordview.gengolex.LanguageNotFoundException;
import lombok.SneakyThrows;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;

@Component
public class FeedCache extends HashMapCacheManager<String> {
private static final Logger logger = LoggerFactory.getLogger(FeedCache.class);

@Autowired
private ResourceResolver resourceResolver;

// This won't throw language not found, just quiet the compiler here
@SneakyThrows(LanguageNotFoundException.class)
@Override
public void init() throws IOException {
String feedsPath = resourceResolver.getFeedsPath();

String[] langTag = {
Language.ENGLISH.getTag(),
Language.PORTUGUESE.getTag(),
Language.JAPANESE.getTag()
};

for (var tag : langTag) {
try (InputStream feedFile = new FileInputStream(feedsPath + "/" + tag + ".json")) {
String feedJson = new String(feedFile.readAllBytes(), StandardCharsets.UTF_8)
.replace("\n", "").trim();

map.put(tag, feedJson);
} catch (FileNotFoundException e) {
logger.warn("Feed is missing for language {}", Language.Companion.byTag(tag).name());
}
}

logger.info("Preloaded {} feeds", map.size());
}
}
2 changes: 1 addition & 1 deletion src/main/resources/application-dev.properties
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ wordview.images_path=classpath:/images/
wordview.lyrics_path=classpath:/lyrics/
wordview.phrases_path=classpath:/phrases/
wordview.translations_path=classpath:/translations/
wordview.feeds_path=classpath:/feeds/
spring.jackson.default-property-inclusion=non_null
wordview.others_path=classpath:/others/

server.compression.enabled=true
server.compression.mime-types=text/html,text/xml,text/plain,text/css,text/javascript,application/javascript,application/json
Expand Down
3 changes: 2 additions & 1 deletion src/main/resources/application-prod.properties
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,5 @@ spring.jpa.open-in-view=false
wordview.dictionaries_path=
wordview.images_path=
wordview.lyrics_path=
wordview.phrases_path=
wordview.phrases_path=
wordview.feeds_path=
Original file line number Diff line number Diff line change
Expand Up @@ -17,33 +17,12 @@
"cover": "https://i.ytimg.com/vi_webp/6gluNoLVKiQ/maxresdefault.webp",
"duration": 132
},
{
"id": "ZnUEeXpxBJ0",
"title": "Aquarela",
"artist": "Toquinho",
"cover": "https://i.ytimg.com/vi_webp/ZnUEeXpxBJ0/maxresdefault.webp",
"duration": 252
},
{
"id": "ZpT9VCUS54s",
"title": "彗星のパレード",
"artist": "まじ娘",
"cover": "https://i.ytimg.com/vi_webp/ZpT9VCUS54s/maxresdefault.webp",
"duration": 261
},
{
"id": "HCTunqv1Xt4",
"title": "When I'm Sixty Four",
"artist": "The Beatles",
"cover": "https://i.ytimg.com/vi_webp/HCTunqv1Xt4/maxresdefault.webp",
"duration": 158
},
{
"id": "9NPv4q57on8",
"title": "あの夢をなぞって",
"artist": "YOASOBI",
"cover": "https://i.ytimg.com/vi_webp/9NPv4q57on8/maxresdefault.webp",
"duration": 240
}
]
}
Expand Down
23 changes: 23 additions & 0 deletions src/main/resources/feeds/ja.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"categories": [
{
"id": "editors-pick",
"entries": [
{
"id": "ZpT9VCUS54s",
"title": "彗星のパレード",
"artist": "まじ娘",
"cover": "https://i.ytimg.com/vi_webp/ZpT9VCUS54s/maxresdefault.webp",
"duration": 261
},
{
"id": "9NPv4q57on8",
"title": "あの夢をなぞって",
"artist": "YOASOBI",
"cover": "https://i.ytimg.com/vi_webp/9NPv4q57on8/maxresdefault.webp",
"duration": 240
}
]
}
]
}
16 changes: 16 additions & 0 deletions src/main/resources/feeds/pt.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"categories": [
{
"id": "editors-pick",
"entries": [
{
"id": "ZnUEeXpxBJ0",
"title": "Aquarela",
"artist": "Toquinho",
"cover": "https://i.ytimg.com/vi_webp/ZnUEeXpxBJ0/maxresdefault.webp",
"duration": 252
}
]
}
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/*
* Copyright (c) 2025 Arthur Araujo
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/

package cc.wordview.api.test.api.controller;

import org.junit.jupiter.api.Test;

import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

public class HomeControllerTest extends ControllerTest {
@Test
void noLearnLangSpecified() throws Exception {
req.get("/home").andExpect(status().isBadRequest());
}

@Test
void english() throws Exception {
req.get("/home?learnLang=en")
.andExpect(status().isOk());
}

@Test
void portuguese() throws Exception {
req.get("/home?learnLang=pt")
.andExpect(status().isOk());
}

@Test
void japanese() throws Exception {
req.get("/home?learnLang=ja")
.andExpect(status().isOk());
}

@Test
void inexistentLang() throws Exception {
req.get("/home?learnLang=sql")
.andExpect(status().isNotFound());
}
}
Loading