-
Notifications
You must be signed in to change notification settings - Fork 0
[feat/#24] 게시물 리스트 조회 API 연동 #29
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
4fc7ea9
0929b4b
c5e344d
66f12d7
8f8e3e3
deef364
019c86b
3c16800
ec21d5f
27e6f40
368ae51
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,21 @@ | ||
| package com.hsLink.hslink.data.di | ||
|
|
||
| import android.content.Context | ||
| import com.hsLink.hslink.data.local.TokenDataStore | ||
| import dagger.Module | ||
| import dagger.Provides | ||
| import dagger.hilt.InstallIn | ||
| import dagger.hilt.android.qualifiers.ApplicationContext | ||
| import dagger.hilt.components.SingletonComponent | ||
| import javax.inject.Singleton | ||
|
|
||
| @Module | ||
| @InstallIn(SingletonComponent::class) | ||
| object DataStoreModule { | ||
|
|
||
| @Provides | ||
| @Singleton | ||
| fun provideTokenDataStore(@ApplicationContext context: Context): TokenDataStore { | ||
| return TokenDataStore(context) | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,32 @@ | ||
| package com.hsLink.hslink.data.dto.response.community | ||
|
|
||
| import kotlinx.serialization.SerialName | ||
| import kotlinx.serialization.Serializable | ||
|
|
||
| @Serializable | ||
| data class CommunityListResponseDto( | ||
| @SerialName("posts") | ||
| val posts: List<CommunityListDto>, | ||
| @SerialName("currentPage") | ||
| val currentPage: Int, | ||
| @SerialName("hasNext") | ||
| val hasNext: Boolean, | ||
| @SerialName("totalElements") | ||
| val totalElements: Int, | ||
| ) | ||
|
|
||
| @Serializable | ||
| data class CommunityListDto( | ||
| @SerialName("id") | ||
| val id: Int, | ||
| @SerialName("title") | ||
| val title: String, | ||
| @SerialName("summary") | ||
| val summary: String, | ||
| @SerialName("author") | ||
| val author: String, | ||
| @SerialName("studentId") | ||
| val studentId: String, | ||
| @SerialName("authorStatus") | ||
| val authorStatus: String, | ||
| ) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,42 @@ | ||
| package com.hsLink.hslink.data.local | ||
|
|
||
| import android.content.Context | ||
| import androidx.datastore.core.DataStore | ||
| import androidx.datastore.preferences.core.Preferences | ||
| import androidx.datastore.preferences.core.edit | ||
| import androidx.datastore.preferences.core.stringPreferencesKey | ||
| import androidx.datastore.preferences.preferencesDataStore | ||
| import kotlinx.coroutines.flow.Flow | ||
| import kotlinx.coroutines.flow.map | ||
| import javax.inject.Inject | ||
|
|
||
| class TokenDataStore @Inject constructor(private val context: Context) { | ||
|
|
||
| private val Context.dataStore: DataStore<Preferences> by preferencesDataStore(name = "hsu_connect_tokens") | ||
|
|
||
| private val accessTokenKey = stringPreferencesKey("access_token") | ||
| private val refreshTokenKey = stringPreferencesKey("refresh_token") | ||
|
|
||
| val accessToken: Flow<String?> = context.dataStore.data | ||
| .map { preferences -> | ||
| preferences[accessTokenKey] | ||
| } | ||
|
|
||
| val refreshToken: Flow<String?> = context.dataStore.data | ||
| .map { preferences -> | ||
| preferences[refreshTokenKey] | ||
| } | ||
|
|
||
| suspend fun saveTokens(accessToken: String, refreshToken: String) { | ||
| context.dataStore.edit { | ||
| it[accessTokenKey] = accessToken | ||
| it[refreshTokenKey] = refreshToken | ||
| } | ||
| } | ||
|
|
||
| suspend fun clearTokens() { | ||
| context.dataStore.edit { | ||
| it.clear() | ||
| } | ||
| } | ||
|
Comment on lines
+13
to
+41
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chainConsider encrypting tokens at rest. DataStore Preferences stores data unencrypted on disk. Access and refresh tokens are sensitive credentials that, if compromised, could allow unauthorized access to user accounts. While Android's app sandboxing provides some protection, consider using EncryptedSharedPreferences or implementing encryption at the application layer for additional security. This is particularly important if:
If token encryption is required, would you like me to provide an implementation example using EncryptedSharedPreferences or a custom encryption wrapper? I'll help you verify this review comment. Let me gather information about DataStore encryption practices and examine the codebase context. 🏁 Script executed: #!/bin/bash
# Search for build.gradle or gradle dependencies to understand project setup
fd -e gradle -e toml | head -20Length of output: 25 🏁 Script executed: #!/bin/bash
# Search for token usage patterns in the codebase to understand sensitivity
rg -n "Token|token" --type=kt -C 3 | head -100Length of output: 26 🏁 Script executed: #!/bin/bash
# Check if EncryptedSharedPreferences is already used or imported anywhere
rg "EncryptedSharedPreferences|encryption|Encrypted" --type=kt -nLength of output: 26 🌐 Web query: 💡 Result: Short answer: Preferences DataStore is not encrypted by default — data is stored on disk in your app's private storage but DataStore doesn't perform encryption for you. Use Jetpack Security (EncryptedSharedPreferences / EncryptedFile), Android Keystore-based encryption, or encrypt before saving if you need confidentiality. [1][2][3] Sources:
🌐 Web query: 💡 Result: Short answer: EncryptedSharedPreferences has been deprecated, and the recommended alternative is to keep using Jetpack DataStore for storage and add your own encryption layer — typically by protecting an AES key with Android Keystore (or using Google Tink) and encrypting values before writing to DataStore. Other options: use a full-database solution with SQLCipher, or a maintained community library (e.g., SafeBox) that replaces EncryptedSharedPreferences. [1][2][3][4][5] Practical options with references
If you want, I can:
Sources: 🏁 Script executed: #!/bin/bash
# Check available dependencies
cat gradle/libs.versions.tomlLength of output: 6336 🏁 Script executed: #!/bin/bash
# Search for token usage and authentication patterns (retry without type filter)
rg -n "Token|token" -C 3 | head -150Length of output: 15601 🏁 Script executed: #!/bin/bash
# Check for security, encryption, or crypto dependencies
rg -n "security|crypto|tink|keystore|cipher" -i -C 2Length of output: 2325 🏁 Script executed: #!/bin/bash
# Look for any authentication or compliance-related context
rg -n "auth|AUTH|compliance|COMPLIANCE|sensitive|SENSITIVE" -C 2 | head -80Length of output: 4999 Encrypt tokens before storage in DataStore. The security concern is valid—tokens are sensitive credentials that grant account access. DataStore Preferences is not encrypted by default, and your app handles authentication tokens for user accounts. However, EncryptedSharedPreferences is deprecated. The recommended approach is to add an encryption layer within DataStore itself. Your project already includes
Implement encryption at the application layer in 🤖 Prompt for AI Agents |
||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,36 @@ | ||
| package com.hsLink.hslink.data.paging | ||
|
|
||
| import androidx.paging.PagingSource | ||
| import androidx.paging.PagingState | ||
| import com.hsLink.hslink.data.service.commuunity.CommunityPostService | ||
| import com.hsLink.hslink.domain.model.community.CommunityPost | ||
| import com.hsLink.hslink.domain.model.community.toEntity | ||
|
|
||
| class CommunityPagingSource( | ||
| private val communityPostService: CommunityPostService, | ||
| private val type: String | ||
| ) : PagingSource<Int, CommunityPost>() { | ||
|
|
||
| override suspend fun load(params: LoadParams<Int>): LoadResult<Int, CommunityPost> { | ||
| val page = params.key ?: 0 | ||
|
|
||
| return try { | ||
| val response = communityPostService.getCommunity(type = type, page = page) | ||
| val posts = response.result.posts.map { it.toEntity() } | ||
|
|
||
| LoadResult.Page( | ||
| data = posts, | ||
| prevKey = if (page == 0) null else page - 1, | ||
| nextKey = if (response.result.hasNext) page + 1 else null | ||
| ) | ||
| } catch (e: Exception) { | ||
| LoadResult.Error(e) | ||
| } | ||
| } | ||
|
Comment on lines
+14
to
+29
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add null safety check for API response. Line 19 accesses Apply this diff to add proper null safety: override suspend fun load(params: LoadParams<Int>): LoadResult<Int, CommunityPost> {
val page = params.key ?: 0
return try {
val response = communityPostService.getCommunity(type = type, page = page)
- val posts = response.result.posts.map { it.toEntity() }
+
+ if (!response.isSuccess || response.result == null) {
+ return LoadResult.Error(Exception(response.message ?: "Unknown error"))
+ }
+
+ val posts = response.result.posts.map { it.toEntity() }
LoadResult.Page(
data = posts,
prevKey = if (page == 0) null else page - 1,
nextKey = if (response.result.hasNext) page + 1 else null
)
} catch (e: Exception) {
LoadResult.Error(e)
}
} |
||
|
|
||
| override fun getRefreshKey(state: PagingState<Int, CommunityPost>): Int? { | ||
| return state.anchorPosition?.let { | ||
| state.closestPageToPosition(it)?.prevKey?.plus(1) ?: state.closestPageToPosition(it)?.nextKey?.minus(1) | ||
| } | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Restore permissive Json configuration
Json.asConverterFactoryuses the strict default settings, so any new/unknown fields from the backend will now triggerSerializationException. This is a regression compared with the typicalJson { ignoreUnknownKeys = true }setup and will break the app as soon as the API adds an extra field. Please reinstate the relaxed configuration (and consider preservingcoerceInputValuesif we had it before) so responses keep deserializing safely.📝 Committable suggestion
🤖 Prompt for AI Agents