Skip to content

Commit 6b4b036

Browse files
committed
api and ui now works for: createChallenge, list(chlg,resp), respond, view response. createChallenge and listchallenge already worked.
1 parent de5efb7 commit 6b4b036

17 files changed

+444
-257
lines changed

api/api/models.py

+3-2
Original file line numberDiff line numberDiff line change
@@ -76,8 +76,8 @@ class Challenge(db.Model):
7676
instructions = db.Column(db.UnicodeText, nullable=False)
7777
grading_notes = db.Column(db.UnicodeText, nullable=False)
7878

79-
creator = db.relationship('User', backref='challenges')
80-
creator_id = db.Column(UUID(as_uuid=True), db.ForeignKey('users.id'))
79+
user = db.relationship('User', backref='challenges')
80+
user_id = db.Column(UUID(as_uuid=True), db.ForeignKey('users.id'))
8181

8282
video = db.relationship('Video', backref=backref('challenges', uselist=False))
8383
video_id = db.Column(UUID(as_uuid=True), db.ForeignKey('videos.id'), nullable=True)
@@ -95,6 +95,7 @@ class User(db.Model):
9595
id = db.Column(UUID(as_uuid=True), server_default=sqlalchemy.text("gen_random_uuid()"), primary_key=True)
9696
name = db.Column(db.Unicode(255))
9797
email = db.Column(db.String(255), unique=True, nullable=False)
98+
# password = db.Column(db.Varchar(255))
9899

99100
created_at = db.Column(db.DateTime(timezone=True), server_default=func.now(), nullable=False)
100101
updated_at = db.Column(db.DateTime(timezone=True), server_default=func.now(), onupdate=datetime.utcnow, nullable=False)

api/api/resources.py

+51-11
Original file line numberDiff line numberDiff line change
@@ -34,22 +34,26 @@ def post(self):
3434

3535
json = request.get_json()
3636

37-
print("REQUESTFIEL", request.files['videoBlob'])
3837
video = m.Video().create_and_upload(request.files['videoBlob'])
3938

40-
# TODO: don't create a new user each time!
41-
user = m.User(
42-
name = request.form['name'],
43-
email = request.form['email']
44-
)
39+
user_name = request.form['name']
40+
user_email = request.form['email']
41+
42+
user = m.User.query.filter_by(name=user_name, email=user_email).one_or_none()
43+
44+
if user == None:
45+
user = m.User(
46+
name = user_name,
47+
email = user_email
48+
)
4549

46-
user.save()
50+
user.save()
4751

4852
challenge = m.Challenge(
4953
title = request.form['title'],
5054
instructions = request.form['instructions'],
5155
grading_notes = request.form['grading_notes'],
52-
creator = user,
56+
user = user,
5357
video = video
5458
)
5559

@@ -59,22 +63,58 @@ def post(self):
5963

6064
class ChallengeList(Resource):
6165
def get(self):
66+
# TODO: Add user name to the response
6267
challenges = m.Challenge.query.options(joinedload('video')).all()
6368

6469
return s.ChallengeSchema(many=True).jsonify(challenges).json, 200
6570

6671
class Response(Resource):
6772
def get(self, response_id):
68-
response = m.Response.get_or_404(response_id)
73+
response = m.Response.query.get_or_404(response_id)
6974

7075
return s.ResponseSchema().jsonify(response).json, 200
7176

7277
class ResponseList(Resource):
7378
def get(self):
74-
responses = m.Response.query.all()
79+
responses = m.Response.query.options(
80+
joinedload('challenge'),
81+
joinedload('user'),
82+
joinedload('video')
83+
).all()
84+
7585
return s.ResponseSchema(many=True).jsonify(responses).json, 200
7686

7787

7888
class CreateResponse(Resource):
7989
def post(self):
80-
return {"<response>": "response"}, 201
90+
if 'videoBlob' not in request.files:
91+
abort(400)
92+
93+
json = request.get_json()
94+
95+
video = m.Video().create_and_upload(request.files['videoBlob'])
96+
97+
user_name = request.form['name']
98+
user_email = request.form['email']
99+
100+
user = m.User.query.filter_by(name=user_name, email=user_email).one_or_none()
101+
102+
if user == None:
103+
user = m.User(
104+
name = user_name,
105+
email = user_email
106+
)
107+
108+
user.save()
109+
110+
challenge = m.Challenge.query.get_or_404(request.form['challengeId'])
111+
112+
response = m.Response(
113+
challenge = challenge,
114+
user = user,
115+
video = video
116+
)
117+
118+
response.save()
119+
120+
return s.ResponseSchema().jsonify(response).json, 201

api/api/schemas.py

+5-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
class VideoSchema(ModelSchema):
77
class Meta:
88
model = models.Video
9-
exclude = ["challenges", "response"]
109

1110
class UserSchema(ModelSchema):
1211
class Meta:
@@ -17,7 +16,12 @@ class Meta:
1716
model = models.Challenge
1817

1918
video = fields.Nested(VideoSchema)
19+
user = fields.Nested(UserSchema)
2020

2121
class ResponseSchema(ModelSchema):
2222
class Meta:
2323
model = models.Response
24+
25+
challenge = fields.Nested(ChallengeSchema)
26+
video = fields.Nested(VideoSchema)
27+
user = fields.Nested(UserSchema)

api/migrations/versions/ae32602be2ae_.py api/migrations/versions/7a8df53a11e8_.py

+5-5
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
11
"""empty message
22
3-
Revision ID: ae32602be2ae
3+
Revision ID: 7a8df53a11e8
44
Revises:
5-
Create Date: 2019-07-02 23:20:48.175119
5+
Create Date: 2019-07-11 19:22:25.849737
66
77
"""
88
from alembic import op
99
import sqlalchemy as sa
1010
from sqlalchemy.dialects import postgresql
1111

1212
# revision identifiers, used by Alembic.
13-
revision = 'ae32602be2ae'
13+
revision = '7a8df53a11e8'
1414
down_revision = None
1515
branch_labels = None
1616
depends_on = None
@@ -39,11 +39,11 @@ def upgrade():
3939
sa.Column('title', sa.Unicode(length=255), nullable=False),
4040
sa.Column('instructions', sa.UnicodeText(), nullable=False),
4141
sa.Column('grading_notes', sa.UnicodeText(), nullable=False),
42-
sa.Column('creator_id', postgresql.UUID(as_uuid=True), nullable=True),
42+
sa.Column('user_id', postgresql.UUID(as_uuid=True), nullable=True),
4343
sa.Column('video_id', postgresql.UUID(as_uuid=True), nullable=True),
4444
sa.Column('created_at', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=False),
4545
sa.Column('updated_at', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=False),
46-
sa.ForeignKeyConstraint(['creator_id'], ['users.id'], ),
46+
sa.ForeignKeyConstraint(['user_id'], ['users.id'], ),
4747
sa.ForeignKeyConstraint(['video_id'], ['videos.id'], ),
4848
sa.PrimaryKeyConstraint('id')
4949
)

ui/src/App.js

-1
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ const App = () => (
2020
<ChallengeList path="/challenges" />
2121
<ChallengeRecorder path="/challenges/create" />
2222
<ResponseRecorder path="/challenges/:challengeId" />
23-
2423
<ResponseList path="/responses" />
2524
<ResponseViewer path="/responses/:responseId" />
2625
</NavPage>

ui/src/api.js

+23-85
Original file line numberDiff line numberDiff line change
@@ -1,92 +1,8 @@
11
const API = "/api";
2-
const VIDEO_API = `${API}/videos`;
32
const CHALLENGE_API = `${API}/challenges`;
3+
const RESPONSE_API = `${API}/responses`;
44

55
class HumanApi {
6-
constructor() {
7-
this.challenges = [
8-
{
9-
challengeId: "45",
10-
name: "car_crash",
11-
instructions: "How much empathy do you have for bad drivers?",
12-
gradingCriteria: "Does the viewer cringe?",
13-
link: "https://fat.gfycat.com/PowerlessDiligentAstarte.webm",
14-
author: "Brian Basham"
15-
},
16-
{
17-
challengeId: "202",
18-
name: "big_buck_bunny",
19-
instructions: "Smile when you see animals!?",
20-
gradingCriteria: "Does the viewer smile when they see animals?",
21-
link:
22-
"https://storage.googleapis.com/the-human-factor-videos/19f32a31-5fd8-4ec9-85c4-5dc9216795f6",
23-
author: "Leo Urbina"
24-
},
25-
{
26-
challengeId: "345",
27-
name: "brian_prices",
28-
instructions: "Find out what Brian's budget is",
29-
gradingCriteria: "How empathetic and curious are you?",
30-
link:
31-
"https://storage.googleapis.com/the-human-factor-videos/d8912f88-3726-48e5-b280-fde3bf17d2b4",
32-
author: "Brian Basham"
33-
},
34-
{
35-
challengeId: "888",
36-
name: "alex_cant_trust_tech",
37-
instructions: "Find a way forward in the conversation",
38-
gradingCriteria: "How empathetic and curious are you?",
39-
link:
40-
"https://storage.googleapis.com/the-human-factor-videos/70e70f89-072e-4de1-9357-5214e5d6d6c9",
41-
author: "Alex Warren"
42-
}
43-
];
44-
45-
this.responses = [
46-
{
47-
responseId: "111",
48-
challengeId: "345",
49-
responder: "Leo Urbina",
50-
notes: "I think I did OK.",
51-
link:
52-
"https://storage.googleapis.com/the-human-factor-videos/cedd88e6-0841-4d2f-8b8e-123bc475f7a7"
53-
},
54-
{
55-
responseId: "505",
56-
challengeId: "345",
57-
responder: "Alex Warren",
58-
notes: "That was uncomfortable.",
59-
link:
60-
"https://storage.googleapis.com/the-human-factor-videos/c1c57ea6-00bd-432f-a71c-09c7685ae70f"
61-
},
62-
{
63-
responseId: "999",
64-
challengeId: "888",
65-
responder: "Brian Basham",
66-
notes: "Couldn't stop laughing.",
67-
link:
68-
"https://storage.googleapis.com/the-human-factor-videos/10ded6f0-5e9f-45a9-88ce-a273cd1bb4ad"
69-
}
70-
];
71-
}
72-
73-
getResponseIds() {
74-
return this.responses.map(response => {
75-
return response.responseId;
76-
});
77-
}
78-
79-
getResponse(responseId) {
80-
for (let response of this.responses) {
81-
if (response.responseId === responseId) {
82-
return response;
83-
}
84-
}
85-
alert("no such response" + responseId);
86-
}
87-
88-
addVideo(blob, callback) {}
89-
906
createChallenge(challenge) {
917
let formData = this.convertToForm(challenge);
928
return fetch(`${CHALLENGE_API}/create`, {
@@ -101,6 +17,20 @@ class HumanApi {
10117
});
10218
}
10319

20+
createResponse(response) {
21+
let formData = this.convertToForm(response);
22+
return fetch(`${RESPONSE_API}/create`, {
23+
method: "POST",
24+
body: formData
25+
})
26+
.then(res => {
27+
return res.json();
28+
})
29+
.catch(error => {
30+
console.error("Failed to createResponse:", error);
31+
});
32+
}
33+
10434
convertToForm(obj) {
10535
var formData = new FormData();
10636
for (const [key, val] of Object.entries(obj)) {
@@ -116,6 +46,14 @@ class HumanApi {
11646
console.error("Failed to fetch challenges");
11747
});
11848
}
49+
50+
fetchResponses() {
51+
return fetch(`${RESPONSE_API}`)
52+
.then(res => res.json())
53+
.catch(error => {
54+
console.error("Failed to fetch challenges");
55+
});
56+
}
11957
}
12058

12159
export default new HumanApi();

ui/src/images/VideoPlaceholder.jpg

18.3 KB
Loading

ui/src/modules/responses/actions.js

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { actions } from "./index";
2+
import api from "api";
3+
import { selectors } from "./";
4+
5+
export const createResponse = data => dispatch => {
6+
dispatch(actions.createResponsePending());
7+
return api.createResponse(data).then(response => {
8+
dispatch(actions.createResponseSuccess(response));
9+
});
10+
};
11+
12+
export const fetchResponses = (force = false) => (dispatch, getState) => {
13+
if (selectors.isLoaded(getState()) && !force) {
14+
console.log("Responses already loaded");
15+
return;
16+
}
17+
18+
dispatch(actions.fetchResponsesPending());
19+
return api
20+
.fetchResponses()
21+
.then(responses => dispatch(actions.fetchResponsesSuccess(responses)));
22+
};

ui/src/modules/responses/index.js

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import { actions, reducer } from "./slice";
2+
import * as selectors from "./selectors";
3+
4+
export { actions, selectors };
5+
export default reducer;

ui/src/modules/responses/selectors.js

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { selectors } from "./slice";
2+
import { State, TEMP_ID } from "modules/utils";
3+
4+
const root = selectors.getResponses;
5+
6+
export const isLoading = state => root(state).meta === State.LOADING;
7+
export const isLoaded = state => root(state).meta === State.LOADED;
8+
9+
export const responses = state => root(state).data;
10+
11+
export const responseCreationPending = (state, props) =>
12+
root(state).data[TEMP_ID].meta === State.CREATING;
13+
14+
export const response = (state, props) => root(state).data[props.responseId];

ui/src/modules/responses/slice.js

+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import { createSlice } from "redux-starter-kit";
2+
import { State, TEMP_ID } from "modules/utils";
3+
4+
export const { actions, selectors, reducer } = createSlice({
5+
slice: "responses",
6+
initialState: { data: {}, meta: State.NOT_LOADED },
7+
reducers: {
8+
// Create responses
9+
createResponsePending(state) {
10+
const response = { meta: State.CREATING };
11+
response.meta = State.CREATING;
12+
state.data[TEMP_ID] = response;
13+
},
14+
createResponseSuccess(state, action) {
15+
const response = action.payload;
16+
console.log(state, response);
17+
state.data[response.id] = response;
18+
},
19+
20+
// Fetch responses
21+
fetchResponsesPending(state) {
22+
state.meta = State.LOADING;
23+
},
24+
fetchResponsesSuccess(state, action) {
25+
const responsesById = action.payload.reduce((acc, response) => {
26+
acc[response.id] = response;
27+
return acc;
28+
}, {});
29+
state.meta = State.LOADED;
30+
state.data = responsesById;
31+
}
32+
}
33+
});

0 commit comments

Comments
 (0)