// findSingleStatement は単一のステートメントをレスポンスとして返す。 // この関数はリクエストパラメータに statementId もしくは voidedStatementId が指定されている時のみ呼ばれる。 func (c *Controller) findSingleStatement(xAPIVersion, user, app string, params url.Values, rw http.ResponseWriter) (int, string) { // リクエストパラメータのバリデート if !validSingleStmtRequestOf(params) { return NewBadRequestErr("Got invalid extra parameter with statementId or voidedStatementId").Response() } includeAttachments, err := parseParamBool(params, "attachments") if err != nil { return NewBadRequestErrF("Invalid attachments parameter given: %s", err).Response() } query := bson.M{ "version": xAPIVersion, "user": user, "app": app, } // データベースのセッションを取得 sess := c.session.New() defer sess.Close() col := sess.DB(miscs.GlobalConfig.MongoDB.DBName).C("statement") // statementId, もしくは voidedStatementId をチェックし、データベースのクエリを生成する。 if statementID := params.Get("statementId"); validator.IsUUID(statementID) { // リクエストパラメータに statementId が指定されていたとき if isVoidedStatement(col, xAPIVersion, user, app, statementID) { // 指定されたステートメントIDが Voided ならば not found return http.StatusNotFound, "Not Found" } query["data.id"] = statementID } else if voidedStatementID := params.Get("voidedStatementId"); validator.IsUUID(voidedStatementID) { // リクエストパラメータに voidedStatementId が指定されていたとき if !isVoidedStatement(col, xAPIVersion, user, app, voidedStatementID) { // 指定されたステートメントIDが Voided """でなければ"" not found return http.StatusNotFound, "Not Found" } query["data.id"] = voidedStatementID } else { return NewBadRequestErr("A statementId or voidedStatementId is required").Response() } var document model.Document err = col.Find(query).One(&document) if err == mgo.ErrNotFound { return http.StatusNotFound, "Statement Not Found" } if err != nil { logger.Err("An unexpected error occured on find DB: ", err) return http.StatusInternalServerError, "Internal Server Error" } res, err := json.Marshal(document.Data) if err != nil { // data はデータベースから受け取った値なので、このエラーが起きる場合は深刻な問題がある。 logger.Err("An unexpected error occured: ", err) return http.StatusInternalServerError, "Internal Server Error" } // attachment を含まないのであればそのまま返す if !includeAttachments { rw.Header().Set("Content-Type", "application/json") return http.StatusOK, string(res) } // attachment を含むリクエスト sha2s := collectSHA2sOfAttachments(model.DocumentSlice{document}) buf, boundary, err := appendAttachments(sess, res, sha2s) if err != nil { logger.Warn(err) return http.StatusInternalServerError, "Internal Server Error" } rw.Header().Set("Content-Type", "multipart/mixed; boundary="+boundary) return http.StatusOK, string(buf) }
// StoreStatement はステートメントの PUT リクエストを扱うハンドラである。 // このハンドラは与えられたステートメントをバリデートし、データベースに挿入する。 // URLパラメータには UUID (ステートメントID) が与えられており、そのIDのステートメントを挿入する。 func (c *Controller) StoreStatement(params martini.Params, w http.ResponseWriter, req *http.Request) (int, string) { w.Header().Set("Access-Control-Allow-Origin", "*") w.Header().Set("X-Experience-API-Version", "1.0.2") user, app := params["user"], params["app"] contentType := req.Header.Get("Content-Type") if len(contentType) == 0 { contentType = "application/json" } statements, attachmentSHA2s, err := c.parseRequestBody(req.Body, contentType) if err != nil { return NewBadRequestErrF("An error occured on parse request: %s", err).Response() } if len(statements) != 1 { return NewBadRequestErr("Statement must be single on PUT request").Response() } // parseRequestBody でチェックしているため変換可能 statement := statements[0].(map[string]interface{}) // xAPI のバージョンを確認, Experience API, Section 6.2 を参照 xAPIVersion := req.Header.Get("X-Experience-API-Version") if err := validator.Statement(validator.ToXAPIVersion(xAPIVersion), statement); err != nil { return NewBadRequestErrF("Invalid statement: %s", err).Response() } // ステートメントIDをチェック, Experience API, Section 7.2.1 を参照 statementID := req.URL.Query().Get("statementId") if !validator.IsUUID(statementID) { return NewBadRequestErr("Statement ID must be valid UUID").Response() } // Attachment と multipart/mixed に指定されるヘッダ情報との整合性をチェック if attachmentSHA2s != nil && !hasSameHashBetween(statements, attachmentSHA2s) { return NewBadRequestErr("Unexpected content hash given on attachment or multipart header").Response() } // 与えられたステートメントと、URLのパラメータのIDをチェック if id, ok := statement["id"]; !ok { // URL にのみ ID が付加されているときは, ステートメントにその ID を補完 statement["id"] = statementID } else if id.(string) != statementID { // ステートメントに指定されている ID と違っている場合はエラー return NewBadRequestErr("ID mismatch between URL parameter and request body").Response() } // タイムスタンプに関する処理 currentTime := time.Now() timestamp := currentTime statement["stored"] = currentTime if ts, ok := statement["timestamp"]; ok { t, err := time.Parse(time.RFC3339Nano, ts.(string)) if err != nil { return NewBadRequestErr("Timestamp must be of the form of RFC3339").Response() } timestamp = t } else { statement["timestamp"] = timestamp } // authority フィールドの補完 statement["authority"] = bson.M{ "objectType": "Agent", "account": bson.M{ "homePage": req.Header.Get(miscs.GlobalConfig.Authority.HomePageHeader), "name": req.Header.Get(miscs.GlobalConfig.Authority.NameHeader), }, } if code, mess := c.insertIntoDB(xAPIVersion, user, app, model.DocumentSlice{ *model.NewDocument(xAPIVersion, user, app, timestamp, statement), }); code != http.StatusOK { return code, mess } return http.StatusNoContent, "No Content" }