- recoded lib

- add sync.py (synchronisize boards)
This commit is contained in:
Joachim Happel 2024-05-28 15:22:28 +02:00
parent 7bd62d9ec9
commit 271f9b3469
6 changed files with 260 additions and 152 deletions

View file

@ -1,47 +1,74 @@
# Nextcloud Deck importieren/exportieren
## Installation
## Voraussetzung
1. Download https://github.com/johappel/nextcloud-import-export/archive/refs/heads/main.zip
oder auf der Komandozeile git clone https://github.com/johappel/nextcloud-import-export.git
2. Gehe in das Verzeichnis nextcloud-import-export (cd nextcloud-import-export)
3. Führe auf der Komandozeile aus:`pip install requests`
4. Kopiere die Datei "sample.config.py" nach "config.py" und trage dort die Daten zu deinen Nextcloudinstanzen ein
[Python](https://www.python.org/downloads/) muss installiert sein. Du kannst diese [Anleitung](https://kinsta.com/de/wissensdatenbank/python-installieren/) nutzen.
## Optional: Git installieren
## Verwendung
Um ein bestimmtes Deck auf eine andfere Nextcloud Instanz zu kopieren gibst du auf der Komandozeile an:
Installiere [Git](https://git-scm.com/book/de/v2/Erste-Schritte-Git-installieren): Das ermöglicht dir, die Skripte aktuell zu halten und dich an der Entwicklung zu beteiligen.
```python
python clone.py --"Name des Decks"
```
oder
```python
python3 clone.py --"Name des Decks"
## Installation der Skripte
1. [Download](https://github.com/johappel/nextcloud-import-export/archive/refs/heads/main.zip) der Skripte oder mit Git
`git clone https://github.com/johappel/nextcloud-import-export.git`
2. Gehe in das Verzeichnis nextcloud-import-export (cd nextcloud-import-export)
3. Führe auf der Kommandozeile aus: `pip install requests`
4. Kopiere die Datei "sample.config.py" nach "config.py" und trage dort die Daten zu deinen Nextcloud-Instanzen ein.
5. Mit diesem Befehl auf der Kommandozeile holst du die neueste Version:
`git pull`
## Anwendungsfälle
Um ein bestimmtes Deck auf eine andere Nextcloud-Instanz zu kopieren, gibst du auf der Kommandozeile einen der folgenden Befehle ein:
1. Um ein Deck zu kopieren:
```sh
python clone.py --board "Name des Decks"
```
Um alle Decks zu kopieren, gibst du ein:
2. Um ein bestehendes Deck auf der Zielinstanz zu löschen und zu ersetzen:
```python
```sh
python sync.py --board "Name des Decks" --replace
```
3. Um ein bestehendes Deck auf der Zielinstanz mit den Daten, Karten und Stacks der Originalinstanz synchron zu halten:
```sh
python sync.py --board "Name des Decks"
```
4. Um ein Backup aller Decks mit Datum des Backups im Titel auf der Zielinstanz zu sichern:
```sh
python backup.py
```
Dank der großartigen Arbeit von @svbergerem:
https://gist.github.com/svbergerem/5914d7f87764901aefddba125af99938
### Funktionen des Skripts
1. **Daten von der Quellinstanz abrufen:**
- `getBoards()`: Ruft die Liste aller Boards ab.
- `getBoardDetails(boardId)`: Ruft Details eines spezifischen Boards ab.
- `getStacks(boardId)`: Ruft die Stacks eines Boards ab.
- `getStacksArchived(boardId)`: Ruft die archivierten Stacks eines Boards ab.
Gib bei den folgenden Funktionen als Parameter 'from' oder 'to' ein, je nachdem, ob du die Daten von der Quellinstanz oder der Zielinstanz abfragst:
- `getBoards(from_or_to)`: Ruft die Liste aller Boards ab.
- `getBoardDetails(boardId, from_or_to)`: Ruft Details eines spezifischen Boards ab.
- `getStacks(boardId, from_or_to)`: Ruft die Stacks eines Boards ab.
- `getStacksArchived(boardId, from_or_to)`: Ruft die archivierten Stacks eines Boards ab.
2. **Daten zur Zielinstanz übertragen:**
- `createBoard(title, color)`: Erstellt ein Board.
- `createLabel(title, color, boardId)`: Erstellt ein Label in einem Board.
- `createStack(title, order, boardId)`: Erstellt einen Stack in einem Board.
- `createCard(title, ctype, order, description, duedate, boardId, stackId)`: Erstellt eine Karte in einem Stack.
- `assignLabel(labelId, cardId, boardId, stackId)`: Weist ein Label einer Karte zu.
- `assignLabel(labelId, cardId, boardId, stackId)`: Weist einer Karte ein Label zu.
- `archiveCard(card, boardId, stackId)`: Archiviert eine Karte.
- `copyCard(card, boardIdTo, stackIdTo, labelsMap)`: Kopiert eine Karte, einschließlich ihrer Labels und archiviertem Status.
- `copyCard(card, boardIdTo, stackIdTo, labelsMap)`: Kopiert eine Karte, einschließlich ihrer Labels und des archivierten Status.
- `deleteBoard(boardIdTo)`: Löscht ein Board.
- `deleteStacks(boardIdTo)`: Löscht alle Listen eines Boards.
- `deleteLabels(boardIdTo)`: Löscht alle Karten eines Boards.
# @todo:
- Synchronisation verbessern (nur dezidierte Karten ersetzen)
- Grafisches User Interface

Binary file not shown.

View file

@ -1,4 +1,10 @@
from .lib import *
from datetime import datetime
from lib import *
# datetime object containing current date and time
now = datetime.now()
# dd/mm/YY H:M:S
dt_string = now.strftime("_%Y-%m-%d-%H-%M-%S")
boards = getBoards()
@ -6,9 +12,9 @@ boards = getBoards()
for board in boards:
boardIdFrom = board['id']
# create board
createdBoard = createBoard(board['title'], board['color'])
createdBoard = createBoard(board['title']+dt_string, board['color'])
boardIdTo = createdBoard['id']
print('Created board', board['title'])
print('Created board', board['title'], dt_string)
# create labels
boardDetails = getBoardDetails(board['id'])

View file

@ -4,52 +4,68 @@ import lib
# Argumente von der Kommandozeile einlesen
parser = argparse.ArgumentParser(description='Klonen eines bestimmten Boards von einer Nextcloud-Instanz zu einer anderen.')
parser.add_argument('--board', type=str, required=True, help='Der Titel des zu klonenden Boards.')
parser.add_argument('--replace', action='store_true', help='Ersetze das Board im Ziel, falls es bereits existiert.')
args = parser.parse_args()
# Board-Titel, den wir klonen möchten
board_to_clone = args.board
# Hole alle Boards von der Quellinstanz
boards = lib.getBoards()
boards_from = lib.getBoards()
# Finde das gewünschte Board
board_to_clone_data = next((board for board in boards if board['title'] == board_to_clone), None)
board_to_clone_data = next((board for board in boards_from if board['title'] == board_to_clone), None)
if not board_to_clone_data:
print(f'Board "{board_to_clone}" nicht gefunden.')
else:
boardIdFrom = board_to_clone_data['id']
# Erstelle das Board in der Zielinstanz
createdBoard = lib.createBoard(board_to_clone_data['title'], board_to_clone_data['color'])
boardIdTo = createdBoard['id']
print(f'Board "{board_to_clone}" erstellt')
# Kopiere die Labels des Boards
boardDetails = lib.getBoardDetails(boardIdFrom)
labelsMap = {}
for label in boardDetails['labels']:
createdLabel = lib.createLabel(label['title'], label['color'], boardIdTo)
labelsMap[label['id']] = createdLabel['id']
# Überprüfe, ob das Board im Ziel bereits existiert
boards_to = lib.getBoards('to')
existing_board_to = next((board for board in boards_to if board['title'] == board_to_clone and 0 == board['deletedAt']), None)
# Kopiere die Stacks und Karten des Boards
stacks = lib.getStacks(boardIdFrom)
stacksMap = {}
for stack in stacks:
createdStack = lib.createStack(stack['title'], stack['order'], boardIdTo)
stackIdTo = createdStack['id']
stacksMap[stack['id']] = stackIdTo
print(f' Stapel "{stack['title']}" erstellt')
# Löschen wenn der parameter --replace gesetzt wurde und das Board existiert
if args.replace and existing_board_to:
# Lösche das bestehende Board im Ziel
print(f'Lösche Board: {existing_board_to["id"]}')
lib.deleteBoard(existing_board_to['id'])
print(f'Board "{board_to_clone}" im Ziel gelöscht')
if 'cards' in stack:
for card in stack['cards']:
lib.copyCard(card, boardIdTo, stackIdTo, labelsMap)
print(f' {len(stack["cards"])} Karten erstellt')
# Erstelle das Board in der Zielinstanz, wenn es nicht existiert oder ersetzt werden soll
if not existing_board_to or args.replace:
createdBoard = lib.createBoard(board_to_clone_data['title'], board_to_clone_data['color'])
boardIdTo = createdBoard['id']
print(f'Board "{board_to_clone}" erstellt')
# Kopiere die archivierten Stacks und Karten des Boards
stacks = lib.getStacksArchived(boardIdFrom)
for stack in stacks:
if 'cards' in stack:
print(f' Stack "{stack['title']}"')
for card in stack['cards']:
lib.copyCard(card, boardIdTo, stacksMap[stack['id']], labelsMap)
print(f' {len(stack["cards"])} archivierte Karten erstellt')
# Kopiere die Labels des Boards
boardDetails = lib.getBoardDetails(boardIdFrom)
labelsMap = {}
for label in boardDetails['labels']:
createdLabel = lib.createLabel(label['title'], label['color'], boardIdTo)
labelsMap[label['id']] = createdLabel['id']
# Kopiere die Stacks und Karten des Boards
stacks = lib.getStacks(boardIdFrom)
stacksMap = {}
for stack in stacks:
createdStack = lib.createStack(stack['title'], stack['order'], boardIdTo)
stackIdTo = createdStack['id']
stacksMap[stack['id']] = stackIdTo
print(f' Stapel "{stack["title"]}" erstellt')
if 'cards' in stack:
for card in stack['cards']:
lib.copyCard(card, boardIdTo, stackIdTo, labelsMap)
print(f' {len(stack["cards"])} Karten erstellt')
# Kopiere die archivierten Stacks und Karten des Boards
stacks = lib.getStacksArchived(boardIdFrom)
for stack in stacks:
if 'cards' in stack:
print(f' Stack "{stack["title"]}"')
for card in stack['cards']:
lib.copyCard(card, boardIdTo, stacksMap[stack['id']], labelsMap)
print(f' {len(stack["cards"])} archivierte Karten erstellt')
else:
print(f'Board "{board_to_clone}" existiert bereits und wird nicht ersetzt (verwenden Sie --replace, um zu ersetzen).')

140
lib.py
View file

@ -1,9 +1,6 @@
# thanks to the awesome work of @svbergerem
# -> svbergerem/nextcloud-deck-export-import.py
# https://gist.github.com/svbergerem/5914d7f87764901aefddba125af99938
import requests
import config
import base64
urlFrom = config.urlFrom
authFrom = config.authFrom
@ -11,123 +8,66 @@ authFrom = config.authFrom
urlTo = config.urlTo
authTo = config.authTo
headers = {'OCS-APIRequest': 'true', 'Content-Type': 'application/json'}
def getBoards():
response = requests.get(
f'{urlFrom}/index.php/apps/deck/api/v1.0/boards',
auth=authFrom,
headers=headers)
def make_request(method, endpoint, from_to='from', json=None):
if from_to == 'from':
url = urlFrom
auth = authFrom
else: # from_to == 'to'
url = urlTo
auth = authTo
response = requests.request(method, f'{url}{endpoint}', auth=auth, headers=headers, json=json)
response.raise_for_status()
return response.json()
def getBoardDetails(boardId):
response = requests.get(
f'{urlFrom}/index.php/apps/deck/api/v1.0/boards/{boardId}',
auth=authFrom,
headers=headers)
response.raise_for_status()
return response.json()
def getBoards(from_to='from'):
boards = make_request('GET', '/index.php/apps/deck/api/v1.0/boards', from_to)
return [board for board in boards if 0 == board['deletedAt']]
def getStacks(boardId):
response = requests.get(
f'{urlFrom}/index.php/apps/deck/api/v1.0/boards/{boardId}/stacks',
auth=authFrom,
headers=headers)
response.raise_for_status()
return response.json()
def getBoardDetails(boardId, from_to='from'):
return make_request('GET', f'/index.php/apps/deck/api/v1.0/boards/{boardId}', from_to)
def getStacksArchived(boardId):
response = requests.get(
f'{urlFrom}/index.php/apps/deck/api/v1.0/boards/{boardId}/stacks/archived',
auth=authFrom,
headers=headers)
response.raise_for_status()
return response.json()
def getStacks(boardId, from_to='from'):
return make_request('GET', f'/index.php/apps/deck/api/v1.0/boards/{boardId}/stacks', from_to)
def getStacksArchived(boardId, from_to='from'):
return make_request('GET', f'/index.php/apps/deck/api/v1.0/boards/{boardId}/stacks/archived', from_to)
def createBoard(title, color):
response = requests.post(
f'{urlTo}/index.php/apps/deck/api/v1.0/boards',
auth=authTo,
json={
'title': title,
'color': color
},
headers=headers)
response.raise_for_status()
board = response.json()
board = make_request('POST', '/index.php/apps/deck/api/v1.0/boards', 'to', json={'title': title, 'color': color})
boardId = board['id']
# remove all default labels
for label in board['labels']:
labelId = label['id']
response = requests.delete(
f'{urlTo}/index.php/apps/deck/api/v1.0/boards/{boardId}/labels/{labelId}',
auth=authTo,
headers=headers)
response.raise_for_status()
make_request('DELETE', f'/index.php/apps/deck/api/v1.0/boards/{boardId}/labels/{labelId}', 'to')
return board
def createLabel(title, color, boardId):
response = requests.post(
f'{urlTo}/index.php/apps/deck/api/v1.0/boards/{boardId}/labels',
auth=authTo,
json={
'title': title,
'color': color
},
headers=headers)
response.raise_for_status()
return response.json()
return make_request('POST', f'/index.php/apps/deck/api/v1.0/boards/{boardId}/labels', 'to', json={'title': title, 'color': color})
def createStack(title, order, boardId):
response = requests.post(
f'{urlTo}/index.php/apps/deck/api/v1.0/boards/{boardId}/stacks',
auth=authTo,
json={
'title': title,
'order': order
},
headers=headers)
response.raise_for_status()
return response.json()
return make_request('POST', f'/index.php/apps/deck/api/v1.0/boards/{boardId}/stacks', 'to', json={'title': title, 'order': order})
def createCard(title, ctype, order, description, duedate, boardId, stackId):
response = requests.post(
f'{urlTo}/index.php/apps/deck/api/v1.0/boards/{boardId}/stacks/{stackId}/cards',
auth=authTo,
json={
'title': title,
'type': ctype,
'order': order,
'description': description,
'duedate': duedate
},
headers=headers)
response.raise_for_status()
return response.json()
try:
return make_request('POST', f'/index.php/apps/deck/api/v1.0/boards/{boardId}/stacks/{stackId}/cards', 'to',
json={'title': title, 'type': ctype, 'order': order, 'description': description, 'duedate': duedate})
except requests.exceptions.HTTPError as e:
print(f"Error creating card: {e}")
print(f"Response: {e.response.text}")
raise
def assignLabel(labelId, cardId, boardId, stackId):
response = requests.put(
f'{urlTo}/index.php/apps/deck/api/v1.0/boards/{boardId}/stacks/{stackId}/cards/{cardId}/assignLabel',
auth=authTo,
json={
'labelId': labelId
},
headers=headers)
response.raise_for_status()
make_request('PUT', f'/index.php/apps/deck/api/v1.0/boards/{boardId}/stacks/{stackId}/cards/{cardId}/assignLabel', 'to', json={'labelId': labelId})
def archiveCard(card, boardId, stackId):
cardId = card['id']
card['archived'] = True
response = requests.put(
f'{urlTo}/index.php/apps/deck/api/v1.0/boards/{boardId}/stacks/{stackId}/cards/{cardId}',
auth=authTo,
json=card,
headers=headers)
response.raise_for_status()
make_request('PUT', f'/index.php/apps/deck/api/v1.0/boards/{boardId}/stacks/{stackId}/cards/{card["id"]}', 'to', json=card)
def copyCard(card, boardIdTo, stackIdTo, labelsMap):
print(f"Copying card '{card['title']}' to board {boardIdTo}, stack {stackIdTo}")
createdCard = createCard(
card['title'],
card['type'],
@ -146,4 +86,16 @@ def copyCard(card, boardIdTo, stackIdTo, labelsMap):
if card['archived']:
archiveCard(createdCard, boardIdTo, stackIdTo)
# Löschfunktionen auf der Zielinstanz
def deleteBoard(boardId):
make_request('DELETE', f'/index.php/apps/deck/api/v1.0/boards/{boardId}', 'to')
def deleteStacks(boardId):
stacks = getStacks(boardId, 'to')
for stack in stacks:
make_request('DELETE', f'/index.php/apps/deck/api/v1.0/boards/{boardId}/stacks/{stack["id"]}', 'to')
def deleteLabels(boardId):
boardDetails = getBoardDetails(boardId, 'to')
for label in boardDetails['labels']:
make_request('DELETE', f'/index.php/apps/deck/api/v1.0/boards/{boardId}/labels/{label["id"]}', 'to')

107
sync.py Normal file
View file

@ -0,0 +1,107 @@
import argparse
import requests
import lib
# Argumente von der Kommandozeile einlesen
parser = argparse.ArgumentParser(description='Clone or sync a specific board from one Nextcloud instance to another.')
parser.add_argument('--board', type=str, required=True, help='The title of the board to clone or sync.')
args = parser.parse_args()
# Board-Titel, den wir klonen oder synchronisieren möchten
board_to_clone = args.board
# Hole alle Boards von der Quellinstanz
source_boards = lib.getBoards()
# Finde das gewünschte Board in der Quellinstanz
board_to_clone_data = next((board for board in source_boards if board['title'] == board_to_clone), None)
if not board_to_clone_data:
print(f'Board "{board_to_clone}" nicht gefunden.')
exit()
boardIdFrom = board_to_clone_data['id']
# Hole alle Boards von der Zielinstanz
target_boards = lib.getBoards('to')
# Überprüfen, ob das Board in der Zielinstanz existiert
target_board_data = next((board for board in target_boards if board['title'] == board_to_clone), None)
if target_board_data:
boardIdTo = target_board_data['id']
print(f'Board "{board_to_clone}" already exists. Syncing...')
else:
# Erstelle das Board in der Zielinstanz
createdBoard = lib.createBoard(board_to_clone_data['title'], board_to_clone_data['color'])
boardIdTo = createdBoard['id']
print(f'Created board "{board_to_clone}"')
# Kopiere oder synchronisiere die Labels des Boards
boardDetails = lib.getBoardDetails(boardIdFrom)
labelsMap = {}
target_board_details = lib.getBoardDetails(boardIdTo,'to')
# Existierende Labels in der Zielinstanz sammeln
existing_labels = {label['title']: label['id'] for label in target_board_details['labels']}
for label in boardDetails['labels']:
if label['title'] in existing_labels:
labelsMap[label['id']] = existing_labels[label['title']]
else:
createdLabel = lib.createLabel(label['title'], label['color'], boardIdTo)
labelsMap[label['id']] = createdLabel['id']
# Kopiere oder synchronisiere die Stacks und Karten des Boards
stacks = lib.getStacks(boardIdFrom)
target_stacks = lib.getStacks(boardIdTo,'to')
stacksMap = {}
# Existierende Stacks in der Zielinstanz sammeln
existing_stacks = {stack['title']: stack['id'] for stack in target_stacks}
for stack in stacks:
if stack['title'] in existing_stacks:
stackIdTo = existing_stacks[stack['title']]
stacksMap[stack['id']] = stackIdTo
print(f' Stack "{stack["title"]}" already exists. Syncing...')
else:
createdStack = lib.createStack(stack['title'], stack['order'], boardIdTo)
stackIdTo = createdStack['id']
stacksMap[stack['id']] = stackIdTo
print(f' Created stack "{stack["title"]}"')
if 'cards' in stack:
for card in stack['cards']:
try:
lib.copyCard(card, boardIdTo, stackIdTo, labelsMap)
except requests.exceptions.HTTPError as e:
print(f' Failed to create card "{card["title"]}". Error: {e}')
print(f' Response: {e.response.text}')
print(f' Created {len(stack["cards"])} cards')
# Kopiere oder synchronisiere die archivierten Stacks und Karten des Boards
archived_stacks = lib.getStacksArchived(boardIdFrom)
target_archived_stacks = lib.getStacksArchived(boardIdTo,'to')
# Existierende archivierte Stacks in der Zielinstanz sammeln
existing_archived_stacks = {stack['title']: stack['id'] for stack in target_archived_stacks}
for stack in archived_stacks:
if stack['title'] in existing_archived_stacks:
stackIdTo = existing_archived_stacks[stack['title']]
print(f' Archived stack "{stack['title']}" already exists. Syncing...')
else:
createdStack = lib.createStack(stack['title'], stack['order'], boardIdTo)
stackIdTo = createdStack['id']
stacksMap[stack['id']] = stackIdTo
print(f' Created archived stack "{stack['title']}"')
if 'cards' in stack:
for card in stack['cards']:
try:
lib.copyCard(card, boardIdTo, stackIdTo, labelsMap)
except requests.exceptions.HTTPError as e:
print(f' Failed to create archived card "{card["title"]}". Error: {e}')
print(f' Response: {e.response.text}')
print(f' Created {len(stack["cards"])} archived cards')