commit
eb35c959df
@ -0,0 +1,52 @@ |
|||||||
|
require_relative 'fog_file_uploader' |
||||||
|
|
||||||
|
class DirectFogUploader < FogFileUploader |
||||||
|
include CarrierWaveDirect::Uploader |
||||||
|
|
||||||
|
def self.for_attachment(attachment) |
||||||
|
for_uploader attachment.file |
||||||
|
end |
||||||
|
|
||||||
|
def self.for_uploader(fog_file_uploader) |
||||||
|
raise ArgumentError, "FogFileUploader expected" unless fog_file_uploader.is_a? FogFileUploader |
||||||
|
|
||||||
|
uploader = self.new |
||||||
|
|
||||||
|
uploader.instance_variable_set "@file", fog_file_uploader.file |
||||||
|
uploader.instance_variable_set "@key", fog_file_uploader.path |
||||||
|
|
||||||
|
uploader |
||||||
|
end |
||||||
|
|
||||||
|
## |
||||||
|
# Generates the direct upload form for the given attachment. |
||||||
|
# |
||||||
|
# @param attachment [Attachment] The attachment for which a file is to be uploaded. |
||||||
|
# @param success_action_redirect [String] URL to redirect to if successful (none by default, using status). |
||||||
|
# @param success_action_status [String] The HTTP status to return on success (201 by default). |
||||||
|
# @param max_file_size [Integer] The maximum file size to be allowed in bytes. |
||||||
|
def self.direct_fog_hash( |
||||||
|
attachment:, |
||||||
|
success_action_redirect: nil, |
||||||
|
success_action_status: "201", |
||||||
|
max_file_size: Setting.attachment_max_size * 1024 |
||||||
|
) |
||||||
|
uploader = for_attachment attachment |
||||||
|
|
||||||
|
if success_action_redirect.present? |
||||||
|
uploader.success_action_redirect = success_action_redirect |
||||||
|
uploader.use_action_status = false |
||||||
|
else |
||||||
|
uploader.success_action_status = success_action_status |
||||||
|
uploader.use_action_status = true |
||||||
|
end |
||||||
|
|
||||||
|
hash = uploader.direct_fog_hash(enforce_utf8: false, max_file_size: max_file_size) |
||||||
|
|
||||||
|
if success_action_redirect.present? |
||||||
|
hash.merge(success_action_redirect: success_action_redirect) |
||||||
|
else |
||||||
|
hash.merge(success_action_status: success_action_status) |
||||||
|
end |
||||||
|
end |
||||||
|
end |
@ -0,0 +1,53 @@ |
|||||||
|
#-- encoding: UTF-8 |
||||||
|
|
||||||
|
#-- copyright |
||||||
|
# OpenProject is an open source project management software. |
||||||
|
# Copyright (C) 2012-2020 the OpenProject GmbH |
||||||
|
# |
||||||
|
# This program is free software; you can redistribute it and/or |
||||||
|
# modify it under the terms of the GNU General Public License version 3. |
||||||
|
# |
||||||
|
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: |
||||||
|
# Copyright (C) 2006-2017 Jean-Philippe Lang |
||||||
|
# Copyright (C) 2010-2013 the ChiliProject Team |
||||||
|
# |
||||||
|
# This program is free software; you can redistribute it and/or |
||||||
|
# modify it under the terms of the GNU General Public License |
||||||
|
# as published by the Free Software Foundation; either version 2 |
||||||
|
# of the License, or (at your option) any later version. |
||||||
|
# |
||||||
|
# This program is distributed in the hope that it will be useful, |
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||||
|
# GNU General Public License for more details. |
||||||
|
# |
||||||
|
# You should have received a copy of the GNU General Public License |
||||||
|
# along with this program; if not, write to the Free Software |
||||||
|
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
||||||
|
# |
||||||
|
# See docs/COPYRIGHT.rdoc for more details. |
||||||
|
#++ |
||||||
|
|
||||||
|
class Attachments::FinishDirectUploadJob < ApplicationJob |
||||||
|
queue_with_priority :high |
||||||
|
|
||||||
|
def perform(attachment_id) |
||||||
|
attachment = Attachment.pending_direct_uploads.where(id: attachment_id).first |
||||||
|
local_file = attachment && attachment.file.local_file |
||||||
|
|
||||||
|
if local_file.nil? |
||||||
|
return Rails.logger.error("File for attachment #{attachment_id} was not uploaded.") |
||||||
|
end |
||||||
|
|
||||||
|
begin |
||||||
|
attachment.downloads = 0 |
||||||
|
attachment.set_file_size local_file unless attachment.filesize && attachment.filesize > 0 |
||||||
|
attachment.set_content_type local_file unless attachment.content_type.present? |
||||||
|
attachment.set_digest local_file unless attachment.digest.present? |
||||||
|
|
||||||
|
attachment.save! if attachment.changed? |
||||||
|
ensure |
||||||
|
File.unlink(local_file.path) if File.exist?(local_file.path) |
||||||
|
end |
||||||
|
end |
||||||
|
end |
@ -0,0 +1,148 @@ |
|||||||
|
//-- copyright
|
||||||
|
// OpenProject is an open source project management software.
|
||||||
|
// Copyright (C) 2012-2020 the OpenProject GmbH
|
||||||
|
//
|
||||||
|
// This program is free software; you can redistribute it and/or
|
||||||
|
// modify it under the terms of the GNU General Public License version 3.
|
||||||
|
//
|
||||||
|
// OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
|
||||||
|
// Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||||
|
// Copyright (C) 2010-2013 the ChiliProject Team
|
||||||
|
//
|
||||||
|
// This program is free software; you can redistribute it and/or
|
||||||
|
// modify it under the terms of the GNU General Public License
|
||||||
|
// as published by the Free Software Foundation; either version 2
|
||||||
|
// of the License, or (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program; if not, write to the Free Software
|
||||||
|
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||||
|
//
|
||||||
|
// See docs/COPYRIGHT.rdoc for more details.
|
||||||
|
//++
|
||||||
|
|
||||||
|
import {Injectable} from "@angular/core"; |
||||||
|
import {HttpEvent, HttpResponse} from "@angular/common/http"; |
||||||
|
import {HalResource} from "core-app/modules/hal/resources/hal-resource"; |
||||||
|
import {from, Observable, of} from "rxjs"; |
||||||
|
import {share, switchMap} from "rxjs/operators"; |
||||||
|
import {OpenProjectFileUploadService, UploadBlob, UploadFile, UploadInProgress} from './op-file-upload.service'; |
||||||
|
|
||||||
|
interface PrepareUploadResult { |
||||||
|
url:string; |
||||||
|
form:FormData; |
||||||
|
response:any; |
||||||
|
} |
||||||
|
|
||||||
|
@Injectable() |
||||||
|
export class OpenProjectDirectFileUploadService extends OpenProjectFileUploadService { |
||||||
|
/** |
||||||
|
* Upload a single file, get an UploadResult observable |
||||||
|
* @param {string} url |
||||||
|
* @param {UploadFile} file |
||||||
|
* @param {string} method |
||||||
|
*/ |
||||||
|
public uploadSingle(url:string, file:UploadFile|UploadBlob, method:string = 'post', responseType:'text'|'json' = 'text') { |
||||||
|
const observable = from(this.getDirectUploadFormFrom(url, file)) |
||||||
|
.pipe( |
||||||
|
switchMap(this.uploadToExternal(file, method, responseType)), |
||||||
|
share() |
||||||
|
); |
||||||
|
|
||||||
|
return [file, observable] as UploadInProgress; |
||||||
|
} |
||||||
|
|
||||||
|
private uploadToExternal(file:UploadFile|UploadBlob, method:string, responseType:string):(result:PrepareUploadResult) => Observable<HttpEvent<unknown>> { |
||||||
|
return result => { |
||||||
|
result.form.append('file', file, file.customName || file.name); |
||||||
|
|
||||||
|
return this |
||||||
|
.http |
||||||
|
.request<HalResource>( |
||||||
|
method, |
||||||
|
result.url, |
||||||
|
{ |
||||||
|
body: result.form, |
||||||
|
// Observe the response, not the body
|
||||||
|
observe: 'events', |
||||||
|
// This is important as the CORS policy for the bucket is * and you can't use credentals then,
|
||||||
|
// besides we don't need them here anyway.
|
||||||
|
withCredentials: false, |
||||||
|
responseType: responseType as any, |
||||||
|
// Subscribe to progress events. subscribe() will fire multiple times!
|
||||||
|
reportProgress: true |
||||||
|
} |
||||||
|
) |
||||||
|
.pipe(switchMap(this.finishUpload(result))); |
||||||
|
}; |
||||||
|
} |
||||||
|
|
||||||
|
private finishUpload(result:PrepareUploadResult):(result:HttpEvent<unknown>) => Observable<HttpEvent<unknown>> { |
||||||
|
return event => { |
||||||
|
if (event instanceof HttpResponse) { |
||||||
|
return this |
||||||
|
.http |
||||||
|
.get( |
||||||
|
result.response._links.completeUpload.href, |
||||||
|
{ |
||||||
|
observe: 'response' |
||||||
|
} |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
// Return as new observable due to switchMap
|
||||||
|
return of(event); |
||||||
|
}; |
||||||
|
} |
||||||
|
|
||||||
|
public getDirectUploadFormFrom(url:string, file:UploadFile|UploadBlob):Promise<PrepareUploadResult> { |
||||||
|
const formData = new FormData(); |
||||||
|
const metadata = { |
||||||
|
description: file.description, |
||||||
|
fileName: file.customName || file.name, |
||||||
|
fileSize: file.size, |
||||||
|
contentType: file.type |
||||||
|
}; |
||||||
|
|
||||||
|
/* |
||||||
|
* @TODO We could calculate the MD5 hash here too and pass that. |
||||||
|
* The MD5 hash can be used as the `content-md5` option during the upload to S3 for instance. |
||||||
|
* This way S3 can verify the integrity of the file which we currently don't do. |
||||||
|
*/ |
||||||
|
|
||||||
|
// add the metadata object
|
||||||
|
formData.append( |
||||||
|
'metadata', |
||||||
|
JSON.stringify(metadata), |
||||||
|
); |
||||||
|
|
||||||
|
const result = this |
||||||
|
.http |
||||||
|
.request<HalResource>( |
||||||
|
"post", |
||||||
|
url, |
||||||
|
{ |
||||||
|
body: formData, |
||||||
|
withCredentials: true, |
||||||
|
responseType: "json" as any |
||||||
|
} |
||||||
|
) |
||||||
|
.toPromise() |
||||||
|
.then((res) => { |
||||||
|
let form = new FormData(); |
||||||
|
|
||||||
|
_.each(res._links.addAttachment.form_fields, (value, key) => { |
||||||
|
form.append(key, value); |
||||||
|
}); |
||||||
|
|
||||||
|
return { url: res._links.addAttachment.href, form: form, response: res }; |
||||||
|
}); |
||||||
|
|
||||||
|
return result; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,62 @@ |
|||||||
|
import {Injectable} from "@angular/core"; |
||||||
|
import {BoardActionService} from "core-app/modules/boards/board/board-actions/board-action.service"; |
||||||
|
import {input} from "reactivestates"; |
||||||
|
import {HalResource} from "core-app/modules/hal/resources/hal-resource"; |
||||||
|
import {Observable} from "rxjs"; |
||||||
|
import {filter, map, take} from "rxjs/operators"; |
||||||
|
import {Board} from "core-app/modules/boards/board/board"; |
||||||
|
|
||||||
|
@Injectable() |
||||||
|
export abstract class CachedBoardActionService extends BoardActionService { |
||||||
|
protected cache = input<HalResource[]>(); |
||||||
|
|
||||||
|
protected loadValues(matching?:string):Observable<HalResource[]> { |
||||||
|
this |
||||||
|
.cache |
||||||
|
.putFromPromiseIfPristine(() => this.loadUncached()); |
||||||
|
|
||||||
|
return this |
||||||
|
.cache |
||||||
|
.values$() |
||||||
|
.pipe( |
||||||
|
map(results => { |
||||||
|
if (matching) { |
||||||
|
return results.filter(resource => resource.name.includes(matching)); |
||||||
|
} else { |
||||||
|
return results; |
||||||
|
} |
||||||
|
}), |
||||||
|
take(1) |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
addColumnWithActionAttribute(board:Board, value:HalResource):Promise<Board> { |
||||||
|
if (this.cache.value) { |
||||||
|
// Add the new value to the cache
|
||||||
|
let newValue = [...this.cache.value, value]; |
||||||
|
this.cache.putValue(newValue); |
||||||
|
} |
||||||
|
|
||||||
|
return super.addColumnWithActionAttribute(board, value); |
||||||
|
} |
||||||
|
|
||||||
|
protected require(id:string):Promise<HalResource> { |
||||||
|
this |
||||||
|
.cache |
||||||
|
.putFromPromiseIfPristine(() => this.loadUncached()); |
||||||
|
|
||||||
|
return this |
||||||
|
.cache |
||||||
|
.values$() |
||||||
|
.pipe( |
||||||
|
take(1) |
||||||
|
) |
||||||
|
.toPromise() |
||||||
|
.then(results => { |
||||||
|
return results.find(resource => resource.id === id)!; |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
protected abstract loadUncached():Promise<HalResource[]>; |
||||||
|
} |
||||||
|
|
@ -0,0 +1,62 @@ |
|||||||
|
import {Injectable} from "@angular/core"; |
||||||
|
import {Board} from "core-app/modules/boards/board/board"; |
||||||
|
import {StatusResource} from "core-app/modules/hal/resources/status-resource"; |
||||||
|
import {BoardActionService} from "core-app/modules/boards/board/board-actions/board-action.service"; |
||||||
|
import {WorkPackageResource} from "core-app/modules/hal/resources/work-package-resource"; |
||||||
|
import {HalResource} from "core-app/modules/hal/resources/hal-resource"; |
||||||
|
import {Observable} from "rxjs"; |
||||||
|
import {map} from "rxjs/operators"; |
||||||
|
import {ApiV3FilterBuilder, buildApiV3Filter, FalseValue} from "core-components/api/api-v3/api-v3-filter-builder"; |
||||||
|
import {SubtasksBoardHeaderComponent} from "core-app/modules/boards/board/board-actions/subtasks/subtasks-board-header.component"; |
||||||
|
|
||||||
|
@Injectable() |
||||||
|
export class BoardSubtasksActionService extends BoardActionService { |
||||||
|
filterName = 'parent'; |
||||||
|
|
||||||
|
text = this.I18n.t('js.boards.board_type.action_by_attribute', |
||||||
|
{ attribute: this.I18n.t('js.boards.board_type.action_type.subtasks')}) ; |
||||||
|
|
||||||
|
description = this.I18n.t('js.boards.board_type.action_text_subtasks'); |
||||||
|
|
||||||
|
icon = 'icon-hierarchy'; |
||||||
|
|
||||||
|
public get localizedName() { |
||||||
|
return this.I18n.t('js.boards.board_type.action_type.subtasks'); |
||||||
|
} |
||||||
|
|
||||||
|
public headerComponent() { |
||||||
|
return SubtasksBoardHeaderComponent; |
||||||
|
} |
||||||
|
|
||||||
|
public canMove(workPackage:WorkPackageResource):boolean { |
||||||
|
return !!workPackage.changeParent; |
||||||
|
} |
||||||
|
|
||||||
|
protected loadValues(matching?:string):Observable<HalResource[]> { |
||||||
|
let filters = new ApiV3FilterBuilder(); |
||||||
|
filters.add('is_milestone', '=', false); |
||||||
|
filters.add('project', '=', [this.currentProject.id]); |
||||||
|
|
||||||
|
if (matching) { |
||||||
|
filters.add('subjectOrId', '**', [matching]); |
||||||
|
} |
||||||
|
|
||||||
|
return this |
||||||
|
.apiV3Service |
||||||
|
.work_packages |
||||||
|
.filtered(filters) |
||||||
|
.get() |
||||||
|
.pipe( |
||||||
|
map(collection => collection.elements) |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
protected require(id:string):Promise<HalResource> { |
||||||
|
return this |
||||||
|
.apiV3Service |
||||||
|
.work_packages |
||||||
|
.id(id) |
||||||
|
.get() |
||||||
|
.toPromise(); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,57 @@ |
|||||||
|
//-- copyright
|
||||||
|
// OpenProject is an open source project management software.
|
||||||
|
// Copyright (C) 2012-2020 the OpenProject GmbH
|
||||||
|
//
|
||||||
|
// This program is free software; you can redistribute it and/or
|
||||||
|
// modify it under the terms of the GNU General Public License version 3.
|
||||||
|
//
|
||||||
|
// OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
|
||||||
|
// Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||||
|
// Copyright (C) 2010-2013 the ChiliProject Team
|
||||||
|
//
|
||||||
|
// This program is free software; you can redistribute it and/or
|
||||||
|
// modify it under the terms of the GNU General Public License
|
||||||
|
// as published by the Free Software Foundation; either version 2
|
||||||
|
// of the License, or (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program; if not, write to the Free Software
|
||||||
|
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||||
|
//
|
||||||
|
// See docs/COPYRIGHT.rdoc for more details.
|
||||||
|
//++
|
||||||
|
import {Component, Input, OnInit} from "@angular/core"; |
||||||
|
import {HalResource} from "core-app/modules/hal/resources/hal-resource"; |
||||||
|
import {PathHelperService} from "core-app/modules/common/path-helper/path-helper.service"; |
||||||
|
import {I18nService} from "core-app/modules/common/i18n/i18n.service"; |
||||||
|
import {WorkPackageResource} from "core-app/modules/hal/resources/work-package-resource"; |
||||||
|
import {Highlighting} from "core-components/wp-fast-table/builders/highlighting/highlighting.functions"; |
||||||
|
|
||||||
|
|
||||||
|
@Component({ |
||||||
|
templateUrl: './subtasks-board-header.html', |
||||||
|
styleUrls: ['./subtasks-board-header.sass'], |
||||||
|
host: { 'class': 'title-container -small' } |
||||||
|
}) |
||||||
|
export class SubtasksBoardHeaderComponent implements OnInit { |
||||||
|
@Input() public resource:WorkPackageResource; |
||||||
|
|
||||||
|
text = { |
||||||
|
workPackage: this.I18n.t('js.label_work_package_parent') |
||||||
|
}; |
||||||
|
|
||||||
|
typeHighlightingClass:string; |
||||||
|
|
||||||
|
constructor(readonly pathHelper:PathHelperService, |
||||||
|
readonly I18n:I18nService) { |
||||||
|
} |
||||||
|
|
||||||
|
ngOnInit() { |
||||||
|
this.typeHighlightingClass = Highlighting.inlineClass('type', this.resource.type.id!); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,14 @@ |
|||||||
|
<div class="subtask-board-header" *ngIf="resource"> |
||||||
|
<h2 class="editable-toolbar-title--fixed"> |
||||||
|
<small [textContent]="text.workPackage"></small> |
||||||
|
<br/> |
||||||
|
<span class="work-package-type" |
||||||
|
[ngClass]="typeHighlightingClass" |
||||||
|
[textContent]="resource.type.name"> |
||||||
|
</span> |
||||||
|
<a [href]="pathHelper.workPackagePath(resource.idFromLink)" |
||||||
|
[textContent]="resource.subjectWithId()" |
||||||
|
target="_blank"> |
||||||
|
</a> |
||||||
|
</h2> |
||||||
|
</div> |
@ -0,0 +1,7 @@ |
|||||||
|
// Override line-height for proper |
||||||
|
// display of the h2 + small |
||||||
|
.editable-toolbar-title--fixed |
||||||
|
line-height: 1 !important |
||||||
|
|
||||||
|
.work-package-type |
||||||
|
padding-right: 0.5rem |
@ -0,0 +1,158 @@ |
|||||||
|
#-- encoding: UTF-8 |
||||||
|
|
||||||
|
#-- copyright |
||||||
|
# OpenProject is an open source project management software. |
||||||
|
# Copyright (C) 2012-2020 the OpenProject GmbH |
||||||
|
# |
||||||
|
# This program is free software; you can redistribute it and/or |
||||||
|
# modify it under the terms of the GNU General Public License version 3. |
||||||
|
# |
||||||
|
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: |
||||||
|
# Copyright (C) 2006-2017 Jean-Philippe Lang |
||||||
|
# Copyright (C) 2010-2013 the ChiliProject Team |
||||||
|
# |
||||||
|
# This program is free software; you can redistribute it and/or |
||||||
|
# modify it under the terms of the GNU General Public License |
||||||
|
# as published by the Free Software Foundation; either version 2 |
||||||
|
# of the License, or (at your option) any later version. |
||||||
|
# |
||||||
|
# This program is distributed in the hope that it will be useful, |
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||||
|
# GNU General Public License for more details. |
||||||
|
# |
||||||
|
# You should have received a copy of the GNU General Public License |
||||||
|
# along with this program; if not, write to the Free Software |
||||||
|
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
||||||
|
# |
||||||
|
# See docs/COPYRIGHT.rdoc for more details. |
||||||
|
#++ |
||||||
|
|
||||||
|
require 'roar/decorator' |
||||||
|
require 'roar/json/hal' |
||||||
|
|
||||||
|
module API |
||||||
|
module V3 |
||||||
|
module Attachments |
||||||
|
class AttachmentUploadRepresenter < ::API::Decorators::Single |
||||||
|
include API::Decorators::DateProperty |
||||||
|
include API::Decorators::FormattableProperty |
||||||
|
include API::Decorators::LinkedResource |
||||||
|
|
||||||
|
self_link title_getter: ->(*) { represented.filename } |
||||||
|
|
||||||
|
associated_resource :author, |
||||||
|
v3_path: :user, |
||||||
|
representer: ::API::V3::Users::UserRepresenter |
||||||
|
|
||||||
|
def self.associated_container_getter |
||||||
|
->(*) do |
||||||
|
next unless embed_links && container_representer |
||||||
|
|
||||||
|
container_representer |
||||||
|
.new(represented.container, current_user: current_user) |
||||||
|
end |
||||||
|
end |
||||||
|
|
||||||
|
def self.associated_container_link |
||||||
|
->(*) do |
||||||
|
return nil unless v3_container_name == 'nil_class' || api_v3_paths.respond_to?(v3_container_name) |
||||||
|
|
||||||
|
::API::Decorators::LinkObject |
||||||
|
.new(represented, |
||||||
|
path: v3_container_name, |
||||||
|
property_name: :container, |
||||||
|
title_attribute: container_title_attribute) |
||||||
|
.to_hash |
||||||
|
end |
||||||
|
end |
||||||
|
|
||||||
|
attr_reader :form_url |
||||||
|
attr_reader :form_fields |
||||||
|
|
||||||
|
attr_reader :attachment |
||||||
|
|
||||||
|
def initialize(attachment, options = {}) |
||||||
|
super |
||||||
|
|
||||||
|
fog_hash = DirectFogUploader.direct_fog_hash attachment: attachment |
||||||
|
|
||||||
|
@form_url = fog_hash[:uri] |
||||||
|
@form_fields = fog_hash.except :uri |
||||||
|
@attachment = attachment |
||||||
|
end |
||||||
|
|
||||||
|
associated_resource :container, |
||||||
|
getter: associated_container_getter, |
||||||
|
link: associated_container_link |
||||||
|
|
||||||
|
link :addAttachment do |
||||||
|
{ |
||||||
|
href: form_url, |
||||||
|
method: :post, |
||||||
|
form_fields: form_fields |
||||||
|
} |
||||||
|
end |
||||||
|
|
||||||
|
link :delete do |
||||||
|
{ |
||||||
|
href: api_v3_paths.attachment_upload(represented.id), |
||||||
|
method: :delete |
||||||
|
} |
||||||
|
end |
||||||
|
|
||||||
|
link :staticDownloadLocation do |
||||||
|
{ |
||||||
|
href: api_v3_paths.attachment_content(attachment.id) |
||||||
|
} |
||||||
|
end |
||||||
|
|
||||||
|
link :downloadLocation do |
||||||
|
location = if attachment.external_storage? |
||||||
|
attachment.external_url |
||||||
|
else |
||||||
|
api_v3_paths.attachment_content(attachment.id) |
||||||
|
end |
||||||
|
{ |
||||||
|
href: location |
||||||
|
} |
||||||
|
end |
||||||
|
|
||||||
|
link :completeUpload do |
||||||
|
{ |
||||||
|
href: api_v3_paths.attachment_uploaded(attachment.id) |
||||||
|
} |
||||||
|
end |
||||||
|
|
||||||
|
property :id |
||||||
|
property :file_name, |
||||||
|
getter: ->(*) { filename } |
||||||
|
|
||||||
|
formattable_property :description, |
||||||
|
plain: true |
||||||
|
|
||||||
|
date_time_property :created_at |
||||||
|
|
||||||
|
def _type |
||||||
|
'AttachmentUpload' |
||||||
|
end |
||||||
|
|
||||||
|
def container_representer |
||||||
|
name = v3_container_name.camelcase |
||||||
|
|
||||||
|
"::API::V3::#{name.pluralize}::#{name}Representer".constantize |
||||||
|
rescue NameError |
||||||
|
nil |
||||||
|
end |
||||||
|
|
||||||
|
def v3_container_name |
||||||
|
::API::Utilities::PropertyNameConverter.from_ar_name(represented.container.class.name.underscore).underscore |
||||||
|
end |
||||||
|
|
||||||
|
def container_title_attribute |
||||||
|
represented.container.respond_to?(:subject) ? :subject : :title |
||||||
|
end |
||||||
|
end |
||||||
|
end |
||||||
|
end |
||||||
|
end |
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue