From 1807ff17c6fa9aee2c4e70b57514806fc276b584 Mon Sep 17 00:00:00 2001
From: Grady Ward
Date: Sun, 10 Sep 2023 18:15:33 -0600
Subject: [PATCH] Working!
---
cmd/server/main.go | 3 +-
cmd/server/pactasrv/BUILD.bazel | 4 +
cmd/server/pactasrv/conv_oapi_to_pacta.go | 58 ++++
cmd/server/pactasrv/conv_pacta_to_oapi.go | 45 +++
cmd/server/pactasrv/error.go | 163 +++++++++
cmd/server/pactasrv/initiative.go | 164 +++++++++
cmd/server/pactasrv/pacta_version.go | 144 +++++++-
cmd/server/pactasrv/pactasrv.go | 31 +-
cmd/server/pactasrv/user.go | 46 ++-
frontend/components/standard/Content.vue | 2 +-
frontend/components/standard/Footer.vue | 16 +-
frontend/composables/useURLParams.ts | 56 ++++
frontend/layouts/default.vue | 2 +-
frontend/openapi/generated/pacta/index.ts | 3 +
.../generated/pacta/models/Initiative.ts | 67 ++++
.../pacta/models/InitiativeChanges.ts | 59 ++++
.../pacta/models/InitiativeCreate.ts | 63 ++++
.../pacta/models/PactaVersionChanges.ts | 4 -
.../pacta/services/DefaultService.ts | 145 ++++++--
frontend/pages/admin/pacta-version/[id].vue | 110 ++++++
frontend/pages/admin/pacta-version/index.vue | 42 ++-
frontend/pages/admin/pacta-version/new.vue | 2 +-
openapi/pacta.yaml | 315 +++++++++++++++---
23 files changed, 1447 insertions(+), 97 deletions(-)
create mode 100644 cmd/server/pactasrv/conv_oapi_to_pacta.go
create mode 100644 cmd/server/pactasrv/conv_pacta_to_oapi.go
create mode 100644 cmd/server/pactasrv/error.go
create mode 100644 cmd/server/pactasrv/initiative.go
create mode 100644 frontend/composables/useURLParams.ts
create mode 100644 frontend/openapi/generated/pacta/models/Initiative.ts
create mode 100644 frontend/openapi/generated/pacta/models/InitiativeChanges.ts
create mode 100644 frontend/openapi/generated/pacta/models/InitiativeCreate.ts
create mode 100644 frontend/pages/admin/pacta-version/[id].vue
diff --git a/cmd/server/main.go b/cmd/server/main.go
index bb2248c..5f0f34a 100644
--- a/cmd/server/main.go
+++ b/cmd/server/main.go
@@ -169,7 +169,8 @@ func run(args []string) error {
AllowCredentials: true,
AllowedHeaders: []string{"Authorization", "Content-Type"},
// Enable Debugging for testing, consider disabling in production
- Debug: true,
+ Debug: true,
+ AllowedMethods: []string{"GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"},
}).Handler(r)
} else {
handler = r
diff --git a/cmd/server/pactasrv/BUILD.bazel b/cmd/server/pactasrv/BUILD.bazel
index 13c3ea2..bda5d99 100644
--- a/cmd/server/pactasrv/BUILD.bazel
+++ b/cmd/server/pactasrv/BUILD.bazel
@@ -3,6 +3,10 @@ load("@io_bazel_rules_go//go:def.bzl", "go_library")
go_library(
name = "pactasrv",
srcs = [
+ "conv_oapi_to_pacta.go",
+ "conv_pacta_to_oapi.go",
+ "error.go",
+ "initiative.go",
"pacta_version.go",
"pactasrv.go",
"user.go",
diff --git a/cmd/server/pactasrv/conv_oapi_to_pacta.go b/cmd/server/pactasrv/conv_oapi_to_pacta.go
new file mode 100644
index 0000000..eb25585
--- /dev/null
+++ b/cmd/server/pactasrv/conv_oapi_to_pacta.go
@@ -0,0 +1,58 @@
+package pactasrv
+
+import (
+ "fmt"
+ "regexp"
+
+ api "github.com/RMI/pacta/openapi/pacta"
+ "github.com/RMI/pacta/pacta"
+)
+
+var initiativeIDRegex = regexp.MustCompile(`^[a-zA-Z0-9_-]+$`)
+
+func initiativeCreateToPACTA(i *api.InitiativeCreate) (*pacta.Initiative, error) {
+ if i == nil {
+ return nil, errorBadRequest("InitiativeCreate", "cannot be nil")
+ }
+ if !initiativeIDRegex.MatchString(i.Id) {
+ return nil, errorBadRequest("id", "must contain only alphanumeric characters, underscores, and dashes")
+ }
+ lang, err := pacta.ParseLanguage(string(i.Language))
+ if err != nil {
+ return nil, errorBadRequest("language", err.Error())
+ }
+ var pv *pacta.PACTAVersion
+ if i.PactaVersion != nil {
+ pv = &pacta.PACTAVersion{ID: pacta.PACTAVersionID(*i.PactaVersion)}
+ }
+ return &pacta.Initiative{
+ Affiliation: ifNil(i.Affiliation, ""),
+ ID: pacta.InitiativeID(i.Id),
+ InternalDescription: ifNil(i.InternalDescription, ""),
+ IsAcceptingNewMembers: ifNil(i.IsAcceptingNewMembers, false),
+ IsAcceptingNewPortfolios: ifNil(i.IsAcceptingNewPortfolios, false),
+ Language: lang,
+ Name: i.Name,
+ PACTAVersion: pv,
+ PublicDescription: ifNil(i.PublicDescription, ""),
+ RequiresInvitationToJoin: ifNil(i.RequiresInvitationToJoin, false),
+ }, nil
+}
+
+func pactaVersionCreateToPACTA(p *api.PactaVersionCreate) (*pacta.PACTAVersion, error) {
+ if p == nil {
+ return nil, fmt.Errorf("pactaVersionCreateToPACTA: nil pointer")
+ }
+ return &pacta.PACTAVersion{
+ Name: p.Name,
+ Digest: p.Digest,
+ Description: p.Description,
+ }, nil
+}
+
+func ifNil[T any](t *T, or T) T {
+ if t == nil {
+ return or
+ }
+ return *t
+}
diff --git a/cmd/server/pactasrv/conv_pacta_to_oapi.go b/cmd/server/pactasrv/conv_pacta_to_oapi.go
new file mode 100644
index 0000000..95fecd7
--- /dev/null
+++ b/cmd/server/pactasrv/conv_pacta_to_oapi.go
@@ -0,0 +1,45 @@
+package pactasrv
+
+import (
+ "fmt"
+
+ api "github.com/RMI/pacta/openapi/pacta"
+ "github.com/RMI/pacta/pacta"
+)
+
+func initiativeToOAPI(i *pacta.Initiative) (*api.Initiative, error) {
+ if i == nil {
+ return nil, errorInternal(fmt.Errorf("initiativeToOAPI: can't convert nil pointer"))
+ }
+ return &api.Initiative{
+ Affiliation: i.Affiliation,
+ CreatedAt: i.CreatedAt,
+ Id: string(i.ID),
+ InternalDescription: i.InternalDescription,
+ IsAcceptingNewMembers: i.IsAcceptingNewMembers,
+ IsAcceptingNewPortfolios: i.IsAcceptingNewPortfolios,
+ Language: api.InitiativeLanguage(i.Language),
+ Name: i.Name,
+ PactaVersion: ptr(string(i.PACTAVersion.ID)),
+ PublicDescription: i.PublicDescription,
+ RequiresInvitationToJoin: i.RequiresInvitationToJoin,
+ }, nil
+}
+
+func pactaVersionToOAPI(pv *pacta.PACTAVersion) (*api.PactaVersion, error) {
+ if pv == nil {
+ return nil, errorInternal(fmt.Errorf("pactaVersionToOAPI: can't convert nil pointer"))
+ }
+ return &api.PactaVersion{
+ Id: string(pv.ID),
+ Name: pv.Name,
+ IsDefault: pv.IsDefault,
+ Digest: pv.Digest,
+ Description: pv.Description,
+ CreatedAt: pv.CreatedAt,
+ }, nil
+}
+
+func ptr[T any](t T) *T {
+ return &t
+}
diff --git a/cmd/server/pactasrv/error.go b/cmd/server/pactasrv/error.go
new file mode 100644
index 0000000..f55e735
--- /dev/null
+++ b/cmd/server/pactasrv/error.go
@@ -0,0 +1,163 @@
+package pactasrv
+
+import (
+ "fmt"
+
+ api "github.com/RMI/pacta/openapi/pacta"
+)
+
+type errBadRequest struct {
+ Field string
+ Message string
+}
+
+func (e *errBadRequest) Code() int32 {
+ return 400
+}
+
+func (e *errBadRequest) Error() string {
+ return fmt.Sprintf("bad request: field %q: %s", e.Field, e.Message)
+}
+
+func (e *errBadRequest) Is(target error) bool {
+ _, ok := target.(*errBadRequest)
+ return ok
+}
+
+func errorBadRequest(field string, message string) error {
+ return &errBadRequest{Field: field, Message: message}
+}
+
+type errUnauthorized struct {
+ Action string
+ Resource string
+}
+
+func (e *errUnauthorized) Code() int32 {
+ return 401
+}
+
+func (e *errUnauthorized) Error() string {
+ return fmt.Sprintf("unauthorized to %s %s", e.Action, e.Resource)
+}
+
+func (e *errUnauthorized) Is(target error) bool {
+ _, ok := target.(*errUnauthorized)
+ return ok
+}
+
+func errorUnauthorized(action string, resource string) error {
+ return &errUnauthorized{Action: action, Resource: resource}
+}
+
+type errForbidden struct {
+ Action string
+ Resource string
+}
+
+func (e *errForbidden) Code() int32 {
+ return 403
+}
+
+func (e *errForbidden) Error() string {
+ return fmt.Sprintf("user is not allowed to %s %s", e.Action, e.Resource)
+}
+
+func (e *errForbidden) Is(target error) bool {
+ _, ok := target.(*errForbidden)
+ return ok
+}
+
+func errorForbidden(action string, resource string) error {
+ return &errForbidden{Action: action, Resource: resource}
+}
+
+type errNotFound struct {
+ What string
+ With string
+}
+
+func (e *errNotFound) Code() int32 {
+ return 404
+}
+
+func (e *errNotFound) Error() string {
+ return fmt.Sprintf("not found: %s with %s", e.What, e.With)
+}
+
+func (e *errNotFound) Is(target error) bool {
+ _, ok := target.(*errNotFound)
+ return ok
+}
+
+func errorNotFound(what string, with string) error {
+ return &errNotFound{What: what, With: with}
+}
+
+type errInternal struct {
+ What string
+}
+
+func (e *errInternal) Code() int32 {
+ return 500
+}
+
+func (e *errInternal) Error() string {
+ return fmt.Sprintf("internal error: %s", e.What)
+}
+
+func (e *errInternal) Is(target error) bool {
+ _, ok := target.(*errInternal)
+ return ok
+}
+
+func errorInternal(err error) error {
+ return &errInternal{What: err.Error()}
+}
+
+type errNotImplemented struct {
+ What string
+}
+
+func (e *errNotImplemented) Code() int32 {
+ return 501
+}
+
+func (e *errNotImplemented) Error() string {
+ return fmt.Sprintf("not implemented: %s", e.What)
+}
+
+func (e *errNotImplemented) Is(target error) bool {
+ _, ok := target.(*errNotImplemented)
+ return ok
+}
+
+func errorNotImplemented(what string) error {
+ return &errNotImplemented{What: what}
+}
+
+func errToAPIError(err error) api.Error {
+ if e, ok := err.(*errBadRequest); ok {
+ return api.Error{Code: e.Code(), Message: e.Error()}
+ }
+ if e, ok := err.(*errUnauthorized); ok {
+ return api.Error{Code: e.Code(), Message: e.Error()}
+ }
+ if e, ok := err.(*errForbidden); ok {
+ return api.Error{Code: e.Code(), Message: e.Error()}
+ }
+ if e, ok := err.(*errNotFound); ok {
+ return api.Error{Code: e.Code(), Message: e.Error()}
+ }
+ if e, ok := err.(*errInternal); ok {
+ return api.Error{Code: e.Code(), Message: e.Error()}
+ }
+ if e, ok := err.(*errNotImplemented); ok {
+ return api.Error{Code: e.Code(), Message: e.Error()}
+ }
+ // TODO: log here for an unexpected error condition.
+ return api.Error{
+ Code: 500,
+ Message: "an unexpected error occurred",
+ }
+}
diff --git a/cmd/server/pactasrv/initiative.go b/cmd/server/pactasrv/initiative.go
new file mode 100644
index 0000000..588d361
--- /dev/null
+++ b/cmd/server/pactasrv/initiative.go
@@ -0,0 +1,164 @@
+package pactasrv
+
+import (
+ "context"
+
+ "github.com/RMI/pacta/db"
+ api "github.com/RMI/pacta/openapi/pacta"
+ "github.com/RMI/pacta/pacta"
+)
+
+// Creates a initiative
+// (POST /initiatives)
+func (s *Server) CreateInitiative(ctx context.Context, request api.CreateInitiativeRequestObject) (api.CreateInitiativeResponseObject, error) {
+ err := s.createInitiative(ctx, request)
+ if err != nil {
+ e := errToAPIError(err)
+ return api.CreateInitiativedefaultJSONResponse{
+ Body: e,
+ StatusCode: int(e.Code),
+ }, nil
+ }
+ return api.CreateInitiative200JSONResponse(emptySuccess), nil
+}
+
+func (s *Server) createInitiative(ctx context.Context, request api.CreateInitiativeRequestObject) error {
+ // TODO(#12) Implement Authorization
+ i, err := initiativeCreateToPACTA(request.Body)
+ if err != nil {
+ return err
+ }
+ err = s.DB.CreateInitiative(s.DB.NoTxn(ctx), i)
+ if err != nil {
+ return errorInternal(err)
+ }
+ return nil
+}
+
+// Updates an initiative
+// (PATCH /initiative/{id})
+func (s *Server) UpdateInitiative(ctx context.Context, request api.UpdateInitiativeRequestObject) (api.UpdateInitiativeResponseObject, error) {
+ err := s.updateInitiative(ctx, request)
+ if err != nil {
+ e := errToAPIError(err)
+ return api.UpdateInitiativedefaultJSONResponse{
+ Body: e,
+ StatusCode: int(e.Code),
+ }, nil
+ }
+ return api.UpdateInitiative200JSONResponse(emptySuccess), nil
+}
+
+func (s *Server) updateInitiative(ctx context.Context, request api.UpdateInitiativeRequestObject) error {
+ // TODO(#12) Implement Authorization
+ id := pacta.InitiativeID(request.Id)
+ mutations := []db.UpdateInitiativeFn{}
+ b := request.Params.Body
+ if b.Affiliation != nil {
+ mutations = append(mutations, db.SetInitiativeAffiliation(*b.Affiliation))
+ }
+ if b.InternalDescription != nil {
+ mutations = append(mutations, db.SetInitiativeInternalDescription(*b.InternalDescription))
+ }
+ if b.IsAcceptingNewMembers != nil {
+ mutations = append(mutations, db.SetInitiativeIsAcceptingNewMembers(*b.IsAcceptingNewMembers))
+ }
+ if b.IsAcceptingNewPortfolios != nil {
+ mutations = append(mutations, db.SetInitiativeIsAcceptingNewPortfolios(*b.IsAcceptingNewPortfolios))
+ }
+ if b.Language != nil {
+ lang, err := pacta.ParseLanguage(string(*b.Language))
+ if err != nil {
+ return errorBadRequest("language", err.Error())
+ }
+ mutations = append(mutations, db.SetInitiativeLanguage(lang))
+ }
+ if b.Name != nil {
+ mutations = append(mutations, db.SetInitiativeName(*b.Name))
+ }
+ if b.PactaVersion != nil {
+ mutations = append(mutations, db.SetInitiativePACTAVersion(pacta.PACTAVersionID(*b.PactaVersion)))
+ }
+ if b.PublicDescription != nil {
+ mutations = append(mutations, db.SetInitiativePublicDescription(*b.PublicDescription))
+ }
+ if b.RequiresInvitationToJoin != nil {
+ mutations = append(mutations, db.SetInitiativeRequiresInvitationToJoin(*b.RequiresInvitationToJoin))
+ }
+ err := s.DB.UpdateInitiative(s.DB.NoTxn(ctx), id, mutations...)
+ if err != nil {
+ return errorInternal(err)
+ }
+ return nil
+}
+
+// Deletes an initiative by id
+// (DELETE /initiative/{id})
+func (s *Server) DeleteInitiative(ctx context.Context, request api.DeleteInitiativeRequestObject) (api.DeleteInitiativeResponseObject, error) {
+ err := s.deleteInitiative(ctx, request)
+ if err != nil {
+ e := errToAPIError(err)
+ return api.DeleteInitiativedefaultJSONResponse{
+ Body: e,
+ StatusCode: int(e.Code),
+ }, nil
+ }
+ return api.DeleteInitiative200JSONResponse(emptySuccess), nil
+}
+
+func (s *Server) deleteInitiative(ctx context.Context, request api.DeleteInitiativeRequestObject) error {
+ // TODO(#12) Implement Authorization
+ err := s.DB.DeleteInitiative(s.DB.NoTxn(ctx), pacta.InitiativeID(request.Id))
+ if err != nil {
+ return errorInternal(err)
+ }
+ return nil
+}
+
+// Returns an initiative by ID
+// (GET /initiative/{id})
+func (s *Server) FindInitiativeById(ctx context.Context, request api.FindInitiativeByIdRequestObject) (api.FindInitiativeByIdResponseObject, error) {
+ result, err := s.findInitiativeById(ctx, request)
+ if err != nil {
+ e := errToAPIError(err)
+ return api.FindInitiativeByIddefaultJSONResponse{
+ Body: e,
+ StatusCode: int(e.Code),
+ }, nil
+ }
+ return api.FindInitiativeById200JSONResponse(*result), nil
+}
+
+func (s *Server) findInitiativeById(ctx context.Context, request api.FindInitiativeByIdRequestObject) (*api.Initiative, error) {
+ // TODO(#12) Implement Authorization
+ i, err := s.DB.Initiative(s.DB.NoTxn(ctx), pacta.InitiativeID(request.Id))
+ if err != nil {
+ if db.IsNotFound(err) {
+ return nil, errorNotFound("initiative", request.Id)
+ }
+ return nil, errorInternal(err)
+ }
+ return initiativeToOAPI(i)
+}
+
+// Returns all initiatives
+// (GET /initiatives)
+func (s *Server) ListInitiatives(ctx context.Context, request api.ListInitiativesRequestObject) (api.ListInitiativesResponseObject, error) {
+ result, err := s.listInitiatives(ctx, request)
+ if err != nil {
+ e := errToAPIError(err)
+ return api.ListInitiativesdefaultJSONResponse{
+ Body: e,
+ StatusCode: int(e.Code),
+ }, nil
+ }
+ return api.ListInitiatives200JSONResponse(result), nil
+}
+
+func (s *Server) listInitiatives(ctx context.Context, request api.ListInitiativesRequestObject) ([]api.Initiative, error) {
+ is, err := s.DB.AllInitiatives(s.DB.NoTxn(ctx))
+ if err != nil {
+ return nil, errorInternal(err)
+ }
+ return dereference(mapAll(is, initiativeToOAPI))
+}
diff --git a/cmd/server/pactasrv/pacta_version.go b/cmd/server/pactasrv/pacta_version.go
index 48f2f64..3f0a464 100644
--- a/cmd/server/pactasrv/pacta_version.go
+++ b/cmd/server/pactasrv/pacta_version.go
@@ -2,48 +2,164 @@ package pactasrv
import (
"context"
- "fmt"
+ "github.com/RMI/pacta/db"
api "github.com/RMI/pacta/openapi/pacta"
"github.com/RMI/pacta/pacta"
- "go.uber.org/zap"
)
// Returns a version of the PACTA model by ID
// (GET /pacta-version/{id})
func (s *Server) FindPactaVersionById(ctx context.Context, request api.FindPactaVersionByIdRequestObject) (api.FindPactaVersionByIdResponseObject, error) {
- return nil, fmt.Errorf("not implemented")
+ pv, err := s.findPactaVersionById(ctx, request)
+ if err != nil {
+ e := errToAPIError(err)
+ return api.FindPactaVersionByIddefaultJSONResponse{
+ Body: e,
+ StatusCode: int(e.Code),
+ }, nil
+ }
+ return api.FindPactaVersionById200JSONResponse(*pv), nil
+}
+
+func (s *Server) findPactaVersionById(ctx context.Context, request api.FindPactaVersionByIdRequestObject) (*api.PactaVersion, error) {
+ // TODO(#12) Implement Authorization
+ pv, err := s.DB.PACTAVersion(s.DB.NoTxn(ctx), pacta.PACTAVersionID(request.Id))
+ if err != nil {
+ return nil, errorInternal(err)
+ }
+ return pactaVersionToOAPI(pv)
}
// Returns all versions of the PACTA model
// (GET /pacta-versions)
func (s *Server) ListPactaVersions(ctx context.Context, request api.ListPactaVersionsRequestObject) (api.ListPactaVersionsResponseObject, error) {
- return nil, fmt.Errorf("not implemented")
+ pvs, err := s.listPactaVersions(ctx, request)
+ if err != nil {
+ e := errToAPIError(err)
+ return api.ListPactaVersionsdefaultJSONResponse{
+ Body: e,
+ StatusCode: int(e.Code),
+ }, nil
+ }
+ return api.ListPactaVersions200JSONResponse(pvs), nil
+}
+
+func (s *Server) listPactaVersions(ctx context.Context, _request api.ListPactaVersionsRequestObject) ([]api.PactaVersion, error) {
+ // TODO(#12) Implement Authorization
+ pvs, err := s.DB.PACTAVersions(s.DB.NoTxn(ctx))
+ if err != nil {
+ return nil, errorInternal(err)
+ }
+ return dereference(mapAll(pvs, pactaVersionToOAPI))
}
// Creates a PACTA version
// (POST /pacta-versions)
func (s *Server) CreatePactaVersion(ctx context.Context, request api.CreatePactaVersionRequestObject) (api.CreatePactaVersionResponseObject, error) {
- // TODO(grady) Authz
- _, err := s.DB.CreatePACTAVersion(s.DB.NoTxn(ctx), &pacta.PACTAVersion{
- Name: request.Body.Name,
- Description: request.Body.Description,
- Digest: request.Body.Digest,
- })
+ err := s.createPactaVersion(ctx, request)
+ if err != nil {
+ e := errToAPIError(err)
+ return api.CreatePactaVersiondefaultJSONResponse{
+ Body: e,
+ StatusCode: int(e.Code),
+ }, nil
+ }
+ return api.CreatePactaVersion200JSONResponse(emptySuccess), nil
+}
+
+func (s *Server) createPactaVersion(ctx context.Context, request api.CreatePactaVersionRequestObject) error {
+ // TODO(#12) Implement Authorization
+ pv, err := pactaVersionCreateToPACTA(request.Body)
if err != nil {
- return nil, zap.Error(ctx, "failed to create PACTA version", zap.Error(err))
+ return errorBadRequest("body", err.Error())
}
- return nil, nil
+ if _, err := s.DB.CreatePACTAVersion(s.DB.NoTxn(ctx), pv); err != nil {
+ return errorInternal(err)
+ }
+ return nil
}
// Updates a PACTA version
// (PATCH /pacta-version/{id})
func (s *Server) UpdatePactaVersion(ctx context.Context, request api.UpdatePactaVersionRequestObject) (api.UpdatePactaVersionResponseObject, error) {
- return nil, fmt.Errorf("not implemented")
+ err := s.updatePactaVersion(ctx, request)
+ if err != nil {
+ e := errToAPIError(err)
+ return api.UpdatePactaVersiondefaultJSONResponse{
+ Body: e,
+ StatusCode: int(e.Code),
+ }, nil
+ }
+ return api.UpdatePactaVersion200JSONResponse(emptySuccess), nil
+}
+
+func (s *Server) updatePactaVersion(ctx context.Context, request api.UpdatePactaVersionRequestObject) error {
+ // TODO(#12) Implement Authorization
+ id := pacta.PACTAVersionID(request.Id)
+ mutations := []db.UpdatePACTAVersionFn{}
+ b := request.Body
+ if b.Description != nil {
+ mutations = append(mutations, db.SetPACTAVersionDescription(*b.Description))
+ }
+ if b.Digest != nil {
+ mutations = append(mutations, db.SetPACTAVersionDigest(*b.Digest))
+ }
+ if b.Name != nil {
+ mutations = append(mutations, db.SetPACTAVersionName(*b.Name))
+ }
+ err := s.DB.UpdatePACTAVersion(s.DB.NoTxn(ctx), id, mutations...)
+ if err != nil {
+ return errorInternal(err)
+ }
+ return nil
+
}
// Deletes a pacta version by ID
// (DELETE /pacta-version/{id})
func (s *Server) DeletePactaVersion(ctx context.Context, request api.DeletePactaVersionRequestObject) (api.DeletePactaVersionResponseObject, error) {
- return nil, fmt.Errorf("not implemented")
+ err := s.deletePactaVersion(ctx, request)
+ if err != nil {
+ e := errToAPIError(err)
+ return api.DeletePactaVersiondefaultJSONResponse{
+ Body: e,
+ StatusCode: int(e.Code),
+ }, nil
+ }
+ return api.DeletePactaVersion200JSONResponse(emptySuccess), nil
+}
+
+func (s *Server) deletePactaVersion(ctx context.Context, request api.DeletePactaVersionRequestObject) error {
+ // TODO(#12) Implement Authorization
+ id := pacta.PACTAVersionID(request.Id)
+ err := s.DB.DeletePACTAVersion(s.DB.NoTxn(ctx), id)
+ if err != nil {
+ return errorInternal(err)
+ }
+ return nil
+}
+
+// Marks this version of the PACTA model as the default
+// (POST /pacta-version/{id}/set-default)
+func (s *Server) MarkPactaVersionAsDefault(ctx context.Context, request api.MarkPactaVersionAsDefaultRequestObject) (api.MarkPactaVersionAsDefaultResponseObject, error) {
+ err := s.markPactaVersionAsDefault(ctx, request)
+ if err != nil {
+ e := errToAPIError(err)
+ return api.MarkPactaVersionAsDefaultdefaultJSONResponse{
+ Body: e,
+ StatusCode: int(e.Code),
+ }, nil
+ }
+ return api.MarkPactaVersionAsDefault200JSONResponse(emptySuccess), nil
+}
+
+func (s *Server) markPactaVersionAsDefault(ctx context.Context, request api.MarkPactaVersionAsDefaultRequestObject) error {
+ // TODO(#12) Implement Authorization
+ id := pacta.PACTAVersionID(request.Id)
+ err := s.DB.SetDefaultPACTAVersion(s.DB.NoTxn(ctx), id)
+ if err != nil {
+ return errorInternal(err)
+ }
+ return nil
}
diff --git a/cmd/server/pactasrv/pactasrv.go b/cmd/server/pactasrv/pactasrv.go
index 94b8092..f179e60 100644
--- a/cmd/server/pactasrv/pactasrv.go
+++ b/cmd/server/pactasrv/pactasrv.go
@@ -2,9 +2,10 @@ package pactasrv
import (
"context"
+ "fmt"
"github.com/RMI/pacta/db"
-
+ api "github.com/RMI/pacta/openapi/pacta"
"github.com/RMI/pacta/pacta"
)
@@ -62,3 +63,31 @@ type DB interface {
type Server struct {
DB DB
}
+
+var emptySuccess api.EmptySuccess = api.EmptySuccess{}
+
+func mapAll[I any, O any](is []I, f func(I) (O, error)) ([]O, error) {
+ os := make([]O, len(is))
+ for i, v := range is {
+ o, err := f(v)
+ if err != nil {
+ return nil, err
+ }
+ os[i] = o
+ }
+ return os, nil
+}
+
+func dereference[T any](ts []*T, e error) ([]T, error) {
+ if e != nil {
+ return nil, e
+ }
+ result := make([]T, len(ts))
+ for i, t := range ts {
+ if t == nil {
+ return nil, errorInternal(fmt.Errorf("dereference: nil pointer for %T at index %d", t, i))
+ }
+ result[i] = *t
+ }
+ return result, nil
+}
diff --git a/cmd/server/pactasrv/user.go b/cmd/server/pactasrv/user.go
index 74f8589..9d78bb8 100644
--- a/cmd/server/pactasrv/user.go
+++ b/cmd/server/pactasrv/user.go
@@ -2,7 +2,6 @@ package pactasrv
import (
"context"
- "fmt"
api "github.com/RMI/pacta/openapi/pacta"
)
@@ -10,17 +9,56 @@ import (
// Returns a user by ID
// (GET /user/{id})
func (s *Server) FindUserById(ctx context.Context, request api.FindUserByIdRequestObject) (api.FindUserByIdResponseObject, error) {
- return nil, fmt.Errorf("not implemented")
+ u, err := s.findUserById(ctx, request)
+ if err != nil {
+ e := errToAPIError(err)
+ return api.FindUserByIddefaultJSONResponse{
+ Body: e,
+ StatusCode: int(e.Code),
+ }, nil
+ }
+ return api.FindUserById200JSONResponse(*u), nil
+}
+
+func (s *Server) findUserById(ctx context.Context, request api.FindUserByIdRequestObject) (*api.User, error) {
+ // TODO(#12) Implement Authorization
+ return nil, errorNotImplemented("findUserById")
}
// Updates user properties
// (PATCH /user/{id})
func (s *Server) UpdateUser(ctx context.Context, request api.UpdateUserRequestObject) (api.UpdateUserResponseObject, error) {
- return nil, fmt.Errorf("not implemented")
+ err := s.updateUser(ctx, request)
+ if err != nil {
+ e := errToAPIError(err)
+ return api.UpdateUserdefaultJSONResponse{
+ Body: e,
+ StatusCode: int(e.Code),
+ }, nil
+ }
+ return api.UpdateUser200JSONResponse(emptySuccess), nil
+}
+
+func (s *Server) updateUser(ctx context.Context, request api.UpdateUserRequestObject) error {
+ // TODO(#12) Implement Authorization
+ return errorNotImplemented("updateUser")
}
// Deletes a user by ID
// (DELETE /user/{id})
func (s *Server) DeleteUser(ctx context.Context, request api.DeleteUserRequestObject) (api.DeleteUserResponseObject, error) {
- return nil, fmt.Errorf("not implemented")
+ err := s.deleteUser(ctx, request)
+ if err != nil {
+ e := errToAPIError(err)
+ return api.DeleteUserdefaultJSONResponse{
+ Body: e,
+ StatusCode: int(e.Code),
+ }, nil
+ }
+ return api.DeleteUser200JSONResponse(emptySuccess), nil
+}
+
+func (s *Server) deleteUser(ctx context.Context, request api.DeleteUserRequestObject) error {
+ // TODO(#12) Implement Authorization
+ return errorNotImplemented("deleteUser")
}
diff --git a/frontend/components/standard/Content.vue b/frontend/components/standard/Content.vue
index baa3327..60d9b53 100644
--- a/frontend/components/standard/Content.vue
+++ b/frontend/components/standard/Content.vue
@@ -28,7 +28,7 @@
color: rgb(0 0 0 / 85%);
}
- a {
+ a & :not(.p-button) {
font-weight: 600;
color: rgb(0 0 0 / 85%);
}
diff --git a/frontend/components/standard/Footer.vue b/frontend/components/standard/Footer.vue
index 8d7c8b0..9d21574 100644
--- a/frontend/components/standard/Footer.vue
+++ b/frontend/components/standard/Footer.vue
@@ -1,6 +1,18 @@
+
+
-
+
A project of Rocky Mountain Institute
-
© 2023 RMI
+
+
showStandardDebug = !showStandardDebug"
+ />
+ © 2023 RMI
+
diff --git a/frontend/composables/useURLParams.ts b/frontend/composables/useURLParams.ts
new file mode 100644
index 0000000..95692e6
--- /dev/null
+++ b/frontend/composables/useURLParams.ts
@@ -0,0 +1,56 @@
+import type { RouteParams, LocationQuery } from 'vue-router'
+import { useRoute, stringifyQuery } from 'vue-router'
+import { computed, type WritableComputedRef } from 'vue'
+
+export const useURLParams = () => {
+ const route = useRoute()
+ const router = useRouter()
+
+ const getVal = (src: RouteParams | LocationQuery, key: string): string | undefined => {
+ const val = src[key]
+ if (!val) {
+ return undefined
+ }
+
+ if (Array.isArray(val)) {
+ if (val.length === 0) {
+ return undefined
+ }
+ if (!val[0]) {
+ return undefined
+ }
+ return val[0]
+ }
+
+ return val
+ }
+
+ const setVal = (key: string, val: string | undefined) => {
+ const query = new URLSearchParams(stringifyQuery(router.currentRoute.value.query))
+ if (val) {
+ query.set(key, val)
+ } else {
+ query.delete(key)
+ }
+ let qs = query.toString()
+ if (qs) {
+ qs = '?' + qs
+ }
+ void router.replace(qs)
+ }
+
+ return {
+ fromQuery: (key: string): string | undefined => {
+ return getVal(route.query, key)
+ },
+ fromQueryReactive: (key: string): WritableComputedRef
=> {
+ return computed({
+ get: () => getVal(router.currentRoute.value.query, key),
+ set: (val: string | undefined) => { setVal(key, val) }
+ })
+ },
+ fromParams: (key: string): string | undefined => {
+ return getVal(route.params, key)
+ }
+ }
+}
diff --git a/frontend/layouts/default.vue b/frontend/layouts/default.vue
index e7843f2..bb29859 100644
--- a/frontend/layouts/default.vue
+++ b/frontend/layouts/default.vue
@@ -33,7 +33,7 @@ onMounted(() => {
{{ setError(error) }}
{{ clearError() }}
-
+
diff --git a/frontend/openapi/generated/pacta/index.ts b/frontend/openapi/generated/pacta/index.ts
index a8fbfd5..d98730b 100644
--- a/frontend/openapi/generated/pacta/index.ts
+++ b/frontend/openapi/generated/pacta/index.ts
@@ -12,6 +12,9 @@ export type { OpenAPIConfig } from './core/OpenAPI';
export type { EmptySuccess } from './models/EmptySuccess';
export type { Error } from './models/Error';
+export { Initiative } from './models/Initiative';
+export { InitiativeChanges } from './models/InitiativeChanges';
+export { InitiativeCreate } from './models/InitiativeCreate';
export { Language } from './models/Language';
export type { PactaVersion } from './models/PactaVersion';
export type { PactaVersionChanges } from './models/PactaVersionChanges';
diff --git a/frontend/openapi/generated/pacta/models/Initiative.ts b/frontend/openapi/generated/pacta/models/Initiative.ts
new file mode 100644
index 0000000..a04fcfc
--- /dev/null
+++ b/frontend/openapi/generated/pacta/models/Initiative.ts
@@ -0,0 +1,67 @@
+/* generated using openapi-typescript-codegen -- do no edit */
+/* istanbul ignore file */
+/* tslint:disable */
+/* eslint-disable */
+
+export type Initiative = {
+ /**
+ * the human readable identifier for the initiative, can only include alphanumeric characters, dashes and underscores
+ */
+ id: string;
+ /**
+ * the human meaningful name of the version of the initiative
+ */
+ name: string;
+ /**
+ * the group that sponsors/created/owns this initiative
+ */
+ affiliation: string;
+ /**
+ * Additional information about the initiative
+ */
+ publicDescription: string;
+ /**
+ * Additional information about the initiative, for participants only
+ */
+ internalDescription: string;
+ /**
+ * If set, only users who have been invited to join this initiative can join it, otherwise, anyone can join it. Defaults to false.
+ */
+ requiresInvitationToJoin: boolean;
+ /**
+ * If set, new users can join the initiative. Defaults to false.
+ */
+ isAcceptingNewMembers: boolean;
+ /**
+ * If set, users that are members of this initiative can add portfolios to it.
+ */
+ isAcceptingNewPortfolios: boolean;
+ /**
+ * The language this initiative should be conducted in.
+ */
+ language: Initiative.language;
+ /**
+ * The pacta model that this initiative should use, if not specified, the default pacta model will be used.
+ */
+ pactaVersion?: string;
+ /**
+ * The time at which this initiative was created.
+ */
+ createdAt: string;
+};
+
+export namespace Initiative {
+
+ /**
+ * The language this initiative should be conducted in.
+ */
+ export enum language {
+ EN = 'en',
+ FR = 'fr',
+ ES = 'es',
+ DE = 'de',
+ }
+
+
+}
+
diff --git a/frontend/openapi/generated/pacta/models/InitiativeChanges.ts b/frontend/openapi/generated/pacta/models/InitiativeChanges.ts
new file mode 100644
index 0000000..aeb4bd5
--- /dev/null
+++ b/frontend/openapi/generated/pacta/models/InitiativeChanges.ts
@@ -0,0 +1,59 @@
+/* generated using openapi-typescript-codegen -- do no edit */
+/* istanbul ignore file */
+/* tslint:disable */
+/* eslint-disable */
+
+export type InitiativeChanges = {
+ /**
+ * the human meaningful name of the version of the initiative
+ */
+ name?: string;
+ /**
+ * the group that sponsors/created/owns this initiative
+ */
+ affiliation?: string;
+ /**
+ * Additional information about the initiative
+ */
+ publicDescription?: string;
+ /**
+ * Additional information about the initiative, for participants only
+ */
+ internalDescription?: string;
+ /**
+ * If set, only users who have been invited to join this initiative can join it, otherwise, anyone can join it. Defaults to false.
+ */
+ requiresInvitationToJoin?: boolean;
+ /**
+ * If set, new users can join the initiative. Defaults to false.
+ */
+ isAcceptingNewMembers?: boolean;
+ /**
+ * If set, users that are members of this initiative can add portfolios to it.
+ */
+ isAcceptingNewPortfolios?: boolean;
+ /**
+ * The language this initiative should be conducted in.
+ */
+ language?: InitiativeChanges.language;
+ /**
+ * The pacta model that this initiative should use, if not specified, the default pacta model will be used.
+ */
+ pactaVersion?: string;
+};
+
+export namespace InitiativeChanges {
+
+ /**
+ * The language this initiative should be conducted in.
+ */
+ export enum language {
+ EN = 'en',
+ FR = 'fr',
+ ES = 'es',
+ DE = 'de',
+ }
+
+
+}
+
diff --git a/frontend/openapi/generated/pacta/models/InitiativeCreate.ts b/frontend/openapi/generated/pacta/models/InitiativeCreate.ts
new file mode 100644
index 0000000..2d1335f
--- /dev/null
+++ b/frontend/openapi/generated/pacta/models/InitiativeCreate.ts
@@ -0,0 +1,63 @@
+/* generated using openapi-typescript-codegen -- do no edit */
+/* istanbul ignore file */
+/* tslint:disable */
+/* eslint-disable */
+
+export type InitiativeCreate = {
+ /**
+ * the human readable identifier for the initiative, can only include alphanumeric characters, dashes and underscores
+ */
+ id: string;
+ /**
+ * the human meaningful name of the version of the initiative
+ */
+ name: string;
+ /**
+ * the group that sponsors/created/owns this initiative
+ */
+ affiliation?: string;
+ /**
+ * Additional information about the initiative
+ */
+ publicDescription?: string;
+ /**
+ * Additional information about the initiative, for participants only
+ */
+ internalDescription?: string;
+ /**
+ * If set, only users who have been invited to join this initiative can join it, otherwise, anyone can join it. Defaults to false.
+ */
+ requiresInvitationToJoin?: boolean;
+ /**
+ * If set, new users can join the initiative. Defaults to false.
+ */
+ isAcceptingNewMembers?: boolean;
+ /**
+ * If set, users that are members of this initiative can add portfolios to it.
+ */
+ isAcceptingNewPortfolios?: boolean;
+ /**
+ * The language this initiative should be conducted in.
+ */
+ language: InitiativeCreate.language;
+ /**
+ * The id of the PACTA model that this initiative should use, if not specified, the default PACTA model will be used.
+ */
+ pactaVersion?: string;
+};
+
+export namespace InitiativeCreate {
+
+ /**
+ * The language this initiative should be conducted in.
+ */
+ export enum language {
+ EN = 'en',
+ FR = 'fr',
+ ES = 'es',
+ DE = 'de',
+ }
+
+
+}
+
diff --git a/frontend/openapi/generated/pacta/models/PactaVersionChanges.ts b/frontend/openapi/generated/pacta/models/PactaVersionChanges.ts
index 2e04be2..43277ea 100644
--- a/frontend/openapi/generated/pacta/models/PactaVersionChanges.ts
+++ b/frontend/openapi/generated/pacta/models/PactaVersionChanges.ts
@@ -16,9 +16,5 @@ export type PactaVersionChanges = {
* The hash (typically SHA256) that uniquely identifies this version of the PACTA model.
*/
digest?: string;
- /**
- * Whether this version of the PACTA model is the default version
- */
- isDefault?: boolean;
};
diff --git a/frontend/openapi/generated/pacta/services/DefaultService.ts b/frontend/openapi/generated/pacta/services/DefaultService.ts
index d8e32d4..126c405 100644
--- a/frontend/openapi/generated/pacta/services/DefaultService.ts
+++ b/frontend/openapi/generated/pacta/services/DefaultService.ts
@@ -4,6 +4,9 @@
/* eslint-disable */
import type { EmptySuccess } from '../models/EmptySuccess';
import type { Error } from '../models/Error';
+import type { Initiative } from '../models/Initiative';
+import type { InitiativeChanges } from '../models/InitiativeChanges';
+import type { InitiativeCreate } from '../models/InitiativeCreate';
import type { PactaVersion } from '../models/PactaVersion';
import type { PactaVersionChanges } from '../models/PactaVersionChanges';
import type { PactaVersionCreate } from '../models/PactaVersionCreate';
@@ -40,14 +43,14 @@ export class DefaultService {
* Updates a PACTA version
* Updates a PACTA version's settable properties
* @param id ID of PACTA version to update
- * @param body PACTA Version object properties to update
+ * @param requestBody PACTA Version object properties to update
* @returns EmptySuccess pacta version updated successfully
* @returns Error unexpected error
* @throws ApiError
*/
public updatePactaVersion(
id: string,
- body: PactaVersionChanges,
+ requestBody: PactaVersionChanges,
): CancelablePromise {
return this.httpRequest.request({
method: 'PATCH',
@@ -55,12 +58,8 @@ export class DefaultService {
path: {
'id': id,
},
- query: {
- 'body': body,
- },
- errors: {
- 403: `caller does not have access or PACTA version does not exist`,
- },
+ body: requestBody,
+ mediaType: 'application/json',
});
}
@@ -81,8 +80,24 @@ export class DefaultService {
path: {
'id': id,
},
- errors: {
- 403: `caller does not have access or pacta version does not exist`,
+ });
+ }
+
+ /**
+ * Marks this version of the PACTA model as the default
+ * @param id ID of pacta version to fetch
+ * @returns EmptySuccess updated successfully
+ * @returns Error unexpected error
+ * @throws ApiError
+ */
+ public markPactaVersionAsDefault(
+ id: string,
+ ): CancelablePromise {
+ return this.httpRequest.request({
+ method: 'POST',
+ url: '/pacta-version/{id}/set-default',
+ path: {
+ 'id': id,
},
});
}
@@ -116,12 +131,105 @@ export class DefaultService {
url: '/pacta-versions',
body: requestBody,
mediaType: 'application/json',
- errors: {
- 403: `caller does not have access to create PACTA versions`,
+ });
+ }
+
+ /**
+ * Returns an initiative by ID
+ * @param id ID of the initiative to fetch
+ * @returns Initiative the initiative requested
+ * @returns Error unexpected error
+ * @throws ApiError
+ */
+ public findInitiativeById(
+ id: string,
+ ): CancelablePromise {
+ return this.httpRequest.request({
+ method: 'GET',
+ url: '/initiative/{id}',
+ path: {
+ 'id': id,
+ },
+ });
+ }
+
+ /**
+ * Updates an initiative
+ * Updates an initiative's settable properties
+ * @param id ID of the initiative to update
+ * @param body initiative object properties to update
+ * @returns EmptySuccess initiative updated successfully
+ * @returns Error unexpected error
+ * @throws ApiError
+ */
+ public updateInitiative(
+ id: string,
+ body: InitiativeChanges,
+ ): CancelablePromise {
+ return this.httpRequest.request({
+ method: 'PATCH',
+ url: '/initiative/{id}',
+ path: {
+ 'id': id,
+ },
+ query: {
+ 'body': body,
},
});
}
+ /**
+ * Deletes an initiative by id
+ * deletes an initiative based on the ID supplied
+ * @param id ID of initiative to delete
+ * @returns EmptySuccess initiative deleted successfully
+ * @returns Error unexpected error
+ * @throws ApiError
+ */
+ public deleteInitiative(
+ id: string,
+ ): CancelablePromise {
+ return this.httpRequest.request({
+ method: 'DELETE',
+ url: '/initiative/{id}',
+ path: {
+ 'id': id,
+ },
+ });
+ }
+
+ /**
+ * Returns all initiatives
+ * @returns Initiative gets all initiatives
+ * @returns Error unexpected error
+ * @throws ApiError
+ */
+ public listInitiatives(): CancelablePromise | Error> {
+ return this.httpRequest.request({
+ method: 'GET',
+ url: '/initiatives',
+ });
+ }
+
+ /**
+ * Creates a initiative
+ * Creates a new initiative
+ * @param requestBody Initiative object properties to update
+ * @returns EmptySuccess initiative created successfully
+ * @returns Error unexpected error
+ * @throws ApiError
+ */
+ public createInitiative(
+ requestBody: InitiativeCreate,
+ ): CancelablePromise {
+ return this.httpRequest.request({
+ method: 'POST',
+ url: '/initiatives',
+ body: requestBody,
+ mediaType: 'application/json',
+ });
+ }
+
/**
* Returns a user by ID
* Returns a user based on a single ID
@@ -139,9 +247,6 @@ export class DefaultService {
path: {
'id': id,
},
- errors: {
- 403: `caller does not have access or user does not exist`,
- },
});
}
@@ -150,14 +255,14 @@ export class DefaultService {
* Updates a user's settable properties
* @param id ID of user to update
* @param requestBody User object properties to update
- * @returns User the new user object
+ * @returns EmptySuccess the new user object
* @returns Error unexpected error
* @throws ApiError
*/
public updateUser(
id: string,
requestBody: UserChanges,
- ): CancelablePromise {
+ ): CancelablePromise {
return this.httpRequest.request({
method: 'PATCH',
url: '/user/{id}',
@@ -166,9 +271,6 @@ export class DefaultService {
},
body: requestBody,
mediaType: 'application/json',
- errors: {
- 403: `caller does not have access or user does not exist`,
- },
});
}
@@ -189,9 +291,6 @@ export class DefaultService {
path: {
'id': id,
},
- errors: {
- 403: `caller does not have access or user does not exist`,
- },
});
}
diff --git a/frontend/pages/admin/pacta-version/[id].vue b/frontend/pages/admin/pacta-version/[id].vue
new file mode 100644
index 0000000..4586865
--- /dev/null
+++ b/frontend/pages/admin/pacta-version/[id].vue
@@ -0,0 +1,110 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/frontend/pages/admin/pacta-version/index.vue b/frontend/pages/admin/pacta-version/index.vue
index d3686bd..af0749d 100644
--- a/frontend/pages/admin/pacta-version/index.vue
+++ b/frontend/pages/admin/pacta-version/index.vue
@@ -8,13 +8,29 @@ const { error: { withLoadingAndErrorHandling, handleOAPIError } } = useModal()
const prefix = 'admin/pacta-version'
const pactaVersions = useState(`${prefix}.pactaVersions`, () => [])
+const newPV = () => router.push('/admin/pacta-version/new')
+const markDefault = (id: string) => withLoadingAndErrorHandling(
+ () => pactaClient.markPactaVersionAsDefault(id)
+ .then(handleOAPIError)
+ .then(() => { pactaVersions.value = pactaVersions.value.map(pv => ({ ...pv, isDefault: id === pv.id })) }),
+ `${prefix}.markPactaVersionAsDefault`
+)
const deletePV = (id: string) => withLoadingAndErrorHandling(
() => pactaClient.deletePactaVersion(id)
.then(handleOAPIError)
.then(() => { pactaVersions.value = pactaVersions.value.filter(pv => pv.id !== id) }),
`${prefix}.deletePactaVersion`
)
-const newPV = () => router.push('/admin/pacta-version/new')
+
+// TODO(#13) Remove this from the on-mounted hook
+onMounted(async () => {
+ await withLoadingAndErrorHandling(
+ () => pactaClient.listPactaVersions()
+ .then(handleOAPIError)
+ .then(pvs => { pactaVersions.value = pvs }),
+ `${prefix}.getPactaVersions`
+ )
+})
@@ -25,6 +41,9 @@ const newPV = () => router.push('/admin/pacta-version/new')
router.push('/admin/pacta-version/new')
data-type="date"
sortable
/>
-
+
+
+
+
+
+
+
+ markDefault(slotProps.data.id)"
+ />
+
+
+
router.push('/admin/pacta-version/new')
icon="pi pi-plus"
@click="newPV"
/>
+
diff --git a/frontend/pages/admin/pacta-version/new.vue b/frontend/pages/admin/pacta-version/new.vue
index c0adc23..54bfbf3 100644
--- a/frontend/pages/admin/pacta-version/new.vue
+++ b/frontend/pages/admin/pacta-version/new.vue
@@ -17,7 +17,7 @@ const pactaVersion = useState(`${prefix}.pactaVersion`, () => ({
const discard = () => router.push('/admin/pacta-version')
const save = () => withLoadingAndErrorHandling(
- () => pactaClient.createPactaVersion(pactaVersion.value).then(handleOAPIError),
+ () => pactaClient.createPactaVersion(pactaVersion.value).then(handleOAPIError).then(() => router.push('/admin/pacta-version')),
`${prefix}.save`
)
diff --git a/openapi/pacta.yaml b/openapi/pacta.yaml
index 90ed8df..b354ee9 100644
--- a/openapi/pacta.yaml
+++ b/openapi/pacta.yaml
@@ -58,12 +58,13 @@ paths:
required: true
schema:
type: string
- - name: body
- in: query
- description: PACTA Version object properties to update
- required: true
- schema:
- $ref: '#/components/schemas/PactaVersionChanges'
+ requestBody:
+ description: PACTA Version object properties to update
+ required: true
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/PactaVersionChanges'
responses:
'200':
description: pacta version updated successfully
@@ -71,14 +72,6 @@ paths:
application/json:
schema:
$ref: '#/components/schemas/EmptySuccess'
-
- '403':
- description: caller does not have access or PACTA version does not exist
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/Error'
-
default:
description: unexpected error
content:
@@ -104,19 +97,38 @@ paths:
application/json:
schema:
$ref: '#/components/schemas/EmptySuccess'
- '403':
- description: caller does not have access or pacta version does not exist
+ default:
+ description: unexpected error
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+
+ /pacta-version/{id}/set-default:
+ post:
+ summary: Marks this version of the PACTA model as the default
+ operationId: markPactaVersionAsDefault
+ parameters:
+ - name: id
+ in: path
+ description: ID of pacta version to fetch
+ required: true
+ schema:
+ type: string
+ responses:
+ '200':
+ description: updated successfully
content:
application/json:
schema:
- $ref: '#/components/schemas/Error'
+ $ref: '#/components/schemas/EmptySuccess'
default:
description: unexpected error
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
-
+
/pacta-versions:
get:
summary: Returns all versions of the PACTA model
@@ -154,12 +166,130 @@ paths:
application/json:
schema:
$ref: '#/components/schemas/EmptySuccess'
- '403':
- description: caller does not have access to create PACTA versions
+ default:
+ description: unexpected error
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+
+ /initiative/{id}:
+ get:
+ summary: Returns an initiative by ID
+ operationId: findInitiativeById
+ parameters:
+ - name: id
+ in: path
+ description: ID of the initiative to fetch
+ required: true
+ schema:
+ type: string
+ responses:
+ '200':
+ description: the initiative requested
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Initiative'
+ default:
+ description: unexpected error
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ patch:
+ summary: Updates an initiative
+ description: Updates an initiative's settable properties
+ operationId: updateInitiative
+ parameters:
+ - name: id
+ in: path
+ description: ID of the initiative to update
+ required: true
+ schema:
+ type: string
+ - name: body
+ in: query
+ description: initiative object properties to update
+ required: true
+ schema:
+ $ref: '#/components/schemas/InitiativeChanges'
+ responses:
+ '200':
+ description: initiative updated successfully
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/EmptySuccess'
+ default:
+ description: unexpected error
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+
+ delete:
+ summary: Deletes an initiative by id
+ description: deletes an initiative based on the ID supplied
+ operationId: deleteInitiative
+ parameters:
+ - name: id
+ in: path
+ description: ID of initiative to delete
+ required: true
+ schema:
+ type: string
+ responses:
+ '200':
+ description: initiative deleted successfully
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/EmptySuccess'
+ default:
+ description: unexpected error
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
+
+ /initiatives:
+ get:
+ summary: Returns all initiatives
+ operationId: listInitiatives
+ responses:
+ '200':
+ description: gets all initiatives
+ content:
+ application/json:
+ schema:
+ type: array
+ items:
+ $ref: '#/components/schemas/Initiative'
+ default:
+ description: unexpected error
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/Error'
+ post:
+ summary: Creates a initiative
+ description: Creates a new initiative
+ operationId: createInitiative
+ requestBody:
+ description: Initiative object properties to update
+ required: true
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/InitiativeCreate'
+ responses:
+ '200':
+ description: initiative created successfully
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/EmptySuccess'
default:
description: unexpected error
content:
@@ -186,12 +316,6 @@ paths:
application/json:
schema:
$ref: '#/components/schemas/User'
- '403':
- description: caller does not have access or user does not exist
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/Error'
default:
description: unexpected error
content:
@@ -222,15 +346,7 @@ paths:
content:
application/json:
schema:
- $ref: '#/components/schemas/User'
-
- '403':
- description: caller does not have access or user does not exist
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/Error'
-
+ $ref: '#/components/schemas/EmptySuccess'
default:
description: unexpected error
content:
@@ -256,12 +372,6 @@ paths:
application/json:
schema:
$ref: '#/components/schemas/EmptySuccess'
- '403':
- description: caller does not have access or user does not exist
- content:
- application/json:
- schema:
- $ref: '#/components/schemas/Error'
default:
description: unexpected error
content:
@@ -334,9 +444,128 @@ components:
digest:
type: string
description: The hash (typically SHA256) that uniquely identifies this version of the PACTA model.
- isDefault:
+
+ InitiativeCreate:
+ type: object
+ required:
+ - id
+ - name
+ - language
+ properties:
+ id:
+ type: string
+ description: the human readable identifier for the initiative, can only include alphanumeric characters, dashes and underscores
+ name:
+ type: string
+ description: the human meaningful name of the version of the initiative
+ affiliation:
+ type: string
+ description: the group that sponsors/created/owns this initiative
+ publicDescription:
+ type: string
+ description: Additional information about the initiative
+ internalDescription:
+ type: string
+ description: Additional information about the initiative, for participants only
+ requiresInvitationToJoin:
type: boolean
- description: Whether this version of the PACTA model is the default version
+ description: If set, only users who have been invited to join this initiative can join it, otherwise, anyone can join it. Defaults to false.
+ isAcceptingNewMembers:
+ type: boolean
+ description: If set, new users can join the initiative. Defaults to false.
+ isAcceptingNewPortfolios:
+ type: boolean
+ description: If set, users that are members of this initiative can add portfolios to it.
+ language:
+ description: The language this initiative should be conducted in.
+ type: string
+ enum: *LANGUAGES
+ pactaVersion:
+ type: string
+ description: The id of the PACTA model that this initiative should use, if not specified, the default PACTA model will be used.
+
+ Initiative:
+ type: object
+ required:
+ - id
+ - name
+ - affiliation
+ - publicDescription
+ - internalDescription
+ - requiresInvitationToJoin
+ - isAcceptingNewMembers
+ - isAcceptingNewPortfolios
+ - pactaVersionId
+ - language
+ - createdAt
+ properties:
+ id:
+ type: string
+ description: the human readable identifier for the initiative, can only include alphanumeric characters, dashes and underscores
+ name:
+ type: string
+ description: the human meaningful name of the version of the initiative
+ affiliation:
+ type: string
+ description: the group that sponsors/created/owns this initiative
+ publicDescription:
+ type: string
+ description: Additional information about the initiative
+ internalDescription:
+ type: string
+ description: Additional information about the initiative, for participants only
+ requiresInvitationToJoin:
+ type: boolean
+ description: If set, only users who have been invited to join this initiative can join it, otherwise, anyone can join it. Defaults to false.
+ isAcceptingNewMembers:
+ type: boolean
+ description: If set, new users can join the initiative. Defaults to false.
+ isAcceptingNewPortfolios:
+ type: boolean
+ description: If set, users that are members of this initiative can add portfolios to it.
+ language:
+ description: The language this initiative should be conducted in.
+ type: string
+ enum: *LANGUAGES
+ pactaVersion:
+ type: string
+ description: The pacta model that this initiative should use, if not specified, the default pacta model will be used.
+ createdAt:
+ type: string
+ format: date-time
+ description: The time at which this initiative was created.
+
+ InitiativeChanges:
+ type: object
+ properties:
+ name:
+ type: string
+ description: the human meaningful name of the version of the initiative
+ affiliation:
+ type: string
+ description: the group that sponsors/created/owns this initiative
+ publicDescription:
+ type: string
+ description: Additional information about the initiative
+ internalDescription:
+ type: string
+ description: Additional information about the initiative, for participants only
+ requiresInvitationToJoin:
+ type: boolean
+ description: If set, only users who have been invited to join this initiative can join it, otherwise, anyone can join it. Defaults to false.
+ isAcceptingNewMembers:
+ type: boolean
+ description: If set, new users can join the initiative. Defaults to false.
+ isAcceptingNewPortfolios:
+ type: boolean
+ description: If set, users that are members of this initiative can add portfolios to it.
+ language:
+ description: The language this initiative should be conducted in.
+ type: string
+ enum: *LANGUAGES
+ pactaVersion:
+ type: string
+ description: The pacta model that this initiative should use, if not specified, the default pacta model will be used.
User:
type: object