Initial DB.

master
Kaitlyn Parkhurst 3 years ago
parent 351fe2a257
commit 97d0ff1467
  1. 17
      DB/bin/create-class
  2. 21
      DB/dist.ini
  3. 68
      DB/etc/schema.sql
  4. 22
      DB/lib/WeightGrapher/DB.pm
  5. 176
      DB/lib/WeightGrapher/DB/Result/AuthPassword.pm
  6. 128
      DB/lib/WeightGrapher/DB/Result/AuthToken.pm
  7. 233
      DB/lib/WeightGrapher/DB/Result/Person.pm
  8. 151
      DB/lib/WeightGrapher/DB/Result/PersonSetting.pm

@ -0,0 +1,17 @@
#!/usr/bin/env bash
CLASS_NAME="WeightGrapher::DB"
# Generate a random 8 character name for the docker container that holds the PSQL
# database.
PSQL_NAME=$(cat /dev/urandom | LC_ALL=C tr -dc 'a-zA-Z' | fold -w 8 | head -n 1)
# Launch a PSQL Instance
PSQL_DOCKER=`docker run --rm --name $PSQL_NAME -e POSTGRES_PASSWORD=dbic -e POSTGRES_USER=dbic -e POSTGRES_DB=dbic -d \
--mount type=bind,src=$PWD/etc/schema.sql,dst=/docker-entrypoint-initdb.d/schema.sql postgres:11`
docker run --rm --link $PSQL_NAME:psqldb --mount type=bind,src=$PWD,dst=/app symkat/schema_builder /bin/build-schema $CLASS_NAME
docker kill $PSQL_DOCKER
sudo chown -R $USER:$USER lib

@ -0,0 +1,21 @@
name = WeightGrapher-DB
author = Kaitlyn Parkhurst <symkat@symkat.com>
license = Perl_5
copyright_holder = Kaitlyn Parkhurst
copyright_year = 2022
abstract = WeightGrapher Database
version = 1
[@Basic]
[Prereqs]
DBIx::Class::InflateColumn::Serializer = 0
DBIx::Class::Schema::Config = 0
DBIx::Class::DeploymentHandler = 0
DBIx::Class::Schema::ResultSetNames = 0
MooseX::AttributeShortcuts = 0
MooseX::Getopt = 0
Data::GUID = 0
DBD::Pg = 0
Crypt::Eksblowfish::Bcrypt = 0
Crypt::Random = 0

@ -0,0 +1,68 @@
CREATE EXTENSION IF NOT EXISTS citext;
CREATE TABLE person (
id serial PRIMARY KEY,
name text not null,
email citext not null unique,
is_enabled boolean not null default true,
is_admin boolean not null default false,
created_at timestamptz not null default current_timestamp
);
-- Settings for a given user. | Use with care, add things to the data model when you should.
CREATE TABLE person_settings (
id serial PRIMARY KEY,
person_id int not null references person(id),
name text not null,
value json not null default '{}',
created_at timestamptz not null default current_timestamp,
-- Allow ->find_or_new_related()
CONSTRAINT unq_person_id_name UNIQUE(person_id, name)
);
CREATE TABLE auth_password (
person_id int not null unique references person(id),
password text not null,
salt text not null,
updated_at timestamptz not null default current_timestamp,
created_at timestamptz not null default current_timestamp
);
CREATE TABLE auth_token (
id serial PRIMARY KEY,
person_id int not null references person(id),
scope text not null,
token text not null,
created_at timestamptz not null default current_timestamp
);
---
---
---
-- CREATE TABLE graph (
-- id serial PRIMARY KEY,
-- person_id int not null references person(id),
-- title text not null,
-- unit text not null, -- lb / kg / st
-- created_at timestamptz not null default current_timestamp
-- );
--
-- CREATE TABLE graph_settings (
-- id serial PRIMARY KEY,
-- graph_id int not null references graph(id),
-- name text not null,
-- value json not null default '{}',
--
-- -- Allow ->find_or_new_related()
-- CONSTRAINT unq_graph_id_name UNIQUE(graph_id, name)
-- );
--
-- CREATE TABLE graph_data (
-- id serial PRIMARY KEY,
-- graph_id int not null references graph(id),
-- value numeric not null,
-- note text ,
-- ts timestamptz not null default current_timestamp
-- );

@ -0,0 +1,22 @@
use utf8;
package WeightGrapher::DB;
# Created by DBIx::Class::Schema::Loader
# DO NOT MODIFY THE FIRST PART OF THIS FILE
use strict;
use warnings;
use base 'DBIx::Class::Schema';
__PACKAGE__->load_components("Schema::Config", "Schema::ResultSetNames");
__PACKAGE__->load_namespaces;
# Created by DBIx::Class::Schema::Loader v0.07049 @ 2022-08-29 23:53:08
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:rztJ5RmpMtq7ukHg5gS0vw
# You can replace this text with custom code or comments, and it will be preserved on regeneration
1;

@ -0,0 +1,176 @@
use utf8;
package WeightGrapher::DB::Result::AuthPassword;
# Created by DBIx::Class::Schema::Loader
# DO NOT MODIFY THE FIRST PART OF THIS FILE
=head1 NAME
WeightGrapher::DB::Result::AuthPassword
=cut
use strict;
use warnings;
use base 'DBIx::Class::Core';
=head1 COMPONENTS LOADED
=over 4
=item * L<DBIx::Class::InflateColumn::DateTime>
=item * L<DBIx::Class::InflateColumn::Serializer>
=back
=cut
__PACKAGE__->load_components("InflateColumn::DateTime", "InflateColumn::Serializer");
=head1 TABLE: C<auth_password>
=cut
__PACKAGE__->table("auth_password");
=head1 ACCESSORS
=head2 person_id
data_type: 'integer'
is_foreign_key: 1
is_nullable: 0
=head2 password
data_type: 'text'
is_nullable: 0
=head2 salt
data_type: 'text'
is_nullable: 0
=head2 updated_at
data_type: 'timestamp with time zone'
default_value: current_timestamp
is_nullable: 0
=head2 created_at
data_type: 'timestamp with time zone'
default_value: current_timestamp
is_nullable: 0
=cut
__PACKAGE__->add_columns(
"person_id",
{ data_type => "integer", is_foreign_key => 1, is_nullable => 0 },
"password",
{ data_type => "text", is_nullable => 0 },
"salt",
{ data_type => "text", is_nullable => 0 },
"updated_at",
{
data_type => "timestamp with time zone",
default_value => \"current_timestamp",
is_nullable => 0,
},
"created_at",
{
data_type => "timestamp with time zone",
default_value => \"current_timestamp",
is_nullable => 0,
},
);
=head1 UNIQUE CONSTRAINTS
=head2 C<auth_password_person_id_key>
=over 4
=item * L</person_id>
=back
=cut
__PACKAGE__->add_unique_constraint("auth_password_person_id_key", ["person_id"]);
=head1 RELATIONS
=head2 person
Type: belongs_to
Related object: L<WeightGrapher::DB::Result::Person>
=cut
__PACKAGE__->belongs_to(
"person",
"WeightGrapher::DB::Result::Person",
{ id => "person_id" },
{ is_deferrable => 0, on_delete => "NO ACTION", on_update => "NO ACTION" },
);
# Created by DBIx::Class::Schema::Loader v0.07049 @ 2022-08-29 23:53:08
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:vdn8gq3VmE1q9sqlN7w3fA
__PACKAGE__->set_primary_key('person_id');
use Crypt::Eksblowfish::Bcrypt qw( bcrypt_hash en_base64 de_base64 );
use Crypt::Random;
sub check_password {
my ( $self, $password ) = @_;
return de_base64($self->password) eq bcrypt_hash({
key_nul => 1,
cost => 8,
salt => de_base64($self->salt),
}, $password );
}
sub set_password {
my ( $self, $password ) = @_;
$self->_fill_password( $password );
$self->insert;
return $self;
}
sub update_password {
my ( $self, $password ) = @_;
$self->_fill_password( $password );
$self->update;
return $self;
}
sub _fill_password {
my ( $self, $password ) = @_;
my $salt = random_salt();
$self->password(
en_base64(
bcrypt_hash({
key_nul => 1,
cost => 8,
salt => $salt,
}, $password )
)
);
$self->salt( en_base64($salt) );
}
sub random_salt {
Crypt::Random::makerandom_octet( Length => 16 );
}
1;

@ -0,0 +1,128 @@
use utf8;
package WeightGrapher::DB::Result::AuthToken;
# Created by DBIx::Class::Schema::Loader
# DO NOT MODIFY THE FIRST PART OF THIS FILE
=head1 NAME
WeightGrapher::DB::Result::AuthToken
=cut
use strict;
use warnings;
use base 'DBIx::Class::Core';
=head1 COMPONENTS LOADED
=over 4
=item * L<DBIx::Class::InflateColumn::DateTime>
=item * L<DBIx::Class::InflateColumn::Serializer>
=back
=cut
__PACKAGE__->load_components("InflateColumn::DateTime", "InflateColumn::Serializer");
=head1 TABLE: C<auth_token>
=cut
__PACKAGE__->table("auth_token");
=head1 ACCESSORS
=head2 id
data_type: 'integer'
is_auto_increment: 1
is_nullable: 0
sequence: 'auth_token_id_seq'
=head2 person_id
data_type: 'integer'
is_foreign_key: 1
is_nullable: 0
=head2 scope
data_type: 'text'
is_nullable: 0
=head2 token
data_type: 'text'
is_nullable: 0
=head2 created_at
data_type: 'timestamp with time zone'
default_value: current_timestamp
is_nullable: 0
=cut
__PACKAGE__->add_columns(
"id",
{
data_type => "integer",
is_auto_increment => 1,
is_nullable => 0,
sequence => "auth_token_id_seq",
},
"person_id",
{ data_type => "integer", is_foreign_key => 1, is_nullable => 0 },
"scope",
{ data_type => "text", is_nullable => 0 },
"token",
{ data_type => "text", is_nullable => 0 },
"created_at",
{
data_type => "timestamp with time zone",
default_value => \"current_timestamp",
is_nullable => 0,
},
);
=head1 PRIMARY KEY
=over 4
=item * L</id>
=back
=cut
__PACKAGE__->set_primary_key("id");
=head1 RELATIONS
=head2 person
Type: belongs_to
Related object: L<WeightGrapher::DB::Result::Person>
=cut
__PACKAGE__->belongs_to(
"person",
"WeightGrapher::DB::Result::Person",
{ id => "person_id" },
{ is_deferrable => 0, on_delete => "NO ACTION", on_update => "NO ACTION" },
);
# Created by DBIx::Class::Schema::Loader v0.07049 @ 2022-08-29 23:53:08
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:BYnapLoZXU3d6DbiJpsdkA
# You can replace this text with custom code or comments, and it will be preserved on regeneration
1;

@ -0,0 +1,233 @@
use utf8;
package WeightGrapher::DB::Result::Person;
# Created by DBIx::Class::Schema::Loader
# DO NOT MODIFY THE FIRST PART OF THIS FILE
=head1 NAME
WeightGrapher::DB::Result::Person
=cut
use strict;
use warnings;
use base 'DBIx::Class::Core';
=head1 COMPONENTS LOADED
=over 4
=item * L<DBIx::Class::InflateColumn::DateTime>
=item * L<DBIx::Class::InflateColumn::Serializer>
=back
=cut
__PACKAGE__->load_components("InflateColumn::DateTime", "InflateColumn::Serializer");
=head1 TABLE: C<person>
=cut
__PACKAGE__->table("person");
=head1 ACCESSORS
=head2 id
data_type: 'integer'
is_auto_increment: 1
is_nullable: 0
sequence: 'person_id_seq'
=head2 name
data_type: 'text'
is_nullable: 0
=head2 email
data_type: 'citext'
is_nullable: 0
=head2 is_enabled
data_type: 'boolean'
default_value: true
is_nullable: 0
=head2 is_admin
data_type: 'boolean'
default_value: false
is_nullable: 0
=head2 created_at
data_type: 'timestamp with time zone'
default_value: current_timestamp
is_nullable: 0
=cut
__PACKAGE__->add_columns(
"id",
{
data_type => "integer",
is_auto_increment => 1,
is_nullable => 0,
sequence => "person_id_seq",
},
"name",
{ data_type => "text", is_nullable => 0 },
"email",
{ data_type => "citext", is_nullable => 0 },
"is_enabled",
{ data_type => "boolean", default_value => \"true", is_nullable => 0 },
"is_admin",
{ data_type => "boolean", default_value => \"false", is_nullable => 0 },
"created_at",
{
data_type => "timestamp with time zone",
default_value => \"current_timestamp",
is_nullable => 0,
},
);
=head1 PRIMARY KEY
=over 4
=item * L</id>
=back
=cut
__PACKAGE__->set_primary_key("id");
=head1 UNIQUE CONSTRAINTS
=head2 C<person_email_key>
=over 4
=item * L</email>
=back
=cut
__PACKAGE__->add_unique_constraint("person_email_key", ["email"]);
=head1 RELATIONS
=head2 auth_password
Type: might_have
Related object: L<WeightGrapher::DB::Result::AuthPassword>
=cut
__PACKAGE__->might_have(
"auth_password",
"WeightGrapher::DB::Result::AuthPassword",
{ "foreign.person_id" => "self.id" },
{ cascade_copy => 0, cascade_delete => 0 },
);
=head2 auth_tokens
Type: has_many
Related object: L<WeightGrapher::DB::Result::AuthToken>
=cut
__PACKAGE__->has_many(
"auth_tokens",
"WeightGrapher::DB::Result::AuthToken",
{ "foreign.person_id" => "self.id" },
{ cascade_copy => 0, cascade_delete => 0 },
);
=head2 person_settings
Type: has_many
Related object: L<WeightGrapher::DB::Result::PersonSetting>
=cut
__PACKAGE__->has_many(
"person_settings",
"WeightGrapher::DB::Result::PersonSetting",
{ "foreign.person_id" => "self.id" },
{ cascade_copy => 0, cascade_delete => 0 },
);
# Created by DBIx::Class::Schema::Loader v0.07049 @ 2022-08-29 23:53:08
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:I1siWN8RScSHO2w7nsrddg
use Data::GUID;
sub setting {
my ( $self, $setting, $value ) = @_;
if ( defined $value ) {
my $rs = $self->find_or_new_related( 'person_settings', { name => $setting } );
$rs->value( ref $value ? $value : { value => $value } );
$rs->update if $rs->in_storage;
$rs->insert unless $rs->in_storage;
return $value;
} else {
my $result = $self->find_related('person_settings', { name => $setting });
return undef unless $result;
return $self->_get_setting_value($result);
}
}
sub _get_setting_value {
my ( $self, $setting ) = @_;
if ( ref $setting->value eq 'HASH' and keys %{$setting->value} == 1 and exists $setting->value->{value} ) {
return $setting->value->{value};
}
return $setting->value;
}
sub get_settings {
my ( $self ) = @_;
my $return = {};
foreach my $setting ( $self->search_related( 'person_settings', {} )->all ) {
$return->{${\($setting->name)}} = $self->_get_setting_value($setting);
}
return $return;
}
sub create_auth_token {
my ( $self, $scope ) = @_;
my $token = Data::GUID->guid_string;
$self->create_related( 'auth_tokens', {
token => $token,
scope => $scope,
});
return $token;
}
1;

@ -0,0 +1,151 @@
use utf8;
package WeightGrapher::DB::Result::PersonSetting;
# Created by DBIx::Class::Schema::Loader
# DO NOT MODIFY THE FIRST PART OF THIS FILE
=head1 NAME
WeightGrapher::DB::Result::PersonSetting
=cut
use strict;
use warnings;
use base 'DBIx::Class::Core';
=head1 COMPONENTS LOADED
=over 4
=item * L<DBIx::Class::InflateColumn::DateTime>
=item * L<DBIx::Class::InflateColumn::Serializer>
=back
=cut
__PACKAGE__->load_components("InflateColumn::DateTime", "InflateColumn::Serializer");
=head1 TABLE: C<person_settings>
=cut
__PACKAGE__->table("person_settings");
=head1 ACCESSORS
=head2 id
data_type: 'integer'
is_auto_increment: 1
is_nullable: 0
sequence: 'person_settings_id_seq'
=head2 person_id
data_type: 'integer'
is_foreign_key: 1
is_nullable: 0
=head2 name
data_type: 'text'
is_nullable: 0
=head2 value
data_type: 'json'
default_value: '{}'
is_nullable: 0
serializer_class: 'JSON'
=head2 created_at
data_type: 'timestamp with time zone'
default_value: current_timestamp
is_nullable: 0
=cut
__PACKAGE__->add_columns(
"id",
{
data_type => "integer",
is_auto_increment => 1,
is_nullable => 0,
sequence => "person_settings_id_seq",
},
"person_id",
{ data_type => "integer", is_foreign_key => 1, is_nullable => 0 },
"name",
{ data_type => "text", is_nullable => 0 },
"value",
{
data_type => "json",
default_value => "{}",
is_nullable => 0,
serializer_class => "JSON",
},
"created_at",
{
data_type => "timestamp with time zone",
default_value => \"current_timestamp",
is_nullable => 0,
},
);
=head1 PRIMARY KEY
=over 4
=item * L</id>
=back
=cut
__PACKAGE__->set_primary_key("id");
=head1 UNIQUE CONSTRAINTS
=head2 C<unq_person_id_name>
=over 4
=item * L</person_id>
=item * L</name>
=back
=cut
__PACKAGE__->add_unique_constraint("unq_person_id_name", ["person_id", "name"]);
=head1 RELATIONS
=head2 person
Type: belongs_to
Related object: L<WeightGrapher::DB::Result::Person>
=cut
__PACKAGE__->belongs_to(
"person",
"WeightGrapher::DB::Result::Person",
{ id => "person_id" },
{ is_deferrable => 0, on_delete => "NO ACTION", on_update => "NO ACTION" },
);
# Created by DBIx::Class::Schema::Loader v0.07049 @ 2022-08-29 23:53:08
# DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:lgwAMRyofSiVVgh5M+B+LQ
# You can replace this text with custom code or comments, and it will be preserved on regeneration
1;
Loading…
Cancel
Save