├── .gitignore
├── Dockerfile
├── LICENSE
├── README.md
├── assets
└── header.png
├── cmd
└── main.go
├── docker-compose.yml
├── go.mod
├── go.sum
└── pkg
├── analytics
└── influx.go
└── database
└── metadata.go
/.gitignore:
--------------------------------------------------------------------------------
1 | .env
2 | .vscode
3 | cmd/__debug*
4 | cmd/gifmo.db
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM golang:alpine AS builder
2 |
3 | WORKDIR /app
4 |
5 | COPY go.mod go.sum ./
6 | RUN go mod download
7 |
8 | COPY cmd ./cmd
9 | COPY pkg ./pkg
10 |
11 | RUN go build -o server ./cmd
12 |
13 | FROM alpine:latest
14 | COPY --from=builder /app/server ./server
15 |
16 | # Expose the port that the server listens on
17 | EXPOSE 8080
18 |
19 | # Set the entry point for the container
20 | CMD ["./server"]
21 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2024 Aleksei Cherepanov
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | 
2 |
3 | # 🚀 Universal Redirect
4 | 
5 | 
6 | 
7 | 
8 | 
9 | 
10 |
11 | **Universal Redirect** is a lightweight and efficient server designed to redirect incoming requests to platform-specific URLs. Perfect for directing users to the App Store or Play Store based on their device platform. Additionally, you can append custom metadata to the link and retrieve it in your app.
12 |
13 | ## 📚 Table of Contents
14 |
15 | 1. [🚀 Usage](#usage)
16 | 2. [⚙️ How It Works](#how-it-works)
17 | 3. [🛠️ Installation](#installation)
18 | 1. [📋 Requirements](#requirements)
19 | 2. [📂 Clone the Repository](#clone-the-repository)
20 | 3. [🐳 Docker](#docker)
21 | 4. [🌐 Dokku](#dokku)
22 | 4. [⚙️ Configuration](#configuration)
23 | 5. [📈 Analytics](#analytics)
24 |
25 | ## 🚀 Usage
26 |
27 | 1. Deploy the server on your hosting platform.
28 | 2. Create a link using the following format: `https://your-host.com?meta=your-meta`.
29 | 3. Redirect users to this link.
30 | 4. Retrieve the metadata in your app using a GET request to `https://your-host.com/meta`.
31 |
32 | ## ⚙️ How It Works
33 |
34 | 1. The user clicks the link.
35 | 2. The server reads the user agent to determine the user's platform and caches the client's IP.
36 | 3. The server redirects the user to the appropriate platform-specific URL.
37 | 4. Metadata is saved to the Redis cache.
38 | 5. The app retrieves the metadata from the Redis cache.
39 |
40 | ## 🛠️ Installation
41 |
42 | ### 📋 Requirements
43 |
44 | - Redis server
45 | - Default redirect URL
46 |
47 | ### 📂 Clone the Repository
48 |
49 | ```bash
50 | git clone https://github.com/ftp27/go-universal-redirect.git
51 | cd go-universal-redirect
52 | ```
53 |
54 | ### 🐳 Docker
55 |
56 | #### Build the Container
57 |
58 | ```bash
59 | docker build -t universal-redirect .
60 | ```
61 |
62 | #### Run the Container
63 |
64 | ```bash
65 | docker compose up -d
66 | ```
67 |
68 | ### 🌐 Dokku
69 |
70 | #### Create the App
71 |
72 | ```bash
73 | dokku apps:create universal-redirect
74 | ```
75 |
76 | #### Create the Redis Service
77 |
78 | ```bash
79 | dokku redis:create universal-redirect
80 | dokku redis:link universal-redirect universal-redirect
81 | ```
82 |
83 | #### Set Configuration
84 |
85 | **Required:**
86 |
87 | ```bash
88 | dokku config:set universal-redirect LINK_DEFAULT=https://example.com
89 | ```
90 |
91 | **Optional (InfluxDB):**
92 |
93 | ```bash
94 | dokku config:set universal-redirect INFLUX_TOKEN=... INFLUX_HOST=... INFLUX_DATABASE=...
95 | ```
96 |
97 | **Optional (Platform-Specific Links):**
98 |
99 | ```bash
100 | dokku config:set universal-redirect LINK_APPSTORE=https://apps.apple.com LINK_GOOGLEPLAY=https://play.google.com
101 | ```
102 |
103 | #### Configure Ports
104 |
105 | ```bash
106 | dokku ports:add universal-redirect http:80:8080 https:443:8080
107 | ```
108 |
109 | #### Set Domain (Optional)
110 |
111 | ```bash
112 | dokku domains:set universal-redirect example.com
113 | ```
114 |
115 | #### Set SSL (Optional)
116 |
117 | ```bash
118 | dokku letsencrypt:enable universal-redirect
119 | ```
120 |
121 | #### Add Certificates (Optional)
122 |
123 | ```bash
124 | tar cvf certs.tar server.crt server.key
125 | dokku certs:add universal-redirect < certs.tar
126 | ```
127 |
128 | #### Deploy the App
129 |
130 | ```bash
131 | git remote add dokku ... # Add the Dokku remote
132 | git push dokku main
133 | ```
134 |
135 | ## ⚙️ Configuration
136 |
137 | Customize the server by setting the following environment variables:
138 |
139 | - **`REDIS_URL`** - URL of the Redis server **(required)**
140 | - **`LINK_DEFAULT`** - Default redirect URL **(required)**
141 | - **`LINK_APPSTORE`** - App Store redirect URL __(optional)__
142 | - **`LINK_GOOGLEPLAY`** - Play Store redirect URL __(optional)__
143 | - **`PORT`** - Server port (default: 8080)
144 |
145 | For InfluxDB analytics, configure the following variables:
146 |
147 | - **`INFLUX_TOKEN`** - InfluxDB token (optional)
148 | - **`INFLUX_HOST`** - InfluxDB host (optional)
149 | - **`INFLUX_DATABASE`** - InfluxDB database (optional)
150 |
151 | ## 📈 Analytics
152 |
153 | Universal Redirect supports InfluxDB for analytics. It sends each click and install event to the `link` measurement, along with platform and type information (`click` or `install`). Here are some example queries:
154 |
155 | - **Total Clicks By Day:**
156 | ```sql
157 | SELECT
158 | DATE_BIN(INTERVAL '1 day', time, '1970-01-01T00:00:00Z'::TIMESTAMP) AS day,
159 | SUM(value) as value
160 | FROM "link"
161 | WHERE time >= now() - INTERVAL '30 day' AND type = 'click'
162 | GROUP BY day
163 | ```
164 |
165 | ```flux
166 | from(bucket: "")
167 | |> range(start: v.timeRangeStart, stop: v.timeRangeStop)
168 | |> filter(fn: (r) => r["_measurement"] == "link")
169 | |> filter(fn: (r) => r["type"] == "click")
170 | |> group(columns: [])
171 | |> aggregateWindow(every: 1d, fn: sum, createEmpty: false)
172 | |> yield(name: "sum")
173 | ```
174 |
175 | - **Total Clicks by Platform:**
176 | ```sql
177 | SELECT
178 | SUM("value") AS "total_value",
179 | platform
180 | FROM "link"
181 | WHERE time >= now() - INTERVAL '30 day' AND type = 'click'
182 | GROUP BY platform
183 | ```
184 |
185 | ```flux
186 | from(bucket: "")
187 | |> range(start: v.timeRangeStart, stop: v.timeRangeStop)
188 | |> filter(fn: (r) => r["_measurement"] == "link")
189 | |> filter(fn: (r) => r["_field"] == "value")
190 | |> group(columns: ["platform"])
191 | |> aggregateWindow(every: 1d, fn: sum, createEmpty: false)
192 | |> yield(name: "sum")
193 | ```
194 |
--------------------------------------------------------------------------------
/assets/header.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ftp27/go-universal-redirect/06ba738b7b787c95fa9bbabdc91ed09f54367a4e/assets/header.png
--------------------------------------------------------------------------------
/cmd/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "context"
5 | "net/http"
6 | "os"
7 | "strconv"
8 | "time"
9 |
10 | // Networking
11 | "github.com/mileusna/useragent"
12 |
13 | // Logging
14 | "github.com/rs/zerolog"
15 | "github.com/rs/zerolog/log"
16 |
17 | // Packages
18 | "github.com/ftp27/go-universal-redirect/pkg/analytics"
19 | "github.com/ftp27/go-universal-redirect/pkg/database"
20 | )
21 |
22 | var (
23 | port string
24 | defaultLink string
25 | platformLinks map[string]string
26 |
27 | meta *database.Metadata
28 | tracker *analytics.InfluxAnalytics
29 | )
30 |
31 | func main() {
32 | setuoLog()
33 | setupConfig()
34 | setupMeta()
35 | setupAnalytics()
36 |
37 | startServer()
38 | }
39 |
40 | func setupMeta() {
41 | host, isExists := os.LookupEnv("REDIS_URL")
42 | if !isExists {
43 | log.Fatal().Str("env", "REDIS_URL").Msg("Not found")
44 | return
45 | }
46 | lifetimeStr, isExists := os.LookupEnv("META_LIFETIME")
47 | lifetime := time.Hour
48 | if isExists {
49 | intLifetime, err := strconv.Atoi(lifetimeStr)
50 | if err != nil {
51 | log.Fatal().Err(err).Str("env", "META_LIFETIME").Msg("Failed to parse")
52 | return
53 | }
54 | lifetime = time.Duration(intLifetime) * time.Second
55 | }
56 |
57 | metadata, err := database.NewMetadata(host, lifetime)
58 | if err != nil {
59 | log.Fatal().Err(err).Msg("Failed to create metadata")
60 | return
61 | }
62 | meta = metadata
63 | }
64 |
65 | func setupAnalytics() {
66 | id, isExists := os.LookupEnv("INFLUX_DATABASE")
67 | if isExists {
68 | tracker = analytics.NewInfluxAnalytics(id)
69 | }
70 | }
71 |
72 | func setuoLog() {
73 | zerolog.TimeFieldFormat = zerolog.TimeFormatUnix
74 | log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr})
75 | }
76 |
77 | func setupConfig() {
78 | serverPort, isExists := os.LookupEnv("PORT")
79 | if !isExists {
80 | port = ":8080"
81 | } else {
82 | port = ":" + serverPort
83 | }
84 | link, isExists := os.LookupEnv("LINK_DEFAULT")
85 | if !isExists {
86 | log.Fatal().Str("env", "LINK_DEFAULT").Msg("Not found")
87 | return
88 | }
89 | links := make(map[string]string)
90 | links["iOS"] = os.Getenv("LINK_APPSTORE")
91 | links["Android"] = os.Getenv("LINK_GOOGLEPLAY")
92 | platformLinks = links
93 | defaultLink = link
94 | }
95 |
96 | func startServer() {
97 | log.Info().Str("addr", port).Msg("Starting server")
98 | http.HandleFunc("/", linkHandler)
99 | http.HandleFunc("/meta", metaHandler)
100 |
101 | http.ListenAndServe(port, nil)
102 | }
103 |
104 | func linkHandler(w http.ResponseWriter, r *http.Request) {
105 | platform := getOS(r)
106 | link := getLink(platform)
107 | ip := getIP(r)
108 |
109 | metaData := r.URL.Query().Get("meta")
110 | if metaData != "" {
111 | err := meta.Set(ip, metaData)
112 | if err != nil {
113 | log.Error().Err(err).Str("ip", ip).Msg("Failed to set metadata")
114 | }
115 | }
116 | if tracker != nil {
117 | err := tracker.LogClick(metaData, platform, context.Background())
118 | if err != nil {
119 | log.Error().Err(err).Str("ip", ip).Msg("Failed to log click")
120 | }
121 | }
122 |
123 | http.Redirect(w, r, link, http.StatusMovedPermanently)
124 | }
125 |
126 | func metaHandler(w http.ResponseWriter, r *http.Request) {
127 | ip := getIP(r)
128 | metaData, _ := meta.Get(ip)
129 | if metaData != "" && tracker != nil {
130 | platform := getOS(r)
131 | err := tracker.LogInstall(metaData, platform, context.Background())
132 | if err != nil {
133 | log.Error().Err(err).Str("ip", ip).Msg("Failed to log install")
134 | }
135 | }
136 | w.Write([]byte(metaData))
137 | }
138 |
139 | func getOS(r *http.Request) string {
140 | s := r.Header.Get("User-Agent")
141 | if s == "" {
142 | return "Unknown"
143 | }
144 | return useragent.Parse(s).OS
145 | }
146 |
147 | func getLink(platform string) string {
148 | link := platformLinks[platform]
149 | if link != "" {
150 | return link
151 | } else {
152 | return defaultLink
153 | }
154 | }
155 |
156 | func getIP(r *http.Request) string {
157 | cloudflareIP := r.Header.Get("CF-Connecting-IP")
158 | if cloudflareIP != "" {
159 | return cloudflareIP
160 | }
161 | ip := r.Header.Get("X-Real-IP")
162 | if ip != "" {
163 | return ip
164 | }
165 | return ""
166 | }
167 |
--------------------------------------------------------------------------------
/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: '3.8'
2 |
3 | services:
4 | app:
5 | build: .
6 | environment:
7 | REDIS_URL: redis://redis:6379
8 | LINK_DEFAULT: https://example.com
9 | # Optional: Uncomment to use InfluxDB
10 | # INFLUX_TOKEN: your_influx_token_here
11 | # INFLUX_HOST: your_influx_host_here
12 | # INFLUX_DATABASE: your_influx_database_here
13 | # Optional: Uncomment for platform-specific links
14 | # LINK_APPSTORE: https://apps.apple.com
15 | # LINK_GOOGLEPLAY: https://play.google.com
16 | ports:
17 | - "80:8080"
18 | - "443:8080"
19 | depends_on:
20 | - redis
21 | redis:
22 | image: redis
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/ftp27/go-universal-redirect
2 |
3 | go 1.22.3
4 |
5 | require (
6 | github.com/InfluxCommunity/influxdb3-go v0.9.0
7 | github.com/go-redis/redis v6.15.9+incompatible
8 | github.com/mileusna/useragent v1.3.4
9 | github.com/rs/zerolog v1.33.0
10 | )
11 |
12 | require (
13 | github.com/apache/arrow/go/v15 v15.0.2 // indirect
14 | github.com/goccy/go-json v0.10.2 // indirect
15 | github.com/google/flatbuffers v24.3.7+incompatible // indirect
16 | github.com/influxdata/line-protocol/v2 v2.2.1 // indirect
17 | github.com/klauspost/compress v1.17.7 // indirect
18 | github.com/klauspost/cpuid/v2 v2.2.7 // indirect
19 | github.com/mattn/go-colorable v0.1.13 // indirect
20 | github.com/mattn/go-isatty v0.0.19 // indirect
21 | github.com/onsi/ginkgo v1.16.5 // indirect
22 | github.com/onsi/gomega v1.34.1 // indirect
23 | github.com/pierrec/lz4/v4 v4.1.21 // indirect
24 | github.com/zeebo/xxh3 v1.0.2 // indirect
25 | golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 // indirect
26 | golang.org/x/mod v0.19.0 // indirect
27 | golang.org/x/net v0.27.0 // indirect
28 | golang.org/x/sync v0.7.0 // indirect
29 | golang.org/x/sys v0.22.0 // indirect
30 | golang.org/x/text v0.16.0 // indirect
31 | golang.org/x/tools v0.23.0 // indirect
32 | golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect
33 | google.golang.org/genproto/googleapis/rpc v0.0.0-20240528184218-531527333157 // indirect
34 | google.golang.org/grpc v1.65.0 // indirect
35 | google.golang.org/protobuf v1.34.1 // indirect
36 | )
37 |
--------------------------------------------------------------------------------
/go.sum:
--------------------------------------------------------------------------------
1 | github.com/InfluxCommunity/influxdb3-go v0.9.0 h1:67Zw0JQti07qV8Wu5dn3GoRD8C0pNWTgmTvXP8A/ct0=
2 | github.com/InfluxCommunity/influxdb3-go v0.9.0/go.mod h1:oK0jPp5mL8s8yKIaMVLL+A/A6G48BPGuXSE0ErAbId8=
3 | github.com/apache/arrow/go/v15 v15.0.2 h1:60IliRbiyTWCWjERBCkO1W4Qun9svcYoZrSLcyOsMLE=
4 | github.com/apache/arrow/go/v15 v15.0.2/go.mod h1:DGXsR3ajT524njufqf95822i+KTh+yea1jass9YXgjA=
5 | github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
6 | github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
7 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
8 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
9 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
10 | github.com/frankban/quicktest v1.11.0/go.mod h1:K+q6oSqb0W0Ininfk863uOk1lMy69l/P6txr3mVT54s=
11 | github.com/frankban/quicktest v1.11.2/go.mod h1:K+q6oSqb0W0Ininfk863uOk1lMy69l/P6txr3mVT54s=
12 | github.com/frankban/quicktest v1.13.0 h1:yNZif1OkDfNoDfb9zZa9aXIpejNR4F23Wely0c+Qdqk=
13 | github.com/frankban/quicktest v1.13.0/go.mod h1:qLE0fzW0VuyUAJgPU19zByoIr0HtCHN/r/VLSOOIySU=
14 | github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
15 | github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
16 | github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
17 | github.com/go-redis/redis v6.15.9+incompatible h1:K0pv1D7EQUjfyoMql+r/jZqCLizCGKFlFgcHWWmHQjg=
18 | github.com/go-redis/redis v6.15.9+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA=
19 | github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
20 | github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
21 | github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
22 | github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
23 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
24 | github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
25 | github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
26 | github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
27 | github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
28 | github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
29 | github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
30 | github.com/google/flatbuffers v24.3.7+incompatible h1:BxGUkIQnOciBu33bd5BdvqY8Qvo0O/GR4SPhh7x9Ed0=
31 | github.com/google/flatbuffers v24.3.7+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8=
32 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
33 | github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
34 | github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
35 | github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
36 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
37 | github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
38 | github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
39 | github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
40 | github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
41 | github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
42 | github.com/influxdata/line-protocol-corpus v0.0.0-20210519164801-ca6fa5da0184/go.mod h1:03nmhxzZ7Xk2pdG+lmMd7mHDfeVOYFyhOgwO61qWU98=
43 | github.com/influxdata/line-protocol-corpus v0.0.0-20210922080147-aa28ccfb8937 h1:MHJNQ+p99hFATQm6ORoLmpUCF7ovjwEFshs/NHzAbig=
44 | github.com/influxdata/line-protocol-corpus v0.0.0-20210922080147-aa28ccfb8937/go.mod h1:BKR9c0uHSmRgM/se9JhFHtTT7JTO67X23MtKMHtZcpo=
45 | github.com/influxdata/line-protocol/v2 v2.0.0-20210312151457-c52fdecb625a/go.mod h1:6+9Xt5Sq1rWx+glMgxhcg2c0DUaehK+5TDcPZ76GypY=
46 | github.com/influxdata/line-protocol/v2 v2.1.0/go.mod h1:QKw43hdUBg3GTk2iC3iyCxksNj7PX9aUSeYOYE/ceHY=
47 | github.com/influxdata/line-protocol/v2 v2.2.1 h1:EAPkqJ9Km4uAxtMRgUubJyqAr6zgWM0dznKMLRauQRE=
48 | github.com/influxdata/line-protocol/v2 v2.2.1/go.mod h1:DmB3Cnh+3oxmG6LOBIxce4oaL4CPj3OmMPgvauXh+tM=
49 | github.com/klauspost/compress v1.17.7 h1:ehO88t2UGzQK66LMdE8tibEd1ErmzZjNEqWkjLAKQQg=
50 | github.com/klauspost/compress v1.17.7/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
51 | github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM=
52 | github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
53 | github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
54 | github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
55 | github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
56 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
57 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
58 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
59 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
60 | github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
61 | github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
62 | github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
63 | github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
64 | github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
65 | github.com/mileusna/useragent v1.3.4 h1:MiuRRuvGjEie1+yZHO88UBYg8YBC/ddF6T7F56i3PCk=
66 | github.com/mileusna/useragent v1.3.4/go.mod h1:3d8TOmwL/5I8pJjyVDteHtgDGcefrFUX4ccGOMKNYYc=
67 | github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
68 | github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
69 | github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
70 | github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
71 | github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
72 | github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
73 | github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
74 | github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU=
75 | github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
76 | github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
77 | github.com/onsi/gomega v1.34.1 h1:EUMJIKUjM8sKjYbtxQI9A4z2o+rruxnzNvpknOXie6k=
78 | github.com/onsi/gomega v1.34.1/go.mod h1:kU1QgUvBDLXBJq618Xvm2LUX6rSAfRaFRTcdOeDLwwY=
79 | github.com/pierrec/lz4/v4 v4.1.21 h1:yOVMLb6qSIDP67pl/5F7RepeKYu/VmTyEXvuMI5d9mQ=
80 | github.com/pierrec/lz4/v4 v4.1.21/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
81 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
82 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
83 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
84 | github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
85 | github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
86 | github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
87 | github.com/rs/zerolog v1.33.0 h1:1cU2KZkvPxNyfgEmhHAz/1A9Bz+llsdYzklWFzgp0r8=
88 | github.com/rs/zerolog v1.33.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss=
89 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
90 | github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
91 | github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
92 | github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
93 | github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
94 | github.com/zeebo/assert v1.3.0 h1:g7C04CbJuIDKNPFHmsk4hwZDO5O+kntRxzaUoNXj+IQ=
95 | github.com/zeebo/assert v1.3.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0=
96 | github.com/zeebo/xxh3 v1.0.2 h1:xZmwmqxHZA8AI603jOQ0tMqmBr9lPeFwGg6d+xy9DC0=
97 | github.com/zeebo/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaDcA=
98 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
99 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
100 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
101 | golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8=
102 | golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY=
103 | golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
104 | golang.org/x/mod v0.19.0 h1:fEdghXQSo20giMthA7cd28ZC+jts4amQ3YMXiP5oMQ8=
105 | golang.org/x/mod v0.19.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
106 | golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
107 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
108 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
109 | golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
110 | golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
111 | golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys=
112 | golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE=
113 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
114 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
115 | golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
116 | golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
117 | golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
118 | golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
119 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
120 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
121 | golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
122 | golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
123 | golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
124 | golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
125 | golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
126 | golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
127 | golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
128 | golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
129 | golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
130 | golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
131 | golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI=
132 | golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
133 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
134 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
135 | golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
136 | golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
137 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
138 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
139 | golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
140 | golang.org/x/tools v0.23.0 h1:SGsXPZ+2l4JsgaCKkx+FQ9YZ5XEtA1GZYuoDjenLjvg=
141 | golang.org/x/tools v0.23.0/go.mod h1:pnu6ufv6vQkll6szChhK3C3L/ruaIv5eBeztNG8wtsI=
142 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
143 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
144 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
145 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
146 | golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 h1:+cNy6SZtPcJQH3LJVLOSmiC7MMxXNOb3PU/VUEz+EhU=
147 | golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90=
148 | gonum.org/v1/gonum v0.12.0 h1:xKuo6hzt+gMav00meVPUlXwSdoEJP46BR+wdxQEFK2o=
149 | gonum.org/v1/gonum v0.12.0/go.mod h1:73TDxJfAAHeA8Mk9mf8NlIppyhQNo5GLTcYeqgo2lvY=
150 | google.golang.org/genproto/googleapis/rpc v0.0.0-20240528184218-531527333157 h1:Zy9XzmMEflZ/MAaA7vNcoebnRAld7FsPW1EeBB7V0m8=
151 | google.golang.org/genproto/googleapis/rpc v0.0.0-20240528184218-531527333157/go.mod h1:EfXuqaE1J41VCDicxHzUDm+8rk+7ZdXzHV0IhO/I6s0=
152 | google.golang.org/grpc v1.65.0 h1:bs/cUb4lp1G5iImFFd3u5ixQzweKizoZJAwBNLR42lc=
153 | google.golang.org/grpc v1.65.0/go.mod h1:WgYC2ypjlB0EiQi6wdKixMqukr6lBc0Vo+oOgjrM5ZQ=
154 | google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
155 | google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
156 | google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
157 | google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
158 | google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
159 | google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
160 | google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg=
161 | google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
162 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
163 | gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
164 | gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
165 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
166 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
167 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
168 | gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
169 | gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
170 | gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
171 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
172 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
173 |
--------------------------------------------------------------------------------
/pkg/analytics/influx.go:
--------------------------------------------------------------------------------
1 | package analytics
2 |
3 | import (
4 | "context"
5 | "time"
6 |
7 | "github.com/InfluxCommunity/influxdb3-go/influxdb3"
8 | )
9 |
10 | type InfluxAnalytics struct {
11 | database string
12 | }
13 |
14 | type Data struct {
15 | Measurement string `lp:"measurement"`
16 | Type string `lp:"tag,type"`
17 | Platform string `lp:"tag,platform"`
18 | Value int64 `lp:"field,value"`
19 | Time time.Time `lp:"timestamp"`
20 | }
21 |
22 | func NewInfluxAnalytics(database string) *InfluxAnalytics {
23 | return &InfluxAnalytics{database: database}
24 | }
25 |
26 | func (a *InfluxAnalytics) LogClick(meta string, platform string, ctx context.Context) error {
27 | client, err := influxdb3.NewFromEnv()
28 | if err != nil {
29 | return err
30 | }
31 | defer client.Close()
32 | point := Data{
33 | Measurement: "link",
34 | Type: "click",
35 | Platform: platform,
36 | Value: 1,
37 | Time: time.Now(),
38 | }
39 | points := []any{&point}
40 | return client.WriteData(
41 | ctx,
42 | points,
43 | influxdb3.WithDatabase(a.database),
44 | )
45 | }
46 |
47 | func (a *InfluxAnalytics) LogInstall(meta string, platform string, ctx context.Context) error {
48 | client, err := influxdb3.NewFromEnv()
49 | if err != nil {
50 | return err
51 | }
52 | defer client.Close()
53 | point := Data{
54 | Measurement: "link",
55 | Type: "install",
56 | Platform: platform,
57 | Value: 1,
58 | Time: time.Now(),
59 | }
60 | points := []any{&point}
61 | return client.WriteData(
62 | ctx,
63 | points,
64 | influxdb3.WithDatabase(a.database),
65 | )
66 | }
67 |
--------------------------------------------------------------------------------
/pkg/database/metadata.go:
--------------------------------------------------------------------------------
1 | package database
2 |
3 | import (
4 | "time"
5 |
6 | "github.com/go-redis/redis"
7 | )
8 |
9 | type Metadata struct {
10 | rdClient *redis.Client
11 | metaLifetime time.Duration
12 | }
13 |
14 | func NewMetadata(host string, lifetime time.Duration) (*Metadata, error) {
15 | opt, err := redis.ParseURL(host)
16 | if err != nil {
17 | return nil, err
18 | }
19 | db := redis.NewClient(opt)
20 | return &Metadata{
21 | rdClient: db,
22 | metaLifetime: lifetime,
23 | }, nil
24 | }
25 |
26 | func (m *Metadata) Get(ip string) (string, error) {
27 | return m.rdClient.Get(ip).Result()
28 | }
29 |
30 | func (m *Metadata) Set(ip, value string) error {
31 | return m.rdClient.Set(ip, value, m.metaLifetime).Err()
32 | }
33 |
--------------------------------------------------------------------------------