Skip to content

Commit

Permalink
Merge pull request #138 from mitama-org/feature/push-socket
Browse files Browse the repository at this point in the history
Feature/push socket
  • Loading branch information
boke0 authored Mar 5, 2021
2 parents 6e0a4d4 + 9b67e8f commit 78ee3ce
Show file tree
Hide file tree
Showing 26 changed files with 777 additions and 54 deletions.
4 changes: 2 additions & 2 deletions mitama/app/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,8 @@ def __call__(self, request):
def set_middleware(self, middlewares):
self.app.middlewares.extend(middlewares)

def convert_fullurl(self, req, url):
scheme = req.scheme
def convert_fullurl(self, req, url, scheme=None):
scheme = scheme if scheme is not None else req.scheme
hostname = req.host
path = self.path
if path[0] != "/":
Expand Down
16 changes: 10 additions & 6 deletions mitama/app/http/__init__.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
import base64
import re
import ssl
from http.server import HTTPServer
from socketserver import StreamRequestHandler
from wsgiref import simple_server

from gevent.pywsgi import WSGIServer
from geventwebsocket.handler import WebSocketHandler

from .request import Request
from .response import Response


def run_app(app, port, request_factory=Request.parse_stream):
with simple_server.make_server("", int(port), app.wsgi) as server:
server.serve_forever()
def run_app(app, port):
server = WSGIServer(
("localhost", int(port)),
app.wsgi,
handler_class=WebSocketHandler
)
server.serve_forever()
8 changes: 8 additions & 0 deletions mitama/app/http/request.py
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,14 @@ def body(self):
self._body = self._rfile.read(int(length))
return self._body

@property
def websocket(self):
if hasattr(self, "_websocket"):
return self._websocket
else:
self._websocket = self.environ.get("wsgi.websocket")
return self._websocket

def post(self):
if hasattr(self, "_post"):
return self._post
Expand Down
7 changes: 7 additions & 0 deletions mitama/app/static/sw.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
self.addEventListener('push', function(event) {
if (event.data) {
let data = event.data.json();
let title = data.title;
event.waitUntil(self.registration.showNotification(title, data))
}
})
2 changes: 1 addition & 1 deletion mitama/app/templates/500.html
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
{% extends 'base/plain' %}
{% extends 'base/error' %}
{% block body %}
<div id='content'>
<h1>500 Internal Server Error</h1>
Expand Down
18 changes: 18 additions & 0 deletions mitama/app/templates/base/error
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<!DOCTYPE html>
<html>
<head>
<meta charset='utf-8'>
<meta name='viewport' content='width=device-width, initial-scale=1'>
<title>{{ title }}</title>
<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-giJF6kkoqNQ00vy+HMDP7azOuL0xtbfIcaT9wjKHr8RbDVddVHyTfAAsrekwKmP1" crossorigin="anonymous">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/font/bootstrap-icons.css">
<link rel="stylesheet" href="/static/mitama-style.css">
{% block head %}
{% endblock %}
</head>
<body>
{% block body %}
{% endblock %}
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js" integrity="sha384-ygbV9kiqUc6oa4msXn9868pTtWMgiQaeYH7/t7LECLbyPA2x65Kgf80OJFdroafW" crossorigin="anonymous"></script>
</body>
</html>
4 changes: 2 additions & 2 deletions mitama/db/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,12 +90,12 @@ def update(self):
pass

def delete(self):
self.query.session.delete(self)
self.query.session.commit()
try:
self.on("delete")()
except Exception:
pass
self.query.session.delete(self)
self.query.session.commit()

def on(self, evt):
return getattr(self, evt)
Expand Down
2 changes: 1 addition & 1 deletion mitama/models/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
from mitama._extra import _classproperty

from .core_db import db
from .nodes import User, Group, Node, UserGroup, UserInvite, AuthorizationError, Role, InnerRole
from .nodes import User, Group, Node, UserGroup, UserInvite, AuthorizationError, Role, InnerRole, PushSubscription
from .permissions import permission, inner_permission

Permission = permission(db, [
Expand Down
24 changes: 24 additions & 0 deletions mitama/models/nodes.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@
import random
import secrets
import smtplib
import pywebpush

import bcrypt
import jwt
import magic
import json
from Crypto.Random import get_random_bytes
from Crypto.Hash import SHA256

Expand Down Expand Up @@ -266,6 +268,9 @@ def is_ancestor(self, node):
layer = layer_
return False

def push(self, data):
for subscription in self.subscriptions:
subscription.push(data)

class Group(AbstractNode, db.Model):
"""グループのモデルクラスです
Expand Down Expand Up @@ -574,3 +579,22 @@ def retrieve(cls, obj=None, id=None, **kwargs):
@property
def object(self):
return self.user if self.user is not None else self.group

class PushSubscription(db.Model):
__tablename__ = "mitama_push_subscription"
user_id = Column(String(64), ForeignKey("mitama_user._id", ondelete="CASCADE"))
user = relationship("User", backref="subscriptions")
subscription = Column(String(1024))

def push(self, data):
try:
webpush(
subscription_info=json.loads(self.subscription),
data=data,
vapid_private_key=self.project.vapid.private_key,
vapid_claims={
"sub": "mailto:{}".format(self.project.vapid.mailto)
}
)
except Exception as err:
print(err)
78 changes: 63 additions & 15 deletions mitama/portal/controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
Permission,
InnerPermission,
UserGroup,
PushSubscription
)
from mitama.app.forms import ValidationError
from mitama.noimage import load_noimage_group, load_noimage_user
Expand All @@ -25,6 +26,8 @@
RegisterForm,
InviteForm,
UserUpdateForm,
UserPasswordUpdateForm,
SubscriptionForm,
GroupCreateForm,
GroupUpdateForm,
AppUpdateForm,
Expand Down Expand Up @@ -96,7 +99,6 @@ def signup(self, request):
user.name = form["name"]
user.icon = resize_icon(form["icon"]) if form["icon"] is not None else user.icon
user.create()
print(user._id)
sess["jwt_token"] = user.get_jwt()
roles = invite.roles;
if len(roles) > 0:
Expand Down Expand Up @@ -334,10 +336,11 @@ def retrieve(self, req):
},
)

def update(self, req):
template = self.view.get_template("user/update.html")
def update_profile(self, req):
template = self.view.get_template("user/update/profile.html")
user = User.retrieve(screen_name=req.params["id"])
roles = Role.list()
error = ""
if req.method == "POST":
form = UserUpdateForm(req.post())
try:
Expand All @@ -352,25 +355,70 @@ def update(self, req):
return Response.redirect(self.app.convert_url("/users/" + user.screen_name + "/settings"))
except Exception as err:
error = str(err)
return Response.render(
template,
{
"error": error,
"user": user,
"screen_name": form["screen_name"] or user.screen_name,
"name": form["name"] or user.name,
"icon": resize_icon(form["icon"]),
"roles": roles
},
)
return Response.render(
template,
{
"user": user,
"screen_name": user.screen_name,
"name": user.name,
"icon": user.icon,
"roles": roles
"roles": roles,
"error": error
},
)

def update_password(self, req):
template = self.view.get_template("user/update/password.html")
user = User.retrieve(screen_name=req.params["id"])
if user._id != req.user._id:
return Response.redirect(self.app.convert_url("/"))
error = ""
if req.method == "POST":
form = UserPasswordUpdateForm(req.post())
try:
if form["password"] != form["password_"]:
raise Exception("パスワードが一致しません")
user.set_password(form["password"])
user.update()
error = "パスワードを変更しました"
except Exception as err:
error = str(err)
return Response.render(
template,
{
"user": user,
"error": error
},
)

def update_notification(self, req):
template = self.view.get_template("user/update/notification.html")
user = User.retrieve(screen_name=req.params["id"])
if user._id != req.user._id:
return Response.redirect(self.app.convert_url("/"))
error = ""
if req.method == "POST":
form = SubscriptionForm(req.post())
try:
if form["action"] == "subscribe":
subscription = PushSubscription()
subscription.subscription = form["subscription"]
subscription.create()
user.subscriptions.append(subscription)
user.update()
error="通知の購読を設定しました"
else:
subscription = PushSubscription.retrieve(subscription = form["subscription"])
subscription.delete()
error="通知の購読を解除しました"
except Exception as err:
error = str(err)
return Response.render(
template,
{
"user": user,
"vapid_public_key": self.app.project.vapid["public_key"],
"error": error
},
)

Expand Down
7 changes: 7 additions & 0 deletions mitama/portal/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,13 @@ class UserUpdateForm(Form):
screen_name = Field(label="ログイン名")
roles = Field(label="役割", listed=True)

class SubscriptionForm(Form):
action = Field(required=True)
subscription = Field()

class UserPasswordUpdateForm(Form):
password = Field(label="パスワード", required=True)
password_ = Field(label="確認用パスワード", required=True)

class GroupCreateForm(Form):
icon = FileField(label="プロフィール画像", initial=load_noimage_group())
Expand Down
9 changes: 7 additions & 2 deletions mitama/portal/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from mitama.app import Router
from mitama.app.method import view, post
from mitama.models import Permission, InnerPermission
from mitama.utils.controllers import static_files, mitama_favicon
from mitama.utils.controllers import static_files, mitama_favicon, mitama_service_worker, mitama_manifest
from mitama.utils.middlewares import SessionMiddleware, CsrfMiddleware

from .controller import (
Expand Down Expand Up @@ -44,6 +44,8 @@ def router(self):
[
view("/static/<path:path>", static_files()),
view("/favicon.ico", mitama_favicon()),
view("/sw.js", mitama_service_worker()),
view("/manifest.json", mitama_manifest()),
Router(
[
view("/setup", RegisterController, "setup"),
Expand All @@ -58,7 +60,10 @@ def router(self):
view("/users/invite", UsersController, "create"),
view("/users/invite/<id>/delete", UsersController, "cancel"),
view("/users/<id>", UsersController, "retrieve"),
view("/users/<id>/settings", UsersController, "update"),
view("/users/<id>/settings", UsersController, "update_profile"),
view("/users/<id>/settings/profile", UsersController, "update_profile"),
view("/users/<id>/settings/password", UsersController, "update_password"),
view("/users/<id>/settings/notification", UsersController, "update_notification"),
view("/users/<id>/delete", UsersController, "delete"),
view("/groups", GroupsController, "list"),
view("/groups/create", GroupsController, "create"),
Expand Down
1 change: 1 addition & 0 deletions mitama/portal/templates/base.html
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
{% extends 'base/plain' %}
{% block head %}
<link rel="manifest" href="/manifest.json">
{% endblock %}
{% block body %}
{% include "header.html" %}
Expand Down
11 changes: 11 additions & 0 deletions mitama/portal/templates/home.html
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,15 @@
{{ welcome_message | markdown }}
</div>
</div>
<script>
window.addEventListener("load", () => {
if ("serviceWorker" in navigator){
navigator.serviceWorker.register("{{ url('/sw.js') }}", { scope: "/" }).then(reg => {
console.log(reg)
}).catch(e => {
console.error(e)
})
}
});
</script>
{% endblock %}
4 changes: 3 additions & 1 deletion mitama/portal/templates/user/update.html
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ <h2 class='mt-4 mb-3'>プロフィール変更</h2>
{% else %}
<h2 class='mt-4 mb-3'>{{user.name}}のプロフィール変更</h2>
{% endif %}
<form method='POST' action='' enctype='multipart/form-data' class="tight">
<form method='POST' action='' enctype='multipart/form-data' class="mb-5">
<div class='mb-3'>
<label class='form-label'>アイコン</label>
<div id='image-form'>
Expand Down Expand Up @@ -67,5 +67,7 @@ <h2 class='mt-4 mb-3'>{{user.name}}のプロフィール変更</h2>
{% endif %}
{{ forms.csrf(request) }}
</form>
{% if request.user == user %}
{% endif %}
</div>
{% endblock %}
Loading

0 comments on commit 78ee3ce

Please sign in to comment.