Skip to content

Commit d3f5186

Browse files
committed
Merge branch 'dev' of github.com:Code4GovTech/DMP-CMS-Backend-API into fix/multi-repo
2 parents aa48f2d + 2bdfa5c commit d3f5186

File tree

10 files changed

+297
-180
lines changed

10 files changed

+297
-180
lines changed

.github/workflows/ci.yml

+8
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,10 @@ jobs:
6565
SUPABASE_URL: ${{ vars[format('APP_{0}_SUPABASE_URL', needs.set_vars.outputs.APP_ENV)] }}
6666
SUPABASE_KEY: ${{ secrets[format('APP_{0}_SUPABASE_KEY', needs.set_vars.outputs.APP_ENV)] }}
6767
SECRET_KEY: ${{ secrets[format('APP_{0}_SECRET_KEY', needs.set_vars.outputs.APP_ENV)] }}
68+
POSTGRES_DB_HOST: ${{ secrets[format('APP_{0}_POSTGRES_DB_HOST', needs.set_vars.outputs.APP_ENV)] }}
69+
POSTGRES_DB_NAME: ${{ secrets[format('APP_{0}_POSTGRES_DB_NAME', needs.set_vars.outputs.APP_ENV)] }}
70+
POSTGRES_DB_USER: ${{ secrets[format('APP_{0}_POSTGRES_DB_USER', needs.set_vars.outputs.APP_ENV)] }}
71+
POSTGRES_DB_PASS: ${{ secrets[format('APP_{0}_POSTGRES_DB_PASS', needs.set_vars.outputs.APP_ENV)] }}
6872
steps:
6973
- name: Checkout code
7074
uses: actions/checkout@v2
@@ -87,6 +91,10 @@ jobs:
8791
echo "SUPABASE_URL=${SUPABASE_URL}" >> .env
8892
echo "SUPABASE_KEY=${SUPABASE_KEY}" >> .env
8993
echo "SECRET_KEY=${SECRET_KEY}" >> .env
94+
echo "POSTGRES_DB_HOST=${POSTGRES_DB_HOST}" >> .env
95+
echo "POSTGRES_DB_NAME=${POSTGRES_DB_NAME}" >> .env
96+
echo "POSTGRES_DB_USER=${POSTGRES_DB_USER}" >> .env
97+
echo "POSTGRES_DB_PASS=${POSTGRES_DB_PASS}" >> .env
9098
mv .env ${{ env.DOT_ENV_FILE_NAME }}
9199
92100
- name: Copy env file to DEV Server

app.py

+29-80
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,25 @@
11
from flask import Flask, jsonify,request,url_for
2-
from db import SupabaseInterface
32
from collections import defaultdict
43
from flasgger import Swagger
54
import re,os,traceback
5+
from query import PostgresORM
66
from utils import *
77
from flask_cors import CORS,cross_origin
88
from v2_app import v2
9+
from flask_sqlalchemy import SQLAlchemy
10+
from models import db
11+
912

1013

1114
app = Flask(__name__)
1215
CORS(app,supports_credentials=True)
1316

1417

18+
app.config['SQLALCHEMY_DATABASE_URI'] = PostgresORM.get_postgres_uri()
19+
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
20+
21+
db.init_app(app)
22+
1523
Swagger(app)
1624

1725
GITHUB_TOKEN =os.getenv('GITHUB_TOKEN')
@@ -45,61 +53,6 @@ def greeting():
4553

4654

4755

48-
49-
@app.route('/get-data', methods=['GET'])
50-
@cross_origin(supports_credentials=True)
51-
@require_secret_key
52-
def get_data():
53-
"""
54-
Fetch data from Supabase.
55-
---
56-
responses:
57-
200:
58-
description: Data fetched successfully
59-
schema:
60-
type: array
61-
items:
62-
type: object
63-
500:
64-
description: Error fetching data
65-
schema:
66-
type: object
67-
properties:
68-
error:
69-
type: string
70-
"""
71-
try:
72-
response = SupabaseInterface().get_instance().client.table('dmp_pr_updates').select('*').execute()
73-
data = response.data
74-
return jsonify(data)
75-
except Exception as e:
76-
return jsonify({'error': str(e)}), 200
77-
78-
79-
80-
@app.route('/v1/issues', methods=['GET'])
81-
@require_secret_key
82-
def v1get_issues():
83-
try:
84-
response = SupabaseInterface().get_instance().client.table('dmp_issue_updates').select('*').execute()
85-
data = response.data
86-
87-
#group data based on issues
88-
grouped_data = defaultdict(list)
89-
for record in data:
90-
issue_url = record['issue_url']
91-
grouped_data[issue_url].append({
92-
'id': record['id'],
93-
'name': record['body_text']
94-
})
95-
96-
result = [{'issue_url': issue_url, 'issues': issues} for issue_url, issues in grouped_data.items()]
97-
grouped_data = group_by_owner(result)
98-
return jsonify(grouped_data)
99-
100-
except Exception as e:
101-
error_traceback = traceback.format_exc()
102-
return jsonify({'error': str(e), 'traceback': error_traceback}), 200
10356

10457

10558
@app.route('/issues', methods=['GET'])
@@ -127,21 +80,18 @@ def get_issues():
12780
type: string
12881
"""
12982
try:
130-
# Fetch all issues with their details
131-
response = SupabaseInterface().get_instance().client.table('dmp_orgs').select('*, dmp_issues(*)').execute()
132-
res = []
133-
134-
for org in response.data:
135-
obj = {}
136-
issues = org['dmp_issues']
137-
obj['org_id'] = org['id']
138-
obj['org_name'] = org['name']
139-
renamed_issues = [{"id": issue["id"], "name": issue["title"]} for issue in issues]
140-
obj['issues'] = renamed_issues
141-
142-
res.append(obj)
143-
144-
return jsonify({"issues": res})
83+
# Fetch all issues with their details
84+
data = PostgresORM.get_issue_query()
85+
response = []
86+
87+
for result in data:
88+
response.append({
89+
'org_id': result.org_id,
90+
'org_name': result.org_name,
91+
'issues': result.issues
92+
})
93+
94+
return jsonify({"issues": response})
14595

14696
except Exception as e:
14797
error_traceback = traceback.format_exc()
@@ -190,16 +140,15 @@ def get_issues_by_owner(owner):
190140
description: Error message
191141
"""
192142
try:
193-
# Construct the GitHub URL based on the owner parameter
194-
org_link = f"https://github.com/{owner}"
195-
143+
196144
# Fetch organization details from dmp_orgs table
197-
response = SupabaseInterface().get_instance().client.table('dmp_orgs').select('name', 'description').eq('name', owner).execute()
198-
199-
if not response.data:
145+
response = PostgresORM.get_issue_owner(owner)
146+
if not response:
200147
return jsonify({'error': "Organization not found"}), 404
201-
202-
return jsonify(response.data)
148+
149+
orgs_dict = [org.to_dict() for org in response]
150+
151+
return jsonify(orgs_dict)
203152

204153
except Exception as e:
205154
error_traceback = traceback.format_exc()
@@ -260,7 +209,7 @@ def get_issues_by_owner_id(owner, issue):
260209
if val['body_text']:
261210
if "Weekly Goals" in val['body_text'] and not w_goal_url:
262211
w_goal_url = val['body_text']
263-
plain_text_body = markdown2.markdown(val['body_text'])
212+
plain_text_body = markdown.markdown(val['body_text'])
264213

265214
tasks = re.findall(r'\[(x| )\]', plain_text_body)
266215
total_tasks = len(tasks)

db.py

-61
Original file line numberDiff line numberDiff line change
@@ -1,61 +0,0 @@
1-
import os, sys
2-
from typing import Any
3-
from supabase import create_client, Client
4-
from supabase.lib.client_options import ClientOptions
5-
from abc import ABC, abstractmethod
6-
7-
client_options = ClientOptions(postgrest_client_timeout=None)
8-
9-
10-
11-
class SupabaseInterface():
12-
13-
_instance = None
14-
15-
def __init__(self):
16-
if not SupabaseInterface._instance:
17-
18-
# Load environment variables
19-
from dotenv import load_dotenv
20-
load_dotenv()
21-
22-
SUPABASE_URL = os.getenv('SUPABASE_URL')
23-
SUPABASE_KEY = os.getenv('SUPABASE_KEY')
24-
self.client: Client = create_client(SUPABASE_URL, SUPABASE_KEY)
25-
SupabaseInterface._instance = self
26-
else:
27-
SupabaseInterface._instance = self._instance
28-
29-
30-
31-
@staticmethod
32-
def get_instance():
33-
# Static method to retrieve the singleton instance
34-
if not SupabaseInterface._instance:
35-
# If no instance exists, create a new one
36-
SupabaseInterface._instance = SupabaseInterface()
37-
return SupabaseInterface._instance
38-
39-
40-
def readAll(self, table):
41-
data = self.client.table(f"{table}").select("*").execute()
42-
return data.data
43-
44-
def add_data(self, data,table_name):
45-
data = self.client.table(table_name).insert(data).execute()
46-
return data.data
47-
48-
def add_data_filter(self, data, table_name):
49-
# Construct the filter based on the provided column names and values
50-
filter_data = {column: data[column] for column in ['dmp_id','issue_number','owner']}
51-
52-
# Check if the data already exists in the table based on the filter
53-
existing_data = self.client.table(table_name).select("*").eq('dmp_id',data['dmp_id']).execute()
54-
55-
# If the data already exists, return without creating a new record
56-
if existing_data.data:
57-
return "Data already exists"
58-
59-
# If the data doesn't exist, insert it into the table
60-
new_data = self.client.table(table_name).insert(data).execute()
61-
return new_data.data

models.py

+141
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
from flask import Flask
2+
from flask_sqlalchemy import SQLAlchemy
3+
from datetime import datetime
4+
5+
db = SQLAlchemy()
6+
7+
class DmpOrg(db.Model):
8+
__tablename__ = 'dmp_orgs'
9+
10+
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
11+
created_at = db.Column(db.DateTime, default=datetime.utcnow, nullable=False)
12+
name = db.Column(db.String, nullable=False)
13+
description = db.Column(db.Text, nullable=True)
14+
link = db.Column(db.String, nullable=False)
15+
repo_owner = db.Column(db.String, nullable=False)
16+
17+
# Relationship to DmpIssueUpdate
18+
issues = db.relationship('DmpIssueUpdate', backref='organization', lazy=True)
19+
20+
# Updated relationship name to avoid conflict
21+
dmp_issues = db.relationship('DmpIssue', backref='organization', lazy=True)
22+
23+
def __repr__(self):
24+
return f"<DmpOrg(id={self.id}, name={self.name})>"
25+
26+
def to_dict(self):
27+
return {
28+
'id': self.id,
29+
'created_at': self.created_at.isoformat(),
30+
'name': self.name,
31+
'description': self.description,
32+
'link': self.link,
33+
'repo_owner': self.repo_owner
34+
}
35+
36+
class DmpIssue(db.Model):
37+
__tablename__ = 'dmp_issues'
38+
39+
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
40+
issue_url = db.Column(db.String, nullable=False)
41+
issue_number = db.Column(db.Integer, nullable=False)
42+
mentor_username = db.Column(db.String, nullable=True)
43+
contributor_username = db.Column(db.String, nullable=True)
44+
title = db.Column(db.String, nullable=False)
45+
org_id = db.Column(db.Integer, db.ForeignKey('dmp_orgs.id'), nullable=False)
46+
description = db.Column(db.Text, nullable=True)
47+
repo = db.Column(db.String, nullable=True)
48+
49+
50+
# Relationship to Prupdates
51+
pr_updates = db.relationship('Prupdates', backref='pr_details', lazy=True)
52+
53+
def __repr__(self):
54+
return f"<DmpIssue(id={self.id}, title={self.title})>"
55+
56+
def to_dict(self):
57+
return {
58+
'id': self.id,
59+
'issue_url': self.issue_url,
60+
'issue_number': self.issue_number,
61+
'mentor_username': self.mentor_username,
62+
'contributor_username': self.contributor_username,
63+
'title': self.title,
64+
'org_id': self.org_id,
65+
'description': self.description,
66+
'repo': self.repo
67+
}
68+
69+
class DmpIssueUpdate(db.Model):
70+
__tablename__ = 'dmp_issue_updates'
71+
72+
created_at = db.Column(db.DateTime, default=datetime.utcnow, nullable=False)
73+
body_text = db.Column(db.Text, nullable=False)
74+
comment_link = db.Column(db.String, nullable=False)
75+
comment_id = db.Column(db.BigInteger, primary_key=True, nullable=False)
76+
comment_api = db.Column(db.String, nullable=False)
77+
comment_updated_at = db.Column(db.DateTime, nullable=False)
78+
dmp_id = db.Column(db.Integer, db.ForeignKey('dmp_orgs.id'), nullable=False)
79+
created_by = db.Column(db.String, nullable=False)
80+
81+
def __repr__(self):
82+
return f"<DmpIssueUpdate(comment_id={self.comment_id}, dmp_id={self.dmp_id})>"
83+
84+
def to_dict(self):
85+
return {
86+
'created_at': self.created_at.isoformat(),
87+
'body_text': self.body_text,
88+
'comment_link': self.comment_link,
89+
'comment_id': self.comment_id,
90+
'comment_api': self.comment_api,
91+
'comment_updated_at': self.comment_updated_at.isoformat(),
92+
'dmp_id': self.dmp_id,
93+
'created_by': self.created_by
94+
}
95+
96+
class Prupdates(db.Model):
97+
__tablename__ = 'dmp_pr_updates'
98+
99+
created_at = db.Column(db.DateTime, nullable=False, default=datetime.utcnow)
100+
pr_id = db.Column(db.Integer, nullable=False,primary_key=True)
101+
status = db.Column(db.String, nullable=False)
102+
title = db.Column(db.String, nullable=False)
103+
pr_updated_at = db.Column(db.DateTime, nullable=False, default=datetime.utcnow)
104+
merged_at = db.Column(db.DateTime)
105+
closed_at = db.Column(db.DateTime)
106+
dmp_id = db.Column(db.Integer, db.ForeignKey('dmp_issues.id'), nullable=False) # ForeignKey relationship
107+
link = db.Column(db.String, nullable=False)
108+
109+
def __repr__(self):
110+
return f'<PullRequest {self.pr_id} - {self.title}>'
111+
112+
def to_dict(self):
113+
return {
114+
'created_at': self.created_at.isoformat(),
115+
'pr_id': self.pr_id,
116+
'status': self.status,
117+
'title': self.title,
118+
'pr_updated_at': self.pr_updated_at.isoformat(),
119+
'merged_at': self.merged_at.isoformat() if self.merged_at else None,
120+
'closed_at': self.closed_at.isoformat() if self.closed_at else None,
121+
'dmp_id': self.dmp_id,
122+
'link': self.link
123+
}
124+
125+
class DmpWeekUpdate(db.Model):
126+
__tablename__ = 'dmp_week_updates'
127+
128+
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
129+
issue_url = db.Column(db.String, nullable=False)
130+
week = db.Column(db.Integer, nullable=False)
131+
total_task = db.Column(db.Integer, nullable=False)
132+
completed_task = db.Column(db.Integer, nullable=False)
133+
progress = db.Column(db.Integer, nullable=False)
134+
task_data = db.Column(db.Text, nullable=False)
135+
dmp_id = db.Column(db.Integer, nullable=False)
136+
137+
def __repr__(self):
138+
return f"<DmpWeekUpdate(id={self.id}, week={self.week}, dmp_id={self.dmp_id})>"
139+
140+
# if __name__ == '__main__':
141+
# db.create_all()

0 commit comments

Comments
 (0)