// StoreMultStatement はステートメントを単一、もしくは複数挿入するためのハンドラである。 // ステートメントは配列、もしくは単一のJSONの形でリクエストボディに与えられる。 func (c *Controller) StoreMultStatement(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() } // xAPI のバージョンを確認, Experience API, Section 6.2 を参照 xAPIVersion := req.Header.Get("X-Experience-API-Version") if err := validator.MultStatement(validator.ToXAPIVersion(xAPIVersion), statements); err != nil { return NewBadRequestErrF("Invalid statement: %s", err).Response() } // attachments がある場合、チェック if attachmentSHA2s != nil && !hasSameHashBetween(statements, attachmentSHA2s) { return NewBadRequestErr("Unexpected content hash given on attachment or multipart header").Response() } // authority を取得 authority := bson.M{ "objectType": "Agent", "account": bson.M{ "homePage": req.Header.Get(miscs.GlobalConfig.Authority.HomePageHeader), "name": req.Header.Get(miscs.GlobalConfig.Authority.NameHeader), }, } docs, insertedIDs, err := parseStatementsAndGetIDs(xAPIVersion, user, app, statements, authority) if err != nil { return NewBadRequestErrF("Invalid statements: %s", err).Response() } if status, mess := c.insertIntoDB(xAPIVersion, user, app, docs); status != http.StatusOK { return status, mess } result, err := json.Marshal(insertedIDs) if err != nil { logger.Err("An unexpected error occured: ", err) return http.StatusInternalServerError, "Internal Server Error" } w.Header().Set("Content-Type", "application/json") return http.StatusOK, string(result) }
// queryOfAgent は agnet の文字列を受け取り、データベースのクエリを返す。 // 引数に与えられた文字列が変換不可ならエラーを返す。 func queryOfAgent(xAPIVersion, agentString string, relatedActivities bool) (bson.M, error) { var agent map[string]interface{} err := json.Unmarshal([]byte(agentString), &agent) if err != nil { return nil, err } if err = validator.Agent(validator.ToXAPIVersion(xAPIVersion), agent); err != nil { return nil, err } terms := termsOfAgent(agent, relatedActivities) return queryOfTerms(terms), 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" }