mirror of
https://github.com/astuto/astuto.git
synced 2025-12-16 19:57:52 +01:00
532 lines
17 KiB
Ruby
532 lines
17 KiB
Ruby
|
|
require 'swagger_helper'
|
||
|
|
|
||
|
|
RSpec.describe 'api/v1/posts', type: :request do
|
||
|
|
include_context 'API Authentication'
|
||
|
|
|
||
|
|
before(:each) do
|
||
|
|
@post = FactoryBot.create(:post)
|
||
|
|
@pending_post = FactoryBot.create(:post, approval_status: 'pending')
|
||
|
|
end
|
||
|
|
|
||
|
|
path '/api/v1/posts' do
|
||
|
|
get('List posts') do
|
||
|
|
tags 'Posts'
|
||
|
|
description 'List posts with optional filters. Posts are returned from newest to oldest.'
|
||
|
|
security [{ api_key: [] }]
|
||
|
|
produces 'application/json'
|
||
|
|
|
||
|
|
parameter name: :limit, in: :query, type: :integer, required: false, description: 'Number of posts to return. Defaults to 20.'
|
||
|
|
parameter name: :offset, in: :query, type: :integer, required: false, description: 'Offset the starting point of posts to return. Defaults to 0.'
|
||
|
|
parameter name: :board_id, in: :query, type: :integer, required: false, description: 'If specified, only posts from this board will be returned.'
|
||
|
|
parameter name: :user_id, in: :query, type: :integer, required: false, description: 'If specified, only posts from this user will be returned.'
|
||
|
|
|
||
|
|
response(200, 'successful') do
|
||
|
|
let(:Authorization) { "Bearer #{@moderator_api_token}" }
|
||
|
|
|
||
|
|
schema type: :array, items: { '$ref' => '#/components/schemas/Post' }
|
||
|
|
|
||
|
|
run_test!
|
||
|
|
end
|
||
|
|
|
||
|
|
response(401, 'unauthorized') do
|
||
|
|
let(:Authorization) { nil }
|
||
|
|
|
||
|
|
schema '$ref' => '#/components/schemas/Error'
|
||
|
|
|
||
|
|
run_test!
|
||
|
|
end
|
||
|
|
end
|
||
|
|
|
||
|
|
post('Create post') do
|
||
|
|
tags 'Posts'
|
||
|
|
description 'Create a new post.'
|
||
|
|
security [{ api_key: [] }]
|
||
|
|
consumes 'application/json'
|
||
|
|
produces 'application/json'
|
||
|
|
|
||
|
|
parameter name: :post_parameter, in: :body, schema: {
|
||
|
|
type: :object,
|
||
|
|
properties: {
|
||
|
|
title: { type: :string, description: 'Title of the post' },
|
||
|
|
description: { type: :string, description: 'Content of the post' },
|
||
|
|
board_id: { type: :integer, description: 'ID of the board where the post will be created' },
|
||
|
|
impersonated_user_id: { type: :integer, nullable: true, description: 'ID of the user to impersonate (optional; requires admin role)' }
|
||
|
|
},
|
||
|
|
required: %w[title board_id]
|
||
|
|
}
|
||
|
|
|
||
|
|
response(201, 'successful') do
|
||
|
|
let(:Authorization) { "Bearer #{@moderator_api_token}" }
|
||
|
|
let(:post_parameter) { { title: 'New post', board_id: FactoryBot.create(:board).id } }
|
||
|
|
|
||
|
|
schema '$ref' => '#/components/schemas/Id'
|
||
|
|
|
||
|
|
before do
|
||
|
|
@current_tenant = Current.tenant # Need to store the current tenant to use it later after request
|
||
|
|
@post_count_before = Post.count
|
||
|
|
end
|
||
|
|
|
||
|
|
run_test! do |response|
|
||
|
|
Current.tenant = @current_tenant # Restore the current tenant
|
||
|
|
created_post = Post.find(JSON.parse(response.body)['id'])
|
||
|
|
|
||
|
|
expect(Post.count).to eq(@post_count_before + 1)
|
||
|
|
expect(created_post.title).to eq(post_parameter[:title])
|
||
|
|
expect(created_post.board_id).to eq(post_parameter[:board_id])
|
||
|
|
expect(created_post.user_id).to eq(@moderator.id)
|
||
|
|
end
|
||
|
|
end
|
||
|
|
|
||
|
|
response(400, 'bad request') do
|
||
|
|
let(:Authorization) { "Bearer #{@moderator_api_token}" }
|
||
|
|
let(:post_parameter) { { title: nil } }
|
||
|
|
|
||
|
|
schema '$ref' => '#/components/schemas/Error'
|
||
|
|
|
||
|
|
run_test!
|
||
|
|
end
|
||
|
|
|
||
|
|
response(401, 'unauthorized') do
|
||
|
|
let(:Authorization) { nil }
|
||
|
|
let(:post_parameter) { { title: 'New post', board_id: FactoryBot.create(:board).id } }
|
||
|
|
|
||
|
|
schema '$ref' => '#/components/schemas/Error'
|
||
|
|
|
||
|
|
run_test!
|
||
|
|
end
|
||
|
|
|
||
|
|
# Impersonation works for admin users
|
||
|
|
response(201, 'successful') do
|
||
|
|
let(:Authorization) { "Bearer #{@admin_api_token}" }
|
||
|
|
let(:post_parameter) { { title: 'New post', board_id: FactoryBot.create(:board).id, impersonated_user_id: FactoryBot.create(:user).id } }
|
||
|
|
|
||
|
|
schema '$ref' => '#/components/schemas/Id'
|
||
|
|
|
||
|
|
before do
|
||
|
|
@current_tenant = Current.tenant # Need to store the current tenant to use it later after request
|
||
|
|
@post_count_before = Post.count
|
||
|
|
end
|
||
|
|
|
||
|
|
run_test! do |response|
|
||
|
|
Current.tenant = @current_tenant # Restore the current tenant
|
||
|
|
created_post = Post.find(JSON.parse(response.body)['id'])
|
||
|
|
|
||
|
|
expect(Post.count).to eq(@post_count_before + 1)
|
||
|
|
expect(created_post.title).to eq(post_parameter[:title])
|
||
|
|
expect(created_post.board_id).to eq(post_parameter[:board_id])
|
||
|
|
expect(created_post.user_id).to eq(post_parameter[:impersonated_user_id])
|
||
|
|
end
|
||
|
|
end
|
||
|
|
|
||
|
|
# Impersonation doesn't work for non-admin users
|
||
|
|
response(401, 'unauthorized') do
|
||
|
|
let(:Authorization) { "Bearer #{@moderator_api_token}" }
|
||
|
|
let(:post_parameter) { { title: 'New post', board_id: FactoryBot.create(:board).id, impersonated_user_id: FactoryBot.create(:user).id } }
|
||
|
|
|
||
|
|
schema '$ref' => '#/components/schemas/Error'
|
||
|
|
|
||
|
|
run_test!
|
||
|
|
end
|
||
|
|
end
|
||
|
|
end
|
||
|
|
|
||
|
|
path '/api/v1/posts/{id}' do
|
||
|
|
get('Get a post') do
|
||
|
|
tags 'Posts'
|
||
|
|
description 'Get a post by id.'
|
||
|
|
security [{ api_key: [] }]
|
||
|
|
produces 'application/json'
|
||
|
|
|
||
|
|
parameter name: :id, in: :path, type: :integer, required: true, description: 'ID of the post.'
|
||
|
|
|
||
|
|
response(200, 'successful') do
|
||
|
|
let(:Authorization) { "Bearer #{@moderator_api_token}" }
|
||
|
|
let(:id) { @post.id }
|
||
|
|
|
||
|
|
schema '$ref' => '#/components/schemas/Post'
|
||
|
|
|
||
|
|
run_test!
|
||
|
|
end
|
||
|
|
|
||
|
|
response(401, 'unauthorized') do
|
||
|
|
let(:Authorization) { nil }
|
||
|
|
let(:id) { @post.id }
|
||
|
|
|
||
|
|
schema '$ref' => '#/components/schemas/Error'
|
||
|
|
|
||
|
|
run_test!
|
||
|
|
end
|
||
|
|
|
||
|
|
response(404, 'not found') do
|
||
|
|
let(:Authorization) { "Bearer #{@moderator_api_token}" }
|
||
|
|
let(:id) { -1 }
|
||
|
|
|
||
|
|
schema '$ref' => '#/components/schemas/Error'
|
||
|
|
|
||
|
|
run_test!
|
||
|
|
end
|
||
|
|
end
|
||
|
|
|
||
|
|
put('Update a post') do
|
||
|
|
tags 'Posts'
|
||
|
|
description 'Update a post.'
|
||
|
|
security [{ api_key: [] }]
|
||
|
|
consumes 'application/json'
|
||
|
|
produces 'application/json'
|
||
|
|
|
||
|
|
parameter name: :id, in: :path, type: :integer, required: true, description: 'ID of the post.'
|
||
|
|
|
||
|
|
parameter name: :post, in: :body, schema: {
|
||
|
|
type: :object,
|
||
|
|
properties: {
|
||
|
|
title: { type: :string, description: 'New title of the post' },
|
||
|
|
description: { type: :string, description: 'New content of the post' }
|
||
|
|
},
|
||
|
|
}
|
||
|
|
|
||
|
|
response(200, 'successful') do
|
||
|
|
let(:Authorization) { "Bearer #{@moderator_api_token}" }
|
||
|
|
let(:id) { @post.id }
|
||
|
|
let(:post) { { title: 'New title', description: 'New description' } }
|
||
|
|
|
||
|
|
schema '$ref' => '#/components/schemas/Id'
|
||
|
|
|
||
|
|
before do
|
||
|
|
@current_tenant = Current.tenant # Need to store the current tenant to use it later after request
|
||
|
|
expect(@post.title).not_to eq('New title')
|
||
|
|
expect(@post.description).not_to eq('New description')
|
||
|
|
end
|
||
|
|
|
||
|
|
run_test! do |response|
|
||
|
|
Current.tenant = @current_tenant # Restore the current tenant
|
||
|
|
@post = Post.find(@post.id)
|
||
|
|
expect(@post.title).to eq('New title')
|
||
|
|
expect(@post.description).to eq('New description')
|
||
|
|
end
|
||
|
|
end
|
||
|
|
|
||
|
|
response(401, 'unauthorized') do
|
||
|
|
let(:Authorization) { nil }
|
||
|
|
let(:id) { @post.id }
|
||
|
|
let(:post) { { title: 'New title', description: 'New description' } }
|
||
|
|
|
||
|
|
schema '$ref' => '#/components/schemas/Error'
|
||
|
|
|
||
|
|
run_test!
|
||
|
|
end
|
||
|
|
|
||
|
|
response(404, 'not found') do
|
||
|
|
let(:Authorization) { "Bearer #{@moderator_api_token}" }
|
||
|
|
let(:id) { -1 }
|
||
|
|
let(:post) { { title: 'New title', description: 'New description' } }
|
||
|
|
|
||
|
|
schema '$ref' => '#/components/schemas/Error'
|
||
|
|
|
||
|
|
run_test!
|
||
|
|
end
|
||
|
|
|
||
|
|
response(422, 'unprocessable entity') do
|
||
|
|
let(:Authorization) { "Bearer #{@moderator_api_token}" }
|
||
|
|
let(:id) { @post.id }
|
||
|
|
let(:post) { { title: nil, description: nil } }
|
||
|
|
|
||
|
|
schema '$ref' => '#/components/schemas/Error'
|
||
|
|
|
||
|
|
run_test!
|
||
|
|
end
|
||
|
|
end
|
||
|
|
|
||
|
|
delete('Delete a post') do
|
||
|
|
tags 'Posts'
|
||
|
|
description 'Delete a post by id.'
|
||
|
|
security [{ api_key: [] }]
|
||
|
|
produces 'application/json'
|
||
|
|
|
||
|
|
parameter name: :id, in: :path, type: :integer, required: true, description: 'ID of the post.'
|
||
|
|
|
||
|
|
response(200, 'successful') do
|
||
|
|
let(:Authorization) { "Bearer #{@moderator_api_token}" }
|
||
|
|
let(:id) { @post.id }
|
||
|
|
|
||
|
|
schema '$ref' => '#/components/schemas/Id'
|
||
|
|
|
||
|
|
before do
|
||
|
|
@current_tenant = Current.tenant # Need to store the current tenant to use it later after request
|
||
|
|
expect(Post.find_by(id: @post.id)).to be_present
|
||
|
|
end
|
||
|
|
|
||
|
|
run_test! do |response|
|
||
|
|
Current.tenant = @current_tenant # Restore the current tenant
|
||
|
|
expect(Post.find_by(id: @post.id)).to be_nil
|
||
|
|
end
|
||
|
|
end
|
||
|
|
|
||
|
|
response(401, 'unauthorized') do
|
||
|
|
let(:Authorization) { nil }
|
||
|
|
let(:id) { @post.id }
|
||
|
|
|
||
|
|
schema '$ref' => '#/components/schemas/Error'
|
||
|
|
|
||
|
|
run_test!
|
||
|
|
end
|
||
|
|
|
||
|
|
response(404, 'not found') do
|
||
|
|
let(:Authorization) { "Bearer #{@moderator_api_token}" }
|
||
|
|
let(:id) { -1 }
|
||
|
|
|
||
|
|
schema '$ref' => '#/components/schemas/Error'
|
||
|
|
|
||
|
|
run_test!
|
||
|
|
end
|
||
|
|
end
|
||
|
|
end
|
||
|
|
|
||
|
|
path '/api/v1/posts/{id}/update_board' do
|
||
|
|
put('Update post board') do
|
||
|
|
tags 'Posts'
|
||
|
|
description 'Move post to another board.'
|
||
|
|
security [{ api_key: [] }]
|
||
|
|
consumes 'application/json'
|
||
|
|
produces 'application/json'
|
||
|
|
|
||
|
|
parameter name: :id, in: :path, type: :integer, required: true, description: 'ID of the post.'
|
||
|
|
|
||
|
|
parameter name: :post, in: :body, schema: {
|
||
|
|
type: :object,
|
||
|
|
properties: {
|
||
|
|
board_id: { type: :integer, description: 'ID of the new board' }
|
||
|
|
},
|
||
|
|
required: %w[board_id]
|
||
|
|
}
|
||
|
|
|
||
|
|
response(200, 'successful') do
|
||
|
|
let(:Authorization) { "Bearer #{@moderator_api_token}" }
|
||
|
|
let(:id) { @post.id }
|
||
|
|
let(:post) { { board_id: FactoryBot.create(:board).id } }
|
||
|
|
|
||
|
|
schema '$ref' => '#/components/schemas/Id'
|
||
|
|
|
||
|
|
before do
|
||
|
|
@current_tenant = Current.tenant # Need to store the current tenant to use it later after request
|
||
|
|
expect(@post.board_id).not_to eq(post[:board_id])
|
||
|
|
end
|
||
|
|
|
||
|
|
run_test! do |response|
|
||
|
|
Current.tenant = @current_tenant # Restore the current tenant
|
||
|
|
@post = Post.find(@post.id)
|
||
|
|
expect(@post.board_id).to eq(post[:board_id])
|
||
|
|
end
|
||
|
|
end
|
||
|
|
|
||
|
|
response(400, 'bad request') do
|
||
|
|
let(:Authorization) { "Bearer #{@moderator_api_token}" }
|
||
|
|
let(:id) { @post.id }
|
||
|
|
let(:post) { { board_id: nil } }
|
||
|
|
|
||
|
|
schema '$ref' => '#/components/schemas/Error'
|
||
|
|
|
||
|
|
run_test!
|
||
|
|
end
|
||
|
|
|
||
|
|
response(401, 'unauthorized') do
|
||
|
|
let(:Authorization) { nil }
|
||
|
|
let(:id) { @post.id }
|
||
|
|
let(:post) { { board_id: FactoryBot.create(:board).id } }
|
||
|
|
|
||
|
|
schema '$ref' => '#/components/schemas/Error'
|
||
|
|
|
||
|
|
run_test!
|
||
|
|
end
|
||
|
|
|
||
|
|
response(404, 'not found') do
|
||
|
|
let(:Authorization) { "Bearer #{@moderator_api_token}" }
|
||
|
|
let(:id) { -1 }
|
||
|
|
let(:post) { { board_id: FactoryBot.create(:board).id } }
|
||
|
|
|
||
|
|
schema '$ref' => '#/components/schemas/Error'
|
||
|
|
|
||
|
|
run_test!
|
||
|
|
end
|
||
|
|
end
|
||
|
|
end
|
||
|
|
|
||
|
|
path '/api/v1/posts/{id}/update_status' do
|
||
|
|
put('Update post status') do
|
||
|
|
tags 'Posts'
|
||
|
|
description 'Update post status.'
|
||
|
|
security [{ api_key: [] }]
|
||
|
|
consumes 'application/json'
|
||
|
|
produces 'application/json'
|
||
|
|
|
||
|
|
parameter name: :id, in: :path, type: :integer, required: true, description: 'ID of the post.'
|
||
|
|
|
||
|
|
parameter name: :post, in: :body, schema: {
|
||
|
|
type: :object,
|
||
|
|
properties: {
|
||
|
|
post_status_id: { type: :integer, description: 'ID of the new post status. Send null if you want to remove current status.' },
|
||
|
|
impersonated_user_id: { type: :integer, nullable: true, description: 'ID of the user to impersonate (optional; requires admin role)' }
|
||
|
|
},
|
||
|
|
required: %w[post_status_id]
|
||
|
|
}
|
||
|
|
|
||
|
|
response(200, 'successful') do
|
||
|
|
let(:Authorization) { "Bearer #{@moderator_api_token}" }
|
||
|
|
let(:id) { @post.id }
|
||
|
|
let(:post) { { post_status_id: FactoryBot.create(:post_status).id } }
|
||
|
|
|
||
|
|
schema '$ref' => '#/components/schemas/Id'
|
||
|
|
|
||
|
|
before do
|
||
|
|
@current_tenant = Current.tenant # Need to store the current tenant to use it later after request
|
||
|
|
expect(@post.post_status_id).not_to eq(post[:post_status_id])
|
||
|
|
end
|
||
|
|
|
||
|
|
run_test! do |response|
|
||
|
|
Current.tenant = @current_tenant # Restore the current tenant
|
||
|
|
@post = Post.find(@post.id)
|
||
|
|
@post_status_change = PostStatusChange.where(post_id: @post.id).order(created_at: :desc).first
|
||
|
|
|
||
|
|
expect(@post.post_status_id).to eq(post[:post_status_id])
|
||
|
|
expect(@post_status_change.user_id).to eq(@moderator.id)
|
||
|
|
end
|
||
|
|
end
|
||
|
|
|
||
|
|
# Impersonation
|
||
|
|
response(200, 'successful') do
|
||
|
|
let(:Authorization) { "Bearer #{@admin_api_token}" }
|
||
|
|
let(:id) { @post.id }
|
||
|
|
let(:post) { { post_status_id: FactoryBot.create(:post_status).id, impersonated_user_id: FactoryBot.create(:user).id } }
|
||
|
|
|
||
|
|
schema '$ref' => '#/components/schemas/Id'
|
||
|
|
|
||
|
|
before do
|
||
|
|
@current_tenant = Current.tenant # Need to store the current tenant to use it later after request
|
||
|
|
expect(@post.post_status_id).not_to eq(post[:post_status_id])
|
||
|
|
end
|
||
|
|
|
||
|
|
run_test! do |response|
|
||
|
|
Current.tenant = @current_tenant # Restore the current tenant
|
||
|
|
@post = Post.find(@post.id)
|
||
|
|
@post_status_change = PostStatusChange.where(post_id: @post.id).order(created_at: :desc).first
|
||
|
|
|
||
|
|
expect(@post.post_status_id).to eq(post[:post_status_id])
|
||
|
|
expect(@post_status_change.user_id).to eq(post[:impersonated_user_id])
|
||
|
|
end
|
||
|
|
end
|
||
|
|
|
||
|
|
response(401, 'unauthorized') do
|
||
|
|
let(:Authorization) { nil }
|
||
|
|
let(:id) { @post.id }
|
||
|
|
let(:post) { { post_status_id: FactoryBot.create(:post_status).id } }
|
||
|
|
|
||
|
|
schema '$ref' => '#/components/schemas/Error'
|
||
|
|
|
||
|
|
run_test!
|
||
|
|
end
|
||
|
|
|
||
|
|
response(404, 'not found') do
|
||
|
|
let(:Authorization) { "Bearer #{@moderator_api_token}" }
|
||
|
|
let(:id) { -1 }
|
||
|
|
let(:post) { { post_status_id: FactoryBot.create(:post_status).id } }
|
||
|
|
|
||
|
|
schema '$ref' => '#/components/schemas/Error'
|
||
|
|
|
||
|
|
run_test!
|
||
|
|
end
|
||
|
|
end
|
||
|
|
end
|
||
|
|
|
||
|
|
path '/api/v1/posts/{id}/approve' do
|
||
|
|
put('Approve post') do
|
||
|
|
tags 'Posts'
|
||
|
|
description 'Approve a post that is pending approval.'
|
||
|
|
security [{ api_key: [] }]
|
||
|
|
produces 'application/json'
|
||
|
|
|
||
|
|
parameter name: :id, in: :path, type: :integer, required: true, description: 'ID of the post.'
|
||
|
|
|
||
|
|
response(200, 'successful') do
|
||
|
|
let(:Authorization) { "Bearer #{@moderator_api_token}" }
|
||
|
|
let(:id) { @pending_post.id }
|
||
|
|
|
||
|
|
schema '$ref' => '#/components/schemas/Id'
|
||
|
|
|
||
|
|
before do
|
||
|
|
@current_tenant = Current.tenant # Need to store the current tenant to use it later after request
|
||
|
|
expect(@pending_post.approval_status).to eq('pending')
|
||
|
|
end
|
||
|
|
|
||
|
|
run_test! do |response|
|
||
|
|
Current.tenant = @current_tenant # Restore the current tenant
|
||
|
|
@post = Post.find(@pending_post.id)
|
||
|
|
expect(@post.approval_status).to eq('approved')
|
||
|
|
end
|
||
|
|
end
|
||
|
|
|
||
|
|
response(401, 'unauthorized') do
|
||
|
|
let(:Authorization) { nil }
|
||
|
|
let(:id) { @pending_post.id }
|
||
|
|
|
||
|
|
schema '$ref' => '#/components/schemas/Error'
|
||
|
|
|
||
|
|
run_test!
|
||
|
|
end
|
||
|
|
|
||
|
|
response(404, 'not found') do
|
||
|
|
let(:Authorization) { "Bearer #{@moderator_api_token}" }
|
||
|
|
let(:id) { -1 }
|
||
|
|
|
||
|
|
schema '$ref' => '#/components/schemas/Error'
|
||
|
|
|
||
|
|
run_test!
|
||
|
|
end
|
||
|
|
end
|
||
|
|
end
|
||
|
|
|
||
|
|
path '/api/v1/posts/{id}/reject' do
|
||
|
|
put('Reject post') do
|
||
|
|
tags 'Posts'
|
||
|
|
description 'Reject a post that is pending approval.'
|
||
|
|
security [{ api_key: [] }]
|
||
|
|
produces 'application/json'
|
||
|
|
|
||
|
|
parameter name: :id, in: :path, type: :integer, required: true, description: 'ID of the post.'
|
||
|
|
|
||
|
|
response(200, 'successful') do
|
||
|
|
let(:Authorization) { "Bearer #{@moderator_api_token}" }
|
||
|
|
let(:id) { @pending_post.id }
|
||
|
|
|
||
|
|
schema '$ref' => '#/components/schemas/Id'
|
||
|
|
|
||
|
|
before do
|
||
|
|
@current_tenant = Current.tenant # Need to store the current tenant to use it later after request
|
||
|
|
expect(@pending_post.approval_status).to eq('pending')
|
||
|
|
end
|
||
|
|
|
||
|
|
run_test! do |response|
|
||
|
|
Current.tenant = @current_tenant # Restore the current tenant
|
||
|
|
@post = Post.find(@pending_post.id)
|
||
|
|
expect(@post.approval_status).to eq('rejected')
|
||
|
|
end
|
||
|
|
end
|
||
|
|
|
||
|
|
response(401, 'unauthorized') do
|
||
|
|
let(:Authorization) { nil }
|
||
|
|
let(:id) { @pending_post.id }
|
||
|
|
|
||
|
|
schema '$ref' => '#/components/schemas/Error'
|
||
|
|
|
||
|
|
run_test!
|
||
|
|
end
|
||
|
|
|
||
|
|
response(404, 'not found') do
|
||
|
|
let(:Authorization) { "Bearer #{@moderator_api_token}" }
|
||
|
|
let(:id) { -1 }
|
||
|
|
|
||
|
|
schema '$ref' => '#/components/schemas/Error'
|
||
|
|
|
||
|
|
run_test!
|
||
|
|
end
|
||
|
|
end
|
||
|
|
end
|
||
|
|
end
|