holivia 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +14 -0
- data/example_content.xml +52 -0
- data/examples/compose.json +58 -0
- data/exe/holivia +4 -0
- data/lib/holivia/cli/help.rb +84 -0
- data/lib/holivia/cli.rb +10 -21
- data/lib/holivia/client.rb +7 -1
- data/lib/holivia/commands/auth.rb +25 -6
- data/lib/holivia/commands/base.rb +28 -0
- data/lib/holivia/commands/env.rb +90 -0
- data/lib/holivia/commands/format.rb +49 -0
- data/lib/holivia/commands/item.rb +107 -0
- data/lib/holivia/commands/selfcare.rb +88 -5
- data/lib/holivia/commands/slide.rb +51 -0
- data/lib/holivia/config_error.rb +5 -0
- data/lib/holivia/configuration.rb +63 -5
- data/lib/holivia/env_manager.rb +46 -0
- data/lib/holivia/version.rb +1 -1
- data/lib/holivia.rb +6 -4
- metadata +25 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 6e2d692d4098918b86e1fb733a303c51b8bde37583841b2f97f11bf58b2da880
|
|
4
|
+
data.tar.gz: 14095f6175eb6fdae474eeb6b31c0b4c856a675da375461d8d5a0748c27ec022
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: a881a29e44e585bee272a5fe5def48a57b089ca81be00c9ee9d54da355ecd8273c4befbedd98e26d77a978baa0a37b5b12d2a2f1af03afac10519e85e291053f
|
|
7
|
+
data.tar.gz: 1478b0f331188b5b5043f01ab4e80643b173f69bb04adeefeba87d37a28ceda194d66fb7c701034298ad2257d6afae38131990b760829c0ba332ab586425ff1a
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
## [Unreleased]
|
|
2
2
|
|
|
3
|
+
## [0.3.0] - 2026-03-11
|
|
4
|
+
|
|
5
|
+
- Add multi-environment support with `~/.holivia/config.yml`
|
|
6
|
+
- Add `holivia env` commands (show, list, add, use, remove, debug)
|
|
7
|
+
- Add `holivia version` command
|
|
8
|
+
- Add scriptable login with `--email`/`--password` flags
|
|
9
|
+
- Add `ConfigError` for friendly CLI error messages
|
|
10
|
+
- Add `update_env` for toggling debug per environment
|
|
11
|
+
- Add selfcare CRUD commands (show, create, update, compose, schema)
|
|
12
|
+
- Add content format, slide, and slide item commands
|
|
13
|
+
- Allow removing current env with auto-switch to remaining
|
|
14
|
+
- Update compose example to omit audio items (uploaded separately)
|
|
15
|
+
- Update help with workflow for audio uploads and all new commands
|
|
16
|
+
|
|
3
17
|
## [0.2.0] - 2026-03-09
|
|
4
18
|
|
|
5
19
|
- Add logout command and `Client#delete` method
|
data/example_content.xml
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
<slides>
|
|
2
|
+
<slide title="Les relations, un déterminant vital de la santé" duration="1">
|
|
3
|
+
<slideItem type="richText">
|
|
4
|
+
Imaginez une consultation médicale tout à fait banale. On vous interroge sur le tabac, l'activité physique, la tension artérielle, l'alimentation… Et puis, soudain, une question inattendue : « Et vos relations, en ce moment, elles ressemblent à quoi ? » Sur le papier, cela paraît presque déplacé. Dans la vie réelle, c'est souvent l'inverse : nous traitons la qualité de nos liens comme un "bonus" — précieux, certes, mais secondaire. Or, une vaste synthèse scientifique invite à changer radicalement de perspective : les relations sociales ne relèvent pas seulement du confort psychologique. Elles pèsent sur la survie elle-même.
|
|
5
|
+
</slideItem>
|
|
6
|
+
</slide>
|
|
7
|
+
<slide title="Quand le lien social devient un "facteur de survie"" duration="1">
|
|
8
|
+
<slideItem type="richText">
|
|
9
|
+
Dans une vaste synthèse d'études scientifiques qui suivent des personnes dans le temps, Holt-Lunstad et ses collègues montrent que les individus disposant de relations sociales plus solides ont tendance à vivre plus longtemps que ceux dont les liens sont plus faibles. Autrement dit, à l'échelle d'une vie, la présence de liens sociaux robustes s'associe à un avantage comparable à celui de facteurs de santé classiquement scrutés.<br/><br/>Les auteurs vont plus loin : ils soulignent que l'importance des relations "rivalise" avec des risques bien connus (comme certains facteurs de mode de vie et biomédicaux) — au point de recommander que la question des liens sociaux soit prise au sérieux dans une perspective de santé publique.<br/><br/>Une précision essentielle : cet effet n'est pas cantonné à un sous-groupe fragile. Il apparaît robuste à travers l'âge, le sexe, le statut de santé initial (population générale vs patients), et différents types de mortalité. Ce que cela suggère, en filigrane : le lien social n'est pas seulement une ressource "quand tout va mal", mais une dimension continue de notre écologie de santé.
|
|
10
|
+
</slideItem>
|
|
11
|
+
</slide>
|
|
12
|
+
<slide title=""Avoir des relations", cela veut dire quoi, exactement ?" duration="1">
|
|
13
|
+
<slideItem type="richText">
|
|
14
|
+
L'un des apports les plus utiles de l'article, pour penser concrètement nos vies, est de montrer que "les relations" ne se résument pas à une variable unique. Les auteurs distinguent deux grandes dimensions :<br/><br/>1) La dimension structurelle : à quel point vous êtes "inséré" dans un tissu social<br/><ul><li>vivre seul ou non,</li><li>être marié ou non,</li><li>taille et densité du réseau,</li><li>participation à des activités, rôles sociaux (ami, collègue, voisin, membre d'un groupe),</li><li>indices composites d'intégration sociale (qui combinent plusieurs éléments).</li></ul><br/>2) La dimension fonctionnelle : ce que ces relations "font" pour vous<br/><ul><li>le soutien reçu (aide concrète, émotionnelle, informationnelle),</li><li>le soutien perçu (la conviction que l'aide serait disponible si nécessaire),</li><li>la solitude (se sentir isolé, ne pas appartenir).</li></ul><br/>Et voici une nuance psychologiquement très parlante : ces dimensions ne se recouvrent que partiellement. On peut être "bien entouré" en apparence et se sentir seul ; ou avoir peu de liens mais les vivre comme profondément fiables et soutenants.
|
|
15
|
+
</slideItem>
|
|
16
|
+
</slide>
|
|
17
|
+
<slide title="Ce qui protège le plus : l'intégration sociale… et l'expérience subjective" duration="1">
|
|
18
|
+
<slideItem type="richText">
|
|
19
|
+
Quand on regarde plus finement quelles mesures prédisent le mieux la survie, un résultat se détache : la profondeur et la diversité de l'intégration sociale semblent particulièrement puissantes.<br/><br/>Mais l'autre message fort est que l'expérience subjective compte énormément :<br/><ul><li>le soutien perçu est protecteur</li><li>une moindre solitude apparaît fortement associée à la survie</li><li>tandis que des indicateurs très simples (ex. "vivre seul ou non") sont moins prédictifs</li></ul><br/>Un point particulièrement mobilisateur : les auteurs nuancent l'idée d'un simple "seuil" (les très isolés iraient mal, les autres seraient "OK"). Ils décrivent plutôt un gradient : chaque pas vers davantage d'intégration, de soutien perçu, et moins de solitude s'associe, en moyenne, à un gain incrémental. Dit autrement : en matière de liens, <b>les petits ajustements peuvent compter</b>.
|
|
20
|
+
</slideItem>
|
|
21
|
+
</slide>
|
|
22
|
+
<slide title="Comment les relations influencent-elles la santé "par en dessous" ?" duration="1">
|
|
23
|
+
<slideItem type="richText">
|
|
24
|
+
L'article discute plusieurs voies explicatives — et c'est ici que le lien social cesse d'être abstrait.<br/><br/>1) Les voies comportementales<br/>Les relations façonnent nos habitudes : elles peuvent encourager ou modéliser des comportements de santé (activité physique, alimentation, suivi médical, adhérence aux traitements), via des normes implicites et une régulation quotidienne ("on se motive", "on veille", "on se rappelle"). Les auteurs notent aussi que l'environnement social peut renforcer des habitudes moins favorables, même si ce n'est pas le cœur de la vaste synthèse d'études scientifiques.<br/><br/>2) Les voies psychologiques<br/>Deux grands modèles sont mis en avant :<br/><ul><li>le effet tampon face au stress : en période d'épreuve (maladie, transition, deuil, crise), le soutien relationnel modifie l'évaluation cognitive de la situation et amortit les réponses émotionnelles ;</li><li>le modèle des effets généraux : même hors crise, être relié à des autres structure l'identité, apporte des rôles, du sens, de l'estime de soi, et organise la vie quotidienne de manière plus contenante.</li></ul><br/>3) Les voies biologiques<br/>Les auteurs évoquent des liens entre soutien social et paramètres physiologiques (fonction immunitaire, inflammation, régulation neuroendocrinienne du stress). Ils rappellent aussi que des interactions relationnelles négatives (hostilité conjugale, climat tendu) ont été associées à des marqueurs défavorables (inflammation, cicatrisation plus lente), illustrant la façon dont la qualité relationnelle peut "passer dans le corps".
|
|
25
|
+
</slideItem>
|
|
26
|
+
</slide>
|
|
27
|
+
<slide title="L'angle mort qui change tout : la qualité des liens" duration="1">
|
|
28
|
+
<slideItem type="richText">
|
|
29
|
+
La vaste synthèse d'études scientifiques montre un effet global : "plus de relations" s'associe à une meilleure survie. Mais les auteurs insistent sur une limite majeure : beaucoup de mesures ne captent pas la qualité (par exemple, "être marié" ne dit rien d'un mariage soutenant ou corrosif). Ils rappellent que les relations négatives sont liées à davantage de risque, et suggèrent que les bénéfices observés pourraient même être sous-estimés si l'on distinguait mieux les liens protecteurs des liens toxiques.<br/><br/>Message psychoéducatif clé : augmenter le contact ne suffit pas. L'enjeu est d'augmenter la <b>fiabilité</b>, la <b>chaleur</b>, la <b>réciprocité</b> — et, parfois, de réduire l'exposition à des interactions chroniquement délétères.
|
|
30
|
+
</slideItem>
|
|
31
|
+
</slide>
|
|
32
|
+
<slide title="Votre "bilan relationnel santé"" duration="1">
|
|
33
|
+
<slideItem type="richText">
|
|
34
|
+
Prenez une feuille. Dessinez trois colonnes : <b>Structure</b> / <b>Fonction</b> / <b>Climat</b>.<br/><br/>1) Structure : votre intégration (0–2 points par item)<br/><ul><li>Ai-je au moins deux rôles sociaux vivants en dehors du travail (ami, voisin, groupe, association, activité) ?</li><li>Est-ce que je participe à au moins une activité régulière (même modeste) qui me met en contact ?</li><li>Ai-je des liens dans plus d'un contexte (famille et amis, ou amis et collectif, etc.) ?</li></ul><br/>2) Fonction : le soutien perçu et la solitude (0–2 points)<br/><ul><li>Si j'avais un pépin, est-ce que je sais qui je pourrais appeler ?</li><li>Est-ce que je me sens "appartenir" à au moins un petit cercle où je compte ?</li><li>Est-ce que je vis souvent une <b>solitude douloureuse</b>, même entouré (inversez le score : 2 = rarement, 0 = souvent) ?</li></ul><br/>3) Climat : la qualité (0–2 points)<br/><ul><li>Mes relations clés sont-elles plutôt une source de sécurité que de tension ?</li><li>Est-ce que je peux être moi-même sans marcher sur des œufs ?</li><li>Ai-je une relation où la réciprocité est réelle (je donne et je reçois) ?</li></ul><br/>Additionnez. Puis notez, en une phrase : "Mon prochain pas relationnel santé, réaliste, serait…"<br/><br/>L'objectif n'est pas de vous juger, mais de repérer où un petit déplacement aurait le plus d'effet.
|
|
35
|
+
</slideItem>
|
|
36
|
+
</slide>
|
|
37
|
+
<slide title="Mise en pratique : 5 stratégies simples" duration="1">
|
|
38
|
+
<slideItem type="richText">
|
|
39
|
+
<ol><li><b>Miser sur l'intégration "en profondeur", pas sur l'accumulation</b><br/>Les indices complexes d'intégration sociale sont ceux qui ressortent le plus fortement. Concrètement : plutôt qu'ajouter des contacts, cherchez un rôle stable (activité, groupe, engagement léger) qui vous donne une place reconnaissable.</li><li><b>Travailler le soutien perçu : rendre l'aide imaginable</b><br/>Le soutien perçu est un prédicteur notable. Une action très simple : choisissez une personne et testez une demande petite et précise ("Tu aurais 10 minutes cette semaine pour que je te parle d'un truc ?"). Ce type de micro-expérience rend le soutien plus "réel" mentalement.</li><li><b>Si vous êtes seul : privilégier des formats qui nourrissent vraiment le lien</b><br/>L'isolement n'est pas qu'une affaire de logement : c'est aussi une affaire de qualité de connexion et de sentiment d'appartenance. Or la vaste synthèse d'études scientifiques souligne l'importance de la solitude et de l'expérience subjective. Cherchez un format où l'on se voit suffisamment pour qu'un lien s'épaississe (rendez-vous régulier, activité partagée, marche hebdo, club).</li><li><b>Faire de l'hygiène relationnelle : réduire ce qui "abîme" le corps</b><br/>Les auteurs rappellent que les relations négatives ne sont pas neutres. Sans dramatiser : identifiez une interaction récurrente qui vous laisse vidé, tendu, humilié — et introduisez une limite concrète (durée, sujet, fréquence), ou un tiers, ou une distance. La santé relationnelle n'est pas seulement l'ajout de liens : c'est aussi la diminution de l'érosion.</li><li><b>Relier santé et relations : impliquer votre réseau, même modestement</b><br/>Les auteurs plaident pour que les soins et la prévention intègrent le réseau social (par exemple, soutien dans l'adhérence, présence à des étapes clés, organisation). À l'échelle individuelle : demandez à quelqu'un de vous accompagner dans un objectif de santé (marche, rendez-vous, routine), non comme "coach", mais comme <b>allié</b>.</li></ol>
|
|
40
|
+
</slideItem>
|
|
41
|
+
</slide>
|
|
42
|
+
<slide title="Conclusion : traiter le lien social comme une infrastructure de santé" duration="1">
|
|
43
|
+
<slideItem type="richText">
|
|
44
|
+
Une des idées les plus fortes de Holt-Lunstad et al. est presque philosophique : <b>nous n'existons pas en isolation</b>. Les relations influencent la santé par des voies cognitives, émotionnelles, comportementales et biologiques — lentement, cumulativement, parfois silencieusement.<br/><br/>La bonne nouvelle, si l'on adopte la logique du gradient : vous n'avez pas besoin de "devenir ultra-sociable" du jour au lendemain pour que quelque chose bouge. Un rôle social de plus, une relation nourrie avec plus de fiabilité, un pas qui réduit la solitude, une limite posée face à une interaction délétère… peuvent constituer des investissements de santé au même titre que certaines routines de prévention.<br/><br/>Si vous sentez que la question relationnelle est sensible (solitude tenace, histoire de liens douloureux, difficulté à demander, tendance à s'isoler), un accompagnement peut aider à faire ces pas de manière progressive et sécurisée — par exemple avec un expert Holivia, pour transformer des intentions vagues ("il faut que je voie plus de monde") en stratégies fines et soutenables.
|
|
45
|
+
</slideItem>
|
|
46
|
+
</slide>
|
|
47
|
+
<slide title="Référence" duration="1">
|
|
48
|
+
<slideItem type="richText">
|
|
49
|
+
Holt-Lunstad, J., Smith, T. B., & Layton, J. B. (2010). Social relationships and mortality risk: A meta-analytic review. <i>PLOS Medicine</i>, 7(7), e1000316. https://doi.org/10.1371/journal.pmed.1000316
|
|
50
|
+
</slideItem>
|
|
51
|
+
</slide>
|
|
52
|
+
</slides>
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
{
|
|
2
|
+
"title": "Gestion du stress au travail",
|
|
3
|
+
"duration": 20,
|
|
4
|
+
"description": "Parcours complet sur le stress professionnel",
|
|
5
|
+
"goal": "Comprendre et réduire le stress",
|
|
6
|
+
"content_type": "video_type",
|
|
7
|
+
"locale": "fr",
|
|
8
|
+
"formats": [
|
|
9
|
+
{
|
|
10
|
+
"format_type": "video",
|
|
11
|
+
"slides": [
|
|
12
|
+
{
|
|
13
|
+
"title": "Vidéo principale",
|
|
14
|
+
"duration": 8,
|
|
15
|
+
"items": [
|
|
16
|
+
{ "item_type": "WistiaItem", "code": "abc123def" }
|
|
17
|
+
]
|
|
18
|
+
}
|
|
19
|
+
]
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
"format_type": "audio",
|
|
23
|
+
"slides": [
|
|
24
|
+
{
|
|
25
|
+
"title": "Version audio",
|
|
26
|
+
"duration": 5
|
|
27
|
+
}
|
|
28
|
+
]
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
"format_type": "text",
|
|
32
|
+
"slides": [
|
|
33
|
+
{
|
|
34
|
+
"title": "Introduction",
|
|
35
|
+
"duration": 3,
|
|
36
|
+
"items": [
|
|
37
|
+
{ "item_type": "RichTextItem", "content": "<h2>Le stress</h2><p>Le stress est une réaction naturelle de l'organisme...</p>" }
|
|
38
|
+
]
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
"title": "Les causes principales",
|
|
42
|
+
"duration": 4,
|
|
43
|
+
"items": [
|
|
44
|
+
{ "item_type": "RichTextItem", "content": "<h2>Causes</h2><ul><li>Surcharge de travail</li><li>Manque de contrôle</li><li>Conflits interpersonnels</li></ul>" },
|
|
45
|
+
{ "item_type": "RichTextItem", "content": "<h3>Facteurs aggravants</h3><p>Le manque de sommeil et la sédentarité amplifient les effets du stress.</p>" }
|
|
46
|
+
]
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
"title": "Techniques de gestion",
|
|
50
|
+
"duration": 5,
|
|
51
|
+
"items": [
|
|
52
|
+
{ "item_type": "RichTextItem", "content": "<h2>3 techniques simples</h2><ol><li>La respiration abdominale</li><li>La pause active</li><li>Le recadrage cognitif</li></ol>" }
|
|
53
|
+
]
|
|
54
|
+
}
|
|
55
|
+
]
|
|
56
|
+
}
|
|
57
|
+
]
|
|
58
|
+
}
|
data/exe/holivia
CHANGED
|
@@ -6,6 +6,10 @@ require "holivia/cli"
|
|
|
6
6
|
begin
|
|
7
7
|
Holivia::CLI.start(ARGV)
|
|
8
8
|
rescue Holivia::ApiError => e
|
|
9
|
+
warn "Error (#{e.status}): #{e.message}"
|
|
10
|
+
warn JSON.pretty_generate(e.details) if e.details.is_a?(Hash)
|
|
11
|
+
exit 1
|
|
12
|
+
rescue Holivia::ConfigError => e
|
|
9
13
|
warn "Error: #{e.message}"
|
|
10
14
|
exit 1
|
|
11
15
|
end
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Holivia
|
|
4
|
+
class CLI
|
|
5
|
+
module Help
|
|
6
|
+
HELP_TEXT = <<~HELP
|
|
7
|
+
HOLIVIA CLI
|
|
8
|
+
===========
|
|
9
|
+
|
|
10
|
+
Environment:
|
|
11
|
+
holivia env Show current environment
|
|
12
|
+
holivia env list List all configured environments
|
|
13
|
+
holivia env add <name> --url <url> Add or update an environment (--debug / --no-debug)
|
|
14
|
+
holivia env use <name> Switch to an environment
|
|
15
|
+
holivia env remove <name> Remove an environment and its credentials
|
|
16
|
+
holivia env debug [name] --true/--false Toggle debug logging for an environment
|
|
17
|
+
Config stored at ~/.holivia/config.yml. Defaults to staging if no config exists.
|
|
18
|
+
|
|
19
|
+
Authentication:
|
|
20
|
+
holivia login [--email EMAIL --password PASSWORD] Log in (flags or interactive prompt)
|
|
21
|
+
holivia logout Log out and clear stored credentials
|
|
22
|
+
Tokens are stored at ~/.holivia/credentials.<env>.json and refreshed automatically on 401.
|
|
23
|
+
|
|
24
|
+
Selfcare Contents:
|
|
25
|
+
holivia selfcare index List all selfcare contents
|
|
26
|
+
holivia selfcare show <id> Show a selfcare content by id
|
|
27
|
+
holivia selfcare create [options] Create a selfcare content
|
|
28
|
+
holivia selfcare update <id> [options] Update a selfcare content
|
|
29
|
+
holivia selfcare compose Create a full content tree atomically (no audio)
|
|
30
|
+
holivia selfcare schema Show allowed values for content_types, format_types, item_types
|
|
31
|
+
|
|
32
|
+
Content Formats:
|
|
33
|
+
holivia selfcare format create [options] Add a format to a selfcare content
|
|
34
|
+
holivia selfcare format update <id> [options] Update a content format
|
|
35
|
+
|
|
36
|
+
Slides:
|
|
37
|
+
holivia selfcare slide create [options] Add a slide to a format
|
|
38
|
+
holivia selfcare slide update <id> [options] Update a slide
|
|
39
|
+
|
|
40
|
+
Slide Items:
|
|
41
|
+
holivia selfcare item create [options] Add an item to a slide
|
|
42
|
+
holivia selfcare item update <id> [options] Update a slide item
|
|
43
|
+
|
|
44
|
+
Data Model:
|
|
45
|
+
SelfcareContent → ContentFormat (format_type: text, audio, video)
|
|
46
|
+
→ Slide (title, duration) → SlideItem (one of: RichTextItem, VideoItem,
|
|
47
|
+
WistiaItem, QuizItem, AudioItem, MeditationItem, BreathingItem)
|
|
48
|
+
BreathingItem and MeditationItem are rendered full screen: one per slide.
|
|
49
|
+
|
|
50
|
+
Workflow:
|
|
51
|
+
1. holivia selfcare schema Discover valid enum values
|
|
52
|
+
2. holivia selfcare compose Create a full content tree in one atomic request
|
|
53
|
+
Text and video items are created inline. Audio items require a file upload,
|
|
54
|
+
so compose creates the slide without items — then add them using the slide ID:
|
|
55
|
+
holivia selfcare item create --slide-id <id> --item-type AudioItem --audio <path>
|
|
56
|
+
OR build everything incrementally:
|
|
57
|
+
holivia selfcare create → create the content
|
|
58
|
+
holivia selfcare format create → attach a format
|
|
59
|
+
holivia selfcare slide create → add a slide to the format
|
|
60
|
+
holivia selfcare item create → add items to the slide (supports audio upload)
|
|
61
|
+
|
|
62
|
+
Data Input:
|
|
63
|
+
Create/update commands accept flags (run <command> --help for details).
|
|
64
|
+
Compose accepts --file <path> for a JSON payload.
|
|
65
|
+
All create/update/compose commands also accept piped JSON via stdin.
|
|
66
|
+
Audio uploads use --audio <path> on item create/update (sent as multipart/form-data).
|
|
67
|
+
Accepted audio formats: MP3, MP4, WAV, OGG, FLAC, AAC, M4A, WebM. Max size: 100 MB.
|
|
68
|
+
|
|
69
|
+
Errors:
|
|
70
|
+
Validation errors (422) show structured details:
|
|
71
|
+
path: "formats[0].slides[0]" errors: { "title": ["can't be blank"] }
|
|
72
|
+
Auth errors return 401. Not found returns 404.
|
|
73
|
+
|
|
74
|
+
Discovery:
|
|
75
|
+
Run holivia selfcare schema to get current allowed values for all enums
|
|
76
|
+
and item types with their permitted params. Always use this over hardcoded
|
|
77
|
+
values — it is the source of truth.
|
|
78
|
+
|
|
79
|
+
Version:
|
|
80
|
+
holivia version Print version
|
|
81
|
+
HELP
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
data/lib/holivia/cli.rb
CHANGED
|
@@ -1,39 +1,28 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "holivia"
|
|
4
|
+
require_relative "cli/help"
|
|
4
5
|
require_relative "commands/auth"
|
|
5
6
|
require_relative "commands/selfcare"
|
|
7
|
+
require_relative "commands/format"
|
|
8
|
+
require_relative "commands/slide"
|
|
9
|
+
require_relative "commands/env"
|
|
10
|
+
require_relative "commands/item"
|
|
6
11
|
|
|
7
12
|
module Holivia
|
|
8
13
|
class CLI
|
|
9
14
|
def self.start(args)
|
|
10
15
|
command = args.shift
|
|
11
16
|
case command
|
|
12
|
-
when "login" then Commands::Auth.new.login
|
|
17
|
+
when "login" then Commands::Auth.new.login(args)
|
|
13
18
|
when "logout" then Commands::Auth.new.logout
|
|
14
|
-
when "
|
|
15
|
-
when "
|
|
19
|
+
when "env" then Commands::Env.route(args)
|
|
20
|
+
when "selfcare" then Commands::Selfcare.route(args)
|
|
21
|
+
when "version", "--version", "-v" then puts "holivia #{Holivia::VERSION}"
|
|
22
|
+
when "--help", "-h", nil then puts Help::HELP_TEXT
|
|
16
23
|
else warn "Unknown command: #{command}"
|
|
17
24
|
exit 1
|
|
18
25
|
end
|
|
19
26
|
end
|
|
20
|
-
|
|
21
|
-
def self.route_selfcare(args)
|
|
22
|
-
subcommand = args.shift
|
|
23
|
-
case subcommand
|
|
24
|
-
when "index" then Commands::Selfcare.new.index
|
|
25
|
-
else warn "Unknown selfcare command: #{subcommand}"
|
|
26
|
-
exit 1
|
|
27
|
-
end
|
|
28
|
-
end
|
|
29
|
-
|
|
30
|
-
def self.print_help
|
|
31
|
-
puts "Usage: holivia <command>"
|
|
32
|
-
puts ""
|
|
33
|
-
puts "Commands:"
|
|
34
|
-
puts " login Log in to Holivia"
|
|
35
|
-
puts " logout Log out of Holivia"
|
|
36
|
-
puts " selfcare index List selfcare contents"
|
|
37
|
-
end
|
|
38
27
|
end
|
|
39
28
|
end
|
data/lib/holivia/client.rb
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "faraday"
|
|
4
|
+
require "faraday/multipart"
|
|
4
5
|
|
|
5
6
|
module Holivia
|
|
6
7
|
class Client
|
|
@@ -18,6 +19,10 @@ module Holivia
|
|
|
18
19
|
request(:get, path, params:, headers:)
|
|
19
20
|
end
|
|
20
21
|
|
|
22
|
+
def patch(path, body: {}, headers: {})
|
|
23
|
+
request(:patch, path, body:, headers:)
|
|
24
|
+
end
|
|
25
|
+
|
|
21
26
|
def delete(path, headers: {})
|
|
22
27
|
request(:delete, path, headers:)
|
|
23
28
|
end
|
|
@@ -52,8 +57,9 @@ module Holivia
|
|
|
52
57
|
|
|
53
58
|
def connection
|
|
54
59
|
@connection ||= http.new(url: base_url) do |f|
|
|
60
|
+
f.request :multipart
|
|
55
61
|
f.request :json
|
|
56
|
-
f.response :logger if Holivia.configuration.debug
|
|
62
|
+
f.response :logger if Holivia.configuration.debug?
|
|
57
63
|
f.response :json
|
|
58
64
|
f.adapter Faraday.default_adapter
|
|
59
65
|
end
|
|
@@ -1,16 +1,21 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "io/console"
|
|
4
|
+
require "optparse"
|
|
4
5
|
|
|
5
6
|
module Holivia
|
|
6
7
|
module Commands
|
|
7
8
|
class Auth
|
|
8
|
-
def login
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
9
|
+
def login(args = [])
|
|
10
|
+
options = {}
|
|
11
|
+
OptionParser.new do |opts|
|
|
12
|
+
opts.banner = "Usage: holivia login [--email EMAIL --password PASSWORD]"
|
|
13
|
+
opts.on("--email EMAIL") { |v| options[:email] = v }
|
|
14
|
+
opts.on("--password PASSWORD") { |v| options[:password] = v }
|
|
15
|
+
end.parse!(args)
|
|
16
|
+
|
|
17
|
+
email = options[:email] || prompt("Email: ")
|
|
18
|
+
password = options[:password] || prompt_secret("Password: ")
|
|
14
19
|
|
|
15
20
|
Holivia::Auth.new.login(email: email, password: password)
|
|
16
21
|
puts "Logged in successfully."
|
|
@@ -20,6 +25,20 @@ module Holivia
|
|
|
20
25
|
Holivia::Auth.new.logout
|
|
21
26
|
puts "Logged out successfully."
|
|
22
27
|
end
|
|
28
|
+
|
|
29
|
+
private
|
|
30
|
+
|
|
31
|
+
def prompt(label)
|
|
32
|
+
print label
|
|
33
|
+
$stdin.gets.chomp
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def prompt_secret(label)
|
|
37
|
+
print label
|
|
38
|
+
result = $stdin.noecho(&:gets).chomp
|
|
39
|
+
puts
|
|
40
|
+
result
|
|
41
|
+
end
|
|
23
42
|
end
|
|
24
43
|
end
|
|
25
44
|
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "optparse"
|
|
4
|
+
|
|
5
|
+
module Holivia
|
|
6
|
+
module Commands
|
|
7
|
+
class Base
|
|
8
|
+
private
|
|
9
|
+
|
|
10
|
+
def client
|
|
11
|
+
@client ||= Client.new
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def output(response)
|
|
15
|
+
puts JSON.pretty_generate(response)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def piped_json
|
|
19
|
+
return {} if $stdin.tty?
|
|
20
|
+
|
|
21
|
+
input = $stdin.read
|
|
22
|
+
return {} if input.empty?
|
|
23
|
+
|
|
24
|
+
JSON.parse(input, symbolize_names: true)
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "base"
|
|
4
|
+
|
|
5
|
+
module Holivia
|
|
6
|
+
module Commands
|
|
7
|
+
class Env < Base
|
|
8
|
+
def self.route(args)
|
|
9
|
+
subcommand = args.shift
|
|
10
|
+
case subcommand
|
|
11
|
+
when "add" then new.add(args)
|
|
12
|
+
when "use" then new.use(args)
|
|
13
|
+
when "remove" then new.remove(args)
|
|
14
|
+
when "debug" then new.debug(args)
|
|
15
|
+
when "list" then new.list
|
|
16
|
+
when nil then new.show
|
|
17
|
+
else warn "Unknown env command: #{subcommand}"
|
|
18
|
+
exit 1
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def show
|
|
23
|
+
puts "Environment: #{config.current_env}"
|
|
24
|
+
puts "URL: #{config.base_url}"
|
|
25
|
+
puts "Debug: #{config.debug?}"
|
|
26
|
+
puts "Credentials: #{File.exist?(config.credentials_path) ? "yes" : "no"}"
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def list
|
|
30
|
+
config.environments.each do |name, env|
|
|
31
|
+
prefix = name == config.current_env ? "*" : " "
|
|
32
|
+
puts "#{prefix} #{name}\t#{env["url"]}"
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def add(args = [])
|
|
37
|
+
options = {}
|
|
38
|
+
OptionParser.new do |opts|
|
|
39
|
+
opts.banner = "Usage: holivia env add <name> --url <url> [--debug]"
|
|
40
|
+
opts.on("--url URL") { |v| options[:url] = v }
|
|
41
|
+
opts.on("--debug") { options[:debug] = true }
|
|
42
|
+
opts.on("--no-debug") { options[:debug] = false }
|
|
43
|
+
end.parse!(args)
|
|
44
|
+
|
|
45
|
+
name = args.shift
|
|
46
|
+
abort "Usage: holivia env add <name> --url <url>" unless name && options[:url]
|
|
47
|
+
|
|
48
|
+
config.add_env(name, url: options[:url], debug: options[:debug])
|
|
49
|
+
puts "Environment '#{name}' added."
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def use(args = [])
|
|
53
|
+
name = args.shift
|
|
54
|
+
abort "Usage: holivia env use <name>" unless name
|
|
55
|
+
|
|
56
|
+
config.use_env(name)
|
|
57
|
+
puts "Switched to '#{name}'."
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def remove(args = [])
|
|
61
|
+
name = args.shift
|
|
62
|
+
abort "Usage: holivia env remove <name>" unless name
|
|
63
|
+
|
|
64
|
+
config.remove_env(name)
|
|
65
|
+
puts "Environment '#{name}' removed."
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def debug(args = [])
|
|
69
|
+
value = nil
|
|
70
|
+
OptionParser.new do |opts|
|
|
71
|
+
opts.banner = "Usage: holivia env debug [name] --true/--false"
|
|
72
|
+
opts.on("--true") { value = true }
|
|
73
|
+
opts.on("--false") { value = false }
|
|
74
|
+
end.parse!(args)
|
|
75
|
+
|
|
76
|
+
name = args.shift || config.current_env
|
|
77
|
+
abort "Usage: holivia env debug [name] --true/--false" if value.nil?
|
|
78
|
+
|
|
79
|
+
config.update_env(name, debug: value)
|
|
80
|
+
puts "Debug #{value ? "on" : "off"} for '#{name}'."
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
private
|
|
84
|
+
|
|
85
|
+
def config
|
|
86
|
+
@config ||= Holivia.configuration
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "base"
|
|
4
|
+
|
|
5
|
+
module Holivia
|
|
6
|
+
module Commands
|
|
7
|
+
class Format < Base
|
|
8
|
+
BASE_PATH = "/api/v1/backoffice/content_formats"
|
|
9
|
+
|
|
10
|
+
def self.route(args)
|
|
11
|
+
subcommand = args.shift
|
|
12
|
+
case subcommand
|
|
13
|
+
when "create" then new.create(args)
|
|
14
|
+
when "update" then new.update(args)
|
|
15
|
+
else warn "Unknown selfcare format command: #{subcommand}"
|
|
16
|
+
exit 1
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def create(args = [])
|
|
21
|
+
options = {}
|
|
22
|
+
OptionParser.new do |opts|
|
|
23
|
+
opts.banner = "Usage: holivia selfcare format create [options]"
|
|
24
|
+
opts.on("--selfcare-content-id ID", Integer) { |v| options[:selfcare_content_id] = v }
|
|
25
|
+
opts.on("--format-type TYPE") { |v| options[:format_type] = v }
|
|
26
|
+
end.parse!(args)
|
|
27
|
+
options = options.merge(piped_json)
|
|
28
|
+
|
|
29
|
+
abort "No options provided. Use --help for usage." if options.empty?
|
|
30
|
+
output(client.post(BASE_PATH, body: options))
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def update(args = [])
|
|
34
|
+
id = args.shift
|
|
35
|
+
abort "Usage: holivia selfcare format update <id> [options]" unless id
|
|
36
|
+
|
|
37
|
+
options = {}
|
|
38
|
+
OptionParser.new do |opts|
|
|
39
|
+
opts.banner = "Usage: holivia selfcare format update <id> [options]"
|
|
40
|
+
opts.on("--format-type TYPE") { |v| options[:format_type] = v }
|
|
41
|
+
end.parse!(args)
|
|
42
|
+
options = options.merge(piped_json)
|
|
43
|
+
|
|
44
|
+
abort "No options provided. Use --help for usage." if options.empty?
|
|
45
|
+
output(client.patch("#{BASE_PATH}/#{id}", body: options))
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "base"
|
|
4
|
+
|
|
5
|
+
module Holivia
|
|
6
|
+
module Commands
|
|
7
|
+
class Item < Base
|
|
8
|
+
BASE_PATH = "/api/v1/backoffice/slide_items"
|
|
9
|
+
MIME_TYPES = {
|
|
10
|
+
".mp3" => "audio/mpeg",
|
|
11
|
+
".mp4" => "audio/mp4",
|
|
12
|
+
".wav" => "audio/wav",
|
|
13
|
+
".ogg" => "audio/ogg",
|
|
14
|
+
".flac" => "audio/flac",
|
|
15
|
+
".aac" => "audio/aac",
|
|
16
|
+
".m4a" => "audio/mp4",
|
|
17
|
+
".webm" => "audio/webm"
|
|
18
|
+
}.freeze
|
|
19
|
+
|
|
20
|
+
def self.route(args)
|
|
21
|
+
subcommand = args.shift
|
|
22
|
+
case subcommand
|
|
23
|
+
when "create" then new.create(args)
|
|
24
|
+
when "update" then new.update(args)
|
|
25
|
+
else warn "Unknown selfcare item command: #{subcommand}"
|
|
26
|
+
exit 1
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def create(args = []) # rubocop:disable Metrics/AbcSize,Metrics/MethodLength
|
|
31
|
+
options = {}
|
|
32
|
+
OptionParser.new do |opts|
|
|
33
|
+
opts.banner = "Usage: holivia selfcare item create [options]"
|
|
34
|
+
opts.on("--slide-id ID", Integer) { |v| options[:slide_id] = v }
|
|
35
|
+
opts.on("--item-type TYPE") { |v| options[:item_type] = v }
|
|
36
|
+
# RichTextItem
|
|
37
|
+
opts.on("--content CONTENT") { |v| options[:content] = v }
|
|
38
|
+
# VideoItem
|
|
39
|
+
opts.on("--url URL") { |v| options[:url] = v }
|
|
40
|
+
# WistiaItem
|
|
41
|
+
opts.on("--code CODE") { |v| options[:code] = v }
|
|
42
|
+
# QuizItem
|
|
43
|
+
opts.on("--form-url URL") { |v| options[:form_url] = v }
|
|
44
|
+
opts.on("--score-threshold N", Integer) { |v| options[:score_threshold] = v }
|
|
45
|
+
opts.on("--total-score N", Integer) { |v| options[:total_score] = v }
|
|
46
|
+
# MeditationItem / BreathingItem
|
|
47
|
+
opts.on("--title TITLE") { |v| options[:title] = v }
|
|
48
|
+
opts.on("--duration DURATION", Integer) { |v| options[:duration] = v }
|
|
49
|
+
opts.on("--subtitle SUBTITLE") { |v| options[:subtitle] = v }
|
|
50
|
+
# BreathingItem
|
|
51
|
+
opts.on("--cycles N", Integer) { |v| options[:cycles] = v }
|
|
52
|
+
opts.on("--start-delay N", Integer) { |v| options[:start_delay] = v }
|
|
53
|
+
opts.on("--sequence SEQUENCE") { |v| options[:sequence] = JSON.parse(v) }
|
|
54
|
+
# Audio file upload
|
|
55
|
+
opts.on("--audio FILE") { |v| options[:audio] = v }
|
|
56
|
+
end.parse!(args)
|
|
57
|
+
options = options.merge(piped_json)
|
|
58
|
+
|
|
59
|
+
abort "No options provided. Use --help for usage." if options.empty?
|
|
60
|
+
output(client.post(BASE_PATH, body: build_body(options)))
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def update(args = []) # rubocop:disable Metrics/AbcSize,Metrics/MethodLength
|
|
64
|
+
id = args.shift
|
|
65
|
+
abort "Usage: holivia selfcare item update <id> [options]" unless id
|
|
66
|
+
|
|
67
|
+
options = {}
|
|
68
|
+
OptionParser.new do |opts|
|
|
69
|
+
opts.banner = "Usage: holivia selfcare item update <id> [options]"
|
|
70
|
+
opts.on("--item-type TYPE") { |v| options[:item_type] = v }
|
|
71
|
+
opts.on("--content CONTENT") { |v| options[:content] = v }
|
|
72
|
+
opts.on("--url URL") { |v| options[:url] = v }
|
|
73
|
+
opts.on("--code CODE") { |v| options[:code] = v }
|
|
74
|
+
opts.on("--form-url URL") { |v| options[:form_url] = v }
|
|
75
|
+
opts.on("--score-threshold N", Integer) { |v| options[:score_threshold] = v }
|
|
76
|
+
opts.on("--total-score N", Integer) { |v| options[:total_score] = v }
|
|
77
|
+
opts.on("--title TITLE") { |v| options[:title] = v }
|
|
78
|
+
opts.on("--duration DURATION", Integer) { |v| options[:duration] = v }
|
|
79
|
+
opts.on("--subtitle SUBTITLE") { |v| options[:subtitle] = v }
|
|
80
|
+
opts.on("--cycles N", Integer) { |v| options[:cycles] = v }
|
|
81
|
+
opts.on("--start-delay N", Integer) { |v| options[:start_delay] = v }
|
|
82
|
+
opts.on("--sequence SEQUENCE") { |v| options[:sequence] = JSON.parse(v) }
|
|
83
|
+
opts.on("--audio FILE") { |v| options[:audio] = v }
|
|
84
|
+
end.parse!(args)
|
|
85
|
+
options = options.merge(piped_json)
|
|
86
|
+
|
|
87
|
+
abort "No options provided. Use --help for usage." if options.empty?
|
|
88
|
+
output(client.patch("#{BASE_PATH}/#{id}", body: build_body(options)))
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
private
|
|
92
|
+
|
|
93
|
+
def build_body(options)
|
|
94
|
+
audio_path = options.delete(:audio)
|
|
95
|
+
return options unless audio_path
|
|
96
|
+
|
|
97
|
+
mime = detect_mime(audio_path)
|
|
98
|
+
options.merge(audio: Faraday::Multipart::FilePart.new(audio_path, mime))
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def detect_mime(path)
|
|
102
|
+
ext = File.extname(path).downcase
|
|
103
|
+
MIME_TYPES.fetch(ext, "application/octet-stream")
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
end
|
|
@@ -1,14 +1,97 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative "base"
|
|
4
|
+
|
|
3
5
|
module Holivia
|
|
4
6
|
module Commands
|
|
5
|
-
class Selfcare
|
|
7
|
+
class Selfcare < Base
|
|
6
8
|
BASE_PATH = "/api/v1/backoffice/selfcare_contents"
|
|
9
|
+
COMPOSE_EXAMPLE_PATH = Holivia.root.join("examples", "compose.json").to_s
|
|
10
|
+
|
|
11
|
+
def self.route(args) # rubocop:disable Metrics/CyclomaticComplexity,Metrics/MethodLength,Metrics/AbcSize
|
|
12
|
+
subcommand = args.shift
|
|
13
|
+
case subcommand
|
|
14
|
+
when "index" then new.index(args)
|
|
15
|
+
when "show" then new.show(args)
|
|
16
|
+
when "create" then new.create(args)
|
|
17
|
+
when "update" then new.update(args)
|
|
18
|
+
when "compose" then new.compose(args)
|
|
19
|
+
when "schema" then new.schema(args)
|
|
20
|
+
when "format" then Format.route(args)
|
|
21
|
+
when "slide" then Slide.route(args)
|
|
22
|
+
when "item" then Item.route(args)
|
|
23
|
+
else warn "Unknown selfcare command: #{subcommand}"
|
|
24
|
+
exit 1
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def index(_args = [])
|
|
29
|
+
output(client.get(BASE_PATH))
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def show(args = [])
|
|
33
|
+
id = args.shift
|
|
34
|
+
abort "Usage: holivia selfcare show <id>" unless id
|
|
35
|
+
|
|
36
|
+
output(client.get("#{BASE_PATH}/#{id}"))
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def create(args = []) # rubocop:disable Metrics/AbcSize
|
|
40
|
+
options = {}
|
|
41
|
+
OptionParser.new do |opts|
|
|
42
|
+
opts.banner = "Usage: holivia selfcare create [options]"
|
|
43
|
+
opts.on("--title TITLE") { |v| options[:title] = v }
|
|
44
|
+
opts.on("--locale LOCALE") { |v| options[:locale] = v }
|
|
45
|
+
opts.on("--content-type TYPE") { |v| options[:content_type] = v }
|
|
46
|
+
opts.on("--duration DURATION", Integer) { |v| options[:duration] = v }
|
|
47
|
+
opts.on("--description DESC") { |v| options[:description] = v }
|
|
48
|
+
end.parse!(args)
|
|
49
|
+
options = options.merge(piped_json)
|
|
50
|
+
|
|
51
|
+
abort "No options provided. Use --help for usage." if options.empty?
|
|
52
|
+
output(client.post(BASE_PATH, body: options))
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def update(args = []) # rubocop:disable Metrics/AbcSize,Metrics/MethodLength
|
|
56
|
+
id = args.shift
|
|
57
|
+
abort "Usage: holivia selfcare update <id> [options]" unless id
|
|
58
|
+
|
|
59
|
+
options = {}
|
|
60
|
+
OptionParser.new do |opts|
|
|
61
|
+
opts.banner = "Usage: holivia selfcare update <id> [options]"
|
|
62
|
+
opts.on("--title TITLE") { |v| options[:title] = v }
|
|
63
|
+
opts.on("--locale LOCALE") { |v| options[:locale] = v }
|
|
64
|
+
opts.on("--content-type TYPE") { |v| options[:content_type] = v }
|
|
65
|
+
opts.on("--duration DURATION", Integer) { |v| options[:duration] = v }
|
|
66
|
+
opts.on("--description DESC") { |v| options[:description] = v }
|
|
67
|
+
end.parse!(args)
|
|
68
|
+
options = options.merge(piped_json)
|
|
69
|
+
|
|
70
|
+
abort "No options provided. Use --help for usage." if options.empty?
|
|
71
|
+
output(client.patch("#{BASE_PATH}/#{id}", body: options))
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def compose(args = []) # rubocop:disable Metrics/MethodLength
|
|
75
|
+
show_example = false
|
|
76
|
+
options = {}
|
|
77
|
+
OptionParser.new do |opts|
|
|
78
|
+
opts.banner = "Usage: holivia selfcare compose --file <path>"
|
|
79
|
+
opts.on("--file FILE") { |v| options = JSON.parse(File.read(v), symbolize_names: true) }
|
|
80
|
+
opts.on("--example", "Print a valid compose JSON template") { show_example = true }
|
|
81
|
+
end.parse!(args)
|
|
82
|
+
|
|
83
|
+
if show_example
|
|
84
|
+
puts File.read(COMPOSE_EXAMPLE_PATH)
|
|
85
|
+
return
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
options = options.merge(piped_json)
|
|
89
|
+
abort "Usage: holivia selfcare compose --file <path>" if options.empty?
|
|
90
|
+
output(client.post("#{BASE_PATH}/compose", body: options))
|
|
91
|
+
end
|
|
7
92
|
|
|
8
|
-
def
|
|
9
|
-
client
|
|
10
|
-
response = client.get(BASE_PATH)
|
|
11
|
-
puts JSON.pretty_generate(response)
|
|
93
|
+
def schema(_args = [])
|
|
94
|
+
output(client.get("#{BASE_PATH}/schema"))
|
|
12
95
|
end
|
|
13
96
|
end
|
|
14
97
|
end
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "base"
|
|
4
|
+
|
|
5
|
+
module Holivia
|
|
6
|
+
module Commands
|
|
7
|
+
class Slide < Base
|
|
8
|
+
BASE_PATH = "/api/v1/backoffice/slides"
|
|
9
|
+
|
|
10
|
+
def self.route(args)
|
|
11
|
+
subcommand = args.shift
|
|
12
|
+
case subcommand
|
|
13
|
+
when "create" then new.create(args)
|
|
14
|
+
when "update" then new.update(args)
|
|
15
|
+
else warn "Unknown selfcare slide command: #{subcommand}"
|
|
16
|
+
exit 1
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def create(args = [])
|
|
21
|
+
options = {}
|
|
22
|
+
OptionParser.new do |opts|
|
|
23
|
+
opts.banner = "Usage: holivia selfcare slide create [options]"
|
|
24
|
+
opts.on("--content-format-id ID", Integer) { |v| options[:content_format_id] = v }
|
|
25
|
+
opts.on("--title TITLE") { |v| options[:title] = v }
|
|
26
|
+
opts.on("--duration DURATION", Integer) { |v| options[:duration] = v }
|
|
27
|
+
end.parse!(args)
|
|
28
|
+
options = options.merge(piped_json)
|
|
29
|
+
|
|
30
|
+
abort "No options provided. Use --help for usage." if options.empty?
|
|
31
|
+
output(client.post(BASE_PATH, body: options))
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def update(args = [])
|
|
35
|
+
id = args.shift
|
|
36
|
+
abort "Usage: holivia selfcare slide update <id> [options]" unless id
|
|
37
|
+
|
|
38
|
+
options = {}
|
|
39
|
+
OptionParser.new do |opts|
|
|
40
|
+
opts.banner = "Usage: holivia selfcare slide update <id> [options]"
|
|
41
|
+
opts.on("--title TITLE") { |v| options[:title] = v }
|
|
42
|
+
opts.on("--duration DURATION", Integer) { |v| options[:duration] = v }
|
|
43
|
+
end.parse!(args)
|
|
44
|
+
options = options.merge(piped_json)
|
|
45
|
+
|
|
46
|
+
abort "No options provided. Use --help for usage." if options.empty?
|
|
47
|
+
output(client.patch("#{BASE_PATH}/#{id}", body: options))
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
@@ -1,13 +1,71 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "yaml"
|
|
4
|
+
require_relative "env_manager"
|
|
5
|
+
|
|
3
6
|
module Holivia
|
|
4
7
|
class Configuration
|
|
5
|
-
|
|
8
|
+
include EnvManager
|
|
9
|
+
|
|
10
|
+
CONFIG_DIR = File.expand_path("~/.holivia")
|
|
11
|
+
DEFAULT_ENV = "staging"
|
|
12
|
+
DEFAULT_URL = "https://staging.holivia.fr"
|
|
13
|
+
|
|
14
|
+
attr_reader :config_dir
|
|
15
|
+
|
|
16
|
+
def initialize(config_dir: CONFIG_DIR)
|
|
17
|
+
@config_dir = config_dir
|
|
18
|
+
@config = load_config
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def current_env
|
|
22
|
+
@config["current_env"]
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def environments
|
|
26
|
+
@config["environments"]
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def base_url
|
|
30
|
+
environments.dig(current_env, "url")
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def credentials_path
|
|
34
|
+
File.join(config_dir, "credentials.#{current_env}.json")
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def debug?
|
|
38
|
+
environments.dig(current_env, "debug") == true
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
private
|
|
42
|
+
|
|
43
|
+
attr_reader :config
|
|
44
|
+
|
|
45
|
+
def config_path
|
|
46
|
+
File.join(config_dir, "config.yml")
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def load_config
|
|
50
|
+
raw = YAML.safe_load_file(config_path)
|
|
51
|
+
validated_config(raw)
|
|
52
|
+
rescue Errno::ENOENT
|
|
53
|
+
default_config
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def validated_config(raw)
|
|
57
|
+
return default_config unless raw.is_a?(Hash)
|
|
58
|
+
return default_config unless raw["environments"].is_a?(Hash)
|
|
59
|
+
return default_config unless raw["current_env"].is_a?(String)
|
|
60
|
+
|
|
61
|
+
raw
|
|
62
|
+
end
|
|
6
63
|
|
|
7
|
-
def
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
64
|
+
def default_config
|
|
65
|
+
{
|
|
66
|
+
"current_env" => DEFAULT_ENV,
|
|
67
|
+
"environments" => { DEFAULT_ENV => { "url" => DEFAULT_URL } }
|
|
68
|
+
}
|
|
11
69
|
end
|
|
12
70
|
end
|
|
13
71
|
end
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "yaml"
|
|
4
|
+
require "fileutils"
|
|
5
|
+
|
|
6
|
+
module Holivia
|
|
7
|
+
module EnvManager
|
|
8
|
+
def add_env(name, url:, debug: nil)
|
|
9
|
+
env = { "url" => url }
|
|
10
|
+
env["debug"] = debug unless debug.nil?
|
|
11
|
+
config["environments"][name] = env
|
|
12
|
+
config["current_env"] = name if config["environments"].size == 1
|
|
13
|
+
save_config
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def use_env(name)
|
|
17
|
+
raise ConfigError, "Unknown environment '#{name}'" unless config["environments"].key?(name)
|
|
18
|
+
|
|
19
|
+
config["current_env"] = name
|
|
20
|
+
save_config
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def update_env(name, **attrs)
|
|
24
|
+
raise ConfigError, "Unknown environment '#{name}'" unless config["environments"].key?(name)
|
|
25
|
+
|
|
26
|
+
attrs.each { |k, v| config["environments"][name][k.to_s] = v }
|
|
27
|
+
save_config
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def remove_env(name)
|
|
31
|
+
raise ConfigError, "Cannot remove '#{name}' — it is the only environment" if config["environments"].size == 1
|
|
32
|
+
|
|
33
|
+
config["environments"].delete(name)
|
|
34
|
+
config["current_env"] = config["environments"].keys.first if name == current_env
|
|
35
|
+
FileUtils.rm_f(File.join(config_dir, "credentials.#{name}.json"))
|
|
36
|
+
save_config
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
private
|
|
40
|
+
|
|
41
|
+
def save_config
|
|
42
|
+
FileUtils.mkdir_p(config_dir)
|
|
43
|
+
File.write(config_path, YAML.dump(config))
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
data/lib/holivia/version.rb
CHANGED
data/lib/holivia.rb
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "pathname"
|
|
3
4
|
require_relative "holivia/version"
|
|
4
5
|
require_relative "holivia/configuration"
|
|
5
6
|
require_relative "holivia/api_error"
|
|
7
|
+
require_relative "holivia/config_error"
|
|
6
8
|
require_relative "holivia/client"
|
|
7
9
|
require_relative "holivia/auth"
|
|
8
10
|
|
|
@@ -10,12 +12,12 @@ module Holivia
|
|
|
10
12
|
class << self
|
|
11
13
|
attr_writer :configuration
|
|
12
14
|
|
|
13
|
-
def
|
|
14
|
-
|
|
15
|
+
def root
|
|
16
|
+
Pathname.new(File.expand_path("..", __dir__))
|
|
15
17
|
end
|
|
16
18
|
|
|
17
|
-
def
|
|
18
|
-
|
|
19
|
+
def configuration
|
|
20
|
+
@configuration ||= Configuration.new
|
|
19
21
|
end
|
|
20
22
|
end
|
|
21
23
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: holivia
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.3.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Holivia
|
|
@@ -23,6 +23,20 @@ dependencies:
|
|
|
23
23
|
- - "~>"
|
|
24
24
|
- !ruby/object:Gem::Version
|
|
25
25
|
version: '2.0'
|
|
26
|
+
- !ruby/object:Gem::Dependency
|
|
27
|
+
name: faraday-multipart
|
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
|
29
|
+
requirements:
|
|
30
|
+
- - "~>"
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '1.0'
|
|
33
|
+
type: :runtime
|
|
34
|
+
prerelease: false
|
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - "~>"
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: '1.0'
|
|
26
40
|
description: API client library and command-line tool for the Holivia platform
|
|
27
41
|
email:
|
|
28
42
|
- dev@holivia.fr
|
|
@@ -38,15 +52,25 @@ files:
|
|
|
38
52
|
- LICENSE.txt
|
|
39
53
|
- README.md
|
|
40
54
|
- Rakefile
|
|
55
|
+
- example_content.xml
|
|
56
|
+
- examples/compose.json
|
|
41
57
|
- exe/holivia
|
|
42
58
|
- lib/holivia.rb
|
|
43
59
|
- lib/holivia/api_error.rb
|
|
44
60
|
- lib/holivia/auth.rb
|
|
45
61
|
- lib/holivia/cli.rb
|
|
62
|
+
- lib/holivia/cli/help.rb
|
|
46
63
|
- lib/holivia/client.rb
|
|
47
64
|
- lib/holivia/commands/auth.rb
|
|
65
|
+
- lib/holivia/commands/base.rb
|
|
66
|
+
- lib/holivia/commands/env.rb
|
|
67
|
+
- lib/holivia/commands/format.rb
|
|
68
|
+
- lib/holivia/commands/item.rb
|
|
48
69
|
- lib/holivia/commands/selfcare.rb
|
|
70
|
+
- lib/holivia/commands/slide.rb
|
|
71
|
+
- lib/holivia/config_error.rb
|
|
49
72
|
- lib/holivia/configuration.rb
|
|
73
|
+
- lib/holivia/env_manager.rb
|
|
50
74
|
- lib/holivia/version.rb
|
|
51
75
|
- mise.toml
|
|
52
76
|
- sig/holivia.rbs
|