aboutsummaryrefslogtreecommitdiff
path: root/src/main/kotlin/io/jamesbarnett/redditlite
diff options
context:
space:
mode:
authorJames Barnett <noreply@jamesbarnett.xyz>2020-04-10 13:34:23 +0100
committerJames Barnett <noreply@jamesbarnett.xyz>2020-04-10 13:34:23 +0100
commit78400d587ea5367d3424333913ff4f94ca3f1908 (patch)
tree2cf5f5ff8069740b0b7dd00853a4ea8c13d6e05c /src/main/kotlin/io/jamesbarnett/redditlite
parentd5a608143ad2250d8b35b9e4a488d39faaf5a021 (diff)
downloadreddit-lite-78400d587ea5367d3424333913ff4f94ca3f1908.tar.xz
reddit-lite-78400d587ea5367d3424333913ff4f94ca3f1908.zip
Reimplement in Kotlin
Diffstat (limited to 'src/main/kotlin/io/jamesbarnett/redditlite')
-rw-r--r--src/main/kotlin/io/jamesbarnett/redditlite/RedditLiteApplication.kt11
-rw-r--r--src/main/kotlin/io/jamesbarnett/redditlite/controller/LandingPageController.kt13
-rw-r--r--src/main/kotlin/io/jamesbarnett/redditlite/controller/SubredditController.kt37
-rw-r--r--src/main/kotlin/io/jamesbarnett/redditlite/model/Comment.kt44
-rw-r--r--src/main/kotlin/io/jamesbarnett/redditlite/model/Post.kt41
-rw-r--r--src/main/kotlin/io/jamesbarnett/redditlite/model/PostDetail.kt9
-rw-r--r--src/main/kotlin/io/jamesbarnett/redditlite/service/SubredditService.kt62
7 files changed, 217 insertions, 0 deletions
diff --git a/src/main/kotlin/io/jamesbarnett/redditlite/RedditLiteApplication.kt b/src/main/kotlin/io/jamesbarnett/redditlite/RedditLiteApplication.kt
new file mode 100644
index 0000000..b6d7f4c
--- /dev/null
+++ b/src/main/kotlin/io/jamesbarnett/redditlite/RedditLiteApplication.kt
@@ -0,0 +1,11 @@
+package io.jamesbarnett.redditlite
+
+import org.springframework.boot.autoconfigure.SpringBootApplication
+import org.springframework.boot.runApplication
+
+@SpringBootApplication
+class RedditLiteApplication
+
+fun main(args: Array<String>) {
+ runApplication<RedditLiteApplication>(*args)
+}
diff --git a/src/main/kotlin/io/jamesbarnett/redditlite/controller/LandingPageController.kt b/src/main/kotlin/io/jamesbarnett/redditlite/controller/LandingPageController.kt
new file mode 100644
index 0000000..c2881bd
--- /dev/null
+++ b/src/main/kotlin/io/jamesbarnett/redditlite/controller/LandingPageController.kt
@@ -0,0 +1,13 @@
+package io.jamesbarnett.redditlite.controller
+
+import org.springframework.stereotype.Controller
+import org.springframework.web.bind.annotation.GetMapping
+import org.springframework.web.servlet.ModelAndView
+
+@Controller
+class LandingPageController {
+ @GetMapping("/")
+ fun renderLandingPage() : ModelAndView {
+ return ModelAndView("landing")
+ }
+} \ No newline at end of file
diff --git a/src/main/kotlin/io/jamesbarnett/redditlite/controller/SubredditController.kt b/src/main/kotlin/io/jamesbarnett/redditlite/controller/SubredditController.kt
new file mode 100644
index 0000000..182d269
--- /dev/null
+++ b/src/main/kotlin/io/jamesbarnett/redditlite/controller/SubredditController.kt
@@ -0,0 +1,37 @@
+package io.jamesbarnett.redditlite.controller
+
+import io.jamesbarnett.redditlite.service.SubredditService
+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.RequestMapping
+import org.springframework.web.bind.annotation.RequestParam
+import org.springframework.web.servlet.ModelAndView
+
+@Controller
+@RequestMapping("/r")
+class SubredditController (val subredditService: SubredditService) {
+
+ @GetMapping("/{subreddit}")
+ fun renderPosts(@PathVariable subreddit: String,
+ @RequestParam("after", required = false) postAfterId: String?,
+ @RequestParam("showThumbs", defaultValue = "true") showThumbs: Boolean
+ ): ModelAndView {
+ val posts = subredditService.getPosts(subreddit, postAfterId)
+ return ModelAndView("posts")
+ .addObject("showThumbs", showThumbs)
+ .addObject("postAfterId", postAfterId)
+ .addObject("subreddit", subreddit)
+ .addObject("posts", posts)
+ .addObject("nextPostId", posts.last().name)
+ }
+
+ @GetMapping("/{subreddit}/comments/{postId}")
+ fun renderPostDetail(@PathVariable subreddit: String, @PathVariable postId: String): ModelAndView {
+ val postDetail = subredditService.getPostDetail(subreddit, postId)
+ return ModelAndView("postDetail")
+ .addObject("subreddit", subreddit)
+ .addObject("postDetail", postDetail)
+ }
+}
+
diff --git a/src/main/kotlin/io/jamesbarnett/redditlite/model/Comment.kt b/src/main/kotlin/io/jamesbarnett/redditlite/model/Comment.kt
new file mode 100644
index 0000000..219270d
--- /dev/null
+++ b/src/main/kotlin/io/jamesbarnett/redditlite/model/Comment.kt
@@ -0,0 +1,44 @@
+package io.jamesbarnett.redditlite.model
+
+import com.fasterxml.jackson.annotation.JsonIgnore
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties
+import com.fasterxml.jackson.annotation.JsonProperty
+import com.fasterxml.jackson.databind.JsonNode
+import com.fasterxml.jackson.databind.ObjectMapper
+import com.github.marlonlom.utilities.timeago.TimeAgo
+import org.apache.commons.text.StringEscapeUtils
+import java.time.Instant
+
+@JsonIgnoreProperties(ignoreUnknown = true)
+data class Comment(
+ val author: String?,
+ val score: Int,
+ @JsonProperty("is_submitter")
+ val isSubmitter: Boolean,
+ @JsonProperty("created_utc")
+ val createdDate: Instant?,
+ @JsonProperty("author_flair_text")
+ val flairText: String?,
+ val body: String?,
+ @JsonProperty("body_html")
+ val bodyHtml: String?,
+ val depth: Int,
+ @JsonIgnore
+ val replies: MutableList<Comment> = mutableListOf()
+) {
+ companion object {
+ operator fun invoke(jsonNode: JsonNode, objectMapper: ObjectMapper): Comment {
+ val topLevelComment = objectMapper.treeToValue(jsonNode, Comment::class.java)
+ jsonNode
+ .path("replies")
+ .path("data")
+ .path("children")
+ .findValues("data")
+ .forEach {topLevelComment.replies.add(invoke(it, objectMapper))}
+ return topLevelComment
+ }
+ }
+ val isChild get() = depth > 0
+ val relativeCreatedDate: String? get() = createdDate?.let { TimeAgo.using(createdDate.toEpochMilli()) }
+ val bodyHtmlUnescaped: String? get() = StringEscapeUtils.unescapeHtml4(bodyHtml)
+} \ No newline at end of file
diff --git a/src/main/kotlin/io/jamesbarnett/redditlite/model/Post.kt b/src/main/kotlin/io/jamesbarnett/redditlite/model/Post.kt
new file mode 100644
index 0000000..4cd723d
--- /dev/null
+++ b/src/main/kotlin/io/jamesbarnett/redditlite/model/Post.kt
@@ -0,0 +1,41 @@
+package io.jamesbarnett.redditlite.model
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties
+import com.fasterxml.jackson.annotation.JsonProperty
+import com.github.marlonlom.utilities.timeago.TimeAgo
+import java.time.Instant
+
+@JsonIgnoreProperties(ignoreUnknown = true)
+data class Post (
+ val id: String,
+ val name: String,
+ val title: String,
+ val domain: String,
+ val score: Int,
+ val author: String,
+ private val thumbnail: String?,
+ val ups: Int,
+ @JsonProperty("num_comments")
+ val commentCount: Int,
+ val url: String,
+ @JsonProperty("created_utc")
+ val createdDate: Instant,
+ @JsonProperty("is_self")
+ val isSelfPost: Boolean,
+ @JsonProperty("selftext_html")
+ val selftextHtml: String?,
+ val subreddit: String
+) {
+ val primaryLink: String get() {
+ return if (isSelfPost) {
+ "/r/${subreddit}/comments/${id}"
+ } else {
+ url
+ }
+ }
+ val subredditPath get() = "/r/${subreddit}"
+ val relativeCreatedDate: String get() = TimeAgo.using(createdDate.toEpochMilli())
+ val thumbnailUrl get() = thumbnail?.let { if(thumbnail.startsWith("http")) thumbnail else null }
+//val thumbnail get() = if(thumbnailRaw?.startsWith("http") == true) thumbnailRaw else null
+
+} \ No newline at end of file
diff --git a/src/main/kotlin/io/jamesbarnett/redditlite/model/PostDetail.kt b/src/main/kotlin/io/jamesbarnett/redditlite/model/PostDetail.kt
new file mode 100644
index 0000000..c5b58f0
--- /dev/null
+++ b/src/main/kotlin/io/jamesbarnett/redditlite/model/PostDetail.kt
@@ -0,0 +1,9 @@
+package io.jamesbarnett.redditlite.model
+
+import org.apache.commons.text.StringEscapeUtils
+
+data class PostDetail(val post: Post, val comments: List<Comment>) {
+ val isSelfPost get() = post.isSelfPost
+ val selftextHtmlUnescaped: String? get() = StringEscapeUtils.unescapeHtml4(post.selftextHtml)
+ val commentCount get() = post.commentCount
+}
diff --git a/src/main/kotlin/io/jamesbarnett/redditlite/service/SubredditService.kt b/src/main/kotlin/io/jamesbarnett/redditlite/service/SubredditService.kt
new file mode 100644
index 0000000..8c01c93
--- /dev/null
+++ b/src/main/kotlin/io/jamesbarnett/redditlite/service/SubredditService.kt
@@ -0,0 +1,62 @@
+package io.jamesbarnett.redditlite.service
+
+import com.fasterxml.jackson.databind.ObjectMapper
+import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule
+import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
+import com.fasterxml.jackson.module.kotlin.treeToValue
+import io.jamesbarnett.redditlite.model.Comment
+import io.jamesbarnett.redditlite.model.Post
+import io.jamesbarnett.redditlite.model.PostDetail
+import org.springframework.boot.web.client.RestTemplateBuilder
+import org.springframework.stereotype.Service
+import org.springframework.web.client.RestTemplate
+import java.time.Duration
+
+const val REDDIT_API_ROOT_URL = "https://reddit.com/r/"
+
+@Service
+class SubredditService (restTemplateBuilder: RestTemplateBuilder) {
+
+ val objectMapper: ObjectMapper = jacksonObjectMapper()
+ .registerModule(JavaTimeModule())
+
+ val restTemplate: RestTemplate = restTemplateBuilder
+ .setConnectTimeout(Duration.ofSeconds(5))
+ .setReadTimeout(Duration.ofSeconds(10))
+ // Custom UA required to prevent rate limiting: https://github.com/reddit-archive/reddit/wiki/API#rules
+ .defaultHeader("User-Agent", "reddit-lite")
+ .build()
+
+ fun getPosts(subreddit: String, postsAfterId: String?): List<Post> {
+ val jsonResponse = restTemplate.getForObject("${REDDIT_API_ROOT_URL}${subreddit}/.json${if (postsAfterId != null) "?after=${postsAfterId}" else ""}", String::class.java)
+ return objectMapper.readTree(jsonResponse)
+ .path("data")
+ .path("children")
+ .findValues("data")
+ .map { objectMapper.treeToValue(it, Post::class.java) }
+ }
+
+ fun getPostDetail(subreddit: String, postId: String): PostDetail {
+ val jsonResponse = restTemplate.getForObject("${REDDIT_API_ROOT_URL}${subreddit}/comments/${postId}/.json", String::class.java)
+ val rootNode = objectMapper.readTree(jsonResponse)
+
+ val post = objectMapper.treeToValue(
+ rootNode
+ .path(0)
+ .path("data")
+ .path("children")
+ .path(0)
+ .path("data"),
+ Post::class.java)
+
+ val comments = rootNode
+ .path(1)
+ .path("data")
+ .path("children")
+ .findValues("data")
+ .map { Comment(it, objectMapper) }
+
+ return PostDetail(post, comments)
+ }
+
+} \ No newline at end of file