Skip to content

Commit

Permalink
add /api/contests (differed from /api/contest), /api/usergroups, adap…
Browse files Browse the repository at this point in the history
…t models (#116)

* rename "contests" endpoint to "contest"

* build: add lint cmd

* feat$(Flask, database): Outsource contestgroups to own table, add usergroup model actions

* feat$(Flask, database, models): in case response is single-valued, return single value instead of arr

* refactor$(Vue, createcontest): adapt to new contest POST model

* feat$(Flask, api): differ api/contest and api/contests, usergroups route

* pylint ignore TODO

* style$(Flask): codeclimate
  • Loading branch information
Felix Wu authored Jun 3, 2018
1 parent 3770476 commit 72dbac1
Show file tree
Hide file tree
Showing 9 changed files with 240 additions and 28 deletions.
2 changes: 1 addition & 1 deletion .pylintrc
Original file line number Diff line number Diff line change
Expand Up @@ -402,7 +402,7 @@ spelling-store-unknown-words=no
[MISCELLANEOUS]

# List of note tags to take in consideration, separated by a comma.
notes=FIXME,XXX,TODO
notes=FIXME,XXX


[BASIC]
Expand Down
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,10 +72,10 @@ The `SECRET_KEY` variable can be set to whatever you want.
|---|---|---|---|---|---|
| GET | tags | - | Comma-seperated list of tags <br> to be matched by <br> the returned tasks <br> _Returns the task_ | No | `/api/tasks?tags=graphs,implementation` |

### `/api/contests`
### `/api/contest`

| Method | Parameter | HTTP Header <br> and Body | Description | Required | Example |
|---|---|---|---|---|---|
| GET | code | - | The contest's hexadecimal code <br> _Returns the contest_ | Yes | `/api/contests?code=f9bf78b9a18ce6d46a0cd2b0b86df9da` |
| GET | code | - | The contest's hexadecimal code <br> _Returns the contest_ | Yes | `/api/contest?code=f9bf78b9a18ce6d46a0cd2b0b86df9da` |
| POST | - | `Content-Type = application/json` <br> <br> { <br> `"contestname": "testContest-1"`, <br> `"date_start": "2017-05-12"`, <br> `"date_end": "2017-06-12"`, <br> `"visible": 1`, <br> `"contestgroups": [1, 2, 3]` <br> } | Add a new contest <br> _Return the new contest's code_ | - | - |
| DELETE | code | - | The contest's hexadecimal code <br> _Deletes the contest_ | Yes | `/api/contests?code=f9bf78b9a18ce6d46a0cd2b0b86df9da` |
| DELETE | code | - | The contest's hexadecimal code <br> _Deletes the contest_ | Yes | `/api/contest?code=f9bf78b9a18ce6d46a0cd2b0b86df9da` |
2 changes: 1 addition & 1 deletion client/src/components/ContestDashboard.vue
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ export default {
};
},
mounted() {
axios.get('/api/contests?code=' + this.$route.params.id).then(response => {
axios.get('/api/contest?code=' + this.$route.params.id).then(response => {
this.items = response.data.tasks;
this.name = response.data.contestname;
});
Expand Down
4 changes: 2 additions & 2 deletions client/src/components/CreateContest.vue
Original file line number Diff line number Diff line change
Expand Up @@ -321,12 +321,12 @@ export default {
};
// Create AXIOS Post request
axios.post('/api/contests', {
axios.post('/api/contest', {
'contestname': this.contestname,
'date_start': new Date().toISOString().substring(0, 19),
'date_end': this.contestdate,
'visible': this.visible,
'contestgroups': this.selectedgroups,
'usergroups': this.selectedgroups,
'tasks': this.tasks.map(task => task.taskid)
}, config)
.then(function (resp) {
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@
},
"scripts": {
"dev": "concurrently \"cd client/ && yarn serve\" \"FLASK_APP=server FLASK_DEBUG=1 flask run --with-threads\"",
"db-rewrite": "rm -rf server/database/database.db && sqlite3 server/database/database.db < server/database/schema.sql"
"db-rewrite": "rm -rf server/database/database.db && sqlite3 server/database/database.db < server/database/schema.sql",
"lint": "pylint server"
},
"dependencies": {
"eslint": "^4.19.1"
Expand Down
93 changes: 87 additions & 6 deletions server/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -196,16 +196,16 @@ def tasks_json_modify(taskJson: dict):
return None


@app.route('/api/contests', methods=['GET', 'POST', 'DELETE'])
def api_contests():
@app.route('/api/contest', methods=['GET', 'POST', 'DELETE'])
def api_contest():
"""
Contest Endpoint: POST with Content-Type = application/json
{
"contestname": "testContest-1",
"date_start": "2017-05-12",
"date_end": "2017-06-12",
"visible": 1,
"contestgroups": [1, 2, 3] (groupids),
"usergroups": [1, 2, 3] (groupids),
"tasks": [1,2,3] (taskids)
}
"""
Expand All @@ -214,6 +214,7 @@ def api_contests():
content = {"Error": "\'code\' parameter missing"}
return content, HTTPStatus.BAD_REQUEST

# get contest JSON
contestJSON = models.select_contest(
params=('*'),
conditions=('{}=\"{}\"'.format(
Expand All @@ -222,15 +223,25 @@ def api_contests():
))
)

# get array of all contest's tasks
tasksInContestJSON = models.get_tasks_in_contest(
get_queryparam('code'))

# get array of all contest's groups
groupsInContestJSON = models.select_group_in_contest(
params=('*'),
conditions=('{}=\"{}\"'.format(
settings.DB_COLUMNS.GROUP_IN_CONTEST_CONTEST,
get_queryparam('code')
))
)

returnJSON = contestJSON
returnJSON["tasks"] = tasksInContestJSON
returnJSON["usergroups"] = groupsInContestJSON

return jsonify(returnJSON)
elif request.method == 'POST':

postJSON = request.get_json()
if not postJSON:
return None
Expand All @@ -240,8 +251,7 @@ def api_contests():
postJSON[settings.DB_COLUMNS.CONTEST_CONTESTNAME],
postJSON[settings.DB_COLUMNS.CONTEST_DATE_START],
postJSON[settings.DB_COLUMNS.CONTEST_DATE_END],
postJSON[settings.DB_COLUMNS.CONTEST_VISIBLE],
postJSON[settings.DB_COLUMNS.CONTEST_CONTESTGROUPS]
postJSON[settings.DB_COLUMNS.CONTEST_VISIBLE]
)

# insert taskids with contestcode in db contains_task table
Expand All @@ -250,6 +260,15 @@ def api_contests():
contestCode,
taskID
)

# insert groupIds with contestcode in db group_in_contest table
for groupID in postJSON["usergroups"]:
models.insert_group_in_contest(
groupID,
contestCode
)

# return the contestcode
return contestCode

elif request.method == 'DELETE':
Expand All @@ -266,6 +285,68 @@ def api_contests():
return None


@app.route('/api/contests', methods=['GET'])
def api_contests():
if request.method == 'GET':
contests = models.select_contest(
params=('*'),
conditions=('{}=\"{}\"').format(
settings.DB_COLUMNS.CONTEST_VISIBLE,
1)
)
return contests
else:
return None


@app.route('/api/usergroup', methods=['GET', 'POST'])
def api_usergroup():
"""
Contest Endpoint: POST with Content-Type = application/json
-> ?group - insert new group
{
"groupname": groupname (String)
"groupadmin": userID of group admin
}
-> ?users - add user to group
{
"usergroup": usergroupID
"user": userID
}
"""
if request.method == 'GET':
returnJSON = models.select_in_usergroup(
params=('*'),
conditions=('{}=\"{}\"').format(
settings.DB_COLUMNS.USERGROUP_GROUPID,
get_queryparam('groupID')
))
return returnJSON
elif request.method == 'POST':
if get_queryparam('users'):
# add user to group
postJSON = request.get_json()
if not postJSON:
return None
else:
models.insert_in_usergroup(
postJSON[settings.DB_COLUMNS.IN_USERGROUP_USERGROUP],
postJSON[settings.DB_COLUMNS.IN_USERGROUP_USER])
elif get_queryparam('group'):
# add a new Usergroup
postJSON = request.get_json()
if not postJSON:
return None
else:
usergroupID = models.insert_usergroup(
postJSON[settings.DB_COLUMNS.USERGROUP_GROUPNAME],
postJSON[settings.DB_COLUMNS.USERGROUP_GROUPADMIN])
return usergroupID
else:
return None
else:
return None

@app.route('/', defaults={'path': ''})
@app.route('/<path:path>')
def catch_all(path):
Expand Down
134 changes: 125 additions & 9 deletions server/database/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,8 @@ def select_task(params=(), conditions=()):
queryResult = cur.execute(queryString)

response = queryResult.fetchall()
if len(response) == 0:
response = response[0] if len(response) == 1 else response
if not response:
return None
else:
return response
Expand All @@ -73,22 +74,19 @@ def insert_contest(
name: str,
dateStart: str,
dateEnd: str,
visible: int,
contestgroups: list):
visible: int):
with sql.connect(DATABASE_PATH) as dbcon:
cur = dbcon.cursor()
randomCode = secrets.token_hex(8)
dateStart = parser.parse(dateStart)
dateEnd = parser.parse(dateEnd)
contestgroups = json.dumps(contestgroups)
cur.execute(
'INSERT INTO Contest (contestcode, contestname, date_start, date_end, visible, contestgroups) VALUES (?,?,?,?,?,?)',
'INSERT INTO Contest (contestcode, contestname, date_start, date_end, visible) VALUES (?,?,?,?,?)',
(randomCode,
name,
dateStart,
dateEnd,
visible,
contestgroups))
visible))
dbcon.commit()
return randomCode

Expand Down Expand Up @@ -122,7 +120,8 @@ def select_contest(params=(), conditions=()):
queryString = queryString[:-4]
queryResult = cur.execute(queryString)

response = queryResult.fetchone()
response = queryResult.fetchall()
response = response[0] if len(response) == 1 else response
if not response:
return None
else:
Expand Down Expand Up @@ -187,7 +186,8 @@ def select_user(params=(), conditions=()):
queryString = queryString[:-4]
queryResult = cur.execute(queryString)

response = queryResult.fetchone()
response = queryResult.fetchall()
response = response[0] if len(response) == 1 else response
if not response:
return None
else:
Expand Down Expand Up @@ -283,11 +283,126 @@ def select_contains_task(params=(), conditions=()):
queryResult = cur.execute(queryString)

response = queryResult.fetchall()
response = response[0] if len(response) == 1 else response
if not response:
return None
else:
return response


def insert_usergroup(
groupname: str,
groupadmin: int):
with sql.connect(DATABASE_PATH) as dbcon:
cur = dbcon.cursor()
cur.execute(
'INSERT INTO Usergroup (groupname, groupadmin) VALUES (?,?)',
(groupname, groupadmin))
groupID = cur.lastrowid
dbcon.commit()
return groupID


def insert_group_in_contest(
usergroup: int,
contest: int):
with sql.connect(DATABASE_PATH) as dbcon:
cur = dbcon.cursor()
cur.execute(
'INSERT INTO group_in_contest (usergroup, contest) VALUES (?,?)',
(usergroup, contest))
dbcon.commit()


def select_group_in_contest(params=(), conditions=()):
with sql.connect(DATABASE_PATH) as dbcon:
cur = dbcon.cursor()
if cur.rowcount == 0:
return None
if params == () and conditions == ():
return None
else:
# convert one-value tuples to real tuples
if not isinstance(params, tuple):
params = (params,)
if not isinstance(conditions, tuple):
conditions = (conditions,)

if params != ():
queryString = 'SELECT'
# add a format-placeholder for every parameter
for paramString in params:
queryString += ' {},'.format(paramString)
queryString = queryString[:-1]
queryString += ' FROM group_in_contest'
if conditions != ():
queryString += ' WHERE'
for conditionString in conditions:
queryString += ' {} AND'.format(conditionString)
queryString = queryString[:-4]
queryResult = cur.execute(queryString)

response = queryResult.fetchall()
response = response[0] if len(response) == 1 else response
if not response:
return None
else:
return response


def insert_in_usergroup(
usergroup: int,
user: int):
with sql.connect(DATABASE_PATH) as dbcon:
cur = dbcon.cursor()
cur.execute(
'INSERT INTO in_usergroup (usergroup, user) VALUES (?,?)',
(usergroup, user))
dbcon.commit()


def select_in_usergroup(params=(), conditions=()):
with sql.connect(DATABASE_PATH) as dbcon:
cur = dbcon.cursor()
if cur.rowcount == 0:
return None
if params == () and conditions == ():
return None
else:
# convert one-value tuples to real tuples
if not isinstance(params, tuple):
params = (params,)
if not isinstance(conditions, tuple):
conditions = (conditions,)

if params != ():
queryString = 'SELECT'
# add a format-placeholder for every parameter
for paramString in params:
queryString += ' {},'.format(paramString)
queryString = queryString[:-1]
queryString += ' FROM in_usergroup'
if conditions != ():
queryString += ' WHERE'
for conditionString in conditions:
queryString += ' {} AND'.format(conditionString)
queryString = queryString[:-4]
queryResult = cur.execute(queryString)

response = queryResult.fetchall()
response = response[0] if len(response) == 1 else response
if not response:
return None
else:
return response

# TODO:
# def get_users_in_contest(contestCode: int):
# queryString = 'SELECT Task.* \
# FROM in_usergroup, Contest \
# WHERE in_usergroup.usergroup = Contest AND \
# contains_task.contest = \"{}\"'.format(contestCode)


def get_tasks_in_contest(contestCode: int):
queryString = 'SELECT Task.* \
Expand All @@ -302,6 +417,7 @@ def get_tasks_in_contest(contestCode: int):
queryResult = cur.execute(queryString)

response = queryResult.fetchall()
response = response[0] if len(response) == 1 else response
if not response:
return None
else:
Expand Down
Loading

0 comments on commit 72dbac1

Please sign in to comment.