diff --git a/coderd/apidoc/docs.go b/coderd/apidoc/docs.go index 60b7bbfd0f06d..48b9fe284844c 100644 --- a/coderd/apidoc/docs.go +++ b/coderd/apidoc/docs.go @@ -2037,6 +2037,32 @@ const docTemplate = `{ } }, "/organizations": { + "get": { + "security": [ + { + "CoderSessionToken": [] + } + ], + "produces": [ + "application/json" + ], + "tags": [ + "Organizations" + ], + "summary": "Get organizations", + "operationId": "get-organizations", + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/codersdk.Organization" + } + } + } + } + }, "post": { "security": [ { diff --git a/coderd/apidoc/swagger.json b/coderd/apidoc/swagger.json index 43db2a118291a..f75fc96989955 100644 --- a/coderd/apidoc/swagger.json +++ b/coderd/apidoc/swagger.json @@ -1779,6 +1779,28 @@ } }, "/organizations": { + "get": { + "security": [ + { + "CoderSessionToken": [] + } + ], + "produces": ["application/json"], + "tags": ["Organizations"], + "summary": "Get organizations", + "operationId": "get-organizations", + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/codersdk.Organization" + } + } + } + } + }, "post": { "security": [ { diff --git a/coderd/coderd.go b/coderd/coderd.go index 0a3414fdb984c..26f2c66bb43d3 100644 --- a/coderd/coderd.go +++ b/coderd/coderd.go @@ -865,6 +865,7 @@ func New(options *Options) *API { apiKeyMiddleware, ) r.Post("/", api.postOrganizations) + r.Get("/", api.organizations) r.Route("/{organization}", func(r chi.Router) { r.Use( httpmw.ExtractOrganizationParam(options.Database), diff --git a/coderd/organizations.go b/coderd/organizations.go index 24d55fa950c65..83492b6cdb5bc 100644 --- a/coderd/organizations.go +++ b/coderd/organizations.go @@ -11,12 +11,38 @@ import ( "github.com/coder/coder/v2/coderd/audit" "github.com/coder/coder/v2/coderd/database" + "github.com/coder/coder/v2/coderd/database/db2sdk" "github.com/coder/coder/v2/coderd/database/dbtime" "github.com/coder/coder/v2/coderd/httpapi" "github.com/coder/coder/v2/coderd/httpmw" "github.com/coder/coder/v2/codersdk" ) +// @Summary Get organizations +// @ID get-organizations +// @Security CoderSessionToken +// @Produce json +// @Tags Organizations +// @Success 200 {object} []codersdk.Organization +// @Router /organizations [get] +func (api *API) organizations(rw http.ResponseWriter, r *http.Request) { + ctx := r.Context() + organizations, err := api.Database.GetOrganizations(ctx) + if httpapi.Is404Error(err) { + httpapi.ResourceNotFound(rw) + return + } + if err != nil { + httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ + Message: "Internal error fetching organizations.", + Detail: err.Error(), + }) + return + } + + httpapi.Write(ctx, rw, http.StatusOK, db2sdk.List(organizations, convertOrganization)) +} + // @Summary Get organization by ID // @ID get-organization-by-id // @Security CoderSessionToken diff --git a/coderd/organizations_test.go b/coderd/organizations_test.go index 347048ed67a5c..47c8415feef8f 100644 --- a/coderd/organizations_test.go +++ b/coderd/organizations_test.go @@ -27,10 +27,15 @@ func TestMultiOrgFetch(t *testing.T) { require.NoError(t, err) } - orgs, err := client.OrganizationsByUser(ctx, codersdk.Me) + myOrgs, err := client.OrganizationsByUser(ctx, codersdk.Me) + require.NoError(t, err) + require.NotNil(t, myOrgs) + require.Len(t, myOrgs, len(makeOrgs)+1) + + orgs, err := client.Organizations(ctx) require.NoError(t, err) require.NotNil(t, orgs) - require.Len(t, orgs, len(makeOrgs)+1) + require.ElementsMatch(t, myOrgs, orgs) } func TestOrganizationsByUser(t *testing.T) { diff --git a/codersdk/organizations.go b/codersdk/organizations.go index 041087b26709a..758db099f95c9 100644 --- a/codersdk/organizations.go +++ b/codersdk/organizations.go @@ -215,6 +215,21 @@ func (c *Client) OrganizationByName(ctx context.Context, name string) (Organizat return organization, json.NewDecoder(res.Body).Decode(&organization) } +func (c *Client) Organizations(ctx context.Context) ([]Organization, error) { + res, err := c.Request(ctx, http.MethodGet, "/api/v2/organizations", nil) + if err != nil { + return []Organization{}, xerrors.Errorf("execute request: %w", err) + } + defer res.Body.Close() + + if res.StatusCode != http.StatusOK { + return []Organization{}, ReadBodyAsError(res) + } + + var organizations []Organization + return organizations, json.NewDecoder(res.Body).Decode(&organizations) +} + func (c *Client) Organization(ctx context.Context, id uuid.UUID) (Organization, error) { // OrganizationByName uses the exact same endpoint. It accepts a name or uuid. // We just provide this function for type safety. diff --git a/docs/api/organizations.md b/docs/api/organizations.md index a1f8273549f80..4c4f49bb9d9d6 100644 --- a/docs/api/organizations.md +++ b/docs/api/organizations.md @@ -87,6 +87,62 @@ curl -X POST http://coder-server:8080/api/v2/licenses/refresh-entitlements \ To perform this operation, you must be authenticated. [Learn more](authentication.md). +## Get organizations + +### Code samples + +```shell +# Example request using curl +curl -X GET http://coder-server:8080/api/v2/organizations \ + -H 'Accept: application/json' \ + -H 'Coder-Session-Token: API_KEY' +``` + +`GET /organizations` + +### Example responses + +> 200 Response + +```json +[ + { + "created_at": "2019-08-24T14:15:22Z", + "description": "string", + "display_name": "string", + "icon": "string", + "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", + "is_default": true, + "name": "string", + "updated_at": "2019-08-24T14:15:22Z" + } +] +``` + +### Responses + +| Status | Meaning | Description | Schema | +| ------ | ------------------------------------------------------- | ----------- | ----------------------------------------------------------------- | +| 200 | [OK](https://tools.ietf.org/html/rfc7231#section-6.3.1) | OK | array of [codersdk.Organization](schemas.md#codersdkorganization) | + +