Esempio n. 1
0
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
}
Esempio n. 2
0
// 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"
}