From 942c02cd9a056de2bc79a5badad91f7b97ad2800 Mon Sep 17 00:00:00 2001
From: kaiyou <pierre@jaury.eu>
Date: Tue, 31 Mar 2020 15:02:40 +0200
Subject: [PATCH] Improve the profile and service cli

---
 hiboo/profile/cli.py      | 93 ++++++++++++++++++++++++++++++++-------
 hiboo/service/__init__.py |  2 +-
 hiboo/service/cli.py      | 16 +++++++
 requirements-prod.txt     |  1 +
 requirements.txt          |  1 +
 5 files changed, 97 insertions(+), 16 deletions(-)
 create mode 100644 hiboo/service/cli.py

diff --git a/hiboo/profile/cli.py b/hiboo/profile/cli.py
index a23e051a..318595cb 100644
--- a/hiboo/profile/cli.py
+++ b/hiboo/profile/cli.py
@@ -2,28 +2,91 @@ from hiboo.profile import blueprint
 from hiboo import models
 
 import click
+import terminaltables
+import csv
+
+
+@blueprint.cli.command()
+@click.argument("service_uuid")
+@click.option("-s", "--status")
+@click.option("-u", "--username")
+def list(service_uuid, status=None, username=None, ):
+    """ Select and list profiles.
+    """
+    criteria = [models.Profile.service_uuid == service_uuid]
+    if status is not None:
+        criteria.append(models.Profile.status == status)
+    if username is not None:
+        criteria.append(models.Profile.username.ilike('%{}%'.format(username)))
+    click.echo(terminaltables.SingleTable([('uuid', 'username', 'status', 'claim_names')]+
+    [
+        [profile.uuid, profile.username, profile.status, ','.join([item.username for item in profile.claimnames])]
+        for profile in models.Profile.query.filter(*criteria).all()
+    ], title='Profiles').table)
 
 
 @blueprint.cli.command()
 @click.argument("service_uuid")
 @click.argument("username")
-@click.argument("password_hash")
 @click.argument("claim_names", nargs=-1)
-def unclaimed(service_uuid, username, password_hash, claim_names):
+@click.option("-p", "--password-hash", "password_hash")
+def unclaimed(service_uuid, username, claim_names, password_hash=None):
+    """ This creates or updates an unclaimed profile.
+    """
     assert models.Service.query.get(service_uuid)
-    assert not models.Profile.query.filter_by(service_uuid=service_uuid, username=username).first()
-    profile = models.Profile(
-        service_uuid=service_uuid,
-        username=username,
-        status=models.Profile.UNCLAIMED,
-        extra={"password": password_hash}
-    )
-    models.db.session.add(profile)
-    for username in claim_names:
-        claim_name = models.ClaimName(
+    profile = models.Profile.query.filter_by(service_uuid=service_uuid, username=username).first()
+    # Do nothing if the profile is already claimed
+    if profile and not profile.status == models.Profile.UNCLAIMED:
+        click.echo("Profile {} was claimed already".format(username))
+        return
+    # Create the profile if necessary
+    if profile:
+        click.echo("Profile {} exists already".format(username))
+    else:
+        profile = models.Profile(
             service_uuid=service_uuid,
-            profile=profile,
-            username=username
+            username=username,
+            status=models.Profile.UNCLAIMED,
         )
-        models.db.session.add(claim_name)
+        click.echo("Profile {} was created".format(username))
+        models.db.session.add(profile)
+    # Update the profile password if one was provided
+    if password_hash is not None:  
+        profile.extra={"password": password_hash}
+        click.echo("Password updated for profile {}".format(username))
+    # Update profile claim names if any is provided (never delete an existing claim name)
+    for username in claim_names:
+        claim_name = models.ClaimName.query.filter_by(service_uuid=service_uuid, username=username).first()
+        if claim_name and not claim_name.profile == profile:
+            click.echo("Claim name conflict for {}, already belongs to {}".format(username, claim_name.profile.username))
+        elif claim_name:
+            click.echo("Claim {} already exists for {}".format(username, profile.username))
+        else:
+            claim_name = models.ClaimName(
+                service_uuid=service_uuid,
+                profile=profile,
+                username=username
+            )
+            click.echo("Claim created for {}, belongs to {}".format(username, profile.username))
+            models.db.session.add(claim_name)
     models.db.session.commit()
+
+
+@blueprint.cli.command()
+@click.argument("service_uuid")
+@click.argument("filename")
+def csv_unclaimed(service_uuid, filename="/dev/stdin"):
+    """ Import unclaimed profiles from a CSV.
+
+    The first two columns must be the username and password, additional
+    columns will be treated as additional claim names.
+
+    Duplicates will be taken care of and updated, unless the profile has alredy
+    been claimed.
+    """
+    assert models.Service.query.get(service_uuid)
+    with open(filename, "r") as csvfile:
+        reader = csv.reader(csvfile, delimiter=',', quotechar='"')
+        for row in reader:
+            unclaimed.callback(service_uuid, row[0], row[2:], row[1])
+
diff --git a/hiboo/service/__init__.py b/hiboo/service/__init__.py
index 6332441f..92e48805 100644
--- a/hiboo/service/__init__.py
+++ b/hiboo/service/__init__.py
@@ -3,4 +3,4 @@ import flask
 
 blueprint = flask.Blueprint("service", __name__, template_folder="templates")
 
-from hiboo.service import admin
+from hiboo.service import admin, cli
diff --git a/hiboo/service/cli.py b/hiboo/service/cli.py
new file mode 100644
index 00000000..45d9b2b0
--- /dev/null
+++ b/hiboo/service/cli.py
@@ -0,0 +1,16 @@
+from hiboo.service import blueprint
+from hiboo import models
+
+import click
+import terminaltables
+
+
+@blueprint.cli.command()
+def list():
+    """ List all services.
+    """
+    click.echo(terminaltables.SingleTable([('uuid', 'name', 'application')]+
+    [
+        [service.uuid, service.name, service.application_id]
+        for service in models.Service.query.all()
+    ], title='Services').table)
\ No newline at end of file
diff --git a/requirements-prod.txt b/requirements-prod.txt
index 3a499fe5..d546aad5 100644
--- a/requirements-prod.txt
+++ b/requirements-prod.txt
@@ -48,6 +48,7 @@ redis==3.3.11
 requests==2.22.0
 six==1.13.0
 SQLAlchemy==1.3.11
+terminaltables==3.1.0
 urllib3==1.25.7
 validators==0.14.0
 Werkzeug==0.16.0
diff --git a/requirements.txt b/requirements.txt
index a2e40bd2..a7a855b2 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -21,3 +21,4 @@ mysqlclient
 psycopg2
 jwcrypto
 argon2_cffi
+terminaltables
-- 
GitLab