commiter-cli 0.3.0__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.
- commiter/__init__.py +3 -0
- commiter/adapters/__init__.py +0 -0
- commiter/adapters/base.py +96 -0
- commiter/adapters/django_rest.py +247 -0
- commiter/adapters/express.py +204 -0
- commiter/adapters/fastapi.py +170 -0
- commiter/adapters/flask.py +169 -0
- commiter/adapters/nextjs.py +180 -0
- commiter/adapters/prisma.py +76 -0
- commiter/adapters/raw_sql.py +191 -0
- commiter/adapters/react.py +129 -0
- commiter/adapters/sqlalchemy.py +99 -0
- commiter/adapters/supabase.py +68 -0
- commiter/auth.py +130 -0
- commiter/cli.py +667 -0
- commiter/correlator.py +208 -0
- commiter/extractors/__init__.py +0 -0
- commiter/extractors/api_calls.py +91 -0
- commiter/extractors/api_endpoints.py +354 -0
- commiter/extractors/backend_files.py +33 -0
- commiter/extractors/base.py +40 -0
- commiter/extractors/db_operations.py +69 -0
- commiter/extractors/dependencies.py +219 -0
- commiter/generic_resolver.py +204 -0
- commiter/handler_index.py +97 -0
- commiter/lib.py +63 -0
- commiter/middleware_index.py +350 -0
- commiter/models.py +117 -0
- commiter/parser.py +1283 -0
- commiter/prefix_index.py +211 -0
- commiter/report/__init__.py +0 -0
- commiter/report/ai.py +120 -0
- commiter/report/api_guide.py +217 -0
- commiter/report/architecture.py +930 -0
- commiter/report/console.py +254 -0
- commiter/report/json_output.py +122 -0
- commiter/report/markdown.py +163 -0
- commiter/scanner.py +383 -0
- commiter/type_index.py +304 -0
- commiter/uploader.py +46 -0
- commiter/utils/__init__.py +0 -0
- commiter/utils/env_reader.py +78 -0
- commiter/utils/file_classifier.py +187 -0
- commiter/utils/path_helpers.py +73 -0
- commiter/utils/tsconfig_resolver.py +281 -0
- commiter/wrapper_index.py +288 -0
- commiter_cli-0.3.0.dist-info/METADATA +14 -0
- commiter_cli-0.3.0.dist-info/RECORD +96 -0
- commiter_cli-0.3.0.dist-info/WHEEL +5 -0
- commiter_cli-0.3.0.dist-info/entry_points.txt +2 -0
- commiter_cli-0.3.0.dist-info/top_level.txt +2 -0
- tests/__init__.py +0 -0
- tests/fixtures/arch_backend/app.py +22 -0
- tests/fixtures/arch_backend/middleware/__init__.py +0 -0
- tests/fixtures/arch_backend/middleware/rate_limit.py +4 -0
- tests/fixtures/arch_backend/routes/__init__.py +0 -0
- tests/fixtures/arch_backend/routes/analytics.py +20 -0
- tests/fixtures/arch_backend/routes/auth.py +29 -0
- tests/fixtures/arch_backend/routes/projects.py +60 -0
- tests/fixtures/arch_backend/routes/users.py +55 -0
- tests/fixtures/arch_monorepo/apps/api/app.py +30 -0
- tests/fixtures/arch_monorepo/apps/api/middleware/__init__.py +0 -0
- tests/fixtures/arch_monorepo/apps/api/middleware/auth.py +17 -0
- tests/fixtures/arch_monorepo/apps/api/middleware/rate_limit.py +10 -0
- tests/fixtures/arch_monorepo/apps/api/routes/__init__.py +0 -0
- tests/fixtures/arch_monorepo/apps/api/routes/auth.py +46 -0
- tests/fixtures/arch_monorepo/apps/api/routes/invites.py +30 -0
- tests/fixtures/arch_monorepo/apps/api/routes/notifications.py +25 -0
- tests/fixtures/arch_monorepo/apps/api/routes/projects.py +80 -0
- tests/fixtures/arch_monorepo/apps/api/routes/tasks.py +91 -0
- tests/fixtures/arch_monorepo/apps/api/routes/users.py +48 -0
- tests/fixtures/arch_monorepo/apps/api/services/__init__.py +0 -0
- tests/fixtures/arch_monorepo/apps/api/services/email.py +11 -0
- tests/fixtures/backend_b/app.py +17 -0
- tests/fixtures/fastapi_app/app.py +48 -0
- tests/fixtures/fastapi_crossfile/routes.py +18 -0
- tests/fixtures/fastapi_crossfile/schemas.py +21 -0
- tests/fixtures/flask_app/app.py +33 -0
- tests/fixtures/flask_blueprint/app.py +7 -0
- tests/fixtures/flask_blueprint/routes/items.py +13 -0
- tests/fixtures/flask_blueprint/routes/users.py +20 -0
- tests/fixtures/middleware_test_flask/routes/public.py +8 -0
- tests/fixtures/middleware_test_flask/routes/users.py +26 -0
- tests/fixtures/python_deep_imports/app/__init__.py +0 -0
- tests/fixtures/python_deep_imports/app/api/__init__.py +0 -0
- tests/fixtures/python_deep_imports/app/api/health.py +11 -0
- tests/fixtures/python_deep_imports/app/api/v1/__init__.py +0 -0
- tests/fixtures/python_deep_imports/app/api/v1/items.py +18 -0
- tests/fixtures/python_deep_imports/app/api/v1/users.py +27 -0
- tests/fixtures/python_deep_imports/app/schemas/__init__.py +0 -0
- tests/fixtures/python_deep_imports/app/schemas/item.py +13 -0
- tests/fixtures/python_deep_imports/app/schemas/user.py +15 -0
- tests/fixtures/python_deep_imports/app/shared/__init__.py +0 -0
- tests/fixtures/python_deep_imports/app/shared/models.py +7 -0
- tests/fixtures/raw_sql_test/app.py +54 -0
- tests/test_architecture.py +757 -0
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
"""Wrapper function index: traces API calls through helper/wrapper functions.
|
|
2
|
+
|
|
3
|
+
Builds an index of exported JS/TS functions that internally call fetch() or axios,
|
|
4
|
+
then resolves calls to those wrappers from component files back to the underlying URLs.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import os
|
|
10
|
+
from dataclasses import dataclass, field
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
from typing import TYPE_CHECKING
|
|
13
|
+
|
|
14
|
+
from commiter.adapters.base import APICallMatch
|
|
15
|
+
from commiter.adapters.react import ReactAdapter
|
|
16
|
+
from commiter.parser import (
|
|
17
|
+
find_js_exported_functions,
|
|
18
|
+
find_js_re_exports,
|
|
19
|
+
build_constants_from_file,
|
|
20
|
+
get_source,
|
|
21
|
+
node_text,
|
|
22
|
+
JSReExport,
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
if TYPE_CHECKING:
|
|
26
|
+
from tree_sitter import Tree
|
|
27
|
+
from commiter.utils.tsconfig_resolver import TSConfigResolver
|
|
28
|
+
|
|
29
|
+
# Directories likely to contain API wrapper functions
|
|
30
|
+
WRAPPER_DIR_HINTS = {"lib", "services", "utils", "api", "helpers", "client", "hooks"}
|
|
31
|
+
|
|
32
|
+
# File extensions to try when resolving import paths
|
|
33
|
+
RESOLVE_EXTENSIONS = [".ts", ".tsx", ".js", ".jsx", "/index.ts", "/index.js", "/index.tsx"]
|
|
34
|
+
|
|
35
|
+
_react_adapter = ReactAdapter()
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
@dataclass
|
|
39
|
+
class WrapperDefinition:
|
|
40
|
+
"""An exported function that contains direct fetch/axios calls."""
|
|
41
|
+
function_name: str
|
|
42
|
+
file_path: str
|
|
43
|
+
line: int
|
|
44
|
+
api_calls: list[APICallMatch] = field(default_factory=list)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class WrapperIndex:
|
|
48
|
+
"""Index of API wrapper functions within a single repository.
|
|
49
|
+
|
|
50
|
+
Usage:
|
|
51
|
+
idx = WrapperIndex()
|
|
52
|
+
# Pass 1: index all potential wrapper files
|
|
53
|
+
for path in files:
|
|
54
|
+
idx.index_file(path, tree, source)
|
|
55
|
+
# Pass 2: resolve calls from components
|
|
56
|
+
calls = idx.resolve("getUser", "../lib/api", "/project/components/Dash.tsx")
|
|
57
|
+
"""
|
|
58
|
+
|
|
59
|
+
def __init__(self, alias_resolver: "TSConfigResolver | None" = None,
|
|
60
|
+
env_vars: dict[str, str] | None = None) -> None:
|
|
61
|
+
# func_name -> list of wrapper definitions (same name can exist in multiple files)
|
|
62
|
+
self._wrappers: dict[str, list[WrapperDefinition]] = {}
|
|
63
|
+
# normalized absolute path (no extension) -> actual absolute path
|
|
64
|
+
self._indexed_paths: dict[str, str] = {}
|
|
65
|
+
self._alias_resolver = alias_resolver
|
|
66
|
+
self._env_vars = env_vars or {}
|
|
67
|
+
# barrel file re-export cache: abs_path -> list[JSReExport]
|
|
68
|
+
self._re_exports: dict[str, list[JSReExport]] = {}
|
|
69
|
+
# unmatched exports for chained wrapper resolution
|
|
70
|
+
self._unmatched_exports: dict[str, list] = {}
|
|
71
|
+
|
|
72
|
+
def index_file(self, file_path: str, tree: Tree, source: bytes) -> None:
|
|
73
|
+
"""Pass 1: scan a file for exported functions containing API calls.
|
|
74
|
+
|
|
75
|
+
Scans all JS/TS files — the actual filter is whether exported functions
|
|
76
|
+
contain fetch()/axios calls, not which directory the file is in.
|
|
77
|
+
Unmatched exports are stored for chain resolution in resolve_chains().
|
|
78
|
+
"""
|
|
79
|
+
abs_path = os.path.abspath(file_path)
|
|
80
|
+
# Store path without extension for import resolution
|
|
81
|
+
stem = _strip_extension(abs_path)
|
|
82
|
+
self._indexed_paths[stem] = abs_path
|
|
83
|
+
# Also store index-stripped version: /lib/api/index.ts -> /lib/api
|
|
84
|
+
if Path(abs_path).stem == "index":
|
|
85
|
+
parent_stem = str(Path(abs_path).parent)
|
|
86
|
+
self._indexed_paths[parent_stem] = abs_path
|
|
87
|
+
|
|
88
|
+
# Cache re-exports for barrel file resolution
|
|
89
|
+
re_exports = find_js_re_exports(tree.root_node, source)
|
|
90
|
+
if re_exports:
|
|
91
|
+
self._re_exports[abs_path] = re_exports
|
|
92
|
+
|
|
93
|
+
# Build per-file constants for URL resolution in wrappers
|
|
94
|
+
constants = build_constants_from_file(tree.root_node, source, self._env_vars)
|
|
95
|
+
|
|
96
|
+
exports = find_js_exported_functions(tree.root_node, source, file_path=file_path)
|
|
97
|
+
for export in exports:
|
|
98
|
+
# Scan the exported function body for fetch/axios calls
|
|
99
|
+
fetch_calls = _react_adapter._find_fetch_calls(export.body_node, source, constants)
|
|
100
|
+
axios_calls = _react_adapter._find_axios_calls(export.body_node, source, constants)
|
|
101
|
+
all_calls = fetch_calls + axios_calls
|
|
102
|
+
|
|
103
|
+
if all_calls:
|
|
104
|
+
wrapper = WrapperDefinition(
|
|
105
|
+
function_name=export.name,
|
|
106
|
+
file_path=abs_path,
|
|
107
|
+
line=export.line,
|
|
108
|
+
api_calls=all_calls,
|
|
109
|
+
)
|
|
110
|
+
self._wrappers.setdefault(export.name, []).append(wrapper)
|
|
111
|
+
else:
|
|
112
|
+
# Store unmatched exports for chained wrapper resolution
|
|
113
|
+
body_text = node_text(export.body_node, source)
|
|
114
|
+
self._unmatched_exports.setdefault(abs_path, []).append(
|
|
115
|
+
(export, body_text, constants)
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
def resolve_chains(self) -> None:
|
|
119
|
+
"""Resolve chained wrappers: functions that call other wrapper functions.
|
|
120
|
+
|
|
121
|
+
Called by the scanner after all files have been indexed.
|
|
122
|
+
Exported functions that didn't directly contain fetch/axios calls are
|
|
123
|
+
re-checked to see if they call any known wrapper function.
|
|
124
|
+
"""
|
|
125
|
+
if not self._unmatched_exports:
|
|
126
|
+
return
|
|
127
|
+
|
|
128
|
+
# Build a set of all known wrapper function names
|
|
129
|
+
known_wrappers = set(self._wrappers.keys())
|
|
130
|
+
if not known_wrappers:
|
|
131
|
+
return
|
|
132
|
+
|
|
133
|
+
# Multiple passes to resolve chains of depth > 1
|
|
134
|
+
for _pass in range(3):
|
|
135
|
+
newly_resolved = 0
|
|
136
|
+
remaining: dict[str, list] = {}
|
|
137
|
+
|
|
138
|
+
for file_path, exports in self._unmatched_exports.items():
|
|
139
|
+
still_unmatched = []
|
|
140
|
+
for export, body_text, constants in exports:
|
|
141
|
+
# Check if the function body calls any known wrapper
|
|
142
|
+
found_calls = self._find_chained_calls(body_text, known_wrappers)
|
|
143
|
+
if found_calls:
|
|
144
|
+
wrapper = WrapperDefinition(
|
|
145
|
+
function_name=export.name,
|
|
146
|
+
file_path=file_path,
|
|
147
|
+
line=export.line,
|
|
148
|
+
api_calls=found_calls,
|
|
149
|
+
)
|
|
150
|
+
self._wrappers.setdefault(export.name, []).append(wrapper)
|
|
151
|
+
known_wrappers.add(export.name)
|
|
152
|
+
newly_resolved += 1
|
|
153
|
+
else:
|
|
154
|
+
still_unmatched.append((export, body_text, constants))
|
|
155
|
+
|
|
156
|
+
if still_unmatched:
|
|
157
|
+
remaining[file_path] = still_unmatched
|
|
158
|
+
|
|
159
|
+
self._unmatched_exports = remaining
|
|
160
|
+
if newly_resolved == 0:
|
|
161
|
+
break # no progress, stop
|
|
162
|
+
|
|
163
|
+
def _find_chained_calls(self, body_text: str, known_wrappers: set[str]) -> list:
|
|
164
|
+
"""Check if a function body calls any known wrapper function.
|
|
165
|
+
|
|
166
|
+
Returns the underlying API calls from the wrapper being called.
|
|
167
|
+
"""
|
|
168
|
+
all_calls = []
|
|
169
|
+
for wrapper_name in known_wrappers:
|
|
170
|
+
# Check if the wrapper name appears as a function call in the body
|
|
171
|
+
# Match: wrapperName( or obj.wrapperName(
|
|
172
|
+
if wrapper_name + "(" in body_text or "." + wrapper_name + "(" in body_text:
|
|
173
|
+
# Get the API calls from this wrapper
|
|
174
|
+
for wrapper_def in self._wrappers.get(wrapper_name, []):
|
|
175
|
+
all_calls.extend(wrapper_def.api_calls)
|
|
176
|
+
return all_calls
|
|
177
|
+
|
|
178
|
+
def resolve(self, func_name: str, import_path: str, caller_file: str) -> list[WrapperDefinition] | None:
|
|
179
|
+
"""Pass 2: resolve a function call to its wrapper definitions.
|
|
180
|
+
|
|
181
|
+
Args:
|
|
182
|
+
func_name: The imported function name (e.g. "getUser").
|
|
183
|
+
import_path: The import module path (e.g. "../lib/api").
|
|
184
|
+
caller_file: Absolute path of the file that imports it.
|
|
185
|
+
|
|
186
|
+
Returns:
|
|
187
|
+
List of matching WrapperDefinitions, or None if not a known wrapper.
|
|
188
|
+
"""
|
|
189
|
+
if func_name not in self._wrappers:
|
|
190
|
+
return None
|
|
191
|
+
|
|
192
|
+
resolved_path = self._resolve_import_path(import_path, caller_file)
|
|
193
|
+
if resolved_path is None:
|
|
194
|
+
return None
|
|
195
|
+
|
|
196
|
+
# Filter wrappers to those defined in the resolved file
|
|
197
|
+
matches = [
|
|
198
|
+
w for w in self._wrappers[func_name]
|
|
199
|
+
if w.file_path == resolved_path
|
|
200
|
+
]
|
|
201
|
+
if matches:
|
|
202
|
+
return matches
|
|
203
|
+
|
|
204
|
+
# Barrel file fallback: check if resolved_path re-exports this name
|
|
205
|
+
actual_path = self._resolve_through_barrel(func_name, resolved_path)
|
|
206
|
+
if actual_path:
|
|
207
|
+
matches = [
|
|
208
|
+
w for w in self._wrappers[func_name]
|
|
209
|
+
if w.file_path == actual_path
|
|
210
|
+
]
|
|
211
|
+
return matches if matches else None
|
|
212
|
+
|
|
213
|
+
return None
|
|
214
|
+
|
|
215
|
+
def _resolve_import_path(self, import_path: str, caller_file: str) -> str | None:
|
|
216
|
+
"""Resolve an import path to an absolute file path."""
|
|
217
|
+
if not import_path.startswith("."):
|
|
218
|
+
# Non-relative import — try tsconfig alias resolution
|
|
219
|
+
if self._alias_resolver is None:
|
|
220
|
+
return None
|
|
221
|
+
resolved = self._alias_resolver.resolve(import_path, caller_file)
|
|
222
|
+
if resolved:
|
|
223
|
+
stem = _strip_extension(resolved)
|
|
224
|
+
if stem in self._indexed_paths:
|
|
225
|
+
return self._indexed_paths[stem]
|
|
226
|
+
return None
|
|
227
|
+
|
|
228
|
+
caller_dir = str(Path(os.path.abspath(caller_file)).parent)
|
|
229
|
+
base = os.path.normpath(os.path.join(caller_dir, import_path))
|
|
230
|
+
|
|
231
|
+
# Try exact match first
|
|
232
|
+
if base in self._indexed_paths:
|
|
233
|
+
return self._indexed_paths[base]
|
|
234
|
+
|
|
235
|
+
# Try with extensions
|
|
236
|
+
for ext in RESOLVE_EXTENSIONS:
|
|
237
|
+
candidate = base + ext
|
|
238
|
+
candidate_stem = _strip_extension(candidate)
|
|
239
|
+
if candidate_stem in self._indexed_paths:
|
|
240
|
+
return self._indexed_paths[candidate_stem]
|
|
241
|
+
|
|
242
|
+
return None
|
|
243
|
+
|
|
244
|
+
def _resolve_through_barrel(self, name: str, barrel_path: str) -> str | None:
|
|
245
|
+
"""Follow re-exports in a barrel file to find the actual source file."""
|
|
246
|
+
re_exports = self._re_exports.get(barrel_path)
|
|
247
|
+
if not re_exports:
|
|
248
|
+
return None
|
|
249
|
+
|
|
250
|
+
barrel_dir = str(Path(barrel_path).parent)
|
|
251
|
+
|
|
252
|
+
for re_export in re_exports:
|
|
253
|
+
# Check if this re-export includes the name we're looking for
|
|
254
|
+
if name not in re_export.names and "*" not in re_export.names:
|
|
255
|
+
continue
|
|
256
|
+
|
|
257
|
+
# Resolve the re-export's module path relative to the barrel file
|
|
258
|
+
target_base = os.path.normpath(os.path.join(barrel_dir, re_export.module_path))
|
|
259
|
+
target_stem = _strip_extension(target_base)
|
|
260
|
+
|
|
261
|
+
if target_stem in self._indexed_paths:
|
|
262
|
+
return self._indexed_paths[target_stem]
|
|
263
|
+
|
|
264
|
+
# Try with extensions
|
|
265
|
+
for ext in RESOLVE_EXTENSIONS:
|
|
266
|
+
candidate_stem = _strip_extension(target_base + ext)
|
|
267
|
+
if candidate_stem in self._indexed_paths:
|
|
268
|
+
return self._indexed_paths[candidate_stem]
|
|
269
|
+
|
|
270
|
+
return None
|
|
271
|
+
|
|
272
|
+
def _is_wrapper_candidate(self, file_path: str) -> bool:
|
|
273
|
+
"""Check if a file is likely to contain API wrapper functions."""
|
|
274
|
+
parts = Path(file_path).parts
|
|
275
|
+
return any(part.lower() in WRAPPER_DIR_HINTS for part in parts)
|
|
276
|
+
|
|
277
|
+
@property
|
|
278
|
+
def wrapper_count(self) -> int:
|
|
279
|
+
"""Total number of indexed wrapper functions."""
|
|
280
|
+
return sum(len(defs) for defs in self._wrappers.values())
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
def _strip_extension(path: str) -> str:
|
|
284
|
+
"""Strip JS/TS file extensions from a path."""
|
|
285
|
+
p = Path(path)
|
|
286
|
+
while p.suffix in (".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"):
|
|
287
|
+
p = p.with_suffix("")
|
|
288
|
+
return str(p)
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: commiter-cli
|
|
3
|
+
Version: 0.3.0
|
|
4
|
+
Summary: Commiter CLI — scan repositories and enrich architecture snapshots on commiter.dev
|
|
5
|
+
Requires-Python: >=3.10
|
|
6
|
+
Requires-Dist: tree-sitter>=0.21.0
|
|
7
|
+
Requires-Dist: tree-sitter-python>=0.21.0
|
|
8
|
+
Requires-Dist: tree-sitter-javascript>=0.21.0
|
|
9
|
+
Requires-Dist: tree-sitter-typescript>=0.21.0
|
|
10
|
+
Requires-Dist: click>=8.0
|
|
11
|
+
Requires-Dist: rich>=13.0
|
|
12
|
+
Requires-Dist: tomli>=2.0; python_version < "3.11"
|
|
13
|
+
Provides-Extra: dev
|
|
14
|
+
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
commiter/__init__.py,sha256=akqY0gZgdXcMvoBSK0Yfel8t5IvTr7gpAh94E6oq-Y4,101
|
|
2
|
+
commiter/auth.py,sha256=wLeVWYTPIcXIZplh5WT29oOU3LnrhvwMicyJmjoiM_s,4222
|
|
3
|
+
commiter/cli.py,sha256=FqjmqR8Kjwq2WZhIEo3Chq6Bx9dXZFBc_s-zOu81cY0,15173
|
|
4
|
+
commiter/correlator.py,sha256=PMDhIJTZX8I9pQ_aQugoMWIS3qwMdb_hKPS8MiOl1xk,7130
|
|
5
|
+
commiter/generic_resolver.py,sha256=qPXMC6TjpZo32RiiWwpctnN_vMu9POtpaIb06t2G4vs,6142
|
|
6
|
+
commiter/handler_index.py,sha256=SSMOKI4pqKmtTDIMeEwyGQso-J3QYXqSv1RtcHuGo-Y,3180
|
|
7
|
+
commiter/lib.py,sha256=Ce6LYgvQMeYub3WFOPiT1TKSAOU2gglLEhY32G_cAAs,2082
|
|
8
|
+
commiter/middleware_index.py,sha256=L8LnjgkcELVBtHlr-q6S0GIxaKyW5UGKcHtepZr2mng,15530
|
|
9
|
+
commiter/models.py,sha256=8BftI08-zMyOMxMyeJ_7S_4JZSyAOQWGCyNQjhN_6aY,2852
|
|
10
|
+
commiter/parser.py,sha256=6G7TH-0crotF8BQVkTgpQXPap10l_5jhsqHZBHPQIpg,45982
|
|
11
|
+
commiter/prefix_index.py,sha256=Z5FME0iYkEC6twlXnBwdsjPgSoUty3mG7taWCU22yqM,9173
|
|
12
|
+
commiter/scanner.py,sha256=69o1HJNJtP8I36juD4KdHU8sXfAPFO7m3TuIMnfvI2U,14900
|
|
13
|
+
commiter/type_index.py,sha256=jqqAvMZKgeo3jWAk_vMspi5GtX0IwK-u08LRF63XHS4,12458
|
|
14
|
+
commiter/uploader.py,sha256=6Wa1F00Tw9yzVx2VVAK4PZEh82zlPpq23xtmVnp83D8,1564
|
|
15
|
+
commiter/wrapper_index.py,sha256=B5JGCi0NRRzNnlEIUn_pdEHWhkKI3rSS7XY_xKGtKik,11636
|
|
16
|
+
commiter/adapters/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
17
|
+
commiter/adapters/base.py,sha256=YE11QC71pbj9JqnURgLRlP4mZL5g626hYU0bjQnBg6k,2925
|
|
18
|
+
commiter/adapters/django_rest.py,sha256=3IRKAA8oQW2mn-H1XqS_jzpJ7aJqvqF56gJl_BEeCmA,10023
|
|
19
|
+
commiter/adapters/express.py,sha256=tsZeQh_O-Ypo3dq07HsDDeOXdbRgNeePpB-FyC9jnbw,8384
|
|
20
|
+
commiter/adapters/fastapi.py,sha256=Xn7SzuNKBFtIIw8GblFL9giqfKF8o_rAjBC9yYOX93Y,6289
|
|
21
|
+
commiter/adapters/flask.py,sha256=PNKcHC0ZHYkmDXaotkHXziM9ix3U4BA1j_vWfHOvgBY,6089
|
|
22
|
+
commiter/adapters/nextjs.py,sha256=EfKhJQSIq-haAUm8uKY_GDL4gRtDBcz61zhcNcO-z4E,7504
|
|
23
|
+
commiter/adapters/prisma.py,sha256=1VV-_gsjMm09LY_t729RqSsmw2IQi8e6F75FVHaHTdY,2243
|
|
24
|
+
commiter/adapters/raw_sql.py,sha256=yrAVLf-KqZDIddYPeMb5Beqg-74esKo5W9GrJRJjZi8,6743
|
|
25
|
+
commiter/adapters/react.py,sha256=R-lX8kLCs5kVgAtHLrXVuBUnAPQ5XyjH9lCsXwT44R0,4825
|
|
26
|
+
commiter/adapters/sqlalchemy.py,sha256=DLqAAlPtHIE_upLS0I6MXJ5L3iiiyVC0cFSGANdKS8Y,3148
|
|
27
|
+
commiter/adapters/supabase.py,sha256=nAdPrFfR47rBLxsEAPPQgXLFNuldZmqwrfVEf82cW38,2350
|
|
28
|
+
commiter/extractors/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
29
|
+
commiter/extractors/api_calls.py,sha256=EvHeyVbfmG7XTpr9A-j53QLgTL6uNvmAEyuYQAZF34s,3420
|
|
30
|
+
commiter/extractors/api_endpoints.py,sha256=7Rt-yzyJNbvPYVr6KgqLBi8uJHmZR2AISxVsEYLZyek,15144
|
|
31
|
+
commiter/extractors/backend_files.py,sha256=D4RTYDee2kqyw4Al1Uued9qyJbS5V6RsFaHfpfbLC3k,1269
|
|
32
|
+
commiter/extractors/base.py,sha256=oKWcgJMsyJxNs2Mms_8EykLySi62gYzzG7uSvg2SQks,1286
|
|
33
|
+
commiter/extractors/db_operations.py,sha256=iwOTN8DuuSyuVDLzFCjEtYkK8Z2RzXPa9EBiZGYCE7A,2524
|
|
34
|
+
commiter/extractors/dependencies.py,sha256=X_mIV6gzgkKwRk66HRq3JtXYMxp_xZqowPh7yIbEQrw,8933
|
|
35
|
+
commiter/report/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
36
|
+
commiter/report/ai.py,sha256=pDrVe8v770rGbqhx7KMJ9BRpoWoSLOKJuDq-kuv_wzY,4497
|
|
37
|
+
commiter/report/api_guide.py,sha256=qro4j9dKDY5nt7ga68iBjpZGjVBuYyYA_X5M3h6zSbg,7049
|
|
38
|
+
commiter/report/architecture.py,sha256=vNnOhp6aIm7uKQ67QV5O95JrVsAwhggpVAbB7hgmSTA,34369
|
|
39
|
+
commiter/report/console.py,sha256=9BHwg__YNVNIqk89eqtbfL4zOWhceuCzC3iI0cWidEw,9544
|
|
40
|
+
commiter/report/json_output.py,sha256=KVPtPMlXvQW_NgrWt0qYZeSvImhBY30o70AiA_Zu7h4,4247
|
|
41
|
+
commiter/report/markdown.py,sha256=ZlmUWejjeOgEPQ3zfC-yqiBeCQo7MWdqOffeFml3brg,6092
|
|
42
|
+
commiter/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
43
|
+
commiter/utils/env_reader.py,sha256=p1475wifO226p5a5EHSlRD70AWhEbx_PaqhGyebdpw8,2318
|
|
44
|
+
commiter/utils/file_classifier.py,sha256=VHoyAPcCXn9hWDKdUjsGxnp6SUDWUwJW065rZjRFmcA,6620
|
|
45
|
+
commiter/utils/path_helpers.py,sha256=zrERWLwwjNj4_6G3DoSQt3AabdsByVEGa4IsyOjRAIo,2140
|
|
46
|
+
commiter/utils/tsconfig_resolver.py,sha256=lDtrSKe48e9q_-4qhZkI0Pw4cK6YfmiZBSLVjJjHVe4,9813
|
|
47
|
+
tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
48
|
+
tests/test_architecture.py,sha256=xAx6ol3t7i8MbRtKqtHMd2TQo5p5icyr4c5Tpu4kcDs,31654
|
|
49
|
+
tests/fixtures/arch_backend/app.py,sha256=gSsfB3rQbLahdI3Ob4pEp-MpD8A6vuxOMnogNCVGxA8,683
|
|
50
|
+
tests/fixtures/arch_backend/middleware/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
51
|
+
tests/fixtures/arch_backend/middleware/rate_limit.py,sha256=hat4OUddjtd8bTnsFqBT2DqBBXfXGgxb1LktJSEhLto,103
|
|
52
|
+
tests/fixtures/arch_backend/routes/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
53
|
+
tests/fixtures/arch_backend/routes/analytics.py,sha256=R5hyIlFFowHg7q76SVj3QCNXWWsnpnK0R8U-XjYzyco,679
|
|
54
|
+
tests/fixtures/arch_backend/routes/auth.py,sha256=GnlDo8r0N1pdF-LO1SFnHEQRgv0njZjH7pJR7lFXJ70,1113
|
|
55
|
+
tests/fixtures/arch_backend/routes/projects.py,sha256=9vK4j23PFyxMw5pOE5hyOBEY92sWOV7Jo1T8oLlaibQ,2018
|
|
56
|
+
tests/fixtures/arch_backend/routes/users.py,sha256=7WivpdDMLo8l958vg5pLwrhKk0SFcZe9cv8wFaLFx0g,1733
|
|
57
|
+
tests/fixtures/arch_monorepo/apps/api/app.py,sha256=jBufss2dNYrY3otjVrPxCcqjFS4V77cxMS_qNh8FQ44,963
|
|
58
|
+
tests/fixtures/arch_monorepo/apps/api/middleware/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
59
|
+
tests/fixtures/arch_monorepo/apps/api/middleware/auth.py,sha256=N0IVIS5nRwRfnB-PTMDYlxkudSb7SnA14rlblSo47sY,584
|
|
60
|
+
tests/fixtures/arch_monorepo/apps/api/middleware/rate_limit.py,sha256=MxA_Li2qxAeZ1wvYYEQZROio8iHV1BF1ul9YUFrxaNc,271
|
|
61
|
+
tests/fixtures/arch_monorepo/apps/api/routes/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
62
|
+
tests/fixtures/arch_monorepo/apps/api/routes/auth.py,sha256=zKHlSGffcZySsKM3yChla0HCEtevQ8mqvMx1k4J-znM,1572
|
|
63
|
+
tests/fixtures/arch_monorepo/apps/api/routes/invites.py,sha256=3kgBa7PflXaMPNCZJhsIwzmrdcl5L9FeAdHuGIMhUQE,967
|
|
64
|
+
tests/fixtures/arch_monorepo/apps/api/routes/notifications.py,sha256=3ulnftBHAWHdogUH2ADj6sbXnkx_oIOgYtAHtKzbRNk,899
|
|
65
|
+
tests/fixtures/arch_monorepo/apps/api/routes/projects.py,sha256=83xiFFFNO4bVMeXGQrY_2B0KEZDRUlD5rTbAnPvdQks,3001
|
|
66
|
+
tests/fixtures/arch_monorepo/apps/api/routes/tasks.py,sha256=OHx_Z-90SMk8VEOErw52tb46LKuKKNlb8McO020u7Tc,3098
|
|
67
|
+
tests/fixtures/arch_monorepo/apps/api/routes/users.py,sha256=hRHlLldKPr0IaK0rLPWVvB_g7szXjF8CQSe6wowjJVQ,1518
|
|
68
|
+
tests/fixtures/arch_monorepo/apps/api/services/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
69
|
+
tests/fixtures/arch_monorepo/apps/api/services/email.py,sha256=ej056Mh71IVoOfNUYC7evmTxr1Rre8Swx5-SW23_NrI,311
|
|
70
|
+
tests/fixtures/backend_b/app.py,sha256=KrusADfRoADGFxbDuWBHuFJcdYuDFCB1Dh7zJ-PKzTE,620
|
|
71
|
+
tests/fixtures/fastapi_app/app.py,sha256=HBAGTumuLPuS9i7vv0F-XHqB9Gzb2dUycPojqEKYmAo,1005
|
|
72
|
+
tests/fixtures/fastapi_crossfile/routes.py,sha256=DL4eh_3OaLy-Wm0Ogp3jJbo1BOVZo20SJ82L7Fh0tYE,409
|
|
73
|
+
tests/fixtures/fastapi_crossfile/schemas.py,sha256=Bqs-RMfN1JniVW0GoVyXNeFWmtamV6FoQVgUzgezsgw,375
|
|
74
|
+
tests/fixtures/flask_app/app.py,sha256=PLba9Sz99b3fDMSCMp7hKy3b0QTCwP6nxZx15qbfsW4,990
|
|
75
|
+
tests/fixtures/flask_blueprint/app.py,sha256=k8bvY-iCrzNTigS-vMxtB9hvOcyCRMaMmrBnySefqUM,246
|
|
76
|
+
tests/fixtures/flask_blueprint/routes/items.py,sha256=5qyjOk-EIh_51iPpf7RUMuw7SO9pBPcViylUtxCy3EY,264
|
|
77
|
+
tests/fixtures/flask_blueprint/routes/users.py,sha256=0rJwDeynoOej4K1INRJSsP-datsYjXdDdd39emYQrGQ,472
|
|
78
|
+
tests/fixtures/middleware_test_flask/routes/public.py,sha256=y7Qpn7C-cfidH84I0fYHe4c3z1Dq1_92k4DHSofRDww,167
|
|
79
|
+
tests/fixtures/middleware_test_flask/routes/users.py,sha256=h5hiXzOckXaKmzuPjUed5d91pRQGvxMoTdkXl1GObuI,623
|
|
80
|
+
tests/fixtures/python_deep_imports/app/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
81
|
+
tests/fixtures/python_deep_imports/app/api/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
82
|
+
tests/fixtures/python_deep_imports/app/api/health.py,sha256=Gi6BcH25pT52fytzPAhW9n5rfJWYTbymEGMLeaeShL8,255
|
|
83
|
+
tests/fixtures/python_deep_imports/app/api/v1/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
84
|
+
tests/fixtures/python_deep_imports/app/api/v1/items.py,sha256=T6W5rERDi1BWUnz91UAod4R-tlmj3pveY9yuhRcdEss,533
|
|
85
|
+
tests/fixtures/python_deep_imports/app/api/v1/users.py,sha256=p040s51LK6yyFBLx2e-LXR70-DHYWRRZkinl8tiS760,892
|
|
86
|
+
tests/fixtures/python_deep_imports/app/schemas/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
87
|
+
tests/fixtures/python_deep_imports/app/schemas/item.py,sha256=VWmjI_A78OQR10eOskum9yGvGkZ-DiY4XE6G24bw0dw,268
|
|
88
|
+
tests/fixtures/python_deep_imports/app/schemas/user.py,sha256=JM4u4nxdjuGEJoqpegvsNZIV1p6Kfa6LemKmCS7zhUA,269
|
|
89
|
+
tests/fixtures/python_deep_imports/app/shared/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
90
|
+
tests/fixtures/python_deep_imports/app/shared/models.py,sha256=MqAwNXRc4T2m1znLOgpeo4Q5awqNOx9-WHr2QHb7U6Y,148
|
|
91
|
+
tests/fixtures/raw_sql_test/app.py,sha256=PK0EuSgSQRt1QFX3sf9DlIubEOlUbQJ_emAtwbtRzPo,1665
|
|
92
|
+
commiter_cli-0.3.0.dist-info/METADATA,sha256=etW7HEWkKq_Fan97Nps-jybqbwwdYDN0Gu-n5Esprz8,523
|
|
93
|
+
commiter_cli-0.3.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
|
|
94
|
+
commiter_cli-0.3.0.dist-info/entry_points.txt,sha256=rfydR0cFujz7uMaNk3jquW3lBGB1JxITlbQv-SG80AU,47
|
|
95
|
+
commiter_cli-0.3.0.dist-info/top_level.txt,sha256=FSBMqcZffZuLmPcEgl2XsjzHNI_CbnwrWWHvzf6CwBM,15
|
|
96
|
+
commiter_cli-0.3.0.dist-info/RECORD,,
|
tests/__init__.py
ADDED
|
File without changes
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
from flask import Flask
|
|
2
|
+
from flask_cors import CORS
|
|
3
|
+
from routes.auth import auth_bp
|
|
4
|
+
from routes.users import users_bp
|
|
5
|
+
from routes.projects import projects_bp
|
|
6
|
+
from routes.analytics import analytics_bp
|
|
7
|
+
from middleware.rate_limit import rate_limit
|
|
8
|
+
|
|
9
|
+
app = Flask(__name__)
|
|
10
|
+
CORS(app, supports_credentials=True, origins=["https://myapp.vercel.app"])
|
|
11
|
+
|
|
12
|
+
@app.before_request
|
|
13
|
+
def log_request():
|
|
14
|
+
pass
|
|
15
|
+
|
|
16
|
+
app.register_blueprint(auth_bp, url_prefix="/v1/auth")
|
|
17
|
+
app.register_blueprint(users_bp, url_prefix="/v1/users")
|
|
18
|
+
app.register_blueprint(projects_bp, url_prefix="/v1/projects")
|
|
19
|
+
app.register_blueprint(analytics_bp, url_prefix="/v1/analytics")
|
|
20
|
+
|
|
21
|
+
if __name__ == "__main__":
|
|
22
|
+
app.run(port=5000)
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
from flask import Blueprint, request, jsonify
|
|
2
|
+
|
|
3
|
+
analytics_bp = Blueprint("analytics", __name__)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
@analytics_bp.route("/commits", methods=["GET"])
|
|
7
|
+
@login_required
|
|
8
|
+
def get_commit_stats():
|
|
9
|
+
"""Get commit analysis for a project."""
|
|
10
|
+
project_id = request.args.get("project_id")
|
|
11
|
+
stats = db.table("commit_analysis").select("*").eq("project_id", project_id).execute()
|
|
12
|
+
return jsonify(stats=stats.data)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@analytics_bp.route("/activity", methods=["GET"])
|
|
16
|
+
@login_required
|
|
17
|
+
def get_activity():
|
|
18
|
+
"""Get recent activity feed for the user."""
|
|
19
|
+
activity = db.table("activity_log").select("*").eq("user_id", request.user_id).execute()
|
|
20
|
+
return jsonify(activity=activity.data)
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
from flask import Blueprint, request, jsonify
|
|
2
|
+
|
|
3
|
+
auth_bp = Blueprint("auth", __name__)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
@auth_bp.route("/login", methods=["POST"])
|
|
7
|
+
def login():
|
|
8
|
+
"""Exchange credentials for a JWT token."""
|
|
9
|
+
email = request.json["email"]
|
|
10
|
+
password = request.json["password"]
|
|
11
|
+
user = db.table("users").select("*").eq("email", email).single().execute()
|
|
12
|
+
session = db.table("sessions").insert({"user_id": user.data["id"]}).execute()
|
|
13
|
+
return jsonify(access_token="jwt...", expires_in=3600)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@auth_bp.route("/refresh", methods=["POST"])
|
|
17
|
+
def refresh_token():
|
|
18
|
+
"""Refresh an expired access token using the refresh cookie."""
|
|
19
|
+
session = db.table("sessions").select("*").eq("token", request.cookies.get("refresh")).single().execute()
|
|
20
|
+
db.table("sessions").update({"refreshed_at": "now()"}).eq("id", session.data["id"]).execute()
|
|
21
|
+
return jsonify(access_token="jwt...", expires_in=3600)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@auth_bp.route("/logout", methods=["POST"])
|
|
25
|
+
@login_required
|
|
26
|
+
def logout():
|
|
27
|
+
"""Invalidate the current session."""
|
|
28
|
+
db.table("sessions").delete().eq("user_id", request.user_id).execute()
|
|
29
|
+
return jsonify(success=True)
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
from flask import Blueprint, request, jsonify
|
|
2
|
+
|
|
3
|
+
projects_bp = Blueprint("projects", __name__)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
@projects_bp.route("/", methods=["GET"])
|
|
7
|
+
@login_required
|
|
8
|
+
def list_projects():
|
|
9
|
+
"""List projects for the authenticated user."""
|
|
10
|
+
projects = db.table("projects").select("*").eq("owner_id", request.user_id).execute()
|
|
11
|
+
return jsonify(projects=projects.data)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@projects_bp.route("/", methods=["POST"])
|
|
15
|
+
@login_required
|
|
16
|
+
def create_project():
|
|
17
|
+
"""Create a new project."""
|
|
18
|
+
name = request.json["name"]
|
|
19
|
+
description = request.json.get("description", "")
|
|
20
|
+
repo_url = request.json.get("repo_url")
|
|
21
|
+
project = db.table("projects").insert({
|
|
22
|
+
"name": name,
|
|
23
|
+
"description": description,
|
|
24
|
+
"repo_url": repo_url,
|
|
25
|
+
"owner_id": request.user_id,
|
|
26
|
+
}).execute()
|
|
27
|
+
return jsonify(project=project.data[0])
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@projects_bp.route("/<project_id>", methods=["GET"])
|
|
31
|
+
@login_required
|
|
32
|
+
def get_project(project_id):
|
|
33
|
+
"""Get a single project with its milestones."""
|
|
34
|
+
project = db.table("projects").select("*").eq("id", project_id).single().execute()
|
|
35
|
+
milestones = db.table("milestones").select("*").eq("project_id", project_id).execute()
|
|
36
|
+
return jsonify(project=project.data, milestones=milestones.data)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@projects_bp.route("/<project_id>", methods=["DELETE"])
|
|
40
|
+
@login_required
|
|
41
|
+
def delete_project(project_id):
|
|
42
|
+
"""Delete a project and its milestones."""
|
|
43
|
+
db.table("milestones").delete().eq("project_id", project_id).execute()
|
|
44
|
+
db.table("projects").delete().eq("id", project_id).execute()
|
|
45
|
+
return jsonify(success=True)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
@projects_bp.route("/<project_id>/milestones", methods=["POST"])
|
|
49
|
+
@login_required
|
|
50
|
+
def create_milestone(project_id):
|
|
51
|
+
"""Add a milestone to a project."""
|
|
52
|
+
title = request.json["title"]
|
|
53
|
+
due_date = request.json.get("due_date")
|
|
54
|
+
milestone = db.table("milestones").insert({
|
|
55
|
+
"project_id": project_id,
|
|
56
|
+
"title": title,
|
|
57
|
+
"due_date": due_date,
|
|
58
|
+
"status": "open",
|
|
59
|
+
}).execute()
|
|
60
|
+
return jsonify(milestone=milestone.data[0])
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
from flask import Blueprint, request, jsonify
|
|
2
|
+
|
|
3
|
+
users_bp = Blueprint("users", __name__)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
@users_bp.route("/", methods=["GET"])
|
|
7
|
+
@login_required
|
|
8
|
+
def list_users():
|
|
9
|
+
"""List all users with pagination."""
|
|
10
|
+
users = db.table("users").select("id, display_name, avatar_url").execute()
|
|
11
|
+
return jsonify(users=users.data, total=len(users.data))
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@users_bp.route("/<user_id>", methods=["GET"])
|
|
15
|
+
@login_required
|
|
16
|
+
def get_user(user_id):
|
|
17
|
+
"""Get a single user profile."""
|
|
18
|
+
user = db.table("users").select("*").eq("id", user_id).single().execute()
|
|
19
|
+
return jsonify(user=user.data)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@users_bp.route("/me", methods=["PUT"])
|
|
23
|
+
@login_required
|
|
24
|
+
def update_profile():
|
|
25
|
+
"""Update the authenticated user's profile."""
|
|
26
|
+
display_name = request.json.get("display_name")
|
|
27
|
+
bio = request.json.get("bio")
|
|
28
|
+
avatar_url = request.json.get("avatar_url")
|
|
29
|
+
user = db.table("users").update({
|
|
30
|
+
"display_name": display_name,
|
|
31
|
+
"bio": bio,
|
|
32
|
+
"avatar_url": avatar_url,
|
|
33
|
+
}).eq("id", request.user_id).execute()
|
|
34
|
+
return jsonify(user=user.data[0])
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@users_bp.route("/me/connections", methods=["GET"])
|
|
38
|
+
@login_required
|
|
39
|
+
def list_connections():
|
|
40
|
+
"""List the authenticated user's connections."""
|
|
41
|
+
connections = db.table("connections").select("*").eq("user_id", request.user_id).execute()
|
|
42
|
+
return jsonify(connections=connections.data)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
@users_bp.route("/me/connections", methods=["POST"])
|
|
46
|
+
@login_required
|
|
47
|
+
def create_connection():
|
|
48
|
+
"""Send a connection request."""
|
|
49
|
+
target_id = request.json["target_user_id"]
|
|
50
|
+
conn = db.table("connections").insert({
|
|
51
|
+
"user_id": request.user_id,
|
|
52
|
+
"target_user_id": target_id,
|
|
53
|
+
"status": "pending",
|
|
54
|
+
}).execute()
|
|
55
|
+
return jsonify(connection=conn.data[0])
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
from flask import Flask
|
|
2
|
+
from flask_cors import CORS
|
|
3
|
+
from routes.auth import auth_bp
|
|
4
|
+
from routes.users import users_bp
|
|
5
|
+
from routes.projects import projects_bp
|
|
6
|
+
from routes.tasks import tasks_bp
|
|
7
|
+
from routes.notifications import notifications_bp
|
|
8
|
+
from routes.invites import invites_bp
|
|
9
|
+
from middleware.auth import require_auth
|
|
10
|
+
from middleware.rate_limit import rate_limit
|
|
11
|
+
|
|
12
|
+
app = Flask(__name__)
|
|
13
|
+
CORS(app, supports_credentials=True, origins=[
|
|
14
|
+
"https://teamboard.dev",
|
|
15
|
+
"http://localhost:3000",
|
|
16
|
+
])
|
|
17
|
+
|
|
18
|
+
@app.before_request
|
|
19
|
+
def log_request():
|
|
20
|
+
pass
|
|
21
|
+
|
|
22
|
+
app.register_blueprint(auth_bp, url_prefix="/v1/auth")
|
|
23
|
+
app.register_blueprint(users_bp, url_prefix="/v1/users")
|
|
24
|
+
app.register_blueprint(projects_bp, url_prefix="/v1/projects")
|
|
25
|
+
app.register_blueprint(tasks_bp, url_prefix="/v1/tasks")
|
|
26
|
+
app.register_blueprint(notifications_bp, url_prefix="/v1/notifications")
|
|
27
|
+
app.register_blueprint(invites_bp, url_prefix="/v1/invites")
|
|
28
|
+
|
|
29
|
+
if __name__ == "__main__":
|
|
30
|
+
app.run(port=5000)
|
|
File without changes
|