func parseStatementsAndGetIDs(version, user, app string, reqBody []interface{}, authority bson.M) (model.DocumentSlice, []string, error) { docs := make(model.DocumentSlice, 0, len(reqBody)) insertedIDs := make([]string, 0, len(reqBody)) currentTime := time.Now() for _, v := range reqBody { stmt, ok := v.(map[string]interface{}) if !ok { return nil, nil, errors.New("unexpected statement structure") } // ID 入っていなければ補完 if _, ok := stmt["id"]; !ok { stmt["id"] = uuid.NewV4().String() } // タイムスタンプに関する処理 timestamp := currentTime stmt["stored"] = currentTime if ts, ok := stmt["timestamp"]; ok { t, err := time.Parse(time.RFC3339Nano, ts.(string)) if err != nil { return nil, nil, fmt.Errorf("timestamp must be of the form of RFC3339") } timestamp = t } else { stmt["timestamp"] = timestamp } // authority フィールドの補完 stmt["authority"] = authority docs = append(docs, *model.NewDocument(version, user, app, timestamp, stmt)) insertedIDs = append(insertedIDs, stmt["id"].(string)) } return docs, insertedIDs, nil }
// 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" }