Initial commit.
This commit is contained in:
parent
46ae5930bc
commit
fe3dc21b56
37
.eslintrc.js
Normal file
37
.eslintrc.js
Normal file
@ -0,0 +1,37 @@
|
||||
module.exports = {
|
||||
env: {
|
||||
browser: true,
|
||||
es2021: true
|
||||
},
|
||||
extends: [
|
||||
'plugin:react/recommended',
|
||||
'standard-with-typescript'
|
||||
],
|
||||
overrides: [
|
||||
],
|
||||
parserOptions: {
|
||||
ecmaVersion: 'latest',
|
||||
sourceType: 'module',
|
||||
project: 'tsconfig.json'
|
||||
},
|
||||
plugins: [
|
||||
'react',
|
||||
'no-relative-import-paths'
|
||||
],
|
||||
rules: {
|
||||
indent: ['error', 4, { SwitchCase: 1 }],
|
||||
'no-relative-import-paths/no-relative-import-paths': ['warn', { allowSameFolder: true }],
|
||||
'@typescript-eslint/indent': ['error', 4],
|
||||
'react/jsx-indent': ['error', 4],
|
||||
'react/jsx-indent-props': ['error', 4]
|
||||
},
|
||||
settings: {
|
||||
'import/resolver': {
|
||||
typescript: {
|
||||
project: [
|
||||
'tsconfig.json'
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
34
Build.PL
Normal file
34
Build.PL
Normal file
@ -0,0 +1,34 @@
|
||||
#!/usr/bin/env perl
|
||||
use Module::Build;
|
||||
|
||||
my $home = $ENV{HOME};
|
||||
|
||||
my $build = Module::Build->new(
|
||||
module_name => 'OwlcodeAds',
|
||||
license => 'AGPLv3',
|
||||
dist_author => 'Sergio Iglesias <contact@owlcode.tech>',
|
||||
dist_abstract => 'Web de anuncios de Owlcode.tech.',
|
||||
requires => {
|
||||
'Email::MIME' => 0,
|
||||
'Email::Sender::Simple' => 0,
|
||||
'Email::Sender::Transport::SMTP' => 0,
|
||||
'Mojolicious' => 0,
|
||||
'Moo' => 0,
|
||||
'Crypt::URandom' => 0,
|
||||
'Crypt::Bcrypt' => 0,
|
||||
'JSON' => 0,
|
||||
'DBIx::Class' => 0,
|
||||
'DBD::Pg' => 0,
|
||||
'DBIx::Class::DeploymentHandler' => 0,
|
||||
'UUID::URandom' => 0,
|
||||
'Module::Pluggable' => 0,
|
||||
'Mojo::Redis' => 0,
|
||||
'List::AllUtils' => 0,
|
||||
'DateTime::Format::Pg' => 0,
|
||||
'DateTime::Format::ISO8601::Format' => 0,
|
||||
'Test::Most' => 0,
|
||||
'Carp::Always' => 0,
|
||||
'Try::Tiny' => 0,
|
||||
},
|
||||
);
|
||||
$build->create_build_script;
|
5
babel.config.json
Normal file
5
babel.config.json
Normal file
@ -0,0 +1,5 @@
|
||||
{
|
||||
"presets": [
|
||||
"@babel/preset-react"
|
||||
]
|
||||
}
|
18
dbicdh/PostgreSQL/deploy/0/001-auto-__VERSION.sql
Normal file
18
dbicdh/PostgreSQL/deploy/0/001-auto-__VERSION.sql
Normal file
@ -0,0 +1,18 @@
|
||||
--
|
||||
-- Created by SQL::Translator::Producer::PostgreSQL
|
||||
-- Created on Wed Aug 2 16:12:43 2023
|
||||
--
|
||||
;
|
||||
--
|
||||
-- Table: dbix_class_deploymenthandler_versions
|
||||
--
|
||||
CREATE TABLE "dbix_class_deploymenthandler_versions" (
|
||||
"id" serial NOT NULL,
|
||||
"version" character varying(50) NOT NULL,
|
||||
"ddl" text,
|
||||
"upgrade_sql" text,
|
||||
PRIMARY KEY ("id"),
|
||||
CONSTRAINT "dbix_class_deploymenthandler_versions_version" UNIQUE ("version")
|
||||
);
|
||||
|
||||
;
|
25
dbicdh/PostgreSQL/deploy/0/001-auto.sql
Normal file
25
dbicdh/PostgreSQL/deploy/0/001-auto.sql
Normal file
@ -0,0 +1,25 @@
|
||||
--
|
||||
-- Created by SQL::Translator::Producer::PostgreSQL
|
||||
-- Created on Wed Aug 2 16:12:43 2023
|
||||
--
|
||||
;
|
||||
--
|
||||
-- Table: users
|
||||
--
|
||||
CREATE TABLE "users" (
|
||||
"uuid" uuid NOT NULL,
|
||||
"username" text NOT NULL,
|
||||
"name" text NOT NULL,
|
||||
"surname" text NOT NULL,
|
||||
"encrypted_password" text NOT NULL,
|
||||
"email" text NOT NULL,
|
||||
"verified" boolean NOT NULL,
|
||||
"verification_token" text,
|
||||
"register_date" timestamp DEFAULT NOW() NOT NULL,
|
||||
"last_activity" timestamp DEFAULT NOW() NOT NULL,
|
||||
PRIMARY KEY ("uuid"),
|
||||
CONSTRAINT "unique_constraint_email" UNIQUE ("email"),
|
||||
CONSTRAINT "unique_constraint_username" UNIQUE ("username")
|
||||
);
|
||||
|
||||
;
|
26
dbicdh/PostgreSQL/upgrade/-1-0/001-auto.sql
Normal file
26
dbicdh/PostgreSQL/upgrade/-1-0/001-auto.sql
Normal file
@ -0,0 +1,26 @@
|
||||
-- Convert schema '/home/sergio/OwlcodeAds/script/../dbicdh/_source/deploy/-1/001-auto.yml' to '/home/sergio/OwlcodeAds/script/../dbicdh/_source/deploy/0/001-auto.yml':;
|
||||
|
||||
;
|
||||
BEGIN;
|
||||
|
||||
;
|
||||
CREATE TABLE "users" (
|
||||
"uuid" uuid NOT NULL,
|
||||
"username" text NOT NULL,
|
||||
"name" text NOT NULL,
|
||||
"surname" text NOT NULL,
|
||||
"encrypted_password" text NOT NULL,
|
||||
"email" text NOT NULL,
|
||||
"verified" boolean NOT NULL,
|
||||
"verification_token" text,
|
||||
"register_date" timestamp DEFAULT NOW() NOT NULL,
|
||||
"last_activity" timestamp DEFAULT NOW() NOT NULL,
|
||||
PRIMARY KEY ("uuid"),
|
||||
CONSTRAINT "unique_constraint_email" UNIQUE ("email"),
|
||||
CONSTRAINT "unique_constraint_username" UNIQUE ("username")
|
||||
);
|
||||
|
||||
;
|
||||
|
||||
COMMIT;
|
||||
|
91
dbicdh/_source/deploy/0/001-auto-__VERSION.yml
Normal file
91
dbicdh/_source/deploy/0/001-auto-__VERSION.yml
Normal file
@ -0,0 +1,91 @@
|
||||
---
|
||||
schema:
|
||||
procedures: {}
|
||||
tables:
|
||||
dbix_class_deploymenthandler_versions:
|
||||
constraints:
|
||||
- deferrable: 1
|
||||
expression: ''
|
||||
fields:
|
||||
- id
|
||||
match_type: ''
|
||||
name: ''
|
||||
on_delete: ''
|
||||
on_update: ''
|
||||
options: []
|
||||
reference_fields: []
|
||||
reference_table: ''
|
||||
type: PRIMARY KEY
|
||||
- deferrable: 1
|
||||
expression: ''
|
||||
fields:
|
||||
- version
|
||||
match_type: ''
|
||||
name: dbix_class_deploymenthandler_versions_version
|
||||
on_delete: ''
|
||||
on_update: ''
|
||||
options: []
|
||||
reference_fields: []
|
||||
reference_table: ''
|
||||
type: UNIQUE
|
||||
fields:
|
||||
ddl:
|
||||
data_type: text
|
||||
default_value: ~
|
||||
is_nullable: 1
|
||||
is_primary_key: 0
|
||||
is_unique: 0
|
||||
name: ddl
|
||||
order: 3
|
||||
size:
|
||||
- 0
|
||||
id:
|
||||
data_type: int
|
||||
default_value: ~
|
||||
is_auto_increment: 1
|
||||
is_nullable: 0
|
||||
is_primary_key: 1
|
||||
is_unique: 0
|
||||
name: id
|
||||
order: 1
|
||||
size:
|
||||
- 0
|
||||
upgrade_sql:
|
||||
data_type: text
|
||||
default_value: ~
|
||||
is_nullable: 1
|
||||
is_primary_key: 0
|
||||
is_unique: 0
|
||||
name: upgrade_sql
|
||||
order: 4
|
||||
size:
|
||||
- 0
|
||||
version:
|
||||
data_type: varchar
|
||||
default_value: ~
|
||||
is_nullable: 0
|
||||
is_primary_key: 0
|
||||
is_unique: 1
|
||||
name: version
|
||||
order: 2
|
||||
size:
|
||||
- 50
|
||||
indices: []
|
||||
name: dbix_class_deploymenthandler_versions
|
||||
options: []
|
||||
order: 1
|
||||
triggers: {}
|
||||
views: {}
|
||||
translator:
|
||||
add_drop_table: 0
|
||||
filename: ~
|
||||
no_comments: 0
|
||||
parser_args:
|
||||
sources:
|
||||
- __VERSION
|
||||
parser_type: SQL::Translator::Parser::DBIx::Class
|
||||
producer_args: {}
|
||||
producer_type: SQL::Translator::Producer::YAML
|
||||
show_warnings: 0
|
||||
trace: 0
|
||||
version: 1.63
|
164
dbicdh/_source/deploy/0/001-auto.yml
Normal file
164
dbicdh/_source/deploy/0/001-auto.yml
Normal file
@ -0,0 +1,164 @@
|
||||
---
|
||||
schema:
|
||||
procedures: {}
|
||||
tables:
|
||||
users:
|
||||
constraints:
|
||||
- deferrable: 1
|
||||
expression: ''
|
||||
fields:
|
||||
- uuid
|
||||
match_type: ''
|
||||
name: ''
|
||||
on_delete: ''
|
||||
on_update: ''
|
||||
options: []
|
||||
reference_fields: []
|
||||
reference_table: ''
|
||||
type: PRIMARY KEY
|
||||
- deferrable: 1
|
||||
expression: ''
|
||||
fields:
|
||||
- email
|
||||
match_type: ''
|
||||
name: unique_constraint_email
|
||||
on_delete: ''
|
||||
on_update: ''
|
||||
options: []
|
||||
reference_fields: []
|
||||
reference_table: ''
|
||||
type: UNIQUE
|
||||
- deferrable: 1
|
||||
expression: ''
|
||||
fields:
|
||||
- username
|
||||
match_type: ''
|
||||
name: unique_constraint_username
|
||||
on_delete: ''
|
||||
on_update: ''
|
||||
options: []
|
||||
reference_fields: []
|
||||
reference_table: ''
|
||||
type: UNIQUE
|
||||
fields:
|
||||
email:
|
||||
data_type: text
|
||||
default_value: ~
|
||||
is_nullable: 0
|
||||
is_primary_key: 0
|
||||
is_unique: 1
|
||||
name: email
|
||||
order: 6
|
||||
size:
|
||||
- 0
|
||||
encrypted_password:
|
||||
data_type: text
|
||||
default_value: ~
|
||||
is_nullable: 0
|
||||
is_primary_key: 0
|
||||
is_unique: 0
|
||||
name: encrypted_password
|
||||
order: 5
|
||||
size:
|
||||
- 0
|
||||
last_activity:
|
||||
data_type: timestamp
|
||||
default_value: !!perl/ref
|
||||
=: NOW()
|
||||
is_nullable: 0
|
||||
is_primary_key: 0
|
||||
is_unique: 0
|
||||
name: last_activity
|
||||
order: 10
|
||||
size:
|
||||
- 0
|
||||
name:
|
||||
data_type: text
|
||||
default_value: ~
|
||||
is_nullable: 0
|
||||
is_primary_key: 0
|
||||
is_unique: 0
|
||||
name: name
|
||||
order: 3
|
||||
size:
|
||||
- 0
|
||||
register_date:
|
||||
data_type: timestamp
|
||||
default_value: !!perl/ref
|
||||
=: NOW()
|
||||
is_nullable: 0
|
||||
is_primary_key: 0
|
||||
is_unique: 0
|
||||
name: register_date
|
||||
order: 9
|
||||
size:
|
||||
- 0
|
||||
surname:
|
||||
data_type: text
|
||||
default_value: ~
|
||||
is_nullable: 0
|
||||
is_primary_key: 0
|
||||
is_unique: 0
|
||||
name: surname
|
||||
order: 4
|
||||
size:
|
||||
- 0
|
||||
username:
|
||||
data_type: text
|
||||
default_value: ~
|
||||
is_nullable: 0
|
||||
is_primary_key: 0
|
||||
is_unique: 1
|
||||
name: username
|
||||
order: 2
|
||||
size:
|
||||
- 0
|
||||
uuid:
|
||||
data_type: uuid
|
||||
default_value: ~
|
||||
is_nullable: 0
|
||||
is_primary_key: 1
|
||||
is_unique: 0
|
||||
name: uuid
|
||||
order: 1
|
||||
size:
|
||||
- 0
|
||||
verification_token:
|
||||
data_type: text
|
||||
default_value: ~
|
||||
is_nullable: 1
|
||||
is_primary_key: 0
|
||||
is_unique: 0
|
||||
name: verification_token
|
||||
order: 8
|
||||
size:
|
||||
- 0
|
||||
verified:
|
||||
data_type: boolean
|
||||
default_value: ~
|
||||
is_nullable: 0
|
||||
is_primary_key: 0
|
||||
is_unique: 0
|
||||
name: verified
|
||||
order: 7
|
||||
size:
|
||||
- 0
|
||||
indices: []
|
||||
name: users
|
||||
options: []
|
||||
order: 1
|
||||
triggers: {}
|
||||
views: {}
|
||||
translator:
|
||||
add_drop_table: 0
|
||||
filename: ~
|
||||
no_comments: 0
|
||||
parser_args:
|
||||
sources:
|
||||
- User
|
||||
parser_type: SQL::Translator::Parser::DBIx::Class
|
||||
producer_args: {}
|
||||
producer_type: SQL::Translator::Producer::YAML
|
||||
show_warnings: 0
|
||||
trace: 0
|
||||
version: 1.63
|
16
js-src/components/page.tsx
Normal file
16
js-src/components/page.tsx
Normal file
@ -0,0 +1,16 @@
|
||||
import * as React from 'react'
|
||||
import * as ReactRouter from 'react-router-dom'
|
||||
|
||||
const router = ReactRouter.createBrowserRouter([
|
||||
{
|
||||
path: '/',
|
||||
element: (<div>hello</div>)
|
||||
}
|
||||
])
|
||||
export default function Page (): JSX.Element {
|
||||
return (
|
||||
<React.StrictMode>
|
||||
<ReactRouter.RouterProvider router={router}/>
|
||||
</React.StrictMode>
|
||||
)
|
||||
}
|
16
js-src/index.tsx
Normal file
16
js-src/index.tsx
Normal file
@ -0,0 +1,16 @@
|
||||
'use strict'
|
||||
import * as React from 'react'
|
||||
import * as ReactDOMClient from 'react-dom/client'
|
||||
import Page from '@owlads/components/page'
|
||||
|
||||
import '@owlads/style.scss'
|
||||
|
||||
window.addEventListener('load', () => {
|
||||
const container = document.querySelector('div.app-container')
|
||||
console.log(container)
|
||||
if (container !== null) {
|
||||
const root = ReactDOMClient.createRoot(container)
|
||||
console.log('hola')
|
||||
root.render(<Page/>)
|
||||
}
|
||||
})
|
48
lib/OwlcodeAds.pm
Normal file
48
lib/OwlcodeAds.pm
Normal file
@ -0,0 +1,48 @@
|
||||
package OwlcodeAds;
|
||||
|
||||
use v5.36.0;
|
||||
use strict;
|
||||
use warnings;
|
||||
use utf8;
|
||||
|
||||
use Mojo::Base 'Mojolicious', -signatures;
|
||||
|
||||
# This method will run once at server start
|
||||
sub startup ($self) {
|
||||
my $sessions = Mojolicious::Sessions->new;
|
||||
$sessions->cookie_name('OwlcodeAds');
|
||||
$sessions->default_expiration(0);
|
||||
$self->sessions($sessions);
|
||||
|
||||
# Load configuration from config file
|
||||
my $config = $self->plugin('NotYAMLConfig');
|
||||
|
||||
# Configure the application
|
||||
$self->secrets( $config->{secrets} );
|
||||
|
||||
# Router
|
||||
my $r = $self->routes;
|
||||
|
||||
# Normal route to controller
|
||||
{
|
||||
my $api = $r->any('/api');
|
||||
{
|
||||
my $user = $api->any('/user');
|
||||
$user->post('/')->to('Register#register');
|
||||
}
|
||||
$api->any(
|
||||
'/*' => sub ($c) {
|
||||
$c->render(
|
||||
status => 404,
|
||||
json => {
|
||||
error => 'Enpoint not found.'
|
||||
}
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
$r->get('/*')->to('React#react');
|
||||
$r->get('/')->to('React#react');
|
||||
}
|
||||
|
||||
1;
|
16
lib/OwlcodeAds/Controller/React.pm
Normal file
16
lib/OwlcodeAds/Controller/React.pm
Normal file
@ -0,0 +1,16 @@
|
||||
package OwlcodeAds::Controller::React;
|
||||
|
||||
use v5.36.0;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
use utf8;
|
||||
|
||||
use Mojo::Base 'Mojolicious::Controller', -signatures;
|
||||
|
||||
use UUID::URandom qw/create_uuid_string/;
|
||||
|
||||
sub react ($self) {
|
||||
$self->render;
|
||||
}
|
||||
1;
|
185
lib/OwlcodeAds/Controller/Register.pm
Normal file
185
lib/OwlcodeAds/Controller/Register.pm
Normal file
@ -0,0 +1,185 @@
|
||||
package OwlcodeAds::Controller::Register;
|
||||
|
||||
use v5.36.0;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
use utf8;
|
||||
|
||||
use Mojo::Base 'Mojolicious::Controller', -signatures;
|
||||
|
||||
use UUID::URandom qw/create_uuid_string/;
|
||||
|
||||
sub register ($self) {
|
||||
my $params;
|
||||
eval { $params = $self->req->json; };
|
||||
if ($@) {
|
||||
return $self->render(
|
||||
status => 400,
|
||||
json => {
|
||||
error => 'The input is not a json.',
|
||||
}
|
||||
);
|
||||
}
|
||||
if ( ref $params ne 'HASH' ) {
|
||||
return $self->render(
|
||||
status => 400,
|
||||
json => {
|
||||
error => 'The input MUST be a json object.',
|
||||
}
|
||||
);
|
||||
}
|
||||
my ( $uuid, $username, $name, $surname, $password, $repeat_password, $email, $verified );
|
||||
eval {
|
||||
(
|
||||
$uuid, $username, $name, $surname, $password, $repeat_password,
|
||||
$email, $verified
|
||||
) = $self->_validate_or_die($params);
|
||||
};
|
||||
if ($@) {
|
||||
if ( $self->config->{debug} > 1 ) {
|
||||
|
||||
# It is pretty probable that this user error is very common and
|
||||
# not always will be needed when enabling debug
|
||||
# to see this errors, so we use the second level
|
||||
# of verbosity.
|
||||
say STDERR $@;
|
||||
}
|
||||
return;
|
||||
}
|
||||
$self->_register( $uuid, $username, $name, $surname, $password, $email,
|
||||
$verified );
|
||||
}
|
||||
|
||||
sub _validate_or_die ( $self, $params ) {
|
||||
require OwlcodeAds::Email;
|
||||
my $mailer = OwlcodeAds::Email->new;
|
||||
my $uuid = create_uuid_string();
|
||||
my $username = $self->_force_non_empty( $params, 'username' );
|
||||
my $name = $self->_force_non_empty( $params, 'name' );
|
||||
my $surname = $self->_force_non_empty( $params, 'surname' );
|
||||
my $password = $self->_force_valid_password( $params, 'password' );
|
||||
my $repeat_password =
|
||||
$self->_force_valid_password( $params, 'repeat_password' );
|
||||
my $email = $self->_force_valid_email( $params, 'email' );
|
||||
my $verified = $mailer->disabled ? 1 : 0;
|
||||
return ( $uuid, $username, $name, $surname, $password, $repeat_password,
|
||||
$email, $verified );
|
||||
}
|
||||
|
||||
sub _register (
|
||||
$self, $uuid, $username,
|
||||
$name, $surname, $password,
|
||||
$repeat_password, $email, $verified
|
||||
)
|
||||
{
|
||||
require OwlcodeAds::Schema;
|
||||
require OwlcodeAds::Email;
|
||||
|
||||
my $schema = OwlcodeAds::Schema->new;
|
||||
my $mailer = OwlcodeAds::Email->new;
|
||||
|
||||
my $resultset = $schema->resultset('User');
|
||||
my $user = $resultset->new(
|
||||
{
|
||||
uuid => $uuid,
|
||||
username => $username,
|
||||
name => $name,
|
||||
surname => $surname,
|
||||
password => $password,
|
||||
email => $email,
|
||||
verified => $verified,
|
||||
}
|
||||
);
|
||||
eval { $user->insert; };
|
||||
if ($@) {
|
||||
print STDERR $@;
|
||||
$self->render(
|
||||
status => 500,
|
||||
json => {
|
||||
error => 'Unable to create user (Server Error).'
|
||||
}
|
||||
);
|
||||
return;
|
||||
}
|
||||
$self->render(
|
||||
status => 200,
|
||||
json => $user->serialize,
|
||||
);
|
||||
}
|
||||
|
||||
sub _force_valid_email ( $self, $params, $key ) {
|
||||
my $email = $self->_force_non_empty( $params, $key );
|
||||
my ( $username, $domain ) = $email =~ /^(.*?)@(.*)$/;
|
||||
if ( !defined $username || length $username == 0 ) {
|
||||
$self->render(
|
||||
status => 400,
|
||||
json => {
|
||||
error => "$key has not the username part of the domain name.",
|
||||
}
|
||||
);
|
||||
die;
|
||||
}
|
||||
if ( !defined $domain || $domain !~ /\w\.\w/ ) {
|
||||
$self->render(
|
||||
status => 400,
|
||||
json => {
|
||||
error => "$key has not a valid hostname after @.",
|
||||
}
|
||||
);
|
||||
die;
|
||||
}
|
||||
if ( $domain =~ /@/ ) {
|
||||
$self->render(
|
||||
status => 400,
|
||||
json => {
|
||||
error =>
|
||||
"Found multiple @ in the email, refusing to take this mail."
|
||||
}
|
||||
);
|
||||
}
|
||||
return $email;
|
||||
}
|
||||
|
||||
sub _force_valid_password ( $self, $params, $key ) {
|
||||
my $password = $self->_force_non_empty( $params, $key );
|
||||
if ( length $password < 8 ) {
|
||||
$self->render(
|
||||
status => 400,
|
||||
json => {
|
||||
error => "$key should have at least 8 characters.",
|
||||
}
|
||||
);
|
||||
die;
|
||||
}
|
||||
return $password;
|
||||
}
|
||||
|
||||
sub _force_non_empty ( $self, $hash, $key ) {
|
||||
my $value = $self->_force_defined( $hash->{$key} );
|
||||
if ( length $value == 0 ) {
|
||||
$self->render(
|
||||
status => 400,
|
||||
json => {
|
||||
error => "$key must be non empty in json input.",
|
||||
},
|
||||
);
|
||||
die;
|
||||
}
|
||||
return $value;
|
||||
}
|
||||
|
||||
sub _force_defined ( $self, $hash, $key ) {
|
||||
my $value = $hash->{$key};
|
||||
if ( !defined $value ) {
|
||||
$self->render(
|
||||
status => 400,
|
||||
json => {
|
||||
error => "$key must be defined in json input.",
|
||||
}
|
||||
);
|
||||
die;
|
||||
}
|
||||
return $value;
|
||||
}
|
||||
1;
|
161
lib/OwlcodeAds/Email.pm
Normal file
161
lib/OwlcodeAds/Email.pm
Normal file
@ -0,0 +1,161 @@
|
||||
package OwlcodeAds::Email;
|
||||
|
||||
use v5.36.0;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
use utf8;
|
||||
|
||||
use feature 'signatures';
|
||||
|
||||
use Encode qw/decode/;
|
||||
|
||||
use Moo;
|
||||
|
||||
use Email::Sender::Transport::SMTP;
|
||||
use Email::Sender::Simple;
|
||||
use Email::MIME;
|
||||
|
||||
has app => ( is => 'lazy', );
|
||||
|
||||
has username => ( is => 'lazy', );
|
||||
|
||||
has host => ( is => 'lazy', );
|
||||
|
||||
has password => ( is => 'lazy' );
|
||||
|
||||
has config => ( is => 'lazy' );
|
||||
|
||||
has from => ( is => 'lazy' );
|
||||
|
||||
has disabled => ( is => 'lazy' );
|
||||
|
||||
sub _build_disabled ($self) {
|
||||
my $config = $self->config;
|
||||
return $config->{disabled} // 0;
|
||||
}
|
||||
|
||||
sub _build_app ($self) {
|
||||
require OwlcodeAds;
|
||||
return OwlcodeAds->new;
|
||||
}
|
||||
|
||||
sub _build_config ($self) {
|
||||
my $app = $self->app;
|
||||
my $config = $app->config->{email};
|
||||
if ( !defined $config || ref $config ne 'HASH' ) {
|
||||
die 'email key not found in global config, cannot send email.';
|
||||
}
|
||||
return $config;
|
||||
}
|
||||
|
||||
sub _build_username ($self) {
|
||||
my $config = $self->config;
|
||||
my $username = $config->{username};
|
||||
if ( !defined $username ) {
|
||||
die
|
||||
'email key does not have a username key in global config, cannot send email';
|
||||
}
|
||||
return $username;
|
||||
}
|
||||
|
||||
sub _build_password ($self) {
|
||||
my $config = $self->config;
|
||||
my $password = $config->{password};
|
||||
if ( !defined $password ) {
|
||||
die
|
||||
'email key does not have a password key in global config, cannot send email';
|
||||
}
|
||||
return $password;
|
||||
}
|
||||
|
||||
sub _build_host ($self) {
|
||||
my $config = $self->config;
|
||||
my $host = $config->{host};
|
||||
if ( !defined $host ) {
|
||||
die
|
||||
'email key does not have a host key in global config, cannot send email';
|
||||
}
|
||||
return $host;
|
||||
}
|
||||
|
||||
sub _build_from ($self) {
|
||||
my $config = $self->config;
|
||||
my $from = $config->{from};
|
||||
if ( !defined $from ) {
|
||||
die
|
||||
'email key does not have a from key in global config, cannot send email';
|
||||
}
|
||||
return $from;
|
||||
}
|
||||
|
||||
sub _generate_transport {
|
||||
my $self = shift;
|
||||
my $username = $self->username;
|
||||
my $password = $self->password;
|
||||
my $host = $self->host;
|
||||
my $port = $self->port;
|
||||
my $transport = Email::Sender::Transport::SMTP->new(
|
||||
hosts => [$host],
|
||||
ssl => 1,
|
||||
port => $port,
|
||||
sasl_username => $username,
|
||||
sasl_password => $password,
|
||||
);
|
||||
return $transport;
|
||||
}
|
||||
|
||||
sub email ( $self, $text, $to, $subject = 'Email sin título', $html = undef ) {
|
||||
my $username = $self->username;
|
||||
my @parts = (
|
||||
Email::MIME->create(
|
||||
attributes => {
|
||||
content_type => 'multipart/alternative',
|
||||
encoding => 'base64',
|
||||
},
|
||||
parts => [
|
||||
Email::MIME->create(
|
||||
attributes => {
|
||||
charset => 'UTF-8',
|
||||
content_type => 'text/plain',
|
||||
encoding => 'base64',
|
||||
disposition => 'inline',
|
||||
},
|
||||
body_str => decode( 'utf-8', $text ),
|
||||
),
|
||||
(
|
||||
( defined $html )
|
||||
? (
|
||||
Email::MIME->create(
|
||||
attributes => {
|
||||
charset => 'UTF-8',
|
||||
content_type => 'text/html',
|
||||
encoding => 'base64',
|
||||
disposition => 'inline',
|
||||
},
|
||||
body_str => decode( 'utf-8', $html ),
|
||||
)
|
||||
)
|
||||
: ()
|
||||
)
|
||||
]
|
||||
)
|
||||
);
|
||||
my $username = $self->username;
|
||||
my $config = $self->_config;
|
||||
my $email = Email::MIME->create(
|
||||
header_str => [
|
||||
From => decode('utf-8', $username),
|
||||
To => $to,
|
||||
Subject => decode('utf-8', $subject),
|
||||
],
|
||||
attributes => {
|
||||
encoding => 'base64',
|
||||
content_type => 'multipart/mixed'
|
||||
},
|
||||
parts => [@parts],
|
||||
);
|
||||
say Email::Sender::Simple->send( $email,
|
||||
{ transport => $self->_generate_transport } );
|
||||
}
|
||||
1;
|
60
lib/OwlcodeAds/Schema.pm
Normal file
60
lib/OwlcodeAds/Schema.pm
Normal file
@ -0,0 +1,60 @@
|
||||
package OwlcodeAds::Schema;
|
||||
|
||||
use v5.36.0;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
our $VERSION = 0;
|
||||
|
||||
use feature 'signatures';
|
||||
|
||||
|
||||
use parent 'DBIx::Class::Schema';
|
||||
|
||||
__PACKAGE__->load_namespaces();
|
||||
|
||||
my $schema;
|
||||
|
||||
sub Schema ($class) {
|
||||
if ( !defined $schema ) {
|
||||
require OwlcodeAds;
|
||||
my $app = OwlcodeAds->new;
|
||||
my $config = $app->{config};
|
||||
my $database_config = $config->{database};
|
||||
my $dbname = $database_config->{dbname};
|
||||
my $host = $database_config->{host};
|
||||
my $port = $database_config->{port};
|
||||
my $user = $database_config->{user};
|
||||
my $password = $database_config->{password};
|
||||
my $dsn = 'dbi:Pg:';
|
||||
|
||||
if ( !defined $dbname ) {
|
||||
die "The key database/dbname must be configured.";
|
||||
}
|
||||
$dsn .= "dbname=$dbname";
|
||||
if ( defined $host ) {
|
||||
$dsn .= ";host=$host";
|
||||
}
|
||||
if ( defined $port ) {
|
||||
$dsn .= ";port=$port";
|
||||
}
|
||||
|
||||
# Undef is perfectly fine for username and password.
|
||||
$schema = $class->connect(
|
||||
$dsn, $user,
|
||||
$password,
|
||||
{
|
||||
auto_savepoint => 1,
|
||||
Callbacks => {
|
||||
connected => sub {
|
||||
shift->do('set timezone = UTC');
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
return $schema;
|
||||
}
|
||||
1;
|
73
lib/OwlcodeAds/Schema/Result/User.pm
Normal file
73
lib/OwlcodeAds/Schema/Result/User.pm
Normal file
@ -0,0 +1,73 @@
|
||||
package OwlcodeAds::Schema::Result::User;
|
||||
|
||||
use v5.36.0;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use parent 'DBIx::Class::Core';
|
||||
|
||||
__PACKAGE__->table('users');
|
||||
|
||||
__PACKAGE__->add_columns(
|
||||
uuid => {
|
||||
data_type => 'uuid',
|
||||
is_nullable => 0,
|
||||
},
|
||||
username => {
|
||||
data_type => 'text',
|
||||
is_nullable => 0,
|
||||
},
|
||||
name => {
|
||||
data_type => 'text',
|
||||
is_nullable => 0,
|
||||
},
|
||||
surname => {
|
||||
data_type => 'text',
|
||||
is_nullable => 0,
|
||||
},
|
||||
encrypted_password => {
|
||||
data_type => 'text',
|
||||
is_nullable => 0,
|
||||
},
|
||||
email => {
|
||||
data_type => 'text',
|
||||
is_nullable => 0,
|
||||
},
|
||||
verified => {
|
||||
data_type => 'boolean',
|
||||
is_nullable => 0,
|
||||
},
|
||||
verification_token => {
|
||||
data_type => 'text',
|
||||
is_nullable => 1,
|
||||
},
|
||||
register_date => {
|
||||
data_type => 'timestamp',
|
||||
is_nullable => 0,
|
||||
default_value => \'NOW()',
|
||||
},
|
||||
last_activity => {
|
||||
data_type => 'timestamp',
|
||||
is_nullable => 0,
|
||||
default_value => \'NOW()',
|
||||
},
|
||||
);
|
||||
|
||||
sub serialize ($self) {
|
||||
return {
|
||||
uuid => $self->uuid,
|
||||
username => $self->username,
|
||||
name => $self->name,
|
||||
surname => $self->surname,
|
||||
email => $self->email,
|
||||
verified => $self->verified,
|
||||
register_date => $self->register_date,
|
||||
last_activity => $self->last_activity,
|
||||
};
|
||||
}
|
||||
__PACKAGE__->set_primary_key('uuid');
|
||||
__PACKAGE__->add_unique_constraint( "unique_constraint_username",
|
||||
['username'] );
|
||||
__PACKAGE__->add_unique_constraint( "unique_constraint_email", ["email"] );
|
||||
1;
|
19
owlcode_ads.example.yml
Normal file
19
owlcode_ads.example.yml
Normal file
@ -0,0 +1,19 @@
|
||||
---
|
||||
secrets:
|
||||
- change_me_for_a_proper_secret_generated_with_pwgen
|
||||
database:
|
||||
dbname: owlcode_ads
|
||||
host: localhost
|
||||
port: 5432
|
||||
user: lastres
|
||||
password: topsecret
|
||||
hypnotoad:
|
||||
listen:
|
||||
- http://*:3000
|
||||
email:
|
||||
disabled: 0
|
||||
host: example.com
|
||||
username: example@example.com
|
||||
password: topsecret
|
||||
from: I am a example, change me <example@example.com>
|
||||
debug: 0
|
49
package.json
Normal file
49
package.json
Normal file
@ -0,0 +1,49 @@
|
||||
{
|
||||
"name": "OwlcodeAds",
|
||||
"version": "0.1.1",
|
||||
"description": "",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@babel/preset-react": "^7.18.6",
|
||||
"@types/react": "^18.0.28",
|
||||
"@types/react-dom": "^18.0.11",
|
||||
"@typescript-eslint/eslint-plugin": "^5.57.0",
|
||||
"babel-loader": "^9.1.2",
|
||||
"css-loader": "^6.8.1",
|
||||
"css-minimizer-webpack-plugin": "^5.0.1",
|
||||
"eslint": "^8.36.0",
|
||||
"eslint-config-standard-with-typescript": "^34.0.1",
|
||||
"eslint-import-resolver-typescript": "^3.5.3",
|
||||
"eslint-plugin-import": "^2.27.5",
|
||||
"eslint-plugin-n": "^15.6.1",
|
||||
"eslint-plugin-no-relative-import-paths": "^1.5.2",
|
||||
"eslint-plugin-promise": "^6.1.1",
|
||||
"eslint-plugin-react": "^7.32.2",
|
||||
"file-loader": "^6.2.0",
|
||||
"mini-css-extract-plugin": "^2.7.6",
|
||||
"sass": "^1.64.2",
|
||||
"sass-loader": "^13.3.2",
|
||||
"style-loader": "^3.3.3",
|
||||
"ts-loader": "^9.4.2",
|
||||
"typescript": "^5.0.2",
|
||||
"typescript-transform-paths": "^3.4.6",
|
||||
"url-loader": "^4.1.1",
|
||||
"webpack": "^5.38.1",
|
||||
"webpack-cli": "^4.7.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"@reduxjs/toolkit": "^1.9.5",
|
||||
"babel-preset-react": "^6.24.1",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-redux": "^8.1.2",
|
||||
"react-router": "^6.14.2",
|
||||
"react-router-dom": "^6.14.2"
|
||||
}
|
||||
}
|
3
public/js/main.bundle.js
Normal file
3
public/js/main.bundle.js
Normal file
File diff suppressed because one or more lines are too long
62
public/js/main.bundle.js.LICENSE.txt
Normal file
62
public/js/main.bundle.js.LICENSE.txt
Normal file
@ -0,0 +1,62 @@
|
||||
/**
|
||||
* @license React
|
||||
* react-dom.production.min.js
|
||||
*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @license React
|
||||
* react.production.min.js
|
||||
*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @license React
|
||||
* scheduler.production.min.js
|
||||
*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @remix-run/router v1.7.2
|
||||
*
|
||||
* Copyright (c) Remix Software Inc.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE.md file in the root directory of this source tree.
|
||||
*
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
/**
|
||||
* React Router DOM v6.14.2
|
||||
*
|
||||
* Copyright (c) Remix Software Inc.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE.md file in the root directory of this source tree.
|
||||
*
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
/**
|
||||
* React Router v6.14.2
|
||||
*
|
||||
* Copyright (c) Remix Software Inc.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE.md file in the root directory of this source tree.
|
||||
*
|
||||
* @license MIT
|
||||
*/
|
30
script/install.pl
Normal file
30
script/install.pl
Normal file
@ -0,0 +1,30 @@
|
||||
#!/usr/bin/env perl
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
use aliased 'DBIx::Class::DeploymentHandler' => 'DH';
|
||||
use Getopt::Long;
|
||||
use FindBin;
|
||||
use lib "$FindBin::Bin/../lib";
|
||||
use OwlcodeAds::Schema;
|
||||
|
||||
my $force_overwrite = 0;
|
||||
|
||||
unless ( GetOptions( 'force_overwrite!' => \$force_overwrite ) ) {
|
||||
die "Invalid options";
|
||||
}
|
||||
|
||||
my $schema = OwlcodeAds::Schema->Schema;
|
||||
|
||||
my $dh = DH->new(
|
||||
{
|
||||
schema => $schema,
|
||||
script_directory => "$FindBin::Bin/../dbicdh",
|
||||
databases => 'PostgreSQL',
|
||||
sql_translator_args => { add_drop_table => 0 },
|
||||
force_overwrite => $force_overwrite,
|
||||
}
|
||||
);
|
||||
|
||||
$dh->install({version => 0});
|
||||
$dh->upgrade;
|
11
script/owlcode_ads
Executable file
11
script/owlcode_ads
Executable file
@ -0,0 +1,11 @@
|
||||
#!/usr/bin/env perl
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use Mojo::File qw(curfile);
|
||||
use lib curfile->dirname->sibling('lib')->to_string;
|
||||
use Mojolicious::Commands;
|
||||
|
||||
# Start command line interface for application
|
||||
Mojolicious::Commands->start_app('OwlcodeAds');
|
47
script/prepare.pl
Normal file
47
script/prepare.pl
Normal file
@ -0,0 +1,47 @@
|
||||
#!/usr/bin/env perl
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
use aliased 'DBIx::Class::DeploymentHandler' => 'DH';
|
||||
use Getopt::Long;
|
||||
use FindBin;
|
||||
use lib "$FindBin::Bin/../lib";
|
||||
use OwlcodeAds::Schema;
|
||||
|
||||
my $force_overwrite = 1;
|
||||
|
||||
unless ( GetOptions( 'force_overwrite!' => \$force_overwrite ) ) {
|
||||
die "Invalid options";
|
||||
}
|
||||
|
||||
my $schema = OwlcodeAds::Schema->Schema;
|
||||
|
||||
my $dh = DH->new(
|
||||
{
|
||||
schema => $schema,
|
||||
script_directory => "$FindBin::Bin/../../dbicdh",
|
||||
databases => 'PostgreSQL',
|
||||
sql_translator_args => { add_drop_table => 0 },
|
||||
force_overwrite => $force_overwrite,
|
||||
}
|
||||
);
|
||||
|
||||
$dh->prepare_deploy;
|
||||
eval {
|
||||
$dh->prepare_upgrade(
|
||||
{
|
||||
(
|
||||
( $OwlcodeAds::Schema::VERSION > 0 )
|
||||
? (
|
||||
from_version => $OwlcodeAds::Schema::VERSION - 1,
|
||||
to_version => $OwlcodeAds::Schema::VERSION
|
||||
)
|
||||
: ()
|
||||
),
|
||||
}
|
||||
);
|
||||
};
|
||||
if ($@) {
|
||||
print "$@\n";
|
||||
$dh->prepare_install;
|
||||
}
|
29
script/upgrade.pl
Normal file
29
script/upgrade.pl
Normal file
@ -0,0 +1,29 @@
|
||||
#!/usr/bin/env perl
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
use aliased 'DBIx::Class::DeploymentHandler' => 'DH';
|
||||
use Getopt::Long;
|
||||
use FindBin;
|
||||
use lib "$FindBin::Bin/../lib";
|
||||
use OwlcodeAds::Schema;
|
||||
|
||||
my $force_overwrite = 1;
|
||||
|
||||
unless ( GetOptions( 'force_overwrite!' => \$force_overwrite ) ) {
|
||||
die "Invalid options";
|
||||
}
|
||||
|
||||
my $schema = OwlcodeAds::Schema->Schema;
|
||||
|
||||
my $dh = DH->new(
|
||||
{
|
||||
schema => $schema,
|
||||
script_directory => "$FindBin::Bin/../dbicdh",
|
||||
databases => 'PostgreSQL',
|
||||
sql_translator_args => { add_drop_table => 0 },
|
||||
force_overwrite => $force_overwrite,
|
||||
}
|
||||
);
|
||||
|
||||
$dh->upgrade;
|
12
templates/react/react.html.ep
Normal file
12
templates/react/react.html.ep
Normal file
@ -0,0 +1,12 @@
|
||||
<html>
|
||||
<head>
|
||||
<script src="/js/main.bundle.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>
|
||||
<p>Activa javascript para usar esta web.</p>
|
||||
</noscript>
|
||||
<div class="app-container">
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
30
tsconfig.json
Normal file
30
tsconfig.json
Normal file
@ -0,0 +1,30 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"outDir": "./public/js/",
|
||||
"noImplicitAny": true,
|
||||
"module": "es2020",
|
||||
"target": "es2020",
|
||||
"jsx": "react",
|
||||
"allowJs": true,
|
||||
"moduleResolution": "node",
|
||||
"strictNullChecks": true,
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@owlads/*": ["js-src/*"],
|
||||
"/*": ["js-src/*"]
|
||||
},
|
||||
"plugins": [
|
||||
{
|
||||
"transform": "typescript-transform-paths"
|
||||
},
|
||||
{
|
||||
"transform": "typescript-transform-paths",
|
||||
"afterDeclarations": true
|
||||
}
|
||||
]
|
||||
},
|
||||
"include": [
|
||||
"js-src/*.ts", "js-src/*/*.ts",
|
||||
"js-src/*.tsx", "js-src/*/*.tsx"
|
||||
]
|
||||
}
|
68
webpack.config.js
Normal file
68
webpack.config.js
Normal file
@ -0,0 +1,68 @@
|
||||
const path = require('path')
|
||||
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
|
||||
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin')
|
||||
|
||||
module.exports = {
|
||||
entry: './js-src/index.tsx',
|
||||
mode: 'production',
|
||||
devtool: 'source-map',
|
||||
plugins: [
|
||||
new MiniCssExtractPlugin({
|
||||
filename: '[name].css',
|
||||
chunkFilename: '[id].css'
|
||||
})
|
||||
],
|
||||
output: {
|
||||
filename: '[name].bundle.js',
|
||||
path: path.resolve(__dirname, 'public/js/'),
|
||||
libraryTarget: 'umd',
|
||||
library: 'main',
|
||||
umdNamedDefine: true
|
||||
},
|
||||
resolve: {
|
||||
extensions: ['.js', '.jsx', '.ts', '.tsx'],
|
||||
roots: [
|
||||
path.resolve(__dirname, 'js-src/')
|
||||
],
|
||||
alias: {
|
||||
'@owlads': path.resolve(__dirname, 'js-src')
|
||||
}
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.s[ac]ss$/i,
|
||||
use: [
|
||||
'style-loader',
|
||||
'css-loader',
|
||||
'sass-loader'
|
||||
]
|
||||
},
|
||||
{
|
||||
test: /\.css$/i,
|
||||
use: [MiniCssExtractPlugin.loader, 'css-loader']
|
||||
},
|
||||
{
|
||||
test: /\.tsx?$/,
|
||||
use: 'ts-loader',
|
||||
exclude: /node_modules/
|
||||
},
|
||||
{
|
||||
test: /\.jpe?g|png$/,
|
||||
exclude: /node_modules/,
|
||||
use: ['url-loader', 'file-loader']
|
||||
},
|
||||
{
|
||||
test: /\.(js|jsx)$/,
|
||||
exclude: /node_modules/,
|
||||
loader: 'babel-loader'
|
||||
}
|
||||
]
|
||||
},
|
||||
optimization: {
|
||||
minimizer: [
|
||||
'...',
|
||||
new CssMinimizerPlugin()
|
||||
],
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user