diff --git a/Sources/src/main/kotlin/allin/Application.kt b/Sources/src/main/kotlin/allin/Application.kt index 209ec90..0bc84fa 100644 --- a/Sources/src/main/kotlin/allin/Application.kt +++ b/Sources/src/main/kotlin/allin/Application.kt @@ -38,7 +38,7 @@ val Application.dataSource: AllInDataSource get() = allInDataSource fun main() { - embeddedServer(Netty, port = 8080, host = "0.0.0.0") { + embeddedServer(Netty, port = 10001, host = "0.0.0.0") { extracted() }.start(wait = true) } @@ -85,6 +85,7 @@ private fun Application.extracted() { betRouter() participationRouter() betDetailRouter() + friendRouter() kronJob(BET_VERIFY_DELAY) { dataSource.betDataSource.updateBetStatuses(ZonedDateTime.now()) diff --git a/Sources/src/main/kotlin/allin/data/AllInDataSource.kt b/Sources/src/main/kotlin/allin/data/AllInDataSource.kt index a0861bb..4cf498b 100644 --- a/Sources/src/main/kotlin/allin/data/AllInDataSource.kt +++ b/Sources/src/main/kotlin/allin/data/AllInDataSource.kt @@ -4,4 +4,5 @@ abstract class AllInDataSource { abstract val userDataSource: UserDataSource abstract val betDataSource: BetDataSource abstract val participationDataSource: ParticipationDataSource + abstract val friendDataSource: FriendDataSource } \ No newline at end of file diff --git a/Sources/src/main/kotlin/allin/data/FriendDataSource.kt b/Sources/src/main/kotlin/allin/data/FriendDataSource.kt new file mode 100644 index 0000000..bb72df0 --- /dev/null +++ b/Sources/src/main/kotlin/allin/data/FriendDataSource.kt @@ -0,0 +1,8 @@ +package allin.data + +interface FriendDataSource { + fun addFriend(sender: String, receiver: String) + fun getFriendFromUserId(id: String): List + fun deleteFriend(senderId: String, receiverId: String): Boolean + fun isFriend(firstUser: String, secondUser: String): Boolean +} \ No newline at end of file diff --git a/Sources/src/main/kotlin/allin/data/mock/MockDataSource.kt b/Sources/src/main/kotlin/allin/data/mock/MockDataSource.kt index 5b93834..8dbf3f1 100644 --- a/Sources/src/main/kotlin/allin/data/mock/MockDataSource.kt +++ b/Sources/src/main/kotlin/allin/data/mock/MockDataSource.kt @@ -1,9 +1,6 @@ package allin.data.mock -import allin.data.AllInDataSource -import allin.data.BetDataSource -import allin.data.ParticipationDataSource -import allin.data.UserDataSource +import allin.data.* import allin.model.* import java.time.ZonedDateTime @@ -29,4 +26,5 @@ class MockDataSource : AllInDataSource() { override val userDataSource: UserDataSource by lazy { MockUserDataSource(mockData) } override val betDataSource: BetDataSource by lazy { MockBetDataSource(mockData) } override val participationDataSource: ParticipationDataSource by lazy { MockParticipationDataSource(mockData) } + override val friendDataSource: FriendDataSource by lazy { MockFriendDataSource(mockData) } } diff --git a/Sources/src/main/kotlin/allin/data/mock/MockFriendDataSource.kt b/Sources/src/main/kotlin/allin/data/mock/MockFriendDataSource.kt new file mode 100644 index 0000000..585d394 --- /dev/null +++ b/Sources/src/main/kotlin/allin/data/mock/MockFriendDataSource.kt @@ -0,0 +1,22 @@ +package allin.data.mock + +import allin.data.FriendDataSource + +class MockFriendDataSource(mockData: MockDataSource.MockData) : FriendDataSource { + override fun addFriend(sender: String, receiver: String) { + TODO("Not yet implemented") + } + + override fun getFriendFromUserId(id: String): List { + TODO("Not yet implemented") + } + + override fun deleteFriend(senderId: String, receiverId: String): Boolean { + TODO("Not yet implemented") + } + + override fun isFriend(firstUser: String, secondUser: String): Boolean { + TODO("Not yet implemented") + } + +} \ No newline at end of file diff --git a/Sources/src/main/kotlin/allin/data/postgres/PostgresDataSource.kt b/Sources/src/main/kotlin/allin/data/postgres/PostgresDataSource.kt index 1da7d66..3e08eff 100644 --- a/Sources/src/main/kotlin/allin/data/postgres/PostgresDataSource.kt +++ b/Sources/src/main/kotlin/allin/data/postgres/PostgresDataSource.kt @@ -1,9 +1,6 @@ package allin.data.postgres -import allin.data.AllInDataSource -import allin.data.BetDataSource -import allin.data.ParticipationDataSource -import allin.data.UserDataSource +import allin.data.* import allin.ext.execute import org.ktorm.database.Database @@ -117,9 +114,20 @@ class PostgresDataSource : AllInDataSource() { ) """.trimIndent() ) + + database.execute( + """ + CREATE TABLE IF NOT EXISTS friend( + sender VARCHAR(255), + receiver VARCHAR(255), + CONSTRAINT pk_friend PRIMARY KEY (sender,receiver) + ) + """.trimIndent() + ) } override val userDataSource: UserDataSource by lazy { PostgresUserDataSource(database) } override val betDataSource: BetDataSource by lazy { PostgresBetDataSource(database) } override val participationDataSource: ParticipationDataSource by lazy { PostgresParticipationDataSource(database) } + override val friendDataSource: FriendDataSource by lazy { PostgresFriendDataSource(database) } } \ No newline at end of file diff --git a/Sources/src/main/kotlin/allin/data/postgres/PostgresFriendDataSource.kt b/Sources/src/main/kotlin/allin/data/postgres/PostgresFriendDataSource.kt new file mode 100644 index 0000000..cbfd179 --- /dev/null +++ b/Sources/src/main/kotlin/allin/data/postgres/PostgresFriendDataSource.kt @@ -0,0 +1,42 @@ +package allin.data.postgres + +import allin.data.FriendDataSource +import allin.data.postgres.entities.FriendEntity +import allin.data.postgres.entities.friends +import org.ktorm.database.Database +import org.ktorm.dsl.and +import org.ktorm.dsl.eq +import org.ktorm.entity.add +import org.ktorm.entity.filter +import org.ktorm.entity.map +import org.ktorm.entity.removeIf + +class PostgresFriendDataSource(private val database: Database) : FriendDataSource { + override fun addFriend(sender: String, receiver: String) { + database.friends.add( + FriendEntity { + this.sender = sender + this.receiver = receiver + } + ) + } + + override fun getFriendFromUserId(id: String): List { + val friendList = database.friends.map { it.toFriend() } + val friendPairs = friendList.map { it.sender to it.receiver }.toSet() + return friendList + .filter { it.sender == id } + .map { it.receiver } + } + + override fun deleteFriend(senderId: String, receiverId: String): Boolean { + database.friends.removeIf { it.sender eq senderId } + return database.friends.removeIf { it.sender eq senderId } > 0 + } + + override fun isFriend(firstUser: String, secondUser: String) = + database.friends + .filter { (it.sender eq firstUser) and (it.receiver eq secondUser) } + .map { it.toFriend() } + .isNotEmpty() +} \ No newline at end of file diff --git a/Sources/src/main/kotlin/allin/data/postgres/entities/FriendEntity.kt b/Sources/src/main/kotlin/allin/data/postgres/entities/FriendEntity.kt new file mode 100644 index 0000000..5ef4920 --- /dev/null +++ b/Sources/src/main/kotlin/allin/data/postgres/entities/FriendEntity.kt @@ -0,0 +1,29 @@ +package allin.data.postgres.entities + +import allin.model.Friend +import org.ktorm.database.Database +import org.ktorm.entity.Entity +import org.ktorm.entity.sequenceOf +import org.ktorm.schema.Table +import org.ktorm.schema.varchar + +interface FriendEntity : Entity { + companion object : Entity.Factory() + + var sender: String + var receiver: String + + fun toFriend() = + Friend( + sender = sender, + receiver = receiver, + ) + +} + +object FriendsEntity : Table("friend") { + val sender = varchar("sender").primaryKey().bindTo { it.sender } + val receiver = varchar("receiver").primaryKey().bindTo { it.receiver } +} + +val Database.friends get() = this.sequenceOf(FriendsEntity) diff --git a/Sources/src/main/kotlin/allin/model/ApiMessage.kt b/Sources/src/main/kotlin/allin/model/ApiMessage.kt index a09c1e1..2d1a045 100644 --- a/Sources/src/main/kotlin/allin/model/ApiMessage.kt +++ b/Sources/src/main/kotlin/allin/model/ApiMessage.kt @@ -13,4 +13,7 @@ object ApiMessage { const val NOT_ENOUGH_COINS = "Not enough coins." const val NO_GIFT = "Can't get daily gift." const val USER_CANT_BE_DELETE = "This user can't be delete now !" + const val FRIENDS_ALREADY_EXISTS = "User already exists in your Friends List." + const val FRIENDS_DOESNT_EXISTS = "User already exists in your Friends List." + } \ No newline at end of file diff --git a/Sources/src/main/kotlin/allin/model/Friend.kt b/Sources/src/main/kotlin/allin/model/Friend.kt new file mode 100644 index 0000000..a9efe26 --- /dev/null +++ b/Sources/src/main/kotlin/allin/model/Friend.kt @@ -0,0 +1,7 @@ +package allin.model + + +data class Friend( + val sender: String, + val receiver: String +) \ No newline at end of file diff --git a/Sources/src/main/kotlin/allin/routing/friendRouter.kt b/Sources/src/main/kotlin/allin/routing/friendRouter.kt new file mode 100644 index 0000000..c1ad79b --- /dev/null +++ b/Sources/src/main/kotlin/allin/routing/friendRouter.kt @@ -0,0 +1,114 @@ +package allin.routing + +import allin.dataSource +import allin.ext.hasToken +import allin.model.ApiMessage +import io.github.smiley4.ktorswaggerui.dsl.post +import io.ktor.http.* + +import io.ktor.server.application.* +import io.ktor.server.auth.* +import io.ktor.server.auth.jwt.* +import io.ktor.server.request.* +import io.ktor.server.response.* +import io.ktor.server.routing.* + +fun Application.friendRouter() { + + val userDataSource = this.dataSource.userDataSource + val friendDataSource = this.dataSource.friendDataSource + + + routing { + authenticate { + + post("/friend/add", { + description = "Allows a user to add a friend" + request { + headerParameter("JWT token of the logged user") + body { + description = "User to add in the friends list" + } + } + response { + HttpStatusCode.Created to { + description = "the friend has been added" + body() { + description = "Friend with assigned id" + } + } + HttpStatusCode.Conflict to { + description = "Friend already exist in the friends list" + body(ApiMessage.FRIENDS_ALREADY_EXISTS) + } + } + }) { + hasToken { principal -> + val requestMap = call.receive>() + val usernameFriend = requestMap["username"] ?: return@hasToken call.respond(HttpStatusCode.BadRequest, "Username is missing") + val username = tokenManagerBet.getUsernameFromToken(principal) + + val user = userDataSource.getUserByUsername(username).first + val userFriend = userDataSource.getUserByUsername(usernameFriend).first + + if (user == null || userFriend == null) { + call.respond(HttpStatusCode.Conflict, ApiMessage.USER_NOT_FOUND) + } else { + val friendlist = friendDataSource.getFriendFromUserId(user.id) + if (friendlist.contains(userFriend.id)) { + call.respond(HttpStatusCode.Conflict,ApiMessage.FRIENDS_ALREADY_EXISTS) + } else { + friendDataSource.addFriend(user.id, userFriend.id) + call.respond(HttpStatusCode.Created, usernameFriend) + } + } + } + + } + post("/friend/delete", { + description = "Allows a user to delete a friend" + request { + headerParameter("JWT token of the logged user") + body { + description = "User to delete in the friends list" + } + } + response { + HttpStatusCode.Created to { + description = "the friend has been delete" + body() { + description = "Friend with assigned id" + } + } + HttpStatusCode.Conflict to { + description = "Friend doesn't exist in the friends list" + body(ApiMessage.FRIENDS_DOESNT_EXISTS) + } + } + }) { + hasToken { principal -> + val requestMap = call.receive>() + val usernameFriend = requestMap["username"] ?: return@hasToken call.respond(HttpStatusCode.BadRequest, "Username is missing") + val username = tokenManagerBet.getUsernameFromToken(principal) + + val user = userDataSource.getUserByUsername(username).first + val userFriend = userDataSource.getUserByUsername(usernameFriend).first + + if (user == null || userFriend == null) { + call.respond(HttpStatusCode.Conflict, ApiMessage.USER_NOT_FOUND) + } else { + val friendlist = friendDataSource.getFriendFromUserId(user.id) + if (!friendlist.contains(userFriend.id)) { + call.respond(HttpStatusCode.Conflict,ApiMessage.FRIENDS_DOESNT_EXISTS) + } else { + friendDataSource.deleteFriend(user.id, userFriend.id) + call.respond(HttpStatusCode.Created, usernameFriend) + } + } + } + + } + + } + } +} \ No newline at end of file