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