From b10520d7bdf53a5c3e90e04400f748eb824860ff Mon Sep 17 00:00:00 2001 From: David Moore Date: Mon, 15 Jul 2024 10:06:54 +1000 Subject: [PATCH] add go url shortener example --- README.md | 7 +- v1/url-shortener/.gitignore | 17 +++ v1/url-shortener/.vscode/launch.json | 14 +++ v1/url-shortener/README.md | 38 +++++++ v1/url-shortener/go.mod | 19 ++++ v1/url-shortener/go.sum | 42 ++++++++ v1/url-shortener/golang.dockerfile | 26 +++++ v1/url-shortener/nitric.aws.yaml | 68 ++++++++++++ v1/url-shortener/nitric.yaml | 10 ++ v1/url-shortener/resources/main.go | 35 ++++++ v1/url-shortener/services/notify/main.go | 24 +++++ v1/url-shortener/services/shortener/main.go | 113 ++++++++++++++++++++ 12 files changed, 410 insertions(+), 3 deletions(-) create mode 100644 v1/url-shortener/.gitignore create mode 100644 v1/url-shortener/.vscode/launch.json create mode 100644 v1/url-shortener/README.md create mode 100644 v1/url-shortener/go.mod create mode 100644 v1/url-shortener/go.sum create mode 100644 v1/url-shortener/golang.dockerfile create mode 100644 v1/url-shortener/nitric.aws.yaml create mode 100644 v1/url-shortener/nitric.yaml create mode 100644 v1/url-shortener/resources/main.go create mode 100644 v1/url-shortener/services/notify/main.go create mode 100644 v1/url-shortener/services/shortener/main.go diff --git a/README.md b/README.md index befb84e..38333e5 100644 --- a/README.md +++ b/README.md @@ -83,9 +83,10 @@ When utilizing the `nitric new` command to initiate a new project, the available ### Go -| Name | Description | Features | -| ------------------------------ | ---------------- | -------- | -| [go-starter](./v1/go-starter/) | REST API Starter | APIs | +| Name | Description | Features | +| ------------------------------------ | -------------------------- | ------------------------------ | +| [go-starter](./v1/go-starter/) | REST API Starter | APIs | +| [url-shortener](./v1/url-shortener/) | URL Shortener with Pub/Sub | APIs, Topics, Key Value Stores | ## About Nitric diff --git a/v1/url-shortener/.gitignore b/v1/url-shortener/.gitignore new file mode 100644 index 0000000..380c102 --- /dev/null +++ b/v1/url-shortener/.gitignore @@ -0,0 +1,17 @@ +# Binaries for programs and plugins +*.exe +*.exe~ +*.dll +*.so +*.dylib + +# Test binary, built with `go test -c` +*.test + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out + +# 'nitric run' log directory +.nitric/ + +git.store \ No newline at end of file diff --git a/v1/url-shortener/.vscode/launch.json b/v1/url-shortener/.vscode/launch.json new file mode 100644 index 0000000..2fd4dee --- /dev/null +++ b/v1/url-shortener/.vscode/launch.json @@ -0,0 +1,14 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "type": "go", + "name": "Debug Nitric App", + "request": "attach", + "cwd": "${workspaceFolder}" + } + ] +} diff --git a/v1/url-shortener/README.md b/v1/url-shortener/README.md new file mode 100644 index 0000000..b7ef775 --- /dev/null +++ b/v1/url-shortener/README.md @@ -0,0 +1,38 @@ +

+ +## Project Description + +Create a shortened URL that redirects to the full URL and receive notifications when URLs are created. + +## About Nitric + +This is a [Nitric](https://nitric.io) Golang project, but Nitric is a framework for rapid development of cloud-native and serverless applications in many languages. + +Using Nitric you define your apps in terms of the resources they need, then write the code for serverless function based APIs, event subscribers and scheduled jobs. + +Apps built with Nitric can be deployed to AWS, Azure or Google Cloud all from the same code base so you can focus on your products, not your cloud provider. + +Nitric makes it easy to: + +- Create smart [serverless functions and APIs](https://nitric.io/docs/apis) +- Build reliable distributed apps that use [events](https://nitric.io/docs/messaging/topics) and/or [queues](https://nitric.io/docs/messaging/queues) +- Securely store, retrieve and rotate [secrets](https://nitric.io/docs/secrets) +- Read and write files from [buckets](https://nitric.io/docs/storage) + +## Learning Nitric + +Nitric provides detailed and intuitive [documentation](https://nitric.io/docs) and [guides](https://nitric.io/docs/getting-started) to help you get started quickly. + +If you'd rather chat with the maintainers or community, come and join our [Discord](https://nitric.io/chat) server, [GitHub Discussions](https://github.com/nitrictech/nitric/discussions) or find us on [Twitter](https://twitter.com/nitric_io). + +## Running this project + +To run this project you'll need the [Nitric CLI](https://nitric.io/docs/installation) installed, then you can use the CLI commands to run, build or deploy the project. + +```bash +# install dependencies +go mod tidy + +# run locally +nitric start +``` diff --git a/v1/url-shortener/go.mod b/v1/url-shortener/go.mod new file mode 100644 index 0000000..41d8957 --- /dev/null +++ b/v1/url-shortener/go.mod @@ -0,0 +1,19 @@ +module github.com/nitrictech/examples/v1/url-shortener + +go 1.21 + +toolchain go1.21.4 + +require github.com/nitrictech/go-sdk v1.0.7 + +require ( + github.com/missionMeteora/toolkit v0.0.0-20170713173850-88364e3ef8cc // indirect + github.com/nitrictech/nitric/core v0.0.0-20240704002030-37a4af867ae4 // indirect + github.com/nitrictech/protoutils v0.0.0-20220321044654-02667a814cdf // indirect + golang.org/x/net v0.27.0 // indirect + golang.org/x/sys v0.22.0 // indirect + golang.org/x/text v0.16.0 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094 // indirect + google.golang.org/grpc v1.65.0 // indirect + google.golang.org/protobuf v1.34.2 // indirect +) diff --git a/v1/url-shortener/go.sum b/v1/url-shortener/go.sum new file mode 100644 index 0000000..89373ca --- /dev/null +++ b/v1/url-shortener/go.sum @@ -0,0 +1,42 @@ +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= +github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= +github.com/golang/mock v1.7.0-rc.1 h1:YojYx61/OLFsiv6Rw1Z96LpldJIy31o+UHmwAUMJ6/U= +github.com/golang/mock v1.7.0-rc.1/go.mod h1:s42URUywIqd+OcERslBJvOjepvNymP31m3q8d/GkuRs= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/missionMeteora/toolkit v0.0.0-20170713173850-88364e3ef8cc h1:/oFlKiuu6L1sIvZ7A363qMhNM+DUQL5WsVe1xIRQnFU= +github.com/missionMeteora/toolkit v0.0.0-20170713173850-88364e3ef8cc/go.mod h1:AtX+JBtXbQ+taj82QFzCSgN5EzM4Bi0YRyS+TVbjENs= +github.com/nitrictech/go-sdk v1.0.7 h1:SQsy522EhKCIc6NIHUMqDA4fDzD3gW0MHqABg1F840c= +github.com/nitrictech/go-sdk v1.0.7/go.mod h1:QmLPatYHvbQ+8ajVDxHplgQIyWWP9SzMXDL+PI/gL2E= +github.com/nitrictech/nitric/core v0.0.0-20240704002030-37a4af867ae4 h1:oQbSOBj7gr1pOZJD6QxmMrvI8DjKMETwV0IcXmv98fs= +github.com/nitrictech/nitric/core v0.0.0-20240704002030-37a4af867ae4/go.mod h1:BQBNISFpA3R6TYyb76Q/c06gCBELCxooiocoq9Z5YAI= +github.com/nitrictech/protoutils v0.0.0-20220321044654-02667a814cdf h1:8MB8W8ylM8sCM2COGfiO39/tB6BTdiawLszaUGCNL5w= +github.com/nitrictech/protoutils v0.0.0-20220321044654-02667a814cdf/go.mod h1:b2lzk2a4o1bvSrSCE6yvTldHuXCJymuDVhdMJGOSslw= +github.com/nxadm/tail v1.4.11 h1:8feyoE3OzPrcshW5/MJ4sGESc5cqmGkGCWlco4l0bqY= +github.com/nxadm/tail v1.4.11/go.mod h1:OTaG3NK980DZzxbRq6lEuzgU+mug70nY11sMd4JXXHc= +github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= +github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= +github.com/onsi/gomega v1.28.1 h1:MijcGUbfYuznzK/5R4CPNoUP/9Xvuo20sXfEm6XxoTA= +github.com/onsi/gomega v1.28.1/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8PPpoQ= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys= +golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE= +golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= +golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= +golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094 h1:BwIjyKYGsK9dMCBOorzRri8MQwmi7mT9rGHsCEinZkA= +google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094/go.mod h1:Ue6ibwXGpU+dqIcODieyLOcgj7z8+IcskoNIgZxtrFY= +google.golang.org/grpc v1.65.0 h1:bs/cUb4lp1G5iImFFd3u5ixQzweKizoZJAwBNLR42lc= +google.golang.org/grpc v1.65.0/go.mod h1:WgYC2ypjlB0EiQi6wdKixMqukr6lBc0Vo+oOgjrM5ZQ= +google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= +google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/v1/url-shortener/golang.dockerfile b/v1/url-shortener/golang.dockerfile new file mode 100644 index 0000000..49a9f38 --- /dev/null +++ b/v1/url-shortener/golang.dockerfile @@ -0,0 +1,26 @@ + +FROM golang:alpine as build + +ARG HANDLER + +WORKDIR /app/ + +COPY go.mod *.sum ./ + +RUN go mod download + +COPY . . + +# Build the Go App from the provided HANDLER (this will be based on matches in your nitric.yaml fle) +RUN go build -o /bin/main ./${HANDLER}/... + +FROM alpine + +COPY --from=build /bin/main /bin/main + +RUN chmod +x-rw /bin/main +RUN apk update && \ + apk add --no-cache tzdata ca-certificates && \ + update-ca-certificates + +ENTRYPOINT ["/bin/main"] \ No newline at end of file diff --git a/v1/url-shortener/nitric.aws.yaml b/v1/url-shortener/nitric.aws.yaml new file mode 100644 index 0000000..3268050 --- /dev/null +++ b/v1/url-shortener/nitric.aws.yaml @@ -0,0 +1,68 @@ +# The nitric provider to use +provider: nitric/aws@1.10.0 +# The target aws region to deploy to +# See available regions: +# https://docs.aws.amazon.com/general/latest/gr/lambda-service.html +region: us-east-1 +# Optional Configuration Below + +# The timezone that deployed schedules will run with +# Format is in tz identifiers: +# https://en.wikipedia.org/wiki/List_of_tz_database_time_zones +# schedule-timezone: Australia/Sydney # Available since v0.27.0 + +# Import existing AWS Resources +# Currently only secrets are supported +# Available since v0.28.0 +# import: +# # A name ARN map of secrets, where the name matches the nitric name of the secret you would like to import +# secrets: # Available since v0.28.0 +# # In typescript this would import the provided secret reference for a secret declared as +# # const mySecret = secret('my-secret'); +# my-secret: arn:... + +# # Apply configuration to nitric APIs +# apis: +# # The nitric name of the API to configure +# my-api: +# # Array of domains to apply to the API +# # The domain or parent domain must have a hosted zone already in Route53 +# domains: +# - api.example.com + +# # Configure your deployed functions/services +# config: +# # How functions without a type will be deployed +# default: +# # configure a sample rate for telemetry (between 0 and 1) e.g. 0.5 is 50% +# telemetry: 0 +# # configure functions to deploy to AWS lambda +# lambda: # Available since v0.26.0 +# # set 128MB of RAM +# # See lambda configuration docs here: +# # https://docs.aws.amazon.com/lambda/latest/dg/configuration-function-common.html#configuration-memory-console +# memory: 128 +# # set a timeout of 15 seconds +# # See lambda timeout values here: +# # https://docs.aws.amazon.com/lambda/latest/dg/configuration-function-common.html#configuration-timeout-console +# timeout: 15 +# # set a provisioned concurrency value +# # For info on provisioned concurrency for AWS Lambda see: +# # https://docs.aws.amazon.com/lambda/latest/dg/configuration-concurrency.html +# provisioned-concurrency: 0 +# # Configure VPCs that the lambda can access +# vpc: +# # Array of existing security group ids to apply +# security-group-ids: +# - sg-xxx +# # Array of existing subnet ids to apply +# subnet-ids: +# - subnet-xxx +# # Additional deployment types +# # You can target these types by setting a `type` in your project configuration +# big-service: +# telemetry: 0 +# lambda: +# memory: 1024 +# timeout: 60 +# provisioned-concurrency: 1 diff --git a/v1/url-shortener/nitric.yaml b/v1/url-shortener/nitric.yaml new file mode 100644 index 0000000..2c3ec5f --- /dev/null +++ b/v1/url-shortener/nitric.yaml @@ -0,0 +1,10 @@ +name: url-shortener-5 +services: + - match: services/* + runtime: go + type: "" + start: go run ./$SERVICE_PATH/... +runtimes: + go: + dockerfile: ./golang.dockerfile + args: {} diff --git a/v1/url-shortener/resources/main.go b/v1/url-shortener/resources/main.go new file mode 100644 index 0000000..bad2eb2 --- /dev/null +++ b/v1/url-shortener/resources/main.go @@ -0,0 +1,35 @@ +package resources + +import ( + "sync" + + "github.com/nitrictech/go-sdk/nitric" +) + +type Resource struct { + MainApi nitric.Api + UrlKvStore nitric.KvStore + NotifyTopic nitric.SubscribableTopic +} + +var ( + resource *Resource + resourceOnce sync.Once +) + +func Get() *Resource { + resourceOnce.Do(func() { + mainApi, err := nitric.NewApi("main") + if err != nil { + panic(err) + } + + resource = &Resource{ + MainApi: mainApi, + UrlKvStore: nitric.NewKv("urls"), + NotifyTopic: nitric.NewTopic("notify"), + } + }) + + return resource +} diff --git a/v1/url-shortener/services/notify/main.go b/v1/url-shortener/services/notify/main.go new file mode 100644 index 0000000..b5f3e71 --- /dev/null +++ b/v1/url-shortener/services/notify/main.go @@ -0,0 +1,24 @@ +package main + +import ( + "fmt" + + "github.com/nitrictech/examples/v1/url-shortener/resources" + "github.com/nitrictech/go-sdk/handler" + "github.com/nitrictech/go-sdk/nitric" +) + +func main() { + resources.Get().NotifyTopic.Subscribe(func(ctx *handler.MessageContext, next handler.MessageHandler) (*handler.MessageContext, error) { + fmt.Println("Received message: ", ctx.Request.Message()) + // notify on discord or slack etc + + return ctx, nil + }) + + if err := nitric.Run(); err != nil { + fmt.Println(err) + } + + fmt.Println("Notify service has started") +} diff --git a/v1/url-shortener/services/shortener/main.go b/v1/url-shortener/services/shortener/main.go new file mode 100644 index 0000000..a9891fd --- /dev/null +++ b/v1/url-shortener/services/shortener/main.go @@ -0,0 +1,113 @@ +package main + +import ( + "context" + "encoding/json" + "fmt" + "math/rand" + "net/http" + "strings" + + "github.com/nitrictech/examples/v1/url-shortener/resources" + "github.com/nitrictech/go-sdk/handler" + "github.com/nitrictech/go-sdk/nitric" +) + +func generateShortCode() string { + s := "" + for i := 0; i < 6; i++ { + s += string(rand.Intn(26) + 97) + } + + return s +} + +var shortenData struct { + Url string `json:"url"` +} + +func main() { + urlKvStore, err := resources.Get().UrlKvStore.Allow(nitric.KvStoreSet, nitric.KvStoreGet) + if err != nil { + panic(err) + } + + topicPublish, err := resources.Get().NotifyTopic.Allow(nitric.TopicPublish) + if err != nil { + panic(err) + } + + resources.Get().MainApi.Post("/shorten", func(ctx *handler.HttpContext, next handler.HttpHandler) (*handler.HttpContext, error) { + err := json.Unmarshal(ctx.Request.Data(), &shortenData) + if err != nil { + ctx.Response.Status = http.StatusBadRequest + ctx.Response.Body = []byte("Invalid JSON") + return next(ctx) + } + + if strings.TrimSpace(shortenData.Url) == "" { + ctx.Response.Status = 400 + ctx.Response.Body = []byte("URL is required") + return next(ctx) + } + + shortCode := generateShortCode() + + err = urlKvStore.Set(context.Background(), shortCode, map[string]interface{}{ + "url": shortenData.Url, + }) + if err != nil { + ctx.Response.Status = 500 + ctx.Response.Body = []byte("Error shortening URL") + return next(ctx) + } + + // notify the topic + err = topicPublish.Publish(context.Background(), map[string]interface{}{ + "shortCode": shortCode, + "url": shortenData.Url, + }) + if err != nil { + ctx.Response.Status = 500 + ctx.Response.Body = []byte("Error notifying topic") + return next(ctx) + } + + for key, val := range ctx.Request.Headers() { + fmt.Println(key, val) + } + + origin := "" + if val, ok := ctx.Request.Headers()["X-Forwarded-For"]; ok { + origin = val[0] + } else if val, ok := ctx.Request.Headers()["x-forwarded-for"]; ok { + origin = val[0] + } + + ctx.Response.Body = []byte(fmt.Sprintf("%s/%s", origin, shortCode)) + + return next(ctx) + }) + + resources.Get().MainApi.Get("/:code", func(ctx *handler.HttpContext, next handler.HttpHandler) (*handler.HttpContext, error) { + code := ctx.Request.PathParams()["code"] + + data, err := urlKvStore.Get(context.Background(), code) + // perform a 301 redirect to the long URL + if err == nil { + ctx.Response.Headers["Location"] = []string{data["url"].(string)} + ctx.Response.Status = 301 + } else { + fmt.Println("Error getting URL: ", err) + ctx.Response.Status = 404 + } + + return next(ctx) + }) + + if err := nitric.Run(); err != nil { + fmt.Println(err) + } + + fmt.Println("Shortner service has started") +}