From a0f044786fe0fda1d964aa6e45b06822e247fd9f Mon Sep 17 00:00:00 2001 From: Harsh Shandilya Date: Thu, 5 Jun 2025 22:28:23 +0530 Subject: [PATCH] feat: add get tags endpoint --- .../dev/msfjarvis/claw/api/LobstersApi.kt | 3 +++ .../kotlin/dev/msfjarvis/claw/api/ApiTest.kt | 10 ++++++++ .../dev/msfjarvis/claw/api/ApiWrapper.kt | 4 ++++ api/src/test/resources/tags.json | 1 + .../kotlin/dev/msfjarvis/claw/model/Tag.kt | 23 +++++++++++++++++++ 5 files changed, 41 insertions(+) create mode 100644 api/src/test/resources/tags.json create mode 100644 model/src/main/kotlin/dev/msfjarvis/claw/model/Tag.kt diff --git a/api/src/main/kotlin/dev/msfjarvis/claw/api/LobstersApi.kt b/api/src/main/kotlin/dev/msfjarvis/claw/api/LobstersApi.kt index aed010b1..761b71ce 100644 --- a/api/src/main/kotlin/dev/msfjarvis/claw/api/LobstersApi.kt +++ b/api/src/main/kotlin/dev/msfjarvis/claw/api/LobstersApi.kt @@ -9,6 +9,7 @@ package dev.msfjarvis.claw.api import com.slack.eithernet.ApiResult import dev.msfjarvis.claw.model.LobstersPost import dev.msfjarvis.claw.model.LobstersPostDetails +import dev.msfjarvis.claw.model.Tag import dev.msfjarvis.claw.model.User import retrofit2.http.GET import retrofit2.http.Path @@ -30,6 +31,8 @@ interface LobstersApi { @GET("/") suspend fun getCSRFToken(): ApiResult + @GET("tags.json") suspend fun getTags(): ApiResult, Unit> + companion object { const val BASE_URL = "https://lobste.rs" } diff --git a/api/src/test/kotlin/dev/msfjarvis/claw/api/ApiTest.kt b/api/src/test/kotlin/dev/msfjarvis/claw/api/ApiTest.kt index 7a3f22fb..9b0c808c 100644 --- a/api/src/test/kotlin/dev/msfjarvis/claw/api/ApiTest.kt +++ b/api/src/test/kotlin/dev/msfjarvis/claw/api/ApiTest.kt @@ -11,6 +11,7 @@ import com.slack.eithernet.ApiResult.Success import com.slack.eithernet.test.newEitherNetController import dev.msfjarvis.claw.model.LobstersPost import dev.msfjarvis.claw.model.LobstersPostDetails +import dev.msfjarvis.claw.model.Tag import dev.msfjarvis.claw.model.User import dev.msfjarvis.claw.util.TestUtils.assertIs import kotlinx.coroutines.test.runTest @@ -59,4 +60,13 @@ class ApiTest { "RPe1m3JaXAq0ti7t15hNixpLvmgJ793Ti6y4fOpxtfenyXQK3WU19m7MJb8RGoIf-tC3C9Ilwb8pv0WJUWLbNA" ) } + + @Test + fun `retrieve tags`() = runTest { + val tags = api.getTags() + assertIs>>(tags) + assertThat(tags.value).isNotEmpty() + assertThat(tags.value.first().tag).isEqualTo("ruby") + assertThat(tags.value.first().description).isEqualTo("Ruby programming") + } } diff --git a/api/src/test/kotlin/dev/msfjarvis/claw/api/ApiWrapper.kt b/api/src/test/kotlin/dev/msfjarvis/claw/api/ApiWrapper.kt index 923a774d..07c90122 100644 --- a/api/src/test/kotlin/dev/msfjarvis/claw/api/ApiWrapper.kt +++ b/api/src/test/kotlin/dev/msfjarvis/claw/api/ApiWrapper.kt @@ -12,6 +12,7 @@ import com.slack.eithernet.test.enqueue import dev.msfjarvis.claw.api.converters.CSRFTokenConverter import dev.msfjarvis.claw.model.LobstersPost import dev.msfjarvis.claw.model.LobstersPostDetails +import dev.msfjarvis.claw.model.Tag import dev.msfjarvis.claw.model.User import dev.msfjarvis.claw.util.TestUtils.getResource import kotlinx.serialization.ExperimentalSerializationApi @@ -31,6 +32,8 @@ class ApiWrapper(controller: EitherNetController) { json.decodeFromString(getResource("post_details_tdfoqh.json")) private val user: User = json.decodeFromString(getResource("msfjarvis.json")) + private val tags: List = json.decodeFromString(getResource("tags.json")) + val api = controller.api init { @@ -38,6 +41,7 @@ class ApiWrapper(controller: EitherNetController) { controller.enqueue(LobstersApi::getHottestPosts) { success(hottest) } controller.enqueue(LobstersApi::getPostDetails) { success(postDetails) } controller.enqueue(LobstersApi::getUser) { success(user) } + controller.enqueue(LobstersApi::getTags) { success(tags) } controller.enqueue(LobstersApi::getCSRFToken) { success( CSRFTokenConverter.convert( diff --git a/api/src/test/resources/tags.json b/api/src/test/resources/tags.json new file mode 100644 index 00000000..732cb643 --- /dev/null +++ b/api/src/test/resources/tags.json @@ -0,0 +1 @@ +[{"tag":"ruby","description":"Ruby programming","privileged":false,"is_media":false,"active":true,"hotness_mod":0.0,"category":"languages"},{"tag":"event","description":"Events, conferences, and meetups","privileged":false,"is_media":false,"active":true,"hotness_mod":0.0,"category":"genre"},{"tag":"person","description":"Stories about particular persons","privileged":false,"is_media":false,"active":true,"hotness_mod":0.0,"category":"culture"},{"tag":"meta","description":"Lobsters-related bikeshedding - report bugs at https://github.com/lobsters/lobsters","privileged":false,"is_media":false,"active":true,"hotness_mod":-0.25,"category":"lobsters"},{"tag":"php","description":"PHP programming","privileged":false,"is_media":false,"active":true,"hotness_mod":0.0,"category":"languages"},{"tag":"python","description":"Python programming","privileged":false,"is_media":false,"active":true,"hotness_mod":0.0,"category":"languages"},{"tag":"news","description":"General news and current events","privileged":false,"is_media":false,"active":false,"hotness_mod":0.0,"category":"genre"},{"tag":"design","description":"Visual design","privileged":false,"is_media":false,"active":true,"hotness_mod":0.0,"category":"interaction"},{"tag":"science","description":"It's \"Science\"","privileged":false,"is_media":false,"active":true,"hotness_mod":0.0,"category":"field"},{"tag":"video","description":"Link to a video","privileged":false,"is_media":true,"active":true,"hotness_mod":0.0,"category":"format"},{"tag":"pdf","description":"Link to a PDF document","privileged":false,"is_media":true,"active":true,"hotness_mod":0.0,"category":"format"},{"tag":"lua","description":"Lua programming","privileged":false,"is_media":false,"active":true,"hotness_mod":0.0,"category":"languages"},{"tag":"lisp","description":"Lisp and Scheme programming","privileged":false,"is_media":false,"active":true,"hotness_mod":0.0,"category":"languages"},{"tag":"javascript","description":"Javascript programming","privileged":false,"is_media":false,"active":true,"hotness_mod":0.0,"category":"languages"},{"tag":"vim","description":"Vim editor","privileged":false,"is_media":false,"active":true,"hotness_mod":0.0,"category":"tools"},{"tag":"browsers","description":"Web browsers","privileged":false,"is_media":false,"active":true,"hotness_mod":0.0,"category":"platforms"},{"tag":"security","description":"Netsec, appsec, and infosec","privileged":false,"is_media":false,"active":true,"hotness_mod":0.0,"category":"practices"},{"tag":"openbsd","description":"OpenBSD","privileged":false,"is_media":false,"active":true,"hotness_mod":0.0,"category":"os"},{"tag":"c","description":"C programming","privileged":false,"is_media":false,"active":true,"hotness_mod":0.0,"category":"languages"},{"tag":"go","description":"Golang programming","privileged":false,"is_media":false,"active":true,"hotness_mod":0.0,"category":"languages"},{"tag":"mobile","description":"Mobile app/web development","privileged":false,"is_media":false,"active":true,"hotness_mod":0.0,"category":"platforms"},{"tag":"linux","description":"Linux","privileged":false,"is_media":false,"active":true,"hotness_mod":0.0,"category":"os"},{"tag":"scaling","description":"Scaling and architecture","privileged":false,"is_media":false,"active":true,"hotness_mod":0.0,"category":"practices"},{"tag":"unix","description":"*nix","privileged":false,"is_media":false,"active":true,"hotness_mod":0.0,"category":"os"},{"tag":"android","description":"Android","privileged":false,"is_media":false,"active":true,"hotness_mod":0.0,"category":"os"},{"tag":"ios","description":"Apple iOS","privileged":false,"is_media":false,"active":true,"hotness_mod":0.0,"category":"os"},{"tag":"privacy","description":"Privacy","privileged":false,"is_media":false,"active":true,"hotness_mod":0.0,"category":"practices"},{"tag":"ipv6","description":"IPv6","privileged":false,"is_media":false,"active":false,"hotness_mod":0.0,"category":"platforms"},{"tag":"devops","description":"DevOps","privileged":false,"is_media":false,"active":true,"hotness_mod":0.0,"category":"practices"},{"tag":"hardware","description":"Hardware","privileged":false,"is_media":false,"active":true,"hotness_mod":0.0,"category":"field"},{"tag":"assembly","description":"Assembly programming","privileged":false,"is_media":false,"active":true,"hotness_mod":0.0,"category":"languages"},{"tag":"compsci","description":"Other computer science/programming","privileged":false,"is_media":false,"active":true,"hotness_mod":0.0,"category":"compsci"},{"tag":"book","description":"Link to a book (not an ad or review)","privileged":false,"is_media":true,"active":true,"hotness_mod":0.0,"category":"format"},{"tag":"freebsd","description":"FreeBSD","privileged":false,"is_media":false,"active":true,"hotness_mod":0.0,"category":"os"},{"tag":"reversing","description":"Reverse engineering","privileged":false,"is_media":false,"active":true,"hotness_mod":0.0,"category":"practices"},{"tag":"networking","description":"Networking","privileged":false,"is_media":false,"active":true,"hotness_mod":0.0,"category":"compsci"},{"tag":"release","description":"Software releases and announcements","privileged":false,"is_media":false,"active":true,"hotness_mod":0.0,"category":"genre"},{"tag":"law","description":"Law, patents, and licensing","privileged":false,"is_media":false,"active":true,"hotness_mod":0.0,"category":"culture"},{"tag":"databases","description":"Databases (SQL, NoSQL)","privileged":false,"is_media":false,"active":true,"hotness_mod":0.0,"category":"tools"},{"tag":"virtualization","description":"Virtualization","privileged":false,"is_media":false,"active":true,"hotness_mod":0.0,"category":"practices"},{"tag":"api","description":"API development/implementation","privileged":false,"is_media":false,"active":true,"hotness_mod":0.0,"category":"practices"},{"tag":"web","description":"Web development and news","privileged":false,"is_media":false,"active":true,"hotness_mod":0.0,"category":"platforms"},{"tag":"rant","description":"Rants and raves","privileged":false,"is_media":false,"active":true,"hotness_mod":-0.25,"category":"genre"},{"tag":"show","description":"Show Lobsters / Projects","privileged":false,"is_media":true,"active":true,"hotness_mod":0.0,"category":"format"},{"tag":"math","description":"Mathematics","privileged":false,"is_media":false,"active":true,"hotness_mod":0.0,"category":"field"},{"tag":"visualization","description":"Data visualization","privileged":false,"is_media":false,"active":true,"hotness_mod":0.0,"category":"interaction"},{"tag":"mac","description":"Apple macOS","privileged":false,"is_media":false,"active":true,"hotness_mod":0.0,"category":"os"},{"tag":"windows","description":"Windows","privileged":false,"is_media":false,"active":true,"hotness_mod":0.0,"category":"os"},{"tag":"emacs","description":"Emacs editor","privileged":false,"is_media":false,"active":true,"hotness_mod":0.0,"category":"tools"},{"tag":"philosophy","description":"Philosophy","privileged":false,"is_media":false,"active":true,"hotness_mod":0.0,"category":"culture"},{"tag":"finance","description":"Finance and economics","privileged":false,"is_media":false,"active":true,"hotness_mod":0.0,"category":"field"},{"tag":"announce","description":"Site anouncement","privileged":true,"is_media":false,"active":true,"hotness_mod":1.0,"category":"lobsters"},{"tag":"scala","description":"Scala programming","privileged":false,"is_media":false,"active":true,"hotness_mod":0.0,"category":"languages"},{"tag":"erlang","description":"Erlang development","privileged":false,"is_media":false,"active":true,"hotness_mod":0.0,"category":"languages"},{"tag":"satire","description":"Satire, parody, and other humor","privileged":false,"is_media":false,"active":true,"hotness_mod":-0.25,"category":"genre"},{"tag":"cogsci","description":"Cognitive Science","privileged":false,"is_media":false,"active":true,"hotness_mod":0.0,"category":"field"},{"tag":"java","description":"Java programming","privileged":false,"is_media":false,"active":true,"hotness_mod":0.0,"category":"languages"},{"tag":"practices","description":"Development and team practices","privileged":false,"is_media":false,"active":true,"hotness_mod":0.0,"category":"practices"},{"tag":"job","description":"Employment/Internship opportunities","privileged":false,"is_media":false,"active":true,"hotness_mod":0.0,"category":"genre"},{"tag":"interview","description":"Lobsters interviews","privileged":true,"is_media":false,"active":true,"hotness_mod":0.0,"category":"lobsters"},{"tag":"vcs","description":"Git, Mercurial, Perforce, Subversion and other version control systems","privileged":false,"is_media":false,"active":true,"hotness_mod":0.0,"category":"tools"},{"tag":"programming","description":"Use when every tag or no specific tag applies","privileged":false,"is_media":false,"active":true,"hotness_mod":0.0,"category":"compsci"},{"tag":"games","description":"Game design and study","privileged":false,"is_media":false,"active":true,"hotness_mod":0.0,"category":"platforms"},{"tag":"rust","description":"Rust programming","privileged":false,"is_media":false,"active":true,"hotness_mod":0.0,"category":"languages"},{"tag":"slides","description":"Slide deck","privileged":false,"is_media":true,"active":true,"hotness_mod":0.0,"category":"format"},{"tag":"merkle-trees","description":"And related similar data structures. Not business/scam news.","privileged":false,"is_media":false,"active":true,"hotness_mod":0.0,"category":"platforms"},{"tag":"dotnet","description":"C#, F#, .NET programming","privileged":false,"is_media":false,"active":true,"hotness_mod":0.0,"category":"languages"},{"tag":"haskell","description":"Haskell programming","privileged":false,"is_media":false,"active":true,"hotness_mod":0.0,"category":"languages"},{"tag":"ask","description":"Ask Lobsters","privileged":false,"is_media":true,"active":true,"hotness_mod":0.0,"category":"format"},{"tag":"distributed","description":"Distributed systems","privileged":false,"is_media":false,"active":true,"hotness_mod":0.0,"category":"compsci"},{"tag":"culture","description":"Technical communities and culture","privileged":false,"is_media":false,"active":true,"hotness_mod":-0.25,"category":"culture"},{"tag":"perl","description":"Perl programming","privileged":false,"is_media":false,"active":true,"hotness_mod":0.0,"category":"languages"},{"tag":"art","description":"Art","privileged":false,"is_media":false,"active":true,"hotness_mod":0.0,"category":"genre"},{"tag":"ml","description":"MetaLanguage, OCaml programming","privileged":false,"is_media":false,"active":true,"hotness_mod":0.0,"category":"languages"},{"tag":"audio","description":"Link to audio (podcast, interview)","privileged":false,"is_media":true,"active":true,"hotness_mod":0.0,"category":"format"},{"tag":"swift","description":"Swift programming","privileged":false,"is_media":false,"active":true,"hotness_mod":0.0,"category":"languages"},{"tag":"compilers","description":"Compiler design","privileged":false,"is_media":false,"active":true,"hotness_mod":0.0,"category":"tools"},{"tag":"elixir","description":"Elixir programming","privileged":false,"is_media":false,"active":true,"hotness_mod":0.0,"category":"languages"},{"tag":"graphics","description":"Graphics programming","privileged":false,"is_media":false,"active":true,"hotness_mod":0.0,"category":"compsci"},{"tag":"testing","description":"Software testing","privileged":false,"is_media":false,"active":true,"hotness_mod":0.0,"category":"practices"},{"tag":"historical","description":"History and retrospectives (not for things that happen to be old)","privileged":false,"is_media":false,"active":true,"hotness_mod":0.0,"category":"genre"},{"tag":"debugging","description":"Debugging techniques","privileged":false,"is_media":false,"active":true,"hotness_mod":0.0,"category":"practices"},{"tag":"cryptography","description":"Cryptography","privileged":false,"is_media":false,"active":true,"hotness_mod":0.0,"category":"field"},{"tag":"netbsd","description":"NetBSD","privileged":false,"is_media":false,"active":true,"hotness_mod":0.0,"category":"os"},{"tag":"systemd","description":"Linux systemd","privileged":false,"is_media":false,"active":true,"hotness_mod":0.0,"category":"tools"},{"tag":"nodejs","description":"Node.js programming","privileged":false,"is_media":false,"active":true,"hotness_mod":0.0,"category":"languages"},{"tag":"css","description":"Cascading Style Sheets","privileged":false,"is_media":false,"active":true,"hotness_mod":0.0,"category":"languages"},{"tag":"elm","description":"Elm programming","privileged":false,"is_media":false,"active":true,"hotness_mod":0.0,"category":"languages"},{"tag":"education","description":"Education","privileged":false,"is_media":false,"active":true,"hotness_mod":0.0,"category":"field"},{"tag":"formalmethods","description":"Formal methods","privileged":false,"is_media":false,"active":true,"hotness_mod":0.0,"category":"compsci"},{"tag":"fortran","description":"Fortran programming","privileged":false,"is_media":false,"active":true,"hotness_mod":0.0,"category":"languages"},{"tag":"c++","description":"C++ programming","privileged":false,"is_media":false,"active":true,"hotness_mod":0.0,"category":"languages"},{"tag":"objectivec","description":"Objective-C programming","privileged":false,"is_media":false,"active":true,"hotness_mod":0.0,"category":"languages"},{"tag":"performance","description":"Performance and optimization","privileged":false,"is_media":false,"active":true,"hotness_mod":0.0,"category":"practices"},{"tag":"ai","description":"Developing artificial intelligence, machine learning. Tag AI usage only with `vibecoding`.","privileged":false,"is_media":false,"active":true,"hotness_mod":0.0,"category":"compsci"},{"tag":"dragonflybsd","description":"DragonFly BSD","privileged":false,"is_media":false,"active":true,"hotness_mod":0.0,"category":"os"},{"tag":"clojure","description":"Clojure programming","privileged":false,"is_media":false,"active":true,"hotness_mod":0.0,"category":"languages"},{"tag":"plt","description":"Programming language theory, types, design","privileged":false,"is_media":false,"active":true,"hotness_mod":0.0,"category":"compsci"},{"tag":"illumos","description":"illumos","privileged":false,"is_media":false,"active":true,"hotness_mod":0.0,"category":"os"},{"tag":"d","description":"D programming","privileged":false,"is_media":false,"active":true,"hotness_mod":0.0,"category":"languages"},{"tag":"apl","description":"Array Programming Languages such as APL, J, and K","privileged":false,"is_media":false,"active":true,"hotness_mod":0.0,"category":"languages"},{"tag":"transcript","description":"Includes transcript of audio or video","privileged":false,"is_media":true,"active":true,"hotness_mod":0.0,"category":"format"},{"tag":"osdev","description":"Operating system design and development when no specific OS tag exists","privileged":false,"is_media":false,"active":true,"hotness_mod":0.0,"category":"compsci"},{"tag":"wasm","description":"webassembly","privileged":false,"is_media":false,"active":true,"hotness_mod":0.0,"category":"platforms"},{"tag":"a11y","description":"accessibility, assistive technology, standards","privileged":false,"is_media":false,"active":true,"hotness_mod":0.0,"category":"interaction"},{"tag":"email","description":"e-mail","privileged":false,"is_media":false,"active":true,"hotness_mod":0.0,"category":"platforms"},{"tag":"zig","description":"Zig programming","privileged":false,"is_media":false,"active":true,"hotness_mod":0.0,"category":"languages"},{"tag":"nix","description":"package manager, distribution, and related systems like guix","privileged":false,"is_media":false,"active":true,"hotness_mod":0.0,"category":"os"},{"tag":"kotlin","description":"Kotlin programming","privileged":false,"is_media":false,"active":true,"hotness_mod":0.0,"category":"languages"},{"tag":"retrocomputing","description":"Reproductions, emulators, and other revivals of historic technologies","privileged":false,"is_media":false,"active":true,"hotness_mod":0.0,"category":"platforms"},{"tag":"gleam","description":"Strongly-typed BEAM language","privileged":false,"is_media":false,"active":true,"hotness_mod":0.0,"category":"languages"},{"tag":"editors","description":"Code editors and IDEs without a specific tag","privileged":false,"is_media":false,"active":true,"hotness_mod":0.0,"category":"tools"},{"tag":"vscode","description":"Visual Studio Code editor","privileged":false,"is_media":false,"active":true,"hotness_mod":0.0,"category":"tools"},{"tag":"vibecoding","description":"Using AI/LLM, coding tools. Don't also tag with `ai`.","privileged":false,"is_media":false,"active":true,"hotness_mod":0.0,"category":"tools"},{"tag":"concatenative","description":" stackbased languages like Forth, Factor, Joy","privileged":false,"is_media":false,"active":true,"hotness_mod":0.0,"category":"languages"}] \ No newline at end of file diff --git a/model/src/main/kotlin/dev/msfjarvis/claw/model/Tag.kt b/model/src/main/kotlin/dev/msfjarvis/claw/model/Tag.kt new file mode 100644 index 00000000..54c5621c --- /dev/null +++ b/model/src/main/kotlin/dev/msfjarvis/claw/model/Tag.kt @@ -0,0 +1,23 @@ +/* + * Copyright © Harsh Shandilya. + * Use of this source code is governed by an MIT-style + * license that can be found in the LICENSE file or at + * https://opensource.org/licenses/MIT. + */ +package dev.msfjarvis.claw.model + +import dev.drewhamilton.poko.Poko +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +@Poko +class Tag( + val tag: String, + val description: String, + val privileged: Boolean, + val active: Boolean, + val category: String, + @SerialName("is_media") val isMedia: Boolean, + @SerialName("hotness_mod") val hotnessMod: Double, +)