mirror of
https://github.com/pawelorzech/Swoosh.git
synced 2026-03-31 11:55:47 +00:00
feat: add newsletter model, API endpoint, and per-account preferences
- Create NewsletterModels.kt with GhostNewsletter and NewslettersResponse - Add getNewsletters() endpoint to GhostApiService - Add optional newsletter/emailSegment query params to createPost/updatePost - Create NewsletterPreferences for per-account newsletter toggle - Add fetchNewsletters() and fetchSubscriberCount() to PostRepository - Pass newsletter params through PostRepository to API service - Add Robolectric tests for NewsletterPreferences
This commit is contained in:
parent
807c6d559e
commit
ed11577be1
5 changed files with 217 additions and 6 deletions
|
|
@ -0,0 +1,33 @@
|
|||
package com.swoosh.microblog.data
|
||||
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
|
||||
class NewsletterPreferences private constructor(
|
||||
private val prefs: SharedPreferences,
|
||||
private val accountIdProvider: () -> String
|
||||
) {
|
||||
|
||||
constructor(context: Context) : this(
|
||||
prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE),
|
||||
accountIdProvider = { AccountManager(context).getActiveAccount()?.id ?: "" }
|
||||
)
|
||||
|
||||
/** Constructor for testing with plain SharedPreferences and a fixed account ID. */
|
||||
constructor(prefs: SharedPreferences, accountId: String) : this(
|
||||
prefs = prefs,
|
||||
accountIdProvider = { accountId }
|
||||
)
|
||||
|
||||
private fun activeAccountId(): String = accountIdProvider()
|
||||
|
||||
fun isNewsletterEnabled(): Boolean =
|
||||
prefs.getBoolean("newsletter_enabled_${activeAccountId()}", false)
|
||||
|
||||
fun setNewsletterEnabled(enabled: Boolean) =
|
||||
prefs.edit().putBoolean("newsletter_enabled_${activeAccountId()}", enabled).apply()
|
||||
|
||||
companion object {
|
||||
const val PREFS_NAME = "newsletter_prefs"
|
||||
}
|
||||
}
|
||||
|
|
@ -2,6 +2,7 @@ package com.swoosh.microblog.data.api
|
|||
|
||||
import com.swoosh.microblog.data.model.GhostSite
|
||||
import com.swoosh.microblog.data.model.MembersResponse
|
||||
import com.swoosh.microblog.data.model.NewslettersResponse
|
||||
import com.swoosh.microblog.data.model.PageWrapper
|
||||
import com.swoosh.microblog.data.model.PagesResponse
|
||||
import com.swoosh.microblog.data.model.PostWrapper
|
||||
|
|
@ -28,14 +29,18 @@ interface GhostApiService {
|
|||
@POST("ghost/api/admin/posts/")
|
||||
@Headers("Content-Type: application/json")
|
||||
suspend fun createPost(
|
||||
@Body body: PostWrapper
|
||||
@Body body: PostWrapper,
|
||||
@Query("newsletter") newsletter: String? = null,
|
||||
@Query("email_segment") emailSegment: String? = null
|
||||
): Response<PostsResponse>
|
||||
|
||||
@PUT("ghost/api/admin/posts/{id}/")
|
||||
@Headers("Content-Type: application/json")
|
||||
suspend fun updatePost(
|
||||
@Path("id") id: String,
|
||||
@Body body: PostWrapper
|
||||
@Body body: PostWrapper,
|
||||
@Query("newsletter") newsletter: String? = null,
|
||||
@Query("email_segment") emailSegment: String? = null
|
||||
): Response<PostsResponse>
|
||||
|
||||
@DELETE("ghost/api/admin/posts/{id}/")
|
||||
|
|
@ -61,6 +66,12 @@ interface GhostApiService {
|
|||
@Query("include") include: String = "newsletters,labels"
|
||||
): Response<MembersResponse>
|
||||
|
||||
@GET("ghost/api/admin/newsletters/")
|
||||
suspend fun getNewsletters(
|
||||
@Query("filter") filter: String = "status:active",
|
||||
@Query("limit") limit: String = "all"
|
||||
): Response<NewslettersResponse>
|
||||
|
||||
@GET("ghost/api/admin/users/me/")
|
||||
suspend fun getCurrentUser(): Response<UsersResponse>
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,21 @@
|
|||
package com.swoosh.microblog.data.model
|
||||
|
||||
data class NewslettersResponse(
|
||||
val newsletters: List<GhostNewsletter>
|
||||
)
|
||||
|
||||
data class GhostNewsletter(
|
||||
val id: String,
|
||||
val uuid: String?,
|
||||
val name: String,
|
||||
val slug: String,
|
||||
val description: String?,
|
||||
val status: String?,
|
||||
val visibility: String?,
|
||||
val subscribe_on_signup: Boolean?,
|
||||
val sort_order: Int?,
|
||||
val sender_name: String?,
|
||||
val sender_email: String?,
|
||||
val created_at: String?,
|
||||
val updated_at: String?
|
||||
)
|
||||
|
|
@ -8,6 +8,7 @@ import com.swoosh.microblog.data.api.GhostApiService
|
|||
import com.swoosh.microblog.data.db.AppDatabase
|
||||
import com.swoosh.microblog.data.db.LocalPostDao
|
||||
import com.swoosh.microblog.data.model.*
|
||||
import com.swoosh.microblog.data.model.GhostNewsletter
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.withContext
|
||||
|
|
@ -68,10 +69,18 @@ class PostRepository(private val context: Context) {
|
|||
}
|
||||
}
|
||||
|
||||
suspend fun createPost(post: GhostPost): Result<GhostPost> =
|
||||
suspend fun createPost(
|
||||
post: GhostPost,
|
||||
newsletter: String? = null,
|
||||
emailSegment: String? = null
|
||||
): Result<GhostPost> =
|
||||
withContext(Dispatchers.IO) {
|
||||
try {
|
||||
val response = getApi().createPost(PostWrapper(listOf(post)))
|
||||
val response = getApi().createPost(
|
||||
PostWrapper(listOf(post)),
|
||||
newsletter = newsletter,
|
||||
emailSegment = emailSegment
|
||||
)
|
||||
if (response.isSuccessful) {
|
||||
Result.success(response.body()!!.posts.first())
|
||||
} else {
|
||||
|
|
@ -82,10 +91,20 @@ class PostRepository(private val context: Context) {
|
|||
}
|
||||
}
|
||||
|
||||
suspend fun updatePost(id: String, post: GhostPost): Result<GhostPost> =
|
||||
suspend fun updatePost(
|
||||
id: String,
|
||||
post: GhostPost,
|
||||
newsletter: String? = null,
|
||||
emailSegment: String? = null
|
||||
): Result<GhostPost> =
|
||||
withContext(Dispatchers.IO) {
|
||||
try {
|
||||
val response = getApi().updatePost(id, PostWrapper(listOf(post)))
|
||||
val response = getApi().updatePost(
|
||||
id,
|
||||
PostWrapper(listOf(post)),
|
||||
newsletter = newsletter,
|
||||
emailSegment = emailSegment
|
||||
)
|
||||
if (response.isSuccessful) {
|
||||
Result.success(response.body()!!.posts.first())
|
||||
} else {
|
||||
|
|
@ -96,6 +115,35 @@ class PostRepository(private val context: Context) {
|
|||
}
|
||||
}
|
||||
|
||||
suspend fun fetchNewsletters(): Result<List<GhostNewsletter>> =
|
||||
withContext(Dispatchers.IO) {
|
||||
try {
|
||||
val response = getApi().getNewsletters()
|
||||
if (response.isSuccessful) {
|
||||
Result.success(response.body()!!.newsletters)
|
||||
} else {
|
||||
Result.failure(Exception("Newsletters fetch failed ${response.code()}: ${response.errorBody()?.string()}"))
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Result.failure(e)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun fetchSubscriberCount(): Result<Int> =
|
||||
withContext(Dispatchers.IO) {
|
||||
try {
|
||||
val response = getApi().getMembers(limit = 1)
|
||||
if (response.isSuccessful) {
|
||||
val total = response.body()!!.meta?.pagination?.total ?: 0
|
||||
Result.success(total)
|
||||
} else {
|
||||
Result.failure(Exception("Member count fetch failed ${response.code()}"))
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Result.failure(e)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun deletePost(id: String): Result<Unit> =
|
||||
withContext(Dispatchers.IO) {
|
||||
try {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,98 @@
|
|||
package com.swoosh.microblog.data
|
||||
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import org.junit.Assert.*
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.robolectric.RobolectricTestRunner
|
||||
import org.robolectric.RuntimeEnvironment
|
||||
import org.robolectric.annotation.Config
|
||||
|
||||
@RunWith(RobolectricTestRunner::class)
|
||||
@Config(sdk = [28], application = android.app.Application::class)
|
||||
class NewsletterPreferencesTest {
|
||||
|
||||
private lateinit var prefs: SharedPreferences
|
||||
private lateinit var newsletterPreferences: NewsletterPreferences
|
||||
|
||||
private val testAccountId = "test-account-123"
|
||||
|
||||
@Before
|
||||
fun setup() {
|
||||
val context = RuntimeEnvironment.getApplication()
|
||||
prefs = context.getSharedPreferences(NewsletterPreferences.PREFS_NAME, Context.MODE_PRIVATE)
|
||||
prefs.edit().clear().commit()
|
||||
newsletterPreferences = NewsletterPreferences(prefs, testAccountId)
|
||||
}
|
||||
|
||||
// --- Default values ---
|
||||
|
||||
@Test
|
||||
fun `default newsletter enabled is false`() {
|
||||
assertFalse(newsletterPreferences.isNewsletterEnabled())
|
||||
}
|
||||
|
||||
// --- Setting and getting ---
|
||||
|
||||
@Test
|
||||
fun `setting newsletter enabled to true persists`() {
|
||||
newsletterPreferences.setNewsletterEnabled(true)
|
||||
assertTrue(newsletterPreferences.isNewsletterEnabled())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `setting newsletter enabled to false persists`() {
|
||||
newsletterPreferences.setNewsletterEnabled(true)
|
||||
newsletterPreferences.setNewsletterEnabled(false)
|
||||
assertFalse(newsletterPreferences.isNewsletterEnabled())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `newsletter enabled persists across instances`() {
|
||||
newsletterPreferences.setNewsletterEnabled(true)
|
||||
val newInstance = NewsletterPreferences(prefs, testAccountId)
|
||||
assertTrue(newInstance.isNewsletterEnabled())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `toggling on then off round-trips correctly`() {
|
||||
newsletterPreferences.setNewsletterEnabled(true)
|
||||
assertTrue(newsletterPreferences.isNewsletterEnabled())
|
||||
newsletterPreferences.setNewsletterEnabled(false)
|
||||
assertFalse(newsletterPreferences.isNewsletterEnabled())
|
||||
}
|
||||
|
||||
// --- Per-account isolation ---
|
||||
|
||||
@Test
|
||||
fun `different accounts have independent newsletter settings`() {
|
||||
val prefs1 = NewsletterPreferences(prefs, "account-1")
|
||||
val prefs2 = NewsletterPreferences(prefs, "account-2")
|
||||
|
||||
prefs1.setNewsletterEnabled(true)
|
||||
prefs2.setNewsletterEnabled(false)
|
||||
|
||||
assertTrue(prefs1.isNewsletterEnabled())
|
||||
assertFalse(prefs2.isNewsletterEnabled())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `enabling for one account does not affect another`() {
|
||||
val prefs1 = NewsletterPreferences(prefs, "account-a")
|
||||
val prefs2 = NewsletterPreferences(prefs, "account-b")
|
||||
|
||||
prefs1.setNewsletterEnabled(true)
|
||||
|
||||
assertTrue(prefs1.isNewsletterEnabled())
|
||||
assertFalse(prefs2.isNewsletterEnabled())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `empty account id still works`() {
|
||||
val emptyPrefs = NewsletterPreferences(prefs, "")
|
||||
emptyPrefs.setNewsletterEnabled(true)
|
||||
assertTrue(emptyPrefs.isNewsletterEnabled())
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue