mirror of
https://github.com/pawelorzech/Swoosh.git
synced 2026-03-31 20:15:41 +00:00
feat: add extended tag model (GhostTagFull) and tag CRUD API endpoints
Add TagModels.kt with GhostTagFull, TagsResponse, TagWrapper, TagCount data classes for full Ghost tag management. Add getTags, getTag, createTag, updateTag, deleteTag endpoints to GhostApiService.
This commit is contained in:
parent
0891013df6
commit
d0019947f8
3 changed files with 244 additions and 0 deletions
|
|
@ -2,6 +2,8 @@ package com.swoosh.microblog.data.api
|
||||||
|
|
||||||
import com.swoosh.microblog.data.model.PostWrapper
|
import com.swoosh.microblog.data.model.PostWrapper
|
||||||
import com.swoosh.microblog.data.model.PostsResponse
|
import com.swoosh.microblog.data.model.PostsResponse
|
||||||
|
import com.swoosh.microblog.data.model.TagsResponse
|
||||||
|
import com.swoosh.microblog.data.model.TagWrapper
|
||||||
import okhttp3.MultipartBody
|
import okhttp3.MultipartBody
|
||||||
import okhttp3.RequestBody
|
import okhttp3.RequestBody
|
||||||
import retrofit2.Response
|
import retrofit2.Response
|
||||||
|
|
@ -40,6 +42,26 @@ interface GhostApiService {
|
||||||
@GET("ghost/api/admin/users/me/")
|
@GET("ghost/api/admin/users/me/")
|
||||||
suspend fun getCurrentUser(): Response<UsersResponse>
|
suspend fun getCurrentUser(): Response<UsersResponse>
|
||||||
|
|
||||||
|
@GET("ghost/api/admin/tags/")
|
||||||
|
suspend fun getTags(
|
||||||
|
@Query("limit") limit: String = "all",
|
||||||
|
@Query("include") include: String = "count.posts"
|
||||||
|
): Response<TagsResponse>
|
||||||
|
|
||||||
|
@GET("ghost/api/admin/tags/{id}/")
|
||||||
|
suspend fun getTag(@Path("id") id: String): Response<TagsResponse>
|
||||||
|
|
||||||
|
@POST("ghost/api/admin/tags/")
|
||||||
|
@Headers("Content-Type: application/json")
|
||||||
|
suspend fun createTag(@Body body: TagWrapper): Response<TagsResponse>
|
||||||
|
|
||||||
|
@PUT("ghost/api/admin/tags/{id}/")
|
||||||
|
@Headers("Content-Type: application/json")
|
||||||
|
suspend fun updateTag(@Path("id") id: String, @Body body: TagWrapper): Response<TagsResponse>
|
||||||
|
|
||||||
|
@DELETE("ghost/api/admin/tags/{id}/")
|
||||||
|
suspend fun deleteTag(@Path("id") id: String): Response<Unit>
|
||||||
|
|
||||||
@Multipart
|
@Multipart
|
||||||
@POST("ghost/api/admin/images/upload/")
|
@POST("ghost/api/admin/images/upload/")
|
||||||
suspend fun uploadImage(
|
suspend fun uploadImage(
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,26 @@
|
||||||
|
package com.swoosh.microblog.data.model
|
||||||
|
|
||||||
|
data class TagsResponse(
|
||||||
|
val tags: List<GhostTagFull>,
|
||||||
|
val meta: Meta?
|
||||||
|
)
|
||||||
|
|
||||||
|
data class TagWrapper(
|
||||||
|
val tags: List<GhostTagFull>
|
||||||
|
)
|
||||||
|
|
||||||
|
data class GhostTagFull(
|
||||||
|
val id: String? = null,
|
||||||
|
val name: String,
|
||||||
|
val slug: String? = null,
|
||||||
|
val description: String? = null,
|
||||||
|
val feature_image: String? = null,
|
||||||
|
val visibility: String? = "public",
|
||||||
|
val accent_color: String? = null,
|
||||||
|
val count: TagCount? = null,
|
||||||
|
val created_at: String? = null,
|
||||||
|
val updated_at: String? = null,
|
||||||
|
val url: String? = null
|
||||||
|
)
|
||||||
|
|
||||||
|
data class TagCount(val posts: Int?)
|
||||||
|
|
@ -0,0 +1,196 @@
|
||||||
|
package com.swoosh.microblog.data.model
|
||||||
|
|
||||||
|
import com.google.gson.Gson
|
||||||
|
import org.junit.Assert.*
|
||||||
|
import org.junit.Test
|
||||||
|
|
||||||
|
class TagModelsTest {
|
||||||
|
|
||||||
|
private val gson = Gson()
|
||||||
|
|
||||||
|
// --- GhostTagFull defaults ---
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `GhostTagFull default id is null`() {
|
||||||
|
val tag = GhostTagFull(name = "test")
|
||||||
|
assertNull(tag.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `GhostTagFull default visibility is public`() {
|
||||||
|
val tag = GhostTagFull(name = "test")
|
||||||
|
assertEquals("public", tag.visibility)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `GhostTagFull default optional fields are null`() {
|
||||||
|
val tag = GhostTagFull(name = "test")
|
||||||
|
assertNull(tag.slug)
|
||||||
|
assertNull(tag.description)
|
||||||
|
assertNull(tag.feature_image)
|
||||||
|
assertNull(tag.accent_color)
|
||||||
|
assertNull(tag.count)
|
||||||
|
assertNull(tag.created_at)
|
||||||
|
assertNull(tag.updated_at)
|
||||||
|
assertNull(tag.url)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `GhostTagFull stores all fields`() {
|
||||||
|
val tag = GhostTagFull(
|
||||||
|
id = "tag-1",
|
||||||
|
name = "kotlin",
|
||||||
|
slug = "kotlin",
|
||||||
|
description = "Posts about Kotlin",
|
||||||
|
feature_image = "https://example.com/kotlin.png",
|
||||||
|
visibility = "public",
|
||||||
|
accent_color = "#FF5722",
|
||||||
|
count = TagCount(posts = 42),
|
||||||
|
created_at = "2024-01-01T00:00:00.000Z",
|
||||||
|
updated_at = "2024-06-15T12:00:00.000Z",
|
||||||
|
url = "https://blog.example.com/tag/kotlin/"
|
||||||
|
)
|
||||||
|
assertEquals("tag-1", tag.id)
|
||||||
|
assertEquals("kotlin", tag.name)
|
||||||
|
assertEquals("kotlin", tag.slug)
|
||||||
|
assertEquals("Posts about Kotlin", tag.description)
|
||||||
|
assertEquals("https://example.com/kotlin.png", tag.feature_image)
|
||||||
|
assertEquals("public", tag.visibility)
|
||||||
|
assertEquals("#FF5722", tag.accent_color)
|
||||||
|
assertEquals(42, tag.count?.posts)
|
||||||
|
assertEquals("2024-01-01T00:00:00.000Z", tag.created_at)
|
||||||
|
assertEquals("2024-06-15T12:00:00.000Z", tag.updated_at)
|
||||||
|
assertEquals("https://blog.example.com/tag/kotlin/", tag.url)
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- TagCount ---
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `TagCount stores post count`() {
|
||||||
|
val count = TagCount(posts = 10)
|
||||||
|
assertEquals(10, count.posts)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `TagCount allows null posts`() {
|
||||||
|
val count = TagCount(posts = null)
|
||||||
|
assertNull(count.posts)
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- GSON serialization ---
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `GhostTagFull serializes to JSON correctly`() {
|
||||||
|
val tag = GhostTagFull(
|
||||||
|
name = "android",
|
||||||
|
description = "Android development",
|
||||||
|
accent_color = "#3DDC84"
|
||||||
|
)
|
||||||
|
val json = gson.toJson(tag)
|
||||||
|
assertTrue(json.contains("\"name\":\"android\""))
|
||||||
|
assertTrue(json.contains("\"description\":\"Android development\""))
|
||||||
|
assertTrue(json.contains("\"accent_color\":\"#3DDC84\""))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `GhostTagFull deserializes from JSON correctly`() {
|
||||||
|
val json = """{
|
||||||
|
"id": "abc123",
|
||||||
|
"name": "tech",
|
||||||
|
"slug": "tech",
|
||||||
|
"description": "Technology posts",
|
||||||
|
"visibility": "public",
|
||||||
|
"accent_color": "#1E88E5",
|
||||||
|
"count": {"posts": 15},
|
||||||
|
"created_at": "2024-01-01T00:00:00.000Z",
|
||||||
|
"updated_at": "2024-06-01T00:00:00.000Z",
|
||||||
|
"url": "https://blog.example.com/tag/tech/"
|
||||||
|
}"""
|
||||||
|
val tag = gson.fromJson(json, GhostTagFull::class.java)
|
||||||
|
assertEquals("abc123", tag.id)
|
||||||
|
assertEquals("tech", tag.name)
|
||||||
|
assertEquals("tech", tag.slug)
|
||||||
|
assertEquals("Technology posts", tag.description)
|
||||||
|
assertEquals("public", tag.visibility)
|
||||||
|
assertEquals("#1E88E5", tag.accent_color)
|
||||||
|
assertEquals(15, tag.count?.posts)
|
||||||
|
assertEquals("2024-01-01T00:00:00.000Z", tag.created_at)
|
||||||
|
assertEquals("2024-06-01T00:00:00.000Z", tag.updated_at)
|
||||||
|
assertEquals("https://blog.example.com/tag/tech/", tag.url)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `GhostTagFull deserializes with missing optional fields`() {
|
||||||
|
val json = """{"name": "minimal"}"""
|
||||||
|
val tag = gson.fromJson(json, GhostTagFull::class.java)
|
||||||
|
assertEquals("minimal", tag.name)
|
||||||
|
assertNull(tag.id)
|
||||||
|
assertNull(tag.slug)
|
||||||
|
assertNull(tag.description)
|
||||||
|
assertNull(tag.accent_color)
|
||||||
|
assertNull(tag.count)
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- TagsResponse ---
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `TagsResponse deserializes with tags and meta`() {
|
||||||
|
val json = """{
|
||||||
|
"tags": [
|
||||||
|
{"id": "1", "name": "news", "slug": "news", "count": {"posts": 5}},
|
||||||
|
{"id": "2", "name": "tech", "slug": "tech", "count": {"posts": 12}}
|
||||||
|
],
|
||||||
|
"meta": {"pagination": {"page": 1, "limit": 15, "pages": 1, "total": 2, "next": null, "prev": null}}
|
||||||
|
}"""
|
||||||
|
val response = gson.fromJson(json, TagsResponse::class.java)
|
||||||
|
assertEquals(2, response.tags.size)
|
||||||
|
assertEquals("news", response.tags[0].name)
|
||||||
|
assertEquals(5, response.tags[0].count?.posts)
|
||||||
|
assertEquals("tech", response.tags[1].name)
|
||||||
|
assertEquals(12, response.tags[1].count?.posts)
|
||||||
|
assertNotNull(response.meta)
|
||||||
|
assertEquals(1, response.meta?.pagination?.page)
|
||||||
|
assertEquals(2, response.meta?.pagination?.total)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `TagsResponse deserializes with null meta`() {
|
||||||
|
val json = """{"tags": [{"name": "solo"}], "meta": null}"""
|
||||||
|
val response = gson.fromJson(json, TagsResponse::class.java)
|
||||||
|
assertEquals(1, response.tags.size)
|
||||||
|
assertNull(response.meta)
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- TagWrapper ---
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `TagWrapper wraps tags for API request`() {
|
||||||
|
val wrapper = TagWrapper(listOf(GhostTagFull(name = "new-tag", description = "A new tag")))
|
||||||
|
val json = gson.toJson(wrapper)
|
||||||
|
assertTrue(json.contains("\"tags\""))
|
||||||
|
assertTrue(json.contains("\"new-tag\""))
|
||||||
|
assertTrue(json.contains("\"A new tag\""))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `TagWrapper serializes accent_color`() {
|
||||||
|
val wrapper = TagWrapper(listOf(GhostTagFull(
|
||||||
|
name = "colored",
|
||||||
|
accent_color = "#FF0000"
|
||||||
|
)))
|
||||||
|
val json = gson.toJson(wrapper)
|
||||||
|
assertTrue(json.contains("\"accent_color\":\"#FF0000\""))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `TagCount zero posts`() {
|
||||||
|
val count = TagCount(posts = 0)
|
||||||
|
assertEquals(0, count.posts)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `GhostTagFull with internal visibility`() {
|
||||||
|
val tag = GhostTagFull(name = "internal-tag", visibility = "internal")
|
||||||
|
assertEquals("internal", tag.visibility)
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue