mirror of
https://github.com/jlengrand/geospatial-messenger.git
synced 2026-03-10 08:21:17 +00:00
Switch on Exposed
This commit is contained in:
@@ -37,6 +37,7 @@ dependencies {
|
||||
compile('org.springframework.boot:spring-boot-starter-jdbc')
|
||||
compile('org.springframework.boot:spring-boot-devtools')
|
||||
compile('cz.jirutka.spring:spring-data-jdbc-repository:0.5.1')
|
||||
compile('org.jetbrains.exposed:exposed:0.4-SNAPSHOT')
|
||||
|
||||
compile("org.jetbrains.kotlin:kotlin-stdlib:${kotlinVersion}")
|
||||
// Always use the latest "dash revision", here -2
|
||||
|
||||
@@ -2,43 +2,44 @@ package io.spring.messenger.config
|
||||
|
||||
import io.spring.messenger.domain.Message
|
||||
import io.spring.messenger.domain.User
|
||||
import io.spring.messenger.repository.MessageRepository
|
||||
import io.spring.messenger.repository.UserRepository
|
||||
import io.spring.messenger.repository.*
|
||||
import org.jetbrains.exposed.sql.Database
|
||||
import org.jetbrains.exposed.sql.StdOutSqlLogger
|
||||
import org.jetbrains.exposed.sql.exists
|
||||
import org.springframework.boot.CommandLineRunner
|
||||
import javax.sql.DataSource
|
||||
|
||||
import org.springframework.context.annotation.Bean
|
||||
import org.springframework.context.annotation.Configuration
|
||||
import org.springframework.jdbc.datasource.DataSourceTransactionManager
|
||||
import org.springframework.transaction.PlatformTransactionManager
|
||||
import org.springframework.transaction.annotation.EnableTransactionManagement
|
||||
|
||||
@EnableTransactionManagement
|
||||
@Configuration
|
||||
open class DatabaseConfig {
|
||||
|
||||
@Bean
|
||||
open fun transactionManager(dataSource: DataSource): PlatformTransactionManager {
|
||||
return DataSourceTransactionManager(dataSource)
|
||||
}
|
||||
open fun db(dataSource: DataSource) = Database.connect(dataSource)
|
||||
|
||||
@Bean
|
||||
open fun init(userRepository: UserRepository, messageRepository: MessageRepository) = CommandLineRunner {
|
||||
open fun init(db: Database, userRepository: UserRepository, messageRepository: MessageRepository) = CommandLineRunner {
|
||||
val swhite = User("swhite", "Skyler", "White")
|
||||
val jpinkman = User("jpinkman", "Jesse", "Pinkman")
|
||||
val walter = User("wwhite", "Walter", "White")
|
||||
val sgoodman = User("sgoodman", "Saul", "Goodman")
|
||||
|
||||
db.transaction {
|
||||
logger.addLogger(StdOutSqlLogger())
|
||||
|
||||
if(userRepository.count() == 0L) {
|
||||
userRepository.create(swhite)
|
||||
userRepository.create(jpinkman)
|
||||
userRepository.create(walter)
|
||||
userRepository.create(sgoodman)
|
||||
}
|
||||
if(!Users.exists()) {
|
||||
create(Users)
|
||||
userRepository.create(swhite)
|
||||
userRepository.create(jpinkman)
|
||||
userRepository.create(walter)
|
||||
userRepository.create(sgoodman)
|
||||
}
|
||||
|
||||
if(messageRepository.count() == 0L) {
|
||||
messageRepository.create(Message("This is a test!", swhite.userName))
|
||||
if(!Messages.exists()) {
|
||||
create(Messages)
|
||||
messageRepository.create(Message("This is a test!", swhite.userName))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,5 +7,6 @@ data class Message(
|
||||
var content: String,
|
||||
var author: String,
|
||||
var location: Point? = null,
|
||||
@Id var id: Long? = null
|
||||
)
|
||||
@Id var id: Int? = null
|
||||
)
|
||||
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
package io.spring.messenger.domain
|
||||
|
||||
import org.postgis.Point
|
||||
import org.springframework.data.annotation.Id
|
||||
|
||||
data class MessageWithAuthor(
|
||||
var content: String,
|
||||
var author: User,
|
||||
var location: Point? = null,
|
||||
@Id var id: Long? = null
|
||||
)
|
||||
@@ -1,44 +1,36 @@
|
||||
package io.spring.messenger.repository
|
||||
|
||||
import com.nurkiewicz.jdbcrepository.JdbcRepository
|
||||
import com.nurkiewicz.jdbcrepository.RowUnmapper
|
||||
import com.nurkiewicz.jdbcrepository.TableDescription
|
||||
import io.spring.messenger.domain.Message
|
||||
import org.jetbrains.exposed.sql.*
|
||||
import org.jetbrains.exposed.sql.statements.UpdateBuilder
|
||||
import org.postgis.PGbox2d
|
||||
import org.postgis.PGgeometry
|
||||
import org.postgis.Point
|
||||
import org.springframework.jdbc.core.RowMapper
|
||||
import org.springframework.beans.factory.annotation.Autowired
|
||||
import org.springframework.stereotype.Repository
|
||||
|
||||
@Repository
|
||||
open class MessageRepository : JdbcRepository<Message, Long>(mapper(), unmapper(), TableDescription("messages", "id")) {
|
||||
open class MessageRepository @Autowired constructor(val db: Database) {
|
||||
|
||||
open fun findByBoundingBox(box: PGbox2d): List<Message>
|
||||
= jdbcOperations.query("""SELECT * FROM ${table.name}
|
||||
WHERE location &&
|
||||
ST_MakeEnvelope(${box.llb.x}, ${box.llb.y}, ${box.urt.x}, ${box.urt.y}
|
||||
, 4326)""", rowMapper)
|
||||
|
||||
override fun <S : Message> postCreate(entity: S, generatedId: Number?): S {
|
||||
if (generatedId != null) entity.id = generatedId.toLong()
|
||||
return entity
|
||||
open fun create(m: Message) = db.transaction {
|
||||
m.id = Messages.insert(map(m)).get(Messages.id)
|
||||
m
|
||||
}
|
||||
}
|
||||
|
||||
private fun mapper() = RowMapper<Message> {
|
||||
rs, rowNum -> Message(
|
||||
rs.getString("content"),
|
||||
rs.getString("author"),
|
||||
(rs.getObject("location") as PGgeometry?)?.geometry as Point?,
|
||||
rs.getLong("id"))
|
||||
}
|
||||
open fun findAll() = db.transaction {
|
||||
unmap(Messages.selectAll())
|
||||
}
|
||||
|
||||
private fun unmapper() = RowUnmapper<Message> {
|
||||
m ->
|
||||
val map = mutableMapOf(Pair("id", m.id), Pair("content", m.content), Pair("author", m.author))
|
||||
if (m.location != null) {
|
||||
m.location!!.srid = 4326
|
||||
map["location"] = PGgeometry(m.location)
|
||||
}
|
||||
map
|
||||
}
|
||||
open fun findByBoundingBox(box: PGbox2d) = db.transaction {
|
||||
unmap(Messages.select { Users.location within box })
|
||||
}
|
||||
|
||||
private fun map(m: Message): Messages.(UpdateBuilder<*>) -> Unit = {
|
||||
if (m.id != null) it[id] = m.id
|
||||
it[content] = m.content
|
||||
it[author] = m.author
|
||||
it[location] = m.location
|
||||
}
|
||||
|
||||
private fun unmap(rows: SizedIterable<ResultRow>): List<Message> =
|
||||
rows.map { Message(it[Messages.content], it[Messages.author], it[Messages.location], it[Messages.id]) }
|
||||
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package io.spring.messenger.repository
|
||||
|
||||
import org.jetbrains.exposed.sql.*
|
||||
import org.postgis.PGbox2d
|
||||
import org.postgis.PGgeometry
|
||||
import org.postgis.Point
|
||||
|
||||
fun Table.point(name: String, srid: Int = 4326): Column<Point> = registerColumn(name, PointColumnType())
|
||||
|
||||
class PointColumnType(val srid: Int = 4326): ColumnType() {
|
||||
|
||||
override fun sqlType(): String = "GEOMETRY(Point, $srid)"
|
||||
|
||||
override fun valueFromDB(value: Any): Any {
|
||||
if (value is PGgeometry) {
|
||||
return value.geometry
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
override fun notNullValueToDB(value: Any): Any {
|
||||
if (value is Point) {
|
||||
if (value.srid == Point.UNKNOWN_SRID) value.srid = srid
|
||||
return PGgeometry(value)
|
||||
}
|
||||
return value
|
||||
}
|
||||
}
|
||||
|
||||
infix fun ExpressionWithColumnType<*>.within(box: PGbox2d) : Op<Boolean> {
|
||||
return WithinOp(this, box)
|
||||
}
|
||||
|
||||
class WithinOp(val expr1: Expression<*>, val box: PGbox2d) : Op<Boolean>() {
|
||||
|
||||
override fun toSQL(queryBuilder: QueryBuilder) = """${expr1.toSQL(queryBuilder)}
|
||||
&& ST_MakeEnvelope(${box.llb.x}, ${box.llb.y}, ${box.urt.x}, ${box.urt.y}, 4326)"""
|
||||
}
|
||||
|
||||
17
src/main/kotlin/io/spring/messenger/repository/Tables.kt
Normal file
17
src/main/kotlin/io/spring/messenger/repository/Tables.kt
Normal file
@@ -0,0 +1,17 @@
|
||||
package io.spring.messenger.repository
|
||||
|
||||
import org.jetbrains.exposed.sql.Table
|
||||
|
||||
object Messages : Table() {
|
||||
val id = integer("id").autoIncrement().primaryKey()
|
||||
val content = text("content")
|
||||
val author = reference("author", Users.userName)
|
||||
val location = point("location").nullable()
|
||||
}
|
||||
|
||||
object Users : Table() {
|
||||
val userName = text("user_name").primaryKey()
|
||||
val firstName = text("first_name")
|
||||
val lastName = text("last_name")
|
||||
val location = point("location").nullable()
|
||||
}
|
||||
@@ -1,47 +1,42 @@
|
||||
package io.spring.messenger.repository
|
||||
|
||||
import com.nurkiewicz.jdbcrepository.JdbcRepository
|
||||
import com.nurkiewicz.jdbcrepository.RowUnmapper
|
||||
import io.spring.messenger.domain.User
|
||||
import org.jetbrains.exposed.sql.*
|
||||
import org.jetbrains.exposed.sql.statements.UpdateBuilder
|
||||
import org.postgis.PGbox2d
|
||||
import org.postgis.PGgeometry
|
||||
import org.postgis.Point
|
||||
import org.springframework.jdbc.core.RowMapper
|
||||
import org.springframework.beans.factory.annotation.Autowired
|
||||
import org.springframework.stereotype.Repository
|
||||
|
||||
@Repository
|
||||
open class UserRepository : JdbcRepository<User, String>(mapper(), unmapper(), "\"users\"", "user_name") {
|
||||
open class UserRepository @Autowired constructor(val db: Database) {
|
||||
|
||||
open fun updateLocation(userName:String, location: Point): Unit {
|
||||
open fun updateLocation(userName:String, location: Point) = db.transaction {
|
||||
logger.addLogger(StdOutSqlLogger())
|
||||
location.srid = 4326
|
||||
jdbcOperations.update("UPDATE ${table.name} SET location = '${PGgeometry(location)}' WHERE user_name = '$userName'")
|
||||
Users.update({Users.userName eq userName}) { it[Users.location] = location}
|
||||
}
|
||||
|
||||
open fun findByBoundingBox(box: PGbox2d): List<User>
|
||||
= jdbcOperations.query("""SELECT * FROM ${table.name}
|
||||
WHERE location &&
|
||||
ST_MakeEnvelope(${box.llb.x}, ${box.llb.y}, ${box.urt.x}, ${box.urt.y}
|
||||
, 4326)""", rowMapper)
|
||||
open fun create(user: User) = db.transaction {
|
||||
Users.insert( map(user) )
|
||||
}
|
||||
|
||||
}
|
||||
open fun findAll() = db.transaction {
|
||||
unmap(Users.selectAll())
|
||||
}
|
||||
|
||||
private fun mapper() = RowMapper<User> {
|
||||
rs, rowNum -> User(
|
||||
rs.getString("user_name"),
|
||||
rs.getString("first_name"),
|
||||
rs.getString("last_name"),
|
||||
(rs.getObject("location") as PGgeometry?)?.geometry as Point?)
|
||||
}
|
||||
open fun findByBoundingBox(box: PGbox2d) = db.transaction {
|
||||
unmap(Users.select { Users.location within box })
|
||||
}
|
||||
|
||||
private fun map(u: User): Users.(UpdateBuilder<*>) -> Unit = {
|
||||
it[userName] = u.userName
|
||||
it[firstName] = u.firstName
|
||||
it[lastName] = u.lastName
|
||||
it[location] = u.location
|
||||
}
|
||||
|
||||
private fun unmap(rows: SizedIterable<ResultRow>): List<User> =
|
||||
rows.map { User(it[Users.userName], it[Users.firstName], it[Users.lastName], it[Users.location]) }
|
||||
|
||||
private fun unmapper() = RowUnmapper<User> {
|
||||
user ->
|
||||
val map = mutableMapOf<String, Any>(
|
||||
Pair("user_name", user.userName),
|
||||
Pair("first_name", user.firstName),
|
||||
Pair("last_name", user.lastName))
|
||||
if (user.location != null) {
|
||||
user.location!!.srid = 4326
|
||||
map["location"] = PGgeometry(user.location)
|
||||
}
|
||||
map;
|
||||
}
|
||||
@@ -21,8 +21,9 @@ class MessageController @Autowired constructor(val repository: MessageRepository
|
||||
fun findMessages() = repository.findAll()
|
||||
|
||||
@GetMapping("/bbox/{xMin},{yMin},{xMax},{yMax}")
|
||||
fun findByBoundingBox(@PathVariable userName:String, @PathVariable xMin:Double,
|
||||
@PathVariable yMin:Double, @PathVariable xMax:Double, @PathVariable yMax:Double)
|
||||
fun findByBoundingBox(@PathVariable userName:String,
|
||||
@PathVariable xMin:Double, @PathVariable yMin:Double,
|
||||
@PathVariable xMax:Double, @PathVariable yMax:Double)
|
||||
= repository.findByBoundingBox(PGbox2d(Point(xMin, yMin), Point(xMax, yMax)))
|
||||
|
||||
@GetMapping("/subscribe")
|
||||
|
||||
25
src/main/kotlin/io/spring/messenger/web/SseBroadcaster.kt
Normal file
25
src/main/kotlin/io/spring/messenger/web/SseBroadcaster.kt
Normal file
@@ -0,0 +1,25 @@
|
||||
package io.spring.messenger.web
|
||||
|
||||
import org.springframework.http.MediaType
|
||||
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter
|
||||
import java.util.*
|
||||
import java.util.Collections.synchronizedSet
|
||||
|
||||
class SseBroadcaster {
|
||||
|
||||
private var sseEmitters = synchronizedSet(HashSet<SseEmitter>());
|
||||
|
||||
fun subscribe(): SseEmitter {
|
||||
val sseEmitter: SseEmitter = SseEmitter()
|
||||
sseEmitter.onCompletion({ this.sseEmitters.remove(sseEmitter) });
|
||||
this.sseEmitters.add(sseEmitter);
|
||||
return sseEmitter
|
||||
}
|
||||
|
||||
fun send(o:Any) {
|
||||
synchronized (sseEmitters) {
|
||||
sseEmitters.iterator().forEach { it.send(o, MediaType.APPLICATION_JSON) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,15 +15,16 @@ class UserController @Autowired constructor(val repository: UserRepository) {
|
||||
fun findAll() = repository.findAll()
|
||||
|
||||
@PostMapping
|
||||
fun create(@RequestBody user: User) = repository.create(user)
|
||||
fun create(@RequestBody u: User) = repository.create(u)
|
||||
|
||||
@PutMapping("/{userName}/location/{x},{y}") @ResponseStatus(NO_CONTENT)
|
||||
fun updateLocation(@PathVariable userName:String, @PathVariable x: Double, @PathVariable y: Double)
|
||||
= repository.updateLocation(userName, Point(x, y))
|
||||
|
||||
@GetMapping("/bbox/{xMin},{yMin},{xMax},{yMax}")
|
||||
fun findByBoundingBox(@PathVariable userName:String, @PathVariable xMin:Double,
|
||||
@PathVariable yMin:Double, @PathVariable xMax:Double, @PathVariable yMax:Double)
|
||||
fun findByBoundingBox(@PathVariable userName:String,
|
||||
@PathVariable xMin:Double, @PathVariable yMin:Double,
|
||||
@PathVariable xMax:Double, @PathVariable yMax:Double)
|
||||
= repository.findByBoundingBox(PGbox2d(Point(xMin, yMin), Point(xMax, yMax)))
|
||||
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
CREATE EXTENSION IF NOT EXISTS postgis;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS "users" (
|
||||
user_name text PRIMARY KEY,
|
||||
first_name text,
|
||||
last_name text,
|
||||
location GEOMETRY(Point, 4326)
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS users_gix
|
||||
ON "users"
|
||||
USING GIST (location);
|
||||
|
||||
|
||||
CREATE TABLE IF NOT EXISTS messages (
|
||||
id SERIAL PRIMARY KEY,
|
||||
content text NOT NULL,
|
||||
author text REFERENCES users(user_name),
|
||||
location GEOMETRY(Point, 4326)
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS messages_gix
|
||||
ON messages
|
||||
USING GIST (location);
|
||||
@@ -38,7 +38,7 @@ var centerDefined = false;
|
||||
geolocation.on("change:position", function () {
|
||||
var coordinates = geolocation.getPosition();
|
||||
$.ajax({
|
||||
url: "/user/" + $('#select-user').val() + "swhite/location/" + coordinates[0] + "," + coordinates[1], type: "PUT"
|
||||
url: "/user/" + $('#select-user').val() + "/location/" + coordinates[0] + "," + coordinates[1], type: "PUT"
|
||||
});
|
||||
if (!centerDefined) {
|
||||
view.setCenter(coordinates);
|
||||
|
||||
Reference in New Issue
Block a user