api: initial commit
Signed-off-by: Harsh Shandilya <me@msfjarvis.dev>
This commit is contained in:
parent
29c374859b
commit
fcfcbfbf92
|
@ -11,8 +11,8 @@ version = "1.0"
|
|||
repositories { google() }
|
||||
|
||||
dependencies {
|
||||
implementation(project(":common"))
|
||||
implementation("androidx.activity:activity-compose:1.3.0-alpha08")
|
||||
implementation(projects.common)
|
||||
implementation("androidx.activity:activity-compose:1.3.0-beta01")
|
||||
}
|
||||
|
||||
android {
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
/build
|
|
@ -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"
|
||||
}
|
||||
}
|
|
@ -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>,
|
||||
)
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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())
|
||||
}
|
||||
}
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -1,9 +1,8 @@
|
|||
buildscript {
|
||||
repositories {
|
||||
gradlePluginPortal()
|
||||
jcenter()
|
||||
google()
|
||||
mavenCentral()
|
||||
gradlePluginPortal()
|
||||
}
|
||||
dependencies {
|
||||
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.5.10")
|
||||
|
@ -18,21 +17,19 @@ version = "1.0"
|
|||
|
||||
allprojects {
|
||||
repositories {
|
||||
jcenter()
|
||||
mavenCentral()
|
||||
maven { url = uri("https://maven.pkg.jetbrains.space/public/p/compose/dev") }
|
||||
google()
|
||||
}
|
||||
apply(plugin = "com.diffplug.spotless")
|
||||
configure<com.diffplug.gradle.spotless.SpotlessExtension> {
|
||||
kotlin {
|
||||
target("**/*.kt")
|
||||
targetExclude("**/build/**")
|
||||
ktlint().userData(mapOf("indent_size" to "2", "continuation_indent_size" to "2"))
|
||||
ktfmt().googleStyle()
|
||||
}
|
||||
kotlinGradle {
|
||||
target("*.gradle.kts")
|
||||
ktlint().userData(mapOf("indent_size" to "2", "continuation_indent_size" to "2"))
|
||||
ktfmt().googleStyle()
|
||||
}
|
||||
format("xml") {
|
||||
|
|
|
@ -15,7 +15,7 @@ kotlin {
|
|||
sourceSets {
|
||||
val jvmMain by getting {
|
||||
dependencies {
|
||||
implementation(project(":common"))
|
||||
implementation(projects.common)
|
||||
implementation(compose.desktop.currentOs)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
repositories {
|
||||
google()
|
||||
jcenter()
|
||||
gradlePluginPortal()
|
||||
mavenCentral()
|
||||
maven { url = uri("https://maven.pkg.jetbrains.space/public/p/compose/dev") }
|
||||
gradlePluginPortal()
|
||||
}
|
||||
}
|
||||
|
||||
rootProject.name = "Claw"
|
||||
|
||||
enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS")
|
||||
|
||||
enableFeaturePreview("VERSION_CATALOGS")
|
||||
|
||||
include(":android")
|
||||
|
||||
include(":desktop")
|
||||
include(":api")
|
||||
|
||||
include(":common")
|
||||
|
||||
include(":desktop")
|
||||
|
|
Loading…
Reference in New Issue