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,17 @@
|
|
|
1
|
+
import jwt
|
|
2
|
+
from functools import wraps
|
|
3
|
+
from flask import request, jsonify
|
|
4
|
+
|
|
5
|
+
def require_auth(f):
|
|
6
|
+
@wraps(f)
|
|
7
|
+
def decorated(*args, **kwargs):
|
|
8
|
+
token = request.headers.get("Authorization", "").replace("Bearer ", "")
|
|
9
|
+
if not token:
|
|
10
|
+
return jsonify(error="Missing token"), 401
|
|
11
|
+
try:
|
|
12
|
+
payload = jwt.decode(token, "secret", algorithms=["HS256"])
|
|
13
|
+
request.user_id = payload["sub"]
|
|
14
|
+
except jwt.ExpiredSignatureError:
|
|
15
|
+
return jsonify(error="Token expired"), 401
|
|
16
|
+
return f(*args, **kwargs)
|
|
17
|
+
return decorated
|
|
File without changes
|
|
@@ -0,0 +1,46 @@
|
|
|
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
|
+
email = request.json["email"]
|
|
9
|
+
password = request.json["password"]
|
|
10
|
+
user = db.table("users").select("*").eq("email", email).single().execute()
|
|
11
|
+
session = db.table("sessions").insert({
|
|
12
|
+
"user_id": user.data["id"],
|
|
13
|
+
"ip_address": request.remote_addr,
|
|
14
|
+
}).execute()
|
|
15
|
+
return jsonify(access_token="jwt...", refresh_token="rt...", expires_in=3600)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@auth_bp.route("/refresh", methods=["POST"])
|
|
19
|
+
def refresh_token():
|
|
20
|
+
refresh = request.cookies.get("refresh_token")
|
|
21
|
+
session = db.table("sessions").select("*").eq("refresh_token", refresh).single().execute()
|
|
22
|
+
db.table("sessions").update({"refreshed_at": "now()"}).eq("id", session.data["id"]).execute()
|
|
23
|
+
return jsonify(access_token="jwt...", expires_in=3600)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@auth_bp.route("/logout", methods=["POST"])
|
|
27
|
+
@login_required
|
|
28
|
+
def logout():
|
|
29
|
+
db.table("sessions").delete().eq("user_id", request.user_id).execute()
|
|
30
|
+
return jsonify(success=True)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@auth_bp.route("/signup", methods=["POST"])
|
|
34
|
+
def signup():
|
|
35
|
+
email = request.json["email"]
|
|
36
|
+
password = request.json["password"]
|
|
37
|
+
display_name = request.json["display_name"]
|
|
38
|
+
user = db.table("users").insert({
|
|
39
|
+
"email": email,
|
|
40
|
+
"display_name": display_name,
|
|
41
|
+
}).execute()
|
|
42
|
+
db.table("workspaces").insert({
|
|
43
|
+
"name": f"{display_name}'s Workspace",
|
|
44
|
+
"owner_id": user.data[0]["id"],
|
|
45
|
+
}).execute()
|
|
46
|
+
return jsonify(user=user.data[0], access_token="jwt...")
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
from flask import Blueprint, request, jsonify
|
|
2
|
+
|
|
3
|
+
invites_bp = Blueprint("invites", __name__)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
@invites_bp.route("/", methods=["POST"])
|
|
7
|
+
@login_required
|
|
8
|
+
def create_invite():
|
|
9
|
+
email = request.json["email"]
|
|
10
|
+
workspace_id = request.json["workspace_id"]
|
|
11
|
+
invite = db.table("invites").insert({
|
|
12
|
+
"email": email,
|
|
13
|
+
"workspace_id": workspace_id,
|
|
14
|
+
"invited_by": request.user_id,
|
|
15
|
+
"status": "pending",
|
|
16
|
+
}).execute()
|
|
17
|
+
return jsonify(invite=invite.data[0])
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@invites_bp.route("/<invite_id>/accept", methods=["POST"])
|
|
21
|
+
@login_required
|
|
22
|
+
def accept_invite(invite_id):
|
|
23
|
+
invite = db.table("invites").select("*").eq("id", invite_id).single().execute()
|
|
24
|
+
db.table("invites").update({"status": "accepted"}).eq("id", invite_id).execute()
|
|
25
|
+
db.table("workspace_members").insert({
|
|
26
|
+
"workspace_id": invite.data["workspace_id"],
|
|
27
|
+
"user_id": request.user_id,
|
|
28
|
+
"role": "member",
|
|
29
|
+
}).execute()
|
|
30
|
+
return jsonify(success=True)
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
from flask import Blueprint, request, jsonify
|
|
2
|
+
|
|
3
|
+
notifications_bp = Blueprint("notifications", __name__)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
@notifications_bp.route("/", methods=["GET"])
|
|
7
|
+
@login_required
|
|
8
|
+
def list_notifications():
|
|
9
|
+
notifications = db.table("notifications").select("*").eq("user_id", request.user_id).order("created_at", desc=True).limit(50).execute()
|
|
10
|
+
return jsonify(notifications=notifications.data)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@notifications_bp.route("/read", methods=["POST"])
|
|
14
|
+
@login_required
|
|
15
|
+
def mark_read():
|
|
16
|
+
notification_ids = request.json["ids"]
|
|
17
|
+
db.table("notifications").update({"read": True}).in_("id", notification_ids).execute()
|
|
18
|
+
return jsonify(success=True)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@notifications_bp.route("/unread-count", methods=["GET"])
|
|
22
|
+
@login_required
|
|
23
|
+
def unread_count():
|
|
24
|
+
result = db.table("notifications").select("id", count="exact").eq("user_id", request.user_id).eq("read", False).execute()
|
|
25
|
+
return jsonify(count=result.count)
|
|
@@ -0,0 +1,80 @@
|
|
|
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
|
+
workspace_id = request.args.get("workspace_id")
|
|
10
|
+
projects = db.table("projects").select("*").eq("workspace_id", workspace_id).execute()
|
|
11
|
+
return jsonify(projects=projects.data)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@projects_bp.route("/", methods=["POST"])
|
|
15
|
+
@login_required
|
|
16
|
+
def create_project():
|
|
17
|
+
name = request.json["name"]
|
|
18
|
+
description = request.json.get("description", "")
|
|
19
|
+
workspace_id = request.json["workspace_id"]
|
|
20
|
+
color = request.json.get("color", "#3b82f6")
|
|
21
|
+
project = db.table("projects").insert({
|
|
22
|
+
"name": name,
|
|
23
|
+
"description": description,
|
|
24
|
+
"workspace_id": workspace_id,
|
|
25
|
+
"color": color,
|
|
26
|
+
"owner_id": request.user_id,
|
|
27
|
+
}).execute()
|
|
28
|
+
# Create default columns for kanban
|
|
29
|
+
for col_name in ["To Do", "In Progress", "Done"]:
|
|
30
|
+
db.table("columns").insert({
|
|
31
|
+
"project_id": project.data[0]["id"],
|
|
32
|
+
"name": col_name,
|
|
33
|
+
}).execute()
|
|
34
|
+
return jsonify(project=project.data[0])
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@projects_bp.route("/<project_id>", methods=["GET"])
|
|
38
|
+
@login_required
|
|
39
|
+
def get_project(project_id):
|
|
40
|
+
project = db.table("projects").select("*").eq("id", project_id).single().execute()
|
|
41
|
+
members = db.table("project_members").select("*, users(display_name, avatar_url)").eq("project_id", project_id).execute()
|
|
42
|
+
columns = db.table("columns").select("*").eq("project_id", project_id).order("position").execute()
|
|
43
|
+
return jsonify(project=project.data, members=members.data, columns=columns.data)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
@projects_bp.route("/<project_id>", methods=["PUT"])
|
|
47
|
+
@login_required
|
|
48
|
+
def update_project(project_id):
|
|
49
|
+
updates = {k: v for k, v in request.json.items() if k in ("name", "description", "color", "archived")}
|
|
50
|
+
project = db.table("projects").update(updates).eq("id", project_id).execute()
|
|
51
|
+
return jsonify(project=project.data[0])
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
@projects_bp.route("/<project_id>", methods=["DELETE"])
|
|
55
|
+
@login_required
|
|
56
|
+
def delete_project(project_id):
|
|
57
|
+
db.table("tasks").delete().eq("project_id", project_id).execute()
|
|
58
|
+
db.table("columns").delete().eq("project_id", project_id).execute()
|
|
59
|
+
db.table("project_members").delete().eq("project_id", project_id).execute()
|
|
60
|
+
db.table("projects").delete().eq("id", project_id).execute()
|
|
61
|
+
return jsonify(success=True)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
@projects_bp.route("/<project_id>/members", methods=["POST"])
|
|
65
|
+
@login_required
|
|
66
|
+
def add_member(project_id):
|
|
67
|
+
user_id = request.json["user_id"]
|
|
68
|
+
role = request.json.get("role", "member")
|
|
69
|
+
member = db.table("project_members").insert({
|
|
70
|
+
"project_id": project_id,
|
|
71
|
+
"user_id": user_id,
|
|
72
|
+
"role": role,
|
|
73
|
+
}).execute()
|
|
74
|
+
# Send notification to the invited user
|
|
75
|
+
db.table("notifications").insert({
|
|
76
|
+
"user_id": user_id,
|
|
77
|
+
"type": "project_invite",
|
|
78
|
+
"data": {"project_id": project_id, "invited_by": request.user_id},
|
|
79
|
+
}).execute()
|
|
80
|
+
return jsonify(member=member.data[0])
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
from flask import Blueprint, request, jsonify
|
|
2
|
+
|
|
3
|
+
tasks_bp = Blueprint("tasks", __name__)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
@tasks_bp.route("/", methods=["GET"])
|
|
7
|
+
@login_required
|
|
8
|
+
def list_tasks():
|
|
9
|
+
project_id = request.args.get("project_id")
|
|
10
|
+
column_id = request.args.get("column_id")
|
|
11
|
+
query = db.table("tasks").select("*, assignee:users(display_name, avatar_url)")
|
|
12
|
+
if project_id:
|
|
13
|
+
query = query.eq("project_id", project_id)
|
|
14
|
+
if column_id:
|
|
15
|
+
query = query.eq("column_id", column_id)
|
|
16
|
+
tasks = query.order("position").execute()
|
|
17
|
+
return jsonify(tasks=tasks.data)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@tasks_bp.route("/", methods=["POST"])
|
|
21
|
+
@login_required
|
|
22
|
+
def create_task():
|
|
23
|
+
title = request.json["title"]
|
|
24
|
+
description = request.json.get("description", "")
|
|
25
|
+
project_id = request.json["project_id"]
|
|
26
|
+
column_id = request.json["column_id"]
|
|
27
|
+
assignee_id = request.json.get("assignee_id")
|
|
28
|
+
priority = request.json.get("priority", "medium")
|
|
29
|
+
due_date = request.json.get("due_date")
|
|
30
|
+
task = db.table("tasks").insert({
|
|
31
|
+
"title": title,
|
|
32
|
+
"description": description,
|
|
33
|
+
"project_id": project_id,
|
|
34
|
+
"column_id": column_id,
|
|
35
|
+
"assignee_id": assignee_id,
|
|
36
|
+
"priority": priority,
|
|
37
|
+
"due_date": due_date,
|
|
38
|
+
"creator_id": request.user_id,
|
|
39
|
+
}).execute()
|
|
40
|
+
# Notify assignee
|
|
41
|
+
if assignee_id and assignee_id != request.user_id:
|
|
42
|
+
db.table("notifications").insert({
|
|
43
|
+
"user_id": assignee_id,
|
|
44
|
+
"type": "task_assigned",
|
|
45
|
+
"data": {"task_id": task.data[0]["id"], "assigned_by": request.user_id},
|
|
46
|
+
}).execute()
|
|
47
|
+
return jsonify(task=task.data[0])
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
@tasks_bp.route("/<task_id>", methods=["PUT"])
|
|
51
|
+
@login_required
|
|
52
|
+
def update_task(task_id):
|
|
53
|
+
allowed = ("title", "description", "column_id", "assignee_id", "priority", "due_date", "position", "completed")
|
|
54
|
+
updates = {k: v for k, v in request.json.items() if k in allowed}
|
|
55
|
+
task = db.table("tasks").update(updates).eq("id", task_id).execute()
|
|
56
|
+
# Log activity
|
|
57
|
+
db.table("activity_log").insert({
|
|
58
|
+
"user_id": request.user_id,
|
|
59
|
+
"action": "task_updated",
|
|
60
|
+
"entity_type": "task",
|
|
61
|
+
"entity_id": task_id,
|
|
62
|
+
"changes": updates,
|
|
63
|
+
}).execute()
|
|
64
|
+
return jsonify(task=task.data[0])
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
@tasks_bp.route("/<task_id>", methods=["DELETE"])
|
|
68
|
+
@login_required
|
|
69
|
+
def delete_task(task_id):
|
|
70
|
+
db.table("comments").delete().eq("task_id", task_id).execute()
|
|
71
|
+
db.table("tasks").delete().eq("id", task_id).execute()
|
|
72
|
+
return jsonify(success=True)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
@tasks_bp.route("/<task_id>/comments", methods=["GET"])
|
|
76
|
+
@login_required
|
|
77
|
+
def list_comments(task_id):
|
|
78
|
+
comments = db.table("comments").select("*, author:users(display_name, avatar_url)").eq("task_id", task_id).order("created_at").execute()
|
|
79
|
+
return jsonify(comments=comments.data)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
@tasks_bp.route("/<task_id>/comments", methods=["POST"])
|
|
83
|
+
@login_required
|
|
84
|
+
def create_comment(task_id):
|
|
85
|
+
body = request.json["body"]
|
|
86
|
+
comment = db.table("comments").insert({
|
|
87
|
+
"task_id": task_id,
|
|
88
|
+
"author_id": request.user_id,
|
|
89
|
+
"body": body,
|
|
90
|
+
}).execute()
|
|
91
|
+
return jsonify(comment=comment.data[0])
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
from flask import Blueprint, request, jsonify
|
|
2
|
+
|
|
3
|
+
users_bp = Blueprint("users", __name__)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
@users_bp.route("/me", methods=["GET"])
|
|
7
|
+
@login_required
|
|
8
|
+
def get_profile():
|
|
9
|
+
user = db.table("users").select("*").eq("id", request.user_id).single().execute()
|
|
10
|
+
return jsonify(user=user.data)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@users_bp.route("/me", methods=["PUT"])
|
|
14
|
+
@login_required
|
|
15
|
+
def update_profile():
|
|
16
|
+
display_name = request.json.get("display_name")
|
|
17
|
+
bio = request.json.get("bio")
|
|
18
|
+
avatar_url = request.json.get("avatar_url")
|
|
19
|
+
user = db.table("users").update({
|
|
20
|
+
"display_name": display_name,
|
|
21
|
+
"bio": bio,
|
|
22
|
+
"avatar_url": avatar_url,
|
|
23
|
+
}).eq("id", request.user_id).execute()
|
|
24
|
+
return jsonify(user=user.data[0])
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@users_bp.route("/me/avatar", methods=["POST"])
|
|
28
|
+
@login_required
|
|
29
|
+
def upload_avatar():
|
|
30
|
+
file = request.files["avatar"]
|
|
31
|
+
url = storage.upload(f"avatars/{request.user_id}", file)
|
|
32
|
+
db.table("users").update({"avatar_url": url}).eq("id", request.user_id).execute()
|
|
33
|
+
return jsonify(avatar_url=url)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@users_bp.route("/<user_id>", methods=["GET"])
|
|
37
|
+
@login_required
|
|
38
|
+
def get_user(user_id):
|
|
39
|
+
user = db.table("users").select("id, display_name, bio, avatar_url").eq("id", user_id).single().execute()
|
|
40
|
+
return jsonify(user=user.data)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
@users_bp.route("/search", methods=["GET"])
|
|
44
|
+
@login_required
|
|
45
|
+
def search_users():
|
|
46
|
+
query = request.args.get("q", "")
|
|
47
|
+
users = db.table("users").select("id, display_name, avatar_url").ilike("display_name", f"%{query}%").execute()
|
|
48
|
+
return jsonify(users=users.data)
|
|
File without changes
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"""Email notification service — sends via Celery background tasks."""
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def send_invite_email(email, workspace_name, invited_by_name):
|
|
5
|
+
"""Queue an invite email for async delivery."""
|
|
6
|
+
pass
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def send_task_notification(user_email, task_title, action):
|
|
10
|
+
"""Queue a task notification email."""
|
|
11
|
+
pass
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
from flask import Flask, jsonify
|
|
2
|
+
|
|
3
|
+
app = Flask(__name__)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
@app.route("/api/reports", methods=["GET"])
|
|
7
|
+
def list_reports():
|
|
8
|
+
reports = db.table("reports").select("*").execute()
|
|
9
|
+
return jsonify(data=reports.data)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@app.route("/api/user-stats/<user_id>", methods=["GET"])
|
|
13
|
+
def user_stats(user_id):
|
|
14
|
+
# This service also reads from the 'users' table — shared with flask_app
|
|
15
|
+
user = db.table("users").select("name, email").eq("id", user_id).execute()
|
|
16
|
+
stats = db.table("reports").select("*").eq("user_id", user_id).execute()
|
|
17
|
+
return jsonify(user=user.data[0], report_count=len(stats.data))
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
from fastapi import FastAPI, Depends
|
|
2
|
+
from pydantic import BaseModel
|
|
3
|
+
from typing import Optional
|
|
4
|
+
|
|
5
|
+
app = FastAPI()
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class ItemCreate(BaseModel):
|
|
9
|
+
name: str
|
|
10
|
+
price: float
|
|
11
|
+
description: Optional[str] = None
|
|
12
|
+
category: str
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class ItemResponse(BaseModel):
|
|
16
|
+
id: int
|
|
17
|
+
name: str
|
|
18
|
+
price: float
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class UserCreate(BaseModel):
|
|
22
|
+
email: str
|
|
23
|
+
password: str
|
|
24
|
+
display_name: str
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def get_current_user():
|
|
28
|
+
pass
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@app.post("/api/items")
|
|
32
|
+
def create_item(item: ItemCreate):
|
|
33
|
+
return {"id": 1, "name": item.name, "price": item.price}
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@app.get("/api/items/{item_id}")
|
|
37
|
+
def get_item(item_id: int):
|
|
38
|
+
return {"id": item_id, "name": "Test", "price": 9.99}
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@app.post("/api/users", response_model=UserCreate)
|
|
42
|
+
def create_user(user: UserCreate, current_user=Depends(get_current_user)):
|
|
43
|
+
return user
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
@app.delete("/api/items/{item_id}")
|
|
47
|
+
def delete_item(item_id: int, current_user=Depends(get_current_user)):
|
|
48
|
+
return {"status": "deleted"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
from fastapi import FastAPI, Depends
|
|
2
|
+
from .schemas import UserCreate, UserResponse, ItemCreate
|
|
3
|
+
|
|
4
|
+
app = FastAPI()
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def get_current_user():
|
|
8
|
+
pass
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@app.post("/api/users")
|
|
12
|
+
def create_user(user: UserCreate):
|
|
13
|
+
return {"id": 1, "email": user.email}
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@app.post("/api/items")
|
|
17
|
+
def create_item(item: ItemCreate, current_user=Depends(get_current_user)):
|
|
18
|
+
return {"id": 1, "name": item.name}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
from pydantic import BaseModel
|
|
2
|
+
from typing import Optional
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class UserCreate(BaseModel):
|
|
6
|
+
email: str
|
|
7
|
+
password: str
|
|
8
|
+
display_name: str
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class UserResponse(BaseModel):
|
|
12
|
+
id: int
|
|
13
|
+
email: str
|
|
14
|
+
display_name: str
|
|
15
|
+
is_active: bool = True
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class ItemCreate(BaseModel):
|
|
19
|
+
name: str
|
|
20
|
+
price: float
|
|
21
|
+
description: Optional[str] = None
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
from flask import Flask, request, jsonify
|
|
2
|
+
|
|
3
|
+
app = Flask(__name__)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
@app.route("/api/users", methods=["GET"])
|
|
7
|
+
@login_required
|
|
8
|
+
def list_users():
|
|
9
|
+
users = db.table("users").select("*").execute()
|
|
10
|
+
return jsonify(data=users.data, count=len(users.data))
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@app.route("/api/users/<user_id>", methods=["GET"])
|
|
14
|
+
@login_required
|
|
15
|
+
def get_user(user_id):
|
|
16
|
+
user = db.table("users").select("*").eq("id", user_id).execute()
|
|
17
|
+
return jsonify(data=user.data[0])
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@app.route("/api/users", methods=["POST"])
|
|
21
|
+
def create_user():
|
|
22
|
+
name = request.json["name"]
|
|
23
|
+
email = request.json["email"]
|
|
24
|
+
password = request.json["password"]
|
|
25
|
+
user = db.table("users").insert({"name": name, "email": email}).execute()
|
|
26
|
+
return jsonify(id=user.data[0]["id"], status="created")
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@app.route("/api/items/<item_id>", methods=["DELETE"])
|
|
30
|
+
@login_required
|
|
31
|
+
def delete_item(item_id):
|
|
32
|
+
db.table("items").delete().eq("id", item_id).execute()
|
|
33
|
+
return jsonify(status="deleted")
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
from flask import Blueprint, request, jsonify
|
|
2
|
+
|
|
3
|
+
items_bp = Blueprint("items", __name__)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
@items_bp.get("/")
|
|
7
|
+
def list_items():
|
|
8
|
+
return jsonify(items=[])
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@items_bp.delete("/<item_id>")
|
|
12
|
+
def delete_item(item_id):
|
|
13
|
+
return jsonify(status="deleted")
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
from flask import Blueprint, request, jsonify
|
|
2
|
+
|
|
3
|
+
users_bp = Blueprint("users", __name__)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
@users_bp.route("/", methods=["GET"])
|
|
7
|
+
def list_users():
|
|
8
|
+
return jsonify(users=[])
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@users_bp.route("/<user_id>", methods=["GET"])
|
|
12
|
+
def get_user(user_id):
|
|
13
|
+
return jsonify(id=user_id, name="John")
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@users_bp.route("/", methods=["POST"])
|
|
17
|
+
def create_user():
|
|
18
|
+
name = request.json["name"]
|
|
19
|
+
email = request.json["email"]
|
|
20
|
+
return jsonify(id=1, name=name)
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
from flask import Blueprint, request, jsonify
|
|
2
|
+
|
|
3
|
+
bp = Blueprint("users", __name__)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
@bp.before_request
|
|
7
|
+
def require_auth():
|
|
8
|
+
"""All routes on this blueprint require authentication."""
|
|
9
|
+
if not request.headers.get("Authorization"):
|
|
10
|
+
return jsonify(error="Unauthorized"), 401
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@bp.route("/", methods=["GET"])
|
|
14
|
+
def list_users():
|
|
15
|
+
return jsonify(users=[])
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@bp.route("/<user_id>", methods=["GET"])
|
|
19
|
+
def get_user(user_id):
|
|
20
|
+
return jsonify(id=user_id, name="John")
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@bp.route("/", methods=["POST"])
|
|
24
|
+
def create_user():
|
|
25
|
+
name = request.json["name"]
|
|
26
|
+
return jsonify(id=1, name=name)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
from fastapi import APIRouter
|
|
2
|
+
|
|
3
|
+
# Two levels up to schemas
|
|
4
|
+
from ...schemas.item import ItemCreate, ItemUpdate
|
|
5
|
+
|
|
6
|
+
router = APIRouter()
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@router.post("/items")
|
|
10
|
+
def create_item(item: ItemCreate):
|
|
11
|
+
result = db.table("items").insert({"name": item.name, "price": item.price}).execute()
|
|
12
|
+
return {"id": 1, "name": item.name}
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@router.put("/items/{item_id}")
|
|
16
|
+
def update_item(item_id: int, item: ItemUpdate):
|
|
17
|
+
db.table("items").update({"name": item.name}).eq("id", item_id).execute()
|
|
18
|
+
return {"status": "updated"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
from fastapi import APIRouter, Depends
|
|
2
|
+
|
|
3
|
+
# Two levels up: from app/api/v1/ -> app/schemas/user.py (../../schemas.user)
|
|
4
|
+
from ...schemas.user import UserCreate, UserResponse
|
|
5
|
+
|
|
6
|
+
# Three levels relative: from app/api/v1/ -> app/shared/models.py (../../shared.models)
|
|
7
|
+
from ...shared.models import PaginationParams
|
|
8
|
+
|
|
9
|
+
router = APIRouter()
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@router.get("/users")
|
|
13
|
+
def list_users(pagination: PaginationParams):
|
|
14
|
+
users = db.table("users").select("*").execute()
|
|
15
|
+
return {"users": users.data, "page": pagination.page}
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@router.post("/users")
|
|
19
|
+
def create_user(user: UserCreate):
|
|
20
|
+
result = db.table("users").insert({"email": user.email}).execute()
|
|
21
|
+
return {"id": result.data[0]["id"], "email": user.email}
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@router.get("/users/{user_id}")
|
|
25
|
+
def get_user(user_id: int):
|
|
26
|
+
user = db.table("users").select("*").eq("id", user_id).execute()
|
|
27
|
+
return {"user": user.data[0]}
|
|
File without changes
|