[mb-commits] branch, data-nes, updated. Allow relating a work to a URL without attributes

MusicBrainz Git Server <[email protected]> Fri, 01 Feb 2013 10:42:45 +0000
Newsgroups gmane.comp.audio.musicbrainz.cvs
Message-ID <E1U1E4z-0003Mw-8u@wiley>
The branch, data-nes has been updated
       via  http://git.musicbrainz.org/gitweb/?p=musicbrainz-server/core.git;a=commit;h=3626afd6a63585587e98af2190aec9bd73fb4f12 (commit)
       via  http://git.musicbrainz.org/gitweb/?p=musicbrainz-server/core.git;a=commit;h=84179a1e05b74639ab8e6c7d8ff7ba0d7f901554 (commit)
      from  http://git.musicbrainz.org/gitweb/?p=musicbrainz-server/core.git;a=commit;h=2bdc4e841f65b5608d88952dc8df4a3ba64a7405 (commit)

Summary of changes:
 lib/MusicBrainz/Server/Context.pm                  |    9 ++
 .../Server/Controller/Edit/Relationship.pm         |  127 +++++++++++--------
 lib/MusicBrainz/Server/Controller/Role/Alias.pm    |   34 +++---
 .../Server/Controller/Role/Annotation.pm           |    6 +-
 lib/MusicBrainz/Server/Controller/Role/Cleanup.pm  |    4 +-
 lib/MusicBrainz/Server/Controller/Role/Load.pm     |    4 +-
 lib/MusicBrainz/Server/Controller/Work.pm          |   12 +-
 lib/MusicBrainz/Server/Data/NES/CoreEntity.pm      |   65 ++++++++++
 lib/MusicBrainz/Server/Data/NES/URL.pm             |   44 +++++++
 lib/MusicBrainz/Server/Data/NES/Work.pm            |   89 ++++----------
 lib/MusicBrainz/Server/Data/Role/NES.pm            |   37 +------
 lib/MusicBrainz/Server/Data/Utils.pm               |    2 +-
 lib/MusicBrainz/Server/Entity/NES/Edit.pm          |    8 ++
 lib/MusicBrainz/Server/Entity/NES/Relationship.pm  |   14 ++
 lib/MusicBrainz/Server/Entity/Tree/URL.pm          |   22 ++++
 lib/MusicBrainz/Server/Entity/Tree/Work.pm         |   31 +++++
 lib/MusicBrainz/Server/Model/MB.pm                 |   10 ++-
 lib/MusicBrainz/Server/NES.pm                      |   77 ++++++++++++
 lib/MusicBrainz/Server/NES/Controller/Utils.pm     |   52 ++++----
 root/components/common-macros.tt                   |    2 +
 20 files changed, 442 insertions(+), 207 deletions(-)
 create mode 100644 lib/MusicBrainz/Server/Data/NES/CoreEntity.pm
 create mode 100644 lib/MusicBrainz/Server/Data/NES/URL.pm
 create mode 100644 lib/MusicBrainz/Server/Entity/NES/Edit.pm
 create mode 100644 lib/MusicBrainz/Server/Entity/NES/Relationship.pm
 create mode 100644 lib/MusicBrainz/Server/Entity/Tree/URL.pm
 create mode 100644 lib/MusicBrainz/Server/NES.pm

Those revisions listed above that are new to this repository have
not appeared on any other notification email; so we list those
revisions in full, below.

- Log -----------------------------------------------------------------
commit 3626afd6a63585587e98af2190aec9bd73fb4f12
Author: Oliver Charles <[email protected]>
Date:   Fri Feb 1 12:02:14 2013 +0000

    Allow relating a work to a URL without attributes

diff --git a/lib/MusicBrainz/Server/Controller/Edit/Relationship.pm b/lib/MusicBrainz/Server/Controller/Edit/Relationship.pm
index 44da34c..dec899b 100644
--- a/lib/MusicBrainz/Server/Controller/Edit/Relationship.pm
+++ b/lib/MusicBrainz/Server/Controller/Edit/Relationship.pm
@@ -5,6 +5,8 @@ BEGIN { extends 'MusicBrainz::Server::Controller' };
 
 use MusicBrainz::Server::Constants qw( $EDIT_RELATIONSHIP_DELETE );
 use MusicBrainz::Server::Data::Utils qw( type_to_model );
+use MusicBrainz::Server::Entity::NES::Relationship;
+use MusicBrainz::Server::Entity::Tree::Work;
 use MusicBrainz::Server::Edit::Relationship::Delete;
 use MusicBrainz::Server::Edit::Relationship::Edit;
 use MusicBrainz::Server::Translation qw( l ln );
@@ -293,69 +295,86 @@ sub create_url : Local RequireAuth Edit
         $c->detach('/error_500');
     }
 
-    my $entity = $model->get_by_gid($gid);
-    unless (defined $entity) {
-        $c->stash( message => l('Entity not found') );
-        $c->detach('/error_404');
-    }
+    $c->model('MB')->with_nes_transaction(sub {
+        my $entity = $model->get_by_gid($gid);
+        unless (defined $entity) {
+            $c->stash( message => l('Entity not found') );
+            $c->detach('/error_404');
+        }
 
-    my $tree = $c->model('LinkType')->get_tree(@types);
-    my %type_info = build_type_info($tree);
+        my $tree = $c->model('LinkType')->get_tree(@types);
+        my %type_info = build_type_info($tree);
+
+        if (!%type_info) {
+            $c->stash(
+                template => 'edit/relationship/cannot_create.tt',
+                type0 => $types[0],
+                type1 => $types[1]
+            );
+            $c->detach;
+        }
 
-    if (!%type_info) {
         $c->stash(
-            template => 'edit/relationship/cannot_create.tt',
-            type0 => $types[0],
-            type1 => $types[1]
+            root      => $tree,
+            type_info => JSON->new->latin1->encode(\%type_info),
         );
-        $c->detach;
-    }
 
-    $c->stash(
-        root      => $tree,
-        type_info => JSON->new->latin1->encode(\%type_info),
-    );
+        my $attr_tree = $c->model('LinkAttributeType')->get_tree;
+        $c->stash( attr_tree => $attr_tree );
+        $self->attr_tree($attr_tree);
 
-    my $attr_tree = $c->model('LinkAttributeType')->get_tree;
-    $c->stash( attr_tree => $attr_tree );
-    $self->attr_tree($attr_tree);
-
-    my $form = $c->form(
-        form => 'Relationship::URL',
-        reverse => $types[0] eq 'url',
-        root => $tree,
-        attr_tree => $attr_tree
-    );
-
-    $c->stash(
-        entity => $entity,
-        type => $type,
-    );
+        my $form = $c->form(
+            form => 'Relationship::URL',
+            reverse => $types[0] eq 'url',
+            root => $tree,
+            attr_tree => $attr_tree
+        );
 
-    if ($c->form_posted && $form->submitted_and_valid($c->req->params)) {
-        my @attributes = $self->flatten_attributes($form->field('attrs'));
-        my $url = $c->model('URL')->find_or_insert($form->field('url')->value);
+        $c->stash(
+            entity => $entity,
+            type => $type,
+        );
 
-        my $e0 = $types[0] eq 'url' ? $url : $entity;
-        my $e1 = $types[1] eq 'url' ? $url : $entity;
+        if ($c->form_posted && $form->submitted_and_valid($c->req->params)) {
+            my $edit = $c->model('NES::Edit')->open;
+
+            my @attributes = $self->flatten_attributes($form->field('attrs'));
+            my $url = $c->model('NES::URL')->find_or_insert($edit, $c->user, $form->field('url')->value);
+
+            $c->model('NES::Work')->update(
+                $edit, $c->user, $entity,
+                MusicBrainz::Server::Entity::Tree::Work->new(
+                    relationships => [
+                        MusicBrainz::Server::Entity::NES::Relationship->new(
+                            link_type_id => $form->field('link_type_id')->value,
+                            target => $url
+                        )
+                    ]
+                )
+            );
 
-        $c->stash( url => $form->field('url')->value );
-        $c->model('MB')->with_transaction(sub {
-            $self->try_and_insert(
-                $c, $form,
-                @types,
-                entity0 => $e0,
-                entity1 => $e1,
-                link_type_id => $form->field('link_type_id')->value,
-                attributes => \@attributes,
-                ended => 0
-            ) or $self->detach_existing($c);
-        });
+            # my $e0 = $types[0] eq 'url' ? $url : $entity;
+            # my $e1 = $types[1] eq 'url' ? $url : $entity;
+
+            # $c->stash( url => $form->field('url')->value );
+            # $c->model('MB')->with_transaction(sub {
+            #     $self->try_and_insert(
+            #         $c, $form,
+            #         @types,
+            #         entity0 => $e0,
+            #         entity1 => $e1,
+            #         link_type_id => $form->field('link_type_id')->value,
+            #         attributes => \@attributes,
+            #         ended => 0
+            #     ) or $self->detach_existing($c);
+            # });
+
+            # my $redirect = $c->controller(type_to_model($type))->action_for('show');
+            # $c->response->redirect($c->uri_for_action($redirect, [ $gid ]));
+            # $c->detach;
 
-        my $redirect = $c->controller(type_to_model($type))->action_for('show');
-        $c->response->redirect($c->uri_for_action($redirect, [ $gid ]));
-        $c->detach;
-    }
+        }
+    });
 }
 
 sub delete : Local RequireAuth Edit
diff --git a/lib/MusicBrainz/Server/Controller/Role/Annotation.pm b/lib/MusicBrainz/Server/Controller/Role/Annotation.pm
index 3d566b2..57732d8 100644
--- a/lib/MusicBrainz/Server/Controller/Role/Annotation.pm
+++ b/lib/MusicBrainz/Server/Controller/Role/Annotation.pm
@@ -125,7 +125,7 @@ sub edit_annotation : Chained('load') PathPart RequireAuth Edit
         else
         {
             run_update_form(
-                $self, $c, $form,
+                $self->{model}, $c, $form,
                 build_tree => sub {
                     my $values = shift;
 
diff --git a/lib/MusicBrainz/Server/Data/NES/CoreEntity.pm b/lib/MusicBrainz/Server/Data/NES/CoreEntity.pm
new file mode 100644
index 0000000..9df8568
--- /dev/null
+++ b/lib/MusicBrainz/Server/Data/NES/CoreEntity.pm
@@ -0,0 +1,65 @@
+package MusicBrainz::Server::Data::NES::CoreEntity;
+use MooseX::Role::Parameterized;
+
+parameter 'root' => (
+    isa => 'Str',
+    required => 1
+);
+
+role {
+    my $params = shift;
+
+    requires 'tree_to_json', 'map_core_entity';
+
+    method create => sub {
+        my ($self, $edit, $editor, $tree) = @_;
+
+        my $response = $self->request($params->root . '/create', {
+            edit => $edit->id,
+            editor => $editor->id,
+            $self->tree_to_json($tree)
+        });
+
+        return $self->get_revision($response->{ref})
+    };
+
+    method update => sub {
+        my ($self, $edit, $editor, $base_revision, $tree) = @_;
+
+        die 'Need a base revision' unless $base_revision;
+
+        my $final_tree = $tree->complete
+            ? $tree
+            : $self->view_tree($base_revision)->merge($tree);
+
+        my $response = $self->request($params->root . '/update', {
+            edit => $edit->id,
+            editor => $editor->id,
+            revision => $base_revision->revision_id,
+            $self->tree_to_json($final_tree)
+        });
+
+        return undef;
+    };
+
+    method get_revision => sub {
+        my ($self, $revision_id) = @_;
+        return $self->_new_from_core_entity(
+            $self->request($params->root . '/view-revision', { revision => $revision_id }));
+    };
+
+    method get_by_gid => sub {
+        my ($self, $gid) = @_;
+        return $self->_new_from_core_entity(
+            $self->request('/work/find-latest', { mbid => $gid }))
+    };
+
+    method _new_from_core_entity => sub {
+        my ($self, $response) = @_;
+        return keys %$response == 0
+            ? undef
+            : $self->map_core_entity($response);
+    };
+};
+
+1;
diff --git a/lib/MusicBrainz/Server/Data/NES/URL.pm b/lib/MusicBrainz/Server/Data/NES/URL.pm
new file mode 100644
index 0000000..e8a8fad
--- /dev/null
+++ b/lib/MusicBrainz/Server/Data/NES/URL.pm
@@ -0,0 +1,44 @@
+package MusicBrainz::Server::Data::NES::URL;
+use Moose;
+use namespace::autoclean;
+
+use MusicBrainz::Server::Entity::Tree::URL;
+use MusicBrainz::Server::Entity::URL;
+
+with 'MusicBrainz::Server::Data::Role::NES';
+with 'MusicBrainz::Server::Data::NES::CoreEntity' => {
+    root => '/url'
+};
+
+sub find_or_insert {
+    my ($self, $edit, $editor, $url) = @_;
+    $self->create(
+        $edit, $editor,
+        MusicBrainz::Server::Entity::Tree::URL->new(
+            url => MusicBrainz::Server::Entity::URL->new(
+                url => $url
+            )
+        )
+    );
+}
+
+sub tree_to_json {
+    my ($self, $tree) = @_;
+
+    return (
+        url => $tree->url->url->as_string
+    );
+}
+
+sub map_core_entity {
+    my ($self, $response) = @_;
+    my %data = %{ $response->{data} };
+    return MusicBrainz::Server::Entity::URL->new(
+        url => $data{url},
+
+        gid => $response->{mbid},
+        revision_id => $response->{revision}
+    );
+}
+
+1;
diff --git a/lib/MusicBrainz/Server/Data/NES/Work.pm b/lib/MusicBrainz/Server/Data/NES/Work.pm
index 7205f63..aae8f8a 100644
--- a/lib/MusicBrainz/Server/Data/NES/Work.pm
+++ b/lib/MusicBrainz/Server/Data/NES/Work.pm
@@ -6,59 +6,18 @@ use MusicBrainz::Server::Entity::Work;
 use MusicBrainz::Server::WebService::Serializer::JSON::2::Utils qw( boolean );
 
 with 'MusicBrainz::Server::Data::Role::NES';
+with 'MusicBrainz::Server::Data::NES::CoreEntity' => {
+    root => '/work'
+};
 
-sub create {
-    my ($self, $edit, $editor, $tree) = @_;
+around create => sub {
+    my ($orig, $self, $edit, $editor, $tree) = @_;
 
     $tree->annotation('') unless $tree->annotation_set;
     $tree->aliases([]) unless $tree->aliases_set;
 
-    my $response = $self->request('/work/create', {
-        edit => $edit->id,
-        editor => $editor->id,
-        _work_tree($tree)
-    });
-
-    return $self->get_revision($response->{ref});
-}
-
-sub update {
-    my ($self, $edit, $editor, $base_revision, $tree) = @_;
-
-    die 'Need a base revision' unless $base_revision;
-
-    my $final_tree = do {
-        if( $tree->work_set && $tree->aliases_set && $tree->iswcs_set ) {
-            $tree
-        }
-        else {
-            my $original_tree = $self->view_tree($base_revision);
-
-            $original_tree->work($tree->work)
-                if ($tree->work_set);
-
-            $original_tree->aliases($tree->aliases)
-                if ($tree->aliases_set);
-
-            $original_tree->iswcs($tree->iswcs)
-                if ($tree->iswcs_set);
-
-            $original_tree->annotation($tree->annotation)
-                if ($tree->annotation_set);
-
-            $original_tree;
-        }
-    };
-
-    my $response = $self->request('/work/update', {
-        edit => $edit->id,
-        editor => $editor->id,
-        revision => $base_revision->revision_id,
-        _work_tree($final_tree)
-    });
-
-    return undef;
-}
+    $self->$orig($edit, $editor, $tree);
+};
 
 sub view_tree {
     my ($self, $revision) = @_;
@@ -71,8 +30,8 @@ sub view_tree {
     );
 }
 
-sub _work_tree {
-    my $tree = shift;
+sub tree_to_json {
+    my ($self, $tree) = @_;
 
     return (
         work => do {
@@ -99,26 +58,22 @@ sub _work_tree {
                 type => $_->type_id,
                 locale => $_->locale
             }, @{ $tree->aliases }
-        ]
+        ],
+        relationships => {
+            url => [
+                map +{
+                    target => $_->target->gid,
+                    type => $_->link_type_id
+                }, grep {
+                    $_->target->isa('MusicBrainz::Server::Entity::URL')
+                } @{ $tree->relationships }
+            ]
+        }
     );
 }
 
-sub get_revision {
-    my ($self, $revision_id) = @_;
-    return _new_from_response(
-        $self->request('/work/view-revision', { revision => $revision_id }));
-}
-
-sub get_by_gid {
-    my ($self, $gid) = @_;
-    return _new_from_response(
-        $self->request('/work/find-latest', { mbid => $gid }))
-}
-
-sub _new_from_response {
-    my ($response) = @_;
-    return undef if keys %$response == 0;
-
+sub map_core_entity {
+    my ($self, $response) = @_;
     my %data = %{ $response->{data} };
     return MusicBrainz::Server::Entity::Work->new(
         name => $data{name},
diff --git a/lib/MusicBrainz/Server/Data/Utils.pm b/lib/MusicBrainz/Server/Data/Utils.pm
index f02b703..308b121 100644
--- a/lib/MusicBrainz/Server/Data/Utils.pm
+++ b/lib/MusicBrainz/Server/Data/Utils.pm
@@ -63,7 +63,7 @@ Readonly my %TYPE_TO_MODEL => (
     'release'       => 'Release',
     'release_group' => 'ReleaseGroup',
     'url'           => 'URL',
-    'work'          => 'Work',
+    'work'          => 'NES::Work',
 );
 
 sub copy_escape {
diff --git a/lib/MusicBrainz/Server/Entity/NES/Edit.pm b/lib/MusicBrainz/Server/Entity/NES/Edit.pm
new file mode 100644
index 0000000..465e4dc
--- /dev/null
+++ b/lib/MusicBrainz/Server/Entity/NES/Edit.pm
@@ -0,0 +1,8 @@
+package MusicBrainz::Server::Entity::NES::Edit;
+use Moose;
+
+has id => (
+    is => 'ro',
+);
+
+1;
diff --git a/lib/MusicBrainz/Server/Entity/NES/Relationship.pm b/lib/MusicBrainz/Server/Entity/NES/Relationship.pm
new file mode 100644
index 0000000..a16a8a2
--- /dev/null
+++ b/lib/MusicBrainz/Server/Entity/NES/Relationship.pm
@@ -0,0 +1,14 @@
+package MusicBrainz::Server::Entity::NES::Relationship;
+use Moose;
+
+has target => (
+    is => 'ro',
+    required => 1
+);
+
+has link_type_id => (
+    is => 'ro',
+    required => 1
+);
+
+1;
diff --git a/lib/MusicBrainz/Server/Entity/Tree/URL.pm b/lib/MusicBrainz/Server/Entity/Tree/URL.pm
new file mode 100644
index 0000000..87a6836
--- /dev/null
+++ b/lib/MusicBrainz/Server/Entity/Tree/URL.pm
@@ -0,0 +1,22 @@
+package MusicBrainz::Server::Entity::Tree::URL;
+use Moose;
+
+has url => (
+    is => 'rw',
+    predicate => 'url_set',
+);
+
+sub merge {
+    my ($self, $tree) = @_;
+
+    die 'Undefined';
+
+    return $self;
+}
+
+sub complete {
+    my $tree = shift;
+    die 'Undefined';
+}
+
+1;
diff --git a/lib/MusicBrainz/Server/Entity/Tree/Work.pm b/lib/MusicBrainz/Server/Entity/Tree/Work.pm
index 8d4c4f7..d71270e 100644
--- a/lib/MusicBrainz/Server/Entity/Tree/Work.pm
+++ b/lib/MusicBrainz/Server/Entity/Tree/Work.pm
@@ -21,4 +21,35 @@ has annotation => (
     predicate => 'annotation_set'
 );
 
+has relationships => (
+    is => 'rw',
+    predicate => 'relationships_set'
+);
+
+sub merge {
+    my ($self, $tree) = @_;
+
+    $self->work($tree->work)
+        if ($tree->work_set);
+
+    $self->aliases($tree->aliases)
+        if ($tree->aliases_set);
+
+    $self->iswcs($tree->iswcs)
+        if ($tree->iswcs_set);
+
+    $self->annotation($tree->annotation)
+        if ($tree->annotation_set);
+
+    $self->relationships($tree->relationships)
+        if ($tree->relationships_set);
+
+    return $self;
+}
+
+sub complete {
+    my $tree = shift;
+    return $tree->work_set && $tree->aliases_set && $tree->iswcs_set;
+}
+
 1;
diff --git a/lib/MusicBrainz/Server/Model/MB.pm b/lib/MusicBrainz/Server/Model/MB.pm
index db9f34a..3a90e48 100644
--- a/lib/MusicBrainz/Server/Model/MB.pm
+++ b/lib/MusicBrainz/Server/Model/MB.pm
@@ -6,6 +6,7 @@ extends 'Catalyst::Model';
 use DBDefs;
 use Module::Pluggable::Object;
 use MusicBrainz::Server::Context;
+use Moose::Util qw( find_meta );
 
 has 'context' => (
     isa        => 'MusicBrainz::Server::Context',
@@ -56,7 +57,9 @@ sub models {
     );
 
     for my $model (sort $searcher->plugins) {
-        next if $model =~ /Data::Role/;
+        Class::MOP::load_class($model);
+        my $meta = find_meta($model);
+        next if $model =~ /Data::Role/ || !defined($meta) || $meta->isa('Moose::Meta::Role');
         my ($model_name) = ($model =~ m/.*::Data::(.*)/);
         $model =~ s/^MusicBrainz::Server::Data:://;
 
diff --git a/lib/MusicBrainz/Server/NES.pm b/lib/MusicBrainz/Server/NES.pm
index 80d8401..3226f08 100644
--- a/lib/MusicBrainz/Server/NES.pm
+++ b/lib/MusicBrainz/Server/NES.pm
@@ -61,11 +61,16 @@ sub with_transaction {
     $self->session_token($self->request('/open-session', {})->{token});
 
     return try {
-        return $code->();
+        my $ret = $code->();
+        $self->request('/close-session', {});
+
+        return $ret;
     }
-    finally {
+    catch {
         try { $self->request('/close-session', {}) };
         $self->clear_session_token;
+
+        die $_;
     };
 }
 
diff --git a/lib/MusicBrainz/Server/NES/Controller/Utils.pm b/lib/MusicBrainz/Server/NES/Controller/Utils.pm
index 6f295c7..f7aa71c 100644
--- a/lib/MusicBrainz/Server/NES/Controller/Utils.pm
+++ b/lib/MusicBrainz/Server/NES/Controller/Utils.pm
@@ -38,17 +38,17 @@ sub run_edit_form {
 }
 
 sub run_update_form {
-    my ($controller, $c, $form, %opts) = @_;
+    my ($model, $c, $form, %opts) = @_;
 
     run_edit_form(
         $c, $form,
         on_post => sub {
             my ($values, $edit) = @_;
 
-            my $revision = $c->model( $controller->{model} )->get_revision(
+            my $revision = $c->model( $model )->get_revision(
                 $values->{revision_id});
 
-            $c->model( $controller->{model} )->update(
+            $c->model( $model )->update(
                 $edit, $c->user, $revision,
                 $opts{build_tree}->($values, $revision)
             );
@@ -104,7 +104,7 @@ sub create_update {
         %opts,
         callback => sub {
             my $form = shift;
-            run_update_form($controller, $c, $form, %opts);
+            run_update_form($controller->{model}, $c, $form, %opts);
         }
     );
 }
diff --git a/root/components/common-macros.tt b/root/components/common-macros.tt
index ee84d23..a26cf6f 100644
--- a/root/components/common-macros.tt
+++ b/root/components/common-macros.tt
@@ -170,6 +170,8 @@ END -%]
             l('release groups');
         CASE 'editor';
             l('editors');
+        CASE 'url';
+            l('urls');
         CASE 'work';
             l('works');
         CASE DEFAULT;

commit 84179a1e05b74639ab8e6c7d8ff7ba0d7f901554
Author: Oliver Charles <[email protected]>
Date:   Tue Jan 29 16:41:05 2013 +0000

    Wrap all NES calls in an NES session

diff --git a/lib/MusicBrainz/Server/Context.pm b/lib/MusicBrainz/Server/Context.pm
index a97c6b0..f3e811a 100644
--- a/lib/MusicBrainz/Server/Context.pm
+++ b/lib/MusicBrainz/Server/Context.pm
@@ -4,6 +4,7 @@ use Moose;
 use DBDefs;
 use MusicBrainz::Server::Replication ':replication_type';
 use MusicBrainz::Server::CacheManager;
+use MusicBrainz::Server::NES;
 use aliased 'MusicBrainz::Server::DatabaseConnectionFactory';
 use Class::MOP;
 use LWP::UserAgent;
@@ -47,6 +48,14 @@ has lwp => (
     }
 );
 
+has nes => (
+    is => 'ro',
+    lazy => 1,
+    default => sub {
+        return MusicBrainz::Server::NES->new( lwp => shift->lwp )
+    }
+);
+
 has data_prefix => (
     isa => 'Str',
     is => 'ro',
diff --git a/lib/MusicBrainz/Server/Controller/Role/Alias.pm b/lib/MusicBrainz/Server/Controller/Role/Alias.pm
index 25258e9..e870d46 100644
--- a/lib/MusicBrainz/Server/Controller/Role/Alias.pm
+++ b/lib/MusicBrainz/Server/Controller/Role/Alias.pm
@@ -27,12 +27,14 @@ sub aliases : Chained('load') PathPart('aliases')
     my $entity = $c->stash->{entity};
     my $m = $self->{model};
 
-    my $aliases = $c->model($m)->get_aliases($entity);
-    alias_type_model($c, $m)->load(@$aliases);
-
-    $c->stash(
-        aliases => $aliases,
-    );
+    $c->model('MB')->with_nes_transaction(sub {
+        my $aliases = $c->model($m)->get_aliases($entity);
+        alias_type_model($c, $m)->load(@$aliases);
+
+        $c->stash(
+            aliases => $aliases,
+        );
+    });
 }
 
 sub alias : Chained('load') PathPart('alias') CaptureArgs(0)
@@ -41,15 +43,17 @@ sub alias : Chained('load') PathPart('alias') CaptureArgs(0)
 
     my $qp = $c->req->query_params;
 
-    my $all_aliases = $c->model( $self->{model} )->get_aliases($c->stash->{entity});
-    my ($alias) = grep {
-        $_->name eq $qp->{name}
-    } @$all_aliases or $c->detach('/error_404');
-
-    $c->stash(
-        alias => $alias,
-        all_aliases => $all_aliases
-    );
+    $c->model('MB')->with_nes_transaction(sub {
+        my $all_aliases = $c->model( $self->{model} )->get_aliases($c->stash->{entity});
+        my ($alias) = grep {
+            $_->name eq $qp->{name}
+        } @$all_aliases or $c->detach('/error_404');
+
+        $c->stash(
+            alias => $alias,
+            all_aliases => $all_aliases
+        )
+    });
 }
 
 sub add_alias : Chained('load') PathPart('add-alias') RequireAuth Edit
diff --git a/lib/MusicBrainz/Server/Controller/Role/Annotation.pm b/lib/MusicBrainz/Server/Controller/Role/Annotation.pm
index 6f05b2b..3d566b2 100644
--- a/lib/MusicBrainz/Server/Controller/Role/Annotation.pm
+++ b/lib/MusicBrainz/Server/Controller/Role/Annotation.pm
@@ -25,7 +25,9 @@ after 'load' => sub
     my $entity = $c->stash->{entity};
     my $model = $self->{model};
 
-    $c->model($model)->load_annotation($entity);
+    $c->model('MB')->with_nes_transaction(sub {
+        $c->model($model)->load_annotation($entity);
+    });
 };
 
 sub latest_annotation : Chained('load') PathPart('annotation')
diff --git a/lib/MusicBrainz/Server/Controller/Role/Cleanup.pm b/lib/MusicBrainz/Server/Controller/Role/Cleanup.pm
index 924c886..7f9bdeb 100644
--- a/lib/MusicBrainz/Server/Controller/Role/Cleanup.pm
+++ b/lib/MusicBrainz/Server/Controller/Role/Cleanup.pm
@@ -6,7 +6,9 @@ after show => sub {
     my ($self, $c) = @_;
     my $entity = $c->stash->{entity};
     $c->stash(
-        eligible_for_cleanup => $c->model( $self->config->{model} )->is_empty($entity)
+        eligible_for_cleanup => $c->model('MB')->with_nes_transaction(sub {
+            $c->model( $self->config->{model} )->is_empty($entity)
+        })
     )
 };
 
diff --git a/lib/MusicBrainz/Server/Controller/Role/Load.pm b/lib/MusicBrainz/Server/Controller/Role/Load.pm
index 928d86b..461f122 100644
--- a/lib/MusicBrainz/Server/Controller/Role/Load.pm
+++ b/lib/MusicBrainz/Server/Controller/Role/Load.pm
@@ -58,7 +58,9 @@ role
         my ($self, $c, $id) = @_;
 
         if (is_guid($id)) {
-            return $c->model($model)->get_by_gid($id);
+            return $c->model('MB')->with_nes_transaction(sub {
+                $c->model($model)->get_by_gid($id)
+            });
         }
         else {
             # This will detach for us
diff --git a/lib/MusicBrainz/Server/Controller/Work.pm b/lib/MusicBrainz/Server/Controller/Work.pm
index 5769cca..8d38260 100644
--- a/lib/MusicBrainz/Server/Controller/Work.pm
+++ b/lib/MusicBrainz/Server/Controller/Work.pm
@@ -40,11 +40,13 @@ after 'load' => sub
     my ($self, $c) = @_;
 
     my $work = $c->stash->{work};
-    # $c->model('Work')->load_meta($work);
-    $c->model('NES::ISWC')->load_for_works($work);
-    if ($c->user_exists) {
-        $c->model('Work')->rating->load_user_ratings($c->user->id, $work);
-    }
+    $c->model('MB')->with_nes_transaction(sub {
+        # $c->model('Work')->load_meta($work);
+        $c->model('NES::ISWC')->load_for_works($work);
+        if ($c->user_exists) {
+            $c->model('Work')->rating->load_user_ratings($c->user->id, $work);
+        }
+    });
 };
 
 sub show : PathPart('') Chained('load')
diff --git a/lib/MusicBrainz/Server/Data/Role/NES.pm b/lib/MusicBrainz/Server/Data/Role/NES.pm
index 17e9e8d..259da87 100644
--- a/lib/MusicBrainz/Server/Data/Role/NES.pm
+++ b/lib/MusicBrainz/Server/Data/Role/NES.pm
@@ -3,42 +3,9 @@ use Moose::Role;
 
 with 'MusicBrainz::Server::Data::Role::Context';
 
-use Data::Dumper::Concise qw( Dumper );
-use Devel::Dwarn;
-use Encode;
-use JSON;
-use Try::Tiny;
-use Time::HiRes qw( gettimeofday tv_interval );
-
 sub request {
-    my ($self, $path, $body) = @_;
-
-    my $uri = DBDefs->DATA_ACCESS_SERVICE.$path;
-    my $content = to_json ($body, { canonical => 1 });
-
-    printf STDERR "> Request: $uri\n";
-    printf STDERR Dumper($body), "\n";
-
-    my $t0 = [ gettimeofday ];
-    my $response = $self->c->lwp->post($uri, Content => encode('utf8', $content));
-    my $t = tv_interval($t0);
-
-    printf STDERR "Response in ${t}s\n";
-    printf STDERR $response->content;
-
-    if (!$response->is_success) {
-        printf STDERR "FAILURE!\n";
-        die 'Failed request: ' . $response->content;;
-    }
-
-    return try {
-        printf STDERR "\n\n";
-
-        return decode_json($response->content);
-    }
-    catch {
-        die 'Failed to decode response: ' . $response->content;
-    }
+    my $self = shift;
+    return $self->c->nes->request(@_);
 }
 
 1;
diff --git a/lib/MusicBrainz/Server/Model/MB.pm b/lib/MusicBrainz/Server/Model/MB.pm
index 26ef183..db9f34a 100644
--- a/lib/MusicBrainz/Server/Model/MB.pm
+++ b/lib/MusicBrainz/Server/Model/MB.pm
@@ -19,6 +19,11 @@ sub with_transaction {
     Sql::run_in_transaction($code, $self->context->sql);
 }
 
+sub with_nes_transaction {
+    my ($self, $code) = @_;
+    $self->context->nes->with_transaction($code);
+}
+
 sub _build_context {
     my $self = shift;
 
diff --git a/lib/MusicBrainz/Server/NES.pm b/lib/MusicBrainz/Server/NES.pm
new file mode 100644
index 0000000..80d8401
--- /dev/null
+++ b/lib/MusicBrainz/Server/NES.pm
@@ -0,0 +1,72 @@
+package MusicBrainz::Server::NES;
+use Moose;
+
+use Data::Dumper::Concise qw( Dumper );
+use Devel::Dwarn;
+use Encode;
+use JSON;
+use Try::Tiny;
+use Time::HiRes qw( gettimeofday tv_interval );
+
+has lwp => (
+    is => 'ro',
+    required => 1
+);
+
+has session_token => (
+    is => 'rw',
+    clearer => 'clear_session_token',
+    predicate => 'in_session'
+);
+
+sub request {
+    my ($self, $path, $body) = @_;
+
+    my $uri = DBDefs->DATA_ACCESS_SERVICE.$path;
+    my $content = to_json ($body, { canonical => 1 });
+
+    printf STDERR "> Request: $uri\n";
+    printf STDERR Dumper($body), "\n";
+
+    my @headers;
+    push @headers, ('MB-Session' => $self->session_token)
+        if $self->in_session && $self->session_token;
+
+    my $t0 = [ gettimeofday ];
+    my $response = $self->lwp->post($uri, @headers, Content => encode('utf8', $content));
+    my $t = tv_interval($t0);
+
+    printf STDERR "Response in ${t}s\n";
+    printf STDERR $response->content;
+
+    if (!$response->is_success) {
+        printf STDERR "FAILURE!\n";
+        die "Failed request '$path': " . $response->content;;
+    }
+
+    return try {
+        printf STDERR "\n\n";
+
+        return decode_json($response->content);
+    }
+    catch {
+        die 'Failed to decode response: ' . $response->content;
+    }
+}
+
+sub with_transaction {
+    my ($self, $code) = @_;
+
+    $self->clear_session_token;
+    $self->session_token($self->request('/open-session', {})->{token});
+
+    return try {
+        return $code->();
+    }
+    finally {
+        try { $self->request('/close-session', {}) };
+        $self->clear_session_token;
+    };
+}
+
+1;
diff --git a/lib/MusicBrainz/Server/NES/Controller/Utils.pm b/lib/MusicBrainz/Server/NES/Controller/Utils.pm
index 708c098..6f295c7 100644
--- a/lib/MusicBrainz/Server/NES/Controller/Utils.pm
+++ b/lib/MusicBrainz/Server/NES/Controller/Utils.pm
@@ -10,29 +10,31 @@ use Sub::Exporter -setup => {
 sub run_edit_form {
     my ($c, $form, %opts) = @_;
 
-    my $values = $form->values;
-    my $edit = $c->model('NES::Edit')->open;
-
-    my $work = $opts{on_post}->($values, $edit);
-
-    if ($values->{edit_note}) {
-        $c->model('EditNote')->add_note(
-            $edit->id,
-            {
-                editor_id => $c->user->id,
-                text => $values->{edit_note}
-            }
-        );
-    }
+    return $c->model('MB')->with_nes_transaction(sub {
+        my $values = $form->values;
+        my $edit = $c->model('NES::Edit')->open;
+
+        my $work = $opts{on_post}->($values, $edit);
+
+        if ($values->{edit_note}) {
+            $c->model('EditNote')->add_note(
+                $edit->id,
+                {
+                    editor_id => $c->user->id,
+                    text => $values->{edit_note}
+                }
+            );
+        }
 
-    # NES:
-    # my $privs = $c->user->privileges;
-    # if ($c->user->is_auto_editor &&
-    #     $form->field('as_auto_editor') &&
-    #     !$form->field('as_auto_editor')->value) {
-    # }
+        # NES:
+        # my $privs = $c->user->privileges;
+        # if ($c->user->is_auto_editor &&
+        #     $form->field('as_auto_editor') &&
+        #     !$form->field('as_auto_editor')->value) {
+        # }
 
-    return $work;
+        return $work;
+    });
 }
 
 sub run_update_form {

-----------------------------------------------------------------------


hooks/post-receive
-- 
mb_server