playing with sockets. lets stick with http for now
parent
2b66a729fd
commit
03cc320ad2
@ -0,0 +1,316 @@
|
||||
## CONFIG
|
||||
# change this to your external ip address for your server
|
||||
#(needs to be external to allow tor routing)
|
||||
SERVER_ADDR = '128.232.229.63:5555'
|
||||
|
||||
# imports
|
||||
from kivy.uix.screenmanager import Screen,ScreenManager
|
||||
from kivymd.app import MDApp
|
||||
from kivymd.uix.button import MDFillRoundFlatButton, MDIconButton
|
||||
from kivymd.uix.toolbar import MDToolbar
|
||||
from kivymd.uix.screen import MDScreen
|
||||
from kivy.lang import Builder
|
||||
from kivy.uix.boxlayout import BoxLayout
|
||||
from kivymd.theming import ThemeManager
|
||||
from kivy.properties import ObjectProperty,ListProperty
|
||||
import time,os
|
||||
from collections import OrderedDict
|
||||
from functools import partial
|
||||
from kivy.uix.screenmanager import NoTransition
|
||||
from kivymd.uix.label import MDLabel
|
||||
from kivy.uix.widget import Widget
|
||||
from kivymd.uix.list import OneLineListItem
|
||||
from kivymd.uix.card import MDCard, MDSeparator
|
||||
from kivymd.uix.boxlayout import MDBoxLayout
|
||||
from kivy.uix.gridlayout import GridLayout
|
||||
from kivy.metrics import dp
|
||||
from kivy.properties import NumericProperty
|
||||
from kivymd.uix.list import * #MDList, ILeftBody, IRightBody, ThreeLineAvatarListItem, TwoLineAvatarListItem, BaseListItem, ImageLeftWidget
|
||||
from kivy.uix.image import Image, AsyncImage
|
||||
import requests,json
|
||||
from kivy.storage.jsonstore import JsonStore
|
||||
from kivy.core.window import Window
|
||||
from kivy.core.text import LabelBase
|
||||
import shutil
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Window.size = (640, 1136) #(2.65 * 200, 5.45 * 200)
|
||||
|
||||
def log(x):
|
||||
with open('log.txt','a+') as of:
|
||||
of.write(str(x)+'\n')
|
||||
|
||||
|
||||
|
||||
class MyLayout(MDBoxLayout):
|
||||
scr_mngr = ObjectProperty(None)
|
||||
post_id = ObjectProperty()
|
||||
|
||||
def change_screen(self, screen, *args):
|
||||
self.scr_mngr.current = screen
|
||||
|
||||
def view_post(self,post_id):
|
||||
self.post_id=post_id
|
||||
self.change_screen('view')
|
||||
|
||||
|
||||
class MyBoxLayout(MDBoxLayout): pass
|
||||
class MyLabel(MDLabel): pass
|
||||
|
||||
|
||||
|
||||
|
||||
#### LOGIN
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
def get_tor_proxy_session():
|
||||
session = requests.session()
|
||||
# Tor uses the 9050 port as the default socks port
|
||||
session.proxies = {'http': 'socks5://127.0.0.1:9050',
|
||||
'https': 'socks5://127.0.0.1:9050'}
|
||||
return session
|
||||
|
||||
def get_async_tor_proxy_session():
|
||||
from requests_futures.sessions import FuturesSession
|
||||
session = FuturesSession()
|
||||
# Tor uses the 9050 port as the default socks port
|
||||
session.proxies = {'http': 'socks5://127.0.0.1:9050',
|
||||
'https': 'socks5://127.0.0.1:9050'}
|
||||
return session
|
||||
|
||||
|
||||
def get_tor_python_session():
|
||||
from torpy.http.requests import TorRequests
|
||||
with TorRequests() as tor_requests:
|
||||
with tor_requests.get_session() as s:
|
||||
return s
|
||||
|
||||
|
||||
class MainApp(MDApp):
|
||||
title = 'Komrade'
|
||||
#api = 'http://localhost:5555/api'
|
||||
api = 'http://%s/api' % SERVER_ADDR
|
||||
#api = 'http://komrades.net:5555/api'
|
||||
logged_in=False
|
||||
store = JsonStore('komrade.json')
|
||||
login_expiry = 60 * 60 * 24 * 7 # once a week
|
||||
#login_expiry = 5 # 5 seconds
|
||||
|
||||
def get_session(self):
|
||||
return get_async_tor_proxy_session()
|
||||
# return get_tor_proxy_session()
|
||||
#return get_tor_python_session()
|
||||
|
||||
def get_username(self):
|
||||
if hasattr(self,'username'): return self.username
|
||||
self.load_store()
|
||||
if hasattr(self,'username'): return self.username
|
||||
return ''
|
||||
|
||||
def build(self):
|
||||
self.username=''
|
||||
# bind
|
||||
global app,root
|
||||
app = self
|
||||
#self.username = self.store.get('userd').get('username')
|
||||
self.load_store()
|
||||
self.root = root = Builder.load_file('root.kv')
|
||||
|
||||
# edit logo
|
||||
logo=root.ids.toolbar.ids.label_title
|
||||
logo.font_name='assets/Strengthen.ttf'
|
||||
logo.font_size='58dp'
|
||||
logo.pos_hint={'center_y':0.43}
|
||||
# icons
|
||||
icons=root.ids.toolbar.ids.right_actions.children
|
||||
for icon in icons:
|
||||
#log(dir(icon))
|
||||
#icon.icon='android' #user_font_size='200sp'
|
||||
icon.font_size='58dp'
|
||||
icon.user_font_size='58dp'
|
||||
icon.width='58dp'
|
||||
icon.size_hint=(None,None)
|
||||
icon.height='58dp'
|
||||
|
||||
if not self.is_logged_in():
|
||||
self.root.change_screen('login')
|
||||
log(self.username)
|
||||
else:
|
||||
# self.root.post_id=190
|
||||
self.root.change_screen('feed')
|
||||
return self.root
|
||||
|
||||
def load_store(self):
|
||||
if not self.store.exists('user'): return
|
||||
userd=self.store.get('user')
|
||||
if not userd: userd={}
|
||||
self.logged_in_when = userd.get('logged_in_when')
|
||||
self.username = userd.get('username','')
|
||||
|
||||
def is_logged_in(self,just_check_timestamp=True, use_caching=True):
|
||||
if self.logged_in: return True
|
||||
if not use_caching: return False
|
||||
|
||||
###
|
||||
if not self.store.exists('user'): return False
|
||||
userd=self.store.get('user')
|
||||
if not userd: userd={}
|
||||
if userd.get('logged_in'):
|
||||
un=userd.get('username')
|
||||
timestamp=userd.get('logged_in_when')
|
||||
|
||||
# just a time check
|
||||
if timestamp and just_check_timestamp:
|
||||
if time.time() - timestamp < self.login_expiry:
|
||||
self.logged_in=True
|
||||
#self.username=un
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def save_login(self,un):
|
||||
self.logged_in=True
|
||||
self.username=un
|
||||
# self.store.put('username',un)
|
||||
self.store.put('user',username=un,logged_in=True,logged_in_when=time.time())
|
||||
self.root.change_screen('feed')
|
||||
|
||||
|
||||
def login(self,un=None,pw=None):
|
||||
url = self.api+'/login'
|
||||
|
||||
with self.get_session() as sess:
|
||||
#res = requests.post(url, json={'name':un, 'passkey':pw})
|
||||
res = sess.post(url, json={'name':un, 'passkey':pw})
|
||||
|
||||
if res.status_code==200:
|
||||
data=res.json()
|
||||
self.save_login(un)
|
||||
return True
|
||||
else:
|
||||
# self.root.ids.login_status.text=res.text
|
||||
return False
|
||||
|
||||
def register(self,un,pw):
|
||||
url = self.api+'/register'
|
||||
|
||||
with self.get_session() as sess:
|
||||
#res = requests.post(url, json={'name':un, 'passkey':pw})
|
||||
res = sess.post(url, json={'name':un, 'passkey':pw})
|
||||
if res.status_code==200:
|
||||
self.save_login(un)
|
||||
else:
|
||||
pass
|
||||
#self.root.ids.login_status.text=res.text
|
||||
|
||||
def post(self, content='', img_src=[]):
|
||||
log('content: '+str(content))
|
||||
log('img_src: '+str(img_src))
|
||||
|
||||
jsond = {'content':str(content)}
|
||||
|
||||
# upload?
|
||||
filename=img_src[0] if img_src and os.path.exists(img_src[0]) else ''
|
||||
|
||||
url_upload=self.api+'/upload'
|
||||
url_post = self.api+'/post'
|
||||
|
||||
server_filename=''
|
||||
media_uid=None
|
||||
|
||||
with self.get_session() as sess:
|
||||
if filename:
|
||||
log(filename)
|
||||
# copy file to cache
|
||||
|
||||
self.root.ids.add_post_screen.ids.post_status.text='Uploading file'
|
||||
with sess.post(url_upload,files={'file':open(filename,'rb')}) as r1:
|
||||
if r1.status_code==200:
|
||||
rdata1 = r1.json()
|
||||
server_filename = rdata1.get('filename','')
|
||||
media_uid=rdata1.get('media_uid')
|
||||
if server_filename:
|
||||
self.root.ids.add_post_screen.ids.post_status.text='File uploaded'
|
||||
|
||||
# pre-cache
|
||||
cache_filename = os.path.join('cache','img',server_filename)
|
||||
cache_filedir = os.path.dirname(cache_filename)
|
||||
if not os.path.exists(cache_filedir): os.makedirs(cache_filedir)
|
||||
shutil.copyfile(filename,cache_filename)
|
||||
|
||||
# add post
|
||||
self.root.ids.add_post_screen.ids.post_status.text='Creating post'
|
||||
jsond={'img_src':server_filename, 'content':content, 'username':self.username, 'media_uid':media_uid}
|
||||
|
||||
|
||||
|
||||
# post
|
||||
with sess.post(url_post, json=jsond) as r2:
|
||||
log('got back from post: ' + r2.text)
|
||||
rdata2 = r2.json()
|
||||
post_id = rdata2.get('post_id',None)
|
||||
if post_id:
|
||||
self.root.ids.add_post_screen.ids.post_status.text='Post created'
|
||||
self.root.view_post(post_id)
|
||||
|
||||
# pre-cache
|
||||
with open(os.path.join('cache','json',str(post_id)+'.json'),'w') as of:
|
||||
json.dump(jsond, of)
|
||||
|
||||
|
||||
|
||||
|
||||
def get_post(self,post_id):
|
||||
# get json from cache?
|
||||
ofn_json = os.path.join('cache','json',str(post_id)+'.json')
|
||||
if os.path.exists(ofn_json):
|
||||
with open(ofn_json) as f:
|
||||
jsond = json.load(f)
|
||||
else:
|
||||
with self.get_session() as sess:
|
||||
with sess.get(self.api+'/post/'+str(post_id)) as r:
|
||||
jsond = r.json()
|
||||
|
||||
# cache it!
|
||||
with open(ofn_json,'w') as of:
|
||||
json.dump(jsond, of)
|
||||
|
||||
return jsond
|
||||
|
||||
def get_posts(self):
|
||||
with self.get_session() as sess:
|
||||
with sess.get(self.api+'/posts') as r:
|
||||
log(r.text)
|
||||
jsond=r.json()
|
||||
return jsond['posts']
|
||||
return []
|
||||
|
||||
def get_image(self, img_src):
|
||||
# is there an image?
|
||||
if not img_src: return
|
||||
# is it cached?
|
||||
ofn_image = os.path.join('cache','img',img_src)
|
||||
if not os.path.exists(ofn_image):
|
||||
# create dir?
|
||||
ofn_image_dir = os.path.split(ofn_image)[0]
|
||||
if not os.path.exists(ofn_image_dir): os.makedirs(ofn_image_dir)
|
||||
log('getting image!')
|
||||
with self.get_session() as sess:
|
||||
with sess.get(self.api+'/download/'+img_src,stream=True) as r:
|
||||
with open(ofn_image,'wb') as of:
|
||||
shutil.copyfileobj(r.raw, of)
|
||||
return ofn_image
|
||||
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
App = MainApp()
|
||||
App.run()
|
@ -0,0 +1,102 @@
|
||||
#:include screens/login/login.kv
|
||||
#:include screens/feed/feed.kv
|
||||
#:include screens/post/post.kv
|
||||
#:include screens/messages/messages.kv
|
||||
#:include screens/notifications/notifications.kv
|
||||
|
||||
|
||||
#:import get_color_from_hex kivy.utils.get_color_from_hex
|
||||
#:import images_path kivymd.images_path
|
||||
#:import colors kivymd.color_definitions.colors
|
||||
#:import partial functools.partial
|
||||
#:import NoTransition kivy.uix.screenmanager.NoTransition
|
||||
# :import MDCarousel kivymd.uix.carousel.MDCarousel
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## CLASS DEFS
|
||||
|
||||
<MyBoxLayout>:
|
||||
orientation: "vertical"
|
||||
pos_hint: {'center_x':0.5, 'center_y':0.5}
|
||||
size_hint:0.5,0.5
|
||||
padding:'10dp'
|
||||
md_bg_color:0,0,0,1
|
||||
canvas:
|
||||
Color:
|
||||
rgb: 1,0,0,2
|
||||
Line:
|
||||
width: 1
|
||||
rectangle: (self.x, self.y, self.width, self.height)
|
||||
|
||||
|
||||
<MyLabel>:
|
||||
theme_text_color: 'Custom'
|
||||
text_color: (1,0,0,1)
|
||||
pos_hint: {'center_y': 0.5}
|
||||
halign: 'center'
|
||||
height: self.texture_size[1]
|
||||
font_family: 'Courier'
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
### LAYOUT
|
||||
|
||||
MyLayout:
|
||||
scr_mngr: scr_mngr
|
||||
orientation: 'vertical'
|
||||
height: self.minimum_height
|
||||
|
||||
canvas:
|
||||
Color:
|
||||
rgba: 0.925,0.925,0.925,1 #get_color_from_hex(colors['Gray']['900'])
|
||||
|
||||
Rectangle:
|
||||
pos: self.pos
|
||||
size: self.size
|
||||
source: 'assets/komrade2.png'
|
||||
|
||||
|
||||
MDToolbar:
|
||||
id: toolbar
|
||||
title: app.title
|
||||
pos_hint: {'center_x': .5, 'center_y': 0.95}
|
||||
md_bg_color: 0.1,0.1,0.1,1
|
||||
background_palette: 'Red'
|
||||
background_hue: '500'
|
||||
specific_text_color: 1,0,0,1
|
||||
right_action_items: [['post-outline', partial(root.change_screen, 'feed')], ['pencil-plus-outline', partial(root.change_screen, 'post')], ['message-processing-outline', partial(root.change_screen, 'messages')], ['bell-outline', partial(root.change_screen, 'notifications')], ['account-circle-outline', partial(root.change_screen, 'notifications')]]
|
||||
#left_action_items: [[f"assets/fist2.png", partial(root.change_screen, 'feed')]]
|
||||
font_context: None
|
||||
font_name: f'assets/Strengthen.ttf'
|
||||
|
||||
|
||||
ScreenManager:
|
||||
id: scr_mngr
|
||||
# transition: NoTransition()
|
||||
|
||||
LoginScreen:
|
||||
id: login_screen
|
||||
|
||||
FeedScreen:
|
||||
id: feed_screen
|
||||
|
||||
AddPostScreen:
|
||||
id: add_post_screen
|
||||
|
||||
ViewPostScreen:
|
||||
id: view_post_screen
|
||||
|
||||
MessagesScreen:
|
||||
id: messages_screen
|
||||
|
||||
NotificationsScreen:
|
||||
id: notifications_screen
|
@ -0,0 +1,10 @@
|
||||
Traceback (most recent call last):
|
||||
File "/home/ryan/github/Komrade/venv/lib/python3.6/site-packages/twisted/application/app.py", line 312, in runReactorWithLogging
|
||||
reactor.run()
|
||||
File "/home/ryan/github/Komrade/venv/lib/python3.6/site-packages/twisted/internet/base.py", line 1282, in run
|
||||
self.startRunning(installSignalHandlers=installSignalHandlers)
|
||||
File "/home/ryan/github/Komrade/venv/lib/python3.6/site-packages/twisted/internet/base.py", line 1262, in startRunning
|
||||
ReactorBase.startRunning(self)
|
||||
File "/home/ryan/github/Komrade/venv/lib/python3.6/site-packages/twisted/internet/base.py", line 765, in startRunning
|
||||
raise error.ReactorNotRestartable()
|
||||
twisted.internet.error.ReactorNotRestartable
|
@ -0,0 +1,129 @@
|
||||
from neomodel import *
|
||||
|
||||
NEO4J_USERNAME='neo4j'
|
||||
NEO4J_PASSWORD='driveyourplowoverthebonesofthedead'
|
||||
NEO4J_SERVER='localhost'
|
||||
NEO4J_PORT=7687
|
||||
|
||||
config.DATABASE_URL = f'bolt://{NEO4J_USERNAME}:{NEO4J_PASSWORD}@{NEO4J_SERVER}:{NEO4J_PORT}' # default
|
||||
config.ENCRYPTED_CONNECTION = False
|
||||
|
||||
# exit()
|
||||
# G = Graph(password='drive your plow over the bones of the dead')
|
||||
# Nodes = NodeMatcher(G)
|
||||
# Edges = RelationshipMatcher(G)
|
||||
|
||||
class Media(StructuredNode):
|
||||
uid=UniqueIdProperty()
|
||||
ext=StringProperty()
|
||||
|
||||
@property
|
||||
def filename(self):
|
||||
return self.uid[:3]+'/'+self.uid[3:]+self.ext
|
||||
|
||||
|
||||
@property
|
||||
def data(self):
|
||||
return {'uid':self.uid, 'ext':self.ext, 'filename':self.filename}
|
||||
|
||||
|
||||
class Person(StructuredNode):
|
||||
uid = UniqueIdProperty()
|
||||
name = StringProperty(unique_index=True, required=True)
|
||||
name_irl = StringProperty()
|
||||
passkey = StringProperty()
|
||||
|
||||
wrote = RelationshipTo('Post','WROTE')
|
||||
follows = RelationshipTo('Person','FOLLOWS')
|
||||
followed_by = RelationshipFrom('Person','FOLLOWS')
|
||||
located_in = RelationshipTo('Place','LOCATED_IN')
|
||||
in_group = RelationshipTo('Group','IN_GROUP')
|
||||
based_in = RelationshipTo('Place','LOCATED')
|
||||
has_avatar = RelationshipTo('Media','HAS_MEDIA')
|
||||
|
||||
@property
|
||||
def img_src_avatar(self):
|
||||
avatar = self.has_avatar.first()
|
||||
if avatar:
|
||||
return avatar.filename
|
||||
return ''
|
||||
|
||||
@property
|
||||
def data(self):
|
||||
img_avatar
|
||||
dx={'uid':self.uid, 'name':self.name, 'name_irl':self.name_irl,
|
||||
'img_src_avatar':self.img_src_avatar}
|
||||
|
||||
|
||||
class Post(StructuredNode):
|
||||
# properties
|
||||
uid = UniqueIdProperty()
|
||||
content = StringProperty()
|
||||
has_media = RelationshipTo('Media','HAS_MEDIA')
|
||||
|
||||
# relations
|
||||
written_by = RelationshipFrom('Person','WROTE')
|
||||
located_in = RelationshipTo('Place','LOCATED')
|
||||
|
||||
@property
|
||||
def author(self):
|
||||
written_by = self.written_by
|
||||
if not written_by: return None
|
||||
person=written_by[0]
|
||||
person.passkey=None
|
||||
return person
|
||||
|
||||
@property
|
||||
def img_src(self):
|
||||
print(dir(self.has_media))
|
||||
if self.has_media:
|
||||
media = self.has_media[0]
|
||||
return media.filename
|
||||
return None
|
||||
|
||||
@property
|
||||
def data(self):
|
||||
return {'uid':self.uid, 'content':self.content, 'img_src':self.img_src}
|
||||
|
||||
|
||||
class Place(StructuredNode):
|
||||
# properties
|
||||
uid = UniqueIdProperty()
|
||||
name = StringProperty(unique_index=True)
|
||||
|
||||
posted_in = RelationshipFrom('Post','LOCATED')
|
||||
based_in = RelationshipFrom('Person','LOCATED')
|
||||
|
||||
@property
|
||||
def data(self):
|
||||
return {'uid':self.uid, 'name':self.name}
|
||||
|
||||
|
||||
class Group(StructuredNode):
|
||||
uid = UniqueIdProperty()
|
||||
name = StringProperty(unique_index=True)
|
||||
motto = StringProperty()
|
||||
mission = StringProperty()
|
||||
|
||||
has_members = RelationshipFrom('Person','IN_GROUP')
|
||||
|
||||
@property
|
||||
def data(self):
|
||||
return {'uid':self.uid, 'name':self.name, 'motto':self.motto, 'mission':self.mission}
|
||||
|
||||
|
||||
|
||||
def test_models():
|
||||
jim = Person(name='Jim').save()
|
||||
post = Post(content='woooo').save()
|
||||
jim.wrote.connect(post)
|
||||
|
||||
print(dir(post))
|
||||
print(jim.uid, post.uid)
|
||||
print(list(post.written_by.all()))
|
||||
|
||||
print(post.author)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
test_models()
|
@ -0,0 +1,233 @@
|
||||
import os,time
|
||||
from pathlib import Path
|
||||
from models import *
|
||||
from werkzeug.utils import secure_filename
|
||||
from werkzeug.security import generate_password_hash,check_password_hash
|
||||
from twisted.internet import defer
|
||||
from twisted.internet.defer import ensureDeferred
|
||||
from twisted.logger import Logger
|
||||
log = Logger()
|
||||
|
||||
from klein import Klein
|
||||
|
||||
import json
|
||||
jsonify = json.dumps
|
||||
|
||||
# Start server
|
||||
|
||||
app = Klein()
|
||||
app.config = {}
|
||||
app.config["DEBUG"] = True
|
||||
app.config['SECRET_KEY'] = 'Bring out number weight & measure in a year of dearth'
|
||||
# socketio = SocketIO(app)
|
||||
app.config['UPLOAD_DIR'] = 'uploads/'
|
||||
ALLOWED_EXTENSIONS = {'png', 'jpg', 'jpeg', 'gif'}
|
||||
|
||||
|
||||
## errors
|
||||
|
||||
|
||||
@app.route('/')
|
||||
def home(request): return jsonify({'error':'404 go home friend'})
|
||||
|
||||
|
||||
# Api Functions
|
||||
|
||||
def read_request_json(request):
|
||||
json_str = str(request.content.read().decode('utf-8'))
|
||||
# print('json_str',type(json_str),json_str)
|
||||
return json.loads(json_str)
|
||||
|
||||
def get_person(name):
|
||||
print('sleeping...')
|
||||
time.sleep(10)
|
||||
person = Person.nodes.get_or_none(name=name)
|
||||
print('returning?')
|
||||
return person
|
||||
|
||||
## LOGIN
|
||||
@app.route('/api/login',methods=['POST'])
|
||||
def login(request):
|
||||
data=read_request_json(request)
|
||||
print('data',data)
|
||||
|
||||
|
||||
|
||||
name=data.get('name','')
|
||||
passkey=data.get('passkey','')
|
||||
if not (name and passkey):
|
||||
request.setResponseCode(400)
|
||||
return jsonify({'error':'Login failed'})
|
||||
|
||||
# print('sleeping...')
|
||||
# time.sleep(10)
|
||||
# person = yield Person.nodes.get_or_none(name=name)
|
||||
# print('sleeping...?')
|
||||
person = get_person(name)
|
||||
|
||||
if person is None:
|
||||
request.setResponseCode(401)
|
||||
return jsonify({'error':'User exists'})
|
||||
|
||||
real_passkey = person.passkey
|
||||
if not check_password_hash(real_passkey, passkey):
|
||||
request.setResponseCode(401)
|
||||
return jsonify({'error':'Login failed'})
|
||||
|
||||
request.setResponseCode(200)
|
||||
return jsonify({'success':'Login success'})
|
||||
|
||||
@app.route('/api/register',methods=['POST'])
|
||||
def register(request):
|
||||
data=read_request_json(request)
|
||||
|
||||
name=data.get('name','')
|
||||
passkey=data.get('passkey','')
|
||||
|
||||
if not (name and passkey):
|
||||
request.setResponseCode(400)
|
||||
return {'error':'Register failed'},status.HTTP_400_BAD_REQUEST
|
||||
|
||||
person = Person.nodes.get_or_none(name=name)
|
||||
if person is not None:
|
||||
request.setResponseCode(401)
|
||||
return {'error':'User exists'}
|
||||
|
||||
passkey = generate_password_hash(passkey)
|
||||
|
||||
person = Person(name=name,passkey=passkey).save()
|
||||
|
||||
print('REGISTERED!',data)
|
||||
return {'success':'Account created', 'username':name},status.HTTP_200_OK
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## CREATE
|
||||
|
||||
def allowed_file(filename):
|
||||
return '.' in filename and \
|
||||
filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS
|
||||
|
||||
|
||||
@app.route('/api/upload',methods=['POST'])
|
||||
def upload(request):
|
||||
files = request.files
|
||||
# check if the post request has the file part
|
||||
if 'file' not in request.files:
|
||||
return {'error':'No file found'},status.HTTP_204_NO_CONTENT
|
||||
|
||||
file = request.files['file']
|
||||
|
||||
# if user does not select file, browser also
|
||||
# submit an empty part without filename
|
||||
print('filename!',file.filename)
|
||||
if file.filename == '':
|
||||
return {'error':'No filename'},status.HTTP_206_PARTIAL_CONTENT
|
||||
|
||||
if file and allowed_file(file.filename):
|
||||
print('uploading file...')
|
||||
#prefix,filename = get_random_filename(file.filename) #secure_filename(file.filename)
|
||||
#odir = os.path.join(app.config['UPLOAD_DIR'], os.path.dirname(filename))
|
||||
#if not os.path.exists(odir):
|
||||
ext = os.path.splitext(file.filename)[-1]
|
||||
media = Media(ext=ext).save()
|
||||
uid = media.uid
|
||||
filename = media.filename
|
||||
prefix,fn=filename.split('/')
|
||||
|
||||
folder = os.path.join(app.config['UPLOAD_DIR'], prefix)
|
||||
if not os.path.exists(folder): os.makedirs(folder)
|
||||
file.save(os.path.join(folder, fn))
|
||||
|
||||
|
||||
|
||||
#return redirect(url_for('uploaded_file', filename=filename))
|
||||
return {'media_uid':uid, 'filename':filename}, status.HTTP_200_OK
|
||||
|
||||
return {'error':'Upload failed'},status.HTTP_406_NOT_ACCEPTABLE
|
||||
|
||||
|
||||
@app.route('/api/post',methods=['POST'])
|
||||
@app.route('/api/post/<post_id>',methods=['GET'])
|
||||
def post(request,post_id=None):
|
||||
|
||||
if request.method == 'POST':
|
||||
# get data
|
||||
data=read_request_json(request)
|
||||
print('POST /api/post:',data)
|
||||
|
||||
# make post
|
||||
post = Post(content=data.get('content','')).save()
|
||||
|
||||
# attach author
|
||||
name = data.get('username')
|
||||
if name:
|
||||
author = Person.nodes.get_or_none(name=name)
|
||||
author.wrote.connect(post)
|
||||
|
||||
# attach media
|
||||
media_uid=data.get('media_uid')
|
||||
if media_uid:
|
||||
media=Media.nodes.get_or_none(uid=media_uid)
|
||||
post.has_media.connect(media)
|
||||
|
||||
|
||||
|
||||
# return
|
||||
post_id=post.uid
|
||||
print('created new post!',post_id)
|
||||
return {'post_id':post_id},status.HTTP_200_OK
|
||||
|
||||
print('got post id!',post_id)
|
||||
post = Post.nodes.get_or_none(uid=post_id)
|
||||
if not post: return {},status.HTTP_204_NO_CONTENT
|
||||
return post.data,status.HTTP_200_OK
|
||||
|
||||
@app.route('/api/download/<prefix>/<filename>',methods=['GET'])
|
||||
def download(request, prefix, filename):
|
||||
filedir = os.path.join(app.config['UPLOAD_DIR'], prefix)
|
||||
print(filedir, filename)
|
||||
return send_from_directory(filedir, filename)
|
||||
|
||||
### READ
|
||||
|
||||
|
||||
@app.route('/api/followers/<name>')
|
||||
def get_followers(request, name=None):
|
||||
person = Person.match(G, name).first()
|
||||
data = [p.data for p in person.followers]
|
||||
return jsonify(data)
|
||||
|
||||
@app.route('/api/followers/<name>')
|
||||
def get_follows(request, name=None):
|
||||
person = Person.match(G, name).first()
|
||||
data = [p.data for p in person.follows]
|
||||
return jsonify(data)
|
||||
|
||||
|
||||
@app.route('/api/posts')
|
||||
@app.route('/api/posts/<name>')
|
||||
def get_posts(request, name=None):
|
||||
if name:
|
||||
person = Person.nodes.get_or_none(name=name)
|
||||
data = [p.data for p in person.wrote.all] if person is not None else []
|
||||
else:
|
||||
data = [p.data for p in Post.nodes.all()]
|
||||
# print(data)
|
||||
|
||||
return jsonify({'posts':data})
|
||||
|
||||
@app.route('/api/post/<int:id>')
|
||||
def get_post(request, id=None):
|
||||
post = Post.match(G, int(id)).first()
|
||||
data = post.data
|
||||
return jsonify(data)
|
||||
|
||||
|
||||
|
||||
if __name__=='__main__':
|
||||
app.run(host='0.0.0.0', port=5555)
|
||||
# socketio.run(app, host='0.0.0.0', port=5000)
|
@ -0,0 +1,204 @@
|
||||
import flask,os
|
||||
from flask import request, jsonify, send_from_directory
|
||||
from pathlib import Path
|
||||
from models import *
|
||||
from flask_api import FlaskAPI, status, exceptions
|
||||
from werkzeug.security import generate_password_hash,check_password_hash
|
||||
from flask import Flask, flash, request, redirect, url_for
|
||||
from werkzeug.utils import secure_filename
|
||||
|
||||
|
||||
# works better with tor?
|
||||
import json
|
||||
jsonify = json.dumps
|
||||
# jsonify = str
|
||||
|
||||
# Start server
|
||||
|
||||
app = flask.Flask(__name__)
|
||||
app.config["DEBUG"] = True
|
||||
app.config['UPLOAD_DIR'] = 'uploads/'
|
||||
ALLOWED_EXTENSIONS = {'png', 'jpg', 'jpeg', 'gif'}
|
||||
|
||||
@app.route('/')
|
||||
def home(): return {'error':'404 go home friend'}
|
||||
|
||||
|
||||
# Api Functions
|
||||
|
||||
## LOGIN
|
||||
@app.route('/api/login',methods=['POST'])
|
||||
def login():
|
||||
data=request.json
|
||||
|
||||
name=data.get('name','')
|
||||
passkey=data.get('passkey','')
|
||||
if not (name and passkey):
|
||||
return {'error':'Login failed'},status.HTTP_400_BAD_REQUEST
|
||||
|
||||
person = Person.nodes.get_or_none(name=name)
|
||||
if person is None:
|
||||
return {'error':'User exists'},status.HTTP_401_UNAUTHORIZED
|
||||
|
||||
real_passkey = person.passkey
|
||||
if not check_password_hash(real_passkey, passkey):
|
||||
return {'error':'Login failed'},status.HTTP_401_UNAUTHORIZED
|
||||
|
||||
return {'success':'Login success'},status.HTTP_200_OK
|
||||
|
||||
@app.route('/api/register',methods=['POST'])
|
||||
def register():
|
||||
data=request.json
|
||||
|
||||
name=data.get('name','')
|
||||
passkey=data.get('passkey','')
|
||||
|
||||
if not (name and passkey):
|
||||
return {'error':'Register failed'},status.HTTP_400_BAD_REQUEST
|
||||
|
||||
person = Person.nodes.get_or_none(name=name)
|
||||
if person is not None:
|
||||
return {'error':'User exists'},status.HTTP_401_UNAUTHORIZED
|
||||
|
||||
passkey = generate_password_hash(passkey)
|
||||
|
||||
person = Person(name=name,passkey=passkey).save()
|
||||
|
||||
print('REGISTERED!',data)
|
||||
return {'success':'Account created', 'username':name},status.HTTP_200_OK
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## CREATE
|
||||
|
||||
def allowed_file(filename):
|
||||
return '.' in filename and \
|
||||
filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS
|
||||
|
||||
def get_random_filename(filename):
|
||||
import uuid
|
||||
fn=uuid.uuid4().hex
|
||||
return (fn[:3],fn[3:]+os.path.splitext(filename)[-1])
|
||||
|
||||
@app.route('/api/upload',methods=['POST'])
|
||||
def upload():
|
||||
files = request.files
|
||||
# check if the post request has the file part
|
||||
if 'file' not in request.files:
|
||||
return {'error':'No file found'},status.HTTP_204_NO_CONTENT
|
||||
|
||||
file = request.files['file']
|
||||
|
||||
# if user does not select file, browser also
|
||||
# submit an empty part without filename
|
||||
print('filename!',file.filename)
|
||||
if file.filename == '':
|
||||
return {'error':'No filename'},status.HTTP_206_PARTIAL_CONTENT
|
||||
|
||||
if file and allowed_file(file.filename):
|
||||
print('uploading file...')
|
||||
#prefix,filename = get_random_filename(file.filename) #secure_filename(file.filename)
|
||||
#odir = os.path.join(app.config['UPLOAD_DIR'], os.path.dirname(filename))
|
||||
#if not os.path.exists(odir):
|
||||
ext = os.path.splitext(file.filename)[-1]
|
||||
media = Media(ext=ext).save()
|
||||
uid = media.uid
|
||||
filename = media.filename
|
||||
prefix,fn=filename.split('/')
|
||||
|
||||
folder = os.path.join(app.config['UPLOAD_DIR'], prefix)
|
||||
if not os.path.exists(folder): os.makedirs(folder)
|
||||
file.save(os.path.join(folder, fn))
|
||||
|
||||
|
||||
|
||||
#return redirect(url_for('uploaded_file', filename=filename))
|
||||
return {'media_uid':uid, 'filename':filename}, status.HTTP_200_OK
|
||||
|
||||
return {'error':'Upload failed'},status.HTTP_406_NOT_ACCEPTABLE
|
||||
|
||||
|
||||
@app.route('/api/post',methods=['POST'])
|
||||
@app.route('/api/post/<post_id>',methods=['GET'])
|
||||
def post(post_id=None):
|
||||
|
||||
if request.method == 'POST':
|
||||
# get data
|
||||
data=request.json
|
||||
print('POST /api/post:',data)
|
||||
|
||||
# make post
|
||||
post = Post(content=data.get('content','')).save()
|
||||
|
||||
# attach author
|
||||
name = data.get('username')
|
||||
if name:
|
||||
author = Person.nodes.get_or_none(name=name)
|
||||
author.wrote.connect(post)
|
||||
|
||||
# attach media
|
||||
media_uid=data.get('media_uid')
|
||||
if media_uid:
|
||||
media=Media.nodes.get_or_none(uid=media_uid)
|
||||
post.has_media.connect(media)
|
||||
|
||||
|
||||
|
||||
# return
|
||||
post_id=post.uid
|
||||
print('created new post!',post_id)
|
||||
return {'post_id':post_id},status.HTTP_200_OK
|
||||
|
||||
print('got post id!',post_id)
|
||||
post = Post.nodes.get_or_none(uid=post_id)
|
||||
if not post: return {},status.HTTP_204_NO_CONTENT
|
||||
return post.data,status.HTTP_200_OK
|
||||
|
||||
@app.route('/api/download/<prefix>/<filename>',methods=['GET'])
|
||||
def download(prefix, filename):
|
||||
filedir = os.path.join(app.config['UPLOAD_DIR'], prefix)
|
||||
print(filedir, filename)
|
||||
return send_from_directory(filedir, filename)
|
||||
|
||||
### READ
|
||||
|
||||
|
||||
@app.route('/api/followers/<name>')
|
||||
def get_followers(name=None):
|
||||
person = Person.match(G, name).first()
|
||||
data = [p.data for p in person.followers]
|
||||
return jsonify(data)
|
||||
|
||||
@app.route('/api/followers/<name>')
|
||||
def get_follows(name=None):
|
||||
person = Person.match(G, name).first()
|
||||
data = [p.data for p in person.follows]
|
||||
return jsonify(data)
|
||||
|
||||
|
||||
@app.route('/api/posts')
|
||||
@app.route('/api/posts/<name>')
|
||||
def get_posts(name=None):
|
||||
if name:
|
||||
person = Person.nodes.get_or_none(name=name)
|
||||
data = [p.data for p in person.wrote.all] if person is not None else []
|
||||
else:
|
||||
data = [p.data for p in Post.nodes.all()]
|
||||
# print(data)
|
||||
|
||||
return jsonify({'posts':data})
|
||||
|
||||
@app.route('/api/post/<int:id>')
|
||||
def get_post(id=None):
|
||||
post = Post.match(G, int(id)).first()
|
||||
data = post.data
|
||||
return jsonify(data)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
app.run(host='0.0.0.0', port=5555)
|
@ -1,110 +1,129 @@
|
||||
from neomodel import *
|
||||
|
||||
NEO4J_USERNAME='neo4j'
|
||||
NEO4J_PASSWORD='drive your plow over the bones of the dead'
|
||||
NEO4J_PASSWORD='driveyourplowoverthebonesofthedead'
|
||||
NEO4J_SERVER='localhost'
|
||||
NEO4J_PORT=7687
|
||||
|
||||
config.DATABASE_URL = f'bolt://{NEO4J_PASSWORD}:{NEO4J_PASSWORD}@{NEO4J_SERVER}:{NEO4J_PORT}' # default
|
||||
config.DATABASE_URL = f'bolt://{NEO4J_USERNAME}:{NEO4J_PASSWORD}@{NEO4J_SERVER}:{NEO4J_PORT}' # default
|
||||
config.ENCRYPTED_CONNECTION = False
|
||||
|
||||
exit()
|
||||
G = Graph(password='drive your plow over the bones of the dead')
|
||||
Nodes = NodeMatcher(G)
|
||||
Edges = RelationshipMatcher(G)
|
||||
# exit()
|
||||
# G = Graph(password='drive your plow over the bones of the dead')
|
||||
# Nodes = NodeMatcher(G)
|
||||
# Edges = RelationshipMatcher(G)
|
||||
|
||||
class Media(StructuredNode):
|
||||
uid=UniqueIdProperty()
|
||||
ext=StringProperty()
|
||||
|
||||
|
||||
class MyGraphObject(GraphObject):
|
||||
@property
|
||||
def data(self):
|
||||
return dict(self.__ogm__.node)
|
||||
def filename(self):
|
||||
return self.uid[:3]+'/'+self.uid[3:]+self.ext
|
||||
|
||||
class Person(MyGraphObject):
|
||||
__primarykey__ = 'name'
|
||||
|
||||
name = Property()
|
||||
name_irl = Property()
|
||||
passkey = Property()
|
||||
|
||||
posts = RelatedTo('Post','WROTE')
|
||||
follows = RelatedTo('Person','FOLLOWS')
|
||||
followers = RelatedFrom('Person','FOLLOWS')
|
||||
based_in = RelatedTo('Place','BASED_IN')
|
||||
groups = RelatedTo('Group','IN_GROUP')
|
||||
avatar = RelatedTo('Media','HAS_MEDIA')
|
||||
@property
|
||||
def data(self):
|
||||
return {'uid':self.uid, 'ext':self.ext, 'filename':self.filename}
|
||||
|
||||
class Post(MyGraphObject):
|
||||
# properties
|
||||
title = Property()
|
||||
content = Property()
|
||||
img_src = Property()
|
||||
|
||||
# relations
|
||||
author = RelatedFrom('Person','WROTE')
|
||||
location = RelatedTo('Place','BASED_IN')
|
||||
class Person(StructuredNode):
|
||||
uid = UniqueIdProperty()
|
||||
name = StringProperty(unique_index=True, required=True)
|
||||
name_irl = StringProperty()
|
||||
passkey = StringProperty()
|
||||
|
||||
wrote = RelationshipTo('Post','WROTE')
|
||||
follows = RelationshipTo('Person','FOLLOWS')
|
||||
followed_by = RelationshipFrom('Person','FOLLOWS')
|
||||
located_in = RelationshipTo('Place','LOCATED_IN')
|
||||
in_group = RelationshipTo('Group','IN_GROUP')
|
||||
based_in = RelationshipTo('Place','LOCATED')
|
||||
has_avatar = RelationshipTo('Media','HAS_MEDIA')
|
||||
|
||||
@property
|
||||
def img_src_avatar(self):
|
||||
avatar = self.has_avatar.first()
|
||||
if avatar:
|
||||
return avatar.filename
|
||||
return ''
|
||||
|
||||
@property
|
||||
def data(self):
|
||||
dx=dict(self.__ogm__.node)
|
||||
|
||||
authors=list(self.author)
|
||||
locations=list(self.location)
|
||||
dx['author']=authors[0].name if authors else ''
|
||||
dx['location']=locations[0].name if locations else ''
|
||||
img_avatar
|
||||
dx={'uid':self.uid, 'name':self.name, 'name_irl':self.name_irl,
|
||||
'img_src_avatar':self.img_src_avatar}
|
||||
|
||||
return dx
|
||||
|
||||
class Place(MyGraphObject):
|
||||
class Post(StructuredNode):
|
||||
# properties
|
||||
__primarykey__ = 'name'
|
||||
name = Property()
|
||||
|
||||
# relations
|
||||
citizens = RelatedFrom('Person','BASED_IN')
|
||||
|
||||
class Group(MyGraphObject):
|
||||
# properties
|
||||
__primarykey__ = 'name'
|
||||
name = Property()
|
||||
motto = Property()
|
||||
mission = Property()
|
||||
uid = UniqueIdProperty()
|
||||
content = StringProperty()
|
||||
has_media = RelationshipTo('Media','HAS_MEDIA')
|
||||
|
||||
# relations
|
||||
members = RelatedFrom('Person','IN_GROUP')
|
||||
written_by = RelationshipFrom('Person','WROTE')
|
||||
located_in = RelationshipTo('Place','LOCATED')
|
||||
|
||||
@property
|
||||
def author(self):
|
||||
written_by = self.written_by
|
||||
if not written_by: return None
|
||||
person=written_by[0]
|
||||
person.passkey=None
|
||||
return person
|
||||
|
||||
@property
|
||||
def img_src(self):
|
||||
print(dir(self.has_media))
|
||||
if self.has_media:
|
||||
media = self.has_media[0]
|
||||
return media.filename
|
||||
return None
|
||||
|
||||
def test_models():
|
||||
x = Person(); x.name='MrX'
|
||||
y = Person(); y.name='MrY'
|
||||
p1 = Post(); p1.title='Post 1'; p1.content='Hello world!'
|
||||
p2 = Post(); p2.title='Post 2'; p2.content='Hello world!!!'
|
||||
p3 = Post(); p3.title='Post 3'; p3.content='Hello world!!!!!!!!!!!!'
|
||||
@property
|
||||
def data(self):
|
||||
return {'uid':self.uid, 'content':self.content, 'img_src':self.img_src}
|
||||
|
||||
x.follows.add(y)
|
||||
x.posts.add(p1)
|
||||
y.posts.add(p2)
|
||||
G.push(y)
|
||||
|
||||
y.posts.add(p3)
|
||||
class Place(StructuredNode):
|
||||
# properties
|
||||
uid = UniqueIdProperty()
|
||||
name = StringProperty(unique_index=True)
|
||||
|
||||
posted_in = RelationshipFrom('Post','LOCATED')
|
||||
based_in = RelationshipFrom('Person','LOCATED')
|
||||
|
||||
g1=Group(); g1.name='BlocBloc'
|
||||
c1=Place(); c1.name='Utopia'
|
||||
c2=Place(); c2.name='Dystopia'
|
||||
@property
|
||||
def data(self):
|
||||
return {'uid':self.uid, 'name':self.name}
|
||||
|
||||
|
||||
class Group(StructuredNode):
|
||||
uid = UniqueIdProperty()
|
||||
name = StringProperty(unique_index=True)
|
||||
motto = StringProperty()
|
||||
mission = StringProperty()
|
||||
|
||||
x.based_in.add(c1)
|
||||
y.based_in.add(c2)
|
||||
has_members = RelationshipFrom('Person','IN_GROUP')
|
||||
|
||||
x.groups.add(g1)
|
||||
y.groups.add(g1)
|
||||
@property
|
||||
def data(self):
|
||||
return {'uid':self.uid, 'name':self.name, 'motto':self.motto, 'mission':self.mission}
|
||||
|
||||
|
||||
|
||||
for a in [x,y,p1,p2,p3,g1,c1,c2]: G.push(a)
|
||||
|
||||
def test_models():
|
||||
jim = Person(name='Jim').save()
|
||||
post = Post(content='woooo').save()
|
||||
jim.wrote.connect(post)
|
||||
|
||||
print(dir(post))
|
||||
print(jim.uid, post.uid)
|
||||
print(list(post.written_by.all()))
|
||||
|
||||
# test_models()
|
||||
print(post.author)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
test_models()
|
@ -0,0 +1,5 @@
|
||||
from gevent.pywsgi import WSGIServer
|
||||
from server import app
|
||||
|
||||
http_server = WSGIServer(('', 5555), app)
|
||||
http_server.serve_forever()
|
@ -1 +1,3 @@
|
||||
python3 server.py
|
||||
# gunicorn -w 4 -b 0.0.0.0:5555 server:app
|
||||
# python run.py
|
||||
python server.py
|
@ -0,0 +1,6 @@
|
||||
2020-08-12T14:13:34+0100 [twisted.logger._global#warn] Warning: primary log target selected twice at </home/ryan/github/Komrade/venv/lib/python3.6/site-packages/twisted/application/app.py:213> - previously selected at </home/ryan/github/Komrade/venv/lib/python3.6/site-packages/twisted/python/log.py:214>. Remove one of the calls to beginLoggingTo.
|
||||
2020-08-12T14:13:34+0100 [twisted.scripts._twistd_unix.UnixAppLogger#info] twistd 20.3.0 (/home/ryan/github/Komrade/venv/bin/python 3.6.9) starting up.
|
||||
2020-08-12T14:13:34+0100 [twisted.scripts._twistd_unix.UnixAppLogger#info] reactor class: twisted.internet.epollreactor.EPollReactor.
|
||||
2020-08-12T14:13:34+0100 [-] Site starting on 8080
|
||||
2020-08-12T14:13:34+0100 [twisted.web.server.Site#info] Starting factory <twisted.web.server.Site object at 0x7f91e00ba400>
|
||||
2020-08-12T14:13:34+0100 [twisted.scripts._twistd_unix.UnixAppLogger#info] Server Shut Down.
|
Loading…
Reference in New Issue