# frozen_string_literal: true # # ronin-db-activerecord - ActiveRecord backend for the Ronin Database. # # Copyright (c) 2022-2024 Hal Brodigan (postmodern.mod3 at gmail.com) # # ronin-db-activerecord is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published # by the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # ronin-db-activerecord 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 Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with ronin-db-activerecord. If not, see . # require 'ronin/db/model' require 'ronin/db/model/importable' require 'active_record' module Ronin module DB # # Represents a phone number. # # @since 0.2.0 # class PhoneNumber < ActiveRecord::Base include Model include Model::Importable # @!attribute [rw] id # The primary key of the phone number. # # @return [Integer] attribute :id, :integer # @!attribute [rw] number # The phone number. # # @return [String] attribute :number, :string validates :number, presence: true, format: { with: /\A(?:\+?\d{1,3}[\s.-])?(?:(?:\(\d{3}\)|\d{3})[\s.-])?\d{3}[\s.-]\d{4}\z/, message: 'must be a valid phone number' }, uniqueness: true # @!attribute [rw] country_code # The phone number country code (the first one or two digits). # # @return [String, nil] attribute :country_code, :string validates :country_code, format: { with: /\A\d{1,3}\z/, message: 'must be a valid country code number' }, allow_nil: true # @!attribute [rw] area_code # The phone number area code (three digits after the country code). # # @return [String, nil] attribute :area_code, :string validates :area_code, format: { with: /\A\d{3}\z/, message: 'must be a valid area code number' }, allow_nil: true # @!attribute [rw] prefix # The phone number prefix (three digits after the area code). # # @return [String] attribute :prefix, :string validates :prefix, presence: true, format: { with: /\A\d{3}\z/, message: 'must be a valid prefix number' } # @!attribute [rw] line_number # The phone number line number (last four digits). # # @return [String] attribute :line_number, :string validates :line_number, presence: true, format: { with: /\A\d{4}\z/, message: 'must be a valid line number' } # @!attribute [rw] created_at # Tracks when the phone number was first created. # # @return [Time] attribute :created_at, :datetime # @!attribute [rw] personal_phone_numbers # The association of people that use this phone number. # # @return [Array] has_many :personal_phone_numbers, dependent: :destroy # @!attribute [rw] people # The people that use this phone number. # # @return [Array] has_many :people, through: :personal_phone_numbers # @!attribute [rw] organization_phone_number # The association of the organization that use the phone number. # # @return [OrganiationPhoneNumber, nil] has_one :organization_phone_number, dependent: :destroy # @!attribute [rw] organization_department # The organization department that uses the email address. # # @return [OrganizationDepartment, nil] has_one :organization_department, dependent: :nullify # @!attribute [rw] organization_members # The organization member that use the phone number. # # @return [OrganizationMember, nil] has_one :organization_member, dependent: :nullify # @!attribute [rw] notes # The associated notes. # # @return [Array] has_many :notes, dependent: :destroy # # Looks up the phone number. # # @param [String] number # The phone number to query. # # @return [PhoneNumber, nil] # The found phone number. # # @api public # def self.lookup(number) find_by(number: number) end # # Parses the phone number. # # @param [String] number # The raw phone number to parse. # # @return [Hash{Symbol => String,nil}] # The parsed attributes of the phone number. # # @api private # def self.parse(number) if (match = number.match(/\A(?:(?:\+?(?\d{1,3})[\s.-])?(?:\((?\d{3})\)|(?\d{3}))[\s.-])?(?\d{3})[\s.-](?\d{4})\z/)) { number: number, country_code: match[:country_code], area_code: match[:area_code], prefix: match[:prefix], line_number: match[:line_number] } else raise(ArgumentError,"invalid phone number: #{number.inspect}") end end # # Imports an phone number. # # @param [String] number # The phone number to import. # # @return [PhoneNumber] # The imported phone number. # # @api public # def self.import(number) create(parse(number)) end # # Queries all phone numbers associated with the person. # # @param [String] full_name # The person's full name. # # @return [Array] # The phone numbers associated with the person. # # @api public # def self.for_person(full_name) joins(:people).where(people: {full_name: full_name}) end # # Queries all phone numbers associated with the organization name. # # @param [String] name # The organization's name. # # @return [Array] # The phone numbers associated with the organization. # # @api public # def self.for_organization(name) joins(organization_phone_number: :organization).where( organization_phone_number: { ronin_organizations: {name: name} } ) end # # Finds all similar phone numbers with the matching phone number # components. # # @param [String] number # The phone number to parse and search for. # # @return [Array] # The similar phone numbers. # # @api public # def self.similar_to(number) attributes = parse(number) attributes.delete(:number) attributes.compact! where(**attributes) end # # Finds all phone numbers with the matching country code. # # @param [String] country_code # The country code to search for. # # @return [Array] # The phone numbers with the matching country code. # # @api public # def self.with_country_code(country_code) where(country_code: country_code) end # # Finds all phone numbers with the matching area code. # # @param [String] area_code # The area code to search for. # # @return [Array] # The phone numbers with the matching area code. # # @api public # def self.with_area_code(area_code) where(area_code: area_code) end # # Finds all phone numbers with the matching prefix. # # @param [String] prefix # The prefix to search for. # # @return [Array] # The phone numbers with the matching prefix. # # @api public # def self.with_prefix(prefix) where(prefix: prefix) end # # Finds all phone numbers with the matching line number. # # @param [String] line_number # The line number to search for. # # @return [Array] # The phone numbers with the matching line number. # # @api public # def self.with_line_number(line_number) where(line_number: line_number) end # # Converts the phone number to a String. # # @return [String] # def to_s number end end end end require 'ronin/db/personal_phone_number' require 'ronin/db/person' require 'ronin/db/organization_phone_number' require 'ronin/db/organization_department' require 'ronin/db/organization_member' require 'ronin/db/note'