Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 27 additions & 3 deletions backend/cmd/api/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,6 @@ import (
"syscall"
"time"

"github.com/hibiken/asynq"

"backend/internal/bootstrap"
"backend/internal/infrastructure/queue"
"backend/internal/infrastructure/ws"
Expand Down Expand Up @@ -72,14 +70,35 @@ func main() {
fmt.Fprintf(os.Stderr, "startup failed: %v\n", err)
os.Exit(1)
}
worker.Register(queue.TypeWelcomeEmail, asynq.HandlerFunc(queue.HandleWelcomeEmail))
worker.Register(queue.TypeWelcomeEmail, queue.NewHandleWelcomeEmail(app.EmailSender))
go func() {
if err := worker.Run(workerCtx); err != nil {
slog.Error("queue: worker error", "err", err)
}
}()
}

// --- Redis Streams consumer (unwired — add when you have a concrete use case) ---
// The streams infrastructure (Producer, Consumer, events) is fully implemented.
// Wire a consumer here following this pattern when you need it:
//
// streamCtx, streamCancel := context.WithCancel(context.Background())
// consumer, err := streams.NewConsumer(app.Config.RedisURL, streams.StreamUserCreated, "api", "api-1")
// if err != nil {
// slog.Error("streams: failed to create consumer", "err", err)
// } else {
// go func() {
// _ = consumer.Run(streamCtx, func(ctx context.Context, data []byte) error {
// var evt streams.UserCreatedEvent
// if err := json.Unmarshal(data, &evt); err != nil { return err }
// payload, _ := json.Marshal(queue.WelcomeEmailPayload{UserID: evt.UserID, Email: evt.Email, Name: evt.Name})
// return app.Enqueuer.Enqueue(ctx, queue.TypeWelcomeEmail, payload)
// })
// _ = consumer.Close()
// }()
// }
// // In the shutdown sequence below, call: streamCancel()

srv, err := server.NewServer(app, hub)
if err != nil {
fmt.Fprintf(os.Stderr, "startup failed: %v\n", err)
Expand Down Expand Up @@ -111,5 +130,10 @@ func main() {
slog.Error("enqueuer close error", "error", err)
}
}
if app.StreamProducer != nil {
if err := app.StreamProducer.Close(); err != nil {
slog.Error("stream producer close error", "error", err)
}
}
slog.Info("graceful shutdown complete")
}
139 changes: 139 additions & 0 deletions backend/docs/swagger/docs.go
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,108 @@ const docTemplate = `{
}
}
}
},
"delete": {
"security": [
{
"BearerAuth": []
}
],
"description": "Removes the authenticated user's profile record from the database. The Firebase account is not deleted.",
"produces": [
"application/json"
],
"tags": [
"users"
],
"summary": "Delete current user account",
"responses": {
"204": {
"description": "No Content"
},
"401": {
"description": "Unauthorized",
"schema": {
"type": "object",
"additionalProperties": {
"type": "string"
}
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"type": "object",
"additionalProperties": {
"type": "string"
}
}
}
}
},
"patch": {
"security": [
{
"BearerAuth": []
}
],
"description": "Upserts the authenticated user's profile record. Requires a valid Firebase ID token.",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"users"
],
"summary": "Update current user profile",
"parameters": [
{
"description": "Profile update",
"name": "body",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/handlers.updateMeRequest"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/handlers.UserAlias"
}
},
"400": {
"description": "Bad Request",
"schema": {
"type": "object",
"additionalProperties": {
"type": "string"
}
}
},
"401": {
"description": "Unauthorized",
"schema": {
"type": "object",
"additionalProperties": {
"type": "string"
}
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"type": "object",
"additionalProperties": {
"type": "string"
}
}
}
}
}
},
"/api/v1/storage/presign": {
Expand Down Expand Up @@ -479,6 +581,32 @@ const docTemplate = `{
}
}
},
"handlers.UserAlias": {
"type": "object",
"properties": {
"created_at": {
"type": "string"
},
"email": {
"type": "string"
},
"firebase_uid": {
"type": "string"
},
"id": {
"type": "integer"
},
"name": {
"type": "string"
},
"photo_url": {
"type": "string"
},
"updated_at": {
"type": "string"
}
}
},
"handlers.presignRequest": {
"type": "object",
"required": [
Expand Down Expand Up @@ -535,6 +663,17 @@ const docTemplate = `{
"type": "string"
}
}
},
"handlers.updateMeRequest": {
"type": "object",
"required": [
"name"
],
"properties": {
"name": {
"type": "string"
}
}
}
},
"securityDefinitions": {
Expand Down
139 changes: 139 additions & 0 deletions backend/docs/swagger/swagger.json
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,108 @@
}
}
}
},
"delete": {
"security": [
{
"BearerAuth": []
}
],
"description": "Removes the authenticated user's profile record from the database. The Firebase account is not deleted.",
"produces": [
"application/json"
],
"tags": [
"users"
],
"summary": "Delete current user account",
"responses": {
"204": {
"description": "No Content"
},
"401": {
"description": "Unauthorized",
"schema": {
"type": "object",
"additionalProperties": {
"type": "string"
}
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"type": "object",
"additionalProperties": {
"type": "string"
}
}
}
}
},
"patch": {
"security": [
{
"BearerAuth": []
}
],
"description": "Upserts the authenticated user's profile record. Requires a valid Firebase ID token.",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"users"
],
"summary": "Update current user profile",
"parameters": [
{
"description": "Profile update",
"name": "body",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/handlers.updateMeRequest"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/handlers.UserAlias"
}
},
"400": {
"description": "Bad Request",
"schema": {
"type": "object",
"additionalProperties": {
"type": "string"
}
}
},
"401": {
"description": "Unauthorized",
"schema": {
"type": "object",
"additionalProperties": {
"type": "string"
}
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"type": "object",
"additionalProperties": {
"type": "string"
}
}
}
}
}
},
"/api/v1/storage/presign": {
Expand Down Expand Up @@ -473,6 +575,32 @@
}
}
},
"handlers.UserAlias": {
"type": "object",
"properties": {
"created_at": {
"type": "string"
},
"email": {
"type": "string"
},
"firebase_uid": {
"type": "string"
},
"id": {
"type": "integer"
},
"name": {
"type": "string"
},
"photo_url": {
"type": "string"
},
"updated_at": {
"type": "string"
}
}
},
"handlers.presignRequest": {
"type": "object",
"required": [
Expand Down Expand Up @@ -529,6 +657,17 @@
"type": "string"
}
}
},
"handlers.updateMeRequest": {
"type": "object",
"required": [
"name"
],
"properties": {
"name": {
"type": "string"
}
}
}
},
"securityDefinitions": {
Expand Down
Loading
Loading