[#394] Resolve "signalc: send a message from a number registered in a previous session (persistence)"
Closes #394 (closed)
context
- we are building signalc up piece-by-piece
- last MR (!496 (merged) resolving #393 (closed)) we build a CLI toy that could use signalc to register and verify a signal account and send messages from it, but since this toy held all the state about the account in memory, it would have to re-register and re-verify after stopping and starting again
- this MR adds a postgresql persistence layer and (1) moves the singal protocol store logic that had been in-memory to disk storage, (2) adds a new store for account data and stores it to disk as well
- as a result: we can now use the same phone number in our toy demo across several sessions without having to re-register. yay! :)
changes
persistence layer
- add sql representation for protocol store & account state
- abstract both as "stores" (ie: use a repository pattern that encapsulates the storage implementation. makes it easy to swap in an in-memory store or use the store as a mocking boundary in tests)
configuration
- create Application module that constructs itself from an injected set of Configs
-
Config.App
objects are built out of nested data classes which can cascade on top of one another to override defaults and inject dependencies at runtime -
Application
objects can be constructed in the boot sequence or before running tests to generate different configurations for different settings - provide test/dev/prod defaults as properties on
Config
to cver the most common settings - settings of note:
- reads db url and uses it to construct account & protocol stores which it exposes as a field on
app
- sets signal service config strings (which could later be read from env vars) and exposes the
signalConfig
object needed to construct signal-aware classes - leaves seam to modify what kind of stores will be used for Accounts and Protocol persistence layer
- reads db url and uses it to construct account & protocol stores which it exposes as a field on
- abandon an attempt to read cascading configs from yaml, but leave breadcrumbs here: #410 logic layer
logic layer
- pass
Appplication
as first arg to every class inlogic
layer - refactor AccountManager to work w/ AccountStore & ProtocolStore (as injected as fields on App)
- store state transtions from new -> registered -> verified in store
- retrieve accounts and their state across sessions (via
store#findOrCreate
) - add ability to reason about multiple accounts to the protocol store layer (which means making one protocol store for each account)
- move SignalServiceAccountManager objects (which we use to update signal server about account state via http calls) from lazy field on account data classes to memoized hash map encapsulated by AccountManager (reason: these objects now are stateful as they require a reference to
app
to know about the protocol store, so they can't be constructed as stateless properties of a data class)
- refactor Messenger -> MessageSender to work w/ stores on App
- as with AccountManager, only instantiate one supervising object per app instanste, maintain a memoized collection of accounts
- include
sender
as argument to#send
and enforce that only a verified account may send a message - require that
recipient
be an address (not a string), and provide helpers for constructing an address from either a phone number or a UUID
- note: both of these should work well as actor-like classes that consume messages over channels as a concurrency strategy in the near future
tooling
- migrations:
- adopt liquibase as migration framework
- add liquibase gradle task to run migrations
- add gitlab tooling to use liquibase to run migrations in CI
- forward localhost:5432 to the postgres docker container so we can:
- run db-touching tests locally without modifying configs
- do the above without having to run migrations against a non-dockerized version of the db
- util classes
- provide
MigrationUtil
(which can generate SQL statements to be used in liquibase migrations from exposed classes) - provide
InMemoryAccountStore
andInMemoryProtocolStore
implementations for possible future use as testing aids - start to add custom matchers (to assert things about message sending)
- provide
- spruce up make for signalc (give it all the commands it needs to run, stop, test, migrate, etc.)
- bake the signal truststore into the docker image (so we don't need to mount it and so we can have it in CI)
demo
- update
Main.kt
to user new persistence layer so it can look up pre-existing accounts and send messages to them (without having to register them again)
Edited by aguestuser