cinchdb 0.1.1__py3-none-any.whl → 0.1.3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- cinchdb/utils/name_validator.py +6 -6
- {cinchdb-0.1.1.dist-info → cinchdb-0.1.3.dist-info}/METADATA +16 -18
- {cinchdb-0.1.1.dist-info → cinchdb-0.1.3.dist-info}/RECORD +6 -22
- {cinchdb-0.1.1.dist-info → cinchdb-0.1.3.dist-info}/entry_points.txt +0 -1
- cinchdb/api/__init__.py +0 -5
- cinchdb/api/app.py +0 -76
- cinchdb/api/auth.py +0 -290
- cinchdb/api/main.py +0 -137
- cinchdb/api/routers/__init__.py +0 -25
- cinchdb/api/routers/auth.py +0 -135
- cinchdb/api/routers/branches.py +0 -368
- cinchdb/api/routers/codegen.py +0 -164
- cinchdb/api/routers/columns.py +0 -290
- cinchdb/api/routers/data.py +0 -479
- cinchdb/api/routers/databases.py +0 -184
- cinchdb/api/routers/projects.py +0 -133
- cinchdb/api/routers/query.py +0 -156
- cinchdb/api/routers/tables.py +0 -349
- cinchdb/api/routers/tenants.py +0 -216
- cinchdb/api/routers/views.py +0 -219
- {cinchdb-0.1.1.dist-info → cinchdb-0.1.3.dist-info}/WHEEL +0 -0
- {cinchdb-0.1.1.dist-info → cinchdb-0.1.3.dist-info}/licenses/LICENSE +0 -0
cinchdb/api/routers/data.py
DELETED
@@ -1,479 +0,0 @@
|
|
1
|
-
"""Data CRUD router for CinchDB API."""
|
2
|
-
|
3
|
-
from typing import List, Dict, Any, Optional
|
4
|
-
from fastapi import APIRouter, Depends, HTTPException, Query, Path
|
5
|
-
from pydantic import BaseModel, Field
|
6
|
-
|
7
|
-
from cinchdb.core.database import CinchDB
|
8
|
-
from cinchdb.api.auth import (
|
9
|
-
AuthContext,
|
10
|
-
require_write_permission,
|
11
|
-
require_read_permission,
|
12
|
-
)
|
13
|
-
|
14
|
-
|
15
|
-
router = APIRouter()
|
16
|
-
|
17
|
-
|
18
|
-
class DataRecord(BaseModel):
|
19
|
-
"""Generic data record with dynamic fields."""
|
20
|
-
|
21
|
-
data: Dict[str, Any] = Field(description="Record data as key-value pairs")
|
22
|
-
|
23
|
-
class Config:
|
24
|
-
extra = "allow"
|
25
|
-
|
26
|
-
|
27
|
-
class CreateDataRequest(BaseModel):
|
28
|
-
"""Request to create a new data record."""
|
29
|
-
|
30
|
-
data: Dict[str, Any] = Field(description="Record data as key-value pairs")
|
31
|
-
|
32
|
-
|
33
|
-
class UpdateDataRequest(BaseModel):
|
34
|
-
"""Request to update an existing data record."""
|
35
|
-
|
36
|
-
data: Dict[str, Any] = Field(description="Record data to update")
|
37
|
-
|
38
|
-
|
39
|
-
class BulkCreateRequest(BaseModel):
|
40
|
-
"""Request to create multiple records."""
|
41
|
-
|
42
|
-
records: List[Dict[str, Any]] = Field(description="List of records to create")
|
43
|
-
|
44
|
-
|
45
|
-
class FilterParams(BaseModel):
|
46
|
-
"""Query parameters for filtering data."""
|
47
|
-
|
48
|
-
limit: Optional[int] = Field(
|
49
|
-
None, description="Maximum number of records to return"
|
50
|
-
)
|
51
|
-
offset: Optional[int] = Field(None, description="Number of records to skip")
|
52
|
-
filters: Optional[Dict[str, Any]] = Field(None, description="Column filters")
|
53
|
-
|
54
|
-
|
55
|
-
def parse_query_filters(
|
56
|
-
limit: Optional[int] = Query(
|
57
|
-
None, description="Maximum number of records to return"
|
58
|
-
),
|
59
|
-
offset: Optional[int] = Query(None, description="Number of records to skip"),
|
60
|
-
**query_params,
|
61
|
-
) -> Dict[str, Any]:
|
62
|
-
"""Parse query parameters into filters dictionary."""
|
63
|
-
filters = {}
|
64
|
-
|
65
|
-
# Remove pagination params and non-filter params
|
66
|
-
excluded_params = {"limit", "offset", "database", "branch", "tenant"}
|
67
|
-
|
68
|
-
for key, value in query_params.items():
|
69
|
-
if key not in excluded_params and value is not None:
|
70
|
-
filters[key] = value
|
71
|
-
|
72
|
-
return {"limit": limit, "offset": offset, "filters": filters}
|
73
|
-
|
74
|
-
|
75
|
-
# Helper function to create a generic Pydantic model for a table
|
76
|
-
def create_table_model(table_name: str) -> type:
|
77
|
-
"""Create a generic Pydantic model for a table."""
|
78
|
-
return type(
|
79
|
-
f"Table_{table_name}",
|
80
|
-
(BaseModel,),
|
81
|
-
{
|
82
|
-
"__annotations__": {"data": Dict[str, Any]},
|
83
|
-
"Config": type(
|
84
|
-
"Config",
|
85
|
-
(),
|
86
|
-
{"extra": "allow", "json_schema_extra": {"table_name": table_name}},
|
87
|
-
),
|
88
|
-
},
|
89
|
-
)
|
90
|
-
|
91
|
-
|
92
|
-
@router.get("/{table_name}/data", response_model=List[Dict[str, Any]])
|
93
|
-
async def list_table_data(
|
94
|
-
table_name: str = Path(..., description="Table name"),
|
95
|
-
database: str = Query(..., description="Database name"),
|
96
|
-
branch: str = Query(..., description="Branch name"),
|
97
|
-
tenant: str = Query(..., description="Tenant name"),
|
98
|
-
limit: Optional[int] = Query(
|
99
|
-
None, description="Maximum number of records to return"
|
100
|
-
),
|
101
|
-
offset: Optional[int] = Query(None, description="Number of records to skip"),
|
102
|
-
auth: AuthContext = Depends(require_read_permission),
|
103
|
-
):
|
104
|
-
"""List all records in a table with optional filtering and pagination."""
|
105
|
-
db_name = database
|
106
|
-
branch_name = branch
|
107
|
-
|
108
|
-
# Check branch permissions
|
109
|
-
await require_read_permission(auth, branch_name)
|
110
|
-
|
111
|
-
try:
|
112
|
-
# Create CinchDB instance
|
113
|
-
db = CinchDB(
|
114
|
-
database=db_name,
|
115
|
-
branch=branch_name,
|
116
|
-
tenant=tenant,
|
117
|
-
project_dir=auth.project_dir,
|
118
|
-
)
|
119
|
-
|
120
|
-
# Verify table exists
|
121
|
-
db.tables.get_table(
|
122
|
-
table_name
|
123
|
-
) # This will raise ValueError if table doesn't exist
|
124
|
-
|
125
|
-
# Create a generic model for the table
|
126
|
-
table_model = create_table_model(table_name)
|
127
|
-
|
128
|
-
# Parse filters from query parameters
|
129
|
-
# For simplicity, we'll handle basic filters directly in the query
|
130
|
-
# More complex filtering with operators would require parameter parsing
|
131
|
-
filters = {}
|
132
|
-
|
133
|
-
# Get records
|
134
|
-
records = db.data.select(table_model, limit=limit, offset=offset, **filters)
|
135
|
-
|
136
|
-
# Convert to dict format for response
|
137
|
-
return [
|
138
|
-
record.model_dump() if hasattr(record, "model_dump") else record.__dict__
|
139
|
-
for record in records
|
140
|
-
]
|
141
|
-
|
142
|
-
except ValueError as e:
|
143
|
-
raise HTTPException(status_code=404, detail=str(e))
|
144
|
-
|
145
|
-
|
146
|
-
@router.get("/{table_name}/data/{record_id}", response_model=Dict[str, Any])
|
147
|
-
async def get_record_by_id(
|
148
|
-
table_name: str = Path(..., description="Table name"),
|
149
|
-
record_id: str = Path(..., description="Record ID"),
|
150
|
-
database: str = Query(..., description="Database name"),
|
151
|
-
branch: str = Query(..., description="Branch name"),
|
152
|
-
tenant: str = Query(..., description="Tenant name"),
|
153
|
-
auth: AuthContext = Depends(require_read_permission),
|
154
|
-
):
|
155
|
-
"""Get a specific record by ID."""
|
156
|
-
db_name = database
|
157
|
-
branch_name = branch
|
158
|
-
|
159
|
-
# Check branch permissions
|
160
|
-
await require_read_permission(auth, branch_name)
|
161
|
-
|
162
|
-
try:
|
163
|
-
# Create CinchDB instance
|
164
|
-
db = CinchDB(
|
165
|
-
database=db_name,
|
166
|
-
branch=branch_name,
|
167
|
-
tenant=tenant,
|
168
|
-
project_dir=auth.project_dir,
|
169
|
-
)
|
170
|
-
|
171
|
-
# Verify table exists
|
172
|
-
db.tables.get_table(table_name)
|
173
|
-
|
174
|
-
table_model = create_table_model(table_name)
|
175
|
-
|
176
|
-
record = db.data.find_by_id(table_model, record_id)
|
177
|
-
|
178
|
-
if not record:
|
179
|
-
raise HTTPException(
|
180
|
-
status_code=404, detail=f"Record with ID {record_id} not found"
|
181
|
-
)
|
182
|
-
|
183
|
-
return record.model_dump() if hasattr(record, "model_dump") else record.__dict__
|
184
|
-
|
185
|
-
except ValueError as e:
|
186
|
-
raise HTTPException(status_code=404, detail=str(e))
|
187
|
-
|
188
|
-
|
189
|
-
@router.post("/{table_name}/data", response_model=Dict[str, Any])
|
190
|
-
async def create_record(
|
191
|
-
request: CreateDataRequest,
|
192
|
-
table_name: str = Path(..., description="Table name"),
|
193
|
-
database: str = Query(..., description="Database name"),
|
194
|
-
branch: str = Query(..., description="Branch name"),
|
195
|
-
tenant: str = Query(..., description="Tenant name"),
|
196
|
-
auth: AuthContext = Depends(require_write_permission),
|
197
|
-
):
|
198
|
-
"""Create a new record in the table."""
|
199
|
-
db_name = database
|
200
|
-
branch_name = branch
|
201
|
-
|
202
|
-
# Check branch permissions
|
203
|
-
await require_write_permission(auth, branch_name)
|
204
|
-
|
205
|
-
try:
|
206
|
-
# Create CinchDB instance
|
207
|
-
db = CinchDB(
|
208
|
-
database=db_name,
|
209
|
-
branch=branch_name,
|
210
|
-
tenant=tenant,
|
211
|
-
project_dir=auth.project_dir,
|
212
|
-
)
|
213
|
-
|
214
|
-
# Verify table exists
|
215
|
-
db.tables.get_table(table_name)
|
216
|
-
|
217
|
-
table_model = create_table_model(table_name)
|
218
|
-
|
219
|
-
# Create model instance from request data
|
220
|
-
instance = table_model(**request.data)
|
221
|
-
|
222
|
-
# Create the record
|
223
|
-
created_record = db.data.create(instance)
|
224
|
-
|
225
|
-
return (
|
226
|
-
created_record.model_dump()
|
227
|
-
if hasattr(created_record, "model_dump")
|
228
|
-
else created_record.__dict__
|
229
|
-
)
|
230
|
-
|
231
|
-
except ValueError as e:
|
232
|
-
if "already exists" in str(e):
|
233
|
-
raise HTTPException(status_code=409, detail=str(e))
|
234
|
-
raise HTTPException(status_code=400, detail=str(e))
|
235
|
-
|
236
|
-
|
237
|
-
@router.put("/{table_name}/data/{record_id}", response_model=Dict[str, Any])
|
238
|
-
async def update_record(
|
239
|
-
request: UpdateDataRequest,
|
240
|
-
table_name: str = Path(..., description="Table name"),
|
241
|
-
record_id: str = Path(..., description="Record ID"),
|
242
|
-
database: str = Query(..., description="Database name"),
|
243
|
-
branch: str = Query(..., description="Branch name"),
|
244
|
-
tenant: str = Query(..., description="Tenant name"),
|
245
|
-
auth: AuthContext = Depends(require_write_permission),
|
246
|
-
):
|
247
|
-
"""Update an existing record."""
|
248
|
-
db_name = database
|
249
|
-
branch_name = branch
|
250
|
-
|
251
|
-
# Check branch permissions
|
252
|
-
await require_write_permission(auth, branch_name)
|
253
|
-
|
254
|
-
try:
|
255
|
-
# Create CinchDB instance
|
256
|
-
db = CinchDB(
|
257
|
-
database=db_name,
|
258
|
-
branch=branch_name,
|
259
|
-
tenant=tenant,
|
260
|
-
project_dir=auth.project_dir,
|
261
|
-
)
|
262
|
-
|
263
|
-
# Verify table exists
|
264
|
-
db.tables.get_table(table_name)
|
265
|
-
|
266
|
-
table_model = create_table_model(table_name)
|
267
|
-
|
268
|
-
# Ensure the ID is in the data
|
269
|
-
update_data = request.data.copy()
|
270
|
-
update_data["id"] = record_id
|
271
|
-
|
272
|
-
# Create model instance
|
273
|
-
instance = table_model(**update_data)
|
274
|
-
|
275
|
-
# Update the record
|
276
|
-
updated_record = db.data.update(instance)
|
277
|
-
|
278
|
-
return (
|
279
|
-
updated_record.model_dump()
|
280
|
-
if hasattr(updated_record, "model_dump")
|
281
|
-
else updated_record.__dict__
|
282
|
-
)
|
283
|
-
|
284
|
-
except ValueError as e:
|
285
|
-
if "not found" in str(e):
|
286
|
-
raise HTTPException(status_code=404, detail=str(e))
|
287
|
-
raise HTTPException(status_code=400, detail=str(e))
|
288
|
-
|
289
|
-
|
290
|
-
@router.delete("/{table_name}/data/{record_id}")
|
291
|
-
async def delete_record(
|
292
|
-
table_name: str = Path(..., description="Table name"),
|
293
|
-
record_id: str = Path(..., description="Record ID"),
|
294
|
-
database: str = Query(..., description="Database name"),
|
295
|
-
branch: str = Query(..., description="Branch name"),
|
296
|
-
tenant: str = Query(..., description="Tenant name"),
|
297
|
-
auth: AuthContext = Depends(require_write_permission),
|
298
|
-
):
|
299
|
-
"""Delete a specific record by ID."""
|
300
|
-
db_name = database
|
301
|
-
branch_name = branch
|
302
|
-
|
303
|
-
# Check branch permissions
|
304
|
-
await require_write_permission(auth, branch_name)
|
305
|
-
|
306
|
-
try:
|
307
|
-
# Create CinchDB instance
|
308
|
-
db = CinchDB(
|
309
|
-
database=db_name,
|
310
|
-
branch=branch_name,
|
311
|
-
tenant=tenant,
|
312
|
-
project_dir=auth.project_dir,
|
313
|
-
)
|
314
|
-
|
315
|
-
# Verify table exists
|
316
|
-
db.tables.get_table(table_name)
|
317
|
-
|
318
|
-
table_model = create_table_model(table_name)
|
319
|
-
|
320
|
-
# Delete the record
|
321
|
-
deleted = db.data.delete_by_id(table_model, record_id)
|
322
|
-
|
323
|
-
if not deleted:
|
324
|
-
raise HTTPException(
|
325
|
-
status_code=404, detail=f"Record with ID {record_id} not found"
|
326
|
-
)
|
327
|
-
|
328
|
-
return {"message": f"Deleted record {record_id} from table '{table_name}'"}
|
329
|
-
|
330
|
-
except ValueError as e:
|
331
|
-
raise HTTPException(status_code=404, detail=str(e))
|
332
|
-
|
333
|
-
|
334
|
-
@router.post("/{table_name}/data/bulk", response_model=List[Dict[str, Any]])
|
335
|
-
async def bulk_create_records(
|
336
|
-
request: BulkCreateRequest,
|
337
|
-
table_name: str = Path(..., description="Table name"),
|
338
|
-
database: str = Query(..., description="Database name"),
|
339
|
-
branch: str = Query(..., description="Branch name"),
|
340
|
-
tenant: str = Query(..., description="Tenant name"),
|
341
|
-
auth: AuthContext = Depends(require_write_permission),
|
342
|
-
):
|
343
|
-
"""Create multiple records in a single transaction."""
|
344
|
-
db_name = database
|
345
|
-
branch_name = branch
|
346
|
-
|
347
|
-
# Check branch permissions
|
348
|
-
await require_write_permission(auth, branch_name)
|
349
|
-
|
350
|
-
try:
|
351
|
-
# Create CinchDB instance
|
352
|
-
db = CinchDB(
|
353
|
-
database=db_name,
|
354
|
-
branch=branch_name,
|
355
|
-
tenant=tenant,
|
356
|
-
project_dir=auth.project_dir,
|
357
|
-
)
|
358
|
-
|
359
|
-
# Verify table exists
|
360
|
-
db.tables.get_table(table_name)
|
361
|
-
|
362
|
-
table_model = create_table_model(table_name)
|
363
|
-
|
364
|
-
# Create model instances from request data
|
365
|
-
instances = [table_model(**record_data) for record_data in request.records]
|
366
|
-
|
367
|
-
# Bulk create
|
368
|
-
created_records = db.data.bulk_create(instances)
|
369
|
-
|
370
|
-
return [
|
371
|
-
record.model_dump() if hasattr(record, "model_dump") else record.__dict__
|
372
|
-
for record in created_records
|
373
|
-
]
|
374
|
-
|
375
|
-
except ValueError as e:
|
376
|
-
raise HTTPException(status_code=400, detail=str(e))
|
377
|
-
|
378
|
-
|
379
|
-
@router.delete("/{table_name}/data")
|
380
|
-
async def delete_records_with_filters(
|
381
|
-
table_name: str = Path(..., description="Table name"),
|
382
|
-
database: str = Query(..., description="Database name"),
|
383
|
-
branch: str = Query(..., description="Branch name"),
|
384
|
-
tenant: str = Query(..., description="Tenant name"),
|
385
|
-
auth: AuthContext = Depends(require_write_permission),
|
386
|
-
**query_params, # This will capture any additional query parameters as filters
|
387
|
-
):
|
388
|
-
"""Delete records matching filters. Requires at least one filter parameter."""
|
389
|
-
db_name = database
|
390
|
-
branch_name = branch
|
391
|
-
|
392
|
-
# Check branch permissions
|
393
|
-
await require_write_permission(auth, branch_name)
|
394
|
-
|
395
|
-
# Extract filters from query parameters
|
396
|
-
filters = {}
|
397
|
-
excluded_params = {"database", "branch", "tenant"}
|
398
|
-
|
399
|
-
for key, value in query_params.items():
|
400
|
-
if key not in excluded_params and value is not None:
|
401
|
-
filters[key] = value
|
402
|
-
|
403
|
-
if not filters:
|
404
|
-
raise HTTPException(
|
405
|
-
status_code=400,
|
406
|
-
detail="At least one filter parameter is required to prevent accidental deletion of all records",
|
407
|
-
)
|
408
|
-
|
409
|
-
try:
|
410
|
-
# Create CinchDB instance
|
411
|
-
db = CinchDB(
|
412
|
-
database=db_name,
|
413
|
-
branch=branch_name,
|
414
|
-
tenant=tenant,
|
415
|
-
project_dir=auth.project_dir,
|
416
|
-
)
|
417
|
-
|
418
|
-
# Verify table exists
|
419
|
-
db.tables.get_table(table_name)
|
420
|
-
|
421
|
-
table_model = create_table_model(table_name)
|
422
|
-
|
423
|
-
# Delete records with filters
|
424
|
-
deleted_count = db.data.delete(table_model, **filters)
|
425
|
-
|
426
|
-
return {
|
427
|
-
"message": f"Deleted {deleted_count} records from table '{table_name}'",
|
428
|
-
"count": deleted_count,
|
429
|
-
}
|
430
|
-
|
431
|
-
except ValueError as e:
|
432
|
-
raise HTTPException(status_code=400, detail=str(e))
|
433
|
-
|
434
|
-
|
435
|
-
@router.get("/{table_name}/data/count")
|
436
|
-
async def count_records(
|
437
|
-
table_name: str = Path(..., description="Table name"),
|
438
|
-
database: str = Query(..., description="Database name"),
|
439
|
-
branch: str = Query(..., description="Branch name"),
|
440
|
-
tenant: str = Query(..., description="Tenant name"),
|
441
|
-
auth: AuthContext = Depends(require_read_permission),
|
442
|
-
**query_params, # This will capture any additional query parameters as filters
|
443
|
-
):
|
444
|
-
"""Count records in a table with optional filtering."""
|
445
|
-
db_name = database
|
446
|
-
branch_name = branch
|
447
|
-
|
448
|
-
# Check branch permissions
|
449
|
-
await require_read_permission(auth, branch_name)
|
450
|
-
|
451
|
-
# Extract filters from query parameters
|
452
|
-
filters = {}
|
453
|
-
excluded_params = {"database", "branch", "tenant"}
|
454
|
-
|
455
|
-
for key, value in query_params.items():
|
456
|
-
if key not in excluded_params and value is not None:
|
457
|
-
filters[key] = value
|
458
|
-
|
459
|
-
try:
|
460
|
-
# Create CinchDB instance
|
461
|
-
db = CinchDB(
|
462
|
-
database=db_name,
|
463
|
-
branch=branch_name,
|
464
|
-
tenant=tenant,
|
465
|
-
project_dir=auth.project_dir,
|
466
|
-
)
|
467
|
-
|
468
|
-
# Verify table exists
|
469
|
-
db.tables.get_table(table_name)
|
470
|
-
|
471
|
-
table_model = create_table_model(table_name)
|
472
|
-
|
473
|
-
# Count records
|
474
|
-
count = db.data.count(table_model, **filters)
|
475
|
-
|
476
|
-
return {"count": count, "table": table_name, "filters": filters}
|
477
|
-
|
478
|
-
except ValueError as e:
|
479
|
-
raise HTTPException(status_code=404, detail=str(e))
|
cinchdb/api/routers/databases.py
DELETED
@@ -1,184 +0,0 @@
|
|
1
|
-
"""Databases router for CinchDB API."""
|
2
|
-
|
3
|
-
from typing import List
|
4
|
-
from fastapi import APIRouter, Depends, HTTPException
|
5
|
-
from pydantic import BaseModel
|
6
|
-
|
7
|
-
from cinchdb.config import Config
|
8
|
-
from cinchdb.core.path_utils import list_databases
|
9
|
-
from cinchdb.api.auth import (
|
10
|
-
AuthContext,
|
11
|
-
require_write_permission,
|
12
|
-
require_read_permission,
|
13
|
-
)
|
14
|
-
from cinchdb.utils.name_validator import validate_name, InvalidNameError
|
15
|
-
|
16
|
-
|
17
|
-
router = APIRouter()
|
18
|
-
|
19
|
-
|
20
|
-
class DatabaseInfo(BaseModel):
|
21
|
-
"""Database information."""
|
22
|
-
|
23
|
-
name: str
|
24
|
-
is_active: bool
|
25
|
-
is_protected: bool
|
26
|
-
branch_count: int
|
27
|
-
|
28
|
-
|
29
|
-
class CreateDatabaseRequest(BaseModel):
|
30
|
-
"""Request to create a database."""
|
31
|
-
|
32
|
-
name: str
|
33
|
-
description: str = None
|
34
|
-
|
35
|
-
|
36
|
-
@router.get("/", response_model=List[DatabaseInfo])
|
37
|
-
async def list_all_databases(auth: AuthContext = Depends(require_read_permission)):
|
38
|
-
"""List all databases in the project."""
|
39
|
-
config = Config(auth.project_dir)
|
40
|
-
config_data = config.load()
|
41
|
-
|
42
|
-
databases = list_databases(auth.project_dir)
|
43
|
-
|
44
|
-
result = []
|
45
|
-
for db_name in databases:
|
46
|
-
# Count branches
|
47
|
-
branches_path = (
|
48
|
-
auth.project_dir / ".cinchdb" / "databases" / db_name / "branches"
|
49
|
-
)
|
50
|
-
branch_count = (
|
51
|
-
len(list(branches_path.iterdir())) if branches_path.exists() else 0
|
52
|
-
)
|
53
|
-
|
54
|
-
result.append(
|
55
|
-
DatabaseInfo(
|
56
|
-
name=db_name,
|
57
|
-
is_active=db_name == config_data.active_database,
|
58
|
-
is_protected=db_name == "main",
|
59
|
-
branch_count=branch_count,
|
60
|
-
)
|
61
|
-
)
|
62
|
-
|
63
|
-
return result
|
64
|
-
|
65
|
-
|
66
|
-
@router.post("/")
|
67
|
-
async def create_database(
|
68
|
-
request: CreateDatabaseRequest,
|
69
|
-
auth: AuthContext = Depends(require_write_permission),
|
70
|
-
):
|
71
|
-
"""Create a new database."""
|
72
|
-
# Validate database name
|
73
|
-
try:
|
74
|
-
validate_name(request.name, "database")
|
75
|
-
except InvalidNameError as e:
|
76
|
-
raise HTTPException(status_code=400, detail=str(e))
|
77
|
-
|
78
|
-
config = Config(auth.project_dir)
|
79
|
-
|
80
|
-
# Create database directory structure
|
81
|
-
db_path = auth.project_dir / ".cinchdb" / "databases" / request.name
|
82
|
-
if db_path.exists():
|
83
|
-
raise HTTPException(
|
84
|
-
status_code=400, detail=f"Database '{request.name}' already exists"
|
85
|
-
)
|
86
|
-
|
87
|
-
try:
|
88
|
-
# Create the database structure
|
89
|
-
db_path.mkdir(parents=True)
|
90
|
-
branches_dir = db_path / "branches"
|
91
|
-
branches_dir.mkdir()
|
92
|
-
|
93
|
-
# Create main branch
|
94
|
-
main_branch = branches_dir / "main"
|
95
|
-
main_branch.mkdir()
|
96
|
-
|
97
|
-
# Create main tenant
|
98
|
-
tenants_dir = main_branch / "tenants"
|
99
|
-
tenants_dir.mkdir()
|
100
|
-
main_tenant = tenants_dir / "main.db"
|
101
|
-
main_tenant.touch()
|
102
|
-
|
103
|
-
# Create branch metadata
|
104
|
-
import json
|
105
|
-
from datetime import datetime, timezone
|
106
|
-
|
107
|
-
metadata = {
|
108
|
-
"name": "main",
|
109
|
-
"parent": None,
|
110
|
-
"created_at": datetime.now(timezone.utc).isoformat(),
|
111
|
-
}
|
112
|
-
with open(main_branch / "metadata.json", "w") as f:
|
113
|
-
json.dump(metadata, f, indent=2)
|
114
|
-
|
115
|
-
# Create empty changes file
|
116
|
-
with open(main_branch / "changes.json", "w") as f:
|
117
|
-
json.dump([], f)
|
118
|
-
|
119
|
-
return {"message": f"Created database '{request.name}'"}
|
120
|
-
|
121
|
-
except Exception as e:
|
122
|
-
# Clean up on failure
|
123
|
-
if db_path.exists():
|
124
|
-
import shutil
|
125
|
-
|
126
|
-
shutil.rmtree(db_path)
|
127
|
-
raise HTTPException(status_code=500, detail=f"Failed to create database: {e}")
|
128
|
-
|
129
|
-
|
130
|
-
@router.delete("/{name}")
|
131
|
-
async def delete_database(
|
132
|
-
name: str, auth: AuthContext = Depends(require_write_permission)
|
133
|
-
):
|
134
|
-
"""Delete a database."""
|
135
|
-
if name == "main":
|
136
|
-
raise HTTPException(status_code=400, detail="Cannot delete the main database")
|
137
|
-
|
138
|
-
config = Config(auth.project_dir)
|
139
|
-
db_path = auth.project_dir / ".cinchdb" / "databases" / name
|
140
|
-
|
141
|
-
if not db_path.exists():
|
142
|
-
raise HTTPException(status_code=404, detail=f"Database '{name}' not found")
|
143
|
-
|
144
|
-
try:
|
145
|
-
# Delete the database
|
146
|
-
import shutil
|
147
|
-
|
148
|
-
shutil.rmtree(db_path)
|
149
|
-
|
150
|
-
# If this was the active database, switch to main
|
151
|
-
config_data = config.load()
|
152
|
-
if config_data.active_database == name:
|
153
|
-
config_data.active_database = "main"
|
154
|
-
config_data.active_branch = "main"
|
155
|
-
config.save(config_data)
|
156
|
-
|
157
|
-
return {"message": f"Deleted database '{name}'"}
|
158
|
-
|
159
|
-
except Exception as e:
|
160
|
-
raise HTTPException(status_code=500, detail=f"Failed to delete database: {e}")
|
161
|
-
|
162
|
-
|
163
|
-
@router.get("/{name}")
|
164
|
-
async def get_database_info(
|
165
|
-
name: str, auth: AuthContext = Depends(require_read_permission)
|
166
|
-
) -> DatabaseInfo:
|
167
|
-
"""Get information about a specific database."""
|
168
|
-
db_path = auth.project_dir / ".cinchdb" / "databases" / name
|
169
|
-
if not db_path.exists():
|
170
|
-
raise HTTPException(status_code=404, detail=f"Database '{name}' not found")
|
171
|
-
|
172
|
-
config = Config(auth.project_dir)
|
173
|
-
config_data = config.load()
|
174
|
-
|
175
|
-
# Count branches
|
176
|
-
branches_path = db_path / "branches"
|
177
|
-
branch_count = len(list(branches_path.iterdir())) if branches_path.exists() else 0
|
178
|
-
|
179
|
-
return DatabaseInfo(
|
180
|
-
name=name,
|
181
|
-
is_active=name == config_data.active_database,
|
182
|
-
is_protected=name == "main",
|
183
|
-
branch_count=branch_count,
|
184
|
-
)
|