Skip to content

Commit 154f09b

Browse files
FEAT[DXTA-302]: Add team creation api endpoint, add JWT verification (#672)
* FEAT[DXTA-302]: Add team creation api endpoint, add JWT verification * chore: Switch to internal-api dir * feat: move tenant related migrations * feat: add create and add member to team. Refine tenant db schema * feat: Add custom JWT authenticator * refactor: Auth utils refactor * refactor: Add internal api dir inside internals * chore: Add comment about task --------- Co-authored-by: David Abram <david@crocoder.dev>
1 parent 8197f91 commit 154f09b

20 files changed

Lines changed: 831 additions & 4 deletions

cmd/internal-api/main.go

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,11 @@ import (
99
"os/signal"
1010
"time"
1111

12+
"github.com/dxta-dev/app/internal/internal_api/handler"
13+
"github.com/dxta-dev/app/internal/util"
1214
"github.com/go-chi/chi/v5"
1315
"github.com/go-chi/chi/v5/middleware"
16+
"github.com/go-chi/jwtauth/v5"
1417

1518
"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
1619
instrruntime "go.opentelemetry.io/contrib/instrumentation/runtime"
@@ -85,8 +88,6 @@ func main() {
8588

8689
r.Use(middleware.Compress(5, "gzip"))
8790

88-
// TODO: add auth middleware
89-
9091
if isEndpointProvided {
9192
r.Use(func(next http.Handler) http.Handler {
9293
return otelhttp.NewHandler(next, "dxta-app")
@@ -111,8 +112,24 @@ func main() {
111112
srv.IdleTimeout = 30 * time.Second
112113
}
113114

114-
// TODO: add handlers
115-
// r.Get("/path/{var}", handler.SomeHandler)
115+
r.Route("/tenant", func(r chi.Router) {
116+
if os.Getenv("ENABLE_JWT_AUTH") == "true" {
117+
pubKey, _ := util.GetRawPublicKey()
118+
119+
tokenAuth := util.CreateAuthVerifier(pubKey)
120+
121+
r.Use(jwtauth.Verifier(tokenAuth))
122+
r.Use(util.Authenticator())
123+
}
124+
125+
// TO-DO Add middleware if we don't authenticate with JWT
126+
// https://app.plane.so/crocoder/browse/DXTA-307/
127+
128+
r.Post("/teams", handler.CreateTeam)
129+
r.Post("/teams/{team_id}/members/{member_id}", handler.AddMemberToTeam)
130+
r.Post("/members", handler.CreateMember)
131+
})
132+
116133
r.Get("/health", func(w http.ResponseWriter, r *http.Request) {
117134
w.Write([]byte(`OK`))
118135
})
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
CREATE TABLE IF NOT EXISTS "tenants" (
2+
"id" INTEGER PRIMARY KEY NOT NULL,
3+
"organization_id" TEXT NOT NULL UNIQUE,
4+
"db_url" TEXT NOT NULL,
5+
"deleted_at" DATETIME,
6+
"created_at" DATETIME NOT NULL DEFAULT (datetime('now')),
7+
"updated_at" DATETIME NOT NULL DEFAULT (datetime('now'))
8+
);
9+
10+
CREATE TRIGGER "tenants_updated_at"
11+
AFTER UPDATE ON "tenants"
12+
FOR EACH ROW
13+
BEGIN
14+
UPDATE "tenants"
15+
SET updated_at = datetime('now')
16+
WHERE id = OLD.id;
17+
END;
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
ALTER TABLE tenants ALTER COLUMN deleted_at TO deleted_at DATETIME DEFAULT NULL;
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
ALTER TABLE tenants ADD name TEXT NOT NULL DEFAULT '';
2+
ALTER TABLE tenants ADD domain TEXT NOT NULL DEFAULT '';
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
2+
--
3+
-- LibSQL SQL Schema dump automatic generated by geni
4+
--
5+
6+
CREATE TABLE schema_migrations (id VARCHAR(255) NOT NULL PRIMARY KEY);
7+
CREATE TABLE "tenants" ("id" INTEGER PRIMARY KEY NOT NULL, "organization_id" TEXT NOT NULL UNIQUE, "db_url" TEXT NOT NULL, deleted_at DATETIME DEFAULT NULL, "created_at" DATETIME NOT NULL DEFAULT (datetime ('now')), "updated_at" DATETIME NOT NULL DEFAULT (datetime ('now')), name TEXT NOT NULL DEFAULT '', domain TEXT NOT NULL DEFAULT '');
8+
CREATE TRIGGER "tenants_updated_at" AFTER UPDATE ON "tenants" FOR EACH ROW BEGIN
9+
UPDATE "tenants" SET updated_at = datetime ('now') WHERE id = OLD.id;
10+
END;
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
DROP TRIGGER IF EXISTS "settings_set_updated_at";
2+
DROP TRIGGER IF EXISTS "organizations_set_updated_at";
3+
DROP TRIGGER IF EXISTS "teams_set_updated_at";
4+
DROP TRIGGER IF EXISTS "members_set_updated_at";
5+
DROP TRIGGER IF EXISTS "github_organizations_set_updated_at";
6+
DROP TRIGGER IF EXISTS "github_members_set_updated_at";
7+
DROP TRIGGER IF EXISTS "github_teams_set_updated_at";
8+
9+
DROP TABLE IF EXISTS "settings";
10+
DROP TABLE IF EXISTS "organizations";
11+
DROP TABLE IF EXISTS "teams";
12+
DROP TABLE IF EXISTS "members";
13+
DROP TABLE IF EXISTS "teams_members";
14+
DROP TABLE IF EXISTS "github_organizations";
15+
DROP TABLE IF EXISTS "organizations__github_organizations";
16+
DROP TABLE IF EXISTS "github_members";
17+
DROP TABLE IF EXISTS "github_teams";
18+
DROP TABLE IF EXISTS "github_teams__github_members";
Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
CREATE TABLE "settings" (
2+
"id" INTEGER PRIMARY KEY NOT NULL,
3+
"tenant_name" TEXT NOT NULL,
4+
"tenant_domain" TEXT NOT NULL,
5+
"created_at" DATETIME NOT NULL DEFAULT (datetime('now')),
6+
"updated_at" DATETIME NOT NULL DEFAULT (datetime('now'))
7+
);
8+
9+
-- References to DXTA organizations
10+
CREATE TABLE "organizations" (
11+
"id" INTEGER PRIMARY KEY NOT NULL,
12+
"name" TEXT NOT NULL,
13+
"auth_id" TEXT DEFAULT NULL, -- From Better Auth
14+
"created_at" DATETIME NOT NULL DEFAULT (datetime('now')),
15+
"updated_at" DATETIME NOT NULL DEFAULT (datetime('now')),
16+
"deleted_at" DATETIME DEFAULT NULL
17+
);
18+
19+
-- References to DXTA teams
20+
CREATE TABLE "teams" (
21+
"id" INTEGER PRIMARY KEY NOT NULL,
22+
"name" TEXT NOT NULL,
23+
"organization_id" INTEGER NOT NULL,
24+
"created_at" DATETIME NOT NULL DEFAULT (datetime('now')),
25+
"updated_at" DATETIME NOT NULL DEFAULT (datetime('now')),
26+
"deleted_at" DATETIME DEFAULT NULL,
27+
FOREIGN KEY ("organization_id") references "organizations" ("id")
28+
);
29+
30+
CREATE TABLE "members" (
31+
"id" INTEGER PRIMARY KEY NOT NULL,
32+
"name" TEXT NOT NULL,
33+
"email" TEXT DEFAULT NULL,
34+
"created_at" DATETIME NOT NULL DEFAULT (datetime('now')),
35+
"updated_at" DATETIME NOT NULL DEFAULT (datetime('now')),
36+
"deleted_at" DATETIME DEFAULT NULL
37+
);
38+
39+
CREATE TABLE "teams__members" (
40+
"team_id" INTEGER NOT NULL,
41+
"member_id" INTEGER NOT NULL,
42+
"created_at" DATETIME NOT NULL DEFAULT (datetime('now')),
43+
PRIMARY KEY ("team_id", "member_id"),
44+
FOREIGN KEY ("team_id") REFERENCES "teams" ("id"),
45+
FOREIGN KEY ("member_id") REFERENCES "members" ("id")
46+
);
47+
48+
---------------------------------- Github ----------------------------------
49+
50+
CREATE TABLE "github_organizations" (
51+
"id" INTEGER PRIMARY KEY NOT NULL,
52+
"external_id" INTEGER NOT NULL,
53+
"name" TEXT NOT NULL,
54+
"github_app_installation_id" INTEGER NOT NULL,
55+
"created_at" DATETIME NOT NULL DEFAULT (datetime('now')),
56+
"updated_at" DATETIME NOT NULL DEFAULT (datetime('now')),
57+
"deleted_at" DATETIME DEFAULT NULL
58+
);
59+
60+
-- DXTA organizations to github organizations
61+
CREATE TABLE "organizations__github_organizations" (
62+
"organization_id" INTEGER NOT NULL,
63+
"github_organization_id" INTEGER NOT NULL,
64+
"created_at" DATETIME NOT NULL DEFAULT (datetime('now')),
65+
PRIMARY KEY ("organization_id", "github_organization_id"),
66+
FOREIGN KEY ("organization_id") REFERENCES "organizations" ("id"),
67+
FOREIGN KEY ("github_organization_id") REFERENCES "github_organizations" ("id")
68+
);
69+
70+
CREATE TABLE "github_members" (
71+
"id" INTEGER PRIMARY KEY NOT NULL,
72+
"external_id" INTEGER NOT NULL,
73+
"username" TEXT NOT NULL,
74+
"email" TEXT DEFAULT NULL,
75+
"member_id" INTEGER NULL,
76+
"created_at" DATETIME NOT NULL DEFAULT (datetime('now')),
77+
"updated_at" DATETIME NOT NULL DEFAULT (datetime('now')),
78+
"deleted_at" DATETIME DEFAULT NULL,
79+
FOREIGN KEY ("member_id") REFERENCES "members" ("id")
80+
);
81+
82+
CREATE TABLE "github_teams" (
83+
"id" INTEGER PRIMARY KEY NOT NULL,
84+
"external_id" INTEGER NOT NULL,
85+
"name" TEXT NOT NULL,
86+
"github_organization_id" INTEGER NOT NULL,
87+
"created_at" DATETIME NOT NULL DEFAULT (datetime('now')),
88+
"updated_at" DATETIME NOT NULL DEFAULT (datetime('now')),
89+
"deleted_at" DATETIME DEFAULT NULL,
90+
FOREIGN KEY ("github_organization_id") references "github_organizations" ("id")
91+
);
92+
93+
CREATE TABLE "github_teams__github_members" (
94+
"github_team_id" INTEGER NOT NULL,
95+
"github_member_id" INTEGER NOT NULL,
96+
PRIMARY KEY ("github_team_id", "github_member_id"),
97+
FOREIGN KEY ("github_team_id") REFERENCES "github_teams" ("id"),
98+
FOREIGN KEY ("github_member_id") REFERENCES "github_members" ("id")
99+
);
100+
101+
---------------------------------- Last updated at triggers ----------------------------------
102+
103+
CREATE TRIGGER "settings_set_updated_at"
104+
AFTER UPDATE ON "settings"
105+
FOR EACH ROW
106+
BEGIN
107+
UPDATE "settings"
108+
SET updated_at = datetime('now')
109+
WHERE id = OLD.id;
110+
END;
111+
112+
CREATE TRIGGER "organizations_set_updated_at"
113+
AFTER UPDATE ON "organizations"
114+
FOR EACH ROW
115+
BEGIN
116+
UPDATE "organizations"
117+
SET updated_at = datetime('now')
118+
WHERE id = OLD.id;
119+
END;
120+
121+
CREATE TRIGGER "teams_set_updated_at"
122+
AFTER UPDATE ON "teams"
123+
FOR EACH ROW
124+
BEGIN
125+
UPDATE "teams"
126+
SET updated_at = datetime('now')
127+
WHERE id = OLD.id;
128+
END;
129+
130+
CREATE TRIGGER "members_set_updated_at"
131+
AFTER UPDATE ON "members"
132+
FOR EACH ROW
133+
BEGIN
134+
UPDATE "members"
135+
SET updated_at = datetime('now')
136+
WHERE id = OLD.id;
137+
END;
138+
139+
CREATE TRIGGER "github_organizations_set_updated_at"
140+
AFTER UPDATE ON "github_organizations"
141+
FOR EACH ROW
142+
BEGIN
143+
UPDATE "github_organizations"
144+
SET updated_at = datetime('now')
145+
WHERE id = OLD.id;
146+
END;
147+
148+
CREATE TRIGGER "github_members_set_updated_at"
149+
AFTER UPDATE ON "github_members"
150+
FOR EACH ROW
151+
BEGIN
152+
UPDATE "github_members"
153+
SET updated_at = datetime('now')
154+
WHERE id = OLD.id;
155+
END;
156+
157+
CREATE TRIGGER "github_teams_set_updated_at"
158+
AFTER UPDATE ON "github_teams"
159+
FOR EACH ROW
160+
BEGIN
161+
UPDATE "github_teams"
162+
SET updated_at = datetime('now')
163+
WHERE id = OLD.id;
164+
END;
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
2+
--
3+
-- LibSQL SQL Schema dump automatic generated by geni
4+
--
5+
6+
CREATE TABLE schema_migrations (id VARCHAR(255) NOT NULL PRIMARY KEY);
7+
CREATE TABLE "settings" ("id" INTEGER PRIMARY KEY NOT NULL, "tenant_name" TEXT NOT NULL, "tenant_domain" TEXT NOT NULL, "created_at" DATETIME NOT NULL DEFAULT (datetime ('now')), "updated_at" DATETIME NOT NULL DEFAULT (datetime ('now')));
8+
CREATE TABLE "organizations" ("id" INTEGER PRIMARY KEY NOT NULL, "name" TEXT NOT NULL, "auth_id" TEXT DEFAULT NULL, "created_at" DATETIME NOT NULL DEFAULT (datetime ('now')), "updated_at" DATETIME NOT NULL DEFAULT (datetime ('now')), "deleted_at" DATETIME DEFAULT NULL);
9+
CREATE TABLE "teams" ("id" INTEGER PRIMARY KEY NOT NULL, "name" TEXT NOT NULL, "organization_id" INTEGER NOT NULL, "created_at" DATETIME NOT NULL DEFAULT (datetime ('now')), "updated_at" DATETIME NOT NULL DEFAULT (datetime ('now')), "deleted_at" DATETIME DEFAULT NULL, FOREIGN KEY ("organization_id") REFERENCES "organizations" ("id"));
10+
CREATE TABLE "members" ("id" INTEGER PRIMARY KEY NOT NULL, "name" TEXT NOT NULL, "email" TEXT DEFAULT NULL, "created_at" DATETIME NOT NULL DEFAULT (datetime ('now')), "updated_at" DATETIME NOT NULL DEFAULT (datetime ('now')), "deleted_at" DATETIME DEFAULT NULL);
11+
CREATE TABLE "teams__members" ("team_id" INTEGER NOT NULL, "member_id" INTEGER NOT NULL, "created_at" DATETIME NOT NULL DEFAULT (datetime ('now')), PRIMARY KEY ("team_id", "member_id"), FOREIGN KEY ("team_id") REFERENCES "teams" ("id"), FOREIGN KEY ("member_id") REFERENCES "members" ("id"));
12+
CREATE TABLE "github_organizations" ("id" INTEGER PRIMARY KEY NOT NULL, "external_id" INTEGER NOT NULL, "name" TEXT NOT NULL, "github_app_installation_id" INTEGER NOT NULL, "created_at" DATETIME NOT NULL DEFAULT (datetime ('now')), "updated_at" DATETIME NOT NULL DEFAULT (datetime ('now')), "deleted_at" DATETIME DEFAULT NULL);
13+
CREATE TABLE "organizations__github_organizations" ("organization_id" INTEGER NOT NULL, "github_organization_id" INTEGER NOT NULL, "created_at" DATETIME NOT NULL DEFAULT (datetime ('now')), PRIMARY KEY ("organization_id", "github_organization_id"), FOREIGN KEY ("organization_id") REFERENCES "organizations" ("id"), FOREIGN KEY ("github_organization_id") REFERENCES "github_organizations" ("id"));
14+
CREATE TABLE "github_members" ("id" INTEGER PRIMARY KEY NOT NULL, "external_id" INTEGER NOT NULL, "username" TEXT NOT NULL, "email" TEXT DEFAULT NULL, "member_id" INTEGER NULL, "created_at" DATETIME NOT NULL DEFAULT (datetime ('now')), "updated_at" DATETIME NOT NULL DEFAULT (datetime ('now')), "deleted_at" DATETIME DEFAULT NULL, FOREIGN KEY ("member_id") REFERENCES "members" ("id"));
15+
CREATE TABLE "github_teams" ("id" INTEGER PRIMARY KEY NOT NULL, "external_id" INTEGER NOT NULL, "name" TEXT NOT NULL, "github_organization_id" INTEGER NOT NULL, "created_at" DATETIME NOT NULL DEFAULT (datetime ('now')), "updated_at" DATETIME NOT NULL DEFAULT (datetime ('now')), "deleted_at" DATETIME DEFAULT NULL, FOREIGN KEY ("github_organization_id") REFERENCES "github_organizations" ("id"));
16+
CREATE TABLE "github_teams__github_members" ("github_team_id" INTEGER NOT NULL, "github_member_id" INTEGER NOT NULL, PRIMARY KEY ("github_team_id", "github_member_id"), FOREIGN KEY ("github_team_id") REFERENCES "github_teams" ("id"), FOREIGN KEY ("github_member_id") REFERENCES "github_members" ("id"));
17+
CREATE TRIGGER "settings_set_updated_at" AFTER UPDATE ON "settings" FOR EACH ROW BEGIN
18+
UPDATE "settings" SET updated_at = datetime ('now') WHERE id = OLD.id;
19+
END;
20+
CREATE TRIGGER "organizations_set_updated_at" AFTER UPDATE ON "organizations" FOR EACH ROW BEGIN
21+
UPDATE "organizations" SET updated_at = datetime ('now') WHERE id = OLD.id;
22+
END;
23+
CREATE TRIGGER "teams_set_updated_at" AFTER UPDATE ON "teams" FOR EACH ROW BEGIN
24+
UPDATE "teams" SET updated_at = datetime ('now') WHERE id = OLD.id;
25+
END;
26+
CREATE TRIGGER "members_set_updated_at" AFTER UPDATE ON "members" FOR EACH ROW BEGIN
27+
UPDATE "members" SET updated_at = datetime ('now') WHERE id = OLD.id;
28+
END;
29+
CREATE TRIGGER "github_organizations_set_updated_at" AFTER UPDATE ON "github_organizations" FOR EACH ROW BEGIN
30+
UPDATE "github_organizations" SET updated_at = datetime ('now') WHERE id = OLD.id;
31+
END;
32+
CREATE TRIGGER "github_members_set_updated_at" AFTER UPDATE ON "github_members" FOR EACH ROW BEGIN
33+
UPDATE "github_members" SET updated_at = datetime ('now') WHERE id = OLD.id;
34+
END;
35+
CREATE TRIGGER "github_teams_set_updated_at" AFTER UPDATE ON "github_teams" FOR EACH ROW BEGIN
36+
UPDATE "github_teams" SET updated_at = datetime ('now') WHERE id = OLD.id;
37+
END;

go.mod

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,21 +22,31 @@ require (
2222
github.com/antlr/antlr4/runtime/Go/antlr/v4 v4.0.0-20230512164433-5d1fd1a340c9 // indirect
2323
github.com/cenkalti/backoff/v5 v5.0.2 // indirect
2424
github.com/davecgh/go-spew v1.1.1 // indirect
25+
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 // indirect
2526
github.com/facebookgo/clock v0.0.0-20150410010913-600d898af40a // indirect
2627
github.com/felixge/httpsnoop v1.0.4 // indirect
28+
github.com/go-chi/jwtauth/v5 v5.3.3 // indirect
2729
github.com/go-logr/logr v1.4.2 // indirect
2830
github.com/go-logr/stdr v1.2.2 // indirect
31+
github.com/goccy/go-json v0.10.3 // indirect
2932
github.com/gogo/protobuf v1.3.2 // indirect
3033
github.com/golang/mock v1.6.0 // indirect
3134
github.com/google/uuid v1.6.0 // indirect
3235
github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 // indirect
3336
github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 // indirect
3437
github.com/klauspost/compress v1.15.15 // indirect
38+
github.com/lestrrat-go/blackmagic v1.0.2 // indirect
39+
github.com/lestrrat-go/httpcc v1.0.1 // indirect
40+
github.com/lestrrat-go/httprc v1.0.6 // indirect
41+
github.com/lestrrat-go/iter v1.0.2 // indirect
42+
github.com/lestrrat-go/jwx/v2 v2.1.3 // indirect
43+
github.com/lestrrat-go/option v1.0.1 // indirect
3544
github.com/libsql/sqlite-antlr4-parser v0.0.0-20230802215326-5cb5bb604475 // indirect
3645
github.com/mattn/go-isatty v0.0.20 // indirect
3746
github.com/nexus-rpc/sdk-go v0.3.0 // indirect
3847
github.com/pmezard/go-difflib v1.0.0 // indirect
3948
github.com/robfig/cron v1.2.0 // indirect
49+
github.com/segmentio/asm v1.2.0 // indirect
4050
github.com/stretchr/objx v0.5.2 // indirect
4151
github.com/stretchr/testify v1.10.0 // indirect
4252
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
@@ -47,6 +57,7 @@ require (
4757
go.opentelemetry.io/otel/sdk/metric v1.36.0 // indirect
4858
go.opentelemetry.io/otel/trace v1.36.0 // indirect
4959
go.opentelemetry.io/proto/otlp v1.6.0 // indirect
60+
golang.org/x/crypto v0.38.0 // indirect
5061
golang.org/x/exp v0.0.0-20240325151524-a685a6edb6d8 // indirect
5162
golang.org/x/net v0.40.0 // indirect
5263
golang.org/x/sync v0.14.0 // indirect

0 commit comments

Comments
 (0)