diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml new file mode 100644 index 00000000..9aabf3e8 --- /dev/null +++ b/.github/workflows/build.yaml @@ -0,0 +1,23 @@ +name: Build + +on: + push: + branches: [ develop ] + pull_request: + branches: [ develop ] + +jobs: + + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - name: Set up Go + uses: actions/setup-go@v3 + with: + go-version: '1.20' + check-latest: true + + - name: Build + run: make \ No newline at end of file diff --git a/.secrets.baseline b/.secrets.baseline index 59c202e3..76f57e1b 100644 --- a/.secrets.baseline +++ b/.secrets.baseline @@ -128,46 +128,26 @@ "is_secret": false } ], - "driven/contactinfo/uiuc_contactinfo.go": [ + "driven/uiucadapters/laundryadapter.go": [ { "type": "Secret Keyword", - "filename": "driven/contactinfo/uiuc_contactinfo.go", - "hashed_secret": "e8a1c6ef3889d4c1f0fe37af5c9a782d3dc45f00", + "filename": "driven/uiucadapters/laundryadapter.go", + "hashed_secret": "0cf56cce952f22a8ea044fe0ff52f2ddbba6e462", "is_verified": false, - "line_number": 117, + "line_number": 163, "is_secret": false } ], - "driven/courses/uiuc_courses.go": [ + "driven/uiucadapters/wayfindingadapter.go": [ { "type": "Secret Keyword", - "filename": "driven/courses/uiuc_courses.go", - "hashed_secret": "a2b25e53b264bf5320c253983eb50156d07ec037", + "filename": "driven/uiucadapters/wayfindingadapter.go", + "hashed_secret": "abd4b25cafb785cdf391edc04f82d8af45e1405c", "is_verified": false, - "line_number": 133, - "is_secret": false - } - ], - "driven/laundry/csc_laundryview.go": [ - { - "type": "Secret Keyword", - "filename": "driven/laundry/csc_laundryview.go", - "hashed_secret": "09ff4b16fe5c5d3fe24fc78231db976393d46f9a", - "is_verified": false, - "line_number": 99, - "is_secret": false - } - ], - "driven/location/uiuc_buildingdata.go": [ - { - "type": "Secret Keyword", - "filename": "driven/location/uiuc_buildingdata.go", - "hashed_secret": "d42e283b5bff2a623b6f332fd14ac698166810fa", - "is_verified": false, - "line_number": 77, + "line_number": 41, "is_secret": false } ] }, - "generated_at": "2023-02-15T20:09:55Z" + "generated_at": "2023-05-01T21:52:28Z" } diff --git a/CHANGELOG.md b/CHANGELOG.md index cbd12654..b6ead819 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - Prepare the project to become open source [#2](https://github.com/rokwire/gateway-building-block/issues/2) +## 2.0.3 - 5/1/23 +### Added +--appointments end points and interfaces + +### Changed +--updated old code to new building block template model + + ## 1.2.6 - 3/9/23 ### Fixed --Security vulnerability in golang.org/x/text/language diff --git a/CONVENTIONS.md b/CONVENTIONS.md index 520cffb4..246361ab 100644 --- a/CONVENTIONS.md +++ b/CONVENTIONS.md @@ -10,9 +10,9 @@ We should keep the changelog up to date as this is part of the open source platf ## API Documentation When implementing an API: -- Use [swag](https://github.com/swaggo/swag) annotations when defining a new API handler function -- Run `make swagger` to generate the `swagger.yaml`, `swagger.json`, and `docs.go` files stored in the `docs/` folder. To run this command, you will need to install [swag](https://github.com/swaggo/swag). This command will automatically generate Open API 3 documentation for all functions using the annotations. Please do not change any of the files in the `docs/` folder manually. -- Test you API via the documentation - Open http://localhost/gateway/api/doc/ui/ , choose "Local server" from the "Servers" combobox and run your API. This is an alternative to Postman. Make sure to set the correct value in the `HOST` environment variable (eg. http://localhost/gateway) before running the service to access the docs. +- Define the OpenAPI 3.0 documentation for the API in the appropriate yaml files stored in `driver/web/docs` folder. +- Run `make oapi-gen-docs` to generate the `def.yaml` file stored in `driver/web/docs/gen` folder. To run this command, you will need to install [swagger-cli](https://github.com/APIDevTools/swagger-cli). This command will merge all OpenAPI files into the `def.yaml` file. Please do not change the `def.yaml` file manually. +- Test you API via the documentation - Open http://localhost//doc/ui/ , choose "Local server" from the "Servers" combobox and run your API. This is an alternative to Postman. Make sure to set the correct value in the `_BASE_URL` environment variable (eg. http://localhost/) before running the service to access the docs. ## Pull Requests Pull requests should be linked to the associated issue with a [keyword](https://docs.github.com/en/issues/tracking-your-work-with-issues/creating-issues/linking-a-pull-request-to-an-issue#linking-a-pull-request-to-an-issue-using-a-keyword) in the description (eg. `Resolves #{issue number}`). This will close the issue automatically when the PR is merged. @@ -22,18 +22,38 @@ Whenever a new interface is created, a unit test should be created for each func When updating or changing existing implementations, run the associated unit tests to ensure that they still pass. If they do not, the implementation changes likely changed the interface as well. If the change to the interface was intentional, update the unit tests as needed to make them pass and document the [Breaking Change](#breaking-changes). If the change was not intentional, rework your implementation changes to keep the interface consistent and ensure all tests pass. +### Mocks +To test some components of the system in isolation, it may be necessary to mock some interfaces. Mocks should be automatically generated using the [Mockery](https://github.com/vektra/mockery) utility. Mockery can be installed by running `go install github.com/vektra/mockery/v2@latest`. One example of an interface that will need to be mocked is the `interfaces.Storage` interface. To generate (or regenerate) the mocks for the storage interface using Mockery, `cd core/interfaces` then run `mockery --name=Storage`. + ## Releases Whenever a new release is made, the following process should be followed. -1. Make a pull request from `develop` into `main` named `Release vX.X.X` (eg. `Release v1.1.7`) +### Dev Releases +Changes to the `develop` branch will be continuously deployed into the dev environment to be tested. When several significant changes have been merged into the `develop` branch and have been tested, a new dev release should be made. + +To make a dev release: + +1. Checkout the `develop` branch and `git pull` to ensure you have the latest updates locally. +2. Update the "Unreleased" version in the [CHANGELOG](CHANGELOG.md#unreleased) to `[X.X.X] - YYYY-MM-dd` (eg. `[1.1.7] - 2022-06-08`). +3. Update [SECURITY.md](SECURITY.md) to reflect the latest supported and unsupported versions. +4. Update the latest version in any docs or source code as needed. +5. Make any changes needed to document [breaking changes](#breaking-changes) and [deprecations](#deprecations). +6. Commit all changes to the `develop` branch with the commit message `Release vX.X.X` (eg. `Release v1.1.7). +7. Create a new tag from the `develop` branch called `vX.X.X` (eg. `v1.1.7`). +8. Push changes to `develop` branch and create remote tag atomically using `git push --atomic origin develop vX.X.X` (eg. `git push --atomic origin develop v1.1.7`). +> **NOTE:** Pushing to `develop` will automatically trigger a deployment to the `dev` environment. Pushing and creating the new tag atomically will ensure that the deployment pipeline correctly uses the new tag to set the version on the build it generates. + +### Production Releases +When you are ready to move a release to the production environment: +1. Make a pull request from `develop` into `main` named `Release vX.X.X` (eg. `Release v1.1.7`). 2. Review the changes included in the update to ensure they are all production ready. -3. Update the "Unreleased" version in the [CHANGELOG](CHANGELOG.md#unreleased) to `X.X.X - YYYY-MM-dd` (eg. `[1.1.7] - 2022-06-08`) on the `develop` branch. -4. Update [SECURITY.md](SECURITY.md) to reflect the latest supported and unsupported versions on the `develop` branch. -5. Update the latest version in any docs or source code as needed on the `develop` branch. -6. Make any changes needed to document [breaking changes](#breaking-changes) and [deprecations](#deprecations). -7. Merge the pull request using "Create a merge commit" -8. Create a new tag from the `main` branch called `vX.X.X` (eg. `v1.1.7`) -9. **RECOMMENDED** - Publish a new [GitHub Release](https://docs.github.com/en/repositories/releasing-projects-on-github/managing-releases-in-a-repository#creating-a-release) from this tag with the title `vX.X.X` (eg. `v1.1.7`). Include the contents from the [CHANGELOG](CHANGELOG.md) for this latest version in the release notes, as well as a link to the whole [CHANGELOG](CHANGELOG.md) on the `main` branch. For libraries this is highly recommended. +3. Checkout the `main` branch and `git pull` to ensure you have the latest updates locally. +4. Run `git merge --ff-only origin/develop`. If this merge fails, merge any changes from `main` back into `develop` then restart from Step 3. +> **NOTE:** While this is slightly cumbersome, GitHub does not currently support fast-forward merge through the pull request user interface. We want to use fast-forward merging to preserve the linear history from develop without introducing a new merge commit (like `Create a merge commit`), or rebasing and changing commit hashes unnecessarily (like `Rebase and merge`). This will ensure that the exact same commit hash is used to build for staging and production that was used to build for develop. +5. Run `git push`. +6. **RECOMMENDED** - Publish a new [GitHub Release](https://docs.github.com/en/repositories/releasing-projects-on-github/managing-releases-in-a-repository#creating-a-release) from this tag with the title `vX.X.X` (eg. `v1.1.7`). Include the contents from the [CHANGELOG](CHANGELOG.md) for this latest version in the release notes, as well as a link to the whole [CHANGELOG](CHANGELOG.md) on the `main` branch. For libraries this is highly recommended. + +Pushing to the `main` branch will automatically trigger a deployment to the `stage` environment. Once the release has been tested appropriately, the production pipeline can be manually triggered to deploy the same Docker image in the `stage` environment to the `prod` environment. ## Breaking Changes Breaking changes should be avoided when possible, but will sometimes be necessary. In the event that a breaking change does need to be made, this change should be documented clearly for developers relying on the functionality. This includes the following items: @@ -57,4 +77,4 @@ When a release including the deprecation is created, the following steps must be * Update the `Upgrading > Migration steps > Unreleased` section in the README to the latest version (eg. `Upgrading > Migration steps > v1.1.0`) * Include a copy of the upgrade instructions from the README in the release notes -When the deprecated components are finally removed, follow the process to document this as a [Breaking Change](#breaking-changes). \ No newline at end of file +When the deprecated components are finally removed, follow the process to document this as a [Breaking Change](#breaking-changes). diff --git a/Dockerfile b/Dockerfile index 050cfa84..e1c89021 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,29 +1,33 @@ -FROM golang:1.20.1-buster as builder +FROM golang:1.20-alpine as builder ENV CGO_ENABLED=0 -RUN mkdir /gateway-app -WORKDIR /gateway-app +RUN apk add --no-cache --update make git + +RUN mkdir /app +WORKDIR /app # Copy the source from the current directory to the Working Directory inside the container COPY . . RUN make -FROM alpine:3.16.2 +FROM alpine:3.17.2 #we need timezone database -RUN apk --no-cache add tzdata +RUN apk add --no-cache --update tzdata -COPY --from=builder /gateway-app/bin/apigateway / -COPY --from=builder /gateway-app/docs/swagger.yaml /docs/swagger.yaml +COPY --from=builder /app/bin/application / +COPY --from=builder /app/driver/web/docs/gen/def.yaml /driver/web/docs/gen/def.yaml -COPY --from=builder /gateway-app/assets/assets.json /assets/assets.json +COPY --from=builder /app/driver/web/client_permission_policy.csv /driver/web/client_permission_policy.csv +COPY --from=builder /app/driver/web/client_scope_policy.csv /driver/web/client_scope_policy.csv +COPY --from=builder /app/driver/web/admin_permission_policy.csv /driver/web/admin_permission_policy.csv +COPY --from=builder /app/driver/web/bbs_permission_policy.csv /driver/web/bbs_permission_policy.csv +COPY --from=builder /app/driver/web/tps_permission_policy.csv /driver/web/tps_permission_policy.csv +COPY --from=builder /app/driver/web/system_permission_policy.csv /driver/web/system_permission_policy.csv -COPY --from=builder /gateway-app/driver/web/authorization_model.conf /driver/web/authorization_model.conf -COPY --from=builder /gateway-app/driver/web/authorization_policy.csv /driver/web/authorization_policy.csv +COPY --from=builder /app/vendor/github.com/rokwire/core-auth-library-go/v3/authorization/authorization_model_scope.conf /app/vendor/github.com/rokwire/core-auth-library-go/v3/authorization/authorization_model_scope.conf +COPY --from=builder /app/vendor/github.com/rokwire/core-auth-library-go/v3/authorization/authorization_model_string.conf /app/vendor/github.com/rokwire/core-auth-library-go/v3/authorization/authorization_model_string.conf COPY --from=builder /etc/passwd /etc/passwd -#we need timezone database -COPY --from=builder /usr/share/zoneinfo /usr/share/zoneinfo - -ENTRYPOINT ["/apigateway"] +ENTRYPOINT ["/application"] diff --git a/Makefile b/Makefile index a369154c..da368de6 100644 --- a/Makefile +++ b/Makefile @@ -1,56 +1,44 @@ DATE ?= $(shell date +%FT%T%z) -BIN = $(CURDIR)/build +GOBIN = $(CURDIR)/bin BASE = $(CURDIR) MODULE = $(shell cd $(BASE) && $(GO) list -m) PKGS = $(or $(PKG),$(shell cd $(BASE) && $(GO) list ./...)) BUILDS = $(or $(BUILD),$(shell cd $(BASE) && $(GO) list -f "{{if eq .Name \"main\"}}{{.ImportPath}}{{end}}" ./...)) -GIT_VERSION=$(shell git describe --match "v*" 2> /dev/null || cat $(CURDIR)/.version 2> /dev/null || echo v0.0-0-) +GIT_VERSION=$(shell git describe --tags --match "v*" 2> /dev/null || cat $(CURDIR)/.version 2> /dev/null || echo v0.0-0-) BASE_VERSION=$(shell echo $(GIT_VERSION) | cut -f1 -d'-') MAJOR_VERSION=$(shell echo $(BASE_VERSION) | cut -f1 -d'.' | cut -f2 -d'v') MINOR_VERSION=$(shell echo $(BASE_VERSION) | cut -f2 -d'.') -BUILD_VERSION=$(shell echo $(BASE_VERSION) | cut -f3 -d'.' || echo 0) -BUILD_OFFSET=$(shell echo $(GIT_VERSION) | cut -s -f2 -d'-' ) -CODE_OFFSET=$(shell [ -z "$(BUILD_OFFSET)" ] && echo "0" || echo "$(BUILD_OFFSET)") -BUILD_NUMBER=$(shell echo $$(( $(BUILD_VERSION) + $(CODE_OFFSET) ))) -VERSION ?= ${MAJOR_VERSION}.${MINOR_VERSION}.${BUILD_NUMBER} +PATCH_VERSION=$(shell echo $(BASE_VERSION) | cut -f3 -d'.' || echo 0) +COMMIT_OFFSET=$(shell echo $(GIT_VERSION) | cut -s -f2 -d'-') +COMMIT_HASH=$(shell echo $(GIT_VERSION) | cut -s -f3 -d'-') +VERSION=${MAJOR_VERSION}.${MINOR_VERSION}.${PATCH_VERSION}$(if $(COMMIT_OFFSET),+$(COMMIT_OFFSET),) export -n GOBIN -#export PATH=$(BIN): $(shell printenv PATH) GO = go GODOC = godoc GOFMT = gofmt +GOVET = go vet +GOLINT = $(GOBIN)/golint +GOVULN = $(GOBIN)/govulncheck TIMEOUT = 25 V = 0 Q = $(if $(filter 1,$V),,@) M = $(shell printf "\033[34;1m▶\033[0m") -SHELL=bash +SHELL=sh .PHONY: all -all: vendor log-variables checkfmt lint test-short | $(BASE) ; $(info $(M) building executable(s)… $(VERSION) $(DATE)) @ ## Build program binary +all: vendor log-variables checkfmt lint vet vuln test-short | $(BASE) ; $(info $(M) building executable(s)... $(VERSION) $(DATE)) @ ## Build program binary $Q cd $(CURDIR) && $(GO) generate ./... @ret=0 && for d in $(BUILDS); do \ if expr \"$$d\" : \"${MODULE}\" 1>/dev/null; then SRCPATH=$(CURDIR) ; else SRCPATH=$(CURDIR)/$${d/${MODULE}\//} ; fi ; \ echo $$d; \ - cd $${SRCPATH} && env GOBIN=$(CURDIR)/bin $(GO) install \ + cd $${SRCPATH} && $(GO) install \ -tags release \ -ldflags '-X main.Version=$(VERSION) -X main.Build=$(DATE)' || ret=$$? ; \ done ; exit $$ret -# Tools - -$(BIN): - @mkdir -p $@ - -$(BIN)/%: | $(BIN) $(BASE) ; $(info $(M) building $(REPOSITORY)…) - $Q tmp=$$(mktemp -d); \ - (cd $(tmp) && GOPATH=$$tmp $(GO) get $(REPOSITORY) && cp $$tmp/bin/* $(BIN)/.) || ret=$$?; \ - rm -rf $$tmp ; exit $$ret - -GOLINT = $(BIN)/golint -$(GOLINT): REPOSITORY=golang.org/x/lint/golint - # Tests TEST_TARGETS := test-default test-bench test-short test-verbose test-race @@ -61,21 +49,25 @@ test-verbose: ARGS=-v ## Run tests in verbose mode with coverage repo test-race: ARGS=-race ## Run tests with race detector $(TEST_TARGETS): NAME=$(MAKECMDGOALS:test-%=%) $(TEST_TARGETS): test -check test tests: checkfmt lint | $(BASE) ; $(info $(M) running $(NAME:%=% )tests…) @ ## Run tests +check test tests: checkfmt lint vet | $(BASE) ; $(info $(M) running $(NAME:%=% )tests...) @ ## Run tests $Q cd $(CURDIR) && $(GO) test -v -gcflags=-l -timeout $(TIMEOUT)s $(ARGS) ./... .PHONY: cover -cover: checkfmt lint | $(BASE) ; $(info $(M) running coverage…) @ ## Run code coverage tests +cover: tools checkfmt lint vet | $(BASE) ; $(info $(M) running coverage...) @ ## Run code coverage tests $Q cd $(BASE) && 2>&1 $(GO) test -v -gcflags=-l ./... -coverprofile=c.out $Q cd $(BASE) && 2>&1 $(GO) tool cover -html=c.out $Q cd $(BASE) && 2>&1 rm -f c.out .PHONY: lint -lint: $(BASE) $(GOLINT) ; $(info $(M) running golint…) @ ## Run golint +lint: tools | $(BASE) $(GOLINT) ; $(info $(M) running golint...) @ ## Run golint $Q cd $(BASE) && $(GOLINT) -set_exit_status $(PKGS) +.PHONY: vet +vet: ; $(info $(M) running go vet...) @ ## Run go vet + $Q cd $(CURDIR) && $(GOVET) $(PKGS) + .PHONY: checkfmt -checkfmt: ; $(info $(M) Checking formatting…) @ ## Run gofmt to cehck formatting on all source files +checkfmt: ; $(info $(M) checking formatting...) @ ## Run gofmt to cehck formatting on all source files @ret=0 && for d in $$($(GO) list -f '{{.Dir}}' ./...); do \ if [ $$($(GOFMT) -l $$d/*.go | wc -l | sed 's| ||g') -ne "0" ] ; then \ $(GOFMT) -l $$d/*.go ; \ @@ -84,21 +76,23 @@ checkfmt: ; $(info $(M) Checking formatting…) @ ## Run gofmt to cehck formatti done ; exit $$ret .PHONY: fixfmt -fixfmt: ; $(info $(M) Fixings formatting…) @ ## Run gofmt to fix formatting on all source files +fixfmt: vendor ; $(info $(M) fixing formatting...) @ ## Run gofmt to fix formatting on all source files @ret=0 && for d in $$($(GO) list -f '{{.Dir}}' ./...); do \ $(GOFMT) -l -w $$d/*.go || ret=$$? ; \ done ; exit $$ret +.PHONY: vuln +vuln: tools ; $(info $(M) running govulncheck...) @ ## Run govulncheck + $Q cd $(CURDIR) && $(GOVULN) ./... + # Misc .PHONY: clean -clean: ; $(info $(M) cleaning…) @ ## Cleanup everything +clean: ; $(info $(M) cleaning...) @ ## Cleanup everything @rm -rf bin - @rm -rf build @chmod -R +w vendor @rm -rf vendor @rm -f c.out - @rm -f go.sum @rm -f test.html .PHONY: help @@ -114,14 +108,18 @@ version: vendor: $(GO) mod vendor -.PHONY: swagger -swagger: ; - swag init -g driver/web/adapter.go +.PHONY: oapi-gen-types +oapi-gen-types: ; + oapi-codegen --config oapi-codegen-config.yaml driver/web/docs/gen/def.yaml + +.PHONY: oapi-gen-docs +oapi-gen-docs: ; + swagger-cli bundle driver/web/docs/index.yaml --outfile driver/web/docs/gen/def.yaml --type yaml .PHONY: log-variables -log-variables: ; $(info $(M) Log info…) @ ## Log the variables values +log-variables: ; $(info $(M) logging variables...) @ ## Log the variables values @echo "DATE:"$(DATE) - @echo "BIN:"$(BIN) + @echo "GOBIN:"$(GOBIN) @echo "BASE:"$(BASE) @echo "MODULE:"$(MODULE) @echo "PKGS:"$(PKGS) @@ -130,9 +128,15 @@ log-variables: ; $(info $(M) Log info…) @ ## Log the variables values @echo "BASE_VERSION:"$(BASE_VERSION) @echo "MAJOR_VERSION:"$(MAJOR_VERSION) @echo "MINOR_VERSION:"$(MINOR_VERSION) - @echo "BUILD_VERSION:"$(BUILD_VERSION) - @echo "BUILD_OFFSET:"$(BUILD_OFFSET) - @echo "CODE_OFFSET:"$(CODE_OFFSET) - @echo "BUILD_NUMBER:"$(BUILD_NUMBER) + @echo "PATCH_VERSION:"$(PATCH_VERSION) + @echo "COMMIT_OFFSET:"$(COMMIT_OFFSET) + @echo "COMMIT_HASH:"$(COMMIT_HASH) @echo "VERSION:"$(VERSION) +# Tools + +.PHONY: tools +tools: ; $(info $(M) installing tools...) @ ## Install tools + go install golang.org/x/tools/cmd/cover@latest + go install golang.org/x/lint/golint@latest + go install golang.org/x/vuln/cmd/govulncheck@latest diff --git a/core/app_admin.go b/core/app_admin.go new file mode 100644 index 00000000..2914afd6 --- /dev/null +++ b/core/app_admin.go @@ -0,0 +1,199 @@ +// Copyright 2022 Board of Trustees of the University of Illinois. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package core + +import ( + "application/core/interfaces" + "application/core/model" + "time" + + "github.com/google/uuid" + "github.com/rokwire/core-auth-library-go/v3/authutils" + "github.com/rokwire/core-auth-library-go/v3/tokenauth" + "github.com/rokwire/logging-library-go/v2/errors" + "github.com/rokwire/logging-library-go/v2/logutils" +) + +// appAdmin contains admin implementations +type appAdmin struct { + app *Application +} + +// GetExample gets an Example by ID +func (a appAdmin) GetExample(orgID string, appID string, id string) (*model.Example, error) { + return a.app.shared.getExample(orgID, appID, id) +} + +// CreateExample creates a new Example +func (a appAdmin) CreateExample(example model.Example) (*model.Example, error) { + example.ID = uuid.NewString() + err := a.app.storage.InsertExample(example) + if err != nil { + return nil, errors.WrapErrorAction(logutils.ActionCreate, model.TypeExample, nil, err) + } + return &example, nil +} + +// UpdateExample updates an Example +func (a appAdmin) UpdateExample(example model.Example) error { + return a.app.storage.UpdateExample(example) +} + +// AppendExample appends to the data in an example - Example of transaction usage +func (a appAdmin) AppendExample(example model.Example) (*model.Example, error) { + now := time.Now() + var newExample *model.Example + transaction := func(storage interfaces.Storage) error { + oldExample, err := storage.FindExample(example.OrgID, example.AppID, example.ID) + if err != nil || oldExample == nil { + return errors.WrapErrorAction(logutils.ActionFind, model.TypeExample, nil, err) + } + + oldExample.Data = oldExample.Data + "," + example.Data + oldExample.DateUpdated = &now + + err = storage.UpdateExample(*oldExample) + if err != nil { + return errors.WrapErrorAction(logutils.ActionUpdate, model.TypeExample, nil, err) + } + + newExample = oldExample + return nil + } + + err := a.app.storage.PerformTransaction(transaction) + if err != nil { + return nil, errors.WrapErrorAction(logutils.ActionCommit, logutils.TypeTransaction, nil, err) + } + + return newExample, nil +} + +// DeleteExample deletes an Example by ID +func (a appAdmin) DeleteExample(orgID string, appID string, id string) error { + return a.app.storage.DeleteExample(orgID, appID, id) +} + +func (a appAdmin) GetConfig(id string, claims *tokenauth.Claims) (*model.Config, error) { + config, err := a.app.storage.FindConfigByID(id) + if err != nil { + return nil, errors.WrapErrorAction(logutils.ActionFind, model.TypeConfig, nil, err) + } + if config == nil { + return nil, errors.ErrorData(logutils.StatusMissing, model.TypeConfig, &logutils.FieldArgs{"id": id}) + } + + err = claims.CanAccess(config.AppID, config.OrgID, config.System) + if err != nil { + return nil, errors.WrapErrorAction(logutils.ActionValidate, "config access", nil, err) + } + + return config, nil +} + +func (a appAdmin) GetConfigs(configType *string, claims *tokenauth.Claims) ([]model.Config, error) { + configs, err := a.app.storage.FindConfigs(configType) + if err != nil { + return nil, errors.WrapErrorAction(logutils.ActionFind, model.TypeConfig, nil, err) + } + + allowedConfigs := make([]model.Config, 0) + for _, config := range configs { + if err := claims.CanAccess(config.AppID, config.OrgID, config.System); err == nil { + allowedConfigs = append(allowedConfigs, config) + } + } + return allowedConfigs, nil +} + +func (a appAdmin) CreateConfig(config model.Config, claims *tokenauth.Claims) (*model.Config, error) { + // must be a system config if applying to all orgs + if config.OrgID == authutils.AllOrgs && !config.System { + return nil, errors.ErrorData(logutils.StatusInvalid, "config system status", &logutils.FieldArgs{"config.org_id": authutils.AllOrgs}) + } + + err := claims.CanAccess(config.AppID, config.OrgID, config.System) + if err != nil { + return nil, errors.WrapErrorAction(logutils.ActionValidate, "config access", nil, err) + } + + config.ID = uuid.NewString() + config.DateCreated = time.Now().UTC() + err = a.app.storage.InsertConfig(config) + if err != nil { + return nil, errors.WrapErrorAction(logutils.ActionInsert, model.TypeConfig, nil, err) + } + return &config, nil +} + +func (a appAdmin) UpdateConfig(config model.Config, claims *tokenauth.Claims) error { + // must be a system config if applying to all orgs + if config.OrgID == authutils.AllOrgs && !config.System { + return errors.ErrorData(logutils.StatusInvalid, "config system status", &logutils.FieldArgs{"config.org_id": authutils.AllOrgs}) + } + + oldConfig, err := a.app.storage.FindConfig(config.Type, config.AppID, config.OrgID) + if err != nil { + return errors.WrapErrorAction(logutils.ActionFind, model.TypeConfig, nil, err) + } + if oldConfig == nil { + return errors.ErrorData(logutils.StatusMissing, model.TypeConfig, &logutils.FieldArgs{"type": config.Type, "app_id": config.AppID, "org_id": config.OrgID}) + } + + // cannot update a system config if not a system admin + if !claims.System && oldConfig.System { + return errors.ErrorData(logutils.StatusInvalid, "system claim", nil) + } + err = claims.CanAccess(config.AppID, config.OrgID, config.System) + if err != nil { + return errors.WrapErrorAction(logutils.ActionValidate, "config access", nil, err) + } + + now := time.Now().UTC() + config.ID = oldConfig.ID + config.DateUpdated = &now + + err = a.app.storage.UpdateConfig(config) + if err != nil { + return errors.WrapErrorAction(logutils.ActionUpdate, model.TypeConfig, nil, err) + } + return nil +} + +func (a appAdmin) DeleteConfig(id string, claims *tokenauth.Claims) error { + config, err := a.app.storage.FindConfigByID(id) + if err != nil { + return errors.WrapErrorAction(logutils.ActionFind, model.TypeConfig, nil, err) + } + if config == nil { + return errors.ErrorData(logutils.StatusMissing, model.TypeConfig, &logutils.FieldArgs{"id": id}) + } + + err = claims.CanAccess(config.AppID, config.OrgID, config.System) + if err != nil { + return errors.WrapErrorAction(logutils.ActionValidate, "config access", nil, err) + } + + err = a.app.storage.DeleteConfig(id) + if err != nil { + return errors.WrapErrorAction(logutils.ActionDelete, model.TypeConfig, nil, err) + } + return nil +} + +// newAppAdmin creates new appAdmin +func newAppAdmin(app *Application) appAdmin { + return appAdmin{app: app} +} diff --git a/core/app_bbs.go b/core/app_bbs.go new file mode 100644 index 00000000..c693c2ab --- /dev/null +++ b/core/app_bbs.go @@ -0,0 +1,98 @@ +// Copyright 2022 Board of Trustees of the University of Illinois. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package core + +import ( + "application/core/model" + "strconv" + "time" +) + +// appBBs contains BB implementations +type appBBs struct { + app *Application +} + +// GetExample gets an Example by ID +func (a appBBs) GetExample(orgID string, appID string, id string) (*model.Example, error) { + return a.app.shared.getExample(orgID, appID, id) +} + +func (a appBBs) GetAppointmentUnits(providerid int, uin string, accesstoken string) (*[]model.AppointmentUnit, error) { + conf, _ := a.app.GetEnvConfigs() + apptAdapter := a.app.AppointmentAdapters[strconv.Itoa(providerid)] + retData, err := apptAdapter.GetUnits(uin, accesstoken, providerid, conf) + if err != nil { + return nil, err + } + return retData, nil +} + +func (a appBBs) GetPeople(uin string, unitid int, providerid int, accesstoken string) (*[]model.AppointmentPerson, error) { + conf, _ := a.app.GetEnvConfigs() + apptAdapter := a.app.AppointmentAdapters[strconv.Itoa(providerid)] + retData, err := apptAdapter.GetPeople(uin, unitid, providerid, accesstoken, conf) + if err != nil { + return nil, err + } + return retData, nil + +} + +func (a appBBs) GetAppointmentOptions(uin string, unitid int, peopleid int, providerid int, startdate time.Time, enddate time.Time, accesstoken string) (*model.AppointmentOptions, error) { + conf, _ := a.app.GetEnvConfigs() + apptAdapter := a.app.AppointmentAdapters[strconv.Itoa(providerid)] + retData, err := apptAdapter.GetTimeSlots(uin, unitid, peopleid, providerid, startdate, enddate, accesstoken, conf) + if err != nil { + return nil, err + } + return retData, nil +} + +func (a appBBs) CreateAppointment(appt *model.AppointmentPost, accessToken string) (*model.BuildingBlockAppointment, error) { + conf, _ := a.app.GetEnvConfigs() + apptAdapter := a.app.AppointmentAdapters[appt.ProviderID] + ret, err := apptAdapter.CreateAppointment(appt, accessToken, conf) + if err != nil { + return nil, err + } + return ret, nil +} + +func (a appBBs) UpdateAppointment(appt *model.AppointmentPost, accessToken string) (*model.BuildingBlockAppointment, error) { + conf, _ := a.app.GetEnvConfigs() + apptAdapter := a.app.AppointmentAdapters[appt.ProviderID] + ret, err := apptAdapter.UpdateAppointment(appt, accessToken, conf) + if err != nil { + return nil, err + } + return ret, nil +} + +func (a appBBs) DeleteAppointment(uin string, providerid int, sourceid string, accesstoken string) (string, error) { + conf, _ := a.app.GetEnvConfigs() + apptAdapter := a.app.AppointmentAdapters[strconv.Itoa(providerid)] + ret, err := apptAdapter.DeleteAppointment(uin, sourceid, accesstoken, conf) + if err != nil { + return "", err + } + return ret, nil +} + +// newAppBBs creates new appBBs +func newAppBBs(app *Application) appBBs { + appBB := appBBs{app: app} + return appBB +} diff --git a/core/app_client.go b/core/app_client.go new file mode 100644 index 00000000..734cc1e4 --- /dev/null +++ b/core/app_client.go @@ -0,0 +1,163 @@ +// Copyright 2022 Board of Trustees of the University of Illinois. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package core + +import ( + "application/core/interfaces" + "application/core/model" + "application/driven/uiucadapters" + "encoding/json" + "os" +) + +// appClient contains client implementations +type appClient struct { + app *Application + Courseadapter interfaces.Courses + LocationAdapter interfaces.WayFinding + LaundryAdapter interfaces.LaundryService + ContactAdapter interfaces.Contact +} + +// GetExample gets an Example by ID +func (a appClient) GetExample(orgID string, appID string, id string) (*model.Example, error) { + return a.app.shared.getExample(orgID, appID, id) +} + +func (a appClient) ListLaundryRooms() (*model.Organization, error) { + conf, _ := a.app.GetEnvConfigs() + retData, err := a.LaundryAdapter.ListRooms(conf) + if err != nil { + return nil, err + } + return retData, nil +} + +func (a appClient) GetLaundryRoom(roomid string) (*model.RoomDetail, error) { + conf, _ := a.app.GetEnvConfigs() + retData, err := a.LaundryAdapter.GetLaundryRoom(roomid, conf) + if err != nil { + return nil, err + } + return retData, nil +} + +func (a appClient) InitServiceRequest(machineid string) (*model.MachineRequestDetail, error) { + conf, _ := a.app.GetEnvConfigs() + retData, err := a.LaundryAdapter.InitServiceRequest(machineid, conf) + if err != nil { + return nil, err + } + return retData, nil + +} + +func (a appClient) SubmitServiceRequest(machineID string, problemCode string, comments string, firstname string, lastname string, phone string, email string) (*model.ServiceRequestResult, error) { + conf, _ := a.app.GetEnvConfigs() + retData, err := a.LaundryAdapter.SubmitServiceRequest(machineID, problemCode, comments, firstname, lastname, phone, email, conf) + if err != nil { + return nil, err + } + return retData, nil +} + +func (a appClient) GetBuilding(bldgID string, adaOnly bool, latitude float64, longitude float64) (*model.Building, error) { + conf, _ := a.app.GetEnvConfigs() + retData, err := a.LocationAdapter.GetBuilding(bldgID, adaOnly, latitude, longitude, conf) + if err != nil { + return nil, err + } + return retData, nil + +} + +func (a appClient) GetEntrance(bldgID string, adaOnly bool, latitude float64, longitude float64) (*model.Entrance, error) { + conf, _ := a.app.GetEnvConfigs() + retData, err := a.LocationAdapter.GetEntrance(bldgID, adaOnly, latitude, longitude, conf) + if err != nil { + return nil, err + } + return retData, nil + +} + +func (a appClient) GetBuildings() (*[]model.Building, error) { + conf, _ := a.app.GetEnvConfigs() + retData, err := a.LocationAdapter.GetBuildings(conf) + if err != nil { + return nil, err + } + return retData, nil + +} + +func (a appClient) GetContactInfo(uin string, accessToken string, mode string) (*model.Person, int, error) { + conf, _ := a.app.GetEnvConfigs() + retData, statuscode, err := a.ContactAdapter.GetContactInformation(uin, accessToken, mode, conf) + if err != nil { + return nil, statuscode, err + } + return retData, statuscode, nil +} + +func (a appClient) GetGiesCourses(uin string, accessToken string) (*[]model.GiesCourse, int, error) { + conf, _ := a.app.GetEnvConfigs() + retData, statuscode, err := a.Courseadapter.GetGiesCourses(uin, accessToken, conf) + if err != nil { + return nil, statuscode, err + } + return retData, statuscode, nil + +} + +func (a appClient) GetStudentCourses(uin string, termid string, accessToken string) (*[]model.Course, int, error) { + conf, _ := a.app.GetEnvConfigs() + retData, statuscode, err := a.Courseadapter.GetStudentCourses(uin, termid, accessToken, conf) + if err != nil { + return nil, statuscode, err + } + return retData, statuscode, nil +} + +func (a appClient) GetTermSessions() (*[4]model.TermSession, error) { + + retData, err := a.Courseadapter.GetTermSessions() + if err != nil { + return nil, err + } + return retData, nil +} + +// newAppClient creates new appClient +func newAppClient(app *Application) appClient { + + client := appClient{app: app} + //read assets + file, _ := os.ReadFile("./assets/assets.json") + assets := model.Asset{} + _ = json.Unmarshal([]byte(file), &assets) + laundryAssets := make(map[string]model.LaundryDetails) + + for i := 0; i < len(assets.Laundry.Assets); i++ { + laundryAsset := assets.Laundry.Assets[i] + laundryAssets[laundryAsset.LocationID] = laundryAsset.Details + } + + client.ContactAdapter = uiucadapters.NewUIUCContactAdapter() + client.LaundryAdapter = uiucadapters.NewCSCLaundryAdapter(laundryAssets) + client.Courseadapter = uiucadapters.NewCourseAdapter() + client.LocationAdapter = uiucadapters.NewUIUCWayFinding() + return client +} diff --git a/core/app_default.go b/core/app_default.go new file mode 100644 index 00000000..a20598c0 --- /dev/null +++ b/core/app_default.go @@ -0,0 +1,30 @@ +// Copyright 2022 Board of Trustees of the University of Illinois. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package core + +// appDefault contains default implementations +type appDefault struct { + app *Application +} + +// GetVersion gets the current version of this service +func (a appDefault) GetVersion() string { + return a.app.version +} + +// newAppDefault creates new appDefault +func newAppDefault(app *Application) appDefault { + return appDefault{app: app} +} diff --git a/driver/web/rest/common.go b/core/app_shared.go similarity index 57% rename from driver/web/rest/common.go rename to core/app_shared.go index 021bbdc8..d1d03a98 100644 --- a/driver/web/rest/common.go +++ b/core/app_shared.go @@ -12,29 +12,23 @@ // See the License for the specific language governing permissions and // limitations under the License. -package rest +package core import ( - "net/http" - "strconv" + "application/core/model" ) -func getStringQueryParam(r *http.Request, paramName string) *string { - params, ok := r.URL.Query()[paramName] - if ok && len(params[0]) > 0 { - value := params[0] - return &value - } - return nil +// appShared contains shared implementations +type appShared struct { + app *Application } -func getInt64QueryParam(r *http.Request, paramName string) *int64 { - params, ok := r.URL.Query()[paramName] - if ok && len(params[0]) > 0 { - val, err := strconv.ParseInt(params[0], 0, 64) - if err == nil { - return &val - } - } - return nil +// getExample gets an Example by ID +func (a appShared) getExample(orgID string, appID string, id string) (*model.Example, error) { + return a.app.storage.FindExample(orgID, appID, id) +} + +// newAppShared creates new appShared +func newAppShared(app *Application) appShared { + return appShared{app: app} } diff --git a/core/app_system.go b/core/app_system.go new file mode 100644 index 00000000..ec320961 --- /dev/null +++ b/core/app_system.go @@ -0,0 +1,34 @@ +// Copyright 2022 Board of Trustees of the University of Illinois. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package core + +import ( + "application/core/model" +) + +// appSystem contains system implementations +type appSystem struct { + app *Application +} + +// GetExample gets an Example by ID +func (a appSystem) GetExample(orgID string, appID string, id string) (*model.Example, error) { + return a.app.shared.getExample(orgID, appID, id) +} + +// newAppSystem creates new appSystem +func newAppSystem(app *Application) appSystem { + return appSystem{app: app} +} diff --git a/core/app_tps.go b/core/app_tps.go new file mode 100644 index 00000000..01f00469 --- /dev/null +++ b/core/app_tps.go @@ -0,0 +1,34 @@ +// Copyright 2022 Board of Trustees of the University of Illinois. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package core + +import ( + "application/core/model" +) + +// appTPS contains BB implementations +type appTPS struct { + app *Application +} + +// GetExample gets an Example by ID +func (a appTPS) GetExample(orgID string, appID string, id string) (*model.Example, error) { + return a.app.shared.getExample(orgID, appID, id) +} + +// newAppTPS creates new appTPS +func newAppTPS(app *Application) appTPS { + return appTPS{app: app} +} diff --git a/core/application.go b/core/application.go index 422ba3fd..aea4e8bc 100644 --- a/core/application.go +++ b/core/application.go @@ -14,33 +14,80 @@ package core +import ( + "application/core/interfaces" + "application/core/model" + + "github.com/rokwire/core-auth-library-go/v3/authutils" + "github.com/rokwire/logging-library-go/v2/errors" + "github.com/rokwire/logging-library-go/v2/logs" + "github.com/rokwire/logging-library-go/v2/logutils" +) + +type storageListener struct { + app *Application + model.DefaultStorageListener +} + +// OnExampleUpdated notifies that the example collection has changed +func (s *storageListener) OnExampleUpdated() { + s.app.logger.Infof("OnExampleUpdated") + + // TODO: Implement listener +} + // Application represents the core application code based on hexagonal architecture type Application struct { version string build string - Services Services // expose to the drivers adapters + Default interfaces.Default // expose to the drivers adapters + Client interfaces.Client // expose to the drivers adapters + Admin interfaces.Admin // expose to the drivers adapters + BBs interfaces.BBs // expose to the drivers adapters + TPS interfaces.TPS // expose to the drivers adapters + System interfaces.System // expose to the drivers adapters + shared Shared + + AppointmentAdapters map[string]interfaces.Appointments //expose to the different vendor specific appointment adapters + + logger *logs.Logger - storage Storage - laundry Laundry - locationAdapter BuildingLocation - contactInfoAdapter ContactInformation - giesCourseAdapter GiesCourses - studentcourseAdapter StudentCourses - termsessionAdapter TermSessions + storage interfaces.Storage } // Start starts the core part of the application -func (app *Application) Start() { +func (a *Application) Start() { + //set storage listener + storageListener := storageListener{app: a} + a.storage.RegisterStorageListener(&storageListener) } -// NewApplication creates new Application -func NewApplication(version string, build string, storage Storage, laundry Laundry, bldgloc BuildingLocation, contacts ContactInformation, gcAdapter GiesCourses, scAdapter StudentCourses, tsAdapter TermSessions) *Application { +// GetEnvConfigs retrieves the cached database env configs +func (a *Application) GetEnvConfigs() (*model.EnvConfigData, error) { + // Load env configs from database + config, err := a.storage.FindConfig(model.ConfigTypeEnv, authutils.AllApps, authutils.AllOrgs) + if err != nil { + return nil, errors.WrapErrorAction(logutils.ActionGet, model.TypeConfig, nil, err) + } + if config == nil { + return nil, errors.ErrorData(logutils.StatusMissing, model.TypeConfig, &logutils.FieldArgs{"type": model.ConfigTypeEnv, "app_id": authutils.AllApps, "org_id": authutils.AllOrgs}) + } + return model.GetConfigData[model.EnvConfigData](*config) +} - application := Application{version: version, build: build, storage: storage, laundry: laundry, locationAdapter: bldgloc, contactInfoAdapter: contacts, giesCourseAdapter: gcAdapter, studentcourseAdapter: scAdapter, termsessionAdapter: tsAdapter} +// NewApplication creates new Application +func NewApplication(version string, build string, storage interfaces.Storage, appntAdapters map[string]interfaces.Appointments, logger *logs.Logger) *Application { + application := Application{version: version, build: build, storage: storage, logger: logger, AppointmentAdapters: appntAdapters} //add the drivers ports/interfaces - application.Services = &servicesImpl{app: &application} + application.Default = newAppDefault(&application) + application.Client = newAppClient(&application) + application.Admin = newAppAdmin(&application) + application.BBs = newAppBBs(&application) + application.TPS = newAppTPS(&application) + application.System = newAppSystem(&application) + application.shared = newAppShared(&application) return &application } diff --git a/core/interfaces.go b/core/interfaces.go index 244d79e4..2e538dba 100644 --- a/core/interfaces.go +++ b/core/interfaces.go @@ -14,130 +14,9 @@ package core -import ( - model "apigateway/core/model" -) +import "application/core/model" -// Services exposes APIs for the driver adapters -type Services interface { - GetVersion() string - StoreRecord(name string) error - ListLaundryRooms() (*model.Organization, error) - GetLaundryRoom(roomid string) (*model.RoomDetail, error) - InitServiceRequest(machineid string) (*model.MachineRequestDetail, error) - SubmitServiceRequest(machineID string, problemCode string, comments string, firstname string, lastname string, phone string, email string) (*model.ServiceRequestResult, error) - GetBuilding(bldgID string, adaOnly bool, latitude float64, longitude float64) (*model.Building, error) - GetEntrance(bldgID string, adaOnly bool, latitude float64, longitude float64) (*model.Entrance, error) - GetBuildings() (*[]model.Building, error) - GetContactInfo(uin string, accessToken string, mode string) (*model.Person, int, error) - GetGiesCourses(uin string, accessToken string) (*[]model.GiesCourse, int, error) - GetStudentCourses(uin string, termid string, accessToken string) (*[]model.Course, int, error) - GetTermSessions() (*[4]model.TermSession, error) -} - -type servicesImpl struct { - app *Application -} - -func (s *servicesImpl) GetVersion() string { - return s.app.getVersion() -} - -func (s *servicesImpl) StoreRecord(name string) error { - return s.app.storeRecord(name) -} - -func (s *servicesImpl) ListLaundryRooms() (*model.Organization, error) { - lr, err := s.app.listLaundryRooms() - return &lr, err -} - -func (s *servicesImpl) GetLaundryRoom(roomid string) (*model.RoomDetail, error) { - ap, err := s.app.listAppliances(roomid) - return &ap, err -} - -func (s *servicesImpl) InitServiceRequest(machineid string) (*model.MachineRequestDetail, error) { - sr, err := s.app.initServiceRequest(machineid) - return sr, err -} - -func (s *servicesImpl) SubmitServiceRequest(machineID string, problemCode string, comments string, firstname string, lastname string, phone string, email string) (*model.ServiceRequestResult, error) { - srr, err := s.app.submitServiceRequest(machineID, problemCode, comments, firstname, lastname, phone, email) - return srr, err -} - -func (s *servicesImpl) GetBuilding(bldgID string, adaOnly bool, latitude float64, longitude float64) (*model.Building, error) { - bldg, err := s.app.getBuilding(bldgID, adaOnly, latitude, longitude) - return &bldg, err -} - -func (s *servicesImpl) GetEntrance(bldgID string, adaOnly bool, latitude float64, longitude float64) (*model.Entrance, error) { - entrance, err := s.app.getEntrance(bldgID, adaOnly, latitude, longitude) - return entrance, err -} - -func (s *servicesImpl) GetBuildings() (*[]model.Building, error) { - buildings, err := s.app.getBuildings() - return buildings, err -} - -func (s *servicesImpl) GetContactInfo(uin string, accessToken string, mode string) (*model.Person, int, error) { - person, statusCode, err := s.app.getContactInfo(uin, accessToken, mode) - return person, statusCode, err -} - -func (s *servicesImpl) GetGiesCourses(uin string, accessToken string) (*[]model.GiesCourse, int, error) { - courseList, statusCode, err := s.app.getGiesCourses(uin, accessToken) - return courseList, statusCode, err -} - -func (s *servicesImpl) GetStudentCourses(uin string, termid string, accessToken string) (*[]model.Course, int, error) { - courseList, statusCode, err := s.app.getStudentCourses(uin, termid, accessToken) - return courseList, statusCode, err -} - -func (s *servicesImpl) GetTermSessions() (*[4]model.TermSession, error) { - termList, err := s.app.getTermSessions() - return termList, err -} - -// Storage is used by core to storage data - DB storage adapter, file storage adapter etc -type Storage interface { - StoreRecord(name string) error -} - -// Laundry is used by core to request data from the laundry provider -type Laundry interface { - ListRooms() (*model.Organization, error) - GetLaundryRoom(roomid string) (*model.RoomDetail, error) - InitServiceRequest(machineID string) (*model.MachineRequestDetail, error) - SubmitServiceRequest(machineID string, problemCode string, comments string, firstname string, lastname string, phone string, email string) (*model.ServiceRequestResult, error) -} - -// BuildingLocation is used by core to request data from the building location/entrance provider -type BuildingLocation interface { - GetBuilding(bldgID string, adaAccessibleOnly bool, latitude float64, longitude float64) (*model.Building, error) - GetEntrance(bldgID string, adaAccessibleOnly bool, latitude float64, longitude float64) (*model.Entrance, error) - GetBuildings() (*[]model.Building, error) -} - -// ContactInformation is used by core to request data from the contact information provider -type ContactInformation interface { - GetContactInformation(uin string, accessToken string, mode string) (*model.Person, int, error) -} - -// GiesCourses is used by core to request data from teh geis course provider -type GiesCourses interface { - GetGiesCourses(uin string, accessToken string) (*[]model.GiesCourse, int, error) -} - -// StudentCourses is used by core to request data from the course data provider -type StudentCourses interface { - GetStudentCourses(uin string, termid string, accessToken string) (*[]model.Course, int, error) -} - -// TermSessions is used by core to request data from the term sessions data provider -type TermSessions interface { - GetTermSessions() (*[4]model.TermSession, error) +// Shared exposes shared APIs for other interface implementations +type Shared interface { + getExample(orgID string, appID string, id string) (*model.Example, error) } diff --git a/core/interfaces/core.go b/core/interfaces/core.go new file mode 100644 index 00000000..651dd25f --- /dev/null +++ b/core/interfaces/core.go @@ -0,0 +1,79 @@ +// Copyright 2022 Board of Trustees of the University of Illinois. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package interfaces + +import ( + "application/core/model" + "time" + + "github.com/rokwire/core-auth-library-go/v3/tokenauth" +) + +// Default exposes client APIs for the driver adapters +type Default interface { + GetVersion() string +} + +// Client exposes client APIs for the driver adapters +type Client interface { + GetExample(orgID string, appID string, id string) (*model.Example, error) + ListLaundryRooms() (*model.Organization, error) + GetLaundryRoom(roomid string) (*model.RoomDetail, error) + InitServiceRequest(machineid string) (*model.MachineRequestDetail, error) + SubmitServiceRequest(machineID string, problemCode string, comments string, firstname string, lastname string, phone string, email string) (*model.ServiceRequestResult, error) + GetBuilding(bldgID string, adaOnly bool, latitude float64, longitude float64) (*model.Building, error) + GetEntrance(bldgID string, adaOnly bool, latitude float64, longitude float64) (*model.Entrance, error) + GetBuildings() (*[]model.Building, error) + GetContactInfo(uin string, accessToken string, mode string) (*model.Person, int, error) + GetGiesCourses(uin string, accessToken string) (*[]model.GiesCourse, int, error) + GetStudentCourses(uin string, termid string, accessToken string) (*[]model.Course, int, error) + GetTermSessions() (*[4]model.TermSession, error) +} + +// Admin exposes administrative APIs for the driver adapters +type Admin interface { + GetExample(orgID string, appID string, id string) (*model.Example, error) + CreateExample(example model.Example) (*model.Example, error) + UpdateExample(example model.Example) error + AppendExample(example model.Example) (*model.Example, error) + DeleteExample(orgID string, appID string, id string) error + + GetConfig(id string, claims *tokenauth.Claims) (*model.Config, error) + GetConfigs(configType *string, claims *tokenauth.Claims) ([]model.Config, error) + CreateConfig(config model.Config, claims *tokenauth.Claims) (*model.Config, error) + UpdateConfig(config model.Config, claims *tokenauth.Claims) error + DeleteConfig(id string, claims *tokenauth.Claims) error +} + +// BBs exposes Building Block APIs for the driver adapters +type BBs interface { + GetExample(orgID string, appID string, id string) (*model.Example, error) + GetAppointmentUnits(providerid int, uin string, accesstoken string) (*[]model.AppointmentUnit, error) + GetPeople(uin string, unitID int, providerid int, accesstoken string) (*[]model.AppointmentPerson, error) + GetAppointmentOptions(uin string, unitid int, peopleid int, providerid int, startdate time.Time, enddate time.Time, accesstoken string) (*model.AppointmentOptions, error) + CreateAppointment(appt *model.AppointmentPost, accessToken string) (*model.BuildingBlockAppointment, error) + DeleteAppointment(uin string, providerid int, sourceid string, accesstoken string) (string, error) + UpdateAppointment(appt *model.AppointmentPost, accessToken string) (*model.BuildingBlockAppointment, error) +} + +// TPS exposes third-party service APIs for the driver adapters +type TPS interface { + GetExample(orgID string, appID string, id string) (*model.Example, error) +} + +// System exposes system administrative APIs for the driver adapters +type System interface { + GetExample(orgID string, appID string, id string) (*model.Example, error) +} diff --git a/core/interfaces/driven.go b/core/interfaces/driven.go new file mode 100644 index 00000000..90d1aba5 --- /dev/null +++ b/core/interfaces/driven.go @@ -0,0 +1,81 @@ +// Copyright 2022 Board of Trustees of the University of Illinois. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package interfaces + +import ( + "application/core/model" + "time" +) + +// Storage is used by core to storage data - DB storage adapter, file storage adapter etc +type Storage interface { + RegisterStorageListener(listener StorageListener) + PerformTransaction(func(storage Storage) error) error + + FindConfig(configType string, appID string, orgID string) (*model.Config, error) + FindConfigByID(id string) (*model.Config, error) + FindConfigs(configType *string) ([]model.Config, error) + InsertConfig(config model.Config) error + UpdateConfig(config model.Config) error + DeleteConfig(id string) error + + FindExample(orgID string, appID string, id string) (*model.Example, error) + InsertExample(example model.Example) error + UpdateExample(example model.Example) error + DeleteExample(orgID string, appID string, id string) error +} + +// StorageListener represents storage listener +type StorageListener interface { + OnConfigsUpdated() + OnExamplesUpdated() +} + +// Contact represents the adapter needed to pull campus specific contact information +type Contact interface { + GetContactInformation(uin string, accessToken string, mode string, conf *model.EnvConfigData) (*model.Person, int, error) +} + +// Courses represents the Courses adapter needed to pull campus specific course information +type Courses interface { + GetStudentCourses(uin string, termid string, accessToken string, conf *model.EnvConfigData) (*[]model.Course, int, error) + GetTermSessions() (*[4]model.TermSession, error) + GetGiesCourses(uin string, accessToken string, conf *model.EnvConfigData) (*[]model.GiesCourse, int, error) +} + +// LaundryService represents the adapter needed to interact with vendor specific laundry providers +type LaundryService interface { + ListRooms(conf *model.EnvConfigData) (*model.Organization, error) + GetLaundryRoom(roomid string, conf *model.EnvConfigData) (*model.RoomDetail, error) + InitServiceRequest(machineID string, conf *model.EnvConfigData) (*model.MachineRequestDetail, error) + SubmitServiceRequest(machineid string, problemCode string, comments string, firstName string, lastName string, phone string, email string, conf *model.EnvConfigData) (*model.ServiceRequestResult, error) +} + +// WayFinding represents the adapter needed to interact with vendor specific building locations +type WayFinding interface { + GetEntrance(bldgID string, adaAccessibleOnly bool, latitude float64, longitude float64, conf *model.EnvConfigData) (*model.Entrance, error) + GetBuildings(conf *model.EnvConfigData) (*[]model.Building, error) + GetBuilding(bldgID string, adaAccessibleOnly bool, latitude float64, longitude float64, conf *model.EnvConfigData) (*model.Building, error) +} + +// Appointments represents the adapter needed to interace with various appoinment data providers +type Appointments interface { + GetUnits(uin string, accesstoken string, providerid int, conf *model.EnvConfigData) (*[]model.AppointmentUnit, error) + GetPeople(uin string, unitID int, providerid int, accesstoken string, conf *model.EnvConfigData) (*[]model.AppointmentPerson, error) + GetTimeSlots(uin string, unitid int, advisorid int, providerid int, startdate time.Time, enddate time.Time, accesstoken string, conf *model.EnvConfigData) (*model.AppointmentOptions, error) + CreateAppointment(appt *model.AppointmentPost, accesstoken string, conf *model.EnvConfigData) (*model.BuildingBlockAppointment, error) + DeleteAppointment(uin string, sourceid string, accesstoken string, conf *model.EnvConfigData) (string, error) + UpdateAppointment(appt *model.AppointmentPost, accesstoken string, conf *model.EnvConfigData) (*model.BuildingBlockAppointment, error) +} diff --git a/core/model/appointments.go b/core/model/appointments.go new file mode 100644 index 00000000..24da5284 --- /dev/null +++ b/core/model/appointments.go @@ -0,0 +1,113 @@ +// Copyright 2022 Board of Trustees of the University of Illinois. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package model + +import ( + "github.com/rokwire/logging-library-go/v2/logutils" +) + +const ( + //TypeAppointments type + TypeAppointments logutils.MessageDataType = "appointments" +) + +// Question represents a question asked as part of an appointmentr request +type Question struct { + ID string `json:"id" bson:"id"` + ProviderID int `json:"provider_id" bson:"provider_id"` + Required bool `json:"required" bson:"required"` + Type string `json:"type" bson:"type"` + SelectValues []string `json:"select_values" bson:"select_values"` + Question string `json:"question" bson:"question"` +} + +// TimeSlot represents an avaialable appontment timeslot +type TimeSlot struct { + ID int `json:"id" bson:"id"` + ProviderID int `json:"provider_id" bson:"provider_Id"` + UnitID int `json:"unit_id" bson:"unit_id"` + PersonID int `json:"person_id" bson:"person_id"` + StartTime string `json:"start_time" bson:"start_time"` + EndTime string `json:"end_time" bson:"end_time"` + Capacity int `json:"capacity" bson:"capacity"` + Filled int `json:"filled" bson:"filled"` + Details map[string]interface{} `json:"details" bson:"details"` +} + +// AppointmentOptions represents the available timeslots and questions for a unitid/advisorid calendar +type AppointmentOptions struct { + TimeSlots []TimeSlot `json:"time_slots" bson:"time_slots"` + Questions []Question `json:"questions" bson:"questions"` +} + +// AppointmentUnit represents units with availalbe appointment integrations +type AppointmentUnit struct { + ID int `json:"id" bson:"id"` + ProviderID int `json:"provider_id" bson:"provider_id"` + Name string `json:"name" bson:"name"` + Location string `json:"location" bson:"location"` + HoursOfOperation string `json:"hours_of_operation" bson:"hours_of_operation"` + Details string `json:"details" bson:"details"` + NextAvailable string `json:"next_available" bson:"next_available"` + ImageURL string `json:"image_url" bson:"image_url"` +} + +// AppointmentPerson represents a person who is accepting appointments +type AppointmentPerson struct { + ID string `json:"id" bson:"id"` + ProviderID int `json:"provider_id" bson:"provider_id"` + UnitID int `json:"unit_id" bson:"unit_id"` + NextAvailable string `json:"next_available" bson:"next_available"` + Name string `json:"name" bson:"name"` + Notes string `json:"notes" bson:"notes"` + ImageURL string `json:"image_url" bson:"image_url"` +} + +// AppointmentAnswer represents answer data sent from the appointments building block to the gateway building block +type AppointmentAnswer struct { + QuestionID string `json:"question_id" bson:"question_id"` + Values []string `json:"values" bson:"values"` +} + +// ExternalUserID represents external id fields passed into the building block as part of a post operation +type ExternalUserID struct { + UIN string `json:"uin" bson:"uin"` +} + +// AppointmentPost represents the data sent by the appointments building block to the gateway building block +type AppointmentPost struct { + ProviderID string `json:"provider_id" bson:"provider_id"` + UnitID string `json:"unit_id" bson:"unit_id"` + PersonID string `json:"person_id" bson:"person_id"` + Type string `json:"type" bson:"type"` + StartTime string `json:"start_time" bson:"start_time"` + EndTime string `json:"end_time" bson:"end_time"` + UserExternalIDs ExternalUserID `json:"user_external_ids" bson:"user_external_ids"` + SlotID string `json:"slot_id" bson:"slot_id"` + Answers []AppointmentAnswer `json:"answers" bson:"answers"` + SourceID string `json:"source_id" bson:"source_id"` +} + +// BuildingBlockAppointment returns the expected appointment structure to the appointments buildnig block +type BuildingBlockAppointment struct { + ProviderID string `json:"provider_id" bson:"provider_id"` + UnitID string `json:"unit_id" bson:"unit_id"` + PersonID string `json:"person_id" bson:"person_id"` + Type string `json:"type" bson:"type"` + StartTime string `json:"start_time" bson:"start_time"` + EndTime string `json:"end_time" bson:"end_time"` + UserExternalIDs ExternalUserID `json:"user_external_ids" bson:"user_external_ids"` + SourceID string `json:"source_id" bson:"source_id"` +} diff --git a/core/model/assets.go b/core/model/assets.go index 44a12d31..95176eb1 100644 --- a/core/model/assets.go +++ b/core/model/assets.go @@ -14,7 +14,16 @@ package model +import ( + "github.com/rokwire/logging-library-go/v2/logutils" +) + +const ( + //TypeAssets type + TypeAssets logutils.MessageDataType = "assets" +) + // Asset holds the asset information from assets.json type Asset struct { - Laundry LaundryAssets `json:"laundry"` + Laundry LaundryAssets `json:"laundry" bson:"laundry"` } diff --git a/core/model/buildings.go b/core/model/buildings.go index e279e7c8..120cdfe0 100644 --- a/core/model/buildings.go +++ b/core/model/buildings.go @@ -14,6 +14,15 @@ package model +import ( + "github.com/rokwire/logging-library-go/v2/logutils" +) + +const ( + //TypeBuilding type + TypeBuilding logutils.MessageDataType = "building" +) + // Entrance represents the information returned when the closest entrance of a building is requested type Entrance struct { ID string diff --git a/core/model/configs.go b/core/model/configs.go new file mode 100644 index 00000000..024f6c2d --- /dev/null +++ b/core/model/configs.go @@ -0,0 +1,75 @@ +// Copyright 2022 Board of Trustees of the University of Illinois. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package model + +import ( + "time" + + "github.com/rokwire/logging-library-go/v2/errors" + "github.com/rokwire/logging-library-go/v2/logutils" +) + +const ( + // TypeConfig configs type + TypeConfig logutils.MessageDataType = "config" + // TypeConfigData config data type + TypeConfigData logutils.MessageDataType = "config data" + // TypeEnvConfigData env configs type + TypeEnvConfigData logutils.MessageDataType = "env config data" + + // ConfigTypeEnv is the Config Type for EnvConfigData + ConfigTypeEnv string = "env" +) + +// Config contain generic configs +type Config struct { + ID string `json:"id" bson:"_id"` + Type string `json:"type" bson:"type"` + AppID string `json:"app_id" bson:"app_id"` + OrgID string `json:"org_id" bson:"org_id"` + System bool `json:"system" bson:"system"` + Data interface{} `json:"data" bson:"data"` + DateCreated time.Time `json:"date_created" bson:"date_created"` + DateUpdated *time.Time `json:"date_updated" bson:"date_updated"` +} + +// EnvConfigData contains environment configs for this service +type EnvConfigData struct { + ExampleEnv string `json:"example_env" bson:"example_env"` + CentralCampusURL string `json:"GATEWAY_CENTRALCAMPUS_ENDPOINT" bson:"GATEWAY_CENTRALCAMPUS_ENDPOINT"` + CentralCampusKey string `json:"GATEWAY_CENTRALCAMPUS_APIKEY" bson:"GATEWAY_CENTRALCAMPUS_APIKEY"` + GiesCourseURL string `json:"GATEWAY_GIESCOURSES_ENDPOINT" bson:"GATEWAY_GIESCOURSES_ENDPOINT"` + WayFindingURL string `json:"GATEWAY_WAYFINDING_APIURL" bson:"GATEWAY_WAYFINDING_APIURL"` + WayFindingKey string `json:"GATEWAY_WAYFINDING_APIKEY" bson:"GATEWAY_WAYFINDING_APIKEY"` + LaundryViewURL string `json:"GATEWAY_LAUNDRY_APIURL" bson:"GATEWAY_LAUNDRY_APIURL"` + LaundryViewKey string `json:"GATEWAY_LAUNDRY_APIKEY" bson:"GATEWAY_LAUNDRY_APIKEY"` + LaundryServiceKey string `json:"GATEWAY_LAUNDRYSERVICE_APIKEY" bson:"GATEWAY_LAUNDRYSERVICE_APIKEY"` + LaundyrServiceURL string `json:"GATEWAY_LAUNDRYSERVICE_API" bson:"GATEWAY_LAUNDRYSERVICE_API"` + LaundryServiceBasicAuth string `json:"GATEWAY_LAUNDRYSERVICE_BASICAUTH" bson:"GATEWAY_LAUNDRYSERVICE_BASICAUTH"` + EngAppointmentBaseURL string `json:"GATEWAY_APPOINTMENTS_ENGURL" bson:"GATEWAY_APPOINTMENTS_ENGURL"` +} + +// GetConfigData returns a pointer to the given config's Data as the given type T +func GetConfigData[T ConfigData](c Config) (*T, error) { + if data, ok := c.Data.(T); ok { + return &data, nil + } + return nil, errors.ErrorData(logutils.StatusInvalid, TypeConfigData, &logutils.FieldArgs{"type": c.Type}) +} + +// ConfigData represents any set of data that may be stored in a config +type ConfigData interface { + EnvConfigData | map[string]interface{} +} diff --git a/core/model/contactinformation.go b/core/model/contactinformation.go index 6eaf69ec..f9b212b3 100644 --- a/core/model/contactinformation.go +++ b/core/model/contactinformation.go @@ -14,15 +14,24 @@ package model +import ( + "github.com/rokwire/logging-library-go/v2/logutils" +) + +const ( + //TypeContactInfo type + TypeContactInfo logutils.MessageDataType = "contactInfo" +) + // Person represents the basic structure returned to the caller when contanct information is requested type Person struct { - UIN string `json:"uin"` - FirstName string `json:"firstName"` - LastName string `json:"lastName"` - PreferredName string `json:"preferred"` - MailingAddress Address `json:"mailingAddress"` - PermAddress Address `json:"permanentAddress"` - EmergencyContacts []EmergencyContact `json:"emergencycontacts"` + UIN string `json:"uin" bson:"uin"` + FirstName string `json:"firstName" bson:"firstName"` + LastName string `json:"lastName" bson:"lastName"` + PreferredName string `json:"preferred" bson:"preferred"` + MailingAddress Address `json:"mailingAddress" bson:"mailingAddress"` + PermAddress Address `json:"permanentAddress" bson:"permanentAddress"` + EmergencyContacts []EmergencyContact `json:"emergencycontacts" bson:"emergencycontacts"` } // AddressType is used as an enumeration for address types diff --git a/core/model/courses.go b/core/model/courses.go index f7cd889e..bd7367a1 100644 --- a/core/model/courses.go +++ b/core/model/courses.go @@ -17,36 +17,50 @@ package model +import ( + "github.com/rokwire/logging-library-go/v2/logutils" +) + +const ( + //TypeGiesCourse type + TypeGiesCourse logutils.MessageDataType = "giescourse" +) + +const ( + //TypeCourseData type + TypeCourseData logutils.MessageDataType = "coursedata" +) + // GiesCourse represents the elements of a course returned for Gies students type GiesCourse struct { - Term string `json:"term"` - Subject string `json:"subject"` - Number string `json:"number"` - Section string `json:"section"` - Title string `json:"title"` - Instructor string `json:"instructor"` + Term string `json:"term" bson:"term"` + Subject string `json:"subject" bson:"subject"` + Number string `json:"number" bson:"number"` + Section string `json:"section" bson:"section"` + Title string `json:"title" bson:"title"` + Instructor string `json:"instructor" bson:"instructor"` } // CourseSection represents the elements of a course section type CourseSection struct { - Days string `json:"days"` - MeetingDateOrRange string `json:"meeting_dates_or_range"` - Room string `json:"room"` - BuildingName string `json:"buildingname"` - BuildingID string `json:"buildingid"` - InstructionType string `json:"instructiontype"` - Instructor string `json:"instructor"` - StartTime string `json:"start_time"` - EndTime string `json:"endtime"` - Location Building `json:"building"` - CourseReferenceNumber string `json:"courseReferenceNumber"` + Days string `json:"days" bson:"days"` + MeetingDateOrRange string `json:"meeting_dates_or_range" bson:"meeting_dates_or_range"` + Room string `json:"room" bson:"room"` + BuildingName string `json:"buildingname" bson:"buildingname"` + BuildingID string `json:"buildingid" bson:"buildingid"` + InstructionType string `json:"instructiontype" bson:"instructiontype"` + Instructor string `json:"instructor" bson:"instructor"` + StartTime string `json:"start_time" bson:"start_time"` + EndTime string `json:"endtime" bson:"endtime"` + Location Building `json:"building" bson:"building"` + CourseReferenceNumber string `json:"courseReferenceNumber" bson:"courseReferenceNumber"` } // Course represents the full elements of a course type Course struct { - Title string `json:"coursetitle"` - ShortName string `json:"courseshortname"` - Number string `json:"coursenumber"` - InstructionMethod string `json:"instructionmethod"` - Section CourseSection `json:"coursesection"` + Title string `json:"coursetitle" bson:"coursetitle"` + ShortName string `json:"courseshortname" bson:"courseshortname"` + Number string `json:"coursenumber" bson:"coursenumber"` + InstructionMethod string `json:"instructionmethod" bson:"instructionmethod"` + Section CourseSection `json:"coursesection" bson:"coursesection"` } diff --git a/core/model/example.go b/core/model/example.go new file mode 100644 index 00000000..eb206f5c --- /dev/null +++ b/core/model/example.go @@ -0,0 +1,36 @@ +// Copyright 2022 Board of Trustees of the University of Illinois. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package model + +import ( + "time" + + "github.com/rokwire/logging-library-go/v2/logutils" +) + +const ( + //TypeExample example type + TypeExample logutils.MessageDataType = "example" +) + +// Example is a generic Example data type +type Example struct { + ID string `json:"id" bson:"_id"` + OrgID string `json:"org_id" bson:"org_id"` + AppID string `json:"app_id" bson:"app_id"` + Data string `json:"data" bson:"data"` + DateCreated time.Time `json:"date_created" bson:"date_created"` + DateUpdated *time.Time `json:"date_updated" bson:"date_updated"` +} diff --git a/core/model/laundry.go b/core/model/laundry.go index 4d254e06..43bfc640 100644 --- a/core/model/laundry.go +++ b/core/model/laundry.go @@ -14,6 +14,20 @@ package model +import ( + "github.com/rokwire/logging-library-go/v2/logutils" +) + +const ( + //TypeLaundryServiceSubmission type + TypeLaundryServiceSubmission logutils.MessageDataType = "laundryservicerequest" +) + +const ( + //TypeLaundryRooms type + TypeLaundryRooms logutils.MessageDataType = "laundryrooms" +) + // LaundryRoom represents the basic information returned as part of requesting and organization type LaundryRoom struct { ID int @@ -66,11 +80,11 @@ type ServiceRequestResult struct { // ServiceSubmission represents the data required to submit a service request for a laundry machine type ServiceSubmission struct { - MachineID *string `json:"machineid"` - ProblemCode *string `json:"problemcode"` - Comments *string `json:"comments"` - FirstName *string `json:"firstname"` - LastName *string `json:"lastname"` - Phone *string `json:"phone"` - Email *string `json:"email"` + MachineID *string `json:"machineid" bson:"machineid"` + ProblemCode *string `json:"problemcode" bson:"problemcode"` + Comments *string `json:"comments" bson:"comments"` + FirstName *string `json:"firstname" bson:"firstname"` + LastName *string `json:"lastname" bson:"lastname"` + Phone *string `json:"phone" bson:"phone"` + Email *string `json:"email" bson:"email"` } diff --git a/core/model/laundryassets.go b/core/model/laundryassets.go index a1dce711..4d8e9578 100644 --- a/core/model/laundryassets.go +++ b/core/model/laundryassets.go @@ -14,20 +14,29 @@ package model +import ( + "github.com/rokwire/logging-library-go/v2/logutils" +) + +const ( + //TypeLaundryAsset type + TypeLaundryAsset logutils.MessageDataType = "laundryasset" +) + // LaundryAssets represents the laundry elements of assets.json type LaundryAssets struct { - Assets []LaundryAsset `json:"locations"` + Assets []LaundryAsset `json:"locations" bson:"locations"` } // LaundryAsset represents a single laundry room asset type LaundryAsset struct { - LocationID string `json:"laundry_location"` - Details LaundryDetails `json:"location_details"` + LocationID string `json:"laundry_location" bson:"laundry_location"` + Details LaundryDetails `json:"location_details" bson:"laundry_details"` } // LaundryDetails represents the location details of a single laundry room asset type LaundryDetails struct { - Latitude float32 `json:"latitude"` - Longitude float32 `json:"longitude"` - Floor int `json:"floor"` + Latitude float32 `json:"latitude" bson:"lattitude"` + Longitude float32 `json:"longitude" bson:"longitude"` + Floor int `json:"floor" bson:"floor"` } diff --git a/core/model/listener.go b/core/model/listener.go new file mode 100644 index 00000000..d3da2a62 --- /dev/null +++ b/core/model/listener.go @@ -0,0 +1,24 @@ +// Copyright 2022 Board of Trustees of the University of Illinois. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package model + +// DefaultStorageListener default storage listener implementation +type DefaultStorageListener struct{} + +// OnConfigsUpdated notifies that the configs collection has been updated +func (d *DefaultStorageListener) OnConfigsUpdated() {} + +// OnExamplesUpdated notifies that the examples collection has been updated +func (d *DefaultStorageListener) OnExamplesUpdated() {} diff --git a/core/model/termsessions.go b/core/model/termsessions.go index dd13064f..12ca304a 100644 --- a/core/model/termsessions.go +++ b/core/model/termsessions.go @@ -17,9 +17,18 @@ package model +import ( + "github.com/rokwire/logging-library-go/v2/logutils" +) + +const ( + //TypeTermSession type + TypeTermSession logutils.MessageDataType = "termsessions" +) + // TermSession represents the elements of a term session type TermSession struct { - Term string `json:"term"` - TermID string `json:"termid"` - CurrentTerm bool `json:"is_current"` + Term string `json:"term" bson:"term"` + TermID string `json:"termid" bson:"termid"` + CurrentTerm bool `json:"is_current" bson:"is_current"` } diff --git a/core/model/uiuc/buildingdata.go b/core/model/uiuc/buildingdata.go new file mode 100644 index 00000000..38a9bd3b --- /dev/null +++ b/core/model/uiuc/buildingdata.go @@ -0,0 +1,94 @@ +// Copyright 2022 Board of Trustees of the University of Illinois. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package uiuc + +import ( + model "application/core/model" +) + +// CampusEntrance representes a campus specific building entrance +type CampusEntrance struct { + UUID string `json:"uuid"` + Name string `json:"descriptive_name"` + ADACompliant bool `json:"is_ada_compliant"` + Available bool `json:"is_available_for_use"` + ImageURL string `json:"image"` + Latitude float32 `json:"latitude"` + Longitude float32 `json:"longitude"` +} + +// CampusBuilding represents a campus specific building +type CampusBuilding struct { + UUID string `json:"uuid"` + Name string `json:"name"` + Number string `json:"number"` + FullAddress string `json:"location"` + Address1 string `json:"address_1"` + Address2 string `json:"address_2"` + City string `json:"city"` + State string `json:"state"` + ZipCode string `json:"zipcode"` + ImageURL string `json:"image"` + MailCode string `json:"mailcode"` + Entrances []CampusEntrance `json:"entrances"` + Latitude float32 `json:"building_centroid_latitude"` + Longitude float32 `json:"building_centroid_longitude"` +} + +// ServerResponse represents a UIUC specific server response +type ServerResponse struct { + Status string `json:"status"` + HTTPStatusCode int `json:"http_return"` + CollectionType string `json:"collection"` + Count int `json:"count"` + ErrorList string `json:"errors"` + ErrorMessage string `json:"error_text"` +} + +// ServerLocationData respresnts a UIUC specific data structure for building location data +type ServerLocationData struct { + Response ServerResponse `json:"response"` + Buildings []CampusBuilding `json:"results"` +} + +// NewBuilding creates a wayfinding.Building instance from a campusBuilding, +// including all active entrances for the building +func NewBuilding(bldg CampusBuilding) *model.Building { + newBldg := model.Building{ID: bldg.UUID, Name: bldg.Name, ImageURL: bldg.ImageURL, Address1: bldg.Address1, Address2: bldg.Address2, FullAddress: bldg.FullAddress, City: bldg.City, ZipCode: bldg.ZipCode, State: bldg.State, Latitude: bldg.Latitude, Longitude: bldg.Longitude} + newBldg.Entrances = make([]model.Entrance, 0) + for _, n := range bldg.Entrances { + if n.Available { + newBldg.Entrances = append(newBldg.Entrances, *NewEntrance(n)) + } + } + return &newBldg +} + +// NewBuildingList returns a list of wayfinding buildings created frmo a list of campus building objects. +func NewBuildingList(bldgList *[]CampusBuilding) *[]model.Building { + retList := make([]model.Building, len(*bldgList)) + for i := 0; i < len(*bldgList); i++ { + cmpsBldg := (*bldgList)[i] + crntBldng := NewBuilding(cmpsBldg) + retList[i] = *crntBldng + } + return &retList +} + +// NewEntrance creates a wayfinding.Entrance instance from a campusEntrance object +func NewEntrance(ent CampusEntrance) *model.Entrance { + newEnt := model.Entrance{ID: ent.UUID, Name: ent.Name, ADACompliant: ent.ADACompliant, Available: ent.Available, ImageURL: ent.ImageURL, Latitude: ent.Latitude, Longitude: ent.Longitude} + return &newEnt +} diff --git a/driven/contactinfo/uiuc_contactinfo.go b/core/model/uiuc/contactinfo.go similarity index 52% rename from driven/contactinfo/uiuc_contactinfo.go rename to core/model/uiuc/contactinfo.go index bd285502..db927815 100644 --- a/driven/contactinfo/uiuc_contactinfo.go +++ b/core/model/uiuc/contactinfo.go @@ -12,29 +12,27 @@ // See the License for the specific language governing permissions and // limitations under the License. -package contactinfo +package uiuc import ( - model "apigateway/core/model" - "encoding/json" - "errors" - "fmt" - "io/ioutil" - "net/http" + model "application/core/model" ) -type simpleType struct { +// SimpleType contains a common data structure used in some properties of the campus data +type SimpleType struct { Code string `json:"code"` Description string `json:"description"` } -type campusUserData struct { +// CampusUserData represents the full data coming back from campus +type CampusUserData struct { Object string `json:"object"` Version string `json:"version"` - People []campusPerson `json:"list"` + People []CampusPerson `json:"list"` } -type name struct { +// Name represents the fields campus uses to represent a person's name +type Name struct { Pidm int `json:"pidm"` Uin string `json:"uin"` LastName string `json:"lastName"` @@ -42,28 +40,30 @@ type name struct { NameType string `json:"type"` } -type address struct { +// Address represents the campus definition of a person's address +type Address struct { GUID int `json:"guid"` Pidm int `json:"pidm"` FromDate string `json:"fromDate"` ActivityDate string `json:"activityDate"` - Type simpleType `json:"type"` + Type SimpleType `json:"type"` SequenceNum int `json:"sequenceNum"` StreetLine1 string `json:"streetLine1"` City string `json:"city"` - State simpleType `json:"state"` + State SimpleType `json:"state"` ZipCode string `json:"zipCode"` - County simpleType `json:"county"` + County SimpleType `json:"county"` EffectiveStatus string `json:"effectiveStatus"` } -type phone struct { +// Phone represents the canpus definitiono of a person's phone number +type Phone struct { GUID int `json:"guid"` Pidm int `json:"pidm"` SequenceNum int `json:"sequenceNum"` - Type simpleType `json:"type"` + Type SimpleType `json:"type"` ActivityDate string `json:"activityDate"` - LinkedAddressType simpleType `json:"linkedAddressType"` + LinkedAddressType SimpleType `json:"linkedAddressType"` LinkedAddressSequence int `json:"linkedAddressSequence"` AreaCode string `json:"areaCode"` PhoneNumber string `json:"phoneNumber"` @@ -71,54 +71,48 @@ type phone struct { EffectiveStatus string `json:"effectiveStatus"` } -type emergencyContactName struct { +// EmergencyContactName represents the campus definition of an EmergencyContact name +type EmergencyContactName struct { LastName string `json:"lastName"` FirstName string `json:"firstName"` } -type emergencyPhone struct { +// EmergencyPhone represents the campus definition of an emergency phone number +type EmergencyPhone struct { PhoneArea string `json:"areaCode"` PhoneNumber string `json:"phoneNumber"` } -type emergencyAddress struct { - Type simpleType `json:"type"` +// EmergencyAddress represents the campus definition of an emergency contact address +type EmergencyAddress struct { + Type SimpleType `json:"type"` Street1 string `json:"streetLine1"` City string `json:"city"` - State simpleType `json:"state"` + State SimpleType `json:"state"` ZipCode string `json:"zipCode"` } -type emergencyContact struct { +// EmergencyContact represetnts the campus definition of a person's emergency contact person +type EmergencyContact struct { GUID int `json:"guid"` Pidm int `json:"pidm"` Priority string `json:"priority"` - Relationship simpleType `json:"relationship"` - Name emergencyContactName `json:"name"` - Phone emergencyPhone `json:"phone"` - Address emergencyAddress `json:"address"` + Relationship SimpleType `json:"relationship"` + Name EmergencyContactName `json:"name"` + Phone EmergencyPhone `json:"phone"` + Address EmergencyAddress `json:"address"` } -type campusPerson struct { - Names []name `json:"name"` - Addresses []address `json:"address"` - Phone []phone `json:"phone"` - EmergencyContacts []emergencyContact `json:"emergencyContact"` +// CampusPerson represents the campus definitioin of a person's contact information +type CampusPerson struct { + Names []Name `json:"name"` + Addresses []Address `json:"address"` + Phone []Phone `json:"phone"` + EmergencyContacts []EmergencyContact `json:"emergencyContact"` } -// ContactAdapter is a vendor specific structure that implements the contanct information interface -type ContactAdapter struct { - APIKey string - APIEndpoint string -} - -// NewContactAdapter returns a vendor specific implementation of the contanct information interface -func NewContactAdapter(apikey string, url string) *ContactAdapter { - return &ContactAdapter{APIKey: apikey, APIEndpoint: url} - -} - -func newPerson(cr *campusPerson) (*model.Person, error) { +// NewPerson constructs an app formatted person object from the campus representation +func NewPerson(cr *CampusPerson) (*model.Person, error) { ret := model.Person{} for i := 0; i < len(cr.Names); i++ { @@ -172,85 +166,3 @@ func newPerson(cr *campusPerson) (*model.Person, error) { return &ret, nil } - -// GetContactInformation returns a contact information object for a student -func (lv *ContactAdapter) GetContactInformation(uin string, accessToken string, mode string) (*model.Person, int, error) { - - finalURL := lv.APIEndpoint + "/person/contact-summary-query/" + uin - - if mode != "0" { - finalURL = lv.APIEndpoint + "/mock/123456789" - } - - campusData, statusCode, err := lv.getData(finalURL, accessToken) - if err != nil { - return nil, statusCode, err - } - - if len(campusData.People) == 0 { - return nil, 404, errors.New("No contact data found") - } - - retValue, err := newPerson(&campusData.People[0]) - if err != nil { - return nil, http.StatusInternalServerError, err - } - return retValue, statusCode, nil -} - -func (lv *ContactAdapter) getData(targetURL string, accessToken string) (*campusUserData, int, error) { - method := "GET" - - client := &http.Client{} - req, err := http.NewRequest(method, targetURL, nil) - - if err != nil { - return nil, http.StatusInternalServerError, err - } - - req.Header.Add("Authorization", "Bearer "+accessToken) - req.Header.Set("Ocp-Apim-Subscription-Key", lv.APIKey) - res, err := client.Do(req) - if err != nil { - return nil, res.StatusCode, err - } - defer res.Body.Close() - - body, err := ioutil.ReadAll(res.Body) - if err != nil { - return nil, res.StatusCode, err - } - - if res.StatusCode == 401 { - return nil, res.StatusCode, errors.New(res.Status) - } - - if res.StatusCode == 403 { - return nil, res.StatusCode, errors.New(res.Status) - } - - if res.StatusCode == 400 { - return nil, res.StatusCode, errors.New("Bad request to api end point") - } - - if res.StatusCode == 406 { - return nil, res.StatusCode, errors.New("Server returned 406: possible uin claim mismatch") - } - - //campus api returns a 502 when there is no banner contact data for the uin - if res.StatusCode == 502 { - return nil, 404, errors.New(res.Status) - } - if res.StatusCode == 200 { - data := campusUserData{} - err = json.Unmarshal(body, &data) - - if err != nil { - return nil, res.StatusCode, err - } - return &data, res.StatusCode, nil - } - - return nil, res.StatusCode, errors.New("Error making request: " + fmt.Sprint(res.StatusCode) + ": " + string(body)) - -} diff --git a/driven/courses/uiuc_courses.go b/core/model/uiuc/courses.go similarity index 51% rename from driven/courses/uiuc_courses.go rename to core/model/uiuc/courses.go index 07cbaab1..8271a34c 100644 --- a/driven/courses/uiuc_courses.go +++ b/core/model/uiuc/courses.go @@ -15,67 +15,78 @@ * limitations under the License. */ -package courses +package uiuc import ( - model "apigateway/core/model" - "encoding/json" - "errors" - "fmt" - "io/ioutil" - "net/http" + model "application/core/model" "strings" ) -type campusData struct { +// CampusData represents the full data returned by the campus courses end point +type CampusData struct { Object string `json:"object"` Version string `json:"version"` - List []studentTermCourseInfo `json:"list"` + List []StudentTermCourseInfo `json:"list"` } -type studentName struct { + +// StudentName represents the Name property of a student +type StudentName struct { LastName string `json:"lastName"` FirstName string `json:"firstName"` } -type studentDemo struct { - Name studentName `json:"name"` + +// StudentDemo represents the demographic information returned from teh campus courses end point +type StudentDemo struct { + Name StudentName `json:"name"` InstitutionalID string `json:"institutionalId"` } -type codeDescription struct { + +// CodeDescription represents a code description used in the campus course definition +type CodeDescription struct { Description string `json:"description"` Code string `json:"code"` } -type validPartOfTerm struct { +// ValidPartOfTerm represents the campus valid term definition +type ValidPartOfTerm struct { Description string `json:"description"` StartDate string `json:"startDate"` EndDate string `json:"endDate"` Code string `json:"code"` } -type campuscourse struct { +// Campuscourse represents the course data returned by the campus course endpoint +type Campuscourse struct { CourseAbbreviation string `json:"courseAbbreviation"` CourseNumber string `json:"courseNumber"` CourseTitle string `json:"courseTitle"` - ValidCampus codeDescription `json:"validCampus"` - ValidCollege codeDescription `json:"validCollege"` - ValidDepartment codeDescription `json:"validDepartment"` + ValidCampus CodeDescription `json:"validCampus"` + ValidCollege CodeDescription `json:"validCollege"` + ValidDepartment CodeDescription `json:"validDepartment"` } -type instructorName struct { + +// InstructorName represents the data used by campus to show an instructors name +type InstructorName struct { Type string `json:"type"` LastName string `json:"lastName"` FirstName string `json:"firstName"` } -type instructorDemo struct { - Name instructorName `json:"name"` + +// InstructorDemo represents the capus definition of an instructor's demographic information +type InstructorDemo struct { + Name InstructorName `json:"name"` PrimaryIndicator string `json:"primaryIndicator"` InstitutionalID string `json:"institutionalId"` EmailAddress string `json:"emailAddress"` } -type courseSectionInstructor struct { - LightweightPerson instructorDemo `json:"lightweightPerson"` + +// CourseSectionInstructor represents the campus definition of a course's instructor +type CourseSectionInstructor struct { + LightweightPerson InstructorDemo `json:"lightweightPerson"` } -type courseSectionSession struct { +// CourseSectionSession represents a course section meeting details in the campus data +type CourseSectionSession struct { MeetsOnMondayFlag string `json:"meetsOnMondayFlag"` MeetsOnTuesdayFlag string `json:"meetsOnTuesdayFlag"` MeetsOnWednesdayFlag string `json:"meetsOnWednesdayFlag"` @@ -88,54 +99,48 @@ type courseSectionSession struct { StartTime string `json:"startTime"` EndTime string `json:"endTime"` CreditHours string `json:"creditHours"` - CourseSectionInstructor []courseSectionInstructor `json:"courseSectionInstructor"` + CourseSectionInstructor []CourseSectionInstructor `json:"courseSectionInstructor"` MeetingDateOrRange string `json:"meetingDateOrRange"` - ValidMeetingType codeDescription `json:"validMeetingType"` + ValidMeetingType CodeDescription `json:"validMeetingType"` CourseSectionSessionID string `json:"courseSectionSessionID"` - ValidCourseScheduleType codeDescription `json:"validCourseScheduleType"` - ValidBuilding codeDescription `json:"validBuilding"` + ValidCourseScheduleType CodeDescription `json:"validCourseScheduleType"` + ValidBuilding CodeDescription `json:"validBuilding"` } -type courseSection struct { + +// CourseSection represents a course section in the campus data +type CourseSection struct { CourseReferenceNumber string `json:"courseReferenceNumber"` SectionNumber string `json:"sectionNumber"` - ValidTerm codeDescription `json:"validTerm"` - Course campuscourse `json:"course"` + ValidTerm CodeDescription `json:"validTerm"` + Course Campuscourse `json:"course"` StartDate string `json:"startDate"` EndDate string `json:"endDate"` CreditHours string `json:"creditHours"` - ValidPartOfTerm validPartOfTerm `json:"validPartOfTerm"` - CourseSectionSession []courseSectionSession `json:"courseSectionSession"` + ValidPartOfTerm ValidPartOfTerm `json:"validPartOfTerm"` + CourseSectionSession []CourseSectionSession `json:"courseSectionSession"` } -type courseRegistration struct { - ValidRegistrationStatusType codeDescription `json:"validRegistrationStatusType"` - ValidCourseRegistrationLevel codeDescription `json:"validCourseRegistrationLevel"` + +// CourseRegistration represents a students course registration in the campus data +type CourseRegistration struct { + ValidRegistrationStatusType CodeDescription `json:"validRegistrationStatusType"` + ValidCourseRegistrationLevel CodeDescription `json:"validCourseRegistrationLevel"` StudentCourseStartDate string `json:"studentCourseStartDate"` StudentCourseEndDate string `json:"studentCourseEndDate"` - ValidRegistrationStatus codeDescription `json:"validRegistrationStatus"` - ValidGradingMode codeDescription `json:"validGradingMode"` - CourseSection courseSection `json:"courseSection"` -} -type studentTermCourseInfo struct { - Student studentDemo `json:"lightweightPerson"` - ValidEnrollmentStatus codeDescription `json:"validEnrollmentStatus"` - ValidTerm codeDescription `json:"validTerm"` - CourseRegistration []courseRegistration `json:"courseRegistration"` + ValidRegistrationStatus CodeDescription `json:"validRegistrationStatus"` + ValidGradingMode CodeDescription `json:"validGradingMode"` + CourseSection CourseSection `json:"courseSection"` } -// StudentCourseAdapter is a vendor specific structure that implements the GiesCourse lookup interface -type StudentCourseAdapter struct { - CourseAPIEndpoint string - CourseAPIKey string +// StudentTermCourseInfo represents the registration data for a student in a given term +type StudentTermCourseInfo struct { + Student StudentDemo `json:"lightweightPerson"` + ValidEnrollmentStatus CodeDescription `json:"validEnrollmentStatus"` + ValidTerm CodeDescription `json:"validTerm"` + CourseRegistration []CourseRegistration `json:"courseRegistration"` } -// NewCourseAdapter returns a vendor specific implementation of the Course lookup interface -func NewCourseAdapter(url string, apikey string) *StudentCourseAdapter { - return &StudentCourseAdapter{CourseAPIEndpoint: url, CourseAPIKey: apikey} - -} - -// newCourse maps the campus course data to the course data sent back to the app. -func newCourse(cr courseRegistration, courseSectionSessionIndex int) *model.Course { +// NewCourse maps the campus course data to the course data sent back to the app. +func NewCourse(cr CourseRegistration, courseSectionSessionIndex int) *model.Course { ret := model.Course{} ret.Number = cr.CourseSection.CourseReferenceNumber ret.ShortName = cr.CourseSection.Course.CourseAbbreviation + " " + cr.CourseSection.Course.CourseNumber @@ -143,14 +148,14 @@ func newCourse(cr courseRegistration, courseSectionSessionIndex int) *model.Cour ret.InstructionMethod = cr.CourseSection.CourseSectionSession[courseSectionSessionIndex].ValidCourseScheduleType.Code crn := cr.CourseSection.CourseReferenceNumber css := cr.CourseSection.CourseSectionSession[courseSectionSessionIndex] - newCS := newCourseSection(css, crn) + newCS := NewCourseSection(css, crn) ret.Section = *newCS return &ret } -// newCourseSection maps the coursesectionsession data from campus to the coursesection data sent back to the app -func newCourseSection(cs courseSectionSession, crn string) *model.CourseSection { +// NewCourseSection maps the coursesectionsession data from campus to the coursesection data sent back to the app +func NewCourseSection(cs CourseSectionSession, crn string) *model.CourseSection { ret := model.CourseSection{} ret.BuildingName = cs.ValidBuilding.Description ret.Room = cs.Room @@ -202,97 +207,3 @@ func newCourseSection(cs courseSectionSession, crn string) *model.CourseSection return &ret } - -// GetStudentCourses returns a list of courses for the given tudent -func (lv *StudentCourseAdapter) GetStudentCourses(uin string, termid string, accessToken string) (*[]model.Course, int, error) { - - finalURL := lv.CourseAPIEndpoint + "/student-registration/student-enrollment-query/v2_0/" + uin + "/" + termid - - retValue := make([]model.Course, 0) - - campusData, statusCode, err := lv.getData(finalURL, accessToken) - if err != nil { - return nil, statusCode, err - } - - if len(campusData.List) == 0 { - return nil, 404, errors.New("No course data found") - } - - if len(campusData.List[0].CourseRegistration) == 0 { - return nil, 404, errors.New("No course data found") - } - - for i := 0; i < len(campusData.List[0].CourseRegistration); i++ { - course := campusData.List[0].CourseRegistration[i] - if course.ValidRegistrationStatusType.Code == "R" { - for i := 0; i < len(course.CourseSection.CourseSectionSession); i++ { - retValue = append(retValue, *newCourse(course, i)) - } - } - } - - if err != nil { - return nil, http.StatusInternalServerError, err - } - return &retValue, statusCode, nil -} - -func (lv *StudentCourseAdapter) getData(targetURL string, accessToken string) (*campusData, int, error) { - method := "GET" - - client := &http.Client{} - req, err := http.NewRequest(method, targetURL, nil) - - if err != nil { - return nil, http.StatusInternalServerError, err - } - - req.Header.Add("Authorization", "Bearer "+accessToken) - req.Header.Set("Ocp-Apim-Subscription-Key", lv.CourseAPIKey) - - res, err := client.Do(req) - if err != nil { - return nil, res.StatusCode, err - } - defer res.Body.Close() - - body, err := ioutil.ReadAll(res.Body) - if err != nil { - return nil, res.StatusCode, err - } - - if res.StatusCode == 401 { - return nil, res.StatusCode, errors.New(res.Status) - } - - if res.StatusCode == 403 { - return nil, res.StatusCode, errors.New(res.Status) - } - - if res.StatusCode == 400 { - return nil, res.StatusCode, errors.New("Bad request to api end point") - } - - if res.StatusCode == 406 { - return nil, res.StatusCode, errors.New("Server returned 406: possible uin claim mismatch") - } - //campus api returns a 502 when there is no course data - if res.StatusCode == 502 { - return nil, 404, errors.New(res.Status) - } - - if res.StatusCode == 200 || res.StatusCode == 203 { - data := campusData{} - - err = json.Unmarshal(body, &data) - - if err != nil { - return nil, res.StatusCode, err - } - return &data, res.StatusCode, nil - } - - return nil, res.StatusCode, errors.New("Error making request: " + fmt.Sprint(res.StatusCode) + ": " + string(body)) - -} diff --git a/core/model/uiuc/engcalendar.go b/core/model/uiuc/engcalendar.go new file mode 100644 index 00000000..4041c429 --- /dev/null +++ b/core/model/uiuc/engcalendar.go @@ -0,0 +1,100 @@ +/* + * Copyright (c) 2020 Board of Trustees of the University of Illinois. + * All rights reserved. + + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + + * http://www.apache.org/licenses/LICENSE-2.0 + + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package uiuc + +// EngineeringCalendar represents an entry in the engineering calendar list +type EngineeringCalendar struct { + ID int `json:"id"` + Name string `json:"name"` +} + +// EngineeringAdvisor represents an advisor with calendar entries on a given calendar +type EngineeringAdvisor struct { + ID string `json:"advisorId"` + Name string `json:"advisorName"` + Message string `json:"message"` + CalendarID int `json:"calendarId"` + CalendarName string `json:"calendarName"` + Active bool `json:"isActive"` + AppointmentLength int `json:"appointmentLength"` + AvailableSlots int `json:"availableSlots"` + NextAvailableDate string `json:"nextAvailableDate"` + Announcement string `json:"announcement"` + AnnouncementDate string `json:"announcementDate"` +} + +// EngineeringCalendarAdvisors represents a calendar including all advisors +type EngineeringCalendarAdvisors struct { + Adivsors []EngineeringAdvisor `json:"advisors"` + ID int `json:"id"` + Name string `json:"name"` +} + +// EngineeringTimeSlot represents a time slot on an advisors calendar +type EngineeringTimeSlot struct { + ID int `json:"id"` + StartDate string `json:"startDate"` + EndDate string `json:"endDate"` +} + +// EngineeringQuestion represents a question on an advisor's appointment app +type EngineeringQuestion struct { + ID string `json:"id"` + Order int `json:"order"` + Title string `json:"title"` + Type string `json:"type"` + UploadType string `json:"uploadType"` + SelectionValues []string `json:"selectionValues"` +} + +// EngineeringAdvisorWithSchedule represents an advisor plus their schedule information +type EngineeringAdvisorWithSchedule struct { + TimeSlots []EngineeringTimeSlot `json:"slotTimes"` + Questions []EngineeringQuestion `json:"questions"` + ID string `json:"advisorId"` + Name string `json:"advisorName"` + Message string `json:"message"` + CalendarID int `json:"calendarId"` + CalendarName string `json:"calendarName"` + Active bool `json:"isActive"` + AppointmentLength int `json:"appointmentLength"` + AvailableSlots int `json:"availableSlots"` + NextAvailableDate string `json:"nextAvailableDate"` + Announcement string `json:"announcement"` + AnnouncementDate string `json:"announcementDate"` +} + +// EngineeringAdvisorAppointments represents an advisors availability +type EngineeringAdvisorAppointments struct { + TimeSlots []EngineeringTimeSlot `json:"slots"` + Questions []EngineeringQuestion `json:"questions"` +} + +// EngineeringAnswer represnets an answer to an engineering question +type EngineeringAnswer struct { + QuestionID string `json:"questionId"` + Value string `json:"value"` + UploadID int `json:"uploadId"` +} + +// EngineeringAppointmentPost represents data needed to create an appointmen +type EngineeringAppointmentPost struct { + UIN int `json:"uin"` + SlotID int `json:"slotId"` + Answers []EngineeringAnswer `json:"answers"` +} diff --git a/core/model/uiuc/gies_courses.go b/core/model/uiuc/gies_courses.go new file mode 100644 index 00000000..a9c747f9 --- /dev/null +++ b/core/model/uiuc/gies_courses.go @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2020 Board of Trustees of the University of Illinois. + * All rights reserved. + + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + + * http://www.apache.org/licenses/LICENSE-2.0 + + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package uiuc + +import ( + model "application/core/model" +) + +// GIESCourse represents a light weight course definition used for GIES specific student operations +type GIESCourse struct { + Term string `json:"Term"` + Subject string `json:"Subject"` + Number string `json:"Number"` + Section string `json:"Section"` + Title string `json:"Title"` + Instructor string `json:"Instructor"` +} + +// NewGiesCourse returns an app formatted GiesCourse object from the campus definition +func NewGiesCourse(cr GIESCourse) *model.GiesCourse { + ret := model.GiesCourse{} + ret.Instructor = cr.Instructor + ret.Number = cr.Number + ret.Section = cr.Section + ret.Subject = cr.Subject + ret.Term = cr.Term + ret.Title = cr.Title + return &ret +} diff --git a/core/model/uiuc/laundryview.go b/core/model/uiuc/laundryview.go new file mode 100644 index 00000000..4b4d2f89 --- /dev/null +++ b/core/model/uiuc/laundryview.go @@ -0,0 +1,131 @@ +// Copyright 2022 Board of Trustees of the University of Illinois. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package uiuc + +import ( + model "application/core/model" + "encoding/xml" + "regexp" + "strconv" +) + +// Appliance represents a CCS definition of a washer/dryer +type Appliance struct { + XMLName xml.Name `xml:"appliance"` + ApplianceKey string `xml:"appliance_desc_key"` + LrmStatus string `xml:"lrm_status"` + ApplianceType string `xml:"appliance_type"` + Status string `xml:"status"` + OutOfService string `xml:"out_of_service"` + Label string `xml:"label"` + AvgCycleTime string `xml:"avg_cycle_time"` + TimeRemaining string `xml:"time_remaining"` +} + +// Laundryroom represents the csc definition of a laundry room +type Laundryroom struct { + XMLName xml.Name `xml:"laundry_room"` + Name string `xml:"laundry_room_name"` + CampusName string `xml:"campus_name"` + Appliances []*Appliance `xml:"appliances>appliance"` +} + +// Laundrylocation represents the CSC definition of a laundry location +type Laundrylocation struct { + Location int `xml:"location"` + XMLName xml.Name `xml:"laundryroom"` + Campusname string `xml:"campus_name"` + Laundryroomname string `xml:"laundry_room_name"` + Status string `xml:"status"` +} + +// School represents the csc definition of a customer +type School struct { + XMLName xml.Name `xml:"school"` + SchoolName string `xml:"school_name"` + LaundryRooms []*Laundrylocation `xml:"laundry_rooms>laundryroom"` +} + +// Capacity represents the available washers and dryers in a room +type Capacity struct { + XMLName xml.Name `xml:"laundry_room"` + NumWashers string `xml:"washer"` + NumDryers string `xml:"dryer"` +} + +// Machinedetail represents the CSC machine details needed to submit a service ticket +type Machinedetail struct { + Address string `json:"address"` + LaundryLocation string `json:"laundryLocaiton"` + MachineID string `json:"machineId"` + MachineType string `json:"machineType"` + Message string `json:"message"` + Property string `json:"property"` + RecentServiceDate string `json:"recentServiceDate"` + RecentServiceNotes string `json:"recentServiceNotes"` + RecentServiceStatus string `json:"recentServiceStatus"` + SiteID string `json:"siteID"` +} + +// NewMachineRequestDetail creates an app formatted machinerequestdetail ojbect from campus data +func NewMachineRequestDetail(machineid string, message string, serviceStatus string, machinetype string) *model.MachineRequestDetail { + var openTicket = serviceStatus == "Open" + mrd := model.MachineRequestDetail{MachineID: machineid, Message: message, OpenIssue: openTicket, MachineType: machinetype} + return &mrd +} + +// NewLaundryRoom returns an app formatted laundry room object from campus data +func NewLaundryRoom(id int, name string, status string, location *model.LaundryDetails) *model.LaundryRoom { + lr := model.LaundryRoom{Name: name, ID: id, Status: status, Location: location} + return &lr +} + +// NewAppliance returns an app formatted appliance ojbect from campus data +func NewAppliance(id string, appliancetype string, cycletime int, status string, timeremaining string, label string) *model.Appliance { + + var finalStatus string + switch status { + case "Available": + finalStatus = "available" + case "In Use": + finalStatus = "in_use" + default: + finalStatus = "out_of_service" + } + + if finalStatus == "available" || finalStatus == "out_of_service" { + appl := model.Appliance{ID: id, ApplianceType: appliancetype, AverageCycleTime: cycletime, Status: finalStatus, Label: label} + return &appl + } + + re := regexp.MustCompile("[0-9]+") + intsInString := re.FindAllString(timeremaining, 1) + + if intsInString != nil { + intConvValue, err := strconv.ParseInt(intsInString[0], 10, 32) + if err != nil { + appl := model.Appliance{ID: id, ApplianceType: appliancetype, AverageCycleTime: cycletime, Status: finalStatus, Label: label} + return &appl + } + + trValue := int(intConvValue) + appl := model.Appliance{ID: id, ApplianceType: appliancetype, AverageCycleTime: cycletime, Status: finalStatus, TimeRemaining: &trValue, Label: label} + return &appl + } + + appl := model.Appliance{ID: id, ApplianceType: appliancetype, AverageCycleTime: cycletime, Status: finalStatus, Label: label} + return &appl + +} diff --git a/core/model/user.go b/core/model/user.go index a1269fb6..252f1676 100644 --- a/core/model/user.go +++ b/core/model/user.go @@ -14,6 +14,15 @@ package model +import ( + "github.com/rokwire/logging-library-go/v2/logutils" +) + +const ( + //TypeShibbolethUser type + TypeShibbolethUser logutils.MessageDataType = "shibboleth_user" +) + ////////////////////////// // ShibbolethUser represents shibboleth auth entity diff --git a/core/services.go b/core/services.go deleted file mode 100644 index 50b31839..00000000 --- a/core/services.go +++ /dev/null @@ -1,112 +0,0 @@ -// Copyright 2022 Board of Trustees of the University of Illinois. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package core - -import ( - model "apigateway/core/model" -) - -func (app *Application) getVersion() string { - return app.version -} - -func (app *Application) storeRecord(name string) error { - return app.storage.StoreRecord(name) -} - -func (app *Application) listLaundryRooms() (model.Organization, error) { - lr, _ := app.laundry.ListRooms() - return *lr, nil -} - -func (app *Application) listAppliances(id string) (model.RoomDetail, error) { - ap, _ := app.laundry.GetLaundryRoom(id) - return *ap, nil -} - -func (app *Application) initServiceRequest(machineid string) (*model.MachineRequestDetail, error) { - sr, err := app.laundry.InitServiceRequest(machineid) - if err != nil { - return nil, err - } - return sr, nil -} - -func (app *Application) submitServiceRequest(machineID string, problemCode string, comments string, firstname string, lastname string, phone string, email string) (*model.ServiceRequestResult, error) { - srr, err := app.laundry.SubmitServiceRequest(machineID, problemCode, comments, firstname, lastname, phone, email) - if err != nil { - return nil, err - } - return srr, nil -} - -func (app *Application) getBuilding(bldgID string, adaOnly bool, latitude float64, longitude float64) (model.Building, error) { - bldg, err := app.locationAdapter.GetBuilding(bldgID, adaOnly, latitude, longitude) - if err != nil { - return *bldg, err - } - return *bldg, nil -} - -func (app *Application) getEntrance(bldgID string, adaOnly bool, latitude float64, longitude float64) (*model.Entrance, error) { - entrance, err := app.locationAdapter.GetEntrance(bldgID, adaOnly, latitude, longitude) - if err != nil { - if entrance == nil { - return nil, nil - } - return entrance, err - } - return entrance, nil -} - -func (app *Application) getBuildings() (*[]model.Building, error) { - buildings, err := app.locationAdapter.GetBuildings() - if err != nil { - return nil, err - } - return buildings, nil -} - -func (app *Application) getContactInfo(uin string, accessToken string, mode string) (*model.Person, int, error) { - person, statusCode, err := app.contactInfoAdapter.GetContactInformation(uin, accessToken, mode) - if err != nil { - return nil, statusCode, err - } - return person, 200, nil -} - -func (app *Application) getGiesCourses(uin string, accessToken string) (*[]model.GiesCourse, int, error) { - courseList, statusCode, err := app.giesCourseAdapter.GetGiesCourses(uin, accessToken) - if err != nil { - return nil, statusCode, err - } - return courseList, 200, nil -} - -func (app *Application) getStudentCourses(uin string, termid string, accessToken string) (*[]model.Course, int, error) { - courseList, statusCode, err := app.studentcourseAdapter.GetStudentCourses(uin, termid, accessToken) - if err != nil { - return nil, statusCode, err - } - return courseList, 200, nil -} - -func (app *Application) getTermSessions() (*[4]model.TermSession, error) { - termSessions, err := app.termsessionAdapter.GetTermSessions() - if err != nil { - return nil, err - } - return termSessions, nil -} diff --git a/driven/courses/uiuc_gies_courses.go b/driven/courses/uiuc_gies_courses.go deleted file mode 100644 index 8858b06a..00000000 --- a/driven/courses/uiuc_gies_courses.go +++ /dev/null @@ -1,138 +0,0 @@ -/* - * Copyright (c) 2020 Board of Trustees of the University of Illinois. - * All rights reserved. - - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - - * http://www.apache.org/licenses/LICENSE-2.0 - - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package courses - -import ( - model "apigateway/core/model" - "encoding/json" - "errors" - "fmt" - "io/ioutil" - "net/http" -) - -type course struct { - Term string `json:"Term"` - Subject string `json:"Subject"` - Number string `json:"Number"` - Section string `json:"Section"` - Title string `json:"Title"` - Instructor string `json:"Instructor"` -} - -// GiesCourseAdapter is a vendor specific structure that implements the GiesCourse lookup interface -type GiesCourseAdapter struct { - APIEndpoint string -} - -// NewGiesCourseAdapter returns a vendor specific implementation of the Course lookup interface -func NewGiesCourseAdapter(url string) *GiesCourseAdapter { - return &GiesCourseAdapter{APIEndpoint: url} - -} - -func newGiesCourse(cr course) *model.GiesCourse { - ret := model.GiesCourse{} - ret.Instructor = cr.Instructor - ret.Number = cr.Number - ret.Section = cr.Section - ret.Subject = cr.Subject - ret.Term = cr.Term - ret.Title = cr.Title - return &ret -} - -// GetGiesCourses returns a list of courses for the given GIES student -func (lv *GiesCourseAdapter) GetGiesCourses(uin string, accessToken string) (*[]model.GiesCourse, int, error) { - - finalURL := lv.APIEndpoint + "/" + uin - - retValue := make([]model.GiesCourse, 0) - - campusData, statusCode, err := lv.getData(finalURL, accessToken) - if err != nil { - return nil, statusCode, err - } - - if len(campusData) == 0 { - return nil, 404, errors.New("No course data found") - } - - for i := 0; i < len(campusData); i++ { - course := campusData[i] - retValue = append(retValue, *newGiesCourse(course)) - } - - if err != nil { - return nil, http.StatusInternalServerError, err - } - return &retValue, statusCode, nil -} - -func (lv *GiesCourseAdapter) getData(targetURL string, accessToken string) ([]course, int, error) { - method := "GET" - - client := &http.Client{} - req, err := http.NewRequest(method, targetURL, nil) - - if err != nil { - return nil, http.StatusInternalServerError, err - } - - req.Header.Add("access_token", accessToken) - res, err := client.Do(req) - if err != nil { - return nil, res.StatusCode, err - } - defer res.Body.Close() - - body, err := ioutil.ReadAll(res.Body) - if err != nil { - return nil, res.StatusCode, err - } - - if res.StatusCode == 401 { - return nil, res.StatusCode, errors.New(res.Status) - } - - if res.StatusCode == 403 { - return nil, res.StatusCode, errors.New(res.Status) - } - - if res.StatusCode == 400 { - return nil, res.StatusCode, errors.New("Bad request to api end point") - } - - if res.StatusCode == 406 { - return nil, res.StatusCode, errors.New("Server returned 406: possible uin claim mismatch") - } - - if res.StatusCode == 200 { - data := make([]course, 0) - - err = json.Unmarshal(body, &data) - - if err != nil { - return nil, res.StatusCode, err - } - return data, res.StatusCode, nil - } - - return nil, res.StatusCode, errors.New("Error making request: " + fmt.Sprint(res.StatusCode) + ": " + string(body)) - -} diff --git a/driven/laundry/csc_laundryview.go b/driven/laundry/csc_laundryview.go deleted file mode 100644 index 8c0da2b4..00000000 --- a/driven/laundry/csc_laundryview.go +++ /dev/null @@ -1,520 +0,0 @@ -// Copyright 2022 Board of Trustees of the University of Illinois. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package laundry - -import ( - model "apigateway/core/model" - "encoding/json" - "encoding/xml" - "errors" - "io/ioutil" - "log" - "net/http" - "regexp" - "strconv" - "strings" -) - -type appliance struct { - XMLName xml.Name `xml:"appliance"` - ApplianceKey string `xml:"appliance_desc_key"` - LrmStatus string `xml:"lrm_status"` - ApplianceType string `xml:"appliance_type"` - Status string `xml:"status"` - OutOfService string `xml:"out_of_service"` - Label string `xml:"label"` - AvgCycleTime string `xml:"avg_cycle_time"` - TimeRemaining string `xml:"time_remaining"` -} - -type laundryroom struct { - XMLName xml.Name `xml:"laundry_room"` - Name string `xml:"laundry_room_name"` - CampusName string `xml:"campus_name"` - Appliances []*appliance `xml:"appliances>appliance"` -} - -type laundrylocation struct { - Location int `xml:"location"` - XMLName xml.Name `xml:"laundryroom"` - Campusname string `xml:"campus_name"` - Laundryroomname string `xml:"laundry_room_name"` - Status string `xml:"status"` -} - -type school struct { - XMLName xml.Name `xml:"school"` - SchoolName string `xml:"school_name"` - LaundryRooms []*laundrylocation `xml:"laundry_rooms>laundryroom"` -} - -type capacity struct { - XMLName xml.Name `xml:"laundry_room"` - NumWashers string `xml:"washer"` - NumDryers string `xml:"dryer"` -} - -type machinedetail struct { - Address string `json:"address"` - LaundryLocation string `json:"laundryLocaiton"` - MachineID string `json:"machineId"` - MachineType string `json:"machineType"` - Message string `json:"message"` - Property string `json:"property"` - RecentServiceDate string `json:"recentServiceDate"` - RecentServiceNotes string `json:"recentServiceNotes"` - RecentServiceStatus string `json:"recentServiceStatus"` - SiteID string `json:"siteID"` -} - -// CSCLaundryView is a vendor specific structure that implements the Laundry interface -type CSCLaundryView struct { - //configuration information (url, api keys...gets passed into here) - APIKey string - APIUrl string - - ServiceOCPSubscriptionKey string - ServiceAPIUrl string - serviceToken string - serviceSubscriptionKey string - serviceCookie string - serviceBasicAuthToken string - laundryAssets map[string]model.LaundryDetails -} - -// NewCSCLaundryAdapter returns a vendor specific implementation of the Laundry interface -func NewCSCLaundryAdapter(apikey string, url string, subscriptionkey string, serviceapiurl string, assets map[string]model.LaundryDetails, authToken string) *CSCLaundryView { - return &CSCLaundryView{APIKey: apikey, APIUrl: url, ServiceOCPSubscriptionKey: subscriptionkey, ServiceAPIUrl: serviceapiurl, laundryAssets: assets, serviceBasicAuthToken: authToken} - -} - -// ListRooms lists the laundry rooms -func (lv *CSCLaundryView) ListRooms() (*model.Organization, error) { - - url := lv.APIUrl + "/school/?api_key=" + lv.APIKey + "&method=getRoomData&type=json" - resp, err := http.Get(url) - if err != nil { - return nil, err - } - defer resp.Body.Close() - - if resp.Status == "200 OK" { - body, bodyerr := ioutil.ReadAll(resp.Body) - if bodyerr != nil { - return nil, err - } - - var nS school - out := []byte(body) - if err := xml.Unmarshal(out, &nS); err != nil { - log.Fatal("could not unmarshal xml data") - return nil, err - } - org := model.Organization{SchoolName: nS.SchoolName} - org.LaundryRooms = make([]*model.LaundryRoom, 0) - - for _, lr := range nS.LaundryRooms { - if len(lv.laundryAssets) > 0 { - org.LaundryRooms = append(org.LaundryRooms, newLaundryRoom(lr.Location, lr.Laundryroomname, lr.Status, lv.getLocationData(strconv.Itoa(lr.Location)))) - } else { - org.LaundryRooms = append(org.LaundryRooms, newLaundryRoom(lr.Location, lr.Laundryroomname, lr.Status, lv.getLocationData("0"))) - } - } - return &org, nil - } - return nil, err -} - -// GetLaundryRoom returns the room details along with the list of machines in that room -func (lv *CSCLaundryView) GetLaundryRoom(roomid string) (*model.RoomDetail, error) { - - url := lv.APIUrl + "/room/?api_key=" + lv.APIKey + "&method=getAppliances&location=" + roomid + "&type=json" - resp, err := http.Get(url) - if err != nil { - return nil, err - } - defer resp.Body.Close() - - if resp.Status == "200 OK" { - body, bodyerr := ioutil.ReadAll(resp.Body) - if bodyerr != nil { - return nil, err - } - - var lr laundryroom - out := []byte(body) - if err := xml.Unmarshal(out, &lr); err != nil { - log.Fatal("could not unmarshal xml data") - return nil, err - } - - rd := model.RoomDetail{CampusName: lr.CampusName, RoomName: lr.Name} - rd.Appliances = make([]*model.Appliance, len(lr.Appliances)) - roomCapacity, _ := lv.getNumAvailable(roomid) - rd.NumDryers = evalNumAvailable(roomCapacity.NumDryers) - rd.NumWashers = evalNumAvailable(roomCapacity.NumWashers) - - for i, appl := range lr.Appliances { - avgCycle, _ := strconv.Atoi(appl.AvgCycleTime) - rd.Appliances[i] = newAppliance(appl.ApplianceKey, appl.ApplianceType, avgCycle, appl.Status, appl.TimeRemaining, appl.Label) - } - - if len(lv.laundryAssets) > 0 { - rd.Location = lv.getLocationData(roomid) - } - return &rd, nil - } - return nil, err -} - -func (lv *CSCLaundryView) getLocationData(roomid string) *model.LaundryDetails { - if asset, ok := lv.laundryAssets[roomid]; ok { - return &asset - } - return &model.LaundryDetails{Latitude: 0, Longitude: 0, Floor: 0} -} - -// InitServiceRequest gets machine request details needed to initialize a laundry service request -func (lv *CSCLaundryView) InitServiceRequest(machineID string) (*model.MachineRequestDetail, error) { - - err := lv.getServiceSubscriptionKey() - if err != nil { - return nil, err - } - - err = lv.getServiceToken() - - if err != nil { - return nil, err - } - - md, err := lv.getMachineDetails(machineID) - if err != nil { - return nil, err - } - - mrd := newMachineRequestDetail(md.MachineID, md.Message, md.RecentServiceStatus, md.MachineType) - mrd.ProblemCodes, err = lv.getProblemCodes(md.MachineType) - if err != nil { - return nil, err - } - return mrd, nil -} - -// SubmitServiceRequest submits a request for a machine -func (lv *CSCLaundryView) SubmitServiceRequest(machineid string, problemCode string, comments string, firstName string, lastName string, phone string, email string) (*model.ServiceRequestResult, error) { - - err := lv.getServiceSubscriptionKey() - if err != nil { - return nil, err - } - - err = lv.getServiceToken() - - if err != nil { - return nil, err - } - - srr, err := lv.submitTicket(machineid, problemCode, comments, firstName, lastName, phone, email) - if err != nil { - return nil, err - } - return srr, nil -} - -func newMachineRequestDetail(machineid string, message string, serviceStatus string, machinetype string) *model.MachineRequestDetail { - var openTicket = serviceStatus == "Open" - mrd := model.MachineRequestDetail{MachineID: machineid, Message: message, OpenIssue: openTicket, MachineType: machinetype} - return &mrd -} - -func newLaundryRoom(id int, name string, status string, location *model.LaundryDetails) *model.LaundryRoom { - lr := model.LaundryRoom{Name: name, ID: id, Status: status, Location: location} - return &lr -} - -func (lv *CSCLaundryView) getNumAvailable(roomid string) (*capacity, error) { - - url := lv.APIUrl + "/room/?api_key=" + lv.APIKey + "&method=getNumAvailable&location=" + roomid + "&type=json" - resp, err := http.Get(url) - if err != nil { - return nil, err - } - defer resp.Body.Close() - - if resp.Status == "200 OK" { - body, bodyerr := ioutil.ReadAll(resp.Body) - if bodyerr != nil { - return nil, err - } - - var cap capacity - out := []byte(body) - if err := xml.Unmarshal(out, &cap); err != nil { - log.Fatal("could not unmarshal xml data") - return nil, err - } - - return &cap, nil - } - return nil, err -} - -func newAppliance(id string, appliancetype string, cycletime int, status string, timeremaining string, label string) *model.Appliance { - - var finalStatus string - switch status { - case "Available": - finalStatus = "available" - case "In Use": - finalStatus = "in_use" - default: - finalStatus = "out_of_service" - } - - if finalStatus == "available" || finalStatus == "out_of_service" { - appl := model.Appliance{ID: id, ApplianceType: appliancetype, AverageCycleTime: cycletime, Status: finalStatus, Label: label} - return &appl - } - - re := regexp.MustCompile("[0-9]+") - intsInString := re.FindAllString(timeremaining, 1) - - if intsInString != nil { - intConvValue, err := strconv.ParseInt(intsInString[0], 10, 32) - if err != nil { - appl := model.Appliance{ID: id, ApplianceType: appliancetype, AverageCycleTime: cycletime, Status: finalStatus, Label: label} - return &appl - } - - trValue := int(intConvValue) - appl := model.Appliance{ID: id, ApplianceType: appliancetype, AverageCycleTime: cycletime, Status: finalStatus, TimeRemaining: &trValue, Label: label} - return &appl - } - - appl := model.Appliance{ID: id, ApplianceType: appliancetype, AverageCycleTime: cycletime, Status: finalStatus, Label: label} - return &appl - -} - -func evalNumAvailable(inputstr string) int { - if i, err := strconv.Atoi(inputstr); err == nil { - return i - } - return 0 -} - -func (lv *CSCLaundryView) getServiceSubscriptionKey() error { - url := lv.ServiceAPIUrl + "/getSubscriptionKey" - method := "POST" - - payload := `{"subscription-id": "uiuc", "key-type": "primaryKey" }` - - headers := make(map[string]string) - headers["Ocp-Apim-Subscription-Key"] = lv.ServiceOCPSubscriptionKey - headers["Content-Type"] = "application/json" - headers["Authorization"] = "Basic " + lv.serviceBasicAuthToken - - body, err := lv.makeLaundryServiceWebRequest(url, method, headers, payload) - if err != nil { - return err - } - - var dat map[string]interface{} - if err := json.Unmarshal(body, &dat); err != nil { - return err - } - - if _, keyExists := dat["subscription-key"]; !keyExists { - return errors.New("Subscription key not returned") - } - - lv.serviceSubscriptionKey = dat["subscription-key"].(string) - - return nil -} - -func (lv *CSCLaundryView) getServiceToken() error { - url := lv.ServiceAPIUrl + "/generateToken?subscription-key=" + lv.serviceSubscriptionKey - method := "GET" - - headers := make(map[string]string) - headers["Content-Type"] = "application/json" - headers["Authorization"] = "Basic " + lv.serviceBasicAuthToken - - body, err := lv.makeLaundryServiceWebRequest(url, method, headers, "") - - if err != nil { - return err - } - - var dat map[string]interface{} - if err := json.Unmarshal(body, &dat); err != nil { - return err - } - - if _, keyExists := dat["token"]; !keyExists { - return errors.New("token not returned") - } - - lv.serviceToken = dat["token"].(string) - return nil -} - -func (lv *CSCLaundryView) makeLaundryServiceWebRequest(url string, method string, headers map[string]string, postParams string) ([]byte, error) { - payload := strings.NewReader(postParams) - client := http.Client{} - req, err := http.NewRequest(method, url, payload) - - if err != nil { - return nil, err - } - - for headername, headerval := range headers { - req.Header.Add(headername, headerval) - } - - res, err := client.Do(req) - - if err != nil { - log.Printf("%v", err.Error()) - return nil, err - } - - defer res.Body.Close() - for _, cookie := range res.Cookies() { - if cookie.Name == "session" { - lv.serviceCookie = cookie.Value - } - } - - if res.StatusCode != 200 { - test, err := ioutil.ReadAll(res.Body) - log.Printf("%v", string(test)) - return nil, err - } - - body, err := ioutil.ReadAll(res.Body) - if err != nil { - return nil, err - } - return body, nil -} - -func (lv *CSCLaundryView) getMachineDetails(machineid string) (*machinedetail, error) { - md := machinedetail{} - - url := lv.ServiceAPIUrl + "/machineDetails?subscription-key=" + lv.serviceSubscriptionKey - method := "POST" - - payload := `{"machineId":"` + machineid + `"}` - - headers := make(map[string]string) - headers["X-CSRFToken"] = lv.serviceToken - headers["Cookie"] = "session=" + lv.serviceCookie - headers["Content-Type"] = "application/json" - headers["Authorization"] = "Basic " + lv.serviceBasicAuthToken - - body, err := lv.makeLaundryServiceWebRequest(url, method, headers, payload) - - if err != nil { - return nil, err - } - - if err := json.Unmarshal(body, &md); err != nil { - return nil, err - } - return &md, nil -} - -func (lv *CSCLaundryView) getProblemCodes(machinetype string) ([]string, error) { - url := lv.ServiceAPIUrl + "/problemCodes?subscription-key=" + lv.serviceSubscriptionKey - method := "POST" - - payload := `{"machineType": "` + machinetype + `"}` - - headers := make(map[string]string) - headers["X-CSRFToken"] = lv.serviceToken - headers["Cookie"] = "session=" + lv.serviceCookie - headers["Content-Type"] = "application/json" - headers["Authorization"] = "Basic " + lv.serviceBasicAuthToken - - body, err := lv.makeLaundryServiceWebRequest(url, method, headers, payload) - if err != nil { - return nil, err - } - - var dat map[string][]string - if err := json.Unmarshal(body, &dat); err != nil { - return nil, err - } - - return dat["problemCodeList"], nil -} - -func (lv *CSCLaundryView) submitTicket(machineid string, problemCode string, comments string, firstName string, lastName string, phone string, email string) (*model.ServiceRequestResult, error) { - url := lv.ServiceAPIUrl + "/submitServiceRequest?subscription-key=" + lv.serviceSubscriptionKey - method := "POST" - headers := make(map[string]string) - headers["X-CSRFToken"] = lv.serviceToken - headers["Cookie"] = "session=" + lv.serviceCookie - headers["Content-Type"] = "application/json" - headers["Authorization"] = "Basic " + lv.serviceBasicAuthToken - - payload := struct { - MachineID string `json:"machineId"` - ProblemCode string `json:"problemCode"` - Comments string `json:"comments"` - FirstName string `json:"firstName"` - LastName string `json:"lastName"` - Phone string `json:"phone"` - Email string `json:"email"` - }{ - MachineID: machineid, - ProblemCode: problemCode, - Comments: comments, - FirstName: firstName, - LastName: lastName, - Phone: phone, - Email: email, - } - - postData, err := json.Marshal(payload) - if err != nil { - return nil, err - } - - body, err := lv.makeLaundryServiceWebRequest(url, method, headers, string(postData)) - - if err != nil { - return nil, err - } - - var obj interface{} - - if err := json.Unmarshal(body, &obj); err != nil { - return nil, err - } - - m := obj.(map[string]interface{}) - //already a request for this machine, so got back a machine details object - if m["machineId"] != nil { - result := model.ServiceRequestResult{Message: "A ticket already exists for this machine", RequestNumber: "0", Status: "Failed"} - return &result, nil - } - result := model.ServiceRequestResult{Message: m["message"].(string), RequestNumber: m["serviceRequestNumber"].(string), Status: "Success"} - return &result, nil - -} diff --git a/driven/laundry/csc_laundryview_testfile.txt b/driven/laundry/csc_laundryview_testfile.txt deleted file mode 100644 index 4c036b28..00000000 --- a/driven/laundry/csc_laundryview_testfile.txt +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright (c) 2020 Board of Trustees of the University of Illinois. - * All rights reserved. - - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - - * http://www.apache.org/licenses/LICENSE-2.0 - - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package laundry - -import ( - "log" - "os" - "testing" -) - -func TestSchoolsCall(t *testing.T) { - laundryKey := getEnvKey("GATEWAY_LAUNDRY_APIKEY", true) - laundryAPI := getEnvKey("GATEWAY_LAUNDRY_APIURL", true) - luandryServiceKey := getEnvKey("GATEWAY_LAUNDRYSERVICE_APIKEY", true) - laundryServiceAPI := getEnvKey("GATEWAY_LAUNDRYSERVICE_API", true) - - laundryAdapter := NewCSCLaundryAdapter(laundryKey, laundryAPI, luandryServiceKey, laundryServiceAPI) - _, err := laundryAdapter.ListRooms() - if err != nil { - t.Fatalf(`test failed`) - } -} - -func TestSchoolsCallInvalidKey(t *testing.T) { - laundryKey := getEnvKey("GATEWAY_LAUNDRY_APIKEY", true) - laundryAPI := getEnvKey("GATEWAY_LAUNDRY_APIURL", true) - luandryServiceKey := getEnvKey("GATEWAY_LAUNDRYSERVICE_APIKEY", true) - laundryServiceAPI := getEnvKey("GATEWAY_LAUNDRYSERVICE_API", true) - - laundryAdapter := NewCSCLaundryAdapter(laundryKey, laundryAPI, luandryServiceKey, laundryServiceAPI) - _, err := laundryAdapter.ListRooms() - if err != nil { - t.Fatalf(`test failed`) - } -} - -func getEnvKey(key string, required bool) string { - //get from the environment - value, exist := os.LookupEnv(key) - if !exist { - if required { - log.Fatal("No provided environment variable for " + key) - } else { - log.Printf("No provided environment variable for " + key) - } - } - return value -} diff --git a/driven/location/uiuc_buildingdata.go b/driven/location/uiuc_buildingdata.go deleted file mode 100644 index 35f6cbe8..00000000 --- a/driven/location/uiuc_buildingdata.go +++ /dev/null @@ -1,241 +0,0 @@ -// Copyright 2022 Board of Trustees of the University of Illinois. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package buildinglocation - -import ( - model "apigateway/core/model" - "bytes" - "encoding/json" - "errors" - "fmt" - "io/ioutil" - "mime/multipart" - "net/http" -) - -type campusEntrance struct { - UUID string `json:"uuid"` - Name string `json:"descriptive_name"` - ADACompliant bool `json:"is_ada_compliant"` - Available bool `json:"is_available_for_use"` - ImageURL string `json:"image"` - Latitude float32 `json:"latitude"` - Longitude float32 `json:"longitude"` -} - -type campusBuilding struct { - UUID string `json:"uuid"` - Name string `json:"name"` - Number string `json:"number"` - FullAddress string `json:"location"` - Address1 string `json:"address_1"` - Address2 string `json:"address_2"` - City string `json:"city"` - State string `json:"state"` - ZipCode string `json:"zipcode"` - ImageURL string `json:"image"` - MailCode string `json:"mailcode"` - Entrances []campusEntrance `json:"entrances"` - Latitude float32 `json:"building_centroid_latitude"` - Longitude float32 `json:"building_centroid_longitude"` -} - -type serverResponse struct { - Status string `json:"status"` - HTTPStatusCode int `json:"http_return"` - CollectionType string `json:"collection"` - Count int `json:"count"` - ErrorList string `json:"errors"` - ErrorMessage string `json:"error_text"` -} - -type serverLocationData struct { - Response serverResponse `json:"response"` - Buildings []campusBuilding `json:"results"` -} - -// UIUCWayFinding is a vendor specific structure that implements the BuildingLocation interface -type UIUCWayFinding struct { - APIKey string - APIUrl string -} - -// NewUIUCWayFinding returns a new instance of a UIUCWayFinding struct -func NewUIUCWayFinding(apikey string, apiurl string) *UIUCWayFinding { - return &UIUCWayFinding{APIKey: apikey, APIUrl: apiurl} -} - -// NewBuilding creates a wayfinding.Building instance from a campusBuilding, -// including all active entrances for the building -func NewBuilding(bldg campusBuilding) *model.Building { - newBldg := model.Building{ID: bldg.UUID, Name: bldg.Name, ImageURL: bldg.ImageURL, Address1: bldg.Address1, Address2: bldg.Address2, FullAddress: bldg.FullAddress, City: bldg.City, ZipCode: bldg.ZipCode, State: bldg.State, Latitude: bldg.Latitude, Longitude: bldg.Longitude} - newBldg.Entrances = make([]model.Entrance, 0) - for _, n := range bldg.Entrances { - if n.Available { - newBldg.Entrances = append(newBldg.Entrances, *NewEntrance(n)) - } - } - return &newBldg -} - -// NewBuildingList returns a list of wayfinding buildings created frmo a list of campus building objects. -func NewBuildingList(bldgList *[]campusBuilding) *[]model.Building { - retList := make([]model.Building, len(*bldgList)) - for i := 0; i < len(*bldgList); i++ { - cmpsBldg := (*bldgList)[i] - crntBldng := NewBuilding(cmpsBldg) - retList[i] = *crntBldng - } - return &retList -} - -// NewEntrance creates a wayfinding.Entrance instance from a campusEntrance object -func NewEntrance(ent campusEntrance) *model.Entrance { - newEnt := model.Entrance{ID: ent.UUID, Name: ent.Name, ADACompliant: ent.ADACompliant, Available: ent.Available, ImageURL: ent.ImageURL, Latitude: ent.Latitude, Longitude: ent.Longitude} - return &newEnt -} - -// GetEntrance returns the active entrance closest to the user's position that meets the ADA Accessibility filter requirement -func (uwf *UIUCWayFinding) GetEntrance(bldgID string, adaAccessibleOnly bool, latitude float64, longitude float64) (*model.Entrance, error) { - lat := fmt.Sprintf("%f", latitude) - long := fmt.Sprintf("%f", longitude) - url := uwf.APIUrl + "/ccf" - - parameters := "{\"v\": 2, \"ranged\": true, \"point\": {\"latitude\": " + lat + ", \"longitude\": " + long + "}}" - bldSelection := "\"number\": \"" + bldgID + "\"" - adaSelection := "" - if adaAccessibleOnly { - adaSelection = ",\"entrances\": {\"ada_compliant\": true}" - } - query := "{" + bldSelection + adaSelection + "}" - - bldg, err := uwf.getBuildingData(url, query, parameters, false) - if err != nil { - ent := model.Entrance{} - return &ent, err - } - ent := uwf.closestEntrance((*bldg)[0]) - if ent != nil { - return NewEntrance(*ent), nil - } - return nil, nil -} - -// GetBuildings returns a list of all buildings -func (uwf *UIUCWayFinding) GetBuildings() (*[]model.Building, error) { - url := uwf.APIUrl + "/ccf" - parameters := "{\"v\": 2}" - - cmpBldgs, err := uwf.getBuildingData(url, "{}", parameters, true) - if err != nil { - return nil, err - } - returnList := NewBuildingList(cmpBldgs) - return returnList, nil -} - -// GetBuilding returns the requested building with all of its entrances that meet the ADA accessibility filter -func (uwf *UIUCWayFinding) GetBuilding(bldgID string, adaAccessibleOnly bool, latitude float64, longitude float64) (*model.Building, error) { - url := uwf.APIUrl + "/ccf" - lat := fmt.Sprintf("%f", latitude) - long := fmt.Sprintf("%f", longitude) - - parameters := "" - if latitude == 0 && longitude == 0 { - parameters = "{\"v\": 2, \"ranged\": true, \"point\": {\"latitude\": " + lat + ", \"longitude\": " + long + "}}" - } else { - parameters = "{\"v\": 2}" - } - - bldSelection := "\"number\": \"" + bldgID + "\"" - adaSelection := "" - if adaAccessibleOnly { - adaSelection = ",\"entrances\": {\"ada_compliant\": true}" - } - query := "{" + bldSelection + adaSelection + "}" - cmpBldg, err := uwf.getBuildingData(url, query, parameters, false) - if err != nil { - bldg := model.Building{} - return &bldg, err - } - return NewBuilding((*cmpBldg)[0]), nil -} - -// the entrance list coming back from a ranged query to the API is sorted closest to farthest from -// the user's coordinates. The first entrance in the list that is active and matches the ADA filter -// will be the one to return -func (uwf *UIUCWayFinding) closestEntrance(bldg campusBuilding) *campusEntrance { - for _, n := range bldg.Entrances { - if n.Available { - return &n - } - } - return nil -} - -func (uwf *UIUCWayFinding) getBuildingData(targetURL string, queryString string, parameters string, allBuildings bool) (*[]campusBuilding, error) { - method := "POST" - - payload := &bytes.Buffer{} - writer := multipart.NewWriter(payload) - _ = writer.WriteField("collection", "buildings") - _ = writer.WriteField("action", "fetch") - _ = writer.WriteField("query", queryString) - _ = writer.WriteField("parameters", parameters) - err := writer.Close() - if err != nil { - return nil, err - } - - client := &http.Client{} - fmt.Println(targetURL) - req, err := http.NewRequest(method, targetURL, payload) - - if err != nil { - return nil, err - } - - req.Header.Add("Authorization", "Bearer "+uwf.APIKey) - req.Header.Set("Content-Type", writer.FormDataContentType()) - res, err := client.Do(req) - if err != nil { - return nil, err - } - defer res.Body.Close() - - body, err := ioutil.ReadAll(res.Body) - if err != nil { - return nil, err - } - - if res.StatusCode == 400 { - return nil, errors.New("Bad request to api end point") - } - - //used to indicate no building found - if res.StatusCode == 202 { - return nil, errors.New("Building not found") - } - - data := serverLocationData{} - err = json.Unmarshal(body, &data) - - if err != nil { - return nil, err - } - - campusBldgs := data.Buildings - return &campusBldgs, nil -} diff --git a/driven/storage/adapter.go b/driven/storage/adapter.go index 58798da5..810c4877 100644 --- a/driven/storage/adapter.go +++ b/driven/storage/adapter.go @@ -15,75 +15,292 @@ package storage import ( + "application/core/interfaces" + "application/core/model" "context" "fmt" - "log" "strconv" + "strings" + "sync" "time" - "github.com/google/uuid" + "github.com/rokwire/logging-library-go/v2/errors" + "github.com/rokwire/logging-library-go/v2/logs" + "github.com/rokwire/logging-library-go/v2/logutils" + "golang.org/x/sync/syncmap" + + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/bson/primitive" "go.mongodb.org/mongo-driver/mongo" ) // Adapter implements the Storage interface type Adapter struct { db *database + + context mongo.SessionContext + + cachedConfigs *syncmap.Map + configsLock *sync.RWMutex } // Start starts the storage -func (sa *Adapter) Start() error { - err := sa.db.start() - return err +func (a *Adapter) Start() error { + err := a.db.start() + if err != nil { + return errors.WrapErrorAction(logutils.ActionInitialize, "storage adapter", nil, err) + } + + //register storage listener + sl := storageListener{adapter: a} + a.RegisterStorageListener(&sl) + + //cache the configs + err = a.cacheConfigs() + if err != nil { + return errors.WrapErrorAction(logutils.ActionCache, model.TypeConfig, nil, err) + } + + return nil } -// NewStorageAdapter creates a new storage adapter instance -func NewStorageAdapter(mongoDBAuth string, mongoDBName string, mongoTimeout string) *Adapter { - timeout, err := strconv.Atoi(mongoTimeout) +// RegisterStorageListener registers a data change listener with the storage adapter +func (a *Adapter) RegisterStorageListener(listener interfaces.StorageListener) { + a.db.listeners = append(a.db.listeners, listener) +} + +// Creates a new Adapter with provided context +func (a *Adapter) withContext(context mongo.SessionContext) *Adapter { + return &Adapter{db: a.db, context: context, cachedConfigs: a.cachedConfigs, configsLock: a.configsLock} +} + +// cacheConfigs caches the configs from the DB +func (a *Adapter) cacheConfigs() error { + a.db.logger.Info("cacheConfigs...") + + configs, err := a.loadConfigs() if err != nil { - log.Println("Set default timeout - 2000") - timeout = 2000 + return errors.WrapErrorAction(logutils.ActionLoad, model.TypeConfig, nil, err) } - timeoutMS := time.Millisecond * time.Duration(timeout) - db := &database{mongoDBAuth: mongoDBAuth, mongoDBName: mongoDBName, mongoTimeout: timeoutMS} - return &Adapter{db: db} + a.setCachedConfigs(configs) + + return nil } -// StoreRecord stores firebase token and links it to the user -func (sa Adapter) StoreRecord(name string) error { - err := sa.db.dbClient.UseSession(context.Background(), func(sessionContext mongo.SessionContext) error { - err := sessionContext.StartTransaction() +func (a *Adapter) setCachedConfigs(configs []model.Config) { + a.configsLock.Lock() + defer a.configsLock.Unlock() + + a.cachedConfigs = &syncmap.Map{} + + for _, config := range configs { + var err error + switch config.Type { + case model.ConfigTypeEnv: + err = parseConfigsData[model.EnvConfigData](&config) + default: + err = parseConfigsData[map[string]interface{}](&config) + } if err != nil { - log.Printf("error starting a transaction - %s", err) - return err + a.db.logger.Warn(err.Error()) } + a.cachedConfigs.Store(config.ID, config) + a.cachedConfigs.Store(fmt.Sprintf("%s_%s_%s", config.Type, config.AppID, config.OrgID), config) + } +} - _, err = sa.db.dinings.InsertOne(map[string]string{ - "_id": uuid.NewString(), - "name": name, - }) +func parseConfigsData[T model.ConfigData](config *model.Config) error { + bsonBytes, err := bson.Marshal(config.Data) + if err != nil { + return errors.WrapErrorAction(logutils.ActionUnmarshal, model.TypeConfig, nil, err) + } - if err != nil { - fmt.Printf("error while storing record (%s) %s\n", name, err) - abortTransaction(sessionContext) - return err + var data T + err = bson.Unmarshal(bsonBytes, &data) + if err != nil { + return errors.WrapErrorAction(logutils.ActionUnmarshal, model.TypeConfigData, &logutils.FieldArgs{"type": config.Type}, err) + } + + config.Data = data + return nil +} + +func (a *Adapter) getCachedConfig(id string, configType string, appID string, orgID string) (*model.Config, error) { + a.configsLock.RLock() + defer a.configsLock.RUnlock() + + var item any + var errArgs logutils.FieldArgs + if id != "" { + errArgs = logutils.FieldArgs{"id": id} + item, _ = a.cachedConfigs.Load(id) + } else { + errArgs = logutils.FieldArgs{"type": configType, "app_id": appID, "org_id": orgID} + item, _ = a.cachedConfigs.Load(fmt.Sprintf("%s_%s_%s", configType, appID, orgID)) + } + + if item != nil { + config, ok := item.(model.Config) + if !ok { + return nil, errors.ErrorAction(logutils.ActionCast, model.TypeConfig, &errArgs) } + return &config, nil + } + return nil, nil +} - //commit the transaction - err = sessionContext.CommitTransaction(sessionContext) - if err != nil { - fmt.Println(err) - return err +func (a *Adapter) getCachedConfigs(configType *string) ([]model.Config, error) { + a.configsLock.RLock() + defer a.configsLock.RUnlock() + + var err error + configList := make([]model.Config, 0) + a.cachedConfigs.Range(func(key, item interface{}) bool { + keyStr, ok := key.(string) + if !ok || item == nil { + return false + } + if !strings.Contains(keyStr, "_") { + return true + } + + config, ok := item.(model.Config) + if !ok { + err = errors.ErrorAction(logutils.ActionCast, model.TypeConfig, &logutils.FieldArgs{"key": key}) + return false } - return nil + + if configType == nil || strings.HasPrefix(keyStr, fmt.Sprintf("%s_", *configType)) { + configList = append(configList, config) + } + + return true }) - return err + return configList, err +} + +// loadConfigs loads configs +func (a *Adapter) loadConfigs() ([]model.Config, error) { + filter := bson.M{} + + var configs []model.Config + err := a.db.configs.Find(a.context, filter, &configs, nil) + if err != nil { + return nil, errors.WrapErrorAction(logutils.ActionFind, model.TypeConfig, nil, err) + } + + return configs, nil +} + +// FindConfig finds the config for the specified type, appID, and orgID +func (a *Adapter) FindConfig(configType string, appID string, orgID string) (*model.Config, error) { + return a.getCachedConfig("", configType, appID, orgID) +} + +// FindConfigByID finds the config for the specified ID +func (a *Adapter) FindConfigByID(id string) (*model.Config, error) { + return a.getCachedConfig(id, "", "", "") +} + +// FindConfigs finds all configs for the specified type +func (a *Adapter) FindConfigs(configType *string) ([]model.Config, error) { + return a.getCachedConfigs(configType) +} + +// InsertConfig inserts a new config +func (a *Adapter) InsertConfig(config model.Config) error { + _, err := a.db.configs.InsertOne(a.context, config) + if err != nil { + return errors.WrapErrorAction(logutils.ActionInsert, model.TypeConfig, nil, err) + } + + return nil +} + +// UpdateConfig updates an existing config +func (a *Adapter) UpdateConfig(config model.Config) error { + filter := bson.M{"_id": config.ID} + update := bson.D{ + primitive.E{Key: "$set", Value: bson.D{ + primitive.E{Key: "type", Value: config.Type}, + primitive.E{Key: "app_id", Value: config.AppID}, + primitive.E{Key: "org_id", Value: config.OrgID}, + primitive.E{Key: "system", Value: config.System}, + primitive.E{Key: "data", Value: config.Data}, + primitive.E{Key: "date_updated", Value: config.DateUpdated}, + }}, + } + _, err := a.db.configs.UpdateOne(a.context, filter, update, nil) + if err != nil { + return errors.WrapErrorAction(logutils.ActionUpdate, model.TypeConfig, &logutils.FieldArgs{"id": config.ID}, err) + } + + return nil } -func abortTransaction(sessionContext mongo.SessionContext) { - err := sessionContext.AbortTransaction(sessionContext) +// DeleteConfig deletes a configuration from storage +func (a *Adapter) DeleteConfig(id string) error { + delFilter := bson.M{"_id": id} + _, err := a.db.configs.DeleteMany(a.context, delFilter, nil) if err != nil { - log.Printf("error on aborting a transaction - %s", err) + return errors.WrapErrorAction(logutils.ActionDelete, model.TypeConfig, &logutils.FieldArgs{"id": id}, err) } + + return nil +} + +// PerformTransaction performs a transaction +func (a *Adapter) PerformTransaction(transaction func(storage interfaces.Storage) error) error { + // transaction + callback := func(sessionContext mongo.SessionContext) (interface{}, error) { + adapter := a.withContext(sessionContext) + + err := transaction(adapter) + if err != nil { + if wrappedErr, ok := err.(*errors.Error); ok && wrappedErr.Internal() != nil { + return nil, wrappedErr.Internal() + } + return nil, err + } + + return nil, nil + } + + session, err := a.db.dbClient.StartSession() + if err != nil { + return errors.WrapErrorAction(logutils.ActionStart, "mongo session", nil, err) + } + context := context.Background() + defer session.EndSession(context) + + _, err = session.WithTransaction(context, callback) + if err != nil { + return errors.WrapErrorAction("performing", logutils.TypeTransaction, nil, err) + } + return nil +} + +func filterArgs(filter bson.M) *logutils.FieldArgs { + args := logutils.FieldArgs{} + for k, v := range filter { + args[k] = v + } + return &args +} + +// NewStorageAdapter creates a new storage adapter instance +func NewStorageAdapter(mongoDBAuth string, mongoDBName string, mongoTimeout string, logger *logs.Logger) *Adapter { + timeout, err := strconv.Atoi(mongoTimeout) + if err != nil { + logger.Infof("Set default timeout - 2000") + timeout = 2000 + } + + cachedConfigs := &syncmap.Map{} + configsLock := &sync.RWMutex{} + + db := &database{mongoDBAuth: mongoDBAuth, mongoDBName: mongoDBName, mongoTimeout: time.Millisecond * time.Duration(timeout), logger: logger} + return &Adapter{db: db, cachedConfigs: cachedConfigs, configsLock: configsLock} } diff --git a/driven/storage/adapter_example.go b/driven/storage/adapter_example.go new file mode 100644 index 00000000..2825abc3 --- /dev/null +++ b/driven/storage/adapter_example.go @@ -0,0 +1,73 @@ +// Copyright 2022 Board of Trustees of the University of Illinois. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package storage + +import ( + "application/core/model" + + "github.com/rokwire/logging-library-go/v2/errors" + "github.com/rokwire/logging-library-go/v2/logutils" + "go.mongodb.org/mongo-driver/bson" +) + +// FindExample finds example by id +func (a *Adapter) FindExample(orgID string, appID string, id string) (*model.Example, error) { + filter := bson.M{"org_id": orgID, "app_id": appID, "_id": id} + + var data *model.Example + err := a.db.examples.FindOne(a.context, filter, &data, nil) + if err != nil { + return nil, errors.WrapErrorAction(logutils.ActionFind, model.TypeExample, filterArgs(filter), err) + } + + return data, nil +} + +// InsertExample inserts a new example +func (a *Adapter) InsertExample(example model.Example) error { + _, err := a.db.examples.InsertOne(a.context, example) + if err != nil { + return errors.WrapErrorAction(logutils.ActionInsert, model.TypeExample, nil, err) + } + + return nil +} + +// UpdateExample updates an example +func (a *Adapter) UpdateExample(example model.Example) error { + filter := bson.M{"org_id": example.OrgID, "app_id": example.AppID, "_id": example.ID} + update := bson.M{"$set": bson.M{"data": example.Data}} + + _, err := a.db.examples.UpdateOne(a.context, filter, update, nil) + if err != nil { + return errors.WrapErrorAction(logutils.ActionUpdate, model.TypeExample, filterArgs(filter), err) + } + return nil +} + +// DeleteExample deletes an example +func (a *Adapter) DeleteExample(orgID string, appID string, id string) error { + filter := bson.M{"org_id": orgID, "app_id": appID, "_id": id} + + res, err := a.db.examples.DeleteOne(a.context, filter, nil) + if err != nil { + return errors.WrapErrorAction(logutils.ActionDelete, model.TypeExample, filterArgs(filter), err) + } + if res.DeletedCount != 1 { + return errors.ErrorData(logutils.StatusMissing, model.TypeConfig, filterArgs(filter)) + } + + return nil +} diff --git a/driven/storage/collection.go b/driven/storage/collection.go index 790df6b5..d7a72e7b 100644 --- a/driven/storage/collection.go +++ b/driven/storage/collection.go @@ -17,9 +17,10 @@ package storage import ( "context" "errors" - "log" + "fmt" "time" + "github.com/rokwire/logging-library-go/v2/logs" "go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/mongo" "go.mongodb.org/mongo-driver/mongo/options" @@ -30,11 +31,12 @@ type collectionWrapper struct { coll *mongo.Collection } -func (collWrapper *collectionWrapper) Find(filter interface{}, result interface{}, findOptions *options.FindOptions) error { - return collWrapper.FindWithContext(context.Background(), filter, result, findOptions) -} +func (collWrapper *collectionWrapper) Find(ctx context.Context, filter interface{}, result interface{}, + findOptions *options.FindOptions) error { + if ctx == nil { + ctx = context.Background() + } -func (collWrapper *collectionWrapper) FindWithContext(ctx context.Context, filter interface{}, result interface{}, findOptions *options.FindOptions) error { ctx, cancel := context.WithTimeout(ctx, collWrapper.database.mongoTimeout) defer cancel() @@ -52,27 +54,10 @@ func (collWrapper *collectionWrapper) FindWithContext(ctx context.Context, filte return err } -func (collWrapper *collectionWrapper) Distinct(fieldName string, filter interface{}, distinctOptions *options.DistinctOptions) ([]interface{}, error) { - return collWrapper.DistinctWithContext(context.Background(), fieldName, filter, distinctOptions) -} - -func (collWrapper *collectionWrapper) DistinctWithContext(ctx context.Context, fieldName string, filter interface{}, distinctOptions *options.DistinctOptions) ([]interface{}, error) { - ctx, cancel := context.WithTimeout(ctx, collWrapper.database.mongoTimeout) - defer cancel() - - if filter == nil { - // Passing bson.D{} as the filter matches all documents in the collection - filter = bson.D{} +func (collWrapper *collectionWrapper) FindOne(ctx context.Context, filter interface{}, result interface{}, findOptions *options.FindOneOptions) error { + if ctx == nil { + ctx = context.Background() } - - return collWrapper.coll.Distinct(ctx, fieldName, filter, distinctOptions) -} - -func (collWrapper *collectionWrapper) FindOne(filter interface{}, result interface{}, findOptions *options.FindOneOptions) error { - return collWrapper.FindOneWithContext(context.Background(), filter, result, findOptions) -} - -func (collWrapper *collectionWrapper) FindOneWithContext(ctx context.Context, filter interface{}, result interface{}, findOptions *options.FindOneOptions) error { ctx, cancel := context.WithTimeout(ctx, collWrapper.database.mongoTimeout) defer cancel() @@ -91,11 +76,10 @@ func (collWrapper *collectionWrapper) FindOneWithContext(ctx context.Context, fi return nil } -func (collWrapper *collectionWrapper) ReplaceOne(filter interface{}, replacement interface{}, replaceOptions *options.ReplaceOptions) error { - return collWrapper.ReplaceOneWithContext(context.Background(), filter, replacement, replaceOptions) -} - -func (collWrapper *collectionWrapper) ReplaceOneWithContext(ctx context.Context, filter interface{}, replacement interface{}, replaceOptions *options.ReplaceOptions) error { +func (collWrapper *collectionWrapper) ReplaceOne(ctx context.Context, filter interface{}, replacement interface{}, replaceOptions *options.ReplaceOptions) error { + if ctx == nil { + ctx = context.Background() + } ctx, cancel := context.WithTimeout(ctx, collWrapper.database.mongoTimeout) defer cancel() @@ -113,37 +97,36 @@ func (collWrapper *collectionWrapper) ReplaceOneWithContext(ctx context.Context, if res == nil { return errors.New("replace one - res is nil") } - matchedCount := res.MatchedCount - if matchedCount == 0 { - return errors.New("replace one - no record replaced") + if replaceOptions.Upsert == nil || !*replaceOptions.Upsert { + matchedCount := res.MatchedCount + if matchedCount == 0 { + return errors.New("replace one - no record replaced") + } } - return nil -} -func (collWrapper *collectionWrapper) InsertOne(data interface{}) (interface{}, error) { - return collWrapper.InsertOneWithContext(context.Background(), data) + return nil } -func (collWrapper *collectionWrapper) InsertOneWithContext(ctx context.Context, data interface{}) (interface{}, error) { +func (collWrapper *collectionWrapper) InsertOne(ctx context.Context, data interface{}) (interface{}, error) { + if ctx == nil { + ctx = context.Background() + } ctx, cancel := context.WithTimeout(ctx, collWrapper.database.mongoTimeout) ins, err := collWrapper.coll.InsertOne(ctx, data) cancel() if err == nil { - if id, ok := ins.InsertedID.(interface{}); ok { - return id, nil - } + return ins.InsertedID, nil } return nil, err } -func (collWrapper *collectionWrapper) InsertMany(documents []interface{}, opts *options.InsertManyOptions) (*mongo.InsertManyResult, error) { - return collWrapper.InsertManyWithContext(context.Background(), documents, opts) -} - -func (collWrapper *collectionWrapper) InsertManyWithContext(ctx context.Context, documents []interface{}, opts *options.InsertManyOptions) (*mongo.InsertManyResult, error) { +func (collWrapper *collectionWrapper) InsertMany(ctx context.Context, documents []interface{}, opts *options.InsertManyOptions) (*mongo.InsertManyResult, error) { + if ctx == nil { + ctx = context.Background() + } ctx, cancel := context.WithTimeout(ctx, collWrapper.database.mongoTimeout) defer cancel() @@ -155,11 +138,11 @@ func (collWrapper *collectionWrapper) InsertManyWithContext(ctx context.Context, return result, nil } -func (collWrapper *collectionWrapper) DeleteMany(filter interface{}, opts *options.DeleteOptions) (*mongo.DeleteResult, error) { - return collWrapper.DeleteManyWithContext(context.Background(), filter, opts) -} +func (collWrapper *collectionWrapper) DeleteMany(ctx context.Context, filter interface{}, opts *options.DeleteOptions) (*mongo.DeleteResult, error) { + if ctx == nil { + ctx = context.Background() + } -func (collWrapper *collectionWrapper) DeleteManyWithContext(ctx context.Context, filter interface{}, opts *options.DeleteOptions) (*mongo.DeleteResult, error) { ctx, cancel := context.WithTimeout(ctx, collWrapper.database.mongoTimeout) defer cancel() @@ -171,11 +154,10 @@ func (collWrapper *collectionWrapper) DeleteManyWithContext(ctx context.Context, return result, nil } -func (collWrapper *collectionWrapper) DeleteOne(filter interface{}, opts *options.DeleteOptions) (*mongo.DeleteResult, error) { - return collWrapper.DeleteOneWithContext(context.Background(), filter, opts) -} - -func (collWrapper *collectionWrapper) DeleteOneWithContext(ctx context.Context, filter interface{}, opts *options.DeleteOptions) (*mongo.DeleteResult, error) { +func (collWrapper *collectionWrapper) DeleteOne(ctx context.Context, filter interface{}, opts *options.DeleteOptions) (*mongo.DeleteResult, error) { + if ctx == nil { + ctx = context.Background() + } ctx, cancel := context.WithTimeout(ctx, collWrapper.database.mongoTimeout) defer cancel() @@ -187,15 +169,29 @@ func (collWrapper *collectionWrapper) DeleteOneWithContext(ctx context.Context, return result, nil } -func (collWrapper *collectionWrapper) UpdateOne(filter interface{}, update interface{}, opts *options.UpdateOptions) (*mongo.UpdateResult, error) { - return collWrapper.UpdateOneWithContext(context.Background(), filter, update, opts) +func (collWrapper *collectionWrapper) UpdateOne(ctx context.Context, filter interface{}, update interface{}, opts *options.UpdateOptions) (*mongo.UpdateResult, error) { + if ctx == nil { + ctx = context.Background() + } + ctx, cancel := context.WithTimeout(ctx, collWrapper.database.mongoTimeout) + defer cancel() + + updateResult, err := collWrapper.coll.UpdateOne(ctx, filter, update, opts) + if err != nil { + return nil, err + } + + return updateResult, nil } -func (collWrapper *collectionWrapper) UpdateOneWithContext(ctx context.Context, filter interface{}, update interface{}, opts *options.UpdateOptions) (*mongo.UpdateResult, error) { +func (collWrapper *collectionWrapper) UpdateMany(ctx context.Context, filter interface{}, update interface{}, opts *options.UpdateOptions) (*mongo.UpdateResult, error) { + if ctx == nil { + ctx = context.Background() + } ctx, cancel := context.WithTimeout(ctx, collWrapper.database.mongoTimeout) defer cancel() - updateResult, err := collWrapper.coll.UpdateOne(ctx, filter, update, opts) + updateResult, err := collWrapper.coll.UpdateMany(ctx, filter, update, opts) if err != nil { return nil, err } @@ -203,8 +199,29 @@ func (collWrapper *collectionWrapper) UpdateOneWithContext(ctx context.Context, return updateResult, nil } -func (collWrapper *collectionWrapper) CountDocuments(filter interface{}) (int64, error) { - ctx, cancel := context.WithTimeout(context.Background(), collWrapper.database.mongoTimeout) +func (collWrapper *collectionWrapper) FindOneAndUpdate(ctx context.Context, filter interface{}, update interface{}, result interface{}, opts *options.FindOneAndUpdateOptions) error { + if ctx == nil { + ctx = context.Background() + } + ctx, cancel := context.WithTimeout(ctx, collWrapper.database.mongoTimeout) + defer cancel() + + singleResult := collWrapper.coll.FindOneAndUpdate(ctx, filter, update, opts) + if singleResult.Err() != nil { + return singleResult.Err() + } + err := singleResult.Decode(result) + if err != nil { + return err + } + return nil +} + +func (collWrapper *collectionWrapper) CountDocuments(ctx context.Context, filter interface{}) (int64, error) { + if ctx == nil { + ctx = context.Background() + } + ctx, cancel := context.WithTimeout(ctx, collWrapper.database.mongoTimeout) defer cancel() if filter == nil { @@ -219,60 +236,49 @@ func (collWrapper *collectionWrapper) CountDocuments(filter interface{}) (int64, return count, nil } -func (collWrapper *collectionWrapper) Watch(pipeline interface{}) error { - if pipeline == nil { - pipeline = []bson.M{} +func (collWrapper *collectionWrapper) Aggregate(ctx context.Context, pipeline interface{}, result interface{}, ops *options.AggregateOptions) error { + if ctx == nil { + ctx = context.Background() } + ctx, cancel := context.WithTimeout(ctx, time.Millisecond*15000) + defer cancel() - var opts *options.ChangeStreamOptions - opts = options.ChangeStream() - opts.SetFullDocument(options.UpdateLookup) - - ctx := context.Background() - cur, err := collWrapper.coll.Watch(ctx, pipeline, opts) - if err != nil { - log.Printf("error watching: %s\n", err) - return err - } - defer cur.Close(ctx) + cursor, err := collWrapper.coll.Aggregate(ctx, pipeline, ops) - var changeDoc map[string]interface{} - log.Println("waiting for changes") - for cur.Next(ctx) { - if e := cur.Decode(&changeDoc); e != nil { - log.Printf("error decoding: %s\n", e) - } - collWrapper.database.onDataChanged(changeDoc) + if err == nil { + err = cursor.All(ctx, result) } - if err := cur.Err(); err != nil { - log.Printf("error cur.Err(): %s\n", err) - return err - } - return nil + return err } -func (collWrapper *collectionWrapper) ListIndexes() ([]bson.M, error) { - ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond*15000) +func (collWrapper *collectionWrapper) ListIndexes(ctx context.Context, l *logs.Logger) ([]bson.M, error) { + if ctx == nil { + ctx = context.Background() + } + ctx, cancel := context.WithTimeout(ctx, time.Millisecond*15000) defer cancel() indexes, err := collWrapper.coll.Indexes().List(ctx, nil) if err != nil { - log.Printf("error getting indexes list: %s\n", err) + l.Errorf("error getting indexes list: %s\n", err) return nil, err } var list []bson.M err = indexes.All(ctx, &list) if err != nil { - log.Printf("error iterating indexes list: %s\n", err) + l.Errorf("error iterating indexes list: %s\n", err) return nil, err } return list, nil } -func (collWrapper *collectionWrapper) AddIndex(keys interface{}, unique bool) error { - ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond*15000) +func (collWrapper *collectionWrapper) AddIndex(ctx context.Context, keys interface{}, unique bool) error { + if ctx == nil { + ctx = context.Background() + } + ctx, cancel := context.WithTimeout(ctx, time.Millisecond*15000) defer cancel() index := mongo.IndexModel{Keys: keys} @@ -287,8 +293,11 @@ func (collWrapper *collectionWrapper) AddIndex(keys interface{}, unique bool) er return err } -func (collWrapper *collectionWrapper) AddIndexWithOptions(keys interface{}, opt *options.IndexOptions) error { - ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond*15000) +func (collWrapper *collectionWrapper) AddIndexWithOptions(ctx context.Context, keys interface{}, opt *options.IndexOptions) error { + if ctx == nil { + ctx = context.Background() + } + ctx, cancel := context.WithTimeout(ctx, time.Millisecond*15000) defer cancel() index := mongo.IndexModel{Keys: keys} @@ -299,8 +308,11 @@ func (collWrapper *collectionWrapper) AddIndexWithOptions(keys interface{}, opt return err } -func (collWrapper *collectionWrapper) DropIndex(name string) error { - ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond*15000) +func (collWrapper *collectionWrapper) DropIndex(ctx context.Context, name string) error { + if ctx == nil { + ctx = context.Background() + } + ctx, cancel := context.WithTimeout(ctx, time.Millisecond*15000) defer cancel() _, err := collWrapper.coll.Indexes().DropOne(ctx, name, nil) @@ -308,26 +320,63 @@ func (collWrapper *collectionWrapper) DropIndex(name string) error { return err } -func (collWrapper *collectionWrapper) Aggregate(pipeline interface{}, result interface{}, ops *options.AggregateOptions) error { - ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond*15000) +func (collWrapper *collectionWrapper) Drop(ctx context.Context) error { + if ctx == nil { + ctx = context.Background() + } + ctx, cancel := context.WithTimeout(ctx, collWrapper.database.mongoTimeout) defer cancel() - cursor, err := collWrapper.coll.Aggregate(ctx, pipeline, ops) - - if err == nil { - err = cursor.All(ctx, result) + err := collWrapper.coll.Drop(ctx) + if err != nil { + return err } + return nil +} - return err +func (collWrapper *collectionWrapper) Watch(pipeline interface{}, l *logs.Logger) { + var rt bson.Raw + var err error + for { + rt, err = collWrapper.watch(pipeline, rt, l) + if err != nil { + l.Errorf("mongo watch error: %s\n", err.Error()) + } + } } -func (collWrapper *collectionWrapper) Drop() error { - ctx, cancel := context.WithTimeout(context.Background(), collWrapper.database.mongoTimeout) - defer cancel() +// Helper function for Watch +func (collWrapper *collectionWrapper) watch(pipeline interface{}, resumeToken bson.Raw, l *logs.Logger) (bson.Raw, error) { + if pipeline == nil { + pipeline = []bson.M{} + } - err := collWrapper.coll.Drop(ctx) + opts := options.ChangeStream() + opts.SetFullDocument(options.UpdateLookup) + if resumeToken != nil { + opts.SetResumeAfter(resumeToken) + } + + ctx := context.Background() + cur, err := collWrapper.coll.Watch(ctx, pipeline, opts) if err != nil { - return err + time.Sleep(time.Second * 3) + return nil, fmt.Errorf("error watching: %s", err) } - return nil + defer cur.Close(ctx) + + var changeDoc map[string]interface{} + l.Infof("%s: waiting for changes\n", collWrapper.coll.Name()) + for cur.Next(ctx) { + if e := cur.Decode(&changeDoc); e != nil { + l.Errorf("error decoding: %s\n", e) + } + collWrapper.database.onDataChanged(changeDoc) + } + + if err := cur.Err(); err != nil { + return cur.ResumeToken(), fmt.Errorf("error cur.Err(): %s", err) + } + + return cur.ResumeToken(), errors.New("unknown error occurred") } diff --git a/driven/storage/database.go b/driven/storage/database.go index 7dabb459..6342775b 100644 --- a/driven/storage/database.go +++ b/driven/storage/database.go @@ -15,10 +15,17 @@ package storage import ( + "application/core/interfaces" "context" - "log" "time" + "github.com/rokwire/logging-library-go/v2/errors" + "github.com/rokwire/logging-library-go/v2/logs" + "github.com/rokwire/logging-library-go/v2/logutils" + + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/bson/primitive" + "go.mongodb.org/mongo-driver/mongo" "go.mongodb.org/mongo-driver/mongo/options" ) @@ -30,17 +37,21 @@ type database struct { db *mongo.Database dbClient *mongo.Client + logger *logs.Logger + + configs *collectionWrapper + examples *collectionWrapper - dinings *collectionWrapper + listeners []interfaces.StorageListener } -func (m *database) start() error { +func (d *database) start() error { - log.Println("database -> start") + d.logger.Info("database -> start") //connect to the database - clientOptions := options.Client().ApplyURI(m.mongoDBAuth) - connectContext, cancel := context.WithTimeout(context.Background(), m.mongoTimeout) + clientOptions := options.Client().ApplyURI(d.mongoDBAuth) + connectContext, cancel := context.WithTimeout(context.Background(), d.mongoTimeout) client, err := mongo.Connect(connectContext, clientOptions) cancel() if err != nil { @@ -48,7 +59,7 @@ func (m *database) start() error { } //ping the database - pingContext, cancel := context.WithTimeout(context.Background(), m.mongoTimeout) + pingContext, cancel := context.WithTimeout(context.Background(), d.mongoTimeout) err = client.Ping(pingContext, nil) cancel() if err != nil { @@ -56,56 +67,62 @@ func (m *database) start() error { } //apply checks - db := client.Database(m.mongoDBName) + db := client.Database(d.mongoDBName) - dinings := &collectionWrapper{database: m, coll: db.Collection("dinings")} - err = m.applyDiningsChecks(dinings) + configs := &collectionWrapper{database: d, coll: db.Collection("configs")} + err = d.applyConfigsChecks(configs) if err != nil { return err } - //asign the db, db client and the collections - m.db = db - m.dbClient = client + examples := &collectionWrapper{database: d, coll: db.Collection("examples")} + err = d.applyExamplesChecks(examples) + if err != nil { + return err + } - m.dinings = dinings + //assign the db, db client and the collections + d.db = db + d.dbClient = client + + d.configs = configs + d.examples = examples + + go d.configs.Watch(nil, d.logger) return nil } -// Create indexes if need -func (m *database) applyDiningsChecks(users *collectionWrapper) error { - log.Println("apply dining checks.....") +func (d *database) applyConfigsChecks(configs *collectionWrapper) error { + d.logger.Info("apply configs checks.....") - indexes, _ := users.ListIndexes() - indexMapping := map[string]interface{}{} - if indexes != nil { - for _, index := range indexes { - name := index["name"].(string) - indexMapping[name] = index - } + err := configs.AddIndex(nil, bson.D{primitive.E{Key: "type", Value: 1}, primitive.E{Key: "app_id", Value: 1}, primitive.E{Key: "org_id", Value: 1}}, true) + if err != nil { + return err } - /* - if indexMapping["field_1"] == nil { - err := users.AddIndex( - bson.D{ - primitive.E{Key: "field_id", Value: 1}, - }, true) - if err != nil { - return err - } - }*/ - - log.Println("apply dining passed") + d.logger.Info("apply configs passed") return nil } -func (m *database) onDataChanged(changeDoc map[string]interface{}) { +func (d *database) applyExamplesChecks(examples *collectionWrapper) error { + d.logger.Info("apply examples checks.....") + + //add compound unique index - org_id + app_id + err := examples.AddIndex(nil, bson.D{primitive.E{Key: "org_id", Value: 1}, primitive.E{Key: "app_id", Value: 1}}, false) + if err != nil { + return errors.WrapErrorAction(logutils.ActionCreate, "index", nil, err) + } + + d.logger.Info("apply examples passed") + return nil +} + +func (d *database) onDataChanged(changeDoc map[string]interface{}) { if changeDoc == nil { return } - log.Printf("onDataChanged: %+v\n", changeDoc) + d.logger.Infof("onDataChanged: %+v\n", changeDoc) ns := changeDoc["ns"] if ns == nil { return @@ -113,9 +130,18 @@ func (m *database) onDataChanged(changeDoc map[string]interface{}) { nsMap := ns.(map[string]interface{}) coll := nsMap["coll"] - if "configs" == coll { - log.Println("configs collection changed") - } else { - log.Println("other collection changed") + switch coll { + case "configs": + d.logger.Info("configs collection changed") + + for _, listener := range d.listeners { + go listener.OnConfigsUpdated() + } + case "examples": + d.logger.Info("examples collection changed") + + for _, listener := range d.listeners { + go listener.OnExamplesUpdated() + } } } diff --git a/driver/web/rest/adminapis.go b/driven/storage/listener.go similarity index 66% rename from driver/web/rest/adminapis.go rename to driven/storage/listener.go index 7a33243d..42291c63 100644 --- a/driver/web/rest/adminapis.go +++ b/driven/storage/listener.go @@ -12,18 +12,15 @@ // See the License for the specific language governing permissions and // limitations under the License. -package rest +package storage -import ( - "apigateway/core" -) +import "application/core/model" -// AdminApisHandler handles the rest Admin APIs implementation -type AdminApisHandler struct { - app *core.Application +type storageListener struct { + adapter *Adapter + model.DefaultStorageListener } -// NewAdminApisHandler creates new rest Handler instance -func NewAdminApisHandler(app *core.Application) AdminApisHandler { - return AdminApisHandler{app: app} +func (s *storageListener) OnConfigsUpdated() { + s.adapter.cacheConfigs() } diff --git a/driven/terms/uiuc_termsessions.go b/driven/terms/uiuc_termsessions.go deleted file mode 100644 index c6a087c7..00000000 --- a/driven/terms/uiuc_termsessions.go +++ /dev/null @@ -1,53 +0,0 @@ -// Copyright 2022 Board of Trustees of the University of Illinois. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package terms - -import ( - model "apigateway/core/model" - "strconv" - "time" -) - -// TermSessionAdapter is a uiuc specific structure that implements the term session interface -type TermSessionAdapter struct { -} - -// NewTermSessionAdapter returns a vendor specific implementation of the contanct information interface -func NewTermSessionAdapter() *TermSessionAdapter { - return &TermSessionAdapter{} - -} - -// GetTermSessions returns a list of term sessions to the client -func (lv *TermSessionAdapter) GetTermSessions() (*[4]model.TermSession, error) { - var termSessions [4]model.TermSession - //if beginning of June make fall term default, spring semester has ended - crntDate := time.Now() - crntYear := strconv.Itoa(crntDate.Year()) - if crntDate.Month() >= 6 && crntDate.Month() <= 12 { - nextYear := strconv.Itoa(crntDate.Year() + 1) - termSessions[2] = model.TermSession{Term: "Fall - " + crntYear, TermID: "1" + crntYear + "8", CurrentTerm: true} - termSessions[1] = model.TermSession{Term: "Summer - " + crntYear, TermID: "1" + crntYear + "5", CurrentTerm: false} - termSessions[0] = model.TermSession{Term: "Spring - " + crntYear, TermID: "1" + crntYear + "1", CurrentTerm: false} - termSessions[3] = model.TermSession{Term: "Spring - " + nextYear, TermID: "1" + nextYear + "1", CurrentTerm: false} - } else { - pastYear := strconv.Itoa(crntDate.Year() - 1) - termSessions[1] = model.TermSession{Term: "Spring - " + crntYear, TermID: "1" + crntYear + "1", CurrentTerm: true} - termSessions[2] = model.TermSession{Term: "Summer - " + crntYear, TermID: "1" + crntYear + "5", CurrentTerm: false} - termSessions[0] = model.TermSession{Term: "Fall - " + pastYear, TermID: "1" + pastYear + "8", CurrentTerm: false} - termSessions[3] = model.TermSession{Term: "Fall - " + crntYear, TermID: "1" + crntYear + "8", CurrentTerm: false} - } - return &termSessions, nil -} diff --git a/driven/uiucadapters/contactadapter.go b/driven/uiucadapters/contactadapter.go new file mode 100644 index 00000000..7c160066 --- /dev/null +++ b/driven/uiucadapters/contactadapter.go @@ -0,0 +1,119 @@ +// Copyright 2022 Board of Trustees of the University of Illinois. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package uiucadapters + +import ( + model "application/core/model" + uiuc "application/core/model/uiuc" + "encoding/json" + "errors" + "fmt" + "io" + "net/http" +) + +// UIUCContactAdapter is a vendor specific structure that implements the contanct information interface +type UIUCContactAdapter struct { +} + +// NewUIUCContactAdapter returns a vendor specific implementation of the contanct information interface +func NewUIUCContactAdapter() UIUCContactAdapter { + return UIUCContactAdapter{} + +} + +// GetContactInformation returns a contact information object for a student +func (lv UIUCContactAdapter) GetContactInformation(uin string, accessToken string, mode string, conf *model.EnvConfigData) (*model.Person, int, error) { + + campusAPI := conf.CentralCampusURL + campusKey := conf.CentralCampusKey + finalURL := campusAPI + "/person/contact-summary-query/" + uin + + if mode != "0" { + finalURL = campusAPI + "/mock/123456789" + } + + campusData, statusCode, err := lv.getData(finalURL, accessToken, campusKey) + if err != nil { + return nil, statusCode, err + } + + if len(campusData.People) == 0 { + return nil, 404, errors.New("no contact data found") + } + + retValue, err := uiuc.NewPerson(&campusData.People[0]) + if err != nil { + return nil, http.StatusInternalServerError, err + } + return retValue, statusCode, nil +} + +func (lv UIUCContactAdapter) getData(targetURL string, accessToken string, apikey string) (*uiuc.CampusUserData, int, error) { + method := "GET" + + client := &http.Client{} + req, err := http.NewRequest(method, targetURL, nil) + + if err != nil { + return nil, http.StatusInternalServerError, err + } + + req.Header.Add("Authorization", "Bearer "+accessToken) + req.Header.Set("Ocp-Apim-Subscription-Key", apikey) + res, err := client.Do(req) + if err != nil { + return nil, res.StatusCode, err + } + defer res.Body.Close() + + body, err := io.ReadAll(res.Body) + if err != nil { + return nil, res.StatusCode, err + } + + if res.StatusCode == 401 { + return nil, res.StatusCode, errors.New(res.Status) + } + + if res.StatusCode == 403 { + return nil, res.StatusCode, errors.New(res.Status) + } + + if res.StatusCode == 400 { + return nil, res.StatusCode, errors.New("bad request to api end point") + } + + if res.StatusCode == 406 { + return nil, res.StatusCode, errors.New("server returned 406: possible uin claim mismatch") + } + + //campus api returns a 502 when there is no banner contact data for the uin + if res.StatusCode == 502 { + return nil, 404, errors.New(res.Status) + } + if res.StatusCode == 200 { + data := uiuc.CampusUserData{} + err = json.Unmarshal(body, &data) + + if err != nil { + return nil, res.StatusCode, err + } + return &data, res.StatusCode, nil + } + + return nil, res.StatusCode, errors.New("Error making request: " + fmt.Sprint(res.StatusCode) + ": " + string(body)) + +} diff --git a/driven/uiucadapters/courseadapter.go b/driven/uiucadapters/courseadapter.go new file mode 100644 index 00000000..a7bb420b --- /dev/null +++ b/driven/uiucadapters/courseadapter.go @@ -0,0 +1,237 @@ +// Copyright 2022 Board of Trustees of the University of Illinois. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package uiucadapters + +import ( + model "application/core/model" + uiuc "application/core/model/uiuc" + "encoding/json" + "errors" + "fmt" + "io" + "net/http" + "strconv" + "time" +) + +// StudentCourseAdapter is a vendor specific structure that implements the UIUC and GiesCourse lookup interface +type StudentCourseAdapter struct { +} + +// NewCourseAdapter returns a vendor specific implementation of the Course lookup interface +func NewCourseAdapter() StudentCourseAdapter { + return StudentCourseAdapter{} + +} + +// GetStudentCourses returns a list of courses for the given tudent +func (lv StudentCourseAdapter) GetStudentCourses(uin string, termid string, accessToken string, conf *model.EnvConfigData) (*[]model.Course, int, error) { + + courseAPIEP := conf.CentralCampusURL + courseAuth := conf.CentralCampusKey + finalURL := courseAPIEP + "/student-registration/student-enrollment-query/v2_0/" + uin + "/" + termid + + retValue := make([]model.Course, 0) + + campusData, statusCode, err := lv.getData(finalURL, accessToken, courseAuth) + if err != nil { + return nil, statusCode, err + } + + if len(campusData.List) == 0 { + return nil, 404, errors.New("no course data found") + } + + if len(campusData.List[0].CourseRegistration) == 0 { + return nil, 404, errors.New("no course data found") + } + + for i := 0; i < len(campusData.List[0].CourseRegistration); i++ { + course := campusData.List[0].CourseRegistration[i] + if course.ValidRegistrationStatusType.Code == "R" { + for i := 0; i < len(course.CourseSection.CourseSectionSession); i++ { + retValue = append(retValue, *uiuc.NewCourse(course, i)) + } + } + } + + if err != nil { + return nil, http.StatusInternalServerError, err + } + return &retValue, statusCode, nil +} + +// GetTermSessions returns a list of term sessions to the client +func (lv StudentCourseAdapter) GetTermSessions() (*[4]model.TermSession, error) { + var termSessions [4]model.TermSession + //if beginning of June make fall term default, spring semester has ended + crntDate := time.Now() + crntYear := strconv.Itoa(crntDate.Year()) + if crntDate.Month() >= 6 && crntDate.Month() <= 12 { + nextYear := strconv.Itoa(crntDate.Year() + 1) + termSessions[2] = model.TermSession{Term: "Fall - " + crntYear, TermID: "1" + crntYear + "8", CurrentTerm: true} + termSessions[1] = model.TermSession{Term: "Summer - " + crntYear, TermID: "1" + crntYear + "5", CurrentTerm: false} + termSessions[0] = model.TermSession{Term: "Spring - " + crntYear, TermID: "1" + crntYear + "1", CurrentTerm: false} + termSessions[3] = model.TermSession{Term: "Spring - " + nextYear, TermID: "1" + nextYear + "1", CurrentTerm: false} + } else { + pastYear := strconv.Itoa(crntDate.Year() - 1) + termSessions[1] = model.TermSession{Term: "Spring - " + crntYear, TermID: "1" + crntYear + "1", CurrentTerm: true} + termSessions[2] = model.TermSession{Term: "Summer - " + crntYear, TermID: "1" + crntYear + "5", CurrentTerm: false} + termSessions[0] = model.TermSession{Term: "Fall - " + pastYear, TermID: "1" + pastYear + "8", CurrentTerm: false} + termSessions[3] = model.TermSession{Term: "Fall - " + crntYear, TermID: "1" + crntYear + "8", CurrentTerm: false} + } + return &termSessions, nil +} + +// GetGiesCourses returns a list of courses for the given GIES student +func (lv StudentCourseAdapter) GetGiesCourses(uin string, accessToken string, conf *model.EnvConfigData) (*[]model.GiesCourse, int, error) { + + GiesURL := conf.GiesCourseURL + + finalURL := GiesURL + "/" + uin + + retValue := make([]model.GiesCourse, 0) + + campusData, statusCode, err := lv.getGiesData(finalURL, accessToken) + if err != nil { + return nil, statusCode, err + } + + if len(campusData) == 0 { + return nil, 404, errors.New("no course data found") + } + + for i := 0; i < len(campusData); i++ { + course := campusData[i] + retValue = append(retValue, *uiuc.NewGiesCourse(course)) + } + + if err != nil { + return nil, http.StatusInternalServerError, err + } + return &retValue, statusCode, nil +} + +func (lv StudentCourseAdapter) getData(targetURL string, accessToken string, authKey string) (*uiuc.CampusData, int, error) { + method := "GET" + + client := &http.Client{} + req, err := http.NewRequest(method, targetURL, nil) + + if err != nil { + return nil, http.StatusInternalServerError, err + } + + req.Header.Add("Authorization", "Bearer "+accessToken) + req.Header.Set("Ocp-Apim-Subscription-Key", authKey) + + res, err := client.Do(req) + if err != nil { + return nil, res.StatusCode, err + } + defer res.Body.Close() + + body, err := io.ReadAll(res.Body) + if err != nil { + return nil, res.StatusCode, err + } + + if res.StatusCode == 401 { + return nil, res.StatusCode, errors.New(res.Status) + } + + if res.StatusCode == 403 { + return nil, res.StatusCode, errors.New(res.Status) + } + + if res.StatusCode == 400 { + return nil, res.StatusCode, errors.New("bad request to api end point") + } + + if res.StatusCode == 406 { + return nil, res.StatusCode, errors.New("server returned 406: possible uin claim mismatch") + } + //campus api returns a 502 when there is no course data + if res.StatusCode == 502 { + return nil, 404, errors.New(res.Status) + } + + if res.StatusCode == 200 || res.StatusCode == 203 { + data := uiuc.CampusData{} + + err = json.Unmarshal(body, &data) + + if err != nil { + return nil, res.StatusCode, err + } + return &data, res.StatusCode, nil + } + + return nil, res.StatusCode, errors.New("Error making request: " + fmt.Sprint(res.StatusCode) + ": " + string(body)) + +} + +func (lv StudentCourseAdapter) getGiesData(targetURL string, accessToken string) ([]uiuc.GIESCourse, int, error) { + method := "GET" + + client := &http.Client{} + req, err := http.NewRequest(method, targetURL, nil) + + if err != nil { + return nil, http.StatusInternalServerError, err + } + + req.Header.Add("access_token", accessToken) + res, err := client.Do(req) + if err != nil { + return nil, res.StatusCode, err + } + defer res.Body.Close() + + body, err := io.ReadAll(res.Body) + if err != nil { + return nil, res.StatusCode, err + } + + if res.StatusCode == 401 { + return nil, res.StatusCode, errors.New(res.Status) + } + + if res.StatusCode == 403 { + return nil, res.StatusCode, errors.New(res.Status) + } + + if res.StatusCode == 400 { + return nil, res.StatusCode, errors.New("bad request to api end point") + } + + if res.StatusCode == 406 { + return nil, res.StatusCode, errors.New("server returned 406: possible uin claim mismatch") + } + + if res.StatusCode == 200 { + data := make([]uiuc.GIESCourse, 0) + + err = json.Unmarshal(body, &data) + + if err != nil { + return nil, res.StatusCode, err + } + return data, res.StatusCode, nil + } + + return nil, res.StatusCode, errors.New("Error making request: " + fmt.Sprint(res.StatusCode) + ": " + string(body)) + +} diff --git a/driven/uiucadapters/engappointmentsadapter.go b/driven/uiucadapters/engappointmentsadapter.go new file mode 100644 index 00000000..d77f9cff --- /dev/null +++ b/driven/uiucadapters/engappointmentsadapter.go @@ -0,0 +1,314 @@ +// Copyright 2022 Board of Trustees of the University of Illinois. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package uiucadapters + +import ( + model "application/core/model" + uiuc "application/core/model/uiuc" + "bytes" + "encoding/json" + "errors" + "fmt" + "io" + "mime/multipart" + "net/http" + "strconv" + "strings" + "time" +) + +// EngineeringAppointmentsAdapter is a college of engineering implementation of the driven/appointments adapter +type EngineeringAppointmentsAdapter struct { +} + +// NewEngineeringAppontmentsAdapter returns a vendor specific implementation of the Appointments interface +func NewEngineeringAppontmentsAdapter() EngineeringAppointmentsAdapter { + return EngineeringAppointmentsAdapter{} + +} + +// GetUnits returns a list of courses for the given tudent +func (lv EngineeringAppointmentsAdapter) GetUnits(uin string, accessToken string, providerid int, conf *model.EnvConfigData) (*[]model.AppointmentUnit, error) { + + baseURL := conf.EngAppointmentBaseURL + finalURL := baseURL + "users/" + uin + "/calendars" + var headers = make(map[string]string) + headers["Authorization"] = "Bearer " + accessToken + + vendorData, err := lv.getVendorData(finalURL, "GET", headers, nil) + if err != nil { + return nil, err + } + + var calendars []uiuc.EngineeringCalendar + err = json.Unmarshal(vendorData, &calendars) + if err != nil { + return nil, err + } + + s := make([]model.AppointmentUnit, 0) + + for i := 0; i < len(calendars); i++ { + calendar := calendars[i] + au := model.AppointmentUnit{ID: calendar.ID, ProviderID: providerid, Name: calendar.Name, Location: "", HoursOfOperation: "", Details: "", NextAvailable: "", ImageURL: ""} + s = append(s, au) + } + + return &s, nil +} + +// GetPeople returns a list of people with appointment calendars from engineering +func (lv EngineeringAppointmentsAdapter) GetPeople(uin string, unitID int, providerid int, accesstoken string, conf *model.EnvConfigData) (*[]model.AppointmentPerson, error) { + baseURL := conf.EngAppointmentBaseURL + finalURL := baseURL + "users/" + uin + "/calendars/" + strconv.FormatInt(int64(unitID), 10) + "/advisors" + var headers = make(map[string]string) + headers["Authorization"] = "Bearer " + accesstoken + + vendorData, err := lv.getVendorData(finalURL, "GET", headers, nil) + if err != nil { + return nil, err + } + + var advisors []uiuc.EngineeringAdvisor + err = json.Unmarshal(vendorData, &advisors) + if err != nil { + return nil, err + } + + s := make([]model.AppointmentPerson, 0) + + for i := 0; i < len(advisors); i++ { + advisor := advisors[i] + p := model.AppointmentPerson{ID: advisor.ID, ProviderID: providerid, UnitID: unitID, Notes: advisor.Message, Name: advisor.Name, NextAvailable: advisor.NextAvailableDate, ImageURL: ""} + s = append(s, p) + } + + return &s, nil +} + +// GetTimeSlots returns an object consisting of the time slots and questions for a given personid between startdate and enddate +func (lv EngineeringAppointmentsAdapter) GetTimeSlots(uin string, unitID int, advisorid int, providerid int, startdate time.Time, enddate time.Time, accesstoken string, conf *model.EnvConfigData) (*model.AppointmentOptions, error) { + baseURL := conf.EngAppointmentBaseURL + //baseURL := "https://myengr.test.engr.illinois.edu/advisingws/api/" + finalURL := baseURL + "users/" + uin + "/calendars/" + strconv.FormatInt(int64(unitID), 10) + "/advisors/" + strconv.FormatInt(int64(advisorid), 10) + "/appointments" + var headers = make(map[string]string) + headers["Authorization"] = "Bearer " + accesstoken + + vendorData, err := lv.getVendorData(finalURL, "GET", headers, nil) + if err != nil { + return nil, err + } + + var options uiuc.EngineeringAdvisorAppointments + err = json.Unmarshal(vendorData, &options) + if err != nil { + return nil, err + } + + ts := make([]model.TimeSlot, 0) + qu := make([]model.Question, 0) + if !startdate.IsZero() && !enddate.IsZero() { + + const timeLayout = "2006-01-02T15:04:00" + for i := 0; i < len(options.TimeSlots); i++ { + timeslot := options.TimeSlots[i] + slotStartDate, _ := time.Parse(timeLayout, timeslot.StartDate) + slotStartDateOnly, _, _ := strings.Cut(timeslot.StartDate, "T") + slotEndDateOnly, _, _ := strings.Cut(timeslot.EndDate, "T") + + slotStartDatepart, _ := time.Parse(time.DateOnly, slotStartDateOnly) + slotEndDatePart, _ := time.Parse(time.DateOnly, slotEndDateOnly) + + if (slotStartDatepart.Equal(startdate) || slotEndDatePart.Equal(enddate)) || (slotStartDate.After(startdate) && slotStartDate.Before(enddate)) { + t := model.TimeSlot{ID: timeslot.ID, EndTime: timeslot.EndDate, StartTime: timeslot.StartDate, UnitID: unitID, ProviderID: providerid, PersonID: advisorid, Capacity: 1, Filled: 0} + ts = append(ts, t) + } + } + } + + for i := 0; i < len(options.Questions); i++ { + question := options.Questions[i] + q := model.Question{ID: question.ID, ProviderID: providerid, Required: true, Type: question.Type, SelectValues: question.SelectionValues, Question: question.Title} + qu = append(qu, q) + } + + returnData := model.AppointmentOptions{Questions: qu, TimeSlots: ts} + return &returnData, nil +} + +// CreateAppointment creates an appointment in the engieering system. +func (lv EngineeringAppointmentsAdapter) CreateAppointment(appt *model.AppointmentPost, accesstoken string, conf *model.EnvConfigData) (*model.BuildingBlockAppointment, error) { + baseURL := conf.EngAppointmentBaseURL + finalURL := baseURL + "Appointment" + var headers = make(map[string]string) + headers["Authorization"] = "Bearer " + accesstoken + headers["Content-Type"] = "application/json" + + slotid, err := strconv.Atoi(appt.SlotID) + if err != nil { + return nil, err + } + eap := engAppointmentPost{UIN: appt.UserExternalIDs.UIN, SlotID: slotid} + eap.Answers = make([]engAppointmentAnswerPost, 0) + + for i := 0; i < len(appt.Answers); i++ { + apptAnswer := appt.Answers[i] + for j := 0; j < len(apptAnswer.Values); j++ { + finalAnswer := apptAnswer.Values[j] + switch finalAnswer { + case "true": + finalAnswer = "X" + case "false": + finalAnswer = "" + } + engAns := engAppointmentAnswerPost{QuestionID: apptAnswer.QuestionID, Value: finalAnswer, UploadID: 0} + eap.Answers = append(eap.Answers, engAns) + } + + } + postData, err := json.Marshal(eap) + if err != nil { + return nil, err + } + payload := strings.NewReader(string(postData)) + _, err = lv.getVendorData(finalURL, "POST", headers, payload) + if err != nil { + return nil, err + } + + retData := model.BuildingBlockAppointment{ProviderID: appt.ProviderID, UnitID: appt.UnitID, PersonID: appt.PersonID, UserExternalIDs: appt.UserExternalIDs, Type: appt.Type, StartTime: appt.StartTime, EndTime: appt.EndTime, SourceID: appt.SlotID} + + return &retData, nil +} + +// UpdateAppointment updates an appointment in the engieering system. +func (lv EngineeringAppointmentsAdapter) UpdateAppointment(appt *model.AppointmentPost, accesstoken string, conf *model.EnvConfigData) (*model.BuildingBlockAppointment, error) { + + slotid, err := strconv.Atoi(appt.SlotID) + if err != nil { + return nil, err + } + + sourceid, err := strconv.Atoi(appt.SourceID) + if err != nil { + return nil, err + } + + if sourceid != slotid { + _, err = lv.DeleteAppointment(appt.UserExternalIDs.UIN, appt.SourceID, accesstoken, conf) + if err != nil { + return nil, err + } + } + + ret, err := lv.CreateAppointment(appt, accesstoken, conf) + if err != nil { + return nil, err + } + + return ret, nil +} + +// DeleteAppointment cancels an appointment in the engineering appointment system +func (lv EngineeringAppointmentsAdapter) DeleteAppointment(uin string, sourceid string, accesstoken string, conf *model.EnvConfigData) (string, error) { + baseURL := conf.EngAppointmentBaseURL + finalURL := baseURL + "Appointment/" + sourceid + + payload := &bytes.Buffer{} + writer := multipart.NewWriter(payload) + _ = writer.WriteField("uin", uin) + err := writer.Close() + if err != nil { + return "", err + } + var headers = make(map[string]string) + headers["Authorization"] = "Bearer " + accesstoken + headers["Content-Type"] = writer.FormDataContentType() + + vendorData, err := lv.getVendorData(finalURL, "DELETE", headers, strings.NewReader(payload.String())) + if err != nil { + return "", err + } + return string(vendorData), nil +} + +func (lv EngineeringAppointmentsAdapter) getVendorData(targetURL string, method string, headers map[string]string, postdata *strings.Reader) ([]byte, error) { + + client := &http.Client{} + + var postbody = io.Reader(nil) + if postdata != nil { + postbody = postdata + } + + req, err := http.NewRequest(method, targetURL, postbody) + + if err != nil { + return nil, err + } + + for key, element := range headers { + req.Header.Add(key, element) + } + + res, err := client.Do(req) + if err != nil { + return nil, err + } + defer res.Body.Close() + + body, err := io.ReadAll(res.Body) + if err != nil { + return nil, err + } + + if res.StatusCode == 400 { + return nil, errors.New(res.Status + " : " + string(body)) + } + if res.StatusCode == 401 { + return nil, errors.New(res.Status + " : " + string(body)) + } + + if res.StatusCode == 403 { + return nil, errors.New(res.Status + ": " + string(body)) + } + + if res.StatusCode == 406 { + return nil, errors.New("server returned 406: possible uin claim mismatch") + } + + if res.StatusCode == 409 { + return nil, errors.New(res.Status + " : " + string(body)) + } + + if res.StatusCode == 200 || res.StatusCode == 201 || res.StatusCode == 204 { + + return body, nil + } + + return nil, errors.New("error making request: " + fmt.Sprint(res.StatusCode) + ": " + string(body)) +} + +type engAppointmentPost struct { + UIN string `json:"uin" bson:"uin"` + SlotID int `json:"slotId" bson:"slotId"` + Answers []engAppointmentAnswerPost `json:"answers" bson:"answers"` +} + +type engAppointmentAnswerPost struct { + QuestionID string `json:"questionId" bson:"questionId"` + Value string `json:"value" bson:"value"` + UploadID int `json:"uploadId" bson:"uploadId"` +} diff --git a/driven/uiucadapters/laundryadapter.go b/driven/uiucadapters/laundryadapter.go new file mode 100644 index 00000000..418dd125 --- /dev/null +++ b/driven/uiucadapters/laundryadapter.go @@ -0,0 +1,430 @@ +// Copyright 2022 Board of Trustees of the University of Illinois. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package uiucadapters + +import ( + model "application/core/model" + uiuc "application/core/model/uiuc" + "encoding/json" + "encoding/xml" + "errors" + "io" + "log" + "net/http" + "strconv" + "strings" +) + +// CSCLaundryView is a vendor specific structure that implements the Laundry interface +type CSCLaundryView struct { + serviceCookie string + laundryAssets map[string]model.LaundryDetails +} + +// NewCSCLaundryAdapter returns a vendor specific implementation of the Laundry interface +func NewCSCLaundryAdapter(assets map[string]model.LaundryDetails) *CSCLaundryView { + return &CSCLaundryView{laundryAssets: assets} + +} + +// ListRooms lists the laundry rooms +func (lv *CSCLaundryView) ListRooms(conf *model.EnvConfigData) (*model.Organization, error) { + + laundryAPI := conf.LaundryViewURL + laundryKey := conf.LaundryViewKey + url := laundryAPI + "/school/?api_key=" + laundryKey + "&method=getRoomData&type=json" + resp, err := http.Get(url) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + if resp.Status == "200 OK" { + body, bodyerr := io.ReadAll(resp.Body) + if bodyerr != nil { + return nil, err + } + + var nS uiuc.School + out := []byte(body) + if err := xml.Unmarshal(out, &nS); err != nil { + log.Fatal("could not unmarshal xml data") + return nil, err + } + org := model.Organization{SchoolName: nS.SchoolName} + org.LaundryRooms = make([]*model.LaundryRoom, 0) + + for _, lr := range nS.LaundryRooms { + if len(lv.laundryAssets) > 0 { + org.LaundryRooms = append(org.LaundryRooms, uiuc.NewLaundryRoom(lr.Location, lr.Laundryroomname, lr.Status, lv.getLocationData(strconv.Itoa(lr.Location)))) + } else { + org.LaundryRooms = append(org.LaundryRooms, uiuc.NewLaundryRoom(lr.Location, lr.Laundryroomname, lr.Status, lv.getLocationData("0"))) + } + } + return &org, nil + } + return nil, err +} + +// GetLaundryRoom returns the room details along with the list of machines in that room +func (lv *CSCLaundryView) GetLaundryRoom(roomid string, conf *model.EnvConfigData) (*model.RoomDetail, error) { + + laundryAPI := conf.LaundryViewURL + laundryKey := conf.LaundryViewKey + url := laundryAPI + "/room/?api_key=" + laundryKey + "&method=getAppliances&location=" + roomid + "&type=json" + resp, err := http.Get(url) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + if resp.Status == "200 OK" { + body, bodyerr := io.ReadAll(resp.Body) + if bodyerr != nil { + return nil, err + } + + var lr uiuc.Laundryroom + out := []byte(body) + if err := xml.Unmarshal(out, &lr); err != nil { + log.Fatal("could not unmarshal xml data") + return nil, err + } + + rd := model.RoomDetail{CampusName: lr.CampusName, RoomName: lr.Name} + rd.Appliances = make([]*model.Appliance, len(lr.Appliances)) + roomCapacity, _ := lv.getNumAvailable(laundryAPI, laundryKey, roomid) + rd.NumDryers = evalNumAvailable(roomCapacity.NumDryers) + rd.NumWashers = evalNumAvailable(roomCapacity.NumWashers) + + for i, appl := range lr.Appliances { + avgCycle, _ := strconv.Atoi(appl.AvgCycleTime) + rd.Appliances[i] = uiuc.NewAppliance(appl.ApplianceKey, appl.ApplianceType, avgCycle, appl.Status, appl.TimeRemaining, appl.Label) + } + + if len(lv.laundryAssets) > 0 { + rd.Location = lv.getLocationData(roomid) + } + return &rd, nil + } + return nil, err +} + +func (lv *CSCLaundryView) getLocationData(roomid string) *model.LaundryDetails { + if asset, ok := lv.laundryAssets[roomid]; ok { + return &asset + } + return &model.LaundryDetails{Latitude: 0, Longitude: 0, Floor: 0} +} + +func (lv *CSCLaundryView) getNumAvailable(apiURL string, apikey string, roomid string) (*uiuc.Capacity, error) { + + url := apiURL + "/room/?api_key=" + apikey + "&method=getNumAvailable&location=" + roomid + "&type=json" + resp, err := http.Get(url) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + if resp.Status == "200 OK" { + body, bodyerr := io.ReadAll(resp.Body) + if bodyerr != nil { + return nil, err + } + + var cap uiuc.Capacity + out := []byte(body) + if err := xml.Unmarshal(out, &cap); err != nil { + log.Fatal("could not unmarshal xml data") + return nil, err + } + + return &cap, nil + } + return nil, err +} + +// InitServiceRequest gets machine request details needed to initialize a laundry service request +func (lv *CSCLaundryView) InitServiceRequest(machineID string, conf *model.EnvConfigData) (*model.MachineRequestDetail, error) { + + serviceURL := conf.LaundyrServiceURL + serviceKey := conf.LaundryServiceKey + serviceAuthToken := conf.LaundryServiceBasicAuth + + authTokens, err := lv.getAuthTokens(serviceURL, serviceKey, serviceAuthToken) + if err != nil { + return nil, err + } + + subscriptionkey := authTokens["SUBSCRIPTIONKEY"] + serviceToken := authTokens["SERVICETOKEN"] + + md, err := lv.getMachineDetails(serviceURL, subscriptionkey, serviceAuthToken, serviceToken, machineID) + if err != nil { + return nil, err + } + + mrd := uiuc.NewMachineRequestDetail(md.MachineID, md.Message, md.RecentServiceStatus, md.MachineType) + mrd.ProblemCodes, err = lv.getProblemCodes(serviceURL, subscriptionkey, serviceToken, serviceAuthToken, md.MachineType) + if err != nil { + return nil, err + } + return mrd, nil +} + +// SubmitServiceRequest submits a request for a machine +func (lv *CSCLaundryView) SubmitServiceRequest(machineid string, problemCode string, comments string, firstName string, lastName string, phone string, email string, conf *model.EnvConfigData) (*model.ServiceRequestResult, error) { + + serviceURL := conf.LaundyrServiceURL + serviceKey := conf.LaundryServiceKey + serviceAuthToken := conf.LaundryServiceBasicAuth + + authTokens, err := lv.getAuthTokens(serviceURL, serviceKey, serviceAuthToken) + if err != nil { + return nil, err + } + + subscriptionkey := authTokens["SUBSCRIPTIONKEY"] + serviceToken := authTokens["SERVICETOKEN"] + + srr, err := lv.submitTicket(serviceURL, subscriptionkey, serviceToken, serviceAuthToken, machineid, problemCode, comments, firstName, lastName, phone, email) + if err != nil { + return nil, err + } + return srr, nil +} + +func (lv *CSCLaundryView) getAuthTokens(serviceURL string, serviceKey string, serviceAuthToken string) (map[string]string, error) { + subscriptionkey, err := lv.getServiceSubscriptionKey(serviceURL, serviceKey, serviceAuthToken) + if err != nil { + return nil, err + } + + serviceToken, err := lv.getServiceToken(serviceURL, serviceAuthToken, subscriptionkey) + if err != nil { + return nil, err + } + + tokens := make(map[string]string) + tokens["SUBSCRIPTIONKEY"] = subscriptionkey + tokens["SERVICETOKEN"] = serviceToken + return tokens, nil + +} + +func (lv *CSCLaundryView) getServiceSubscriptionKey(serviceurl string, serviceKey string, authToken string) (string, error) { + url := serviceurl + "/getSubscriptionKey" + method := "POST" + + payload := `{"subscription-id": "uiuc", "key-type": "primaryKey" }` + + headers := make(map[string]string) + headers["Ocp-Apim-Subscription-Key"] = serviceKey + headers["Content-Type"] = "application/json" + headers["Authorization"] = "Basic " + authToken + + body, err := lv.makeLaundryServiceWebRequest(url, method, headers, payload) + if err != nil { + return "", err + } + + var dat map[string]interface{} + if err := json.Unmarshal(body, &dat); err != nil { + return "", err + } + + if _, keyExists := dat["subscription-key"]; !keyExists { + return "", errors.New("subscription key not returned") + } + + return dat["subscription-key"].(string), nil +} + +func (lv *CSCLaundryView) getServiceToken(serviceurl string, authToken string, subscriptionKey string) (string, error) { + url := serviceurl + "/generateToken?subscription-key=" + subscriptionKey + method := "GET" + + headers := make(map[string]string) + headers["Content-Type"] = "application/json" + headers["Authorization"] = "Basic " + authToken + + body, err := lv.makeLaundryServiceWebRequest(url, method, headers, "") + + if err != nil { + return "", err + } + + var dat map[string]interface{} + if err := json.Unmarshal(body, &dat); err != nil { + return "", err + } + + if _, keyExists := dat["token"]; !keyExists { + return "", errors.New("token not returned") + } + return dat["token"].(string), nil +} + +func (lv *CSCLaundryView) getMachineDetails(serviceurl string, subscriptionkey string, authtoken string, servicetoken string, machineid string) (*uiuc.Machinedetail, error) { + md := uiuc.Machinedetail{} + + url := serviceurl + "/machineDetails?subscription-key=" + subscriptionkey + method := "POST" + + payload := `{"machineId":"` + machineid + `"}` + + headers := make(map[string]string) + headers["X-CSRFToken"] = servicetoken + headers["Cookie"] = "session=" + lv.serviceCookie + headers["Content-Type"] = "application/json" + headers["Authorization"] = "Basic " + authtoken + + body, err := lv.makeLaundryServiceWebRequest(url, method, headers, payload) + + if err != nil { + return nil, err + } + + if err := json.Unmarshal(body, &md); err != nil { + return nil, err + } + return &md, nil +} + +func (lv *CSCLaundryView) getProblemCodes(serviceurl string, subscriptionkey string, servicetoken string, authtoken string, machinetype string) ([]string, error) { + url := serviceurl + "/problemCodes?subscription-key=" + subscriptionkey + method := "POST" + + payload := `{"machineType": "` + machinetype + `"}` + + headers := make(map[string]string) + headers["X-CSRFToken"] = servicetoken + headers["Cookie"] = "session=" + lv.serviceCookie + headers["Content-Type"] = "application/json" + headers["Authorization"] = "Basic " + authtoken + + body, err := lv.makeLaundryServiceWebRequest(url, method, headers, payload) + if err != nil { + return nil, err + } + + var dat map[string][]string + if err := json.Unmarshal(body, &dat); err != nil { + return nil, err + } + + return dat["problemCodeList"], nil +} + +func (lv *CSCLaundryView) submitTicket(serviceurl string, subscriptionkey string, servicetoken string, authtoken string, machineid string, problemCode string, comments string, firstName string, lastName string, phone string, email string) (*model.ServiceRequestResult, error) { + url := serviceurl + "/submitServiceRequest?subscription-key=" + subscriptionkey + method := "POST" + headers := make(map[string]string) + headers["X-CSRFToken"] = servicetoken + headers["Cookie"] = "session=" + lv.serviceCookie + headers["Content-Type"] = "application/json" + headers["Authorization"] = "Basic " + authtoken + + payload := struct { + MachineID string `json:"machineId"` + ProblemCode string `json:"problemCode"` + Comments string `json:"comments"` + FirstName string `json:"firstName"` + LastName string `json:"lastName"` + Phone string `json:"phone"` + Email string `json:"email"` + }{ + MachineID: machineid, + ProblemCode: problemCode, + Comments: comments, + FirstName: firstName, + LastName: lastName, + Phone: phone, + Email: email, + } + + postData, err := json.Marshal(payload) + if err != nil { + return nil, err + } + + body, err := lv.makeLaundryServiceWebRequest(url, method, headers, string(postData)) + + if err != nil { + return nil, err + } + + var obj interface{} + + if err := json.Unmarshal(body, &obj); err != nil { + return nil, err + } + + m := obj.(map[string]interface{}) + //already a request for this machine, so got back a machine details object + if m["machineId"] != nil { + result := model.ServiceRequestResult{Message: "A ticket already exists for this machine", RequestNumber: "0", Status: "Failed"} + return &result, nil + } + result := model.ServiceRequestResult{Message: m["message"].(string), RequestNumber: m["serviceRequestNumber"].(string), Status: "Success"} + return &result, nil + +} + +func (lv *CSCLaundryView) makeLaundryServiceWebRequest(url string, method string, headers map[string]string, postParams string) ([]byte, error) { + payload := strings.NewReader(postParams) + client := http.Client{} + req, err := http.NewRequest(method, url, payload) + + if err != nil { + return nil, err + } + + for headername, headerval := range headers { + req.Header.Add(headername, headerval) + } + + res, err := client.Do(req) + + if err != nil { + log.Printf("%v", err.Error()) + return nil, err + } + + defer res.Body.Close() + for _, cookie := range res.Cookies() { + if cookie.Name == "session" { + lv.serviceCookie = cookie.Value + } + } + + if res.StatusCode != 200 { + test, err := io.ReadAll(res.Body) + log.Printf("%v", string(test)) + return nil, err + } + + body, err := io.ReadAll(res.Body) + if err != nil { + return nil, err + } + return body, nil +} +func evalNumAvailable(inputstr string) int { + if i, err := strconv.Atoi(inputstr); err == nil { + return i + } + return 0 +} diff --git a/driven/uiucadapters/wayfindingadapter.go b/driven/uiucadapters/wayfindingadapter.go new file mode 100644 index 00000000..d9b1ab2f --- /dev/null +++ b/driven/uiucadapters/wayfindingadapter.go @@ -0,0 +1,178 @@ +// Copyright 2022 Board of Trustees of the University of Illinois. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package uiucadapters + +import ( + model "application/core/model" + uiuc "application/core/model/uiuc" + "bytes" + "encoding/json" + "errors" + "fmt" + "io" + "mime/multipart" + "net/http" +) + +// UIUCWayFinding is a vendor specific structure that implements the BuildingLocation interface +type UIUCWayFinding struct { +} + +// NewUIUCWayFinding returns a new instance of a UIUCWayFinding struct +func NewUIUCWayFinding() *UIUCWayFinding { + return &UIUCWayFinding{} +} + +// GetEntrance returns the active entrance closest to the user's position that meets the ADA Accessibility filter requirement +func (uwf *UIUCWayFinding) GetEntrance(bldgID string, adaAccessibleOnly bool, latitude float64, longitude float64, conf *model.EnvConfigData) (*model.Entrance, error) { + apiURL := conf.WayFindingURL + apikey := conf.WayFindingKey + + lat := fmt.Sprintf("%f", latitude) + long := fmt.Sprintf("%f", longitude) + url := apiURL + "/ccf" + + parameters := "{\"v\": 2, \"ranged\": true, \"point\": {\"latitude\": " + lat + ", \"longitude\": " + long + "}}" + bldSelection := "\"number\": \"" + bldgID + "\"" + adaSelection := "" + if adaAccessibleOnly { + adaSelection = ",\"entrances\": {\"ada_compliant\": true}" + } + query := "{" + bldSelection + adaSelection + "}" + + bldg, err := uwf.getBuildingData(url, apikey, query, parameters, false) + if err != nil { + ent := model.Entrance{} + return &ent, err + } + ent := uwf.closestEntrance((*bldg)[0]) + if ent != nil { + return uiuc.NewEntrance(*ent), nil + } + return nil, nil +} + +// GetBuildings returns a list of all buildings +func (uwf *UIUCWayFinding) GetBuildings(conf *model.EnvConfigData) (*[]model.Building, error) { + apiURL := conf.WayFindingURL + apikey := conf.WayFindingKey + + url := apiURL + "/ccf" + parameters := "{\"v\": 2}" + + cmpBldgs, err := uwf.getBuildingData(url, apikey, "{}", parameters, true) + if err != nil { + return nil, err + } + returnList := uiuc.NewBuildingList(cmpBldgs) + return returnList, nil +} + +// GetBuilding returns the requested building with all of its entrances that meet the ADA accessibility filter +func (uwf *UIUCWayFinding) GetBuilding(bldgID string, adaAccessibleOnly bool, latitude float64, longitude float64, conf *model.EnvConfigData) (*model.Building, error) { + apiURL := conf.WayFindingURL + apikey := conf.WayFindingKey + + url := apiURL + "/ccf" + lat := fmt.Sprintf("%f", latitude) + long := fmt.Sprintf("%f", longitude) + + parameters := "" + if latitude == 0 && longitude == 0 { + parameters = "{\"v\": 2, \"ranged\": true, \"point\": {\"latitude\": " + lat + ", \"longitude\": " + long + "}}" + } else { + parameters = "{\"v\": 2}" + } + + bldSelection := "\"number\": \"" + bldgID + "\"" + adaSelection := "" + if adaAccessibleOnly { + adaSelection = ",\"entrances\": {\"ada_compliant\": true}" + } + query := "{" + bldSelection + adaSelection + "}" + cmpBldg, err := uwf.getBuildingData(url, apikey, query, parameters, false) + if err != nil { + bldg := model.Building{} + return &bldg, err + } + return uiuc.NewBuilding((*cmpBldg)[0]), nil +} + +// the entrance list coming back from a ranged query to the API is sorted closest to farthest from +// the user's coordinates. The first entrance in the list that is active and matches the ADA filter +// will be the one to return +func (uwf *UIUCWayFinding) closestEntrance(bldg uiuc.CampusBuilding) *uiuc.CampusEntrance { + for _, n := range bldg.Entrances { + if n.Available { + return &n + } + } + return nil +} + +func (uwf *UIUCWayFinding) getBuildingData(targetURL string, apikey string, queryString string, parameters string, allBuildings bool) (*[]uiuc.CampusBuilding, error) { + method := "POST" + + payload := &bytes.Buffer{} + writer := multipart.NewWriter(payload) + _ = writer.WriteField("collection", "buildings") + _ = writer.WriteField("action", "fetch") + _ = writer.WriteField("query", queryString) + _ = writer.WriteField("parameters", parameters) + err := writer.Close() + if err != nil { + return nil, err + } + + client := &http.Client{} + fmt.Println(targetURL) + req, err := http.NewRequest(method, targetURL, payload) + + if err != nil { + return nil, err + } + + req.Header.Add("Authorization", "Bearer "+apikey) + req.Header.Set("Content-Type", writer.FormDataContentType()) + res, err := client.Do(req) + if err != nil { + return nil, err + } + defer res.Body.Close() + + body, err := io.ReadAll(res.Body) + if err != nil { + return nil, err + } + + if res.StatusCode == 400 { + return nil, errors.New("bad request to api end point") + } + + //used to indicate no building found + if res.StatusCode == 202 { + return nil, errors.New("building not found") + } + + data := uiuc.ServerLocationData{} + err = json.Unmarshal(body, &data) + + if err != nil { + return nil, err + } + + campusBldgs := data.Buildings + return &campusBldgs, nil +} diff --git a/driver/web/adapter.go b/driver/web/adapter.go index c8eda4b5..81016675 100644 --- a/driver/web/adapter.go +++ b/driver/web/adapter.go @@ -15,147 +15,202 @@ package web import ( - "apigateway/core" - "apigateway/driver/web/rest" - "apigateway/utils" + "application/core" + "bytes" "fmt" - "log" "net/http" + "os" + "time" + + "gopkg.in/yaml.v2" "github.com/gorilla/mux" + "github.com/rokwire/core-auth-library-go/v3/authservice" + "github.com/rokwire/core-auth-library-go/v3/tokenauth" + + "github.com/rokwire/logging-library-go/v2/logs" + "github.com/rokwire/logging-library-go/v2/logutils" + httpSwagger "github.com/swaggo/http-swagger" ) // Adapter entity type Adapter struct { - host string - port string - - apisHandler rest.ApisHandler - adminApisHandler rest.AdminApisHandler - laundryapiHandler rest.LaundryApisHandler - buildingapiHandler rest.BuildingAPIHandler - contactapiHandler rest.ContactInfoApisHandler - coursesapiHandler rest.CourseApisHandler - termsapiHandler rest.TermSessionAPIHandler - tokenAuth *TokenAuth - app *core.Application -} + baseURL string + port string + serviceID string -// @title Rokwire Gatewauy Building Block API -// @description Rokwire Rokwire Building Block API Documentation. -// @version 0.1.0 -// @license.name Apache 2.0 -// @license.url http://www.apache.org/licenses/LICENSE-2.0.html -// @host localhost -// @BasePath /gateway/api -// @schemes https + auth *Auth -// @securityDefinitions.apikey RokwireAuth -// @in header -// @name ROKWIRE-API-KEY + cachedYamlDoc []byte -//@securityDefinitions.accesskey ExternalAuth -//@in header -//@name External-Authorization + defaultAPIsHandler DefaultAPIsHandler + clientAPIsHandler ClientAPIsHandler + adminAPIsHandler AdminAPIsHandler + bbsAPIsHandler BBsAPIsHandler + tpsAPIsHandler TPSAPIsHandler + systemAPIsHandler SystemAPIsHandler -// @securityDefinitions.apikey InternalAuth -// @in header -// @name INTERNAL-API-KEY + app *core.Application -// @securityDefinitions.apikey UserAuth -// @in header (add client id token with Bearer prefix to the Authorization value) -// @name Authorization + logger *logs.Logger +} -// @securityDefinitions.apikey AdminUserAuth -// @in header (add admin id token with Bearer prefix to the Authorization value) -// @name Authorization +type handlerFunc = func(*logs.Log, *http.Request, *tokenauth.Claims) logs.HTTPResponse // Start starts the module -func (we Adapter) Start() { +func (a Adapter) Start() { router := mux.NewRouter().StrictSlash(true) // handle apis - //do i need a different adapter for each "endpoint" (laundry, courselist, wayfinding, etc) - //or can I set different routers for different router path prefixise (/laundry, /courselist, ...) - //still learning the gorilla mux library - mainRouter := router.PathPrefix("/gateway/api").Subrouter() - mainRouter.PathPrefix("/doc/ui").Handler(we.serveDocUI()) - mainRouter.HandleFunc("/doc", we.serveDoc) - mainRouter.HandleFunc("/version", we.wrapFunc(we.apisHandler.Version)).Methods("GET") - - // Client APIs - mainRouter.HandleFunc("/record", we.tokenAuthWrapFunc(we.apisHandler.StoreRecord)).Methods("POST") - mainRouter.HandleFunc("/laundry/rooms", we.tokenAuthWrapFunc(we.laundryapiHandler.GetLaundryRooms)).Methods("GET") - mainRouter.HandleFunc("/laundry/room", we.tokenAuthWrapFunc(we.laundryapiHandler.GetRoomDetails)).Methods("GET") - mainRouter.HandleFunc("/laundry/initrequest", we.tokenAuthWrapFunc(we.laundryapiHandler.InitServiceRequest)).Methods("GET") - mainRouter.HandleFunc("/laundry/requestservice", we.tokenAuthWrapFunc(we.laundryapiHandler.SubmitServiceRequest)).Methods("POST") + baseRouter := router.PathPrefix("/" + a.serviceID).Subrouter() + baseRouter.PathPrefix("/doc/ui").Handler(a.serveDocUI()) + baseRouter.HandleFunc("/doc", a.serveDoc) + baseRouter.HandleFunc("/version", a.wrapFunc(a.defaultAPIsHandler.version, nil)).Methods("GET") - mainRouter.HandleFunc("/wayfinding/building", we.tokenAuthWrapFunc(we.buildingapiHandler.GetBuilding)).Methods("GET") - mainRouter.HandleFunc("/wayfinding/entrance", we.tokenAuthWrapFunc(we.buildingapiHandler.GetEntrance)).Methods("GET") - mainRouter.HandleFunc("/wayfinding/buildings", we.tokenAuthWrapFunc(we.buildingapiHandler.GetBuildings)).Methods("GET") + mainRouter := baseRouter.PathPrefix("/api").Subrouter() - mainRouter.HandleFunc("/person/contactinfo", we.tokenAuthWrapFunc(we.contactapiHandler.GetContactInfo)).Methods("GET") - - mainRouter.HandleFunc("/courses/giescourses", we.tokenAuthWrapFunc(we.coursesapiHandler.GetGiesCourses)).Methods("GET") - mainRouter.HandleFunc("/courses/studentcourses", we.tokenAuthWrapFunc(we.coursesapiHandler.GetStudentcourses)).Methods("GET") - - mainRouter.HandleFunc("/termsessions/listcurrent", we.tokenAuthWrapFunc(we.termsapiHandler.GetTermSessions)).Methods("GET") - - log.Fatal(http.ListenAndServe(":"+we.port, router)) + // Client APIs + mainRouter.HandleFunc("/examples/{id}", a.wrapFunc(a.clientAPIsHandler.getExample, a.auth.client.Permissions)).Methods("GET") + mainRouter.HandleFunc("/laundry/rooms", a.wrapFunc(a.clientAPIsHandler.getLaundryRooms, a.auth.client.User)).Methods("GET") + mainRouter.HandleFunc("/laundry/room", a.wrapFunc(a.clientAPIsHandler.getRoomDetails, a.auth.client.User)).Methods("GET") + mainRouter.HandleFunc("/laundry/initrequest", a.wrapFunc(a.clientAPIsHandler.initServiceRequest, a.auth.client.User)).Methods("GET") + mainRouter.HandleFunc("/laundry/reqeustservice", a.wrapFunc(a.clientAPIsHandler.submitServiceRequest, nil)).Methods("GET") + + mainRouter.HandleFunc("/wayfinding/building", a.wrapFunc(a.clientAPIsHandler.getBuilding, a.auth.client.User)).Methods("GET") + mainRouter.HandleFunc("/wayfinding/entrance", a.wrapFunc(a.clientAPIsHandler.getEntrance, a.auth.client.User)).Methods("GET") + mainRouter.HandleFunc("/wayfinding/buildings", a.wrapFunc(a.clientAPIsHandler.getBuildings, a.auth.client.User)).Methods("GET") + + mainRouter.HandleFunc("/person/contactinfo", a.wrapFunc(a.clientAPIsHandler.getContactInfo, a.auth.client.User)).Methods("GET") + mainRouter.HandleFunc("/courses/giescourses", a.wrapFunc(a.clientAPIsHandler.getGiesCourses, a.auth.client.User)).Methods("GET") + mainRouter.HandleFunc("/courses/studentcourses", a.wrapFunc(a.clientAPIsHandler.getStudentCourses, a.auth.client.User)).Methods("GET") + mainRouter.HandleFunc("/termsessions/listcurrent", a.wrapFunc(a.clientAPIsHandler.getTermSessions, a.auth.client.User)).Methods("GET") + + // Admin APIs + adminRouter := mainRouter.PathPrefix("/admin").Subrouter() + adminRouter.HandleFunc("/examples/{id}", a.wrapFunc(a.adminAPIsHandler.getExample, a.auth.admin.Permissions)).Methods("GET") + adminRouter.HandleFunc("/examples", a.wrapFunc(a.adminAPIsHandler.createExample, a.auth.admin.Permissions)).Methods("POST") + adminRouter.HandleFunc("/examples/{id}", a.wrapFunc(a.adminAPIsHandler.updateExample, a.auth.admin.Permissions)).Methods("PUT") + adminRouter.HandleFunc("/examples/{id}", a.wrapFunc(a.adminAPIsHandler.deleteExample, a.auth.admin.Permissions)).Methods("DELETE") + + adminRouter.HandleFunc("/configs/{id}", a.wrapFunc(a.adminAPIsHandler.getConfig, a.auth.admin.Permissions)).Methods("GET") + adminRouter.HandleFunc("/configs", a.wrapFunc(a.adminAPIsHandler.getConfigs, a.auth.admin.Permissions)).Methods("GET") + adminRouter.HandleFunc("/configs", a.wrapFunc(a.adminAPIsHandler.createConfig, a.auth.admin.Permissions)).Methods("POST") + adminRouter.HandleFunc("/configs/{id}", a.wrapFunc(a.adminAPIsHandler.updateConfig, a.auth.admin.Permissions)).Methods("PUT") + adminRouter.HandleFunc("/configs/{id}", a.wrapFunc(a.adminAPIsHandler.deleteConfig, a.auth.admin.Permissions)).Methods("DELETE") + + // BB APIs + bbsRouter := mainRouter.PathPrefix("/bbs").Subrouter() + bbsRouter.HandleFunc("/examples/{id}", a.wrapFunc(a.bbsAPIsHandler.getExample, a.auth.bbs.Permissions)).Methods("GET") + bbsRouter.HandleFunc("/appointments/units", a.wrapFunc(a.bbsAPIsHandler.getAppointmentUnits, a.auth.bbs.Permissions)).Methods("GET") + bbsRouter.HandleFunc("/appointments/people", a.wrapFunc(a.bbsAPIsHandler.getAppointmentPeople, a.auth.bbs.Permissions)).Methods("GET") + bbsRouter.HandleFunc("/appointments/slots", a.wrapFunc(a.bbsAPIsHandler.getAppointmentTimeSlots, a.auth.bbs.Permissions)).Methods("GET") + bbsRouter.HandleFunc("/appointments/questions", a.wrapFunc(a.bbsAPIsHandler.getAppointmentQuestions, a.auth.bbs.Permissions)).Methods("GET") + bbsRouter.HandleFunc("/appointments/qands", a.wrapFunc(a.bbsAPIsHandler.getAppointmentOptions, a.auth.bbs.Permissions)).Methods("GET") + bbsRouter.HandleFunc("/appointments/", a.wrapFunc(a.bbsAPIsHandler.createAppointment, a.auth.bbs.Permissions)).Methods("POST") + bbsRouter.HandleFunc("/appointments/{id}", a.wrapFunc(a.bbsAPIsHandler.deleteAppointment, a.auth.bbs.Permissions)).Methods("DELETE") + bbsRouter.HandleFunc("/appointments/", a.wrapFunc(a.bbsAPIsHandler.updateAppointment, a.auth.bbs.Permissions)).Methods("PUT") + + // TPS APIs + tpsRouter := mainRouter.PathPrefix("/tps").Subrouter() + tpsRouter.HandleFunc("/examples/{id}", a.wrapFunc(a.tpsAPIsHandler.getExample, a.auth.tps.Permissions)).Methods("GET") + + // System APIs + systemRouter := mainRouter.PathPrefix("/system").Subrouter() + systemRouter.HandleFunc("/examples/{id}", a.wrapFunc(a.systemAPIsHandler.getExample, a.auth.system.Permissions)).Methods("GET") + + a.logger.Fatalf("Error serving: %v", http.ListenAndServe(":"+a.port, router)) } -func (we Adapter) serveDoc(w http.ResponseWriter, r *http.Request) { +func (a Adapter) serveDoc(w http.ResponseWriter, r *http.Request) { w.Header().Add("access-control-allow-origin", "*") - http.ServeFile(w, r, "./docs/swagger.yaml") + + if a.cachedYamlDoc != nil { + http.ServeContent(w, r, "", time.Now(), bytes.NewReader([]byte(a.cachedYamlDoc))) + } else { + http.ServeFile(w, r, "./driver/web/docs/gen/def.yaml") + } } -func (we Adapter) serveDocUI() http.Handler { - url := fmt.Sprintf("%s/api/doc", we.host) +func (a Adapter) serveDocUI() http.Handler { + url := fmt.Sprintf("%s/doc", a.baseURL) return httpSwagger.Handler(httpSwagger.URL(url)) } -// functions with no authentication at all -func (we Adapter) wrapFunc(handler http.HandlerFunc) http.HandlerFunc { - return func(w http.ResponseWriter, req *http.Request) { - utils.LogRequest(req) +func loadDocsYAML(baseServerURL string) ([]byte, error) { + data, _ := os.ReadFile("./driver/web/docs/gen/def.yaml") + yamlMap := yaml.MapSlice{} + err := yaml.Unmarshal(data, &yamlMap) + if err != nil { + return nil, err + } - handler(w, req) + for index, item := range yamlMap { + if item.Key == "servers" { + var serverList []interface{} + if baseServerURL != "" { + serverList = []interface{}{yaml.MapSlice{yaml.MapItem{Key: "url", Value: baseServerURL}}} + } + + item.Value = serverList + yamlMap[index] = item + break + } } + + yamlDoc, err := yaml.Marshal(&yamlMap) + if err != nil { + return nil, err + } + + return yamlDoc, nil } -func (we Adapter) tokenAuthWrapFunc(handler http.HandlerFunc) http.HandlerFunc { +func (a Adapter) wrapFunc(handler handlerFunc, authorization tokenauth.Handler) http.HandlerFunc { return func(w http.ResponseWriter, req *http.Request) { - //authenticate token - authenticated, _ := we.tokenAuth.Check(req) - - if authenticated { - handler(w, req) - return + logObj := a.logger.NewRequestLog(req) + + logObj.RequestReceived() + + var response logs.HTTPResponse + if authorization != nil { + responseStatus, claims, err := authorization.Check(req) + if err != nil { + logObj.SendHTTPResponse(w, logObj.HTTPResponseErrorAction(logutils.ActionValidate, logutils.TypeRequest, nil, err, responseStatus, true)) + return + } + + if claims != nil { + logObj.SetContext("account_id", claims.Subject) + } + response = handler(logObj, req, claims) + } else { + response = handler(logObj, req, nil) } - http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized) + + logObj.SendHTTPResponse(w, response) + logObj.RequestComplete() } } // NewWebAdapter creates new WebAdapter instance -func NewWebAdapter(host string, port string, app *core.Application, tokenAuth *TokenAuth) Adapter { - - apisHandler := rest.NewApisHandler(app) - adminApisHandler := rest.NewAdminApisHandler(app) - laundryapiHandler := rest.NewLaundryApisHandler(app) - buildingapiHandler := rest.NewBuildingAPIHandler(app) - contactapiHandler := rest.NewContactInfoApisHandler(app) - coursesapiHandler := rest.NewCourseApisHandler(app) - termsapiHandler := rest.NewTermSessionAPIHandler(app) - - return Adapter{host: host, port: port, - apisHandler: apisHandler, adminApisHandler: adminApisHandler, app: app, laundryapiHandler: laundryapiHandler, - buildingapiHandler: buildingapiHandler, tokenAuth: tokenAuth, - contactapiHandler: contactapiHandler, coursesapiHandler: coursesapiHandler, termsapiHandler: termsapiHandler} -} +func NewWebAdapter(baseURL string, port string, serviceID string, app *core.Application, serviceRegManager *authservice.ServiceRegManager, logger *logs.Logger) Adapter { + yamlDoc, err := loadDocsYAML(baseURL) + if err != nil { + logger.Fatalf("error parsing docs yaml - %s", err.Error()) + } + + auth, err := NewAuth(serviceRegManager) + if err != nil { + logger.Fatalf("error creating auth - %s", err.Error()) + } -// AppListener implements core.ApplicationListener interface -type AppListener struct { - adapter *Adapter + defaultAPIsHandler := NewDefaultAPIsHandler(app) + clientAPIsHandler := NewClientAPIsHandler(app) + adminAPIsHandler := NewAdminAPIsHandler(app) + bbsAPIsHandler := NewBBsAPIsHandler(app) + tpsAPIsHandler := NewTPSAPIsHandler(app) + return Adapter{baseURL: baseURL, port: port, serviceID: serviceID, cachedYamlDoc: yamlDoc, auth: auth, defaultAPIsHandler: defaultAPIsHandler, + clientAPIsHandler: clientAPIsHandler, adminAPIsHandler: adminAPIsHandler, bbsAPIsHandler: bbsAPIsHandler, tpsAPIsHandler: tpsAPIsHandler, app: app, logger: logger} } diff --git a/driver/web/admin_permission_policy.csv b/driver/web/admin_permission_policy.csv new file mode 100644 index 00000000..f20aa0c7 --- /dev/null +++ b/driver/web/admin_permission_policy.csv @@ -0,0 +1,17 @@ +p, all_admin_identity, /identity/api/admin/*, (GET)|(POST)|(PUT)|(DELETE), All identity admin actions + +p, all_examples, /identity/api/admin/examples, (POST), All example admin actions +p, all_examples, /identity/api/admin/examples/*, (GET)|(PUT)|(DELETE), +p, get_examples, /identity/api/admin/examples/*, (GET), Get examples +p, update_examples, /identity/api/admin/examples, (POST), Update examples +p, update_examples, /identity/api/admin/examples/*, (GET)|(PUT), +p, delete_examples, /identity/api/admin/examples/*, (GET)|(DELETE), + +p, all_configs_identity, /identity/api/admin/configs/*, (GET)|(PUT)|(DELETE), All identity config admin actions +p, all_configs_identity, /identity/api/admin/configs, (GET)|(POST), +p, get_configs_identity, /identity/api/admin/configs/*, (GET), Get identity configs +p, get_configs_identity, /identity/api/admin/configs, (GET), +p, update_configs_identity, /identity/api/admin/configs/*, (GET)|(PUT), Update identity configs +p, update_configs_identity, /identity/api/admin/configs, (GET)|(POST), +p, delete_configs_identity, /identity/api/admin/configs/*, (GET)|(DELETE), Delete identity configs +p, delete_configs_identity, /identity/api/admin/configs, (GET), diff --git a/driver/web/apis_admin.go b/driver/web/apis_admin.go new file mode 100644 index 00000000..ae81dc52 --- /dev/null +++ b/driver/web/apis_admin.go @@ -0,0 +1,241 @@ +// Copyright 2022 Board of Trustees of the University of Illinois. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package web + +import ( + "application/core" + "application/core/model" + "encoding/json" + "net/http" + + "github.com/gorilla/mux" + "github.com/rokwire/core-auth-library-go/v3/authutils" + "github.com/rokwire/core-auth-library-go/v3/tokenauth" + "github.com/rokwire/logging-library-go/v2/logs" + "github.com/rokwire/logging-library-go/v2/logutils" +) + +// AdminAPIsHandler handles the rest Admin APIs implementation +type AdminAPIsHandler struct { + app *core.Application +} + +func (h AdminAPIsHandler) getExample(l *logs.Log, r *http.Request, claims *tokenauth.Claims) logs.HTTPResponse { + params := mux.Vars(r) + id := params["id"] + if len(id) <= 0 { + return l.HTTPResponseErrorData(logutils.StatusMissing, logutils.TypePathParam, logutils.StringArgs("id"), nil, http.StatusBadRequest, false) + } + + example, err := h.app.Admin.GetExample(claims.OrgID, claims.AppID, id) + if err != nil { + return l.HTTPResponseErrorAction(logutils.ActionGet, model.TypeExample, nil, err, http.StatusInternalServerError, true) + } + + response, err := json.Marshal(example) + if err != nil { + return l.HTTPResponseErrorAction(logutils.ActionMarshal, logutils.TypeResponseBody, nil, err, http.StatusInternalServerError, false) + } + return l.HTTPResponseSuccessJSON(response) +} + +func (h AdminAPIsHandler) createExample(l *logs.Log, r *http.Request, claims *tokenauth.Claims) logs.HTTPResponse { + var requestData model.Example + err := json.NewDecoder(r.Body).Decode(&requestData) + if err != nil { + return l.HTTPResponseErrorAction(logutils.ActionUnmarshal, logutils.TypeRequestBody, nil, err, http.StatusBadRequest, true) + } + + requestData.OrgID = claims.OrgID + requestData.AppID = claims.AppID + example, err := h.app.Admin.CreateExample(requestData) + if err != nil || example == nil { + return l.HTTPResponseErrorAction(logutils.ActionCreate, model.TypeExample, nil, err, http.StatusInternalServerError, true) + } + + response, err := json.Marshal(example) + if err != nil { + return l.HTTPResponseErrorAction(logutils.ActionMarshal, logutils.TypeResponseBody, nil, err, http.StatusInternalServerError, false) + } + return l.HTTPResponseSuccessJSON(response) +} + +func (h AdminAPIsHandler) updateExample(l *logs.Log, r *http.Request, claims *tokenauth.Claims) logs.HTTPResponse { + params := mux.Vars(r) + id := params["id"] + if len(id) <= 0 { + return l.HTTPResponseErrorData(logutils.StatusMissing, logutils.TypePathParam, logutils.StringArgs("id"), nil, http.StatusBadRequest, false) + } + + var requestData model.Example + err := json.NewDecoder(r.Body).Decode(&requestData) + if err != nil { + return l.HTTPResponseErrorAction(logutils.ActionUnmarshal, logutils.TypeRequestBody, nil, err, http.StatusBadRequest, true) + } + + requestData.ID = id + requestData.OrgID = claims.OrgID + requestData.AppID = claims.AppID + err = h.app.Admin.UpdateExample(requestData) + if err != nil { + return l.HTTPResponseErrorAction(logutils.ActionUpdate, model.TypeExample, nil, err, http.StatusInternalServerError, true) + } + + return l.HTTPResponseSuccess() +} + +func (h AdminAPIsHandler) deleteExample(l *logs.Log, r *http.Request, claims *tokenauth.Claims) logs.HTTPResponse { + params := mux.Vars(r) + id := params["id"] + if len(id) <= 0 { + return l.HTTPResponseErrorData(logutils.StatusMissing, logutils.TypePathParam, logutils.StringArgs("id"), nil, http.StatusBadRequest, false) + } + + err := h.app.Admin.DeleteExample(claims.OrgID, claims.AppID, id) + if err != nil { + return l.HTTPResponseErrorAction(logutils.ActionDelete, model.TypeExample, nil, err, http.StatusInternalServerError, true) + } + + return l.HTTPResponseSuccess() +} + +func (h AdminAPIsHandler) getConfig(l *logs.Log, r *http.Request, claims *tokenauth.Claims) logs.HTTPResponse { + params := mux.Vars(r) + id := params["id"] + if len(id) <= 0 { + return l.HTTPResponseErrorData(logutils.StatusMissing, logutils.TypePathParam, logutils.StringArgs("id"), nil, http.StatusBadRequest, false) + } + + config, err := h.app.Admin.GetConfig(id, claims) + if err != nil { + return l.HTTPResponseErrorAction(logutils.ActionGet, model.TypeConfig, nil, err, http.StatusInternalServerError, true) + } + + data, err := json.Marshal(config) + if err != nil { + return l.HTTPResponseErrorAction(logutils.ActionMarshal, model.TypeConfig, nil, err, http.StatusInternalServerError, false) + } + + return l.HTTPResponseSuccessJSON(data) +} + +func (h AdminAPIsHandler) getConfigs(l *logs.Log, r *http.Request, claims *tokenauth.Claims) logs.HTTPResponse { + var configType *string + typeParam := r.URL.Query().Get("type") + if len(typeParam) > 0 { + configType = &typeParam + } + + configs, err := h.app.Admin.GetConfigs(configType, claims) + if err != nil { + return l.HTTPResponseErrorAction(logutils.ActionGet, model.TypeConfig, nil, err, http.StatusInternalServerError, true) + } + + data, err := json.Marshal(configs) + if err != nil { + return l.HTTPResponseErrorAction(logutils.ActionMarshal, model.TypeConfig, nil, err, http.StatusInternalServerError, false) + } + + return l.HTTPResponseSuccessJSON(data) +} + +type adminUpdateConfigsRequest struct { + AllApps *bool `json:"all_apps,omitempty"` + AllOrgs *bool `json:"all_orgs,omitempty"` + Data interface{} `json:"data"` + System bool `json:"system"` + Type string `json:"type"` +} + +func (h AdminAPIsHandler) createConfig(l *logs.Log, r *http.Request, claims *tokenauth.Claims) logs.HTTPResponse { + var requestData adminUpdateConfigsRequest + err := json.NewDecoder(r.Body).Decode(&requestData) + if err != nil { + return l.HTTPResponseErrorAction(logutils.ActionUnmarshal, logutils.TypeRequestBody, nil, err, http.StatusBadRequest, true) + } + + appID := claims.AppID + if requestData.AllApps != nil && *requestData.AllApps { + appID = authutils.AllApps + } + orgID := claims.OrgID + if requestData.AllOrgs != nil && *requestData.AllOrgs { + orgID = authutils.AllOrgs + } + config := model.Config{Type: requestData.Type, AppID: appID, OrgID: orgID, System: requestData.System, Data: requestData.Data} + + newConfig, err := h.app.Admin.CreateConfig(config, claims) + if err != nil { + return l.HTTPResponseErrorAction(logutils.ActionCreate, model.TypeConfig, nil, err, http.StatusInternalServerError, true) + } + + data, err := json.Marshal(newConfig) + if err != nil { + return l.HTTPResponseErrorAction(logutils.ActionMarshal, model.TypeConfig, nil, err, http.StatusInternalServerError, false) + } + + return l.HTTPResponseSuccessJSON(data) +} + +func (h AdminAPIsHandler) updateConfig(l *logs.Log, r *http.Request, claims *tokenauth.Claims) logs.HTTPResponse { + params := mux.Vars(r) + id := params["id"] + if len(id) <= 0 { + return l.HTTPResponseErrorData(logutils.StatusMissing, logutils.TypePathParam, logutils.StringArgs("id"), nil, http.StatusBadRequest, false) + } + + var requestData adminUpdateConfigsRequest + err := json.NewDecoder(r.Body).Decode(&requestData) + if err != nil { + return l.HTTPResponseErrorAction(logutils.ActionUnmarshal, logutils.TypeRequestBody, nil, err, http.StatusBadRequest, true) + } + + appID := claims.AppID + if requestData.AllApps != nil && *requestData.AllApps { + appID = authutils.AllApps + } + orgID := claims.OrgID + if requestData.AllOrgs != nil && *requestData.AllOrgs { + orgID = authutils.AllOrgs + } + config := model.Config{ID: id, Type: requestData.Type, AppID: appID, OrgID: orgID, System: requestData.System, Data: requestData.Data} + + err = h.app.Admin.UpdateConfig(config, claims) + if err != nil { + return l.HTTPResponseErrorAction(logutils.ActionUpdate, model.TypeConfig, nil, err, http.StatusInternalServerError, true) + } + + return l.HTTPResponseSuccess() +} + +func (h AdminAPIsHandler) deleteConfig(l *logs.Log, r *http.Request, claims *tokenauth.Claims) logs.HTTPResponse { + params := mux.Vars(r) + id := params["id"] + if len(id) <= 0 { + return l.HTTPResponseErrorData(logutils.StatusMissing, logutils.TypePathParam, logutils.StringArgs("id"), nil, http.StatusBadRequest, false) + } + + err := h.app.Admin.DeleteConfig(id, claims) + if err != nil { + return l.HTTPResponseErrorAction(logutils.ActionDelete, model.TypeConfig, nil, err, http.StatusInternalServerError, true) + } + + return l.HTTPResponseSuccess() +} + +// NewAdminAPIsHandler creates new rest Handler instance +func NewAdminAPIsHandler(app *core.Application) AdminAPIsHandler { + return AdminAPIsHandler{app: app} +} diff --git a/driver/web/apis_bbs.go b/driver/web/apis_bbs.go new file mode 100644 index 00000000..b3deef86 --- /dev/null +++ b/driver/web/apis_bbs.go @@ -0,0 +1,365 @@ +// Copyright 2022 Board of Trustees of the University of Illinois. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package web + +import ( + "application/core" + "application/core/model" + utils "application/utils" + "encoding/json" + "errors" + "io" + "net/http" + "strconv" + "time" + + "github.com/gorilla/mux" + "github.com/rokwire/core-auth-library-go/v3/tokenauth" + "github.com/rokwire/logging-library-go/v2/logs" + "github.com/rokwire/logging-library-go/v2/logutils" +) + +// BBsAPIsHandler handles the rest BBs APIs implementation +type BBsAPIsHandler struct { + app *core.Application +} + +func (h BBsAPIsHandler) getExample(l *logs.Log, r *http.Request, claims *tokenauth.Claims) logs.HTTPResponse { + params := mux.Vars(r) + id := params["id"] + if len(id) <= 0 { + return l.HTTPResponseErrorData(logutils.StatusMissing, logutils.TypePathParam, logutils.StringArgs("id"), nil, http.StatusBadRequest, false) + } + + example, err := h.app.BBs.GetExample(claims.OrgID, claims.AppID, id) + if err != nil { + return l.HTTPResponseErrorAction(logutils.ActionGet, model.TypeExample, nil, err, http.StatusInternalServerError, true) + } + + response, err := json.Marshal(example) + if err != nil { + return l.HTTPResponseErrorAction(logutils.ActionMarshal, logutils.TypeResponseBody, nil, err, http.StatusInternalServerError, false) + } + return l.HTTPResponseSuccessJSON(response) +} + +// appointment apis +func (h BBsAPIsHandler) getAppointmentUnits(l *logs.Log, r *http.Request, claims *tokenauth.Claims) logs.HTTPResponse { + + reqParams := utils.ConstructFilter(r) + + reqValues, resp, err := h.checkAppointmentParams(reqParams, r, l) + if err != nil { + return resp + } + + if len(reqValues.UIN) != 9 { + return l.HTTPResponseErrorData(logutils.StatusMissing, logutils.TypePathParam, logutils.StringArgs("external_id"), nil, http.StatusBadRequest, false) + } + + example, err := h.app.BBs.GetAppointmentUnits(reqValues.ProviderID, reqValues.UIN, reqValues.ExternalToken) + if err != nil { + return l.HTTPResponseErrorAction(logutils.ActionGet, model.TypeAppointments, nil, err, http.StatusInternalServerError, true) + } + + response, err := json.Marshal(example) + if err != nil { + return l.HTTPResponseErrorAction(logutils.ActionMarshal, logutils.TypeResponseBody, nil, err, http.StatusInternalServerError, false) + } + return l.HTTPResponseSuccessJSON(response) +} + +func (h BBsAPIsHandler) getAppointmentPeople(l *logs.Log, r *http.Request, claims *tokenauth.Claims) logs.HTTPResponse { + reqParams := utils.ConstructFilter(r) + reqValues, resp, err := h.checkAppointmentParams(reqParams, r, l) + if err != nil { + return resp + } + + if reqValues.ProviderID == 0 { + return l.HTTPResponseErrorData(logutils.StatusInvalid, logutils.TypeQueryParam, logutils.StringArgs("provider_id"), nil, http.StatusBadRequest, false) + } + + if reqValues.UnitID == 0 { + return l.HTTPResponseErrorData(logutils.StatusInvalid, logutils.TypeQueryParam, logutils.StringArgs("unit_id"), nil, http.StatusBadRequest, false) + } + + if reqValues.UIN == "" { + return l.HTTPResponseErrorData(logutils.StatusMissing, logutils.TypePathParam, logutils.StringArgs("external_id"), nil, http.StatusBadRequest, false) + } + + people, err := h.app.BBs.GetPeople(reqValues.UIN, reqValues.UnitID, reqValues.ProviderID, reqValues.ExternalToken) + if err != nil { + return l.HTTPResponseErrorAction(logutils.ActionGet, model.TypeAppointments, nil, err, http.StatusInternalServerError, true) + } + + response, err := json.Marshal(people) + if err != nil { + return l.HTTPResponseErrorAction(logutils.ActionMarshal, logutils.TypeResponseBody, nil, err, http.StatusInternalServerError, false) + } + return l.HTTPResponseSuccessJSON(response) +} + +func (h BBsAPIsHandler) getAppointmentOptions(l *logs.Log, r *http.Request, claims *tokenauth.Claims) logs.HTTPResponse { + + reqParams := utils.ConstructFilter(r) + + reqValues, resp, err := h.checkAppointmentParams(reqParams, r, l) + if err != nil { + return resp + } + + //check request only validates a parameter value if it is in the parameters. Need to make sure we have everythign we need for this call + if reqValues.UIN == "" || reqValues.UnitID == 0 || reqValues.ProviderID == 0 || reqValues.PersonID == 0 || (reqValues.StartTime.IsZero() && !reqValues.EndTime.IsZero()) || (!reqValues.StartTime.IsZero() && reqValues.EndTime.IsZero()) { + return l.HTTPResponseErrorData(logutils.StatusInvalid, logutils.TypeQueryParam, logutils.StringArgs("missing parameter"), nil, http.StatusBadRequest, false) + } + + apptOptions, err := h.app.BBs.GetAppointmentOptions(reqValues.UIN, reqValues.UnitID, reqValues.PersonID, reqValues.ProviderID, reqValues.StartTime, reqValues.EndTime, reqValues.ExternalToken) + if err != nil { + return l.HTTPResponseErrorAction(logutils.ActionGet, model.TypeAppointments, nil, err, http.StatusInternalServerError, true) + } + + response, err := json.Marshal(apptOptions) + if err != nil { + return l.HTTPResponseErrorAction(logutils.ActionMarshal, logutils.TypeResponseBody, nil, err, http.StatusInternalServerError, false) + } + return l.HTTPResponseSuccessJSON(response) +} + +func (h BBsAPIsHandler) createAppointment(l *logs.Log, r *http.Request, claims *tokenauth.Claims) logs.HTTPResponse { + + data, err := io.ReadAll(r.Body) + if err != nil { + return l.HTTPResponseErrorData(logutils.StatusInvalid, logutils.TypeRequestBody, nil, err, http.StatusBadRequest, false) + } + + var record model.AppointmentPost + err = json.Unmarshal(data, &record) + if err != nil { + return l.HTTPResponseErrorAction(logutils.ActionMarshal, logutils.TypeRequestBody, nil, err, http.StatusBadRequest, false) + } + + externalToken := r.Header.Get("External-Authorization") + if externalToken == "" { + return l.HTTPResponseErrorData(logutils.StatusMissing, logutils.TypeHeader, logutils.StringArgs("external auth token"), nil, http.StatusBadRequest, false) + } + + newAppt, err := h.app.BBs.CreateAppointment(&record, externalToken) + if err != nil { + return l.HTTPResponseErrorAction(logutils.ActionGet, model.TypeAppointments, nil, err, http.StatusInternalServerError, true) + } + + response, err := json.Marshal(newAppt) + if err != nil { + return l.HTTPResponseErrorAction(logutils.ActionMarshal, logutils.TypeResponseBody, nil, err, http.StatusInternalServerError, false) + } + return l.HTTPResponseSuccessJSON(response) +} + +func (h BBsAPIsHandler) updateAppointment(l *logs.Log, r *http.Request, claims *tokenauth.Claims) logs.HTTPResponse { + + data, err := io.ReadAll(r.Body) + if err != nil { + return l.HTTPResponseErrorData(logutils.StatusInvalid, logutils.TypeRequestBody, nil, err, http.StatusBadRequest, false) + } + + var record model.AppointmentPost + err = json.Unmarshal(data, &record) + if err != nil { + return l.HTTPResponseErrorAction(logutils.ActionMarshal, logutils.TypeRequestBody, nil, err, http.StatusBadRequest, false) + } + + externalToken := r.Header.Get("External-Authorization") + if externalToken == "" { + return l.HTTPResponseErrorData(logutils.StatusMissing, logutils.TypeHeader, logutils.StringArgs("external auth token"), nil, http.StatusBadRequest, false) + } + + newAppt, err := h.app.BBs.UpdateAppointment(&record, externalToken) + if err != nil { + return l.HTTPResponseErrorAction(logutils.ActionGet, model.TypeAppointments, nil, err, http.StatusInternalServerError, true) + } + + response, err := json.Marshal(newAppt) + if err != nil { + return l.HTTPResponseErrorAction(logutils.ActionMarshal, logutils.TypeResponseBody, nil, err, http.StatusInternalServerError, false) + } + return l.HTTPResponseSuccessJSON(response) +} + +func (h BBsAPIsHandler) deleteAppointment(l *logs.Log, r *http.Request, claims *tokenauth.Claims) logs.HTTPResponse { + + params := mux.Vars(r) + id := params["id"] + if len(id) <= 0 { + return l.HTTPResponseErrorData(logutils.StatusMissing, logutils.TypePathParam, logutils.StringArgs("id"), nil, http.StatusBadRequest, false) + } + + reqParams := utils.ConstructFilter(r) + reqValues, resp, err := h.checkAppointmentParams(reqParams, r, l) + if err != nil { + return resp + } + + if reqValues.UIN == "" { + return l.HTTPResponseErrorData(logutils.StatusInvalid, logutils.TypeQueryParam, logutils.StringArgs("external_id"), nil, http.StatusBadRequest, false) + } + + if reqValues.ProviderID == 0 { + return l.HTTPResponseErrorData(logutils.StatusInvalid, logutils.TypeQueryParam, logutils.StringArgs("provider_id"), nil, http.StatusBadRequest, false) + } + + _, err = h.app.BBs.DeleteAppointment(reqValues.UIN, reqValues.ProviderID, id, reqValues.ExternalToken) + if err != nil { + return l.HTTPResponseErrorAction(logutils.ActionGet, model.TypeAppointments, nil, err, http.StatusInternalServerError, true) + } + + return l.HTTPResponseSuccess() +} + +func (h BBsAPIsHandler) getAppointmentTimeSlots(l *logs.Log, r *http.Request, claims *tokenauth.Claims) logs.HTTPResponse { + + reqParams := utils.ConstructFilter(r) + + reqValues, resp, err := h.checkAppointmentParams(reqParams, r, l) + if err != nil { + return resp + } + + //check request only validates a parameter value if it is in the parameters. Need to make sure we have everythign we need for this call + if reqValues.UIN == "" || reqValues.UnitID == 0 || reqValues.ProviderID == 0 || reqValues.PersonID == 0 || (reqValues.StartTime.IsZero() && !reqValues.EndTime.IsZero()) || (!reqValues.StartTime.IsZero() && reqValues.EndTime.IsZero()) { + return l.HTTPResponseErrorData(logutils.StatusInvalid, logutils.TypeQueryParam, logutils.StringArgs("missing parameter"), nil, http.StatusBadRequest, false) + } + + apptOptions, err := h.app.BBs.GetAppointmentOptions(reqValues.UIN, reqValues.UnitID, reqValues.PersonID, reqValues.ProviderID, reqValues.StartTime, reqValues.EndTime, reqValues.ExternalToken) + if err != nil { + return l.HTTPResponseErrorAction(logutils.ActionGet, model.TypeAppointments, nil, err, http.StatusInternalServerError, true) + } + + response, err := json.Marshal(apptOptions.TimeSlots) + if err != nil { + return l.HTTPResponseErrorAction(logutils.ActionMarshal, logutils.TypeResponseBody, nil, err, http.StatusInternalServerError, false) + } + return l.HTTPResponseSuccessJSON(response) +} + +func (h BBsAPIsHandler) getAppointmentQuestions(l *logs.Log, r *http.Request, claims *tokenauth.Claims) logs.HTTPResponse { + + reqParams := utils.ConstructFilter(r) + + reqValues, resp, err := h.checkAppointmentParams(reqParams, r, l) + if err != nil { + return resp + } + + //check request only validates a parameter value if it is in the parameters. Need to make sure we have everythign we need for this call + if reqValues.UIN == "" || reqValues.UnitID == 0 || reqValues.ProviderID == 0 || reqValues.PersonID == 0 { + return l.HTTPResponseErrorData(logutils.StatusInvalid, logutils.TypeQueryParam, logutils.StringArgs("missing parameter"), nil, http.StatusBadRequest, false) + } + + apptOptions, err := h.app.BBs.GetAppointmentOptions(reqValues.UIN, reqValues.UnitID, reqValues.PersonID, reqValues.ProviderID, reqValues.StartTime, reqValues.EndTime, reqValues.ExternalToken) + if err != nil { + return l.HTTPResponseErrorAction(logutils.ActionGet, model.TypeAppointments, nil, err, http.StatusInternalServerError, true) + } + + response, err := json.Marshal(apptOptions.Questions) + if err != nil { + return l.HTTPResponseErrorAction(logutils.ActionMarshal, logutils.TypeResponseBody, nil, err, http.StatusInternalServerError, false) + } + return l.HTTPResponseSuccessJSON(response) +} + +func (h BBsAPIsHandler) checkAppointmentParams(reqParms *utils.Filter, req *http.Request, l *logs.Log) (timeSlotRequest, logs.HTTPResponse, error) { + + reqValues := timeSlotRequest{UnitID: 0, ProviderID: 0, UIN: "", PersonID: 0, ExternalToken: ""} + externalToken := req.Header.Get("External-Authorization") + if externalToken == "" { + return reqValues, l.HTTPResponseErrorData(logutils.StatusMissing, logutils.TypeHeader, logutils.StringArgs("external auth token"), nil, http.StatusBadRequest, false), errors.New("missing auth token") + } + reqValues.ExternalToken = externalToken + + for _, v := range reqParms.Items { + switch v.Field { + case "provider_id": + provideridstr := v.Value[0] + intvar, err := strconv.Atoi(provideridstr) + if err != nil { + return reqValues, l.HTTPResponseErrorData(logutils.StatusInvalid, logutils.TypeQueryParam, logutils.StringArgs("provider_id"), nil, http.StatusBadRequest, false), err + } + if intvar == 0 { + return reqValues, l.HTTPResponseErrorData(logutils.StatusInvalid, logutils.TypeQueryParam, logutils.StringArgs("provider_id"), nil, http.StatusBadRequest, false), errors.New("invalid providerid") + } + _, ok := h.app.AppointmentAdapters[provideridstr] + if !ok { + return reqValues, l.HTTPResponseErrorData(logutils.StatusInvalid, logutils.TypeQueryParam, logutils.StringArgs("provider_id"), nil, http.StatusBadRequest, false), errors.New("invalid providerid") + } + reqValues.ProviderID = intvar + case "unit_id": + unitidstr := v.Value[0] + intvar, err := strconv.Atoi(unitidstr) + if err != nil { + return reqValues, l.HTTPResponseErrorData(logutils.StatusInvalid, logutils.TypeQueryParam, logutils.StringArgs("unit_id"), nil, http.StatusBadRequest, false), err + } + if intvar == 0 { + return reqValues, l.HTTPResponseErrorData(logutils.StatusInvalid, logutils.TypeQueryParam, logutils.StringArgs("unit_id"), nil, http.StatusBadRequest, false), errors.New("invalid unitid") + } + reqValues.UnitID = intvar + case "person_id": + peopleidstr := v.Value[0] + intvar, err := strconv.Atoi(peopleidstr) + if err != nil { + return reqValues, l.HTTPResponseErrorData(logutils.StatusInvalid, logutils.TypeQueryParam, logutils.StringArgs("person_id"), nil, http.StatusBadRequest, false), err + } + if intvar == 0 { + return reqValues, l.HTTPResponseErrorData(logutils.StatusMissing, logutils.TypePathParam, logutils.StringArgs("external_id"), nil, http.StatusBadRequest, false), errors.New("invalid uin") + } + reqValues.PersonID = intvar + case "external_id": + reqValues.UIN = v.Value[0] + case "start_time": + st, err := time.Parse(time.DateOnly, v.Value[0]) + if err != nil { + return reqValues, l.HTTPResponseErrorData(logutils.StatusInvalid, logutils.TypeQueryParam, logutils.StringArgs("start_time"), nil, http.StatusBadRequest, false), err + } + reqValues.StartTime = st + case "end_time": + et, err := time.Parse(time.DateOnly, v.Value[0]) + if err != nil { + return reqValues, l.HTTPResponseErrorData(logutils.StatusInvalid, logutils.TypeQueryParam, logutils.StringArgs("end_time"), nil, http.StatusBadRequest, false), err + } + reqValues.EndTime = et + } + } + + if reqValues.ProviderID == 0 { + return reqValues, l.HTTPResponseErrorData(logutils.StatusMissing, logutils.TypeQueryParam, logutils.StringArgs("provider_id"), nil, http.StatusBadRequest, false), errors.New("missing provider_id") + } + return reqValues, l.HTTPResponseSuccess(), nil + +} + +// NewBBsAPIsHandler creates new Building Block API handler instance +func NewBBsAPIsHandler(app *core.Application) BBsAPIsHandler { + return BBsAPIsHandler{app: app} +} + +type timeSlotRequest struct { + UnitID int + ProviderID int + UIN string + PersonID int + StartTime time.Time + EndTime time.Time + ExternalToken string +} diff --git a/driver/web/apis_client.go b/driver/web/apis_client.go new file mode 100644 index 00000000..3ae61c0a --- /dev/null +++ b/driver/web/apis_client.go @@ -0,0 +1,621 @@ +// Copyright 2022 Board of Trustees of the University of Illinois. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package web + +import ( + "application/core" + "application/core/model" + utils "application/utils" + "encoding/json" + "io" + "log" + "net/http" + "strconv" + + "github.com/gorilla/mux" + "github.com/rokwire/core-auth-library-go/v3/tokenauth" + "github.com/rokwire/logging-library-go/v2/logs" + "github.com/rokwire/logging-library-go/v2/logutils" +) + +// ClientAPIsHandler handles the client rest APIs implementation +type ClientAPIsHandler struct { + app *core.Application +} + +func (h ClientAPIsHandler) getExample(l *logs.Log, r *http.Request, claims *tokenauth.Claims) logs.HTTPResponse { + params := mux.Vars(r) + id := params["id"] + if len(id) <= 0 { + return l.HTTPResponseErrorData(logutils.StatusMissing, logutils.TypePathParam, logutils.StringArgs("id"), nil, http.StatusBadRequest, false) + } + + example, err := h.app.Client.GetExample(claims.OrgID, claims.AppID, id) + if err != nil { + return l.HTTPResponseErrorAction(logutils.ActionGet, model.TypeExample, nil, err, http.StatusInternalServerError, true) + } + + response, err := json.Marshal(example) + if err != nil { + return l.HTTPResponseErrorAction(logutils.ActionMarshal, logutils.TypeResponseBody, nil, err, http.StatusInternalServerError, false) + } + return l.HTTPResponseSuccessJSON(response) +} + +// GetBuilding returns an the building matching the provided building id +// @Summary Get the requested building with all of its available entrances filterd by the ADA only flag +// @Tags Client +// @ID Building +// @Accept json +// @Produce json +// @Param id query string true "Building identifier" +// @Param adaOnly query bool false "ADA entrances filter" +// @Param lat query number false "latitude coordinate of the user" +// @Param long query number false "longitude coordinate of the user" +// @Success 200 {object} model.Building +// @Security RokwireAuth +// @Router /wayfinding/building [get] +func (h ClientAPIsHandler) getBuilding(l *logs.Log, r *http.Request, claims *tokenauth.Claims) logs.HTTPResponse { + + bldgid := "" + adaOnly := false + reqParams := utils.ConstructFilter(r) + var latitude, longitude float64 + latitude = 0 + longitude = 0 + for _, v := range reqParams.Items { + if v.Field == "id" { + bldgid = v.Value[0] + } + if v.Field == "adaOnly" { + ada, err := strconv.ParseBool(v.Value[0]) + if err != nil { + return l.HTTPResponseErrorData(logutils.StatusInvalid, logutils.TypeQueryParam, logutils.StringArgs("adaOnly"), nil, http.StatusBadRequest, false) + } + adaOnly = ada + } + if v.Field == "lat" { + lat, err := strconv.ParseFloat(v.Value[0], 64) + if err != nil { + return l.HTTPResponseErrorData(logutils.StatusInvalid, logutils.TypeQueryParam, logutils.StringArgs("lat"), nil, http.StatusBadRequest, false) + } + latitude = lat + } + + if v.Field == "long" { + long, err := strconv.ParseFloat(v.Value[0], 64) + if err != nil { + return l.HTTPResponseErrorData(logutils.StatusInvalid, logutils.TypeQueryParam, logutils.StringArgs("long"), nil, http.StatusBadRequest, false) + } + longitude = long + } + } + if bldgid == "" || bldgid == "nil" { + return l.HTTPResponseErrorData(logutils.StatusInvalid, logutils.TypeQueryParam, logutils.StringArgs("id"), nil, http.StatusBadRequest, false) + } + + bldg, err := h.app.Client.GetBuilding(bldgid, adaOnly, latitude, longitude) + if err != nil { + return l.HTTPResponseErrorAction(logutils.ActionGet, model.TypeBuilding, nil, err, http.StatusInternalServerError, true) + } + + resAsJSON, err := json.Marshal(bldg) + if err != nil { + return l.HTTPResponseErrorAction(logutils.ActionMarshal, logutils.TypeResult, nil, err, http.StatusInternalServerError, false) + } + + return l.HTTPResponseSuccessJSON(resAsJSON) +} + +// GetEntrance returns a building entrance record +// @Summary Returns the entrance of the specified building that is closest to the user +// @Tags Client +// @ID Entrance +// @Param id query string true "Building identifier" +// @Param adaOnly query bool false "ADA entrances filter" +// @Param lat query number true "latitude coordinate of the user" +// @Param long query number true "longitude coordinate of the user" +// @Accept json +// @Success 200 {object} model.Entrance +// @Failure 404 {object} rest.errorMessage +// @Security RokwireAuth +// @Router /wayfinding/entrance [get] +func (h ClientAPIsHandler) getEntrance(l *logs.Log, r *http.Request, claims *tokenauth.Claims) logs.HTTPResponse { + bldgid := "" + adaOnly := false + reqParams := utils.ConstructFilter(r) + var latitude, longitude float64 + latitude = 0 + longitude = 0 + for _, v := range reqParams.Items { + if v.Field == "id" { + bldgid = v.Value[0] + } + if v.Field == "adaOnly" { + ada, err := strconv.ParseBool(v.Value[0]) + if err != nil { + return l.HTTPResponseErrorData(logutils.StatusInvalid, logutils.TypeQueryParam, logutils.StringArgs("adaOnly"), nil, http.StatusBadRequest, false) + } + adaOnly = ada + } + if v.Field == "lat" { + lat, err := strconv.ParseFloat(v.Value[0], 64) + if err != nil { + return l.HTTPResponseErrorData(logutils.StatusInvalid, logutils.TypeQueryParam, logutils.StringArgs("lat"), nil, http.StatusBadRequest, false) + } + latitude = lat + } + + if v.Field == "long" { + long, err := strconv.ParseFloat(v.Value[0], 64) + if err != nil { + return l.HTTPResponseErrorData(logutils.StatusInvalid, logutils.TypeQueryParam, logutils.StringArgs("long"), nil, http.StatusBadRequest, false) + } + longitude = long + } + } + if bldgid == "" || bldgid == "nil" { + return l.HTTPResponseErrorData(logutils.StatusInvalid, logutils.TypeQueryParam, logutils.StringArgs("id"), nil, http.StatusBadRequest, false) + } + + entrance, err := h.app.Client.GetEntrance(bldgid, adaOnly, latitude, longitude) + if err != nil { + return l.HTTPResponseErrorAction(logutils.ActionGet, model.TypeBuilding, nil, err, http.StatusInternalServerError, true) + } + + if entrance == nil { + return l.HTTPResponseErrorAction(logutils.ActionFind, model.TypeBuilding, nil, err, http.StatusNotFound, true) + + } + resAsJSON, err := json.Marshal(entrance) + if err != nil { + return l.HTTPResponseErrorAction(logutils.ActionMarshal, logutils.TypeResult, nil, err, http.StatusInternalServerError, false) + } + + return l.HTTPResponseSuccessJSON(resAsJSON) + +} + +// GetBuildings returns a list of all buildings +// @Summary Get a list of all buildings with a list of active entrances +// @Tags Client +// @ID BuildingList +// @Accept json +// @Produce json +// @Success 200 {object} []model.Building +// @Security RokwireAuth +// @Router /wayfinding/buildings [get] +func (h ClientAPIsHandler) getBuildings(l *logs.Log, r *http.Request, claims *tokenauth.Claims) logs.HTTPResponse { + bldgs, err := h.app.Client.GetBuildings() + + if err != nil { + return l.HTTPResponseErrorAction(logutils.ActionGet, model.TypeBuilding, nil, err, http.StatusInternalServerError, true) + } + + if bldgs == nil { + return l.HTTPResponseErrorAction(logutils.ActionGet, model.TypeBuilding, nil, err, http.StatusNotFound, true) + + } + resAsJSON, err := json.Marshal(bldgs) + if err != nil { + return l.HTTPResponseErrorAction(logutils.ActionMarshal, logutils.TypeResult, nil, err, http.StatusInternalServerError, false) + } + + return l.HTTPResponseSuccessJSON(resAsJSON) +} + +// GetTermSessions returns a list of recent, current and upcoming term sessions +// @Summary Get a list of term sessions centered on the calculated current session +// @Tags Client +// @ID TermSession +// @Accept json +// @Produce json +// @Success 200 {object} []model.TermSession +// @Security RokwireAuth +// @Router /termsessions/listcurrent [get] +func (h ClientAPIsHandler) getTermSessions(l *logs.Log, r *http.Request, claims *tokenauth.Claims) logs.HTTPResponse { + + termSessions, err := h.app.Client.GetTermSessions() + if err != nil { + return l.HTTPResponseErrorAction(logutils.ActionGet, model.TypeTermSession, nil, err, http.StatusInternalServerError, true) + } + + if termSessions == nil { + return l.HTTPResponseErrorAction(logutils.ActionGet, model.TypeBuilding, nil, err, http.StatusNotFound, true) + + } + resAsJSON, err := json.Marshal(termSessions) + if err != nil { + return l.HTTPResponseErrorAction(logutils.ActionMarshal, logutils.TypeResult, nil, err, http.StatusInternalServerError, false) + } + + return l.HTTPResponseSuccessJSON(resAsJSON) +} + +// GetContactInfo returns the contact information of a person +// @Summary Returns the name, permanent and mailing addresses, phone number and emergency contact information for a person +// @Tags Client +// @ID ConatctInfo +// @Param id query string true "User ID" +// @Accept json +// @Produce json +// @Success 200 {object} model.Person +// @Security RokwireAuth ExternalAuth +// @Router /person/contactinfo [get] +func (h ClientAPIsHandler) getContactInfo(l *logs.Log, r *http.Request, claims *tokenauth.Claims) logs.HTTPResponse { + + externalToken := r.Header.Get("External-Authorization") + if externalToken == "" { + return l.HTTPResponseErrorData(logutils.StatusMissing, logutils.TypeHeader, logutils.StringArgs("external auth token"), nil, http.StatusBadRequest, false) + } + + mode := "0" + uin := "" + reqParams := utils.ConstructFilter(r) + if reqParams != nil { + for _, v := range reqParams.Items { + switch v.Field { + case "id": + uin = v.Value[0] + case "mode": + mode = v.Value[0] + } + } + } + + if uin == "" || uin == "null" { + return l.HTTPResponseErrorData(logutils.StatusMissing, logutils.TypePathParam, logutils.StringArgs("id"), nil, http.StatusBadRequest, false) + } + + if uin == "123456789" { + mode = "1" + } + + person, statusCode, err := h.app.Client.GetContactInfo(uin, externalToken, mode) + if err != nil { + return h.setReturnDataOnHTTPError(l, statusCode) + } + + resAsJSON, err := json.Marshal(person) + if err != nil { + return l.HTTPResponseErrorAction(logutils.ActionMarshal, logutils.TypeResult, nil, err, http.StatusInternalServerError, false) + } + return l.HTTPResponseSuccessJSON(resAsJSON) +} + +// GetGiesCourses returns a list of registered courses for GIES students +// @Summary Returns a list of registered courses +// @Tags Client +// @ID GiesCourses +// @Param id query string true "User ID" +// @Accept json +// @Produce json +// @Success 200 {object} []model.GiesCourse +// @Security RokwireAuth ExternalAuth +// @Router /courses/giescourses [get] +func (h ClientAPIsHandler) getGiesCourses(l *logs.Log, r *http.Request, claims *tokenauth.Claims) logs.HTTPResponse { + + externalToken := r.Header.Get("External-Authorization") + if externalToken == "" { + return l.HTTPResponseErrorData(logutils.StatusMissing, logutils.TypeHeader, logutils.StringArgs("external auth token"), nil, http.StatusBadRequest, false) + } + + id := "" + reqParams := utils.ConstructFilter(r) + if reqParams != nil { + for _, v := range reqParams.Items { + switch v.Field { + case "id": + id = v.Value[0] + } + } + } + + if id == "" || id == "null" { + return l.HTTPResponseErrorData(logutils.StatusInvalid, logutils.TypeQueryParam, logutils.StringArgs("id"), nil, http.StatusBadRequest, false) + } + + giesCourseList, statusCode, err := h.app.Client.GetGiesCourses(id, externalToken) + if err != nil { + return h.setReturnDataOnHTTPError(l, statusCode) + } + + resAsJSON, err := json.Marshal(giesCourseList) + if err != nil { + return l.HTTPResponseErrorAction(logutils.ActionMarshal, logutils.TypeResult, nil, err, http.StatusInternalServerError, false) + } + return l.HTTPResponseSuccessJSON(resAsJSON) +} + +// GetStudentcourses returns a list of registered courses for a student +// @Summary Returns a list of registered courses +// @Tags Client +// @ID Studentcourses +// @Param id query string true "User ID" +// @Param termid query string true "term id" +// @Accept json +// @Produce json +// @Success 200 {object} []model.Course +// @Security RokwireAuth ExternalAuth +// @Router /courses/studentcourses [get] +func (h ClientAPIsHandler) getStudentCourses(l *logs.Log, r *http.Request, claims *tokenauth.Claims) logs.HTTPResponse { + + externalToken := r.Header.Get("External-Authorization") + if externalToken == "" { + return l.HTTPResponseErrorData(logutils.StatusMissing, logutils.TypeHeader, logutils.StringArgs("external auth token"), nil, http.StatusBadRequest, false) + } + + id := "" + termid := "" + adaOnly := false + var latitude, longitude float64 + latitude = 0 + longitude = 0 + + reqParams := utils.ConstructFilter(r) + if reqParams != nil { + for _, v := range reqParams.Items { + switch v.Field { + case "id": + id = v.Value[0] + case "termid": + termid = v.Value[0] + case "long": + long, err := strconv.ParseFloat(v.Value[0], 64) + if err != nil { + return l.HTTPResponseErrorData(logutils.StatusInvalid, logutils.TypeQueryParam, logutils.StringArgs("long"), nil, http.StatusBadRequest, false) + } + longitude = long + case "lat": + lat, err := strconv.ParseFloat(v.Value[0], 64) + if err != nil { + return l.HTTPResponseErrorData(logutils.StatusInvalid, logutils.TypeQueryParam, logutils.StringArgs("lat"), nil, http.StatusBadRequest, false) + } + latitude = lat + case "adaOnly": + ada, err := strconv.ParseBool(v.Value[0]) + if err != nil { + return l.HTTPResponseErrorData(logutils.StatusInvalid, logutils.TypeQueryParam, logutils.StringArgs("adaOnly"), nil, http.StatusBadRequest, false) + } + adaOnly = ada + } + } + } + + if id == "" || id == "null" { + return l.HTTPResponseErrorData(logutils.StatusMissing, logutils.TypeQueryParam, logutils.StringArgs("id"), nil, http.StatusBadRequest, false) + } + + if termid == "" || termid == "null" { + return l.HTTPResponseErrorData(logutils.StatusMissing, logutils.TypeQueryParam, logutils.StringArgs("termid"), nil, http.StatusBadRequest, false) + } + + courseList, statusCode, err := h.app.Client.GetStudentCourses(id, termid, externalToken) + if err != nil { + return h.setReturnDataOnHTTPError(l, statusCode) + } + + //create a map of buildings we need so we don't retrieve the same building multiple times + neededBuildings := make(map[string]model.Building) + for index, crntCourse := range *courseList { + //check if course is not online here then proceed + if crntCourse.Section.BuildingID != "" { + bldg, bldgexists := neededBuildings[crntCourse.Section.BuildingID] + if bldgexists { + (*courseList)[index].Section.Location = bldg + } else { + bldg, err := (h.app.Client.GetBuilding(crntCourse.Section.BuildingID, adaOnly, latitude, longitude)) + if err != nil { + log.Printf("Error retrieving building details: %s\n", err) + } else { + (*courseList)[index].Section.Location = *bldg + neededBuildings[bldg.ID] = *bldg + } + } + } + + } + + resAsJSON, err := json.Marshal(courseList) + if err != nil { + return l.HTTPResponseErrorAction(logutils.ActionMarshal, logutils.TypeResult, nil, err, http.StatusInternalServerError, false) + } + return l.HTTPResponseSuccessJSON(resAsJSON) +} + +// GetLaundryRooms returns an organization record +// @Summary Get list of all campus laundry rooms +// @Tags Client +// @ID Rooms +// @Accept json +// @Produce json +// @Success 200 {object} model.Organization +// @Security RokwireAuth +// @Router /laundry/rooms [get] +func (h ClientAPIsHandler) getLaundryRooms(l *logs.Log, r *http.Request, claims *tokenauth.Claims) logs.HTTPResponse { + org, err := h.app.Client.ListLaundryRooms() + if err != nil { + return l.HTTPResponseErrorAction(logutils.ActionGet, model.TypeLaundryRooms, nil, err, http.StatusInternalServerError, true) + } + + if org == nil { + return l.HTTPResponseErrorAction(logutils.ActionGet, model.TypeLaundryRooms, nil, err, http.StatusNotFound, true) + + } + resAsJSON, err := json.Marshal(org) + if err != nil { + return l.HTTPResponseErrorAction(logutils.ActionMarshal, logutils.TypeResult, nil, err, http.StatusInternalServerError, false) + } + + return l.HTTPResponseSuccessJSON(resAsJSON) +} + +// GetRoomDetails returns a laundry room detail record +// @Summary Returns the list of machines and the number of washers and dryers available in a laundry room +// @Tags Client +// @ID Room +// @Param id query int true "Room id" +// @Accept json +// @Success 200 {object} model.RoomDetail +// @Security RokwireAuth +// @Router /laundry/roomdetail [get] +func (h ClientAPIsHandler) getRoomDetails(l *logs.Log, r *http.Request, claims *tokenauth.Claims) logs.HTTPResponse { + reqParams := utils.ConstructFilter(r) + id := "" + for _, v := range reqParams.Items { + if v.Field == "id" { + id = v.Value[0] + break + } + } + + if id == "" || id == "nil" { + return l.HTTPResponseErrorData(logutils.StatusMissing, logutils.TypeQueryParam, logutils.StringArgs("id"), nil, http.StatusBadRequest, false) + } + + rd, err := h.app.Client.GetLaundryRoom(id) + if err != nil { + return l.HTTPResponseErrorAction(logutils.ActionGet, model.TypeLaundryRooms, nil, err, http.StatusInternalServerError, true) + } + + if rd == nil { + return l.HTTPResponseErrorAction(logutils.ActionGet, model.TypeLaundryRooms, nil, err, http.StatusNotFound, true) + + } + + resAsJSON, err := json.Marshal(rd) + if err != nil { + return l.HTTPResponseErrorAction(logutils.ActionMarshal, logutils.TypeResult, nil, err, http.StatusInternalServerError, false) + } + + return l.HTTPResponseSuccessJSON(resAsJSON) +} + +// InitServiceRequest returns a laundry room detail record +// @Summary Returns the problem codes and pending service reqeust status for a laundry machine. +// @Tags Client +// @ID InitRequest +// @Param machineid query string true "machine service tag id" +// @Accept json +// @Success 200 {object} model.MachineRequestDetail +// @Security RokwireAuth +// @Router /laundry/initrequest [get] +func (h ClientAPIsHandler) initServiceRequest(l *logs.Log, r *http.Request, claims *tokenauth.Claims) logs.HTTPResponse { + reqParams := utils.ConstructFilter(r) + id := "" + for _, v := range reqParams.Items { + if v.Field == "machineid" { + //do work here + id = v.Value[0] + break + } + } + if id == "" || id == "nil" { + return l.HTTPResponseErrorData(logutils.StatusMissing, logutils.TypeQueryParam, logutils.StringArgs("machineid"), nil, http.StatusBadRequest, false) + } + + mrd, err := h.app.Client.InitServiceRequest(id) + if err != nil { + return l.HTTPResponseErrorAction(logutils.ActionGet, model.TypeLaundryServiceSubmission, nil, err, http.StatusInternalServerError, true) + } + + if mrd == nil { + return l.HTTPResponseErrorAction(logutils.ActionGet, model.TypeLaundryRooms, nil, err, http.StatusNotFound, true) + + } + + resAsJSON, err := json.Marshal(mrd) + if err != nil { + return l.HTTPResponseErrorAction(logutils.ActionMarshal, logutils.TypeResult, nil, err, http.StatusInternalServerError, false) + } + + return l.HTTPResponseSuccessJSON(resAsJSON) +} + +// SubmitServiceRequest returns the results of attempting to submit a service request for a laundyr appliance +// @Tags Client +// @ID RequestService +// @Param data body model.ServiceSubmission true "body json" +// @Accept json +// @Success 200 {object} model.ServiceRequestResult +// @Security RokwireAuth +// @Router /laundry/requestservice [post] +func (h ClientAPIsHandler) submitServiceRequest(l *logs.Log, r *http.Request, claims *tokenauth.Claims) logs.HTTPResponse { + data, err := io.ReadAll(r.Body) + if err != nil { + return l.HTTPResponseErrorData(logutils.StatusInvalid, logutils.TypeRequestBody, nil, err, http.StatusBadRequest, false) + } + + var record model.ServiceSubmission + err = json.Unmarshal(data, &record) + if err != nil { + return l.HTTPResponseErrorAction(logutils.ActionMarshal, logutils.TypeRequestBody, nil, err, http.StatusBadRequest, false) + } + + if record.MachineID == nil || len(*record.MachineID) == 0 { + return l.HTTPResponseErrorData(logutils.StatusMissing, logutils.TypeRequestBody, nil, err, http.StatusBadRequest, false) + } + + if record.ProblemCode == nil || len(*record.ProblemCode) == 0 { + return l.HTTPResponseErrorData(logutils.StatusMissing, logutils.TypeRequestBody, nil, err, http.StatusBadRequest, false) + } + + if record.FirstName == nil || len(*record.FirstName) == 0 { + return l.HTTPResponseErrorData(logutils.StatusMissing, logutils.TypeRequestBody, nil, err, http.StatusBadRequest, false) + } + + if record.LastName == nil || len(*record.LastName) == 0 { + return l.HTTPResponseErrorData(logutils.StatusMissing, logutils.TypeRequestBody, nil, err, http.StatusBadRequest, false) + } + + if record.Email == nil || len(*record.Email) == 0 { + return l.HTTPResponseErrorData(logutils.StatusMissing, logutils.TypeRequestBody, nil, err, http.StatusBadRequest, false) + } + + if record.Phone == nil { + newPhone := "" + record.Phone = &newPhone + } + + sr, err := h.app.Client.SubmitServiceRequest(*record.MachineID, *record.ProblemCode, *record.Comments, *record.FirstName, *record.LastName, *record.Phone, *record.Email) + + if err != nil { + return l.HTTPResponseErrorAction(logutils.ActionGet, model.TypeLaundryServiceSubmission, nil, err, http.StatusInternalServerError, true) + } + + resAsJSON, err := json.Marshal(sr) + if err != nil { + return l.HTTPResponseErrorAction(logutils.ActionMarshal, logutils.TypeResult, nil, err, http.StatusInternalServerError, false) + } + return l.HTTPResponseSuccessJSON(resAsJSON) +} + +// NewClientAPIsHandler creates new client API handler instance +func NewClientAPIsHandler(app *core.Application) ClientAPIsHandler { + return ClientAPIsHandler{app: app} +} + +func (h ClientAPIsHandler) setReturnDataOnHTTPError(l *logs.Log, statuscode int) logs.HTTPResponse { + switch statuscode { + case 401: + return l.HTTPResponseErrorData(logutils.MessageDataStatus(logutils.StatusError), logutils.TypeClaim, logutils.StringArgs("id"), nil, http.StatusForbidden, false) + case 403: + return l.HTTPResponseErrorData(logutils.MessageDataStatus(logutils.StatusError), logutils.TypeClaim, logutils.StringArgs("id"), nil, http.StatusForbidden, false) + case 404: + return l.HTTPResponseErrorAction(logutils.ActionFind, logutils.TypeResult, logutils.StringArgs("id"), nil, statuscode, false) + default: + return l.HTTPResponseErrorData(logutils.MessageDataStatus(logutils.StatusError), logutils.TypeError, logutils.StringArgs("id"), nil, http.StatusInternalServerError, false) + } +} diff --git a/driver/web/apis_default.go b/driver/web/apis_default.go new file mode 100644 index 00000000..ec90d058 --- /dev/null +++ b/driver/web/apis_default.go @@ -0,0 +1,37 @@ +// Copyright 2022 Board of Trustees of the University of Illinois. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package web + +import ( + "application/core" + "net/http" + + "github.com/rokwire/core-auth-library-go/v3/tokenauth" + "github.com/rokwire/logging-library-go/v2/logs" +) + +// DefaultAPIsHandler handles the default rest APIs implementation +type DefaultAPIsHandler struct { + app *core.Application +} + +func (h DefaultAPIsHandler) version(l *logs.Log, r *http.Request, claims *tokenauth.Claims) logs.HTTPResponse { + return l.HTTPResponseSuccessMessage(h.app.Default.GetVersion()) +} + +// NewDefaultAPIsHandler creates new default API Handler instance +func NewDefaultAPIsHandler(app *core.Application) DefaultAPIsHandler { + return DefaultAPIsHandler{app: app} +} diff --git a/driver/web/apis_system.go b/driver/web/apis_system.go new file mode 100644 index 00000000..f5b79a94 --- /dev/null +++ b/driver/web/apis_system.go @@ -0,0 +1,56 @@ +// Copyright 2022 Board of Trustees of the University of Illinois. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package web + +import ( + "application/core" + "application/core/model" + "encoding/json" + "net/http" + + "github.com/gorilla/mux" + "github.com/rokwire/core-auth-library-go/v3/tokenauth" + "github.com/rokwire/logging-library-go/v2/logs" + "github.com/rokwire/logging-library-go/v2/logutils" +) + +// SystemAPIsHandler handles the rest system admin APIs implementation +type SystemAPIsHandler struct { + app *core.Application +} + +func (h SystemAPIsHandler) getExample(l *logs.Log, r *http.Request, claims *tokenauth.Claims) logs.HTTPResponse { + params := mux.Vars(r) + id := params["id"] + if len(id) <= 0 { + return l.HTTPResponseErrorData(logutils.StatusMissing, logutils.TypePathParam, logutils.StringArgs("id"), nil, http.StatusBadRequest, false) + } + + example, err := h.app.System.GetExample(claims.OrgID, claims.AppID, id) + if err != nil { + return l.HTTPResponseErrorAction(logutils.ActionGet, model.TypeExample, nil, err, http.StatusInternalServerError, true) + } + + response, err := json.Marshal(example) + if err != nil { + return l.HTTPResponseErrorAction(logutils.ActionMarshal, logutils.TypeResponseBody, nil, err, http.StatusInternalServerError, false) + } + return l.HTTPResponseSuccessJSON(response) +} + +// NewSystemAPIsHandler creates new system admin API handler instance +func NewSystemAPIsHandler(app *core.Application) SystemAPIsHandler { + return SystemAPIsHandler{app: app} +} diff --git a/driver/web/apis_tps.go b/driver/web/apis_tps.go new file mode 100644 index 00000000..1df4a87d --- /dev/null +++ b/driver/web/apis_tps.go @@ -0,0 +1,56 @@ +// Copyright 2022 Board of Trustees of the University of Illinois. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package web + +import ( + "application/core" + "application/core/model" + "encoding/json" + "net/http" + + "github.com/gorilla/mux" + "github.com/rokwire/core-auth-library-go/v3/tokenauth" + "github.com/rokwire/logging-library-go/v2/logs" + "github.com/rokwire/logging-library-go/v2/logutils" +) + +// TPSAPIsHandler handles the rest third-party service APIs implementation +type TPSAPIsHandler struct { + app *core.Application +} + +func (h TPSAPIsHandler) getExample(l *logs.Log, r *http.Request, claims *tokenauth.Claims) logs.HTTPResponse { + params := mux.Vars(r) + id := params["id"] + if len(id) <= 0 { + return l.HTTPResponseErrorData(logutils.StatusMissing, logutils.TypePathParam, logutils.StringArgs("id"), nil, http.StatusBadRequest, false) + } + + example, err := h.app.TPS.GetExample(claims.OrgID, claims.AppID, id) + if err != nil { + return l.HTTPResponseErrorAction(logutils.ActionGet, model.TypeExample, nil, err, http.StatusInternalServerError, true) + } + + response, err := json.Marshal(example) + if err != nil { + return l.HTTPResponseErrorAction(logutils.ActionMarshal, logutils.TypeResponseBody, nil, err, http.StatusInternalServerError, false) + } + return l.HTTPResponseSuccessJSON(response) +} + +// NewTPSAPIsHandler creates new third-party service API handler instance +func NewTPSAPIsHandler(app *core.Application) TPSAPIsHandler { + return TPSAPIsHandler{app: app} +} diff --git a/driver/web/auth.go b/driver/web/auth.go index 8351d7aa..906b005b 100644 --- a/driver/web/auth.go +++ b/driver/web/auth.go @@ -15,76 +15,172 @@ package web import ( - "fmt" - "log" "net/http" - "time" - "github.com/rokwire/core-auth-library-go/authservice" - "github.com/rokwire/core-auth-library-go/tokenauth" - "github.com/rokwire/logging-library-go/logs" + "github.com/rokwire/core-auth-library-go/v3/authorization" + "github.com/rokwire/logging-library-go/v2/errors" + "github.com/rokwire/logging-library-go/v2/logutils" + + "github.com/rokwire/core-auth-library-go/v3/authservice" + "github.com/rokwire/core-auth-library-go/v3/tokenauth" ) -type cacheUser struct { - lastUsage time.Time +// Auth handler +type Auth struct { + client tokenauth.Handlers + admin tokenauth.Handlers + bbs tokenauth.Handlers + tps tokenauth.Handlers + system tokenauth.Handlers } -// TokenAuth used to encapsualte the tokenauth type from the core auth library -type TokenAuth struct { - tokenAuth *tokenauth.TokenAuth +// NewAuth creates new auth handler +func NewAuth(serviceRegManager *authservice.ServiceRegManager) (*Auth, error) { + client, err := newClientAuth(serviceRegManager) + if err != nil { + return nil, errors.WrapErrorAction(logutils.ActionCreate, "client auth", nil, err) + } + clientHandlers := tokenauth.NewHandlers(client) + + admin, err := newAdminAuth(serviceRegManager) + if err != nil { + return nil, errors.WrapErrorAction(logutils.ActionCreate, "admin auth", nil, err) + } + adminHandlers := tokenauth.NewHandlers(admin) + + bbs, err := newBBsAuth(serviceRegManager) + if err != nil { + return nil, errors.WrapErrorAction(logutils.ActionCreate, "bbs auth", nil, err) + } + bbsHandlers := tokenauth.NewHandlers(bbs) + + tps, err := newTPSAuth(serviceRegManager) + if err != nil { + return nil, errors.WrapErrorAction(logutils.ActionCreate, "tps auth", nil, err) + } + tpsHandlers := tokenauth.NewHandlers(tps) + + system, err := newSystemAuth(serviceRegManager) + if err != nil { + return nil, errors.WrapErrorAction(logutils.ActionCreate, "system auth", nil, err) + } + systemHandlers := tokenauth.NewHandlers(system) + + auth := Auth{ + client: clientHandlers, + admin: adminHandlers, + bbs: bbsHandlers, + tps: tpsHandlers, + system: systemHandlers, + } + return &auth, nil } -// Check checks the request contains a valid Core access token -func (auth TokenAuth) Check(r *http.Request) (bool, *tokenauth.Claims) { - claims, err := auth.tokenAuth.CheckRequestTokens(r) +/////// + +func newClientAuth(serviceRegManager *authservice.ServiceRegManager) (*tokenauth.StandardHandler, error) { + clientPermissionAuth := authorization.NewCasbinStringAuthorization("driver/web/client_permission_policy.csv") + clientScopeAuth := authorization.NewCasbinScopeAuthorization("driver/web/client_scope_policy.csv", serviceRegManager.AuthService.ServiceID) + clientTokenAuth, err := tokenauth.NewTokenAuth(true, serviceRegManager, clientPermissionAuth, clientScopeAuth) if err != nil { - log.Printf("auth -> coreAuthCheck: FAILED to validate token: %s", err.Error()) - return false, nil + return nil, errors.WrapErrorAction(logutils.ActionCreate, "client token auth", nil, err) } - if claims != nil { - if claims.Valid() == nil { - return true, claims + check := func(claims *tokenauth.Claims, req *http.Request) (int, error) { + if claims.Admin { + return http.StatusUnauthorized, errors.ErrorData(logutils.StatusInvalid, "admin claim", nil) } + if claims.System { + return http.StatusUnauthorized, errors.ErrorData(logutils.StatusInvalid, "system claim", nil) + } + + return http.StatusOK, nil } - return false, nil -} -func printDeletedAccountIDs(accountIDs []string) error { - log.Printf("Deleted account IDs: %v\n", accountIDs) - return nil + auth := tokenauth.NewScopeHandler(clientTokenAuth, check) + return auth, nil } -// NewTokenAuth creats a token auth instance -func NewTokenAuth(serviceHost string, coreHost string) *TokenAuth { - serviceID := "gateway" +func newAdminAuth(serviceRegManager *authservice.ServiceRegManager) (*tokenauth.StandardHandler, error) { + adminPermissionAuth := authorization.NewCasbinStringAuthorization("driver/web/admin_permission_policy.csv") + adminTokenAuth, err := tokenauth.NewTokenAuth(true, serviceRegManager, adminPermissionAuth, nil) + if err != nil { + return nil, errors.WrapErrorAction(logutils.ActionCreate, "admin token auth", nil, err) + } + + check := func(claims *tokenauth.Claims, req *http.Request) (int, error) { + if !claims.Admin { + return http.StatusUnauthorized, errors.ErrorData(logutils.StatusInvalid, "admin claim", nil) + } - config := authservice.RemoteAuthDataLoaderConfig{ - AuthServicesHost: coreHost, + return http.StatusOK, nil } - logger := logs.NewLogger(serviceID, nil) - dataLoader, err := authservice.NewRemoteAuthDataLoader(config, []string{"gateway"}, logger) + auth := tokenauth.NewStandardHandler(adminTokenAuth, check) + return auth, nil +} +func newBBsAuth(serviceRegManager *authservice.ServiceRegManager) (*tokenauth.StandardHandler, error) { + bbsPermissionAuth := authorization.NewCasbinStringAuthorization("driver/web/bbs_permission_policy.csv") + bbsTokenAuth, err := tokenauth.NewTokenAuth(true, serviceRegManager, bbsPermissionAuth, nil) if err != nil { - log.Fatalf("Error initializing auth service: %v", err) + return nil, errors.WrapErrorAction(logutils.ActionStart, "bbs token auth", nil, err) } - authHost := fmt.Sprintf("%s/bbs/service-regs", coreHost) - fmt.Println(authHost) - hostArray := make([]string, 1) - hostArray[0] = authHost - authService, err := authservice.NewAuthService(serviceID, serviceHost, dataLoader) + check := func(claims *tokenauth.Claims, req *http.Request) (int, error) { + if !claims.Service { + return http.StatusUnauthorized, errors.ErrorData(logutils.StatusInvalid, "service claim", nil) + } + + if !claims.FirstParty { + return http.StatusUnauthorized, errors.ErrorData(logutils.StatusInvalid, "first party claim", nil) + } + return http.StatusOK, nil + } + + auth := tokenauth.NewStandardHandler(bbsTokenAuth, check) + return auth, nil +} + +func newTPSAuth(serviceRegManager *authservice.ServiceRegManager) (*tokenauth.StandardHandler, error) { + tpsPermissionAuth := authorization.NewCasbinStringAuthorization("driver/web/tps_permission_policy.csv") + tpsTokenAuth, err := tokenauth.NewTokenAuth(true, serviceRegManager, tpsPermissionAuth, nil) if err != nil { - log.Fatalf("Error initializing auth service: %v", err) + return nil, errors.WrapErrorAction(logutils.ActionStart, "tps token auth", nil, err) + } + + check := func(claims *tokenauth.Claims, req *http.Request) (int, error) { + if !claims.Service { + return http.StatusUnauthorized, errors.ErrorData(logutils.StatusInvalid, "service claim", nil) + } + + if claims.FirstParty { + return http.StatusUnauthorized, errors.ErrorData(logutils.StatusInvalid, "first party claim", nil) + } + + return http.StatusOK, nil } - tokenAuth, err := tokenauth.NewTokenAuth(true, authService, nil, nil) + auth := tokenauth.NewStandardHandler(tpsTokenAuth, check) + return auth, nil +} + +func newSystemAuth(serviceRegManager *authservice.ServiceRegManager) (*tokenauth.StandardHandler, error) { + systemPermissionAuth := authorization.NewCasbinStringAuthorization("driver/web/system_permission_policy.csv") + systemTokenAuth, err := tokenauth.NewTokenAuth(true, serviceRegManager, systemPermissionAuth, nil) if err != nil { - log.Fatalf("auth -> newAuth: FAILED to init token auth: %s", err.Error()) + return nil, errors.WrapErrorAction(logutils.ActionCreate, "system token auth", nil, err) + } + + check := func(claims *tokenauth.Claims, req *http.Request) (int, error) { + if !claims.System { + return http.StatusUnauthorized, errors.ErrorData(logutils.StatusInvalid, "system claim", nil) + } + + return http.StatusOK, nil } - auth := TokenAuth{tokenAuth: tokenAuth} - return &auth + auth := tokenauth.NewStandardHandler(systemTokenAuth, check) + return auth, nil } diff --git a/driver/web/authorization_model.conf b/driver/web/authorization_model.conf deleted file mode 100644 index 4f86ba8f..00000000 --- a/driver/web/authorization_model.conf +++ /dev/null @@ -1,11 +0,0 @@ -[request_definition] -r = sub, obj, act - -[policy_definition] -p = sub, obj, act - -[policy_effect] -e = some(where (p.eft == allow)) - -[matchers] -m = r.sub == p.sub && keyMatch(r.obj, p.obj) && regexMatch(r.act, p.act) \ No newline at end of file diff --git a/driver/web/bbs_permission_policy.csv b/driver/web/bbs_permission_policy.csv new file mode 100644 index 00000000..3442339e --- /dev/null +++ b/driver/web/bbs_permission_policy.csv @@ -0,0 +1,5 @@ +p, get_examples, /gateway/api/bbs/examples/*, (GET), Get examples +p, all_external_appointments, /gateway/api/bbs/appointments/*, (GET|POST|PUT|DELETE), All external appointment management actions +p, get_external_appointments, /gateway/api/bbs/appointments/*, (GET), Get appointments in external systems +p, update_external_appointments, /gateway/api/bbs/appointments/*, (GET|POST|PUT), Update appointments in external systems +p, delete_external_appointments, /gateway/api/bbs/appointments/*, (DELETE), Delete appointments in external systems \ No newline at end of file diff --git a/driver/web/client_permission_policy.csv b/driver/web/client_permission_policy.csv new file mode 100644 index 00000000..940903a6 --- /dev/null +++ b/driver/web/client_permission_policy.csv @@ -0,0 +1 @@ +p, get_examples, /identity/api/examples/*, (GET), Get examples diff --git a/driver/web/client_scope_policy.csv b/driver/web/client_scope_policy.csv new file mode 100644 index 00000000..e69de29b diff --git a/driver/web/docs/gen/def.yaml b/driver/web/docs/gen/def.yaml new file mode 100644 index 00000000..e146a096 --- /dev/null +++ b/driver/web/docs/gen/def.yaml @@ -0,0 +1,1297 @@ +openapi: 3.0.3 +info: + title: Rokwire Identity Building Block API + description: Identity Building Block API Documentation + version: 1.0.0 +servers: + - url: 'https://api.rokwire.illinois.edu/identity' + description: Production server + - url: 'https://api-test.rokwire.illinois.edu/identity' + description: Test server + - url: 'https://api-dev.rokwire.illinois.edu/identity' + description: Development server + - url: 'http://localhost/identity' + description: Local server +tags: + - name: Client + description: Client applications APIs. + - name: Admin + description: Clients administration applications APIs. + - name: BBs + description: Building Block APIs. + - name: TPS + description: Third-Party Service APIs. + - name: System + description: Third-Party Service APIs. + - name: Default + description: Default APIs. +paths: + /version: + get: + tags: + - Default + summary: Get version + description: | + Gets current version of this service + responses: + '200': + description: Success + content: + text/plain: + schema: + type: string + example: v1.0.0 + '500': + description: Internal error + '/api/examples/{id}': + get: + tags: + - Client + summary: Gets example + description: | + Gets example record + + **Auth:** Requires valid user token with `get_examples` permission + security: + - bearerAuth: [] + parameters: + - name: id + in: path + description: ID of example to retrieve + required: true + style: simple + explode: false + schema: + type: string + responses: + '200': + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/Example' + '400': + description: Bad request + '401': + description: Unauthorized + '500': + description: Internal error + /api/admin/examples: + post: + tags: + - Admin + summary: Create example + description: | + Creates new example record + + **Auth:** Requires valid admin token with `update_examples` or `all_examples` permission + security: + - bearerAuth: [] + parameters: + - name: id + in: path + description: ID of example to update + required: true + style: simple + explode: false + schema: + type: string + requestBody: + description: New example content + content: + application/json: + schema: + $ref: '#/components/schemas/Example' + responses: + '200': + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/Example' + '400': + description: Bad request + '401': + description: Unauthorized + '500': + description: Internal error + '/api/admin/examples/{id}': + get: + tags: + - Admin + summary: Gets example + description: | + Gets example record + + **Auth:** Requires valid admin token with one of the following permissions: + - `get_examples` + - `update_examples` + - `delete_examples` + - `all_examples` + security: + - bearerAuth: [] + parameters: + - name: id + in: path + description: ID of example to retrieve + required: true + style: simple + explode: false + schema: + type: string + responses: + '200': + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/Example' + '400': + description: Bad request + '401': + description: Unauthorized + '500': + description: Internal error + put: + tags: + - Admin + summary: Update example + description: | + Updates example record + + **Auth:** Requires valid admin token with `update_examples` or `all_examples` permission + security: + - bearerAuth: [] + parameters: + - name: id + in: path + description: ID of example to update + required: true + style: simple + explode: false + schema: + type: string + requestBody: + description: New example content + content: + application/json: + schema: + $ref: '#/components/schemas/Example' + responses: + '200': + description: Success + content: + text/plain: + schema: + type: string + example: Success + '400': + description: Bad request + '401': + description: Unauthorized + '500': + description: Internal error + delete: + tags: + - Admin + summary: Delete example + description: | + Deletes example record + + **Auth:** Requires valid admin token with `delete_examples` or `all_examples` permission + security: + - bearerAuth: [] + parameters: + - name: id + in: path + description: ID of example to delete + required: true + style: simple + explode: false + schema: + type: string + responses: + '200': + description: Success + content: + text/plain: + schema: + type: string + example: Success + '400': + description: Bad request + '401': + description: Unauthorized + '500': + description: Internal error + /api/admin/configs: + get: + tags: + - Admin + summary: Get configs + description: | + Get existing configs by search parameters + + **Auth:** Requires valid admin token with one of the following permissions: + - `get_configs_identity` + - `update_configs_identity` + - `delete_configs_identity` + - `all_configs_identity` + security: + - bearerAuth: [] + parameters: + - name: type + in: query + description: config type + required: false + style: form + explode: false + schema: + type: string + responses: + '200': + description: Success + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Config' + '400': + description: Bad request + '401': + description: Unauthorized + '500': + description: Internal error + post: + tags: + - Admin + summary: Create config + description: | + Creates a new config + + **Auth:** Requires valid admin token with one of the following permissions: + - `update_configs_identity` + - `all_configs_identity` + security: + - bearerAuth: [] + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/Config' + examples: + system: + summary: System-wide config + value: + type: config_type + all_apps: true + all_orgs: true + system: true + data: + example_env: example + org_admin: + summary: Organization-wide config + value: + type: config_type + all_apps: true + all_orgs: false + system: false + data: + example_env: example + app_org_specific: + summary: Application organization specific config + value: + type: config_type + all_apps: false + all_orgs: false + system: false + data: + example_env: example + required: true + responses: + '200': + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/Config' + '400': + description: Bad request + '401': + description: Unauthorized + '500': + description: Internal error + '/api/admin/configs/{id}': + get: + tags: + - Admin + summary: Get config + description: | + Gets config record + + **Auth:** Requires valid admin token with one of the following permissions: + - `get_configs_identity` + - `update_configs_identity` + - `delete_configs_identity` + - `all_configs_identity` + security: + - bearerAuth: [] + parameters: + - name: id + in: path + description: ID of config to retrieve + required: true + style: simple + explode: false + schema: + type: string + responses: + '200': + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/Config' + '400': + description: Bad request + '401': + description: Unauthorized + '500': + description: Internal error + put: + tags: + - Admin + summary: Update config + description: | + Updates existing config record + + **Auth:** Requires valid admin token with one of the following permissions: + - `update_configs_identity` + - `all_configs_identity` + security: + - bearerAuth: [] + parameters: + - name: id + in: path + description: ID of config to update + required: true + style: simple + explode: false + schema: + type: string + requestBody: + description: New config content + content: + application/json: + schema: + $ref: '#/components/schemas/Config' + examples: + system: + summary: System-wide config + value: + type: config_type + all_apps: true + all_orgs: true + system: true + data: + example_env: example + org_admin: + summary: Organization-wide config + value: + type: config_type + all_apps: true + all_orgs: false + system: false + data: + example_env: example + app_org_specific: + summary: Application organization specific config + value: + type: config_type + all_apps: false + all_orgs: false + system: false + data: + example_env: example + required: true + responses: + '200': + description: Success + content: + text/plain: + schema: + type: string + example: Success + '400': + description: Bad request + '401': + description: Unauthorized + '500': + description: Internal error + delete: + tags: + - Admin + summary: Delete config + description: | + Deletes config record + + **Auth:** Requires valid admin token with one of the following permissions: + - `delete_configs_identity` + - `all_configs_identity` + security: + - bearerAuth: [] + parameters: + - name: id + in: path + description: ID of config to delete + required: true + style: simple + explode: false + schema: + type: string + responses: + '200': + description: Success + content: + text/plain: + schema: + type: string + example: Success + '400': + description: Bad request + '401': + description: Unauthorized + '500': + description: Internal error + '/api/bbs/examples/{id}': + get: + tags: + - BBs + summary: Gets example + description: | + Gets example record + + **Auth:** Requires valid first-party service account token with `get_examples` permission + security: + - bearerAuth: [] + parameters: + - name: id + in: path + description: ID of example to retrieve + required: true + style: simple + explode: false + schema: + type: string + responses: + '200': + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/Example' + '400': + description: Bad request + '401': + description: Unauthorized + '500': + description: Internal error + /api/bbs/appointments/units: + get: + tags: + - BBs + summary: Gets the list of units (calendars) for a provider id + description: | + Gets the list of units (calendars) for a provider id + + **Auth:** Requires valid first-party service account token with `get_external_appointments` permission + Requires the External-Authorization header containing the oidc access token from the campus provider + security: + - bearerAuth: [] + parameters: + - name: external_id + in: query + description: External system id of person making the request + required: true + style: simple + explode: false + schema: + type: string + - name: provider_id + in: query + description: Rokwire provider id of the organization tracking/providing appointment data + required: true + style: simple + explode: false + schema: + type: string + responses: + '200': + description: Success + content: + application/json: + schema: + type: object + required: + - id + - provider_id + - name + - location + - hours_of_operation + - details + - next_available + - image_url + properties: + id: + type: int + readOnly: true + provider_id: + type: int + readOnly: true + name: + type: string + readOnly: true + location: + type: string + hours_of_operation: + type: string + details: + type: string + next_available: + type: string + image_url: + type: string + '400': + description: Bad request + '401': + description: Unauthorized + '500': + description: Internal error + /api/bbs/appointments/people: + get: + tags: + - BBs + summary: Gets People with Calendars + description: | + Gets a list of people with calendars inside of a unit + + **Auth:** Requires valid first-party service account token with `get_external_appointments` permission + Requires the External-Authorization header containing the oidc access token from the campus provider + security: + - bearerAuth: [] + parameters: + - name: external_id + in: query + description: External system id of person making the request + required: true + style: simple + explode: false + schema: + type: string + - name: provider_id + in: query + description: Rokwire provider id of the organization tracking/providing appointment data + required: true + style: simple + explode: false + schema: + type: string + - name: unit_id + in: query + description: External id identifying the unit the calendar entries belong to + required: true + style: simple + explode: false + schema: + type: string + responses: + '200': + description: Success + content: + application/json: + schema: + type: object + required: + - id + - provider_id + - unit_id + - next_available + - name + - notes + - image_url + properties: + id: + type: string + readOnly: true + provider_id: + type: int + readOnly: true + unit_id: + type: int + readOnly: true + next_available: + type: string + name: + type: string + notes: + type: string + image_url: + type: string + '400': + description: Bad request + '401': + description: Unauthorized + '500': + description: Internal error + /api/bbs/appointments/slots: + get: + tags: + - BBs + summary: Gets available time slots on a calendar + description: | + Gets a list of available time slots between two dates on a unit calendar + + **Auth:** Requires valid first-party service account token with `get_external_appointments` permission + Requires the External-Authorization header containing the oidc access token from the campus provider + security: + - bearerAuth: [] + parameters: + - name: external_id + in: query + description: External system id of person making the request + required: true + style: simple + explode: false + schema: + type: string + - name: provider_id + in: query + description: Rokwire provider id of the organization tracking/providing appointment data + required: true + style: simple + explode: false + schema: + type: string + - name: unit_id + in: query + description: External id identifying the unit the calendar entries belong to + required: true + style: simple + explode: false + schema: + type: string + - name: person_id + in: query + description: External id identifying the advisor the calendar entry belongs to + required: true + style: simple + explode: false + schema: + type: string + - name: start_time + in: query + description: The first date to look for available slots (yyyy-mm-dd). Required if end_time is provided. + required: false + style: simple + explode: false + schema: + type: string + - name: end_time + in: query + description: The last date to look for available slots (yyyy-mm-dd) Required if start_time is provided + required: false + style: simple + explode: false + schema: + type: string + responses: + '200': + description: Success + content: + application/json: + schema: + type: object + required: + - id + - provider_id + - unit_id + - person_id + - start_time + - end_time + - capacity + - filled + - details + properties: + id: + type: int + readOnly: true + provider_id: + type: int + readOnly: true + unit_id: + type: int + readOnly: true + person_id: + type: int + start_time: + type: string + end_time: + type: string + capacity: + type: int + filled: + type: int + details: + type: map + '400': + description: Bad request + '401': + description: Unauthorized + '500': + description: Internal error + /api/bbs/appointments/questions: + get: + tags: + - BBs + summary: Gets the questions to ask when making an appointment + description: | + Gets a list of the questions to ask when a user requests an appointment for a particular time slot. + + **Auth:** Requires valid first-party service account token with `get_external_appointments` permission + Requires the External-Authorization header containing the oidc access token from the campus provider + security: + - bearerAuth: [] + parameters: + - name: external_id + in: query + description: External system id of person making the request + required: true + style: simple + explode: false + schema: + type: string + - name: provider_id + in: query + description: Rokwire provider id of the organization tracking/providing appointment data + required: true + style: simple + explode: false + schema: + type: string + - name: unit_id + in: query + description: External id identifying the unit the calendar entries belong to + required: true + style: simple + explode: false + schema: + type: string + - name: person_id + in: query + description: External id identifying the advisor the calendar entry belongs to + required: true + style: simple + explode: false + schema: + type: string + responses: + '200': + description: Success + content: + application/json: + schema: + type: array + items: + - type: object + required: + - id + - provider_id + - required + - type + - select_values + - question + properties: + id: + type: string + readOnly: true + provider_id: + type: int + readOnly: true + required: + type: bool + readOnly: true + type: + type: string + readOnly: true + select_values: + type: array + items: + type: string + readOnly: true + question: + type: string + readOnly: true + '400': + description: Bad request + '401': + description: Unauthorized + '500': + description: Internal error + /api/bbs/appointments/qands: + get: + tags: + - BBs + summary: Gets available time slots on a calendar + description: | + Gets a list of available time slots between two dates on a unit calendar + + **Auth:** Requires valid first-party service account token with `get_external_appointments` permission + Requires the External-Authorization header containing the oidc access token from the campus provider + security: + - bearerAuth: [] + parameters: + - name: external_id + in: query + description: External system id of person making the request + required: true + style: simple + explode: false + schema: + type: string + - name: provider_id + in: query + description: Rokwire provider id of the organization tracking/providing appointment data + required: true + style: simple + explode: false + schema: + type: string + - name: unit_id + in: query + description: External id identifying the unit the calendar entries belong to + required: true + style: simple + explode: false + schema: + type: string + - name: person_id + in: query + description: External id identifying the advisor the calendar entry belongs to + required: true + style: simple + explode: false + schema: + type: string + - name: start_time + in: query + description: The first date to look for available slots (yyyy-mm-dd). Required if end_time is provided. + required: false + style: simple + explode: false + schema: + type: string + - name: end_time + in: query + description: The last date to look for available slots (yyyy-mm-dd) Required if start_time is provided + required: false + style: simple + explode: false + schema: + type: string + responses: + '200': + description: Success + content: + application/json: + schema: + type: object + required: + - time_slots + - questions + properties: + time_slots: + type: array + items: + - $ref: '#/paths/~1api~1bbs~1appointments~1slots/get/responses/200/content/application~1json/schema' + readOnly: true + questions: + type: array + items: + - $ref: '#/paths/~1api~1bbs~1appointments~1questions/get/responses/200/content/application~1json/schema/items/0' + readOnly: true + '400': + description: Bad request + '401': + description: Unauthorized + '500': + description: Internal error + /api/bbs/appointments/: + post: + tags: + - BBs + summary: Creates an appointment in the specified provider system and returns the appointment data to the client + description: | + Posts an appointment request to the specified provider calendar. Returns the appointment data back to the client. + + **Auth:** Requires valid first-party service account token with `update_external_appointments` permission + Requires the External-Authorization header containing the oidc access token from the campus provider + security: + - bearerAuth: [] + requestBody: + content: + application/json: + schema: + $ref: '#/paths/~1api~1bbs~1appointments~1/put/requestBody/content/application~1json/schema' + responses: + '200': + description: Success + content: + application/json: + schema: + $ref: '#/paths/~1api~1bbs~1appointments~1/put/responses/200/content/application~1json/schema' + '400': + description: Bad request + '401': + description: Unauthorized + '500': + description: Internal error + put: + tags: + - BBs + summary: updates an appointment in the specified provider system and returns the appointment data to the client + description: | + Updates an appointment in the specified provider system and returns the appointment data to the clients. + For an upate, the source_id field in the request body is required. + + **Auth:** Requires valid first-party service account token with `update_external_appointments` permission + Requires the External-Authorization header containing the oidc access token from the campus provider + security: + - bearerAuth: [] + requestBody: + content: + application/json: + schema: + type: object + required: + - provider_id + - unit_id + - person_id + - type + - start_time + - end_time + - user_external_ids + - slot_id + - answers + optional: _source_id + properties: + provider_id: + type: string + readOnly: true + unit_id: + type: string + readOnly: true + person_id: + type: string + readOnly: true + type: + type: stirng + readOnly: true + start_time: + type: stirng + readOnly: true + end_time: + type: string + readOnly: true + user_external_ids: + type: + - type: object + required: + - uin + properties: + uin: + type: string + readOnly: true + readOnly: true + slot_id: + type: string + readOnly: true + source_id: + type: string + readOnly: true + answers: + type: array + items: + - type: object + required: + - question_id + - values + properties: + question_id: + type: string + readOnly: true + values: + type: array + items: + type: string + readOnly: true + readOnly: true + responses: + '200': + description: Success + content: + application/json: + schema: + type: object + required: + - provider_id + - unit_id + - person_id + - type + - start_time + - end_time + - user_external_ids + - source_id + - answers + properties: + provider_id: + type: string + readOnly: true + unit_id: + type: string + readOnly: true + person_id: + type: string + readOnly: true + type: + type: stirng + readOnly: true + start_time: + type: stirng + readOnly: true + end_time: + type: string + readOnly: true + user_external_ids: + type: + '-$ref': ./ExternalUserID.yaml + readOnly: true + source_id: + type: string + readOnly: true + '400': + description: Bad request + '401': + description: Unauthorized + '500': + description: Internal error + '/api/bbs/appointments/{id}': + delete: + tags: + - BBs + summary: Deletes an appointment in the specified provider system. + description: | + Deletes an appointment request in the specified provider calendar. + + **Auth:** Requires valid first-party service account token with `update_external_appointments` permission + Requires the External-Authorization header containing the oidc access token from the campus provider + security: + - bearerAuth: [] + parameters: + - name: id + in: path + description: source id of the appointment + required: true + style: simple + explode: false + schema: + type: string + - name: external_id + in: query + description: Users UIN + required: true + style: simple + explode: false + schema: + type: string + - name: provider_id + in: query + description: Rokwire provider id of the organization tracking/providing appointment data + required: true + style: simple + explode: false + schema: + type: string + responses: + '200': + description: Success + '400': + description: Bad request + '401': + description: Unauthorized + '500': + description: Internal error + '/api/tps/examples/{id}': + get: + tags: + - TPS + summary: Gets example + description: | + Gets example record + + **Auth:** Requires valid third-party service account token with `get_examples` permission + security: + - bearerAuth: [] + parameters: + - name: id + in: path + description: ID of example to retrieve + required: true + style: simple + explode: false + schema: + type: string + responses: + '200': + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/Example' + '400': + description: Bad request + '401': + description: Unauthorized + '500': + description: Internal error + '/api/system/examples/{id}': + get: + tags: + - System + summary: Gets example + description: | + Gets example record + + **Auth:** Requires valid admin token with `get_examples` permission + security: + - bearerAuth: [] + parameters: + - name: id + in: path + description: ID of example to retrieve + required: true + style: simple + explode: false + schema: + type: string + responses: + '200': + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/Example' + '400': + description: Bad request + '401': + description: Unauthorized + '500': + description: Internal error +components: + securitySchemes: + bearerAuth: + type: http + scheme: bearer + bearerFormat: JWT + schemas: + Config: + required: + - id + - type + - app_id + - org_id + - system + - data + - date_created + - date_updated + type: object + properties: + id: + readOnly: true + type: string + type: + type: string + app_id: + readOnly: true + type: string + org_id: + readOnly: true + type: string + system: + type: boolean + data: + anyOf: + - $ref: '#/components/schemas/EnvConfigData' + date_created: + readOnly: true + type: string + date_updated: + readOnly: true + type: string + nullable: true + EnvConfigData: + type: object + required: + - example_env + properties: + example_env: + type: string + Example: + type: object + required: + - id + - app_id + - org_id + - data + - date_created + properties: + id: + type: string + readOnly: true + org_id: + type: string + readOnly: true + app_id: + type: string + readOnly: true + data: + type: string + date_created: + type: string + readOnly: true + date_updated: + type: string + nullable: true + readOnly: true + _admin_req_update-configs: + required: + - type + - all_apps + - all_orgs + - system + - data + type: object + properties: + type: + type: string + all_apps: + writeOnly: true + type: boolean + all_orgs: + writeOnly: true + type: boolean + system: + type: boolean + data: + anyOf: + - $ref: '#/components/schemas/EnvConfigData' diff --git a/driver/web/docs/index.yaml b/driver/web/docs/index.yaml new file mode 100644 index 00000000..eaf27dab --- /dev/null +++ b/driver/web/docs/index.yaml @@ -0,0 +1,102 @@ +openapi: 3.0.3 +info: + title: Rokwire Identity Building Block API + description: Identity Building Block API Documentation + version: 1.0.0 +servers: + - url: 'https://api.rokwire.illinois.edu/identity' + description: Production server + - url: 'https://api-test.rokwire.illinois.edu/identity' + description: Test server + - url: 'https://api-dev.rokwire.illinois.edu/identity' + description: Development server + - url: 'http://localhost/identity' + description: Local server +tags: + - name: Client + description: Client applications APIs. + - name: Admin + description: Clients administration applications APIs. + - name: BBs + description: Building Block APIs. + - name: TPS + description: Third-Party Service APIs. + - name: System + description: Third-Party Service APIs. + - name: Default + description: Default APIs. +paths: + # Default + /version: + $ref: "./resources/default/version.yaml" + + # Client + /api/examples/{id}: + $ref: "./resources/client/examples-id.yaml" + #/api/laundry/rooms: + # $ref: "./resources/client/laundryrooms.yaml" + #/api/laundry/room: + # $ref: "./resources/client/laundryroom.yaml" + #/api/laundry/initrequest: + # $ref: "./resources/client/initrequest.yaml" + #/api/laundry/requestservice: + # $ref: "./resources/client/requestservice.yaml" + #/api/wayfinding/building: + # $ref: "./resources/client/building.yaml" + #/api/wayfinding/entrance: + # $ref: "./resources/client/entrance.yaml" + #/api/wayfinding/buildings: + # $ref: "./resources/client/buildings.yaml" + #/api/person/contactinfo: + # $ref: "./resources/client/contactinfo.yaml" + #/api/person/giescourses: + # $ref: "./resources/client/giescourses.yaml" + #/api/person/studentcourses: + # $ref: "./resources/client/studentcourses.yaml" + #/api/termsessions/listcurrent: + # $ref: "./resources/client/termsessions.yaml" + + # Admin + /api/admin/examples: + $ref: "./resources/admin/examples.yaml" + /api/admin/examples/{id}: + $ref: "./resources/admin/examples-id.yaml" + /api/admin/configs: + $ref: "./resources/admin/configs.yaml" + /api/admin/configs/{id}: + $ref: "./resources/admin/configs-id.yaml" + + # BBs + /api/bbs/examples/{id}: + $ref: "./resources/bbs/examples-id.yaml" + /api/bbs/appointments/units: + $ref: "./resources/bbs/apptunits.yaml" + /api/bbs/appointments/people: + $ref: "./resources/bbs/apptpeople.yaml" + /api/bbs/appointments/slots: + $ref: "./resources/bbs/apptslots.yaml" + /api/bbs/appointments/questions: + $ref: "./resources/bbs/apptquestions.yaml" + /api/bbs/appointments/qands: + $ref: "./resources/bbs/apptqands.yaml" + /api/bbs/appointments/: + $ref: "./resources/bbs/createappointment.yaml" + /api/bbs/appointments/{id}: + $ref: "./resources/bbs/delappointment.yaml" + + # TPS + /api/tps/examples/{id}: + $ref: "./resources/tps/examples-id.yaml" + + # System + /api/system/examples/{id}: + $ref: "./resources/system/examples-id.yaml" + +components: + securitySchemes: + bearerAuth: + type: http + scheme: bearer + bearerFormat: JWT + schemas: + $ref: "./schemas/index.yaml" \ No newline at end of file diff --git a/driver/web/docs/resources/admin/configs-id.yaml b/driver/web/docs/resources/admin/configs-id.yaml new file mode 100644 index 00000000..f8e84e55 --- /dev/null +++ b/driver/web/docs/resources/admin/configs-id.yaml @@ -0,0 +1,141 @@ +get: + tags: + - Admin + summary: Get config + description: | + Gets config record + + **Auth:** Requires valid admin token with one of the following permissions: + - `get_configs_identity` + - `update_configs_identity` + - `delete_configs_identity` + - `all_configs_identity` + security: + - bearerAuth: [] + parameters: + - name: id + in: path + description: ID of config to retrieve + required: true + style: simple + explode: false + schema: + type: string + responses: + 200: + description: Success + content: + application/json: + schema: + $ref: "../../schemas/application/Config.yaml" + 400: + description: Bad request + 401: + description: Unauthorized + 500: + description: Internal error +put: + tags: + - Admin + summary: Update config + description: | + Updates existing config record + + **Auth:** Requires valid admin token with one of the following permissions: + - `update_configs_identity` + - `all_configs_identity` + security: + - bearerAuth: [] + parameters: + - name: id + in: path + description: ID of config to update + required: true + style: simple + explode: false + schema: + type: string + requestBody: + description: New config content + content: + application/json: + schema: + $ref: "../../schemas/application/Config.yaml" + examples: + system: + summary: System-wide config + value: + type: "config_type" + all_apps: true + all_orgs: true + system: true + data: + example_env: "example" + org_admin: + summary: Organization-wide config + value: + type: "config_type" + all_apps: true + all_orgs: false + system: false + data: + example_env: "example" + app_org_specific: + summary: Application organization specific config + value: + type: "config_type" + all_apps: false + all_orgs: false + system: false + data: + example_env: "example" + required: true + responses: + 200: + description: Success + content: + text/plain: + schema: + type: string + example: Success + 400: + description: Bad request + 401: + description: Unauthorized + 500: + description: Internal error +delete: + tags: + - Admin + summary: Delete config + description: | + Deletes config record + + **Auth:** Requires valid admin token with one of the following permissions: + - `delete_configs_identity` + - `all_configs_identity` + security: + - bearerAuth: [] + parameters: + - name: id + in: path + description: ID of config to delete + required: true + style: simple + explode: false + schema: + type: string + responses: + 200: + description: Success + content: + text/plain: + schema: + type: string + example: Success + 400: + description: Bad request + 401: + description: Unauthorized + 500: + description: Internal error \ No newline at end of file diff --git a/driver/web/docs/resources/admin/configs.yaml b/driver/web/docs/resources/admin/configs.yaml new file mode 100644 index 00000000..bc738cb4 --- /dev/null +++ b/driver/web/docs/resources/admin/configs.yaml @@ -0,0 +1,97 @@ +get: + tags: + - Admin + summary: Get configs + description: | + Get existing configs by search parameters + + **Auth:** Requires valid admin token with one of the following permissions: + - `get_configs_identity` + - `update_configs_identity` + - `delete_configs_identity` + - `all_configs_identity` + security: + - bearerAuth: [] + parameters: + - name: type + in: query + description: config type + required: false + style: form + explode: false + schema: + type: string + responses: + 200: + description: Success + content: + application/json: + schema: + type: array + items: + $ref: "../../schemas/application/Config.yaml" + 400: + description: Bad request + 401: + description: Unauthorized + 500: + description: Internal error +post: + tags: + - Admin + summary: Create config + description: | + Creates a new config + + **Auth:** Requires valid admin token with one of the following permissions: + - `update_configs_identity` + - `all_configs_identity` + security: + - bearerAuth: [] + requestBody: + content: + application/json: + schema: + $ref: "../../schemas/application/Config.yaml" + examples: + system: + summary: System-wide config + value: + type: "config_type" + all_apps: true + all_orgs: true + system: true + data: + example_env: "example" + org_admin: + summary: Organization-wide config + value: + type: "config_type" + all_apps: true + all_orgs: false + system: false + data: + example_env: "example" + app_org_specific: + summary: Application organization specific config + value: + type: "config_type" + all_apps: false + all_orgs: false + system: false + data: + example_env: "example" + required: true + responses: + 200: + description: Success + content: + application/json: + schema: + $ref: "../../schemas/application/Config.yaml" + 400: + description: Bad request + 401: + description: Unauthorized + 500: + description: Internal error \ No newline at end of file diff --git a/driver/web/docs/resources/admin/examples-id.yaml b/driver/web/docs/resources/admin/examples-id.yaml new file mode 100644 index 00000000..60a45ad2 --- /dev/null +++ b/driver/web/docs/resources/admin/examples-id.yaml @@ -0,0 +1,108 @@ +get: + tags: + - Admin + summary: Gets example + description: | + Gets example record + + **Auth:** Requires valid admin token with one of the following permissions: + - `get_examples` + - `update_examples` + - `delete_examples` + - `all_examples` + security: + - bearerAuth: [] + parameters: + - name: id + in: path + description: ID of example to retrieve + required: true + style: simple + explode: false + schema: + type: string + responses: + 200: + description: Success + content: + application/json: + schema: + $ref: "../../schemas/application/Example.yaml" + 400: + description: Bad request + 401: + description: Unauthorized + 500: + description: Internal error +put: + tags: + - Admin + summary: Update example + description: | + Updates example record + + **Auth:** Requires valid admin token with `update_examples` or `all_examples` permission + security: + - bearerAuth: [] + parameters: + - name: id + in: path + description: ID of example to update + required: true + style: simple + explode: false + schema: + type: string + requestBody: + description: New example content + content: + application/json: + schema: + $ref: "../../schemas/application/Example.yaml" + responses: + 200: + description: Success + content: + text/plain: + schema: + type: string + example: Success + 400: + description: Bad request + 401: + description: Unauthorized + 500: + description: Internal error +delete: + tags: + - Admin + summary: Delete example + description: | + Deletes example record + + **Auth:** Requires valid admin token with `delete_examples` or `all_examples` permission + security: + - bearerAuth: [] + parameters: + - name: id + in: path + description: ID of example to delete + required: true + style: simple + explode: false + schema: + type: string + responses: + 200: + description: Success + content: + text/plain: + schema: + type: string + example: Success + 400: + description: Bad request + 401: + description: Unauthorized + 500: + description: Internal error \ No newline at end of file diff --git a/driver/web/docs/resources/admin/examples.yaml b/driver/web/docs/resources/admin/examples.yaml new file mode 100644 index 00000000..70c506c5 --- /dev/null +++ b/driver/web/docs/resources/admin/examples.yaml @@ -0,0 +1,38 @@ +post: + tags: + - Admin + summary: Create example + description: | + Creates new example record + + **Auth:** Requires valid admin token with `update_examples` or `all_examples` permission + security: + - bearerAuth: [] + parameters: + - name: id + in: path + description: ID of example to update + required: true + style: simple + explode: false + schema: + type: string + requestBody: + description: New example content + content: + application/json: + schema: + $ref: "../../schemas/application/Example.yaml" + responses: + 200: + description: Success + content: + application/json: + schema: + $ref: "../../schemas/application/Example.yaml" + 400: + description: Bad request + 401: + description: Unauthorized + 500: + description: Internal error \ No newline at end of file diff --git a/driver/web/docs/resources/bbs/apptpeople.yaml b/driver/web/docs/resources/bbs/apptpeople.yaml new file mode 100644 index 00000000..e54f5eb5 --- /dev/null +++ b/driver/web/docs/resources/bbs/apptpeople.yaml @@ -0,0 +1,49 @@ +get: + tags: + - BBs + summary: Gets People with Calendars + description: | + Gets a list of people with calendars inside of a unit + + **Auth:** Requires valid first-party service account token with `get_external_appointments` permission + Requires the External-Authorization header containing the oidc access token from the campus provider + security: + - bearerAuth: [] + parameters: + - name: external_id + in: query + description: External system id of person making the request + required: true + style: simple + explode: false + schema: + type: string + - name: provider_id + in: query + description: Rokwire provider id of the organization tracking/providing appointment data + required: true + style: simple + explode: false + schema: + type: string + - name: unit_id + in: query + description: External id identifying the unit the calendar entries belong to + required: true + style: simple + explode: false + schema: + type: string + responses: + 200: + description: Success + content: + application/json: + schema: + $ref: "../../schemas/application/AppointmentPerson.yaml" + 400: + description: Bad request + 401: + description: Unauthorized + 500: + description: Internal error \ No newline at end of file diff --git a/driver/web/docs/resources/bbs/apptqands.yaml b/driver/web/docs/resources/bbs/apptqands.yaml new file mode 100644 index 00000000..22fe5377 --- /dev/null +++ b/driver/web/docs/resources/bbs/apptqands.yaml @@ -0,0 +1,73 @@ +get: + tags: + - BBs + summary: Gets available time slots on a calendar + description: | + Gets a list of available time slots between two dates on a unit calendar + + **Auth:** Requires valid first-party service account token with `get_external_appointments` permission + Requires the External-Authorization header containing the oidc access token from the campus provider + security: + - bearerAuth: [] + parameters: + - name: external_id + in: query + description: External system id of person making the request + required: true + style: simple + explode: false + schema: + type: string + - name: provider_id + in: query + description: Rokwire provider id of the organization tracking/providing appointment data + required: true + style: simple + explode: false + schema: + type: string + - name: unit_id + in: query + description: External id identifying the unit the calendar entries belong to + required: true + style: simple + explode: false + schema: + type: string + - name: person_id + in: query + description: External id identifying the advisor the calendar entry belongs to + required: true + style: simple + explode: false + schema: + type: string + - name: start_time + in: query + description: The first date to look for available slots (yyyy-mm-dd). Required if end_time is provided. + required: false + style: simple + explode: false + schema: + type: string + - name: end_time + in: query + description: The last date to look for available slots (yyyy-mm-dd) Required if start_time is provided + required: false + style: simple + explode: false + schema: + type: string + responses: + 200: + description: Success + content: + application/json: + schema: + $ref: "../../schemas/application/AppointmentOptions.yaml" + 400: + description: Bad request + 401: + description: Unauthorized + 500: + description: Internal error \ No newline at end of file diff --git a/driver/web/docs/resources/bbs/apptquestions.yaml b/driver/web/docs/resources/bbs/apptquestions.yaml new file mode 100644 index 00000000..f0161750 --- /dev/null +++ b/driver/web/docs/resources/bbs/apptquestions.yaml @@ -0,0 +1,59 @@ +get: + tags: + - BBs + summary: Gets the questions to ask when making an appointment + description: | + Gets a list of the questions to ask when a user requests an appointment for a particular time slot. + + **Auth:** Requires valid first-party service account token with `get_external_appointments` permission + Requires the External-Authorization header containing the oidc access token from the campus provider + security: + - bearerAuth: [] + parameters: + - name: external_id + in: query + description: External system id of person making the request + required: true + style: simple + explode: false + schema: + type: string + - name: provider_id + in: query + description: Rokwire provider id of the organization tracking/providing appointment data + required: true + style: simple + explode: false + schema: + type: string + - name: unit_id + in: query + description: External id identifying the unit the calendar entries belong to + required: true + style: simple + explode: false + schema: + type: string + - name: person_id + in: query + description: External id identifying the advisor the calendar entry belongs to + required: true + style: simple + explode: false + schema: + type: string + responses: + 200: + description: Success + content: + application/json: + schema: + type: array + items: + - $ref: "../../schemas/application/Question.yaml" + 400: + description: Bad request + 401: + description: Unauthorized + 500: + description: Internal error \ No newline at end of file diff --git a/driver/web/docs/resources/bbs/apptslots.yaml b/driver/web/docs/resources/bbs/apptslots.yaml new file mode 100644 index 00000000..9197362c --- /dev/null +++ b/driver/web/docs/resources/bbs/apptslots.yaml @@ -0,0 +1,73 @@ +get: + tags: + - BBs + summary: Gets available time slots on a calendar + description: | + Gets a list of available time slots between two dates on a unit calendar + + **Auth:** Requires valid first-party service account token with `get_external_appointments` permission + Requires the External-Authorization header containing the oidc access token from the campus provider + security: + - bearerAuth: [] + parameters: + - name: external_id + in: query + description: External system id of person making the request + required: true + style: simple + explode: false + schema: + type: string + - name: provider_id + in: query + description: Rokwire provider id of the organization tracking/providing appointment data + required: true + style: simple + explode: false + schema: + type: string + - name: unit_id + in: query + description: External id identifying the unit the calendar entries belong to + required: true + style: simple + explode: false + schema: + type: string + - name: person_id + in: query + description: External id identifying the advisor the calendar entry belongs to + required: true + style: simple + explode: false + schema: + type: string + - name: start_time + in: query + description: The first date to look for available slots (yyyy-mm-dd). Required if end_time is provided. + required: false + style: simple + explode: false + schema: + type: string + - name: end_time + in: query + description: The last date to look for available slots (yyyy-mm-dd) Required if start_time is provided + required: false + style: simple + explode: false + schema: + type: string + responses: + 200: + description: Success + content: + application/json: + schema: + $ref: "../../schemas/application/TimeSlot.yaml" + 400: + description: Bad request + 401: + description: Unauthorized + 500: + description: Internal error \ No newline at end of file diff --git a/driver/web/docs/resources/bbs/apptunits.yaml b/driver/web/docs/resources/bbs/apptunits.yaml new file mode 100644 index 00000000..34c3ff37 --- /dev/null +++ b/driver/web/docs/resources/bbs/apptunits.yaml @@ -0,0 +1,41 @@ +get: + tags: + - BBs + summary: Gets the list of units (calendars) for a provider id + description: | + Gets the list of units (calendars) for a provider id + + **Auth:** Requires valid first-party service account token with `get_external_appointments` permission + Requires the External-Authorization header containing the oidc access token from the campus provider + security: + - bearerAuth: [] + parameters: + - name: external_id + in: query + description: External system id of person making the request + required: true + style: simple + explode: false + schema: + type: string + - name: provider_id + in: query + description: Rokwire provider id of the organization tracking/providing appointment data + required: true + style: simple + explode: false + schema: + type: string + responses: + 200: + description: Success + content: + application/json: + schema: + $ref: "../../schemas/application/AppointmentUnit.yaml" + 400: + description: Bad request + 401: + description: Unauthorized + 500: + description: Internal error \ No newline at end of file diff --git a/driver/web/docs/resources/bbs/createappointment.yaml b/driver/web/docs/resources/bbs/createappointment.yaml new file mode 100644 index 00000000..130c0ead --- /dev/null +++ b/driver/web/docs/resources/bbs/createappointment.yaml @@ -0,0 +1,59 @@ +post: + tags: + - BBs + summary: Creates an appointment in the specified provider system and returns the appointment data to the client + description: | + Posts an appointment request to the specified provider calendar. Returns the appointment data back to the client. + + **Auth:** Requires valid first-party service account token with `update_external_appointments` permission + Requires the External-Authorization header containing the oidc access token from the campus provider + security: + - bearerAuth: [] + requestBody: + content: + application/json: + schema: + $ref: "../../schemas/application/AppointmentPost.yaml" + responses: + 200: + description: Success + content: + application/json: + schema: + $ref: "../../schemas/application/BuildingBlockAppointment.yaml" + 400: + description: Bad request + 401: + description: Unauthorized + 500: + description: Internal error +put: + tags: + - BBs + summary: updates an appointment in the specified provider system and returns the appointment data to the client + description: | + Updates an appointment in the specified provider system and returns the appointment data to the clients. + For an upate, the source_id field in the request body is required. + + **Auth:** Requires valid first-party service account token with `update_external_appointments` permission + Requires the External-Authorization header containing the oidc access token from the campus provider + security: + - bearerAuth: [] + requestBody: + content: + application/json: + schema: + $ref: "../../schemas/application/AppointmentPost.yaml" + responses: + 200: + description: Success + content: + application/json: + schema: + $ref: "../../schemas/application/BuildingBlockAppointment.yaml" + 400: + description: Bad request + 401: + description: Unauthorized + 500: + description: Internal error \ No newline at end of file diff --git a/driver/web/docs/resources/bbs/delappointment.yaml b/driver/web/docs/resources/bbs/delappointment.yaml new file mode 100644 index 00000000..6e3cc753 --- /dev/null +++ b/driver/web/docs/resources/bbs/delappointment.yaml @@ -0,0 +1,45 @@ +delete: + tags: + - BBs + summary: Deletes an appointment in the specified provider system. + description: | + Deletes an appointment request in the specified provider calendar. + + **Auth:** Requires valid first-party service account token with `update_external_appointments` permission + Requires the External-Authorization header containing the oidc access token from the campus provider + security: + - bearerAuth: [] + parameters: + - name: id + in: path + description: source id of the appointment + required: true + style: simple + explode: false + schema: + type: string + - name: external_id + in: query + description: Users UIN + required: true + style: simple + explode: false + schema: + type: string + - name: provider_id + in: query + description: Rokwire provider id of the organization tracking/providing appointment data + required: true + style: simple + explode: false + schema: + type: string + responses: + 200: + description: Success + 400: + description: Bad request + 401: + description: Unauthorized + 500: + description: Internal error \ No newline at end of file diff --git a/driver/web/docs/resources/bbs/examples-id.yaml b/driver/web/docs/resources/bbs/examples-id.yaml new file mode 100644 index 00000000..2f6ce13f --- /dev/null +++ b/driver/web/docs/resources/bbs/examples-id.yaml @@ -0,0 +1,32 @@ +get: + tags: + - BBs + summary: Gets example + description: | + Gets example record + + **Auth:** Requires valid first-party service account token with `get_examples` permission + security: + - bearerAuth: [] + parameters: + - name: id + in: path + description: ID of example to retrieve + required: true + style: simple + explode: false + schema: + type: string + responses: + 200: + description: Success + content: + application/json: + schema: + $ref: "../../schemas/application/Example.yaml" + 400: + description: Bad request + 401: + description: Unauthorized + 500: + description: Internal error \ No newline at end of file diff --git a/driver/web/docs/resources/bbs/updateappointment.yaml b/driver/web/docs/resources/bbs/updateappointment.yaml new file mode 100644 index 00000000..697625a0 --- /dev/null +++ b/driver/web/docs/resources/bbs/updateappointment.yaml @@ -0,0 +1,29 @@ +put: + tags: + - BBs + summary: updates an appointment in the specified provider system and returns the appointment data to the client + description: | + Updates an appointment in the specified provider system and returns the appointment data to the clients. + + **Auth:** Requires valid first-party service account token with `update_external_appointments` permission + Requires the External-Authorization header containing the oidc access token from the campus provider + security: + - bearerAuth: [] + requestBody: + content: + application/json: + schema: + $ref: "../../schemas/application/AppointmentPost.yaml" + responses: + 200: + description: Success + content: + application/json: + schema: + $ref: "../../schemas/application/BuildingBlockAppointment.yaml" + 400: + description: Bad request + 401: + description: Unauthorized + 500: + description: Internal error \ No newline at end of file diff --git a/driver/web/docs/resources/client/examples-id.yaml b/driver/web/docs/resources/client/examples-id.yaml new file mode 100644 index 00000000..8a0583a9 --- /dev/null +++ b/driver/web/docs/resources/client/examples-id.yaml @@ -0,0 +1,32 @@ +get: + tags: + - Client + summary: Gets example + description: | + Gets example record + + **Auth:** Requires valid user token with `get_examples` permission + security: + - bearerAuth: [] + parameters: + - name: id + in: path + description: ID of example to retrieve + required: true + style: simple + explode: false + schema: + type: string + responses: + 200: + description: Success + content: + application/json: + schema: + $ref: "../../schemas/application/Example.yaml" + 400: + description: Bad request + 401: + description: Unauthorized + 500: + description: Internal error \ No newline at end of file diff --git a/driver/web/docs/resources/default/version.yaml b/driver/web/docs/resources/default/version.yaml new file mode 100644 index 00000000..ad962b63 --- /dev/null +++ b/driver/web/docs/resources/default/version.yaml @@ -0,0 +1,16 @@ +get: + tags: + - Default + summary: Get version + description: | + Gets current version of this service + responses: + 200: + description: Success + content: + text/plain: + schema: + type: string + example: v1.0.0 + 500: + description: Internal error \ No newline at end of file diff --git a/driver/web/docs/resources/system/configs-id.yaml b/driver/web/docs/resources/system/configs-id.yaml new file mode 100644 index 00000000..e5614ff4 --- /dev/null +++ b/driver/web/docs/resources/system/configs-id.yaml @@ -0,0 +1,108 @@ +get: + tags: + - System + summary: Get config + description: | + Gets config record + + **Auth:** Requires valid admin token with one of the following permissions: + - `get_configs_identity` + - `update_configs_identity` + - `delete_configs_identity` + - `all_configs_identity` + security: + - bearerAuth: [] + parameters: + - name: id + in: path + description: ID of config to retrieve + required: true + style: simple + explode: false + schema: + type: string + responses: + 200: + description: Success + content: + application/json: + schema: + $ref: "../../schemas/application/Config.yaml" + 400: + description: Bad request + 401: + description: Unauthorized + 500: + description: Internal error +put: + tags: + - System + summary: Save config + description: | + Saves config record + + **Auth:** Requires valid admin token with `update_configs_identity` or `all_configs_identity` permission + security: + - bearerAuth: [] + parameters: + - name: id + in: path + description: ID of config to update + required: true + style: simple + explode: false + schema: + type: string + requestBody: + description: New config content + content: + application/json: + schema: + $ref: "../../schemas/application/Config.yaml" + responses: + 200: + description: Success + content: + text/plain: + schema: + type: string + example: Success + 400: + description: Bad request + 401: + description: Unauthorized + 500: + description: Internal error +delete: + tags: + - System + summary: Delete config + description: | + Deletes config record + + **Auth:** Requires valid admin token with `delete_configs_identity` or `all_configs_identity` permission + security: + - bearerAuth: [] + parameters: + - name: id + in: path + description: ID of config to delete + required: true + style: simple + explode: false + schema: + type: string + responses: + 200: + description: Success + content: + text/plain: + schema: + type: string + example: Success + 400: + description: Bad request + 401: + description: Unauthorized + 500: + description: Internal error \ No newline at end of file diff --git a/driver/web/docs/resources/system/examples-id.yaml b/driver/web/docs/resources/system/examples-id.yaml new file mode 100644 index 00000000..78d5dc86 --- /dev/null +++ b/driver/web/docs/resources/system/examples-id.yaml @@ -0,0 +1,32 @@ +get: + tags: + - System + summary: Gets example + description: | + Gets example record + + **Auth:** Requires valid admin token with `get_examples` permission + security: + - bearerAuth: [] + parameters: + - name: id + in: path + description: ID of example to retrieve + required: true + style: simple + explode: false + schema: + type: string + responses: + 200: + description: Success + content: + application/json: + schema: + $ref: "../../schemas/application/Example.yaml" + 400: + description: Bad request + 401: + description: Unauthorized + 500: + description: Internal error \ No newline at end of file diff --git a/driver/web/docs/resources/tps/examples-id.yaml b/driver/web/docs/resources/tps/examples-id.yaml new file mode 100644 index 00000000..7e8ec2b8 --- /dev/null +++ b/driver/web/docs/resources/tps/examples-id.yaml @@ -0,0 +1,32 @@ +get: + tags: + - TPS + summary: Gets example + description: | + Gets example record + + **Auth:** Requires valid third-party service account token with `get_examples` permission + security: + - bearerAuth: [] + parameters: + - name: id + in: path + description: ID of example to retrieve + required: true + style: simple + explode: false + schema: + type: string + responses: + 200: + description: Success + content: + application/json: + schema: + $ref: "../../schemas/application/Example.yaml" + 400: + description: Bad request + 401: + description: Unauthorized + 500: + description: Internal error \ No newline at end of file diff --git a/driver/web/docs/schemas/apis/admin/update-configs/Request.yaml b/driver/web/docs/schemas/apis/admin/update-configs/Request.yaml new file mode 100644 index 00000000..efdd1891 --- /dev/null +++ b/driver/web/docs/schemas/apis/admin/update-configs/Request.yaml @@ -0,0 +1,21 @@ +required: + - type + - all_apps + - all_orgs + - system + - data +type: object +properties: + type: + type: string + all_apps: + writeOnly: true + type: boolean + all_orgs: + writeOnly: true + type: boolean + system: + type: boolean + data: + anyOf: + - $ref: "../../../application/EnvConfigData.yaml" \ No newline at end of file diff --git a/driver/web/docs/schemas/application/AppointmentOptions.yaml b/driver/web/docs/schemas/application/AppointmentOptions.yaml new file mode 100644 index 00000000..cb5da284 --- /dev/null +++ b/driver/web/docs/schemas/application/AppointmentOptions.yaml @@ -0,0 +1,15 @@ +type: object +required: +- time_slots +- questions +properties: + time_slots: + type: array + items: + - $ref: "./TimeSlot.yaml" + readOnly: true + questions: + type: array + items: + - $ref: "./Question.yaml" + readOnly: true \ No newline at end of file diff --git a/driver/web/docs/schemas/application/AppointmentPerson.yaml b/driver/web/docs/schemas/application/AppointmentPerson.yaml new file mode 100644 index 00000000..5ce8701f --- /dev/null +++ b/driver/web/docs/schemas/application/AppointmentPerson.yaml @@ -0,0 +1,27 @@ +type: object +required: +- id +- provider_id +- unit_id +- next_available +- name +- notes +- image_url +properties: + id: + type: string + readOnly: true + provider_id: + type: int + readOnly: true + unit_id: + type: int + readOnly: true + next_available: + type: string + name: + type: string + notes: + type: string + image_url: + type: string \ No newline at end of file diff --git a/driver/web/docs/schemas/application/AppointmentPost.yaml b/driver/web/docs/schemas/application/AppointmentPost.yaml new file mode 100644 index 00000000..9db2f71d --- /dev/null +++ b/driver/web/docs/schemas/application/AppointmentPost.yaml @@ -0,0 +1,47 @@ +type: object +required: +- provider_id +- unit_id +- person_id +- type +- start_time +- end_time +- user_external_ids +- slot_id +- answers +optional: + _source_id +properties: + provider_id: + type: string + readOnly: true + unit_id: + type: string + readOnly: true + person_id: + type: string + readOnly: true + type: + type: stirng + readOnly: true + start_time: + type: stirng + readOnly: true + end_time: + type: string + readOnly: true + user_external_ids: + type: + - $ref: "./ExternalUserID.yaml" + readOnly: true + slot_id: + type: string + readOnly: true + source_id: + type: string + readOnly: true + answers: + type: array + items: + - $ref: "./QuestionAnswer.yaml" + readOnly: true \ No newline at end of file diff --git a/driver/web/docs/schemas/application/AppointmentUnit.yaml b/driver/web/docs/schemas/application/AppointmentUnit.yaml new file mode 100644 index 00000000..94edd36e --- /dev/null +++ b/driver/web/docs/schemas/application/AppointmentUnit.yaml @@ -0,0 +1,30 @@ +type: object +required: +- id +- provider_id +- name +- location +- hours_of_operation +- details +- next_available +- image_url +properties: + id: + type: int + readOnly: true + provider_id: + type: int + readOnly: true + name: + type: string + readOnly: true + location: + type: string + hours_of_operation: + type: string + details: + type: string + next_available: + type: string + image_url: + type: string \ No newline at end of file diff --git a/driver/web/docs/schemas/application/BuildingBlockAppointment.yaml b/driver/web/docs/schemas/application/BuildingBlockAppointment.yaml new file mode 100644 index 00000000..c25ece26 --- /dev/null +++ b/driver/web/docs/schemas/application/BuildingBlockAppointment.yaml @@ -0,0 +1,37 @@ +type: object +required: +- provider_id +- unit_id +- person_id +- type +- start_time +- end_time +- user_external_ids +- source_id +- answers +properties: + provider_id: + type: string + readOnly: true + unit_id: + type: string + readOnly: true + person_id: + type: string + readOnly: true + type: + type: stirng + readOnly: true + start_time: + type: stirng + readOnly: true + end_time: + type: string + readOnly: true + user_external_ids: + type: + -$ref: "./ExternalUserID.yaml" + readOnly: true + source_id: + type: string + readOnly: true \ No newline at end of file diff --git a/driver/web/docs/schemas/application/Config.yaml b/driver/web/docs/schemas/application/Config.yaml new file mode 100644 index 00000000..ff110de1 --- /dev/null +++ b/driver/web/docs/schemas/application/Config.yaml @@ -0,0 +1,34 @@ +required: + - id + - type + - app_id + - org_id + - system + - data + - date_created + - date_updated +type: object +properties: + id: + readOnly: true + type: string + type: + type: string + app_id: + readOnly: true + type: string + org_id: + readOnly: true + type: string + system: + type: boolean + data: + anyOf: + - $ref: "./EnvConfigData.yaml" + date_created: + readOnly: true + type: string + date_updated: + readOnly: true + type: string + nullable: true diff --git a/driver/web/docs/schemas/application/EnvConfigData.yaml b/driver/web/docs/schemas/application/EnvConfigData.yaml new file mode 100644 index 00000000..264ff59e --- /dev/null +++ b/driver/web/docs/schemas/application/EnvConfigData.yaml @@ -0,0 +1,6 @@ +type: object +required: +- example_env +properties: + example_env: + type: string \ No newline at end of file diff --git a/driver/web/docs/schemas/application/Example.yaml b/driver/web/docs/schemas/application/Example.yaml new file mode 100644 index 00000000..5e89e1ba --- /dev/null +++ b/driver/web/docs/schemas/application/Example.yaml @@ -0,0 +1,26 @@ +type: object +required: +- id +- app_id +- org_id +- data +- date_created +properties: + id: + type: string + readOnly: true + org_id: + type: string + readOnly: true + app_id: + type: string + readOnly: true + data: + type: string + date_created: + type: string + readOnly: true + date_updated: + type: string + nullable: true + readOnly: true \ No newline at end of file diff --git a/driver/web/docs/schemas/application/ExternalUserID.yaml b/driver/web/docs/schemas/application/ExternalUserID.yaml new file mode 100644 index 00000000..2afc2584 --- /dev/null +++ b/driver/web/docs/schemas/application/ExternalUserID.yaml @@ -0,0 +1,7 @@ +type: object +required: +- uin +properties: + uin: + type: string + readOnly: true \ No newline at end of file diff --git a/driver/web/docs/schemas/application/Question.yaml b/driver/web/docs/schemas/application/Question.yaml new file mode 100644 index 00000000..886c7f1f --- /dev/null +++ b/driver/web/docs/schemas/application/Question.yaml @@ -0,0 +1,29 @@ +type: object +required: +- id +- provider_id +- required +- type +- select_values +- question +properties: + id: + type: string + readOnly: true + provider_id: + type: int + readOnly: true + required: + type: bool + readOnly: true + type: + type: string + readOnly: true + select_values: + type: array + items: + type: string + readOnly: true + question: + type: string + readOnly: true \ No newline at end of file diff --git a/driver/web/docs/schemas/application/QuestionAnswer.yaml b/driver/web/docs/schemas/application/QuestionAnswer.yaml new file mode 100644 index 00000000..33e03a32 --- /dev/null +++ b/driver/web/docs/schemas/application/QuestionAnswer.yaml @@ -0,0 +1,13 @@ +type: object +required: +- question_id +- values +properties: + question_id: + type: string + readOnly: true + values: + type: array + items: + type: string + readOnly: true \ No newline at end of file diff --git a/driver/web/docs/schemas/application/TimeSlot.yaml b/driver/web/docs/schemas/application/TimeSlot.yaml new file mode 100644 index 00000000..1e4dabb8 --- /dev/null +++ b/driver/web/docs/schemas/application/TimeSlot.yaml @@ -0,0 +1,33 @@ +type: object +required: +- id +- provider_id +- unit_id +- person_id +- start_time +- end_time +- capacity +- filled +- details +properties: + id: + type: int + readOnly: true + provider_id: + type: int + readOnly: true + unit_id: + type: int + readOnly: true + person_id: + type: int + start_time: + type: string + end_time: + type: string + capacity: + type: int + filled: + type: int + details: + type: map \ No newline at end of file diff --git a/driver/web/docs/schemas/index.yaml b/driver/web/docs/schemas/index.yaml new file mode 100644 index 00000000..4e12111b --- /dev/null +++ b/driver/web/docs/schemas/index.yaml @@ -0,0 +1,15 @@ +# application +Config: + $ref: "./application/Config.yaml" +EnvConfigData: + $ref: "./application/EnvConfigData.yaml" +Example: + $ref: "./application/Example.yaml" + +# ADMIN section + +## admin configs API +_admin_req_update-configs: + $ref: "./apis/admin/update-configs/Request.yaml" + +# end ADMIN section diff --git a/driver/web/rest/apis.go b/driver/web/rest/apis.go deleted file mode 100644 index d6a5f6b3..00000000 --- a/driver/web/rest/apis.go +++ /dev/null @@ -1,94 +0,0 @@ -// Copyright 2022 Board of Trustees of the University of Illinois. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package rest - -import ( - "apigateway/core" - "encoding/json" - "fmt" - "io/ioutil" - "log" - "net/http" -) - -// ApisHandler handles the rest APIs implementation -type ApisHandler struct { - app *core.Application -} - -// NewApisHandler creates new rest Handler instance -func NewApisHandler(app *core.Application) ApisHandler { - return ApisHandler{app: app} -} - -type getMessagesRequestBody struct { - IDs []string `json:"ids"` -} //@name getMessagesRequestBody - -type sampleRecord struct { - Name *string `json:"name"` -} //@name sampleRecord - -// Version gives the service version -// @Description Gives the service version. -// @Tags Client -// @ID Version -// @Produce plain -// @Success 200 -// @Security RokwireAuth -// @Router /version [get] -func (h ApisHandler) Version(w http.ResponseWriter, r *http.Request) { - w.Write([]byte(h.app.Services.GetVersion())) -} - -// StoreRecord Stores a record -// @Tags Client -// @ID Name -// @Param data body sampleRecord true "body json" -// @Accept json -// @Success 200 -// @Security RokwireAuth UserAuth -// @Router /token [post] -func (h ApisHandler) StoreRecord(w http.ResponseWriter, r *http.Request) { - data, err := ioutil.ReadAll(r.Body) - if err != nil { - log.Printf("Error on marshal token data - %s\n", err.Error()) - http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest) - return - } - - var record sampleRecord - err = json.Unmarshal(data, &record) - if err != nil { - log.Printf("Error on unmarshal the create student guide request data - %s\n", err.Error()) - http.Error(w, err.Error(), http.StatusBadRequest) - return - } - - if record.Name == nil || len(*record.Name) == 0 { - log.Printf("name is empty or null") - http.Error(w, fmt.Sprintf("token is empty or null\n"), http.StatusBadRequest) - return - } - - err = h.app.Services.StoreRecord(*record.Name) - if err != nil { - log.Printf("Error on creating student guide: %s\n", err) - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - - w.WriteHeader(http.StatusOK) -} diff --git a/driver/web/rest/buildingapis.go b/driver/web/rest/buildingapis.go deleted file mode 100644 index 78ed3f09..00000000 --- a/driver/web/rest/buildingapis.go +++ /dev/null @@ -1,249 +0,0 @@ -// Copyright 2022 Board of Trustees of the University of Illinois. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package rest - -import ( - "apigateway/core" - "apigateway/utils" - "encoding/json" - "log" - "net/http" - "strconv" -) - -type errorMessage struct { - Message string -} - -// BuildingAPIHandler handles the building rest APIs implementation -type BuildingAPIHandler struct { - app *core.Application -} - -// NewBuildingAPIHandler creates new rest Handler instance for building location functions -func NewBuildingAPIHandler(app *core.Application) BuildingAPIHandler { - return BuildingAPIHandler{app: app} -} - -// GetBuilding returns an the building matching the provided building id -// @Summary Get the requested building with all of its available entrances filterd by the ADA only flag -// @Tags Client -// @ID Building -// @Accept json -// @Produce json -// @Param id query string true "Building identifier" -// @Param adaOnly query bool false "ADA entrances filter" -// @Param lat query number false "latitude coordinate of the user" -// @Param long query number false "longitude coordinate of the user" -// @Success 200 {object} model.Building -// @Security RokwireAuth -// @Router /wayfinding/building [get] -func (h BuildingAPIHandler) GetBuilding(w http.ResponseWriter, r *http.Request) { - - bldgid := "" - adaOnly := false - reqParams := utils.ConstructFilter(r) - var latitude, longitude float64 - latitude = 0 - longitude = 0 - - for _, v := range reqParams.Items { - if v.Field == "id" { - bldgid = v.Value[0] - } - if v.Field == "adaOnly" { - ada, err := strconv.ParseBool(v.Value[0]) - if err != nil { - log.Printf("Invalid parameter value (adaOnly): %s\n", err) - http.Error(w, err.Error(), http.StatusBadRequest) - return - } - adaOnly = ada - } - if v.Field == "lat" { - lat, err := strconv.ParseFloat(v.Value[0], 64) - if err != nil { - log.Printf("Invalid parameter value (lat): %s\n", err) - http.Error(w, err.Error(), http.StatusBadRequest) - return - } - latitude = lat - } - - if v.Field == "long" { - long, err := strconv.ParseFloat(v.Value[0], 64) - if err != nil { - log.Printf("Invalid parameter value (long): %s\n", err) - http.Error(w, err.Error(), http.StatusBadRequest) - return - } - longitude = long - } - } - - if bldgid == "" { - log.Printf("Error on retrieving building informaiton: missing id parameter") - http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest) - return - } - - bldg, err := h.app.Services.GetBuilding(bldgid, adaOnly, latitude, longitude) - if err != nil { - log.Printf("Error retrieving building details: %s\n", err) - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - - resAsJSON, err := json.Marshal(bldg) - if err != nil { - log.Printf("Error on marshalling building data: %s\n", err) - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - - w.WriteHeader(http.StatusOK) - w.Write(resAsJSON) -} - -// GetEntrance returns a building entrance record -// @Summary Returns the entrance of the specified building that is closest to the user -// @Tags Client -// @ID Entrance -// @Param id query string true "Building identifier" -// @Param adaOnly query bool false "ADA entrances filter" -// @Param lat query number true "latitude coordinate of the user" -// @Param long query number true "longitude coordinate of the user" -// @Accept json -// @Success 200 {object} model.Entrance -// @Failure 404 {object} rest.errorMessage -// @Security RokwireAuth -// @Router /wayfinding/entrance [get] -func (h BuildingAPIHandler) GetEntrance(w http.ResponseWriter, r *http.Request) { - reqParams := utils.ConstructFilter(r) - bldgID := "" - adaOnly := false - var latitude, longitude float64 - - if len(reqParams.Items) < 3 || len(reqParams.Items) > 4 { - log.Printf("Invalid number of parameters passed") - http.Error(w, "Invalid number of parameters", http.StatusBadRequest) - return - } - - for _, v := range reqParams.Items { - if v.Field == "id" { - bldgID = v.Value[0] - } - - if v.Field == "adaOnly" { - ada, err := strconv.ParseBool(v.Value[0]) - if err != nil { - log.Printf("Invalid parameter value (adaOnly): %s\n", err) - http.Error(w, err.Error(), http.StatusBadRequest) - return - } - adaOnly = ada - } - - if v.Field == "lat" { - lat, err := strconv.ParseFloat(v.Value[0], 64) - if err != nil { - log.Printf("Invalid parameter value (lat): %s\n", err) - http.Error(w, err.Error(), http.StatusBadRequest) - return - } - latitude = lat - } - - if v.Field == "long" { - long, err := strconv.ParseFloat(v.Value[0], 64) - if err != nil { - log.Printf("Invalid parameter value (long): %s\n", err) - http.Error(w, err.Error(), http.StatusBadRequest) - return - } - longitude = long - } - } - if latitude == 0 && longitude == 0 { - log.Printf("Missing latitude or longitude parameter") - http.Error(w, "Missing latitude or longitude parameter", http.StatusBadRequest) - return - } - - if bldgID == "" { - log.Printf("Error on retrieving entrance: missing id parameter") - http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest) - return - } - - entrance, err := h.app.Services.GetEntrance(bldgID, adaOnly, latitude, longitude) - if err != nil { - log.Printf("Error retrieving entrance: %s\n", err) - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - - if entrance == nil { - w.WriteHeader(http.StatusNotFound) - resp := errorMessage{Message: "Resource Not Found"} - jsonResp, err := json.Marshal(resp) - if err != nil { - log.Printf("Error on marshaling json response. Err: %s", err) - return - } - w.Write(jsonResp) - return - - } - resAsJSON, err := json.Marshal(entrance) - if err != nil { - log.Printf("Error on marshalling entrance: %s\n", err) - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - - w.WriteHeader(http.StatusOK) - w.Write(resAsJSON) - -} - -// GetBuildings returns a list of all buildings -// @Summary Get a list of all buildings with a list of active entrances -// @Tags Client -// @ID BuildingList -// @Accept json -// @Produce json -// @Success 200 {object} []model.Building -// @Security RokwireAuth -// @Router /wayfinding/buildings [get] -func (h BuildingAPIHandler) GetBuildings(w http.ResponseWriter, r *http.Request) { - bldgs, err := h.app.Services.GetBuildings() - if err != nil { - log.Printf("Error retrieving building list: %s\n", err) - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - - resAsJSON, err := json.Marshal(bldgs) - if err != nil { - log.Printf("Error on marshalling building data: %s\n", err) - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - - w.WriteHeader(http.StatusOK) - w.Write(resAsJSON) -} diff --git a/driver/web/rest/contactinfoapis.go b/driver/web/rest/contactinfoapis.go deleted file mode 100644 index abf0de4f..00000000 --- a/driver/web/rest/contactinfoapis.go +++ /dev/null @@ -1,106 +0,0 @@ -// Copyright 2022 Board of Trustees of the University of Illinois. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package rest - -import ( - "apigateway/core" - "apigateway/utils" - "encoding/json" - "log" - "net/http" -) - -// ContactInfoApisHandler handles the contact information rest APIs implementation -type ContactInfoApisHandler struct { - app *core.Application -} - -// NewContactInfoApisHandler creates new rest Handler instance for contact info functions -func NewContactInfoApisHandler(app *core.Application) ContactInfoApisHandler { - return ContactInfoApisHandler{app: app} -} - -// GetContactInfo returns the contact information of a person -// @Summary Returns the name, permanent and mailing addresses, phone number and emergency contact information for a person -// @Tags Client -// @ID ConatctInfo -// @Param id query string true "User ID" -// @Accept json -// @Produce json -// @Success 200 {object} model.Person -// @Security RokwireAuth ExternalAuth -// @Router /person/contactinfo [get] -func (h ContactInfoApisHandler) GetContactInfo(w http.ResponseWriter, r *http.Request) { - - log.Printf("Beginning call for %s", r.URL) - externalToken := r.Header.Get("External-Authorization") - - id := "" - mode := "0" - - reqParams := utils.ConstructFilter(r) - if reqParams != nil { - for _, v := range reqParams.Items { - switch v.Field { - case "id": - id = v.Value[0] - case "mode": - mode = v.Value[0] - } - } - } - - if id == "123456789" { - mode = "1" - } - - if id == "" || id == "null" { - log.Printf("Error: missing id parameter") - http.Error(w, "Missing id parameter", http.StatusBadRequest) - return - } - - if externalToken == "" { - log.Printf("Error: External access token not includeed for %s", id) - http.Error(w, "Missing external access token", http.StatusBadRequest) - return - } - - person, statusCode, err := h.app.Services.GetContactInfo(id, externalToken, mode) - if err != nil { - log.Printf("Error getting contact information for %s: Server returned %d %s \n", id, statusCode, err.Error()) - switch statusCode { - case 401: - http.Error(w, err.Error(), http.StatusUnauthorized) - case 403: - http.Error(w, err.Error(), http.StatusForbidden) - case 404: - http.Error(w, err.Error(), http.StatusNotFound) - default: - http.Error(w, err.Error(), http.StatusInternalServerError) - } - return - } - - resAsJSON, err := json.Marshal(person) - if err != nil { - log.Printf("Error on marshalling contact information for %s: %s\n", id, err.Error()) - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - - //w.WriteHeader(http.StatusOK) - w.Write(resAsJSON) -} diff --git a/driver/web/rest/coursesapi.go b/driver/web/rest/coursesapi.go deleted file mode 100644 index 6a7f167e..00000000 --- a/driver/web/rest/coursesapi.go +++ /dev/null @@ -1,225 +0,0 @@ -/* - * Copyright (c) 2020 Board of Trustees of the University of Illinois. - * All rights reserved. - - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - - * http://www.apache.org/licenses/LICENSE-2.0 - - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package rest - -import ( - "apigateway/core" - model "apigateway/core/model" - "apigateway/utils" - "encoding/json" - "log" - "net/http" - "strconv" -) - -// CourseApisHandler handles the course information rest APIs implementation -type CourseApisHandler struct { - app *core.Application -} - -// NewCourseApisHandler creates new rest Handler instance for course info functions -func NewCourseApisHandler(app *core.Application) CourseApisHandler { - return CourseApisHandler{app: app} -} - -// GetGiesCourses returns a list of registered courses for GIES students -// @Summary Returns a list of registered courses -// @Tags Client -// @ID GiesCourses -// @Param id query string true "User ID" -// @Accept json -// @Produce json -// @Success 200 {object} []model.GiesCourse -// @Security RokwireAuth ExternalAuth -// @Router /courses/giescourses [get] -func (h CourseApisHandler) GetGiesCourses(w http.ResponseWriter, r *http.Request) { - - externalToken := r.Header.Get("External-Authorization") - - id := "" - - reqParams := utils.ConstructFilter(r) - if reqParams != nil { - for _, v := range reqParams.Items { - switch v.Field { - case "id": - id = v.Value[0] - } - } - } - - if id == "" || id == "null" { - log.Printf("Error: missing id parameter") - http.Error(w, "Missing id parameter", http.StatusBadRequest) - return - } - - if externalToken == "" { - log.Printf("Error: External access token not includeed for %s", id) - http.Error(w, "Missing external access token", http.StatusBadRequest) - return - } - - giesCourseList, statusCode, err := h.app.Services.GetGiesCourses(id, externalToken) - if err != nil { - log.Printf("Error getting gies courses for %s: Server returned %d %s \n", id, statusCode, err.Error()) - switch statusCode { - case 401: - http.Error(w, err.Error(), http.StatusUnauthorized) - case 403: - http.Error(w, err.Error(), http.StatusForbidden) - case 404: - http.Error(w, err.Error(), http.StatusNotFound) - default: - http.Error(w, err.Error(), http.StatusInternalServerError) - } - return - } - - resAsJSON, err := json.Marshal(giesCourseList) - if err != nil { - log.Printf("Error on marshalling gies course information for %s: %s\n", id, err.Error()) - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - - w.Write(resAsJSON) -} - -// GetStudentcourses returns a list of registered courses for a student -// @Summary Returns a list of registered courses -// @Tags Client -// @ID Studentcourses -// @Param id query string true "User ID" -// @Param termid query string true "term id" -// @Accept json -// @Produce json -// @Success 200 {object} []model.Course -// @Security RokwireAuth ExternalAuth -// @Router /courses/studentcourses [get] -func (h CourseApisHandler) GetStudentcourses(w http.ResponseWriter, r *http.Request) { - - externalToken := r.Header.Get("External-Authorization") - - id := "" - termid := "" - adaOnly := false - var latitude, longitude float64 - latitude = 0 - longitude = 0 - - reqParams := utils.ConstructFilter(r) - if reqParams != nil { - for _, v := range reqParams.Items { - switch v.Field { - case "id": - id = v.Value[0] - case "termid": - termid = v.Value[0] - case "long": - long, err := strconv.ParseFloat(v.Value[0], 64) - if err != nil { - log.Printf("Invalid parameter value (long): %s\n", err) - http.Error(w, err.Error(), http.StatusBadRequest) - return - } - longitude = long - case "lat": - lat, err := strconv.ParseFloat(v.Value[0], 64) - if err != nil { - log.Printf("Invalid parameter value (lat): %s\n", err) - http.Error(w, err.Error(), http.StatusBadRequest) - return - } - latitude = lat - case "adaOnly": - ada, err := strconv.ParseBool(v.Value[0]) - if err != nil { - log.Printf("Invalid parameter value (adaOnly): %s\n", err) - http.Error(w, err.Error(), http.StatusBadRequest) - return - } - adaOnly = ada - } - } - } - - if id == "" || id == "null" { - log.Printf("Error: missing id parameter") - http.Error(w, "Missing id parameter", http.StatusBadRequest) - return - } - - if termid == "" || termid == "null" { - log.Printf("Error: missing termid parameter") - http.Error(w, "Missing termid parameter", http.StatusBadRequest) - return - } - - if externalToken == "" { - log.Printf("Error: External access token not includeed for %s", id) - http.Error(w, "Missing external access token", http.StatusBadRequest) - return - } - - courseList, statusCode, err := h.app.Services.GetStudentCourses(id, termid, externalToken) - if err != nil { - log.Printf("Error getting courses for %s: Server returned %d %s \n", id, statusCode, err.Error()) - switch statusCode { - case 401: - http.Error(w, err.Error(), http.StatusUnauthorized) - case 403: - http.Error(w, err.Error(), http.StatusForbidden) - case 404: - http.Error(w, err.Error(), http.StatusNotFound) - default: - http.Error(w, err.Error(), http.StatusInternalServerError) - } - return - } - - //create a map of buildings we need so we don't retrieve the same building multiple times - neededBuildings := make(map[string]model.Building) - for index, crntCourse := range *courseList { - //check if course is not online here then proceed - if crntCourse.Section.BuildingID != "" { - bldg, bldgexists := neededBuildings[crntCourse.Section.BuildingID] - if bldgexists { - (*courseList)[index].Section.Location = bldg - } else { - bldg, err := (h.app.Services.GetBuilding(crntCourse.Section.BuildingID, adaOnly, latitude, longitude)) - if err != nil { - log.Printf("Error retrieving building details: %s\n", err) - } else { - (*courseList)[index].Section.Location = *bldg - neededBuildings[bldg.ID] = *bldg - } - } - } - - } - - resAsJSON, err := json.Marshal(courseList) - if err != nil { - log.Printf("Error on marshalling course information for %s: %s\n", id, err.Error()) - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - - w.Write(resAsJSON) -} diff --git a/driver/web/rest/laundryapis.go b/driver/web/rest/laundryapis.go deleted file mode 100644 index 2a508c4d..00000000 --- a/driver/web/rest/laundryapis.go +++ /dev/null @@ -1,239 +0,0 @@ -// Copyright 2022 Board of Trustees of the University of Illinois. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package rest - -import ( - "apigateway/core" - model "apigateway/core/model" - "apigateway/utils" - "encoding/json" - "fmt" - "io/ioutil" - "log" - "net/http" -) - -// LaundryApisHandler handles the laudnry rest APIs implementation -type LaundryApisHandler struct { - app *core.Application -} - -// NewLaundryApisHandler creates new rest Handler instance for Laundry functions -func NewLaundryApisHandler(app *core.Application) LaundryApisHandler { - return LaundryApisHandler{app: app} -} - -// GetLaundryRooms returns an organization record -// @Summary Get list of all campus laundry rooms -// @Tags Client -// @ID Rooms -// @Accept json -// @Produce json -// @Success 200 {object} model.Organization -// @Security RokwireAuth -// @Router /laundry/rooms [get] -func (h LaundryApisHandler) GetLaundryRooms(w http.ResponseWriter, r *http.Request) { - - org, err := h.app.Services.ListLaundryRooms() - if err != nil { - log.Printf("Error on listing laundry rooms: %s\n", err) - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - - resAsJSON, err := json.Marshal(org) - if err != nil { - log.Printf("Error on marshalling laundry room list: %s\n", err) - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - - w.WriteHeader(http.StatusOK) - w.Write(resAsJSON) -} - -// GetRoomDetails returns a laundry room detail record -// @Summary Returns the list of machines and the number of washers and dryers available in a laundry room -// @Tags Client -// @ID Room -// @Param id query int true "Room id" -// @Accept json -// @Success 200 {object} model.RoomDetail -// @Security RokwireAuth -// @Router /laundry/roomdetail [get] -func (h LaundryApisHandler) GetRoomDetails(w http.ResponseWriter, r *http.Request) { - reqParams := utils.ConstructFilter(r) - id := "" - for _, v := range reqParams.Items { - if v.Field == "id" { - id = v.Value[0] - break - } - } - - if id != "" { - rd, err := h.app.Services.GetLaundryRoom(id) - if err != nil { - log.Printf("Error retrieving laundry room details: %s\n", err) - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - - resAsJSON, err := json.Marshal(rd) - if err != nil { - log.Printf("Error on marshalling laundry room detail: %s\n", err) - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - - w.WriteHeader(http.StatusOK) - w.Write(resAsJSON) - - } else { - //no id field was found - log.Printf("Error on retrieving laundry detail: missing id parameter") - http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest) - return - } - -} - -// InitServiceRequest returns a laundry room detail record -// @Summary Returns the problem codes and pending service reqeust status for a laundry machine. -// @Tags Client -// @ID InitRequest -// @Param machineid query string true "machine service tag id" -// @Accept json -// @Success 200 {object} model.MachineRequestDetail -// @Security RokwireAuth -// @Router /laundry/initrequest [get] -func (h LaundryApisHandler) InitServiceRequest(w http.ResponseWriter, r *http.Request) { - reqParams := utils.ConstructFilter(r) - id := "" - for _, v := range reqParams.Items { - if v.Field == "machineid" { - //do work here - id = v.Value[0] - break - } - } - - if id != "" { - mrd, err := h.app.Services.InitServiceRequest(id) - if err != nil { - log.Printf("Error retrieving machine service details: %s\n", err) - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - - resAsJSON, err := json.Marshal(mrd) - if err != nil { - log.Printf("Error on marshalling laundry room detail: %s\n", err) - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - - w.WriteHeader(http.StatusOK) - w.Write(resAsJSON) - - } else { - //no id field was found - log.Printf("Error on retrieving machine request detail: missing machine id parameter") - http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest) - return - } -} - -// SubmitServiceRequest returns the results of attempting to submit a service request for a laundyr appliance -// @Tags Client -// @ID RequestService -// @Param data body model.ServiceSubmission true "body json" -// @Accept json -// @Success 200 {object} model.ServiceRequestResult -// @Security RokwireAuth -// @Router /laundry/requestservice [post] -func (h LaundryApisHandler) SubmitServiceRequest(w http.ResponseWriter, r *http.Request) { - data, err := ioutil.ReadAll(r.Body) - if err != nil { - log.Printf("Error on marshal token data - %s\n", err.Error()) - http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest) - return - } - - var record model.ServiceSubmission - err = json.Unmarshal(data, &record) - if err != nil { - if jsonErr, ok := err.(*json.SyntaxError); ok { - problemPart := data[jsonErr.Offset : jsonErr.Offset+10] - log.Printf("json error new '%s'", problemPart) - } - log.Printf("Error on unmarshal the request submission data - %s\n", err.Error()) - http.Error(w, err.Error(), http.StatusBadRequest) - return - } - - if record.MachineID == nil || len(*record.MachineID) == 0 { - log.Printf("machine id is empty or null") - http.Error(w, fmt.Sprintf("Missing miachine id\n"), http.StatusBadRequest) - return - } - - if record.ProblemCode == nil || len(*record.ProblemCode) == 0 { - log.Printf("Problem code is empty or null") - http.Error(w, fmt.Sprintf("Missing Problem Code\n"), http.StatusBadRequest) - return - } - - if record.FirstName == nil || len(*record.FirstName) == 0 { - log.Printf("First name is empty or null") - http.Error(w, fmt.Sprintf("Missing first name\n"), http.StatusBadRequest) - return - } - - if record.LastName == nil || len(*record.LastName) == 0 { - log.Printf("Last name is empty or null") - http.Error(w, fmt.Sprintf("missing last name\n"), http.StatusBadRequest) - return - } - - if record.Email == nil || len(*record.Email) == 0 { - log.Printf("Email is empty or null") - http.Error(w, fmt.Sprintf("missing email\n"), http.StatusBadRequest) - return - } - - if record.Phone == nil { - newPhone := "" - record.Phone = &newPhone - } - - sr, err := h.app.Services.SubmitServiceRequest(*record.MachineID, *record.ProblemCode, *record.Comments, *record.FirstName, *record.LastName, *record.Phone, *record.Email) - - if err != nil { - log.Printf("Error submitting laundry service request: %s\n", err) - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - - resAsJSON, err := json.Marshal(sr) - if err != nil { - log.Printf("Error on marshalling laundry service request result: %s\n", err) - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - - w.WriteHeader(http.StatusOK) - w.Write(resAsJSON) -} diff --git a/driver/web/rest/termsessionapis.go b/driver/web/rest/termsessionapis.go deleted file mode 100644 index 3025cb5e..00000000 --- a/driver/web/rest/termsessionapis.go +++ /dev/null @@ -1,61 +0,0 @@ -// Copyright 2022 Board of Trustees of the University of Illinois. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package rest - -import ( - "apigateway/core" - "encoding/json" - "log" - "net/http" -) - -// TermSessionAPIHandler handles the term session information rest APIs implementation -type TermSessionAPIHandler struct { - app *core.Application -} - -// NewTermSessionAPIHandler creates new rest Handler instance for getting term sessions -func NewTermSessionAPIHandler(app *core.Application) TermSessionAPIHandler { - return TermSessionAPIHandler{app: app} -} - -// GetTermSessions returns a list of recent, current and upcoming term sessions -// @Summary Get a list of term sessions centered on the calculated current session -// @Tags Client -// @ID TermSession -// @Accept json -// @Produce json -// @Success 200 {object} []model.TermSession -// @Security RokwireAuth -// @Router /termsessions/listcurrent [get] -func (h TermSessionAPIHandler) GetTermSessions(w http.ResponseWriter, r *http.Request) { - - termSessions, err := h.app.Services.GetTermSessions() - if err != nil { - log.Printf("Error retrieving term sessions: %s\n", err) - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - - resAsJSON, err := json.Marshal(termSessions) - if err != nil { - log.Printf("Error on marshalling term session data: %s\n", err) - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - - w.WriteHeader(http.StatusOK) - w.Write(resAsJSON) -} diff --git a/driver/web/system_permission_policy.csv b/driver/web/system_permission_policy.csv new file mode 100644 index 00000000..1fdf892a --- /dev/null +++ b/driver/web/system_permission_policy.csv @@ -0,0 +1,8 @@ +p, all_system_identity, /identity/api/system/*, (GET)|(POST)|(PUT)|(DELETE), All identity system admin actions + +p, all_examples, /identity/api/system/examples, (POST), All example system actions +p, all_examples, /identity/api/system/examples/*, (GET)|(PUT)|(DELETE), +p, get_examples, /identity/api/system/examples/*, (GET), Get examples +p, update_examples, /identity/api/system/examples, (POST), Update examples +p, update_examples, /identity/api/system/examples/*, (GET)|(PUT), +p, delete_examples, /identity/api/system/examples/*, (GET)|(DELETE), diff --git a/driver/web/tps_permission_policy.csv b/driver/web/tps_permission_policy.csv new file mode 100644 index 00000000..6803c24a --- /dev/null +++ b/driver/web/tps_permission_policy.csv @@ -0,0 +1 @@ +p, get_examples, /identity/api/tps/examples/*, (GET), Get examples diff --git a/go.mod b/go.mod index c6ddf8ee..3b8ad9e3 100644 --- a/go.mod +++ b/go.mod @@ -1,54 +1,56 @@ -module apigateway +module application go 1.20 require ( - github.com/PuerkitoBio/goquery v1.6.1 - github.com/go-openapi/jsonreference v0.19.6 // indirect + github.com/PuerkitoBio/goquery v1.8.1 github.com/google/uuid v1.3.0 github.com/gorilla/mux v1.8.0 - github.com/rokwire/core-auth-library-go v1.0.5 - github.com/rokwire/logging-library-go v1.0.3 - github.com/swaggo/http-swagger v1.2.6 + github.com/rokwire/core-auth-library-go/v3 v3.0.1 + github.com/rokwire/logging-library-go/v2 v2.2.0 + github.com/swaggo/http-swagger v1.3.4 github.com/swaggo/swag v1.8.1 - go.mongodb.org/mongo-driver v1.5.3 + go.mongodb.org/mongo-driver v1.11.3 ) require ( github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible // indirect github.com/KyleBanks/depth v1.2.1 // indirect - github.com/PuerkitoBio/purell v1.1.1 // indirect - github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect - github.com/andybalholm/cascadia v1.1.0 // indirect - github.com/aws/aws-sdk-go v1.39.4 // indirect - github.com/casbin/casbin/v2 v2.31.10 // indirect + github.com/andybalholm/cascadia v1.3.1 // indirect + github.com/casbin/casbin/v2 v2.65.2 // indirect github.com/go-openapi/jsonpointer v0.19.5 // indirect - github.com/go-openapi/spec v0.20.4 // indirect + github.com/go-openapi/spec v0.20.6 // indirect github.com/go-openapi/swag v0.19.15 // indirect - github.com/go-playground/locales v0.13.0 // indirect - github.com/go-playground/universal-translator v0.17.0 // indirect - github.com/go-stack/stack v1.8.0 // indirect - github.com/golang-jwt/jwt v3.2.1+incompatible // indirect + github.com/go-playground/locales v0.14.1 // indirect + github.com/go-playground/universal-translator v0.18.1 // indirect + github.com/golang-jwt/jwt v3.2.2+incompatible // indirect github.com/golang/snappy v0.0.1 // indirect github.com/google/go-cmp v0.5.4 // indirect - github.com/jmespath/go-jmespath v0.4.0 // indirect github.com/josharian/intern v1.0.0 // indirect - github.com/klauspost/compress v1.9.5 // indirect - github.com/leodido/go-urn v1.2.1 // indirect + github.com/klauspost/compress v1.13.6 // indirect + github.com/leodido/go-urn v1.2.2 // indirect github.com/mailru/easyjson v0.7.7 // indirect github.com/pkg/errors v0.9.1 // indirect - github.com/sirupsen/logrus v1.8.1 // indirect - github.com/swaggo/files v0.0.0-20210815190702-a29dd2bc99b2 // indirect + github.com/sirupsen/logrus v1.9.0 // indirect + github.com/swaggo/files v0.0.0-20220610200504-28940afbdbfe // indirect github.com/xdg-go/pbkdf2 v1.0.0 // indirect - github.com/xdg-go/scram v1.0.2 // indirect - github.com/xdg-go/stringprep v1.0.2 // indirect + github.com/xdg-go/scram v1.1.1 // indirect + github.com/xdg-go/stringprep v1.0.3 // indirect github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d // indirect - golang.org/x/crypto v0.0.0-20220314234659-1baeb1ce4c0b // indirect - golang.org/x/net v0.0.0-20220906165146-f3363e06e74c // indirect - golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 // indirect - golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10 // indirect - golang.org/x/text v0.3.8 // indirect + golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d // indirect + golang.org/x/net v0.7.0 // indirect + golang.org/x/sys v0.6.0 // indirect + golang.org/x/text v0.7.0 // indirect golang.org/x/tools v0.1.12 // indirect gopkg.in/go-playground/validator.v9 v9.31.0 // indirect +) + +require ( + github.com/aws/aws-sdk-go v1.44.221 // indirect + github.com/go-openapi/jsonreference v0.20.0 // indirect + github.com/jmespath/go-jmespath v0.4.0 // indirect + github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe // indirect + github.com/rogpeppe/go-internal v1.10.0 // indirect + golang.org/x/sync v0.1.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect ) diff --git a/go.sum b/go.sum index d27fb45f..e7e84891 100644 --- a/go.sum +++ b/go.sum @@ -1,72 +1,36 @@ -github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible h1:1G1pk05UrOh0NlF1oeaaix1x8XzrfjIDK47TY0Zehcw= github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc= github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE= -github.com/PuerkitoBio/goquery v1.6.1 h1:FgjbQZKl5HTmcn4sKBgvx8vv63nhyhIpv7lJpFGCWpk= -github.com/PuerkitoBio/goquery v1.6.1/go.mod h1:GsLWisAFVj4WgDibEWF4pvYnkVQBpKBKeU+7zCJoLcc= -github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI= -github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= -github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M= -github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= +github.com/PuerkitoBio/goquery v1.8.1 h1:uQxhNlArOIdbrH1tr0UXwdVFgDcZDrZVdcpygAcwmWM= +github.com/PuerkitoBio/goquery v1.8.1/go.mod h1:Q8ICL1kNUJ2sXGoAhPGUdYDJvgQgHzJsnnd3H7Ho5jQ= github.com/agiledragon/gomonkey/v2 v2.3.1 h1:k+UnUY0EMNYUFUAQVETGY9uUTxjMdnUkP0ARyJS1zzs= -github.com/agiledragon/gomonkey/v2 v2.3.1/go.mod h1:ap1AmDzcVOAz1YpeJ3TCzIgstoaWLA6jbbgxfB4w2iY= -github.com/andybalholm/cascadia v1.1.0 h1:BuuO6sSfQNFRu1LppgbD25Hr2vLYW25JvxHs5zzsLTo= -github.com/andybalholm/cascadia v1.1.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y= -github.com/aws/aws-sdk-go v1.34.28/go.mod h1:H7NKnBqNVzoTJpGfLrQkkD+ytBA93eiDYi/+8rV9s48= -github.com/aws/aws-sdk-go v1.39.4 h1:nXBChUaG5cinrl3yg4/rUyssOOLH/ohk4S9K03kJirE= -github.com/aws/aws-sdk-go v1.39.4/go.mod h1:585smgzpB/KqRA+K3y/NL/oYRqQvpNJYvLm+LY1U59Q= -github.com/casbin/casbin/v2 v2.31.10 h1:2vlJ/CnrKt33x+Twm2TxjiRfQFBA4JsAAeJelCTefiM= -github.com/casbin/casbin/v2 v2.31.10/go.mod h1:vByNa/Fchek0KZUgG5wEsl7iFsiviAYKRtgrQfcJqHg= -github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/andybalholm/cascadia v1.3.1 h1:nhxRkql1kdYCc8Snf7D5/D3spOX+dBgjA6u8x004T2c= +github.com/andybalholm/cascadia v1.3.1/go.mod h1:R4bJ1UQfqADjvDa4P6HZHLh/3OxWWEqc0Sk8XGwHqvA= +github.com/aws/aws-sdk-go v1.44.221 h1:yndn4uvLolKXPoXIwKHhO5XtwlTnJfXLBKXs84C5+hQ= +github.com/aws/aws-sdk-go v1.44.221/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= +github.com/casbin/casbin/v2 v2.65.2 h1:a8XUm1Xls9sXc4RISPFEDQZrqpsv5y1KwwB174W7i74= +github.com/casbin/casbin/v2 v2.65.2/go.mod h1:vByNa/Fchek0KZUgG5wEsl7iFsiviAYKRtgrQfcJqHg= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY= github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= -github.com/go-openapi/jsonreference v0.19.6 h1:UBIxjkht+AWIgYzCDSv2GN+E/togfwXUJFRTWhl2Jjs= -github.com/go-openapi/jsonreference v0.19.6/go.mod h1:diGHMEHg2IqXZGKxqyvWdfWU/aim5Dprw5bqpKkTvns= -github.com/go-openapi/spec v0.20.4 h1:O8hJrt0UMnhHcluhIdUgCLRWyM2x7QkBXRvOs7m+O1M= -github.com/go-openapi/spec v0.20.4/go.mod h1:faYFR1CvsJZ0mNsmsphTMSoRrNV3TEDoAM7FOEWeq8I= +github.com/go-openapi/jsonreference v0.20.0 h1:MYlu0sBgChmCfJxxUKZ8g1cPWFOB37YSZqewK7OKeyA= +github.com/go-openapi/jsonreference v0.20.0/go.mod h1:Ag74Ico3lPc+zR+qjn4XBUmXymS4zJbYVCZmcgkasdo= +github.com/go-openapi/spec v0.20.6 h1:ich1RQ3WDbfoeTqTAb+5EIxNmpKVJZWBNah9RAT0jIQ= +github.com/go-openapi/spec v0.20.6/go.mod h1:2OpW+JddWPrpXSCIX8eOx7lZ5iyuWj3RYR6VaaBKcWA= github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= github.com/go-openapi/swag v0.19.15 h1:D2NRCBzS9/pEY3gP9Nl8aDqGUcPFrwG2p+CNFrLyrCM= github.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= -github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q= -github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= -github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no= -github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= -github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= -github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk= -github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= -github.com/gobuffalo/attrs v0.0.0-20190224210810-a9411de4debd/go.mod h1:4duuawTqi2wkkpB4ePgWMaai6/Kc6WEz83bhFwpHzj0= -github.com/gobuffalo/depgen v0.0.0-20190329151759-d478694a28d3/go.mod h1:3STtPUQYuzV0gBVOY3vy6CfMm/ljR4pABfrTeHNLHUY= -github.com/gobuffalo/depgen v0.1.0/go.mod h1:+ifsuy7fhi15RWncXQQKjWS9JPkdah5sZvtHc2RXGlg= -github.com/gobuffalo/envy v1.6.15/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI= -github.com/gobuffalo/envy v1.7.0/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI= -github.com/gobuffalo/flect v0.1.0/go.mod h1:d2ehjJqGOH/Kjqcoz+F7jHTBbmDb38yXA598Hb50EGs= -github.com/gobuffalo/flect v0.1.1/go.mod h1:8JCgGVbRjJhVgD6399mQr4fx5rRfGKVzFjbj6RE/9UI= -github.com/gobuffalo/flect v0.1.3/go.mod h1:8JCgGVbRjJhVgD6399mQr4fx5rRfGKVzFjbj6RE/9UI= -github.com/gobuffalo/genny v0.0.0-20190329151137-27723ad26ef9/go.mod h1:rWs4Z12d1Zbf19rlsn0nurr75KqhYp52EAGGxTbBhNk= -github.com/gobuffalo/genny v0.0.0-20190403191548-3ca520ef0d9e/go.mod h1:80lIj3kVJWwOrXWWMRzzdhW3DsrdjILVil/SFKBzF28= -github.com/gobuffalo/genny v0.1.0/go.mod h1:XidbUqzak3lHdS//TPu2OgiFB+51Ur5f7CSnXZ/JDvo= -github.com/gobuffalo/genny v0.1.1/go.mod h1:5TExbEyY48pfunL4QSXxlDOmdsD44RRq4mVZ0Ex28Xk= -github.com/gobuffalo/gitgen v0.0.0-20190315122116-cc086187d211/go.mod h1:vEHJk/E9DmhejeLeNt7UVvlSGv3ziL+djtTr3yyzcOw= -github.com/gobuffalo/gogen v0.0.0-20190315121717-8f38393713f5/go.mod h1:V9QVDIxsgKNZs6L2IYiGR8datgMhB577vzTDqypH360= -github.com/gobuffalo/gogen v0.1.0/go.mod h1:8NTelM5qd8RZ15VjQTFkAW6qOMx5wBbW4dSCS3BY8gg= -github.com/gobuffalo/gogen v0.1.1/go.mod h1:y8iBtmHmGc4qa3urIyo1shvOD8JftTtfcKi+71xfDNE= -github.com/gobuffalo/logger v0.0.0-20190315122211-86e12af44bc2/go.mod h1:QdxcLw541hSGtBnhUc4gaNIXRjiDppFGaDqzbrBd3v8= -github.com/gobuffalo/mapi v1.0.1/go.mod h1:4VAGh89y6rVOvm5A8fKFxYG+wIW6LO1FMTG9hnKStFc= -github.com/gobuffalo/mapi v1.0.2/go.mod h1:4VAGh89y6rVOvm5A8fKFxYG+wIW6LO1FMTG9hnKStFc= -github.com/gobuffalo/packd v0.0.0-20190315124812-a385830c7fc0/go.mod h1:M2Juc+hhDXf/PnmBANFCqx4DM3wRbgDvnVWeG2RIxq4= -github.com/gobuffalo/packd v0.1.0/go.mod h1:M2Juc+hhDXf/PnmBANFCqx4DM3wRbgDvnVWeG2RIxq4= -github.com/gobuffalo/packr/v2 v2.0.9/go.mod h1:emmyGweYTm6Kdper+iywB6YK5YzuKchGtJQZ0Odn4pQ= -github.com/gobuffalo/packr/v2 v2.2.0/go.mod h1:CaAwI0GPIAv+5wKLtv8Afwl+Cm78K/I/VCm/3ptBN+0= -github.com/gobuffalo/syncx v0.0.0-20190224160051-33c29581e754/go.mod h1:HhnNqWY95UYwwW3uSASeV7vtgYkT2t16hJgV3AEPUpw= -github.com/golang-jwt/jwt v3.2.1+incompatible h1:73Z+4BJcrTC+KczS6WvTPvRGOp1WmfEP4Q1lOd9Z/+c= -github.com/golang-jwt/jwt v3.2.1+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= +github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= +github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= +github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= +github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= +github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY= +github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= github.com/golang/mock v1.4.4 h1:l75CXGRSwbaYNpl/Z2X1XIIAMSCquvXgpVZDhwEIJsc= github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4= @@ -74,191 +38,140 @@ github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEW github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.4 h1:L8R9j+yAqZuZjsqh/z+F1NCffTKKLShY6zXTItVIZ8M= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= -github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= -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/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= -github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= -github.com/karrick/godirwalk v1.8.0/go.mod h1:H5KPZjojv4lE+QYImBI8xVtrBRgYrIVsaRPx4tDPEn4= -github.com/karrick/godirwalk v1.10.3/go.mod h1:RoGL9dQei4vP9ilrpETWE8CLOZ1kiN0LhBygSwrAsHA= -github.com/klauspost/compress v1.9.5 h1:U+CaK85mrNNb4k8BNOfgJtJ/gr6kswUCFj6miSzVC6M= -github.com/klauspost/compress v1.9.5/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= -github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/klauspost/compress v1.13.6 h1:P76CopJELS0TiO2mebmnzgWaajssP/EszplttgQxcgc= +github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w= -github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY= +github.com/leodido/go-urn v1.2.2 h1:7z68G0FCGvDk646jz1AelTYNYWrTNm0bEcFAo147wt4= +github.com/leodido/go-urn v1.2.2/go.mod h1:kUaIbLZWttglzwNuG0pgsh5vuV6u2YcGBYz1hIPjtOQ= github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= -github.com/markbates/oncer v0.0.0-20181203154359-bf2de49a0be2/go.mod h1:Ld9puTsIW75CHf65OeIOkyKbteujpZVXDpWK6YGZbxE= -github.com/markbates/safe v1.0.1/go.mod h1:nAqgmRi7cY2nqMc92/bSEeQA+R4OheNU2T1kNSCBdG0= +github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe h1:iruDEfMl2E6fbMZ9s0scYfZQ84/6SPL6zC8ACM2oIL0= github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc= -github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/otiai10/copy v1.7.0 h1:hVoPiN+t+7d2nzzwMiDHPSOogsWAStewq3TwU05+clE= -github.com/otiai10/copy v1.7.0/go.mod h1:rmRl6QPdJj6EiUqXQ/4Nn2lLXoNQjFCQbbNrxgc/t3U= -github.com/otiai10/curr v0.0.0-20150429015615-9b4961190c95/go.mod h1:9qAhocn7zKJG+0mI8eUu6xqkFDYS2kb2saOteoSB3cE= -github.com/otiai10/curr v1.0.0/go.mod h1:LskTG5wDwr8Rs+nNQ+1LlxRjAtTZZjtJW4rMXl6j4vs= -github.com/otiai10/mint v1.3.0/go.mod h1:F5AjcsTsWUqX+Na9fpHb52P8pcRX2CI6A3ctIT91xUo= -github.com/otiai10/mint v1.3.3/go.mod h1:/yxELlJQ0ufhjUwhshSj+wFjZ78CnZ48/1wtmBH1OTc= -github.com/pelletier/go-toml v1.7.0/go.mod h1:vwGMzjaWMwyfHwgIBhI2YUM4fB6nL6lVAvS1LBMMhTE= -github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= -github.com/rogpeppe/go-internal v1.2.2/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= -github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= -github.com/rokwire/core-auth-library-go v1.0.5 h1:3AwcXnPvTNsVL+2RsQkQqQUPA0zHvQSIhPdnkVLYTnQ= -github.com/rokwire/core-auth-library-go v1.0.5/go.mod h1:y5XiXjTD52DDX0iHAR8J0kWls/xCgUtqyBFWjp/cmQo= -github.com/rokwire/logging-library-go v1.0.0/go.mod h1:yntksZF2TDmxid9MwDnAAt95TeLMYo6chL0VUyIaFHk= -github.com/rokwire/logging-library-go v1.0.3 h1:ONaEJO0NbBYtG+gV7+fn2zQqtPDkpSCif+nMuXvJFBA= -github.com/rokwire/logging-library-go v1.0.3/go.mod h1:yntksZF2TDmxid9MwDnAAt95TeLMYo6chL0VUyIaFHk= -github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= -github.com/sirupsen/logrus v1.4.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= -github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= -github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= -github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= -github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= -github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= -github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= -github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= -github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= +github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= +github.com/rokwire/core-auth-library-go/v3 v3.0.1 h1:7kZqnXq3lsb8sU+YxXEtHXjmxvFd5X/VpqQKiy/RnqA= +github.com/rokwire/core-auth-library-go/v3 v3.0.1/go.mod h1:VtpVajbA8JPjOzvEFQpxOm4pfok4z/8a2WshErTZd7s= +github.com/rokwire/logging-library-go/v2 v2.2.0 h1:SKFq+rrl+li1RhEhB7CV+pVcptu/nurX9/DWj/oRaw4= +github.com/rokwire/logging-library-go/v2 v2.2.0/go.mod h1:6QSqTlk5nNQcZweqg0sLCCoIwpRpTu3AmOi7EJy38Tg= +github.com/rwtodd/Go.Sed v0.0.0-20210816025313-55464686f9ef/go.mod h1:8AEUvGVi2uQ5b24BIhcr0GCcpd/RNAFWaN2CJFrWIIQ= +github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= +github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.1.1 h1:2vfRuCMp5sSVIDSqO8oNnWJq7mPa6KVP3iPIwFBuy8A= -github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/swaggo/files v0.0.0-20210815190702-a29dd2bc99b2 h1:+iNTcqQJy0OZ5jk6a5NLib47eqXK8uYcPX+O4+cBpEM= -github.com/swaggo/files v0.0.0-20210815190702-a29dd2bc99b2/go.mod h1:lKJPbtWzJ9JhsTN1k1gZgleJWY/cqq0psdoMmaThG3w= -github.com/swaggo/http-swagger v1.2.6 h1:ihTjChUoSRMpFMjWw+0AkL1Ti4r6v8pCgVYLmQVRlRw= -github.com/swaggo/http-swagger v1.2.6/go.mod h1:CcoICgY3yVDk2u1LQUCMHbAj0fjlxIX+873psXlIKNA= -github.com/swaggo/swag v1.7.9/go.mod h1:gZ+TJ2w/Ve1RwQsA2IRoSOTidHz6DX+PIG8GWvbnoLU= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= +github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/swaggo/files v0.0.0-20220610200504-28940afbdbfe h1:K8pHPVoTgxFJt1lXuIzzOX7zZhZFldJQK/CgKx9BFIc= +github.com/swaggo/files v0.0.0-20220610200504-28940afbdbfe/go.mod h1:lKJPbtWzJ9JhsTN1k1gZgleJWY/cqq0psdoMmaThG3w= +github.com/swaggo/http-swagger v1.3.4 h1:q7t/XLx0n15H1Q9/tk3Y9L4n210XzJF5WtnDX64a5ww= +github.com/swaggo/http-swagger v1.3.4/go.mod h1:9dAh0unqMBAlbp1uE2Uc2mQTxNMU/ha4UbucIg1MFkQ= github.com/swaggo/swag v1.8.1 h1:JuARzFX1Z1njbCGz+ZytBR15TFJwF2Q7fu8puJHhQYI= github.com/swaggo/swag v1.8.1/go.mod h1:ugemnJsPZm/kRwFUnzBlbHRd0JY9zE1M4F+uy2pAaPQ= github.com/tidwall/pretty v1.0.0 h1:HsD+QiTn7sK6flMKIvNmpqz1qrpP3Ps6jOKIKMooyg4= github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= -github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI= github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c= github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= -github.com/xdg-go/scram v1.0.2 h1:akYIkZ28e6A96dkWNJQu3nmCzH3YfwMPQExUYDaRv7w= -github.com/xdg-go/scram v1.0.2/go.mod h1:1WAq6h33pAW+iRreB34OORO2Nf7qel3VV3fjBj+hCSs= -github.com/xdg-go/stringprep v1.0.2 h1:6iq84/ryjjeRmMJwxutI51F2GIPlP5BfTvXHeYjyhBc= -github.com/xdg-go/stringprep v1.0.2/go.mod h1:8F9zXuvzgwmyT5DUm4GUfZGDdT3W+LCvS6+da4O5kxM= +github.com/xdg-go/scram v1.1.1 h1:VOMT+81stJgXW3CpHyqHN3AXDYIMsx56mEFrB37Mb/E= +github.com/xdg-go/scram v1.1.1/go.mod h1:RaEWvsqvNKKvBPvcKeFjrG2cJqOkHTiyTpzz23ni57g= +github.com/xdg-go/stringprep v1.0.3 h1:kdwGpVNwPFtjs98xCGkHjQtGKh86rDcRZN17QEMCOIs= +github.com/xdg-go/stringprep v1.0.3/go.mod h1:W3f5j4i+9rC0kuIEJL0ky1VpHXQU3ocBgklLGvcBnW8= github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d h1:splanxYIlg+5LfHAM6xpdFEAYOk8iySO56hMFq6uLyA= github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA= -github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= -go.mongodb.org/mongo-driver v1.5.3 h1:wWbFB6zaGHpzguF3f7tW94sVE8sFl3lHx8OZx/4OuFI= -go.mongodb.org/mongo-driver v1.5.3/go.mod h1:gRXCHX4Jo7J0IJ1oDQyUxF7jfy19UfxniMS4xxMmUqw= -golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +go.mongodb.org/mongo-driver v1.11.3 h1:Ql6K6qYHEzB6xvu4+AU0BoRoqf9vFPcc4o7MUIdPW8Y= +go.mongodb.org/mongo-driver v1.11.3/go.mod h1:PTSz5yu21bkT/wXpkS7WR5f0ddqw5quethTUn9WM+2g= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190422162423-af44ce270edf/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= -golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20201217014255-9d1352758620 h1:3wPMTskHO3+O6jqTEXyFcsnuxMQOqYSaHsDxcbUXpqA= -golang.org/x/crypto v0.0.0-20201217014255-9d1352758620/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= -golang.org/x/crypto v0.0.0-20220314234659-1baeb1ce4c0b h1:Qwe1rC8PSniVfAFPFJeyUkB+zcysC3RgJBAGk7eqBEU= -golang.org/x/crypto v0.0.0-20220314234659-1baeb1ce4c0b/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/mod v0.4.2 h1:Gz96sIWK3OalVv/I/qNygP42zyoKp3xptRVCWRFEBvo= -golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d h1:sK3txAijHtOK88l68nt020reeT1ZdKLIYetKl95FzVY= +golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20210421230115-4e50805a0758/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM= -golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d h1:20cMwl2fHAzkJMEA+8J4JgqBQcQGzbisXo31MIeenXI= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20220906165146-f3363e06e74c h1:yKufUcDwucU5urd+50/Opbt4AYpqthk7wHpHok8f1lo= -golang.org/x/net v0.0.0-20220906165146-f3363e06e74c/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= -golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190412183630-56d357773e84/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/net v0.0.0-20210916014120-12bc252f5db8/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= +golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g= +golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 h1:uVc8UZUe6tr40fFVnUP5Oj+veunVezqYl9z7DYw9xzw= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190419153524-e8e3143a4f4a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190531175056-4c3a928424d2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210420072515-93ed5bcd2bfe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e h1:WUoyKPm6nCo1BnNUvPGnFG3T5DUVem42yDJZZ4CNxMA= -golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10 h1:WIoqL4EROvwiPdUtaip4VcDdpZ4kha7wBWZrbVKCIZg= -golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= -golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.3/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/text v0.3.8 h1:nAL+RVCQ9uMn3vJZbV+MRnydTJFPf8qqY42YiA6MrqY= -golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= +golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190329151228-23e29df326fe/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190416151739-9c9e1878f421/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190420181800-aa740d480789/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190531172133-b3315ee88b7d/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.1.7 h1:6j8CgantCy3yc8JGBqkDLMKWqZ0RDU2g1HVgacojGWQ= -golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo= golang.org/x/tools v0.1.12 h1:VveCTK38A2rkS8ZqFY25HIDFscX5X9OoEhJd3quQmXU= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= 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 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= 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= 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-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/go-playground/assert.v1 v1.2.1 h1:xoYuJVE7KT85PYWrN730RguIQO0ePzVRfFMXadIrXTM= -gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE= gopkg.in/go-playground/validator.v9 v9.31.0 h1:bmXmP2RSNtFES+bn4uYuHT7iJFJv7Vj+an+ZQdDaD1M= gopkg.in/go-playground/validator.v9 v9.31.0/go.mod h1:+c9/zcJMFNgbLvly1L1V+PpxWdVbfP1avr/N00E2vyQ= 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.8/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/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 h1:tQIYjPdBoyREyB9XMu+nnTclpTYkz2zFM+lzLJFO4gQ= gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/main.go b/main.go index b483d7ff..9350a010 100644 --- a/main.go +++ b/main.go @@ -15,20 +15,16 @@ package main import ( - "apigateway/core" - model "apigateway/core/model" - contactinfo "apigateway/driven/contactinfo" - courses "apigateway/driven/courses" - "apigateway/driven/laundry" - location "apigateway/driven/location" - storage "apigateway/driven/storage" - terms "apigateway/driven/terms" - driver "apigateway/driver/web" - "encoding/json" - "fmt" - "io/ioutil" - "log" - "os" + "application/core" + "application/core/interfaces" + "application/driven/storage" + "application/driven/uiucadapters" + "application/driver/web" + "strings" + + "github.com/rokwire/core-auth-library-go/v3/authservice" + "github.com/rokwire/core-auth-library-go/v3/envloader" + "github.com/rokwire/logging-library-go/v2/logs" ) var ( @@ -38,96 +34,64 @@ var ( Build string ) -/* -func printDeletedAccountIDs(accountIDs []string) error { - log.Printf("Deleted account IDs: %v\n", accountIDs) - return nil -} -*/ - func main() { if len(Version) == 0 { Version = "dev" } - port := getEnvKey("GATEWAY_PORT", true) + serviceID := "gateway" - //mongoDB adapter - mongoDBAuth := getEnvKey("GATEWAY_MONGO_AUTH", true) - mongoDBName := getEnvKey("GATEWAY_MONGO_DATABASE", true) - mongoTimeout := getEnvKey("GATEWAY_MONGO_TIMEOUT", false) - laundryKey := getEnvKey("GATEWAY_LAUNDRY_APIKEY", true) - laundryAPI := getEnvKey("GATEWAY_LAUNDRY_APIURL", true) - luandryServiceKey := getEnvKey("GATEWAY_LAUNDRYSERVICE_APIKEY", true) - laundryServiceAPI := getEnvKey("GATEWAY_LAUNDRYSERVICE_API", true) - laundryServiceToken := getEnvKey("GATEWAY_LAUNDRYSERVICE_BASICAUTH", true) - wayfindingURL := getEnvKey("GATEWAY_WAYFINDING_APIURL", true) - wayfindingKey := getEnvKey("GATEWAY_WAYFINDING_APIKEY", true) - campusInfoAPIKey := getEnvKey("GATEWAY_CENTRALCAMPUS_APIKEY", true) - campusAITSEndPoint := getEnvKey("GATEWAY_CENTRALCAMPUS_ENDPOINT", true) - coursesEndPoint := getEnvKey("GATEWAY_GIESCOURSES_ENDPOINT", true) + //loggerOpts := logs.LoggerOpts{SuppressRequests: logs.NewStandardHealthCheckHTTPRequestProperties(serviceID + "/version")} + loggerOpts := logs.LoggerOpts{ + SensitiveHeaders: []string{"External-Authorization"}, + SuppressRequests: logs.NewStandardHealthCheckHTTPRequestProperties(serviceID + "/version")} + logger := logs.NewLogger(serviceID, &loggerOpts) + envLoader := envloader.NewEnvLoader(Version, logger) - //read assets - file, _ := ioutil.ReadFile("./assets/assets.json") - assets := model.Asset{} - _ = json.Unmarshal([]byte(file), &assets) - laundryAssets := make(map[string]model.LaundryDetails) - - for i := 0; i < len(assets.Laundry.Assets); i++ { - laundryAsset := assets.Laundry.Assets[i] - laundryAssets[laundryAsset.LocationID] = laundryAsset.Details + envPrefix := strings.ReplaceAll(strings.ToUpper(serviceID), "-", "_") + "_" + port := envLoader.GetAndLogEnvVar(envPrefix+"PORT", false, false) + if len(port) == 0 { + port = "80" } - storageAdapter := storage.NewStorageAdapter(mongoDBAuth, mongoDBName, mongoTimeout) - laundryAdapter := laundry.NewCSCLaundryAdapter(laundryKey, laundryAPI, luandryServiceKey, laundryServiceAPI, laundryAssets, laundryServiceToken) - locationAdapter := location.NewUIUCWayFinding(wayfindingKey, wayfindingURL) - contactAdapter := contactinfo.NewContactAdapter(campusInfoAPIKey, campusAITSEndPoint) - giescourseAdapter := courses.NewGiesCourseAdapter(coursesEndPoint) - studentCourseAdapter := courses.NewCourseAdapter(campusAITSEndPoint, campusInfoAPIKey) - termSessionAdapter := terms.NewTermSessionAdapter() - + // mongoDB adapter + mongoDBAuth := envLoader.GetAndLogEnvVar(envPrefix+"MONGO_AUTH", true, true) + mongoDBName := envLoader.GetAndLogEnvVar(envPrefix+"MONGO_DATABASE", true, false) + mongoTimeout := envLoader.GetAndLogEnvVar(envPrefix+"MONGO_TIMEOUT", false, false) + storageAdapter := storage.NewStorageAdapter(mongoDBAuth, mongoDBName, mongoTimeout, logger) err := storageAdapter.Start() if err != nil { - log.Fatal("Cannot start the mongoDB adapter - " + err.Error()) + logger.Fatalf("Cannot start the mongoDB adapter: %v", err) } - log.Printf("MongoDB Started") - - //application - application := core.NewApplication(Version, Build, storageAdapter, laundryAdapter, locationAdapter, contactAdapter, giescourseAdapter, studentCourseAdapter, termSessionAdapter) + // appointment adapters + appointments := make(map[string]interfaces.Appointments) + appointments["2"] = uiucadapters.NewEngineeringAppontmentsAdapter() + // application + application := core.NewApplication(Version, Build, storageAdapter, appointments, logger) application.Start() - //web adapter - host := getEnvKey("GATEWAY_HOST", true) - corehost := getEnvKey("GATEWAY_CORE_HOST", true) - log.Printf(corehost) - - tokenAuth := driver.NewTokenAuth(host, corehost) - fmt.Println("auth setup complete") + // web adapter + baseURL := envLoader.GetAndLogEnvVar(envPrefix+"BASE_URL", true, false) + coreBBBaseURL := envLoader.GetAndLogEnvVar(envPrefix+"CORE_BB_BASE_URL", true, false) - log.Printf("Creating web adapter") - webAdapter := driver.NewWebAdapter(host, port, application, tokenAuth) - - log.Printf("starting web adapter") - webAdapter.Start() -} + authService := authservice.AuthService{ + ServiceID: serviceID, + ServiceHost: baseURL, + FirstParty: true, + AuthBaseURL: coreBBBaseURL, + } -func getEnvKey(key string, required bool) string { - //get from the environment - value, exist := os.LookupEnv(key) - if !exist { - if required { - log.Fatal("No provided environment variable for " + key) - } else { - log.Printf("No provided environment variable for " + key) - } + serviceRegLoader, err := authservice.NewRemoteServiceRegLoader(&authService, nil) + if err != nil { + logger.Fatalf("Error initializing remote service registration loader: %v", err) } - printEnvVar(key, value) - return value -} -func printEnvVar(name string, value string) { - if Version == "dev" { - log.Printf("%s=%s", name, value) + serviceRegManager, err := authservice.NewServiceRegManager(&authService, serviceRegLoader, !strings.HasPrefix(baseURL, "http://localhost")) + if err != nil { + logger.Fatalf("Error initializing service registration manager: %v", err) } + + webAdapter := web.NewWebAdapter(baseURL, port, serviceID, application, serviceRegManager, logger) + webAdapter.Start() }