Buid the program like so:
$ go build -o notification-service ./cmd/notification-service
$ ./notification-service
The program takes no arguments. There is a Dockerfile available.
Configuration is performed using environment variables. This is also the case for the Dockerfile.
Listen address for the websocket connections in the format accepted by the standard library.
Optional, defaults to :8008
if empty.
Listen address for the prometheus metrics server in the format accepted by the
standard library. The metrics are exposed under path /metrics
.
Optional, defaults to :8009
if empty.
Your Firestore project id.
Required.
Path to your Firestore credentials JSON file.
Required if you are not using the emulator (FIRESTORE_EMULATOR_HOST
is not set).
Topic on which APNs notifications will be sent. Probably your iOS app id.
Required.
Path to your APNs certificate file in the PKCS#12 format. They normally come in a different format I think so you need to presumably export this from your keychain.
Required.
Password to your APNs certificate file.
Optional, leave empty if the certificate doesn't have a password.
Execution environment. Affects:
- whether testing or production APNs server is used
Optional, can be set to PRODUCTION
or DEVELOPMENT
. Defaults to PRODUCTION
.
Log level.
Optional, can be set to TRACE
, DEBUG
, ERROR
or DISABLED
. Defaults to DEBUG
.
Optional, defaults to false. If set needs to be either true
or false
.
Specifies if Google pubsub events are generated.
Required, project ID used for Google Cloud Pubsub. Relevant only when
NOTIFICATIONS_GOOGLE_PUBSUB_ENABLED
is set to true.
Path to your Google Cloud credentials JSON file. Relevant only when
NOTIFICATIONS_GOOGLE_PUBSUB_ENABLED
is set to true.
Optional, this is used by the Firestore libraries and can be useful for testing but you shouldn't ever have to set this in production.
See configuration for the address of our metrics endpoint. Many out-of-the-box Go-related metrics are available. We also have custom metrics:
application_handler_calls_total
application_handler_calls_duration
relay_downloader_count
subscription_queue_length
See service/adapters/prometheus
.
The project usually uses the latest Go version as declared by the go.mod
file.
You may not be able to build it using older compilers.
- Obtain a certificate from Apple (see "Obtain a provider certificate from Apple"). For development ensure that you are creating a certificate of type "Apple Push Notification Service SSL (Sanbox)" to avoid sending notifications in production.
- Export the certificate from your keychain in the PKCS#12 format.
You have two options when it comes to using Firestore: a local emulator using Docker or a real Firestore project that we setup for development.
- Start the docker daemon.
- Run
make start-services
to start the Firestore emulator, nosrelay and Redis using Docker compose. - Run the following command changing
NOTIFICATIONS_APNS_CERTIFICATE_PATH
andNOTIFICATIONS_APNS_CERTIFICATE_PASSWORD
:
NOTIFICATIONS_APNS_CERTIFICATE_PATH="/path/to/your/apns/cert.p12" \
NOTIFICATIONS_APNS_CERTIFICATE_PASSWORD="your cert password if you set one" \
FIRESTORE_EMULATOR_HOST=localhost:8200 \
NOTIFICATIONS_FIRESTORE_PROJECT_ID=test-project-id \
NOTIFICATIONS_APNS_TOPIC=com.verse.Nos \
NOTIFICATIONS_ENVIRONMENT=DEVELOPMENT \
REDIS_URL=redis://localhost:6379 \
go run ./cmd/notification-service
- Download credentials for the project. Those are your private credentials don't use them for production.
- Run the following command changing
NOTIFICATIONS_APNS_CERTIFICATE_PATH
,NOTIFICATIONS_APNS_CERTIFICATE_PASSWORD
andNOTIFICATIONS_FIRESTORE_CREDENTIALS_JSON_PATH
:
NOTIFICATIONS_APNS_CERTIFICATE_PATH="/path/to/your/apns/cert.p12" \
NOTIFICATIONS_APNS_CERTIFICATE_PASSWORD="your cert password if you set one" \
NOTIFICATIONS_FIRESTORE_CREDENTIALS_JSON_PATH="/path/to/your/credentials/file.json" \
NOTIFICATIONS_FIRESTORE_PROJECT_ID="nos-notification-service-dev" \
NOTIFICATIONS_APNS_TOPIC=com.verse.Nos \
NOTIFICATIONS_ENVIRONMENT=DEVELOPMENT \
REDIS_URL=redis://localhost:6379 \
go run ./cmd/notification-service
Normally the program doesn't deliver the same notification multiple times. You
could fix it by clearing the emulator but it is probably easier to comment out
lines that look similar to this in
service/app/handler_process_received_event.go
:
exists, err := adapters.Events.Exists(ctx, cmd.event.Id())
if err != nil {
return errors.Wrap(err, "error checking if event exists")
}
if exists {
return nil
}
We recommend reading the Makefile
to discover some targets which you can
execute. It can be used as a shortcut to run various useful commands.
You may have to run the following command to install a linter and a code formatter before executing certain targets:
$ make tools
If you want to check if the pipeline will pass for your commit it should be enough to run the following command:
$ make ci
It is also useful to often run just the tests during development:
$ make test
Easily format your code with the following command:
$ make fmt
Resources which are in my opinion informative and good to read:
When naming tests which tests a specific behaviour it is recommended to follow a
pattern TestNameOfType_ExpectedBehaviour
. Example:
TestRelayDownloader_EventsDownloadedFromRelaysArePublishedUsingPublisher
.
Some constructors are prefixed with the word Must
. Those constructors panic
and should always be accompanied by a normal constructor which isn't prefixed
with the Must
and returns an error. The panicking constructors should only be
used in the following cases:
- when writing tests
- when a static value has to be created e.g.
MustNewHops(1)
and this branch of logic in the code is covered by tests
The notification service receives custom Nostr events which contain user's relays and APNs tokens. The notification service then uses those lists of relays associated with user's public key to get all events in which the user was tagged and to generate APNs notifications for them. When Nos receives such a notification it grabs the events from the notification service by querying it like a normal relay.
flowchart LR
Nos --> |custom registration event| Service --> |notification| APNs --> |notification| Nos --> |request for events| Service
The best entry point to start reading the code is probably service/ports/http
. There you can investigate how registration events are processed and saved in the database. You can also investigate how events are returned in response to REQ
requests.
To see how events are downloaded check out Downloader
as well as RelayDownloader
. One tricky thing there is that we use pubsub to work around the (reasonable as it forces you to do things correctly) Firestore transaction size limit. See the chart below to understand the flow.
flowchart TB
relay-downloader["Relay downloader"]
in-memory-pubsub["In-memory pubsub"]
save-received-event-handler["Save received event handler"]
firestore["Firestore"]
firestore-in-process-block["Firestore"]
apns["APNs"]
process-saved-event-handler["Process saved event handler"]
subgraph Receive nostr event
relay-downloader --> |received nostr event| in-memory-pubsub
end
subgraph Save nostr event
in-memory-pubsub --> |received nostr event| save-received-event-handler
save-received-event-handler --> |received nostr event| firestore
save-received-event-handler --> |`nostr event saved` pubsub event| firestore
end
subgraph Process nostr event
firestore --> |`nostr event saved` pubsub event| process-saved-event-handler
process-saved-event-handler --> |many nostr event tags| firestore-in-process-block
process-saved-event-handler --> |many notifications| apns
process-saved-event-handler --> |many notifications| firestore-in-process-block
end
-
Obtain a Github Token: Follow the instructions in the GitHub documentation to get a personal access token (classic).
-
Log in to GitHub Container Registry:
echo \$CR_PAT | docker login ghcr.io -u \$GITHUB_USERNAME --password-stdin
-
Tag the Image: Tag the image with the
stable
tag as described in the GitHub documentation.docker tag ghcr.io/planetary-social/nos-notification-service-go:latest ghcr.io/planetary-social/nos-notification-service-go:stable && docker push ghcr.io/planetary-social/nos-notification-service-go:stable
-
Trigger the Image Update Process: The image update process checks for new tags every 3 minutes. Therefore, you should see the new image deployed in approximately 5 minutes.