Skip to content

Commit ec341cb

Browse files
authored
Merge pull request #1098 from paug/add-feed
Add feed
2 parents 3f4344e + 4ccdd1c commit ec341cb

7 files changed

Lines changed: 87 additions & 42 deletions

File tree

service/build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ tasks.register("run", JavaExec::class.java) {
3131
classpath(configurations.getByName("runtimeClasspath"))
3232
classpath(kotlin.target.compilations.getByName("main").output.classesDirs)
3333
mainClass.set("androidmakers.service.MainKt")
34+
debug = true
3435
}
3536

3637
configureDeploy("service", "androidmakers.service.MainKt")

service/graphql/schema.graphqls

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,11 @@ schema {
33
mutation: RootMutation
44
}
55

6+
"""
7+
A type representing a formatted kotlinx.datetime.Instant
8+
"""
9+
scalar GraphQLInstant
10+
611
scalar GraphQLLocalDate
712

813
scalar GraphQLLocalDateTime
@@ -57,12 +62,17 @@ type Conference {
5762
type FeedItem {
5863
id: ID!
5964

65+
createdAt: GraphQLInstant!
66+
6067
title: String!
6168

6269
markdown: Markdown!
6370
}
6471

65-
type FeedItemFailure
72+
type FeedItemFailure {
73+
message: String!
74+
}
75+
6676
type FeedItemSuccess {
6777
feedItem: FeedItem!
6878
}
@@ -260,8 +270,6 @@ type SpeakerConnection {
260270
type User {
261271
id: String!
262272

263-
email: String!
264-
265273
isAdmin: Boolean!
266274
}
267275

@@ -308,9 +316,9 @@ input ConferenceOrderBy {
308316
}
309317

310318
input FeedItemInput {
311-
title: String!
319+
title: String
312320

313-
markdown: Markdown!
321+
markdown: Markdown
314322
}
315323

316324
input SessionOrderBy {

service/index.yaml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
indexes:
2+
3+
- kind: FeedItems
4+
ancestor: yes
5+
properties:
6+
- name: createdAt
7+
direction: desc

service/src/main/kotlin/androidmakers/service/context/AuthenticationContext.kt

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,12 @@
11
package androidmakers.service.context
22

3-
import androidmakers.service.firebaseEmail
43
import com.apollographql.apollo.api.ExecutionContext
54

65
internal class AuthenticationContext(val uid: String?) : ExecutionContext.Element {
76
override val key: ExecutionContext.Key<*>
87
get() = Key
98

109
companion object Key : ExecutionContext.Key<AuthenticationContext>
11-
12-
val email: String? by lazy {
13-
uid?.let {
14-
firebaseEmail(it)
15-
}
16-
}
1710
}
1811

1912
internal fun ExecutionContext.uid() = this.get(AuthenticationContext)?.uid

service/src/main/kotlin/androidmakers/service/firebase.kt

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,6 @@ private fun ensureInitialized() {
2727
}
2828
}
2929
}
30-
fun firebaseEmail(uid: String): String {
31-
ensureInitialized()
32-
return FirebaseAuth.getInstance().getUser(uid).email
33-
}
3430

3531
fun String.firebaseUid(): FirebaseUidResult {
3632
if (this == "testToken") {

service/src/main/kotlin/androidmakers/service/graphql/model.kt

Lines changed: 65 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,10 @@
33
package androidmakers.service.graphql
44

55
import androidmakers.service.Sessionize
6-
import androidmakers.service.context.AuthenticationContext
7-
import androidmakers.service.context.bookmarksKeyFactory
8-
import androidmakers.service.context.datastore
9-
import androidmakers.service.context.uid
10-
import androidmakers.service.context.updateMaxAge
11-
import com.apollographql.apollo.annotations.*
6+
import androidmakers.service.context.*
7+
import com.apollographql.apollo.annotations.ApolloExperimental
128
import com.apollographql.apollo.api.ExecutionContext
9+
import com.apollographql.apollo.api.Optional
1310
import com.apollographql.apollo.execution.StringCoercing
1411
import com.apollographql.execution.annotation.GraphQLDefault
1512
import com.apollographql.execution.annotation.GraphQLMutation
@@ -19,8 +16,11 @@ import com.google.cloud.datastore.BooleanValue
1916
import com.google.cloud.datastore.Cursor
2017
import com.google.cloud.datastore.Entity
2118
import com.google.cloud.datastore.Query
22-
import com.google.rpc.context.AttributeContext
19+
import com.google.cloud.datastore.StructuredQuery
20+
import kotlinx.datetime.Instant
2321
import kotlinx.datetime.LocalDateTime
22+
import kotlinx.datetime.toInstant
23+
import kotlin.time.Clock
2424

2525
const val KIND_BOOKMARKS = "Bookmarks"
2626
const val KIND_FEED_ITEMS = "FeedItems"
@@ -35,11 +35,11 @@ typealias Markdown = String
3535
typealias ID = String
3636

3737
class FeedItemInput(
38-
val title: String,
39-
val markdown: Markdown
38+
val title: Optional<String?>,
39+
val markdown: Optional<Markdown?>,
4040
)
4141

42-
internal val adminEmails = setOf("martinbonninandroid@gmail.com", "reno.mathieu@gmail.com")
42+
internal val adminUids = setOf("AY7jNYS4EpNhRHBxW89LjomGCtl1")
4343

4444
sealed interface FeedItemResult
4545

@@ -50,32 +50,69 @@ data class FeedItemSuccess(
5050

5151
class FeedItem(
5252
val id: ID,
53+
val createdAt: GraphQLInstant,
5354
val title: String,
5455
val markdown: Markdown
5556
)
5657

5758
@GraphQLMutation
5859
class RootMutation {
5960
fun updateFeedItem(executionContext: ExecutionContext, id: ID, feedItem: FeedItemInput): FeedItemResult {
60-
TODO()
61-
}
62-
fun addFeedItem(executionContext: ExecutionContext, feedItem: FeedItemInput): FeedItemResult {
6361
val authenticationContext = executionContext.get(AuthenticationContext)!!
64-
val email = authenticationContext.email
65-
check(email != null) {
66-
"Adding to the feed requires authentication"
62+
if (authenticationContext.uid !in adminUids) {
63+
throw Error("Only admins can add feed items")
6764
}
6865

69-
if (email !in adminEmails) {
66+
val datastore = executionContext.datastore()
67+
val key = datastore.newKeyFactory().setKind(KIND_FEED_ITEMS).newKey(id.toLong())
68+
val entity = datastore.get(key)
69+
if (entity == null) {
70+
return FeedItemFailure("No item found for id $id")
71+
}
72+
val updatedEntity = Entity.newBuilder(entity)
73+
.apply {
74+
if (feedItem.title.getOrNull() != null) {
75+
set("title", feedItem.title.getOrThrow())
76+
}
77+
if (feedItem.markdown.getOrNull() != null) {
78+
set("markdown", feedItem.markdown.getOrThrow())
79+
}
80+
}
81+
.build()
82+
83+
val newEntity = datastore.put(updatedEntity)
84+
85+
return FeedItemSuccess(
86+
FeedItem(
87+
id = id,
88+
title = newEntity.getString("title"),
89+
markdown = newEntity.getString("markdown"),
90+
createdAt = Instant.parse(newEntity.getString("createdAt"))
91+
)
92+
)
93+
}
94+
95+
fun addFeedItem(executionContext: ExecutionContext, feedItem: FeedItemInput): FeedItemResult {
96+
val authenticationContext = executionContext.get(AuthenticationContext)!!
97+
if (authenticationContext.uid !in adminUids) {
7098
throw Error("Only admins can add feed items")
7199
}
72100

73101
val datastore = executionContext.datastore()
74102

103+
check(feedItem.title.getOrNull() != null) {
104+
"title is required"
105+
}
106+
check(feedItem.markdown.getOrNull() != null) {
107+
"markdown is required"
108+
}
75109
val key = datastore.newKeyFactory().setKind(KIND_FEED_ITEMS).newKey()
110+
val now = Clock.System.now()
111+
76112
val entity = Entity.newBuilder(key)!!
77-
.set("title", feedItem.title)
78-
.set("markdown", feedItem.markdown)
113+
.set("title", feedItem.title.getOrThrow())
114+
.set("markdown", feedItem.markdown.getOrThrow())
115+
.set("createdAt", now.toString())
79116
.build()
80117

81118
val result = datastore.runInTransaction {
@@ -84,9 +121,10 @@ class RootMutation {
84121

85122
return FeedItemSuccess(
86123
FeedItem(
87-
id = result.key.toString(),
88-
title = feedItem.title,
89-
markdown = feedItem.markdown
124+
id = result.key.id.toString(),
125+
title = result.getString("title"),
126+
markdown = result.getString("markdown"),
127+
createdAt = result.getString("createdAt").toInstant()
90128
)
91129
)
92130
}
@@ -298,6 +336,7 @@ class RootQuery {
298336

299337
val query = Query.newEntityQueryBuilder()
300338
.setKind(KIND_FEED_ITEMS)
339+
.addOrderBy(StructuredQuery.OrderBy.desc("createdAt"))
301340
.setLimit(first)
302341
.apply {
303342
if (after != null) {
@@ -312,9 +351,10 @@ class RootQuery {
312351
results.forEach { entity ->
313352
allItems.add(
314353
FeedItem(
315-
id = entity.key.toString(),
354+
id = entity.key.id.toString(),
316355
title = entity.getString("title"),
317-
markdown = entity.getString("markdown")
356+
markdown = entity.getString("markdown"),
357+
createdAt = entity.getString("createdAt").toInstant(),
318358
)
319359
)
320360
}
@@ -333,13 +373,12 @@ class RootQuery {
333373
if (authenticationContext.uid == null) {
334374
return null
335375
}
336-
return User(id = authenticationContext.uid, email = authenticationContext.email!!, isAdmin = authenticationContext.email in adminEmails)
376+
return User(id = authenticationContext.uid, isAdmin = authenticationContext.uid in adminUids)
337377
}
338378
}
339379

340380
class User(
341381
val id: String,
342-
val email: String,
343382
val isAdmin: Boolean
344383
)
345384

service/src/main/kotlin/androidmakers/service/graphql/scalars.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,3 +62,4 @@ object GraphQLLocalDateTimeCoercing : Coercing<LocalDateTime> {
6262
return internalValue.toString()
6363
}
6464
}
65+

0 commit comments

Comments
 (0)