I am attempting to create a REST API in Go. I have it partially working in that it will return 4 separate json objects like such:
[{"Name":"QA1","Server":"BOT1","Description":"Tools","Apps":""},
{"Name":"QA1","Server":"","Description":"","Apps":"Duo"},
{"Name":"QA1","Server":"","Description":"","Apps":"Git"},
{"Name":"QA1","Server":"","Description":"","Apps":"php"}]
What I want is a single returned object like:
[{"Name":"QA1","Server":"BOT1","Description":"Tools","Apps": "Duo|Git|php"}]
I obviously have the way that I am either making my queries or the structs (or both or something else) not quite correct. I want to make sure I understand how to do this right because I would like to expand on it for other queries and such down the road. I have included the "full" go code below.
To be clear, I'm not simply looking for the solution (though I would of course appreciate that to compare with), but where I've gone wrong in my thinking and what the correct approach would be.
package main
import (
"database/sql"
"encoding/json"
"fmt"
_ "github.com/go-sql-driver/mysql"
"io/ioutil"
"log"
"net/http"
)
// There can be zero or more apps on a volume
type Apps struct {
Name string
}
// Volumes have a name, description, are on a server and have multiple services/apps
type Volume struct {
Name string
Server string
Description string
Services Apps
}
//Handle all requests
func Handler(response http.ResponseWriter, request *http.Request) {
response.Header().Set("Content-type", "text/html")
webpage, err := ioutil.ReadFile("index.html")
if err != nil {
http.Error(response, fmt.Sprintf("home.html file error %v", err), 500)
}
fmt.Fprint(response, string(webpage))
}
// DB Connection
const (
DB_HOST = "mydbhost"
DB_NAME = "mydb"
DB_USER = "mydbuser"
DB_PASS = "mydbpass"
)
// Respond to URLs of the form /api
func APIHandler(response http.ResponseWriter, request *http.Request) {
//Connect to database
dsn := DB_USER + ":" + DB_PASS + "#" + DB_HOST + "/" + DB_NAME + "?charset=utf8"
db, err := sql.Open("mysql", dsn)
if err != nil {
fmt.Println(err.Error())
}
defer db.Close()
// Open doesn't open a connection. Validate DSN data:
err = db.Ping()
if err != nil {
fmt.Println(err.Error())
}
//set mime type to JSON
response.Header().Set("Content-type", "application/json")
result := []*Volume{}
switch request.Method {
case "GET":
srvrnm := request.URL.Query().Get("srvrnm")
appnm := request.URL.Query().Get("appnm")
srvrs, err := db.Prepare("select VOLUMES.name as volnm, SERVERS.name as srvrnm, VOLUMES.description as descr From VOLUMES LEFT JOIN SERVERS ON VOLUMES.server_id = SERVERS.id where SERVERS.name = ?")
if err != nil {
fmt.Print(err)
}
srvcs, err := db.Prepare("select VOLUMES.name as volnm, SUPPRTSVCS.name as app_name From VOLUMES as VOLUMES JOIN HOSTSVCS ON VOLUMES.id = HOSTSVCS.volume_id JOIN SUPPRTSVCS ON SUPPRTSVCS.id = HOSTSVCS.supportsvcs_id where VOLUMES.name = ?")
if err != nil {
fmt.Print(err)
}
// Run the SQL Query to Get Volum & Description From Hostname
srvrrows, err := srvrs.Query(srvrnm)
if err != nil {
fmt.Print(err)
}
for srvrrows.Next() {
var volnm string
var srvrnm string
var descr string
// Scan the First Query
err = srvrrows.Scan(&volnm, &srvrnm, &descr)
if err != nil {
fmt.Println("Error scanning: " + err.Error())
return
}
// Append Slice with results from the scan
result = append(result, &Volume{Name: volnm, Server: srvrnm, Description: descr})
}
// Run the SQL Query for Services/Apps
srvcrows, err := srvcs.Query(appnm)
if err != nil {
fmt.Print(err)
}
for srvcrows.Next() {
var volnm string
var appnm string
// Scan the Second Query
err = srvcrows.Scan(&volnm, &appnm)
if err != nil {
fmt.Println("Error scanning: " + err.Error())
return
}
// Append Slice with results from the scan
result = append(result, &Volume{Name: volnm, Apps: appnm})
}
default:
}
json, err := json.Marshal(result)
if err != nil {
fmt.Println(err)
return
}
fmt.Fprintf(response, string(json))
db.Close()
}
func main() {
port := "1236"
var err string
mux := http.NewServeMux()
mux.Handle("/api", http.HandlerFunc(APIHandler))
mux.Handle("/", http.HandlerFunc(Handler))
// Start listing on a given port with these routes on this server.
log.Print("Listening on port " + port + " ... ")
errs := http.ListenAndServe(":"+port, mux)
if errs != nil {
log.Fatal("ListenAndServe error: ", err)
}
}
From the sounds of it, you want to your result to look like:
[
{"Name":"QA1","Server":"BOT1","Description":"Tools","Apps": ["Duo","Git","php"]
]
Hence you want your Volumes struct to look like:
type Volume struct {
Name string
Server string
Description string
Services []Apps
}
If you want the Apps to actually output Duo|Git|php then you could create a custom type instead of []Apps with a JSON Marshaler implementation. This could simply return json.Marshal(strings.join(names,"|"))
Rather than run two separate queries, it would be more efficient to run a single query that selects the product of volumes & apps together. It is important that this query is sorted by volume so all volume rows are contiguous. Example query output would be:
Name | Server | Desc | App
---- | ------ | ----- | ---
Vol1 | Srv1 | Desc1 | App1
Vol1 | Srv1 | Desc1 | App2
Vol2 | Srv2 | Desc2 | App3
You would then loop over this and detect if you are looking at a new volume. If so, create a new entry in the result. If not, add the App to the list of apps. For example:
var (
volnm string
srvrnm string
descr string
appnm string
v *Volume
result []*Volume
)
for srvrrows.Next() {
if err = srvcrows.Scan(&volnm, &srvrnm, &descr, &appnm);err!=nil {
// Handle error
}
// Add App to current volume if same, otherwise start a new volume
if v!=nil && v.Name == volnm {
v.Services = append(v.Services,Apps{appnm})
} else {
v = &Volume{
Name: volnm,
Server: svrnm,
Description: descr,
Services: []Apps{appnm}}
result = append(result,v)
}
}
// Finished, return result etc...
When taking this approach, you need an appropriate parent record discriminator. I'd just used v.Name == volnm for illustration purposes but this should really be checking the primary key. You can make this an unexported (lowercase) field in the struct if you do not wish to export it through the API.
Related
Since a few days, I'm trying to download file from Huawei, and more precisely on their cloud storage. The issue is, I haven't been able to connect to it...
I found a SDK from huawei : https://github.com/huaweicloud/huaweicloud-sdk-go-v3
But I'm a little bit lost on all the protocol I can use to connect to it, and I haven't been able to make one working, each time. And to be honest, documentations isn't really helping me...
I also found this : https://github.com/huaweicloud/huaweicloud-sdk-go-obs
There is an example of downloading a file. Here, I can't even connect to Huawei... In the AppGalery, project settings, I downloaded the configuration file and tried the endpoint, but without success...
Here is what I tried with obs (I know/guess it should be agc, but I haven't found a package for it), but not working due to the host...
/**
* This sample demonstrates how to download an object
* from OBS in different ways using the OBS SDK for Go.
*/
package huawei
import (
"fmt"
"io"
"github.com/huaweicloud/huaweicloud-sdk-go-obs/obs"
)
type DownloadSample struct {
bucketName string
objectKey string
location string
obsClient *obs.ObsClient
}
func newDownloadSample(ak, sk, endpoint, bucketName, objectKey, location string) *DownloadSample {
obsClient, err := obs.New(ak, sk, endpoint)
if err != nil {
panic(err)
}
return &DownloadSample{obsClient: obsClient, bucketName: bucketName, objectKey: objectKey, location: location}
}
func (sample DownloadSample) GetObject() {
input := &obs.GetObjectInput{}
input.Bucket = sample.bucketName
input.Key = sample.objectKey
fmt.Printf("%+v\n", input)
output, err := sample.obsClient.GetObject(input)
if err != nil {
panic(err)
}
defer func() {
errMsg := output.Body.Close()
if errMsg != nil {
panic(errMsg)
}
}()
fmt.Println("Object content:")
body, err := io.ReadAll(output.Body)
if err != nil {
panic(err)
}
fmt.Println(string(body))
fmt.Println()
}
func RunDownloadSample() {
const (
endpoint = "theEndPointInConfigJSONFile"
ak = "prettySureOfThis"
sk = "prettySureOfThis"
bucketName = "prettySureOfThis"
objectKey = "test.txt" // a txt in the bucket to try to download it
location = ""
)
sample := newDownloadSample(ak, sk, endpoint, bucketName, objectKey, location)
fmt.Println("Download object to string")
sample.GetObject()
}
Thank you for your help
I have a function that retrieve the mongodb admin users using .command
In the below function, I have the DbUsers struct, and I am running the command to retrieve the users from 2 different database.
My question is, how can I concat the 2 results (adminUsers & externalUsers) and return after merged? They are of the same struct.
type DbUsers struct {
...lots of stuff about the server
Users []Users
}
type Users struct {
User string
...lots of stuff
}
func getUsers() Users {
admin := CNX.Database("admin")
external := CNX.Database("$external")
command := bson.D{primitive.E{Key: "usersInfo", Value: 1}}
var adminUsers DbUsers
var externalUsers DbUsers
err := admin.RunCommand(context.TODO(), command).Decode(&adminUsers)
if err != nil {
panic(err)
}
err2 := external.RunCommand(context.TODO(), command).Decode(&externalUsers)
if err2 != nil {
panic(err2)
}
//New to Golang, not sure what I am doing but this doesn't work
return []Users{adminUsers.Users, externalUsers.Users}
}
You can do
return append(adminUsers.Users, externalUsers.Users...)
Recently I tried to document my code, but I had some trouble using godoc because there's some function that didn't came up when I ran godoc -http:localhost:6060
This is what my code looks like:
type MongoDBInterface interface {
ExecuteTransaction(operation func(mongoClient MongoDBInterface) error) error
Count(tableName string, clause bson.M) (int, error)
Distinct(tableName, fieldName string, clause bson.M) ([]interface{}, error)
InsertOrUpdate(tableName string, clause bson.M, data models.BaseModelInterface) (primitive.ObjectID, error)
InsertOrUpdateFields(tableName string, clause bson.M, data interface{}) (primitive.ObjectID, error)
Insert(tableName string, data models.BaseModelInterface) (primitive.ObjectID, error)
Update(tableName string, clause bson.M, data models.BaseModelInterface) error
UpdateFields(tableName string, clause bson.M, data interface{}) error
FindOne(tableName string, clause, opt bson.M, result interface{}) error
FindMany(tableName string, clause, opt bson.M, result interface{}) error
Truncate(tableName string) error
Delete(tableName string, clause bson.M) error
Aggregate(tableName string, pipelines interface{}, result interface{}) error
EnsureCollections() error
}
type mongoDB struct {
session mongo.Session
db *mongo.Database
ctx context.Context
isTransactionEnabled bool
isConnected bool
connString string
}
// NewMongoDB definition
func NewMongoDB() MongoDBInterface {
mongoClient := new(mongoDB)
mongoClient.ctx = context.Background()
dbHost := os.Getenv("DB_HOST")
if dbHost == "" {
dbHost = "localhost"
}
dbUser := os.Getenv("DB_USERNAME")
if dbUser == "" {
dbUser = "dbadmin"
}
dbPswd := os.Getenv("DB_PASSWORD")
if dbPswd == "" {
dbPswd = "dbpassword"
}
dbName := os.Getenv("DB_NAME")
if dbName == "" {
dbName = "dbname"
}
dbAuth := os.Getenv("DB_AUTH")
if dbAuth == "" {
dbAuth = "admin"
}
dbMode := os.Getenv("DB_MODE")
if dbMode == "" {
dbMode = "admin"
}
// temporary
connString := fmt.Sprintf("mongodb+srv://%s:%s#%s/%s?retryWrites=true&w=majority", dbUser, dbPswd, dbHost, dbName)
mongoClient.connString = connString
return mongoClient
}
// connect to mongodb server
func (s *mongoDB) connect() error {
// get query string from env,
// then parse it to get db name
// connString := os.Getenv("MONGODB_CONN_STRING")
connString := s.connString
log.Println("ConnString =>", connString)
parts := strings.Split(connString, "/")
dbName := strings.Split(parts[len(parts)-1], "?")[0]
// prepare options object for connecting to mongodb
opt := options.Client()
opt.ApplyURI(connString)
// set the timeout info from data defined in the env
// timeout, _ := strconv.Atoi(os.Getenv("MONGODB_TIMEOUT_IN_SECOND"))
timeout := 120
timeoutDuration := time.Duration(timeout) * time.Second
opt.ConnectTimeout = &timeoutDuration
// create client object
client, err := mongo.NewClient(opt)
if err != nil {
log.Println(err.Error())
return err
}
// connect to the db server
err = client.Connect(context.Background())
if err != nil {
log.Println(err.Error())
return err
}
// start new session
session, err := client.StartSession()
if err != nil {
log.Println(err.Error())
return err
}
// store session and db info into props
s.session = session
s.db = client.Database(dbName)
s.isConnected = true
log.Println("Connected to database")
return nil
}
The problem is godoc will never render func (s *mongoDB) connect() error but I need it to be documented, can you guys explain to me what's going on with godoc? Or maybe you can give me some solutions and tips for documenting Go code.
You can refer this doc: https://pkg.go.dev/golang.org/x/tools/cmd/godoc
The presentation mode of web pages served by godoc can be controlled with the "m" URL parameter; it accepts a comma-separated list of flag names as value:
- all show documentation for all declarations, not just the exported ones
- methods show all embedded methods, not just those of unexported anonymous fields
- src show the original source code rather than the extracted documentation
- flat present flat (not indented) directory listings using full paths
For instance, https://golang.org/pkg/math/big/?m=all shows the documentation for all (not just the exported) declarations of package big.
?m=all documents all declaration including the non-exported methods
Using Go and AWS-SDK
I'm attempting to query route53 CNAME and A records as listed in the AWS Console under Route53 -> Hosted Zones. I'm able to query using the following code, but it requires the (cryptic) HostedZoneId I have to know ahead of time.
Is there a different function, or a HostedZoneId lookup based on the Domain Name such as XXX.XXX.com ?
AWSLogin(instance)
svc := route53.New(instance.AWSSession)
listParams := &route53.ListResourceRecordSetsInput{
HostedZoneId: aws.String("Z2798GPJN9CUFJ"), // Required
// StartRecordType: aws.String("CNAME"),
}
respList, err := svc.ListResourceRecordSets(listParams)
if err != nil {
fmt.Println(err.Error())
return
}
// Pretty-print the response data.
fmt.Println("All records:")
fmt.Println(respList)
edit: oh, additionally, the StartRecordType with the value "CNAME" throws a validation error, so I'm not sure what I should be using there.
You first have to do a lookup to get the HostedZoneID. Here is the func I wrote for it. :
func GetHostedZoneIdByNameLookup(awsSession string, HostedZoneName string) (HostedZoneID string, err error) {
svc := route53.New(awsSession)
listParams := &route53.ListHostedZonesByNameInput{
DNSName: aws.String(HostedZoneName), // Required
}
req, resp := svc.ListHostedZonesByNameRequest(listParams)
err = req.Send()
if err != nil {
return "", err
}
HostedZoneID = *resp.HostedZones[0].Id
// remove the /hostedzone/ path if it's there
if strings.HasPrefix(HostedZoneID, "/hostedzone/") {
HostedZoneID = strings.TrimPrefix(HostedZoneID, "/hostedzone/")
}
return HostedZoneID, nil
}
I am facing an issue where i have made an api in Go every thing work fine but i am not getting data in postman. When i print the data in logs i am getting the data properly but it is showing blank data in postman.
authorizeModel.go
func GetSkillList() map[string]interface{} {
db := GetDB()
var (
// id int
skillName string
)
type SkillList struct {
name string
}
skillList := SkillList{}
skillArr := []SkillList{}
rows, err := db.Query("select DISTINCT(name) as name from skills where company_id IN ('2') and name != 'Skill Needed' order by name")
if err != nil {
log.Fatal(err)
}
defer rows.Close()
for rows.Next() {
err := rows.Scan(&skillName)
if err != nil {
log.Fatal(err)
}
skillList.name = skillName
skillArr = append(skillArr, skillList)
}
response := u.Message(true, "Skill list retrieved successfully")
response["data"] = skillArr
log.Println(skillArr)
response["authorization"] = false
return response
}
authController.go
var SkillTagList = func(w http.ResponseWriter, r *http.Request) {
resp := models.GetSkillList()
u.Respond(w, resp)
}
routes.go
router.HandleFunc("/api/v1/authorize/skillTagList", controllers.SkillTagList).Methods("POST")
If you see authorizeModel.go i have printed my data in logs i am getting that data successfully in logs. But see the postman screenshot below.
You have to rename name to Name
I'm not sure what is u.Respond(), so I will assume it's a helper function of some framework that you are using, and I will assume u.Respond() is internally using json.Marshal.
If your struct has unexported fields(fields name starting with lowercase letter, in your case name), json.Marshal cannot access those field, and the result won't have name field. That is why you are getting empty objects in JSON.