Skip to content

Commit ba3ce57

Browse files
committed
add workspace and project history parameters
1 parent 1f4dfc7 commit ba3ce57

File tree

4 files changed

+426
-0
lines changed

4 files changed

+426
-0
lines changed

httpmw/projecthistoryparam.go

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
package httpmw
2+
3+
import (
4+
"context"
5+
"database/sql"
6+
"errors"
7+
"fmt"
8+
"net/http"
9+
10+
"github.com/go-chi/chi"
11+
12+
"github.com/coder/coder/database"
13+
"github.com/coder/coder/httpapi"
14+
)
15+
16+
type projectHistoryParamContextKey struct{}
17+
18+
// ProjectHistoryParam returns the project history from the ExtractProjectHistoryParam handler.
19+
func ProjectHistoryParam(r *http.Request) database.ProjectHistory {
20+
projectHistory, ok := r.Context().Value(projectHistoryParamContextKey{}).(database.ProjectHistory)
21+
if !ok {
22+
panic("developer error: project history param middleware not provided")
23+
}
24+
return projectHistory
25+
}
26+
27+
// ExtractProjectHistoryParam grabs project history from the "projecthistory" URL parameter.
28+
func ExtractProjectHistoryParam(db database.Store) func(http.Handler) http.Handler {
29+
return func(next http.Handler) http.Handler {
30+
return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
31+
project := ProjectParam(r)
32+
projectHistoryName := chi.URLParam(r, "projecthistory")
33+
if projectHistoryName == "" {
34+
httpapi.Write(rw, http.StatusBadRequest, httpapi.Response{
35+
Message: "project history name must be provided",
36+
})
37+
return
38+
}
39+
projectHistory, err := db.GetProjectHistoryByProjectIDAndName(r.Context(), database.GetProjectHistoryByProjectIDAndNameParams{
40+
ProjectID: project.ID,
41+
Name: projectHistoryName,
42+
})
43+
if errors.Is(err, sql.ErrNoRows) {
44+
httpapi.Write(rw, http.StatusNotFound, httpapi.Response{
45+
Message: fmt.Sprintf("project history %q does not exist", projectHistoryName),
46+
})
47+
return
48+
}
49+
if err != nil {
50+
httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{
51+
Message: fmt.Sprintf("get project history: %s", err.Error()),
52+
})
53+
return
54+
}
55+
56+
ctx := context.WithValue(r.Context(), projectHistoryParamContextKey{}, projectHistory)
57+
next.ServeHTTP(rw, r.WithContext(ctx))
58+
})
59+
}
60+
}

httpmw/projecthistoryparam_test.go

Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
package httpmw_test
2+
3+
import (
4+
"context"
5+
"crypto/sha256"
6+
"fmt"
7+
"net/http"
8+
"net/http/httptest"
9+
"testing"
10+
"time"
11+
12+
"github.com/go-chi/chi"
13+
"github.com/google/uuid"
14+
"github.com/stretchr/testify/require"
15+
16+
"github.com/coder/coder/cryptorand"
17+
"github.com/coder/coder/database"
18+
"github.com/coder/coder/database/databasefake"
19+
"github.com/coder/coder/httpmw"
20+
)
21+
22+
func TestProjectHistoryParam(t *testing.T) {
23+
t.Parallel()
24+
25+
setupAuthentication := func(db database.Store) (*http.Request, database.Project) {
26+
var (
27+
id, secret = randomAPIKeyParts()
28+
hashed = sha256.Sum256([]byte(secret))
29+
)
30+
r := httptest.NewRequest("GET", "/", nil)
31+
r.AddCookie(&http.Cookie{
32+
Name: httpmw.AuthCookie,
33+
Value: fmt.Sprintf("%s-%s", id, secret),
34+
})
35+
userID, err := cryptorand.String(16)
36+
require.NoError(t, err)
37+
username, err := cryptorand.String(8)
38+
require.NoError(t, err)
39+
user, err := db.InsertUser(r.Context(), database.InsertUserParams{
40+
ID: userID,
41+
Email: "testaccount@coder.com",
42+
Name: "example",
43+
LoginType: database.LoginTypeBuiltIn,
44+
HashedPassword: hashed[:],
45+
Username: username,
46+
CreatedAt: database.Now(),
47+
UpdatedAt: database.Now(),
48+
})
49+
require.NoError(t, err)
50+
_, err = db.InsertAPIKey(r.Context(), database.InsertAPIKeyParams{
51+
ID: id,
52+
UserID: user.ID,
53+
HashedSecret: hashed[:],
54+
LastUsed: database.Now(),
55+
ExpiresAt: database.Now().Add(time.Minute),
56+
})
57+
require.NoError(t, err)
58+
orgID, err := cryptorand.String(16)
59+
require.NoError(t, err)
60+
organization, err := db.InsertOrganization(r.Context(), database.InsertOrganizationParams{
61+
ID: orgID,
62+
Name: "banana",
63+
Description: "wowie",
64+
CreatedAt: database.Now(),
65+
UpdatedAt: database.Now(),
66+
})
67+
require.NoError(t, err)
68+
_, err = db.InsertOrganizationMember(r.Context(), database.InsertOrganizationMemberParams{
69+
OrganizationID: orgID,
70+
UserID: user.ID,
71+
CreatedAt: database.Now(),
72+
UpdatedAt: database.Now(),
73+
})
74+
require.NoError(t, err)
75+
project, err := db.InsertProject(context.Background(), database.InsertProjectParams{
76+
ID: uuid.New(),
77+
OrganizationID: organization.ID,
78+
Name: "moo",
79+
})
80+
require.NoError(t, err)
81+
82+
ctx := chi.NewRouteContext()
83+
ctx.URLParams.Add("organization", organization.Name)
84+
ctx.URLParams.Add("project", project.Name)
85+
r = r.WithContext(context.WithValue(r.Context(), chi.RouteCtxKey, ctx))
86+
return r, project
87+
}
88+
89+
t.Run("None", func(t *testing.T) {
90+
t.Parallel()
91+
db := databasefake.New()
92+
rtr := chi.NewRouter()
93+
rtr.Use(
94+
httpmw.ExtractAPIKey(db, nil),
95+
httpmw.ExtractOrganizationParam(db),
96+
httpmw.ExtractProjectParam(db),
97+
httpmw.ExtractProjectHistoryParam(db),
98+
)
99+
rtr.Get("/", nil)
100+
r, _ := setupAuthentication(db)
101+
rw := httptest.NewRecorder()
102+
rtr.ServeHTTP(rw, r)
103+
104+
res := rw.Result()
105+
defer res.Body.Close()
106+
require.Equal(t, http.StatusBadRequest, res.StatusCode)
107+
})
108+
109+
t.Run("NotFound", func(t *testing.T) {
110+
t.Parallel()
111+
db := databasefake.New()
112+
rtr := chi.NewRouter()
113+
rtr.Use(
114+
httpmw.ExtractAPIKey(db, nil),
115+
httpmw.ExtractOrganizationParam(db),
116+
httpmw.ExtractProjectParam(db),
117+
httpmw.ExtractProjectHistoryParam(db),
118+
)
119+
rtr.Get("/", nil)
120+
121+
r, _ := setupAuthentication(db)
122+
chi.RouteContext(r.Context()).URLParams.Add("projecthistory", "nothin")
123+
rw := httptest.NewRecorder()
124+
rtr.ServeHTTP(rw, r)
125+
126+
res := rw.Result()
127+
defer res.Body.Close()
128+
require.Equal(t, http.StatusNotFound, res.StatusCode)
129+
})
130+
131+
t.Run("ProjectHistory", func(t *testing.T) {
132+
t.Parallel()
133+
db := databasefake.New()
134+
rtr := chi.NewRouter()
135+
rtr.Use(
136+
httpmw.ExtractAPIKey(db, nil),
137+
httpmw.ExtractOrganizationParam(db),
138+
httpmw.ExtractProjectParam(db),
139+
httpmw.ExtractProjectHistoryParam(db),
140+
)
141+
rtr.Get("/", func(rw http.ResponseWriter, r *http.Request) {
142+
_ = httpmw.ProjectHistoryParam(r)
143+
rw.WriteHeader(http.StatusOK)
144+
})
145+
146+
r, project := setupAuthentication(db)
147+
projectHistory, err := db.InsertProjectHistory(context.Background(), database.InsertProjectHistoryParams{
148+
ID: uuid.New(),
149+
ProjectID: project.ID,
150+
Name: "moo",
151+
})
152+
require.NoError(t, err)
153+
chi.RouteContext(r.Context()).URLParams.Add("projecthistory", projectHistory.Name)
154+
rw := httptest.NewRecorder()
155+
rtr.ServeHTTP(rw, r)
156+
157+
res := rw.Result()
158+
defer res.Body.Close()
159+
require.Equal(t, http.StatusOK, res.StatusCode)
160+
})
161+
}

httpmw/workspacehistoryparam.go

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
package httpmw
2+
3+
import (
4+
"context"
5+
"database/sql"
6+
"errors"
7+
"fmt"
8+
"net/http"
9+
10+
"github.com/go-chi/chi"
11+
12+
"github.com/coder/coder/database"
13+
"github.com/coder/coder/httpapi"
14+
)
15+
16+
type workspaceHistoryParamContextKey struct{}
17+
18+
// WorkspaceHistoryParam returns the workspace history from the ExtractWorkspaceHistoryParam handler.
19+
func WorkspaceHistoryParam(r *http.Request) database.WorkspaceHistory {
20+
workspaceHistory, ok := r.Context().Value(workspaceHistoryParamContextKey{}).(database.WorkspaceHistory)
21+
if !ok {
22+
panic("developer error: workspace history param middleware not provided")
23+
}
24+
return workspaceHistory
25+
}
26+
27+
// ExtractWorkspaceHistoryParam grabs workspace history from the "workspacehistory" URL parameter.
28+
func ExtractWorkspaceHistoryParam(db database.Store) func(http.Handler) http.Handler {
29+
return func(next http.Handler) http.Handler {
30+
return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
31+
workspace := WorkspaceParam(r)
32+
workspaceHistoryName := chi.URLParam(r, "workspacehistory")
33+
if workspaceHistoryName == "" {
34+
httpapi.Write(rw, http.StatusBadRequest, httpapi.Response{
35+
Message: "workspace history name must be provided",
36+
})
37+
return
38+
}
39+
workspaceHistory, err := db.GetWorkspaceHistoryByWorkspaceIDAndName(r.Context(), database.GetWorkspaceHistoryByWorkspaceIDAndNameParams{
40+
WorkspaceID: workspace.ID,
41+
Name: workspaceHistoryName,
42+
})
43+
if errors.Is(err, sql.ErrNoRows) {
44+
httpapi.Write(rw, http.StatusNotFound, httpapi.Response{
45+
Message: fmt.Sprintf("workspace history %q does not exist", workspaceHistoryName),
46+
})
47+
return
48+
}
49+
if err != nil {
50+
httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{
51+
Message: fmt.Sprintf("get workspace history: %s", err.Error()),
52+
})
53+
return
54+
}
55+
56+
ctx := context.WithValue(r.Context(), workspaceHistoryParamContextKey{}, workspaceHistory)
57+
next.ServeHTTP(rw, r.WithContext(ctx))
58+
})
59+
}
60+
}

0 commit comments

Comments
 (0)