mirror of
https://github.com/msfjarvis/compose-lobsters
synced 2025-08-14 10:37:05 +05:30
api: initial commit
Signed-off-by: Harsh Shandilya <me@msfjarvis.dev>
This commit is contained in:
parent
29c374859b
commit
fcfcbfbf92
17 changed files with 274 additions and 12 deletions
|
@ -11,8 +11,8 @@ version = "1.0"
|
||||||
repositories { google() }
|
repositories { google() }
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation(project(":common"))
|
implementation(projects.common)
|
||||||
implementation("androidx.activity:activity-compose:1.3.0-alpha08")
|
implementation("androidx.activity:activity-compose:1.3.0-beta01")
|
||||||
}
|
}
|
||||||
|
|
||||||
android {
|
android {
|
||||||
|
|
1
api/.gitignore
vendored
Normal file
1
api/.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
/build
|
23
api/build.gradle.kts
Normal file
23
api/build.gradle.kts
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
|
||||||
|
|
||||||
|
plugins {
|
||||||
|
kotlin("jvm")
|
||||||
|
id("com.google.devtools.ksp") version "1.5.10-1.0.0-beta01"
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
api(libs.thirdparty.retrofit.lib)
|
||||||
|
ksp(libs.thirdparty.moshix.ksp)
|
||||||
|
implementation(libs.thirdparty.moshi.lib)
|
||||||
|
implementation(libs.thirdparty.retrofit.moshiConverter) { exclude(group = "com.squareup.moshi") }
|
||||||
|
testImplementation(libs.kotlin.coroutines.core)
|
||||||
|
testImplementation(libs.testing.kotlintest.junit)
|
||||||
|
testImplementation(libs.testing.mockWebServer)
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.withType<KotlinCompile> {
|
||||||
|
kotlinOptions {
|
||||||
|
jvmTarget = JavaVersion.VERSION_11.toString()
|
||||||
|
languageVersion = "1.5"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,22 @@
|
||||||
|
package dev.msfjarvis.lobsters.data.api
|
||||||
|
|
||||||
|
import dev.msfjarvis.lobsters.model.LobstersPost
|
||||||
|
import dev.msfjarvis.lobsters.model.LobstersPostDetails
|
||||||
|
import retrofit2.http.GET
|
||||||
|
import retrofit2.http.Path
|
||||||
|
import retrofit2.http.Query
|
||||||
|
|
||||||
|
/** Simple interface defining an API for lobste.rs */
|
||||||
|
interface LobstersApi {
|
||||||
|
|
||||||
|
@GET("hottest.json") suspend fun getHottestPosts(@Query("page") page: Int): List<LobstersPost>
|
||||||
|
|
||||||
|
@GET("newest.json") suspend fun getNewestPosts(@Query("page") page: Int): List<LobstersPost>
|
||||||
|
|
||||||
|
@GET("s/{postId}.json")
|
||||||
|
suspend fun getPostDetails(@Path("postId") postId: String): LobstersPostDetails
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val BASE_URL = "https://lobste.rs"
|
||||||
|
}
|
||||||
|
}
|
20
api/src/main/kotlin/dev/msfjarvis/lobsters/model/Comment.kt
Normal file
20
api/src/main/kotlin/dev/msfjarvis/lobsters/model/Comment.kt
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
package dev.msfjarvis.lobsters.model
|
||||||
|
|
||||||
|
import com.squareup.moshi.Json
|
||||||
|
import com.squareup.moshi.JsonClass
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
|
class Comment(
|
||||||
|
@Json(name = "short_id") val shortId: String,
|
||||||
|
@Json(name = "short_id_url") val shortIdUrl: String,
|
||||||
|
@Json(name = "created_at") val createdAt: String,
|
||||||
|
@Json(name = "updated_at") val updatedAt: String,
|
||||||
|
@Json(name = "is_deleted") val isDeleted: Boolean,
|
||||||
|
@Json(name = "is_moderated") val isModerated: Boolean,
|
||||||
|
val score: Long,
|
||||||
|
val flags: Long,
|
||||||
|
val comment: String,
|
||||||
|
val url: String,
|
||||||
|
@Json(name = "indent_level") val indentLevel: Long,
|
||||||
|
@Json(name = "commenting_user") val user: User,
|
||||||
|
)
|
|
@ -0,0 +1,10 @@
|
||||||
|
package dev.msfjarvis.lobsters.model
|
||||||
|
|
||||||
|
import com.squareup.moshi.Json
|
||||||
|
import com.squareup.moshi.JsonClass
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
|
class KeybaseSignature(
|
||||||
|
@Json(name = "kb_username") val kbUsername: String,
|
||||||
|
@Json(name = "sig_hash") val sigHash: String,
|
||||||
|
)
|
|
@ -0,0 +1,20 @@
|
||||||
|
package dev.msfjarvis.lobsters.model
|
||||||
|
|
||||||
|
import com.squareup.moshi.Json
|
||||||
|
import com.squareup.moshi.JsonClass
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
|
class LobstersPost(
|
||||||
|
@Json(name = "short_id") val shortId: String,
|
||||||
|
@Json(name = "short_id_url") val shortIdUrl: String,
|
||||||
|
@Json(name = "created_at") val createdAt: String,
|
||||||
|
val title: String,
|
||||||
|
val url: String,
|
||||||
|
val score: Long,
|
||||||
|
val flags: Long,
|
||||||
|
@Json(name = "comment_count") val commentCount: Long,
|
||||||
|
val description: String,
|
||||||
|
@Json(name = "comments_url") val commentsUrl: String,
|
||||||
|
@Json(name = "submitter_user") val submitter: User,
|
||||||
|
val tags: List<String>,
|
||||||
|
)
|
|
@ -0,0 +1,21 @@
|
||||||
|
package dev.msfjarvis.lobsters.model
|
||||||
|
|
||||||
|
import com.squareup.moshi.Json
|
||||||
|
import com.squareup.moshi.JsonClass
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
|
class LobstersPostDetails(
|
||||||
|
@Json(name = "short_id") val shortId: String,
|
||||||
|
@Json(name = "short_id_url") val shortIdUrl: String,
|
||||||
|
@Json(name = "created_at") val createdAt: String,
|
||||||
|
val title: String,
|
||||||
|
val url: String,
|
||||||
|
val score: Long,
|
||||||
|
val flags: Long,
|
||||||
|
@Json(name = "comment_count") val commentCount: Long,
|
||||||
|
val description: String,
|
||||||
|
@Json(name = "comments_url") val commentsUrl: String,
|
||||||
|
@Json(name = "submitter_user") val submitter: User,
|
||||||
|
val tags: List<String>,
|
||||||
|
val comments: List<Comment>,
|
||||||
|
)
|
19
api/src/main/kotlin/dev/msfjarvis/lobsters/model/User.kt
Normal file
19
api/src/main/kotlin/dev/msfjarvis/lobsters/model/User.kt
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
package dev.msfjarvis.lobsters.model
|
||||||
|
|
||||||
|
import com.squareup.moshi.Json
|
||||||
|
import com.squareup.moshi.JsonClass
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
|
class User(
|
||||||
|
val username: String,
|
||||||
|
@Json(name = "created_at") val createdAt: String,
|
||||||
|
@Json(name = "is_admin") val isAdmin: Boolean,
|
||||||
|
val about: String,
|
||||||
|
@Json(name = "is_moderator") val isModerator: Boolean,
|
||||||
|
val karma: Long = 0,
|
||||||
|
@Json(name = "avatar_url") val avatarUrl: String,
|
||||||
|
@Json(name = "invited_by_user") val invitedByUser: String,
|
||||||
|
@Json(name = "github_username") val githubUsername: String? = null,
|
||||||
|
@Json(name = "twitter_username") val twitterUsername: String? = null,
|
||||||
|
@Json(name = "keybase_signatures") val keybaseSignatures: List<KeybaseSignature> = emptyList(),
|
||||||
|
)
|
|
@ -0,0 +1,89 @@
|
||||||
|
package dev.msfjarvis.lobsters.data.api
|
||||||
|
|
||||||
|
import com.squareup.moshi.Moshi
|
||||||
|
import dev.msfjarvis.lobsters.util.TestUtils
|
||||||
|
import kotlin.test.Test
|
||||||
|
import kotlin.test.assertEquals
|
||||||
|
import kotlin.test.assertTrue
|
||||||
|
import kotlin.test.fail
|
||||||
|
import kotlinx.coroutines.runBlocking
|
||||||
|
import mockwebserver3.Dispatcher
|
||||||
|
import mockwebserver3.MockResponse
|
||||||
|
import mockwebserver3.MockWebServer
|
||||||
|
import mockwebserver3.RecordedRequest
|
||||||
|
import okhttp3.OkHttpClient
|
||||||
|
import org.junit.AfterClass
|
||||||
|
import org.junit.BeforeClass
|
||||||
|
import retrofit2.Retrofit
|
||||||
|
import retrofit2.converter.moshi.MoshiConverterFactory
|
||||||
|
import retrofit2.create
|
||||||
|
|
||||||
|
class LobstersApiTest {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val webServer = MockWebServer()
|
||||||
|
private val moshi = Moshi.Builder().build()
|
||||||
|
private val okHttp = OkHttpClient.Builder().build()
|
||||||
|
private val retrofit =
|
||||||
|
Retrofit.Builder()
|
||||||
|
.client(okHttp)
|
||||||
|
.baseUrl("http://localhost:8080/")
|
||||||
|
.addConverterFactory(MoshiConverterFactory.create(moshi))
|
||||||
|
.build()
|
||||||
|
private val apiClient = retrofit.create<LobstersApi>()
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
@BeforeClass
|
||||||
|
fun setUp() {
|
||||||
|
webServer.start(8080)
|
||||||
|
webServer.dispatcher =
|
||||||
|
object : Dispatcher() {
|
||||||
|
override fun dispatch(request: RecordedRequest): MockResponse {
|
||||||
|
val path = requireNotNull(request.path)
|
||||||
|
return when {
|
||||||
|
path.startsWith("/hottest") ->
|
||||||
|
MockResponse().setBody(TestUtils.getJson("hottest.json")).setResponseCode(200)
|
||||||
|
path.startsWith("/s/") ->
|
||||||
|
MockResponse()
|
||||||
|
.setBody(TestUtils.getJson("post_details_d9ucpe.json"))
|
||||||
|
.setResponseCode(200)
|
||||||
|
else -> fail("'$path' unexpected")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
@AfterClass
|
||||||
|
fun tearDown() {
|
||||||
|
webServer.shutdown()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `api gets correct number of items`() = runBlocking {
|
||||||
|
val posts = apiClient.getHottestPosts(1)
|
||||||
|
assertEquals(25, posts.size)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `no moderator posts in test data`() = runBlocking {
|
||||||
|
val posts = apiClient.getHottestPosts(1)
|
||||||
|
val moderatorPosts = posts.asSequence().filter { it.submitter.isModerator }.toSet()
|
||||||
|
assertTrue(moderatorPosts.isEmpty())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `posts with no urls`() = runBlocking {
|
||||||
|
val posts = apiClient.getHottestPosts(1)
|
||||||
|
val commentsOnlyPosts = posts.asSequence().filter { it.url.isEmpty() }.toSet()
|
||||||
|
assertEquals(2, commentsOnlyPosts.size)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `post details with comments`() = runBlocking {
|
||||||
|
val postDetails = apiClient.getPostDetails("d9ucpe")
|
||||||
|
assertEquals(7, postDetails.commentCount)
|
||||||
|
assertEquals(7, postDetails.comments.size)
|
||||||
|
}
|
||||||
|
}
|
12
api/src/test/kotlin/dev/msfjarvis/lobsters/util/TestUtils.kt
Normal file
12
api/src/test/kotlin/dev/msfjarvis/lobsters/util/TestUtils.kt
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
package dev.msfjarvis.lobsters.util
|
||||||
|
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
|
object TestUtils {
|
||||||
|
fun getJson(path: String): String {
|
||||||
|
// Load the JSON response
|
||||||
|
val uri = javaClass.classLoader.getResource(path)
|
||||||
|
val file = File(uri.path)
|
||||||
|
return String(file.readBytes())
|
||||||
|
}
|
||||||
|
}
|
1
api/src/test/resources/hottest.json
Normal file
1
api/src/test/resources/hottest.json
Normal file
File diff suppressed because one or more lines are too long
1
api/src/test/resources/post_details_d9ucpe.json
Normal file
1
api/src/test/resources/post_details_d9ucpe.json
Normal file
File diff suppressed because one or more lines are too long
|
@ -1,9 +1,8 @@
|
||||||
buildscript {
|
buildscript {
|
||||||
repositories {
|
repositories {
|
||||||
|
google()
|
||||||
|
mavenCentral()
|
||||||
gradlePluginPortal()
|
gradlePluginPortal()
|
||||||
jcenter()
|
|
||||||
google()
|
|
||||||
mavenCentral()
|
|
||||||
}
|
}
|
||||||
dependencies {
|
dependencies {
|
||||||
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.5.10")
|
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.5.10")
|
||||||
|
@ -18,21 +17,19 @@ version = "1.0"
|
||||||
|
|
||||||
allprojects {
|
allprojects {
|
||||||
repositories {
|
repositories {
|
||||||
jcenter()
|
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
maven { url = uri("https://maven.pkg.jetbrains.space/public/p/compose/dev") }
|
maven { url = uri("https://maven.pkg.jetbrains.space/public/p/compose/dev") }
|
||||||
|
google()
|
||||||
}
|
}
|
||||||
apply(plugin = "com.diffplug.spotless")
|
apply(plugin = "com.diffplug.spotless")
|
||||||
configure<com.diffplug.gradle.spotless.SpotlessExtension> {
|
configure<com.diffplug.gradle.spotless.SpotlessExtension> {
|
||||||
kotlin {
|
kotlin {
|
||||||
target("**/*.kt")
|
target("**/*.kt")
|
||||||
targetExclude("**/build/**")
|
targetExclude("**/build/**")
|
||||||
ktlint().userData(mapOf("indent_size" to "2", "continuation_indent_size" to "2"))
|
|
||||||
ktfmt().googleStyle()
|
ktfmt().googleStyle()
|
||||||
}
|
}
|
||||||
kotlinGradle {
|
kotlinGradle {
|
||||||
target("*.gradle.kts")
|
target("*.gradle.kts")
|
||||||
ktlint().userData(mapOf("indent_size" to "2", "continuation_indent_size" to "2"))
|
|
||||||
ktfmt().googleStyle()
|
ktfmt().googleStyle()
|
||||||
}
|
}
|
||||||
format("xml") {
|
format("xml") {
|
||||||
|
|
|
@ -15,7 +15,7 @@ kotlin {
|
||||||
sourceSets {
|
sourceSets {
|
||||||
val jvmMain by getting {
|
val jvmMain by getting {
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation(project(":common"))
|
implementation(projects.common)
|
||||||
implementation(compose.desktop.currentOs)
|
implementation(compose.desktop.currentOs)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
21
gradle/libs.versions.toml
Normal file
21
gradle/libs.versions.toml
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
[versions]
|
||||||
|
coroutines = "1.5.0"
|
||||||
|
kotlin = "1.5.10"
|
||||||
|
moshix = "0.11.2"
|
||||||
|
retrofit = "2.9.0"
|
||||||
|
|
||||||
|
[libraries]
|
||||||
|
|
||||||
|
kotlin-coroutines-android = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android", version.ref = "coroutines" }
|
||||||
|
kotlin-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "coroutines" }
|
||||||
|
kotlin-coroutines-jvm = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm", version.ref = "coroutines" }
|
||||||
|
|
||||||
|
thirdparty-moshi-lib = "com.squareup.moshi:moshi:1.12.0"
|
||||||
|
thirdparty-moshix-ksp = { module = "dev.zacsweers.moshix:moshi-ksp", version.ref = "moshix" }
|
||||||
|
thirdparty-moshix-metadatareflect = { module = "dev.zacsweers.moshix:moshi-metadata-reflect", version.ref = "moshix" }
|
||||||
|
|
||||||
|
thirdparty-retrofit-lib = { module = "com.squareup.retrofit2:retrofit", version.ref = "retrofit" }
|
||||||
|
thirdparty-retrofit-moshiConverter = { module = "com.squareup.retrofit2:converter-moshi", version.ref = "retrofit" }
|
||||||
|
|
||||||
|
testing-kotlintest-junit = { module = "org.jetbrains.kotlin:kotlin-test-junit", version.ref = "kotlin" }
|
||||||
|
testing-mockWebServer = "com.squareup.okhttp3:mockwebserver3-junit4:5.0.0-alpha.2"
|
|
@ -1,17 +1,22 @@
|
||||||
pluginManagement {
|
pluginManagement {
|
||||||
repositories {
|
repositories {
|
||||||
google()
|
google()
|
||||||
jcenter()
|
|
||||||
gradlePluginPortal()
|
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
maven { url = uri("https://maven.pkg.jetbrains.space/public/p/compose/dev") }
|
maven { url = uri("https://maven.pkg.jetbrains.space/public/p/compose/dev") }
|
||||||
|
gradlePluginPortal()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
rootProject.name = "Claw"
|
rootProject.name = "Claw"
|
||||||
|
|
||||||
|
enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS")
|
||||||
|
|
||||||
|
enableFeaturePreview("VERSION_CATALOGS")
|
||||||
|
|
||||||
include(":android")
|
include(":android")
|
||||||
|
|
||||||
include(":desktop")
|
include(":api")
|
||||||
|
|
||||||
include(":common")
|
include(":common")
|
||||||
|
|
||||||
|
include(":desktop")
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue