file.ex (3589B)
1 # Zenflows is designed to implement the Valueflows vocabulary, 2 # written and maintained by srfsh <info@dyne.org>. 3 # Copyright (C) 2021-2023 Dyne.org foundation <foundation@dyne.org>. 4 # 5 # This program is free software: you can redistribute it and/or modify 6 # it under the terms of the GNU Affero General Public License as published by 7 # the Free Software Foundation, either version 3 of the License, or 8 # (at your option) any later version. 9 # 10 # This program is distributed in the hope that it will be useful, 11 # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 # GNU Affero General Public License for more details. 14 # 15 # You should have received a copy of the GNU Affero General Public License 16 # along with this program. If not, see <https://www.gnu.org/licenses/>. 17 18 defmodule Zenflows.File do 19 @moduledoc """ 20 File representation in storage. 21 """ 22 23 use Zenflows.DB.Schema 24 25 require Logger 26 27 alias Ecto.Changeset 28 alias Zenflows.DB.{Schema, Validate} 29 alias Zenflows.VF.{ 30 Agent, 31 EconomicResource, 32 Intent, 33 RecipeResource, 34 ResourceSpecification, 35 } 36 37 @type t() :: %__MODULE__{ 38 hash: String.t(), 39 name: String.t(), 40 description: String.t(), 41 mime_type: String.t(), 42 extension: String.t(), 43 size: pos_integer(), 44 signature: String.t(), 45 width: pos_integer() | nil, 46 height: pos_integer() | nil, 47 bin: String.t() | nil, 48 recipe_resource: RecipeResource.t() | nil, 49 economic_resource: EconomicResource.t() | nil, 50 agent: Agent.t() | nil, 51 resource_specification: ResourceSpecification.t() | nil, 52 intent: Intent.t() | nil, 53 } 54 55 schema "zf_file" do 56 field :hash, :string 57 field :name, :string 58 field :description, :string 59 field :mime_type, :string 60 field :extension, :string 61 field :size, :integer 62 field :signature, :string 63 field :width, :integer 64 field :height, :integer 65 field :bin, :string 66 timestamps() 67 68 belongs_to :recipe_resource, RecipeResource 69 belongs_to :economic_resource, EconomicResource 70 belongs_to :agent, Agent 71 belongs_to :resource_specification, ResourceSpecification 72 belongs_to :intent, Intent 73 end 74 75 @reqr ~w[hash name description mime_type extension size signature]a 76 @cast @reqr ++ ~w[ 77 width height bin 78 recipe_resource_id economic_resource_id agent_id 79 resource_specification_id intent_id 80 ]a 81 82 @spec changeset(Schema.t(), Schema.params()) :: Changeset.t() 83 def changeset(schema \\ %__MODULE__{}, params) do 84 schema 85 |> Changeset.cast(params, @cast) 86 |> Changeset.validate_required(@reqr) 87 |> Validate.key(:hash) 88 |> Validate.name(:name) 89 |> Validate.note(:description) 90 |> Validate.name(:mime_type) 91 |> Validate.name(:extension) 92 |> Changeset.validate_number(:size, greater_than: 0, less_than_or_equal_to: 1024 * 1024 * 25) 93 |> log_size_warning() 94 |> Validate.key(:signature) 95 |> Changeset.validate_number(:width, greater_than: 0) 96 |> Changeset.validate_number(:height, greater_than: 0) 97 |> Validate.img(:bin) 98 |> Changeset.assoc_constraint(:recipe_resource) 99 |> Changeset.assoc_constraint(:economic_resource) 100 |> Changeset.assoc_constraint(:agent) 101 |> Changeset.assoc_constraint(:resource_specification) 102 |> Changeset.assoc_constraint(:intent) 103 |> Changeset.unique_constraint(:hash) 104 |> Changeset.check_constraint(:general, name: :mutex, message: """ 105 one of RecipeResource, EconomicResource, Agent, ResourceSpecification, or Intent must be provided. 106 """) 107 end 108 109 defp log_size_warning(cset) do 110 with {:ok, hash} <- Changeset.fetch_change(cset, :hash), 111 {:ok, n} when n > 1024 * 1024 * 4 <- Changeset.fetch_change(cset, :size), 112 do: Logger.warning("file exceeds 4MiB: #{inspect(hash)}") 113 cset 114 end 115 end