コード例 #1
0
def _get_board_posts(board_type):
    table_name = db.table.board(board_type)
    page = request.args.get('page', 1)

    # if unknown board type
    if not table_name:
        return helper.response_err(ER.INVALID_REQUEST_BODY,
                                   ER.INVALID_REQUEST_BODY_MSG)

    from modules.pagination import Pagination

    fetch_query_str = """
        SELECT `b`.*, '!user', `u`.* 
        FROM `{}` `b` 
        INNER JOIN `users` `u` 
          ON (`u`.`user_id` = `b`.`user_id`)
    """.format(table_name)

    count_query_str = "SELECT COUNT(*) AS `cnt` FROM `{}`".format(table_name)
    order_query_str = "ORDER BY `post_id` DESC"

    with db.engine_rdonly.connect() as connection:
        return helper.response_ok(
            Pagination(connection=connection,
                       fetch=fetch_query_str,
                       count=count_query_str,
                       order=order_query_str,
                       current_page=page).get_result(db.to_relation_model))
コード例 #2
0
def _delete_draftbox(board_type):
    user_id = request.user['user_id']

    with db.engine_rdwr.connect() as connection:
        db.statement(db.table.DRAFT_BOX).where(user_id=user_id, board_type=board_type).limit(1).delete(connection)

        return helper.response_ok({'status': 'success'})
コード例 #3
0
def _modify_board_comment(board_type, comment_id):
    json_form = request.get_json(force=True, silent=True)
    content = json_form.get('content')

    user_id = request.user['user_id']

    if not isinstance(content, str):
        return helper.response_err(ER.INVALID_REQUEST_BODY,
                                   ER.INVALID_REQUEST_BODY_MSG)

    table_name = db.table.comment(board_type)

    # if unknown board type
    if not table_name:
        return helper.response_err(ER.INVALID_REQUEST_BODY,
                                   ER.INVALID_REQUEST_BODY_MSG)

    statement = db.Statement(table_name).set(content=content)\
        .where(user_id=user_id, comment_id=comment_id, status='posted')

    with db.engine_rdwr.connect() as connection:
        updated = statement.update(connection).rowcount
        if updated:
            return helper.response_ok({'status': 'success'})
        else:
            return helper.response_err(ER.AUTHENTICATION_FAILED,
                                       ER.AUTHENTICATION_FAILED_MSG)
コード例 #4
0
def _delete_post(board_type, post_id):
    """
    Delete a post that user has.
    """
    user_id = request.user['user_id']

    table_name = db.table.board(board_type)

    # if unknown board type
    if not table_name:
        return helper.response_err(ER.INVALID_REQUEST_BODY,
                                   ER.INVALID_REQUEST_BODY_MSG)

    statement = db.Statement(table_name)\
        .set(status='deleted')\
        .where(post_id=post_id, user_id=user_id, status='posted')

    with db.engine_rdwr.connect() as connection:
        deleted = statement.update(connection).rowcount

        # if the post does not exist or is not the user's post
        if not deleted:
            return helper.response_err(ER.AUTHENTICATION_FAILED,
                                       ER.AUTHENTICATION_FAILED_MSG)

        return helper.response_ok({'status': 'success'})
コード例 #5
0
def _login():
    from modules.login import generate_jwt_token

    json_form = request.get_json(force=True, silent=True)
    address = json_form.get('address')
    signature = json_form.get('signature')
    signature_version = json_form.get('signature_version', 1)
    user_name = json_form.get('user_name', '')

    platform_type = json_form.get('platform_type')
    if platform_type not in PLATFORM_TYPES:
        return helper.response_err(ER.INVALID_REQUEST_BODY, ER.INVALID_REQUEST_BODY_MSG)

    web3 = get_web3()

    with db.engine_rdwr.connect() as connection:
        jwt_token = generate_jwt_token(
            connection,
            web3, address, signature,
            platform_type=platform_type,
            signature_version=signature_version,
            default_user_name=user_name
        )

        if jwt_token:
            return helper.response_ok(jwt_token)
        else:
            return helper.response_err(ER.AUTHENTICATION_FAILED, ER.AUTHENTICATION_FAILED_MSG)
コード例 #6
0
def _get_board_comment(board_type, comment_id):
    table_name = db.table.comment(board_type)

    # if unknown board type
    if not table_name:
        return helper.response_err(ERR.COMMON.INVALID_REQUEST_BODY)

    statement = db.Statement(table_name).where(comment_id=comment_id,
                                               status='posted')
    subcomments_query_str = """
        SELECT `c`.*, '!user', `u`.* FROM `{}` `c`
        INNER JOIN `users` `u`
        ON (`u`.`user_id` = `c`.`user_id`)
        WHERE `parent_comment_id` = :parent_comment_id AND `status` = :comment_status
        ORDER BY `c`.`created_at` ASC
    """.format(table_name)

    with db.engine_rdonly.connect() as connection:
        comment = statement.select(connection).fetchone()

        # if the comment does not exist
        if not comment:
            return helper.response_err(ERR.COMMON.NOT_EXIST)

        comment = db.to_relation_model(comment)
        subcomments = connection.execute(
            text(subcomments_query_str),
            parent_comment_id=comment['comment_id'],
            comment_status='posted')

        comment.update({'subcomments': db.to_relation_model_list(subcomments)})
        return helper.response_ok(comment)
コード例 #7
0
def _post_board_comment(board_type, post_id):
    json_form = request.get_json(force=True, silent=True)
    content = json_form.get('content')

    user_id = request.user['user_id']

    if not isinstance(content, str):
        return helper.response_err(ERR.COMMON.INVALID_REQUEST_BODY)

    table_name = db.table.comment(board_type)

    # if unknown board type
    if not table_name:
        return helper.response_err(ERR.COMMON.INVALID_REQUEST_BODY)

    statement = db.Statement(table_name).set(user_id=user_id,
                                             post_id=post_id,
                                             content=content)

    with db.engine_rdwr.connect() as connection:
        try:
            comment_id = statement.insert(connection).lastrowid
        except IntegrityError:
            # if failed to insert
            return helper.response_err(ERR.COMMON.INVALID_REQUEST_BODY)

        return helper.response_ok({'comment_id': comment_id})
コード例 #8
0
def _put_draftbox(board_type):
    draft_box = json.dumps(request.get_json(force=True, silent=True))
    user_id = request.user['user_id']

    # if json is null, don't clear original data for safety.
    # the data can be cleared by DELETE method.
    if not draft_box:
        return helper.response_err(ERR.COMMON.NOT_ALLOWED_CONTENT_TYPE)

    put_query_stmt = """
        INSERT INTO `user_post_drafts`
        SET
          `user_id` = :user_id,
          `board_type` = :board_type,
          `draft_box` = :draft_box
        ON DUPLICATE KEY UPDATE
          `board_type` = :board_type,
          `draft_box` = :draft_box
    """

    with db.engine_rdwr.connect() as connection:
        connection.execute(text(put_query_stmt), user_id=user_id, board_type=board_type, draft_box=draft_box)
        return helper.response_ok({
            'status': 'success'
        })
コード例 #9
0
def _get_draftbox(board_type):
    user_id = request.user['user_id']

    with db.engine_rdonly.connect() as connection:
        draft_box_stmt = db.statement(db.table.DRAFT_BOX)\
            .where(user_id=user_id, board_type=board_type).limit(1)\
            .select(connection).fetchone()

        if draft_box_stmt is None:
            # if user's draftbox does not exist, return empty draft box
            return helper.response_ok({
                board_type: []
            })

        else:
            return helper.response_ok(json.loads(draft_box_stmt['draft_box']))
コード例 #10
0
def _put_user_info():
    """
    Modify user's info. Never change user_id, address.
    """
    json_form = request.get_json(force=True, silent=True)
    user_id = request.user['user_id']
    profile_file_id = json_form.get('profile_file_id')

    # only these columns can be changed
    changable_columns = ['name', 'youtube_url', 'facebook_url', 'soundcloud_url', 'spotify_url']
    change_value = {}

    if isinstance(profile_file_id, int):
        change_value.update({'profile_file_id': profile_file_id})

    for column in changable_columns:
        if column in json_form:
            change_value.update({column: json_form.get(column)})

        if column == 'name' and len(change_value['name']) < 1:
            return helper.response_err(ERR.COMMON.TOO_SHORT_PARAMETER)

    with db.engine_rdwr.connect() as connection:
        try:
            db.statement(db.table.USERS).set(**change_value).where(user_id=user_id).update(connection)
        except IntegrityError:
            return helper.response_err(ERR.COMMON.ALREADY_EXIST)
        return helper.response_ok({'status': 'success'})
コード例 #11
0
def _get_community_post(board_type, post_id):
    table_name = db.table.board(board_type)

    # if unknown board type
    if not table_name:
        return helper.response_err(ERR.COMMON.INVALID_REQUEST_BODY)

    if board_type == 'music':
        # if board type is music, show with related music contracts and IPFS file.
        additional_columns = """
            , '!music_contract', `mc`.*
        """
        inner_join = """
            LEFT JOIN `{}` `mc`
              ON (`mc`.`post_id` = `b`.`post_id`)
        """.format(db.table.MUSIC_CONTRACTS, db.table.IPFS_FILES)
    else:
        additional_columns = ''
        inner_join = ''

    post_query_statement = """
        SELECT `b`.* {} FROM `{}` `b`
        {}
        WHERE `b`.`post_id` = :post_id AND `b`.`status` = :status
    """.format(additional_columns, table_name, inner_join)

    ipfs_files_query_statement = """
        SELECT * FROM `ipfs_files` `if`
        INNER JOIN `music_contracts` `mc`
          ON (`mc`.`ipfs_file_id` = `if`.`file_id` OR `mc`.`ipfs_file_id` = `if`.`root_id`)
        WHERE `mc`.`post_id` = :post_id
    """

    tags_statement = db.Statement(
        db.table.tags(board_type)).columns('name').where(post_id=post_id)

    with db.engine_rdonly.connect() as connection:
        post = connection.execute(text(post_query_statement),
                                  post_id=post_id,
                                  status='posted').fetchone()

        # if the post does not exist,
        if post is None:
            return helper.response_err(ERR.COMMON.NOT_EXIST)
        post = db.to_relation_model(post)

        if board_type == 'music':
            ipfs_files = db.to_relation_model_list(
                connection.execute(text(ipfs_files_query_statement),
                                   post_id=post_id))
            post['music_contract'].update({'ipfs_file': ipfs_files})

        tags = tags_statement.select(connection)

        post.update({'tags': [tag['name'] for tag in tags]})
        return helper.response_ok(post)
コード例 #12
0
def _upload_file():
    """
    uploads a file. (Only one file is allowed at once)
    """
    user_id = request.user['user_id']
    file_type = request.values.get('type')

    # if the number of files uploaded is not one
    if len(request.files) != 1:
        return helper.response_err(ERR.COMMON.INVALID_REQUEST_BODY)

    # if not support file type
    if file_type not in s3_policy:
        return helper.response_err(ERR.COMMON.INVALID_REQUEST_BODY)

    file = next(iter(request.files.values()))
    file_name = file.filename

    # read with content length size
    read_len = s3_policy[file_type]['file_size_limit'] + 1
    file_data = file.read(read_len)

    # if the file size is too big
    if file.tell() == read_len:
        return helper.response_err(ERR.COMMON.FILE_SIZE_LIMIT_EXCEEDED)

    # check the number of file user uploaded for defending to upload too many files.
    upload_log_stmt = """
            SELECT COUNT(*) AS `cnt` 
            FROM `files`
            WHERE 
              `user_id` = :user_id AND 
              `created_at` > NOW() - INTERVAL 10 MINUTE AND
              `type` = :file_type
        """

    profile_bucket = MuzikaS3Bucket(file_type=file_type)
    with db.engine_rdwr.connect() as connection:
        upload_cnt = connection.execute(text(upload_log_stmt),
                                        user_id=user_id,
                                        file_type=file_type).fetchone()['cnt']

        # if the file uploaded too much, reject the request
        upload_cnt_limit = s3_policy[file_type].get('upload_count_limit')
        if upload_cnt_limit and upload_cnt >= upload_cnt_limit:
            return helper.response_err(ERR.COMMON.TOO_MANY_REQUEST)

        file = profile_bucket.put(connection=connection,
                                  name=file_name,
                                  value=file_data,
                                  user_id=user_id)

        if not file:
            return helper.response_err(ERR.COMMON.UPLOAD_FAIL)

        return helper.response_ok({'file_id': file['file_id']})
コード例 #13
0
def _get_draft():
    user_id = request.user['user_id']
    board_type = request.args.get('boardType')

    with db.engine_rdonly.connect() as connect:
        draft_list = db.statement(db.table.POST_DRAFTS) \
            .where(user_id=user_id, board_type=board_type) \
            .select(connect)

        return helper.response_ok(
            [dict(draft_row) for draft_row in draft_list])
コード例 #14
0
def _get_user_sign_message(address):
    """
    Returns an user information by wallet address.

    If user(address) does not exist, give a random message for signing
    """

    # if invalid address format, don't generate message
    if not check_address_format(address):
        return helper.response_err(ER.INVALID_REQUEST_BODY, ER.INVALID_REQUEST_BODY_MSG)

    return helper.response_ok(get_message_for_user(address, always_new=True))
コード例 #15
0
def _get_user_ont_sign_message(address):
    """
        Returns an user information by ontology account address.

        If user(address) does not exist, give a random message for signing
        """

    # if invalid address format, don't generate message
    if not check_ont_address_format(address):
        return helper.response_err(ERR.COMMON.INVALID_REQUEST_BODY)

    return helper.response_ok(get_message_for_user(address, always_new=True, protocol='ont'))
コード例 #16
0
def _change_user_profile():
    json_form = request.get_json(force=True, silent=True)
    user_id = request.user['user_id']
    profile_file_id = request.user.get('profile_file_id')

    if not isinstance(profile_file_id, int):
        return helper.response_err(ERR.COMMON.INVALID_REQUEST_BODY)

    with db.engine_rdwr.connect() as connection:
        db.statement(db.table.USERS).set(profile_file_id=profile_file_id).where(user_id=user_id).update(connection)

    return helper.response_ok({'status': 'success'})
コード例 #17
0
def _post_draft():
    user_id = request.user['user_id']
    board_type = request.args.get('boardType')
    data = request.get_json(force=True, silent=True)

    with db.engine_rdwr.connect() as connect:
        draft_id = db.statement(
            db.table.POST_DRAFTS).set(type=board_type,
                                      user_id=user_id,
                                      data=json.dumps(data),
                                      version=1).insert(connect).lastrowid

        return helper.response_ok(draft_id)
コード例 #18
0
def _get_comments_likes(user_id, board_type):
    """
    Gets an user's comment likes list
    """
    table_name = db.table.comment_like(board_type)

    if not table_name:
        return helper.response_err(ERR.COMMON.INVALID_REQUEST_BODY)

    with db.engine_rdonly.connect() as connection:
        comment_like_statement = db.Statement(table_name).where(user_id=user_id)

        return helper.response_ok(db.to_relation_model_list(comment_like_statement.select(connection)))
コード例 #19
0
def _get_user(address):
    """
    Returns an user information by wallet address.

    If user(address) does not exist, give a random message for signing
    """

    # if invalid address format, don't generate message
    if not check_address_format(address):
        return helper.response_err(ER.INVALID_REQUEST_BODY, ER.INVALID_REQUEST_BODY_MSG)

    with db.engine_rdonly.connect() as connection:
        user = db.statement(db.table.USERS).where(address=address).select(connection).fetchone()
        return helper.response_ok(db.to_relation_model(user))
コード例 #20
0
def _change_user_info(column_name, max_len, min_len=0):
    json_form = request.get_json(force=True, silent=True)
    user_id = request.user['user_id']
    value = json_form.get(column_name)

    if len(value) > max_len or len(value) < min_len:
        return helper.response_err(ERR.COMMON.TOO_LONG_PARAMETER)

    with db.engine_rdwr.connect() as connection:
        try:
            db.statement(db.table.USERS).set(**{column_name:value}).where(user_id=user_id).update(connection)
        except IntegrityError:
            return helper.response_err(ERR.COMMON.ALREADY_EXIST)
        return helper.response_ok({'status': 'success'})
コード例 #21
0
def _get_board_posts(board_type):
    table_name = db.table.board(board_type)
    user_id = request.args.get('user_id')
    page = request.args.get('page', 1)

    # if unknown board type
    if not table_name:
        return helper.response_err(ERR.COMMON.INVALID_REQUEST_BODY)

    from modules.pagination import Pagination
    from modules import board

    stmt = board.posts_query_stmt(board_type=board_type, user_id=user_id)

    if board_type == 'music':
        post_type = request.args.get('type')

        if isinstance(post_type, str):
            stmt.where(type=post_type)

        # only accept mined contract for users to show only purchasable contracts.
        stmt.where_advanced(db.table.MUSIC_CONTRACTS, status='success')

    def _to_relation_model(row):
        row = db.to_relation_model(row)
        if board_type == 'music':
            # since ipfs_file is related with music contracts, move ipfs_file row into music_contracts row.
            row['music_contract']['ipfs_file'] = [row['ipfs_file']]
            del row['ipfs_file']
        return row

    with db.engine_rdonly.connect() as connection:
        fetch_query_str = stmt.select(connection,
                                      execute=False,
                                      is_count_query=False)
        count_query_str = stmt.select(connection,
                                      execute=False,
                                      is_count_query=True)
        order_query_str = "ORDER BY `{}`.`post_id` DESC".format(
            db.statement.get_table_alias(table_name))

        return helper.response_ok(
            Pagination(
                connection=connection,
                fetch=fetch_query_str,
                count=count_query_str,
                order=order_query_str,
                current_page=page,
                fetch_params=stmt.fetch_params).get_result(_to_relation_model))
コード例 #22
0
def _get_my_upload_board(board_type):
    """
    :param board_type: 'sheet' or 'streaming'
    :return: BasePost[]
    """
    user = request.user
    with db.engine_rdonly.connect() as connect:
        query = db.statement(db.table.board('music')) \
            .inner_join(db.table.MUSIC_CONTRACTS, 'post_id') \
            .inner_join((db.table.MUSIC_PAYMENTS, db.table.MUSIC_CONTRACTS), 'contract_address') \
            .where(type=board_type) \
            .where_advanced(db.table.MUSIC_PAYMENTS, buyer_address=user['address']) \
            .order('post_id', 'desc').select(connect)

        return helper.response_ok([db.to_relation_model(row) for row in query])
コード例 #23
0
def _put_draft(draft_id):
    user_id = request.user['user_id']
    data = request.get_json(force=True, silent=True)

    with db.engine_rdwr.connect() as connect:
        draft_row = db.statement(db.table.POST_DRAFTS) \
            .where(user_id=user_id, draft_id=draft_id).select(connect).fetchone()

        if draft_row is None:
            return helper.response_err(ERR.DRAFT.NOT_EXISTS)

        db.statement(db.table.POST_DRAFTS).set(data=json.dumps(data)).where(
            draft_id=draft_id).update(connect)

        return helper.response_ok("OK")
コード例 #24
0
def _like_comment(board_type, comment_id):
    user_id = request.user['user_id']

    board_table_name = db.table.board(board_type)
    comment_like_table_name = db.table.comment_like(board_type)

    if not board_table_name or not comment_like_table_name:
        return helper.response_err(ERR.COMMON.INVALID_REQUEST_BODY)

    like_statement = db.Statement(comment_like_table_name).set(comment_id=comment_id, user_id=user_id)

    with db.engine_rdwr.begin() as connection:
        try:
            like_statement.insert(connection)
        except IntegrityError:
            return helper.response_err(ERR.COMMON.ALREADY_EXIST)
        return helper.response_ok({'status': 'success'})
コード例 #25
0
def _delete_draft(draft_id):
    user_id = request.user['user_id']

    with db.engine_rdwr.connect() as connect:
        draft_row = db.statement(db.table.POST_DRAFTS) \
            .where(user_id=user_id, draft_id=draft_id) \
            .select(connect).fetchone()

        if draft_row is None:
            return helper.response_err(ERR.DRAFT.NOT_EXISTS)
        elif draft_row['is_uploaded'] == 1:
            return helper.response_err(ERR.DRAFT.ALREADY_UPLOADED)
        else:
            db.statement(
                db.table.POST_DRAFTS).where(draft_id=draft_id).delete(connect)

            return helper.response_ok("OK")
コード例 #26
0
def _cancel_like_post(board_type, post_id):
    user_id = request.user['user_id']

    board_table_name = db.table.board(board_type)
    like_table_name = db.table.like(board_type)

    if not board_table_name or not like_table_name:
        return helper.response_err(ERR.COMMON.INVALID_REQUEST_BODY)

    like_statement = db.Statement(like_table_name).where(post_id=post_id, user_id=user_id)

    with db.engine_rdwr.begin() as connection:
        deleted = like_statement.delete(connection).rowcount
        if deleted:
            return helper.response_ok({'status': 'success'})
        else:
            return helper.response_err(ERR.COMMON.NOT_EXIST)
コード例 #27
0
def _upload_paper():
    """
    Uploads a paper file from client.

    json form:
    {
        "hash": file hash string,
        "price": uint256,
        "encrypted": true | false,
    }

    Server downloads the paper file by its IPFS process from the client IPFS node and it creates a muzika contract
    for creating a paper in the block chain network.

    Server must have an IPFS node and it pins the object file for spreading the paper file.
    """
    json_form = request.get_json(force=True, silent=True)
    file_hash = json_form.get('hash')
    wallet_address = request.user['address']
    price = json_form.get('price')
    encrypted = json_form.get('encrypted', False)

    # check request json
    if not isinstance(file_hash, str) or not isinstance(
            price, int) or not isinstance(encrypted, bool):
        return helper.response_err(ER.INVALID_REQUEST_BODY,
                                   ER.INVALID_REQUEST_BODY_MSG)

    # pin the file for spreading out the paper
    relay_ipfs = RelayIpfs()
    api = relay_ipfs.get_connection()
    # TODO : process if failed to pin (timeout problem)
    api.pin_add(file_hash)

    # create a paper contract
    web3 = get_web3()
    contract_handler = MuzikaContractHandler()
    paper_contract = contract_handler.get_contract(web3, 'MuzikaPaperContract')

    tx_hash = paper_contract.constructor(
        web3.toChecksumAddress(wallet_address), price, file_hash,
        '').transact()

    return helper.response_ok({'status': 'success'})
コード例 #28
0
def _get_board_post_comments(board_type, post_id):
    comment_table_name = db.table.comment(board_type)
    board_table_name = db.table.board(board_type)

    page = request.args.get('page', 1)

    # if unknown board type
    if not comment_table_name or not board_table_name:
        return helper.response_err(ER.INVALID_REQUEST_BODY,
                                   ER.INVALID_REQUEST_BODY_MSG)

    fetch_query_str = """
        SELECT `c`.*, '!user', `u`.*
        FROM `{}` `c`
        INNER JOIN `{}` `b`
          ON (`c`.`post_id` = `b`.`post_id` AND `c`.`parent_comment_id` IS NULL)
        INNER JOIN `users` `u`
          ON (`u`.`user_id` = `c`.`user_id`)
        WHERE `c`.`post_id` = :post_id AND `c`.`status` = :post_status
    """.format(comment_table_name, board_table_name)

    count_query_str = """
        SELECT COUNT(*) AS `cnt`
        FROM `{}` `c`
        INNER JOIN `{}` `b`
          ON (`c`.`post_id` = `b`.`post_id` AND `c`.`parent_comment_id` IS NULL)
        WHERE `c`.`post_id` = :post_id AND `c`.`status` = :post_status
    """.format(comment_table_name, board_table_name)

    order_query_str = "ORDER BY `c`.`created_at` DESC"

    with db.engine_rdonly.connect() as connection:
        from modules.pagination import Pagination
        return helper.response_ok(
            Pagination(connection=connection,
                       fetch=fetch_query_str,
                       count=count_query_str,
                       order=order_query_str,
                       current_page=page,
                       fetch_params={
                           'post_id': post_id,
                           'post_status': 'posted'
                       }).get_result(db.to_relation_model))
コード例 #29
0
def _delete_board_comment(board_type, comment_id):
    user_id = request.user['user_id']

    table_name = db.table.comment(board_type)

    # if unknown board type
    if not table_name:
        return helper.response_err(ERR.COMMON.INVALID_REQUEST_BODY)

    statement = db.Statement(table_name).set(status='deleted')\
        .where(user_id=user_id, comment_id=comment_id, status='posted')

    with db.engine_rdwr.connect() as connection:
        deleted = statement.update(connection).rowcount

        if not deleted:
            return helper.response_err(ERR.COMMON.AUTHENTICATION_FAILED)

        return helper.response_ok({'status': 'success'})
コード例 #30
0
def _post_board_subcomment(board_type, parent_comment_id):
    json_form = request.get_json(force=True, silent=True)
    content = json_form.get('content')

    user_id = request.user['user_id']

    if not isinstance(content, str) or not isinstance(parent_comment_id, int):
        return helper.response_err(ER.INVALID_REQUEST_BODY,
                                   ER.INVALID_REQUEST_BODY_MSG)

    table_name = db.table.comment(board_type)

    # if unknown board type
    if not table_name:
        return helper.response_err(ER.INVALID_REQUEST_BODY,
                                   ER.INVALID_REQUEST_BODY_MSG)

    statement = db.Statement(table_name).set(
        user_id=user_id, parent_comment_id=parent_comment_id, content=content)

    parent_update_statement = """
        UPDATE `{}`
        SET
          `reply_count` = `reply_count` + 1
        WHERE
          `comment_id` = :parent_comment_id
        LIMIT 1
    """.format(table_name)

    with db.engine_rdwr.begin() as connection:
        try:
            comment_id = statement.insert(connection).lastrowid
        except IntegrityError:
            # if failed to insert
            return helper.response_err(ER.INVALID_REQUEST_BODY,
                                       ER.INVALID_REQUEST_BODY_MSG)

        connection.execute(text(parent_update_statement),
                           parent_comment_id=parent_comment_id)

        return helper.response_ok({'comment_id': comment_id})