gci is configured with the sections standard, default and localmodule, and goimports with the module path as its local prefix, so both formatters agree.
135 lines
3.3 KiB
Go
135 lines
3.3 KiB
Go
package fetch
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"log/slog"
|
|
"slices"
|
|
"time"
|
|
|
|
"github.com/spf13/cobra"
|
|
|
|
"git.avsdev.uk/Go/job-search/internal/cli"
|
|
"git.avsdev.uk/Go/job-search/internal/findatender"
|
|
"git.avsdev.uk/Go/job-search/internal/types"
|
|
)
|
|
|
|
// Release tags for each stage of a procedure, from the OCDS releaseTag
|
|
// codelist. Cancellations are left out, as a cancelled procedure is no longer
|
|
// at any stage.
|
|
var stageTags = map[string][]string{
|
|
"planning": {"planning", "planningUpdate"},
|
|
"opportunity": {"tender", "tenderAmendment", "tenderUpdate"},
|
|
"award": {
|
|
"award", "awardUpdate",
|
|
"contract", "contractUpdate", "contractAmendment",
|
|
"implementation", "implementationUpdate", "contractTermination",
|
|
},
|
|
}
|
|
|
|
// filter keeps the opportunities whose latest release is at stage. Releases
|
|
// are oldest first, so the last one is the procedure's current stage.
|
|
func filter(opps []*types.Opportunity, stage string) []*types.Opportunity {
|
|
var resp []*types.Opportunity
|
|
|
|
for _, opp := range opps {
|
|
if len(opp.Releases) == 0 {
|
|
continue
|
|
}
|
|
latest := opp.Releases[len(opp.Releases)-1]
|
|
for _, tag := range latest.Tags {
|
|
if slices.Contains(stageTags[stage], tag.Tag) {
|
|
resp = append(resp, opp)
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
return resp
|
|
}
|
|
|
|
func commandOnce(opts *cli.Options) *cobra.Command {
|
|
cmd := &cobra.Command{
|
|
Use: "once",
|
|
Short: "Fetch current opportunities - single shot",
|
|
PersistentPreRun: func(cmd *cobra.Command, args []string) {
|
|
slog.Debug("fetch.once.PersistentPreRun")
|
|
},
|
|
PersistentPostRun: func(cmd *cobra.Command, args []string) {
|
|
slog.Debug("fetch.once.PersistentPostRun")
|
|
},
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
slog.Debug("fetch.once.Run")
|
|
|
|
conf, err := opts.RunSetup(cmd, true, true)
|
|
if err != nil {
|
|
return fmt.Errorf("no configuration loaded (%w)", err)
|
|
}
|
|
|
|
since, sinceErr := cmd.Flags().GetTime("since")
|
|
if sinceErr != nil {
|
|
return sinceErr
|
|
}
|
|
|
|
ft := findatender.New(conf, conf.Sources.FindATender.APIRoot)
|
|
defer ft.Shutdown()
|
|
|
|
ignoreRateLimit, _ := cmd.Flags().GetBool("ignore-rate-limit")
|
|
|
|
results, err := ft.FetchAll(since, !ignoreRateLimit)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
planning, _ := cmd.Flags().GetBool("planning")
|
|
opportunities, _ := cmd.Flags().GetBool("opportunities")
|
|
awards, _ := cmd.Flags().GetBool("awards")
|
|
|
|
filterTag := ""
|
|
if planning {
|
|
filterTag = "planning"
|
|
}
|
|
if opportunities {
|
|
filterTag = "opportunity"
|
|
}
|
|
if awards {
|
|
filterTag = "award"
|
|
}
|
|
|
|
if len(filterTag) > 0 {
|
|
results = filter(results, filterTag)
|
|
}
|
|
|
|
jbytes, err := json.MarshalIndent(results, "", " ")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
fmt.Println(string(jbytes))
|
|
|
|
return nil
|
|
},
|
|
}
|
|
cmd.Flags().Time(
|
|
"since",
|
|
time.Time{},
|
|
[]string{
|
|
time.DateOnly, time.DateTime,
|
|
},
|
|
"contract updates published since YYYY-MM-DD[ hh:mm:ss]",
|
|
)
|
|
cmd.Flags().Bool(
|
|
"planning", false, "only return planning (i.e. exclude opportunities and awards)",
|
|
)
|
|
cmd.Flags().Bool(
|
|
"opportunities", false, "only return opportunities (i.e. exclude awards and planning)",
|
|
)
|
|
cmd.Flags().Bool(
|
|
"awards", false, "only return awards (i.e. exclude planning and opportunities)",
|
|
)
|
|
cmd.Flags().Bool(
|
|
"ignore-rate-limit", false, "ignore the rate limit and fail if exceeded",
|
|
)
|
|
cmd.MarkFlagsMutuallyExclusive("planning", "opportunities", "awards")
|
|
return cmd
|
|
}
|