Added source and user marks to opportunities type. Any ignored errors are deferred to //nolint with a comment instead of being silently dropped
This commit is contained in:
1 parent
6e6dec68c0
commit
9b0e6dccfe
7 files changed
+65
-4
No files matched your search
@@ -144,6 +144,7 @@ func convertOne(release *ocds.Release) *types.Opportunity {
|
|||||||
UUIDType: "ocid",
|
UUIDType: "ocid",
|
||||||
BuyerId: ocdsGetBuyerId(release),
|
BuyerId: ocdsGetBuyerId(release),
|
||||||
BuyerName: ocdsGetBuyerName(release),
|
BuyerName: ocdsGetBuyerName(release),
|
||||||
|
Source: "FindATender",
|
||||||
}
|
}
|
||||||
|
|
||||||
if release.Tender != nil {
|
if release.Tender != nil {
|
||||||
|
|||||||
@@ -328,3 +328,28 @@ func TestConvertSameDate(t *testing.T) {
|
|||||||
t.Fatalf("got %+v, want one opportunity titled %q", got, "second")
|
t.Fatalf("got %+v, want one opportunity titled %q", got, "second")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Every converted opportunity records that it came from Find a Tender,
|
||||||
|
// whether or not its release has a tender.
|
||||||
|
func TestConvertSetsSource(t *testing.T) {
|
||||||
|
const want = "FindATender"
|
||||||
|
|
||||||
|
for name, release := range map[string]*ocds.Release{
|
||||||
|
"with tender": {OCID: "ocds-a", ID: "1", Tender: &ocds.Tender{Title: "A"}},
|
||||||
|
"nil tender": {OCID: "ocds-b", ID: "1"},
|
||||||
|
} {
|
||||||
|
if got := convertOne(release).Source; got != want {
|
||||||
|
t.Errorf("convertOne (%s): Source = %q, want %q", name, got, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, o := range Convert([]ocds.Release{
|
||||||
|
{OCID: "ocds-a", ID: "1", Tender: &ocds.Tender{Title: "A"}},
|
||||||
|
{OCID: "ocds-a", ID: "2", Tender: &ocds.Tender{Title: "A amended"}},
|
||||||
|
{OCID: "ocds-b", ID: "1"},
|
||||||
|
}) {
|
||||||
|
if o.Source != want {
|
||||||
|
t.Errorf("Convert: %s Source = %q, want %q", o.UUID, o.Source, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -185,7 +185,7 @@ func clone[T any](v T) T {
|
|||||||
var out T
|
var out T
|
||||||
b, err := json.Marshal(v)
|
b, err := json.Marshal(v)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
_ = json.Unmarshal(b, &out)
|
json.Unmarshal(b, &out) //nolint:errcheck,gosec // b was just marshalled from a T
|
||||||
}
|
}
|
||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
@@ -287,6 +287,6 @@ func ensurePartyID(p *ocds.Organization) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
h := fnv.New32a()
|
h := fnv.New32a()
|
||||||
_, _ = h.Write([]byte(p.Name)) // never fails
|
h.Write([]byte(p.Name)) //nolint:gosec // never fails
|
||||||
p.ID = fmt.Sprintf("GB-FTS-%d", h.Sum32()%1000000)
|
p.ID = fmt.Sprintf("GB-FTS-%d", h.Sum32()%1000000)
|
||||||
}
|
}
|
||||||
@@ -65,7 +65,8 @@ func decodeCursor(c string) (int, error) {
|
|||||||
func badRequest(w http.ResponseWriter, msg string) {
|
func badRequest(w http.ResponseWriter, msg string) {
|
||||||
w.Header().Set("Content-Type", "application/json")
|
w.Header().Set("Content-Type", "application/json")
|
||||||
w.WriteHeader(http.StatusBadRequest)
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
_ = json.NewEncoder(w).Encode(map[string]string{"error": msg}) // the client has gone
|
body := map[string]string{"error": msg}
|
||||||
|
json.NewEncoder(w).Encode(body) //nolint:errcheck,gosec // the client may have gone; nothing to do
|
||||||
}
|
}
|
||||||
|
|
||||||
// parseTime reads a query timestamp. Values without an offset (the documented
|
// parseTime reads a query timestamp. Values without an offset (the documented
|
||||||
|
|||||||
@@ -114,7 +114,7 @@ func (rl *RateLimiter) Middleware(next http.HandlerFunc) http.HandlerFunc {
|
|||||||
w.Header().Set("Retry-After", strconv.Itoa(retry))
|
w.Header().Set("Retry-After", strconv.Itoa(retry))
|
||||||
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
|
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
|
||||||
w.WriteHeader(http.StatusTooManyRequests)
|
w.WriteHeader(http.StatusTooManyRequests)
|
||||||
_, _ = fmt.Fprintf(
|
fmt.Fprintf( //nolint:errcheck // the client may have gone; nothing to do
|
||||||
w, "Rate limit of %d exceeded. Please retry after %d seconds.",
|
w, "Rate limit of %d exceeded. Please retry after %d seconds.",
|
||||||
rl.limit, retry,
|
rl.limit, retry,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,9 +1,14 @@
|
|||||||
package types
|
package types
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Opportunity is a procurement opportunity (an OCDS contracting process,
|
||||||
|
// identified by UUID) as of its latest release, with its classifications,
|
||||||
|
// releases and the user's marks. Fields tagged db:"-" are stored in their
|
||||||
|
// own tables.
|
||||||
type Opportunity struct {
|
type Opportunity struct {
|
||||||
UUID string `db:"uuid" json:"uuid"`
|
UUID string `db:"uuid" json:"uuid"`
|
||||||
UUIDType string `db:"uuid_type" json:"uuid_type"`
|
UUIDType string `db:"uuid_type" json:"uuid_type"`
|
||||||
@@ -22,8 +27,10 @@ type Opportunity struct {
|
|||||||
EndDate *time.Time `db:"end_date" json:"end_date"`
|
EndDate *time.Time `db:"end_date" json:"end_date"`
|
||||||
GrossValue float64 `db:"gross_value" json:"gross_value"`
|
GrossValue float64 `db:"gross_value" json:"gross_value"`
|
||||||
HasDocuments bool `db:"has_documents" json:"has_documents"`
|
HasDocuments bool `db:"has_documents" json:"has_documents"`
|
||||||
|
Source string `db:"source" json:"source"`
|
||||||
LatestReleaseDate time.Time `db:"latest_release_date" json:"latest_release_date"`
|
LatestReleaseDate time.Time `db:"latest_release_date" json:"latest_release_date"`
|
||||||
Releases []OpportunityRelease `db:"-" json:"releases"`
|
Releases []OpportunityRelease `db:"-" json:"releases"`
|
||||||
|
Marks OpportunityMarks `db:"-" json:"marks"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// OpportunityRelease is one OCDS release of an opportunity. JSON holds the
|
// OpportunityRelease is one OCDS release of an opportunity. JSON holds the
|
||||||
@@ -43,3 +50,30 @@ type OpportunityReleaseTag struct {
|
|||||||
ReleaseID string `db:"release_id" json:"release_id"`
|
ReleaseID string `db:"release_id" json:"release_id"`
|
||||||
Tag string `db:"tag" json:"tag"`
|
Tag string `db:"tag" json:"tag"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ErrWonAndLost is returned by OpportunityMarks.Validate when both Won and
|
||||||
|
// Lost are set.
|
||||||
|
var ErrWonAndLost = errors.New("an opportunity cannot be both won and lost")
|
||||||
|
|
||||||
|
// OpportunityMarks are the user's own marks on an opportunity, kept apart
|
||||||
|
// from the fetched data so saving a newer release never changes them. The
|
||||||
|
// marks are independent except that Won and Lost exclude each other (see
|
||||||
|
// Validate).
|
||||||
|
type OpportunityMarks struct {
|
||||||
|
UUID string `db:"uuid" json:"uuid"`
|
||||||
|
Hidden bool `db:"hidden" json:"hidden"`
|
||||||
|
Skipped bool `db:"skipped" json:"skipped"`
|
||||||
|
Interested bool `db:"interested" json:"interested"`
|
||||||
|
BidSubmitted bool `db:"bid_submitted" json:"bid_submitted"`
|
||||||
|
Won bool `db:"won" json:"won"`
|
||||||
|
Lost bool `db:"lost" json:"lost"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate reports whether the marks can be stored together: Won and Lost
|
||||||
|
// cannot both be set.
|
||||||
|
func (m OpportunityMarks) Validate() error {
|
||||||
|
if m.Won && m.Lost {
|
||||||
|
return ErrWonAndLost
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
Reference in new issue
Block a user