Skip to content

Commit

Permalink
fix(map): delete linked connection + signature on other side when rem…
Browse files Browse the repository at this point in the history
…oving a signature with a linked connection
  • Loading branch information
updraft0 committed Jun 15, 2024
1 parent 4b5d17a commit 5731c9f
Show file tree
Hide file tree
Showing 14 changed files with 913 additions and 462 deletions.
2 changes: 1 addition & 1 deletion .scalafmt.conf
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
version = 3.7.2
version = 3.8.2
runner.dialect = scala3
align.preset = more
maxColumn = 120
17 changes: 15 additions & 2 deletions db/src/main/scala/org/updraft0/controltower/db/query/map.scala
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,19 @@ object map:
.run(quote { mapWormholeConnection.filter(whc => whc.mapId == lift(mapId) && whc.id == lift(connectionId)) })
.map(_.headOption)

def getWormholeConnections(
mapId: MapId,
connectionIds: Chunk[ConnectionId],
isDeleted: Boolean
): DbOperation[List[MapWormholeConnection]] =
ctx.run(
quote(
mapWormholeConnection.filter(whc =>
whc.mapId == lift(mapId) && whc.isDeleted == lift(isDeleted) && liftQuery(connectionIds).contains(whc.id)
)
)
)

def getWormholeSystemNames: DbOperation[Map[String, Long]] =
ctx
.run(quote(sde.schema.solarSystem.map(ss => ss.name -> ss.id)))
Expand All @@ -101,9 +114,9 @@ object map:
def getWormholeTypeNames: DbOperation[List[(String, Long)]] =
ctx.run(quote { sde.schema.itemType.filter(_.groupId == lift(WormholeGroupId)).map(it => it.name -> it.id) })

def getMapSystem(mapId: MapId, systemId: Long) =
def getMapSystem(mapId: MapId, systemId: Long): DbOperation[Option[MapSystem]] =
ctx
.run(quote { mapSystem.filter(ms => ms.mapId == lift(mapId) && ms.systemId == lift(systemId)) })
.run(quote(mapSystem.filter(ms => ms.mapId == lift(mapId) && ms.systemId == lift(systemId))))
.map(_.headOption)

private inline def findWormholeMapSignatures(mapId: MapId, systemId: SystemId) =
Expand Down
14 changes: 13 additions & 1 deletion db/src/main/scala/org/updraft0/controltower/db/query/sde.scala
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,9 @@ object sde:
def upsertRegion(region: Region): DbOperation[Long] =
ctx.run(insert(schema.region, lift(region)).onConflictIgnore)

def upsertItemName(name: ItemName): DbOperation[Long] =
ctx.run(insert(schema.itemName, lift(name)).onConflictIgnore)

// inserts
def insertDogmaAttributeCategories(categories: Vector[DogmaAttributeCategory]): DbOperation[Long] =
ctx.run(insertAll(schema.dogmaAttributeCategory, categories), BatchRows).map(_.sum)
Expand Down Expand Up @@ -130,7 +133,16 @@ object sde:
// queries

def getLatestVersion: DbOperation[Option[Version]] =
ctx.run(quote(quote(schema.version).sortBy(_.createdAt)(Ord.desc).take(1))).map(_.headOption)
ctx.run(quote(schema.version.sortBy(_.createdAt)(Ord.desc).take(1))).map(_.headOption)

def getRegions: DbOperation[List[Region]] =
ctx.run(quote(schema.region))

def getConstellations: DbOperation[List[Constellation]] =
ctx.run(quote(schema.constellation))

def getSolarSystem: DbOperation[List[SolarSystem]] =
ctx.run(quote(schema.solarSystem))

// deletes
def deleteConstellation: DbOperation[Long] =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,8 +68,8 @@ object EsiClient:

def apply(sttp: SttpClient): ZIO[Config, Throwable, EsiClient] =
for config <- ZIO.service[Config]
// FIXME wait until improvements land in generic aliases in layers after ZIO 2.1.1
// sttp <- ZIO.service[SttpClient]
// FIXME wait until improvements land in generic aliases in layers after ZIO 2.1.1
// sttp <- ZIO.service[SttpClient]
yield new EsiClient(config, zioLoggingBackend(sttp), SttpClientInterpreter())

inline def withZIO[R, E, A](inline f: EsiClient => ZIO[R, E, A]): ZIO[R & EsiClient, E, A] =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,6 @@ object SdeClient:

def apply(sttp: SttpClient): ZIO[Config, Throwable, SdeClient] =
for config <- ZIO.service[Config]
// FIXME
// FIXME
// sttp <- ZIO.service[SttpClient]
yield apply(config.base, zioLoggingBackend(sttp), SttpClientInterpreter())
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,11 @@ object ReactiveEntitySpec extends ZIOSpecDefault:
override def spec =
suite("counter reactive entity")(
test("can respond to a sequence of get/increment messages"):
for
entity <- MiniReactive(CounterEntity, MiniReactiveConfig(16, Duration.Infinity))
inQ <- entity.enqueue("test")
outQ <- entity.subscribe("test")
_ <- ZStream(Get, Incr, Get, Incr, Incr, Get, Incr, Get).mapZIO(inQ.offer).runDrain.forkScoped
res <- ZStream.fromQueue(outQ).take(4).runCollect
yield assertTrue(res == Chunk(0, 1, 3, 4).map(CounterReply.Current.apply))
for
entity <- MiniReactive(CounterEntity, MiniReactiveConfig(16, Duration.Infinity))
inQ <- entity.enqueue("test")
outQ <- entity.subscribe("test")
_ <- ZStream(Get, Incr, Get, Incr, Incr, Get, Incr, Get).mapZIO(inQ.offer).runDrain.forkScoped
res <- ZStream.fromQueue(outQ).take(4).runCollect
yield assertTrue(res == Chunk(0, 1, 3, 4).map(CounterReply.Current.apply))
)
Original file line number Diff line number Diff line change
Expand Up @@ -14,31 +14,31 @@ object ReferenceProtocolSpec extends ZIOSpecDefault:
def spec =
suite("reference protocol")(
test("can encode/decode StationOperation"):
val value = StationOperation(
operationId = 42,
operationName = "Law School",
services = Array(
StationService(id = 5, name = "Reprocessing Plant"),
StationService(id = 9, name = "Stock Exchange")
)
val value = StationOperation(
operationId = 42,
operationName = "Law School",
services = Array(
StationService(id = 5, name = "Reprocessing Plant"),
StationService(id = 9, name = "Stock Exchange")
)
val json = Json.Obj(
"operationId" -> Json.Num(42),
"operationName" -> Json.Str("Law School"),
"services" -> Json.Arr(
Json.Obj("id" -> Json.Num(5), "name" -> Json.Str("Reprocessing Plant")),
Json.Obj("id" -> Json.Num(9), "name" -> Json.Str("Stock Exchange"))
)
)
val json = Json.Obj(
"operationId" -> Json.Num(42),
"operationName" -> Json.Str("Law School"),
"services" -> Json.Arr(
Json.Obj("id" -> Json.Num(5), "name" -> Json.Str("Reprocessing Plant")),
Json.Obj("id" -> Json.Num(9), "name" -> Json.Str("Stock Exchange"))
)
)

val res = json.as[StationOperation]
val res = json.as[StationOperation]

assertTrue(
res.map(_.operationId) == Right(42),
res.map(_.operationName) == Right("Law School"),
res.map(_.services.toList) == Right(
List(StationService(5, "Reprocessing Plant"), StationService(9, "Stock Exchange"))
),
value.toJsonAST == Right(json)
)
assertTrue(
res.map(_.operationId) == Right(42),
res.map(_.operationName) == Right("Law School"),
res.map(_.services.toList) == Right(
List(StationService(5, "Reprocessing Plant"), StationService(9, "Stock Exchange"))
),
value.toJsonAST == Right(json)
)
)
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ private[map] case class MapSolarSystem(
systemId: SystemId,
name: String,
whClass: WormholeClass,
regionId: Long,
constellationId: Long,
gates: Map[SystemId, Long]
) derives CanEqual

Expand Down Expand Up @@ -432,12 +434,8 @@ object MapEntity extends ReactiveEntity[MapEnv, MapId, MapState, Identified[MapR
_ <- query.map.upsertMapSystemDisplay(
model.MapSystemDisplay(mapId, add.systemId, add.displayData.displayType, add.displayData)
)
sys <- loadSingleSystem(mapId, add.systemId)
conns <- MapQueries.getWormholeConnectionsWithSigsBySystemId(mapId, add.systemId)
ranks <- MapQueries.getWormholeConnectionRanksForSystem(mapId, add.systemId)
yield withState(state.updateOne(sys.sys.systemId, sys, conns, ranks))(s =>
s -> broadcast(s.systemSnapshot(sys.sys.systemId))
)
resp <- reloadSystemSnapshot(mapId, add.systemId)(state)
yield resp

private def insertSystemConnection(
mapId: MapId,
Expand Down Expand Up @@ -584,13 +582,8 @@ object MapEntity extends ReactiveEntity[MapEnv, MapId, MapState, Identified[MapR
.map(lookupExisting(mapSystem, _))
.map((prevOpt, newSig) => toModelSignature(now, sessionId, mapSystemId, prevOpt, newSig))
)(query.map.upsertMapSystemSignature)
// reload the whole system
sys <- loadSingleSystem(mapId, uss.systemId)
conns <- MapQueries.getWormholeConnectionsWithSigsBySystemId(mapId, uss.systemId)
ranks <- MapQueries.getWormholeConnectionRanksForSystem(mapId, uss.systemId)
yield withState(state.updateOne(sys.sys.systemId, sys, conns, ranks))(s =>
s -> broadcast(s.systemSnapshot(sys.sys.systemId))
)
resp <- reloadSystemSnapshot(mapId, uss.systemId)(state)
yield resp

private def removeSystemAndConnection(
mapId: MapId,
Expand Down Expand Up @@ -653,18 +646,52 @@ object MapEntity extends ReactiveEntity[MapEnv, MapId, MapState, Identified[MapR
now: Instant,
rss: MapRequest.RemoveSystemSignatures
) =
// TODO need to recompute state for all the connection ids :) <-- aka if the connection is being deleted,
// remove the whole connection on both sides!
for
// gather all connection ids present in the signatures about to be deleted
connectionIds <- query.map.getSystemConnectionIdsInSignatures(mapId, rss.systemId, rss.signatures.map(_.toChunk))
_ <- query.map.deleteMapWormholeConnections(Chunk.fromIterable(connectionIds), sessionId.characterId)
// gather all system ids those connections affect
systemIdsToRefresh = Chunk.fromIterable(
connectionIds
.map(state.connections)
.toSet
.flatMap(whc => Set(whc.connection.toSystemId, whc.connection.fromSystemId))
)
// delete the connections if any were found
_ <- query.map.deleteMapWormholeConnections(Chunk.fromIterable(connectionIds), sessionId.characterId)
// delete any signatures that map to those connection ids
_ <- query.map.deleteSignaturesWithConnectionIds(Chunk.fromIterable(connectionIds), sessionId.characterId)
deletedConnections <- query.map.getWormholeConnections(mapId, Chunk.fromIterable(connectionIds), isDeleted = true)
// delete the signatures
_ <- rss.signatures match
case None => query.map.deleteMapSystemSignaturesAll(mapId, rss.systemId, now, sessionId.characterId)
case Some(ids) => query.map.deleteMapSystemSignatures(mapId, rss.systemId, ids, sessionId.characterId)
// reload the whole system
sys <- loadSingleSystem(mapId, rss.systemId)
conns <- MapQueries.getWormholeConnectionsWithSigsBySystemId(mapId, rss.systemId)
ranks <- MapQueries.getWormholeConnectionRanksForSystem(mapId, rss.systemId)
// if some connections were found, will reload multiple systems
resp <-
if (systemIdsToRefresh.nonEmpty)
combineMany(
state,
Chunk(removeConnections(deletedConnections)) ++
systemIdsToRefresh.map(sId => reloadSystemSnapshot(mapId, sId))
)
else reloadSystemSnapshot(mapId, rss.systemId)(state)
yield resp

// partially remove connections - state will be updated later with systems
private def removeConnections(connectionsRemoved: List[MapWormholeConnection])(state: MapState) =
val connectionIds = connectionsRemoved.map(_.id)
val nextState = state.copy(
connections = state.connections.removedAll(connectionIds),
connectionRanks = state.connectionRanks.removedAll(connectionIds)
)
val resp =
if (connectionsRemoved.nonEmpty) broadcast(MapResponse.ConnectionsRemoved(connectionsRemoved)) else Chunk.empty
ZIO.succeed((nextState, resp))

private def reloadSystemSnapshot(mapId: MapId, systemId: SystemId)(state: MapState) =
for
sys <- loadSingleSystem(mapId, systemId)
conns <- MapQueries.getWormholeConnectionsWithSigsBySystemId(mapId, systemId)
ranks <- MapQueries.getWormholeConnectionRanksForSystem(mapId, systemId)
yield withState(state.updateOne(sys.sys.systemId, sys, conns, ranks))(s =>
s -> broadcast(s.systemSnapshot(sys.sys.systemId))
)
Expand Down Expand Up @@ -730,6 +757,7 @@ object MapEntity extends ReactiveEntity[MapEnv, MapId, MapState, Identified[MapR
model.SystemDisplayData.Manual(0, 0) // origin position
case Some(model.SystemDisplayData.Manual(x, y)) =>
// this must necessarily replicate the frontend code
// TODO: need to take collisions etc. into account
val newX = x + MagicConstant.SystemBoxSizeX + (MagicConstant.SystemBoxSizeX / 3)
model.SystemDisplayData.Manual(newX - (newX % MagicConstant.GridSnapPx), y)

Expand Down Expand Up @@ -850,7 +878,9 @@ object MapEntity extends ReactiveEntity[MapEnv, MapId, MapState, Identified[MapR
private inline def loadSingleSystem(mapId: MapId, systemId: SystemId) =
MapQueries
.getMapSystemAll(mapId, Some(systemId))
.filterOrDieMessage(_.size == 1)(s"BUG: expected exactly 1 system to be returned")
.filterOrDieMessage(_.size == 1)(
s"BUG: expected exactly 1 (map) system to be returned for (map=$mapId, system=$systemId)"
)
.map(_.head)

private inline def loadSingleConnection(mapId: MapId, connectionId: ConnectionId) =
Expand Down Expand Up @@ -1002,6 +1032,13 @@ private inline def removeSignatureById(arr: Array[MapSystemSignature], idOpt: Op
)
.getOrElse(arr)

private def combineMany(
initial: MapState,
fs: Chunk[MapState => ZIO[MapEnv, Throwable, (MapState, Chunk[Identified[MapResponse]])]]
) =
ZIO.foldLeft(fs)((initial, Chunk.empty[Identified[MapResponse]])):
case ((prev, responses), f) => f(prev).map((s, r) => (s, responses ++ r))

private def loadMapRef() =
ReferenceQueries.getAllSolarSystemsWithGates.map(allSolar =>
MapRef(
Expand All @@ -1012,6 +1049,8 @@ private def loadMapRef() =
systemId = ss.sys.id,
name = ss.sys.name,
whClass = WormholeClasses.ById(ss.sys.whClassId.get),
regionId = ss.sys.regionId,
constellationId = ss.sys.constellationId,
gates = ss.gates.map(sg => sg.outSystemId.value -> sg.inGateId).toMap
)
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,24 +21,24 @@ object UsersSpec extends ZIOSpecDefault:

def spec = suite("JWT auth token")(
test("can be encrypted and decrypted"):
val meta = EsiTokenMeta(CharacterId(1234), "Name1", "abcdef", Instant.EPOCH)
val jwt = JwtAuthResponse(JwtString(SampleTokenValue), 1L, "?", SampleRefresh)

for
enc <- Users.encryptJwtResponse(meta, jwt)
dec <- Users.decryptAuthToken(enc)
yield assertTrue(
enc.characterId == CharacterId(1234L),
enc.expiresAt == meta.expiry,
enc.refreshToken == encrypt(
Base64.raw(enc.nonce),
SampleKey,
Base64.raw(SampleRefresh).toBytes
).stringValue,
enc.token == encrypt(Base64.raw(enc.nonce), SampleKey, SampleTokenValue.getBytes).stringValue,
dec._1 == SampleTokenValue,
dec._2.stringValue == SampleRefresh
)
val meta = EsiTokenMeta(CharacterId(1234), "Name1", "abcdef", Instant.EPOCH)
val jwt = JwtAuthResponse(JwtString(SampleTokenValue), 1L, "?", SampleRefresh)

for
enc <- Users.encryptJwtResponse(meta, jwt)
dec <- Users.decryptAuthToken(enc)
yield assertTrue(
enc.characterId == CharacterId(1234L),
enc.expiresAt == meta.expiry,
enc.refreshToken == encrypt(
Base64.raw(enc.nonce),
SampleKey,
Base64.raw(SampleRefresh).toBytes
).stringValue,
enc.token == encrypt(Base64.raw(enc.nonce), SampleKey, SampleTokenValue.getBytes).stringValue,
dec._1 == SampleTokenValue,
dec._2.stringValue == SampleRefresh
)
).provide(
ZLayer(ZIO.attempt(new SecureRandom())),
ZLayer(ZIO.attempt(TokenCrypto(new SecretKeySpec(SampleKey, "AES"))))
Expand Down
Loading

0 comments on commit 5731c9f

Please sign in to comment.