Skip to content

Commit

Permalink
Use UUID as a public-facing MeetingIntent code
Browse files Browse the repository at this point in the history
  • Loading branch information
balysv committed Jul 14, 2021
1 parent c5080b2 commit 21b21b0
Show file tree
Hide file tree
Showing 15 changed files with 219 additions and 81 deletions.
5 changes: 5 additions & 0 deletions app/src/main/java/com/sama/acl/ResourceAccessService.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.sama.acl

import com.sama.meeting.domain.MeetingIntentCode
import com.sama.meeting.domain.MeetingIntentId
import com.sama.meeting.domain.repositories.MeetingIntentRepository
import com.sama.users.domain.UserId
Expand All @@ -13,4 +14,8 @@ class ResourceAccessService(private val meetingIntentRepository: MeetingIntentRe
fun hasAccess(userId: UserId, meetingIntentId: MeetingIntentId): Boolean {
return meetingIntentRepository.existsByIdAndInitiatorId(meetingIntentId, userId)
}

fun hasAccessByCode(userId: UserId, meetingIntentCode: MeetingIntentCode): Boolean {
return meetingIntentRepository.existsByCodeAndInitiatorId(meetingIntentCode, userId)
}
}
24 changes: 18 additions & 6 deletions app/src/main/java/com/sama/api/meeting/MeetingController.kt
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
package com.sama.api.meeting

import com.sama.api.config.AuthUserId
import com.sama.meeting.application.ConfirmMeetingCommand
import com.sama.meeting.application.InitiateMeetingCommand
import com.sama.meeting.application.MeetingApplicationService
import com.sama.meeting.application.ProposeMeetingCommand
import com.sama.meeting.application.*
import com.sama.meeting.domain.MeetingCode
import com.sama.meeting.domain.MeetingIntentId
import com.sama.users.domain.UserId
Expand All @@ -30,7 +27,7 @@ class MeetingController(
consumes = [APPLICATION_JSON_VALUE],
produces = [APPLICATION_JSON_VALUE],
)
fun initiateMeeting(@AuthUserId userId: UserId, @RequestBody @Valid command: InitiateMeetingCommand) =
fun initiateMeeting(@AuthUserId userId: UserId, @RequestBody @Valid command: InitiateMeetingCommand) =
meetingApplicationService.initiateMeeting(userId, command)

@Operation(
Expand All @@ -39,14 +36,29 @@ class MeetingController(
)
@PostMapping(
"/api/meeting/{meetingIntentId}/propose",
consumes = [APPLICATION_JSON_VALUE]
consumes = [APPLICATION_JSON_VALUE],
produces = [APPLICATION_JSON_VALUE]
)
fun proposeMeeting(
@AuthUserId userId: UserId,
@PathVariable meetingIntentId: MeetingIntentId,
@RequestBody command: ProposeMeetingCommand
) = meetingApplicationService.proposeMeeting(userId, meetingIntentId, command)

@Operation(
summary = "Propose a meeting with a slot selection",
security = [SecurityRequirement(name = "user-auth")]
)
@PostMapping(
"/api/meeting/propose",
consumes = [APPLICATION_JSON_VALUE],
produces = [APPLICATION_JSON_VALUE]
)
fun proposeMeetingV2(
@AuthUserId userId: UserId,
@RequestBody command: ProposeMeetingCommandV2
) = meetingApplicationService.proposeMeeting(userId, command)


@Operation(summary = "Retrieve meeting proposal details using a shared meeting code")
@GetMapping(
Expand Down
1 change: 0 additions & 1 deletion app/src/main/java/com/sama/common/Extensions.kt
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package com.sama.common

import liquibase.pro.packaged.T
import liquibase.pro.packaged.li
import org.springframework.data.repository.CrudRepository
import java.time.Duration
import java.util.*
Expand Down
2 changes: 2 additions & 0 deletions app/src/main/java/com/sama/meeting/application/Commands.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.sama.meeting.application

import com.sama.meeting.domain.MeetingIntentCode
import java.time.ZoneId
import javax.validation.constraints.Min

Expand All @@ -15,4 +16,5 @@ data class InitiateMeetingCommand(
)

data class ProposeMeetingCommand(val proposedSlots: List<MeetingSlotDTO>)
data class ProposeMeetingCommandV2(val meetingIntentCode: MeetingIntentCode, val proposedSlots: List<MeetingSlotDTO>)
data class ConfirmMeetingCommand(val slot: MeetingSlotDTO, val recipientEmail: String)
20 changes: 5 additions & 15 deletions app/src/main/java/com/sama/meeting/application/DTOs.kt
Original file line number Diff line number Diff line change
@@ -1,27 +1,25 @@
package com.sama.meeting.application

import com.sama.meeting.domain.MeetingId
import com.sama.meeting.domain.MeetingIntentCode
import com.sama.meeting.domain.MeetingIntentId
import com.sama.meeting.domain.MeetingSlot
import com.sama.meeting.domain.aggregates.MeetingIntentEntity
import com.sama.meeting.domain.aggregates.MeetingSuggestedSlotEntity
import com.sama.users.domain.UserEntity
import com.sama.users.domain.UserId
import java.time.ZonedDateTime

fun MeetingIntentEntity.toDTO(): MeetingIntentDTO {
return MeetingIntentDTO(
this.id!!,
this.initiatorId!!,
this.toRecipientDTO(),
this.code!!,
this.durationMinutes!!,
this.suggestedSlots.map { it.toDTO() }
)
}

data class MeetingIntentDTO(
val meetingIntentId: MeetingId,
val initiatorId: UserId,
val recipient: RecipientDTO,
val meetingIntentId: MeetingIntentId,
val meetingIntentCode: MeetingIntentCode,
val durationMinutes: Long,
val suggestedSlots: List<MeetingSlotDTO>,
)
Expand All @@ -39,18 +37,10 @@ data class MeetingSlotDTO(
val endDateTime: ZonedDateTime
)

fun MeetingIntentEntity.toRecipientDTO(): RecipientDTO {
return RecipientDTO(null)
}

fun MeetingSlotDTO.toValueObject(): MeetingSlot {
return MeetingSlot(this.startDateTime, this.endDateTime)
}

data class RecipientDTO(
val email: String?
)

fun UserEntity.toInitiatorDTO(): InitiatorDTO {
return InitiatorDTO(this.fullName, this.email)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,25 +41,20 @@ class MeetingApplicationService(
fun initiateMeeting(userId: UserId, command: InitiateMeetingCommand): MeetingIntentDTO {
val meetingId = meetingIntentRepository.nextIdentity()

val suggestedSlots = when (command.suggestionSlotCount) {
0 -> emptyList()
else -> {
val request = command.toSlotSuggestionRequest()
slotSuggestionService.suggestSlots(userId, request).suggestions
.map { MeetingSlot(it.startDateTime, it.endDateTime) }
}
}
val request = command.toSlotSuggestionRequest()
val suggestedSlots = slotSuggestionService.suggestSlots(userId, request).suggestions
.map { MeetingSlot(it.startDateTime, it.endDateTime) }

val meeting = MeetingIntent(
val meetingIntent = MeetingIntent(
meetingId,
userId,
command.durationMinutes.toMinutes(),
command.timeZone,
suggestedSlots
)

val meetingEntity = MeetingIntentEntity.new(meeting).also { meetingIntentRepository.save(it) }
return meetingEntity.toDTO()
val entity = MeetingIntentEntity.new(meetingIntent).also { meetingIntentRepository.save(it) }
return entity.toDTO()
}

private fun InitiateMeetingCommand.toSlotSuggestionRequest(): SlotSuggestionRequest {
Expand All @@ -71,10 +66,12 @@ class MeetingApplicationService(
)
}

@Transactional(readOnly = true)
fun findMeetingIntent(userId: UserId, meetingIntentId: MeetingIntentId): MeetingIntentDTO {
val meetingEntity = meetingIntentRepository.findByIdOrThrow(meetingIntentId)
return meetingEntity.toDTO()
@Transactional
@PreAuthorize("@auth.hasAccessByCode(#userId, #command.meetingIntentCode)")
fun proposeMeeting(userId: UserId, command: ProposeMeetingCommandV2): MeetingInvitationDTO {
val meetingIntentEntity = meetingIntentRepository.findByCodeOrThrow(command.meetingIntentCode)
val proposedSlots = command.proposedSlots.map { it.toValueObject() }
return proposeMeeting(meetingIntentEntity, proposedSlots)
}

@Transactional
Expand All @@ -84,13 +81,20 @@ class MeetingApplicationService(
meetingIntentId: MeetingIntentId,
command: ProposeMeetingCommand
): MeetingInvitationDTO {
val meetingEntity = meetingIntentRepository.findByIdOrThrow(meetingIntentId)
val meetingIntentEntity = meetingIntentRepository.findByIdOrThrow(meetingIntentId)
val proposedSlots = command.proposedSlots.map { it.toValueObject() }
return proposeMeeting(meetingIntentEntity, proposedSlots)
}

private fun proposeMeeting(
meetingIntentEntity: MeetingIntentEntity,
proposedSlots: List<MeetingSlot>
): MeetingInvitationDTO {
val meetingId = meetingRepository.nextIdentity()

val meetingCode = meetingCodeGenerator.generate()
val proposedSlots = command.proposedSlots.map { it.toValueObject() }

val meetingIntent = MeetingIntent.of(meetingEntity).getOrThrow()
val meetingIntent = MeetingIntent.of(meetingIntentEntity).getOrThrow()
val proposedMeeting = meetingIntent
.propose(meetingId, meetingCode, proposedSlots)
.getOrThrow()
Expand Down
3 changes: 3 additions & 0 deletions app/src/main/java/com/sama/meeting/domain/Types.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
package com.sama.meeting.domain

import java.util.*

typealias MeetingIntentId = Long
typealias MeetingIntentCode = UUID
typealias MeetingId = Long
typealias MeetingCode = String
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import org.springframework.data.jpa.convert.threeten.Jsr310JpaConverters
import java.time.Instant
import java.time.ZoneId
import java.time.ZonedDateTime
import java.util.*
import javax.persistence.*

@AggregateRoot
Expand All @@ -24,6 +25,7 @@ class MeetingIntentEntity {
fun new(meetingIntent: MeetingIntent): MeetingIntentEntity {
val meetingEntity = MeetingIntentEntity()
meetingEntity.id = meetingIntent.meetingIntentId
meetingEntity.code = UUID.randomUUID()
meetingEntity.initiatorId = meetingIntent.initiatorId
meetingEntity.durationMinutes = meetingIntent.duration.toMinutes()
meetingEntity.timezone = meetingIntent.timezone
Expand All @@ -46,6 +48,9 @@ class MeetingIntentEntity {
@Id
var id: MeetingIntentId? = null

@Column(nullable = false)
var code: UUID? = null

@Column(nullable = false)
var initiatorId: UserId? = null

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package com.sama.meeting.domain.repositories

import com.sama.common.NotFoundException
import com.sama.meeting.domain.MeetingIntentCode
import com.sama.meeting.domain.MeetingIntentId
import com.sama.meeting.domain.aggregates.MeetingIntentEntity
import com.sama.users.domain.UserId
Expand All @@ -12,5 +14,12 @@ interface MeetingIntentRepository : JpaRepository<MeetingIntentEntity, MeetingIn
@Query("select nextval('sama.meeting_intent_id_seq')", nativeQuery = true)
fun nextIdentity(): MeetingIntentId

fun findByCode(code: MeetingIntentCode): MeetingIntentEntity?

fun existsByIdAndInitiatorId(meetingIntentId: MeetingIntentId, initiatorId: UserId): Boolean
}

fun existsByCodeAndInitiatorId(code: MeetingIntentCode, initiatorId: UserId): Boolean
}

fun MeetingIntentRepository.findByCodeOrThrow(code: MeetingIntentCode): MeetingIntentEntity = findByCode(code)
?: throw NotFoundException(MeetingIntentEntity::class, "code", code)
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ data class SlotSuggestionEngine(
duration: Duration,
count: Int
): List<SlotSuggestion> {
if (count == 0) {
return emptyList()
}

val durationLength = ceil(duration.toMinutes().toDouble() / intervalMinutes).toInt()
val multiDayWeights = listOf(
searchBoundary(startDate, endDate, suggestionDayCount, initiatorTimeZone),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
databaseChangeLog:
- property:
name: uuid_function
value: uuid_generate_v4()
dbms: postgresql

- changeSet:
id: add-uuid-extension
author: balys
changes:
- sql:
sql: CREATE EXTENSION IF NOT EXISTS "uuid-ossp";

- changeSet:
id: add-meeting-intent-public-id
author: balys
changes:
- addColumn:
schemaName: sama
tableName: meeting_intent
columns:
- column:
name: code
type: UUID
defaultValueComputed: ${uuid_function}
constraints:
nullable: false
- createIndex:
schemaName: sama
tableName: meeting_intent
columns:
- column:
name: code
indexName: IDX_code


2 changes: 2 additions & 0 deletions app/src/main/resources/liquibase/db.changelog-sama.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,5 @@ databaseChangeLog:
file: classpath*:liquibase/change-sets/20210704090000-add-proposed-slot-index.yml
- include:
file: classpath*:liquibase/change-sets/20210710090000-add-user-name.yml
- include:
file: classpath*:liquibase/change-sets/20210713090000-add-meeting-intent-public-id.yml
Loading

0 comments on commit 21b21b0

Please sign in to comment.