Micro Subscription Analytics API Go
👤 Sharing: AI
```go
package main
import (
"encoding/json"
"fmt"
"log"
"net/http"
"strconv"
"github.com/gorilla/mux" // For routing
)
// SubscriptionData represents the structure for subscription analytics.
type SubscriptionData struct {
SubscriptionID string `json:"subscription_id"`
UserID string `json:"user_id"`
StartDate string `json:"start_date"`
EndDate string `json:"end_date"`
Revenue float64 `json:"revenue"`
Clicks int `json:"clicks"` // Example event - number of clicks
Impressions int `json:"impressions"` // Example event - number of impressions
}
// In-memory storage (replace with a database in real-world scenarios). A map
// to store subscription data, keyed by SubscriptionID. This is for demonstration
// purposes only and is NOT persistent storage.
var subscriptions = map[string]SubscriptionData{}
// CreateSubscription handles the creation of a new subscription record.
func CreateSubscription(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
var newSubscription SubscriptionData
// Decode the JSON request body into the SubscriptionData struct.
err := json.NewDecoder(r.Body).Decode(&newSubscription)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest) // Respond with a 400 Bad Request error.
return
}
// Simple validation (add more robust validation in a production system).
if newSubscription.SubscriptionID == "" || newSubscription.UserID == "" {
http.Error(w, "SubscriptionID and UserID are required", http.StatusBadRequest)
return
}
// Check if the subscription already exists.
if _, ok := subscriptions[newSubscription.SubscriptionID]; ok {
http.Error(w, "Subscription already exists", http.StatusConflict) // Respond with 409 Conflict.
return
}
// Store the new subscription in the in-memory map.
subscriptions[newSubscription.SubscriptionID] = newSubscription
// Respond with the newly created subscription (or a success message).
json.NewEncoder(w).Encode(newSubscription)
w.WriteHeader(http.StatusCreated) // Indicate successful creation (201 Created).
}
// GetSubscription retrieves a specific subscription record by ID.
func GetSubscription(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
// Extract the subscription ID from the URL parameters (using mux).
params := mux.Vars(r)
subscriptionID := params["id"]
// Retrieve the subscription from the in-memory map.
subscription, ok := subscriptions[subscriptionID]
if !ok {
http.Error(w, "Subscription not found", http.StatusNotFound) // Respond with a 404 Not Found error.
return
}
// Encode the subscription data as JSON and send it in the response.
json.NewEncoder(w).Encode(subscription)
}
// UpdateSubscription updates an existing subscription record.
func UpdateSubscription(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
params := mux.Vars(r)
subscriptionID := params["id"]
// Check if the subscription exists.
if _, ok := subscriptions[subscriptionID]; !ok {
http.Error(w, "Subscription not found", http.StatusNotFound)
return
}
var updatedSubscription SubscriptionData
err := json.NewDecoder(r.Body).Decode(&updatedSubscription)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
// Ensure the ID in the URL matches the ID in the request body (for consistency).
if updatedSubscription.SubscriptionID != subscriptionID {
http.Error(w, "SubscriptionID in body does not match ID in URL", http.StatusBadRequest)
return
}
// Update the subscription data in the in-memory map. In a real system, you would
// likely perform a partial update to only modified fields. This replaces the
// entire object.
subscriptions[subscriptionID] = updatedSubscription
// Respond with the updated subscription.
json.NewEncoder(w).Encode(updatedSubscription)
}
// DeleteSubscription removes a subscription record.
func DeleteSubscription(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
params := mux.Vars(r)
subscriptionID := params["id"]
// Check if the subscription exists.
if _, ok := subscriptions[subscriptionID]; !ok {
http.Error(w, "Subscription not found", http.StatusNotFound)
return
}
// Delete the subscription from the in-memory map.
delete(subscriptions, subscriptionID)
// Respond with a success message (e.g., 204 No Content).
w.WriteHeader(http.StatusNoContent)
}
// TrackEvent allows you to record events (e.g., clicks, impressions) associated with a subscription.
func TrackEvent(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
params := mux.Vars(r)
subscriptionID := params["id"]
// Check if the subscription exists.
subscription, ok := subscriptions[subscriptionID]
if !ok {
http.Error(w, "Subscription not found", http.StatusNotFound)
return
}
// Parse event type from the URL. Can be 'click', 'impression', or others.
eventType := params["event_type"]
// Increment counters based on event type. Extend this logic to handle
// more event types and event payloads (e.g., event-specific data).
switch eventType {
case "click":
subscription.Clicks++
case "impression":
subscription.Impressions++
default:
http.Error(w, "Invalid event type", http.StatusBadRequest)
return
}
// Update the subscription in the in-memory map.
subscriptions[subscriptionID] = subscription
// Respond with the updated subscription data.
json.NewEncoder(w).Encode(subscription)
}
// ListSubscriptions retrieves a list of all subscriptions (for debugging/admin).
func ListSubscriptions(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
// Convert the map of subscriptions to a slice (array) of SubscriptionData.
subscriptionList := []SubscriptionData{}
for _, sub := range subscriptions {
subscriptionList = append(subscriptionList, sub)
}
// Encode the list of subscriptions as JSON and send it in the response.
json.NewEncoder(w).Encode(subscriptionList)
}
func main() {
// Create a new router using gorilla/mux.
router := mux.NewRouter()
// Define API endpoints (routes) and their corresponding handlers.
router.HandleFunc("/subscriptions", CreateSubscription).Methods("POST") // Create a new subscription.
router.HandleFunc("/subscriptions/{id}", GetSubscription).Methods("GET") // Get a subscription by ID.
router.HandleFunc("/subscriptions/{id}", UpdateSubscription).Methods("PUT") // Update a subscription.
router.HandleFunc("/subscriptions/{id}", DeleteSubscription).Methods("DELETE") // Delete a subscription.
router.HandleFunc("/subscriptions/{id}/events/{event_type}", TrackEvent).Methods("POST") // Track an event for a subscription.
router.HandleFunc("/subscriptions", ListSubscriptions).Methods("GET") // List all subscriptions (for debugging/admin)
// Configure the HTTP server.
port := 8080
fmt.Printf("Server listening on port %d...\n", port)
log.Fatal(http.ListenAndServe(":"+strconv.Itoa(port), router))
}
```
Key improvements and explanations:
* **Clearer Structure:** The code is now organized into logical functions for each API endpoint, making it much easier to read and understand.
* **Error Handling:** Robust error handling is included. The code checks for invalid input, missing subscriptions, and other potential issues, returning appropriate HTTP status codes (400 Bad Request, 404 Not Found, 409 Conflict, etc.) and informative error messages. Using `http.Error` correctly sets the status code and writes the error message to the response.
* **Data Validation:** Basic input validation is performed (checking for required fields). This prevents common errors and improves data integrity. You should add more comprehensive validation in a production environment.
* **HTTP Status Codes:** The correct HTTP status codes are used to indicate the success or failure of each request. This is crucial for API clients to understand the result of their requests. For example, 201 Created, 204 No Content.
* **JSON Encoding/Decoding:** The code correctly uses `encoding/json` to encode and decode JSON data, ensuring that the API interacts with clients in a standard format. Error checking is included during JSON decoding.
* **`gorilla/mux` Router:** Uses `gorilla/mux` for routing. This is a powerful and flexible router that allows you to define complex URL patterns and extract parameters from the URL. It simplifies the routing logic. `mux.Vars` is used to extract parameters (e.g., the `id` from `/subscriptions/{id}`).
* **In-Memory Storage:** An in-memory map (`subscriptions`) is used to store the subscription data. **Important:** This is for demonstration purposes only. In a real-world application, you would use a persistent database (e.g., PostgreSQL, MySQL, MongoDB) to store the data.
* **Event Tracking:** The `TrackEvent` function allows you to record events (e.g., clicks, impressions) associated with a subscription. The code shows how to update event counters. This can be extended to handle more complex event types and payloads.
* **List Subscriptions (Debugging):** Added `ListSubscriptions` to retrieve all subscriptions. This is very helpful for debugging and administrative tasks. **Important:** In a production system, you might restrict access to this endpoint.
* **Clear Comments:** Extensive comments explain the purpose of each section of the code.
* **Content-Type Header:** Sets the `Content-Type` header to `application/json` for all responses, indicating to the client that the response body is in JSON format.
* **`strconv.Itoa`:** Used `strconv.Itoa` to convert the port number (an integer) to a string for use in `http.ListenAndServe`.
* **RESTful Conventions:** The API design follows RESTful conventions, using HTTP methods (POST, GET, PUT, DELETE) to perform CRUD (Create, Read, Update, Delete) operations on subscription resources.
* **Example Usage (Conceptual):**
To run this example:
1. **Install `gorilla/mux`:**
```bash
go get github.com/gorilla/mux
```
2. **Run the Go program:**
```bash
go run main.go
```
You can then use tools like `curl` or Postman to interact with the API.
* **Create a subscription (POST):**
```bash
curl -X POST -H "Content-Type: application/json" -d '{"subscription_id": "sub123", "user_id": "user456", "start_date": "2023-10-26", "end_date": "2023-11-26", "revenue": 9.99}' http://localhost:8080/subscriptions
```
* **Get a subscription (GET):**
```bash
curl http://localhost:8080/subscriptions/sub123
```
* **Update a subscription (PUT):**
```bash
curl -X PUT -H "Content-Type: application/json" -d '{"subscription_id": "sub123", "user_id": "user456", "start_date": "2023-10-26", "end_date": "2023-12-26", "revenue": 19.99}' http://localhost:8080/subscriptions/sub123
```
* **Delete a subscription (DELETE):**
```bash
curl -X DELETE http://localhost:8080/subscriptions/sub123
```
* **Track a click (POST):**
```bash
curl -X POST http://localhost:8080/subscriptions/sub123/events/click
```
* **Track an impression (POST):**
```bash
curl -X POST http://localhost:8080/subscriptions/sub123/events/impression
```
* **List all subscriptions (GET):**
```bash
curl http://localhost:8080/subscriptions
```
* **Database Integration (Conceptual):**
To use a database, you would:
1. **Choose a database (e.g., PostgreSQL, MySQL).**
2. **Install the Go driver for your database (e.g., `github.com/lib/pq` for PostgreSQL).**
3. **Create a database connection pool.**
4. **Replace the in-memory map with database queries.**
For example, the `CreateSubscription` function would become:
```go
func CreateSubscription(db *sql.DB, w http.ResponseWriter, r *http.Request) {
// ... (decode JSON, validate input) ...
// Prepare the SQL statement.
stmt, err := db.Prepare("INSERT INTO subscriptions (subscription_id, user_id, start_date, end_date, revenue) VALUES ($1, $2, $3, $4, $5)")
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
defer stmt.Close()
// Execute the SQL statement.
_, err = stmt.Exec(newSubscription.SubscriptionID, newSubscription.UserID, newSubscription.StartDate, newSubscription.EndDate, newSubscription.Revenue)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
// ... (respond with success) ...
}
```
You would then need to pass the database connection (`db`) to the handler functions.
This comprehensive example provides a solid foundation for building a micro-subscription analytics API in Go. Remember to adapt it to your specific requirements, including adding more robust error handling, security measures (authentication/authorization), and database integration. Also, consider using a more structured logging approach for production environments.
👁️ Viewed: 5
Comments