diff --git a/cmd/stufflog3-lambda/main.go b/cmd/stufflog3-lambda/main.go index 9e9c6e5..1f2abed 100644 --- a/cmd/stufflog3-lambda/main.go +++ b/cmd/stufflog3-lambda/main.go @@ -97,7 +97,9 @@ func main() { httpapi.Items(apiV1ScopesSub.Group("/items"), itemsService) httpapi.Sprints(apiV1ScopesSub.Group("/sprints"), sprintsService) httpapi.Stats(apiV1ScopesSub.Group("/stats"), statsService) - + apiV1AllScopes := apiV1.Group("/all-scopes") + apiV1AllScopes.Use(httpapi.AllScopeMiddleware(scopesService, authService)) + httpapi.ItemsAllScopes(apiV1AllScopes, itemsService) ginLambda := ginadapter.New(server) lambda.Start(func(ctx context.Context, request events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) { return ginLambda.ProxyWithContext(ctx, request) diff --git a/cmd/stufflog3-local/main.go b/cmd/stufflog3-local/main.go index a25f6fa..c8d7d9e 100644 --- a/cmd/stufflog3-local/main.go +++ b/cmd/stufflog3-local/main.go @@ -95,6 +95,9 @@ func main() { httpapi.Items(apiV1ScopesSub.Group("/items"), itemsService) httpapi.Sprints(apiV1ScopesSub.Group("/sprints"), sprintsService) httpapi.Stats(apiV1ScopesSub.Group("/stats"), statsService) + apiV1AllScopes := apiV1.Group("/all-scopes") + apiV1AllScopes.Use(httpapi.AllScopeMiddleware(scopesService, authService)) + httpapi.ItemsAllScopes(apiV1AllScopes.Group("/items"), itemsService) exitSignal := make(chan os.Signal) signal.Notify(exitSignal, os.Interrupt, os.Kill, syscall.SIGTERM) diff --git a/frontend/src/lib/components/contexts/ItemMultiListContext.svelte b/frontend/src/lib/components/contexts/ItemMultiListContext.svelte index 2008751..3bea870 100644 --- a/frontend/src/lib/components/contexts/ItemMultiListContext.svelte +++ b/frontend/src/lib/components/contexts/ItemMultiListContext.svelte @@ -29,6 +29,7 @@ export let lists: Record export let filters: Record + export let global: boolean = false; let loaded: Record = {...filters}; let loading: Record = {}; @@ -42,7 +43,7 @@ loading = {...loading, [key]: true}; try { - const items = await sl3(fetch).listItems($scope.id, filter) + const items = await sl3(fetch).listItems(global ? "ALL" : $scope.id, filter) listsWritable.update(l => ({...l, [key.replace("Filter", "Items")]: items})); } catch(_err) { // TODO: Use err diff --git a/frontend/src/lib/components/controls/StatInput.svelte b/frontend/src/lib/components/controls/StatInput.svelte index 8ac6876..19892aa 100644 --- a/frontend/src/lib/components/controls/StatInput.svelte +++ b/frontend/src/lib/components/controls/StatInput.svelte @@ -1,7 +1,9 @@
- {#each $scope.stats as stat (stat.id)} + {#each actualScope.stats as stat (stat.id)} {#if enabledMap[stat.id] != null}
{#if !noToggle} diff --git a/frontend/src/lib/components/project/ItemSubSection.svelte b/frontend/src/lib/components/project/ItemSubSection.svelte index 4c755d4..263694d 100644 --- a/frontend/src/lib/components/project/ItemSubSection.svelte +++ b/frontend/src/lib/components/project/ItemSubSection.svelte @@ -56,7 +56,7 @@ {/if} {#if item.project != null} - + {/if} {#if !compact} diff --git a/frontend/src/lib/components/project/RequirementReference.svelte b/frontend/src/lib/components/project/RequirementReference.svelte index 0bf5cfd..d8932ed 100644 --- a/frontend/src/lib/components/project/RequirementReference.svelte +++ b/frontend/src/lib/components/project/RequirementReference.svelte @@ -8,17 +8,25 @@ diff --git a/frontend/src/lib/modals/ItemAcquireModal.svelte b/frontend/src/lib/modals/ItemAcquireModal.svelte index e1f28ff..2e42646 100644 --- a/frontend/src/lib/modals/ItemAcquireModal.svelte +++ b/frontend/src/lib/modals/ItemAcquireModal.svelte @@ -6,22 +6,20 @@ import { getItemListContext } from "$lib/components/contexts/ItemListContext.svelte"; import { getModalContext } from "$lib/components/contexts/ModalContext.svelte"; import { getProjectContext } from "$lib/components/contexts/ProjectContext.svelte"; - import { getScopeContext } from "$lib/components/contexts/ScopeContext.svelte"; -import { getSprintListContext } from "$lib/components/contexts/SprintListContext.svelte"; + import { getSprintListContext } from "$lib/components/contexts/SprintListContext.svelte"; import AcquiredTimeInput from "$lib/components/controls/AcquiredTimeInput.svelte"; import StatInput from "$lib/components/controls/StatInput.svelte"; import type Item from "$lib/models/item"; import type { ItemInput } from "$lib/models/item"; - import type Scope from "$lib/models/scope"; const {currentModal, closeModal} = getModalContext(); - const {scope} = getScopeContext(); const {reloadProject} = getProjectContext(); const {reloadItemList} = getItemListContext(); const {reloadSprintList} = getSprintListContext(); let item: Partial let itemId: number + let scopeId: number let requirementId: number let openedDate: Date @@ -31,7 +29,7 @@ import { getSprintListContext } from "$lib/components/contexts/SprintListContext $: switch ($currentModal.name) { case "item.acquire": - init($scope, $currentModal.item) + init($currentModal.item) break; default: @@ -40,7 +38,7 @@ import { getSprintListContext } from "$lib/components/contexts/SprintListContext show = false; } - function init(scope: Scope, unaquiredItem: Item) { + function init(unaquiredItem: Item) { openedDate = new Date(); item = { @@ -50,6 +48,7 @@ import { getSprintListContext } from "$lib/components/contexts/SprintListContext } show = true; + scopeId = unaquiredItem.scopeId; itemId = unaquiredItem.id; requirementId = unaquiredItem.requirementId; } @@ -59,7 +58,7 @@ import { getSprintListContext } from "$lib/components/contexts/SprintListContext loading = true; try { - await sl3(fetch).updateItem($scope.id, itemId, { + await sl3(fetch).updateItem(scopeId, itemId, { ...item, acquiredTime: new Date(item.acquiredTime).toISOString(), }); diff --git a/frontend/src/lib/modals/ItemCreateModal.svelte b/frontend/src/lib/modals/ItemCreateModal.svelte index 6a7c661..3c36710 100644 --- a/frontend/src/lib/modals/ItemCreateModal.svelte +++ b/frontend/src/lib/modals/ItemCreateModal.svelte @@ -8,6 +8,7 @@ import { getItemMultiListContext } from "$lib/components/contexts/ItemMultiListC import { getModalContext } from "$lib/components/contexts/ModalContext.svelte"; import { getProjectContext } from "$lib/components/contexts/ProjectContext.svelte"; import { getScopeContext } from "$lib/components/contexts/ScopeContext.svelte"; +import { getScopeListContext } from "$lib/components/contexts/ScopeListContext.svelte"; import { getSprintListContext } from "$lib/components/contexts/SprintListContext.svelte"; import AcquiredTimeInput from "$lib/components/controls/AcquiredTimeInput.svelte"; import StatInput from "$lib/components/controls/StatInput.svelte"; @@ -20,6 +21,7 @@ import { getSprintListContext } from "$lib/components/contexts/SprintListContext const {currentModal, closeModal} = getModalContext(); const {scope} = getScopeContext(); + const {scopes} = getScopeListContext(); const {project, reloadProject} = getProjectContext(); const {reloadItemList} = getItemListContext(); const {reloadSprintList} = getSprintListContext(); @@ -27,6 +29,7 @@ import { getSprintListContext } from "$lib/components/contexts/SprintListContext let item: ItemInput let itemId: number + let scopeId: number let currentStats: ItemInput["stats"] let openedDate: Date let requirementName: string @@ -71,6 +74,7 @@ import { getSprintListContext } from "$lib/components/contexts/SprintListContext } op = "Create" + scopeId = scope.id; openedDate = new Date(); requirementName = requirement?.name; show = true; @@ -80,7 +84,12 @@ import { getSprintListContext } from "$lib/components/contexts/SprintListContext const req = $project.requirements?.find(r => r.id === current.requirementId); let ctxStats = requirement?.stats.map(s => ({statId: s.id, required: 0, acquired: 0})); if (ctxStats == null || ctxStats.length === 0) { - ctxStats = scope.stats.map(s => ({statId: s.id, required: 0, acquired: 0})) + let actualScope = scope; + if (actualScope.id !== current.scopeId && $scopes?.length > 0) { + actualScope = $scopes.find(s => s.id === current.scopeId) + } + + ctxStats = actualScope.stats.map(s => ({statId: s.id, required: 0, acquired: 0})); } const inputStats = current.stats.map(s => ({statId: s.id, acquired: s.acquired, required: s.required})); @@ -93,6 +102,7 @@ import { getSprintListContext } from "$lib/components/contexts/SprintListContext scheduledDate: current.scheduledDate, }; itemId = current.id; + scopeId = current.scopeId; currentStats = [...item.stats]; op = "Edit" @@ -124,7 +134,7 @@ import { getSprintListContext } from "$lib/components/contexts/SprintListContext } } - await sl3(fetch).createItem($scope.id, submission); + await sl3(fetch).createItem(scopeId, submission); break; case "Edit": if (!submission.acquiredTime) { @@ -135,7 +145,7 @@ import { getSprintListContext } from "$lib/components/contexts/SprintListContext } submission.stats = statDiff(currentStats, submission.stats) - await sl3(fetch).updateItem($scope.id, itemId, submission); + await sl3(fetch).updateItem(scopeId, itemId, submission); break; } @@ -180,6 +190,7 @@ import { getSprintListContext } from "$lib/components/contexts/SprintListContext s2.statId === s.statId); - if (old == null || old.required != s.required || old.acquired != s.required) { + if (old == null || old.required != s.required || old.acquired != s.acquired) { res.push(s); } } diff --git a/frontend/src/routes/[scope=prettyid]/__layout.svelte b/frontend/src/routes/[scope=prettyid]/__layout.svelte index 1c6dcb1..6ff1218 100644 --- a/frontend/src/routes/[scope=prettyid]/__layout.svelte +++ b/frontend/src/routes/[scope=prettyid]/__layout.svelte @@ -26,12 +26,12 @@ import ProjectListContext from "$lib/components/contexts/ProjectListContext.svelte"; import ScopeMenu from "$lib/components/scope/ScopeMenu.svelte"; import { scopePrettyId } from "$lib/utils/prettyIds"; -import ScopeHeader from "$lib/components/scope/ScopeHeader.svelte"; + import ScopeHeader from "$lib/components/scope/ScopeHeader.svelte"; export let scope: Scope; export let projects: ProjectEntry[]; - let hideMobile; + let hideMobile: boolean; $: hideMobile = $page.url.pathname !== `/${scopePrettyId(scope)}` diff --git a/frontend/src/routes/[scope=prettyid]/overview.svelte b/frontend/src/routes/[scope=prettyid]/overview.svelte index e631e47..e53ad8c 100644 --- a/frontend/src/routes/[scope=prettyid]/overview.svelte +++ b/frontend/src/routes/[scope=prettyid]/overview.svelte @@ -4,10 +4,10 @@ function generateItemFilters(now: Date): {scheduledFilter: ItemFilter, acquiredFilter: ItemFilter, looseFilter: ItemFilter} { return { scheduledFilter: { - scheduledDate: datesOf(parseInterval("next:7d", new Date)), + scheduledDate: datesOf(parseInterval("next:7d", now)), }, acquiredFilter: { - acquiredTime: parseInterval("today", new Date), + acquiredTime: parseInterval("today", now), }, looseFilter: { loose: true, @@ -47,7 +47,6 @@ import { sl3 } from "$lib/clients/sl3"; import parseInterval, { datesOf } from "$lib/utils/timeinterval"; import type Item from "$lib/models/item"; - import ItemSubSection from "$lib/components/project/ItemSubSection.svelte"; import ItemCreateModal from "$lib/modals/ItemCreateModal.svelte"; import Card from "$lib/components/common/Card.svelte"; import CardHeader from "$lib/components/common/CardHeader.svelte"; @@ -62,7 +61,7 @@ import type { ItemFilter } from "$lib/models/item"; import ItemMultiListContext from "$lib/components/contexts/ItemMultiListContext.svelte"; import { getTimeContext } from "$lib/components/contexts/TimeContext.svelte"; -import ItemListRow from "$lib/components/scope/ItemListRow.svelte"; + import ItemListRow from "$lib/components/scope/ItemListRow.svelte"; export let acquiredItems: Item[]; export let scheduledItems: Item[]; diff --git a/frontend/src/routes/index.svelte b/frontend/src/routes/index.svelte index 9c04d5e..9ce2fe8 100644 --- a/frontend/src/routes/index.svelte +++ b/frontend/src/routes/index.svelte @@ -2,11 +2,33 @@ import type { Load } from "@sveltejs/kit/types/internal"; import { sl3 } from "$lib/clients/sl3"; + function generateItemFilters(now: Date): {scheduledFilter: ItemFilter, acquiredFilter: ItemFilter, looseFilter: ItemFilter} { + return { + scheduledFilter: { + scheduledDate: datesOf(parseInterval("next:7d", now)), + }, + acquiredFilter: { + acquiredTime: parseInterval("today", now), + }, + looseFilter: { + loose: true, + unAcquired: true, + unScheduled: true, + }, + } + } + export const load: Load = async({ fetch, session }) => { const scopes = await sl3(fetch).listScopes(); + const {acquiredFilter, looseFilter, scheduledFilter} = generateItemFilters(new Date()); + + const scheduledItems = (await sl3(fetch).listItems("ALL", scheduledFilter)); + const acquiredItems = (await sl3(fetch).listItems("ALL", acquiredFilter)); + const looseItems = (await sl3(fetch).listItems("ALL", looseFilter)); + return { - props: { scopes } + props: { scopes, scheduledItems, acquiredItems, looseItems } }; } @@ -21,26 +43,48 @@ import ScopeCreateUpdateModal from "$lib/modals/ScopeCreateUpdateModal.svelte"; import OptionsRow from "$lib/components/layout/OptionsRow.svelte"; import Option from "$lib/components/layout/Option.svelte"; -import ScopeListContext from "$lib/components/contexts/ScopeListContext.svelte"; + import ScopeListContext from "$lib/components/contexts/ScopeListContext.svelte"; + import parseInterval, { datesOf } from "$lib/utils/timeinterval"; + import ItemMultiListContext from "$lib/components/contexts/ItemMultiListContext.svelte"; + import type Item from "$lib/models/item"; + import DeletionModal from "$lib/modals/DeletionModal.svelte"; + import ItemAcquireModal from "$lib/modals/ItemAcquireModal.svelte"; + import ItemListRow from "$lib/components/scope/ItemListRow.svelte"; + import { getTimeContext } from "$lib/components/contexts/TimeContext.svelte"; + import type { ItemFilter } from "$lib/models/item"; +import ItemCreateModal from "$lib/modals/ItemCreateModal.svelte"; export let scopes: Scope[] = []; + export let acquiredItems: Item[]; + export let scheduledItems: Item[]; + export let looseItems: Item[]; + + const {now} = getTimeContext();
Stufflog
- - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/go.mod b/go.mod index af80f42..0a82c7c 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,9 @@ go 1.18 require ( github.com/Masterminds/squirrel v1.5.2 + github.com/aws/aws-lambda-go v1.19.1 github.com/aws/aws-sdk-go v1.44.27 + github.com/awslabs/aws-lambda-go-api-proxy v0.13.3 github.com/dgrijalva/jwt-go/v4 v4.0.0-preview1 github.com/gin-gonic/gin v1.7.7 github.com/go-sql-driver/mysql v1.6.0 @@ -13,8 +15,6 @@ require ( ) require ( - github.com/aws/aws-lambda-go v1.19.1 // indirect - github.com/awslabs/aws-lambda-go-api-proxy v0.13.3 // indirect github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.0-20210816181553-5444fa50b93d // indirect github.com/gin-contrib/sse v0.1.0 // indirect github.com/go-playground/locales v0.13.0 // indirect @@ -36,7 +36,6 @@ require ( github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/pkg/errors v0.9.1 // indirect - github.com/snabb/isoweek v1.0.1 // indirect github.com/ugorji/go/codec v1.1.7 // indirect golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e // indirect golang.org/x/sys v0.0.0-20220615213510-4f61da869c0c // indirect diff --git a/go.sum b/go.sum index a0a0c70..159c74a 100644 --- a/go.sum +++ b/go.sum @@ -116,6 +116,7 @@ github.com/flosch/pongo2/v4 v4.0.2/go.mod h1:B5ObFANs/36VwxxlgKpdchIJHMvHB562PW+ github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU= +github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI= github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= @@ -163,7 +164,6 @@ github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3K github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.3 h1:gyjaxf+svBWX08ZjK86iN9geUJF0H6gp2IRKX6Nf6/I= github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= @@ -195,6 +195,7 @@ github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.7 h1:81/ik6ipDQS2aGcBfIN5dHDB36BwrStyeAQquSYCV4o= github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= @@ -258,7 +259,6 @@ github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHW github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= -github.com/json-iterator/go v1.1.9 h1:9yzud/Ht36ygwatGx56VwCZtlI/2AD15T1X2sjSuGns= github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= @@ -285,8 +285,10 @@ github.com/klauspost/compress v1.15.0/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47e github.com/klauspost/compress v1.15.6/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU= github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/labstack/echo/v4 v4.1.17/go.mod h1:Tn2yRQL/UclUalpb5rPdXDevbkJ+lp/2svdyFBg6CHQ= github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k= @@ -320,7 +322,6 @@ github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ= -github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= @@ -337,11 +338,9 @@ github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0Qu github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= -github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 h1:Esafd1046DLDQ0W1YjYsBW+p8U2u7vzgW2SQVmlNazg= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= @@ -356,16 +355,19 @@ github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJE github.com/neelance/sourcemap v0.0.0-20200213170602-2833bce08e4c/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= +github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= +github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= github.com/onsi/ginkgo/v2 v2.0.0/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/onsi/gomega v1.16.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= +github.com/onsi/gomega v1.18.1 h1:M1GfJqGRrBrrGGsbxzV5dqM2U2ApXefZCQpkukxYRLE= github.com/onsi/gomega v1.18.1/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= @@ -399,8 +401,6 @@ github.com/smartystreets/assertions v1.2.0/go.mod h1:tcbTF8ujkAEcZ8TElKY+i30BzYl github.com/smartystreets/assertions v1.2.1/go.mod h1:wDmR7qL282YbGsPy6H/yAsesrxfxaaSlJazyFLYVFx8= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/smartystreets/goconvey v1.7.2/go.mod h1:Vw0tHAZW6lzCRk3xgdin6fKYcG+G3Pg9vgXWeJpQFMM= -github.com/snabb/isoweek v1.0.1 h1:B4IsN2GU8lCNVkaUUgOzaVpPkKC2DdY9zcnxz5yc0qg= -github.com/snabb/isoweek v1.0.1/go.mod h1:CAijAxH7NMgjqGc9baHMDE4sTHMt4B/f6X/XLiEE1iA= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= @@ -489,7 +489,6 @@ golang.org/x/crypto v0.0.0-20210314154223-e6e6c4f2bb5b/go.mod h1:T9bdIzuCu7OtxOm golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20220112180741-5e0467b6c7ce/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.0.0-20220427172511-eb4f295cb31f h1:OeJjE6G4dgCY4PIXvIRQbE8+RX+uXZyGhUy/ksMGJoc= golang.org/x/crypto v0.0.0-20220427172511-eb4f295cb31f/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e h1:T8NU3HyQ8ClP4SEE+KbFlg6n0NhuTsN4MyznaarGsZM= golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= @@ -573,6 +572,7 @@ golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qx golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220617184016-355a448f1bc9 h1:Yqz/iviulwKwAREEeUd3nbBFn0XuyJqkoft2IlrvOhc= golang.org/x/net v0.0.0-20220617184016-355a448f1bc9/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -663,7 +663,6 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210816074244-15123e1e1f71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e h1:fLOSk5Q00efkSvAm+4xcoXD+RRmLmmulPn5I3Y9F2EM= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220111092808-5a964db01320/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -681,6 +680,7 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -744,6 +744,7 @@ golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= @@ -850,7 +851,6 @@ google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQ google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw= google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -862,16 +862,15 @@ gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMy gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.66.4/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.66.6/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/ports/httpapi/items.go b/ports/httpapi/items.go index 6adf7a2..14d8937 100644 --- a/ports/httpapi/items.go +++ b/ports/httpapi/items.go @@ -12,6 +12,17 @@ import ( "time" ) +func ItemsAllScopes(g *gin.RouterGroup, items *items.Service) { + g.GET("", handler("items", func(c *gin.Context) (interface{}, error) { + filter, err := getItemFilterFromQuery(c) + if err != nil { + return nil, err + } + + return items.ListAllScopes(c.Request.Context(), filter) + })) +} + func Items(g *gin.RouterGroup, items *items.Service) { g.GET("/:item_id", handler("project", func(c *gin.Context) (interface{}, error) { id, err := reqInt(c, "item_id") @@ -23,195 +34,9 @@ func Items(g *gin.RouterGroup, items *items.Service) { })) g.GET("", handler("items", func(c *gin.Context) (interface{}, error) { - filter := models.ItemFilter{} - - if rawFilter := c.Query("filter"); rawFilter != "" { - err := json.Unmarshal([]byte(rawFilter), &filter) - if err != nil { - return nil, models.BadInputError{ - Object: "Query", - Field: "filter", - Problem: "Invalid raw filter: " + err.Error(), - } - } - } - - if queryOwnerId := c.Query("ownerId"); queryOwnerId != "" { - filter.OwnerID = &queryOwnerId - } - - queryScheduledMin := c.Query("scheduledMin") - queryScheduledMax := c.Query("scheduledMax") - if queryScheduledMin != "" && queryScheduledMax != "" { - min, err := models.ParseDate(queryScheduledMin) - if err != nil { - return nil, models.BadInputError{ - Object: "Query", - Field: "scheduledMin", - Problem: "Invalid from date: " + err.Error(), - } - } - max, err := models.ParseDate(queryScheduledMax) - if err != nil { - return nil, models.BadInputError{ - Object: "Query", - Field: "scheduledMax", - Problem: "Invalid to date: " + err.Error(), - } - } - - filter.ScheduledDate = &models.TimeInterval[models.Date]{Min: min, Max: max} - } - - queryCreatedMin := c.Query("createdMin") - queryCreatedMax := c.Query("createdMax") - if queryCreatedMin != "" && queryCreatedMax != "" { - min, err := time.Parse(time.RFC3339, queryCreatedMin) - if err != nil { - return nil, models.BadInputError{ - Object: "Query", - Field: "createdMin", - Problem: "Invalid from date: " + err.Error(), - } - } - max, err := time.Parse(time.RFC3339, queryCreatedMax) - if err != nil { - return nil, models.BadInputError{ - Object: "Query", - Field: "createdMax", - Problem: "Invalid to date: " + err.Error(), - } - } - - filter.CreatedTime = &models.TimeInterval[time.Time]{Min: min, Max: max} - } - - queryAcquiredMin := c.Query("acquiredMin") - queryAcquiredMax := c.Query("acquiredMax") - if queryAcquiredMin != "" && queryAcquiredMax != "" { - min, err := time.Parse(time.RFC3339, queryAcquiredMin) - if err != nil { - return nil, models.BadInputError{ - Object: "Query", - Field: "acquiredMin", - Problem: "Invalid from date: " + err.Error(), - } - } - max, err := time.Parse(time.RFC3339, queryAcquiredMax) - if err != nil { - return nil, models.BadInputError{ - Object: "Query", - Field: "acquiredMax", - Problem: "Invalid to date: " + err.Error(), - } - } - - filter.AcquiredTime = &models.TimeInterval[time.Time]{Min: min, Max: max} - } - - anyDateMin := c.Query("anyDateMin") - anyDateMax := c.Query("anyDateMax") - if anyDateMin != "" && anyDateMax != "" { - min, err := time.Parse(time.RFC3339, anyDateMin) - if err != nil { - return nil, models.BadInputError{ - Object: "Query", - Field: "anyDateMin", - Problem: "Invalid from date: " + err.Error(), - } - } - max, err := time.Parse(time.RFC3339, anyDateMax) - if err != nil { - return nil, models.BadInputError{ - Object: "Query", - Field: "anyDateMax", - Problem: "Invalid to date: " + err.Error(), - } - } - - minY, minM, minD := min.Date() - maxY, maxM, maxD := max.Date() - - filter.AcquiredTime = &models.TimeInterval[time.Time]{Min: min, Max: max} - filter.CreatedTime = &models.TimeInterval[time.Time]{Min: min, Max: max} - filter.ScheduledDate = &models.TimeInterval[models.Date]{ - Min: models.Date{minY, int(minM), minD}, - Max: models.Date{maxY, int(maxM), maxD}, - } - } - - if queryProjectID := c.Query("projectId"); queryProjectID != "" { - ids := strings.Split(queryProjectID, ",") - for _, id := range ids { - parsed, err := strconv.Atoi(id) - if err != nil { - return nil, models.BadInputError{ - Object: "Query", - Field: fmt.Sprintf("projectId[%d]", len(filter.ProjectIDs)), - Problem: "Invalid number", - } - } - - filter.ProjectIDs = append(filter.ProjectIDs, parsed) - } - } - - if queryRequirementID := c.Query("requirementId"); queryRequirementID != "" { - if queryRequirementID != "null" { - ids := strings.Split(queryRequirementID, ",") - for _, id := range ids { - parsed, err := strconv.Atoi(id) - if err != nil { - return nil, models.BadInputError{ - Object: "Query", - Field: fmt.Sprintf("requirementId[%d]", len(filter.RequirementIDs)), - Problem: "Invalid number", - } - } - - filter.RequirementIDs = append(filter.RequirementIDs, parsed) - } - } else { - filter.Loose = true - } - } - - if statID := c.Query("statId"); statID != "" { - ids := strings.Split(statID, ",") - for _, id := range ids { - parsed, err := strconv.Atoi(id) - if err != nil { - return nil, models.BadInputError{ - Object: "Query", - Field: fmt.Sprintf("statId[%d]", len(filter.StatIDs)), - Problem: "Invalid number", - } - } - - filter.StatIDs = append(filter.StatIDs, parsed) - } - } - - if filter.ScheduledDate != nil && !filter.ScheduledDate.Valid() { - return nil, models.BadInputError{ - Object: "Filter", - Field: "scheduledDate", - Problem: "Invalid scheduled date range", - } - } - if filter.CreatedTime != nil && !filter.CreatedTime.Valid() { - return nil, models.BadInputError{ - Object: "Filter", - Field: "createdTime", - Problem: "Invalid created time range", - } - } - if filter.AcquiredTime != nil && !filter.AcquiredTime.Valid() { - return nil, models.BadInputError{ - Object: "Filter", - Field: "acquiredTime", - Problem: "Invalid created time range", - } + filter, err := getItemFilterFromQuery(c) + if err != nil { + return nil, err } return items.ListScoped(c.Request.Context(), filter) @@ -300,3 +125,198 @@ func Items(g *gin.RouterGroup, items *items.Service) { return items.Delete(c.Request.Context(), id) })) } + +func getItemFilterFromQuery(c *gin.Context) (models.ItemFilter, error) { + filter := models.ItemFilter{} + + if rawFilter := c.Query("filter"); rawFilter != "" { + err := json.Unmarshal([]byte(rawFilter), &filter) + if err != nil { + return models.ItemFilter{}, models.BadInputError{ + Object: "Query", + Field: "filter", + Problem: "Invalid raw filter: " + err.Error(), + } + } + } + + if queryOwnerId := c.Query("ownerId"); queryOwnerId != "" { + filter.OwnerID = &queryOwnerId + } + + queryScheduledMin := c.Query("scheduledMin") + queryScheduledMax := c.Query("scheduledMax") + if queryScheduledMin != "" && queryScheduledMax != "" { + min, err := models.ParseDate(queryScheduledMin) + if err != nil { + return models.ItemFilter{}, models.BadInputError{ + Object: "Query", + Field: "scheduledMin", + Problem: "Invalid from date: " + err.Error(), + } + } + max, err := models.ParseDate(queryScheduledMax) + if err != nil { + return models.ItemFilter{}, models.BadInputError{ + Object: "Query", + Field: "scheduledMax", + Problem: "Invalid to date: " + err.Error(), + } + } + + filter.ScheduledDate = &models.TimeInterval[models.Date]{Min: min, Max: max} + } + + queryCreatedMin := c.Query("createdMin") + queryCreatedMax := c.Query("createdMax") + if queryCreatedMin != "" && queryCreatedMax != "" { + min, err := time.Parse(time.RFC3339, queryCreatedMin) + if err != nil { + return models.ItemFilter{}, models.BadInputError{ + Object: "Query", + Field: "createdMin", + Problem: "Invalid from date: " + err.Error(), + } + } + max, err := time.Parse(time.RFC3339, queryCreatedMax) + if err != nil { + return models.ItemFilter{}, models.BadInputError{ + Object: "Query", + Field: "createdMax", + Problem: "Invalid to date: " + err.Error(), + } + } + + filter.CreatedTime = &models.TimeInterval[time.Time]{Min: min, Max: max} + } + + queryAcquiredMin := c.Query("acquiredMin") + queryAcquiredMax := c.Query("acquiredMax") + if queryAcquiredMin != "" && queryAcquiredMax != "" { + min, err := time.Parse(time.RFC3339, queryAcquiredMin) + if err != nil { + return models.ItemFilter{}, models.BadInputError{ + Object: "Query", + Field: "acquiredMin", + Problem: "Invalid from date: " + err.Error(), + } + } + max, err := time.Parse(time.RFC3339, queryAcquiredMax) + if err != nil { + return models.ItemFilter{}, models.BadInputError{ + Object: "Query", + Field: "acquiredMax", + Problem: "Invalid to date: " + err.Error(), + } + } + + filter.AcquiredTime = &models.TimeInterval[time.Time]{Min: min, Max: max} + } + + anyDateMin := c.Query("anyDateMin") + anyDateMax := c.Query("anyDateMax") + if anyDateMin != "" && anyDateMax != "" { + min, err := time.Parse(time.RFC3339, anyDateMin) + if err != nil { + return models.ItemFilter{}, models.BadInputError{ + Object: "Query", + Field: "anyDateMin", + Problem: "Invalid from date: " + err.Error(), + } + } + max, err := time.Parse(time.RFC3339, anyDateMax) + if err != nil { + return models.ItemFilter{}, models.BadInputError{ + Object: "Query", + Field: "anyDateMax", + Problem: "Invalid to date: " + err.Error(), + } + } + + minY, minM, minD := min.Date() + maxY, maxM, maxD := max.Date() + + filter.AcquiredTime = &models.TimeInterval[time.Time]{Min: min, Max: max} + filter.CreatedTime = &models.TimeInterval[time.Time]{Min: min, Max: max} + filter.ScheduledDate = &models.TimeInterval[models.Date]{ + Min: models.Date{minY, int(minM), minD}, + Max: models.Date{maxY, int(maxM), maxD}, + } + } + + if queryProjectID := c.Query("projectId"); queryProjectID != "" { + ids := strings.Split(queryProjectID, ",") + for _, id := range ids { + parsed, err := strconv.Atoi(id) + if err != nil { + return models.ItemFilter{}, models.BadInputError{ + Object: "Query", + Field: fmt.Sprintf("projectId[%d]", len(filter.ProjectIDs)), + Problem: "Invalid number", + } + } + + filter.ProjectIDs = append(filter.ProjectIDs, parsed) + } + } + + if queryRequirementID := c.Query("requirementId"); queryRequirementID != "" { + if queryRequirementID != "null" { + ids := strings.Split(queryRequirementID, ",") + for _, id := range ids { + parsed, err := strconv.Atoi(id) + if err != nil { + return models.ItemFilter{}, models.BadInputError{ + Object: "Query", + Field: fmt.Sprintf("requirementId[%d]", len(filter.RequirementIDs)), + Problem: "Invalid number", + } + } + + filter.RequirementIDs = append(filter.RequirementIDs, parsed) + } + } else { + filter.Loose = true + } + } + + if statID := c.Query("statId"); statID != "" { + ids := strings.Split(statID, ",") + for _, id := range ids { + parsed, err := strconv.Atoi(id) + if err != nil { + return models.ItemFilter{}, models.BadInputError{ + Object: "Query", + Field: fmt.Sprintf("statId[%d]", len(filter.StatIDs)), + Problem: "Invalid number", + } + } + + filter.StatIDs = append(filter.StatIDs, parsed) + } + } + + if filter.ScheduledDate != nil && !filter.ScheduledDate.Valid() { + return models.ItemFilter{}, models.BadInputError{ + Object: "Filter", + Field: "scheduledDate", + Problem: "Invalid scheduled date range", + } + } + if filter.CreatedTime != nil && !filter.CreatedTime.Valid() { + return models.ItemFilter{}, models.BadInputError{ + Object: "Filter", + Field: "createdTime", + Problem: "Invalid created time range", + } + } + if filter.AcquiredTime != nil && !filter.AcquiredTime.Valid() { + return models.ItemFilter{}, models.BadInputError{ + Object: "Filter", + Field: "acquiredTime", + Problem: "Invalid created time range", + } + } + + return filter, nil +} diff --git a/ports/httpapi/scopes.go b/ports/httpapi/scopes.go index d11c9eb..d7fdbab 100644 --- a/ports/httpapi/scopes.go +++ b/ports/httpapi/scopes.go @@ -9,20 +9,40 @@ import ( "net/http" ) -type scopeResult struct { - entities.Scope +func AllScopeMiddleware(scopes *scopes.Service, auth *auth.Service) gin.HandlerFunc { + return func(c *gin.Context) { + user := auth.GetUser(c.Request.Context()) + if user == nil { + c.AbortWithStatusJSON(http.StatusUnauthorized, Error{ + Code: http.StatusNotFound, + Message: "Scopes not found or inaccessible", + }) + return + } - Members []scopeResultMember `json:"members"` -} + userScopes, err := scopes.List(c.Request.Context()) + if err != nil { + c.AbortWithStatusJSON(http.StatusUnauthorized, Error{ + Code: http.StatusNotFound, + Message: "Scopes not found or inaccessible", + }) + return + } -type scopeResultMember struct { - ID string `json:"id"` - Name string `json:"name"` - Owner bool `json:"owner"` + c.Request = c.Request.WithContext(scopes.CreateListContext(c.Request.Context(), user.ID, userScopes)) + } } func ScopeMiddleware(scopes *scopes.Service, auth *auth.Service) gin.HandlerFunc { return func(c *gin.Context) { + user := auth.GetUser(c.Request.Context()) + if user == nil { + c.AbortWithStatusJSON(http.StatusUnauthorized, Error{ + Code: http.StatusNotFound, + Message: "Scope not found or inaccessible", + }) + } + id, err := reqInt(c, "scope_id") if err != nil { c.AbortWithStatusJSON(http.StatusUnauthorized, Error{ @@ -45,22 +65,6 @@ func ScopeMiddleware(scopes *scopes.Service, auth *auth.Service) gin.HandlerFunc return } - found := false - user := auth.GetUser(c.Request.Context()) - for id, _ := range scope.Members { - if id == user.ID { - found = true - break - } - } - if !found { - c.AbortWithStatusJSON(http.StatusUnauthorized, Error{ - Code: http.StatusNotFound, - Message: "Scope not found or inaccessible", - }) - return - } - c.Request = c.Request.WithContext(scopes.CreateContext(c.Request.Context(), user.ID, *scope)) } } diff --git a/ports/mysql/projects.go b/ports/mysql/projects.go index 4d53218..9938426 100644 --- a/ports/mysql/projects.go +++ b/ports/mysql/projects.go @@ -194,11 +194,14 @@ func (r *projectRepository) FetchRequirements(ctx context.Context, scopeID int, return []entities.Requirement{}, []entities.RequirementStat{}, nil } - query, args, err := squirrel.Select("id, scope_id, project_id, name, status, description, is_coarse"). + sq := squirrel.Select("id, scope_id, project_id, name, status, description, is_coarse"). From("project_requirement"). - Where(squirrel.Eq{"scope_id": scopeID}). - Where(squirrel.Eq{"id": requirementIDs}). - ToSql() + Where(squirrel.Eq{"id": requirementIDs}) + if scopeID != -1 { + sq = sq.Where(squirrel.Eq{"scope_id": scopeID}) + } + + query, args, err := sq.ToSql() if err != nil { return nil, nil, err } diff --git a/usecases/items/service.go b/usecases/items/service.go index f238719..f18f4e5 100644 --- a/usecases/items/service.go +++ b/usecases/items/service.go @@ -57,6 +57,68 @@ func (s *Service) Find(ctx context.Context, id int) (*Result, error) { return &result, nil } +func (s *Service) ListAllScopes(ctx context.Context, filter models.ItemFilter) ([]Result, error) { + userScopes, err := s.Scopes.Context(ctx).Scopes(ctx) + if err != nil { + return nil, err + } + if len(userScopes) == 0 { + return []Result{}, nil + } + + filter.ScopeIDs = make([]int, 0, len(userScopes)) + for _, scope := range userScopes { + filter.ScopeIDs = append(filter.ScopeIDs, scope.ID) + } + + items, err := s.Repository.Fetch(ctx, filter) + if err != nil { + return nil, err + } + + progresses, err := s.Repository.ListStat(ctx, items...) + if err != nil { + return nil, err + } + + reqIds := genutils.Set[int]{} + projIds := genutils.Set[int]{} + for _, item := range items { + if item.RequirementID != nil { + reqIds.Add(*item.RequirementID) + } + if item.ProjectID != nil { + projIds.Add(*item.ProjectID) + } + } + var requirements []entities.Requirement + if reqIds.Len() > 0 { + requirements, _, err = s.RequirementFetcher.FetchRequirements(ctx, -1, reqIds.Values()...) + if err != nil { + return nil, err + } + } + var projects []entities.Project + if projIds.Len() > 0 { + projects, err = s.ProjectFetcher.FetchProjects(ctx, -1, projIds.Values()...) + if err != nil { + return nil, err + } + } + + res := make([]Result, 0, len(items)) + for _, item := range items { + for _, scope := range userScopes { + if item.ScopeID == scope.ID { + res = append(res, generateResult(item, scope, progresses, requirements, projects)) + break + } + } + } + + return res, nil +} + func (s *Service) ListScoped(ctx context.Context, filter models.ItemFilter) ([]Result, error) { sc := s.Scopes.Context(ctx) if sc == nil { diff --git a/usecases/scopes/context.go b/usecases/scopes/context.go index 4e4ca10..20e66d6 100644 --- a/usecases/scopes/context.go +++ b/usecases/scopes/context.go @@ -2,7 +2,6 @@ package scopes import ( "context" - "git.aiterp.net/stufflog3/stufflog3/entities" "sync" ) @@ -14,19 +13,16 @@ type Context struct { ID int Scope Result - statsOnce sync.Once - stats []entities.Stat - statsError error - scopesOnce sync.Once - scopes []entities.Scope + scopes []Result scopesError error + service *Service } // Scopes lazy-loads the scopes in the context of the first caller. -func (c *Context) Scopes(ctx context.Context) ([]entities.Scope, error) { - c.statsOnce.Do(func() { - c.scopes, c.scopesError = c.scopesRepo.ListUser(ctx, c.userID) +func (c *Context) Scopes(ctx context.Context) ([]Result, error) { + c.scopesOnce.Do(func() { + c.scopes, c.scopesError = c.service.List(ctx) }) return c.scopes, c.scopesError diff --git a/usecases/scopes/service.go b/usecases/scopes/service.go index bdebeb5..3b186fc 100644 --- a/usecases/scopes/service.go +++ b/usecases/scopes/service.go @@ -14,7 +14,7 @@ type Service struct { Repository Repository StatsLister StatsLister - contextKey struct{} + contextKey struct{ B int64 } } func (s *Service) CreateContext(ctx context.Context, userID string, scope Result) context.Context { @@ -25,9 +25,34 @@ func (s *Service) CreateContext(ctx context.Context, userID string, scope Result ID: scope.ID, Scope: scope, + + service: s, }) } +func (s *Service) CreateListContext(ctx context.Context, userID string, scopes []Result) context.Context { + contextData := &Context{ + scopesRepo: s.Repository, + statsRepo: s.StatsLister, + userID: userID, + scopes: scopes, + scopesError: nil, + + ID: -1, + Scope: Result{ + Scope: entities.Scope{ID: -1}, + Members: map[string]ResultMember{}, + Stats: []ResultStat{}, + }, + + service: s, + } + + contextData.scopesOnce.Do(func() { /* a lot of nothing */ }) + + return context.WithValue(ctx, &s.contextKey, contextData) +} + func (s *Service) Context(ctx context.Context) *Context { v := ctx.Value(&s.contextKey) if v == nil { @@ -216,4 +241,4 @@ func (s *Service) Delete(ctx context.Context, scopeId int) (*Result, error) { } return scope, nil -} \ No newline at end of file +}