-
Notifications
You must be signed in to change notification settings - Fork 0
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add questions and responses user endpoints #1
Conversation
libs/s3/objects.go
Outdated
log.Println(err.Error(), input) | ||
} | ||
return nil, err | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a little convoluted, maybe something more like
if err != nil {
log.Println(err.Error(), input)
if aerr, ok := err.(awserr.Error); ok && aerr == s3.ErrCodeNoSuchKey {
return []byte{}, err
}
return nil, err
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💯
libs/s3/objects.go
Outdated
log.Println(err.Error(), input) | ||
} | ||
return nil, err | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
same here
actions/app.go
Outdated
app.GET("/", HomeHandler) | ||
// initialize S3 client session | ||
S3 = s3.NewSession(os.Getenv("S3_API_KEY"), os.Getenv("S3_API_SECRET"), os.Getenv("S3_REGION")) | ||
S3.Bucket = os.Getenv("S3_BUCKET") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is fine, but why not have the single constructor that also takes the bucket? Or pass the bucket as parameters to the methods that use the client?
actions/app.go
Outdated
S3 = s3.NewSession(os.Getenv("S3_API_KEY"), os.Getenv("S3_API_SECRET"), os.Getenv("S3_REGION")) | ||
S3.Bucket = os.Getenv("S3_BUCKET") | ||
|
||
app.GET("/v1/proctor/ping", PingPong) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is part of the API group actually -- I only split mine in Tweaser because there was different auth middleware
actions/risklevels.go
Outdated
} | ||
|
||
// RiskLevelsList is a versioned collection of RiskLevels | ||
type RiskLevelsList struct { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should just be RiskLevels
actions/risklevels.go
Outdated
} | ||
|
||
return d, nil | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this stuff needs to be abstracted into an interface the more I think about it. Let's just pull these few methods out into a helpers
package for now? Then you can call helpers.LoadRiskLevels(version)
actions/risklevels.go
Outdated
|
||
// loadRisklevels loads the risk levels json from S3 and returns a slice of bytes | ||
func loadRiskLevels(version string) ([]byte, error) { | ||
if len(version) == 0 { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this should just be if version == ""
actions/risklevels.go
Outdated
} | ||
} | ||
|
||
log.Println("Loading risk levels version " + version) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
log.Printf("Loading risk levels version %s", verison)
😄
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
heh, still getting used to Printf
😃
actions/risklevels.go
Outdated
|
||
log.Println("Loading risk levels version " + version) | ||
|
||
d, err := S3.GetObject("risklevels/" + version + "/risklevels.json") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
path := fmt.Sprintf("risklevels/%s/risklevels.json", version)
d, err := S3.GetObject(path)
actions/risklevels.go
Outdated
return []byte{}, errors.New("Object not found in S3") | ||
} | ||
return nil, errors.New("Unable to get object from S3") | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder if we are over processing the error states... do you really want to throw a 500 and a stack trace if you get an error getting the object? Or would it be better to just always return []byte{}, err
? This code gets a lot simpler in the latter case (and more Go-y I think).
if err != nil {
return []byte{}, err
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wanted to return a 404 if object doesn't exist, I think that makes more sense than 500
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
im saying if you get an error from that method, just always return a 404
actions/versions.go
Outdated
versions = append(versions, s[len(s)-1]) | ||
} | ||
return versions, nil | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't this just be a method on RiskLevels
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's also used for getting the question versions.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah! perfect! your first interface! 😆
actions/versions.go
Outdated
if len(vl) == 0 { | ||
return "" | ||
} | ||
sort.Strings(vl) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure this works... I think you need to convert these to numbers, no? What about the slice:
["0.1", "0.2", "0.3", ....., "0.20, 0.21", ...., "0.30", "0.31"]
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
also your comment needs quotes around the version numbers if they are strings 😸
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
good point, I had the version as a float initially but then changed it to string to make it more flexible. i've gotta fix that sort algo.
actions/questions.go
Outdated
} | ||
|
||
// QuestionsList is a versioned collection of questions | ||
type QuestionsList struct { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Questions
actions/questions.go
Outdated
} | ||
|
||
var ql *QuestionsList | ||
if err := json.Unmarshal(q, &ql); err != nil { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a double pointer
Nice start! 🥂 |
actions/versions.go
Outdated
// latestVersion gets a slice of versions and returns the latest version as a string | ||
// e.g. ["0.1", "0.10", "1.0"] or ["0.1.0", "0.10.1", "1.0.2"] | ||
// any number of minor/patch versions will work as long as all versions are consistent | ||
// i.e. this is not allowed ["0.1", "0.10.0", "1.0"] and the function will return "" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💩
libs/helpers/helpers.go
Outdated
// LatestVersion gets a slice of versions and returns the latest version as a string | ||
// e.g. ["0.1", "0.10", "1.0"] or ["0.1.0", "0.10.1", "1.0.2"] | ||
// any number of minor/patch versions will work as long as all versions are consistent | ||
// i.e. this is not allowed ["0.1", "0.10.0", "1.0"] and the function will return "" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💩
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🤷♂️
libs/helpers/helpers.go
Outdated
// e.g. ["0.1", "0.10", "1.0"] or ["0.1.0", "0.10.1", "1.0.2"] | ||
// any number of minor/patch versions will work as long as all versions are consistent | ||
// i.e. this is not allowed ["0.1", "0.10.0", "1.0"] and the function will return "" | ||
func LatestVersion(vl []string) string { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
probably should return error too so you can bail from your errors below more gracefully
libs/helpers/helpers.go
Outdated
// i.e. this is not allowed ["0.1", "0.10.0", "1.0"] and the function will return "" | ||
func LatestVersion(vl []string) string { | ||
if len(vl) == 0 { | ||
return "" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should this actually return an error
?
libs/helpers/helpers.go
Outdated
} | ||
|
||
return strings.Join(ss[len(ss)-1], ".") | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I didn't make it take arbitrary length strings, although that should be easy.... this is quite a bit faster
➜ tmp go test -bench=. -benchtime=20s
goos: darwin
goarch: amd64
BenchmarkLatestVersionT-8 10000000 3858 ns/op
BenchmarkLatestVersionC-8 10000000 2741 ns/op
BenchmarkLatestVersionN-8 10000000 2436 ns/op
func LVS(vl []string) string {
if len(vl) == 0 {
return ""
}
// initialize latest with zeros
latest := []int{}
for i := 0; i < len(strings.Split(vl[0], ".")); i++ {
latest = append(latest, 0)
}
// loop over list and compare each to latest
for _, sv := range vl {
current := strings.Split(sv, ".")
if len(current) == 0 {
log.Printf("Error: bad current version")
return ""
}
si := []int{}
for _, s := range current {
i, err := strconv.Atoi(s)
if err != nil {
log.Println("Failed to parse", current)
return ""
}
si = append(si, i)
}
if len(latest) != len(si) {
log.Println("Error format mismatch for", sv)
return ""
}
for i, siv := range si {
if siv > latest[i] {
latest = si
break
}
}
}
l := []byte{}
for _, i := range latest {
l = strconv.AppendInt(l, int64(i), 10)
}
return string(l)
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
hmm, yeah, no need to sort all the versions if we only need to pick the latest one
|
||
// Responses is a collection of responses to questions submitted by the client | ||
type Responses struct { | ||
List map[string]string `json:"responses"` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is kinda strange -- to have the JSON key be responses
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why? have any suggestions?
models/risklevels.go
Outdated
} | ||
|
||
return nil | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm thinking that these Load()
methods should actually be on the S3 client and you can pass a pointer to the struct that should be filled from the storage location. Later, this could become an interface for a storage construct.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it will have to be an interface, otherwise how are we going to pass a pointer to different structs?
lookin good! 👍 |
I've added an S3 Load method that takes any struct which satisfies the |
libs/s3/objects.go
Outdated
} | ||
|
||
return nil | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
lol now you have 3 Load()
methods! Make this take interface{}
instead.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I assumed that wasn't gonna work without even trying it 😁 Much better now!
libs/helpers/helpers.go
Outdated
} | ||
} | ||
} | ||
|
||
return strings.Join(ss[len(ss)-1], "."), nil | ||
return strings.Trim(strings.Join(strings.Fields(fmt.Sprint(latest)), "."), "[]"), nil |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
a lot of slow functions chained together here... even a straight append instead is a lot better
package main
import(
"fmt"
"strings"
"strconv"
"testing"
)
func IntSliceString1(nums []int, separator string) string {
return strings.Trim(strings.Join(strings.Fields(fmt.Sprint(nums)), separator), "[]")
}
func IntSliceString2(nums []int, separator string) string {
if len(nums) == 0 {
return ""
}
s := make([]string, len(nums))
for i, n := range nums {
s[i] = strconv.Itoa(n)
}
return strings.Join(s, separator)
}
func IntSliceString3(nums []int, separator string) string {
if len(nums) == 0 {
return ""
}
s := []string{}
for _, n := range nums {
s = append(s, strconv.Itoa(n))
}
return strings.Join(s, separator)
}
func Benchmark1(b *testing.B) {
foo := []int{1,2,3,4,5,6,7,8,9,10,100,1000}
for n := 0; n < b.N; n++ {
IntSliceString1(foo, ".")
}
}
func Benchmark2(b *testing.B) {
foo := []int{1,2,3,4,5,6,7,8,9,10,100,1000}
for n := 0; n < b.N; n++ {
IntSliceString2(foo, ".")
}
}
func Benchmark3(b *testing.B) {
foo := []int{1,2,3,4,5,6,7,8,9,10,100,1000}
for n := 0; n < b.N; n++ {
IntSliceString3(foo, ".")
}
}
% ➜ tmp go test -bench=. -benchtime=20s
goos: darwin
goarch: amd64
Benchmark1-8 20000000 1950 ns/op
Benchmark2-8 100000000 417 ns/op
Benchmark3-8 50000000 727 ns/op
PASS
ok _/Users/fish/tmp/go/yale/tmp 120.277s
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
but it's a one-liner 😉
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the benchmark stuff is pretty cool!
This is gettin goooooood 🤔 😆 |
This initial PR adds basic functionality for vending questions/answers and calculating data types and risk level based on a response.
Configuration is exclusively via JSON files in S3.