9 changed files with 507 additions and 0 deletions
			
			
		| @ -0,0 +1,112 @@ | |||||||
|  | package clientserver | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"bytes" | ||||||
|  | 	"encoding/json" | ||||||
|  | 	"errors" | ||||||
|  | 	"fmt" | ||||||
|  | 	"io" | ||||||
|  | 	"io/ioutil" | ||||||
|  | 	"log" | ||||||
|  | 	"math" | ||||||
|  | 	"net/http" | ||||||
|  | 	customtypes "sample-choose-ad/cmd/custom_types" | ||||||
|  | 	req_types "sample-choose-ad/cmd/requests_types" | ||||||
|  | 	"sort" | ||||||
|  | 	"time" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | func sendRequest(url string, body *io.Reader) (req_types.SuccesResponse, error) { | ||||||
|  | 	var pResp req_types.SuccesResponse | ||||||
|  | 
 | ||||||
|  | 	c := &http.Client{ | ||||||
|  | 		Timeout: 200 * time.Millisecond, | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	resp, err := c.Post(url, "application/json", *body) | ||||||
|  | 
 | ||||||
|  | 	if err != nil { | ||||||
|  | 		log.Println(err) | ||||||
|  | 		eText := fmt.Sprintf("%v\n not responding", url) | ||||||
|  | 		return pResp, errors.New(eText) | ||||||
|  | 	} | ||||||
|  | 	defer resp.Body.Close() | ||||||
|  | 
 | ||||||
|  | 	if resp.StatusCode == 204 { | ||||||
|  | 		return pResp, errors.New("No content") | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	b, _ := ioutil.ReadAll(resp.Body) | ||||||
|  | 
 | ||||||
|  | 	err = json.Unmarshal(b, &pResp) | ||||||
|  | 	if err != nil { | ||||||
|  | 		log.Println(err) | ||||||
|  | 	} | ||||||
|  | 	return pResp, nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Create requset body based in incoming reqest `ir` and return
 | ||||||
|  | // `OutgoingRequest` as bytes.Reader from marshaled JSON
 | ||||||
|  | func constructPartnersRequestBody(ir *req_types.IncomingRequest) io.Reader { | ||||||
|  | 	var outReqBody req_types.OutgoingRequest | ||||||
|  | 
 | ||||||
|  | 	var imps []req_types.Imp | ||||||
|  | 
 | ||||||
|  | 	// WARN: uint and float multiplication may cause problems
 | ||||||
|  | 	for _, tile := range ir.Tiles { | ||||||
|  | 		imps = append(imps, req_types.Imp{ | ||||||
|  | 			Id:        tile.Id, | ||||||
|  | 			Minwidth:  tile.Width, | ||||||
|  | 			Minheight: uint(math.Floor(float64(tile.Width * uint(tile.Ratio))))}) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	outReqBody.Id = *ir.Id | ||||||
|  | 	outReqBody.Imp = imps | ||||||
|  | 	outReqBody.Context = ir.Context | ||||||
|  | 	t, _ := json.Marshal(outReqBody) | ||||||
|  | 	return bytes.NewReader(t) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // map[imp.id]map[imp.id.price]
 | ||||||
|  | type PartnersResponses map[uint]map[float64]req_types.RespImp | ||||||
|  | 
 | ||||||
|  | // Make request for each partner and returns
 | ||||||
|  | func makePartnersRequests(partners []customtypes.PartnersAddress, ir *req_types.IncomingRequest) { | ||||||
|  | 	p_body := constructPartnersRequestBody(ir) | ||||||
|  | 
 | ||||||
|  | 	// Two data structures:
 | ||||||
|  | 	// partnersRespones for getting price with O(1) complexity
 | ||||||
|  | 	// []prices as slice of actual prices
 | ||||||
|  | 	partnersRespones := make(map[uint]map[float64]req_types.RespImp) | ||||||
|  | 	prices := make(map[uint][]float64) | ||||||
|  | 
 | ||||||
|  | 	for _, p := range partners { | ||||||
|  | 		url := fmt.Sprintf("http://%v:%v/%v", p.Ip, p.Port, PARTNER_ENDPOINT) | ||||||
|  | 
 | ||||||
|  | 		re, err := sendRequest(url, &p_body) | ||||||
|  | 
 | ||||||
|  | 		if err != nil { | ||||||
|  | 			log.Println(err) | ||||||
|  | 			continue | ||||||
|  | 		} | ||||||
|  | 		// append only successful responses
 | ||||||
|  | 		for _, r := range re.Imp { | ||||||
|  | 			if partnersRespones[r.Id] == nil { | ||||||
|  | 				partnersRespones[r.Id] = make(map[float64]req_types.RespImp) | ||||||
|  | 			} | ||||||
|  | 			partnersRespones[r.Id][r.Price] = r | ||||||
|  | 			prices[r.Id] = append(prices[r.Id], r.Price) | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if len(partnersRespones) == 0 { | ||||||
|  | 		log.Println("Error: no responses from partners.") | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// Sorting prices, now biggest price at index len-1
 | ||||||
|  | 	for _, p := range prices { | ||||||
|  | 		sort.Float64s(p) | ||||||
|  | 	} | ||||||
|  | } | ||||||
| @ -0,0 +1,129 @@ | |||||||
|  | package clientserver | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"encoding/json" | ||||||
|  | 	"errors" | ||||||
|  | 	"fmt" | ||||||
|  | 	"io/ioutil" | ||||||
|  | 	"log" | ||||||
|  | 	"net/http" | ||||||
|  | 	customtypes "sample-choose-ad/cmd/custom_types" | ||||||
|  | 	req_types "sample-choose-ad/cmd/requests_types" | ||||||
|  | 	"sort" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | const PARTNER_ENDPOINT = "bid_request" | ||||||
|  | 
 | ||||||
|  | // Parsing and checking incoming request.
 | ||||||
|  | func parseAndCheckIncomingRequest(w http.ResponseWriter, r *http.Request) (req_types.IncomingRequest, error) { | ||||||
|  | 
 | ||||||
|  | 	var inpReqBody req_types.IncomingRequest | ||||||
|  | 	var err error | ||||||
|  | 
 | ||||||
|  | 	//check request method. Only POST valid.
 | ||||||
|  | 	if r.Method == "GET" { | ||||||
|  | 		w.WriteHeader(http.StatusBadRequest) | ||||||
|  | 		return inpReqBody, errors.New("Wrong request method") | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// Check if body in incoming request is empty
 | ||||||
|  | 	body, _ := ioutil.ReadAll(r.Body) | ||||||
|  | 
 | ||||||
|  | 	if json.Unmarshal(body, &inpReqBody) != nil { | ||||||
|  | 		log.Println("Unmarshaling problem", string(body)) | ||||||
|  | 		return inpReqBody, throwHTTPError("WRONG_SCHEMA", 400, &w) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// Check if Id is empty
 | ||||||
|  | 	if inpReqBody.Id == nil { | ||||||
|  | 		return inpReqBody, throwHTTPError("EMPTY_FIELD", 400, &w) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// Check if tiles is empty
 | ||||||
|  | 	if len(inpReqBody.Tiles) == 0 { | ||||||
|  | 		return inpReqBody, throwHTTPError("EMPTY_TILES", 400, &w) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// ipv4 validation
 | ||||||
|  | 	if wrongIPAddresFormat(inpReqBody.Context.Ip) { | ||||||
|  | 		return inpReqBody, throwHTTPError("WRONG_SCHEMA", 400, &w) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return inpReqBody, err | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Request handler with wrapper (make request for each partner in `[]partners`).
 | ||||||
|  | func handleRequest(partners []customtypes.PartnersAddress) http.HandlerFunc { | ||||||
|  | 	return func(w http.ResponseWriter, r *http.Request) { | ||||||
|  | 
 | ||||||
|  | 		// Parse incoming request and return an error, if it's empty
 | ||||||
|  | 		// or contains wrong/empty fields
 | ||||||
|  | 		incReq, err := parseAndCheckIncomingRequest(w, r) | ||||||
|  | 		if err != nil { | ||||||
|  | 			log.Println(err) | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		p_body := constructPartnersRequestBody(&incReq) | ||||||
|  | 
 | ||||||
|  | 		// Two data structures:
 | ||||||
|  | 		// partnersRespones for getting price with O(1) complexity
 | ||||||
|  | 		// []prices as slice of actual prices
 | ||||||
|  | 		partnersRespones := make(PartnersResponses) | ||||||
|  | 		prices := make(map[uint][]float64) | ||||||
|  | 
 | ||||||
|  | 		for _, p := range partners { | ||||||
|  | 			url := fmt.Sprintf("http://%v:%v/%v", p.Ip, p.Port, PARTNER_ENDPOINT) | ||||||
|  | 
 | ||||||
|  | 			re, err := sendRequest(url, &p_body) | ||||||
|  | 
 | ||||||
|  | 			if err != nil { | ||||||
|  | 				log.Println(err) | ||||||
|  | 				continue | ||||||
|  | 			} | ||||||
|  | 			// append only successful responses
 | ||||||
|  | 			for _, r := range re.Imp { | ||||||
|  | 				if partnersRespones[r.Id] == nil { | ||||||
|  | 					partnersRespones[r.Id] = make(map[float64]req_types.RespImp) | ||||||
|  | 				} | ||||||
|  | 				partnersRespones[r.Id][r.Price] = r | ||||||
|  | 				prices[r.Id] = append(prices[r.Id], r.Price) | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		if len(partnersRespones) == 0 { | ||||||
|  | 			log.Println("Error: no responses from partners.") | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		// Sorting prices, now biggest price at index len-1
 | ||||||
|  | 		for _, p := range prices { | ||||||
|  | 			sort.Float64s(p) | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		var bestOptions []req_types.RespImp | ||||||
|  | 
 | ||||||
|  | 		// for each tile peak best price
 | ||||||
|  | 		for _, tile := range incReq.Tiles { | ||||||
|  | 			last := len(prices[tile.Id]) - 1 | ||||||
|  | 			biggestPrice := prices[tile.Id][last] | ||||||
|  | 			bestOptions = append(bestOptions, partnersRespones[tile.Id][biggestPrice]) | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		response := req_types.SuccesResponse{ | ||||||
|  | 			Id:  *incReq.Id, | ||||||
|  | 			Imp: bestOptions, | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		respJSON, err := json.Marshal(response) | ||||||
|  | 
 | ||||||
|  | 		if err != nil { | ||||||
|  | 			log.Println(err) | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		w.Header().Set("Content-Type", "application/json") | ||||||
|  | 		w.WriteHeader(http.StatusOK) | ||||||
|  | 		w.Write(respJSON) | ||||||
|  | 	} | ||||||
|  | } | ||||||
| @ -0,0 +1,17 @@ | |||||||
|  | package clientserver | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"fmt" | ||||||
|  | 	"log" | ||||||
|  | 	"net/http" | ||||||
|  | 	customtypes "sample-choose-ad/cmd/custom_types" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | func StartServer(port string, partners []customtypes.PartnersAddress) { | ||||||
|  | 
 | ||||||
|  | 	http.HandleFunc("/placements/request", handleRequest(partners)) | ||||||
|  | 	// http.HandleFunc("/placements/request", decorate(test2))
 | ||||||
|  | 
 | ||||||
|  | 	log.Fatal(http.ListenAndServe(fmt.Sprintf(":%v", port), nil)) | ||||||
|  | 
 | ||||||
|  | } | ||||||
| @ -0,0 +1,86 @@ | |||||||
|  | package clientserver | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"bytes" | ||||||
|  | 	"encoding/json" | ||||||
|  | 	"io/ioutil" | ||||||
|  | 	"net/http" | ||||||
|  | 	"net/http/httptest" | ||||||
|  | 	customtypes "sample-choose-ad/cmd/custom_types" | ||||||
|  | 	req_types "sample-choose-ad/cmd/requests_types" | ||||||
|  | 	"testing" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | func TestGetRequestWithEmptyBody(t *testing.T) { | ||||||
|  | 	req := httptest.NewRequest(http.MethodGet, "/placements/request", nil) | ||||||
|  | 	w := httptest.NewRecorder() | ||||||
|  | 	_, _ = req, w | ||||||
|  | 	a := handleRequest([]customtypes.PartnersAddress{{Ip: "127.0.0.1", Port: 5050}}) | ||||||
|  | 	a(w, req) | ||||||
|  | 	res := w.Result() | ||||||
|  | 	defer res.Body.Close() | ||||||
|  | 
 | ||||||
|  | 	if res.StatusCode != http.StatusBadRequest { | ||||||
|  | 		t.Errorf("Expects code 400, got %v", res.StatusCode) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func TestPostRequestWithEmptyBody(t *testing.T) { | ||||||
|  | 	req := httptest.NewRequest(http.MethodPost, "/placements/request", nil) | ||||||
|  | 	w := httptest.NewRecorder() | ||||||
|  | 	_, _ = req, w | ||||||
|  | 	a := handleRequest([]customtypes.PartnersAddress{{Ip: "127.0.0.1", Port: 5050}}) | ||||||
|  | 	a(w, req) | ||||||
|  | 	res := w.Result() | ||||||
|  | 	defer res.Body.Close() | ||||||
|  | 
 | ||||||
|  | 	if res.StatusCode != http.StatusBadRequest { | ||||||
|  | 		t.Errorf("Expects code 400, got %v", res.StatusCode) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func TestPostRequestWithRightBody(t *testing.T) { | ||||||
|  | 	body_json := `{ | ||||||
|  |   "id": "123", | ||||||
|  |   "tiles": [ | ||||||
|  |     { | ||||||
|  |       "id": 123, | ||||||
|  |       "width": 122, | ||||||
|  |       "ratio": 1.5 | ||||||
|  |     } | ||||||
|  |   ], | ||||||
|  |   "context": { | ||||||
|  |     "ip": "192.168.1.1", | ||||||
|  |     "user_agent": "curl" | ||||||
|  |   } | ||||||
|  | }` | ||||||
|  | 
 | ||||||
|  | 	req := httptest.NewRequest(http.MethodPost, "/placements/request", bytes.NewBuffer([]byte(body_json))) | ||||||
|  | 	req.Header.Set("Content-Type", "application/json") | ||||||
|  | 	w := httptest.NewRecorder() | ||||||
|  | 	_, _ = req, w | ||||||
|  | 	a := handleRequest([]customtypes.PartnersAddress{{Ip: "127.0.0.1", Port: 5059}}) | ||||||
|  | 	a(w, req) | ||||||
|  | 	res := w.Result() | ||||||
|  | 	defer res.Body.Close() | ||||||
|  | 
 | ||||||
|  | 	data, err := ioutil.ReadAll(res.Body) | ||||||
|  | 	if err != nil { | ||||||
|  | 		t.Errorf("expected error to be nil got %v", err) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if res.StatusCode != http.StatusOK { | ||||||
|  | 		t.Errorf("Expects code 200, got %v", res.StatusCode) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	var d req_types.SuccesResponse | ||||||
|  | 	if json.Unmarshal(data, &d) != nil { | ||||||
|  | 		t.Log("Error parsing json response") | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if d.Imp[0].Title != "bestoption" { | ||||||
|  | 		t.Errorf("Wants title `bestoption`, got %v", d.Imp[0].Title) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | } | ||||||
| @ -0,0 +1,51 @@ | |||||||
|  | package clientserver | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"errors" | ||||||
|  | 	"fmt" | ||||||
|  | 	"log" | ||||||
|  | 	"net/http" | ||||||
|  | 	"regexp" | ||||||
|  | 	"strconv" | ||||||
|  | 	"strings" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | const MAX_PORT_NUM = 65535 | ||||||
|  | 
 | ||||||
|  | // Returns false if ipv4 `correct`.
 | ||||||
|  | func wrongIPAddresFormat(ipv4 string) bool { | ||||||
|  | 	re, err := regexp.Compile(`^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$`) | ||||||
|  | 	if err != nil { | ||||||
|  | 		log.Println(err) | ||||||
|  | 	} | ||||||
|  | 	return !re.Match([]byte(ipv4)) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func throwHTTPError(err_text string, code int, w *http.ResponseWriter) error { | ||||||
|  | 	http.Error(*w, err_text, code) | ||||||
|  | 	eText := fmt.Sprintf("Error: %d %vr", code, err_text) | ||||||
|  | 	return errors.New(eText) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Wait string in format "10.10.10.10:8080", where `10.10.10.10` IPv4,
 | ||||||
|  | // and `8080` port. If ip or port has wrong format, returns error.
 | ||||||
|  | func ParsePartnersAddress(ipAndPort string) (string, int64, error) { | ||||||
|  | 	var err error | ||||||
|  | 	iap := strings.Split(ipAndPort, ":") | ||||||
|  | 
 | ||||||
|  | 	ip := iap[0] | ||||||
|  | 	if wrongIPAddresFormat(ip) { | ||||||
|  | 		err = errors.New(fmt.Sprintf("Wrong ip address format in partner ip: %v", ip)) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	port, e := strconv.ParseInt(iap[1], 10, 32) | ||||||
|  | 	if e != nil { | ||||||
|  | 		err = errors.New(fmt.Sprintf("Wrong port format in partner ip: %v", e)) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if port > MAX_PORT_NUM { | ||||||
|  | 		err = errors.New(fmt.Sprintf("Wrong port in partner ip: grater than %v", MAX_PORT_NUM)) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return ip, port, err | ||||||
|  | } | ||||||
| @ -0,0 +1,6 @@ | |||||||
|  | package customtypes | ||||||
|  | 
 | ||||||
|  | type PartnersAddress struct { | ||||||
|  | 	Ip   string | ||||||
|  | 	Port int64 | ||||||
|  | } | ||||||
| @ -0,0 +1,59 @@ | |||||||
|  | /* | ||||||
|  | Usage: | ||||||
|  | 
 | ||||||
|  | 	sample-choose-ad [flags] | ||||||
|  | 
 | ||||||
|  | The flags are: | ||||||
|  | 
 | ||||||
|  | 	-p | ||||||
|  | 	    Listening port | ||||||
|  | 	-d | ||||||
|  | 	    Adversment partners list in format ip_p1:port,ip_p2:port2...ip_p10:port | ||||||
|  | */ | ||||||
|  | package main | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"flag" | ||||||
|  | 	"log" | ||||||
|  | 	clientserver "sample-choose-ad/cmd/client_server" | ||||||
|  | 	customtypes "sample-choose-ad/cmd/custom_types" | ||||||
|  | 	"strings" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | func main() { | ||||||
|  | 	log.Println("Info: Starting server") | ||||||
|  | 
 | ||||||
|  | 	port := flag.String("p", "", "-p 5050") | ||||||
|  | 	addressesList := flag.String("d", "", "-d '10.10.10.10:5050,10.10.10.20:5050'") | ||||||
|  | 	flag.Parse() | ||||||
|  | 
 | ||||||
|  | 	if *port == "" { | ||||||
|  | 		log.Fatalln("Error: Port number is require!") | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if *addressesList == "" { | ||||||
|  | 		log.Fatalln("Error: Partners list is require!") | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	// Parse first 10 ip:port pairs into `[]partners` slise
 | ||||||
|  | 	var partners []customtypes.PartnersAddress | ||||||
|  | 	for i, p := range strings.Split(*addressesList, ",") { | ||||||
|  | 
 | ||||||
|  | 		if i == 10 { | ||||||
|  | 			log.Println("Warning: Partners count must be less or equal 10!") | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		ip, port, err := clientserver.ParsePartnersAddress(p) | ||||||
|  | 
 | ||||||
|  | 		if err != nil { | ||||||
|  | 			log.Fatalln(err) | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		partners = append(partners, customtypes.PartnersAddress{ | ||||||
|  | 			Ip:   ip, | ||||||
|  | 			Port: port}) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	clientserver.StartServer(*port, partners) | ||||||
|  | } | ||||||
| @ -0,0 +1,31 @@ | |||||||
|  | package req_types | ||||||
|  | 
 | ||||||
|  | type Tile struct { | ||||||
|  | 	Id    uint    `json:"id"` | ||||||
|  | 	Width uint    `json:"width"` | ||||||
|  | 	Ratio float64 `json:"ratio"` | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type AdContext struct { | ||||||
|  | 	Ip        string `json:"ip"` | ||||||
|  | 	UserAgent string `json:"user_agent"` | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type IncomingRequest struct { | ||||||
|  | 	Id      *string   `json:"id"` | ||||||
|  | 	Tiles   []Tile    `json:"tiles"` | ||||||
|  | 	Context AdContext `json:"context"` | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Based in Tile
 | ||||||
|  | type Imp struct { | ||||||
|  | 	Id        uint `json:"id"`        // same as related `Tile.Id`
 | ||||||
|  | 	Minwidth  uint `json:"minwidth"`  // `Tile.Width`
 | ||||||
|  | 	Minheight uint `json:"minheight"` // math.Floor(Tile.Width * Tile.Ratio)
 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type OutgoingRequest struct { | ||||||
|  | 	Id      string    `json:"id"` | ||||||
|  | 	Imp     []Imp     `json:"imp"` | ||||||
|  | 	Context AdContext `json:"context"` | ||||||
|  | } | ||||||
| @ -0,0 +1,16 @@ | |||||||
|  | package req_types | ||||||
|  | 
 | ||||||
|  | type RespImp struct { | ||||||
|  | 	Id     uint    `json:"id"` | ||||||
|  | 	Width  uint    `json:"width"` | ||||||
|  | 	Height uint    `json:"height"` | ||||||
|  | 	Title  string  `json:"title"` | ||||||
|  | 	Url    string  `json:"url"` | ||||||
|  | 	Price  float64 `json:"price"` | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Response from ad partners
 | ||||||
|  | type SuccesResponse struct { | ||||||
|  | 	Id  string    `json:"id"` | ||||||
|  | 	Imp []RespImp `json:"imp"` | ||||||
|  | } | ||||||
					Loading…
					
					
				
		Reference in new issue