├── README.md ├── motiky ├── views │ ├── __init__.py │ ├── index.py │ ├── push.py │ ├── tag.py │ ├── feed.py │ ├── comment.py │ ├── activity.py │ ├── admin.py │ ├── post.py │ └── user.py ├── __init__.py ├── helpers.py ├── logic │ ├── __init__.py │ ├── logic_action.py │ ├── logic_activity.py │ ├── logic_other.py │ ├── logic_tag.py │ ├── logic_comment.py │ ├── logic_feed.py │ ├── logic_user.py │ ├── logic_post.py │ └── models.py ├── strutil.py ├── configs.py ├── authutil.py ├── coreutil.py ├── schema.py ├── application.py ├── cacheutil.py └── upyun.py ├── deploy.py ├── scripts ├── sync.sh ├── pg_backup.sh └── notify_data.py ├── docs └── motiky-api-doc.md ├── worker.py ├── api.conf ├── require.txt ├── ctlapp.sh ├── tests ├── __init__.py ├── test_tag.py ├── test_feed.py ├── test_comment.py ├── test_user.py ├── test_activity.py ├── test_post.py └── test_backend.py ├── manage.py └── supervisord.conf /README.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /motiky/views/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # author: notedit 4 | 5 | -------------------------------------------------------------------------------- /deploy.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import sys 4 | 5 | from fabric.api import env,run,cd 6 | 7 | 8 | -------------------------------------------------------------------------------- /scripts/sync.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | rsync -ravz --exclude=.hg wwwuser@host:/data/backups /data/backups 3 | -------------------------------------------------------------------------------- /motiky/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # author: notedit 4 | 5 | from motiky.application import create_app 6 | -------------------------------------------------------------------------------- /scripts/pg_backup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | export PGPASSWORD='password' 3 | pg_dump -Uuser -hlocalhost -a -f /data/backups/`date +%F`.sql database 4 | -------------------------------------------------------------------------------- /motiky/helpers.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # author: notedit 3 | 4 | import functools 5 | 6 | from flask import g 7 | 8 | 9 | -------------------------------------------------------------------------------- /docs/motiky-api-doc.md: -------------------------------------------------------------------------------- 1 | * 获取tags 列表 2 | 3 | ``` 4 | GET /tags 5 | 6 | RESPONSE 7 | 8 | { 9 | 'results':[ 10 | { 11 | 'id':int, 12 | 'name':str, 13 | 'show':bool, 14 | 'pic_url':str, 15 | 'order_seq':int, 16 | 'recommended':bool, 17 | 'date_create':str => '1988-12-13 00:00:00.12345' 18 | }, 19 | ] 20 | } 21 | 22 | ``` -------------------------------------------------------------------------------- /motiky/logic/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import logic_user 4 | import logic_post 5 | import logic_action 6 | import logic_activity 7 | import logic_feed 8 | import logic_tag 9 | import logic_comment 10 | import logic_other 11 | 12 | # import some other logic here 13 | 14 | 15 | from motiky import coreutil 16 | 17 | backend = coreutil.Backend(coreutil.backend_mapping) 18 | 19 | 20 | -------------------------------------------------------------------------------- /worker.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import sys 4 | import rq 5 | from rq import Queue,Connection,Worker 6 | 7 | 8 | # why this? it can use the sqlalchemy's connection poll from rq Performance notes 9 | # add by notedit 2013-01-24 10 | 11 | with Connection(): 12 | 13 | qs = map(rq.Queue, sys.argv[1:]) or [rq.Queue()] 14 | 15 | w = rq.Worker(qs) 16 | w.work() 17 | 18 | 19 | -------------------------------------------------------------------------------- /motiky/views/index.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # author: notedit 4 | 5 | import sys 6 | import time 7 | import logging 8 | import flask 9 | from flask import g 10 | from flask import request 11 | from flask import redirect 12 | from flask import Response 13 | from flask import current_app 14 | from flask import session 15 | from flask import jsonify 16 | from flask.views import MethodView 17 | from flask.views import View 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /scripts/notify_data.py: -------------------------------------------------------------------------------- 1 | #/usr/bin/evn python 2 | # -*- coding: utf-8 -*- 3 | 4 | import os 5 | import sys 6 | import json 7 | import traceback 8 | 9 | sys.path.insert(0,'../') 10 | 11 | from komandr import * 12 | 13 | from motiky.logic.models import User,Post,Comment,Activity 14 | 15 | from motiky import create_app 16 | from motiky import configs 17 | from motiky.configs import db 18 | 19 | app = create_app(configs.ProductionConfig) 20 | 21 | @command('generate_notify_data') 22 | def generate_notify_data(): 23 | pass 24 | 25 | 26 | main() 27 | -------------------------------------------------------------------------------- /api.conf: -------------------------------------------------------------------------------- 1 | server { 2 | 3 | listen 80; 4 | server_name api.motiky.com; 5 | root /home/wwwuser/motiky; 6 | 7 | access_log /var/log/nginx/access-motiky-api.log; 8 | error_log /var/log/nginx/error-motiky-api.log; 9 | 10 | 11 | location ~ \.htaccess { 12 | deny all; 13 | } 14 | 15 | location / { 16 | proxy_pass http://127.0.0.1:7000; 17 | proxy_set_header Host $host; 18 | proxy_set_header X-Real-IP $remote_addr; 19 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /require.txt: -------------------------------------------------------------------------------- 1 | DBUtils==1.1 2 | Flask==0.9 3 | Flask-And-Redis==0.4 4 | Flask-Cache==0.9.2 5 | Flask-Login==0.1.3 6 | Flask-Mail==0.7.4 7 | Flask-SQLAlchemy==0.16 8 | Flask-Script==0.3.3 9 | Flask-Testing==0.4 10 | Flask-Uploads==0.1.3 11 | Flask-WTF==0.8 12 | Jinja2==2.6 13 | SQLAlchemy==0.7.8 14 | WTForms==1.0.1 15 | Werkzeug==0.8.3 16 | apns==1.1.2 17 | argparse==1.2.1 18 | colander==0.9.8 19 | colorama==0.2.4 20 | greenlet==0.4.0 21 | gunicorn==0.16.0 22 | komandr==0.1.1 23 | nose==1.2.1 24 | psutil==0.6.1 25 | psycopg2==2.4.5 26 | py-bcrypt==0.2 27 | python-dateutil==2.1 28 | redis==2.7.2 29 | requests==0.14.1 30 | rq==0.3.2 31 | setproctitle==1.1.6 32 | sinaweibopy==1.0.5 33 | supervisor==3.0b1 34 | times==0.6 35 | -------------------------------------------------------------------------------- /ctlapp.sh: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env bash 2 | 3 | # Author: liulianxiang 4 | 5 | 6 | MAINMODULE=manage:app 7 | 8 | case $1 in 9 | start) 10 | exec gunicorn -D -w 4 -k gevent -p /tmp/motiky.pid -b 127.0.0.1:9090 $MAINMODULE 11 | ;; 12 | stop) 13 | kill -INT `cat /tmp/motiky.pid` 14 | ;; 15 | restart) 16 | kill -INT `cat /tmp/motiky.pid` 17 | exec gunicorn -D -w 4 -k gevent -p /tmp/motiky.pid -b 127.0.0.1:9090 $MAINMODULE 18 | ;; 19 | debug) 20 | exec gunicorn -w 4 -p /tmp/motiky.pid -b 127.0.0.1:9090 $MAINMODULE 21 | ;; 22 | *) 23 | echo "./ctlapp.sh start | stop | debug | debug" 24 | ;; 25 | esac 26 | -------------------------------------------------------------------------------- /motiky/logic/logic_action.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # author: notedit 3 | # date: 2013-01-30 4 | 5 | import types 6 | import traceback 7 | 8 | from motiky.coreutil import BackendError,register,assert_error 9 | 10 | from motiky.logic.models import User,Post,Action 11 | 12 | from motiky.configs import db 13 | 14 | 15 | 16 | @register('add_action') 17 | def add_action(ainfo={}): 18 | action = Action(**ainfo) 19 | try: 20 | db.session.add(action) 21 | db.session.commit() 22 | except: 23 | db.session.rollback() 24 | raise BackendError('InternalError',traceback.format_exc()) 25 | else: 26 | return action.json 27 | 28 | 29 | # add some other 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import unittest 4 | import logging 5 | import time 6 | import hmac 7 | 8 | from flask.ext.testing import TestCase as Base 9 | 10 | from motiky import create_app 11 | from motiky import configs 12 | from motiky.configs import db,redis 13 | 14 | class TestCase(Base): 15 | 16 | def create_app(self): 17 | app = create_app(configs.TestConfig) 18 | app.config['TESTING'] = True 19 | return app 20 | 21 | def generate_header(self,ukey): 22 | _now = int(time.time()) 23 | token = '%s|%d|%s' % (ukey,_now,hmac.new(configs.TestConfig().APPLICATION_SECRET, 24 | ukey+str(_now)).hexdigest()) 25 | return {'X-MOTIKY-TOKEN':token} 26 | 27 | def setUp(self): 28 | db.create_all() 29 | redis.flushdb() 30 | 31 | def tearDown(self): 32 | db.session.remove() 33 | db.drop_all() 34 | print dir(redis) 35 | -------------------------------------------------------------------------------- /motiky/logic/logic_activity.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # author: notedit 3 | # date: 2013-04-10 4 | 5 | import types 6 | import traceback 7 | 8 | from motiky.coreutil import BackendError,register,assert_error 9 | 10 | from motiky.logic.models import User,Post,Tag,Activity 11 | 12 | from motiky.configs import db 13 | 14 | @register('add_activity') 15 | def add_activity(ainfo): 16 | act = Activity(**ainfo) 17 | try: 18 | db.session.add(act) 19 | db.session.commit() 20 | except: 21 | db.session.rollback() 22 | raise BackendError('InternalError',traceback.format_exc()) 23 | else: 24 | return act.json 25 | 26 | @register('get_new_activity_count') 27 | def get_new_activity_count(user_id,last_update_time): 28 | assert_error(type(user_id) == types.IntType,'ParamError') 29 | count = Activity.query.filter(Activity.to_id == user_id).\ 30 | filter(Activity.date_create > last_update_time).count() 31 | return count 32 | 33 | @register('get_activity_by_user') 34 | def get_activity_by_user(user_id,limit=30,offset=0): 35 | assert_error(type(user_id) == types.IntType,'ParamError') 36 | activitys = Activity.query.filter(Activity.to_id == user_id).\ 37 | order_by(db.desc(Activity.date_create)).limit(limit).offset(offset).all() 38 | return [a.json for a in activitys] 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /manage.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | from flask import current_app 4 | from flask.ext.script import Manager,prompt,prompt_pass,\ 5 | prompt_bool,prompt_choices 6 | from flask.ext.script import Server 7 | from werkzeug import generate_password_hash,check_password_hash 8 | 9 | from motiky import configs 10 | from motiky.configs import db 11 | from motiky import create_app 12 | 13 | from motiky.logic.models import CmsUser 14 | 15 | app = create_app(configs.ProductionConfig) 16 | manager = Manager(app) 17 | 18 | @manager.command 19 | def create_all(): 20 | if prompt_bool("Are you sure? You will init your database"): 21 | db.create_all() 22 | 23 | @manager.command 24 | def drop_all(): 25 | if prompt_bool("Are you sure? You will lose all your data!"): 26 | db.drop_all() 27 | 28 | @manager.option('-u','--username',dest='username',required=True) 29 | @manager.option('-p','--password',dest='password',required=True) 30 | @manager.option('-e','--email',dest='email',required=True) 31 | def createuser(username=None,password=None,email=None): 32 | password = generate_password_hash(password) 33 | cmsuser = CmsUser(username=username,password=password,email=email) 34 | db.session.add(cmsuser) 35 | db.session.commit() 36 | print 'cms user was created' 37 | 38 | manager.add_command('runserver',Server()) 39 | 40 | if __name__ == '__main__': 41 | manager.run() 42 | -------------------------------------------------------------------------------- /motiky/strutil.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import re 4 | import os 5 | import sys 6 | import time 7 | import datetime 8 | from email.utils import formatdate 9 | 10 | def extract_tags(s): 11 | tags = re.findall(r'''#(\w+)?#''',s) 12 | return set(tags) if tags else [] 13 | 14 | def read_data(_file): 15 | file_data = '' 16 | data = _file.read(8192) 17 | while data: 18 | file_data += data 19 | data = _file.read(8192) 20 | return file_data 21 | 22 | def cookie_date(epoch_seconds=None): 23 | rfcdate = formatdate(epoch_seconds) 24 | return '%s-%s-%s GMT' % (rfcdate[:7], rfcdate[8:11], rfcdate[12:25]) 25 | 26 | def int2path(uint,baseurl,extname): 27 | """将32bit正整数转换为path""" 28 | file_key = '' 29 | for i in range(6): 30 | uint,remainder = divmod(uint,36) 31 | if remainder < 10: 32 | file_key = chr(remainder+48) + file_key 33 | else: 34 | file_key = chr(remainder+97-10) + file_key 35 | fullurl = os.path.join(baseurl,file_key[0:2],file_key[2:4],file_key[4:6],file_key+extname) 36 | return fullurl 37 | 38 | def int2ukey(uint): 39 | ukey = '' 40 | for i in range(6): 41 | uint,remainder = divmod(uint,36) 42 | if remainder < 10: 43 | ukey = chr(remainder+48) + ukey 44 | else: 45 | ukey = chr(remainder+97-10) + ukey 46 | return ukey 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /supervisord.conf: -------------------------------------------------------------------------------- 1 | [unix_http_server] 2 | file=/tmp/supervisord.sock 3 | chmod=0755 4 | chown=wwwuser:wwwuser 5 | 6 | [rpcinterface:supervisor] 7 | supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface 8 | 9 | [supervisord] 10 | logfile=/var/log/supervisor/supervisord.log 11 | logfile_maxbytes=10MB 12 | logfile_backups=10 13 | loglevel=info 14 | childlogdir=/var/log/supervisor/ 15 | pidfile=/var/run/supervisord.pid 16 | user=root 17 | 18 | [supervisorctl] 19 | serverurl=unix:///tmp/supervisord.sock 20 | 21 | [program:motiky] 22 | command=gunicorn -w 10 -p /tmp/motiky.pid -b 127.0.0.1:7000 manage:app 23 | process_name=%(program_name)s 24 | numprocs=1 25 | directory=/home/wwwuser/motiky 26 | autostart=true 27 | user=wwwuser 28 | stdout_logfile=/data/supervisor/motiky-out.log 29 | stdout_logfile_maxbytes=1MB 30 | stdout_logfile_backups=10 31 | stderr_logfile=/data/supervisor/motiky-err.log 32 | stderr_logfile_maxbytes=1MB 33 | stderr_logfile_backups=10 34 | 35 | [program:worker] 36 | command=python worker.py 37 | process_name=%(program_name)s_%(process_num)02d 38 | numprocs=2 39 | directory=/home/wwwuser/motiky 40 | autostart=true 41 | user=wwwuser 42 | stdout_logfile=/data/supervisor/motiky-worker-out.log 43 | stdout_logfile_maxbytes=1MB 44 | stdout_logfile_backups=10 45 | stderr_logfile=/data/supervisor/motiky-worker-err.log 46 | stderr_logfile_maxbytes=1MB 47 | stderr_logfile_backups=10 48 | 49 | 50 | -------------------------------------------------------------------------------- /motiky/views/push.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # author: notedit 4 | 5 | import sys 6 | import time 7 | import logging 8 | 9 | import flask 10 | from flask import g 11 | from flask import request 12 | from flask import redirect 13 | from flask import Response 14 | from flask import current_app 15 | from flask import session 16 | from flask import jsonify 17 | from flask import flash 18 | from flask.views import MethodView 19 | from flask.views import View 20 | 21 | from missing import authutil 22 | from missing.site import instance 23 | from missing.logic import backend 24 | from missing.coreutil import BackendError 25 | from missing.configs import redis,rq 26 | 27 | instance = Blueprint('push',__name__) 28 | 29 | 30 | @instance.route('/push',methods=('POST',)) 31 | def push(): 32 | """ 33 | user_id:int 34 | data:{ 35 | 'alert':str, 36 | 'sound':str, 37 | 'custom':dict 38 | } 39 | https://github.com/simonwhitaker/PyAPNs 40 | """ 41 | if not request.json: 42 | return jsonify(error='content-type should be json') 43 | if not set(['user_id','data']).issubset(set(request.json.keys())): 44 | return jsonify(error='params error') 45 | 46 | user_id = request.json['user_id'] 47 | data = request.json['data'] 48 | 49 | rq.enqueue('onepiece.worker.apns_push',user_id=user_id,data=data) 50 | 51 | return jsonify(ok='push is in the queue') 52 | -------------------------------------------------------------------------------- /motiky/configs.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import socket 4 | import datetime 5 | 6 | from redis import Redis 7 | from rq import Connection,Queue 8 | from flask.ext.redis import Redis as fRedis 9 | from flask.ext.sqlalchemy import SQLAlchemy 10 | from flask.ext.cache import Cache 11 | from flask.ext.mail import Mail 12 | 13 | db = SQLAlchemy() 14 | cache = Cache() 15 | mail = Mail() 16 | redis = fRedis() 17 | rq = Queue('motiky',connection=Redis()) 18 | 19 | class DefaultConfig(object): 20 | 21 | DEBUG = False 22 | SECRET_KEY = 'lifeistooshorttowait' 23 | APPLICATION_SECRET = 'lifeistooshorttowait' 24 | SQLALCHEMY_DATABASE_URI = 'postgresql+psycopg2://user:password@localhost/motiky' 25 | SQLALCHEMY_ECHO = False 26 | 27 | class TestConfig(object): 28 | CONFIG_TYPE = 'test' 29 | SQLALCHEMY_DATABASE_URI = 'postgresql+psycopg2://user:password@localhost/test' 30 | SQLALCHEMY_ECHO = False 31 | APPLICATION_SECRET = 'lifeistooshorttowait' 32 | CSRF_ENABLED = False 33 | VIDEO_URL_PREFIX = 'http://localhost' 34 | 35 | class DevConfig(object): 36 | CONFIG_TYPE = 'dev' 37 | SQLALCHEMY_DATABASE_URI = \ 38 | 'postgresql+psycopg2://user:password@localhost/motiky' 39 | 40 | class ProductionConfig(object): 41 | CONFIG_TYPE = 'production' 42 | SQLALCHEMY_ECHO = False 43 | VIDEO_URL_PREFIX = 'http://motiky01.b0.upaiyun.com' 44 | SQLALCHEMY_DATABASE_URI = 'postgresql+psycopg2://user:password@localhost/motiky' 45 | DEBUG = True 46 | 47 | -------------------------------------------------------------------------------- /tests/test_tag.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | 4 | import os 5 | import sys 6 | import types 7 | import json 8 | import time 9 | from datetime import datetime 10 | from datetime import timedelta 11 | 12 | from tests import TestCase 13 | 14 | 15 | from motiky.logic.models import User,Post,UserLikeAsso,Report,Install,\ 16 | UserFollowAsso,Comment,Activity,Action,Tag,Tagging 17 | 18 | from motiky.logic import backend 19 | 20 | from motiky.configs import db,redis 21 | 22 | class TestTag(TestCase): 23 | 24 | 25 | def test_tag_view(self): 26 | tag1 = Tag(name='tag1',show=True,pic_url='pic_url', 27 | recommended=True) 28 | tag2 = Tag(name='tag2',show=True,pic_url='pic_url', 29 | recommended=True) 30 | db.session.add(tag1) 31 | db.session.add(tag2) 32 | db.session.commit() 33 | 34 | user1 = backend.add_user('username01','photo_url01','weibo_id01') 35 | post1 = backend.add_post('post01',user1['id'], 36 | 'video_url','pic_small1') 37 | post2 = backend.add_post('post02',user1['id'], 38 | 'video_url','pic_small2') 39 | tagging1 = Tagging(taggable_type='post',taggable_id=post1['id'],tag_id=tag1.id) 40 | tagging2 = Tagging(taggable_type='post',taggable_id=post2['id'],tag_id=tag1.id) 41 | 42 | headers = self.generate_header('weibo_id01') 43 | # get 44 | resp = self.client.get('/tag/%d'% tag1.id,headers=headers) 45 | data_get = json.loads(resp.data) 46 | assert data_get['tag']['name'] == 'tag1' 47 | 48 | # get tags 49 | resp = self.client.get('/tags',headers=headers) 50 | data_get = json.loads(resp.data) 51 | assert len(data_get['results']) == 2 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /motiky/views/tag.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # author: notedit 3 | # date: 2013-04-16 4 | 5 | import sys 6 | import time 7 | import logging 8 | import flask 9 | from flask import g 10 | from flask import request 11 | from flask import Blueprint 12 | from flask import redirect 13 | from flask import Response 14 | from flask import current_app 15 | from flask import session 16 | from flask import jsonify 17 | from flask import flash 18 | from flask.views import MethodView 19 | from flask.views import View 20 | 21 | from motiky import authutil 22 | from motiky.logic import backend 23 | from motiky.coreutil import BackendError 24 | from motiky.configs import rq 25 | 26 | instance = Blueprint('tag',__name__) 27 | 28 | class TagView(MethodView): 29 | 30 | def get(self,tag_id): 31 | try: 32 | page = int(request.values.get('page')) 33 | except: 34 | page = 1 35 | 36 | limit = 10 37 | offset = (page - 1) * 50 38 | 39 | tag = backend.get_tag(tag_id) 40 | posts = backend.get_tag_post(tag_id,limit=limit,offset=offset) 41 | count = backend.get_tag_post_count(tag_id) 42 | 43 | for post in posts: 44 | try: 45 | user = backend.get_user(post['author_id']) 46 | post['user'] = user 47 | except BackendError,ex: 48 | continue 49 | 50 | return jsonify(tag=tag,posts=posts,count=count,page=page) 51 | 52 | class TagsView(MethodView): 53 | 54 | def get(self): 55 | tags = backend.get_recommend_tags() 56 | return jsonify(results=tags) 57 | 58 | instance.add_url_rule('/tag/',view_func=TagView.as_view('tag'), 59 | methods=['GET',]) 60 | instance.add_url_rule('/tags',view_func=TagsView.as_view('tags'), 61 | methods=['GET',]) 62 | 63 | 64 | 65 | 66 | 67 | 68 | -------------------------------------------------------------------------------- /motiky/authutil.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import os 4 | import sys 5 | import time 6 | import hmac 7 | import datetime 8 | import hashlib 9 | from hashlib import sha1,md5 10 | 11 | import strutil 12 | 13 | from flask import g,request,redirect,session 14 | from flask import current_app as app 15 | 16 | from motiky.configs import db,redis 17 | from motiky.logic.models import User 18 | 19 | def user_required(f): 20 | """必须登陆后才能访问的视图""" 21 | def decorator(*args,**kwargs): 22 | ukey = g.ukey 23 | if not ukey: 24 | return make_response('need a user',403) 25 | rp = redis.pipeline() 26 | rp.exists('USER-UKEY::%s'%ukey) 27 | rp.get('USER-UKEY::%s'%ukey) 28 | res = iter(rp.execute()) 29 | 30 | if not res.next(): 31 | # redis 中不存在 插数据库 32 | user = db.session.query(User).filter(User.uid == ukey).first() 33 | if user is None: 34 | res = make_response('the user does not exist',403) 35 | return res 36 | g.user_id = user.id 37 | rp.set('USER-UKEY::%s'%ukey,user.id) 38 | rp.execute() 39 | else: 40 | g.user_id = int(res.next()) 41 | return f(*args,**kwargs) 42 | return decorator 43 | 44 | def get_user_id(ukey): 45 | """根据ukey来获取user_id""" 46 | if not ukey: 47 | return None 48 | rp = redis.pipeline() 49 | rp.exists('USER-UKEY::%s'%ukey) 50 | rp.get('USER-UKEY::%s'%ukey) 51 | res = iter(rp.execute()) 52 | 53 | user_id = None 54 | if not res.next(): 55 | user = db.session.query(User).filter(User.uid == ukey).first() 56 | if user is None: 57 | return None 58 | g.user_id = user_id = user.id 59 | rp.set('USER-UKEY::%s'%ukey,user.id) 60 | rp.execute() 61 | else: 62 | g.user_id = user_id = int(res.next()) 63 | return user_id 64 | 65 | -------------------------------------------------------------------------------- /tests/test_feed.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | 4 | import os 5 | import sys 6 | import types 7 | import json 8 | import time 9 | from datetime import datetime 10 | from datetime import timedelta 11 | 12 | from StringIO import StringIO 13 | 14 | from tests import TestCase 15 | 16 | 17 | from motiky.logic.models import User,Post,UserLikeAsso,Report,Install,\ 18 | UserFollowAsso,Comment,Activity,Action 19 | 20 | from motiky.logic import backend 21 | 22 | from motiky.configs import db,redis 23 | 24 | class TestFeed(TestCase): 25 | 26 | 27 | def test_feeds_view(self): 28 | user1 = backend.add_user('username01','photo_url01','weibo_id01') 29 | user2 = backend.add_user('username02','photo_url02','weibo_id02') 30 | user3 = backend.add_user('username03','photo_url03','weibo_id03') 31 | user4 = backend.add_user('username04','photo_url04','weibo_id04') 32 | 33 | post1 = backend.add_post('title01',user1['id'],'video_url01', 34 | pic_small='pic_small01') 35 | post2 = backend.add_post('title02',user2['id'],'video_url02', 36 | pic_small='pic_small') 37 | post3 = backend.add_post('title03',user3['id'],'video_url03', 38 | pic_small='pic_small03') 39 | post4 = backend.add_post('title04',user4['id'],'video_url04', 40 | pic_small='pic_small04') 41 | 42 | backend.follow_user(user4['id'],user1['id']) 43 | backend.follow_user(user4['id'],user2['id']) 44 | 45 | headers = self.generate_header('weibo_id04') 46 | 47 | resp = self.client.get('/feeds/%d' % user4['id'],headers=headers) 48 | ret = json.loads(resp.data) 49 | assert len(ret['results']) == 3 50 | 51 | 52 | backend.set_post(post3['id'],{'recommended':True}) 53 | 54 | resp = self.client.get('/feeds/%d'% user4['id'],headers=headers) 55 | ret = json.loads(resp.data) 56 | 57 | assert len(ret['results']) == 4 58 | 59 | 60 | -------------------------------------------------------------------------------- /motiky/logic/logic_other.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # author: notedit 3 | # date: 2013-02-01 4 | 5 | import types 6 | import traceback 7 | 8 | from motiky.coreutil import BackendError,register,assert_error 9 | 10 | from motiky.logic.models import User,Post,Install,Storage 11 | 12 | from motiky.configs import db 13 | 14 | 15 | @register('get_install_by_user') 16 | def get_install_by_user(user_id): 17 | _in = Install.query.filter(Install.user_id == user_id).first() 18 | if _in is None: 19 | raise BackendError('EmptyError','install do not exit') 20 | return _in.json 21 | 22 | @register('new_install') 23 | def new_insall(user_id,device_token,version='',device_type=''): 24 | assert_error(type(user_id) == types.IntType,'ParamError') 25 | assert_error(type(device_token) == types.StringType,'ParamError') 26 | 27 | install = Install(user_id=user_id, 28 | device_token=device_token, 29 | version=version, 30 | device_type=device_type) 31 | 32 | try: 33 | db.session.add(install) 34 | db.session.commit() 35 | except: 36 | db.session.rollback() 37 | raise 38 | return install.json 39 | 40 | @register('set_install') 41 | def set_install(user_id,idict): 42 | install = Install.query.filter(Install.user_id == user_id).first() 43 | if install is None: 44 | raise BackendError('EmptyError','install does not exist') 45 | for k,v in idict.items(): 46 | if v: 47 | setattr(install,k,v) 48 | try: 49 | db.session.commit() 50 | except: 51 | db.session.rollback() 52 | raise BackendError('InternalError',traceback.format_exc()) 53 | else: 54 | return install.json 55 | 56 | @register('add_file_data') 57 | def add_file_data(file_size,file_md5): 58 | st = Storage(file_size=file_size,file_md5=file_md5) 59 | try: 60 | db.session.add(st) 61 | db.session.commit() 62 | except: 63 | db.session.rollback() 64 | raise BackendError('InternalError',traceback.format_exc()) 65 | return st.id 66 | 67 | -------------------------------------------------------------------------------- /motiky/coreutil.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import os 4 | import sys 5 | import time 6 | import logging 7 | import inspect 8 | import traceback 9 | 10 | 11 | 12 | backend_mapping = {} 13 | def register(funcname=None): 14 | global backend_mapping 15 | def inter1(func,funcname=None): 16 | funcname = funcname if funcname else func.__name__ 17 | if not backend_mapping.has_key(funcname): 18 | backend_mapping[funcname] =func 19 | else: 20 | raise KeyError('%s:funcname declare more than once '%repr(funcname)) 21 | def inter2(*args,**kwargs): 22 | return func(*args,**kwargs) 23 | setattr(inter2,'argspec',inspect.getargspec(func)) 24 | return inter2 25 | return lambda func:inter1(func,funcname) 26 | 27 | def assert_error(expr,msg,detail=''): 28 | if not expr: 29 | if not detail: 30 | detail = msg 31 | raise BackendError(msg,detail) 32 | 33 | class BackendError(Exception): 34 | def __init__(self,message,detail): 35 | self.message = message 36 | self.detail = detail 37 | 38 | def __str__(self): 39 | return 'BackendError(%s,%s)' % (self.message,self.detail) 40 | 41 | def __repr__(self): 42 | return 'BackendError(%s,%s)' %(self.message,self.detail) 43 | 44 | 45 | class Backend(object): 46 | '''去掉一些后端不需要导出的方法''' 47 | def __init__(self,func_mapping): 48 | self.func_mapping = func_mapping 49 | 50 | def __getattr__(self,attr_name): 51 | if self.func_mapping.has_key(attr_name): 52 | func = lambda *args,**kwargs: self.__call__(attr_name,*args,**kwargs) 53 | func.__name__ = 'backend.' + attr_name 54 | return func 55 | else: 56 | raise AttributeError('backend does not have %s attibute'%attr_name) 57 | 58 | 59 | def __call__(self,funcname,*args,**kwargs): 60 | try: 61 | return self.func_mapping[funcname](*args,**kwargs) 62 | except BackendError,ex: 63 | print ex.detail 64 | raise ex 65 | except Exception,ex: 66 | excstr = traceback.format_exc() 67 | print excstr 68 | raise BackendError('BackendError',excstr) 69 | 70 | 71 | 72 | 73 | 74 | -------------------------------------------------------------------------------- /tests/test_comment.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | 4 | import os 5 | import sys 6 | import types 7 | import json 8 | import time 9 | from datetime import datetime 10 | from datetime import timedelta 11 | 12 | from StringIO import StringIO 13 | 14 | from tests import TestCase 15 | 16 | 17 | from motiky.logic.models import User,Post,UserLikeAsso,Report,Install,\ 18 | UserFollowAsso,Comment,Activity,Action 19 | 20 | from motiky.logic import backend 21 | 22 | from motiky.configs import db,redis 23 | 24 | class TestComment(TestCase): 25 | 26 | 27 | def test_comment_view(self): 28 | 29 | # post 30 | user1 = backend.add_user('username01','photo_url01','weibo_id01') 31 | headers = self.generate_header('weibo_id01') 32 | post1 = backend.add_post('post01',user1['id'], 33 | 'video_url','pic_small1') 34 | 35 | data = { 36 | 'author_id':user1['id'], 37 | 'content':'comment01', 38 | 'post_id':post1['id'] 39 | } 40 | 41 | resp = self.client.post('/comment',data=json.dumps(data),headers=headers, 42 | content_type='application/json') 43 | 44 | _data = json.loads(resp.data) 45 | assert resp.status_code == 200 46 | assert _data['content'] == 'comment01' 47 | 48 | # delete 49 | resp = self.client.delete('/comment/%d'%_data['id'], 50 | headers=headers,content_type='application/json') 51 | print resp.data 52 | assert resp.status_code == 204 53 | 54 | def test_post_comment_view(self): 55 | 56 | user1 = backend.add_user('username01','photo_url01','weibo_id01') 57 | post1 = backend.add_post('post01',user1['id'], 58 | 'video_url','pic_small1') 59 | comment1 = backend.add_comment(post1['id'],'comment1',user1['id']) 60 | comment2 = backend.add_comment(post1['id'],'comment2',user1['id']) 61 | comment3 = backend.add_comment(post1['id'],'comment3',user1['id']) 62 | 63 | headers = self.generate_header('weibo_id01') 64 | 65 | resp = self.client.get('/post/%d/comment' % post1['id'],headers=headers) 66 | data_get = json.loads(resp.data) 67 | assert len(data_get['comments']) == 3 68 | 69 | -------------------------------------------------------------------------------- /motiky/logic/logic_tag.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # author: notedit 3 | # date: 2013-04-10 4 | 5 | import types 6 | import traceback 7 | 8 | from motiky.coreutil import BackendError,register,assert_error 9 | 10 | from motiky.logic.models import User,Post,Tag,Tagging 11 | 12 | from motiky.configs import db 13 | 14 | # 99999999 编辑推荐 15 | # 99999998 热门 16 | # 这里应该排除推荐推荐和热门 17 | @register('get_all_tags') 18 | def get_all_tags(): 19 | tags = Tag.query.filter(Tag.show == True).\ 20 | filter(db.not_(Tag.id.in_([99999999,99999998]))).\ 21 | order_by(Tag.order_seq.desc()).all() 22 | return [tag.json for tag in tags] 23 | 24 | @register('get_recommend_tags') 25 | def get_recommend_tags(): 26 | tags = Tag.query.filter(Tag.show == True).\ 27 | filter(Tag.recommended == True).\ 28 | order_by(Tag.order_seq.desc()).all() 29 | return [tag.json for tag in tags] 30 | 31 | @register('get_tag') 32 | def get_tag(tag_id): 33 | assert_error(type(tag_id) == types.IntType,'ParamError') 34 | tag = Tag.query.get(tag_id) 35 | return tag.json 36 | 37 | @register('get_tag_post_count') 38 | def get_tag_post_count(tag_id): 39 | assert_error(type(tag_id) == types.IntType,'ParamError') 40 | count = Tagging.query.filter(Tagging.tag_id == tag_id).count() 41 | return count 42 | 43 | @register('get_tag_post') 44 | def get_tag_post(tag_id,limit=10,offset=0): 45 | assert_error(offset>=0,'ParamError') 46 | posts = Post.query.join(Tagging,Post.id == Tagging.taggable_id).\ 47 | filter(Tagging.tag_id == tag_id).\ 48 | filter(Post.show == True).\ 49 | order_by(Tagging.date_create.desc()).\ 50 | limit(limit).offset(offset).all() 51 | return [p.json for p in posts]; 52 | 53 | def _get_tag_id(tagstr): 54 | tag = Tag.query.filter(Tag.name == tagstr).first() 55 | if tag: 56 | return tag.id 57 | _tag = Tag(name=tagstr) 58 | try: 59 | db.session.add(_tag) 60 | db.session.commit() 61 | except: 62 | pass 63 | else: 64 | return _tag.id 65 | 66 | return None 67 | 68 | @register('add_post_tag') 69 | def add_post_tag(post_id,tags): 70 | for tag in tags: 71 | t = _get_tag_id(tag) 72 | if t is None: 73 | continue 74 | try: 75 | tagging = Tagging(taggable_id=post_id,tag_id=t) 76 | db.session.add(tagging) 77 | db.session.commit() 78 | except: 79 | db.session.rollback() 80 | 81 | 82 | 83 | -------------------------------------------------------------------------------- /motiky/schema.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # the schema for motiky's api 4 | 5 | import colander 6 | from datetime import datetime 7 | from colander import SchemaNode,MappingSchema,SequenceSchema 8 | from colander import Int,Date,String,Bool,DateTime 9 | from colander import Range,Length 10 | 11 | class IdListSchema(SequenceSchema): 12 | id = SchemaNode(Int(),validator=Range(min=1)) 13 | 14 | class StrListSchema(SequenceSchema): 15 | _str = SchemaNode(String()) 16 | 17 | class LimitOffsetSchema(MappingSchema): 18 | offset = SchemaNode(Int()) 19 | limit = SchemaNode(Int()) 20 | 21 | class NewUserSchema(MappingSchema): 22 | uid = SchemaNode(String()) 23 | access_token = SchemaNode(String(),missing=u'') 24 | 25 | class UpdateUserSchema(MappingSchema): 26 | username = SchemaNode(String(encoding='utf-8'),missing=None) 27 | photo_url = SchemaNode(String(encoding='utf-8'),missing=None) 28 | signature = SchemaNode(String(encoding='utf-8'),missing=None) 29 | access_token = SchemaNode(String(encoding='utf-8'),missing=u'') 30 | status = SchemaNode(String(encoding='utf-8'),missing=None) 31 | date_update = SchemaNode(DateTime(),missing=None) 32 | 33 | class UserFollowSchema(MappingSchema): 34 | user_ids = IdListSchema() 35 | 36 | class InstallSchema(MappingSchema): 37 | device_type = SchemaNode(String()) 38 | device_token = SchemaNode(String()) 39 | version = SchemaNode(String()) 40 | user_id = SchemaNode(Int()) 41 | 42 | 43 | class PushSchema(MappingSchema): 44 | user_id = SchemaNode(Int()) 45 | install_id = SchemaNode(String()) 46 | action = SchemaNode(String()) 47 | 48 | class NewPostSchema(MappingSchema): 49 | title = SchemaNode(String(),missing=None) 50 | author_id = SchemaNode(Int()) 51 | #tag_id = SchemaNode(Int(),missing=None) 52 | 53 | class UpdatePostSchema(MappingSchema): 54 | title = SchemaNode(String(encoding='utf-8'),missing=None) 55 | author_id = SchemaNode(Int(),missing=None) 56 | pic_small = SchemaNode(String(encoding='utf-8'),missing=None) 57 | date_update = SchemaNode(DateTime(),missing=datetime.now()) 58 | 59 | class NewCommentSchema(MappingSchema): 60 | post_id = SchemaNode(Int()) 61 | author_id = SchemaNode(Int()) 62 | content = SchemaNode(String()) 63 | 64 | class PostLikeSchema(MappingSchema): 65 | user_id = SchemaNode(Int()) 66 | post_id = SchemaNode(Int()) 67 | 68 | class PostUnlikeSchema(MappingSchema): 69 | user_id = SchemaNode(Int()) 70 | post_id = SchemaNode(Int()) 71 | 72 | 73 | -------------------------------------------------------------------------------- /tests/test_user.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | 4 | import os 5 | import sys 6 | import types 7 | import json 8 | import time 9 | from datetime import datetime 10 | from datetime import timedelta 11 | 12 | from tests import TestCase 13 | 14 | 15 | from motiky.logic.models import User,Post,UserLikeAsso,Report,Install,\ 16 | UserFollowAsso,Comment,Activity,Action 17 | 18 | from motiky.logic import backend 19 | 20 | from motiky.configs import db,redis 21 | 22 | class TestUser(TestCase): 23 | 24 | 25 | def test_user_view(self): 26 | 27 | # post 28 | headers = self.generate_header('weibo_id1') 29 | data = {'uid':'weibo_id1', 30 | 'access_token':'1111111111'} 31 | resp = self.client.post('/user',data=json.dumps(data), 32 | headers=headers,content_type='application/json') 33 | print resp.data 34 | _data = json.loads(resp.data) 35 | assert resp.status_code == 200 36 | assert _data['uid'] == 'weibo_id1' 37 | assert _data['access_token'] == '1111111111' 38 | 39 | # get 40 | resp = self.client.get('/user/%d'%_data['id'],headers=headers) 41 | data_get = json.loads(resp.data) 42 | assert data_get['uid'] == 'weibo_id1' 43 | 44 | # put 45 | put_in = {'photo_url':'put_url','date_upate':str(datetime.now())} 46 | resp = self.client.put('/user/%d'%_data['id'], 47 | data=json.dumps(put_in),headers=headers, 48 | content_type='application/json') 49 | print resp.data 50 | assert resp.status_code == 204 51 | 52 | def test_install(self): 53 | user1 = backend.add_user('username01','photo_url01','weibo_id01') 54 | device_token = 'device_token' 55 | 56 | headers = self.generate_header('weibo_id01') 57 | 58 | data = { 59 | 'device_type':'ios', 60 | 'device_token':device_token, 61 | 'version':'1.1.10', 62 | 'user_id':user1['id'] 63 | } 64 | 65 | resp = self.client.post('/install',data=json.dumps(data), 66 | headers=headers,content_type='application/json') 67 | 68 | _data = json.loads(resp.data) 69 | install_id = _data['install_id'] 70 | 71 | install = Install.query.filter(Install.user_id == user1['id']).first() 72 | assert install is not None 73 | assert install.device_token.encode('utf-8') == device_token 74 | 75 | def test_user_follow(self): 76 | headers = self.generate_header('weibo_id01') 77 | pass 78 | 79 | 80 | 81 | 82 | -------------------------------------------------------------------------------- /motiky/application.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | 4 | import os 5 | import logging 6 | import hmac 7 | 8 | from flask import g 9 | from flask import Flask 10 | from flask import request 11 | from flask import make_response 12 | from flask import session 13 | 14 | from motiky import configs 15 | from motiky.configs import db,cache,mail,redis 16 | 17 | from motiky import logic 18 | from motiky import authutil 19 | from motiky.logic import backend 20 | from motiky.coreutil import BackendError 21 | 22 | from motiky.views import user,post,tag,feed,\ 23 | activity,comment 24 | 25 | # add some other view 26 | 27 | __all__ = ['create_app'] 28 | 29 | 30 | DEFAULT_APP_NAME = 'motiky' 31 | 32 | 33 | def create_app(config=None,app_name=None): 34 | 35 | if app_name is None: 36 | app_name = DEFAULT_APP_NAME 37 | 38 | app = Flask(app_name) 39 | 40 | configure_app(app,config) 41 | configure_db(app) 42 | configure_blueprints(app) 43 | configure_cache(app) 44 | configure_handler(app) 45 | return app 46 | 47 | def configure_app(app,config): 48 | app.config.from_object(configs.DefaultConfig()) 49 | 50 | if config is not None: 51 | app.config.from_object(config) 52 | 53 | app.config.from_envvar('APP_CONFIG',silent=True) 54 | 55 | def configure_db(app): 56 | db.init_app(app) 57 | 58 | def configure_cache(app): 59 | redis.init_app(app) 60 | 61 | def configure_handler(app): 62 | 63 | @app.before_request 64 | def authorize(): 65 | print request.headers 66 | if request.path.startswith('/admin'): 67 | return 68 | 69 | token = request.headers.get('X-MOTIKY-TOKEN') or '' 70 | tokens = token.split('|') 71 | if len(tokens) != 3: 72 | print 'unvalid request' 73 | response = make_response('unvalid request',403) 74 | return response 75 | 76 | ukey,_time,signature = tokens 77 | print ukey,_time,signature 78 | sign = hmac.new(app.config.get('APPLICATION_SECRET'),ukey+_time).hexdigest() 79 | if sign != signature: 80 | print 'sian != signature unvalid request' 81 | response = make_response('unvalid request',403) 82 | return response 83 | g.ukey = ukey 84 | 85 | 86 | def configure_blueprints(app): 87 | app.register_blueprint(user.instance,url_prefix=None) 88 | app.register_blueprint(post.instance,url_prefix=None) 89 | app.register_blueprint(tag.instance,url_prefix=None) 90 | app.register_blueprint(feed.instance,url_prefix=None) 91 | app.register_blueprint(comment.instance,url_prefix=None) 92 | app.register_blueprint(activity.instance,url_prefix=None) 93 | 94 | -------------------------------------------------------------------------------- /motiky/views/feed.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # author: notedit 3 | # date: 2013-04-10 4 | 5 | import sys 6 | import time 7 | import logging 8 | import flask 9 | from flask import g 10 | from flask import request 11 | from flask import Blueprint 12 | from flask import redirect 13 | from flask import Response 14 | from flask import current_app 15 | from flask import session 16 | from flask import jsonify 17 | from flask import flash 18 | from flask.views import MethodView 19 | from flask.views import View 20 | 21 | from motiky import authutil 22 | from motiky.logic import backend 23 | from motiky.coreutil import BackendError 24 | from motiky.configs import redis 25 | 26 | instance = Blueprint('feed',__name__) 27 | 28 | FEED_UPDATE_TIME_KEY = 'FEED::UPDATETIME::%(user_id)s' 29 | 30 | def pipe_load(feeds): 31 | # todo some pipe load 32 | feeds_ret = [] 33 | for po in feeds: 34 | try: 35 | user = backend.get_user(po['author_id']) 36 | po['user'] = user 37 | po['like_count'] = backend.get_post_liked_count(po['id']) 38 | po['comment_count'] = backend.get_post_comment_count(po['id']) 39 | except BackendError,ex: 40 | continue 41 | return feeds 42 | 43 | class NewFeedView(MethodView): 44 | 45 | def get(self,user_id): 46 | feed_time_meta = redis.hgetall(FEED_UPDATE_TIME_KEY % {'user_id':user_id}) 47 | try: 48 | last_update_time = int(feed_time_meta.get('last_update_time')) 49 | except: 50 | last_update_time = int(time.time()) 51 | 52 | last_update_time = datetime.fromtimestamp(last_update_time) 53 | res = backend.get_new_feed(user_id,last_update_time) 54 | return jsonify(has_new=res) 55 | 56 | class FeedsView(MethodView): 57 | 58 | def get(self,user_id): 59 | try: 60 | page = int(request.values.get('page')) 61 | except: 62 | page = 1 63 | 64 | limit = 10 65 | offset = (page-1) * limit 66 | 67 | feeds = backend.get_latest_feed(user_id,limit,offset) 68 | 69 | if len(feeds) > 0: 70 | feeds = pipe_load(feeds) 71 | 72 | curr_user = backend.get_user_by_uid(g.ukey) 73 | liked_post_ids = [p['id'] for p in feeds] 74 | liked_dict = backend.is_like_post(curr_user['id'],liked_post_ids) 75 | for up in feeds: 76 | up['is_like'] = liked_dict.get(up['id']) or False 77 | 78 | if page == 1: 79 | redis.hset(FEED_UPDATE_TIME_KEY % {'user_id':user_id}, 80 | 'last_update_time',int(time.time())) 81 | return jsonify(results=feeds,page=page) 82 | 83 | instance.add_url_rule('/feeds/',view_func=FeedsView.as_view('feed')) 84 | instance.add_url_rule('/feeds/notify/',view_func=NewFeedView.as_view('feed_notify')) 85 | -------------------------------------------------------------------------------- /tests/test_activity.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import os 4 | import sys 5 | import types 6 | import json 7 | import time 8 | from datetime import datetime 9 | from datetime import timedelta 10 | 11 | from StringIO import StringIO 12 | 13 | from tests import TestCase 14 | 15 | 16 | from motiky.logic.models import User,Post,UserLikeAsso,Report,Install,\ 17 | UserFollowAsso,Comment,Activity,Action 18 | 19 | from motiky.logic import backend 20 | 21 | from motiky.configs import db,redis 22 | 23 | class TestActivity(TestCase): 24 | 25 | 26 | def test_user_activity_view(self): 27 | 28 | # get 29 | user1 = backend.add_user('username1','photo_url','weibo_id1') 30 | user2 = backend.add_user('username2','photo_url','weibo_id2') 31 | post1 = backend.add_post('title1',user1['id'],'video_url', 32 | pic_small='pic_small') 33 | post2 = backend.add_post('title2',user1['id'],'video_url', 34 | pic_small='pic_small') 35 | 36 | comment1 = backend.add_comment(post1['id'],'comment1',user1['id']) 37 | 38 | ac1 = { 39 | 'post_id':post1['id'], 40 | 'from_id':user1['id'], 41 | 'to_id':user2['id'], 42 | 'atype':'like' 43 | } 44 | ac2 = { 45 | 'post_id':post1['id'], 46 | 'from_id':user1['id'], 47 | 'comment_id':comment1['id'], 48 | 'to_id':user2['id'], 49 | 'atype':'comment' 50 | } 51 | 52 | 53 | ret = backend.add_activity(ac1) 54 | ret = backend.add_activity(ac2) 55 | backend.new_install(user2['id'],'device_token') 56 | 57 | headers = self.generate_header('weibo_id2') 58 | resp = self.client.get('/user/%d/activity' % user2['id'],headers=headers) 59 | _data = json.loads(resp.data) 60 | assert resp.status_code == 200 61 | assert len(_data['results']) == 2 62 | 63 | redis.flushall() 64 | redis.hset('ACTIVITY::UPDATETIME::%(user_id)s' % {'user_id':user2['id']}, 65 | 'last_update_time',int(time.time() - 3600 * 6)) 66 | 67 | resp = self.client.get('/user/%d/activity/count' % user2['id'], 68 | headers=headers) 69 | _data = json.loads(resp.data) 70 | assert resp.status_code == 200 71 | assert _data['count'] == 2 72 | 73 | redis.hset('ACTIVITY::UPDATETIME::%(user_id)s' % {'user_id':user2['id']}, 74 | 'last_update_time',int(time.time()) + 2) 75 | 76 | resp = self.client.get('/user/%d/activity/count' % user2['id'], 77 | headers=headers) 78 | _data = json.loads(resp.data) 79 | assert resp.status_code == 200 80 | assert _data['count'] == 0 81 | 82 | 83 | 84 | 85 | 86 | 87 | -------------------------------------------------------------------------------- /motiky/logic/logic_comment.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # author: notedit 3 | # date: 2013-04-15 4 | 5 | import types 6 | import traceback 7 | 8 | 9 | from motiky.coreutil import BackendError,register,assert_error 10 | 11 | from motiky.logic.models import User,Post,Comment 12 | 13 | from motiky.configs import db,redis 14 | 15 | 16 | @register('add_comment') 17 | def add_comment(post_id,content,author_id): 18 | assert_error(type(post_id) == types.IntType,'ParamError') 19 | assert_error(type(author_id) == types.IntType,'ParamError') 20 | assert_error(type(content) == types.StringType,'ParamError') 21 | 22 | qd = { 23 | 'post_id':post_id, 24 | 'content':content, 25 | 'author_id':author_id 26 | } 27 | co = Comment(**qd) 28 | try: 29 | db.session.add(co) 30 | db.session.commit() 31 | except: 32 | db.session.rollback() 33 | raise BackendError('InternalError',traceback.format_exc()) 34 | return co.json 35 | 36 | @register('set_comment') 37 | def set_comment(comment_id,dinfo): 38 | keys_set = ('post_id','author_id','content','show','date_create') 39 | if not set(dinfo.keys()).issubset(keys_set): 40 | raise BackendError('ParamError','更新的字段不允许') 41 | comment = Comment.query.get(comment_id) 42 | for key,value in dinfo.items(): 43 | if value is not None: 44 | setattr(comment,key,value) 45 | try: 46 | db.session.commit() 47 | except Exception,ex: 48 | db.session.rollback() 49 | raise BackendError('InternalError',traceback.format_exc()) 50 | return comment.json 51 | 52 | @register('get_comment') 53 | def get_comment(comment_id): 54 | assert_error(type(comment_id) == types.IntType,'ParamError') 55 | comm = Comment.query.get(comment_id) 56 | return comm.json 57 | 58 | @register('get_post_comment') 59 | def get_post_comment(post_id,limit=50,offset=0,show=True): 60 | assert_error(type(post_id) == types.IntType,'ParamError') 61 | _ans = [Comment.show == True,] if show else [] 62 | _ans.append(Comment.post_id == post_id) 63 | q = reduce(db.and_,_ans) 64 | comms = Comment.query.filter(q).order_by(Comment.date_create.desc()).\ 65 | limit(limit).offset(offset).all() 66 | return [c.json for c in comms] 67 | 68 | @register('get_post_comment_count') 69 | def get_post_comment_count(post_id,show=True): 70 | _ans = [Comment.show == True,] if show else [] 71 | _ans.append(Comment.post_id == post_id) 72 | q = reduce(db.and_,_ans) 73 | count = Comment.query.filter(q).count() 74 | return count 75 | 76 | @register('add_comment_count') 77 | def add_comment_count(post_id,step=1): 78 | count_key = 'POST::COMMENT_COUNT::%s' % str(post_id) 79 | if redis.exists(count_key): 80 | rp = redis.pipeline() 81 | rp.incr(count_key).expire(count_key,24*5*3600).execute() 82 | 83 | 84 | 85 | -------------------------------------------------------------------------------- /motiky/views/comment.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # author: notedit 4 | 5 | import os 6 | import sys 7 | import md5 8 | import time 9 | import logging 10 | import flask 11 | from flask import g 12 | from flask import request 13 | from flask import Blueprint 14 | from flask import redirect 15 | from flask import Response 16 | from flask import current_app 17 | from flask import session 18 | from flask import jsonify 19 | from flask import flash 20 | from flask.views import MethodView 21 | from flask.views import View 22 | 23 | from motiky import authutil 24 | from motiky import strutil 25 | from motiky.logic import backend 26 | from motiky.coreutil import BackendError 27 | 28 | from motiky.upyun import UpYun,md5,md5file 29 | 30 | from motiky.schema import NewCommentSchema 31 | 32 | instance = Blueprint('comment',__name__) 33 | 34 | 35 | class CommentView(MethodView): 36 | 37 | def post(self): 38 | data = NewCommentSchema().deserialize(request.json) 39 | 40 | user = backend.get_user(data['author_id']) 41 | 42 | post = backend.get_post(data['post_id']) 43 | 44 | if user['uid'] != g.ukey: 45 | return jsonify(error='not the user') 46 | 47 | try: 48 | comment = backend.add_comment( 49 | data['post_id'], 50 | data['content'].encode('utf-8'), 51 | data['author_id'] 52 | ) 53 | except BackendError,ex: 54 | raise 55 | 56 | if post['author_id'] != data['author_id']: 57 | try: 58 | backend.add_activity({ 59 | 'post_id':data['post_id'], 60 | 'comment_id':comment['id'], 61 | 'from_id':data['author_id'], 62 | 'to_id':post['author_id'], 63 | 'atype':'comment' 64 | }) 65 | except BackendError,ex: 66 | pass 67 | 68 | return jsonify(**comment) 69 | 70 | def delete(self,comment_id): 71 | comment = backend.get_comment(comment_id) 72 | try: 73 | backend.set_comment(comment_id,{'show':False}) 74 | except BackendError,ex: 75 | abort(501) 76 | else: 77 | return '',204 78 | 79 | 80 | class PostCommentsView(MethodView): 81 | 82 | def get(self,post_id): 83 | try: 84 | page = int(request.values.get('page','1')) 85 | except: 86 | page = 1 87 | 88 | limit = 20 89 | offset = (page-1)*limit 90 | 91 | comments = backend.get_post_comment(post_id, 92 | limit=limit,offset=offset) 93 | 94 | for comment in comments: 95 | user = backend.get_user(comment['author_id']) 96 | comment['user'] = user 97 | 98 | count = backend.get_post_comment_count(post_id) 99 | total_page = (count + limit -1 ) / limit 100 | return jsonify(comments=comments,page=page,total_page=total_page) 101 | 102 | 103 | instance.add_url_rule('/comment',view_func=CommentView.as_view('comment'), 104 | methods=['POST']) 105 | instance.add_url_rule('/comment/', 106 | view_func=CommentView.as_view('comment_delete'), 107 | methods=['DELETE']) 108 | instance.add_url_rule('/post//comment', 109 | view_func=PostCommentsView.as_view('post_comment'), 110 | methods=['GET']) 111 | 112 | 113 | 114 | 115 | 116 | -------------------------------------------------------------------------------- /motiky/views/activity.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # author: notedit 3 | # date: 2013-04-10 4 | 5 | import sys 6 | import time 7 | import logging 8 | import traceback 9 | from datetime import datetime 10 | 11 | from flask import g 12 | from flask import request 13 | from flask import Blueprint 14 | from flask import redirect 15 | from flask import Response 16 | from flask import current_app 17 | from flask import session 18 | from flask import jsonify 19 | from flask import flash 20 | from flask.views import MethodView 21 | from flask.views import View 22 | 23 | from motiky import authutil 24 | from motiky.logic import backend 25 | from motiky.coreutil import BackendError 26 | from motiky.cacheutil import rcache 27 | from motiky.configs import rq,redis 28 | 29 | instance = Blueprint('activity',__name__) 30 | 31 | ACTIVITY_UPDATE_TIME_KEY = 'ACTIVITY::UPDATETIME::%(user_id)s' 32 | 33 | # atype follow like comment post_reco text 34 | 35 | def filter_activity(ac): 36 | # todo 37 | if ac['atype'] in ('follow','like','comment'): 38 | _user = rcache(3600*24)(backend.get_user)(ac['from_id']) 39 | ac.update({'user':_user}) 40 | 41 | if ac['atype'] in ('like','comment','post_reco'): 42 | _post = rcache(3600*24)(backend.get_post)(ac['post_id']) 43 | ac.update({'post':_post}) 44 | 45 | if ac['atype'] in ('comment'): 46 | _comment = rcache(3600*24)(backend.get_comment)(ac['comment_id']) 47 | ac.update({'comment':_comment}) 48 | 49 | return ac 50 | 51 | class UserNewActivityCountView(MethodView): 52 | 53 | def get(self,user_id): 54 | activity_time_meta = redis.hgetall(ACTIVITY_UPDATE_TIME_KEY % \ 55 | {'user_id':user_id}) 56 | try: 57 | last_update_time = int(activity_time_meta.get('last_update_time')) 58 | except: 59 | last_update_time = int(time.time()) 60 | 61 | last_update_time = datetime.fromtimestamp(last_update_time) 62 | count = backend.get_new_activity_count(user_id,last_update_time) 63 | 64 | return jsonify(count=count) 65 | 66 | class UserActivityView(MethodView): 67 | 68 | def get(self,user_id): 69 | acs = backend.get_activity_by_user(user_id) 70 | ac_list = [] 71 | for ac in acs: 72 | try: 73 | ac = filter_activity(ac) 74 | except: 75 | traceback.print_exc() 76 | continue 77 | ac_list.append(ac) 78 | 79 | try: 80 | _user = backend.get_user(user_id) 81 | except BackendError,ex: 82 | traceback.print_exc() 83 | try: 84 | backend.set_install(user_id,{'badge':0}) 85 | rq.enqueue('motiky.worker.apns_push', 86 | user_id=user_id,data={ 87 | 'badge':0 88 | }) 89 | except BackendError,ex: 90 | traceback.print_exc() 91 | 92 | redis.hset(ACTIVITY_UPDATE_TIME_KEY % {'user_id':user_id}, 93 | 'last_update_time',int(time.time())) 94 | 95 | return jsonify(results=ac_list) 96 | 97 | user_activity_func = UserActivityView.as_view('user_activity') 98 | user_new_activity_count_func = UserNewActivityCountView.as_view('user_new_activity_count') 99 | 100 | instance.add_url_rule('/user//activity', 101 | view_func=user_activity_func,methods=['GET']) 102 | 103 | instance.add_url_rule('/user//activity/count', 104 | view_func=user_new_activity_count_func,methods=['GET']) 105 | 106 | 107 | 108 | -------------------------------------------------------------------------------- /motiky/logic/logic_feed.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # author: notedit 3 | # date: 2013-04-10 4 | 5 | import os 6 | import types 7 | import traceback 8 | from datetime import datetime 9 | 10 | from flask import current_app 11 | 12 | from motiky.coreutil import BackendError,register,assert_error 13 | 14 | from motiky.logic.models import User,Post,Tag,Activity,UserFollowAsso 15 | 16 | from motiky.configs import db,DefaultConfig 17 | 18 | 19 | NEW_FEED_SQL = """ 20 | SELECT id FROM answer WHERE show=true AND date_create > %(last_update_time)s 21 | AND (%(following_limit)s) LIMIT 1 22 | """ 23 | 24 | GET_LATEST_FEED_SQL = """ 25 | SELECT id,title,pic_small,pic_big,video_url,author_id,show,recommended,play_count, 26 | date_create,date_update,date_publish FROM post WHERE date_create < %(now)s AND show=true 27 | AND (%(following_limit)s) ORDER BY date_create DESC 28 | LIMIT %(limit)s OFFSET %(offset)s 29 | """ 30 | 31 | VALUE_LIST = ['id','title','pic_small','pic_big','video_url','author_id', 32 | 'show','recommended','play_count','date_create','date_update', 33 | 'date_publish'] 34 | 35 | def _get_following_user(user_id): 36 | fu = UserFollowAsso.query.filter(UserFollowAsso.user_id == user_id).all() 37 | if fu: 38 | fuids = [u.user_id_to for u in fu if u] 39 | else: 40 | fuids = [] 41 | return fuids 42 | 43 | @register('get_new_feed') 44 | def get_new_feed(user_id,last_update_time): 45 | 46 | fus = _get_following_user(user_id) 47 | fus.extend([user_id]) 48 | fus = set(fus) 49 | 50 | following_limit = [] 51 | if fus: 52 | following_limit.append('author_id IN (%s)' % ','.join([repr(uid) for uid in fus])) 53 | else: 54 | following_limit.append('1=1') 55 | 56 | following_limit.append('recommended = true') 57 | 58 | following_limit = ' OR '.join(following_limit) 59 | 60 | sql = NEW_FEED_SQL % {'following_limit':following_limit,'last_update_time':repr(str(last_update_time))} 61 | 62 | res = db.session.execute(sql).fetchall() 63 | return True if len(res) else False 64 | 65 | @register('get_latest_feed') 66 | def get_latest_feed(user_id,limit,offset): 67 | 68 | qd = { 69 | 'limit':repr(limit), 70 | 'offset':repr(offset) 71 | } 72 | 73 | fus = _get_following_user(user_id) 74 | fus.extend([user_id]) 75 | fus = set(fus) 76 | 77 | following_limit = [] 78 | if fus: 79 | following_limit.append('author_id IN (%s)' % ','.join([repr(uid) for uid in fus])) 80 | else: 81 | following_limit.append('1=1') 82 | 83 | following_limit.append('recommended = true') 84 | 85 | following_limit = ' OR '.join(following_limit) 86 | 87 | _now = repr(str(datetime.now())) 88 | 89 | qd.update({'following_limit':following_limit,'now':_now}) 90 | sql = GET_LATEST_FEED_SQL % qd 91 | 92 | print sql 93 | 94 | video_url_prefix = '/' if not current_app else current_app.config.get('VIDEO_URL_PREFIX') 95 | 96 | res = db.session.execute(sql).fetchall() 97 | ress = [] 98 | for re in res: 99 | _dict = dict(zip(VALUE_LIST,re)) 100 | _dict.update({ 101 | 'date_create':_dict['date_create'].strftime('%Y-%m-%d %H:%M:%S'), 102 | 'date_update':_dict['date_update'].strftime('%Y-%m-%d %H:%M:%S'), 103 | 'date_publish':_dict['date_publish'].strftime('%Y-%m-%d %H:%M:%S'), 104 | 'video_url':os.path.join(video_url_prefix,_dict['video_url'] or ''), 105 | 'pic_small':os.path.join(video_url_prefix,_dict['pic_small'] or ''), 106 | }) 107 | ress.append(_dict) 108 | return ress 109 | -------------------------------------------------------------------------------- /motiky/cacheutil.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # File: cacheutil.py 3 | 4 | """ 5 | 一个进程内缓存的实现 应急的时候才用 并不是很安全 6 | """ 7 | import types 8 | import time 9 | import hashlib 10 | import json 11 | 12 | from motiky.configs import redis 13 | 14 | LOCALCACHE_POOL={} #本地缓存字典 15 | LOCALCACHE_MAX_ITEM_COUNT=10000 #最大存储对象数量 16 | LOCALCACHE_KILL_COUNT=1000 #缓存满时删除对象数量 17 | 18 | 19 | def rcache(timeout, cachekey=None): 20 | """基于redis 的函数调用缓存""" 21 | def inter1(func): 22 | def inter2(*args,**kwargs): 23 | if cachekey is not None: 24 | callstr = cachekey 25 | else: 26 | params = map(lambda xx:repr(xx),args) 27 | for (k,v) in kwargs.items(): 28 | params.append('%s=%s'%(k,repr(v))) 29 | callstr='CALLCACHE::%s(%s)' % (func.func_name,','.join(params)) 30 | retobj = redis.get(callstr) 31 | if retobj: 32 | return json.loads(retobj) 33 | retobj = func(*args,**kwargs) 34 | rp = redis.pipeline() 35 | rp.set(callstr,json.dumps(retobj)).expire(callstr,timeout).execute() 36 | return retobj 37 | return inter2 38 | return inter1 39 | 40 | def delete_cache(cachekey=None): 41 | """用相同的参数删除rcache""" 42 | def inter1(func): 43 | def inter2(*args,**kwargs): 44 | if cachekey is not None: 45 | callstr = cachekey 46 | else: 47 | params = map(lambda xx:repr(xx),args) 48 | for (k,v) in kwargs.items(): 49 | params.append('%s=%s'%(k,repr(v))) 50 | callstr='CALLCACHE::%s(%s)' % (func.func_name,','.join(params)) 51 | redis.delete(callstr) 52 | return inter2 53 | return inter1 54 | 55 | def ugc_control_set(user_id,obj_type,obj_id,timeout): 56 | """ 57 | 设置UGC的时间 58 | 可以在执行ugc操作之前先过来set 一下,如果返回True则允许下一步 59 | False则因为太频繁不允许ugc 60 | """ 61 | assert type(user_id) == types.IntType 62 | assert type(timeout) == types.IntType 63 | q = { 64 | 'user_id':user_id, 65 | 'obj_type':obj_type, 66 | 'obj_id':obj_id 67 | } 68 | _key = """UGC::%(user_id)s::%(obj_type)s::%(obj_id)s""" % q 69 | ret = redis.get(_key) 70 | if ret != None: 71 | return False 72 | else: 73 | redis.pipeline().set(_key,'on').expire(_key,timeout).execute() 74 | return True 75 | 76 | def callcache(timeout): 77 | """函数的调用缓存""" 78 | def inter1(func): 79 | def inter2(*args,**kwargs): 80 | params=map(lambda xx:repr(xx),args) 81 | for (k,v) in kwargs.items(): 82 | params.append('%s=%s'%(k,repr(v))) 83 | callstr='%s(%s)'%(func.__name__,','.join(params)) 84 | try: 85 | cachedict=LOCALCACHE_POOL[callstr] 86 | if cachedict['timeout']==None: 87 | return cachedict['return'] 88 | elif cachedict['timeout']>time.time(): 89 | return cachedict['return'] 90 | else: 91 | del LOCALCACHE_POOL[callstr] 92 | except KeyError: 93 | pass 94 | retobj=func(*args,**kwargs) 95 | if len(LOCALCACHE_POOL)>=LOCALCACHE_MAX_ITEM_COUNT: 96 | clear_localcache() 97 | cachedict={'return':retobj,} 98 | if timeout: 99 | cachedict['timeout']=int(time.time())+timeout 100 | else: 101 | cachedict['timeout']=None 102 | LOCALCACHE_POOL[callstr]=cachedict 103 | return retobj 104 | return inter2 105 | return inter1 106 | 107 | 108 | def clear_localcache(): 109 | """将本地缓存清理出一块空间来""" 110 | for (callstr,cachedict) in LOCALCACHE_POOL.items(): 111 | if cachedict['timeout'] 4 | 5 | import os 6 | import sys 7 | import time 8 | import uuid 9 | import logging 10 | from datetime import datetime 11 | 12 | import requests 13 | 14 | import flask 15 | from flask import g 16 | from flask import request 17 | from flask import Blueprint 18 | from flask import redirect 19 | from flask import Response 20 | from flask import current_app 21 | from flask import session 22 | from flask import jsonify 23 | from flask import flash 24 | from flask.views import MethodView 25 | from flask.views import View 26 | 27 | from werkzeug import secure_filename 28 | 29 | from motiky import authutil 30 | from motiky.logic import backend 31 | from motiky.coreutil import BackendError 32 | from motiky.logic.models import User,Post,Report,Install,\ 33 | UserFollowAsso,UserLikeAsso,Comment,Activity,Tag,Tagging,\ 34 | CmsUser,Storage 35 | 36 | from motiky.upyun import UpYun,md5,md5file 37 | 38 | instance = Blueprint('admin',__name__) 39 | 40 | upYun = UpYun('xxxxxx','xxxxxx','xxxxxx') 41 | upYun.setApiDomain('v0.api.upyun.com') 42 | upYun.debug = True 43 | 44 | @instance.route('/users') 45 | def users_list(): 46 | page = request.values.get('page','1') 47 | try: 48 | page = int(page) 49 | except: 50 | page = 1 51 | 52 | _users = User.query.order_by(User.date_create.desc()).limit(20).\ 53 | offset((page - 1) * 20).all() 54 | 55 | users_count = User.query.count() 56 | 57 | users = [] 58 | for u in _users: 59 | us = u.json 60 | users.append(us) 61 | 62 | total_page = (users_count + 19) / 20 63 | 64 | return render_template('user_list.html',users=users,page=page,total_page=total_page) 65 | 66 | 67 | @instance.route('/posts') 68 | def post_list(): 69 | page = request.values.get('page','1') 70 | try: 71 | page = int(page) 72 | except: 73 | page = 1 74 | 75 | _posts = Post.query.order_by(Post.date_create.desc()).limit(20).\ 76 | offset((page - 1) * 20).all() 77 | 78 | posts_count = Post.query.count() 79 | 80 | posts = [] 81 | for po in _posts: 82 | pos = po.json 83 | user = po.user.json 84 | pos['user'] = user 85 | posts.append(pos) 86 | 87 | total_page = (posts_count + 19) / 20 88 | 89 | return render_template('post_list.html',posts=posts,count=posts_count, 90 | page=page,total_page=total_page) 91 | 92 | @instance.route('/post//recommend') 93 | def post_recommend(post_id): 94 | post = Post.query.get(post_id) 95 | post.recommended = True 96 | db.session.commit() 97 | return redirect('/admin/posts') 98 | 99 | @instance.route('/post//not_recommend') 100 | def post_recommend(post_id): 101 | post = Post.query.get(post_id) 102 | post.recommended = False 103 | db.session.commit() 104 | return redirect('/admin/posts') 105 | 106 | @instance.route('/post//show') 107 | def post_recommend(post_id): 108 | post = Post.query.get(post_id) 109 | post.show = True 110 | db.session.commit() 111 | return redirect('/admin/posts') 112 | 113 | @instance.route('/post//hide') 114 | def post_recommend(post_id): 115 | post = Post.query.get(post_id) 116 | post.show = False 117 | db.session.commit() 118 | return redirect('/admin/posts') 119 | 120 | @instance.route('/tags') 121 | def tag_list(): 122 | page = request.values.get('page','1') 123 | try: 124 | page = int(page) 125 | except: 126 | page = 1 127 | 128 | _tags = Tag.query.order_by(Post.date_create.desc()).limit(20).\ 129 | offset((page - 1) * 20).all() 130 | 131 | tags_count = Tag.query.count() 132 | 133 | tags = [] 134 | for t in _tags: 135 | t = t.json 136 | tags.append(t) 137 | 138 | total_page = (tags_count + 19) / 20 139 | 140 | return render_template('tag_list.html',tags=tags, 141 | page=page,total_page=total_page) 142 | 143 | @instance.route('/add_tag') 144 | def add_tag(): 145 | if request.method == 'GET': 146 | return render_template('add_tag.html') 147 | 148 | name = request.values.get('name') 149 | pic_url = request.values.get('pic_url') 150 | order_seq = request.values.get('order_seq') 151 | show = request.values.get('show') == 'true' or False 152 | recommended = request.values.get('recommended') == 'true' or False 153 | 154 | tag = Tag() 155 | tag.name = name 156 | tag.pic_url = pic_url 157 | tag.order_seq = order_seq 158 | tag.show = show 159 | tag.recommended = recommended 160 | 161 | try: 162 | db.session.add(tag) 163 | db.session.commit() 164 | except: 165 | db.session.rollback() 166 | raise 167 | else: 168 | return redirect('/admin/tags') 169 | 170 | @instance.route('/tag//edit') 171 | def edit_tag(tag_id): 172 | tag = Tag.query.get(tag_id) 173 | 174 | if request.method == 'GET': 175 | return render_template('edit_tag.html',tag=tag.json) 176 | 177 | name = request.values.get('name') 178 | pic_url = request.values.get('pic_url') 179 | order_seq = request.values.get('order_seq') 180 | show = request.values.get('show') == 'true' or False 181 | recommended = request.values.get('recommended') == 'true' or False 182 | 183 | tag.name = name 184 | tag.pic_url = pic_url 185 | tag.order_seq = order_seq 186 | tag.show = show 187 | tag.recommended = recommended 188 | 189 | try: 190 | db.session.commit() 191 | except: 192 | db.session.rollback() 193 | raise 194 | else: 195 | return redirect('/admin/tags') 196 | 197 | 198 | @instance.route('/upload',methods=['POST','GET']) 199 | def upload(): 200 | if request.method == 'GET': 201 | return render_template('upload.html',file_name='') 202 | 203 | pic_file = request.files.get('upload') 204 | fname = secure_filename(pic_file.filename) 205 | extn = os.path.splitext(fname)[1] 206 | cname = '/' + str(uuid.uuid4()) + str(extn) 207 | pic_data = strutil.read_data(pic_file) 208 | upYun.writeFile(cname,pic_data) 209 | 210 | cname = upyun_prefix + cname 211 | return render_template('upload.html',file_name=cname) 212 | 213 | 214 | 215 | -------------------------------------------------------------------------------- /motiky/logic/logic_user.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # author: notedit 3 | # date: 2013-01-30 4 | 5 | import types 6 | import traceback 7 | 8 | 9 | from motiky.coreutil import BackendError,register,assert_error 10 | 11 | from motiky.logic.models import User,Post,UserFollowAsso 12 | 13 | from motiky.configs import db 14 | 15 | @register('get_user') 16 | def get_user(user_id): 17 | multi = False 18 | if type(user_id) == types.ListType: 19 | assert_error(all([type(u) == types.IntType for u in user_id]),'ParamError') 20 | multi = True 21 | else: 22 | assert_error(type(user_id) == types.IntType,'ParamError') 23 | user_id = user_id, 24 | 25 | users = User.query.filter(User.id.in_(user_id)).all() 26 | if not users: 27 | raise BackendError('EmptyError','用户不存在') 28 | 29 | if multi: 30 | return [u.json for u in users] 31 | else: 32 | return users[0].json 33 | 34 | @register('get_user_by_username') 35 | def get_user_by_username(username): 36 | user = User.query.filter(User.username == username).first() 37 | return user.json if user else {} 38 | 39 | @register('get_user_by_email') 40 | def get_user_by_email(email): 41 | user = User.query.filter(User.email == email).first() 42 | return user.json if user else {} 43 | 44 | @register('is_username_exist') 45 | def is_username_exist(username): 46 | assert_error(type(username) == types.StringType,'ParamError') 47 | return True if _check_username(username) else False 48 | 49 | @register('is_email_exist') 50 | def is_email_exist(email): 51 | assert_error(type(email) == types.StringType,'ParamError') 52 | return True if _check_email(email) else False 53 | 54 | def _check_username(username): 55 | u = User.query.filter(db.func.lower(User.username) == username).first() 56 | return u 57 | 58 | def _check_email(email): 59 | u = User.query.filter(User.email == email).first() 60 | return u 61 | 62 | 63 | @register('add_user') 64 | def add_user(username,photo_url,uid,signature='',access_token=''): 65 | assert_error(type(username)==types.StringType,'ParamError','用户昵称应该为字符串') 66 | assert_error(photo_url == None or type(photo_url) == types.StringType,'ParamError') 67 | assert_error(type(uid) == types.StringType,'ParamError') 68 | 69 | qd = { 70 | 'username':username, 71 | 'photo_url':photo_url or '', 72 | 'uid':uid, 73 | 'signature':signature, 74 | 'access_token':access_token 75 | } 76 | 77 | try: 78 | user = User(**qd) 79 | db.session.add(user) 80 | db.session.commit() 81 | except: 82 | db.session.rollback() 83 | raise BackendError('InternalError',traceback.format_exc()) 84 | 85 | return user.json 86 | 87 | @register('set_user') 88 | def set_user(user_id,info_d): 89 | assert_error(type(user_id) == types.IntType,'ParamError') 90 | user = User.query.get(user_id) 91 | try: 92 | for k,v in info_d.items(): 93 | if v is not None: 94 | setattr(user,k,v) 95 | db.session.commit() 96 | except: 97 | db.session.rollback() 98 | raise 99 | else: 100 | return user.json 101 | 102 | @register('get_user_by_uid') 103 | def get_user_by_uid(uid): 104 | assert_error(type(uid) in (types.StringType,types.ListType),'ParamError') 105 | multi = False 106 | if type(uid) == types.ListType: 107 | multi = True 108 | else: 109 | uid = uid, 110 | 111 | users = User.query.filter(User.uid.in_(uid)).all() 112 | if len(users) == 0: 113 | raise BackendError('EmptyError','用户不存在') 114 | 115 | if multi: 116 | return [u.json for u in users] 117 | else: 118 | return users[0].json 119 | 120 | 121 | @register('follow_user') 122 | def follow_user(fid,tid): 123 | assert_error(all([type(_id) == types.IntType for _id in [fid,tid]]),'ParamError') 124 | try: 125 | asso = UserFollowAsso(user_id=fid,user_id_to=tid) 126 | db.session.add(asso) 127 | db.session.commit() 128 | except: 129 | db.session.rollback() 130 | raise 131 | else: 132 | return asso.id 133 | 134 | @register('unfollow_user') 135 | def unfollow_user(fid,tid): 136 | assert_error(all([type(_id) == types.IntType for _id in [fid,tid]]),'ParamError') 137 | asso = UserFollowAsso.query.filter(db.and_(UserFollowAsso.user_id==fid,UserFollowAsso.user_id_to==tid)).\ 138 | first() 139 | if asso is None: 140 | return 141 | try: 142 | db.session.delete(asso) 143 | db.session.commit() 144 | except: 145 | db.session.rollback() 146 | raise 147 | else: 148 | return True 149 | 150 | @register('is_following_user') 151 | def is_following_user(uid,uid_to): 152 | if type(uid_to) == types.IntType: 153 | _count = db.session.query(UserFollowAsso.id).\ 154 | filter(db.and_(UserFollowAsso.user_id == uid,UserFollowAsso.user_id_to == uid_to)).count() 155 | return True if _count > 0 else False 156 | elif type(uid_to) == types.ListType: 157 | follow_uids = db.session.query(UserFollowAsso.user_id_to).\ 158 | filter(db.and_(UserFollowAsso.user_id == uid,UserFollowAsso.user_id_to.in_(uid_to))).all() 159 | follow_uids = [u[0] for u in follow_uids] 160 | ret_list = [(ret,ret in follow_uids) for ret in uid_to] 161 | return dict(ret_list) 162 | 163 | @register('get_user_following') 164 | def get_user_following(user_id,limit=50,offset=0): 165 | assert_error(type(user_id) == types.IntType,'ParamError') 166 | follows = User.query.join(UserFollowAsso,User.id == UserFollowAsso.user_id_to).\ 167 | filter(UserFollowAsso.user_id == user_id).limit(limit).offset(offset).all() 168 | return [u.json for u in follows] 169 | 170 | @register('get_user_following_count') 171 | def get_user_following_count(user_id): 172 | _count = UserFollowAsso.query.filter(UserFollowAsso.user_id == user_id).count() 173 | return _count 174 | 175 | @register('get_user_follower') 176 | def get_user_follower(user_id,limit=50,offset=0): 177 | assert_error(type(user_id) == types.IntType,'ParamError') 178 | follows = User.query.join(UserFollowAsso,User.id == UserFollowAsso.user_id).\ 179 | filter(UserFollowAsso.user_id_to == user_id).limit(limit).offset(offset).all() 180 | return [u.json for u in follows] 181 | 182 | @register('get_user_follower_count') 183 | def get_user_follower_count(user_id): 184 | _count = UserFollowAsso.query.filter(UserFollowAsso.user_id_to == user_id).count() 185 | return _count 186 | 187 | 188 | 189 | 190 | 191 | -------------------------------------------------------------------------------- /motiky/logic/logic_post.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # author: notedit 3 | # date: 2013-01-30 4 | 5 | import types 6 | import traceback 7 | 8 | from motiky.coreutil import BackendError,register,assert_error 9 | 10 | from motiky.logic.models import User,Post,UserLikeAsso,UserPlayAsso 11 | 12 | from motiky.configs import db 13 | 14 | 15 | @register('get_post') 16 | def get_post(post_id): 17 | post = Post.query.get(post_id) 18 | return post.json 19 | 20 | @register('add_post') 21 | def add_post(title,author_id,video_url,pic_small='',pic_big='',show=True,recommended=False): 22 | assert_error(type(title) == types.StringType,'ParamError') 23 | assert_error(type(author_id) == types.IntType,'ParamError') 24 | assert_error(type(video_url) == types.StringType,'ParamError') 25 | 26 | qd = { 27 | 'title':title, 28 | 'author_id':author_id, 29 | 'video_url':video_url, 30 | 'pic_small':pic_small, 31 | 'pic_big':pic_big, 32 | 'show':show, 33 | 'recommended':recommended, 34 | } 35 | try: 36 | p = Post(**qd) 37 | db.session.add(p) 38 | db.session.commit() 39 | except: 40 | db.session.rollback() 41 | raise BackendError('InternalError',traceback.format_exc()) 42 | return p.json 43 | 44 | 45 | 46 | @register('set_post') 47 | def set_post(post_id,pdict): 48 | fd_list = ('title','pic_small','pic_big','author_id','show', 49 | 'recommended','date_create','date_update','date_publish') 50 | cset = set(pdict.keys()) 51 | if not cset.issubset(fd_list): 52 | raise BackendError('ParamError','更新的字段不允许') 53 | post = Post.query.get(post_id) 54 | for k,v in pdict.items(): 55 | if v is not None: 56 | setattr(post,k,v) 57 | try: 58 | db.session.commit() 59 | except: 60 | db.session.rollback() 61 | raise 62 | 63 | return post.json 64 | 65 | 66 | @register('get_latest_post') 67 | def get_latest_post(offset=0,limit=50): 68 | posts = Post.query.filter(Post.show == True).order_by(Post.date_create.desc()).\ 69 | limit(limit).offset(offset).all() 70 | 71 | _posts = [] 72 | for p in posts: 73 | _u = p.user.json 74 | _p = p.json 75 | _p.update({'user':_u}) 76 | _posts.append(_p) 77 | 78 | return _posts 79 | 80 | 81 | @register('get_post_count') 82 | def get_post_count(): 83 | count = Post.query.filter(Post.show == True).count() 84 | return count 85 | 86 | @register('get_hot_post') 87 | def get_hot_post(offset=0,limit=50): 88 | posts = Post.query.filter(Post.show == True).order_by(Post.visite_count.desc()).\ 89 | limit(limit).offset(offset).all() 90 | 91 | _posts = [] 92 | for p in posts: 93 | _u = p.user.json 94 | _p = p.json 95 | _p.update({'user':_u}) 96 | _posts.append(_p) 97 | 98 | return _posts 99 | 100 | @register('get_user_post') 101 | def get_user_post(user_id,offset=0,limit=20): 102 | assert_error(offset >= 0,'ParamError') 103 | posts = Post.query.filter(Post.author_id == user_id).\ 104 | order_by(Post.date_create.desc()).\ 105 | limit(limit).offset(offset).all() 106 | 107 | _posts = [] 108 | for p in posts: 109 | _u = p.user.json 110 | _p = p.json 111 | _p.update({'user':_u}) 112 | _posts.append(_p) 113 | 114 | return _posts 115 | 116 | @register('get_user_post_count') 117 | def get_user_post_count(user_id): 118 | count = Post.query.filter(Post.author_id == user_id).count() 119 | return count 120 | 121 | 122 | @register('get_user_liked_post') 123 | def get_user_liked_post(user_id,offset=0,limit=20): 124 | assert_error(offset >= 0,'ParamError') 125 | posts = Post.query.join(UserLikeAsso,Post.id == UserLikeAsso.post_id).\ 126 | filter(UserLikeAsso.user_id == user_id).\ 127 | order_by(UserLikeAsso.date_create.desc()).\ 128 | limit(limit).offset(offset).all() 129 | 130 | _posts = [] 131 | for p in posts: 132 | _u = p.user.json 133 | _p = p.json 134 | _p.update({'user':_u}) 135 | _posts.append(_p) 136 | 137 | return _posts 138 | 139 | @register('get_user_liked_post_count') 140 | def get_user_liked_post_count(user_id): 141 | count = Post.query.join(UserLikeAsso,Post.id == UserLikeAsso.post_id).\ 142 | filter(UserLikeAsso.user_id == user_id).count() 143 | return count 144 | 145 | @register('get_post_liked_count') 146 | def get_post_liked_count(post_id): 147 | count = UserLikeAsso.query.filter(UserLikeAsso.post_id == post_id).count() 148 | return count 149 | 150 | @register('add_like') 151 | def add_like(user_id,post_id): 152 | ula = UserLikeAsso() 153 | ula.user_id = user_id 154 | ula.post_id = post_id 155 | try: 156 | db.session.add(ula) 157 | db.session.commit() 158 | except: 159 | db.session.rollback() 160 | raise BackendError('InternalError',traceback.format_exc()) 161 | else: 162 | return ula.id 163 | 164 | @register('del_like') 165 | def del_like(user_id,post_id): 166 | assert_error(all([type(x) == types.IntType for x in [user_id,post_id]]), 167 | 'ParamError') 168 | ula = UserLikeAsso.query.filter(UserLikeAsso.user_id == user_id).\ 169 | filter(UserLikeAsso.post_id == post_id).first() 170 | if ula is None: 171 | return 172 | try: 173 | db.session.delete(ula) 174 | db.session.commit() 175 | except: 176 | db.session.rollback() 177 | raise BackendError('InternalError',traceback.format_exc()) 178 | else: 179 | return True 180 | 181 | 182 | @register('add_play') 183 | def add_play(user_id,post_id): 184 | assert_error(all([type(x) for x in [user_id,post_id]]),'ParamError') 185 | ura = UserPlayAsso() 186 | ura.user_id = user_id 187 | ura.post_id = post_id 188 | 189 | try: 190 | db.session.add(ura) 191 | db.session.commit() 192 | except: 193 | db.session.rollback() 194 | raise BackendError('InternalError',traceback.format_exc()) 195 | 196 | @register('add_play_count') 197 | def add_play_count(post_id,count=1): 198 | post = Post.query.get(post_id) 199 | post.play_count += count 200 | try: 201 | db.session.commit() 202 | except: 203 | db.session.rollback() 204 | raise BackendError('InternalError',traceback.format_exc()) 205 | else: 206 | return post.json 207 | 208 | @register('is_like_post') 209 | def is_like_post(uid,post_id): 210 | if type(post_id) == types.IntType: 211 | _count = db.session.query(UserLikeAsso.id).\ 212 | filter(db.and_(UserLikeAsso.user_id == uid, 213 | UserLikeAsso.post_id == post_id)).count() 214 | return True if _count > 0 else False 215 | elif type(post_id) == types.ListType: 216 | liked_post_ids = db.session.query(UserLikeAsso.post_id).\ 217 | filter(db.and_(UserLikeAsso.user_id == uid, 218 | UserLikeAsso.post_id.in_(post_id))).all() 219 | liked_post_ids = [p[0] for p in liked_post_ids] 220 | ret_list = [(ret,ret in liked_post_ids) for ret in post_id] 221 | return dict(ret_list) 222 | 223 | -------------------------------------------------------------------------------- /motiky/upyun.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf8 -*- 2 | import httplib 3 | import md5 as imd5 4 | import base64 5 | import time 6 | import re 7 | 8 | 9 | METADATA_PREFIX = 'x-upyun-meta-' 10 | DL = '/' 11 | 12 | 13 | def md5(src): 14 | m1 = imd5.new() 15 | m1.update(src) 16 | dest1 = m1.hexdigest() 17 | return dest1 18 | 19 | def md5file(fobj): 20 | m = imd5.new() 21 | while True: 22 | d = fobj.read(8096) 23 | if not d: 24 | break 25 | m.update(d) 26 | fobj.seek(0) 27 | return m.hexdigest() 28 | 29 | 30 | def merge_meta(headers, metadata): 31 | final_headers = headers.copy() 32 | for k in metadata.keys(): 33 | final_headers[METADATA_PREFIX + k] = metadata[k] 34 | return final_headers 35 | 36 | class UpYunException(Exception): 37 | '''Raised when a Yupoo method fails. 38 | 39 | More specific details will be included in the exception message 40 | when thrown. 41 | ''' 42 | 43 | #目录条目类 44 | class FolderItem(object): 45 | def __init__(self, filename, filetype, size, number): 46 | self.filename = filename 47 | self.filetype = filetype 48 | self.size = size 49 | self.number = number 50 | 51 | 52 | class UpYun(object): 53 | def __init__(self, bucket, username, password): 54 | self.thehost = 'v0.api.upyun.com' 55 | self.username = username 56 | self.password = password 57 | self.bucket = bucket 58 | self.upAuth = False 59 | self.debug = False 60 | self._tmp_info = None 61 | self.content_md5 = '' 62 | self.file_secret = '' 63 | 64 | #版本 65 | def version(self): 66 | return '1.0.1' 67 | 68 | #设置待上传文件的 Content-MD5 值(如又拍云服务端收到的文件MD5值与用户设置的不一致,将回报 406 Not Acceptable 错误) 69 | def setContentMD5(self, vaule): 70 | self.content_md5 = vaule 71 | 72 | #设置待上传文件的 访问密钥(注意:仅支持图片空!,设置密钥后,无法根据原文件URL直接访问,需带 URL 后面加上 (缩略图间隔标志符+密钥) 进行访问) 73 | #如缩略图间隔标志符为 ! ,密钥为 bac,上传文件路径为 /folder/test.jpg ,那么该图片的对外访问地址为: http://空间域名/folder/test.jpg!bac 74 | def setFileSecret(self, vaule): 75 | self.file_secret = vaule 76 | 77 | #设定api所调用的域名,包括电信,联通,网通,移动,铁通和自动选择 78 | def setApiDomain(self,thehost): 79 | self.thehost = thehost 80 | 81 | #设定是否使用又拍签名 82 | def setAuthType(self,upAuth): 83 | self.upAuth = upAuth 84 | 85 | def getList(self, path='', headers={}, metadata={}): 86 | resp = self._net_worker( 'GET', DL+self.bucket+DL+path, '', headers, metadata) 87 | return resp 88 | 89 | def delete(self, path, headers={}, metadata={}): 90 | resp = self._net_worker('DELETE',DL+self.bucket+DL+path, '',headers,metadata) 91 | return resp 92 | 93 | #获取空间占用大小 94 | def getBucketUsage(self, path='', headers={}, metadata={}): 95 | resp = self.getList(path+'?usage', headers, metadata) 96 | try: 97 | resp = int(resp.read()) 98 | except Exception, e: 99 | resp = None 100 | return resp 101 | 102 | #获取某个目录的空间占用大小 103 | #path目录路径 104 | def getFolderUsage(self, path='', headers={}, metadata={}): 105 | resp = self.getBucketUsage(path, headers, metadata) 106 | return resp 107 | 108 | #新建目录 109 | #path目录路径 110 | #[auto] 是否自动创建父级目录(最多10级) 111 | def mkDir(self, path, auto=False, headers={}, metadata={}): 112 | headers['folder'] = 'create' 113 | if auto == True : 114 | headers['mkdir'] = 'true' 115 | resp = self._net_worker('POST', DL+self.bucket+DL+path, '', headers, metadata) 116 | if resp.status == 200 : 117 | return True 118 | else : 119 | return False 120 | 121 | #删除目录 122 | #path目录路径 123 | def rmDir(self, path, headers={}, metadata={}): 124 | resp = self.delete(path,headers,metadata) 125 | if resp.status == 200 : 126 | return True 127 | else : 128 | return False 129 | 130 | #读取目录,返回FolderItem 131 | #path目录路径 132 | def readDir(self, path='', headers={}, metadata={}): 133 | resp = self.getList(path, headers, metadata) 134 | if resp.status == 200 : 135 | result = re.sub('\t', '\/', resp.read()) 136 | result = re.sub('\n', '\/', result) 137 | b = result.split('\/') 138 | i=0 139 | fis = [] 140 | while i+1 4 | 5 | import os 6 | import sys 7 | import md5 8 | import time 9 | import logging 10 | import flask 11 | from flask import g 12 | from flask import request 13 | from flask import Blueprint 14 | from flask import redirect 15 | from flask import Response 16 | from flask import current_app 17 | from flask import session 18 | from flask import jsonify 19 | from flask import flash 20 | from flask.views import MethodView 21 | from flask.views import View 22 | 23 | from motiky import authutil 24 | from motiky import strutil 25 | from motiky.logic import backend 26 | from motiky.coreutil import BackendError 27 | 28 | from motiky.upyun import UpYun,md5,md5file 29 | 30 | from motiky.schema import NewPostSchema,UpdatePostSchema,\ 31 | PostLikeSchema,PostUnlikeSchema 32 | 33 | instance = Blueprint('post',__name__) 34 | 35 | upYun = UpYun('xxxxxxxx','xxxxxxxxxx','xxxxxxxxx') 36 | upYun.setApiDomain('v0.api.upyun.com') 37 | upYun.debug = True 38 | 39 | def save_file(file_data,ftype): 40 | file_md5 = md5(file_data) 41 | file_size = len(file_data) 42 | file_id = backend.add_file_data(file_size,file_md5) 43 | extname = 'mp4' if ftype == 'video' else 'jpg' 44 | file_url = '%s.%s' % (file_id,extname) 45 | if current_app.config.get('CONFIG_TYPE') == 'production': 46 | upYun.writeFile('/'+file_url,file_data) 47 | else: 48 | writeFile('/storage/' + file_url,file_data) 49 | return file_id,file_url 50 | 51 | 52 | def writeFile(file_url,file_data): 53 | dirname = os.path.dirname(file_url) 54 | if not os.path.exists(dirname): 55 | os.makedirs(dirname,0777) 56 | 57 | with open(file_url,'wb') as f: 58 | f.write(file_data) 59 | f.flush() 60 | 61 | class PostView(MethodView): 62 | 63 | def get(self,post_id): 64 | post = backend.get_post(post_id) 65 | curr_user = backend.get_user_by_uid(g.ukey) 66 | post['is_like'] = backend.is_like_post(curr_user['id'],post['id']) 67 | post['like_count'] = backend.get_post_liked_count(post_id) 68 | post['comment_count'] = backend.get_post_comment_count(post_id) 69 | return jsonify(**post) 70 | 71 | def post(self): 72 | _data = { 73 | 'author_id':request.values.get('author_id'), 74 | 'title':request.values.get('title'), 75 | } 76 | data = NewPostSchema().deserialize(_data) 77 | 78 | user = backend.get_user(data['author_id']) 79 | 80 | if user['uid'] != g.ukey: 81 | return jsonify(error='not the user') 82 | 83 | # video_file 84 | video_file = request.files.get('video_file') 85 | video_data = strutil.read_data(video_file) 86 | video_id,video_url = save_file(video_data,'video') 87 | 88 | # pic_file 89 | pic_file = request.files.get('pic_file') 90 | pic_data = strutil.read_data(pic_file) 91 | pic_id,pic_url = save_file(pic_data,'pic') 92 | 93 | data['title'] = data['title'].encode('utf-8') if data['title'] else '' 94 | try: 95 | post = backend.add_post( 96 | data['title'], 97 | data['author_id'], 98 | video_url, 99 | pic_small=pic_url 100 | ) 101 | except BackendError,ex: 102 | raise 103 | 104 | tags = strutil.extract_tags(data['title']) 105 | 106 | if tags: 107 | backend.add_post_tag(post['id'],tags) 108 | 109 | return jsonify(**post) 110 | 111 | def put(self,post_id): 112 | data = UpdatePostSchema().deserialize(request.json) 113 | try: 114 | backend.set_post(post_id,data) 115 | except BackendError,ex: 116 | abort(501) 117 | else: 118 | return '',204 119 | 120 | def delete(self,post_id): 121 | post = backend.get_post(post_id) 122 | curr_id = authutil.get_user_id(g.ukey) 123 | if post['author_id'] != curr_id: 124 | return jsonify(error='forbid') 125 | try: 126 | backend.set_post(post_id,{'show':'deleted_by_user'}) 127 | except BackendError,ex: 128 | abort(501) 129 | else: 130 | return '',204 131 | 132 | 133 | class PostListView(MethodView): 134 | 135 | def get(self): 136 | try: 137 | page = int(request.values.get('page','1')) 138 | except: 139 | page = 1 140 | 141 | limit = 20 142 | offset = (page-1)*limit 143 | 144 | posts = backend.get_latest_post(limit=limit,offset=offset) 145 | count = backend.get_post_count() 146 | 147 | for post in posts: 148 | post['like_count'] = backend.get_post_liked_count(post['id']) 149 | post['comment_count'] = backend.get_post_comment_count(post['id']) 150 | 151 | total_page = (count + limit -1 ) / limit 152 | return jsonify(posts=posts,page=page,total_page=total_page) 153 | 154 | 155 | class UserPostView(MethodView): 156 | 157 | def get(self,user_id): 158 | try: 159 | page = int(request.values.get('page','1')) 160 | except: 161 | page = 1 162 | 163 | limit = 20 164 | offset = (page-1) * limit 165 | 166 | curr_user = backend.get_user_by_uid(g.ukey) 167 | user_posts = backend.get_user_post(user_id,limit=limit,offset=offset) 168 | 169 | liked_post_ids = [p['id'] for p in user_posts]; 170 | liked_dict = backend.is_like_post(curr_user['id'],liked_post_ids) 171 | for up in user_posts: 172 | up['is_like'] = liked_dict.get(up['id']) or False 173 | up['like_count'] = backend.get_post_liked_count(up['id']) 174 | up['comment_count'] = backend.get_post_comment_count(up['id']) 175 | 176 | 177 | 178 | count = backend.get_user_post_count(user_id) 179 | total_page = (count + limit - 1) / limit 180 | 181 | return jsonify(posts=user_posts,page=page,total_page=total_page) 182 | 183 | class UserLikedPostView(MethodView): 184 | 185 | def get(self,user_id): 186 | try: 187 | page = int(request.values.get('page')) 188 | except: 189 | page = 1 190 | 191 | limit = 20 192 | offset = (page-1) * limit 193 | 194 | liked_posts = backend.get_user_liked_post(user_id,limit=limit,offset=offset) 195 | for p in liked_posts: 196 | p['is_like'] = True 197 | p['like_count'] = backend.get_post_liked_count(p['id']) 198 | p['comment_count'] = backend.get_post_comment_count(p['id']) 199 | 200 | 201 | count = backend.get_user_liked_post_count(user_id) 202 | total_page = (count + limit -1) / limit 203 | 204 | return jsonify(posts=liked_posts,page=page,total_page=total_page) 205 | 206 | class PostLikeView(MethodView): 207 | 208 | def post(self): 209 | data = PostLikeSchema().deserialize(request.json) 210 | try: 211 | ret = backend.add_like(data['user_id'],data['post_id']) 212 | except BackendError,ex: 213 | return jsonify(error='can not add like') 214 | 215 | try: 216 | post = backend.get_post(data['post_id']) 217 | backend.add_activity({ 218 | 'post_id':data['post_id'], 219 | 'from_id':data['user_id'], 220 | 'to_id':post['author_id'], 221 | 'atype':'like' 222 | }) 223 | except BackendError,ex: 224 | pass 225 | 226 | liked_count = backend.get_post_liked_count(data['post_id']) 227 | 228 | return jsonify(like_count=liked_count) 229 | 230 | class PostUnlikeView(MethodView): 231 | 232 | def post(self): 233 | data = PostUnlikeSchema().deserialize(request.json) 234 | try: 235 | ret = backend.del_like(data['user_id'],data['post_id']) 236 | except BackendError,ex: 237 | raise 238 | 239 | liked_count = backend.get_post_liked_count(data['post_id']) 240 | 241 | return jsonify(like_count=liked_count) 242 | 243 | 244 | instance.add_url_rule('/post',view_func=PostView.as_view('post'), 245 | methods=['POST']) 246 | instance.add_url_rule('/post/',view_func=PostView.as_view('post'), 247 | methods=['GET','PUT','DELETE']) 248 | instance.add_url_rule('/posts',view_func=PostListView.as_view('posts'), 249 | methods=['GET']) 250 | instance.add_url_rule('/posts/user/', 251 | view_func=UserPostView.as_view('user_post'), 252 | methods=['GET',]) 253 | instance.add_url_rule('/posts/user//liked', 254 | view_func=UserLikedPostView.as_view('user_liked_post'), 255 | methods=['GET',]) 256 | instance.add_url_rule('/post/like', 257 | view_func=PostLikeView.as_view('post_like'), 258 | methods=['POST',]) 259 | instance.add_url_rule('/post/unlike', 260 | view_func=PostUnlikeView.as_view('post_unlike'), 261 | methods=['POST',]) 262 | 263 | 264 | 265 | 266 | 267 | 268 | -------------------------------------------------------------------------------- /motiky/views/user.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # author: notedit 4 | 5 | import sys 6 | import time 7 | import logging 8 | from datetime import datetime 9 | 10 | import requests 11 | 12 | import flask 13 | from flask import g 14 | from flask import request 15 | from flask import Blueprint 16 | from flask import redirect 17 | from flask import Response 18 | from flask import current_app 19 | from flask import session 20 | from flask import jsonify 21 | from flask import flash 22 | from flask.views import MethodView 23 | from flask.views import View 24 | 25 | from motiky import authutil 26 | from motiky.logic import backend 27 | from motiky.coreutil import BackendError 28 | 29 | from motiky.schema import NewUserSchema,UpdateUserSchema,UserFollowSchema,\ 30 | InstallSchema,PushSchema 31 | 32 | instance = Blueprint('user',__name__) 33 | 34 | class UserView(MethodView): 35 | 36 | def get(self,user_id): 37 | user = backend.get_user(user_id) 38 | return jsonify(**user) 39 | 40 | def get_weibo_info(self,uid,access_token): 41 | if current_app.config.get('CONFIG_TYPE') == 'test': 42 | return {'username':'notedit', 43 | 'photo_url':'photo_url', 44 | 'signature':'signature'} 45 | 46 | resp = requests.get('https://api.weibo.com/2/users/show.json', 47 | params={ 48 | 'uid':uid, 49 | 'access_token':access_token 50 | }) 51 | _ = resp.json 52 | return {'username':_['screen_name'], 53 | 'photo_url':_['avatar_large'], 54 | 'signature':_['description']} 55 | 56 | 57 | def post(self): 58 | data = NewUserSchema().deserialize(request.json) 59 | print 'data',data 60 | try: 61 | user = backend.get_user_by_uid(data['uid'].encode('utf-8')) 62 | user = backend.set_user(user['id'], 63 | { 64 | 'uid':data['uid'].encode('utf-8'), 65 | 'access_token':data['access_token'].encode('utf-8') 66 | }) 67 | except BackendError,ex: 68 | if ex.message == 'EmptyError': 69 | user = {} 70 | else: 71 | return jsonify(error='服务器开小差了') 72 | else: 73 | return jsonify(**user) 74 | 75 | _data = self.get_weibo_info(data['uid'],data['access_token']) 76 | data.update(_data) 77 | try: 78 | user = backend.add_user( 79 | data['username'].encode('utf-8'), 80 | data['photo_url'].encode('utf-8'), 81 | data['uid'].encode('utf-8'), 82 | data['signature'].encode('utf-8'), 83 | data['access_token'].encode('utf-8') 84 | ) 85 | except BackendError,ex: 86 | return jsonify(error='add new user error') 87 | 88 | print 'add user',user 89 | user.update({'new':True}) 90 | return jsonify(**user) 91 | 92 | def put(self,user_id): 93 | data = UpdateUserSchema().deserialize(request.json) 94 | try: 95 | user = backend.set_user(user_id,data) 96 | except BackendError,ex: 97 | raise ex 98 | else: 99 | return '',204 100 | 101 | 102 | class InstallView(MethodView): 103 | 104 | def post(self): 105 | data = InstallSchema().deserialize(request.json) 106 | 107 | user_id = data['user_id'] 108 | try: 109 | install = backend.get_install_by_user(user_id) 110 | except BackendError,ex: 111 | install = None 112 | 113 | if not install: 114 | install = backend.new_install( 115 | data['user_id'], 116 | data['device_token'].encode('utf-8'), 117 | data['version'], 118 | data['device_type']) 119 | 120 | return jsonify(install_id=install['id']) 121 | 122 | 123 | class UserFollowView(MethodView): 124 | 125 | def post(self): 126 | '''关注用户''' 127 | data = UserFollowSchema().deserialize(request.json) 128 | from_id = authutil.get_user_id(g.ukey) 129 | for uid in data['user_ids']: 130 | try: 131 | backend.follow_user(from_id,uid) 132 | backend.add_activity({ 133 | 'from_id':from_id, 134 | 'to_id':uid, 135 | 'atype':'follow' 136 | }) 137 | except BackendError,ex: 138 | pass 139 | 140 | return '',201 141 | 142 | 143 | def delete(self,user_id_to): 144 | '''取消关注''' 145 | user_id = authutil.get_user_id(g.ukey) 146 | try: 147 | backend.unfollow_user(user_id,user_id_to) 148 | except BackendError,ex: 149 | raise 150 | return '',204 151 | 152 | 153 | class UserIsFollowingView(MethodView): 154 | 155 | def get(self,user_id_to): 156 | user_id = authutil.get_user_id(g.ukey) 157 | ret = backend.is_following_user(user_id,user_id_to) 158 | return jsonify(is_follow=ret) 159 | 160 | 161 | class UserFollowingView(MethodView): 162 | 163 | def get(self,user_id): 164 | try: 165 | page = int(request.values.get('page')) 166 | except: 167 | page = 1 168 | 169 | limit = 50 170 | offset = (page-1) * 50 171 | 172 | following_users = backend.get_user_following(user_id,limit=limit, 173 | offset=offset) 174 | 175 | curr_user = backend.get_user_by_uid(g.ukey) 176 | 177 | fids = [u['id'] for u in following_users] 178 | fdict = backend.is_following_user(curr_user['id'],fids) 179 | for fu in following_users: 180 | fu['follower_count'] = backend.get_user_follower_count(fu['id']) 181 | fu['is_follow'] = fdict.get(fu['id']) or False 182 | count = backend.get_user_following_count(user_id) 183 | total_page = (count + 49) / 50 184 | return jsonify(users=following_users,page=page,total_page=total_page) 185 | 186 | 187 | class UserFollowerView(MethodView): 188 | 189 | def get(self,user_id): 190 | try: 191 | page = int(request.values.get('page')) 192 | except: 193 | page = 1 194 | 195 | limit = 50 196 | offset = (page - 1) * 50 197 | followers = backend.get_user_follower(user_id,limit=limit,offset=offset) 198 | fids = [u['id'] for u in followers] 199 | 200 | curr_user = backend.get_user_by_uid(g.ukey) 201 | 202 | fdict = backend.is_following_user(curr_user['id'],fids) 203 | for fu in followers: 204 | fu['follower_count'] = backend.get_user_follower_count(fu['id']) 205 | fu['is_follow'] = fdict.get(fu['id']) or False 206 | count = backend.get_user_follower_count(user_id) 207 | total_page = (count + 49) / 50 208 | return jsonify(users=followers,page=page,total_page=total_page) 209 | 210 | class ProfileView(MethodView): 211 | 212 | def get(self,user_id): 213 | user = backend.get_user(user_id) 214 | 215 | user_following_count = backend.get_user_following_count(user_id) 216 | user_follower_count = backend.get_user_follower_count(user_id) 217 | user_post_count = backend.get_user_post_count(user_id) 218 | user_liked_post_count = backend.get_user_liked_post_count(user_id) 219 | 220 | curr_user = backend.get_user_by_uid(g.ukey) 221 | 222 | is_follow = backend.is_following_user(curr_user['id'],user_id) 223 | 224 | pd = { 225 | 'is_follow':is_follow, 226 | 'following_count':user_following_count, 227 | 'follower_count':user_follower_count, 228 | 'post_count':user_post_count, 229 | 'liked_post_count':user_liked_post_count 230 | } 231 | user.update(pd) 232 | return jsonify(**user) 233 | 234 | 235 | instance.add_url_rule('/user',view_func=UserView.as_view('user'), 236 | methods=['POST',]) 237 | instance.add_url_rule('/user/',view_func=UserView.as_view('user'), 238 | methods=['GET','PUT']) 239 | instance.add_url_rule('/install',view_func=InstallView.as_view('install'), 240 | methods=['POST']) 241 | instance.add_url_rule('/user/follow',view_func=UserFollowView.as_view('user_follow'), 242 | methods=['POST']) 243 | instance.add_url_rule('/user/follow/',view_func=UserFollowView.as_view('user_follow'), 244 | methods=['DELETE']) 245 | instance.add_url_rule('/user/isfollowing/', 246 | view_func=UserIsFollowingView.as_view('user_is_following'), 247 | methods=['GET']) 248 | instance.add_url_rule('/user/following/', 249 | view_func=UserFollowingView.as_view('user_following'), 250 | methods=['GET']) 251 | instance.add_url_rule('/user/follower/', 252 | view_func=UserFollowerView.as_view('user_follower'), 253 | methods=['GET']) 254 | instance.add_url_rule('/user/profile/', 255 | view_func=ProfileView.as_view('user_profile'), 256 | methods=['GET']) 257 | 258 | -------------------------------------------------------------------------------- /motiky/logic/models.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | 4 | # add your models here 5 | import os 6 | import uuid 7 | import contextlib 8 | from datetime import datetime 9 | 10 | from flask import current_app 11 | from werkzeug import cached_property 12 | from sqlalchemy.dialects.postgresql import ARRAY,UUID 13 | 14 | from motiky.configs import db,DefaultConfig 15 | 16 | DATE_FMT = '%Y-%m-%d %H:%M:%S' 17 | DATE_DEFAULT = '2013-05-30 12:00:00' 18 | 19 | def format_date(date): 20 | return date.strftime(DATE_FMT) if date else DATE_DEFAULT 21 | 22 | class User(db.Model): 23 | 24 | __tablename__ = 'user_info' 25 | id = db.Column(db.Integer,primary_key=True) 26 | username = db.Column(db.String(50)) 27 | email = db.Column(db.String(50),unique=True) 28 | photo_url = db.Column(db.String(100)) 29 | signature = db.Column(db.String(255)) 30 | status = db.Column(db.String(20)) 31 | uid = db.Column(db.String(100),index=True,unique=True) 32 | push_on = db.Column(db.Boolean,default=True) 33 | access_token = db.Column(db.String(128)) 34 | date_create = db.Column(db.DateTime,default=datetime.now) 35 | date_update = db.Column(db.DateTime,default=datetime.now) 36 | 37 | @cached_property 38 | def json(self): 39 | return dict(id=self.id, 40 | username=self.username, 41 | email=self.email, 42 | photo_url=self.photo_url, 43 | signature=self.signature, 44 | status=self.status, 45 | uid=str(self.uid), 46 | push_on=self.push_on, 47 | access_token=self.access_token, 48 | date_create=format_date(self.date_create), 49 | date_update=format_date(self.date_update)) 50 | 51 | 52 | class Post(db.Model): 53 | 54 | __tablename__ = 'post' 55 | id = db.Column(db.Integer,primary_key=True) 56 | title = db.Column(db.String(300)) 57 | pic_small = db.Column(db.String(255)) 58 | pic_big = db.Column(db.String(255)) 59 | video_url = db.Column(db.String(255)) 60 | author_id = db.Column(db.Integer,db.ForeignKey(User.id),index=True) 61 | show = db.Column(db.Boolean,default=True,index=True) 62 | recommended = db.Column(db.Boolean,default=False,index=True) 63 | play_count = db.Column(db.Integer,default=0) 64 | date_create = db.Column(db.DateTime,default=datetime.now) 65 | date_update = db.Column(db.DateTime,default=datetime.now) 66 | date_publish = db.Column(db.DateTime,default=datetime.now) 67 | user = db.relation(User, innerjoin=True, lazy="joined") 68 | 69 | @cached_property 70 | def json(self): 71 | video_prefix = current_app.config.get('VIDEO_URL_PREFIX') if current_app \ 72 | else DefaultConfig.VIDEO_URL_PREFIX 73 | return dict(id=self.id, 74 | title=self.title, 75 | pic_big=self.pic_big, 76 | pic_small=os.path.join(video_prefix,self.pic_small or ''), 77 | video_url=os.path.join(video_prefix,self.video_url or ''), 78 | author_id=self.author_id, 79 | show=self.show, 80 | recommended=self.recommended, 81 | play_count=self.play_count, 82 | date_create=format_date(self.date_create), 83 | date_update=format_date(self.date_update), 84 | date_publish=format_date(self.date_publish)) 85 | 86 | 87 | class Report(db.Model): 88 | 89 | __tablename__ = 'report' 90 | id = db.Column(db.Integer,primary_key=True) 91 | user_id = db.Column(db.Integer,db.ForeignKey(User.id),index=True) 92 | post_id = db.Column(db.Integer,db.ForeignKey(Post.id),index=True) 93 | user = db.relation(User,innerjoin=True,lazy='joined') 94 | post = db.relation(Post,innerjoin=True,lazy='joined') 95 | date_create = db.Column(db.DateTime,default=datetime.now) 96 | 97 | 98 | class Install(db.Model): 99 | 100 | __tablename__ = 'install' 101 | id = db.Column(db.Integer,primary_key=True) 102 | user_id = db.Column(db.Integer,db.ForeignKey(User.id),index=True,unique=True) 103 | version = db.Column(db.String(20)) 104 | badge = db.Column(db.Integer,default=0) 105 | device_token = db.Column(db.String(100),index=True) 106 | device_type = db.Column(db.String(20)) 107 | date_create = db.Column(db.DateTime,default=datetime.now) 108 | 109 | @property 110 | def json(self): 111 | return dict(id=self.id, 112 | user_id=self.user_id, 113 | version=self.version, 114 | badge=self.badge, 115 | device_token=self.device_token, 116 | device_type=self.device_type, 117 | date_create=format_date(self.date_create)) 118 | 119 | 120 | class UserFollowAsso(db.Model): 121 | 122 | __tablename__ = 'user_follow_asso' 123 | id = db.Column(db.Integer,primary_key=True) 124 | user_id = db.Column(db.Integer,db.ForeignKey(User.id),index=True) 125 | user_id_to = db.Column(db.Integer,db.ForeignKey(User.id),index=True) 126 | date_create = db.Column(db.DateTime,default=datetime.now) 127 | 128 | __table_args__ = ( 129 | db.UniqueConstraint('user_id','user_id_to'), 130 | ) 131 | 132 | 133 | class UserLikeAsso(db.Model): 134 | 135 | __tablename__ = 'user_like_asso' 136 | id = db.Column(db.Integer,primary_key=True) 137 | user_id = db.Column(db.Integer,db.ForeignKey(User.id),index=True) 138 | post_id = db.Column(db.Integer,db.ForeignKey(Post.id),index=True) 139 | date_create = db.Column(db.DateTime,default=datetime.now) 140 | 141 | __table_args__ = ( 142 | db.UniqueConstraint('user_id','post_id'), 143 | ) 144 | 145 | class UserPlayAsso(db.Model): 146 | 147 | __tablename__ = 'user_play_asso' 148 | id = db.Column(db.Integer,primary_key=True) 149 | user_id = db.Column(db.Integer) 150 | post_id = db.Column(db.Integer) 151 | date_create = db.Column(db.DateTime,default=datetime.now) 152 | 153 | 154 | class Comment(db.Model): 155 | 156 | __tablename__ = 'comment' 157 | id = db.Column(db.Integer,primary_key=True) 158 | post_id = db.Column(db.Integer,index=True) 159 | author_id = db.Column(db.Integer,db.ForeignKey(User.id)) 160 | content = db.Column(db.String(1000)) 161 | show = db.Column(db.Boolean,default=True) 162 | date_create = db.Column(db.DateTime,default=datetime.now) 163 | 164 | @cached_property 165 | def json(self): 166 | return dict(id=self.id, 167 | post_id=self.post_id, 168 | author_id=self.author_id, 169 | content=self.content, 170 | show=self.show, 171 | date_create=format_date(self.date_create)) 172 | 173 | 174 | class Activity(db.Model): 175 | 176 | __tablename__ = 'activity' 177 | id = db.Column(db.Integer,primary_key=True) 178 | post_id = db.Column(db.Integer) 179 | comment_id = db.Column(db.Integer) 180 | from_id = db.Column(db.Integer) 181 | to_id = db.Column(db.Integer) 182 | atype = db.Column(db.String(20)) 183 | # atype follow like comment post_reco text 184 | date_create = db.Column(db.DateTime,default=datetime.now) 185 | 186 | @cached_property 187 | def json(self): 188 | return dict(id=self.id, 189 | post_id=self.post_id, 190 | comment_id=self.comment_id, 191 | from_id=self.from_id, 192 | to_id=self.to_id, 193 | atype=self.atype, 194 | date_create=format_date(self.date_create)) 195 | 196 | 197 | class Action(db.Model): 198 | 199 | __tablename__ = 'action' 200 | id = db.Column(db.Integer,primary_key=True) 201 | post_id = db.Column(db.Integer) 202 | user_id = db.Column(db.Integer,index=True) 203 | atype = db.Column(db.String(20),index=True) 204 | payload = db.Column(db.String(255)) 205 | date_create = db.Column(db.DateTime,default=datetime.now) 206 | 207 | @cached_property 208 | def json(self): 209 | return dict(id=self.id, 210 | post_id=self.post_id, 211 | user_id=self.user_id, 212 | atype=self.atype, 213 | payload=self.payload, 214 | date_create=format_date(self.date_create)) 215 | 216 | 217 | class Tag(db.Model): 218 | 219 | __tablename__ = 'tag' 220 | id = db.Column(db.Integer,primary_key=True) 221 | name = db.Column(db.String(255),index=True) 222 | show = db.Column(db.Boolean,default=True) 223 | pic_url = db.Column(db.String(255)) 224 | order_seq = db.Column(db.Integer,default=0) 225 | recommended = db.Column(db.Boolean,default=False) 226 | date_create = db.Column(db.DateTime,default=datetime.now) 227 | 228 | @cached_property 229 | def json(self): 230 | return dict(id=self.id, 231 | name=self.name, 232 | show=self.show, 233 | pic_url=self.pic_url, 234 | order_seq=self.order_seq, 235 | recommended=self.recommended, 236 | date_create=format_date(self.date_create)) 237 | 238 | 239 | class Tagging(db.Model): 240 | 241 | __tablename__ = 'tagging' 242 | id = db.Column(db.Integer,primary_key=True) 243 | taggable_type = db.Column(db.String(20)) 244 | taggable_id = db.Column(db.Integer) 245 | tag_id = db.Column(db.Integer) 246 | user_id = db.Column(db.Integer) 247 | date_create = db.Column(db.DateTime,default=datetime.now) 248 | 249 | @cached_property 250 | def json(self): 251 | return dict(id=self.id, 252 | taggable_type=self.taggable_type, 253 | taggalbe_id=self.taggable_id, 254 | tag_id=self.tag_id, 255 | user_id=self.user_id, 256 | date_create=format_date(self.date_create)) 257 | 258 | 259 | class Storage(db.Model): 260 | __tablename__ = 'storage' 261 | id = db.Column(UUID(),default=lambda:str(uuid.uuid4()),primary_key=True) 262 | file_md5 = db.Column(db.String(80)) 263 | file_size = db.Column(db.Integer) 264 | date_create = db.Column(db.DateTime,default=datetime.now) 265 | 266 | 267 | class CmsUser(db.Model): 268 | __tablename__ = 'cms_user' 269 | id = db.Column(db.Integer,primary_key=True) 270 | username = db.Column(db.String(50)) 271 | email = db.Column(db.String(50)) 272 | password = db.Column(db.String(80)) 273 | date_create = db.Column(db.DateTime,default=datetime.now) 274 | 275 | 276 | 277 | -------------------------------------------------------------------------------- /tests/test_backend.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | 4 | import os 5 | import sys 6 | import types 7 | import json 8 | import time 9 | from datetime import datetime 10 | from datetime import timedelta 11 | from tests import TestCase 12 | 13 | 14 | from motiky.logic.models import User,Post,UserLikeAsso,Report,Install,\ 15 | UserFollowAsso,Comment,Activity,Action,Tag,Tagging 16 | 17 | from motiky.logic import backend 18 | 19 | from motiky.configs import db,redis 20 | 21 | class TestUserLogic(TestCase): 22 | 23 | def test_get_user(self): 24 | user = User(username='username01',photo_url='photo_url01',uid='weibo_id01') 25 | db.session.add(user) 26 | db.session.commit() 27 | 28 | _user = backend.get_user(user.id) 29 | assert _user['username'] == user.username 30 | 31 | 32 | def test_add_user(self): 33 | user = backend.add_user('username','photo_url','weibo_id') 34 | assert user['username'] == 'username' 35 | 36 | def test_set_user(self): 37 | user = backend.add_user('username','photo_url','weibo_id') 38 | _user = backend.set_user(user['id'],{'username':'username2', 39 | 'photo_url':'photo_url2'}) 40 | assert _user['username'] == 'username2' 41 | 42 | def test_get_user_by_uid(self): 43 | user1 = backend.add_user('username01','photo_url01','weibo_id01') 44 | user2 = backend.add_user('username02','photo_url02','weibo_id02') 45 | 46 | user = backend.get_user_by_uid(user1['uid']) 47 | assert user['username'] == user1['username'] 48 | 49 | users = backend.get_user_by_uid([user1['uid'],user2['uid']]) 50 | assert len(users) == 2 51 | 52 | 53 | def test_follow_user(self): 54 | user1 = backend.add_user('username01','photo_url01','weibo_id01') 55 | user2 = backend.add_user('username02','photo_url02','weibo_id02') 56 | 57 | ret = backend.follow_user(user1['id'],user2['id']) 58 | assert ret > 0 59 | 60 | def test_unfollow_user(self): 61 | user1 = backend.add_user('username01','photo_url01','weibo_id01') 62 | user2 = backend.add_user('username02','photo_url02','weibo_id02') 63 | 64 | ret = backend.follow_user(user1['id'],user2['id']) 65 | 66 | ret = backend.unfollow_user(user1['id'],user2['id']) 67 | assert ret == True 68 | 69 | def test_is_following_user(self): 70 | user1 = backend.add_user('username01','photo_url01','weibo_id01') 71 | user2 = backend.add_user('username02','photo_url02','weibo_id02') 72 | 73 | backend.follow_user(user1['id'],user2['id']) 74 | ret = backend.is_following_user(user1['id'],user2['id']) 75 | assert ret == True 76 | 77 | def test_get_user_following(self): 78 | user1 = backend.add_user('username01','photo_url01','weibo_id01') 79 | user2 = backend.add_user('username02','photo_url02','weibo_id02') 80 | user3 = backend.add_user('username03','photo_url03','weibo_id03') 81 | 82 | backend.follow_user(user1['id'],user2['id']) 83 | backend.follow_user(user1['id'],user3['id']) 84 | 85 | users = backend.get_user_following(user1['id']) 86 | assert len(users) == 2 87 | 88 | count = backend.get_user_following_count(user1['id']) 89 | assert count == 2 90 | 91 | def test_get_user_follower(self): 92 | user1 = backend.add_user('username01','photo_url01','weibo_id01') 93 | user2 = backend.add_user('username02','photo_url02','weibo_id02') 94 | user3 = backend.add_user('username03','photo_url03','weibo_id03') 95 | 96 | backend.follow_user(user2['id'],user1['id']) 97 | backend.follow_user(user3['id'],user1['id']) 98 | 99 | users = backend.get_user_follower(user1['id']) 100 | assert len(users) == 2 101 | 102 | count = backend.get_user_follower_count(user1['id']) 103 | assert count == 2 104 | 105 | 106 | class TestPostLogic(TestCase): 107 | 108 | def test_get_post(self): 109 | user = backend.add_user('username','photo_url','weibo_id') 110 | post = Post(title='title',author_id=user['id'],pic_small='pic_small') 111 | db.session.add(post) 112 | db.session.commit() 113 | 114 | _post = backend.get_post(post.id) 115 | assert _post['title'] == 'title' 116 | 117 | def test_add_post(self): 118 | user = backend.add_user('username','photo_url','weibo_id') 119 | post = backend.add_post('title',user['id'],'video_url', 120 | pic_small='pic_small') 121 | 122 | assert post['title'] == 'title' 123 | 124 | post = backend.set_post(post['id'],{'title':'title2'}) 125 | assert post['title'] == 'title2' 126 | 127 | def test_get_user_post(self): 128 | user = backend.add_user('username','photo_url','weibo_id') 129 | post1 = backend.add_post('title1',user['id'],'video_url', 130 | pic_small='pic_small') 131 | post2 = backend.add_post('title2',user['id'],'video_url', 132 | pic_small='pic_small') 133 | 134 | posts = backend.get_user_post(user['id']) 135 | assert len(posts) == 2 136 | 137 | count = backend.get_user_post_count(user['id']) 138 | assert count == 2 139 | 140 | 141 | def test_get_user_liked_post(self): 142 | user1 = backend.add_user('username1','photo_url','weibo_id1') 143 | user2 = backend.add_user('username2','photo_url','weibo_id2') 144 | post1 = backend.add_post('title1',user1['id'],'video_url', 145 | pic_small='pic_small') 146 | post2 = backend.add_post('title2',user1['id'],'video_url', 147 | pic_small='pic_small') 148 | 149 | ula1 = UserLikeAsso(user_id=user2['id'],post_id=post1['id']) 150 | ula2 = UserLikeAsso(user_id=user2['id'],post_id=post2['id']) 151 | db.session.add(ula1) 152 | db.session.add(ula2) 153 | db.session.commit() 154 | 155 | posts = backend.get_user_liked_post(user2['id']) 156 | assert len(posts) == 2 157 | 158 | count = backend.get_user_liked_post_count(user2['id']) 159 | assert count == 2 160 | 161 | 162 | def test_add_like(self): 163 | user1 = backend.add_user('username1','photo_url','weibo_id1') 164 | post1 = backend.add_post('title1',user1['id'],'video_url', 165 | pic_small='pic_small') 166 | 167 | ret = backend.add_like(user1['id'],post1['id']) 168 | assert ret == 1 169 | 170 | ret = backend.del_like(user1['id'],post1['id']) 171 | assert ret == True 172 | 173 | def test_is_like_post(self): 174 | user1 = backend.add_user('username1','photo_url','weibo_id1') 175 | post1 = backend.add_post('title1',user1['id'],'video_url', 176 | pic_small='pic_small') 177 | post2 = backend.add_post('title2',user1['id'],'video_url', 178 | pic_small='pic_small') 179 | 180 | backend.add_like(user1['id'],post1['id']) 181 | backend.add_like(user1['id'],post2['id']) 182 | 183 | ret = backend.is_like_post(user1['id'],[post1['id'],post2['id']]) 184 | assert ret[post1['id']] == True 185 | 186 | 187 | class TestActivityLogic(TestCase): 188 | 189 | def test_add_activity(self): 190 | user1 = backend.add_user('username1','photo_url','weibo_id1') 191 | user2 = backend.add_user('username2','photo_url','weibo_id2') 192 | post1 = backend.add_post('title1',user1['id'],'video_url', 193 | pic_small='pic_small') 194 | post2 = backend.add_post('title2',user1['id'],'video_url', 195 | pic_small='pic_small') 196 | 197 | ac1 = { 198 | 'post_id':post1['id'], 199 | 'from_id':user1['id'], 200 | 'to_id':user2['id'], 201 | 'atype':'like' 202 | } 203 | ac2 = { 204 | 'post_id':post1['id'], 205 | 'from_id':user1['id'], 206 | 'to_id':user2['id'], 207 | 'atype':'comment' 208 | } 209 | 210 | ret = backend.add_activity(ac1) 211 | assert ret['to_id'] == user2['id'] 212 | ret = backend.add_activity(ac2) 213 | 214 | rets = backend.get_activity_by_user(user2['id']) 215 | assert len(rets) == 2 216 | 217 | 218 | class TestOtherLogic(TestCase): 219 | 220 | def test_new_install(self): 221 | user1 = backend.add_user('username1','photo_url','weibo_id1') 222 | device_token = '1234567890' 223 | install = backend.new_install(user1['id'],device_token) 224 | assert install['device_token'] == device_token 225 | 226 | install = backend.get_install_by_user(user1['id']) 227 | 228 | assert install['device_token'] == device_token 229 | 230 | install = backend.set_install(user1['id'],{'badge':20}) 231 | print install 232 | assert install['badge'] == 20 233 | 234 | def test_add_file_data(self): 235 | st = backend.add_file_data(10,'1234567890') 236 | st = backend.add_file_data(10,'123243435454545') 237 | assert len(st) == 36 238 | 239 | class TestCommentLogic(TestCase): 240 | 241 | def test_comment(self): 242 | user1 = backend.add_user('username1','photo_url','weibo_id1') 243 | user2 = backend.add_user('username2','photo_url','weibo_id2') 244 | post1 = backend.add_post('title1',user1['id'],'video_url', 245 | pic_small='pic_small') 246 | post2 = backend.add_post('title2',user1['id'],'video_url', 247 | pic_small='pic_small') 248 | 249 | 250 | comment1 = backend.add_comment(post1['id'],'comment1',user2['id']) 251 | assert comment1['post_id'] == post1['id'] 252 | 253 | comment2 = backend.add_comment(post1['id'],'comment2',user2['id']) 254 | 255 | comments = backend.get_post_comment(post1['id']) 256 | 257 | assert len(comments) == 2 258 | 259 | ret = backend.get_post_comment_count(post1['id']) 260 | assert ret == 2 261 | 262 | class TestTagLogic(TestCase): 263 | 264 | def test_tag(self): 265 | 266 | tag1 = Tag(name='tag1',show=True,pic_url='pic_url', 267 | recommended=True) 268 | tag2 = Tag(name='tag2',show=True,pic_url='pic_url', 269 | recommended=True) 270 | db.session.add(tag1) 271 | db.session.add(tag2) 272 | db.session.commit() 273 | 274 | tags = backend.get_all_tags() 275 | assert len(tags) == 2 276 | 277 | tags = backend.get_recommend_tags() 278 | assert len(tags) == 2 279 | 280 | tag = backend.get_tag(tag1.id) 281 | 282 | assert tag['name'] == 'tag1' 283 | 284 | user = backend.add_user('username','photo_url','weibo_id') 285 | post1 = backend.add_post('title1',user['id'],'video_url', 286 | pic_small='pic_small') 287 | post2 = backend.add_post('title2',user['id'],'video_url', 288 | pic_small='pic_small') 289 | 290 | tagging1 = Tagging(taggable_type='post',taggable_id=post1['id'], 291 | tag_id=tag1.id) 292 | tagging2 = Tagging(taggable_type='post',taggable_id=post2['id'], 293 | tag_id=tag1.id) 294 | 295 | db.session.add(tagging1) 296 | db.session.add(tagging2) 297 | db.session.commit() 298 | 299 | posts = backend.get_tag_post(tag1.id) 300 | assert len(posts) == 2 301 | 302 | post_count = backend.get_tag_post_count(tag1.id) 303 | assert post_count == 2 304 | 305 | 306 | class TestFeedLogic(TestCase): 307 | 308 | def test_get_latest_feed(self): 309 | user1 = backend.add_user('username01','photo_url01','weibo_id01') 310 | user2 = backend.add_user('username02','photo_url02','weibo_id02') 311 | user3 = backend.add_user('username03','photo_url03','weibo_id03') 312 | user4 = backend.add_user('username04','photo_url04','weibo_id04') 313 | 314 | post1 = backend.add_post('title01',user1['id'],'video_url01', 315 | pic_small='pic_small01') 316 | post2 = backend.add_post('title02',user2['id'],'video_url02', 317 | pic_small='pic_small') 318 | post3 = backend.add_post('title03',user3['id'],'video_url03', 319 | pic_small='pic_small03') 320 | post4 = backend.add_post('title04',user4['id'],'video_url04', 321 | pic_small='pic_small04') 322 | 323 | backend.follow_user(user4['id'],user1['id']) 324 | backend.follow_user(user4['id'],user2['id']) 325 | 326 | 327 | ret = backend.get_latest_feed(user4['id'],limit=10,offset=0) 328 | assert len(ret) == 3 329 | 330 | backend.set_post(post3['id'],{'recommended':True}) 331 | 332 | ret = backend.get_latest_feed(user4['id'],limit=10,offset=0) 333 | assert len(ret) == 4 334 | 335 | --------------------------------------------------------------------------------