Skip to content

Commit 40ea359

Browse files
committed
Init
1 parent 655feb5 commit 40ea359

10 files changed

Lines changed: 223 additions & 2 deletions

File tree

.github/workflows/linux.yml

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
# This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node
2+
# For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions
3+
4+
name: test
5+
6+
on:
7+
push:
8+
branches: [ release, test ]
9+
10+
jobs:
11+
build:
12+
13+
runs-on: ${{ matrix.os }}
14+
environment: build
15+
if: "!contains(github.event.head_commit.message, 'skip ci') && !contains(github.event.head_commit.message, 'skip linux')"
16+
17+
strategy:
18+
matrix:
19+
os: [ubuntu-latest]
20+
21+
steps:
22+
- uses: actions/checkout@v2
23+
24+
# script:
25+
- run: cp sample.env .env
26+
- run: bin/test
27+

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,3 +158,4 @@ cython_debug/
158158
# and can be added to the global gitignore or merged into this file. For a more nuclear
159159
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
160160
#.idea/
161+
/temp/

README.md

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,42 @@
1-
# electerm-sync-server-python
2-
electerm-sync-server-python
1+
# Nodejs Electerm sync server
2+
3+
[![Build Status](https://img.shields.io/endpoint.svg?url=https%3A%2F%2Factions-badge.atrox.dev%2Fatrox%2Fsync-dotenv%2Fbadge)](https://github.com/electerm/electerm-sync-server-python/actions)
4+
5+
A simple electerm data sync server with python.
6+
7+
## Use
8+
9+
Requires python3
10+
11+
```bash
12+
git clone git@github.com:electerm/electerm-sync-server-python.git
13+
cd electerm-sync-server-python
14+
pip install -r requirements.txt
15+
16+
# create env file, then edit .env
17+
cp sample.env .env
18+
19+
python src/app.py
20+
21+
# would show something like
22+
# server running at http://127.0.0.1:7837
23+
24+
# in electerm sync settings, set custom sync server with:
25+
# server url: http://127.0.0.1:7837
26+
# JWT_SECRET: your JWT_SECRET in .env
27+
# JWT_USER_NAME: one JWT_USER in .env
28+
```
29+
30+
## Test
31+
32+
```bash
33+
bin/test
34+
```
35+
36+
## Write your own data store
37+
38+
Just take [src/file-store.js](src/file-store.js) as an example, write your own read/write method
39+
40+
## License
41+
42+
MIT

bin/release

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
#!/bin/bash
2+
cd `dirname $0`
3+
cd ..
4+
git co main
5+
git pull
6+
git pull
7+
git delete-branch release
8+
git create-branch release
9+
git push origin release -u
10+
git co -

bin/test

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
#!/bin/bash
2+
cd `dirname $0`
3+
cd ..
4+
python tests/test.py

requirements.txt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
flask
2+
python-dotenv
3+
pylint
4+
flask_jwt_extended
5+
flask_jwt

sample.env

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
# server port
2+
PORT=7837
3+
4+
# server host
5+
HOST=127.0.0.1
6+
7+
# jwt secret, make sure you change it in production
8+
JWT_SECRET=283hsdfye@!2@9oijnjSwda09
9+
JWT_USERS=username1,username2,xxxx,hhhh
10+
# FILE_STORE_PATH=/home/some/folder

src/app.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
from flask import Flask, jsonify, request
2+
from flask_jwt_extended import JWTManager, jwt_required, get_jwt_identity
3+
from dotenv import load_dotenv
4+
from file_store import read, write
5+
import os
6+
7+
load_dotenv()
8+
9+
app = Flask(__name__)
10+
app.config['JWT_SECRET_KEY'] = os.environ['JWT_SECRET']
11+
app.config['JWT_IDENTITY_CLAIM'] = 'id'
12+
jwt = JWTManager(app)
13+
14+
@app.route('/api/sync', methods=['GET', 'PUT'])
15+
@jwt_required()
16+
def sync():
17+
user_id = get_jwt_identity()
18+
users = os.environ['JWT_USERS'].split(',')
19+
if user_id not in users:
20+
return jsonify({'status': 'error', 'message': 'Unauthorized!'}), 401
21+
22+
if request.method == 'GET':
23+
return read(user_id)
24+
25+
if request.method == 'PUT':
26+
data = request.get_json()
27+
return write(data, user_id)
28+
29+
return jsonify({'status': 'error', 'message': 'Unsupported method!'}), 405
30+
31+
@app.route('/test', methods=['GET'])
32+
def test():
33+
return 'ok'
34+
35+
36+
if __name__ == '__main__':
37+
port = os.environ['PORT']
38+
host = os.environ['HOST']
39+
app.run(
40+
host=host,
41+
port=port,
42+
debug=True,
43+
load_dotenv=True
44+
)

src/file_store.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import os
2+
import json
3+
4+
file_dir = os.environ.get('FILE_STORE_PATH') or os.getcwd()
5+
6+
def write(data, user_id):
7+
json_body = json.dumps(data or {})
8+
file_path = os.path.join(file_dir, f"{user_id}.json")
9+
with open(file_path, 'w') as f:
10+
f.write(json_body)
11+
return "ok", 200
12+
13+
def read(user_id):
14+
file_path = os.path.join(file_dir, f"{user_id}.json")
15+
if os.path.isfile(file_path):
16+
with open(file_path, 'r') as f:
17+
file_data = json.load(f)
18+
return file_data, 200
19+
else:
20+
return "File not found", 404

tests/test.py

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import unittest
2+
import json
3+
import tempfile
4+
import os
5+
import sys
6+
from dotenv import load_dotenv
7+
8+
load_dotenv()
9+
sys.path.append(os.path.dirname(os.path.abspath(__file__)) + "/..")
10+
11+
from src.file_store import read, write
12+
13+
class TestExample(unittest.TestCase):
14+
15+
def setUp(self):
16+
self.test_user_id = 'test_user_id'
17+
self.test_data = {'key': 'value'}
18+
19+
def test_write(self):
20+
# Use temporary file for testing
21+
with tempfile.NamedTemporaryFile() as f:
22+
23+
# Write to file
24+
write_status, write_code = write(self.test_data, self.test_user_id)
25+
26+
# Test status and code
27+
self.assertEqual(write_status, 'ok')
28+
self.assertEqual(write_code, 200)
29+
30+
# Read from file
31+
read_data, read_code = read(self.test_user_id)
32+
33+
# Test read data and code
34+
self.assertEqual(read_data, self.test_data)
35+
self.assertEqual(read_code, 200)
36+
37+
def test_read_not_found(self):
38+
# Use temporary file for testing
39+
with tempfile.TemporaryDirectory() as tempdir:
40+
os.environ['FILE_STORE_PATH'] = tempdir
41+
42+
# Read from unexisting file
43+
read_data, read_code = read(self.test_user_id + '0000')
44+
45+
# Test not found data and code
46+
self.assertEqual(read_data, 'File not found')
47+
self.assertEqual(read_code, 404)
48+
49+
def test_read(self):
50+
# Use temporary file for testing
51+
with tempfile.TemporaryDirectory() as tempdir:
52+
# Read from unexisting file
53+
read_data, read_code = read(self.test_user_id)
54+
55+
# Test not found data and code
56+
self.assertEqual(read_data, self.test_data)
57+
self.assertEqual(read_code, 200)
58+
59+
if __name__ == '__main__':
60+
unittest.main()

0 commit comments

Comments
 (0)