chore: setup boilerplate for particle processor worker

This sets up an example of listening to firestore data and reacting. It
also sets up our first worker in the kubernetes cluster.
This commit is contained in:
talksik
2026-03-25 09:52:23 -07:00
parent 86bdc78852
commit d8a45b22d3
6 changed files with 126 additions and 0 deletions
+20
View File
@@ -0,0 +1,20 @@
# golang two stage build
FROM golang:1.25 AS first-stage
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download && go mod verify
COPY . .
WORKDIR /app/cmd/particleprocessorworker
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o main
RUN ls
FROM alpine:latest AS second-stage
WORKDIR /app
COPY --from=first-stage /app/cmd/particleprocessorworker .
RUN echo "copied over binary to production stage"
CMD ["./main"]
+67
View File
@@ -0,0 +1,67 @@
package main
import (
"context"
"fmt"
"log"
"log/slog"
"github.com/flowy-live/llink/internal/utils"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"cloud.google.com/go/firestore"
)
func createClient(ctx context.Context) *firestore.Client {
projectId := utils.MustGetEnv("GCP_PROJECT")
client, err := firestore.NewClient(ctx, projectId)
if err != nil {
log.Fatalf("Failed to create client: %v", err)
}
return client
}
// The purpose of the particle processor worker is to listen for new particles
// across all streams and perform side effects such as
// - generate transcript if the particle is of type media
// - send mobile notifications if a client is offline
// - update the parent stream's `last_child_created_at`
func main() {
ctx := context.Background()
client := createClient(ctx)
defer client.Close()
it := client.CollectionGroup("children").Snapshots(ctx)
var initialLoad = true
for {
snap, err := it.Next()
if e := status.Code(err); e == codes.DeadlineExceeded || e == codes.Canceled {
panic(fmt.Errorf("error: %w", err))
}
if err != nil {
slog.Error("error in processing snapshot", "error", err)
}
if snap != nil {
if initialLoad {
slog.Info("initial load", "changeCount", len(snap.Changes))
} else {
for _, change := range snap.Changes {
switch change.Kind {
case firestore.DocumentAdded:
slog.Info("document added: ")
case firestore.DocumentModified:
slog.Info("document modified")
case firestore.DocumentRemoved:
slog.Info("document removed")
}
slog.Info("received document snapshot", "data", change.Doc.Data())
}
}
}
initialLoad = false
}
}
+2
View File
@@ -23,7 +23,9 @@ require (
cloud.google.com/go/auth v0.17.0 // indirect
cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect
cloud.google.com/go/compute/metadata v0.9.0 // indirect
cloud.google.com/go/firestore v1.21.0 // indirect
cloud.google.com/go/iam v1.5.3 // indirect
cloud.google.com/go/longrunning v0.7.0 // indirect
cloud.google.com/go/monitoring v1.24.2 // indirect
dario.cat/mergo v1.0.2 // indirect
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect
+2
View File
@@ -8,6 +8,8 @@ cloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIi
cloud.google.com/go/auth/oauth2adapt v0.2.8/go.mod h1:XQ9y31RkqZCcwJWNSx2Xvric3RrU88hAYYbjDWYDL+c=
cloud.google.com/go/compute/metadata v0.9.0 h1:pDUj4QMoPejqq20dK0Pg2N4yG9zIkYGdBtwLoEkH9Zs=
cloud.google.com/go/compute/metadata v0.9.0/go.mod h1:E0bWwX5wTnLPedCKqk3pJmVgCBSM6qQI1yTBdEb3C10=
cloud.google.com/go/firestore v1.21.0 h1:BhopUsx7kh6NFx77ccRsHhrtkbJUmDAxNY3uapWdjcM=
cloud.google.com/go/firestore v1.21.0/go.mod h1:1xH6HNcnkf/gGyR8udd6pFO4Z7GWJSwLKQMx/u6UrP4=
cloud.google.com/go/iam v1.5.3 h1:+vMINPiDF2ognBJ97ABAYYwRgsaqxPbQDlMnbHMjolc=
cloud.google.com/go/iam v1.5.3/go.mod h1:MR3v9oLkZCTlaqljW6Eb2d3HGDGK5/bDv93jhfISFvU=
cloud.google.com/go/logging v1.13.0 h1:7j0HgAp0B94o1YRDqiqm26w4q1rDMH7XNRU34lJXHYc=
+31
View File
@@ -0,0 +1,31 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: particleprocessorworker
spec:
selector:
matchLabels:
app: particleprocessorworker
replicas: 1
template:
metadata:
labels:
app: particleprocessorworker
spec:
serviceAccountName: default-service-account
nodeSelector:
cloud.google.com/gke-spot: "true"
terminationGracePeriodSeconds: 15
containers:
- name: particleprocessorworker
image: "particleprocessorworker"
resources:
requests:
memory: "52Mi"
cpu: 50m
limits:
memory: "52Mi"
cpu: 50m
env:
- name: "GCP_PROJECT"
value: "flowy-dev-440017"
+4
View File
@@ -34,6 +34,10 @@ profiles:
context: .
docker:
dockerfile: Dockerfile
- image: particleprocessorworker
context: .
docker:
dockerfile: Dockerfile.particleprocessorworker
manifests:
rawYaml:
- k8s/dev/*