Skip to content

Commit f4eee72

Browse files
committed
Add make reset command for local development environments
1 parent dc4e6e0 commit f4eee72

6 files changed

Lines changed: 434 additions & 2 deletions

File tree

api/Makefile

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,3 +156,15 @@ generate-docs:
156156
.PHONY: add-known-sdk-version
157157
add-known-sdk-version:
158158
poetry run python scripts/add-known-sdk-version.py $(opts)
159+
160+
.PHONY: reset
161+
reset: docker-up
162+
@echo "🔥 Nuking local database..."
163+
poetry run python manage.py waitfordb
164+
poetry run python manage.py flush --noinput
165+
@echo "📦 Running migrations..."
166+
poetry run python manage.py migrate
167+
poetry run python manage.py createcachetable
168+
@echo "🌱 Seeding database with test data..."
169+
poetry run python manage.py reset_local_database
170+
@echo "✅ Database reset complete"

api/app/settings/local.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
ENABLE_AXES = False
44

55

6-
ALLOWED_HOSTS.extend([".ngrok.io", "127.0.0.1", "localhost"])
6+
ALLOWED_HOSTS.extend([".ngrok.io", "127.0.0.1", "localhost", "testserver"])
77

88
INSTALLED_APPS.extend(["debug_toolbar", "django_extensions"])
99

Lines changed: 296 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,296 @@
1+
import json
2+
import logging
3+
from typing import Any
4+
5+
from django.core.management import BaseCommand
6+
from django.urls import reverse
7+
from rest_framework.test import APIClient
8+
9+
logger = logging.getLogger(__name__)
10+
11+
12+
class Command(BaseCommand):
13+
help = "Resets and seeds the database with test data for local development"
14+
15+
def handle(self, *args: Any, **kwargs: Any) -> None:
16+
email = "local@flagsmith.com"
17+
password = "not-gonna-tell"
18+
19+
client = APIClient()
20+
21+
# Create user via signup API
22+
signup_url = reverse("api-v1:custom_auth:ffadminuser-list")
23+
signup_response = client.post(
24+
signup_url,
25+
data={
26+
"email": email,
27+
"password": password,
28+
"re_password": password,
29+
"first_name": "Local",
30+
"last_name": "Developer",
31+
},
32+
)
33+
auth_token = signup_response.json()["key"]
34+
client.credentials(HTTP_AUTHORIZATION=f"Token {auth_token}")
35+
36+
# Create organisation via API (user becomes admin automatically)
37+
org_name = "Acme, Inc."
38+
org_url = reverse("api-v1:organisations:organisation-list")
39+
org_response = client.post(org_url, data={"name": org_name})
40+
organisation_id = org_response.json()["id"]
41+
42+
# Create project via API
43+
project_name = "AI Booster"
44+
project_url = reverse("api-v1:projects:project-list")
45+
project_response = client.post(
46+
project_url,
47+
data={"name": project_name, "organisation": organisation_id},
48+
)
49+
project_id = project_response.json()["id"]
50+
51+
# Create environments via API
52+
env_url = reverse("api-v1:environments:environment-list")
53+
dev_env_response = client.post(
54+
env_url,
55+
data={"name": "Development", "project": project_id},
56+
)
57+
dev_environment = dev_env_response.json()
58+
dev_env_id = dev_environment["id"]
59+
dev_env_api_key = dev_environment["api_key"]
60+
61+
client.post(
62+
env_url,
63+
data={"name": "Staging", "project": project_id},
64+
)
65+
66+
client.post(
67+
env_url,
68+
data={"name": "Production", "project": project_id},
69+
)
70+
71+
# Create features via API
72+
feature_url = reverse(
73+
"api-v1:projects:project-features-list", args=[project_id]
74+
)
75+
76+
dark_mode_response = client.post(
77+
feature_url,
78+
data={
79+
"name": "dark_mode",
80+
"description": "Enable dark mode theme for the application",
81+
"default_enabled": True,
82+
"type": "FLAG",
83+
},
84+
)
85+
dark_mode_id = dark_mode_response.json()["id"]
86+
87+
client.post(
88+
feature_url,
89+
data={
90+
"name": "ai_assistant",
91+
"description": "Enable AI-powered assistant features",
92+
"default_enabled": False,
93+
"type": "FLAG",
94+
},
95+
)
96+
97+
client.post(
98+
feature_url,
99+
data={
100+
"name": "api_rate_limit",
101+
"description": "Maximum API requests per minute",
102+
"default_enabled": True,
103+
"type": "CONFIG",
104+
"initial_value": "100",
105+
},
106+
)
107+
108+
client.post(
109+
feature_url,
110+
data={
111+
"name": "welcome_message",
112+
"description": "Welcome message displayed to users",
113+
"default_enabled": True,
114+
"type": "CONFIG",
115+
"initial_value": "Welcome to AI Booster!",
116+
},
117+
)
118+
119+
client.post(
120+
feature_url,
121+
data={
122+
"name": "feature_config",
123+
"description": "JSON configuration for feature behavior",
124+
"default_enabled": True,
125+
"type": "CONFIG",
126+
"initial_value": '{"theme": "modern", "animations": true}',
127+
},
128+
)
129+
130+
beta_features_response = client.post(
131+
feature_url,
132+
data={
133+
"name": "beta_features",
134+
"description": "Enable access to beta features",
135+
"default_enabled": True,
136+
"type": "FLAG",
137+
},
138+
)
139+
beta_features_id = beta_features_response.json()["id"]
140+
141+
# Create segments via API
142+
segment_url = reverse(
143+
"api-v1:projects:project-segments-list", args=[project_id]
144+
)
145+
146+
premium_segment_response = client.post(
147+
segment_url,
148+
data=json.dumps(
149+
{
150+
"name": "Premium Users",
151+
"description": "Users with premium subscription and active status",
152+
"project": project_id,
153+
"rules": [
154+
{
155+
"type": "ALL",
156+
"rules": [
157+
{
158+
"type": "ANY",
159+
"rules": [],
160+
"conditions": [
161+
{
162+
"property": "subscription_tier",
163+
"operator": "EQUAL",
164+
"value": "premium",
165+
},
166+
{
167+
"property": "account_age",
168+
"operator": "GREATER_THAN_INCLUSIVE",
169+
"value": "30",
170+
},
171+
],
172+
}
173+
],
174+
"conditions": [],
175+
}
176+
],
177+
}
178+
),
179+
content_type="application/json",
180+
)
181+
premium_segment_id = premium_segment_response.json()["id"]
182+
183+
beta_segment_response = client.post(
184+
segment_url,
185+
data=json.dumps(
186+
{
187+
"name": "Beta Testers",
188+
"description": "Users enrolled in beta testing program",
189+
"project": project_id,
190+
"rules": [
191+
{
192+
"type": "ALL",
193+
"rules": [],
194+
"conditions": [
195+
{
196+
"property": "beta_tester",
197+
"operator": "EQUAL",
198+
"value": "true",
199+
},
200+
],
201+
}
202+
],
203+
}
204+
),
205+
content_type="application/json",
206+
)
207+
beta_segment_id = beta_segment_response.json()["id"]
208+
209+
client.post(
210+
segment_url,
211+
data=json.dumps(
212+
{
213+
"name": "50% Rollout",
214+
"description": "50% of users for gradual feature rollout",
215+
"project": project_id,
216+
"rules": [
217+
{
218+
"type": "ALL",
219+
"rules": [],
220+
"conditions": [
221+
{
222+
"property": "id",
223+
"operator": "PERCENTAGE_SPLIT",
224+
"value": "50",
225+
},
226+
],
227+
}
228+
],
229+
}
230+
),
231+
content_type="application/json",
232+
)
233+
234+
# Create feature segments via API
235+
feature_segment_url = reverse("api-v1:features:feature-segment-list")
236+
237+
client.post(
238+
feature_segment_url,
239+
data=json.dumps(
240+
{
241+
"feature": dark_mode_id,
242+
"segment": premium_segment_id,
243+
"environment": dev_env_id,
244+
}
245+
),
246+
content_type="application/json",
247+
)
248+
249+
client.post(
250+
feature_segment_url,
251+
data=json.dumps(
252+
{
253+
"feature": beta_features_id,
254+
"segment": beta_segment_id,
255+
"environment": dev_env_id,
256+
}
257+
),
258+
content_type="application/json",
259+
)
260+
261+
# Create identities via API
262+
identity_url = reverse(
263+
"api-v1:environments:environment-identities-list", args=[dev_env_api_key]
264+
)
265+
266+
client.post(identity_url, data={"identifier": "user_123456"})
267+
client.post(identity_url, data={"identifier": "test_user"})
268+
269+
# Print summary
270+
self.stdout.write(self.style.SUCCESS("\nDatabase seeded successfully\n"))
271+
272+
self.stdout.write("Created entities:\n")
273+
self.stdout.write(f" Organisation: {org_name}\n")
274+
self.stdout.write(f" Project: {project_name}\n")
275+
self.stdout.write(" Environments (3):\n")
276+
self.stdout.write(" Development\n")
277+
self.stdout.write(" Staging\n")
278+
self.stdout.write(" Production\n")
279+
self.stdout.write(" Features (6):\n")
280+
self.stdout.write(" dark_mode (FLAG, enabled)\n")
281+
self.stdout.write(" ai_assistant (FLAG, disabled)\n")
282+
self.stdout.write(" api_rate_limit (CONFIG)\n")
283+
self.stdout.write(" welcome_message (CONFIG)\n")
284+
self.stdout.write(" feature_config (CONFIG)\n")
285+
self.stdout.write(" beta_features (FLAG, enabled)\n")
286+
self.stdout.write(" Segments (3):\n")
287+
self.stdout.write(" Premium Users\n")
288+
self.stdout.write(" Beta Testers\n")
289+
self.stdout.write(" 50% Rollout\n")
290+
self.stdout.write(" Identities (2):\n")
291+
self.stdout.write(" user_123456\n")
292+
self.stdout.write(" test_user\n")
293+
294+
self.stdout.write("\nLogin credentials:\n")
295+
self.stdout.write(f" Email: {email}\n")
296+
self.stdout.write(f" Password: {password}\n")

api/pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ exclude_also = [
6363
[tool.coverage.run]
6464
omit = [
6565
"app/settings/common.py",
66+
"app/settings/local.py",
6667
"manage.py",
6768
"e2etests/*",
6869
"scripts/*",

0 commit comments

Comments
 (0)