Skip to content

Commit 9f1671c

Browse files
committed
fixup! chore: move multi-org endpoints into enterprise directory
1 parent 3fe0fc2 commit 9f1671c

File tree

6 files changed

+357
-305
lines changed

6 files changed

+357
-305
lines changed

coderd/database/db2sdk/db2sdk.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -586,3 +586,18 @@ func RBACPermission(permission rbac.Permission) codersdk.Permission {
586586
Action: codersdk.RBACAction(permission.Action),
587587
}
588588
}
589+
590+
func Organization(organization database.Organization) codersdk.Organization {
591+
return codersdk.Organization{
592+
MinimalOrganization: codersdk.MinimalOrganization{
593+
ID: organization.ID,
594+
Name: organization.Name,
595+
DisplayName: organization.DisplayName,
596+
Icon: organization.Icon,
597+
},
598+
Description: organization.Description,
599+
CreatedAt: organization.CreatedAt,
600+
UpdatedAt: organization.UpdatedAt,
601+
IsDefault: organization.IsDefault,
602+
}
603+
}

coderd/organizations.go

Lines changed: 2 additions & 281 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,9 @@
11
package coderd
22

33
import (
4-
"database/sql"
5-
"errors"
6-
"fmt"
74
"net/http"
85

9-
"github.com/google/uuid"
10-
"golang.org/x/xerrors"
11-
12-
"github.com/coder/coder/v2/coderd/audit"
13-
"github.com/coder/coder/v2/coderd/database"
146
"github.com/coder/coder/v2/coderd/database/db2sdk"
15-
"github.com/coder/coder/v2/coderd/database/dbtime"
167
"github.com/coder/coder/v2/coderd/httpapi"
178
"github.com/coder/coder/v2/coderd/httpmw"
189
"github.com/coder/coder/v2/codersdk"
@@ -40,7 +31,7 @@ func (api *API) organizations(rw http.ResponseWriter, r *http.Request) {
4031
return
4132
}
4233

43-
httpapi.Write(ctx, rw, http.StatusOK, db2sdk.List(organizations, convertOrganization))
34+
httpapi.Write(ctx, rw, http.StatusOK, db2sdk.List(organizations, db2sdk.Organization))
4435
}
4536

4637
// @Summary Get organization by ID
@@ -55,275 +46,5 @@ func (*API) organization(rw http.ResponseWriter, r *http.Request) {
5546
ctx := r.Context()
5647
organization := httpmw.OrganizationParam(r)
5748

58-
httpapi.Write(ctx, rw, http.StatusOK, convertOrganization(organization))
59-
}
60-
61-
// @Summary Create organization
62-
// @ID create-organization
63-
// @Security CoderSessionToken
64-
// @Accept json
65-
// @Produce json
66-
// @Tags Organizations
67-
// @Param request body codersdk.CreateOrganizationRequest true "Create organization request"
68-
// @Success 201 {object} codersdk.Organization
69-
// @Router /organizations [post]
70-
func (api *API) postOrganizations(rw http.ResponseWriter, r *http.Request) {
71-
var (
72-
// organizationID is required before the audit log entry is created.
73-
organizationID = uuid.New()
74-
ctx = r.Context()
75-
apiKey = httpmw.APIKey(r)
76-
auditor = api.Auditor.Load()
77-
aReq, commitAudit = audit.InitRequest[database.Organization](rw, &audit.RequestParams{
78-
Audit: *auditor,
79-
Log: api.Logger,
80-
Request: r,
81-
Action: database.AuditActionCreate,
82-
OrganizationID: organizationID,
83-
})
84-
)
85-
aReq.Old = database.Organization{}
86-
defer commitAudit()
87-
88-
var req codersdk.CreateOrganizationRequest
89-
if !httpapi.Read(ctx, rw, r, &req) {
90-
return
91-
}
92-
93-
if req.Name == codersdk.DefaultOrganization {
94-
httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{
95-
Message: fmt.Sprintf("Organization name %q is reserved.", codersdk.DefaultOrganization),
96-
})
97-
return
98-
}
99-
100-
_, err := api.Database.GetOrganizationByName(ctx, req.Name)
101-
if err == nil {
102-
httpapi.Write(ctx, rw, http.StatusConflict, codersdk.Response{
103-
Message: "Organization already exists with that name.",
104-
})
105-
return
106-
}
107-
if !errors.Is(err, sql.ErrNoRows) {
108-
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
109-
Message: fmt.Sprintf("Internal error fetching organization %q.", req.Name),
110-
Detail: err.Error(),
111-
})
112-
return
113-
}
114-
115-
var organization database.Organization
116-
err = api.Database.InTx(func(tx database.Store) error {
117-
if req.DisplayName == "" {
118-
req.DisplayName = req.Name
119-
}
120-
121-
organization, err = tx.InsertOrganization(ctx, database.InsertOrganizationParams{
122-
ID: organizationID,
123-
Name: req.Name,
124-
DisplayName: req.DisplayName,
125-
Description: req.Description,
126-
Icon: req.Icon,
127-
CreatedAt: dbtime.Now(),
128-
UpdatedAt: dbtime.Now(),
129-
})
130-
if err != nil {
131-
return xerrors.Errorf("create organization: %w", err)
132-
}
133-
_, err = tx.InsertOrganizationMember(ctx, database.InsertOrganizationMemberParams{
134-
OrganizationID: organization.ID,
135-
UserID: apiKey.UserID,
136-
CreatedAt: dbtime.Now(),
137-
UpdatedAt: dbtime.Now(),
138-
Roles: []string{
139-
// TODO: When organizations are allowed to be created, we should
140-
// come back to determining the default role of the person who
141-
// creates the org. Until that happens, all users in an organization
142-
// should be just regular members.
143-
},
144-
})
145-
if err != nil {
146-
return xerrors.Errorf("create organization admin: %w", err)
147-
}
148-
149-
_, err = tx.InsertAllUsersGroup(ctx, organization.ID)
150-
if err != nil {
151-
return xerrors.Errorf("create %q group: %w", database.EveryoneGroup, err)
152-
}
153-
return nil
154-
}, nil)
155-
if err != nil {
156-
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
157-
Message: "Internal error inserting organization member.",
158-
Detail: err.Error(),
159-
})
160-
return
161-
}
162-
163-
aReq.New = organization
164-
httpapi.Write(ctx, rw, http.StatusCreated, convertOrganization(organization))
165-
}
166-
167-
// @Summary Update organization
168-
// @ID update-organization
169-
// @Security CoderSessionToken
170-
// @Accept json
171-
// @Produce json
172-
// @Tags Organizations
173-
// @Param organization path string true "Organization ID or name"
174-
// @Param request body codersdk.UpdateOrganizationRequest true "Patch organization request"
175-
// @Success 200 {object} codersdk.Organization
176-
// @Router /organizations/{organization} [patch]
177-
func (api *API) patchOrganization(rw http.ResponseWriter, r *http.Request) {
178-
var (
179-
ctx = r.Context()
180-
organization = httpmw.OrganizationParam(r)
181-
auditor = api.Auditor.Load()
182-
aReq, commitAudit = audit.InitRequest[database.Organization](rw, &audit.RequestParams{
183-
Audit: *auditor,
184-
Log: api.Logger,
185-
Request: r,
186-
Action: database.AuditActionWrite,
187-
OrganizationID: organization.ID,
188-
})
189-
)
190-
aReq.Old = organization
191-
defer commitAudit()
192-
193-
var req codersdk.UpdateOrganizationRequest
194-
if !httpapi.Read(ctx, rw, r, &req) {
195-
return
196-
}
197-
198-
// "default" is a reserved name that always refers to the default org (much like the way we
199-
// use "me" for users).
200-
if req.Name == codersdk.DefaultOrganization {
201-
httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{
202-
Message: fmt.Sprintf("Organization name %q is reserved.", codersdk.DefaultOrganization),
203-
})
204-
return
205-
}
206-
207-
err := database.ReadModifyUpdate(api.Database, func(tx database.Store) error {
208-
var err error
209-
organization, err = tx.GetOrganizationByID(ctx, organization.ID)
210-
if err != nil {
211-
return err
212-
}
213-
214-
updateOrgParams := database.UpdateOrganizationParams{
215-
UpdatedAt: dbtime.Now(),
216-
ID: organization.ID,
217-
Name: organization.Name,
218-
DisplayName: organization.DisplayName,
219-
Description: organization.Description,
220-
Icon: organization.Icon,
221-
}
222-
223-
if req.Name != "" {
224-
updateOrgParams.Name = req.Name
225-
}
226-
if req.DisplayName != "" {
227-
updateOrgParams.DisplayName = req.DisplayName
228-
}
229-
if req.Description != nil {
230-
updateOrgParams.Description = *req.Description
231-
}
232-
if req.Icon != nil {
233-
updateOrgParams.Icon = *req.Icon
234-
}
235-
236-
organization, err = tx.UpdateOrganization(ctx, updateOrgParams)
237-
if err != nil {
238-
return err
239-
}
240-
return nil
241-
})
242-
243-
if httpapi.Is404Error(err) {
244-
httpapi.ResourceNotFound(rw)
245-
return
246-
}
247-
if database.IsUniqueViolation(err) {
248-
httpapi.Write(ctx, rw, http.StatusConflict, codersdk.Response{
249-
Message: fmt.Sprintf("Organization already exists with the name %q.", req.Name),
250-
Validations: []codersdk.ValidationError{{
251-
Field: "name",
252-
Detail: "This value is already in use and should be unique.",
253-
}},
254-
})
255-
return
256-
}
257-
if err != nil {
258-
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
259-
Message: "Internal error updating organization.",
260-
Detail: fmt.Sprintf("update organization: %s", err.Error()),
261-
})
262-
return
263-
}
264-
265-
aReq.New = organization
266-
httpapi.Write(ctx, rw, http.StatusOK, convertOrganization(organization))
267-
}
268-
269-
// @Summary Delete organization
270-
// @ID delete-organization
271-
// @Security CoderSessionToken
272-
// @Produce json
273-
// @Tags Organizations
274-
// @Param organization path string true "Organization ID or name"
275-
// @Success 200 {object} codersdk.Response
276-
// @Router /organizations/{organization} [delete]
277-
func (api *API) deleteOrganization(rw http.ResponseWriter, r *http.Request) {
278-
var (
279-
ctx = r.Context()
280-
organization = httpmw.OrganizationParam(r)
281-
auditor = api.Auditor.Load()
282-
aReq, commitAudit = audit.InitRequest[database.Organization](rw, &audit.RequestParams{
283-
Audit: *auditor,
284-
Log: api.Logger,
285-
Request: r,
286-
Action: database.AuditActionDelete,
287-
OrganizationID: organization.ID,
288-
})
289-
)
290-
aReq.Old = organization
291-
defer commitAudit()
292-
293-
if organization.IsDefault {
294-
httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{
295-
Message: "Default organization cannot be deleted.",
296-
})
297-
return
298-
}
299-
300-
err := api.Database.DeleteOrganization(ctx, organization.ID)
301-
if err != nil {
302-
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
303-
Message: "Internal error deleting organization.",
304-
Detail: fmt.Sprintf("delete organization: %s", err.Error()),
305-
})
306-
return
307-
}
308-
309-
aReq.New = database.Organization{}
310-
httpapi.Write(ctx, rw, http.StatusOK, codersdk.Response{
311-
Message: "Organization has been deleted.",
312-
})
313-
}
314-
315-
// convertOrganization consumes the database representation and outputs an API friendly representation.
316-
func convertOrganization(organization database.Organization) codersdk.Organization {
317-
return codersdk.Organization{
318-
MinimalOrganization: codersdk.MinimalOrganization{
319-
ID: organization.ID,
320-
Name: organization.Name,
321-
DisplayName: organization.DisplayName,
322-
Icon: organization.Icon,
323-
},
324-
Description: organization.Description,
325-
CreatedAt: organization.CreatedAt,
326-
UpdatedAt: organization.UpdatedAt,
327-
IsDefault: organization.IsDefault,
328-
}
49+
httpapi.Write(ctx, rw, http.StatusOK, db2sdk.Organization(organization))
32950
}

coderd/organizations_test.go

Lines changed: 0 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -12,31 +12,7 @@ import (
1212
"github.com/coder/coder/v2/testutil"
1313
)
1414

15-
func TestMultiOrgFetch(t *testing.T) {
16-
t.Parallel()
17-
client := coderdtest.New(t, nil)
18-
_ = coderdtest.CreateFirstUser(t, client)
19-
ctx := testutil.Context(t, testutil.WaitLong)
2015

21-
makeOrgs := []string{"foo", "bar", "baz"}
22-
for _, name := range makeOrgs {
23-
_, err := client.CreateOrganization(ctx, codersdk.CreateOrganizationRequest{
24-
Name: name,
25-
DisplayName: name,
26-
})
27-
require.NoError(t, err)
28-
}
29-
30-
myOrgs, err := client.OrganizationsByUser(ctx, codersdk.Me)
31-
require.NoError(t, err)
32-
require.NotNil(t, myOrgs)
33-
require.Len(t, myOrgs, len(makeOrgs)+1)
34-
35-
orgs, err := client.Organizations(ctx)
36-
require.NoError(t, err)
37-
require.NotNil(t, orgs)
38-
require.ElementsMatch(t, myOrgs, orgs)
39-
}
4016

4117
func TestOrganizationsByUser(t *testing.T) {
4218
t.Parallel()

enterprise/coderd/coderd.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,24 @@ func New(ctx context.Context, options *Options) (_ *API, err error) {
183183
return api.refreshEntitlements(ctx)
184184
}
185185

186+
// /organizations route
187+
api.AGPL.APISubRoutes.Organizations.Group(func(r chi.Router) {
188+
r.Use(
189+
api.RequireFeatureMW(codersdk.FeatureMultipleOrganizations),
190+
httpmw.RequireExperiment(api.AGPL.Experiments, codersdk.ExperimentMultiOrganization),
191+
)
192+
r.Post("/", api.postOrganizations)
193+
})
194+
195+
api.AGPL.APISubRoutes.SingleOrganization.Group(func(r chi.Router) {
196+
r.Use(
197+
api.RequireFeatureMW(codersdk.FeatureMultipleOrganizations),
198+
httpmw.RequireExperiment(api.AGPL.Experiments, codersdk.ExperimentMultiOrganization),
199+
)
200+
r.Patch("/", api.patchOrganization)
201+
r.Delete("/", api.deleteOrganization)
202+
})
203+
186204
api.AGPL.APIHandler.Group(func(r chi.Router) {
187205
r.Get("/entitlements", api.serveEntitlements)
188206
// /regions overrides the AGPL /regions endpoint
@@ -240,6 +258,7 @@ func New(ctx context.Context, options *Options) (_ *API, err error) {
240258
r.Delete("/", api.deleteWorkspaceProxy)
241259
})
242260
})
261+
243262
r.Route("/organizations/{organization}/groups", func(r chi.Router) {
244263
r.Use(
245264
apiKeyMiddleware,

0 commit comments

Comments
 (0)